Réduire les coûts et accélérer
Cinq petites expériences – mettre les documents fixes en tête économise 64 %, demander de la concision réduit la sortie de 92 %, désactiver la réflexion pour les questions simples, construire son propre cache de résultats, et la concurrence fait passer 10 requêtes de 9,5 à 2,3 secondes.
- Environ 35 minutes
- Niveau : Intermédiaire
- Testé : 2026-09-14 deepseek-flash
Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.
Une application d'IA en production est appelée des milliers de fois par jour. Économiser un peu à chaque appel finit par faire beaucoup ; gagner un peu de vitesse à chaque appel change nettement le ressenti des utilisateurs.
Cette leçon fait cinq petites expériences, chacune étant une méthode applicable directement à un projet, chacune avec des chiffres mesurés. Toutes utilisent deepseek-flash, avec les prix des heures pleines de septembre 2026. Code complet dans code/06-production/cost_latency.py.
1. Les documents fixes en tête
La leçon 4 du module 01 a présenté le cache de contexte de DeepSeek : si le début d'une requête est identique à celui d'une requête précédente, cette partie est facturée au prix avec cache, un cinquantième du prix sans cache.
Expérience : trois questions différentes, chacune accompagnée de la même documentation de httpx (environ 15 000 tokens). Une formulation met la question d'abord et la documentation ensuite ; l'autre met la documentation dans le message system et la question ensuite.
for label, build in [
("问题在前", lambda q: [{"role": "user", "content": f"问题:{q}\n\n参考文档:\n{DOCS}\n\n一句话回答。"}]),
("文档在前", lambda q: [{"role": "system", "content": f"参考文档:\n{DOCS}"}, {"role": "user", "content": q + "一句话回答。"}]),
]:
== 1. 缓存:固定的资料放在前面,还是问题放在前面
问题在前:三次的缓存命中 ['0/15168', '0/15169', '0/15167'],共 0.01379 美元
文档在前:三次的缓存命中 ['0/15167', '14976/15168', '14976/15166'],共 0.00500 美元
Avec la question en tête, aucun des trois essais ne touche le cache : chaque question est différente, donc le début de la requête aussi, et la documentation identique qui suit ne sert à rien. Avec la documentation en tête, le premier essai n'a pas de cache, les deux suivants touchent chacun 14 976 tokens. Le coût total passe de 0,01379 à 0,00500 dollar : 64 % d'économie.
Il n'y a eu ici que trois questions, et le « démarrage à froid » du premier essai pèse lourd. Plus on pose de questions, plus la formulation avec la documentation en tête se rapproche de « ne payer que pour la question elle-même ».
On a seulement changé l'ordre, sans une ligne de code en plus. Vérifiez vos prompts : le message system contient-il l'heure actuelle, le nom de l'utilisateur, un identifiant aléatoire, bref quelque chose qui change à chaque fois ? Déplacez-le à la fin.
2. Faire moins parler le modèle
La sortie coûte 4 fois plus que l'entrée, et la vitesse de génération détermine directement combien de temps l'utilisateur attend.
q = "httpx 和 requests 有什么区别?"
for label, prompt in [("不做要求", q), ("要求简短", q + "用三句话以内回答。")]:
== 2. 输出长度:不做要求 vs 要求简短
不做要求:输出 650 词元,3.3 秒,0.00078 美元
要求简短:输出 52 词元,0.8 秒,0.00007 美元
Une seule phrase ajoutée, « réponds en trois phrases au plus », et la sortie passe de 650 à 52 tokens, le coût tombe au dixième, l'attente de 3,3 à 0,8 seconde.
Par défaut, le modèle tend à écrire de façon exhaustive ; c'est une bonne chose dans certains cas, du gaspillage dans d'autres. Réfléchissez à la quantité d'information dont vos utilisateurs ont vraiment besoin, et écrivez-la dans le prompt. On peut aussi fixer max_tokens comme limite stricte, pour éviter qu'il écrive sans fin dans certains cas, mais rappelez-vous qu'il coupe net la réponse (leçon 3 du module 00) ; c'est donc surtout le prompt qui fait le travail.
3. Pas de réflexion pour les questions simples
== 3. 简单问题开不开思考
不思考:输出 26 词元,0.7 秒,0.00004 美元 | '用 `params` 参数传字典,如 `httpx.get(url, param'
思考:输出 146 词元,1.9 秒,0.00019 美元 | '用 `params` 参数传字典或元组列表,如 `httpx.get(url, '
Pour une question comme « comment passer des paramètres de requête ? », les deux modes donnent presque la même réponse ; avec la réflexion, cela coûte près de 5 fois plus et fait attendre 1,2 seconde de plus. La leçon 3 du module 02 l'a montré : la réflexion convient aux questions à raisonnement en plusieurs étapes. Dans une application, les questions sont souvent tantôt difficiles, tantôt faciles, et on peut les traiter séparément selon leur type : réflexion désactivée pour les requêtes simples, activée seulement pour les analyses complexes. Pour juger de la difficulté, le classifieur de la leçon suivante peut s'en charger au passage.
4. Même question, renvoyer directement la réponse précédente
Dans beaucoup d'applications, les utilisateurs posent sans cesse les mêmes questions. Plutôt que d'appeler le modèle à chaque fois, on stocke les réponses :
cache = {}
def cached_ask(question):
key = hashlib.sha256(" ".join(question.lower().split()).encode()).hexdigest() # 忽略大小写和多余空格
if key in cache:
return cache[key], 0.0
text, u, _ = ask([{"role": "user", "content": question}])
cache[key] = text
return text, cost(u)
Quatre questions, formulées un peu différemment : « httpx 怎么设置代理? » (comment définir un proxy avec httpx ?), la même phrase une seconde fois, « HTTPX 怎么设置代理? » (majuscules, une espace en plus), « httpx 怎么设置代理 » (sans point d'interrogation).
== 4. 自己做结果缓存:同样的问题直接返回上次的答案
问了 4 次(写法略有不同),实际调用 2 次模型,共 0.00239 美元
Les trois premières ont été reconnues comme la même question, avec un seul appel au modèle. La dernière, faute de point d'interrogation, a été traitée comme une nouvelle question. La normalisation est donc insuffisante : il faudrait aussi retirer la ponctuation. L'exercice 2 vous la fera améliorer.
Plus loin, on peut utiliser les embeddings de la leçon 5 du module 01 pour un « cache sémantique » : des questions de sens proche (« comment configurer un proxy » et « comment définir un serveur proxy ») touchent la même entrée de cache. Mais prudence : sens proche ne veut pas dire même réponse ; les vecteurs de « comment définir le délai avec httpx » et « comment définir le délai avec requests » sont très proches.
Quand ne pas utiliser de cache de résultats :
- La réponse change. Les réponses qui dépendent de données en temps réel ou d'informations personnelles de l'utilisateur ne se partagent pas entre utilisateurs ni dans le temps.
- Conversations à plusieurs tours. Pour une question comme « et en asynchrone ? », la réponse dépend de ce qui précède ; on ne peut pas mettre en cache cette phrase seule.
- La diversité est voulue. Pour rédiger un texte publicitaire ou trouver un nom, les utilisateurs veulent justement des résultats différents.
Le cache doit avoir une durée d'expiration. Quand la documentation est mise à jour, les anciennes réponses peuvent devenir obsolètes.
5. La concurrence
== 5. 串行 vs 并发
10 个请求:一个一个来 9.5 秒,5 个并发 2.3 秒
10 requêtes indépendantes envoyées l'une après l'autre prennent 9,5 secondes ; 5 à la fois, seulement 2,3 secondes. L'essentiel du temps d'un appel de modèle se passe à attendre le serveur ; pendant ce temps, votre programme ne fait rien et peut parfaitement envoyer d'autres requêtes en même temps.
La leçon 4 du module 03 a montré comment limiter la concurrence avec un pool de threads et un sémaphore. Plus de concurrence n'est pas toujours mieux : les fournisseurs ont des limites de concurrence et renvoient 429 au-delà ; avec trop de concurrence, votre machine et votre réseau risquent aussi de ne pas suivre.
Autres moyens
- Un modèle plus petit, moins cher. La méthode de la leçon 6 du module 01 : comparer sur votre jeu d'évaluation ; si le modèle bon marché suffit, prenez-le. On peut aussi répartir selon la difficulté : le simple à un petit modèle, le difficile seulement au grand.
- Éviter les heures pleines. Chez DeepSeek, le prix en heures creuses est la moitié de celui des heures pleines (en septembre 2026, les heures pleines sont de 9:00 à 12:00 et de 14:00 à 18:00, heure de Pékin, les jours ouvrés). Les traitements par lots non urgents, comme les évaluations nocturnes ou le rattrapage de données en retard, vont dans les heures creuses.
- Réduire le contexte inutile. Le RAG récupère-t-il 5 morceaux ou 3 ? L'historique garde-t-il 20 messages ou 10 ? Les retours d'outils de l'agent peuvent-ils être encore plus courts ? Vérifiez chaque point avec le jeu d'évaluation, et ne changez que si la qualité ne baisse pas.
- La sortie en streaming. Elle n'économise rien, mais donne une impression de rapidité bien plus grande (leçon 2 du module 03).
Mesurer d'abord, modifier ensuite
Chaque optimisation de cette leçon doit d'abord être mesurée dans votre propre application : où partent aujourd'hui l'argent et le temps ? Le journal de la leçon précédente répond justement à cette question. Triez par coût, trouvez le type de requêtes le plus cher, et commencez par lui. Après l'optimisation, vérifiez avec le jeu d'évaluation que la qualité ne s'est pas dégradée. Économiser pour répondre faux n'en vaut pas la peine.
Exercices
- Améliorez la normalisation de
cached_ask: retirer toute ponctuation et tout espace, unifier caractères pleine chasse et demi-chasse. Refaites l'expérience : les 4 questions ne donnent-elles plus qu'un seul appel au modèle ? - Avec le journal de la leçon 3 (
traces.jsonl), calculez quelle proportion des tokens d'entrée touche le cache quand l'agent répond à une question. Réfléchissez à la façon de réordonner les messages pour augmenter ce taux. - Dans la 2e expérience, remplacez « réponds en trois phrases au plus » par
max_tokens=60, sans modifier le prompt. À quoi ressemble la réponse ? Quelle méthode est la meilleure ?
Auto-test
1. À documentation et question égales, pourquoi « la documentation en tête » est-il tellement moins cher que « la question en tête » ?
Le cache de DeepSeek ne reconnaît que la partie identique au début de la requête. Avec la question en tête, la question change à chaque fois, donc le début aussi, et la documentation qui suit ne peut pas toucher le cache : tout est facturé plein tarif à chaque fois. Avec la documentation en tête, le début est toujours identique ; après le premier essai, il touche le cache et est facturé à un cinquantième du prix.
2. Quand ne peut-on pas utiliser de cache de résultats ?
Quand la réponse change avec le temps (données en temps réel, documentation mise à jour), quand elle dépend d'informations personnelles de l'utilisateur, quand la question dépend du contexte de la conversation (comme « et en asynchrone ? »), et pour les tâches où l'utilisateur veut justement un résultat différent à chaque fois (texte publicitaire, choix d'un nom). De plus, le cache doit avoir une durée d'expiration pour ne pas renvoyer de réponses périmées.
3. Pourquoi les requêtes concurrentes réduisent-elles autant la durée totale ? Plus de concurrence est-il toujours mieux ?
L'essentiel du temps d'un appel de modèle se passe à attendre la réponse du serveur, pendant lequel le programme est inactif ; envoyer plusieurs requêtes en même temps fait se chevaucher ces attentes. Plus n'est pas toujours mieux : au-delà de la limite du fournisseur, on est limité (429), et votre machine et votre réseau ont aussi leurs limites ; il faut donc contrôler la concurrence, avec un sémaphore par exemple.
Questions et discussion
Bloqué sur cette leçon ? Posez votre question ici. Et si vous pouvez répondre à quelqu'un, n'hésitez pas.
Une question rapporte 3 points, une réponse 6. Les messages paraissent après vérification.
Chargement de la discussion…