Si tu fais tourner Qwen3.6 27B avec MTP sur une image custom basée sur la branche am17an depuis début mai, tu peux arrêter. La PR #22673 a atterri dans ggml-org/llama.cpp master le 16 mai, donc le long détour par branche custom est terminé.
Mais “MTP a fusionné” n’est pas la vraie news. La news c’est ce qui est arrivé après la fusion : trois PRs de suivi ont atterri dans les quatre jours suivants et changent silencieusement comment MTP se comporte au point de fonctionnement où la plupart des gens l’utilisent. Et l’une d’elles est un retournement de valeur par défaut que je pense que la plupart des utilisateurs ont raté.
Voici ce que j’ai trouvé sur Olares One (RTX 5090M Laptop, 24 GB GDDR7, sm_120 Blackwell consumer mobile).
Les trois PRs de suivi qui comptent
Après la fusion du 16 mai, trois PRs ont atterri sur tools/server/ et common/speculative/ qui touchent au chemin de draft MTP :
#23269 — “MTP clean-up” (ggerganov, 2026-05-19) — le changement majeur est enterré dans le corps de la PR :
Change default value of
--spec-draft-n-maxfrom16to3
Plus plusieurs corrections moins visibles : --spec-draft-p-min était cassé et redevient fonctionnel (default 0.0), la logique de réutilisation de graphe était fausse pour les graphes avec batch.token && batch.embd (exactement le graphe de draft MTP), et toutes les implémentations spéculatives voient maintenant les tokens acceptés — important quand on chaîne plusieurs impls. Le corps de la PR vaut le coup d’être lu.
#23287 — “Move to backend sampling for MTP draft path” (gaugarg-nv, 2026-05-20) — patch signé NVIDIA. Déplace le sampling du chemin de draft hors du host et sur le backend CUDA. Moins d’aller-retours host sur la boucle critique de drafting, moins de surcoût de synchronisation par étape de draft.
#22522 — “Programmatic Dependent Launch (PDL) for newer NVIDIA GPUs (Hopper+)” (aendk, 2026-05-20) — PDL est une fonctionnalité CUDA 12+ qui permet au GPU de planifier le prochain kernel avant que le précédent ne soit fini, réduisant la latence de lancement. Marqué “Hopper+” mais Blackwell consumer mobile (sm_120) qualifie.
Aucune de ces PRs n’est MTP-la-feature, c’est MTP-la-feature-mais-vraiment-réglée. Si tu as arrêté de suivre après la fusion du 16 mai, tu te dirais que le build master c’est “MTP marche” et tu passerais à autre chose. Mais le point de fonctionnement a bougé sous tes pieds.
Avant : ma baseline prod v1.0.8
Jusqu’à hier soir, mon llamacppqwen36mtpone v1.0.8 tournait sur une image custom buildée depuis la branche pre-merge d’am17an (aamsellem/llamacpp-mtp:0.1.0) — la seule façon d’avoir MTP sur Blackwell avant la fusion. Config issue directement de la recette MTP Reddit sortie début mai :
--spec-type mtp --spec-draft-n-max 5
--ctx-size 262144 --cache-type-k q4_0 --cache-type-v q4_0
--batch-size 512 --ubatch-size 512 --parallel 1 --flash-attn on
Validée sur unsloth/Qwen3.6-27B-MTP-GGUF UD-Q3_K_XL (14,5 GB) :
72,75 t/s AVG, ~64 % d’acceptation des drafts.
Stable, zéro OOM à 262K full, pas de cycle de dégradation. Assez bon pour que j’arrête de bidouiller.
Après : master HEAD avec les trois PRs de suivi
J’ai buildé aamsellem/llama-cpp-mtp:master-ad27757 depuis ggml-org/llama.cpp master HEAD ad27757 (qui est le commit de fusion de #23287, donc les trois PRs sont incluses). Même modèle, même KV cache, même context window. Deux changements de flags :
--spec-type mtp→--spec-type draft-mtp(flag renommé upstream le 2026-05-13)--spec-draft-n-max 5→3(correspond au nouveau default upstream de #23269)
Dix runs Space Invaders consécutifs (2000 tokens chacun) :
| Run | t/s | draft_n | acceptés | accept % |
|---|---|---|---|---|
| 1 | 74,90 | 1668 | 1442 | 86,5 % |
| 2 | 72,60 | 1713 | 1428 | 83,4 % |
| 3 | 75,01 | 1653 | 1447 | 87,5 % |
| 4 | 74,32 | 1663 | 1444 | 86,8 % |
| 5 | 74,31 | 1660 | 1445 | 87,0 % |
| 6 | 73,73 | 1672 | 1441 | 86,2 % |
| 7 | 73,87 | 1668 | 1442 | 86,5 % |
| 8 | 75,14 | 1639 | 1452 | 88,6 % |
| 9 | 74,35 | 1659 | 1446 | 87,2 % |
| 10 | 74,62 | 1650 | 1448 | 87,8 % |
74,28 t/s AVG, 86,7 % d’acceptation AVG. Range 72,60 – 75,14, σ = 2,54.
Pure vitesse : +2,1 % vs v1.0.8.
Acceptance : +22 points de pourcentage (64 % → 86,7 %).
Pourquoi le saut d’acceptance est la vraie news
Si tu regardes juste le delta de throughput, +2 % c’est oubliable. Le chiffre intéressant c’est le saut d’acceptance.
À n_max=5 avec l’ancien sampling, on draftait 5 tokens par étape et on en acceptait ~3. Le modèle proposait des tokens que la verify pass rejetait assez souvent pour que ~40 % du compute de draft soit gaspillé.
À n_max=3 avec backend sampling, on draft 3 tokens par étape et on en accepte ~2,6. Moins de drafts, mais chacun beaucoup plus probable d’être correct. Le taux verify-reject est passé de 36 % à 13 %. C’est un régime de fonctionnement différent — plus proche de ce que MTP-le-papier promet vraiment.
Les deux changements se composent. n_max=3 réduit la profondeur de recherche, donc chaque proposition de draft est dans un territoire plus haute-probabilité. Le backend sampling supprime un goulot CPU host qui introduisait probablement une dérive latence-induite dans la distribution de draft (les tokens samplés sur host étaient moins cohérents avec la distribution next-token attendue par le GPU à cause de l’aller-retour). Ensemble : recherche plus étroite, couplage plus serré, acceptance beaucoup plus haute.
Le throughput est resté à peu près plat parce que faire moins de drafts (perte un peu) mais en accepter plus (gain un peu) finit au même endroit. Le gain que tu obtiens n’est pas du t/s — c’est moins de variance et moins de compute gaspillé. Regarde le range : σ = 2,54 sur 10 runs. Le setup précédent avait σ autour de 5-6 sur le même prompt. Plus serré = vraie valeur si tu fais tourner une stack agentique où la consistance compte plus que le peak.
Le bug de propagation du driver CUDA, et le fix discret d’Anbeeld
Un détail dont personne ne va parler mais qui m’a coûté un cycle de build : à b9219, ggml-org/llama.cpp master rate son link final quand on build avec BUILD_SHARED_LIBS=ON + GGML_CUDA=ON sur Ubuntu 24.04 / CUDA 13.0. L’erreur :
/usr/bin/ld: libggml-cuda.so.0.12.0: undefined reference to `cuMemCreate'
undefined reference to `cuMemAddressReserve'
[...11 symboles cuMem*/cuDevice* de plus]
La librairie partagée elle-même link (elle a le droit d’avoir des symboles non résolus), mais le link de l’exécutable downstream refuse de les résoudre transitivement.
Cause racine : ggml/src/ggml-cuda/CMakeLists.txt link ggml-cuda contre CUDA::cuda_driver en scope PRIVATE. Pour les exécutables qui linkent contre ggml-cuda via ggml puis llama, la dépendance ne se propage pas.
Anbeeld a déjà corrigé ça sur son fork BeeLlama (Anbeeld/beellama.cpp#18) avec un ajout d’une ligne :
target_link_libraries(ggml-cuda PRIVATE CUDA::cuda_driver)
if (NOT GGML_BACKEND_DL)
target_link_libraries(ggml-cuda INTERFACE $<LINK_ONLY:CUDA::cuda_driver>)
endif()
J’ai déposé le même bug upstream comme ggml-org/llama.cpp#23357 avec le fix d’Anbeeld comme solution proposée. En attente de review.
En attendant, mon image embarque un workaround :
-DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs -lcuda"
-DCMAKE_SHARED_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs -lcuda"
Fonctionnellement identique, juste moins élégant.
Ce que j’ai shippé
llamacppqwen36mtpone v1.0.9 sur ma source de marché Olares One :
- Image :
aamsellem/llama-cpp-mtp:master-ad27757(1635 MB, amd64+CUDA13+sm_120, Docker Hub digestsha256:7451496e224c9) - Repo HF source :
unsloth/Qwen3.6-27B-MTP-GGUF(le nom canonique après la re-release Unsloth du 16 mai — ils ont renomméGGUF-MTPenMTP-GGUF) - Mêmes bits du modèle Q3_K_XL UD que v1.0.8
--spec-type draft-mtp --spec-draft-n-max 3(correspond aux nouveaux defaults upstream)- q4_0 KV @ 262K context
Drop l’image custom de la branche am17an qui nous portait depuis début mai. Si tu attendais que MTP atterrisse proprement dans ggml-org/llama.cpp master avant de switcher off ton propre build custom : c’est le moment.
À tester ensuite
Quelques trucs que je n’ai pas encore mesurés et que tu pourrais vouloir :
--spec-draft-p-minest de nouveau fonctionnel. Default 0.0 = ne filtre pas. Je serais curieux de ce que 0,5 ou 0,7 fait au point n_max=3 / 86 % accept.--spec-draft-n-max 2serait la sonde suivante. Si 5→3 n’a rien perdu en throughput, est-ce qu’on peut descendre plus bas sans perdre l’acceptance ? À n_max=2 tu tournerais essentiellement en spéculatif-1 pur avec une tête MTP.- PDL (#22522) : c’est activé par défaut sur sm_90+ maintenant, mais j’ai pas mesuré quelle fraction du delta +2 % vs v1.0.8 vient de PDL vs le n_max vs le backend sampling. Désactiver PDL via env var isolerait sa contribution.
Si tu fais tourner un de ces tests sur Blackwell consumer (5090 desktop, 5090M mobile ou 5080), envoie-moi un mot — j’adorerais comparer les chiffres.
Hardware : Olares One — RTX 5090M Laptop (24 GB GDDR7, sm_120 Blackwell consumer mobile), Intel Core Ultra 9 275HX 24-core, 96 GB DDR5. Software : ma propre source de marché Olares déployée sur orales-one-market.aamsellem.workers.dev — install en un clic d’apps IA optimisées pour le device Olares One. Bench prompt : completion du jeu Space Invaders HTML, 2000 tokens, temp=0,6 top_k=20 min_p=0. Dix runs consécutifs, single user, pas de warmup au-delà du chargement modèle.