Cross-track lesson · Run & build
Where a QB2 actually keeps model weights and compiled kernels, and how to make tt-inference-server reuse what already shipped instead of downloading it again.
Your QB2 shipped with Qwen3-32B already on disk. Serving Models on QB2 shows the one command that uses it. This lesson is the layer underneath: what that directory actually is, why the obvious flag is the wrong one, and how to reuse the compiled kernel cache as well as the weights.
~/data/tt-cache is tt-inference-server’s persistent volume root. Each model it knows
about gets a cache_root beneath it, and the one your box shipped with looks like this:
~/data/tt-cache/ <- persistent volume root
└── volume_id_tt_transformers-Qwen3-32B-vqb2_launch/ <- one model's cache_root
├── weights/Qwen3-32B/ <- ~62 GB, a normal HF model dir
├── tt_metal_cache/cache_Qwen3-32B/P300x2/P150x4/ <- ~30 GB of compiled kernels
├── logs/
└── model_file_symlinks_map/
weights/Qwen3-32B/ holds exactly what you’d expect — config.json, tokenizer.json,
model.safetensors.index.json and 17 .safetensors shards. Only the location is unusual,
which is why --host-weights-dir works on it directly.
It is not a Hugging Face cache, and this is the part that catches people: a factory QB2
has no ~/.cache/huggingface and no ~/models at all. Reaching for --host-hf-cache
therefore resolves HOST_HF_HOME, then HF_HOME, then ~/.cache/huggingface, finds
nothing, and downloads all 62 GB again.
model_file_symlinks_map/ looks broken, and that’s fine. It holds a symlink to
/home/container_app_user/cache_root/weights/Qwen3-32B — a path that exists inside the
serving container, not on your host, so ls reports a broken link. It’s tt-inference-server’s
own bookkeeping. Leave it alone.
--host-weights-dir gets you the weights but not the 30 GB of compiled kernels. For those
you point run.py at the whole persistent volume with --host-volume ~/data/tt-cache — but
don’t run that yet, because on a stock box it does the opposite of what you want.
run.py doesn’t look inside that directory to see what’s there. It computes the name it
expects, as volume_id_<impl>-<model>-v<version>. That version is not the
tt-inference-server release number; it comes from the model spec entry matching your model
and device. For Qwen3-32B on p300x2 that entry is impl: tt_transformers, version: 0.17.0, so run.py goes looking for volume_id_tt_transformers-Qwen3-32B-v0.17.0.
Your box ships volume_id_tt_transformers-Qwen3-32B-vqb2_launch — a build label, not a
version. The names don’t match, so run.py finds nothing to reuse and quietly starts
downloading 62 GB into a brand-new volume beside the one it was meant to use. Set up the
alias first, then launch.
Don’t take the version above on faith; it moves between releases, and there are four
different Qwen3-32B spec entries.
# HF_TOKEN first: run.py validates it up front whenever --docker-server is passed,
# including on this dry run. Without it you get "⛔ HF_TOKEN not set." and no output.
export HF_TOKEN=hf_...
python3 ~/.local/lib/tt-inference-server/run.py \
--model Qwen3-32B --tt-device p300x2 \
--workflow server --docker-server \
--host-volume ~/data/tt-cache \
--print-docker-cmd --skip-system-sw-validation
--print-docker-cmd prints the docker run it would issue and starts nothing, so this is
safe to run before the alias exists. Read the src= path in the --mount line — that’s the
exact directory name it wants. The same output carries
TT_CACHE_PATH=.../tt_metal_cache/cache_Qwen3-32B/P300x2, confirming the kernel cache you’re
about to alias is the one the container will read.
--skip-system-sw-validation is there only because validation shells out to tt-smi and will
stop you before anything prints if the chips are busy. You’re only printing a command, so
skipping it is safe — don’t carry that flag into a real run.
Substituting whatever name you actually saw:
Copy the directory name out of the src= path Step 1 printed — don’t retype the v0.17.0
above, which is only what this spec version happened to want:
cd ~/data/tt-cache
# Paste the basename of the src= path from Step 1 here.
WANTED=volume_id_tt_transformers-Qwen3-32B-v0.17.0
ln -s volume_id_tt_transformers-Qwen3-32B-vqb2_launch "$WANTED"
ls -l "$WANTED" # should resolve to the -vqb2_launch directory
This replaces the --host-weights-dir command from the chapter — it is not a next step.
Both start a server on the same four chips and the same port, and the chips have one owner at
a time. If one is already running, stop it first (Ctrl-C, or docker ps then docker stop)
and confirm with sudo lsof -w /dev/tenstorrent/* printing nothing.
export HF_TOKEN=hf_...
python3 ~/.local/lib/tt-inference-server/run.py \
--model Qwen3-32B --tt-device p300x2 \
--workflow server --docker-server \
--host-volume ~/data/tt-cache
Because the alias tracks a version that moves, upgrading tt-inference-server will eventually
invalidate it and you’ll be back at step 1. That maintenance cost is exactly why
--host-weights-dir is the better default for most people: it’s version-independent, and a
one-time kernel compile beats a symlink that silently stops matching.