leapshared commited on
Commit
687e67b
·
verified ·
1 Parent(s): d70d0d1

dinov2-base pooler_output ONNX(224x224, opset 20), 내보낸 도구, 검증 숫자

Browse files
Files changed (3) hide show
  1. README.md +56 -0
  2. leaf_kind_onnx.py +280 -0
  3. model.onnx +3 -0
README.md ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: facebook/dinov2-base
4
+ library_name: onnx
5
+ pipeline_tag: image-feature-extraction
6
+ tags:
7
+ - onnx
8
+ - dinov2
9
+ - robotics
10
+ - openarm-sciedu
11
+ ---
12
+
13
+ # dinov2-base-onnx
14
+
15
+ [facebook/dinov2-base](https://huggingface.co/facebook/dinov2-base)(커밋 `f9e44c814b77203eaa57a6bdbbd535f21ede1415`)의
16
+ `pooler_output`(최종 layernorm 뒤 CLS 토큰, 768)만 내는 ONNX 그래프다.
17
+
18
+ OpenArm SciEdu 대화층의 로컬 나뭇잎 판정기([leapshared/leaf_kind_dinov2_logreg](https://huggingface.co/leapshared/leaf_kind_dinov2_logreg))가
19
+ 쓴다. torch·transformers가 없는 대화층 컨테이너에서 onnxruntime(CPU)으로 특징을 낸다.
20
+
21
+ ## 입출력
22
+ - 입력 `pixel_values`: [N, 3, 224, 224] float32. `AutoImageProcessor(facebook/dinov2-base)` 전처리를 마친 값이다.
23
+ - 짧은 변 256으로 bicubic 줄이기(antialias) → 가운데 224 자르기 → /255 → ImageNet mean/std, RGB
24
+ - 출력 `pooler_output`: [N, 768] float32. N은 1~64.
25
+ - **224×224 전용이다.** 위치 임베딩(37×37 → 16×16 bicubic 보간)을 torch로 한 번 셈해 상수로 넣었다. 그래서 그래프에 보간이
26
+ 없고, 다른 크기는 받지 않는다.
27
+
28
+ ## 만든 법
29
+ - `leaf_kind_onnx.py export`(이 저장소에 사본이 있다. 원본은 `LEAP-LAB-KUS/openarmsciedu-robot`의
30
+ `scripts/diagnostics/leaf_kind_onnx.py`). CPU에서 내보냈다.
31
+ - torch 2.11.0, transformers 5.5.4, onnx 1.23.1, onnxscript 0.7.2. `torch.onnx.export(dynamo=True)`, opset 20.
32
+ - 가중치는 파일 안에 있다(외부 데이터 없음). 노드마다 붙는 stack_trace 메타데이터는 지웠다(내보낸 PC의 경로). 그래서 같은
33
+ 도구로 다시 내보내면 같은 파일이 나온다(두 번 내보내 sha256이 같음을 확인).
34
+ - `model.onnx`: 343.0 MB, sha256 `8f905c48a542eddc7e43e77d98918683dfc59ecb827ab5789ef8c09a4ac0cc2a`
35
+ - 모델 메타데이터 `source_model`, `source_revision`에 원본을 적었다.
36
+
37
+ ## 검증 (2026-10-04, 로봇 PC CPU, onnxruntime 1.30.0)
38
+ 녹화 `leapshared/Leaf_rules_20261002_225313_subtask`의 가슴 카메라 프레임 698장을 서빙 카메라처럼 JPEG(품질 80)으로 묶었다.
39
+ 그 사진에서 torch 길(transformers 처리기 + `Dinov2Model`)과 onnx 길(numpy 전처리 + 이 파일)을 견줬다.
40
+ - 전처리: numpy로 옮긴 것(`dialogue/leaf_onnx.py`)이 transformers 처리기와 원소 105,068,544개 중 다른 것 0
41
+ - 특징: 코사인 최소 0.999999999994(1 − 코사인 ≤ 6.4e-12), 원소 최대 차이 2.4e-5(원소 크기 중앙 1.07)
42
+ - 판정(`leaf_kind_dinov2_logreg`): 한 장 판정 698/698 같음, 대화층 판정(세 장 한 번 포함) 654/654 같음
43
+ - 검증 에피소드 6·7·46·47의 카드 22장(세 장 한 번): 두 길 모두 22/22
44
+ - 그리퍼가 카드를 쥔 프레임 22장, 빈 줄 4장: 두 길 모두 none
45
+ - 3장 판정 지연(JPEG 풀기 포함, AMD Ryzen 5 7500F 4스레드, 두 번 잼): onnx 중앙 324·353 ms, torch 중앙 425·431 ms
46
+
47
+ ## 쓰는 법
48
+ ```python
49
+ import onnxruntime as ort
50
+ session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
51
+ features = session.run(None, {"pixel_values": pixel_values})[0] # [N, 768]
52
+ ```
53
+ 전처리를 numpy로 똑같이 하는 코드는 `LEAP-LAB-KUS/openarmsciedu-robot`의 `dialogue/leaf_onnx.py`(`pixel_values`)에 있다.
54
+
55
+ ## 라이선스
56
+ 원본 DINOv2(Meta AI)의 Apache-2.0을 따른다. 가중치는 원본 그대로이고, 위치 임베딩만 224×224로 미리 보간한 값으로 바꿨다.
leaf_kind_onnx.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """DINOv2-base 특징(pooler_output)을 ONNX로 내보내고, 로컬 나뭇잎 판정기의 두 길(torch·onnxruntime)을 녹화 사진으로
2
+ 견준다(이슈 #28). 로봇에 붙지 않고 GPU도 쓰지 않는다(CPU로 내보내고 잰다).
3
+
4
+ # 1. 내보내기: torch·transformers에 onnx·onnxscript·onnxruntime을 얹은 파이썬
5
+ CUDA_VISIBLE_DEVICES= HF_HUB_OFFLINE=1 <python> scripts/diagnostics/leaf_kind_onnx.py export --out outputs/leaf_onnx/model.onnx
6
+ # 2. 견주기: 위 파이썬 + 녹화 leapshared/Leaf_rules_20261002_225313_subtask의 로컬 사본
7
+ CUDA_VISIBLE_DEVICES= HF_HUB_OFFLINE=1 <python> scripts/diagnostics/leaf_kind_onnx.py compare --onnx outputs/leaf_onnx/model.onnx
8
+
9
+ 저장소 venv에는 onnx·onnxruntime이 없다. 2026-10-04에는 venv의 site-packages를 .pth로 읽는 임시 venv에
10
+ 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만 더 깔아 돌렸다.
11
+
12
+ export는 facebook/dinov2-base(고정 커밋)의 pooler_output만 내는 그래프를 만든다.
13
+ - 입력은 pixel_values [N, 3, 224, 224] float32(전처리를 마친 값), 출력은 pooler_output [N, 768]이다.
14
+ - 224x224의 위치 임베딩(37x37 → 16x16 bicubic)은 torch로 한 번 셈해 상수로 넣는다. 대화층은 늘 224x224를 넣으므로
15
+ 그래프에 보간이 남지 않는다.
16
+ - 내보낸 뒤 같은 입력에서 torch와 견주고, 메타데이터(원본 모델·커밋·도구 판)를 적는다.
17
+
18
+ compare는 녹화에서 서빙 카메라처럼(RGB → BGR → JPEG 품질 80) 사진을 만들어 두 길에 넣는다.
19
+ - 검증 에피소드 6·7·46·47의 카드 22장: 결정 프레임(팔이 출발하기 5프레임 전)과 그 −12·−6 프레임으로 세 장 한 번 판정
20
+ - 같은 카드를 그리퍼가 쥐는 프레임 22장, 그 에피소드의 첫 프레임(빈 줄) 4장: 한 장씩. none이어야 한다
21
+ - 나머지 에피소드의 카드 결정 프레임과 쥐는 프레임: 한 장씩. 특징을 넓게 견준다
22
+ - 찍는 것: 전처리(numpy와 transformers의 원소 차이), 특징 코사인, 판정 일치, 판정기 만들기, 3장 판정 지연(JPEG 풀기 포함)
23
+
24
+ 허브 leapshared/dinov2-base-onnx에 올린 사본은 export만 쓴다(compare는 이 저장소의 dialogue/와 녹화 도구가 있어야 한다).
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import hashlib
31
+ import json
32
+ import statistics
33
+ import sys
34
+ import time
35
+ from pathlib import Path
36
+
37
+ import numpy as np
38
+
39
+ HERE = Path(__file__).resolve()
40
+ REPO = next((p for p in HERE.parents if (p / "pyproject.toml").is_file()), HERE.parent) # 허브 사본은 저장소 밖
41
+ sys.path.insert(0, str(REPO))
42
+
43
+ SOURCE = "facebook/dinov2-base"
44
+ REVISION = "f9e44c814b77203eaa57a6bdbbd535f21ede1415" # 2026-10-04 main(이 PC HF 캐시)
45
+ SIZE = 224
46
+ DATASET = "Leaf_rules_20261002_225313_subtask"
47
+ HELD_OUT = (6, 7, 46, 47)
48
+ KIND = {"복엽": "compound", "단엽": "simple"}
49
+
50
+
51
+ def sha256(path: Path) -> str:
52
+ h = hashlib.sha256()
53
+ with open(path, "rb") as f:
54
+ for block in iter(lambda: f.read(1 << 20), b""):
55
+ h.update(block)
56
+ return h.hexdigest()
57
+
58
+
59
+ def cosines(a: np.ndarray, b: np.ndarray) -> np.ndarray:
60
+ a, b = np.asarray(a, np.float64), np.asarray(b, np.float64)
61
+ return (a * b).sum(1) / (np.linalg.norm(a, axis=1) * np.linalg.norm(b, axis=1))
62
+
63
+
64
+ # ── export ──
65
+ def export(out: Path, revision: str, opset: int | None) -> None:
66
+ import onnx
67
+ import onnxruntime as ort
68
+ import torch
69
+ import transformers
70
+ from transformers import AutoModel
71
+
72
+ torch.manual_seed(0)
73
+ model = AutoModel.from_pretrained(SOURCE, revision=revision).eval()
74
+ probe = torch.randn(3, 3, SIZE, SIZE)
75
+ with torch.no_grad():
76
+ want = model(pixel_values=probe).pooler_output # 고치기 전 모델의 답
77
+
78
+ class Pooler(torch.nn.Module):
79
+ """pixel_values [N, 3, 224, 224] → pooler_output [N, 768]. 위치 임베딩은 224x224로 미리 셈한 상수다."""
80
+
81
+ def __init__(self, inner) -> None:
82
+ super().__init__()
83
+ self.inner = inner
84
+ embeddings = inner.embeddings
85
+ tokens = torch.zeros(1, 1 + (SIZE // embeddings.patch_size) ** 2, inner.config.hidden_size)
86
+ with torch.no_grad():
87
+ self.register_buffer("pos", embeddings.interpolate_pos_encoding(tokens, SIZE, SIZE).clone())
88
+ embeddings.interpolate_pos_encoding = lambda _embeddings, _height, _width: self.pos
89
+
90
+ def forward(self, pixel_values):
91
+ return self.inner(pixel_values=pixel_values).pooler_output
92
+
93
+ pooler = Pooler(model).eval()
94
+ with torch.no_grad():
95
+ got = pooler(probe)
96
+ print(f"상수 위치 임베딩: 원래 모델과 최대 차이 {float((got - want).abs().max()):.3g}")
97
+
98
+ out.parent.mkdir(parents=True, exist_ok=True)
99
+ batch = torch.export.Dim("batch", min=1, max=64)
100
+ torch.onnx.export(pooler, (probe,), str(out), input_names=["pixel_values"], output_names=["pooler_output"],
101
+ dynamic_shapes={"pixel_values": {0: batch}}, opset_version=opset, dynamo=True,
102
+ external_data=False)
103
+ graph = onnx.load(str(out))
104
+ for node in graph.graph.node: # 노드마다 붙는 stack_trace 등: 내보낸 PC의 경로·줄 번호라 계산과 상관없다. 지우면 파일이 늘 같다
105
+ del node.metadata_props[:]
106
+ onnx.helper.set_model_props(graph, {
107
+ "source_model": SOURCE, "source_revision": revision, "output": "pooler_output",
108
+ "input": f"pixel_values [N, 3, {SIZE}, {SIZE}] float32: AutoImageProcessor 전처리를 마친 값",
109
+ "position_embeddings": f"{SIZE}x{SIZE} 입력으로 미리 셈한 상수",
110
+ "exported_with": f"torch {torch.__version__}, transformers {transformers.__version__}, onnx {onnx.__version__}",
111
+ "made_by": "LEAP-LAB-KUS/openarmsciedu-robot scripts/diagnostics/leaf_kind_onnx.py export",
112
+ })
113
+ onnx.save(graph, str(out))
114
+ onnx.checker.check_model(str(out))
115
+ opsets = {o.domain or "ai.onnx": o.version for o in graph.opset_import}
116
+ session = ort.InferenceSession(str(out), providers=["CPUExecutionProvider"])
117
+ for n in (1, 3):
118
+ ort_out = session.run(None, {"pixel_values": probe[:n].numpy()})[0]
119
+ diff = np.abs(ort_out - want[:n].numpy()).max()
120
+ print(f"onnxruntime {ort.__version__}, {n}장: 최대 차이 {diff:.3g}, 코사인 최소 "
121
+ f"{cosines(ort_out, want[:n].numpy()).min():.8f}")
122
+ print(f"썼다: {out} ({out.stat().st_size / 1e6:.1f} MB, sha256 {sha256(out)}, opset {opsets})")
123
+
124
+
125
+ # ── compare ──
126
+ def serving_jpeg(image) -> bytes:
127
+ """서빙 카메라(serving/cameras.py)처럼: RGB → BGR → JPEG 품질 80."""
128
+ import cv2
129
+
130
+ rgb = np.asarray(image.convert("RGB"), dtype=np.uint8)
131
+ ok, buf = cv2.imencode(".jpg", cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR), [cv2.IMWRITE_JPEG_QUALITY, 80])
132
+ assert ok
133
+ return buf.tobytes()
134
+
135
+
136
+ def photos(root: Path) -> list[dict]:
137
+ """녹화의 사진들: {"name", "kind", "group", "held_out", "jpeg"}. 검증 카드는 세 장(−12·−6·0)이 한 판정이다."""
138
+ sys.path.insert(0, str(REPO / "scripts" / "diagnostics"))
139
+ sys.path.insert(0, str(REPO / "scripts" / "verifier"))
140
+ import common
141
+ from leaf_view import all_cards
142
+
143
+ meta = common.episodes_meta(root)
144
+ by_episode: dict[int, list[dict]] = {}
145
+ for card in all_cards(root):
146
+ by_episode.setdefault(card["episode"], []).append(card)
147
+ rows = []
148
+ for episode, cards in sorted(by_episode.items()):
149
+ held = episode in HELD_OUT
150
+ want = {0} if held else set()
151
+ for c in cards:
152
+ want |= {c["decide"], c["close"]} | ({max(0, c["decide"] - 12), max(0, c["decide"] - 6)} if held else set())
153
+ got = common.read_frames(root, meta, episode, common.CHEST, sorted(want))
154
+ if held:
155
+ rows.append({"name": f"ep{episode:02d} 첫 프레임", "kind": "none", "group": "first", "held_out": True,
156
+ "jpeg": [serving_jpeg(got[0])]})
157
+ for c in cards:
158
+ tag = f"ep{episode:02d}#{c['card']}"
159
+ offsets = (-12, -6, 0) if held else (0,)
160
+ rows.append({"name": tag, "kind": KIND[c["kind"]], "group": "card", "held_out": held,
161
+ "jpeg": [serving_jpeg(got[max(0, c["decide"] + o)]) for o in offsets]})
162
+ rows.append({"name": f"{tag} 쥠", "kind": "none", "group": "close", "held_out": held,
163
+ "jpeg": [serving_jpeg(got[c["close"]])]})
164
+ return rows
165
+
166
+
167
+ def compare(onnx_path: Path, root: Path, threads: int, repeat: int, out: Path | None) -> None:
168
+ import torch
169
+ import transformers
170
+
171
+ from dialogue.leaf_kind.leaf_kind import LeafKind
172
+ from dialogue.leaf_local import LocalLeafJudge, rgb_frame
173
+ from dialogue.leaf_onnx import OnnxLeafKind, pixel_values
174
+
175
+ classifier = REPO / "dialogue" / "leaf_kind" / "classifier.json"
176
+ torch.set_num_threads(threads)
177
+ started = time.perf_counter()
178
+ reference = LeafKind(classifier=classifier, device="cpu", threshold=0.9)
179
+ torch_load = time.perf_counter() - started
180
+ started = time.perf_counter()
181
+ candidate = OnnxLeafKind(onnx_path, classifier, threshold=0.9, threads=threads)
182
+ onnx_load = time.perf_counter() - started
183
+
184
+ rows = photos(root)
185
+ x0, y0, x1, y1 = reference.crop
186
+ pixel_diff = pixel_total = 0
187
+ feats_t, feats_o, frame_rows, judged = [], [], [], []
188
+ for row in rows:
189
+ frames = [rgb_frame(j) for j in row["jpeg"]]
190
+ for frame in frames: # 한 장씩: 전처리와 특징
191
+ crop = np.ascontiguousarray(frame[y0:y1, x0:x1])
192
+ ref_px = reference.processor(images=[crop], return_tensors="np")["pixel_values"]
193
+ pixel_diff += int((ref_px != pixel_values([crop])).sum())
194
+ pixel_total += ref_px.size
195
+ ft, fo = reference.features([frame]), candidate.features([frame])
196
+ feats_t.append(ft[0])
197
+ feats_o.append(fo[0])
198
+ pt, po = reference.proba([frame])[0], candidate.proba([frame])[0]
199
+ frame_rows.append({"name": row["name"], "kind": row["kind"], "torch": reference.classes[int(pt.argmax())],
200
+ "onnx": candidate.classes[int(po.argmax())], "dp": float(np.abs(pt - po).max())})
201
+ a, b = reference.judge(frames), candidate.judge(frames) # 대화층처럼 한 번에
202
+ judged.append({"name": row["name"], "kind": row["kind"], "group": row["group"], "held_out": row["held_out"],
203
+ "frames": len(frames), "torch": a, "onnx": b})
204
+
205
+ cos = cosines(np.stack(feats_t), np.stack(feats_o))
206
+ absdiff = np.abs(np.stack(feats_t) - np.stack(feats_o))
207
+ same_vote = sum(r["torch"] == r["onnx"] for r in frame_rows)
208
+ same_judge = sum((j["torch"]["kind"], j["torch"]["votes"]) == (j["onnx"]["kind"], j["onnx"]["votes"]) for j in judged)
209
+ print(f"판정기 만들기(꾸러미를 불러온 뒤): torch {torch_load:.2f} s, onnx {onnx_load:.2f} s (스레드 {threads})")
210
+ print(f"전처리: 다른 원소 {pixel_diff}/{pixel_total}")
211
+ print(f"특징 {len(cos)}장: 코사인 최소 {cos.min():.12f}(1 − 코사인 최대 {1 - cos.min():.2g}), 원소 최대 차이 "
212
+ f"{absdiff.max():.3g}(특징 원소 크기 중앙 {np.median(np.abs(np.stack(feats_t))):.3g}), 세 부류 확률 최대 차이 "
213
+ f"{max(r['dp'] for r in frame_rows):.3g}")
214
+ print(f"한 장 판정(argmax) 같음 {same_vote}/{len(frame_rows)}, 판정(kind·votes) 같음 {same_judge}/{len(judged)}")
215
+
216
+ def tally(select, label):
217
+ chosen = [j for j in judged if select(j)]
218
+ for path in ("torch", "onnx"):
219
+ right = sum(j[path]["kind"] == j["kind"] for j in chosen)
220
+ other = {}
221
+ for j in chosen:
222
+ if j[path]["kind"] != j["kind"]:
223
+ other[str(j[path]["kind"])] = other.get(str(j[path]["kind"]), 0) + 1
224
+ print(f" {label} {path}: 맞음 {right}/{len(chosen)}" + (f", 다른 답 {other}" if other else ""))
225
+
226
+ tally(lambda j: j["held_out"] and j["group"] == "card", "검증 카드(세 장 한 번)")
227
+ tally(lambda j: j["held_out"] and j["group"] == "close", "검증 쥐는 프레임")
228
+ tally(lambda j: j["held_out"] and j["group"] == "first", "검증 첫 프레임(빈 줄)")
229
+ tally(lambda j: not j["held_out"] and j["group"] == "card", "나머지 카드(한 장)")
230
+ tally(lambda j: not j["held_out"] and j["group"] == "close", "나머지 쥐는 프레임")
231
+
232
+ # 지연: 대화층과 같은 길(JPEG 세 장 → 풀기 → 자르기 → 전처리 → 특징 → 판정)
233
+ three = next(r for r in rows if r["held_out"] and r["group"] == "card")["jpeg"]
234
+ latency = {}
235
+ for path, clf in (("torch", reference), ("onnx", candidate)):
236
+ judge = LocalLeafJudge(clf, path)
237
+ for _ in range(3):
238
+ judge.judge(three)
239
+ times = []
240
+ for _ in range(repeat):
241
+ started = time.perf_counter()
242
+ judge.judge(three)
243
+ times.append((time.perf_counter() - started) * 1000)
244
+ latency[path] = times
245
+ print(f"3장 판정 {path}: 중앙 {statistics.median(times):.0f} ms, 최대 {max(times):.0f} ms ({repeat}번)")
246
+ if out:
247
+ out.parent.mkdir(parents=True, exist_ok=True)
248
+ out.write_text(json.dumps({
249
+ "onnx": str(onnx_path), "onnx_sha256": sha256(onnx_path), "threads": threads,
250
+ "versions": {"torch": torch.__version__, "transformers": transformers.__version__,
251
+ "onnxruntime": __import__("onnxruntime").__version__},
252
+ "load_s": {"torch": torch_load, "onnx": onnx_load}, "pixel_diff": [pixel_diff, pixel_total],
253
+ "cosine": {"min": float(cos.min()), "median": float(np.median(cos))}, "feature_absdiff_max": float(absdiff.max()),
254
+ "frames": frame_rows, "judged": judged, "latency_ms": latency}, ensure_ascii=False, indent=1))
255
+ print("썼다:", out)
256
+
257
+
258
+ def main() -> int:
259
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
260
+ sub = parser.add_subparsers(dest="command", required=True)
261
+ ex = sub.add_parser("export", help="dinov2-base pooler_output → ONNX(CPU)")
262
+ ex.add_argument("--out", type=Path, default=REPO / "outputs" / "leaf_onnx" / "model.onnx")
263
+ ex.add_argument("--revision", default=REVISION)
264
+ ex.add_argument("--opset", type=int, default=None, help="비우면 torch 내보내기의 기본값")
265
+ co = sub.add_parser("compare", help="녹화 사진으로 torch 길과 onnxruntime 길 견주기")
266
+ co.add_argument("--onnx", type=Path, required=True)
267
+ co.add_argument("--root", type=Path, default=Path.home() / ".cache" / "huggingface" / "lerobot" / "leapshared" / DATASET)
268
+ co.add_argument("--threads", type=int, default=4, help="대화층 설정 leaf.local.threads와 같게")
269
+ co.add_argument("--repeat", type=int, default=30)
270
+ co.add_argument("--out", type=Path, default=REPO / "outputs" / "leaf_onnx" / "compare.json")
271
+ args = parser.parse_args()
272
+ if args.command == "export":
273
+ export(args.out, args.revision, args.opset)
274
+ else:
275
+ compare(args.onnx, args.root, args.threads, args.repeat, args.out)
276
+ return 0
277
+
278
+
279
+ if __name__ == "__main__":
280
+ raise SystemExit(main())
model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8f905c48a542eddc7e43e77d98918683dfc59ecb827ab5789ef8c09a4ac0cc2a
3
+ size 343041303