Exécuter Qwen3.8-27B sur une seule carte RTX 3090, avec vLLM mis à jour et DFlash2.
Comment une branche de vLLM 0.28.0 verrouillée, avec des embeddings réquantifiés et la spéculation DFlash2, permet de faire fonctionner un modèle hybride de 27 milliards de paramètres sur une carte de 24 Go, et en quoi un contexte trop long ralentit son fonctionnement.
Un modèle de 27 milliards de paramètres fonctionnant sur une seule carte graphique grand public signifie généralement un choix difficile entre la longueur du contexte, la concurrence et la vitesse. Une version modifiée de vLLM adaptée à Qwen3.8-27B change la donne sur une RTX 3090 de 24 GB : dans un travail de type agent-harness, elle atteint environ 177 tokens par seconde en décodage sur une carte ordinaire (non Ti) dont la consommation est limitée à 300 W. Ce guide présente une installation native sans conteneur, explique quelles optimisations permettent cette vitesse, et montre précisément où cette approche cesse d’être efficace afin que vous puissiez déterminer si elle convient à votre charge de travail.
Les deux profils de démarrage présentés ci-dessous compromettent la longueur du contexte au profit du parallélisme et de la vitesse. Les chiffres proviennent d’un travail d’agent sur une seule machine, il convient donc de les considérer comme une indication et non comme une garantie.
| Config | Context | Parallel requests | My measured decode speed |
|------------|-----------|-------------------|--------------------------|
| CTX=fast | 65,536 | 8 | ~177 tok/s |
| CTX=long | 131,072 | 4 | ~122 tok/s |
Puisque le serveur expose une API compatible OpenAI, l’intégration de celle-ci dans un outil d’agent tel que DeepSeek Harness consiste simplement à pointer le client vers une URL de base du type http://<host>:18020/v1 et à fournir une clé d’accès. Les sous-agents peuvent alors envoyer des requêtes en parallèle vers un modèle local qui répond à des vitesses nécessitant auparavant du matériel de datacenter.
Qu’est-ce que le projet qwen38-27b-rtx3090 exactement
Le projet se trouve dans le répertoire syv-ai/qwen38-27b-rtx3090. Il ne s’agit ni d’un nouveau modèle ni d’un nouvel moteur d’inference. Il s’agit d’une branche basée sur vLLM 0.28.0, combinée à un ensemble de correctifs, de scripts de réquantification et de profils de lancement dont le seul but est d’adapter ce modèle hybride spécifique à une carte de 24 GB et de le faire fonctionner rapidement.
docker compose --profile single up -d constitue l’installation complète si vous êtes satisfait des conteneurs. Le répertoire prend également en charge l’exécution manuelle des mêmes étapes dans un environnement virtuel Python, ce qui est la méthode utilisée ici. Cela permet de conserver les journaux visibles, d’éviter un autre gros fichier image sur le disque et de faciliter la compréhension des modifications apportées à chaque étape.
Pourquoi une valeur de tok/s unique ne signifie pas grand-chose
Le débit de ce stack dépend fortement de la tâche. La génération de code est rapide, l’écriture de prose ouverte est plus lente, tandis que la reproduction de texte déjà présent dans le prompt est considérablement plus rapide (la section « Recherche et rédaction » ci-dessous explique pourquoi). La documentation du projet souligne le même point : un chiffre de débit pour ce stack n’a aucun sens si l’on ne sait pas quel prompt l’a généré. La valeur de 177 tok/s a été mesurée avec des charges de travail d’agent ; vos propres résultats dépendront bien plus de la forme de vos prompts que du hasard.
Installation native, sans Docker
- Linux ou WSL2 avec une RTX 3090 de n’importe quelle version (ce sont les 24 GB de VRAM qui comptent)
- nvidia-smi s’exécute sans erreur
Un raccourci pratique pour les prérequis au niveau du système consiste à laisser un agent de codage ayant accès à la shell (Claude Code, Codex, OpenCode ou similaire) gérer l’installation. Pointez-le vers le répertoire et demandez-lui de mettre en marche l’ensemble d’outils sur votre 3090. Lorsqu’un problème survient, l’agent lit l’erreur et la résout sur place, que ce soit en installant python3.12-dev ou libssl via apt, en mettant à jour le pilote ou en changeant d’ensemble d’outils CUDA. Cela transforme la recherche de paquets manquants, qui nécessitait autrefois des heures de recherches sur les forums, en une étape routine. Vérifiez ce que l’agent exécute, comme vous le feriez pour tout outil ayant accès à la shell.
Étape 1 : cloner le répertoire
Commencez par télécharger le répertoire et y accéder.
git clone https://github.com/syv-ai/qwen38-27b-rtx3090
cd qwen38-27b-rtx3090
Étape 2 : créer un environnement virtuel et installer la version vLLM spécifiée
Le fork cible spécifiquement vLLM 0.28.0, car c’est dans cette version que le décodage spéculatif DFlash2 est devenu intégré à vLLM (fusionné via le PR #52816). Les commandes ci-dessous créent un nouvel environnement virtuel et installent cette version précise ainsi que FlashInfer, les outils de téléchargement de Hugging Face, ninja pour la compilation des noyaux et pandas.
python3.12 -m venv venv
venv/bin/pip install -U pip
venv/bin/pip install vllm==0.28.0 huggingface_hub hf_transfer ninja \
flashinfer-python flashinfer-cubin==0.6.13 pandas
Deux détails ici sont susceptibles de provoquer des erreurs :
- Ne permettez pas à pip de passer
flashinfer-pythonà une autre version. Le lanceur définitFLASHINFER_DISABLE_VERSION_CHECK=1, et les correctifs ont été testés précisément avec cette paire de versions spécifiées.
curand.h. Si votre installation CUDA ne dispose pas des en-têtes curand, la compilation échoue dès le premier démarrage et le serveur passe silencieusement à une solution de secours plus lente. La sortie reste correcte, le débit diminue d’environ 5 %, et rien dans les journaux ne vous en informe. Sur Ubuntu avec CUDA 13, installer libcurand-dev-13-0 via apt résout le problème.Le deuxième point est un bon exemple d’échec que aucun contrôle de santé ne détectera. Si vos valeurs semblent légèrement inférieures, vérifiez d’abord ces en-têtes.
Étape 3 : télécharger le point de contrôle de base
Le point de départ est le point de contrôle dbirks/Qwen3.8-27B-W4A16-AutoRound, d’environ 19,5 GB. Activer le mode de transfert Xet à haute performance accélère considérablement la téléchargement.
HF_XET_HIGH_PERFORMANCE=1 venv/bin/hf download \
dbirks/Qwen3.8-27B-W4A16-AutoRound \
--local-dir models/Qwen3.8-27B-W4A16-AutoRound
Étape 4 : préparer le modèle
Les scripts situés dans prepare/ modifient le point de contrôle. Ils réquantisent les matrices d’incorporation d’entrée et de sortie, que les points de contrôle quantisés en version publique laissent à pleine précision, reconstruisent la tête de draft MTP autour d’un vocabulaire plus adapté, puis téléchargent la version rapide ainsi que le drafteur DFlash2 de 1,2 Go. Tout s’exécute sur la CPU et chaque script se termine en quelques minutes.
M=models/Qwen3.8-27B-W4A16-AutoRound
venv/bin/python prepare/quant_lm_head.py $M
venv/bin/python prepare/quant_embed.py $M
venv/bin/python prepare/quant_mtp.py $M
venv/bin/python prepare/build_draft_vocab.py $M --ids prepare/draft_vocab_ids.json
venv/bin/python prepare/fetch_fast_variant.py
venv/bin/python prepare/fetch_dflash2.py
Étape 5 : appliquer la pile de correctifs
Le répertoire patches/ contient environ quinze fichiers .patch couvrant l’implémentation de la quantification pour l’incorporation de données, l’attention split-KV pour la vérification des drafts, des correctifs pour les noyaux int8 de Marlin, le mécanisme de recherche DFlash2 et bien d’autres choses encore. Leur ordre est défini dans patches/series. La boucle ci-dessous supprime les commentaires et les lignes vides de ce fichier, puis applique chaque correctif au paquet vLLM situé à l’intérieur du venv. Elle saute dflash2-backport.patch, qui n’existe que pour les versions de vLLM antérieures à 0.28.0, période où DFlash2 n’était pas encore intégré nativement.
sed -e 's/#.*//' -e 's/^[[:space:]]*//;s/[[:space:]]*$//' -e '/^$/d' patches/series |
while IFS= read -r name; do
[ "$name" = "dflash2-backport.patch" ] && continue
patch -p1 -d venv/lib/python3.12/site-packages/vllm < "patches/$name"
done
Un correctif qui échoue indique presque toujours un incompatibilité de version. Exécutez venv/bin/pip show vllm et assurez-vous qu’il affiche bien 0.28.0.
Étape 6 : vérifier l’installation
Avant de lancer quoi que ce soit, générez une clé API et exécutez le script de vérification en mode hors ligne. Celui-ci s’assure que chaque correctif a bien été appliqué et que le modèle présente la forme attendue.
openssl rand -hex 24 > api_key.txt
bash verify.sh --no-server # checks all patches applied + model shape correct
Un résultat propre signifie que votre installation correspond, mot pour mot, à la configuration de référence utilisée par les mainteneurs. Pour l’inférence auto-hébergée, où une légère dérive de version est une source constante de confusion, cette reproductibilité est particulièrement précieuse.
Étape 7 : lancer le serveur
Toute configuration s’effectue via des variables d’environnement. La commande par défaut active la spéculation DFlash2 ainsi que le cache de préfixes sur la GPU sélectionnée ; le commentaire indique comment passer au profil à contexte long, ou à un profil de 245 k après avoir exécuté kvarn/install.sh.
CUDA_VISIBLE_DEVICES=0 SPEC=dflash2 PREFIX_CACHE=1 bash single-user/start_qwen.sh
# add CTX=long for ~131k context (4 slots), or CTX=huge after kvarn/install.sh for 245k
Il faut toujours définir explicitement CUDA_VISIBLE_DEVICES. Sinon, vLLM a tendance à choisir la GPU disposant de la plus grande quantité de mémoire libre, ce qui, sur une machine multi-GPU, n’est pas nécessairement la carte souhaitée. La clé d’accès est lue depuis api_key.txt ou depuis VLLM_API_KEY ; il faut la définir avant d’exposer le port à des appareils autres que localhost, car sans clé le serveur accepte des requêtes non authentifiées. Les autres paramètres proviennent de .env. Quelques minutes après le démarrage, vous disposez d’une interface compatible OpenAI capable de servir un modèle de 27 milliards de paramètres à partir d’une seule carte, dont le coût est inférieur à celui d’un vélo d’occasion.
D’où provient la vitesse
L’exécution de vLLM avec des points de contrôle quantisés disponibles sur le marché entraîne une perte de performances en plusieurs endroits peu évidents. Le document d’optimisations du projet (docs/optimizations.md dans le répertoire) décrit neuf modifications. Quatre d’entre elles expliquent la majeure partie de l’amélioration.
La quantification des embeddings que tout le monde ignore
Qwen3.8-27B utilise des embeddings non liés, ce qui signifie des tables d’entrée et de sortie distinctes d’environ 2,5 GB chacune. Les points de contrôle « quantisés » publics conservent les deux en bf16, principalement parce que leur quantification est complexe. Les scripts de préparation les convertissent tous deux en int8, libérant 2,6 GB de VRAM sans perte de qualité mesurable. Sur une carte de 24 GB, cela correspond à peu près à la mémoire nécessaire pour gérer le contexte d’un utilisateur supplémentaire.
Exploiter l’architecture hybride
Seuls 16 des 64 couches du modèle sont des couches d’attention conventionnelles dont la mémoire augmente en fonction du contexte. Les 48 restantes sont des couches Gated DeltaNet, un système d’attention linéaire dont l’état par conversation a une taille fixe, que la conversation compte 100 ou 100 000 tokens. Stock vLLM stockait cet état en fp32, ce qui représentait environ 150 MB par requête, et ne pouvait gérer plus de 37 requêtes simultanées malgré une limite configurée à 64. La version forkée le stocke en fp16, la perplexité reste identique à trois décimales, et tous les slots configurés deviennent utilisables.
DFlash2 : rédiger sept tokens en une seule passe
Ce changement a une importance capitale pour la latence des requêtes individuelles. La décodage spéculatif associe un petit modèle de draft à un grand modèle cible. Le modèle de draft propose plusieurs tokens futurs, et le modèle cible les vérifie tous en une seule passe forward, en conservant le préfixe correct et en se régénérant à partir de la première erreur. La vérification est bien moins coûteuse que la génération sur une GPU limitée en mémoire, car les poids sont lus une seule fois pour plusieurs tokens, ce qui permet à chaque étape de produire plus d’un token.
Le module MTP intégré à Qwen enchaîne quatre tentatives les unes après les autres. Le système Fork le remplace par DFlash2, un générateur à cinq couches qui prédit tout un bloc de sept tokens en une seule passe non autoregressive, en utilisant des états cachés provenant de cinq profondeurs différentes du modèle cible. Le générateur lui-même a été quantifié avec GPTQ, passant de 3,85 GB à 1,19 GB, ce qui lui permet d’utiliser la même carte sans concurrencer fortement la bande passante mémoire. Le résultat est d’environ trois tokens par étape au lieu d’un seul. Comme la décodage spéculatif standard accepte ou rejette les tokens provisoires afin que la distribution de sortie finale corresponde uniquement au modèle cible, l’accélération est sans perte ; la précision GSM8K reste entre 96 % et 96,5 % dans toutes les configurations rapportées par le répertoire.
Un vocabulaire provisoire construit à partir des sorties mêmes du modèle
Le rédacteur ne peut proposer que des tokens présents dans son vocabulaire de sortie réduit ; tout ce qui en est exclu est rejeté par définition. Le vocabulaire initial a été tiré de textes web et couvrait 92 % des tokens que ce modèle génère réellement. Les responsables ont collecté 5,4 millions de tokens issus des propres sorties du modèle et ont reconstruit le vocabulaire à partir de ces données, atteignant ainsi une couverture de 97,5 %. Cette seule modification a permis d’augmenter la productivité d’environ 10 %, sans aucun nouvel équipement ni modification du modèle.
Rédaction par recherche pour les textes cités par le modèle
Une autre technique explique les valeurs les plus extrêmes. Lorsque la sortie reproduit du matériel déjà présent dans l’instruction, comme un code source en cours d’édition ou un document collé, le système contourne l’élaborateur et propose directement des tokens à partir du contexte. La reproduction d’un document de 25 000 tokens atteint 381 tok/s sur le matériel de référence. Les agents de codage passent une grande partie de leur temps à reproduire du code apparu quelques instants plus tôt dans l’instruction, ce qui constitue le cas idéal pour cette technique ; c’est d’ailleurs l’une des raisons pour lesquelles les mesures relatives à l’utilisation de ces agents donnent des résultats élevés.
Pourquoi doubler le contexte ne provoque pas d’épuisement de la VRAM
Le lanceur pour un seul utilisateur utilise par défaut un contexte de 65 000 tokens. En passant à CTX=long, ce chiffre est environ doublé, atteignant 131 000 tokens ; on pourrait s’attendre à une erreur de mémoire insuffisante sur une carte de 24 GB. Or, le serveur démarre normalement et fonctionne simplement un peu plus lentement. Plusieurs décisions de conception expliquent ce phénomène.
- Les poids ont une empreinte fixe. Avec la quantification W4A16, le corps du modèle occupe environ 15 GB, indépendamment de la longueur du contexte.
- Pool KV réservé une fois, en octets. Avant de traiter tout trafic, le fork réserve un budget fixe, d’environ 5,2 GiB ici, et vLLM enregistre le résultat, par exemple "Taille du cache KV GPU : 136 429 tokens." Comme le pool est alloué au démarrage, soit il convient, soit le serveur refuse de démarrer. Il n’y a pas d’allocation par requête qui puisse échouer au milieu d’une tâche de l’agent.
CTX=long les stocke en int8, réduisant approximativement de moitié le coût par token. Les mêmes 5,2 GiB peuvent alors contenir 136 429 tokens au lieu de 68 605, doublant ainsi le contexte maximal à 131 k. La perplexité imposée par l’entraîneur sur un passage identique diffère de seulement 0,2 % entre les deux profils.Pourquoi la limite est de 131 072 et non de 136 429
Tout l’espace mémoire n’est pas disponible pour la cache des requêtes. Chaque requête en cours a également besoin de son état DeltaNet de taille fixe, et DFlash2 ajoute huit emplacements d’état spéculatif par requête en plus de cela. Avec quatre emplacements disponibles dans le profil long, le lanceur fixe la capacité maximale du contexte à 131 072, soit environ 4 % en dessous de la capacité totale, laissant ainsi de l’espace pour ces pages d’état et pour l’alignement des blocs. Cet espace réservé est ce qui permet de tenir la promesse d’absence de débordement mémoire : lors du démarrage, on vérifie le scénario le plus exigeant, à savoir une seule requête de longueur maximale alors que tous les autres emplacements sont également en usage.
Qu’abandonnez-vous à la place
Un contexte plus long se paie en vitesse, et non en pannes. Les tokens sont stockés en format int8, et chaque requête réservée préserve des pages d’état issues d’un pool désormais réparti sur un nombre moindre de slots. Le nombre de slots parallèles passe de 8 à 4, et la vitesse de décodage diminue d’environ 177 à environ 122 tok/s. La carte effectue plus de travail avec les mêmes octets et facture en fonction du débit, plutôt qu’en fonction des exceptions.
Ses limites : le contexte profond pour une seule requête
Le modèle performe le mieux avec des contextes courts et moyens, ce qui correspond à ce que les agents utilisent le plus souvent. Une seule requête atteignant 100 k de tokens se comporte différemment. Sur une carte 3090, une requête décodée avec un prompt court affiche environ 107 tok/s, 78 tok/s pour environ 10 k de tokens et 38 tok/s pour environ 43 k de tokens, avant de chuter à environ 31 tok/s près de 100 k de tokens. La cause en est le taux d’acceptation du modèle, qui diminue avec la profondeur du contexte. Le répertoire d’entraînement montre le même effet, avec un taux d’acceptation stable autour de 0,29 quel que soit le type de cache, ce qui indique qu’il s’agit d’une propriété du modèle et de la profondeur du contexte plutôt que d’un bug non corrigé.
À titre de comparaison, une version dérivée basée sur llama.cpp sur le même type de carte atteint une vitesse constante de 65 à 80 tok/s avec un contexte de 150 k. Elle ne dispose ni d’algorithme de prédiction dont les performances déclinent avec le temps, ni de bloc de vérification empêchant le paiement, ce qui fait qu’elle n’est jamais particulièrement rapide mais n’est pas non plus lente. Le tableau ci-dessous compare les deux solutions côte à côte ; notez que la plage de court contexte de vLLM mélange des données relatives à une seule requête avec celles relatives à un agent multi-slot.
| Context depth | vLLM fork (DFlash2) | llama.cpp ATX fork |
|-----------------|---------------------|--------------------|
| short (<4k) | 107–177 tok/s | 65–80 tok/s |
| ~10k | ~78 tok/s | 65–80 tok/s |
| ~43k | ~38 tok/s | 65–80 tok/s |
| ~100k | ~31 tok/s | 65–80 tok/s |
La règle pratique est évidente : si votre travail consiste principalement en la lecture lente d’un très long document, llama.cpp est le choix le plus fiable ; consultez notre guide sur le déploiement d’un modèle Qwen3.8 MoE sur des RTX 3090s grâce au transfert de tenseurs via llama.cpp pour en savoir plus sur cet équilibre. Si votre travail implique de nombreuses conversations de codage de longueur moyenne, la version dérivée vLLM est nettement supérieure.
Pourquoi cela convient aux agents harness
Des outils tels que Pi, Hermes ou DeepSeek Harness ne gèrent pas une seule conversation. Avec des sous-agents, ils génèrent de nombreux threads parallèles en même temps, généralement cinq à douze, chacun d’une longueur moyenne, la plupart réutilisant un prompt système identique ainsi que le même contexte de base de code. C’est précisément cette charge pour laquelle ce fork a été optimisé :
- Le débit global augmente avec la concurrence. Quatre flux concurrents ont permis d’obtenir plus de 300 tok/s au total sur une seule carte 3090. Les benchmarks de référence du dépôt montrent des valeurs comprises entre 279 et 335 tok/s avec quatre requêtes simultanées, et jusqu’à environ 400 tok/s en total avec huit requêtes et MTP.
PREFIX_CACHE=1, 64 requêtes partageant un prompt système de 5,8 k tokens ont été traitées en 17 secondes au lieu de 222 dans les tests de référence du dépôt, avec une latence médiane passant de 95 s à 8 s. Après la première requête, les sous-agents ne paient presque rien pour les instructions partagées. Dans ce modèle hybride, le cache stocke également l’état récurrent de DeltaNet, et non seulement les données KV d’attention, c’est pourquoi une réponse supplémentaire sur un document de 24 k tokens prend environ 1 seconde au lieu de 23.Il existe un plafond. Au-delà d’environ quatre flux de contexte long résidants, vous êtes limité par la manière dont le pool est divisé, et non par les capacités de calcul. Le document indique clairement que huit requêtes simultanées de contexte long performent nettement moins bien que quatre, le décrivant comme « pas un compromis, mais une perte ». Concevoir un système autour d’environ quatre travailleurs parallèles à profondeur moyenne permet d’obtenir le meilleur rendement de la carte. Pour voir un exemple concret de ce que peut créer une configuration locale Qwen3.8-27B en pratique, consultez la création d’une copie locale d’un jeu avec Qwen3.8-27B et Pi.
Points clés
- La vitesse provient du logiciel : embeddings réquantifiés, état DeltaNet en fp16, générateur DFlash2 à sept tokens et vocabulaire adapté aux sorties réelles du modèle. La GPU reste inchangée.
verify.sh avant de faire confiance à tout résultat de benchmark obtenu.Pour plus de détails, le dossier docs/reproductions du répertoire contient des notes expliquant comment reproduire pas à pas le processus hors Docker sur une carte 3090.