コスト削減と高速化
五つの小さな実験です。固定の資料を先頭に置いて 64% 節約、簡潔さを求めて出力を 92% 削減、簡単な質問では思考をオフ、結果キャッシュの自作、並行処理で 10 件のリクエストを 9.5 秒から 2.3 秒に短縮。
- 約 35 分
- 難易度:中級
- 検証:2026-09-14 deepseek-flash
コードと実行結果は実際に動かしたときのまま載せているため、コメントと出力は中国語です。
AI アプリを公開して毎日何千、何万回と呼ばれるようになると、1 回ごとのわずかな節約が積み重なって大きな額になり、1 回ごとに少し速くなるだけで、ユーザーの感じ方はずっとよくなります。
この課では五つの小さな実験をします。どれもプロジェクトにそのまま使える方法で、どれにも実測した数字があります。実験はすべて deepseek-flash を使い、料金は 2026 年 9 月のピーク料金で計算しています。完全なコードは code/06-production/cost_latency.py にあります。
1. 固定の資料を先頭に置く
モジュール 01 第 4 課で DeepSeek のコンテキストキャッシュを扱いました。リクエストの冒頭が以前のあるリクエストと同じなら、その部分はキャッシュヒットの料金で課金され、ミスの 50 分の 1 になります。
実験:三つの異なる質問に、毎回同じ httpx のドキュメント(約 1.5 万トークン)を付けます。一方の書き方は質問を前に、ドキュメントを後ろに置き、もう一方はドキュメントを system メッセージに入れて、質問を後ろに置きます。
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 美元
質問を前に置くと、3 回とも一度もヒットしませんでした。質問が毎回違えばリクエストの冒頭も違い、後ろのドキュメントが同じでも役に立ちません。ドキュメントを前に置くと、1 回目はキャッシュがなく、残りの 2 回はそれぞれ 14976 トークンがヒットしました。合計の費用は 0.01379 ドルから 0.00500 ドルに下がり、64% の節約です。
ここでは 3 回しか聞いていないので、1 回目の「コールドスタート」が大部分を占めています。聞く回数が増えるほど、ドキュメントを前に置く書き方は「毎回質問そのものの分だけ払う」に近づきます。
順番を入れ替えただけで、コードは 1 行も増えていません。自分のプロンプトを確認してみてください。system メッセージに、現在時刻、ユーザー名、ランダムな番号のような毎回変わるものが入っていないでしょうか。それらは最後に移しましょう。
2. モデルに言葉を減らさせる
出力の料金は入力の 4 倍で、しかも生成の速さがユーザーの待ち時間を直接決めます。
q = "httpx 和 requests 有什么区别?"
for label, prompt in [("不做要求", q), ("要求简短", q + "用三句话以内回答。")]:
== 2. 输出长度:不做要求 vs 要求简短
不做要求:输出 650 词元,3.3 秒,0.00078 美元
要求简短:输出 52 词元,0.8 秒,0.00007 美元
「3 文以内で答えて」と一文加えただけで、出力は 650 トークンから 52 トークンに減り、費用は 10 分の 1 に、待ち時間は 3.3 秒から 0.8 秒になりました。
モデルは既定では網羅的に書く傾向があり、それが役立つ場面もあれば、無駄になる場面もあります。ユーザーが本当はどれだけの情報を必要としているかを考え、プロンプトにはっきり書きましょう。max_tokens を設定して強制的な上限にし、まれに延々と書き続けるのを防ぐこともできますが、回答をそのまま打ち切ってしまうこと(モジュール 00 第 3 課)を忘れないでください。主に頼るべきはプロンプトです。
3. 簡単な質問では思考をオフにする
== 3. 简单问题开不开思考
不思考:输出 26 词元,0.7 秒,0.00004 美元 | '用 `params` 参数传字典,如 `httpx.get(url, param'
思考:输出 146 词元,1.9 秒,0.00019 美元 | '用 `params` 参数传字典或元组列表,如 `httpx.get(url, '
「クエリパラメータの渡し方」のような質問では、二つのモードの回答はほぼ同じで、思考をオンにすると 5 倍近くのお金と 1.2 秒余分の待ち時間がかかります。モジュール 02 第 3 課で扱ったとおり、思考は複数ステップの推論が必要な質問に向いています。一つのアプリの質問には難しいものも簡単なものもあるので、種類ごとに分けて扱えます。簡単な照会では思考をオフに、複雑な分析のときだけオンにする。難しさの判定は、次の課の分類器でついでに行えます。
4. 同じ質問には、前回の答えをそのまま返す
多くのアプリで、ユーザーは同じ質問を何度もします。毎回モデルを呼ぶより、答えを保存しておくほうがよいでしょう。
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)
書き方を少しずつ変えて 4 回聞きます。「httpx 怎么设置代理?」(httpx でプロキシを設定するには?)、同じ一文をもう一度、「HTTPX 怎么设置代理?」(大文字で、空白が一つ多い)、「httpx 怎么设置代理」(疑問符なし)。
== 4. 自己做结果缓存:同样的问题直接返回上次的答案
问了 4 次(写法略有不同),实际调用 2 次模型,共 0.00239 美元
最初の 3 回は同じ質問と認識され、モデルは 1 回しか呼ばれませんでした。最後の 1 回は疑問符がなかったので、新しい質問として扱われました。正規化がまだ足りず、句読点も取り除くべきだということです。練習問題 2 でこれを改善してもらいます。
さらに進めて、モジュール 01 第 5 課の埋め込みで「セマンティックキャッシュ」を作ることもできます。意味の近い質問(「プロキシの設定方法」と「プロキシサーバーを設定するには」)も同じキャッシュにヒットさせるのです。ただし注意してください。意味が近いことは答えが同じことを意味しません。「httpx でタイムアウトを設定するには」と「requests でタイムアウトを設定するには」のベクトルはとても近いのです。
結果キャッシュを使えないのは次のようなときです。
- 答えが変わる。リアルタイムのデータやユーザーの個人情報に依存する回答は、ユーザーをまたいだり時間をまたいだりして共有できません。
- マルチターン会話。「じゃあ非同期は?」のような質問は、答えが前の文脈に依存するので、この一文だけではキャッシュできません。
- 多様性が求められる。コピーの作成や名前付けのようなタスクでは、ユーザーはもともと違う結果を求めています。
キャッシュには有効期限を設けます。ドキュメントが更新されれば、古い答えは時代遅れになるかもしれません。
5. 並行処理
== 5. 串行 vs 并发
10 个请求:一个一个来 9.5 秒,5 个并发 2.3 秒
互いに独立した 10 件のリクエストを一つずつ送ると 9.5 秒かかり、5 件ずつ同時に送ると 2.3 秒で済みます。モデル呼び出しの時間の大部分はサーバーを待つことに使われていて、その間プログラムは何もしていないので、ほかのリクエストを同時に送ってまったくかまわないのです。
モジュール 03 第 4 課で、スレッドプールとセマフォで同時実行数を制御する方法を扱いました。同時実行は多ければよいわけではありません。サービス側には同時実行の制限があり、超えれば 429 が返ってきますし、同時実行が多すぎれば自分のマシンやネットワークも耐えられないかもしれません。
その他の方法
- より小さく、より安いモデルに替える。モジュール 01 第 6 課の方法です。自分の評価セットで比べ、安いモデルで基準を満たせるなら安いほうを使います。質問の難しさで振り分けることもできます。簡単なものは小さなモデルに、難しいものだけ大きなモデルに。
- ピーク時間帯を避ける。DeepSeek のオフピーク時間帯の料金はピーク時間帯の半額です(2026 年 9 月時点で、北京時間の平日 9:00~12:00 と 14:00~18:00 がピーク)。急がないバッチ処理、たとえば夜間の評価の実行や、たまったデータの処理は、オフピーク時間帯に回します。
- 不要なコンテキストを減らす。RAG で取るのは 5 チャンクか 3 チャンクか。会話の履歴は 20 件残すか 10 件か。エージェントのツールの戻り値をもっと短くできないか。どれも評価セットで検証し、効果が落ちないことを確かめてから変えます。
- ストリーミング出力。お金は節約できませんが、ユーザーにはずっと速く感じられます(モジュール 03 第 2 課)。
まず測ってから変える
この課のどの最適化も、まず自分のアプリで測ってください。今お金はどこにかかり、時間はどこにかかっているのか。前の課のログがちょうどこの問いに答えてくれます。費用で並べ替えて最も高い種類のリクエストを見つけ、そこから最適化を始めます。最適化した後は、評価セットで効果が悪くなっていないことを確かめます。お金を節約しても答えを間違えるようになったら、割に合いません。
練習問題
cached_askの正規化を改善してください。すべての句読点と空白を取り除き、全角と半角をそろえます。もう一度実験して、4 回の質問でモデルが 1 回しか呼ばれなくなったか確かめてください。- 第 3 課のログ(
traces.jsonl)を使って、エージェントが一つの質問に答えるとき、入力トークンのどれだけの割合がキャッシュにヒットしているかを計算してください。メッセージの順番をどう調整すればヒット率を上げられるか考えてみてください。 - 2 番目の実験で、「3 文以内で答えて」を
max_tokens=60に置き換え、プロンプトは変えないでください。回答はどうなりますか。どちらの方法がよいでしょうか。
確認テスト
1. 同じドキュメントと質問なのに、「ドキュメントが先」が「質問が先」よりずっと安いのはなぜですか?
DeepSeek のキャッシュは、リクエストの冒頭が同じ部分しか認識しません。質問を先に置くと、質問が毎回違うので冒頭も違い、後ろのドキュメントはキャッシュにヒットできず、毎回全額で課金されます。ドキュメントを先に置くと、冒頭のドキュメントが毎回同じなので、1 回目以降はキャッシュにヒットし、50 分の 1 の料金で課金されます。
2. 結果キャッシュを使えないのはどんなときですか?
答えが時間とともに変わる(リアルタイムのデータに依存する、ドキュメントが更新される)とき、答えがユーザーの個人情報に依存するとき、質問が会話の文脈に依存する(たとえば「じゃあ非同期は?」)とき、そしてユーザーがもともと毎回違う結果を望んでいるタスク(コピーの作成、名前付け)です。さらに、古い答えを返さないよう、キャッシュには有効期限を設けます。
3. 並行してリクエストすると合計の所要時間が大きく縮むのはなぜですか?同時実行数は大きいほどよいのですか?
1 回のモデル呼び出しの時間の大部分はサーバーの返答を待つことに使われ、待っている間プログラムは空いているので、複数のリクエストを同時に送れば、その待ち時間を重ねられます。同時実行数は大きいほどよいわけではありません。サービス側の同時実行の制限を超えればレート制限(429)がかかり、自分のマシンやネットワークにも耐えられる上限があるので、セマフォのような方法で同時実行数を制御する必要があります。
質問と議論
このレッスンでつまずいたところは、ここで質問してください。他の人の質問に答えるのも歓迎です。
質問で 3 ポイント、回答で 6 ポイント。審査を通過すると公開されます。
議論を読み込んでいます…