rlx-voxtral
Mistral Voxtral speech LM for RLX: a Whisper-style audio encoder + 4× projector + Llama text decoder, with a fully native Rust/RLX frontend (Whisper log-mel + Mistral transcription template) — no Python runtime. Audio and text embeddings are fused additively at audio_token_id placeholders before the Llama trunk runs.
Quick start
# Native transcription: 16 kHz mono WAV → text (log-mel + prompt built in Rust)
# or via the just recipe
Weights: HuggingFace mistralai/Voxtral-Mini-3B-2507 (safetensors with audio_tower.*, multi_modal_projector.*, language_model.* tensors) plus its config.json and tokenizer.
CLI flags (src/cli.rs)
| Flag | Purpose |
|---|---|
--weights PATH |
Model safetensors (required) |
--wav PATH |
16 kHz mono WAV (required unless --dry) |
--transcribe |
Native log-mel + Mistral transcription prompt |
--prompt-ids 1,24,… |
Explicit prompt token ids (when not --transcribe) |
--lang / --language |
Transcription language |
--config PATH |
Override config.json |
--device cpu|metal|mlx|cuda|… |
Backend |
--max-tokens N |
Decode cap (default 256) |
--dry |
Load + validate config, skip forward |
Public API
use ;
use Device;
let runner = builder
.weights
.device
.max_new_tokens
.build?;
// End-to-end native transcription (WAV → token ids)
let tokens = runner.transcribe_wav?;
let text = decode_token_ids?;
# Ok
Lower-level entry points are also public: runner.encode_audio(&mel) (encoder + projector → audio embeds), runner.generate(prompt_ids, &mel) (fused prefill + bucketed KV decode), and the graph builders build_voxtral_encoder_built, build_voxtral_projector_built, build_voxtral_prefill_built, build_voxtral_decode_built. Audio helpers: pcm_to_mel, mel_from_flat, MelSpectrogram, fuse_inputs_embeds, transcription_prompt_ids.
How it fits
| Piece | Crate |
|---|---|
| Whisper-style audio encoder | rlx-whisper |
| Llama text decoder trunk | rlx-llama32 |
| Compile / device routing | rlx-runtime |
The decoder compiles the prefill and a single fixed-cap decode graph once, then serves every past_len by padding the KV cache to the bucket and masking padded slots (bucket_decode_mask + compact_bucketed_kv_buffer) — no per-token recompile.
Tests