A lightweight, dependency-free C11 implementation of Segment Anything Model 3 (SAM3) built from scratch for efficient inference on Apple Silicon and x86 CPUs.
Inspired by ggml and llama.cpp, sam3.c implements the full SAM3 pipeline — image encoder, prompt encoder, mask decoder — in ~57K lines of portable C with zero Python dependencies.
Four-mask segmentation output from sam3.c — each object highlighted in a distinct color
| sam3.c | Official SAM3 (Python) | |
|---|---|---|
| Language | C11, no dependencies | Python + PyTorch |
| Binary size | Single static binary | GB-scale environment |
| GPU support | Apple Metal (native) | CUDA |
| Precision | FP32, FP16, BF16 | FP32 |
| Memory | Arena allocator, mmap weights | PyTorch allocator |
| Startup | Instant (mmap) | Seconds (model load) |
- Built from scratch in pure C — no PyTorch, no ONNX, no wrappers. Every tensor op, every layer, written by hand.
- Metal GPU backend — hardware-accelerated inference on Apple Silicon (M1/M2/M3/M4).
- Multithreaded CPU backend — optimized SIMD kernels with thread pool for x86 and ARM.
- FP16 and BF16 support — run inference in half precision for lower memory and faster compute.
- Custom
.sam3weight format — mmap-friendly binary format with O(1) tensor lookup via hash table. - Full SAM3 pipeline — image encoder (Hiera, EfficientViT, TinyViT), prompt encoder (points, boxes, masks), mask decoder, text encoder, and tokenizer.
- Video object tracking — memory-based frame-by-frame propagation with point, box, and mask prompts. Supports MPEG video files and frame directories.
- Multiple backbones — Hiera (full accuracy), EfficientViT-B1 (lightweight, 512px), and TinyViT-21M (128x128 masks at 1008px input).
- Unified CLI — single
sam3_clibinary withsegment,convert, andinfosubcommands. Supports stdin/stdout piping, JSON output, and multi-mask color overlays. - 48 unit tests — comprehensive test suite covering numerical operators, memory management, and end-to-end inference.
- Built-in profiling — latency tracing subsystem to identify bottlenecks.
| Backbone | Input Size | Mask Resolution | Parameters | Encode (ms) | Segment (ms) | Use Case |
|---|---|---|---|---|---|---|
| Hiera | 1008x1008 | 288x288 | 1.6B | 2336 | 1224 | Full accuracy |
| TinyViT-21M | 1008x1008 | 128x128 | 0.8B | 487 | 363 | Balanced quality/speed |
| EfficientViT-B1 | 512x512 | 64x64 | 0.8B | 70 | 177 | Fastest, interactive |
Timings on Apple M4 (10-core GPU, Metal backend, Release build). Encode = sam3_set_image, Segment = sam3_segment with a box prompt. See BENCHMARK.md for full results.
All backbones share the same prompt encoder, mask decoder, and text encoder. The backbone is selected automatically based on the checkpoint.
git clone https://github.com/rifkybujana/sam3.c.git
cd sam3.c
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)First build note: the build fetches and statically compiles FFmpeg, openh264, and libvpx into
build/external/. Expect ~10-15 minutes on first configure; subsequent incremental builds are fast. The resulting binary has no runtime dependency on system ffmpeg.
Download a SAM3 checkpoint in SafeTensors format, then convert to the optimized .sam3 format:
# Hiera (default backbone)
./sam3_cli convert -i models/sam3.safetensors -o models/sam3.sam3
# TinyViT or EfficientViT (specify backbone)
./sam3_cli convert -i models/tinyvit.safetensors -o models/tinyvit.sam3 --backbone tinyvit
./sam3_cli convert -i models/evit.safetensors -o models/evit.sam3 --backbone efficientvitSAM 3.1 ships as a PyTorch .pt checkpoint only
(sam3.1_multiplex.pt, ~3.3 GB). Convert in two steps:
# 1. Normalize into .safetensors (unwraps {"model": ...}, remaps
# sam3_model.* -> detector.* and sam2_predictor.* -> tracker.*)
python tools/pt_to_safetensors.py \
models/sam3.1_multiplex.pt \
models/sam3.1_multiplex.safetensors
# 2. Convert to .sam3 with the SAM 3.1 variant flag
./sam3_cli convert \
-i models/sam3.1_multiplex.safetensors \
-o models/sam3.1.sam3 \
--variant sam3.1
# 3. Use it
./sam3_cli segment -m models/sam3.1.sam3 -i img.jpg -t "cat"Only the image-detector path is wired in this release. The SAM 3.1 multiplex tracker and joint multi-object video pass are planned for follow-up work — SAM 3 continues to handle all video tracking today.
# Point prompt (foreground point at x=500, y=375)
./sam3_cli segment -m models/sam3.sam3 -i photo.jpg -p 500,375,1 --overlay
# Text prompt
./sam3_cli segment -m models/sam3.sam3 -i photo.jpg -t "person" --overlay
# Box prompt
./sam3_cli segment -m models/sam3.sam3 -i photo.jpg -b 100,100,400,400 --allTrack an object across frames of a video:
./sam3_cli track --model models/sam3.sam3 --video clip.mp4 \
--point 504,504,1 --frame 0 --output out/Output: out/frame_NNNNN.png binary mask per frame.
./sam3_cli info models/sam3.sam3ctest --output-on-failuresam3.c ships bindings for multiple languages under bindings/:
- Python —
bindings/python/, CFFI-based. Requires Python ≥ 3.9. - Rust —
bindings/rust/. Cargo workspace withsam3-sys(FFI) andsam3(safe API: ownedCtx, typed prompt enum, RAII result cleanup,SegmentResult::nmsmatching the CLI's post-processing). Seebindings/rust/README.md.
Python cache-API gap: the caching API is exposed in the Rust binding but not yet in Python; see
docs/superpowers/plans/2026-04-22-cache-api-bindings.md.
pip install -e bindings/pythonsetup.py runs CMake under the hood: it configures the project with
-DSAM3_SHARED=ON, builds libsam3.{dylib,so} into build-python/,
and copies the shared library into the installed package. No manual
cmake step, no DYLD_LIBRARY_PATH/LD_LIBRARY_PATH setup — the
package ships its own libsam3 next to sam3/_lib.py and loads it by
relative path. First install takes ~10-15 minutes because CMake also
compiles FFmpeg from source; reinstalls are fast.
Requirements:
- Python ≥ 3.9
- CMake ≥ 3.20 and a C11 toolchain on
$PATH cffi>=1.15,numpy>=1.21(installed automatically)- On macOS, Xcode command-line tools; on Linux, a recent GCC/Clang
Minimal usage:
import sam3
with sam3.Model("models/sam3.sam3") as model:
model.set_image("photo.jpg")
result = model.segment(text="person")
print(result.masks.shape, result.iou_scores[:3])Run the test suite:
pip install -e 'bindings/python[dev]'
pytest bindings/python/tests -vTroubleshooting — if import sam3 raises
OSError: libsam3 not found at .../sam3/libsam3.dylib, the package
was installed without the bundled shared library (usually a stale
install from before setup.py was updated). Force a rebuild with:
pip install --force-reinstall --no-deps -e bindings/pythonBuild libsam3 shared once, then build/test the crate against it:
# 1. Build libsam3.{dylib,so}
cmake -S . -B build -DSAM3_SHARED=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# 2. Build + test the Rust workspace (point the loader at build/)
cd bindings/rust
DYLD_LIBRARY_PATH=../../build cargo test --release # macOS
LD_LIBRARY_PATH=../../build cargo test --release # LinuxInstall libsam3 system-wide (cmake --install build) to skip the env
var step; the loader will then find it on the default search path. See
bindings/rust/README.md for the three supported runtime resolution
workflows (env var, system install, rpath-shipped).
Minimal Rust usage:
use sam3::{Ctx, Prompt};
let mut ctx = Ctx::new()?;
ctx.load_model("models/efficient.sam3")?; // auto-loads co-located BPE
ctx.set_image_file("photo.jpg")?;
ctx.set_text("person")?;
let raw = ctx.segment(&[Prompt::Text("person")])?;
let hits = raw.nms(0.5, 0.5, 0.0)?; // 200 candidates → ~N detections
println!("found {} objects, top score {:.3}",
hits.n_masks(), hits.iou_scores()[0]);sam3.c
├── include/sam3/ Public API headers
├── src/
│ ├── core/ Tensor ops, arena allocator, compute graph, weight loader
│ ├── backend/
│ │ ├── cpu/ Multithreaded CPU kernels (SIMD-optimized)
│ │ └── metal/ Apple Metal GPU backend
│ ├── model/ SAM3 layers
│ │ ├── image_encoder Vision transformer (Hiera, EfficientViT, TinyViT)
│ │ ├── prompt_encoder Point, box, and mask prompts
│ │ ├── mask_decoder Lightweight mask prediction head
│ │ ├── text_encoder Text prompt encoding
│ │ └── tokenizer BPE tokenizer
│ └── util/ Logging, error codes
├── tools/ Unified CLI (sam3_cli: segment, convert, info)
└── tests/ 48 test files
On an Apple M4 with the Metal backend, EfficientViT delivers end-to-end image-to-mask in ~250 ms (4 FPS), making interactive point-and-click segmentation practical. Once the image is encoded, each additional prompt on the same image resolves in under 200 ms.
Hiera-Large trades speed for accuracy at 3.6 s per image with 5184 patches and 32 transformer blocks. Multi-prompt workflows amortize the 2.3 s encode cost.
The Metal backend achieves 90.8% of theoretical F32 peak (3086 / 3400 GFLOPS) on matmul microbenchmarks and up to 149x speedup over CPU for F16 workloads.
Full kernel-level and pipeline benchmark results are in BENCHMARK.md.
sam3.c caches encoded image and text features so repeated prompts on the same inputs resolve in microseconds instead of re-running the encoders. The caches are enabled by default with tunable slot counts, an LRU RAM budget, and optional disk spill.
Defaults — override via sam3_cache_opts + sam3_init_ex():
| Tunable | Default | Purpose |
|---|---|---|
n_image_slots |
8 | Max cached image entries |
n_text_slots |
16 | Max cached text entries |
image_mem_budget_bytes |
1 GiB (~4 hot slots at 256 MiB) | RAM ceiling; excess slots spill to disk |
image_spill_dir |
auto-created /tmp dir |
Where spilled bundles live |
Populate the cache while the app is idle so the user's first prompt only pays segment latency:
sam3_precache_image_file(ctx, "photo.jpg"); /* runs image encoder now */
sam3_precache_text(ctx, "person"); /* runs text encoder now */
/* Later: these hit the cache and return in microseconds */
sam3_set_image_file(ctx, "photo.jpg");
sam3_set_text(ctx, "person");Serialize an encoded entry to a .sam3cache file and reload it on the
next startup. Files are model-signature-gated — loading into a different
model is rejected with SAM3_EMODEL:
sam3_cache_save_image(ctx, pixels, w, h, "photo.sam3cache");
/* Next run, after sam3_load_model(): */
sam3_cache_load_image(ctx, "photo.sam3cache");
sam3_set_image(ctx, pixels, w, h); /* cache hit */struct sam3_cache_stats s;
sam3_cache_stats(ctx, &s);
/* s.image_hits, s.image_misses, s.image_evictions, and text_* */
sam3_cache_clear(ctx, SAM3_CACHE_IMAGE | SAM3_CACHE_TEXT);Video tracking has its own two-tier cache for per-frame backbone
features, tuned via sam3_video_start_opts:
frame_cache_backend_budget— resident RAM (default 4 GiB)frame_cache_spill_budget— disk spill (default 16 GiB;SIZE_MAXdisables spill)
See include/sam3/sam3.h for the full cache API.
Model weights use the .sam3 binary format — a compact, mmap-friendly layout designed for instant loading:
- 48-byte header + 176-byte tensor descriptors + page-aligned data blob
- FNV-1a hash table for O(1) tensor lookup by name
- Supports FP32, FP16, BF16, I32, I8, and Q8_0 (block-quantized int8)
- Converted from SafeTensors via
sam3_cli convert
See docs/weight-format.md for the full specification.
MIT — see LICENSE.