d3-flash 8B Mirai-M
- Vendor
- vLLM Semantic Router
- Quantization
- Mirai-M
- Parameters
- 8B
- Size
- 4.3 GB
$ brew install mirai$ mirai --model trymirai/d3-flash-MIntegrate with SDK
https://github.com/trymirai/uzu
| 1 | import Foundation |
| 2 | import Uzu |
| 3 | |
| 4 | public func runChat() async throws { |
| 5 | let engineConfig = EngineConfig.create() |
| 6 | let engine = try await Engine.create(config: engineConfig) |
| 7 | |
| 8 | guard let model = try await engine.model(identifier: "trymirai/d3-flash-M") else { |
| 9 | return |
| 10 | } |
| 11 | for try await update in try await engine.download(model: model).iterator() { |
| 12 | print(String(format: "\r\u{001B}[2KDownload progress: %.2f%%", update.progress() * 100), terminator: "") |
| 13 | fflush(stdout) |
| 14 | } |
| 15 | print() |
| 16 | |
| 17 | let messages = [ |
| 18 | ChatMessage.system().withText(text: "You are a helpful assistant"), |
| 19 | ChatMessage.user().withText(text: "Tell me a short, funny story about a robot") |
| 20 | ] |
| 21 | let session = try await engine.chat(model: model, config: .create()) |
| 22 | let stream = await session.replyWithStream(input: messages, config: .create()) |
| 23 | var message: ChatMessage? = nil |
| 24 | for try await update in stream.iterator() { |
| 25 | switch update { |
| 26 | case .replies(let replies): |
| 27 | let reply = replies.last |
| 28 | message = reply?.message |
| 29 | print("Generated tokens: \(reply?.stats.tokensCountOutput ?? 0)") |
| 30 | case .error(let error): |
| 31 | print("Error: \(error)") |
| 32 | case .toolResults: |
| 33 | break |
| 34 | } |
| 35 | } |
| 36 | print("Reasoning: \(message?.reasoning() ?? "empty")") |
| 37 | print("Text: \(message?.text() ?? "empty")") |
| 38 | } |
| 39 | |
TypeScript
pnpm add typescript ts-node @types/node -D pnpm add @trymirai/uzu
import {
ChatConfig,
ChatMessage,
ChatReplyConfig,
ChatSessionStreamChunkError,
ChatSessionStreamChunkReplies,
Engine,
EngineConfig
} from '@trymirai/uzu';
async function main() {
let engineConfig = EngineConfig.create();
let engine = await Engine.create(engineConfig);
let model = await engine.model('trymirai/d3-flash-M');
if (!model) {
throw new Error('Model not found');
}
for await (const update of await engine.download(model)) {
process.stdout.write(`\rDownload progress: ${(update.progress * 100).toFixed(2)}%`);
}
console.log();
let messages = [
ChatMessage.system().withText('You are a helpful assistant'),
ChatMessage.user().withText('Tell me a short, funny story about a robot')
];
let session = await engine.chat(model, ChatConfig.create());
let stream = await session.replyWithStream(messages, ChatReplyConfig.create());
let message: ChatMessage | undefined;
for await (const chunk of stream) {
if (chunk instanceof ChatSessionStreamChunkReplies) {
message = chunk.replies[0]?.message;
console.log('Generated tokens: ', chunk.replies[0]?.stats.tokensCountOutput);
} else if (chunk instanceof ChatSessionStreamChunkError) {
console.error('Error: ', chunk.error);
}
}
console.log('Reasoning: ', message?.reasoning);
console.log('Text: ', message?.text);
}
main().catch((error) => {
console.error(error);
});
Python
uv add uzu
import asyncio
from uzu import (
ChatConfig,
ChatMessage,
ChatReplyConfig,
ChatSessionStreamChunk,
Engine,
EngineConfig,
)
async def main() -> None:
engine_config = EngineConfig.create()
engine = await Engine.create(engine_config)
model = await engine.model("trymirai/d3-flash-M")
if model is None:
raise RuntimeError("Model not found")
async for update in (await engine.download(model)).iterator():
print(f"\rDownload progress: {update.progress:.2%}", end="", flush=True)
print()
messages = [
ChatMessage.system().with_text("You are a helpful assistant"),
ChatMessage.user().with_text("Tell me a short, funny story about a robot"),
]
session = await engine.chat(model, ChatConfig.create())
stream = await session.reply_with_stream(messages, ChatReplyConfig.create())
message: ChatMessage | None = None
async for chunk in stream.iterator():
if isinstance(chunk, ChatSessionStreamChunk.Replies):
replies = chunk.replies
if replies:
reply = replies[0]
message = reply.message
print(f"Generated tokens: {reply.stats.tokens_count_output}")
elif isinstance(chunk, ChatSessionStreamChunk.Error):
print(f"Error: {chunk.error}")
if message is not None:
print(f"Reasoning: {message.reasoning}")
print(f"Text: {message.text}")
if __name__ == "__main__":
asyncio.run(main())
Rust
cargo add uzu --git https://github.com/trymirai/uzu cargo add tokio --features full
use std::io::{self, Write};
use uzu::{
engine::{Engine, EngineConfig},
session::chat::ChatSessionStreamChunk,
types::session::chat::{ChatConfig, ChatMessage, ChatReplyConfig},
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let engine_config = EngineConfig::default();
let engine = Engine::new(engine_config).await?;
let model = engine.model("trymirai/d3-flash-M".to_string()).await?.ok_or("Model not found")?;
let downloader = engine.download(&model).await?;
while let Some(update) = downloader.next().await {
print!("\r\u{001B}[2KDownload progress: {:.2}%", update.progress() * 100.0);
io::stdout().flush()?;
}
println!();
let messages = vec![
ChatMessage::system().with_text("You are a helpful assistant".to_string()),
ChatMessage::user().with_text("Tell me a short, funny story about a robot".to_string()),
];
let session = engine.chat(model, ChatConfig::default()).await?;
let stream = session.reply_with_stream(messages, ChatReplyConfig::default()).await;
let mut last_message: Option<ChatMessage> = None;
while let Some(chunk) = stream.next().await {
match chunk {
ChatSessionStreamChunk::ToolResults {
..
} => {},
ChatSessionStreamChunk::Replies {
replies,
} => {
if let Some(reply) = replies.first() {
last_message = Some(reply.message.clone());
println!("Generated tokens: {}", reply.stats.tokens_count_output.unwrap_or_default());
}
},
ChatSessionStreamChunk::Error {
error,
} => {
println!("Error: {error}");
},
}
}
if let Some(message) = last_message {
println!("Reasoning: {}", message.reasoning().unwrap_or_default());
println!("Text: {}", message.text().unwrap_or_default());
}
Ok(())
}
Details
A compact, 4-bit text decision model built from d3-flash, returning probabilities for choice, yes/no and scoring questions.
INT4 asymmetric · G64 · BF16 scales · RHT32 · YAQA · No training
Held-out evaluation
On 5,500 CLINC150+OOS text decisions, d3-flash-M reaches 95.45% accuracy, 0.38 percentage points below the unquantized teacher. YAQA reduces mean teacher KL by 8.4× relative to RTN.
| Checkpoint | Accuracy ↑ | Macro F1 ↑ | Teacher KL ↓ |
|---|---|---|---|
| Unquantized d3-flash teacher | 95.84% | 96.43% | 0.00000 |
| INT4 G64 · RTN | 93.40% | 93.82% | 0.07883 |
| d3-flash-M · YAQA | 95.45% | 96.13% | 0.00938 |
Every checkpoint was evaluated on the same requests with the same option masks at temperature 1. CLINC150+OOS was held out entirely from calibration. These are the measurements for this quantized text checkpoint; the original model's broader evaluations are retained in the upstream documentation below.
Quantization method
| Component | Format |
|---|---|
| Weights | 4-bit asymmetric integers |
| Group size | 64 |
| Zero points | Packed 4-bit integers |
| Quantization scales | BF16 |
| Outlier handling | Block-diagonal Random Hadamard Transforms, block size 32 |
| Transformer matrices | YAQA post-training quantization |
| Input embedding and decision head | Round-to-nearest |
The checkpoint was prepared with basic YAQA, without weight training or quantization-aware distillation. Calibration used 3,072 text decisions, balanced across 12 public Decision Index datasets covering reasoning, natural-language inference, intent classification, retrieval, tool selection and humor.
Fisher targets were sampled from the frozen, unquantized teacher at temperature 1. Each request contributed one masked decision output, with gradients through the full input context.
Model format and runtime
This release uses a DecisionModelConfig in config.json. Its decision object contains the 255 ordered answer codes and softmax temperature (1.0), taken from the original model. A single model.safetensors contains both the INT4 G64 text backbone and the FP32 255 × 4096 decision readout at decoder.embedding.output_embedding.weights, with a FullPrecisionSpec annotation. The readout materializes the effective RTN weights, including their quantization and Hadamard transform. The backbone retains its packed weights and annotations.
Decision inputs
The chat template renders one question per request. In the checkpoint directory:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(".")
request = {
"state": "Please cancel my subscription.",
"question": {
"type": "choice",
"criteria": {"cancel": None, "support": None},
},
}
token_ids = tokenizer.apply_chat_template(
[{"role": "user", "content": request}], tokenize=True, return_dict=False,
)state accepts text or JSON. Questions support choice (an ordered criteria mapping, up to 255 options), noul (false/true, with optional descriptions), and score (2–10 ordered levels). Optional instructions replace the default question text. The template always includes the decision prefix; each question needs its own full-context prefill.
For Uzu integration, dispatch on type: "DecisionModelConfig", reuse the Qwen3.5 layers and M matrix loaders, read tokenizer.json directly, and pass the structured request to MiniJinja. Read answer order and softmax temperature from decision.codes and decision.temperature. The input vocabulary and decision head have different sizes: the head has one row per code. Retain FP32 readout math. The standalone Jinja includes the same code order for Hugging Face tokenization; no separate decision_config.json is needed.
A decision runtime runs one full-context prefill per question, applies the FP32 readout to the final RMS-normalized hidden state at the last real prompt position, masks unused options and applies temperature-1 softmax. Preserve option order. Choice returns the highest-probability option; yes/no returns the probability of true; score returns the expected level index. The eight full-attention layers remain noncausal; the 24 Gated DeltaNet layers remain causal.
Quantized inference was validated with clean_quant's DecisionModel.load. The upstream Transformers and Semantic Router examples below describe the original model and its multimodal serving interfaces.
Citation
@misc{mirai-quant,
title = {{Mirai Quantization}: Redefining the speed-quality frontier for local LLMs on Apple silicon},
author = {Artur Chakhvadze and Ryan Mathieu and Roman Knyazhitskiy and Nikolai Voinilenko and Chen-Chen Yeh and Artur Mullakhmetov and Eugene Bokhan and others},
note = {In collaboration with others at Mirai Labs},
month = {June},
year = {2026},
url = {https://trymirai.com/blog/quantization}
}Original d3-flash model card
The original card is reproduced below from vllm-sr/d3-flash, revision 13dc1cc8ef23caefa765907e1d764751e5629f7d. Its upstream features, serving instructions and benchmark results refer to the original, unquantized model, including its vision encoder.
Read the original d3-flash documentation

d3-flash is the 9B multimodal foundation decision model of Decision 3.0, the decision models of vLLM Semantic Router. Give it an input (text or JSON, optionally with images and videos) and the questions you need answered: pick one of several options, say yes or no, or rate on a scale. It answers them all in one call and returns a probability for every answer, without generating text.
Try it free at decision.vllm-sr.ai · Official Space: vllm-sr/Decision-3.0
| Parameters | 8.39B, including the 0.46B vision encoder |
| Inputs | Text or JSON, plus images and videos (several per request) |
| Decision types | Choice · Yes / No · Score |
| License | Apache-2.0 |
Highlights
- Jev Decision Index 0.3.2: 55.8 on the official text board, first among the entries of similar size and on the board's Pareto frontier; public suite 57.79, measured with the official 0.3 kit on the released weights: all 140,178 public requests answered, none unsupported.
- +7.6 over Decision 2.0 (its 9B model: 48.2 on the official board): +11.0 on the public suite, ahead in all five of its areas, and +7.3 and +4.2 on the private same-skill tests and new-domain tasks.
- Reads images: multiple images per request (PNG, JPEG or WebP), given as paths, URLs, PIL images or base64 data URLs; every question of the request sees all of them.
- Reads videos: multiple videos per request (MP4, WebM, MOV or MKV), given as paths, URLs, base64 data URLs or frame arrays, read at 2 frames per second; Perception Test (multiple-choice video QA): 73.3 (internal evaluation).
- Speed: a median of 19.2 ms for a text request, 166 ms for a request with an image and 371 ms for a request with a 10-second video, on one AMD Instinct MI325X GPU, one request at a time with AutoModel.
- Serving: 95 requests/s with p99 under 1 s on one AMD Instinct MI325X with vLLM Semantic Router, 1.8× AutoModel (one-question text, with batching, now the default); with one request per forward, the former default, it gave answers identical to AutoModel's (Serving performance).
- Many questions, one call: Choice, Yes / No and Score questions about the same input are answered together, each from its own forward pass over the input, with a probability for every option.
Quickstart
pip install "transformers==5.17.0" torch torchvision pillow opencv-python-headless safetensors accelerate
pip install flash-linear-attention # optional: fast GPU kernels for the linear-attention layersimport json
from huggingface_hub import hf_hub_download
from transformers import AutoModel
model = AutoModel.from_pretrained("vllm-sr/d3-flash", trust_remote_code=True)
# Text
result = model.system_one(
state="The order arrived damaged yesterday. The customer has a receipt and asks for a replacement today.",
questions={
"route": {
"type": "choice",
"instructions": "Which team should handle this request?",
"criteria": {
"returns": "Refunds, replacements and damaged deliveries",
"billing": "Payments, invoices and charges",
"technical": "Product setup and faults"
}
},
"receipt": {
"type": "noul",
"instructions": "Does the customer have a receipt?"
},
"urgency": {
"type": "score",
"instructions": "How urgent is this request?",
"criteria": [
"Routine",
"Soon",
"Today"
]
}
},
)
print(json.dumps(result["answers"], indent=2))
# Text and an image (or several)
receipt = hf_hub_download("vllm-sr/d3-flash", "assets/example-receipt.png")
result = model.system_one(
state="The customer says the blender arrived cracked and attached the receipt.",
images=[receipt], # local paths, http(s) URLs, PIL images or base64 data URLs
questions={
"route": {
"type": "choice",
"instructions": "Which team should handle this request?",
"criteria": {
"returns": "Refunds, replacements and damaged deliveries",
"billing": "Payments, invoices and charges",
"technical": "Product setup and faults"
}
},
"on_receipt": {
"type": "noul",
"instructions": "Does the receipt list the blender?"
},
"payment": {
"type": "choice",
"instructions": "How was the order paid?",
"criteria": {
"card": None,
"cash": None,
"gift card": None
}
}
},
)
print(json.dumps(result["answers"], indent=2))
# Text and a video (or several)
clip = hf_hub_download("vllm-sr/d3-flash", "assets/example-video.mp4")
result = model.system_one(
state="A short clip from a test camera.",
videos=[clip], # local paths, http(s) URLs, base64 data URLs or frame arrays
questions={
"direction": {
"type": "choice",
"instructions": "Which way does the square move?",
"criteria": {
"right": "From left to right",
"left": "From right to left",
"still": "It does not move"
}
},
"color_change": {
"type": "noul",
"instructions": "Does the square change color?"
}
},
)
print(json.dumps(result["answers"], indent=2))
# Or as a pipeline:
# transformers.pipeline("decision", model="vllm-sr/d3-flash", trust_remote_code=True)(state=..., questions=..., images=..., videos=...)Images go before the text of the request, each read at up to 1.6 megapixels; videos follow the images, read at 2 frames per second (at most 32 frames spread over the whole video, each at up to 0.2 megapixels). Every question of the request sees all of them.
Run with vLLM Semantic Router
vLLM Semantic Router serves Decision 3.0 natively in Engine mode, which runs one model behind the System One API on your own machine. Its answers matched this package's own d3_server.py exactly, for text, images and videos, with one request per forward, the former default (measured on AMD Instinct MI325X); with batching, now the default, a few near-tie decisions change (Serving performance).
curl -fsSL https://vllm-sr.ai/install.sh | bash -s -- --channel dev --no-launch
export PATH="$HOME/.local/bin:$PATH"
vllm-sr serve -e vllm-sr/d3-flashThe platform is detected automatically; choose one with --platform auto|cpu|cuda|rocm (default auto).
Engine mode and Decision 3.0 are in the development channel (--channel dev) until a stable vLLM Semantic Router release includes them. The first start pulls the image for your machine and downloads the model. Then ask questions about a request:
curl -s localhost:8899/v1/systemone -H 'content-type: application/json' -d '{
"model": "vllm-sr/d3-flash",
"state": "Write a Python function that merges two sorted lists.",
"questions": {
"kind": {"type": "choice", "instructions": "What kind of work is this?",
"criteria": {"code": "Writing or fixing code", "math": "Mathematics", "chat": "Anything else"}},
"reasoning": {"type": "noul", "instructions": "Does answering this need multi-step reasoning?"}
}
}'Send images as base64 data URLs (PNG, JPEG or WebP) in images and videos (MP4, WebM, QuickTime or Matroska) in videos; every question sees them:
curl -s localhost:8899/v1/systemone -H 'content-type: application/json' --data-binary @- <Measured on 2026-10-13, one AMD Instinct MI325X per replica and each side alone on the GPU: ROCm 7.2 with PyTorch 2.12; AutoModel and the package server with Transformers 5.17.0; vLLM Semantic Router Engine mode built from source, with serving optimizations that are newer than its published images. Requests: a support ticket of about 330 tokens with 1, 4 or 16 questions; a 1,456 × 1,092 PNG (1.6 MP) with 1 or 8 questions; a 10-second 1080p MP4, read at 2 frames per second; a 32k-token text; every image and video request carries new bytes. One request at a time: 3 warm-up calls, then up to 20 timed calls. Under load: 1, 2, 4, … 64 closed-loop clients, each level warmed up and then counted for 15 to 60 s; Engine mode admits 32 requests in flight per worker and answers the rest at once with 503. Start-up: model files on local disk; Engine mode from the start of its router process, without the CLI's image pull.
## Evaluation
### Text: Jev Decision Index 0.3.2
| Model | Jev Decision Index ↑ | Public ↑ | Same-skill tests ↑ | New-domain tasks ↑ |
| --- | ---: | ---: | ---: | ---: |
| **d3-flash** | **55.8** | **57.8** | **54.6** | **48.6** |
| Bespoke Nimble 9B v3 | 54.7 | 57.2 | 52.3 | 49.1 |
| Sieve 9B Plus | 50.9 | 53.4 | 49.3 | 45.4 |
| Nace.AI Drex v1.5 | 49.7 | 58.1 | 49.7 | 39.7 |
| JevK5 9B v0.3.3 | 48.4 | 47.6 | 48.2 | 43.3 |
| Decision 2.0 (9B) | 48.2 | 46.8 | 47.4 | 44.5 |
On the official text board, d3-flash is on the Pareto frontier, where no entry with as many parameters or fewer scores higher, and ranks first among the 18 entries within 0.75 to 1.33 times its size. Five of the six Decision 3.0 models are on that frontier; d3-edge, second among the entries of its size, is not.


