tt-openvla
A TT-Metal / TTNN bring-up of OpenVLA-7B, a vision-language-action model for robot manipulation.
- What it does: given a camera image and a natural-language instruction, it predicts a 7-DoF end-effector action (dx, dy, dz, droll, dpitch, dyaw, gripper).
- How: a fused DINOv2 + SigLIP vision backbone feeds a LLaMA-2-7B language model. The action comes out as extra vocabulary tokens and is decoded back through a bin mapping.
- Hardware: a 2-chip Blackhole P300 mesh.
- Status: Experimental.
- On 2 of 4 test inputs, the action tokens match the Hugging Face reference exactly.
- On the other two, the largest action difference is 0.045, from bf16 numerical drift.
- No task-success evaluation has been run (see Limitations).
- Code: tsingletaryTT/tt-openvla.
Packaged and published with tt-model-manager as a v6 thin bundle (manifest schema 6): a pip/venv install, not a container.
No checkpoint is hosted here. Weights are fetched at install or serve time from openvla/openvla-7b at a pinned revision, unconverted. The two vision towers, the projector and LLaMA-2-7B all load from that checkpoint's own tensors.
At a glance
| Architecture | About 7B parameters, in this order: 1. DINOv2 ViT-L/14 (4 register tokens) + SigLIP ViT-So400M/14, at 224 px 2. 3-layer GELU projector (2176 → LLaMA dim) 3. 32-layer LLaMA-2-7B, reusing tt-metal tt_transformers attention, RoPE and KV cache |
| Hardware | p300: 2 Blackhole chips, tensor-parallel 1×2 mesh. One chip overflows L1 in decode |
| Input / output | In: one RGB image (224 × 224 after preprocessing) and one instruction. Out: one 7-DoF action, from 7 generated tokens (1 prefill + 6 decode) |
| License | Port code MIT · vendored tt-metal code Apache-2.0 · weights MIT per upstream. The LLaMA backbone is a Llama 2 derivative, so the Llama 2 Community License and AUP apply. See Licensing |
| Status | Experimental |
| Model CI v0 | not yet run |
Intended use
Direct use: research and bring-up evaluation of OpenVLA inference on Tenstorrent hardware:
- interactive exploration through the Gradio UI;
- offline or simulated action prediction through
POST /act, for comparison against GPU OpenVLA.
Out-of-scope use:
- Driving a physical robot without an independent safety layer, meaning workspace limits, velocity and force clamps, an e-stop and a human supervisor. This port hasn't been evaluated on any robot or simulator task, and its actions aren't bit-identical to the reference model (see Limitations).
- Robot embodiments or scenes outside the Open X-Embodiment pretraining mix. Upstream states that OpenVLA doesn't generalize zero-shot to unseen embodiments or setups, and recommends fine-tuning instead.
- Any use the Llama 2 Acceptable Use Policy prohibits. That includes the §2 activities that present a risk of death or bodily harm, for example "Operation of critical infrastructure, transportation technologies, or heavy machinery".
- Safety-critical, medical, or unsupervised deployment of any kind.
Quickstart
uv tool install tenstorrent # once: the Tenstorrent CLI, `tt`
tt model pull episod/tt-openvla
tt serve episod/tt-openvla
tt model pull installs the v6 thin bundle: a hermetic uv venv with a pinned Python 3.12, ttnn==0.77.0, and the two bundled wheels.
- Weights: it also fetches
openvla/openvla-7bat revision47a0ec7(about 15 GB) into the bundle's own HF cache (<install>/.hf). Weights come down by default;tthas no--with-weightsflag. - Port:
servestarts uvicorn on port 20000, or the next free port. The Gradio UI is at/and the REST endpoint at/act. - Readiness: the mesh open and model load run in the FastAPI lifespan, so uvicorn's
Application startup complete.means the backend is ready.
Measured on a fresh install (one P300c board, 2026-09-27):
| step | time |
|---|---|
| Launch to ready, weights already pulled | 55 s, which includes building the on-disk tensor cache |
| Launch to ready, weights not pulled | 6.5 min: the server downloads the 15 GB itself, at the same pinned revision |
First /act on a fresh install |
55 s, most of it the one-time kernel compile |
| Later warm calls | about 0.33 s |
No other model repos are fetched, apart from a small NousResearch LLaMA-2 config/tokenizer that tt-metal's tt_transformers reads.
Without tt-cli, tt-model alone does the whole job:
tt-model pull episod/tt-openvla --with-weights
tt-model serve episod/tt-openvla
Serve profiles
| profile | hardware | mesh |
|---|---|---|
| p300 | P300 (2 Blackhole chips) | 1×2, MESH_DEVICE=P300, fabric 1D |
One prediction runs at a time. The Gradio UI is limited to concurrency_limit=1. /act requests and UI requests share one lock around the backend, so concurrent requests queue rather than run two decode loops on the mesh.
Using it
Not an OpenAI-compatible API. A continuous action vector isn't natural-language output, so there is no /v1/chat/completions.
Gradio UI (/)
- Upload an image, or use the bundled example.
- Write an instruction.
- Choose the unnormalization dataset (default
bridge_orig). - Click Predict action.
The UI shows the decoded 7-DoF action, the raw generated action-token IDs, and the measured latency.
REST: POST /act
The request is the one openvla/openvla's reference server (vla-scripts/deploy.py) takes:
- Plain form: a json-numpy-encoded
{image, instruction, unnorm_key?}. - Encoded form:
{"encoded": ...}.
Both build the same prompt as upstream: In: What action should the robot take to {instruction.lower()}?\nOut:, plus the empty token upstream's predict_action appends.
It differs from upstream in three ways:
- Response shape:
{"action": [...]}, where upstream returns the bare array. Upstream clients that doaction = resp.json()needresp.json()["action"]. - Default
unnorm_key:bridge_orig. Upstream defaults toNone, which errors for openvla-7b. - Errors: a malformed payload, including a non-string
instruction, returns HTTP 400 with adetailmessage. Upstream returns 200 with an"error"field.
import json_numpy; json_numpy.patch() # pip install json-numpy
import requests, numpy as np
from PIL import Image
image = np.array(Image.open("my_photo.jpg").convert("RGB")) # uint8 HxWx3
resp = requests.post("http://localhost:20000/act", json={
"image": image,
"instruction": "pick up the remote control",
# "unnorm_key": "bridge_orig", # optional; any key in openvla-7b config.json norm_stats
})
action = resp.json()["action"] # 7 floats: dx, dy, dz, droll, dpitch, dyaw, gripper
- Shared backend: the route reuses the backend the UI loaded, so there's no second model load.
- Determinism: decoding is greedy. Repeated and concurrent requests for the same input returned identical actions.
From the GitHub repo
gradio_app/app.pyruns the same demo directly: on Blackhole by default (hold a 2-chip lease first), or on CPU with--backend reference.tt/hf_reference.pyruns the upstream HFAutoModelForVision2Seqreference that the accuracy numbers below were measured against.
Expected performance
| stage / metric | vs reference | result |
|---|---|---|
| DINOv2 featurizer (openvla-7b weights) | PCC vs the CPU reference with the same weights | 0.99979 (0.99978-0.99988 across 4 inputs on the served 1×2 mesh) |
| SigLIP fused featurizer (openvla-7b weights) | same | 0.99975 (0.99972-0.99980) |
| Vision backbone + projector | same | 0.99970 (0.99961-0.99976) |
| Prefill logits | PCC vs HF AutoModelForVision2Seq (fp32, rev 47a0ec7) |
0.994-0.995 on 4 inputs |
| 7 greedy action tokens, end to end | exact match vs HF AutoModelForVision2Seq + predict_action |
7/7 on 2 of 4 inputs; 5/7 and 1/7 on the other two |
| Decoded action | max |Δ| vs HF, per input | 0.0 / 0.0 / 0.0107 / 0.0451 |
| Task success (BridgeData V2 / LIBERO) | none | not measured |
/act latency, warm, installed bundle |
none | median 334.2 ms (min 328.8, max 412.9), N=20; about 3.0 actions/s for one sequential client |
4 concurrent /act requests |
none | all 4 returned HTTP 200 and queued, finishing within 1.35 s in total. The actions were identical to the sequential call |
First /act on a fresh install (kernel JIT) |
none | 55 s, once per install |
Methodology: the latency and concurrency figures come from HTTP POST /act on one P300c board (2 Blackhole chips, 1×2 mesh, ttnn==0.77.0 from PyPI). The request was the bundled gradio_app/assets/example.jpg with "open the drawer" and unnorm_key=bridge_orig, json-numpy encoded, measured client-side. There was one untimed first call, then N=20 sequential warm calls and one 4-way concurrent burst. The run used this bundle revision (weights 47a0ec7) on 2026-09-27.
The accuracy rows come from a separate run on the same board against the upstream HF reference on CPU (timm 0.9.16, transformers 4.40.1, greedy). It used four image/instruction pairs: the repo's example image with "open the drawer", a house in a field with "move forward", a dog with "Pick up the ball", and a synthetic orange image with "move forward". The script is tt/hf_reference.py in the GitHub repo.
Why the remaining token mismatches look like precision, not a bug:
- Per layer: all-token hidden-state PCC against HF is at least 0.99995 at every layer. Last-token PCC decays smoothly, from 0.9999 at layer 0 to about 0.997 at layer 30, with no step change.
- Where the flips happen: at low-margin steps. HF's own bf16 run flips a token on one of the same inputs.
- Not tried yet: precision options such as an fp32 LM head or HiFi4 decode.
Limitations
- Actions are close to the reference, not identical to it.
- Two of the four test inputs match HF token for token. On the other two, the first divergent token appears at a low-margin step, and the decoded action differs by at most 0.045.
- Outputs are deterministic run to run, but a different input can land on a different bin than GPU OpenVLA.
- No task-level evaluation. There are no BridgeData V2, LIBERO, or real-robot results. The hardware checks used four still images, not robot scenes or closed-loop control.
- The server binds
0.0.0.0with no authentication. - One validated topology: P300, 2 chips. A single chip doesn't fit (L1 overflow in decode).
/actis serialized. Throughput does not scale with concurrent clients: four simultaneous requests finish one after another.- Chatty logs. tt-metal logs a
Failed to discover available ethernet linkswarning hundreds of times per/actcall. It is harmless, but it makes the log grow fast.
Risks and safety considerations
Physical harm. The model outputs end-effector deltas and a gripper command. If a robot executes them without independent limits, wrong actions can:
- move it unexpectedly;
- collide with people or objects;
- drop or crush what it's holding.
Nothing in this package bounds, filters, or sanity-checks actions. Keep a safety layer and an e-stop in the loop, and prefer simulation.
Silent numerical deviation. On some inputs this port produces a different action than the reference (see Limitations), and the output looks plausible either way. A wrong action and a right one are indistinguishable from the response alone.
Out-of-distribution inputs. For scenes, cameras, or embodiments outside the training mix, OpenVLA still returns a confident-looking action rather than refusing.
Unnormalization choice. Actions are unnormalized with per-dataset statistics (
unnorm_key). The wrong key for your robot rescales every motion, and the server defaults silently tobridge_orig.Network exposure. An unauthenticated
/acton0.0.0.0lets anyone on the network issue actions to a connected control loop. Bind to localhost, or put it behind a proxy with auth.Supply chain. The processor loads with
trust_remote_code=True, which executes the upstream repo's Python at serve time. That code is pinned to revision47a0ec7, the same revision as the weights.Pass these risks on. The Llama 2 AUP (§4) requires disclosing known dangers of your AI system to end users. If you build on this package, pass these risks along.
Licensing
| component | license | notes |
|---|---|---|
Port code (tt_openvla_serving wheel, GitHub repo) |
MIT | matches openvla/openvla |
Vendored tt-metal models/common + models/tt_transformers (tt_openvla_models_closure wheel) |
Apache-2.0 | upstream tenstorrent/tt-metal v0.77.0 |
openvla/openvla-7b weights |
MIT (upstream declaration) | The LLaMA-2-7B backbone is a Meta Llama 2 derivative, so the Llama 2 Community License and Acceptable Use Policy also apply: the 700M-MAU clause, the redistribution notice and the prohibited uses. The card's license: other reflects those terms |
This repo hosts no weights. Downloading and running openvla-7b is between you and its licensors.
Changelog
| date | change |
|---|---|
| 2026-09-27 | tt-openvla-serving 0.2.1 (0.2.0 plus two review fixes: a 400 for a non-string /act instruction, and a separate converted-weight cache per local checkpoint). See the list below this table. |
| 2026-09-15 | tt-openvla-serving 0.1.1: added the POST /act REST endpoint. The 0.1.0 wheel was removed |
| 2026-09-15 | First v6 thin bundle (tt-model package-thin), serving on the tt-dit-server kind alongside the existing card |
| 2026-09-10 | Model card published (code and weights pointers only) |
The 2026-09-27 release (tt-openvla-serving 0.2.1):
- Vision towers: now loaded from openvla-7b's own checkpoint tensors, replacing timm's generic pretrained weights.
- Fresh installs: weights are downloaded at the pinned revision
47a0ec7. This fixes the "expected 3 safetensors shards, found []" startup failure. - Two RoPE bugs fixed, which had masked each other:
- a missing RoPE initialization in the CPU reference;
- the wrong rotation pairs in TTNN.
- Prompt: the empty token upstream's
predict_actionappends is now added. /act:- serialized, so a 4-way concurrent burst no longer hangs the server;
- lowercases the instruction;
- accepts
{"encoded": ...}; - returns 400 on a bad payload.
- Manifest: records the weights revision and
tt_metal_version0.77.0. - Re-verified on hardware from a fresh install with an empty HF cache: 334 ms warm
/act, 4/4 concurrent requests OK.
Related packages
None.
Feedback
- Questions or problems with this package: open a discussion at https://huggingface.co/episod/tt-openvla/discussions. That is the one channel that reaches the bundle's author.
- A problem with the
tttooling itself: usett report issue. It files against tenstorrent/tt-cli and doesn't reach this package's author. - Product feedback: [email protected].
Provenance
| component | built from |
|---|---|
| tt-metal | ttnn==0.77.0 (PyPI), plus a vendored models/ closure from tt-metal v0.77.0 source. The manifest tt_metal_version is 0.77.0 |
| runtime | kind tt-dit-server: gradio_app.asgi:app under uvicorn, with gradio 5.40.0, transformers 4.52.4, torch 2.14.0+cpu |
| weights | openvla/openvla-7b @ 47a0ec7fc4ec123775a391911046cf33cf9ed83f, pinned in the manifest and the code |
| port code | tsingletaryTT/tt-openvla @ b056100, built by scripts/build-serving-wheel.sh |
| wheels (sha256, first 16) | tt_openvla_serving-0.2.1 32b600f81db3e156 · tt_openvla_models_closure-0.77.0 47b658fb62c30ffc |
| Model CI v0 | not yet run |
| build | 2026-09-27 · tt-model-manager (producer tt_kernel_version 0.1.0) |
Model tree for episod/tt-openvla
Base model
openvla/openvla-7b