sam3-blackhole

Meta's SAM3 (Segment Anything with Concepts, 840.5M params) for text-prompted open-vocabulary instance segmentation: an image plus noun-phrase prompts such as "car" gives per-instance masks, boxes and scores. The ViT-H/14 backbone and the 6-layer fusion encoder run on a single Tenstorrent Blackhole p150a via tt-nn (FPN neck, text encoder, decoder and segmentation head stay on the host CPU): 0.77 s end-to-end on a 1920x1080 image vs 3.20 s on CPU (4.15x), ~330 ms / 3.0 FPS warm, 0.998 top-mask IoU vs the CPU reference. Weights are facebook/sam3 (gated, Meta SAM License); port source github.com/changh95/tt-sam3 @ cb728e85275334973b39d5acf6ae376ff2a94a68.

Runs on p150 (mesh P150).

Packaged and published with tt-model-manager 0.1.0 (manifest schema 5.1).

Quickstart

tt-model pull  changh95/sam3-blackhole --with-weights
tt-model serve changh95/sam3-blackhole

pull --with-weights downloads the Docker image and the facebook/sam3 weights at 3c879f39826c281e95690f02c7821c4de09afae7 (into your HF cache; they are not in the image). serve starts the model's own HTTP server on port 20000 (or the next free port, if that one is busy); the first start compiles kernels for your device, which takes several minutes, and the server is ready when it logs Application startup complete.

With tt-cli

tt serve changh95/sam3-blackhole          # pulls the image + weights, boots, waits for READY
tt model stop changh95/sam3-blackhole

The port is the one serve printed (20000, or the next free one). This is the model's own HTTP API: the ready card's /v1/models hint and tt-model curl are OpenAI-shaped and do not apply (GET /v1/models exists only so they do not 404).

Prerequisite: gated weights

facebook/sam3 is gated under the Meta SAM License. Request access on the Hub, then hf auth login on the host before serve; the 3.45 GB sam3.pt (+ config.json) lands in your HF cache at the pinned revision and is bind-mounted into the container.

Call it

PORT=20000   # the port serve printed
curl -s localhost:$PORT/health      # {"status":"ok","model":"sam3-blackhole","device":{...}}
curl -s localhost:$PORT/info        # weights repo + revision, limits, source commit

python3 - <<'EOF'
import base64, json, urllib.request
img = base64.b64encode(open("media/source_1.png", "rb").read()).decode()
req = {"image": img, "prompts": ["car"], "threshold": 0.5, "max_instances": 50, "mask_format": "png"}
r = urllib.request.Request("http://127.0.0.1:20000/predict", data=json.dumps(req).encode(),
                           headers={"Content-Type": "application/json"})
out = json.load(urllib.request.urlopen(r))
top = out["results"][0]["instances"][0]
print(out["width"], out["height"], out["timing_ms"], top["score"], top["box_xyxy"])
open("mask0.png", "wb").write(base64.b64decode(top["mask_png"]))   # 8-bit 0/255 PNG, HxW
EOF

Request (POST /predict, JSON): image (base64 PNG/JPEG, any size; it is squashed to the fixed 1008x1008 the port validated), prompts (1-4 noun phrases; each is an independent query and costs one forward), threshold (0.5; keep score > threshold as upstream), max_instances (50, per prompt, highest score first), mask_format (png default | rle COCO uncompressed column-major | none).

Response: width, height, input_size (1008), results[] one per prompt with prompt, presence (image-level presence probability), num_instances and instances[] sorted by score desc, each with score, box_xyxy (pixels in the ORIGINAL image), mask_area (pixels) and mask_png (base64 8-bit 0/255 PNG at the original resolution) or mask_rle; plus timing_ms (decode / preprocess / forward / forward_per_prompt / postprocess / total). 400 for bad input, 503 while starting, 500 with the exception text on failure. Box or point prompts are not supported by this port (text prompts only, one image per call).

First boot

Weights: 3.45 GB download once (HF cache). The first start JIT-compiles 40 ttnn kernels (several minutes) into `/.cache/tt-model/sam3-blackhole/cache/; the server reports READY (Application startup complete) only after two warm-up forwards, so the first request is already fast. Later boots reuse the kernel cache (roughly a minute: load the 3.45 GB checkpoint, convert ~840M params to bfloat8_b/bf16, upload). Host RAM: ~8 GB transient while loading. The decoder runs in eager mode (SAM3_TORCH_COMPILE=0`, ~3% slower than the README's compiled rows) because the runtime image has no C++ compiler.

Demo (from the port README)

Street scene, prompt "car", full pipeline on a p150a.

Input (media/source_1.png) CPU prediction (media/target_1.png) TT-NN prediction (media/target_1_ttnn.png)

Results (from the port README)

Per-stage PCC, tt-nn vs the torch reference (random-weight shadow tests):

Stage PCC Notes
patch_embed 0.999 CPU F.conv2d + fast H2D (pre-bf16 + ROW_MAJOR + device tilize)
vit_block (single) 0.99 LN + attention + MLP, bfloat8_b weights, HiFi2
vit_backbone (32 blocks) 0.988 window attention (ws=24) + 4 global blocks
FPN neck (per scale) 0.99 CPU conv, bf16 cached weights
Fusion encoder (6 layers) 0.98 device SDPA + cross-attention + FFN
RoPE exact atol 1e-6 vs complex reference
E2E masks (200 queries) 0.977 full pipeline mask PCC
E2E scores 0.997 classification score PCC
Top mask IoU 0.998 binary mask overlap of the highest-scoring query

Real data, 1920x1080 street scene, prompt "car":

Impl Top score Top mask IoU Pipeline time
Torch CPU reference 0.9762 - 3.20 s
TT-NN on p150a 0.9779 0.9984 0.77 s

Warm throughput at batch 1 (tt_sam3/benchmark.py): ViT backbone 168 ms (5.97 FPS, 16x CPU); full E2E ~330 ms (3.0 FPS, ~4x CPU). Layout: image 1008x1008 -> patch embed (CPU) -> ViT-H/14 [device, bfloat8_b/HiFi2/fused SDPA/RoPE] -> FPN neck (CPU bf16) -> text encoder (CPU, cached per prompt) -> fusion encoder [device] -> decoder (CPU, bf16 autocast) -> segmentation head (CPU) -> 200 query masks (288x288) + scores. Known caveats: batch 1; each prompt re-runs the full forward (no image-feature caching); non-square images are squashed like upstream; the CPU stages dominate on a small-core host (torch threads capped at min(17, cpu_count)).

Licensing

The weights (facebook/sam3) and the upstream model code (sam3 PyPI package) are governed by Meta's SAM License (2025-11-19; not an OSI licence) and the repo is gated - request access from Meta before pulling. The port code under code/tt_sam3 was written by Hyunggi Chang (github.com/changh95/tt-sam3 @ cb728e85) and is published under the same terms, since a port cannot grant more than its upstream does. code/tt_sam3/assets/bpe_simple_vocab_16e6.txt.gz is the CLIP BPE vocabulary from the upstream sam3 repository (MIT-licensed CLIP asset), vendored because the PyPI wheel omits it. Weights are never redistributed by this package; they are fetched from the upstream repo under its own terms.

Provenance

The exact sources the image was built from — code/ in this repo is byte-identical to the model code inside the image:

component built from
tt-metal 8b98410e730bb504fea43a88609756e34821d91d
code/ digest 19f718f9cdbd063a (sha256, first 16 hex digits)
built 2026-09-12T05:52:10+00:00 by tt-model 0.1.0
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for changh95/sam3-p150

Base model

facebook/sam3
Finetuned
(29)
this model