### Images: Jev Decision Index vision board 0.3.2
| Model | Vision Index ↑ | Public ↑ | Private ↑ |
| --- | ---: | ---: | ---: |
| **d3-flash** | **66.9** | **69.9** | **63.9** |
| JPT-9B | 61.7 | 65.4 | 58.0 |
| Winnow-E4B | 42.1 | 48.1 | 36.1 |
On the official vision board, the six Decision 3.0 models form the whole Pareto frontier: no other entry is on it. d3-flash ranks first among the three entries within 0.75 to 1.33 times its size, and fourth of 33 overall.
d3-flash on the public vision benchmarks († approximate rebuild):
| Benchmark | d3-flash |
| --- | ---: |
| CV-Bench | 75.2 |
| BLINK | 55.5 |
| RealWorldQA | 63.4 |
| CharXiv † | 83.0 |
| InfographicVQA † | 95.1 |
| Mind2Web † | 85.1 |
| Winoground | 83.3 |
| KIE (CORD+FUNSD) † | 96.3 |
| Moderation (Hateful Memes) | 41.3 |
| R-Bench-M | 20.8 |
| MMMU-Pro vision | 38.7 |
### Videos: Perception Test
| Perception Test (validation) | d3-flash |
| --- | ---: |
| **All 19,140 questions** | **73.3** |
| Memory | 75.5 |
| Abstraction | 68.7 |
| Physics | 66.1 |
| Semantics | 86.1 |
Multiple-choice video question answering (three options per question, chance 33.3): top-1 accuracy (%), videos read with the defaults above.
Jev Decision Index: official leaderboard, text and vision boards 0.3.2, 2026-10-12; the boards evaluated v3.1.0, which has the same weights as this version. Size bands and the Pareto frontier use the parameter counts listed by the boards. d3-flash on the public vision benchmarks and the Perception Test: internal evaluation.
## License
Apache-2.0 ([LICENSE](https://huggingface.co/trymirai/d3-flash-M/blob/main/LICENSE)). Built on [Qwen/Qwen3.5-9B](https://huggingface.co/Qwen/Qwen3.5-9B) (Apache-2.0).
## Citation
@misc{d3_flash_2026, title = {{d3-flash}: A Multimodal Foundation Decision Model}, author = {{vLLM Semantic Router Team}}, year = {2026}, howpublished = {\url{https://huggingface.co/vllm-sr/d3-flash}} }
Trained on AMD Instinct MI325X GPUs.