TurboVLA: LIBERO (GGUF for vla.cpp)
This is a GGUF conversion of the LIBERO checkpoint from H-EmbodVis/TurboVLA
(paper, code). It runs on
vla.cpp, a lightweight C++ inference engine for
Vision-Language-Action models built on top of llama.cpp.
TurboVLA has no LLM. A DINOv3 ViT-B/16 encodes each of the two cameras, and BERT-base encodes the instruction. Six Grounding-DINO bi-attention fusion layers exchange information between the two, each followed by a text enhancer. A three-layer ACT decoder then reads the fused tokens plus two state tokens and emits a 12×7 action chunk in one forward pass, with no denoising loop. The vision tower, the text encoder and the decoder are all in the single GGUF, so there is no mmproj file. Only the BERT tokenizer runs outside the engine.
Source checkpoint
H-EmbodVis/TurboVLA @ cb53005 checkpoints/libero/turbovla_libero.pth
This is one policy trained on all four LIBERO suites (spatial, object, goal, 10). The repo's RoboTwin checkpoint is not converted.
Update (2026-09-30): re-converted to include DINOv3's final LayerNorm (vit.norm), which the checkpoint
was trained through and the first conversion left out. vla.cpp from
PR #32 on applies it and refuses a GGUF without it. Older
vla.cpp builds still load this file but skip the norm.
Files
| File | Size | Description |
|---|---|---|
turbovla-libero-f32.gguf | 822 MiB | Complete model, F32: DINOv3 ViT-B/16 + BERT-base + fusion + ACT decoder, plus the per-instruction text padding table |
DINOv3_LICENSE.md | The license these weights are distributed under (see License) |
Geometry
| Vision | DINOv3 ViT-B/16 with 4 registers and axial RoPE, 2 views (agentview, wrist) at 256×256, 256 patches each |
| Language | BERT-base-uncased, padded to 11, 14 or 21 tokens (see below) |
| Fusion | 6 bi-attention + text-enhancer layers at hidden 256 |
| Action | ACT decoder, 3 layers, 12 learned queries → horizon 12 × dim 7, tanh-bounded |
| State | 8-D (eef pos, axis-angle, gripper qpos), mean/std normalized |
Text padding is part of the model. The action decoder attends to the padded text rows as well, so the length an instruction is padded to changes the actions. The checkpoint pads each of its 40 training instructions to the length its training batch used: 11, 14 or 21 tokens. This GGUF stores that table, keyed by token ids, so pass unpadded BERT ids and vla.cpp picks the length. Instructions not in the table are padded to 21.
Usage
cmake -S . -B build -DGGML_CUDA=ON && cmake --build build -j
# one-shot: --text is tokenized by scripts/tokenize_prompt.py (needs transformers)
./build/vla-cli -hf vrfai/turbovla-libero-gguf \
--image agentview.png --image wrist.png \
--text "put the bowl on the plate" \
--state <8 floats, already normalized> --pretty
# or serve it
./build/vla-server turbovla-libero-f32.gguf
Images are 256×256 RGB, rotated 180° as LIBERO renders them (img[::-1, ::-1]). The engine returns the 12×7
chunk normalized to [-1, 1]. The client does the rest, following the release's evaluation protocol:
- state:
(s - mean) / (std + 1e-6) - arm dims 0-5:
(a + 1) / 2 * (max - min) + min - gripper dim 6:
-1ifa < 0, else+1
The statistics come from libero_all4_stats.json in the upstream repo, under the libero_all4_no_noops key. The
vla.cpp LIBERO client (eval/client, --arch turbovla) does all of this and downloads the stats itself.
Precision options: --weight-dtype bf16 --flash-attn is the fastest configuration on CUDA and Metal. On the
OpenVINO CPU plugin, leave --flash-attn off.
Fidelity
With the final norm applied, CPU F32 matches upstream fp32 PyTorch to 9e-6. The table below was measured on
the first conversion, before vit.norm was added, and has not been re-measured.
These numbers are the max |Δ| of the normalized action chunk against upstream TurboVLA running in fp32 PyTorch, over four seeded cases. The cases cover one instruction from each padding group plus one instruction the checkpoint does not list.
| Backend | F32 (default) | --weight-dtype bf16 |
|---|---|---|
| CPU | 9.3e-6 | 3.7e-2 |
| CUDA | 3-5e-3 | 3-4e-2 |
| Metal | 1.9e-3 | 4.5e-2 |
| SYCL | 8.8e-6 | 4.0e-2 |
OpenVINO CPU / iGPU (GGML_OPENVINO_GPU_PRECISION=f32) | 1.6e-6 / 4.6e-6 | – |
For scale, upstream's own release precision (the whole model in bf16) is up to 3.4e-2 from fp32 on the same cases. Running on the NPU through OpenVINO gives wrong actions, so don't use it.
Reproducing this conversion
python scripts/convert_turbovla_to_gguf.py checkpoints/libero/turbovla_libero.pth \
-o turbovla-libero-f32.gguf --verify
The converter needs torch, transformers (for the BERT tokenizer used to key the padding table) and the
gguf package. It does not need access to the gated DINOv3 repository.
License
The weights are fine-tuned from Meta's DINOv3, and upstream releases them under the DINOv3 License. This repository redistributes them in GGUF form under that same license and includes a copy of it, as the license requires. The TurboVLA code is Apache-2.0. Cite the TurboVLA paper if you use this model.