Suite rapide du post BeeLlama 262K d’hier. Je claimais 107 t/s à 262K full sur Qwen3.6 27B comme strict upgrade vs mes meilleurs paths précédents. Un lecteur a posé la question évidente : « on peut ajouter la vision ? »
Réponse courte : oui, et ça tombe à 106 t/s à 200K de contexte avec la vision active. Trois findings notables :
- BeeLlama supporte
--mmprojaux côtés de--spec-type dflashpour Qwen3.6 27B. Le même combo crash sur Gemma 4 (fattn.cu:1265: fatal error). Le fork est target-spécifique. - 200K de contexte bat 128K de +4,4 % sur cette stack. Contre-intuitif mais reproductible — même effet d’alignement cudagraph qui avait donné le sweet spot 128K sur text-only.
- 262K + mmproj = OOM (1 GB de trop sur 24 GB de VRAM). Le plafond 200K est solide.
TL;DR
| Stack | t/s avg | Contexte | Vision | Range | KV @ contexte |
|---|---|---|---|---|---|
llamacppqwen36beellamaone v1.0.2 (text-only) | 107,54 | 262K | ❌ | 17,7 | ~8 GB |
llamacppqwen36beellamavisionone v1.0.0 | 106,43 | 200K | ✅ | 23,4 | ~6,2 GB |
| BeeLlama vision @ 128K (test only) | 101,98 | 128K | ✅ | 16,9 | ~4 GB |
-1 % de throughput, -23 % de contexte, +vision, +DFlash drafter qui fire toujours alongside mmproj (27 % acceptance). Shippé comme llamacppqwen36beellamavisionone v1.0.0 dans le catalog orales-one-market.
L’incompatibilité BeeLlama vs Gemma 4
Cette semaine j’ai essayé de swapper BeeLlama (aamsellem/beellama-cpp:0.1.2) sur un déploiement Gemma 4 26B-A4B + mmproj F16 + q4_0 KV sur le même hardware (RTX 5090M, sm_120 Blackwell consumer mobile, 24 GB). Crash au warmup :
TCQ decode: context-adaptive V alpha enabled
/app/ggml/src/ggml-cuda/fattn.cu:1265: fatal error
libggml-base.so.0(+0x1adb6)[0x7ee7b3241db6]
libggml-cuda.so(+0x23e181)[0x7ee7a788b181]
libggml-cuda.so(_Z24ggml_cuda_flash_attn_ext...)
Ça prend du sens dès qu’on regarde la généalogie du fork : BeeLlama est Anbeeld/beellama.cpp ← spiritbuun/buun-llama-cpp ← TheTom/llama-cpp-turboquant ← ggml-org/llama.cpp. Chaque maillon de la chaîne modifie le path CUDA flash attention pour Qwen3.6 hybrid arch (Gated DeltaNet + SSM) + TCQ (turbo cache quant) + DFlash. Aucune de ces modifications n’a été validée contre le sliding window attention de Gemma 4. Le crash n’est pas un bug — c’est out-of-scope.
L’implication : BeeLlama est le bon fork pour Qwen3.6 DFlash, le fork AtomicBot est le bon pour Gemma 4 + MTP. Choisir par classe de modèle, pas par wishlist de features.
Ce que je ne savais pas en démarrant : est-ce que BeeLlama supporte --mmproj aux côtés de --spec-type dflash pour Qwen3.6 ? Pas documenté. Aurait pu être « mmproj marche seulement sans spec » (le path MTP a exactement cette incompatibilité upstream — multimodal + MTP est interdit en llama.cpp). Test obligatoire.
La config
Même target + drafter que le v1.0.2 text-only, plus le mmproj depuis unsloth/Qwen3.6-27B-GGUF :
TARGET: unsloth/Qwen3.6-27B-UD-Q3_K_XL.gguf # 14,5 GB
DRAFT: spiritbuun/Qwen3.6-27B-DFlash-GGUF dflash-draft-3.6-q8_0.gguf # 1,85 GB
MMPROJ: unsloth/Qwen3.6-27B-GGUF mmproj-F16.gguf # 0,93 GB
# Args du serveur
--mmproj /models/$MMPROJ_FILE
--spec-type dflash
--spec-dflash-cross-ctx 1024
-ngl 99 --spec-draft-ngl 99
--ctx-size 200000
--cache-type-k turbo3 --cache-type-v turbo3
--batch-size 2048 --ubatch-size 2048 # ubatch ≥ image_max_tokens (~1100) obligatoire
--parallel 1 --kv-unified
--flash-attn on --jinja --no-mmap --mlock
--temp 0.6 --top-k 20 --min-p 0.0
Le --ubatch-size 2048 est obligatoire quand mmproj est chargé (le default text-only de 256 ne marche pas). Qwen3.6 a un image_max_tokens autour de 1100 par requête. Le vision encoder est non-causal, donc il ne peut pas split les image tokens à travers plusieurs ubatches. Si ubatch < image_max_tokens, le server assert au premier image input (llama.cpp PR #21550). Safe default = --ubatch-size 2048 pour toute chart avec --mmproj.
Le smoke test
Génération d’une petite PNG 64×64 gradient (rouge à droite, teal-noir à gauche) envoyée via le content type OpenAI-compatible image_url. Réponse :
« The image shows a gradient. It transitions from a dark, almost black color on the left to a vibrant red on the right. »
Correct. Telemetry DFlash drafter : draft_n=221, draft_n_accepted=61 → 27,6 % d’acceptance sur ce prompt vision. Le drafter fire vraiment aux côtés de mmproj — BeeLlama supporte le combo. Win surprenant vu la fragilité de ce path côté Gemma 4.
Le bench
J’ai démarré à 128K en m’attendant au classique « vision adds latency ». 101,98 t/s avg sur 10 runs, range 92,69-109,62. Pas mal, mais l’histogramme penchait plus bas que le résultat text-only 128K (qui avait fait 116 t/s avg sur la même machine hier).
Puis push à 200K. Attendu : autre drop. Obtenu : l’inverse.
| Contexte | Runs | AVG t/s | MIN | MAX | Range | Notes |
|---|---|---|---|---|---|---|
| 128K | 10 | 101,98 | 92,69 | 109,62 | 16,94 | baseline vision |
| 200K | 10 | 106,43 | 93,29 | 116,67 | 23,38 | +4,4 % vs 128K |
| 262K + mmproj | n/a | OOM | — | — | — | dépasse 24 GB |
200K est +4,4 % plus rapide que 128K, avec une range plus large. C’est le même effet observé hier sur text-only — un « sweet spot » où les tailles de capture cudagraph s’alignent mieux avec les chunks prefill. Sur la stack text-only le sweet spot était à 128K. Avec mmproj ajouté (qui change les shapes prefill), le sweet spot se décale à 200K.
C’est le genre de finding que je ne croirais pas sur un seul bench, mais l’histogramme et la cohérence 10-run sont propres. Le MIN à 93 t/s @ 200K est égal au MIN @ 128K, le MAX est plus haut (116,67 vs 109,62), et la médiane est plus haute. Meilleur à tous les percentiles.
Pourquoi 262K + mmproj OOMs
Math VRAM sur budget 24 GB :
Target Q3_K_XL 14,5 GB
DFlash drafter q8_0 1,85 GB
mmproj F16 0,93 GB
turbo3 KV @ 262K ~8,2 GB
Compute buffer + vLLM ~0,5 GB
────────────────────────────
TOTAL @ 262K + mmproj ~26 GB ← OOM (~1,5 GB de trop)
TOTAL @ 200K + mmproj ~24 GB ← fit avec ~0,3 GB de marge
TOTAL @ 262K text-only ~24,3 GB ← fit, c'est pourquoi v1.0.2 ship en 262K
Les 0,93 GB du mmproj font basculer la config text-only 262K en OOM. Deux solutions : (a) drop contexte à 200K (cette app), (b) drop le quant du drafter à q4_k_m (perd ~1 GB mais DFlash acceptance baisse). J’ai pris (a) parce que 200K reste 6× tout autre déploiement Qwen3.6 27B vision que j’ai vu, et la perf scale gracieusement.
DFlash acceptance sur prompts vision
Datapoint rapide sur le comportement spec decoding : sur le smoke test PNG gradient, le drafter DFlash a hit 27,6 % acceptance pos-0. Sur le prompt standard Space Invaders HTML pendant le bench 10-run, acceptance moyenne autour de 28 % — même range. Donc mmproj ne semble pas dégrader la capacité du drafter à prédire la distribution de sortie du target. Le vision encoder produit des tokens texte standard après le segment image, et à partir de là c’est de la génération Qwen3.6 régulière — DFlash le traite comme n’importe quel prompt.
Les 27-28 % d’acceptance sont au même niveau que ce que je vois sur text-only avec le même drafter. Validation raisonnable que le kernel attention modifié de BeeLlama propage les hidden states target au drafter correctement même quand des image tokens sont dans le prompt.
Ce que ça veut dire pour le catalog
Trois paths Qwen3.6 27B sur orales-one-market :
| App | Path | Best for |
|---|---|---|
llamacppqwen36beellamaone v1.0.2 | BeeLlama text-only @ 262K | Max throughput + max contexte, pas vision |
llamacppqwen36beellamavisionone v1.0.0 (NEW) | BeeLlama + mmproj @ 200K | Vision + long contexte single-user |
llamacppqwen36mtpone v1.0.8 | am17an MTP @ 262K | Plus proche de llama.cpp main upstream, pas de DFlash dependency |
Les users d’Olares One choisissent par use case. L’app vision est single-user oriented (BeeLlama est --parallel 1 only). Pour vision multi-user (PagedAttention) sur une autre classe de modèle, le catalog ship vllmgemma426ba4bvisionone qui fait 135 t/s @ 128K sur Gemma 4 — modèle différent, stack différente, tradeoff différent.
Reproductibilité
Chart Helm et config exacte dans llamacppqwen36beellamavisionone v1.0.0. Pull aamsellem/beellama-cpp:0.1.2 depuis Docker Hub si tu as un GPU sm_120 et tu veux pas rebuild depuis les sources. Même image que le post text-only, juste un wiring chart différent.
Si tu run une autre carte sm_120 (5090 desktop, 5080, 5070 Ti) et veux pousser au-delà de 200K, tu as ~8 GB de marge VRAM en plus sur 32 GB — probablement que 256K + mmproj + DFlash marche là-bas. Curieux de connaître tes chiffres si tu testes.
Open questions
- Pourquoi 200K bat 128K ? Mon meilleur guess : alignement des tailles de capture cudagraph avec les prefill chunks. Le même effet sur text-only avait son sweet spot à 128K. Avec mmproj chargé (qui change les shapes prefill), le sweet spot se décale. Pas assez profilé pour confirmer.
- Est-ce que CopySpec (l’ajout unique de BeeLlama au-dessus du fork buun) aide sur workloads vision ? Designed pour repeated suffix matching — pourrait être utile pour tâches OCR où le modèle ré-émet du boilerplate document. Pas bench encore.
- Audio path ? Qwen3.6 a un audio config aussi dans certaines variantes. mmproj-F16 ici est image-only. À investiguer.