Download leaf_kind_onnx.py from leapshared/dinov2-base-onnx: direct link, hf CLI and curl.
- Browser
- Download file 15.3 kB
-
https://huggingface.co/leapshared/dinov2-base-onnx/resolve/main/leaf_kind_onnx.py
- Command line
-
hf download hf://leapshared/dinov2-base-onnx/leaf_kind_onnx.py
-
curl -L -o leaf_kind_onnx.py https://huggingface.co/leapshared/dinov2-base-onnx/resolve/main/leaf_kind_onnx.py
15.3 kB
| """DINOv2-base ํน์ง(pooler_output)์ ONNX๋ก ๋ด๋ณด๋ด๊ณ , ๋ก์ปฌ ๋๋ญ์ ํ์ ๊ธฐ์ ๋ ๊ธธ(torchยทonnxruntime)์ ๋ นํ ์ฌ์ง์ผ๋ก | |
| ๊ฒฌ์ค๋ค(์ด์ #28). ๋ก๋ด์ ๋ถ์ง ์๊ณ GPU๋ ์ฐ์ง ์๋๋ค(CPU๋ก ๋ด๋ณด๋ด๊ณ ์ฐ๋ค). | |
| # 1. ๋ด๋ณด๋ด๊ธฐ: torchยทtransformers์ onnxยทonnxscriptยทonnxruntime์ ์น์ ํ์ด์ฌ | |
| CUDA_VISIBLE_DEVICES= HF_HUB_OFFLINE=1 <python> scripts/diagnostics/leaf_kind_onnx.py export --out outputs/leaf_onnx/model.onnx | |
| # 2. ๊ฒฌ์ฃผ๊ธฐ: ์ ํ์ด์ฌ + ๋ นํ leapshared/Leaf_rules_20261002_225313_subtask์ ๋ก์ปฌ ์ฌ๋ณธ | |
| CUDA_VISIBLE_DEVICES= HF_HUB_OFFLINE=1 <python> scripts/diagnostics/leaf_kind_onnx.py compare --onnx outputs/leaf_onnx/model.onnx | |
| ์ ์ฅ์ venv์๋ onnxยทonnxruntime์ด ์๋ค. 2026-10-04์๋ venv์ site-packages๋ฅผ .pth๋ก ์ฝ๋ ์์ venv์ | |
| onnx 1.23.1ยทonnxscript 0.7.2ยทonnx-ir 1.0.0ยทml-dtypes 0.6.0ยทonnxruntime 1.30.0ยทflatbuffers 25.12.19๋ง ๋ ๊น์ ๋๋ ธ๋ค. | |
| export๋ facebook/dinov2-base(๊ณ ์ ์ปค๋ฐ)์ pooler_output๋ง ๋ด๋ ๊ทธ๋ํ๋ฅผ ๋ง๋ ๋ค. | |
| - ์ ๋ ฅ์ pixel_values [N, 3, 224, 224] float32(์ ์ฒ๋ฆฌ๋ฅผ ๋ง์น ๊ฐ), ์ถ๋ ฅ์ pooler_output [N, 768]์ด๋ค. | |
| - 224x224์ ์์น ์๋ฒ ๋ฉ(37x37 โ 16x16 bicubic)์ torch๋ก ํ ๋ฒ ์ ํด ์์๋ก ๋ฃ๋๋ค. ๋ํ์ธต์ ๋ 224x224๋ฅผ ๋ฃ์ผ๋ฏ๋ก | |
| ๊ทธ๋ํ์ ๋ณด๊ฐ์ด ๋จ์ง ์๋๋ค. | |
| - ๋ด๋ณด๋ธ ๋ค ๊ฐ์ ์ ๋ ฅ์์ torch์ ๊ฒฌ์ฃผ๊ณ , ๋ฉํ๋ฐ์ดํฐ(์๋ณธ ๋ชจ๋ธยท์ปค๋ฐยท๋๊ตฌ ํ)๋ฅผ ์ ๋๋ค. | |
| compare๋ ๋ นํ์์ ์๋น ์นด๋ฉ๋ผ์ฒ๋ผ(RGB โ BGR โ JPEG ํ์ง 80) ์ฌ์ง์ ๋ง๋ค์ด ๋ ๊ธธ์ ๋ฃ๋๋ค. | |
| - ๊ฒ์ฆ ์ํผ์๋ 6ยท7ยท46ยท47์ ์นด๋ 22์ฅ: ๊ฒฐ์ ํ๋ ์(ํ์ด ์ถ๋ฐํ๊ธฐ 5ํ๋ ์ ์ )๊ณผ ๊ทธ โ12ยทโ6 ํ๋ ์์ผ๋ก ์ธ ์ฅ ํ ๋ฒ ํ์ | |
| - ๊ฐ์ ์นด๋๋ฅผ ๊ทธ๋ฆฌํผ๊ฐ ์ฅ๋ ํ๋ ์ 22์ฅ, ๊ทธ ์ํผ์๋์ ์ฒซ ํ๋ ์(๋น ์ค) 4์ฅ: ํ ์ฅ์ฉ. none์ด์ด์ผ ํ๋ค | |
| - ๋๋จธ์ง ์ํผ์๋์ ์นด๋ ๊ฒฐ์ ํ๋ ์๊ณผ ์ฅ๋ ํ๋ ์: ํ ์ฅ์ฉ. ํน์ง์ ๋๊ฒ ๊ฒฌ์ค๋ค | |
| - ์ฐ๋ ๊ฒ: ์ ์ฒ๋ฆฌ(numpy์ transformers์ ์์ ์ฐจ์ด), ํน์ง ์ฝ์ฌ์ธ, ํ์ ์ผ์น, ํ์ ๊ธฐ ๋ง๋ค๊ธฐ, 3์ฅ ํ์ ์ง์ฐ(JPEG ํ๊ธฐ ํฌํจ) | |
| ํ๋ธ leapshared/dinov2-base-onnx์ ์ฌ๋ฆฐ ์ฌ๋ณธ์ export๋ง ์ด๋ค(compare๋ ์ด ์ ์ฅ์์ dialogue/์ ๋ นํ ๋๊ตฌ๊ฐ ์์ด์ผ ํ๋ค). | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import statistics | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| HERE = Path(__file__).resolve() | |
| REPO = next((p for p in HERE.parents if (p / "pyproject.toml").is_file()), HERE.parent) # ํ๋ธ ์ฌ๋ณธ์ ์ ์ฅ์ ๋ฐ | |
| sys.path.insert(0, str(REPO)) | |
| SOURCE = "facebook/dinov2-base" | |
| REVISION = "f9e44c814b77203eaa57a6bdbbd535f21ede1415" # 2026-10-04 main(์ด PC HF ์บ์) | |
| SIZE = 224 | |
| DATASET = "Leaf_rules_20261002_225313_subtask" | |
| HELD_OUT = (6, 7, 46, 47) | |
| KIND = {"๋ณต์ฝ": "compound", "๋จ์ฝ": "simple"} | |
| def sha256(path: Path) -> str: | |
| h = hashlib.sha256() | |
| with open(path, "rb") as f: | |
| for block in iter(lambda: f.read(1 << 20), b""): | |
| h.update(block) | |
| return h.hexdigest() | |
| def cosines(a: np.ndarray, b: np.ndarray) -> np.ndarray: | |
| a, b = np.asarray(a, np.float64), np.asarray(b, np.float64) | |
| return (a * b).sum(1) / (np.linalg.norm(a, axis=1) * np.linalg.norm(b, axis=1)) | |
| # โโ export โโ | |
| def export(out: Path, revision: str, opset: int | None) -> None: | |
| import onnx | |
| import onnxruntime as ort | |
| import torch | |
| import transformers | |
| from transformers import AutoModel | |
| torch.manual_seed(0) | |
| model = AutoModel.from_pretrained(SOURCE, revision=revision).eval() | |
| probe = torch.randn(3, 3, SIZE, SIZE) | |
| with torch.no_grad(): | |
| want = model(pixel_values=probe).pooler_output # ๊ณ ์น๊ธฐ ์ ๋ชจ๋ธ์ ๋ต | |
| class Pooler(torch.nn.Module): | |
| """pixel_values [N, 3, 224, 224] โ pooler_output [N, 768]. ์์น ์๋ฒ ๋ฉ์ 224x224๋ก ๋ฏธ๋ฆฌ ์ ํ ์์๋ค.""" | |
| def __init__(self, inner) -> None: | |
| super().__init__() | |
| self.inner = inner | |
| embeddings = inner.embeddings | |
| tokens = torch.zeros(1, 1 + (SIZE // embeddings.patch_size) ** 2, inner.config.hidden_size) | |
| with torch.no_grad(): | |
| self.register_buffer("pos", embeddings.interpolate_pos_encoding(tokens, SIZE, SIZE).clone()) | |
| embeddings.interpolate_pos_encoding = lambda _embeddings, _height, _width: self.pos | |
| def forward(self, pixel_values): | |
| return self.inner(pixel_values=pixel_values).pooler_output | |
| pooler = Pooler(model).eval() | |
| with torch.no_grad(): | |
| got = pooler(probe) | |
| print(f"์์ ์์น ์๋ฒ ๋ฉ: ์๋ ๋ชจ๋ธ๊ณผ ์ต๋ ์ฐจ์ด {float((got - want).abs().max()):.3g}") | |
| out.parent.mkdir(parents=True, exist_ok=True) | |
| batch = torch.export.Dim("batch", min=1, max=64) | |
| torch.onnx.export(pooler, (probe,), str(out), input_names=["pixel_values"], output_names=["pooler_output"], | |
| dynamic_shapes={"pixel_values": {0: batch}}, opset_version=opset, dynamo=True, | |
| external_data=False) | |
| graph = onnx.load(str(out)) | |
| for node in graph.graph.node: # ๋ ธ๋๋ง๋ค ๋ถ๋ stack_trace ๋ฑ: ๋ด๋ณด๋ธ PC์ ๊ฒฝ๋กยท์ค ๋ฒํธ๋ผ ๊ณ์ฐ๊ณผ ์๊ด์๋ค. ์ง์ฐ๋ฉด ํ์ผ์ด ๋ ๊ฐ๋ค | |
| del node.metadata_props[:] | |
| onnx.helper.set_model_props(graph, { | |
| "source_model": SOURCE, "source_revision": revision, "output": "pooler_output", | |
| "input": f"pixel_values [N, 3, {SIZE}, {SIZE}] float32: AutoImageProcessor ์ ์ฒ๋ฆฌ๋ฅผ ๋ง์น ๊ฐ", | |
| "position_embeddings": f"{SIZE}x{SIZE} ์ ๋ ฅ์ผ๋ก ๋ฏธ๋ฆฌ ์ ํ ์์", | |
| "exported_with": f"torch {torch.__version__}, transformers {transformers.__version__}, onnx {onnx.__version__}", | |
| "made_by": "LEAP-LAB-KUS/openarmsciedu-robot scripts/diagnostics/leaf_kind_onnx.py export", | |
| }) | |
| onnx.save(graph, str(out)) | |
| onnx.checker.check_model(str(out)) | |
| opsets = {o.domain or "ai.onnx": o.version for o in graph.opset_import} | |
| session = ort.InferenceSession(str(out), providers=["CPUExecutionProvider"]) | |
| for n in (1, 3): | |
| ort_out = session.run(None, {"pixel_values": probe[:n].numpy()})[0] | |
| diff = np.abs(ort_out - want[:n].numpy()).max() | |
| print(f"onnxruntime {ort.__version__}, {n}์ฅ: ์ต๋ ์ฐจ์ด {diff:.3g}, ์ฝ์ฌ์ธ ์ต์ " | |
| f"{cosines(ort_out, want[:n].numpy()).min():.8f}") | |
| print(f"์ผ๋ค: {out} ({out.stat().st_size / 1e6:.1f} MB, sha256 {sha256(out)}, opset {opsets})") | |
| # โโ compare โโ | |
| def serving_jpeg(image) -> bytes: | |
| """์๋น ์นด๋ฉ๋ผ(serving/cameras.py)์ฒ๋ผ: RGB โ BGR โ JPEG ํ์ง 80.""" | |
| import cv2 | |
| rgb = np.asarray(image.convert("RGB"), dtype=np.uint8) | |
| ok, buf = cv2.imencode(".jpg", cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR), [cv2.IMWRITE_JPEG_QUALITY, 80]) | |
| assert ok | |
| return buf.tobytes() | |
| def photos(root: Path) -> list[dict]: | |
| """๋ นํ์ ์ฌ์ง๋ค: {"name", "kind", "group", "held_out", "jpeg"}. ๊ฒ์ฆ ์นด๋๋ ์ธ ์ฅ(โ12ยทโ6ยท0)์ด ํ ํ์ ์ด๋ค.""" | |
| sys.path.insert(0, str(REPO / "scripts" / "diagnostics")) | |
| sys.path.insert(0, str(REPO / "scripts" / "verifier")) | |
| import common | |
| from leaf_view import all_cards | |
| meta = common.episodes_meta(root) | |
| by_episode: dict[int, list[dict]] = {} | |
| for card in all_cards(root): | |
| by_episode.setdefault(card["episode"], []).append(card) | |
| rows = [] | |
| for episode, cards in sorted(by_episode.items()): | |
| held = episode in HELD_OUT | |
| want = {0} if held else set() | |
| for c in cards: | |
| want |= {c["decide"], c["close"]} | ({max(0, c["decide"] - 12), max(0, c["decide"] - 6)} if held else set()) | |
| got = common.read_frames(root, meta, episode, common.CHEST, sorted(want)) | |
| if held: | |
| rows.append({"name": f"ep{episode:02d} ์ฒซ ํ๋ ์", "kind": "none", "group": "first", "held_out": True, | |
| "jpeg": [serving_jpeg(got[0])]}) | |
| for c in cards: | |
| tag = f"ep{episode:02d}#{c['card']}" | |
| offsets = (-12, -6, 0) if held else (0,) | |
| rows.append({"name": tag, "kind": KIND[c["kind"]], "group": "card", "held_out": held, | |
| "jpeg": [serving_jpeg(got[max(0, c["decide"] + o)]) for o in offsets]}) | |
| rows.append({"name": f"{tag} ์ฅ ", "kind": "none", "group": "close", "held_out": held, | |
| "jpeg": [serving_jpeg(got[c["close"]])]}) | |
| return rows | |
| def compare(onnx_path: Path, root: Path, threads: int, repeat: int, out: Path | None) -> None: | |
| import torch | |
| import transformers | |
| from dialogue.leaf_kind.leaf_kind import LeafKind | |
| from dialogue.leaf_local import LocalLeafJudge, rgb_frame | |
| from dialogue.leaf_onnx import OnnxLeafKind, pixel_values | |
| classifier = REPO / "dialogue" / "leaf_kind" / "classifier.json" | |
| torch.set_num_threads(threads) | |
| started = time.perf_counter() | |
| reference = LeafKind(classifier=classifier, device="cpu", threshold=0.9) | |
| torch_load = time.perf_counter() - started | |
| started = time.perf_counter() | |
| candidate = OnnxLeafKind(onnx_path, classifier, threshold=0.9, threads=threads) | |
| onnx_load = time.perf_counter() - started | |
| rows = photos(root) | |
| x0, y0, x1, y1 = reference.crop | |
| pixel_diff = pixel_total = 0 | |
| feats_t, feats_o, frame_rows, judged = [], [], [], [] | |
| for row in rows: | |
| frames = [rgb_frame(j) for j in row["jpeg"]] | |
| for frame in frames: # ํ ์ฅ์ฉ: ์ ์ฒ๋ฆฌ์ ํน์ง | |
| crop = np.ascontiguousarray(frame[y0:y1, x0:x1]) | |
| ref_px = reference.processor(images=[crop], return_tensors="np")["pixel_values"] | |
| pixel_diff += int((ref_px != pixel_values([crop])).sum()) | |
| pixel_total += ref_px.size | |
| ft, fo = reference.features([frame]), candidate.features([frame]) | |
| feats_t.append(ft[0]) | |
| feats_o.append(fo[0]) | |
| pt, po = reference.proba([frame])[0], candidate.proba([frame])[0] | |
| frame_rows.append({"name": row["name"], "kind": row["kind"], "torch": reference.classes[int(pt.argmax())], | |
| "onnx": candidate.classes[int(po.argmax())], "dp": float(np.abs(pt - po).max())}) | |
| a, b = reference.judge(frames), candidate.judge(frames) # ๋ํ์ธต์ฒ๋ผ ํ ๋ฒ์ | |
| judged.append({"name": row["name"], "kind": row["kind"], "group": row["group"], "held_out": row["held_out"], | |
| "frames": len(frames), "torch": a, "onnx": b}) | |
| cos = cosines(np.stack(feats_t), np.stack(feats_o)) | |
| absdiff = np.abs(np.stack(feats_t) - np.stack(feats_o)) | |
| same_vote = sum(r["torch"] == r["onnx"] for r in frame_rows) | |
| same_judge = sum((j["torch"]["kind"], j["torch"]["votes"]) == (j["onnx"]["kind"], j["onnx"]["votes"]) for j in judged) | |
| print(f"ํ์ ๊ธฐ ๋ง๋ค๊ธฐ(๊พธ๋ฌ๋ฏธ๋ฅผ ๋ถ๋ฌ์จ ๋ค): torch {torch_load:.2f} s, onnx {onnx_load:.2f} s (์ค๋ ๋ {threads})") | |
| print(f"์ ์ฒ๋ฆฌ: ๋ค๋ฅธ ์์ {pixel_diff}/{pixel_total}") | |
| print(f"ํน์ง {len(cos)}์ฅ: ์ฝ์ฌ์ธ ์ต์ {cos.min():.12f}(1 โ ์ฝ์ฌ์ธ ์ต๋ {1 - cos.min():.2g}), ์์ ์ต๋ ์ฐจ์ด " | |
| f"{absdiff.max():.3g}(ํน์ง ์์ ํฌ๊ธฐ ์ค์ {np.median(np.abs(np.stack(feats_t))):.3g}), ์ธ ๋ถ๋ฅ ํ๋ฅ ์ต๋ ์ฐจ์ด " | |
| f"{max(r['dp'] for r in frame_rows):.3g}") | |
| print(f"ํ ์ฅ ํ์ (argmax) ๊ฐ์ {same_vote}/{len(frame_rows)}, ํ์ (kindยทvotes) ๊ฐ์ {same_judge}/{len(judged)}") | |
| def tally(select, label): | |
| chosen = [j for j in judged if select(j)] | |
| for path in ("torch", "onnx"): | |
| right = sum(j[path]["kind"] == j["kind"] for j in chosen) | |
| other = {} | |
| for j in chosen: | |
| if j[path]["kind"] != j["kind"]: | |
| other[str(j[path]["kind"])] = other.get(str(j[path]["kind"]), 0) + 1 | |
| print(f" {label} {path}: ๋ง์ {right}/{len(chosen)}" + (f", ๋ค๋ฅธ ๋ต {other}" if other else "")) | |
| tally(lambda j: j["held_out"] and j["group"] == "card", "๊ฒ์ฆ ์นด๋(์ธ ์ฅ ํ ๋ฒ)") | |
| tally(lambda j: j["held_out"] and j["group"] == "close", "๊ฒ์ฆ ์ฅ๋ ํ๋ ์") | |
| tally(lambda j: j["held_out"] and j["group"] == "first", "๊ฒ์ฆ ์ฒซ ํ๋ ์(๋น ์ค)") | |
| tally(lambda j: not j["held_out"] and j["group"] == "card", "๋๋จธ์ง ์นด๋(ํ ์ฅ)") | |
| tally(lambda j: not j["held_out"] and j["group"] == "close", "๋๋จธ์ง ์ฅ๋ ํ๋ ์") | |
| # ์ง์ฐ: ๋ํ์ธต๊ณผ ๊ฐ์ ๊ธธ(JPEG ์ธ ์ฅ โ ํ๊ธฐ โ ์๋ฅด๊ธฐ โ ์ ์ฒ๋ฆฌ โ ํน์ง โ ํ์ ) | |
| three = next(r for r in rows if r["held_out"] and r["group"] == "card")["jpeg"] | |
| latency = {} | |
| for path, clf in (("torch", reference), ("onnx", candidate)): | |
| judge = LocalLeafJudge(clf, path) | |
| for _ in range(3): | |
| judge.judge(three) | |
| times = [] | |
| for _ in range(repeat): | |
| started = time.perf_counter() | |
| judge.judge(three) | |
| times.append((time.perf_counter() - started) * 1000) | |
| latency[path] = times | |
| print(f"3์ฅ ํ์ {path}: ์ค์ {statistics.median(times):.0f} ms, ์ต๋ {max(times):.0f} ms ({repeat}๋ฒ)") | |
| if out: | |
| out.parent.mkdir(parents=True, exist_ok=True) | |
| out.write_text(json.dumps({ | |
| "onnx": str(onnx_path), "onnx_sha256": sha256(onnx_path), "threads": threads, | |
| "versions": {"torch": torch.__version__, "transformers": transformers.__version__, | |
| "onnxruntime": __import__("onnxruntime").__version__}, | |
| "load_s": {"torch": torch_load, "onnx": onnx_load}, "pixel_diff": [pixel_diff, pixel_total], | |
| "cosine": {"min": float(cos.min()), "median": float(np.median(cos))}, "feature_absdiff_max": float(absdiff.max()), | |
| "frames": frame_rows, "judged": judged, "latency_ms": latency}, ensure_ascii=False, indent=1)) | |
| print("์ผ๋ค:", out) | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| sub = parser.add_subparsers(dest="command", required=True) | |
| ex = sub.add_parser("export", help="dinov2-base pooler_output โ ONNX(CPU)") | |
| ex.add_argument("--out", type=Path, default=REPO / "outputs" / "leaf_onnx" / "model.onnx") | |
| ex.add_argument("--revision", default=REVISION) | |
| ex.add_argument("--opset", type=int, default=None, help="๋น์ฐ๋ฉด torch ๋ด๋ณด๋ด๊ธฐ์ ๊ธฐ๋ณธ๊ฐ") | |
| co = sub.add_parser("compare", help="๋ นํ ์ฌ์ง์ผ๋ก torch ๊ธธ๊ณผ onnxruntime ๊ธธ ๊ฒฌ์ฃผ๊ธฐ") | |
| co.add_argument("--onnx", type=Path, required=True) | |
| co.add_argument("--root", type=Path, default=Path.home() / ".cache" / "huggingface" / "lerobot" / "leapshared" / DATASET) | |
| co.add_argument("--threads", type=int, default=4, help="๋ํ์ธต ์ค์ leaf.local.threads์ ๊ฐ๊ฒ") | |
| co.add_argument("--repeat", type=int, default=30) | |
| co.add_argument("--out", type=Path, default=REPO / "outputs" / "leaf_onnx" / "compare.json") | |
| args = parser.parse_args() | |
| if args.command == "export": | |
| export(args.out, args.revision, args.opset) | |
| else: | |
| compare(args.onnx, args.root, args.threads, args.repeat, args.out) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |