Instructions to use patdev/k3-a40-bootstrap with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use patdev/k3-a40-bootstrap with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf patdev/k3-a40-bootstrap:BF16 # Run inference directly in the terminal: llama cli -hf patdev/k3-a40-bootstrap:BF16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf patdev/k3-a40-bootstrap:BF16 # Run inference directly in the terminal: llama cli -hf patdev/k3-a40-bootstrap:BF16
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf patdev/k3-a40-bootstrap:BF16 # Run inference directly in the terminal: ./llama-cli -hf patdev/k3-a40-bootstrap:BF16
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf patdev/k3-a40-bootstrap:BF16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf patdev/k3-a40-bootstrap:BF16
Use Docker
docker model run hf.co/patdev/k3-a40-bootstrap:BF16
- LM Studio
- Jan
- Ollama
How to use patdev/k3-a40-bootstrap with Ollama:
ollama run hf.co/patdev/k3-a40-bootstrap:BF16
- Unsloth Desktop
- Docker Model Runner
How to use patdev/k3-a40-bootstrap with Docker Model Runner:
docker model run hf.co/patdev/k3-a40-bootstrap:BF16
- Lemonade
How to use patdev/k3-a40-bootstrap with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull patdev/k3-a40-bootstrap:BF16
Run and chat with the model
lemonade run user.k3-a40-bootstrap-BF16
List all available models
lemonade list
- Atomic Chat
|
Download RESULTS.md from patdev/k3-a40-bootstrap: direct link, hf CLI and curl.
- Browser
- Download file 24.2 kB
-
https://huggingface.co/patdev/k3-a40-bootstrap/resolve/main/RESULTS.md
- Command line
-
hf download hf://patdev/k3-a40-bootstrap/RESULTS.md
-
curl -L -o RESULTS.md https://huggingface.co/patdev/k3-a40-bootstrap/resolve/main/RESULTS.md
24.2 kB
| # Résultats mesurés — Kimi-K3, Kimi-Linear, Qwen3-Coder sur A40 | |
| Toutes les valeurs ci-dessous sont **mesurées**, jamais estimées, sauf mention | |
| explicite. Le coût au million est calculé depuis le tarif horaire RunPod et le | |
| débit mesuré. Protocole commun : `bench_endpoint.sh`, publié à côté. | |
| ## Synthèse | |
| | | Kimi-K3 REAP448 | Kimi-Linear 48B | Qwen3-Coder-30B | | |
| |---|---|---|---| | |
| | moteur | llama.cpp | vLLM 0.27.1 | vLLM 0.27.1 | | |
| | matériel | 5× A40 | **1× A40** | **1× A40** | | |
| | $/h | 2,20 | 0,44 | 0,44 | | |
| | decode 1 flux | 7,29 tok/s | 67–71 tok/s | **77,6–130 tok/s** | | |
| | agrégé 32 flux | ~14,5 (4 flux) | 780 tok/s | **1 166 tok/s** | | |
| | prefill à froid | ~165 tok/s | **6 162 tok/s** | 3 957 tok/s | | |
| | prefill à chaud | ~cache OK | 6 430 (pas de cache) | **63 315 tok/s** | | |
| | contexte | 524 288 | **1 048 576** | 262 144 | | |
| | $/1M à 32 flux | ~85 (1 flux) | 0,157 | **0,105** | | |
| | français | cassé | fluide | correct | | |
| | tool calling | — | oui (`kimi_k2`) | oui (`qwen3_coder`) | | |
| ## Kimi-K3 sur llama.cpp — la progression complète | |
| ``` | |
| Colibri CUDA custom, L40S 2,32 tok/s point de depart | |
| llama.cpp 2x A40, apres corrections 2,94 | |
| llama.cpp 5x A40, placement identique 5,44 <- effet vCPU (16 -> 41) | |
| llama.cpp 5x A40, tout en VRAM 7,35 | |
| idem sur 600 tokens reels 7,22 | |
| idem contexte 262 144 7,25 | |
| idem contexte 524 288 7,29 <- plafond | |
| idem contexte 1 048 576 OOM (29 Gio de KV f16) | |
| ``` | |
| Le débit est **plat sur 32× de contexte** : c'est la géométrie KDA+MLA qui le | |
| permet (24 couches sur 93 mettent en cache, les 69 autres portent un état | |
| récurrent de taille constante). 16 384 n'était pas une limite matérielle, juste | |
| un défaut jamais testé. | |
| ### Ce qui bornait vraiment le débit | |
| Six placements couvrant 60 à 88 Go de VRAM n'avaient rien changé. La cause n'est | |
| ni le kernel ni la bande passante : | |
| ``` | |
| 1 flux 5,56 tok/s agrege | |
| 2 flux 9,85 tok/s 1,77x | |
| 4 flux 14,53 tok/s 2,61x | |
| ``` | |
| La concurrence monte, donc le matériel était **inactif** entre les tokens : | |
| llama.cpp répartit les couches entre GPU et les exécute en séquence, un seul | |
| calcule à la fois. Confirmation arithmétique indépendante : ~7 Go lus par token | |
| à 138 ms = **51 Go/s contre 696 disponibles**, soit 7 % du plafond. | |
| ### Six hypothèses réfutées par la mesure | |
| | hypothèse | ce qui la tue | | |
| |---|---| | |
| | le modèle est cassé | Tokyo/Washington/Stockholm corrects, récursion Fibonacci correcte, chinois fluide | | |
| | le calcul CPU des experts domine | 16 → 8 experts ne donne que **+28 %** | | |
| | les traversées CPU↔GPU dominent | autofit (1–2) ≈ n-cpu-moe 86 (172) | | |
| | le kernel MoE est le goulot | fused SiTU **0 %**, warp-row **+1,2 %** | | |
| | on est borné par la bande passante | 7 % du plafond mesuré | | |
| | le code généré est cassé | test direct correct ; le prompt du selftest était ambigu | | |
| ### DSpark : zéro gain, zéro télémétrie | |
| ``` | |
| avec speculation 7,32 / 7,33 tok/s aucune metrique draft_n | |
| sans speculation 7,33 / 7,35 tok/s | |
| ``` | |
| Le mécanisme n'était pas cassé, le moteur l'était : la même idée sous vLLM donne | |
| +86 % avec 68 % d'acceptation et une télémétrie complète. | |
| ## Kimi-Linear 48B — 1M de contexte sur une seule carte | |
| ``` | |
| 27 couches = 7 MLA + 20 KDA KV/token = 7 x (512+64) x 2 = 7,88 Ko | |
| (Kimi-K3 : 24 couches -> 27,6 Ko) | |
| poids AWQ4 28,4 Gio + 7,88 Gio de KV a 1M = 36,3 Go sur une carte de 46 | |
| ``` | |
| vLLM le confirme au démarrage : **`GPU KV cache size: 1 416 566 tokens`**. | |
| ``` | |
| flux agrege /flux $/1M | |
| 1 67,4 67,4 1,814 | |
| 4 206,6 51,6 0,592 | |
| 8 352,4 44,0 0,347 | |
| 16 556,1 34,8 0,220 | |
| 32 779,9 24,4 0,157 | |
| ``` | |
| ### La spéculation n-gram y est cassée | |
| L'échantillonnage par rejet est censé être **sans perte** : toute différence de | |
| sortie est un bug. Elle corrompt le code, de façon reproductible : | |
| ``` | |
| avec speculation sans | |
| add() return a -b):\n return a - return a - b\n\ndef multiply(a, | |
| fibonacci fibonacci(n-1(n-1) + fibonacci(n-2) fibonacci(n-1) + fibonacci(n-2) | |
| ``` | |
| Signature : un fragment tout juste émis, recollé au mauvais endroit. La prose et | |
| la copie littérale restent parfaites — c'est pourquoi il a fallu des sondes | |
| ciblées : le code est dense en répétitions courtes (parenthèses, opérateurs) sur | |
| lesquelles le n-gram s'accroche. | |
| Elle est en plus **3 à 4× plus lente**, vLLM désactivant les CUDA graphs sous | |
| spéculation sur `TritonMLABackend` : | |
| ``` | |
| flux avec spec sans spec rapport | |
| 1 23,1 71,2 3,1x | |
| 4 58,7 249,6 4,3x | |
| 32 203,9 781,0 3,8x | |
| $/1M a 32 0,600 0,157 | |
| ``` | |
| ## Qwen3-Coder-30B — le meilleur compromis pour Claude Code | |
| ``` | |
| flux agrege /flux $/1M | |
| 1 77,6 77,6 1,575 | |
| 4 265,3 66,3 0,461 | |
| 8 496,8 62,1 0,246 | |
| 16 782,4 48,9 0,156 | |
| 32 1 165,6 36,4 0,105 | |
| prefill 38 494 tok a froid en 10,42 s = 3 693 tok/s ; a chaud 1,07 s = 63 315 | |
| decode a 119k de contexte : 34,3 tok/s | |
| ``` | |
| ### La spéculation dépend du régime | |
| Deux pods identiques tournant **simultanément**, pour éliminer la dérive d'hôte : | |
| ``` | |
| A. generation pure (aucun recouvrement) 102,21 -> 58,49 tok/s -43 % | |
| B. edition de code (la sortie reprend) 130,16 -> 242,39 tok/s +86 % | |
| ``` | |
| Ce n'est pas un gain gratuit, c'est un **pari sur la répétition**. Claude Code | |
| vit entièrement dans le régime B. Télémétrie : 706 acceptés sur 1035 = 68,2 %, | |
| par position 179/156/131/120/120, longueur acceptée moyenne 3,41. | |
| Mais elle **s'effondre sous concurrence**, car vLLM rabaisse | |
| `max_num_scheduled_tokens` à 2048 : | |
| ``` | |
| 1 flux 4 8 16 32 | |
| sans speculation 77,6 265,3 496,8 782,4 1165,6 | |
| avec speculation 79,2 110,1 154,4 271,2 296,1 | |
| ``` | |
| ## Comparaison externe | |
| ``` | |
| API Kimi-K3 officielle (RunPod) 29,2 tok/s ~16 $/1M pleine precision | |
| notre Kimi-K3 auto-heberge 7,3 tok/s ~85 $/1M 1,14 bit/poids | |
| notre Qwen3-Coder-30B 77,6 tok/s 0,105-1,58 4 bits | |
| ``` | |
| L'auto-hébergement de Kimi-K3 sur A40 est dominé sur tous les axes. Il n'achète | |
| ni le prix ni la qualité — il achète la confidentialité et le contrôle du | |
| contexte. | |
| ### Travaux antérieurs (dépôt Kimi-K3-L4-GenAI-TurboQuant, 8 août 2026, 1× L4) | |
| ``` | |
| vLLM AWQ Qwen3.5-9B + LoRA K3 + MTP-1 32,320 tok/s 485/533 = 90,99 % | |
| ORT GenAI CUDA INT4 30,271 | |
| vLLM AWQ + LoRA K3, sans speculation 22,674 | |
| OpenVINO INT4 g128, 8 threads (CPU) 5,029 | |
| ``` | |
| Cette lignée concluait déjà que **vLLM + AWQ est la bonne pile**. Le chemin suivi | |
| ici y arrive indépendamment, en mesurant la sérialisation de llama.cpp. | |
| Le benchmark TurboQuant MLA du même dépôt confirme par un chemin totalement | |
| indépendant la dérivation faite ici : 4 718 592 octets/couche pour 4096 tokens = | |
| 1152 octets/token/couche × 24 couches = **27,6 Ko/token**, exactement la valeur | |
| calculée depuis `config.json`. En TQ4 : compression 3,945×, **6,844 Gio pour 1M | |
| tokens sur 24 couches** — de quoi mettre Kimi-K3 à 1M dans les ~25 Go restés | |
| libres, si l'implémentation existait ailleurs que dans une opération ONNX | |
| compilée en sm_89. | |
| ## Aucune donnée sur Qwen3-Coder-Next 80B | |
| Trois tentatives, trois échecs d'infrastructure, zéro requête servie. | |
| ``` | |
| 1 bloque 22 min a l'init NCCL peer-to-peer sur 2x A40 | |
| 2 NCCL_P2P_DISABLE=1 leve ce blocage, puis boucle ~10 min sur | |
| "No available shared memory broadcast block found in 60 seconds" | |
| 3 --enforce-eager passe la compilation et charge les poids (23,23 Gio/GPU) | |
| et le KV (16,23 Gio/GPU), puis rebloque au meme endroit | |
| ``` | |
| `NCCL_P2P_DISABLE=1` est **nécessaire** au tensor parallelism sur une paire | |
| d'A40 RunPod. Le second blocage reste non diagnostiqué ; le suspect est | |
| `VLLM_WORKER_MULTIPROC_METHOD=spawn`, que j'avais introduit en même temps que le | |
| correctif NCCL — deux variables changées d'un coup, ce qui était une erreur de | |
| méthode. Son log de démarrage a tout de même livré un point dur : | |
| `enable_prefix_caching=False`, la même limitation hybride que Kimi-Linear. | |
| ## Limites d'exploitation mesurées | |
| - **Proxy RunPod : coupure à ~125 s** (Cloudflare `524`). Un prefill à froid | |
| plus long ne passe pas en un appel — mesuré à ~40k tokens sur Kimi-K3 et | |
| ~232k sur Qwen. Parade : découper, chaque requête étendant le préfixe caché. | |
| - **Prefill de Kimi-K3, dégradation douce** : 228,6 → 149,8 tok/s de 8k à 43k de | |
| contexte, soit −34 % pour 5× le contexte. Bien mieux que quadratique, puisque | |
| seules 24 des 93 couches ont une attention complète. | |
| - **`--gpu-memory-utilization 0.93` fait échouer Kimi-Linear** au tout dernier | |
| pas, dans le rejection sampler qui réclame 160 Mio quand il en reste 143. | |
| 0,90 laisse la marge et conserve plus d'1M de tokens de KV. | |
| --- | |
| # Optimisation vers 500 tok/s — endpoint Anthropic, 1× A40 | |
| ## Le résultat | |
| ``` | |
| edition de code, mono-flux, via /v1/completions (6 essais consecutifs) | |
| 555,96 598,49 579,65 567,95 575,66 570,69 tok/s moyenne 574,7 | |
| via /v1/messages, protocole Anthropic (le chemin de Claude Code) | |
| 504,94 563,26 565,83 tok/s non streame | |
| 578,00 tok/s streame | |
| ``` | |
| Tous au-dessus de 500, y compris à travers le pont. Le coût tombe à | |
| **0,21 $ / 1M de tokens en mono-flux** sur une carte à 0,44 $/h. | |
| ## Le levier, et l'indicateur trompeur | |
| ``` | |
| profondeur debit acceptation longueur acceptee | |
| n=5 259,2 tok/s 68,2 % 3,41 | |
| n=12 363,9 tok/s 26,9 % 3,23 | |
| n=24 574,7 tok/s 71,5 % 17,16 | |
| ``` | |
| **Le taux d'acceptation n'est pas la métrique utile.** Il chute de 68 % à 27 % | |
| entre n=5 et n=12 pendant que le débit monte de 40 % : ce qui compte est le | |
| nombre de tokens émis *par passe avant*, pas la fraction acceptée. Suivre le | |
| taux d'acceptation aurait conduit à conclure que n=12 était une régression. | |
| Le signal exploitable était la **queue par position** : à n=12 elle restait | |
| plate (257/118/82/72/71/63/61/59/59/58/58/51), donc le 12ᵉ token deviné était | |
| encore accepté ~20 % du temps — la profondeur n'était pas saturée. À n=24, la | |
| longueur acceptée moyenne atteint **17,16 tokens**, parce qu'en édition de code | |
| la sortie est largement prédictible depuis le prompt. | |
| Aucun motif de corruption détecté à n=24 sur Qwen, et les annotations de type | |
| sont correctement appliquées. | |
| ## L'autre levier : `max-num-batched-tokens` | |
| vLLM le rabaisse à 2048 dès que la spéculation est active, et prévient lui-même | |
| que c'est sous-optimal. Le corriger à 16384 récupère une partie de | |
| l'effondrement en concurrence : | |
| ``` | |
| flux 1 4 8 16 32 | |
| sans speculation 77,6 265,3 496,8 782,4 1165,6 | |
| spec n=5 79,2 110,1 154,4 271,2 296,1 | |
| spec n=12 + 16384 93,3 186,9 278,4 297,2 336,9 | |
| ``` | |
| ## Le compromis à assumer | |
| La spéculation **gagne en mono-flux et perd en agrégé** : les brouillons | |
| consomment le budget de batch. Il n'existe pas de réglage qui gagne partout. | |
| ``` | |
| un seul utilisateur (Claude Code) speculation n=24 -> 575 tok/s, 0,21 $/1M | |
| service multi-utilisateurs speculation OFF -> 1166 tok/s a 32 flux, 0,105 $/1M | |
| ``` | |
| Les deux franchissent 500 tok/s sur une seule A40, par des chemins opposés. | |
| Le défaut du dépôt est la spéculation, puisque la cible visée est Claude Code. | |
| ## Le plafond physique, et pourquoi il est dépassé | |
| ``` | |
| poids actifs lus par token ~4,7 Go / 696 Go/s = 6,8 ms | |
| plafond mono-flux SANS speculation ~147 tok/s theorique, ~130 mesure | |
| ``` | |
| 575 tok/s en mono-flux dépasse ce plafond d'un facteur 4,4 — ce qui n'est | |
| possible que parce que la spéculation émet plusieurs tokens par lecture des | |
| poids. Sans elle, aucun réglage ne peut approcher 500 en mono-flux sur cette | |
| carte. | |
| --- | |
| # CORRECTION — le cache de préfixe fonctionne sur Kimi-Linear | |
| Une section plus haut affirme que Kimi-Linear n'a pas de cache de préfixe, sur la | |
| base d'un rapport froid/chaud de 1,04×. **C'était une erreur de configuration de | |
| ma part, pas une limite du modèle** : le pod concerné ne passait pas | |
| `--enable-prefix-caching`. Avec le flag explicite, sur la même carte : | |
| ``` | |
| sans le flag avec le flag | |
| prefill a froid 6 162 tok/s 7 647 tok/s | |
| prefill a chaud 6 430 tok/s 36 257 tok/s | |
| rapport 1,04x 4,74x | |
| ``` | |
| Conséquence pour un client agent : un prompt système de 32k **est** mis en cache | |
| sur Kimi-Linear. L'argument qui le disqualifiait pour Claude Code tombe. | |
| ## Kimi-Linear, mesures définitives (pod persistant, flags corrects) | |
| ``` | |
| flux 1 4 8 16 32 | |
| agrege 83,9 250,6 402,9 616,4 772,4 tok/s | |
| $/1M 1,457 0,488 0,303 0,198 0,158 | |
| prefill froid 7 647 tok/s (Qwen : 4 485) | |
| prefill chaud 36 257 tok/s (Qwen : 70 568) | |
| edition de code 100,5 tok/s (Qwen : 575) | |
| contexte 1 048 576 KV 1 505 550 tokens | |
| protocole Anthropic : les 5 controles passent | |
| ``` | |
| Kimi-Linear franchit 500 tok/s **en agrégé dès 16 flux** (616,4). Qwen les | |
| franchit **en mono-flux** (575). Les deux tiennent la cible sur une seule A40, | |
| par des chemins différents : la spéculation pour Qwen, la concurrence pour Kimi | |
| — chez qui la spéculation reste inutilisable puisqu'elle corrompt le code. | |
| ## Choix recommandé | |
| | besoin | modèle | pourquoi | | |
| |---|---|---| | |
| | Claude Code, un utilisateur | **Qwen3-Coder-30B** | 575 tok/s en édition de code, cache de préfixe 16×, 0,21 $/1M | | |
| | contexte au-delà de 262k | **Kimi-Linear-48B** | 1 048 576 tokens sur une carte, prefill à froid 1,7× meilleur | | |
| | service multi-utilisateurs | Qwen sans spéculation | 1 166 tok/s à 32 flux, 0,105 $/1M | | |
| --- | |
| # Endurance agentique — 553 tours de codage en boucle | |
| Un pic à 575 tok/s sur 900 tokens ne dit rien de la tenue sur une session. Test | |
| simulant le régime réel de Claude Code : prompt système volumineux, tours | |
| successifs avec résultats d'outils réinjectés, sorties de code, contexte qui | |
| gonfle puis se compacte. | |
| ``` | |
| 553 tours 387 100 tokens emis contexte 2 484 -> 35 352 | |
| debit soutenu 268,2 tok/s 0,456 $ / 1M | |
| par tour min 108 mediane 330 max 453 | |
| premier tiers 301,0 tok/s | |
| dernier tiers 304,6 tok/s <- aucune derive | |
| ``` | |
| **Le débit ne se dégrade pas sur la durée.** La variation par tour suit le cycle | |
| du cache de préfixe, pas une fatigue du serveur. | |
| ## Le compactage de l'historique décide de la moitié du débit | |
| Première version du test : retirer les deux plus anciens messages **à chaque | |
| tour** dès que l'historique dépasse une taille. Le préfixe change donc à chaque | |
| requête, le cache est invalidé en permanence : | |
| ``` | |
| elagage tour par tour 173,8 tok/s soutenus | |
| compactage par blocs 268,2 tok/s soutenus +54 % | |
| ``` | |
| Visible dans le journal au tour 127 : le compactage retire 32 messages, le | |
| contexte retombe de 35k à 19k, le tour suivant tombe à 165 tok/s le temps que | |
| le cache se reconstruise, puis remonte immédiatement à 360-410. | |
| **Un compactage rare et massif coûte un tour. Un élagage permanent coûte la | |
| moitié du débit.** Cela vaut pour tout client agent qui gère une fenêtre. | |
| ## Les trois régimes, à ne pas confondre | |
| ``` | |
| pic, 900 tokens, contexte court 575 tok/s 0,21 $/1M | |
| tour isole a 20k de contexte 421 tok/s | |
| session soutenue, compactage compris 268 tok/s 0,46 $/1M | |
| ``` | |
| Le troisième est celui qui décrit un usage réel. C'est lui qu'il faut citer | |
| pour dimensionner, et non le premier. | |
| --- | |
| # Plusieurs sessions Claude Code simultanées | |
| Deux mécanismes indépendants, qui tirent en sens contraire. | |
| ## Le cache de préfixe est global, et c'est un atout | |
| Ce n'est pas un cache par session mais un arbre partagé entre toutes les | |
| requêtes. Plusieurs clients Claude Code envoient le **même** prompt système : | |
| il n'est donc prefillé qu'une fois pour tous. | |
| ``` | |
| sessions blocs reutilisees | |
| 2 96 % (24 480 / 25 369) | |
| 4 96 % (49 104 / 50 997) | |
| 8 92 % (94 208 / 102 253) | |
| ``` | |
| Il survit aussi **entre** les campagnes : un second passage sur les mêmes | |
| prompts est parti à 187,5 tok/s agrégés contre 115,1 au premier, sans autre | |
| changement que le cache déjà chaud. | |
| ## La spéculation, elle, ne gagne que si l'on est seul | |
| Sessions agentiques simultanées, même matériel, même protocole : | |
| ``` | |
| AVEC spec n=24 SANS spec ecart | |
| sessions agrege /session agrege /session | |
| 1 - - 121,8 121,8 | |
| 2 187,5 93,8 204,2 102,1 +9 % | |
| 4 240,4 60,1 333,9 83,5 +39 % | |
| 8 323,8 40,5 503,2 62,9 +55 % | |
| ``` | |
| Les brouillons consomment le budget de batch : dès **deux** sessions la | |
| spéculation coûte plus qu'elle ne rapporte, et l'écart se creuse. Elle reste | |
| imbattable en solo sur de l'édition de code (575 tok/s), régime où elle émet | |
| 17,16 tokens par lecture des poids. | |
| **Le défaut du dépôt est donc `VL_SPEC=off`.** Passer à `on` pour un usage | |
| strictement mono-session. | |
| ## Ce que ça donne concrètement | |
| ``` | |
| 1 session, edition de code, spec on 575 tok/s 0,21 $/1M | |
| 2 sessions, spec off 102 tok/s ch. 0,60 $/1M | |
| 4 sessions, spec off 83 tok/s ch. 0,37 $/1M | |
| 8 sessions, spec off 63 tok/s ch. 0,24 $/1M | |
| ``` | |
| Le débit par session baisse, le coût au million s'améliore : la carte est | |
| mieux occupée. Huit sessions Claude Code simultanées tiennent sur une seule | |
| A40 à 0,44 $/h, à 63 tok/s chacune. | |
| La capacité KV n'est pas le mur : ~450 000 tokens de cache pour des sessions | |
| qui plafonnent autour de 35 000 chacune, soit une douzaine avant saturation. | |
| --- | |
| # Le plafond de l'A40, et pourquoi 8 × 200 tok/s n'y tient pas | |
| ## La montée en charge, jusqu'au mur | |
| ``` | |
| sessions agrege /session $/1M cache partage | |
| 1 122,9 122,9 0,995 76 % | |
| 2 209,5 104,7 0,583 95 % | |
| 4 331,2 82,8 0,369 94 % | |
| 8 512,1 64,0 0,239 96 % | |
| 16 776,8 48,5 0,157 93 % | |
| 32 1 159,7 36,2 0,105 92 % | |
| ``` | |
| À 32 sessions : 36,25 pas par seconde × ~18 Go de poids lus par pas = | |
| **660 Go/s sur les 696 disponibles, soit 95 % du plafond de bande passante**. | |
| Le débit agrégé de ~1 160 tok/s est la limite matérielle, pas un défaut de | |
| réglage. | |
| Une cible de 8 × 200 = 1 600 tok/s demanderait 200 pas/s, soit **~2 000 Go/s** — | |
| presque trois fois ce que la carte peut lire. | |
| ## L'ordonnancement asynchrone ne donne rien | |
| vLLM le refusait tant que la spéculation tournait ; une fois celle-ci coupée, il | |
| devient disponible. Résultat, dans le bruit : | |
| ``` | |
| sessions sans async avec async | |
| 1 121,8 122,9 | |
| 2 204,2 209,5 | |
| 4 333,9 331,2 | |
| 8 503,2 505,5 | |
| ``` | |
| Cohérent avec le reste : dans ce régime on est **borné par le GPU**, il n'y a | |
| donc pas d'ordonnancement CPU à recouvrir. | |
| ## La spéculation perd à toute profondeur, dès deux sessions | |
| Testée à deux profondeurs pour écarter l'idée qu'une profondeur faible | |
| ménagerait le budget de batch : | |
| ``` | |
| sessions sans spec spec n=24 spec n=4 | |
| 4 83,5 60,1 25,3 | |
| 8 64,0 40,5 22,1 | |
| 16 48,5 - 23,1 | |
| ``` | |
| `n=4` est **encore pire** que `n=24`. Le coût n'est donc pas le budget de batch | |
| mais la **vérification payée sur chaque séquence** : plus il y a de séquences, | |
| plus elle pèse. La spéculation reste imbattable en solo (575 tok/s en édition | |
| de code, 17,16 tokens émis par lecture des poids) et perdante partout ailleurs. | |
| ## Comparatif matériel, sur ce qui borne vraiment | |
| | | $/h | VRAM | bande passante | vs A40 | $/token relatif | | |
| |---|---|---|---|---|---| | |
| | **A40** | 0,44 | 48 Go | 696 Go/s | — | **référence** | | |
| | RTX 4090 | 0,74 | 24 Go | 1008 Go/s | 1,45× | 1,16× | | |
| | L40 | 0,82 | 48 Go | 864 Go/s | 1,24× | 1,50× | | |
| | L40S | 0,99 | 48 Go | 864 Go/s | 1,24× | 1,81× | | |
| | A100 SXM | 1,59 | 80 Go | 2039 Go/s | 2,93× | 1,23× | | |
| **L'A40 est l'optimum économique** : toute alternative coûte plus cher au token. | |
| Elles achètent de la latence, pas de l'économie. Le RTX 4090 est en outre | |
| disqualifié par ses 24 Go, qui ne laissent presque rien au KV. | |
| Une cible de 200 tok/s par session à 8 sessions n'est atteignable que sur A100 | |
| SXM (~190 tok/s attendus, extrapolés de la bande passante — non mesurés). | |
| --- | |
| # L'élagage des experts ne donne rien — et ce que ça révèle | |
| Hypothèse testée : à forte concurrence tous les experts finissent par être lus à | |
| chaque pas, donc c'est la taille **totale** du modèle qui borne le débit, pas les | |
| 3 B actifs. Réduire la taille devrait donc augmenter le débit proportionnellement. | |
| `mattbucci/Qwen3-Coder-REAP-25B-A3B-AWQ` : 103 experts au lieu de 128, | |
| **12,9 Gio au lieu de 16,9** (−24 %). Gain attendu ~1,31×. | |
| ``` | |
| sessions 30B complet (16,9 Gio) REAP-25B (12,9 Gio) | |
| 1 122,9 124,9 | |
| 8 512,1 511,8 | |
| 32 1 159,7 1 171,6 | |
| ``` | |
| **Identique au bruit près.** L'hypothèse est réfutée : le débit n'est pas borné | |
| par la lecture des poids. | |
| ## Ce que le mur n'est pas | |
| ``` | |
| lecture des poids -24 % de poids -> 0 % de debit refute | |
| calcul brut 1171 tok/s x 3 G x 2 = ~7 TFLOPS | |
| sur ~150 TFLOPS disponibles = 5 % ecarte | |
| ordonnancement CPU --async-scheduling : dans le bruit refute | |
| speculation perdante a n=24 ET a n=4 refute | |
| ``` | |
| Il reste **l'efficacité des kernels MoE** : la dispersion des tokens vers 128 | |
| experts, avec très peu de lignes par expert et par pas. C'est un mur | |
| d'implémentation, pas de matériel — ce qui explique d'un coup pourquoi aucun des | |
| quatre leviers testés n'a bougé le débit. | |
| ## Décision | |
| Le modèle non élagué est conservé : le REAP coûte 20 % des experts pour zéro | |
| gain mesuré, donc de la qualité perdue sans contrepartie. Il reste disponible | |
| (`VL_MODEL=reap`) au cas où les 4 Go de VRAM libérés seraient utiles au KV. | |
| --- | |
| # Le modèle dense : quatrième hypothèse réfutée | |
| Expérience discriminante. Si le plafond venait de la dispersion des tokens vers | |
| 128 experts, un modèle **dense** de taille totale comparable devait se comporter | |
| différemment. `cyankiwi/Qwen3.8-27B-AWQ-INT4` : 19,6 Gio, aucun expert. | |
| ``` | |
| sessions MoE Coder-30B Dense 27B ecart | |
| 1 122,9 26,2 4,7x plus lent | |
| 8 512,1 167,1 3,1x plus lent | |
| 32 1 159,7 320,8 3,6x plus lent | |
| montee 32/1 9,4x 12,2x | |
| ``` | |
| Le dense monte légèrement mieux en concurrence, mais part de si bas que ça ne | |
| compense jamais. **La dispersion MoE n'est pas le mur — c'est l'avantage** : le | |
| MoE ne lit que ~5 Gio d'actifs par token là où le dense en lit 19,6. | |
| ## Ce que le plafond n'est pas | |
| Cinq mécanismes testés, cinq réfutations : | |
| ``` | |
| lecture des poids -24 % de poids -> 0 % de debit | |
| calcul brut MoE ~5 %, dense ~11 % du plafond FLOPS | |
| bande passante MoE ~45 %, dense ~30 % des 696 Go/s | |
| dispersion MoE le dense est 3 a 4x plus lent | |
| ordonnancement CPU --async-scheduling dans le bruit | |
| speculation perdante a n=24 ET a n=4 | |
| ``` | |
| **Aucun mécanisme validé** n'explique le plafond de ~1 160 tok/s. Ce qui est | |
| établi, c'est ce qu'il n'est pas. Publier cette liste vaut mieux qu'habiller une | |
| sixième hypothèse en explication. | |
| ## Configuration retenue | |
| `cyankiwi/Qwen3-Coder-30B-A3B-Instruct-AWQ-4bit`, spéculation coupée, | |
| ordonnancement asynchrone, `max-num-seqs 64`, KV en fp8, contexte 262 144. | |
| C'est la meilleure combinaison mesurée, et les six variantes essayées sont | |
| documentées ci-dessus pour qu'on ne les retente pas. | |