Niveau de raisonnement d’un LLM local : quel coût ?
Le niveau de raisonnement rallonge le brouillon interne, pas les poids ni la quantification. Mesurez son coût réel en tokens, contexte et temps CPU ou GPU.
Ce que modifie le niveau de raisonnement d’un LLM local
Le niveau de raisonnement indique au modèle combien de temps il doit réfléchir avant de répondre. Il modifie uniquement la longueur de la phase de raisonnement. Les poids stockés sur disque sont identiques à tous les niveaux, la quantification est identique et la réponse provient de la même passe forward. Ce qui change, c’est le nombre de tokens que le modèle consacre d’abord à son brouillon interne.
Cette distinction est importante en raison de l’utilisation de ces tokens. Avec une API hébergée, les tokens de raisonnement apparaissent sur la facture. Sur un VPS que vous administrez, ils se traduisent par un temps de génération consommé par votre CPU ou votre GPU, ainsi que par de l’espace dans la fenêtre de contexte. Un modèle réglé sur son niveau d’effort maximal peut consacrer la majeure partie de sa sortie au raisonnement avant d’afficher le premier mot de la réponse. Sur du matériel auto-hébergé, cela peut faire la différence entre une réponse en deux secondes et une réponse en deux minutes.
Où se trouve le niveau : dans le chat template, pas dans les poids
Un modèle de raisonnement est entraîné pour produire un segment de raisonnement, généralement encadré par les balises <think> et </think>, avant sa réponse finale. Le niveau d’effort est une instruction que le chat template du modèle écrit dans le prompt. Ce template est un fichier Jinja fourni avec le modèle. Il lit une variable telle que reasoning_effort et génère une ligne différente au niveau du système pour chaque valeur. Le modèle a été entraîné à raccourcir ou à allonger son scratchpad en fonction de cette ligne.
Deux conséquences en découlent. Les noms des niveaux appartiennent au modèle, pas à votre runtime. Un nom indiqué dans la fiche d’un modèle peut donc ne rien signifier pour un autre. De plus, si un élément de la chaîne remplace le chat template du modèle par un template générique, la variable n’est jamais générée et le paramètre reste sans effet, sans message d’erreur.
Vérifiée le 2026-08-20, la fiche du modèle Qwen3.8-27B documente trois niveaux d’effort : low, medium et xhigh, avec xhigh comme valeur par défaut. Il n’existe pas de high. Le raisonnement lui-même est activé avec enable_thinking, par défaut, et la fiche documente également preserve_thinking, activé par défaut, qui conserve dans l’historique de la conversation le raisonnement des tours précédents. gpt-oss utilise à la place low, medium et high. De nombreuses autres familles acceptent uniquement une valeur booléenne. Consultez la fiche correspondant à la version exacte que vous avez téléchargée, car ces noms ne constituent pas une norme. Faire fonctionner un modèle 27B sur un VPS est la première étape. Cette page explique quoi configurer une fois qu’il répond.
Pourquoi un effort de raisonnement élevé coûte plus cher sur un VPS
Tokens de sortie. Les tokens de raisonnement sont générés au même rythme que les tokens de réponse. Ils passent par la même boucle de décodage et dépendent des performances de votre matériel. Supposons qu’une tâche produise 200 tokens de réponse et 4,000 tokens de raisonnement. Vous générez 4,200 tokens, mais le lecteur n’en voit que 200. Votre débit de décodage dépend de la bande passante mémoire et de la quantification choisie. Le seul levier restant est donc le nombre de tokens.
Temps réel. L’utilisateur attend le premier token de la réponse, car tout ce qui précède se résume à un écran vide ou à un indicateur de chargement. Le raisonnement est généré en premier. L’attente correspond donc environ au nombre de tokens de raisonnement divisé par votre débit de décodage, auquel s’ajoute le traitement du prompt. Si vous doublez la longueur du raisonnement, vous doublez cette attente.
Contexte. Les tokens de raisonnement occupent la fenêtre de contexte comme n’importe quels autres tokens. Avec preserve_thinking activé, le brouillon du premier tour reste présent dans le prompt au cinquième tour. Le traitement du prompt ralentit donc à chaque tour, tandis que la fenêtre se remplit par les deux extrémités. Augmenter num_ctx pour le conserver consomme de la mémoire de cache KV. Sur un VPS sans GPU, cette mémoire correspond à de la RAM système dont vous ne disposez peut-être pas en quantité suffisante.
Quand augmenter le niveau, et quand le laisser bas
Augmentez-le pour les tâches où une mauvaise étape intermédiaire fausse le résultat : calculs en plusieurs étapes et conversions d’unités, planification d’une modification sur plusieurs fichiers, code qui doit compiler et problèmes à contraintes où une seule réponse doit satisfaire plusieurs conditions. Dans ces cas, le brouillon de raisonnement joue un rôle réel, et l’allonger est un moyen peu coûteux de détecter une erreur que le modèle aurait sinon conservée.
Laissez-le bas lorsque la réponse figure déjà dans l’entrée et que le travail consiste à la transposer. L’extraction, la classification, l’ajout d’étiquettes, la traduction, la réécriture, la synthèse et la mise en forme relèvent toutes de cette catégorie. Le segment de raisonnement se contente principalement de reformuler la tâche et laisse au modèle la possibilité de revenir sur une première intuition correcte.
Laissez également le niveau bas pour toute interaction en temps réel. Dans une boîte de dialogue ou un éditeur, vous intervenez directement ; une réponse rapide que vous pouvez corriger vaut mieux qu’une réponse lente qu’il faut attendre. C’est le véritable compromis lorsque vous dirigez un agent de programmation vers un modèle local : un agent effectue de nombreux petits appels, et le coût du raisonnement s’applique à chacun d’eux.
Définir le niveau dans llama.cpp
llama.cpp écrit directement la variable dans le template. Le runtime est donc l’endroit où vous pouvez vérifier que le niveau a bien été transmis. Définissez -m sur le fichier GGUF dont vous disposez déjà.
llama-server -m ./qwen3.8-27b-Q4_K_M.gguf \
--jinja \
--reasoning-effort medium \
--reasoning-format deepseek \
-c 32768 \
--host 127.0.0.1 --port 8080--jinja utilise le chat template du modèle et est activé par défaut dans les builds actuels. --reasoning-effort accepte default, minimal, low, medium, high, xhigh ou max, où default signifie conserver la valeur par défaut du template. Cette liste correspond au vocabulaire de llama.cpp, pas à celui du modèle. Utilisez donc uniquement un nom listé par la fiche du modèle : un niveau que le template ne définit pas peut provoquer une erreur du template au moment de la requête. --reasoning-format deepseek déplace le raisonnement de message.content vers message.reasoning_content. Cette séparation peut ainsi être mesurée dans la section suivante.
Pour désactiver le raisonnement au lieu de le raccourcir, définissez vous-même la variable du template :
llama-server -m ./qwen3.8-27b-Q4_K_M.gguf --jinja \
--chat-template-kwargs '{"enable_thinking": false}'--reasoning-budget utilise un mécanisme différent. Il limite le segment de raisonnement en tokens. 0 le termine immédiatement, tandis que -1 le laisse sans limite. Il ne demande pas au modèle de planifier un raisonnement plus court. Ces deux options s’appliquent à l’ensemble du serveur. llama-server n’accepte pas reasoning_effort comme champ par requête. Pour servir simultanément deux niveaux d’effort, vous devez donc lancer deux processus sur deux ports.
vLLM expose la même variable pour chaque requête, dans le corps compatible avec l’API OpenAI :
{"model": "Qwen/Qwen3.8-27B",
"messages": [{"role": "user", "content": "Summarise this changelog in two lines."}],
"chat_template_kwargs": {"reasoning_effort": "medium"}}Comment définir le niveau dans Ollama
Ollama possède son propre champ, think, sur /api/chat et /api/generate. Il accepte true, false ou l’une des valeurs low, medium, high et max, sachant que max demande le niveau le plus élevé proposé par le modèle. Le raisonnement est activé par défaut pour les modèles qui le prennent en charge.
ollama run qwen3.8:27b --think=low "Draft a one line commit message for a README typo fix"{"model": "qwen3.8:27b",
"messages": [{"role": "user", "content": "Which HTTP status code means the request body was too large?"}],
"think": "low",
"stream": false}Le raisonnement revient dans message.thinking et la réponse dans message.content, déjà séparés pour vous. Dans une session interactive ollama run, /set think et /set nothink permettent de l’activer ou de le désactiver sans redémarrer.
Vous constatez maintenant le décalage. Le vocabulaire d’Ollama comprend low, medium, high et max. Le template de Qwen3.8 définit low, medium et xhigh. Il faut donc faire correspondre une valeur à l’autre. De plus, un modèle Ollama contient un template intégré à son tag, et non le fichier Jinja du dépôt d’origine. Le niveau transmis au modèle dépend donc de ce template intégré. Ne supposez pas que le réglage a été pris en compte. La vérification prend environ une minute.
Comment mesurer si le niveau a réellement été pris en compte
Envoyez le même prompt avec plusieurs niveaux, en définissant temperature sur 0, puis comparez le nombre de tokens. Ici, jq construit le corps de la requête pour vous éviter d’échapper les guillemets manuellement.
for level in low medium max; do
body=$(jq -n --arg lvl "$level" '{
model: "qwen3.8:27b",
messages: [{role: "user", content: "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take? Answer in minutes."}],
think: $lvl,
stream: false,
options: {temperature: 0, num_ctx: 8192}
}')
echo "== $level"
curl -s http://localhost:11434/api/chat -d "$body" | jq '{
thinking_chars: (.message.thinking // "" | length),
answer_chars: (.message.content | length),
eval_count: .eval_count,
seconds: (.total_duration / 1e9),
tok_per_sec: (.eval_count / (.eval_duration / 1e9))
}'
doneeval_count correspond à chaque token généré, raisonnement compris. L’écart entre deux niveaux correspond donc presque entièrement au raisonnement. thinking_chars fournit directement cette répartition. Deux conditions doivent être réunies : les nombres doivent varier selon le niveau, et la réponse doit rester correcte avec le niveau inférieur. Si eval_count reste dans la marge de bruit lors des trois exécutions, le niveau est ignoré. Il faut alors utiliser un runtime qui le transmet, et non choisir un autre nom de niveau.
Le temps total ne représente qu’une partie du problème. Mesurez donc le délai jusqu’au premier token de réponse en activant le streaming, puis en vous arrêtant au premier bloc content non vide. Cette méthode nécessite jq et bc.
start=$(date +%s.%N)
curl -sN http://localhost:11434/api/chat -d '{
"model": "qwen3.8:27b",
"messages": [{"role": "user", "content": "Explain what a reverse proxy does, in three sentences."}],
"think": "low",
"stream": true
}' |
while IFS= read -r line; do
if [ -n "$(printf '%s' "$line" | jq -r '.message.content // ""')" ]; then
echo "first answer token after $(echo "$(date +%s.%N) - $start" | bc)s"
break
fi
doneExécutez le test avec low, puis avec max. La différence correspond au temps d’attente supplémentaire. Avec llama.cpp, les mêmes nombres sont renvoyés dans la réponse, sans calcul shell nécessaire :
curl -s http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "local", "temperature": 0,
"messages": [{"role": "user", "content": "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take?"}]}' | jq '{
reasoning_chars: (.choices[0].message.reasoning_content // "" | length),
answer_chars: (.choices[0].message.content | length),
predicted_n: .timings.predicted_n,
tok_per_sec: .timings.predicted_per_second
}'Effectuez ce test sur votre propre serveur. Une comparaison publiée des efforts a été mesurée sur un matériel différent du vôtre. Votre débit de décodage est le facteur qui convertit un nombre de tokens en secondes. Mesurer le nombre de tokens par seconde sur votre propre serveur fournit cette valeur : le nombre de tokens de raisonnement divisé par votre débit de décodage correspond au temps d’attente que vous venez d’ajouter.
Ce qui peut mal tourner
La réponse est tronquée ou content est vide alors que thinking est rempli. La limite de génération a été consommée par le raisonnement. Le paramètre num_predict d’Ollama plafonne la génération entière, raisonnement compris, et le raisonnement intervient en premier. Avec une limite de 512 tokens et un niveau d’effort élevé, la limite peut donc être atteinte avant le début de la réponse. Ollama indique alors "done_reason": "length" pour cette réponse. Augmentez la limite ou réduisez le niveau d’effort. Comment num_predict compte les tokens décrit cette interaction en détail.
Le niveau ne change rien. Le nombre de tokens est identique à tous les niveaux. Soit le runtime ne transmet pas la variable, soit le template ne la lit pas. Vérifiez le template réellement utilisé par votre runtime, et non celui du dépôt d’origine. Avec --jinja et --chat-template-kwargs, llama.cpp injecte la variable manuellement. Cela fournit donc un bon contrôle : si le niveau fonctionne avec llama.cpp mais pas ailleurs, le modèle fonctionne et l’autre runtime ignore la variable.
Un nom de niveau est rejeté. Une erreur de template au moment de la requête, ou un échec sur le premier message alors que le serveur fonctionne correctement, signifie généralement que vous avez transmis un niveau que le template ne définit pas, par exemple high à un modèle dont la fiche ne répertorie que low, medium et xhigh.
Les conversations à plusieurs tours ralentissent à chaque tour. L’ancien raisonnement est conservé dans l’historique. Définissez preserve_thinking sur false si le modèle le prend en charge, ou supprimez le champ thinking des messages que vous renvoyez. Sinon, le traitement du prompt augmente à chaque tour, alors que les réponses restent de la même longueur.
La qualité baisse avec un faible niveau d’effort pour une tâche que vous pensiez simple. Certaines extractions ne sont pas de simples extractions. Si l’entrée nécessite une conversion d’unité ou l’application successive d’une règle, il s’agit d’une tâche de raisonnement avec une sortie courte. Augmentez le niveau pour cet appel uniquement, plutôt que pour l’ensemble du serveur.
Exécuter simultanément deux niveaux
llama.cpp fixe le niveau au démarrage. Une machine qui sert à la fois un éditeur et un traitement batch nocturne doit donc exécuter deux processus sur deux ports, chacun avec son propre --reasoning-effort. Deux processus signifient aussi deux copies des poids en mémoire, sauf si vous exécutez les tâches à des moments différents. Sur un VPS, l’organisation la moins coûteuse consiste généralement à utiliser un serveur avec un faible niveau d’effort pour les opérations attendues par un utilisateur, puis à lancer selon un calendrier un traitement avec un niveau d’effort plus élevé pour les tâches que personne ne surveille. Ce qui se passe lorsque plusieurs utilisateurs partagent un modèle local s’applique également ici : les tokens de raisonnement correspondent à du travail de décodage. Augmenter le niveau d’effort réduit donc votre concurrence effective à peu près dans la même proportion que l’augmentation du nombre de tokens.
FAQ
Quel niveau d’effort de raisonnement dois-je utiliser par défaut ?
Commencez par le niveau le plus bas proposé par le modèle et augmentez-le uniquement pour les tâches que vous avez vu échouer. Plusieurs modèles de raisonnement sont livrés avec un niveau élevé par défaut, et Qwen3.8-27B utilise xhigh, son niveau maximal, par défaut en août 2026. Ce choix vise à obtenir de bons résultats dans les tableaux de benchmarks, qui ne facturent pas le temps. Sur votre propre matériel, vous payez ce temps en secondes. Faites donc du niveau élevé une option à activer selon la tâche, plutôt que le paramètre hérité par chaque requête.
Les tokens de raisonnement sont-ils pris en compte dans la fenêtre de contexte ?
Oui. Ce sont des tokens ordinaires de la sortie, et ils occupent la fenêtre de contexte avec le reste. Leur conservation au tour suivant dépend du runtime et du modèle. La fiche de Qwen3.8 documente preserve_thinking, activé par défaut. Ce paramètre conserve les raisonnements précédents dans l’historique. Une longue conversation contient donc tous les brouillons produits par le modèle. Définissez-le sur false ou supprimez le champ thinking des messages que vous rejouez. Le traitement du prompt ne continue alors plus de croître.
Pourquoi le changement du niveau de raisonnement ne modifie-t-il pas mon nombre de tokens ?
Le paramètre n’atteint pas le chat template. Le niveau est une variable du template. Il ne fonctionne que si le runtime la transmet et si le template fourni avec le modèle la lit. Certains runtimes fournissent leur propre template avec le modèle au lieu du fichier Jinja du dépôt d’origine. La variable est alors ignorée sans qu’aucune erreur ne soit affichée. Vérifiez-le en envoyant le même prompt avec le niveau le plus bas puis le plus élevé, avec temperature à 0, et en comparant eval_count. Si les nombres sont identiques à l’incertitude près, le niveau est ignoré.
Un effort de raisonnement plus faible rend-il le modèle moins précis ?
Cela dépend de la tâche. Il vaut mieux le mesurer que le supposer. Lorsque la réponse figure déjà dans l’entrée, par exemple pour l’extraction ou la réécriture, un brouillon plus court ne change généralement rien. Lorsqu’une étape intermédiaire doit être correcte avant la suivante, par exemple pour une opération arithmétique en plusieurs étapes ou du code qui doit compiler, la précision diminue avec un brouillon plus court. Constituez un ensemble de vingt prompts issus de votre charge de travail réelle, exécutez-les avec deux niveaux et temperature à 0, puis comptez les réponses incorrectes. Ce nombre est propre à votre charge de travail. Aucun tableau publié ne peut vous le fournir.