Run Meta's SAM 3 on macOS (Apple Silicon MPS or CPU). No triton, no CUDA, no fork.
SAM 3 is published as code + weights, but the code assumes CUDA throughout — hardcoded .cuda() calls, NVIDIA-only triton kernels in the import path, bfloat16-locked fused ops, and a couple of honest bugs. On a Mac you can't even import sam3 without errors. sam3-mac is a small shim that monkey-patches around those issues so image inference Just Works on M-series Macs (and any other non-CUDA host).
import sam3_mac
sam3_mac.install() # auto-picks mps / cpu
model, processor = sam3_mac.build_image_model(confidence_threshold=0.5)
from PIL import Image
state = processor.set_image(Image.open("car.jpg"))
out = processor.set_text_prompt("damage on car", state)
print(out["boxes"].shape, out["scores"], out["masks"].shape)- Prerequisites
- Installation
- Getting access to SAM 3 weights
- Quick start (full working example)
- API reference
- Inputs and outputs
- What this library patches and why
- Performance
- Troubleshooting
- Limitations / what's not supported
- License
- macOS (any recent version). Tested on macOS 15 on Apple M4 Pro.
- Python 3.10 or newer. SAM 3 itself officially requires 3.12, so 3.12 is the safe target. Homebrew install:
brew install [email protected]. - About 5 GB free disk for the model weights (cached to
~/.cache/huggingface/on first run). - A HuggingFace account approved for access to the gated
facebook/sam3repo — see below.
This library does not require CUDA, Xcode command-line tools (beyond Python itself), or any GPU driver — Apple Silicon's MPS backend is built into PyTorch and works out of the box.
# 1. Create a virtual environment (recommended)
python3.12 -m venv .venv
source .venv/bin/activate
# 2. Install PyTorch (MPS-enabled wheels come from PyPI by default on macOS)
pip install "torch>=2.7" torchvision
# 3. Install SAM 3 from source + its runtime deps
pip install "git+https://github.com/facebookresearch/sam3.git"
pip install einops opencv-python-headless scikit-image pycocotools pandas psutil "numpy<2"
# 4. Install this library
pip install sam3-mac
# or, from a clone:
# pip install -e .Why so many transitive deps? SAM 3's
pyproject.tomllists some runtime dependencies (e.g.einops,pycocotools,psutil) under "notebook" / "training" extras even thoughimport sam3actually requires them.sam3-macdoesn't bundle these so you can pin them yourself, but the list above is what you need at minimum.
The model weights (~3.5 GB) live on HuggingFace at facebook/sam3 and are gated — Meta has to approve your account before download works. One-time setup:
-
Sign in (or create a free account) at https://huggingface.co.
-
Go to https://huggingface.co/facebook/sam3 and click Request access. Approval typically comes through within minutes.
-
Create a read-only access token: https://huggingface.co/settings/tokens.
-
Authenticate your machine:
pip install -U huggingface_hub hf auth login # paste your token; answer "N" to the git-credential question
The first inference downloads the weights into ~/.cache/huggingface/ and reuses them after that.
A complete script that loads SAM 3 and runs damage detection on an image:
# basic.py
import time
import sam3_mac
from PIL import Image
# 1. Patch SAM 3 so it works on Mac. Call BEFORE `import sam3`.
device = sam3_mac.install(verbose=True) # auto-picks mps / cpu
print(f"using device: {device}")
# 2. Build the model + processor. This downloads weights on first run.
t0 = time.perf_counter()
model, processor = sam3_mac.build_image_model(confidence_threshold=0.5)
print(f"model loaded in {time.perf_counter() - t0:.1f}s")
# 3. Run inference on an image with a text prompt.
image = Image.open("car.jpg").convert("RGB")
state = processor.set_image(image)
out = processor.set_text_prompt("damage on car", state)
# 4. Inspect results.
boxes = out["boxes"].detach().cpu().float().numpy() # (N, 4) — [x1, y1, x2, y2] in pixels
scores = out["scores"].detach().cpu().float().numpy() # (N,)
masks = out["masks"].detach().cpu().bool().numpy() # (N, H, W) — pixel mask per detection
print(f"{boxes.shape[0]} detections")
for i in range(boxes.shape[0]):
x1, y1, x2, y2 = (int(round(v)) for v in boxes[i].tolist())
print(f" #{i}: score={scores[i]:.2f} bbox=[{x1},{y1},{x2},{y2}] mask_pixels={int(masks[i].sum())}")Run it:
python basic.pyExpected output (on a damaged-car photo):
using device: cpu
model loaded in 6.2s
2 detections
#0: score=0.61 bbox=[412,341,1383,1359] mask_pixels=430877
#1: score=0.52 bbox=[493,210,1356,820] mask_pixels=181403
A working version is included as examples/basic.py — call it with python examples/basic.py path/to/image.jpg "damage on car".
sam3_mac.install(device: str | None = None, verbose: bool = False) -> strApply all patches. Call before import sam3 (or before build_image_model(), which imports sam3 internally).
device— one of"mps","cpu","cuda", orNoneto auto-detect (cuda > mps > cpu).verbose— log each patch as it's applied.- Returns the device that was set up. If the requested device isn't available, falls back to
"cpu"with a warning. On a CUDA host this is a no-op (SAM 3 runs natively). - Idempotent — repeated calls return the original device without re-patching.
sam3_mac.build_image_model(confidence_threshold: float = 0.5) -> (model, processor)Convenience builder. Returns a (model, Sam3Processor) pair on the device install() set up, with the required float32 cast already applied (see Patches #5).
- Equivalent to
model = build_sam3_image_model(device=...).float().to(device); processor = Sam3Processor(model, ...). - Requires
install()to have been called first.
sam3_mac.get_device() -> str | NoneThe device install() set up, or None if not called yet.
processor.set_image(image, state=None) -> dict
imageaccepts:PIL.Image.Image(recommended)np.ndarrayof shape(H, W, 3), dtype uint8, RGB. This library patches the upstream numpy-shape bug, so passing ndarray is safe.
- Returns an opaque state dict you pass to
set_text_prompt.
processor.set_text_prompt(prompt: str, state: dict) -> dict
promptis a short noun phrase. Short, concrete phrases ("scratch","dent","broken headlight","damage on car") work better than long sentences.- Returns the same dict mutated with detection results:
"boxes"—torch.Tensorof shape(N, 4),[x1, y1, x2, y2]in pixel coords of the original image."scores"—torch.Tensorof shape(N,), confidence in[0, 1]."masks"—torch.Tensorof shape(N, H, W), boolean per-pixel masks at original image resolution. (Without this library, masks would be(N, 1, H, W)— patch #5 squeezes the redundant dim.)"masks_logits"— same shape asmasksbutfloat32post-sigmoid probabilities, if you want to threshold differently.
Call set_text_prompt multiple times with the same state to run multiple prompts against the same image without re-encoding.
Six patches, each load-bearing. Without each one, SAM 3 fails on Mac with a specific error:
| # | Patch | Without it... |
|---|---|---|
| 1 | Stub sam3.model.edt so import sam3 doesn't pull in triton |
ModuleNotFoundError: No module named 'triton' at import sam3. Triton has no Mac wheels. |
| 2 | Redirect torch.zeros(device="cuda") and .cuda() calls to target device |
AssertionError: Torch not compiled with CUDA enabled during build_sam3_image_model(). SAM 3 hardcodes "cuda" in ~20 spots. |
| 3 | Replace sam3.perflib.fused.addmm_act with an unfused, dtype-preserving implementation |
MPS: NotImplementedError: aten::_addmm_activation.out. CPU: mat1 and mat2 must have the same dtype, but got BFloat16 and Float. The fused op hardcodes bf16 inputs. |
| 4 | Coerce numpy input → PIL in Sam3Processor.set_image (upstream bug) |
All predicted x-coordinates come back scaled by 3. SAM 3 reads image.shape[-2:] for ndarray, but np.array(pil) is (H, W, C), so width=3 (channels). |
| 5 | Squeeze (N, 1, H, W) masks → (N, H, W) in set_text_prompt |
Per-mask iteration gives (1, H, W), breaking downstream shape checks. |
| 6 | PYTORCH_ENABLE_MPS_FALLBACK=1 (if unset) |
NotImplementedError on a few ops MPS hasn't implemented yet (aten::_assert_async etc.). |
Plus one non-patch behavior: the SAM 3 checkpoint mixes bf16 and float32 parameters across submodules. CUDA hides this with torch.autocast; off CUDA you'd get matmul dtype mismatches. sam3_mac.build_image_model() applies a model.float() after construction so you don't have to remember.
On an M4 Pro, image-only inference (SAM 3 internally resizes to 1008×1008) is roughly:
| Device | Time per inference (warm) |
|---|---|
| CPU | ~3 s |
| MPS | ~7–8 s |
MPS is currently slower than CPU because the MPS-fallback path ping-pongs unsupported ops to CPU and back. As PyTorch's MPS backend grows op coverage this should flip. For now, device="cpu" is the recommended default on Mac.
Model load + first inference takes ~10–15 s due to weight load and PyTorch kernel warmup; subsequent inferences are stable at the numbers above.
huggingface_hub.errors.GatedRepoError: Cannot access gated repo
Your HuggingFace access request to facebook/sam3 hasn't been approved yet, OR your CLI isn't authenticated. Visit https://huggingface.co/facebook/sam3, click "Request access", and run hf auth login once approved.
ModuleNotFoundError: No module named 'einops' (or pycocotools, psutil, ...)
SAM 3 lists these as "notebook/training" extras but actually needs them at runtime. Install:
pip install einops opencv-python-headless scikit-image pycocotools pandas psutil "numpy<2"ModuleNotFoundError: No module named 'triton'
You imported sam3 before calling sam3_mac.install(). Order matters: install first, then import.
import sam3_mac
sam3_mac.install()
import sam3 # only after install()RuntimeError: failed assertion 'Destination NDArray and Accumulator NDArray cannot have different datatype'
You're on MPS and the addmm_act patch didn't apply. Make sure sam3_mac.install(device="mps") was called before constructing the model. If it was, fall back to device="cpu" — that's tested and reliable.
Predicted boxes all have width 2–3 pixels at the left edge of the image
The numpy-shape bug (patch #4) didn't apply. Either you bypassed sam3_mac.install(), or you called set_image with a torch.Tensor (not patched — pass PIL or numpy ndarray).
First inference takes 60+ seconds That's normal on first run — PyTorch is compiling MPS kernels and loading the 3.5 GB model from disk. Subsequent inferences are fast.
- Video tracking /
Sam3VideoPredictor: pulls in the tracker code path that legitimately needs triton (Euclidean distance transform kernels). The triton stub raises a clear error if hit. Image inference is all this library targets. - Training: not tested, not patched.
- Custom CUDA op kernels beyond what's in upstream SAM 3.
- Mixing CUDA and non-CUDA in the same process:
install()modifiestorch.*globally inside the process. Don't run this in a process that also expects native.cuda()behavior. - Pinned to SAM 3 as of early 2026. If upstream restructures internals, patch #3 (which inspects
sys.modulesfor consumers ofaddmm_act) may need updating.
MIT. See LICENSE.