Serving GR00T N1.5-3B (nvidia/GR00T-N1.5-3B) on Blackhole with tt-model-manager
This repo is a tt-model container package source: tt-model.yaml + the code under code/. tt-model package --container builds the OCI image (tt-metal 668c2907575 built from source + the port + the HTTP stack),
tt-model serve runs it against the card, tt-model push publishes it to this HF repo. The server is
code/gr00t_p150/server/app.py (FastAPI, kind: tt-dit-server); what it runs on the device is the port's Stage-1
path β every op a TTNN op, captured once into four Metal traces (vision, llm, adapter, denoise) and replayed per
request; the persistent-megakernel denoise (Stage 2) is not part of this release.
| tt-metal tree the image is built from | /home/deepgadget/experiments/gr00t/publish/tt-metal-668c2907575 β a clean local clone of tt-metal main @ 668c290757550588d0ce46b180c344a462a2aaf5 (v0.79.0-dev20260914-1), submodules populated, git status empty (the working clone /home/deepgadget/experiments/gr00t/tt-metal at the same commit has untracked dirs and would be recorded as dirty); torch pin 2.11.0 |
| port code shipped | code/models/experimental/gr00t/ = tt-metal ref gr00t-p150-snapshot, commit 5dc0517ee3bec090b7ead025001a343c2f63aa7b (parent 668c290; frozen 2026-09-14T23:36 KST), minus __pycache__, .omc tool state and 2,001 of the 2,004 per-test result JSONs (the three N1.5 e2e results the card cites and the d0/d1/d2/mk_k1 summaries stay) |
| weights | nvidia/GR00T-N1.5-3B @ 869830fc749c35f34771aa5209f923ac57e4564e: model-0000{1,2,3}-of-00003.safetensors (5,448,327,040 B), model.safetensors.index.json, config.json, experiment_cfg/metadata.json, LICENSE β a pinned pointer, never in the image |
| app | gr00t_p150.server.app:app (uvicorn --lifespan on; the lifespan does weights β tokenizer β device β model β trace capture β warm-up β READY) |
| device recipe | models.experimental.gr00t.tt.device.open_gr00t_device(trace_region_size=64 MiB, l1_small_size=32768, device_id=0); asserts the p150a's 11Γ10 compute grid and 8 DRAM banks; TTPolicy(dtype_policy="mixed_dit", trace_layout="per_stage") |
| serving shape | embodiment gr1 (slot 24), layout gr1: 1 camera (ego_view) Γ 1 frame Γ 256 image tokens, β€ 102 text tokens, LLM sequence padded to 384, state 7/7/6/6 β 16 Γ [7, 7, 6, 6] actions, batch 1 |
Below, $PUB=/home/deepgadget/experiments/gr00t/publish (this repo is $PUB/GR00T-N1.5-3B-p150),
$TREE=/home/deepgadget/experiments/gr00t/tt-metal (the built working clone, Python 3.10 venv),
$ROOT=/home/deepgadget/experiments/tt-models (tt-model-manager tooling).
1. Layout
tt-model.yaml authoring manifest (schema 5.1) -- the whole build/serve interface, incl. the model card text
SERVING.md this file
GPU_COMPARISON.md RTX 5090 vs p150a, matching definitions and caveats
LICENSE the NVIDIA License (weights); LICENSE-NOTICE.md says what is under which licence
media/demo_ego_view.png the demo frame (256x256, GR1 sim robot_sim.PickNPlace traj 0 / step 100)
media/demo_observation.json the matching raw joint state + instruction (request body minus the image)
media/demo_actions.png served actions vs the fp32 reference for that request
code/models/experimental/gr00t/ the tt-nn port (tt-metal tree layout): common/ (host pipeline: preprocessing, prompts,
normalisation, checkpoint, weight plan), tt/ (device model, layers, traces, megakernel WIP),
reference/ (fp32 torch reference), tests/, benchmarks/
code/models/{common,tt_dit,demos}/ the 16 tt-metal files the port imports from the tree (source.code in tt-model.yaml)
code/gr00t_p150/ the policy server: server/{app,schemas,smoke_test}.py, demo/n15/ (canonical demo
observation + golden actions + noise), assets/tokenizer/n15/ (vendored Qwen2 tokenizer)
code/scripts/ download_weights.sh, bench_http.py
requirements.lock the image venv (Python 3.12) as `uv pip freeze` -- written by `tt-model package`
tt_kernel_manifest.json, image/ written by `tt-model package`; uploaded by `tt-model push`
The port keeps its tt-metal-tree absolute imports (models.experimental.gr00t.*, models.tt_dit.*,
models.common.*): in the image the code lands at /opt/tt-metal/<path> with PYTHONPATH=/opt/tt-metal, and the
image's tt-metal copy excludes models/, so everything under models/ comes from this repo's code/ (the port) and
source.code (the 16 tree files). The server package gr00t_p150 is shared with changh95/GR00T-N1.6-3B-p150; this
repo ships only the N1.5 assets (demo/n15, assets/tokenizer/n15).
2. Run on the HOST for validation (no Docker)
The tree venv ($TREE/python_env, Python 3.10, torch 2.11.0+cpu, ttnn editable) has everything except
fastapi/uvicorn. Do not install into the tree venv; put the HTTP stack in a side directory and prepend it to
PYTHONPATH ($PUB/http-side already holds fastapi 0.141.1 / uvicorn 0.53.0 / pydantic 2.13.5 / starlette 1.6.0):
export PATH=$HOME/.local/bin:$PATH
mkdir -p $PUB/http-side && uv pip install --python $TREE/python_env/bin/python --target $PUB/http-side fastapi uvicorn "pydantic>=2"
cd $PUB/GR00T-N1.5-3B-p150
export TT_METAL_HOME=$TREE ARCH_NAME=blackhole
export PYTHONPATH=$PWD/code:$TREE:$TREE/ttnn:$PUB/http-side # code/ FIRST so models.experimental.gr00t is the snapshot
export TT_METAL_CACHE=$HOME/.cache/tt-metal-cache-gr00t # warm JIT kernels of the port's device sessions
export HF_MODEL=nvidia/GR00T-N1.5-3B TT_WEIGHTS_REVISION=869830fc749c35f34771aa5209f923ac57e4564e
export GR00T_VERSION=n15 TT_MESH_SHAPE=1x1 TT_DEVICE_ID=0 TT_METAL_VISIBLE_DEVICES=0
export GR00T_TOKENIZER_DIR_N15=$PWD/code/gr00t_p150/assets/tokenizer/n15 # optional: the server picks the vendored dir itself
export GR00T_TT_CACHE=$HOME/.cache/gr00t-tt # host .pt plan tier + .tensorbin device tier (~6.4 GB for N1.5)
# import check, no device (what the image's verify.sh does):
$TREE/python_env/bin/python -c "import gr00t_p150.server.app as a, sys; assert a.app and 'ttnn' not in sys.modules; print('ok')"
# serve + smoke + stop -- ONE device-lock hold (the box has one p150a shared with other sessions):
DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh bash -c '
'$TREE'/python_env/bin/python -m uvicorn --host 127.0.0.1 --port 20010 --lifespan on gr00t_p150.server.app:app & UV=$!
python3 code/gr00t_p150/server/smoke_test.py --url http://127.0.0.1:20010 --wait 1800 --out /tmp/gr00t-n15-smoke.json; RC=$?
kill -TERM $UV; wait $UV; exit $RC'
Boot log landmarks (they drive tt-model serve's checklist): Loading weights: nvidia/GR00T-N1.5-3B @ 869830fcβ¦ β
Tokenizer files: β¦ β Opening device 0 (trace_region_size=67108864, l1_small_size=32768) β Loading pipeline: Gr00tTT.from_pretrained(n15, β¦) β Model built in N s (weights N s, 778 tensors, 3176 MB on device, cache path warm|cold) β Warming up: capture per_stage traces on the n15 demo observation, then N traced predict(s) β
Warmup k/N: β¦ ms (encode, device, decode) β Warm-up fidelity vs golden fp32: actions PCC β¦ β Warmup complete (capture s, first ms, steady ms; boot s) -- per_stage traces ['vision', 'llm', 'adapter', 'denoise'] β uvicorn
Application startup complete. Any failure raises and uvicorn exits non-zero (no CPU fallback); the boot also fails
if the warm-up actions' PCC vs the shipped golden is below GR00T_WARMUP_PCC_MIN (0.999). SIGTERM / Ctrl-C:
Releasing traces and device tensors β Closing device.
Measured host boots (warm ~/.cache/gr00t-tt and warm TT_METAL_CACHE; $PUB/logs/server_n15_*.log): first boot of a
process 14.6 s to READY (model built 9.3 s incl. 9.2 s reading the .tensorbin tier, warm + capture 2.5 s, 5 traced
warm-ups 44.4β44.8 ms); an immediately following boot 4.5 s (0.6 s model build with the tiers in the page cache); on the
frozen snapshot worktree 5.2 s (server_n15_snapshot_20260914-235713.log). Kernels are rebuilt once for a new
TT_METAL_HOME path (~42 s for the 355 kernels of the four traces, docs/publish/snapshot.md Β§2.3).
Expected smoke line (host, snapshot worktree, $PUB/logs/smoke_n15_snapshot_20260914-235713.log):
PASS GR00T-N1.5-3B-p150 n15: PCC(actions)=0.999982 max|d|=0.0289 PCC(action_pred_valid)=0.999976 repeat_maxdiff=0.0e+00
seed_path_ok device_ms=42.95/42.16 total_ms=47.22/44.83 wall_ms=50
per_group[left_arm=0.99995/0.0214 right_arm=0.99997/0.0172 left_hand=0.94335/0.0179 right_hand=0.99999/0.0289]
The per-group PCC / max|d| equal the port's own device test (code/models/experimental/gr00t/tests/tt/results/ test_e2e_predict_actions_vs_golden_n15_20260914-230743.json) to the printed precision; the traced policy is
deterministic, so every run reproduces them bit-exactly.
Offline overrides: GR00T_WEIGHTS_DIR=<dir with the snapshot files> (shimmed into a private hub cache so the port's
snapshot_dir() resolves to it), GR00T_TOKENIZER_DIR=<dir>.
Offline checks that need no device:
cd $PUB/GR00T-N1.5-3B-p150
# manifest + launcher preview (from the repo dir -- `root: code` is CWD-relative); must print VALID
$ROOT/.venv/bin/python -c "from tt_kernel.container_manifest import load_container_manifest; m = load_container_manifest('tt-model.yaml', check_sources=True); p = m.resolve_profile(); print('VALID', m.name, m.kind, p.hardware, p.mesh_device, m.weights_ref)"
# Python 3.12 resolution of runtime.packages (what the image does) -- must keep numpy<2 and torch 2.11.0+cpu
uv venv --python 3.12 $PUB/depcheck-n15 -q
uv pip install --python $PUB/depcheck-n15/bin/python --dry-run torch==2.11.0 fastapi uvicorn "pydantic>=2" pillow "numpy>=1.24.4,<2" \
safetensors huggingface_hub "transformers==5.12.1" "opencv-python-headless==4.8.1.78" "torchvision==0.26.0" pytest \
--extra-index-url https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match
# -> torch 2.11.0+cpu, torchvision 0.26.0+cpu, numpy 1.26.4, transformers 5.12.1, opencv-python-headless 4.8.1.78 (cp312 wheel), pytest 9.1.1 (logs/uv-dryrun-n15.log)
3. Package, serve, push (Docker)
Rootless Docker on this box needs source $ROOT/bin/docker-env.sh first (PATH + DOCKER_HOST; the bare docker
on PATH is podman). Run every tt-model command from this directory: source.tt_metal and
extra_code[].root: code resolve against the process CWD. --out points outside any git checkout because stage()
deletes <out>/gr00t-n1.5-3b-p150 before rebuilding. The manifest name is the slug gr00t-n1.5-3b-p150, so that is
the build directory, the cache directory (~/.cache/tt-model/gr00t-n1.5-3b-p150/) and the generated card's title.
source $ROOT/bin/docker-env.sh
cd $PUB/GR00T-N1.5-3B-p150
$ROOT/.venv/bin/tt-model package --container tt-model.yaml --out $PUB/build # tt-metal C++ build (ccache) + venv + verify.sh
# device: serve -> smoke -> bench -> stop inside ONE lock hold (`serve` returns after READY and leaves the container
# running on the card; a second with-device.sh caller would probe a busy card and reset it)
DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh bash -c '
source '$ROOT'/bin/docker-env.sh
'$ROOT'/.venv/bin/tt-model serve '$PUB'/build/gr00t-n1.5-3b-p150/tt_kernel_manifest.json 2>&1 | tee '$PUB'/logs/serve-n15.log || exit 1
PORT=$(grep -o "127.0.0.1:[0-9]*" '$PUB'/logs/serve-n15.log | head -1 | cut -d: -f2)
python3 code/gr00t_p150/server/smoke_test.py --url http://127.0.0.1:$PORT --out '$PUB'/logs/smoke-n15.json; RC=$?
python3 code/scripts/bench_http.py --url http://127.0.0.1:$PORT --reps 50 --warmup 10 --out '$PUB'/logs/bench-http-n15.json
'$ROOT'/.venv/bin/tt-model logs changh95/GR00T-N1.5-3B-p150 > '$PUB'/logs/container-n15.log 2>&1
'$ROOT'/.venv/bin/tt-model stop changh95/GR00T-N1.5-3B-p150; exit $RC'
$ROOT/.venv/bin/tt-model push $PUB/build/gr00t-n1.5-3b-p150 --publish
serve pre-downloads the pinned files (a cache hit on this host), then docker run --user 0:0 --device /dev/tenstorrent --ipc host with /dev/hugepages-1G, ~/.cache/huggingface at /hf (rw, HF_HOME=/hf) and
~/.cache/tt-model/gr00t-n1.5-3b-p150/{cache,weights,tensors} at /cache (TT_METAL_CACHE, JIT kernels),
/weight-cache (TT_DIT_CACHE_DIR; the port's weight tiers go to /weight-cache/gr00t-tt, ~6.4 GB) and
/tensor-cache; it exports exactly HF_MODEL=nvidia/GR00T-N1.5-3B, MESH_DEVICE=P150, TT_MESH_SHAPE=1x1 plus
serve.env from tt-model.yaml, publishes the first free port from 20000 and waits β€ 1800 s for Application startup complete. tt-cli users: tt serve changh95/GR00T-N1.5-3B-p150 / tt model stop changh95/GR00T-N1.5-3B-p150
(after the push).
push makes code/ and image/ on the Hub exactly the staged trees and replaces README.md with the generated card
(everything worth keeping lives in card.description / card.quickstart of tt-model.yaml); media/, SERVING.md,
GPU_COMPARISON.md, LICENSE, LICENSE-NOTICE.md, tt-model.yaml and .gitattributes at the repo root survive; the
license / pipeline_tag / base_model front matter is restored afterwards with huggingface_hub.metadata_update.
4. Container validation (the image built from this manifest)
Image tt-model/gr00t-n1.5-3b-p150:443501bd3b7a (sha256:443501bd3b7afce579bf743eeedd64809886653c29a9d7125614e8451cd61a7b,
3.2 GB as 46 OCI blobs), built 2026-09-14 15:17β15:30 UTC from the manifest above (tt-model package: tt-metal C++ build
with a warm ccache β 7 min, Python 3.12 venv, all 17 verify: lines + the launcher's own passed inside the image;
13 min 17 s in total). Validated on the p150a on 2026-09-15 00:56β01:02 KST: three tt-model serve boots of this exact
image, each followed by smoke_test.py, a 50-request bench_http.py and tt-model stop, all inside one device-lock hold
per run (publish/logs/validate-n15.run2.log = boots 1β2, validate-n15.run3.log = boot 3; per-boot files
{serve,smoke,bench-http,container,info}-n15.{2,3}.*; boot 1's per-file artefacts were overwritten by boot 3 and survive
only inside validate-n15.run2.log).
| boot | caches (~/.cache/tt-model/gr00t-n1.5-3b-p150/) |
host load | tt-model serve β READY |
container boot (lifespan) | smoke | bench_http.py (10 warm-up + 50): device / server-side median (p90) |
tt-model stop |
|---|---|---|---|---|---|---|---|
| 1 (cold) | weights/ and cache/ empty before; 6.4 GB + 411 MB after |
two concurrent tt-metal image builds + CPU tests (load avg 16β110) | 1 min 43 s (103.6 s wall) | 100.9 s: device open β 4 s β Model built in 8.9 s (β¦ cache path cold) (checkpoint β host plan β .tensorbin) β trace capture 87.3 s incl. the JIT of the four traces' kernels (riscv-tt-elf-g++ lines in the container log) β 3 warm-ups 50.9 / 45.8 / 101.5 ms |
PASS β PCC(actions) 0.999982, max|d| 0.0289; PCC(action_pred_valid) 0.999976; device 46.96 / 42.21 ms, total 54.42 / 48.21 ms |
44.27 (55.35) / 53.59 (91.12) ms; encode median 3.2, p90 41 ms; client wall 58.1 (94.5) | clean SIGTERM shutdown 4.9 s |
| 2 (warm, immediately after) | reused | same loaded host | 20.2 s (21.3 s wall) | 15.1 s: Model built in 0.6 s (β¦ cache path warm), capture 6.6 s, warm-ups 47.3 / 119.4 / 65.3 ms (encode spikes = host contention) |
PASS β same fidelity; device 50.88 / 42.59 ms, total 67.44 / 45.45 ms | 44.51 (55.76) / 58.14 (96.86) ms; encode median 5.8, p90 38 ms; client wall 64.4 (104.2) | clean 3.1 s |
| 3 (warm, quiet host) | reused | load avg β 9, no other device or build work | 9.7 s (10.0 s wall) | 7.6 s: model 0.6 s, capture 3.2 s, warm-ups 44.5 β 44.3 ms | PASS β same fidelity; device 42.99 / 42.19 ms, total 47.31 / 44.95 ms, client wall 50 ms | 42.43 (43.18) / 45.40 (49.20) ms, min 41.66 / 44.46; decode 1.06, encode 1.63, decode_actions 0.14; client wall 47.6 (51.8) | clean 2.0 s |
Fidelity is identical in every boot and identical to the host runs of Β§2 β the served actions JSON of the container is
byte-for-byte the host run's (smoke-n15.3.json vs smoke_n15_snapshot_20260914-235713.json) β and equal to the
port's device test tests/tt/results/test_e2e_predict_actions_vs_golden_n15_20260914-230743.json to the printed precision:
left_arm 0.99995 / 0.0214, right_arm 0.99997 / 0.0172, left_hand 0.94335 / 0.0179 (abs-gated), right_hand
0.99999 / 0.0289. The boot-3 benchmark is the card's "served" row; the device median 42.4 ms is 0.7 ms above the port's
own bench_e2e device figure (41.68 ms: upload + traces + readback) and the loaded-host runs show that the extra
latency and the p90 tails sit in the host-side stages (encode), not on the device.
GET /info inside the container reports weights.snapshot_dir = /hf/hub/models--nvidia--GR00T-N1.5-3B/snapshots/869830fcβ¦
(the mounted HF cache), source.tt_metal = {commit 668c290β¦, ttnn_dist_version 0.65.2.dev10011},
source.port_snapshot_commit = 5dc0517ee3bβ¦, device_facts grid 11Γ10 / DRAM 8Γ1 (info-n15.3.json).
Device-sharing note for this host. The first validation attempt (00:32β00:40 KST) ran while another session's
container was still holding the card after that session's lock hold had ended; bin/with-device.sh's probe hung and
reset the card twice, then refused to run. Since then every device entry goes through
publish/scripts/run_validation_n15_when_free.sh, which waits until docker ps shows no tt-model-* container and the
lock is free. Use the manifest path with tt-model stop / logs for an un-pushed package (tt-model stop changh95/GR00T-N1.5-3B-p150 only works after pull).
5. Request / response contract
| route | returns |
|---|---|
GET /health |
{"status": "ok" | "starting", "model": "GR00T-N1.5-3B-p150", "device": "blackhole:0"} β 200 always; ok only after the warm-up |
GET /info |
model / version / task / hardware / stage (stage1-ttnn-traces + megakernel note) / weights (repo, revision, snapshot dir actually loaded) / source (port snapshot commit, server version, tt-metal commit, ttnn dist version) / inputs (the full observation contract) / outputs / noise.default_seed / limits / device_facts (11Γ10 grid, 8 DRAM banks) / warmup_latency_ms / warmup_fidelity_vs_golden / license |
GET /v1/models |
OpenAI-shaped stub {"object": "list", "data": [{"id": "nvidia/GR00T-N1.5-3B", "object": "model", "owned_by": "changh95"}]} so the tt-model ready card / tt-model curl do not 404; this is not a chat API |
GET /demo |
{"request": <ready-to-post demo body>, "expected": <fp32 reference actions + gates>} |
POST /predict |
one observation β one 16-step action chunk (below) |
POST /predict request (JSON; unknown fields β 422):
| field | type | meaning |
|---|---|---|
images |
{"ego_view": <b64 PNG/JPEG>} (or a one-element list per camera) |
exactly the GR1 camera; any resolution with sides in [64, 4096] px; the server applies the checkpoint's eval chain (x/255 β 0.95 centre crop β bilinear antialiased 224Γ224 β Eagle2.5 normalisation), bit-exact vs the reference preprocessing |
state |
{"left_arm": [[7]], "right_arm": [[7]], "left_hand": [[6]], "right_hand": [[6]]} |
raw physical joint state, one time step ((D,) or (1, D)), finite floats; sin/cos-encoded and padded to 64 dims on the host |
instruction |
str (aliases language, prompt) |
task text, 1β2000 chars, β€ 102 BPE tokens after the port's prompt build (repr([instruction]) inside the Eagle2 chat prompt); longer β 400, never truncated |
embodiment |
"gr1", optional |
must equal the server's; anything else β 400 |
seed |
int β₯ 0, optional | CPU-generator seed of the [1, 16, 32] flow-matching noise; default 0 (the deployed policy's) |
noise |
[[32 floats] Γ 16], optional |
explicit initial noise; mutually exclusive with seed |
state_dtype |
"float64" (default) | "float32" |
dtype of the state before normalisation (the GR1 dataset stores float64) |
return_normalized |
bool, default false | also return the model-space chunk action_pred_normalized ([16][32]) and action_pred_valid_hd ([16, 26]) |
Response (200):
{"actions": {"left_arm": [[7 floats] x 16], "right_arm": [...], "left_hand": [...], "right_hand": [...]},
"action_horizon": 16, "action_keys": ["left_arm", "right_arm", "left_hand", "right_hand"], "action_dims": {"left_arm": 7, "right_arm": 7, "left_hand": 6, "right_hand": 6},
"normalized": false, "embodiment": "gr1", "embodiment_id": 24, "version": "n15", "model": "GR00T-N1.5-3B-p150", "layout": "gr1",
"images": {"ego_view": {"frames": 1, "received_hw": [256, 256]}}, "seq_len": 296, "prompt_tokens": 14, "state_dtype": "float64",
"noise_source": "seed" | "client", "seed": 0 | null,
"timing_ms": {"decode": 1.5, "encode": 2.6, "device": 43.0, "decode_actions": 0.1, "total": 47.2}}
actions are physical joint targets (radians) β (y + 1) / 2 Β· (max β min) + min with the checkpoint's
experiment_cfg/metadata.json GR1 statistics β exactly what Gr00tPolicy.get_action returns. timing_ms: decode =
base64 + image decode + validation; encode = the port's host preprocessing; device = predict_normalized (static-shape
asserts, input writes, 4 Γ execute_trace, one blocking readback); decode_actions = un-normalisation; total = the
whole handler. Errors: 400 structural problems ({"detail": {"errors": [...], "inputs": {...}}}), undecodable image,
bad state / instruction / noise, other embodiment; 503 while starting; 500 Type: text on a device failure.
Handlers are synchronous and serialised on one lock; batch 1.
6. Environment (read in the lifespan, never at import)
| variable | container value (serve.env) |
meaning |
|---|---|---|
HF_MODEL / TT_WEIGHTS_REVISION |
nvidia/GR00T-N1.5-3B (launcher) / 869830fc⦠|
must be the version's pinned repo and sha (both are asserted against the port's own configs.HF_REPOS / HF_SNAPSHOT_SHAS) |
GR00T_VERSION |
n15 |
the server code is shared by both GR00T packages and never guesses |
GR00T_EMBODIMENT / GR00T_LAYOUT |
gr1 / gr1 |
the only device-validated layout of this release |
GR00T_POLICY / GR00T_TRACE_LAYOUT |
mixed_dit / per_stage |
TTPolicy knobs; bf16 / mixed and two exist in the port but are not the benchmarked defaults |
GR00T_TRACE_REGION_SIZE / GR00T_L1_SMALL_SIZE |
default 67108864 / 32768 | the port's validated open_gr00t_device values |
GR00T_TOKENIZER_DIR_N15 |
/opt/tt-metal/gr00t_p150/assets/tokenizer/n15 |
vendored Qwen2 tokenizer (not in the weights repo); GR00T_TOKENIZER_DIR overrides |
GR00T_TT_CACHE |
/weight-cache/gr00t-tt |
host .pt plan tier + .tensorbin device tier (persisted under ~/.cache/tt-model/gr00t-n1.5-3b-p150/weights) |
GR00T_PROJECT_ROOT / GR00T_GOLDEN_ROOT |
/nonexistent |
the port's dev-box defaults; nothing on the serve path needs them |
GR00T_WARMUP_RUNS / GR00T_WARMUP_PCC_MIN |
3 / default 0.999 |
traced predicts before READY; boot fails below the PCC floor vs the shipped golden |
GR00T_DEFAULT_SEED, GR00T_TORCH_THREADS, GR00T_WEIGHTS_DIR |
unset | request default seed (0), torch intra-op threads, offline snapshot dir |
TT_MESH_SHAPE, TT_DEVICE_ID, TT_METAL_VISIBLE_DEVICES |
1x1 (launcher), 0, 0 |
one chip; any other mesh shape is refused |
7. Caveats
- Stage-1 path only. TTNN ops in Metal traces; the persistent-megakernel denoise (K1 streaming kernel: 464 GB/s bf16 /
414 GB/s bfp8,
code/models/experimental/gr00t/tests/tt/results/mk_k1_summary.md) is not integrated intoGr00tTTand not served. - Shape-locked traces. One embodiment (
gr1), one camera, one frame, β€ 102 text tokens (LLM S padded to 384), batch 1. Other embodiments / cameras / longer instructions β 400 before anything reaches the device. Concurrent requests queue on one lock. - Deterministic noise. Actions are a deterministic function of (observation, seed | noise); the reference noise of the
demo is not seed-reproducible, so the smoke test posts it as
noise. Same observation + same noise β bit-identical actions. - Base checkpoint. Outputs are the GR1 sim data-config actions of the released N1.5 checkpoint; a real robot needs NVIDIA's post-training.
- First container boot is cold: the port converts the 5.4 GB checkpoint into its
.ptplan tier and the.tensorbindevice tier under/weight-cache/gr00t-ttand JITs the kernels of the four traces into/cache; both persist under~/.cache/tt-model/gr00t-n1.5-3b-p150/, so later boots are seconds (measured values in Β§4). - Python 3.12 image vs 3.10 tree venv. Host runs are an approximation; the image is authoritative (its
verify.shimports the app and the port with no device; the served smoke in Β§4 is the proof). - Weights licence.
nvidia/GR00T-N1.5-3Bis under the NVIDIA License (non-commercial: research or evaluation use); the server reports it under/info -> license. SeeLICENSE-NOTICE.md. tt-model curland the ready card's/v1/modelshint are OpenAI-shaped and are not this API; use the routes above.