depth-anything-3-blackhole
Depth Anything 3, metric branch (DA3Metric-Large: DinoV2-L backbone + DPT head, 0.35 B params) for metric monocular depth in metres, running on a single Tenstorrent Blackhole p150a via tt-nn: the 24-block DinoV2-L backbone on chip in bf16 (HiFi4, fp32 accumulate), patch-embed and the DPT head on the host CPU in bf16. Port-reported 5.23 fps median (7.9x the fp32 CPU baseline), KITTI Eigen AbsRel 0.0930 (canonical 0.0906), Pearson PCC 0.999641 vs the canonical DA3-Metric output. Weights: depth-anything/DA3Metric-Large (Apache-2.0, pinned); port code Apache-2.0, source github.com/changh95/tt-Depth-Anything-3 @ dc5fe3171bdbc4fa1c5896f864d8825553c422ba.
Runs on p150 (mesh P150).
Packaged and published with tt-model-manager 0.1.0 (manifest schema 5.1).
Quickstart
tt-model pull changh95/depth-anything-3-blackhole --with-weights
tt-model serve changh95/depth-anything-3-blackhole
pull --with-weights downloads the Docker image and the depth-anything/DA3Metric-Large weights at 4010e39f3634a45bc60553321fb49fb760bd594e (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/depth-anything-3-blackhole # pulls the image + weights, starts the server
tt model stop changh95/depth-anything-3-blackhole
The server listens on the port serve printed (20000, or the next free one). This is
the model's own FastAPI app, not an OpenAI API: tt-model curl and the
/v1/models hint on the ready card do not apply (/v1/models answers only so they do
not 404). The real routes are GET /health, GET /info, POST /predict.
Call it
PORT=20000
curl -s localhost:$PORT/health # {"status":"ok"|"starting", "model":..., "device":"blackhole:0"}
curl -s localhost:$PORT/info # weights repo+revision, source commit, input/output contract
# one RGB image (base64 PNG/JPEG) -> metric depth. Default: 16-bit PNG at the model's 518x518.
python3 - <<'EOF'
import base64, io, json, urllib.request, numpy as np
from PIL import Image
img = base64.b64encode(open("media/source_1.png", "rb").read()).decode()
req = {"image": img, # required
"output_format": "png", # "png" (default, uint16 PNG) | "npz" (float32, lossless) | "json"
"resize_to_original": False, # True -> depth resized to the input's HxW (bilinear)
"max_depth_m": None, # optional clamp in metres (no sky head: sky can be huge)
"png_scale": 1000.0} # png only: uint16 = round(depth_m * scale); 1000 = mm
r = json.load(urllib.request.urlopen(urllib.request.Request(
"http://127.0.0.1:20000/predict", json.dumps(req).encode(), {"Content-Type": "application/json"})))
u16 = np.array(Image.open(io.BytesIO(base64.b64decode(r["depth"]["data"]))))
depth_m = u16.astype(np.float32) / r["depth"]["scale"]
print(r["shape"], r["original_size"], r["stats_m"], r["timing_ms"])
EOF
Response fields: depth (format png16|npz|json, data base64, plus scale /
saturation_m / saturated_pixels for png16 or keys/dtype for npz), shape of
the returned map, original_size [H, W] of the input, model_input_size [518, 518],
resized_to_original, units: "metres", stats_m (min/p05/median/p95/max),
timing_ms (preprocess / infer / postprocess / total). Errors: 400 bad image, 503 while
starting, 500 with the exception text.
Input handling: the image is squashed (no letterbox) to 518x518, ImageNet-normalised,
exactly as the port's KITTI evaluation was run; one image per request, batch 1.
depth = exp(raw head output) in metres; the port has no sky head, so far-sky pixels
can exceed 80 m -- pass max_depth_m (the KITTI eval caps at 80) or use the float npz
output. The 16-bit PNG saturates at 65535 / png_scale metres (65.5 m at the default
1000; use png_scale: 256 for the KITTI GT convention, 256 m).
First boot
The 1.34 GB model.safetensors is downloaded into your HF cache before the container
starts. On the first start the 24 DinoV2 blocks are JIT-compiled (170 kernels, several
minutes); the kernel cache persists under `/.cache/tt-model/depth-anything-3-blackhole/cacheso later starts take well under a minute. The server logsLoading weights, Warming upandWarmup complete; it is ready when uvicorn prints Application startup complete. Steady state is ~190 ms per image on the port author's box (~75 ms on chip, ~110 ms for the CPU DPT head, which wants AVX512_BF16 and physical cores; DA3_TORCH_THREADS` overrides
torch's thread count).
Smoke test (from this repo, after serve):
python code/models/server/smoke_test.py --url http://127.0.0.1:20000
Results reported by the port (KITTI Eigen test split)
| Metric | CPU baseline (fp32) | Chip pipeline (bf16) |
|---|---|---|
| Throughput | 0.66 fps | 5.23 fps (median, 7.9x) |
| KITTI Eigen AbsRel | 0.0906 | 0.0930 |
| KITTI Eigen RMSE (m) | 3.156 | 3.127 |
| KITTI Eigen delta<1.25 | 95.91% | 95.88% |
| Pearson PCC vs canonical DA3-Metric | 1.000000 | 0.999641 |
Three KITTI Eigen test images through the chip pipeline (turbo colormap, log-clipped 1-50 m; bright = near, dark = far):
What is in code/
models/experimental/depth_anything_v3/ -- reference/dinov2_l_dpt.py (pure-torch
DA3-Metric, loads the safetensors via HF_MODEL / TT_WEIGHTS_REVISION or
DA3_WEIGHTS_DIR), tt/ttnn_da3_metric.py (24-block backbone on chip, bf16 channels-last
CPU head; owns the device), eval/ (KITTI Eigen loader, metrics, canonical comparison;
DA3_EVAL_DATA points at the KITTI download), tests/ (fps/PCC benchmark and KITTI eval,
pytest). models/server/app.py is the FastAPI app above; models/server/smoke_test.py
the smoke test. TODO.md / results.tsv are the port's iteration log. The eval/test
harness (KITTI data, opencv-python, the canonical depth_anything_3 package) is not
part of the serving image's dependency set.
Licensing
Port code: Apache-2.0 (github.com/changh95/tt-Depth-Anything-3 @ dc5fe31). Weights: depth-anything/DA3Metric-Large, released by ByteDance under Apache-2.0 (the nested DA3NESTED-GIANT-LARGE-1.1 checkpoint, which is CC BY-NC 4.0, is no longer used). Upstream model: ByteDance-Seed/Depth-Anything-3. Weights are never baked into the image; they are fetched into your own HF cache.
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 |
049807d21fe970d4 (sha256, first 16 hex digits) |
| built | 2026-09-12T04:52:43+00:00 by tt-model 0.1.0 |





