danilobukvic commited on
Commit
359247c
Β·
verified Β·
1 Parent(s): 9863411

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +193 -46
README.md CHANGED
@@ -36,18 +36,42 @@ This export was produced by hand-wrapping the three sub-modules of `Sam3Model` a
36
 
37
  ```
38
  sam3-text-onnx/
39
- β”œβ”€β”€ vision_encoder.onnx # graph (6.2 MB)
40
- β”œβ”€β”€ vision_encoder.onnx.data # weights (1.84 GB)
41
- β”œβ”€β”€ text_encoder.onnx # graph (3.0 MB)
42
- β”œβ”€β”€ text_encoder.onnx.data # weights (1.35 GB)
43
- β”œβ”€β”€ decoder.onnx # graph + weights inline (96 MB)
44
- β”œβ”€β”€ export_sam3_vision.py # script that produced vision_encoder.onnx
45
- β”œβ”€β”€ export_sam3_text.py # script that produced text_encoder.onnx
46
- β”œβ”€β”€ export_sam3_decoder.py # script that produced decoder.onnx
47
- └── validate_sam3_e2e.py # end-to-end Python validation harness
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  ```
49
 
50
- Total: **~3.3 GB at fp32**. Quantization is recommended for browser deployment β€” see [Quantization & next steps](#quantization--next-steps).
51
 
52
  ## Architecture
53
 
@@ -191,73 +215,196 @@ const { input_ids, attention_mask } = await tokenizer("seed", {
191
  | **Out** | `pred_boxes` | `[batch, 200, 4]` | float32 | Boxes in xyxy format, **normalized to [0, 1]** |
192
  | **Out** | `pred_logits` | `[batch, 200]` | float32 | Apply sigmoid for [0, 1] confidence scores |
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  ## Performance
195
 
196
- Measured on CPU (Intel laptop), single image:
197
 
198
- | Component | Time |
199
- |---|---|
200
- | vision_encoder | ~150 s |
201
- | text_encoder | ~7 s |
202
- | decoder | ~8 s |
203
- | **Total per image** | **~170 s** |
204
- | Re-run with different prompt (same image) | **~15 s** (vision cached) |
205
 
206
- GPU (CUDA execution provider, RTX-class) should be roughly 10-15Γ— faster. WebGPU in modern Chrome should fall between the two.
207
 
208
- The architecture is designed for prompt iteration: cache the vision encoder output, re-run text + decoder per prompt.
209
 
210
- ## Validation
 
 
 
 
 
 
 
211
 
212
- Compared end-to-end against the original PyTorch `Sam3Model.forward()` on a microscope-style seed image with prompt `"seed"`:
213
 
214
- | Metric | PyTorch SAM 3 | This ONNX export |
215
- |---|---|---|
216
- | Detections > 0.5 | 12 | 12 |
217
- | Top scores | 0.66–0.93 | 0.66–0.93 |
218
- | Box locations | clustered at seeds | clustered at seeds |
219
 
220
- Numerically equivalent within tracing noise. See `validate_sam3_e2e.py` to reproduce.
221
 
222
- ## Quantization & next steps
223
 
224
- The fp32 export totals ~3.3 GB which is impractical for browser deployment. The natural follow-up is per-component quantization, mirroring what `onnx-community/sam3-tracker-ONNX` does for the tracker variant:
225
 
226
- ```js
227
- dtype: {
228
- vision_encoder: "q4", // ~470 MB
229
- prompt_encoder_mask_decoder: "fp32", // kept at full precision
230
- }
 
 
 
 
231
  ```
232
 
233
- Estimated sizes after quantization:
234
 
235
- | Component | fp32 | fp16 | q8 | q4 |
236
- |---|---|---|---|---|
237
- | vision_encoder | 1.84 GB | ~900 MB | ~470 MB | ~240 MB |
238
- | text_encoder | 1.35 GB | ~675 MB | ~340 MB | ~170 MB |
239
- | decoder | 96 MB | ~48 MB | ~25 MB | ~13 MB |
240
- | **Total** | **3.3 GB** | **~1.6 GB** | **~830 MB** | **~420 MB** |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
- I haven't published quantized versions yet. If you build them, PRs welcome.
243
 
244
  ## Known caveats and TODOs
245
 
246
  - **Fixed input size**: The vision encoder's positional embeddings are precomputed for 1008Γ—1008 input. Other sizes will produce shape mismatches. Use the SAM 3 image processor to handle resizing/padding.
247
  - **Geometry prompts not supported**: This export covers the text-only path. SAM 3's optional box/point prompts are not wired in β€” would need a separate decoder variant.
248
  - **Dynamic batch is dynamic but untested**: The export uses symbolic batch dim but I've only validated batch=1. Higher batch sizes should work but no guarantees.
 
 
249
  - **Tracer warnings during export**: A few `TracerWarning: Converting a tensor to a Python boolean...` were emitted. These bake config flags (e.g. `is_causal`, attention backend selection) into the graph at export time. Fine for inference with the same model config, but means the ONNX isn't reusable across configs.
250
  - **No `transformers.js` integration**: At time of writing, `transformers.js` only has `Sam3TrackerModel` (point/box) but not `Sam3Model` (text-promptable image). Until that lands, use `onnxruntime-web` directly.
 
251
 
252
  ## How this was built
253
 
254
  Three Python scripts, one per sub-module, each calling `torch.onnx.export` on a thin wrapper around the SAM 3 component:
255
 
256
  1. `export_sam3_vision.py` β€” wraps `Sam3VisionModel`, flattens `Sam3VisionEncoderOutput` into a tuple of tensors. Uses the default (dynamo) exporter.
257
- 2. `export_sam3_text.py` β€” wraps `CLIPTextModelWithProjection` + the `text_projection` Linear layer.
258
- 3. `export_sam3_decoder.py` β€” wraps `detr_encoder` + `detr_decoder` + `mask_decoder` + `dot_product_scoring` + box head as a single pipeline. **Uses `dynamo=False`** (legacy tracer) β€” the dynamo exporter crashes on the attention reshape-after-transpose pattern.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
- All three are in this repo. See `validate_sam3_e2e.py` for the end-to-end pipeline.
 
 
261
 
262
  ## License
263
 
 
36
 
37
  ```
38
  sam3-text-onnx/
39
+ β”‚
40
+ β”œβ”€ fp32 reference (3.3 GB total) β€” bit-equivalent to PyTorch
41
+ β”‚ β”œβ”€β”€ vision_encoder.onnx (6.2 MB graph)
42
+ β”‚ β”œβ”€β”€ vision_encoder.onnx.data (1.84 GB weights)
43
+ β”‚ β”œβ”€β”€ text_encoder.onnx (3.0 MB graph)
44
+ β”‚ β”œβ”€β”€ text_encoder.onnx.data (1.35 GB weights)
45
+ β”‚ └── decoder.onnx (96 MB, weights inline)
46
+ β”‚
47
+ β”œβ”€ int8 dynamic quantization (839 MB total) β€” production default
48
+ β”‚ β”œβ”€β”€ vision_encoder_int8.onnx (473 MB)
49
+ β”‚ β”œβ”€β”€ text_encoder_int8.onnx (340 MB)
50
+ β”‚ └── decoder_int8.onnx (26 MB)
51
+ β”‚
52
+ β”œβ”€ int4 MatMul quantization (654 MB total) β€” browser/mobile
53
+ β”‚ β”œβ”€β”€ vision_encoder_int4.onnx (5.6 MB graph)
54
+ β”‚ β”œβ”€β”€ vision_encoder_int4.onnx.data (279 MB weights)
55
+ β”‚ β”œβ”€β”€ text_encoder_int4.onnx (2.7 MB graph)
56
+ β”‚ β”œβ”€β”€ text_encoder_int4.onnx.data (348 MB weights)
57
+ β”‚ └── decoder_int4.onnx (19 MB)
58
+ β”‚
59
+ β”œβ”€ Export scripts (PyTorch β†’ ONNX)
60
+ β”‚ β”œβ”€β”€ export_sam3_vision.py # produces vision_encoder.onnx
61
+ β”‚ β”œβ”€β”€ export_sam3_text.py # produces text_encoder.onnx
62
+ β”‚ └── export_sam3_decoder.py # produces decoder.onnx
63
+ β”‚
64
+ β”œβ”€ Quantization scripts (ONNX fp32 β†’ smaller)
65
+ β”‚ β”œβ”€β”€ quantize_sam3_fp16.py # ⚠️ currently broken, see Known Caveats
66
+ β”‚ β”œβ”€β”€ quantize_sam3_int8.py
67
+ β”‚ └── quantize_sam3_int4.py
68
+ β”‚
69
+ └─ Validation scripts
70
+ β”œβ”€β”€ validate_sam3_e2e.py # end-to-end pipeline test
71
+ └── validate_all_variants.py # compares fp32/int8/int4 side-by-side
72
  ```
73
 
74
+ Three precision variants, each independently usable. See [Which precision should I use?](#which-precision-should-i-use) for guidance.
75
 
76
  ## Architecture
77
 
 
215
  | **Out** | `pred_boxes` | `[batch, 200, 4]` | float32 | Boxes in xyxy format, **normalized to [0, 1]** |
216
  | **Out** | `pred_logits` | `[batch, 200]` | float32 | Apply sigmoid for [0, 1] confidence scores |
217
 
218
+ ## Which precision should I use?
219
+
220
+ Three precision variants are published. They share the same input/output contracts above β€” pick whichever matches your constraints.
221
+
222
+ | Need… | Use | Why |
223
+ |---|---|---|
224
+ | Reference accuracy / research baseline | **fp32** | Bit-exact match against PyTorch. Reproducible numbers. |
225
+ | Production server inference (CPU or GPU > 6 GB VRAM) | **int8** | 4Γ— smaller than fp32, same top detections, scores within 0.001 of baseline. AVX-512 VNNI and CUDA both accelerate int8 ops natively. |
226
+ | Browser / mobile / GPU with ≀ 4 GB VRAM | **int4** | 5Γ— smaller than fp32, keeps the right top-N detections, modest quality loss in low-confidence range. |
227
+ | GPU inference on a 4 GB consumer card | **int8 or int4 (not fp32)** | fp32 exhausts VRAM via intermediate Softmax buffers (~1.7 GB). int8 and int4 both fit. |
228
+ | Fastest CPU inference | **int8** | Modern CPUs accelerate int8 GEMMs via AVX-VNNI; ~3Γ— faster than fp32 even on CPU. |
229
+
230
+ **Default recommendation: int8.** It's the same quality as fp32 in practice but a quarter of the size and several times faster. Only use fp32 if you need bit-exact reproducibility for academic work. Only use int4 if you're shipping to a context where every megabyte matters (browsers, mobile, embedded).
231
+
232
+ ### Validation evidence
233
+
234
+ On a microscope-style seed image with prompt `"seed"` (the test image used during development):
235
+
236
+ | Variant | Detections > 0.5 | Max score | Top 5 scores |
237
+ |---|---|---|---|
238
+ | fp32 (baseline) | 12 | 0.926 | 0.926, 0.889, 0.878, 0.874, 0.868 |
239
+ | int8 | 12 | 0.925 | 0.925, 0.905, 0.902, 0.898, 0.872 |
240
+ | int4 | 12 | 0.898 | 0.898, 0.863, 0.844, 0.797, 0.784 |
241
+
242
+ int8 is essentially indistinguishable from fp32. int4 keeps the right top detections but scores are ~3% lower with more noise in the low-confidence range.
243
+
244
  ## Performance
245
 
246
+ Per-component timings for a single 1008Γ—1008 image. Measured on a laptop with a quad-core Intel CPU and an RTX 3050 Laptop (4 GB VRAM):
247
 
248
+ | Variant | vision (CPU) | vision (GPU) | text | decoder (CPU) | Total (best path) |
249
+ |---|---|---|---|---|---|
250
+ | fp32 | 181 s | OOM on 4 GB | 7 s | 13 s | **201 s (CPU only)** |
251
+ | int8 | 53 s | 27 s | 1 s | 14 s | **42 s (hybrid)** |
252
+ | int4 | 166 s | 12 s | 0.1 s | 16 s | **28 s (hybrid)** |
 
 
253
 
254
+ ### The GPU+CPU hybrid pattern
255
 
256
+ On consumer GPUs with limited VRAM, the **vision encoder benefits massively from GPU** (it's a heavy ViT) but the **decoder runs better on CPU** because its attention layers need ~860 MB intermediate buffers that don't fit alongside the encoder in VRAM. The hybrid setup:
257
 
258
+ ```python
259
+ vsess = ort.InferenceSession("vision_encoder_int4.onnx",
260
+ providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
261
+ tsess = ort.InferenceSession("text_encoder_int4.onnx",
262
+ providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
263
+ dsess = ort.InferenceSession("decoder_int4.onnx",
264
+ providers=["CPUExecutionProvider"]) # ← decoder on CPU
265
+ ```
266
 
267
+ With this split, the int4 vision encoder runs in **~12 seconds on an RTX 3050** (vs. 166 seconds CPU-only) β€” a 14Γ— speedup on the bottleneck. Total inference drops from ~3 minutes to ~30 seconds.
268
 
269
+ On a server-class GPU (16+ GB VRAM) all three can run on GPU and inference is sub-second.
 
 
 
 
270
 
271
+ ### Prompt-iteration architecture
272
 
273
+ The decoder uses pre-computed vision features as inputs, so swapping the prompt while keeping the image only re-runs the cheap text + decoder path (`~15 s` end-to-end, vs ~30+ seconds for a fresh image). This makes interactive use viable.
274
 
275
+ ## Validation
276
 
277
+ The fp32 ONNX export was compared end-to-end against the original PyTorch `Sam3Model.forward()` on a microscope-style seed image with prompt `"seed"`. Detection count, top scores, and box locations matched bit-for-bit within tracing noise.
278
+
279
+ The quantized variants (int8, int4) were then compared against the fp32 ONNX baseline β€” see the [precision selection table](#which-precision-should-i-use) for results.
280
+
281
+ To reproduce on your own image:
282
+
283
+ ```powershell
284
+ python validate_sam3_e2e.py path/to/image.png "seed" # single variant
285
+ python validate_all_variants.py path/to/image.png "seed" # all 3 side-by-side
286
  ```
287
 
288
+ ## Quantization results
289
 
290
+ Three precision variants are published in this repo. Actual measured sizes:
291
+
292
+ | Component | fp32 | int8 (dynamic) | int4 (MatMul block) |
293
+ |---|---|---|---|
294
+ | vision_encoder | 1.84 GB | **473 MB** (3.7Γ—) | **285 MB** (6.5Γ—) |
295
+ | text_encoder | 1.35 GB | **340 MB** (4.0Γ—) | **350 MB** (3.9Γ—) |
296
+ | decoder | 96 MB | **26 MB** (3.6Γ—) | **19 MB** (4.9Γ—) |
297
+ | **Total** | **3.3 GB** | **839 MB** (4.0Γ—) | **654 MB** (5.0Γ—) |
298
+
299
+ ### Notes on each variant
300
+
301
+ **int8 β€” `quantize_sam3_int8.py`** uses `onnxruntime.quantization.quantize_dynamic` with `QuantType.QInt8`. Per-tensor symmetric quantization of MatMul/Gemm weights. No calibration data needed. Outputs are bit-equivalent to fp32 in practice.
302
+
303
+ **int4 β€” `quantize_sam3_int4.py`** uses `MatMulNBitsQuantizer` with `block_size=128, bits=4, is_symmetric=True`. Only quantizes MatMul ops; other ops stay fp32. This is the same scheme HuggingFace uses with `dtype: "q4"` in `transformers.js`, and what `onnx-community/sam3-tracker-ONNX` uses for its vision encoder.
304
+
305
+ **Note on text encoder size**: int4 only saves a small amount over int8 for the text encoder because most of its size is in the **CLIP token embedding table** (a lookup matrix, not a MatMul). The block-wise int4 scheme targets MatMul weights specifically, so embeddings stay fp32 in both.
306
+
307
+ ### fp16 status
308
+
309
+ A fully-working **fp16** variant is not currently published. `onnxconverter_common.float16.convert_float_to_float16` produces type-mismatch errors when applied to the decoder β€” PyTorch's traced `Cast` ops create incompatible type boundaries that the converter doesn't propagate cleanly through. Blocking `Cast`/`CastLike` from conversion fixes the vision encoder but the decoder has additional `Mul` ops with the same problem.
310
 
311
+ For practical purposes, **int8 dominates fp16 anyway**: int8 is half the size with equivalent quality. fp16 has no real use case here. See `quantize_sam3_fp16.py` for the current (partially working) attempt if you want to try fixing it.
312
 
313
  ## Known caveats and TODOs
314
 
315
  - **Fixed input size**: The vision encoder's positional embeddings are precomputed for 1008Γ—1008 input. Other sizes will produce shape mismatches. Use the SAM 3 image processor to handle resizing/padding.
316
  - **Geometry prompts not supported**: This export covers the text-only path. SAM 3's optional box/point prompts are not wired in β€” would need a separate decoder variant.
317
  - **Dynamic batch is dynamic but untested**: The export uses symbolic batch dim but I've only validated batch=1. Higher batch sizes should work but no guarantees.
318
+ - **fp16 export is broken**: See [fp16 status](#fp16-status). int8 is the recommended half-precision alternative.
319
+ - **fp32 on 4 GB VRAM**: The fp32 vision encoder's Softmax allocates a ~1.7 GB intermediate buffer; on consumer GPUs this exhausts VRAM. Use int8/int4 for GPU, or fp32 on CPU only.
320
  - **Tracer warnings during export**: A few `TracerWarning: Converting a tensor to a Python boolean...` were emitted. These bake config flags (e.g. `is_causal`, attention backend selection) into the graph at export time. Fine for inference with the same model config, but means the ONNX isn't reusable across configs.
321
  - **No `transformers.js` integration**: At time of writing, `transformers.js` only has `Sam3TrackerModel` (point/box) but not `Sam3Model` (text-promptable image). Until that lands, use `onnxruntime-web` directly.
322
+ - **Tracer optimized out unused inputs**: The decoder ONNX accepts only `fpn_position_encoding_2` (not `_0` or `_1`) because those weren't actually used in the traced forward path. Inspect `decoder_sess.get_inputs()` for the canonical input names.
323
 
324
  ## How this was built
325
 
326
  Three Python scripts, one per sub-module, each calling `torch.onnx.export` on a thin wrapper around the SAM 3 component:
327
 
328
  1. `export_sam3_vision.py` β€” wraps `Sam3VisionModel`, flattens `Sam3VisionEncoderOutput` into a tuple of tensors. Uses the default (dynamo) exporter.
329
+ 2. `export_sam3_text.py` β€” wraps `CLIPTextModelWithProjection` + the `text_projection` Linear layer. Key gotcha: SAM 3's text config has `max_position_embeddings=32` (shorter than standard CLIP's 77).
330
+ 3. `export_sam3_decoder.py` β€” wraps `detr_encoder` + `detr_decoder` + `mask_decoder` + `dot_product_scoring` + box head as a single pipeline. **Uses `dynamo=False`** (legacy tracer) β€” the dynamo exporter crashes on the attention reshape-after-transpose pattern (`Cannot view a tensor with shape (1, 201, 8, 32) ... as a tensor with shape (s2, 201, 256)`).
331
+
332
+ Quantization scripts (added after the initial export):
333
+
334
+ - `quantize_sam3_int8.py` β€” `onnxruntime.quantization.quantize_dynamic` with `QInt8` weight type. Works on all three components out of the box.
335
+ - `quantize_sam3_int4.py` β€” `MatMulNBitsQuantizer` from `onnxruntime.quantization.matmul_nbits_quantizer`. `block_size=128`, `bits=4`, symmetric. Only quantizes MatMul ops; other ops stay fp32.
336
+ - `quantize_sam3_fp16.py` β€” uses `onnxconverter_common.float16.convert_float_to_float16` with `op_block_list=["Cast", "CastLike"]`. Fixes the vision encoder but the decoder still has type mismatches at `Mul` ops; partial fix only.
337
+
338
+ See `validate_sam3_e2e.py` and `validate_all_variants.py` for the end-to-end pipeline tests.
339
+
340
+ ## Reproducing this export
341
+
342
+ If you want to redo the export from scratch (e.g. to target a different opset, change the input size, or add geometry prompts), here's the full sequence.
343
+
344
+ ### Prerequisites
345
+
346
+ You need **transformers v5** (which is in pre-release as of writing) and a build of `optimum-onnx` from source. The stable `optimum` releases require transformers v4 and don't know about SAM 3. Install from source with strict dependency control:
347
+
348
+ ```powershell
349
+ py -3.11 -m venv .venv
350
+ .\.venv\Scripts\Activate.ps1
351
+ pip install --upgrade pip
352
+
353
+ # Clone transformers from source β€” v5 is required for Sam3Model class
354
+ git clone https://github.com/huggingface/transformers.git
355
+ cd transformers
356
+ pip install .[torch]
357
+ cd ..
358
+
359
+ # Upgrade huggingface_hub to v1+ (transformers v5 requires it; --no-deps avoids
360
+ # pulling old optimum back down)
361
+ pip install --upgrade --no-deps "huggingface_hub>=1.0"
362
+
363
+ # Install optimum-onnx from source with --no-deps to preserve transformers v5
364
+ pip install git+https://github.com/huggingface/optimum-onnx.git --no-deps
365
+
366
+ # Remaining deps
367
+ pip install onnx onnxruntime onnxscript onnx_ir pillow torchvision opencv-python --no-cache-dir
368
+ ```
369
+
370
+ Verify the install:
371
+
372
+ ```powershell
373
+ python -c "from transformers import Sam3Model, Sam3Processor; print('SAM3 OK')"
374
+ python -c "from optimum.exporters.onnx import main_export; print('optimum OK')"
375
+ ```
376
+
377
+ You'll also need a HuggingFace token to download `facebook/sam3`:
378
+
379
+ ```powershell
380
+ $env:HF_TOKEN = "hf_xxx"
381
+ ```
382
+
383
+ ### Run the export
384
+
385
+ ```powershell
386
+ # fp32 export (~15 min total on CPU)
387
+ python export_sam3_vision.py # ~5-10 min, produces vision_encoder.onnx + .data
388
+ python export_sam3_text.py # ~2 min, produces text_encoder.onnx + .data
389
+ python export_sam3_decoder.py # ~3 min, produces decoder.onnx
390
+
391
+ # Validate against PyTorch (~3 min, only do once)
392
+ python validate_sam3_e2e.py path/to/test-image.png "your prompt"
393
+ ```
394
+
395
+ ### Quantize (optional but recommended)
396
+
397
+ ```powershell
398
+ python quantize_sam3_int8.py # ~2 min, produces *_int8.onnx
399
+ python quantize_sam3_int4.py # ~3 min, produces *_int4.onnx
400
+ python validate_all_variants.py path/to/test-image.png "your prompt"
401
+ ```
402
+
403
+ ### Hardware notes
404
 
405
+ - **CPU is sufficient** for both export and inference. GPU helps for fast iteration during inference but isn't needed for the export itself.
406
+ - **At least 8 GB system RAM** is needed during the vision encoder export β€” the model is ~3.4 GB and the tracer holds intermediate state.
407
+ - **`onnxruntime-gpu` requires CUDA 12.x runtime libraries**. If you have an NVIDIA driver supporting CUDA 12.x but no CUDA toolkit installed, `pip install nvidia-cudnn-cu12 nvidia-cublas-cu12 nvidia-cuda-runtime-cu12 nvidia-cufft-cu12` brings the needed DLLs.
408
 
409
  ## License
410