Tenstorrent backend plugin for vLLM.
vllm-tt-plugin integrates Tenstorrent hardware into vLLM using the standard
plugin mechanism. Install it alongside vLLM and, when ttnn is importable
(see TT-Metal),
TT hardware is automatically available as a vLLM platform.
The plugin is self-contained: model registration, platform detection, request validation, scheduling, worker execution, model loading, async decode, single- process standard and multi-lane execution, data-parallel execution all live here. Nothing TT-specific needs to touch vLLM core.
For model authors, the model-plugin interface documents registration, construction, KV storage, execution, and lifecycle, and links to the capability, speculative-decode, and input-reload contracts.
.
+-- src/vllm_tt_plugin/
| +-- entrypoints.py # vLLM plugin entry points
| +-- platform.py # TTPlatform and config validation
| +-- model_registry.py # TT model architecture registration
| +-- worker.py # TT worker implementation
| +-- model_runner.py # TT model execution bridge
| +-- scheduler.py # TT scheduling policy
| +-- lane_scheduler.py # Single-process multi-lane (lane-DP) coordinator
| +-- launcher.py # retained tt-run / MPI launcher (not hooked by vLLM 0.30.0)
| +-- loader.py # TT model loader
| +-- input_batch.py # TT input-batch representation
| +-- async_decode.py # Decode overlap helpers
| +-- config.py # TT plugin config access
| +-- spec_admission.py # Speculative configuration and model-plan validation
| +-- spec_decode.py # Candidate/verification types and greedy acceptance
| +-- spec_accept.py # CPU sampled-acceptance helper (not yet used by the runner)
| +-- utils/ # Common helpers such as device discovery tools for DP
+-- docs/ # TT runtime notes
+-- examples/ # Offline and OpenAI-server examples
+-- tests/tt/ # Server-facing TT plugin tests
If testing a specific model, check the TT-Metal LLMs table for the appropriate tt-metal and vLLM commits.
vLLM requires Python >=3.10,<3.14. Python 3.10.12 is the default python3 on
Ubuntu 22.04.
The installation script builds vLLM 0.30.0 from source with
VLLM_TARGET_DEVICE=empty.
To install against older supported vLLM versions X.Y.Z instead (e.g., 0.25.1),
check out the compat/vllm-X.Y.Z tag (e.g., compat/vllm-0.25.1), the last plugin state
that targets it, and follow the same steps. Nothing is maintained on top of that tag for now.
Pair it with the tt-metal commit the LLMs table lists for it.
Install tt-metal first by following INSTALLING.md. If installing tt-metal from source, build it, create the virtual environment, and set the environment variables needed for tt-metal tests.
Activate the environment where tt-metal is installed, then install vLLM and the TT plugin. Run it from the repository root, which the relative paths inside assume:
source docs/install-vllm-tt.shThe script installs vLLM with
VLLM_TARGET_DEVICE=empty because tt platform is provided by this plugin
at runtime. It then installs the plugin with a few dependencies.
Most dependencies come from the active tt-metal env.
The script also installs vLLM's dependency list itself, fetched from
requirements/common.txt at the pinned vLLM tag, and installs vLLM with
--no-deps. Resolving them the usual way pulls vLLM's CUDA dependency set
(torch pinned, flashinfer, tilelang, nvidia-*), which would fight the
tt-metal env over torch and add several GB. This means the script needs network
access to raw.githubusercontent.com beyond the package index. When installing
inside a container, also set UV_NO_CACHE=1 to keep the uv cache out of the
image layer.
common.txt omits torchvision, which vLLM imports unconditionally from
several model and processor modules, so the script installs it separately: the
CPU build, with --no-deps, at the version the tt-metal env pins alongside its
torch. Environments built from tt-metal's requirements-dev.txt already
carry it; the ttnn wheel does not declare it.
To install or refresh only the plugin package:
uv pip install -e .To run the offline Qwen-VL example, also install its extra:
uv pip install -e ".[examples]"After the first setup, activate the same environment before running vLLM:
source "$PYTHON_ENV_DIR/bin/activate"VLLM_TARGET_DEVICE is a build-time variable only and does not need to be set at
runtime. The TT platform is detected automatically when ttnn is importable.
Install pre-commit hooks once in your active development environment. After
installation, these checks run automatically on every git commit:
uv pip install pre-commit
pre-commit installThe editable install registers two vLLM entry points:
| Entry point group | Name | Target |
|---|---|---|
vllm.general_plugins |
tt_model_registry |
vllm_tt_plugin.entrypoints:register |
vllm.platform_plugins |
tt |
vllm_tt_plugin.entrypoints:platform_plugin |
platform_plugin() returns vllm_tt_plugin.platform.TTPlatform only when
ttnn is importable. This keeps ordinary vLLM environments from accidentally
selecting the TT platform.
Quick checks:
python -c "import vllm_tt_plugin; print(vllm_tt_plugin.__file__)"
python -c "import ttnn; print('ttnn available')"If VLLM_PLUGINS is set, it must allow both TT entry point names:
export VLLM_PLUGINS=tt,tt_model_registryTo run Meta Llama 3.1 or 3.2 models, request access on Hugging Face:
After access is approved, create an access token in Hugging Face settings and log in from Python:
from huggingface_hub import login
login()For the target model, follow any setup instructions in the corresponding tt-metal demo. For Llama 3.1, Llama 3.2, and Qwen 2.5 models, follow the tt-transformers demo instructions for weights and environment variables.
Run offline generation with the default Llama 3.1 70B model:
MESH_DEVICE=T3K python examples/offline_inference_tt.pyMeasure offline performance for one batch of prompts:
MESH_DEVICE=T3K \
python examples/offline_inference_tt.py --measure_perfTo run a different text model, set MESH_DEVICE to N150, N300, T3K, TG,
BH-Galaxy, or a mesh shape such as "(4,8)", then pass --model:
- Llama 3.1 8B:
--model "meta-llama/Llama-3.1-8B" - Llama 3.2 1B:
--model "meta-llama/Llama-3.2-1B" - Llama 3.2 3B:
--model "meta-llama/Llama-3.2-3B" - Qwen 2.5 7B:
--model "Qwen/Qwen2.5-7B" - Qwen 2.5 72B:
--model "Qwen/Qwen2.5-72B" - DeepSeek R1 Distill Llama 70B:
--model "deepseek-ai/DeepSeek-R1-Distill-Llama-70B" - GPT-OSS 20B:
--model "openai/gpt-oss-20b" - GPT-OSS 120B:
--model "openai/gpt-oss-120b"
For Llama 3.1 8B on N150, set --max_model_len 32768; see the tt-metal model
demo for context-length details.
To run Llama 70B on Wormhole Galaxy:
MESH_DEVICE=TG \
LLAMA_DIR=<path-to-weights> \
TT_LLAMA_TEXT_VER=llama3_70b_galaxy \
python examples/offline_inference_tt.py \
--model "meta-llama/Llama-3.1-70B-Instruct" \
--additional-config '{"tt": {"dispatch_core_axis": "col", "sample_on_device_mode": "all", "worker_l1_size": 1344544, "trace_region_size": 216580672}}'To run GPT-OSS 20B on Wormhole Galaxy:
MESH_DEVICE="(4,8)" \
python examples/offline_inference_tt.py \
--model "openai/gpt-oss-20b" \
--max_seqs_in_batch 1To run Qwen3-32B on Blackhole Galaxy:
MESH_DEVICE=TG \
TT_QWEN3_TEXT_VER=qwen3_32b_galaxy \
python examples/offline_inference_tt.py \
--model "Qwen/Qwen3-32B" \
--additional-config '{"tt": {"dispatch_core_axis": "col", "sample_on_device_mode": "all", "worker_l1_size": 1345000, "trace_region_size": 184915840}}'Wormhole Galaxy defaults to FABRIC_1D_RING and Blackhole Galaxy defaults to
FABRIC_2D_TORUS_XY, so those recipes do not need an explicit fabric_config.
Run Llama 3.2 Vision on N300:
MESH_DEVICE=N300 \
python examples/offline_inference_tt.py \
--model "meta-llama/Llama-3.2-11B-Vision-Instruct" \
--multi_modal \
--max_seqs_in_batch 16 \
--num_repeat_prompts 8Useful vision-model variants:
- Llama 3.2 11B Vision on QuietBox: set
MESH_DEVICE=T3Kand--max_seqs_in_batch 32. - Llama 3.2 90B Vision: set
MESH_DEVICE=T3K,--model "meta-llama/Llama-3.2-90B-Vision-Instruct", and--max_seqs_in_batch 4. - Qwen 2.5-VL 32B: set
MESH_DEVICE=T3K,--model "Qwen/Qwen2.5-VL-32B", and--max_seqs_in_batch 32. - Qwen 2.5-VL 72B: set
MESH_DEVICE=T3K,--model "Qwen/Qwen2.5-VL-72B",--max_seqs_in_batch 32,--max_model_len 2048, and--additional-config '{"tt": {"trace_region_size": 28467200}}'. - Gemma 3 27B: set
MESH_DEVICE=T3K,--model "google/gemma-3-27b-it",--max_seqs_in_batch 32,--additional-config '{"tt": {"l1_small_size": 768, "fabric_config": "FABRIC_1D"}}',--multi_modal,--multi_image, and--mm_processor_kwargs '{"use_fast": true, "do_convert_rgb": true}'.
For debugging V1, set VLLM_ENABLE_V1_MULTIPROCESSING=0 to disable
multiprocessing. This is useful for stepping through code or making scheduling
deterministic, but it is not compatible with DP models.
Start the OpenAI-compatible server:
MESH_DEVICE=T3K \
python examples/server_example_tt.pyDiffusionGemma uses a 256-token block-serving contract with stricter launch and request constraints. See DiffusionGemma block serving for the exact command, request validation, and block metrics.
Send a completion request:
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-70B-Instruct",
"prompt": "San Francisco is a",
"max_tokens": 32,
"temperature": 1,
"top_p": 0.9,
"top_k": 10
}'Before a forward is submitted, TT selects one sampling path for the whole submitted batch. If any request or batch feature cannot use on-device sampling, the batch uses vLLM's host-side path. Structured outputs have additional rules; see Structured Output Sampling.
Single-token-output models can explicitly use
"seeded_sampling_policy": "host" to sample requests with an API seed on
host during both prefill and decode. The default, "auto", preserves the
existing routing. With the host policy, one submitted seeded request moves
the whole forward to host sampling, including its unseeded companions.
Seeded greedy requests also use host sampling. This can reduce throughput
and increase latency; batches without submitted seeded requests retain
their existing routing. Models that own multi-token sampling and speculative
decoding configurations reject this option. It does not promise
batch-independent model logits or waive tests.
For vision models, start the server with the correct --model, then send a chat
completion request with image content. Qwen 2.5-VL models can use either a
base64 data:image/... URL or a real URL such as
https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg.
TT options are passed through vLLM's generic additional config namespace:
--additional-config '{"tt": {"sample_on_device_mode": "all"}}'Plugin code reads this through vllm_tt_plugin.config.get_tt_config(), which
returns vllm_config.additional_config["tt"].
Common options:
| Key | Purpose |
|---|---|
sample_on_device_mode |
Select on-device sampling mode, currently all or decode_only when supported by the model. |
seeded_sampling_policy |
auto (default) or host. Select host sampling for submitted seeded requests during prefill and decode; affects the whole submitted forward. Single-token-output models only; incompatible with speculative decoding. |
trace_mode |
Control TT tracing: all, decode_only, or none. Default: all. |
enable_model_warmup |
Warm up the model before the server reports healthy. Default: true. |
trace_region_size |
Trace region size for TT runtime tracing. |
worker_l1_size |
Worker L1 size override. |
l1_small_size |
Small L1 size override. |
fabric_config |
Fabric config such as DISABLED, FABRIC_1D, FABRIC_2D, FABRIC_1D_RING, FABRIC_2D_TORUS_XY, or CUSTOM. Any ttnn.FabricConfig name is accepted. Overrides model defaults; otherwise defaults to Wormhole Galaxy FABRIC_1D_RING, Blackhole Galaxy FABRIC_2D_TORUS_XY, other multi-device FABRIC_1D. |
fabric_reliability_mode |
Fabric reliability mode, such as STRICT_INIT or RELAXED_INIT. |
dispatch_core_axis |
Dispatch core axis, row or col. |
always_compat_sampling |
Use vLLM's LogitProcessor and sampler path even when not required by the batch. Default: false. |
decode_interleave_enabled |
Insert decode-only steps into a run of consecutive prefill steps, so a running request's inter-token latency does not scale with another request's prompt length. Default: true. |
decode_interleave_prefill_steps |
Consecutive prefill steps allowed before one insertion. Raise it to favor time to first token, lower it to favor inter-token latency. Default: 2. |
decode_interleave_decode_steps |
How many decode-only steps one insertion runs. Reaching this count stops the policy choosing decode; it never forces a prefill step. Does not loosen the latency bound; buys more decode progress per insertion, and more cheaply than a shorter prefill run does. Default: 1. |
optimizations |
Select model/runtime optimization profile, such as accuracy or performance. |
register_test_models |
Register non-production TT test models for infrastructure tests. Default: false. |
The TT device sampler draws only from the 32 most probable tokens. A model class declares this limit as max_device_top_k (see Sampling limits). When a request has a temperature above 0 and a top_k that is unset or above the limit, the plugin samples the whole batch on host for that step. Host sampling reads back the full-vocabulary logits on every decode step, so decode is much slower than with device sampling.
Most requests do not set top_k: the OpenAI Python client has no top_k argument, and vllm bench serve sends no sampling parameters. vLLM then uses the default from the model's generation_config.json. Llama 3.1, Llama 3.3 and GPT-OSS define no top_k default, which means all tokens. Gemma 3 and Gemma 4 define top_k: 64. So with these models, every request that does not set top_k samples on host.
To keep these requests on the device, start the server with a default top_k equal to the device limit:
--override-generation-config '{"top_k": 32}'vLLM merges this value into the model's default sampling parameters. It applies only to requests that do not set top_k. A request that sets its own top_k keeps it, and a value above 32 still samples on host.
Use this setting when all of these are true:
sample_on_device_modeis set, and the model supports device sampling on the mesh.- The model's default temperature is above 0. Greedy requests do not use
top_k. - The model's default
top_kis unset or above 32.
Do not use it when the model's default top_k is 32 or less, for example Qwen3 (top_k: 20). The override replaces the model default, so it would widen it. Do not use it when the server samples on host, because it then only changes the distribution and does not make decode faster.
The setting is an approximation: "all tokens", or 64 for Gemma, becomes the 32 most probable tokens. A client that needs the model's full distribution sets top_k in the request (for example extra_body={"top_k": 0} with the OpenAI client) and accepts host sampling for that request.
Some model classes do not declare max_device_top_k, for example the tt-metal models/demos/gemma4 and models/demos/llama31_8b_qb2 classes (tenstorrent/tt-metal#60365). For them, the plugin does not route by top_k, and the device sampler changes any top_k above 32 to 32 without a message. The setting is still recommended for these models: the request then asks for what the device does.
Structured-output decode can use TT device sampling on models that declare
supports_device_grammar. It is off by default. To enable it, set a
sample_on_device_mode and turn off async scheduling. The example also sets
the default top-k, because requests
without top_k to the default Llama model would otherwise sample on host:
MESH_DEVICE=T3K \
python examples/server_example_tt.py \
--no-async-scheduling \
--override-generation-config '{"top_k": 32}' \
--additional-config '{"tt": {"sample_on_device_mode": "decode_only"}}'vLLM enables async scheduling by default, and the platform disables it only
for models without supports_async_decode. On models that declare it,
structured requests keep host sampling unless the server is started with
--no-async-scheduling.
| Step | Sampling path |
|---|---|
| Structured prefill | Host sampling |
| Eligible structured decode | TT device sampling after the sample-time grammar mask arrives |
| Structured decode with logprobs, another host-only sampling option, or no effective device-grammar support | Host sampling |
Eligibility also requires a compatible runtime sampler (not row-sharded),
model warmup when tracing, and no logprobs (including logprobs=0) or other
host-only sampling option. The ordinary device-sampling limits,
max_device_top_k and supports_device_penalties, also apply. Eligibility is
batch-wide: one ineligible request moves the whole decode step to host
sampling. Block-output models reject structured outputs rather than falling
back.
Fallback is selected before forward submission. Once an eligible decode is submitted with deferred device sampling, its sample-time grammar mask must be present and valid. A missing mask or a device-sampling failure is terminal for that runner and requires recovery/restart; it is not retried on host, because the device decode and sampler state may already have advanced.
With VLLM_LOGGING_LEVEL=DEBUG, each device-grammar step logs
TT device grammar sampling active. See
docs/SCHEDULING.md
for the execution flow and
docs/MODEL_CAPABILITIES.md
for the capability contract. Model authors implement the
device grammar contract.
A model class may declare model_capabilities["fabric_config"] as a dictionary
of keyword arguments to ttnn.set_fabric_config, using TTNN enums and config
objects directly. For example:
model_capabilities = {
"fabric_config": {
"config": ttnn.FabricConfig.FABRIC_1D_RING,
"num_planes": 2,
},
}The worker applies hardware defaults, then the model's dictionary, then explicit
fabric_config and fabric_reliability_mode launch overrides. It forwards the
result with ttnn.set_fabric_config(**fabric_kwargs) before opening the mesh.
Any TTNN fabric argument is supported, including router_config; TTNN validates
the arguments. The plugin does not mutate the model's dictionary or store its
TTNN objects in the serialized vLLM configuration. Single-device meshes do not
initialize fabric. Models without this capability keep the hardware defaults.
TT does not profile device memory. get_num_available_blocks_tt() instead
derives a block count from the model's max_tokens_all_users, a per-model,
per-device token budget declared by the model class in tt-metal
(get_max_tokens_all_users, default 131072, sometimes exposed as an environment
override such as GEMMA4_MAX_TOKENS_ALL_USERS), plus headroom for vLLM's
worst-case block allocation and for sliding-window groups on hybrid models.
TTWorker.determine_available_memory() publishes that count both as
cache_config.num_gpu_blocks_override and as an equivalent byte budget. The
latter is what reaches the engine-side KV planner, which in standard DP runs in
a different process from the worker that set the override.
That pool is the budget shared by all concurrent requests. It is set
independently of max_model_len, the per-request context limit, except that
eligible hybrid models raise it to admit one max_model_len request (see
below). Since vLLM
sizes and validates the KV cache against it, the two must be chosen together:
--max-model-len |
Behavior |
|---|---|
| numeric, fits the pool | Used as given. Maximum concurrency is roughly pool / max_model_len. |
| numeric, exceeds the pool | Startup fails in get_kv_cache_configs with the estimated maximum servable length. Block-output models fail earlier, in the TT worker: they must fit max_model_len plus one output canvas. Lower --max-model-len or raise max_tokens_all_users. Eligible hybrid models are the exception: those that set _HYBRID_KV_CACHE_GROUPS_ENABLED and whose get_kv_cache_spec hook returns a nonempty spec. For them the TT worker raises the pool to the smallest size upstream admits for one max_model_len request, plus the output reservation, and logs the change. That pool must still fit device memory, which TT does not profile; KV allocation fails at startup if it does not. With chunked prefill off, every sliding-window group is sized at the full context, so the raised pool can be several times max_model_len. |
-1 |
vLLM auto-fits max_model_len to the pool and syncs the result to the workers and the API-server process. For block-output models the TT worker fits max_model_len to the pool minus one output canvas before vLLM's auto-fit runs. |
| omitted | vLLM uses the HF-derived value. Startup fails if that value exceeds the pool, except for eligible hybrid models, which raise the pool as in the numeric case. |
Explicit -1 auto-fit picks the largest length that fits one request, so it
lands at roughly the whole pool with maximum concurrency 1.00x: a single long
request can occupy the entire KV cache and stall the rest. Prefer an explicit
positive --max-model-len for serving, sized around
max_tokens_all_users / max_num_seqs, and treat auto-fit as a convenience for
one-off runs.
TTPlatform.check_and_update_config() is the main handoff from vLLM into the TT
runtime. It validates configuration, registers TT model architectures, and
selects the TT-owned runtime classes through vLLM's extension points:
| vLLM config field | TT implementation |
|---|---|
parallel_config.worker_cls |
vllm_tt_plugin.worker.TTWorker |
scheduler_config.scheduler_cls |
vllm_tt_plugin.scheduler.TTScheduler or vllm_tt_plugin.lane_scheduler.TTLaneCoordinator |
A TTWorker subclass names the scheduler it runs with in tt_scheduler_cls,
and sets uses_token_kv_cache = False for a stage with no token KV cache.
A requested scheduler_cls is kept when it derives from that class, and replaced
by it when that class derives from the request. Any other pair raises
ValueError. Lane mode also raises unless the worker declares TTScheduler,
inherited or explicit, and the request is unset, TTLaneCoordinator, or a class
TTScheduler derives from.
The execution model matches TT hardware characteristics:
- A TT step is either prefill-only or decode-only.
- Token-chunked prefill is available to any model whose tt-metal class declares
model_capabilities['supports_chunked_prefill']: a long prompt is split across prefill steps and only the chunk that completes the prompt emits a token. Support belongs to the selected tt-metal serving class, including anyTT_MODEL_CLASS_OVERRIDESselection. Model-family registration alone does not establish chunked-prefill support. - Async scheduling overlaps decode submission with host-side scheduling when the model declares support.
- For Galaxy-generator models (Llama3 70B, Qwen3-32B) and GPT-OSS,
--data_parallel_size Nruns asNin-process TT lanes scheduled byTTLaneCoordinator(one engine, one device mesh); see Single-Process Galaxy Serving. - For other models,
--data_parallel_size Nuses standard multi-process DP: each DP rank runs an independent engine core with its own TT submesh, scheduler, and KV cache. Device groups are discovered at startup and assigned viaTT_VISIBLE_DEVICES. A per-rank assignment is accepted only when it exactly matches the discovered group; a conflicting GPU-style--device-idsassignment fails before mesh creation. This is upstream vLLM's standard DP mechanism (no gather/scatter; ranks are fully independent).
For a deeper walk-through of the scheduling and execution model, read
docs/SCHEDULING.md. The model-facing rules that make
resident async decode safe are documented in
docs/DECODE_RELOAD_CONTRACT.md.
The defaults, validation and consumers of each model_capabilities key are
documented in docs/MODEL_CAPABILITIES.md.
Galaxy text models served by the single-execute Galaxy generator
(Llama 3.3 70B via TT_LLAMA_TEXT_VER=llama3_70b_galaxy, Qwen3-32B via
TT_QWEN3_TEXT_VER=qwen3_32b_galaxy) run on a single Galaxy device mesh, so
they use single-process TT lanes: one vLLM engine process with internal TT
lanes.
Serve them with the familiar --data_parallel_size N --max_num_seqs M flags;
the TT backend transparently maps them to N in-process lanes. No config
changes are needed. Wormhole Galaxy example (fabric defaults to FABRIC_1D_RING):
MESH_DEVICE=TG \
TT_LLAMA_TEXT_VER=llama3_70b_galaxy \
python examples/server_example_tt.py \
--model "meta-llama/Llama-3.3-70B-Instruct" \
--data_parallel_size 4 \
--max_num_seqs 8 \
--async-scheduling \
--override-generation-config '{"top_k": 32}' \
--additional-config '{"tt": {"dispatch_core_axis": "col", "sample_on_device_mode": "all", "worker_l1_size": 1344544, "trace_region_size": 220000000}}'--data_parallel_size 4 --max_num_seqs 8 runs 4 TT lanes of 8 requests
each (32 concurrent total); --max_num_seqs is the per-lane capacity. This
conversion is specific to the Galaxy generators and GPT-OSS; other model
families still run --data_parallel_size as multi-process DP.
At startup the backend logs that it is running single-process lane-DP.
The plugin registers TT-prefixed model architectures backed by tt-metal model implementations. Current families:
- Llama 3.1 / 3.2 / 3.3 text models (
TTLlamaForCausalLM) - Llama 3.2 vision models (
TTMllamaForConditionalGeneration) - Qwen 2.5 and Qwen 3 text models (
TTQwen2ForCausalLM,TTQwen3ForCausalLM) - Qwen 3.5 text models on Blackhole (
TTQwen3_5ForConditionalGeneration) - Qwen 2.5-VL and Qwen 3-VL vision-language models
- Mistral and Mistral 3 multimodal models
- Gemma 3 multimodal models
- Gemma 4 text-only models (
TTGemma4ForCausalLM,TTGemma4ForConditionalGeneration,TTGemma4UnifiedForConditionalGeneration) - DiffusionGemma block-output models (
TTDiffusionGemmaForBlockDiffusion,TTDiffusionGemmaForCausalLM) - DeepSeek V3 (
TTDeepseekV3ForCausalLM) - GPT-OSS 20B / 120B (
TTGptOssForCausalLM)
Model availability, supported device shapes, max sequence limits, and required environment variables are documented in the corresponding tt-metal model demos.
Instead of adding a hard-coded line to platform.py, a model can be registered at
startup by dropping a bundle folder under a directory named by the
EXTRA_MODELS_DIR environment variable. Each subfolder holds a vllm_metadata.json
and the adapter class (plus its dependencies):
$EXTRA_MODELS_DIR/
my-model/
vllm_metadata.json # {"arch": "<HFArch>", "main_class": "module:Class", ...}
<adapter class + deps>
At import time the plugin scans EXTRA_MODELS_DIR, appends each folder to sys.path
(so an installed package of the same name is never shadowed), and registers arch
under the plugin's TT-prefixed convention (TT<HFArch>) pointing at main_class.
This lets a distribution tool (e.g. tt-kernel) deliver a ready-to-serve model with no
source edit to the plugin. The built-in map above stays enabled by default; set
TT_VLLM_BUILTIN_MODELS=0 to rely solely on EXTRA_MODELS_DIR.
A checkpoint whose architecture already has a serving class can be pointed at a DIFFERENT one for a single launch, without editing the plugin or building a bundle. This is how one Gemma 4 checkpoint serves as a plain baseline, as an MTP speculative model, or as a dFlash speculative model.
export TT_MODEL_CLASS_OVERRIDES="TTGemma4ForCausalLM=models.demos.gemma4.tt.generator_vllm:Gemma4DFlashForCausalLM"Comma-separate several entries. Each is Architecture=module.path:ClassName.
Four things an operator needs to know:
- Use the
TT-prefixed architecture name.TTPlatform.check_and_update_configrewrites every checkpoint architecture in place with aTTprefix before the registry is consulted, soTTGemma4ForCausalLMis the name that resolves. A bare name is accepted and normalised, but the prefixed form is what takes effect. - Name the architecture the checkpoint actually resolves to. Several aliases
can share one serving class -- Gemma 4 has six -- and overriding one alias
leaves the rest on the default class. Check the
architecturesfield of the checkpoint'sconfig.json. - Precedence. Overrides register first and unconditionally, so they outrank
both
EXTRA_MODELS_DIRbundles and the built-in map for the whole process. Every applied override is logged at INFO asApplied TT_MODEL_CLASS_OVERRIDES: <arch> -> <target>; if that line is missing from the server log, the variable was not set in the environment the engine actually started in. - The target must be importable in the worker process, i.e. on
PYTHONPATHalongside tt-metal'smodels/tree.
The registration environment variables, in the order they are consulted:
TT_MODEL_CLASS_OVERRIDES, EXTRA_MODELS_DIR, then the built-in map
(TT_VLLM_BUILTIN_MODELS=0 disables it). TT_LLAMA_TEXT_VER,
TT_QWEN3_TEXT_VER and TT_QWEN35_TEXT_VER select a version WITHIN a built-in
family and are unrelated to the above.
The plugin supports greedy speculative decoding through a runner-model
contract. TTModelRunner builds candidate blocks, asks the selected tt-metal
adapter to verify candidates, accepts matching draft tokens, and reports
committed tokens to vLLM. The selected adapter must declare
supports_spec_decode=True and return an admissible SpecPlan from
spec_plan. Architecture registration alone does not enable speculation.
Two drafting methods execute today:
speculative_config.method |
Proposal source | Required model implementation |
|---|---|---|
ngram |
vLLM's host NgramProposer |
spec_plan and multi-position verification; no model-side proposer |
custom_class |
The selected adapter's propose_draft_tokens |
spec_plan, multi-position verification and device_propose in spec_requirements |
Both methods currently use argmax_ids verification and host greedy
acceptance. docs/install-vllm-tt.sh installs numba for NgramProposer.
The CPU sampled-acceptance helper exists in spec_accept.py, but
TTModelRunner does not call it. logits and fused_sample execution,
lossless sampled speculation, scheduler-owned paged drafter caches and
TTLaneCoordinator execution are not implemented for this contract.
The following distinctions describe the Gemma serving classes in tt-metal as of 2026-10-01 (Gemma4 source, DiffusionGemma source). The tt-metal model recipe determines supported checkpoints, hardware, batch sizes and context lengths; these declarations alone do not establish validation for every configuration.
| tt-metal serving class | Execution path | How the operator selects speculation |
|---|---|---|
Gemma4DFlashContractForCausalLM |
Runner-model contract, output_tokens_per_step=1 |
Select this class and pass the custom_class settings below |
Gemma4DFlashForCausalLM |
Adaptive block output; the model owns its draft/verify loop | Select this class without speculative_config |
Gemma4MTPForCausalLM |
Adaptive block output; no MTP contract adapter is present | Select this class without speculative_config |
DiffusionGemmaForCausalLM |
Fixed block output, not speculative decoding | Follow DiffusionGemma; do not pass speculative_config |
New speculative adapters should implement the runner-model contract. Migrating the existing dFlash and MTP speculative block-output classes does not require removing DiffusionGemma's fixed block output.
Gemma4DFlashContractForCausalLM admits max_num_seqs > 1, but the current
adapter speculates only for a request that decodes alone. Shared prefill or a
peer joining the request's decode step ends speculation for that request;
the request continues ordinary decode and does not resume drafting when the
peer leaves. Temporary unscheduling alone preserves the request's retained
proposal. Admitting concurrent requests does not establish batched dFlash
speculation.
For a compatible Gemma4 dFlash model recipe, select the contract class for the
architecture the checkpoint uses. For example, a checkpoint that resolves to
TTGemma4ForCausalLM uses:
export TT_MODEL_CLASS_OVERRIDES="TTGemma4ForCausalLM=models.demos.gemma4.tt.generator_vllm:Gemma4DFlashContractForCausalLM"
export GEMMA4_DFLASH_VERIFY=5
export GEMMA4_CHUNKED_PREFILL_TRACE=0Add the following arguments to the compatible model's server command. The
model value is a dispatch marker, not a checkpoint or an importable proposer:
--speculative-config '{"method":"custom_class","model":"vllm_tt_plugin.model_owned_drafter","num_speculative_tokens":5}'
--no-async-scheduling
Gemma4DFlashContractForCausalLM.spec_plan uses
effective_k=GEMMA4_DFLASH_VERIFY (default 5). spec_plan rejects smaller
requested draft lengths, and TTPlatform reduces larger requested draft
lengths to effective_k. To request async scheduling, set
GEMMA4_CONTRACT_ASYNC=1 before model import and replace
--no-async-scheduling with --async-scheduling. Both
supports_async_decode and supports_async_spec_decode must be true.
Verification steps remain serialized through acceptance and proposal;
eligible ordinary decode steps can overlap. Standard multi-process DP is
distinct from the unsupported TTLaneCoordinator path. Device validation of
contract async execution with DP greater than one remains unverified here.
Two model-side limits require attention in current dFlash recipes:
- Keep
GEMMA4_CHUNKED_PREFILL_TRACE=0: traced prefill replay does not capture the dFlash hidden-state taps (tt-metal #58432). - A bounded sliding KV ring needs enough capacity to preserve the attention
window during verification. The contract adapter declines drafting when the
bounded sliding KV ring cannot safely hold the candidate block; this can
reduce long-context generation to ordinary decode. The
contract CI recipe proposed in tt-metal #57855
uses
GEMMA4_SPEC_RING_HEADROOM_BLOCKS=16with a 64-token page size. Extra ring capacity consumes KV memory, so use the model recipe's matching memory budget. Automatic sizing remains tracked by tt-metal #57788. The adaptive block-output dFlash class only warns about insufficient ring capacity and can corrupt in-window KV (tt-metal #57701).
Use temperature=0 with no penalties to exercise greedy speculation.
TTModelRunner._publish_draft withholds drafts for requests with non-zero
temperature or penalties.
A sampled request never enters a verify. A verify in argmax_ids mode
commits the target argmax on every row, so it cannot apply a row's temperature
or penalties. When the model supports narrow decode, every step that includes
a sampled or penalized request runs as the ordinary decode, with the selected
model's normal sampling path; the other rows' drafts on that step are dropped,
so no request speculates while a sampled one shares its batch. A model without
narrow decode verifies every decode step, so TTPlatform.validate_request
refuses a request with a non-zero temperature or a penalty on that launch.
TTPlatform.validate_request rejects logprobs, structured output,
logit_bias, bad_words, allowed_token_ids and nonzero min_tokens on a
speculative launch. Prompt logprobs remain unsupported for all TT models.
Only vLLM's Python frontend runs these checks, so a speculative launch with
VLLM_USE_RUST_FRONTEND=1 is refused at startup.
See the model contract for candidate shapes, acceptance, K+1 lookahead reservation and lifecycle hooks. See the capability reference for defaults and validation, and the speculative server tests for the dummy model's validation scope.
TTPlatform rejects or adjusts unsupported feature combinations early, giving a
clear error before anything reaches the device:
- Tensor parallel and pipeline parallel execution are provided by the models internal implementation, not exposed at the vLLM level.
- Speculative decoding supports greedy
argmax_idsverification withngramor the model-ownedcustom_classdrafter on a compatible tt-metal adapter. See Speculative Decoding for launch settings, model selection and sampling limits. - LoRA is not currently supported.
- Chunked prefill is gated on the model's declared capability, not on a
model_typeallowlist. vLLM enables it by default; pass--no-enable-chunked-prefillto opt out. When it stays on,max_num_batched_tokensis left as vLLM set it (2048 forvllm serve/server_example_tt.py, 8192 forLLM(), or an explicit--max-num-batched-tokens). When it is disabled, a budget smaller thanmax_model_lenis raised tomax_model_lenso a full prompt still fits in one step. Resume offsets need an alignment that depends on the model's program config and on the length of each remaining span, and the tt-metal generator corrects them itself. - Where chunked prefill is active, multimodal inputs are never split across a chunk boundary.
- Prompt logprobs are rejected at request validation time.
- Prefix caching is enabled only for models that declare TT support for it.
- Async decode overlap is enabled only for models that declare the capability.
- Multi-host MPI data parallelism is not supported.
- vLLM's V2 model runner. The plugin implements only the V1 model-runner
contract and pins
VLLM_USE_V2_MODEL_RUNNER=0; setting it to1is refused.
These are TT runtime characteristics, not vLLM plugin API limitations.
Offline benchmarking is done by passing --measure_perf to
offline_inference_tt.py:
MESH_DEVICE=T3K \
python examples/offline_inference_tt.py \
--model meta-llama/Llama-3.1-70B-Instruct \
--measure_perfClient-server benchmarking can be done with vllm bench serve after starting
the server:
vllm bench serve --model meta-llama/Llama-3.1-70B-Instruct \
--dataset-name random \
--random-input-len 128 \
--random-output-len 128 \
--num-prompts 32 \
--ignore-eos \
--percentile-metrics ttft,tpot,itl,e2elFor prefix-cache experiments, use prompts with shared prefixes:
python examples/offline_inference_tt.py \
--prompts_json examples/prompts_overlapping.jsonYou can also pass --random-prefix-len <N> to vllm bench serve.
The plugin ships server-facing tests under tests/tt. Start a vLLM server with
a TT model, then run:
pytest tests/tt -v \
--tt-server-url=http://localhost:8000 \
--tt-model-name=meta-llama/Llama-3.1-8B-InstructFor reasoning models, pass --tt-reasoning-token-budget=N to add N to the
total generation cap in the bad-word and mixed structured/plain chat tests.
Their original caps (100 tokens for bad words; 8/16/64/16 for
choice/regex/JSON/plain) form the base allowance, and all output assertions
remain in place. Reasoning and answer tokens share the resulting total cap.
The default is 0, preserving existing runs. This option does not change
raw-completion, recall, penalty or token-count tests.
Choose N from matched native-reference completions with a generous discovery
ceiling and normal EOS stopping; record reasoning and answer counts separately.
A safety allowance of twice the largest completed reasoning count is a useful
starting point, not a guarantee for unseen prompts or seeds. Include enough
space for the final answer and framing when choosing the extra allowance.
A reference that hits its discovery ceiling or runtime limit has not established
a usable limit. Keep its failure
visible and investigate it before treating a larger test budget as a fix.
For GPT-OSS, run the chunked-prefill recall gate through its supported Harmony chat rendering. The recall-only options preserve the original user prompts, three rounds of four concurrent requests, and complete-identifier matching:
Configure the GPT-OSS server with
--structured-outputs-config '{"reasoning_parser":"openai_gptoss"}' so that
analysis is separated from the final content returned by chat completions.
"$VIRTUAL_ENV/bin/python" -m pytest tests/tt -v \
--tt-server-url=http://localhost:8000 \
--tt-model-name=openai/gpt-oss-120b \
--tt-chunked-prefill-budget=8192 \
--tt-recall-reasoning-effort=low \
--tt-recall-reasoning-token-budget=1000The recall cap is the original 24 plus the explicit 1,000-token allowance, totaling 1024 for both reasoning and answer tokens. This finite diagnostic ceiling was used for all 14 original GPT-OSS 120B inputs; it is not inferred from a shorter observed answer. The gate requires normal stop and nonempty final content; analysis, tool handoffs and length-truncated answers cannot pass. The chosen effort is sent to chat completions without rewriting the user's text.
Without --tt-recall-reasoning-effort, recall uses the existing legacy requests
and 24-token cap, even if a recall allowance is supplied. The recall-only options
do not change the bad-word/structured allowance or any other test's request cap.
No model name is used to select this behavior. Check the server's effective
chunked-prefill settings before running; a recall pass alone cannot prove that
the scheduler split the prompt. Supported-input diagnostic evidence does not
replace earlier raw-input scores or qualify an untested runtime revision.
Tests cover request isolation, sampling behavior, penalties, logprobs, host-only parameter handling, and TT utility helpers.
Plugin-local unit tests that do not require a running server live directly
under tests/, for example:
"$VIRTUAL_ENV/bin/python" -m pytest tests/test_lane_scheduler.pyThese need no Tenstorrent hardware, only an importable ttnn. On a host without
tt-metal, put the CI stub on the path instead. This is what the
host-tests-stubbed workflow job runs:
PYTHONPATH=tests/host-stubs "$VIRTUAL_ENV/bin/python" -m pytest tests/ --ignore=tests/ttThe stub answers only the device-independent ttnn names the plugin touches and
raises from anything that would reach a device, so a test that starts depending
on real hardware fails loudly rather than passing against a fake.
Hybrid attention models combine sliding-window and full-attention layers.
The registered TT model class supplies get_kv_cache_spec(vllm_config) so
vLLM can plan KV groups. The model adapter allocates physical storage through
allocate_kv_cache_per_layer(per_layer_specs) and consumes
page_tables_per_layer when the resolved cache has multiple groups.
See model setup and KV storage for specification names, allocation triples, storage sharing, and page-table routing. Model-specific device and DP support must be validated for the selected adapter and mesh; the interface alone does not establish support.
- Normal Python changes under
src/vllm_tt_plugin/take effect after restarting the Python or vLLM process. - Reinstall the plugin when package metadata or entry points change, such as
edits to
pyproject.toml. - Model capability declarations (
model_capabilitiesdict on the model class) are the preferred way to gate features like async decode and prefix caching, rather than hard-coded model-name checks.
Contributions are welcome! Bug reports and feature requests should be filed via GitHub Issues. Bug fixes and new functionality are submitted via pull requests. Pull requests are reviewed weekly. See CONTRIBUTING.md for details.
- LICENSE: Overall license for this project (Apache 2.0), except where specified
- LICENSE_understanding.txt: Tenstorrent's clarification of how the Apache 2.0 license applies to this repository
Single-token-output models may declare model_capabilities["max_device_top_k"] to bound stochastic device sampling. Requests submitted in the current step outside 1..max_device_top_k, including unbounded top-k, use the host sampler with the original distribution. Greedy requests are unaffected. Models without the capability keep their existing routing. To keep requests that do not set top_k on the device, see Default top-k for device sampling.
Block-output models (output_tokens_per_step > 1) cannot declare this capability: their model-owned sampler produces a complete canvas, which the host sampler cannot replace. The combination is rejected at startup.
Sampling batches preserve signed 64-bit request seeds, including through slot compaction. Host generators retain the original seed. A model whose device sampler requires a narrower seed remains responsible for conversion at its device boundary. Host and device samplers need not generate identical random streams.
Models can declare supports_compact_host_logits to return only the scheduled
host sampling rows. supports_selective_host_readback additionally permits
selective device-to-host transfers and requires compact-logit support.
Adapters must preserve row order, buffer ownership, and asynchronous read
completion. Active or custom logits processors can require the full slot
layout. See the host sampling output contract
for the sample_rows callbacks and fallback requirements.