15 KiB
Qwen3.6-35B-A3B: pictures, several requests at once and speed
This page is for people who run Qwen3.6-35B-A3B (35B mixture of experts, 3B active, 256 experts, top-8) with Strata and want to know how pictures, parallel requests and the prompt chunk behave. For sizes and memory, see MODELS.md.
Strata runs Unsloth's GGUFs with the MTP layer
(UD-Q4_K_XL, UD-Q6_K_XL, Q8_0) on its own engine path (src/qwen35; strata switches to it from the file's
architecture). It runs on NVIDIA and AMD cards. The Intel Arc and Mac engines do not run it.
Quick start
Pick it in the app's model list, in strata launch or in setup:
strata launch --family qwen36 --model ud-q4_k_xl
strata launch --family qwen36 --model ud-q4_k_xl --vision on --parallel 2
| Option | Effect |
|---|---|
--vision on|off|gpu|cpu |
Terminal: pictures and which reader. Source setup uses --vision yes|no|gpu|cpu and --vision-tokens N. |
--parallel N |
How many requests are answered at once, 1 to 8. Source setup has the same option. |
--prefill 512 |
Engine flag in the config's args, not a strata launch option: the prompt chunk size (Prompt chunk). |
Pictures
The vision encoder is Unsloth's mmproj-F16.gguf (0.9 GB, pinned revision, SHA-256 checked by setup): a 27-layer ViT
with a 2x2 merger, the same projector family as the Flash-Next models' (qwen3vl_merger; only its output width
differs: 2048 here, 2560 there). Strata runs it with the same helper (strata-vision, llama.cpp's mtmd), so the
encoder choices are the Flash-Next ones (Images).
Pictures are on by default, by the same rule as for every model on NVIDIA and AMD cards:
- The CPU reader runs under 24 GB of graphics memory. It takes a few seconds per picture and no VRAM.
- The card's reader runs from 24 GB (GPU encoder, about 1.2 GB of VRAM at up to 1,024 image tokens). On AMD it is
strata-vision-hip, with the CPU reader as its fallback.
Your explicit choice is kept, with a note. A picture becomes up to 1,024 tokens of the context (the CPU default is 300).
The engine puts the encoder's rows where the prompt has <|image_pad|> tokens and gives every row its 2-D position,
interleaved M-RoPE as llama.cpp does for this model (rotary sections 11 / 11 / 10).
Pictures work with several requests at once, with the MTP drafts and with the conversation cache. The same conversation sent again continues from the held state when its pictures are the same (matched by the server's key for each picture). A different picture starts that conversation again.
Measured on: RTX 3060 12 GB (CUDA) and an R9700 box (32 threads) and a 3060 box (10 threads), UD-Q4_K_XL, Strata 0.1.45.
| Encoder | Time per picture (640x400 = 260 tokens / 800x450 = 350 tokens) | VRAM |
|---|---|---|
| GPU, RTX 3060 | 0.10 s / 0.15 s | 1,179 MiB at up to 1,024 tokens (1,337 MiB at 2,048), CUDA context included |
| CPU, 32 threads (R9700 box) | 0.9 s / 1.1 s | none |
| CPU, 10 threads (3060 box) | 1.6 s / 2.1 s | none |
Several requests at once
"parallel": N in the model's config (strata-qwen36-<size>.json) or --parallel N makes the engine keep N
conversations open (--batch N, up to 8) and decode them together. Every window carries one token of each
conversation, so the dense weights, the head and every routed expert that conversations share are read once for all of
them. The server speaks the same engine protocol as for Flash-Next (BATCHING.md).
A new request waits only for the read of its own prompt, which runs between the decode windows of the others. A
conversation continues in the slot that holds its start, or the engine copies the longer held start from another slot.
A request that runs alone (the solo path, with its MTP drafts) moves into a batch slot when another arrives, and back
when it is alone again, and gives the same text either way.
STRATA_Q36_READ_WINDOWS=4 lets the decoding slots produce more tokens during a long prompt read, at the cost of a
slower read. The default is one decode window per chunk.
MTP drafts. A request that has the engine to itself keeps its MTP drafts. With several requests the batch window has
no drafts unless --batch-mtp is in the config's args (opt-in). Each slot's window then also carries the proposal its
MTP block made, one per slot, up to the rows to spare in the 8-row window.
What a slot costs. Each slot keeps its own cache in VRAM: 1.5 GB at a context of 32K (fp32 keys and values of the 10 attention layers and the MTP block, 45 KB per token) and 0.07 GB of recurrent state. Setup writes the context for one conversation, so N slots take N times that from the room the experts would have. On a card that does not hold every expert (12 and 16 GB, the hybrid mode) the expert slots lose that room, and a batch reads more distinct experts from RAM.
Setup offers --parallel where the sessions do not eat the experts' room, says the price in the same breath and asks
"How many requests at once?" Enter keeps one at a time, and any number you give is kept.
| Card type | Slots setup offers |
|---|---|
| Every expert still fits | Up to 3 to 4 (3 on a 32 GB card at 32K) |
| Hybrid mode | The count whose sessions take at most 35% of the room the experts would have. 12 GB: 4 slots at 8K, none at 32K. 16 GB: 2 slots at 32K. |
The engine says what the sessions take before it allocates them and refuses, with the numbers, a count that cannot fit.
In the hybrid mode the text of a request can differ a little between running alone and running beside others. The
experts that sit on the GPU or on the CPU change with the routing of all the requests, and the two compute with
different rounding. With the slot tables fixed (STRATA_Q36_FIXED_SLOTS=1, a test setting) every slot equals its solo
run there too.
Prompt chunk
The MoE prompt kernels read every expert once per chunk, so a bigger chunk feeds them more rows per expert. With every expert in VRAM the engine takes the largest of 4096 / 2048 / 1024 that fits beside the experts, the 0.77 GiB reserve and the sessions' caches.
| Situation | Chunk |
|---|---|
| Every expert in VRAM, 8K to 64K context | 4096 |
| Every expert in VRAM, 100K context | 2048 |
| Every expert in VRAM, 131K context | 512 |
--batch 2..4 |
1024 at most, so the other slots are not held for a whole chunk |
--batch 8 |
512 |
| Hybrid mode | 2048; 1024 or 512 on a card with 7.5 or 5.5 GiB free |
In the hybrid mode a bigger chunk takes expert slots (an RTX 3060 at chunk 4096 read prompts 5% faster and decoded 13%
slower). --prefill 512 (or STRATA_Q36_CHUNK=512) gives the 512 size, and then the text is the base engine's, bit
for bit. The chunk size changes the GEMM tiles the BLAS picks and with them the last bits of the prompt state. This is
the size of the llama.cpp CPU-against-HIP difference on the same file, not a quality loss. A prompt shorter than the
chunk (every chat turn after the first) is one chunk either way.
Speed and exactness
Every change on the decode path gives the old kernels' bits. STRATA_Q36_VERIFY=1 strata ... --no-graph runs the old
kernel beside each new one on the real activations and compares them bit for bit. STRATA_Q36_OLD_KERNELS=1 switches
the new kernels off. Gate/up for Q4_K experts uses the V2K kernels (STRATA_EXPERT_V2K, the default on gfx12).
Measured on: AMD Radeon AI PRO R9700 32 GB (HIP gfx1201), UD-Q4_K_XL, Strata 0.1.45 against the engine before it, 5 interleaved pairs, medians; llama.cpp 972b5fc HIP on the same file (
-fa 1).
| Before | 0.1.45 | llama.cpp | |
|---|---|---|---|
| Decode tg128 | 83.2 tok/s | 117.8 | 81.9 to 82.1 |
| Prompt pp4096 | 3,453 | 6,220 (chunk 4096) | 3,794 (-ub 2048) / 3,042 (-ub 512) |
More numbers are in History.
Limits
- Eight slots at most (the window holds 8 rows).
--batch-mtpshares those rows between slots and proposals. - A sampling request in a batch decodes one token per window without proposals.
logit_biasis not available on this engine path. The server refuses a request that has it (as it does with--batchfor Flash-Next).- A slot that stopped being alone goes back to the solo path only through the server's own hand-over. The MTP block's cache has holes for the tokens a slot decoded in batch windows, so the drafts of a later solo stretch are a little worse. The text is not affected.
- Intel Arc (SYCL) and Mac do not run this family.
Testing
The gates are in tools/qwen35/ and talk to the engine through its serve protocol (no server needed). Run them under
the box's GPU lock.
| Script | Checks |
|---|---|
q36_solo_gate.py + q36_cmp_gate.py |
The 10 parity prompts x 128 greedy tokens through GEN, compared with another build's. |
q36_batch_gate.py --slots N --waves --long 2500 |
Every slot's tokens equal its solo tokens, slots reused, a long prompt read beside decoding slots. |
q36_proto_edge.py |
max_new 1, BSTOP, a busy slot, STOP during a prompt read, held-state reuse, sampled requests, the context's end. |
q36_interleave_probe.py |
What the decoding slots see while a long prompt is read. |
q36_vision_cmp.py (with q36_mtmd_dump.cpp built against llama.cpp) |
The picture comparison against llama.cpp. |
q36_vision_proto.py |
Pictures through GENI / BGENI (solo, in a batch, the conversation cache). |
q36_vision_serve.py, q36_serve_par.py, q36_serve_handoff.py |
The same through the server. |
q36_par_bench.py |
The aggregate-throughput A/B against llama-server -np N. |
History / older measurements
Pictures checked against llama.cpp
Measured on: R9700 32 GB (HIP), UD-Q4_K_XL, Strata 0.1.45. llama.cpp's
mtmdand its own graph, the encoder on the CPU on both sides, the same prompt ids, llama.cpp's answer fed to both, logits compared step by step over 24 steps. The floor is llama.cpp's CPU backend against its own HIP run.
| Picture and question | Prompt tokens | First token | Mean KL, llama.cpp HIP vs Strata | Floor |
|---|---|---|---|---|
| 640x400, text in the image | 288 | same | 0.029 | 0.056 |
| 640x400, list the shapes | 294 | same | 0.035 | 0.039 |
| 800x450, text in the image | 378 | same | 0.021 | 0.031 |
| 800x450, describe the shapes | 381 | same | 0.0005 | 0.0019 |
| 1024x1024, the title (1,024 image tokens, 3 chunks) | 1,042 | same | 0.0022 | 0.0026 |
| two pictures in one request | 655 | differs (a near tie; the floor's KL at this step is 0.16) | 0.014 | 0.022 |
The answers are the same text as llama.cpp's. With the picture options on, a request without pictures gives the same tokens as without them (10 prompts x 128 greedy tokens).
Aggregate decode speed, several requests at once
Measured on: same prompts, greedy, 128 new tokens each, wall time from first request to last answer, 5 interleaved pairs, medians, Strata 0.1.45 with
--batch 8 --mtp 0againstllama-server -np N.
| Card | Requests | Strata tok/s | llama.cpp | Ratio |
|---|---|---|---|---|
| R9700 32 GB, UD-Q4_K_XL | 1 | 83.9 | 73.8 | 1.14 |
| 2 | 173.5 | 107.4 | 1.61 | |
| 4 | 252.0 | 165.0 | 1.53 | |
| 8 | 325.4 | 208.1 | 1.56 | |
RTX 3060 12 GB, hybrid (llama.cpp -ncmoe 28 -t 8, the best split of a sweep) |
1 | 64.0 | 41.1 | 1.56 |
| 2 | 85.1 | 52.5 | 1.62 | |
| 4 | 99.3 | 63.0 | 1.58 |
All pairs were faster for Strata. Every slot's tokens equal the tokens of the same request alone.
Several requests with the base engine, and with --batch-mtp
Measured on: R9700 32 GB, 5 interleaved pairs, Strata 0.1.45 (before the speed work below).
| Requests | --mtp 0 |
--mtp 2 --batch-mtp |
Ratio |
|---|---|---|---|
| 1 | 83.7 tok/s | 134.0 | 1.60 |
| 2 | 173.1 | 207.0 | 1.20 |
| 4 | 251.0 | 275.0 | 1.10 |
| 8 | 324.0 | 320.5 | 0.99 (no row to spare: no proposals) |
Measured on: hybrid cards, UD-Q4_K_XL, medians of 5 interleaved pairs,
--mtp 2; "together" is the total tokens per second of N simultaneous requests against one request at a time with the draft layer.
| Card, context | Slots | One request alone, against 1 slot | N together, against one at a time |
|---|---|---|---|
| RTX 3060 12 GB, 8K | 2 / 4 / 8 | -3.5% / -4.3% / -12% | 0.99x / 1.15x / 1.10x |
| RTX A4000 16 GB, 32K | 2 / 4 | -10.6% / -29.9% | 1.03x / 0.76x |
| Tesla P100 16 GB, 32K | 2 / 4 | -7% / -23% | 0.98x / 0.81x |
Without the draft layer (--mtp 0) the same slots give 1.26x to 1.64x together (3060 at 8K: 4 slots 1.64x for -5.5%
alone). A single request with drafts is already about 1.6x as fast as one without, so on a small card the main gain is
that a second user starts at once. On a card that holds every expert (R9700 32 GB, 8K prompts), 4 slots run 1.45x
one-at-a-time-with-drafts (1.59x with --batch-mtp).
Prompt read beside decoding slots
Measured on: R9700 32 GB, Strata 0.1.45, three decoding slots.
A 3,008-token prompt was read in 0.95 s (3,200 tok/s, 6 chunks). Each decoding slot paused at most 160 ms (one chunk)
and produced 20 tokens meanwhile. With STRATA_Q36_READ_WINDOWS=4 they produced 65 tokens during a 1.14 s read.
Speed work in 0.1.45
Measured on: R9700 32 GB (HIP gfx1201), UD-Q4_K_XL, 5 interleaved pairs, 3 repetitions each, medians; base = the engine before 0.1.45.
| Base | 0.1.45 | Ratio | |
|---|---|---|---|
--mtp 2, 10 prompts greedy 128 |
167.7 tok/s | 183.7 | 1.10 |
1 / 2 / 4 / 8 requests (--batch 8 --mtp 0) |
85.2 / 182.4 / 271.9 / 361.1 | 122.4 / 205.1 / 303.3 / 386.9 | 1.44 / 1.12 / 1.12 / 1.07 |
| Prompt pp4096 at chunk 512 / 1024 / 2048 / 4096 | 3,453 / 4,786 / 5,766 / 6,223 |
UD-Q6_K_XL on the same card (every main layer's experts in VRAM, chunk stays 512): decode 80.4 to 106.9 tok/s, pp4096
2,160 to 2,200 (llama.cpp 3,486 at -ub 2048, 2,738 at -ub 512), --mtp 2 154.7 to 167.3. --chunk 2048 there moves
the experts to the hybrid placement (205 of 256 per layer in VRAM): prompt 3,500 tok/s, decode 94. That is an option,
not the default.
Measured on: RTX 3060 12 GB (CUDA sm_86, 20 threads), UD-Q4_K_XL in the hybrid mode (37 of 256 experts per layer in VRAM), Strata 0.1.45.
Decode tg128 58.5 to 59.7 tok/s (+2%), pp2048 1,958 to 2,022 (+3.3%), 10/10 prompts identical, plain and with --mtp 2.
llama.cpp 972b5fc CUDA (-ncmoe 26 -t 8 -ub 2048): pp2048 1,328, tg128 48 to 52. The GPU part of a token is the dense
Q8_0 at the card's bandwidth and the rest waits for the CPU pool, so only the small kernels' share is gained.
KL against the 512 chunk on six real 1.5K to 3.2K-token documents (48 teacher-forced rows each): chunk 1024 4.0e-3,
2048 4.2e-3, 4096 3.6e-3, top-1 agreement 44 to 48 of 48 rows. Greedy 64 tokens after those documents: the default chunk
gives the base engine's text for 2 of 6, --prefill 512 for 6 of 6.