LLMシステムのコスト最適化:費用の実際の使途

本当に重要な場所でトークンを活用しましょう。

目次

LLMのコストは利用量に対して線形に比例して増加します。1日10,000リクエスト、1リクエストあたり0.01ドルで処理するシステムの場合、日額コストは100ドル、年間では365ドルになります。エンタープライズ規模では、それが1万ドルを超えます。

コスト最適化とは、ケチをつけることではありません。重要箇所においてトークンを適切に配分することです。

無駄に消費されるトークンは、より良い回答のために使えたはずのトークンです。

LLM cost optimization strategies

トークンの予算管理

コストを制御する最も簡単な方法は、制限を設定することです。セッションごと、タスクごと、あるいは日単位で設定します。

戦略1: セッションごとの予算

セッションごとの予算はシンプルです:

class SessionBudget:
    def __init__(self, budget_tokens: int = 10000):
        self.budget = budget_tokens
        self.used = 0

    def allocate(self, tokens: int) -> bool:
        if self.used + tokens <= self.budget:
            self.used += tokens
            return True
        return False

    def remaining(self) -> int:
        return self.budget - self.used

戦略2: タスクごとの予算

タスクごとの予算はより実用的です。異なるタスクには異なる量のコンテキストが必要だからです:

task_budgets:
  classify:
    max_tokens: 100
    model: qwen3-1.7b
  summarize:
    max_tokens: 500
    model: qwen3-8b
  code_review:
    max_tokens: 2000
    model: qwen2.5-coder-7b
  reason:
    max_tokens: 4000
    model: qwen3-32b

戦略3: 適応型予算

適応型予算は、実際の使用状況に基づいて調整されます。分類タスクが常に80トークンを使っているなら、100トークンを割り当てるのをやめます:

class AdaptiveBudget:
    def __init__(self):
        self.task_history = {}

    def allocate(self, task_type: str) -> int:
        if task_type in self.task_history:
            return int(self.task_history[task_type] * 1.5)
        return 1000

    def record(self, task_type: str, tokens_used: int):
        if task_type not in self.task_history:
            self.task_history[task_type] = tokens_used
        else:
            self.task_history[task_type] = (
                0.9 * self.task_history[task_type] + 0.1 * tokens_used
            )

指数平滑移動平均(0.9の重み)により、最近の使用状況が過去の履歴よりも重視されます。ワークロードの変動度に応じて重みを調整してください。

APIとローカル推論

大規模な利用では、ローカル推論の方がコストが安くなります。損分点はハードウェアとAPIの料金によって異なります。

モデル API (百万トークンあたり$) ローカルコスト/時間 損分点
GPT-4o $2.50 / $10.00 N/A
Claude Sonnet 4 $3.00 / $15.00 N/A
Qwen2.5-72B $0.50 / $2.00 ~$0.50 ~1日4時間
qwen3-32b $0.30 / $1.20 ~$0.20 ~1日2時間
qwen3-8b $0.10 / $0.40 ~$0.05 ~1日1時間

ハードウェアの計算:

ハードウェア 初期費用 月額電気代 APIとの損分点
RTX 3090 (中古) $600 $15 ~4ヶ月
RTX 4090 $1,500 $20 ~6ヶ月
RTX 5080 $1,000 $18 ~5ヶ月
DGX Spark $2,000 $30 ~8ヶ月

中規模の利用(1日1時間以上)では、ローカル推論は自己完結します。高頻度利用では、節約効果は劇的です。ただし、初期投資が必要です。RTX 5080は1,000ドルです。APIの請求は一時停止できますが、ハードウェアはできません。

フォールバック戦略

お気に入りのモデルが高すぎる、または遅すぎる場合、より安価なものにフォールバックします。重要なのは、品質が「十分かどうか」を判断することです。

戦略1: 品質ベースのフォールバック

品質ベースのフォールバックは、出力が閾値を満たすまでモデルを試します:

class QualityFallback:
    def __init__(self, quality_threshold: float = 0.8):
        self.threshold = quality_threshold
        self.models = [
            {"model": "claude-sonnet-4", "cost": 0.015},
            {"model": "qwen2.5-72b", "cost": 0.002},
            {"model": "qwen3-32b", "cost": 0.001},
            {"model": "qwen3-8b", "cost": 0.0004},
        ]

    def route(self, prompt: str) -> str:
        for model_config in self.models:
            result = self.call_model(model_config["model"], prompt)
            if self.evaluate_quality(result) >= self.threshold:
                return result
        return self.call_model(self.models[0]["model"], prompt)

問題は評価そのものです。別のモデルを呼び出さずに品質をどう測定しますか?一部のシステムでは小さな分類器を使います。他ではヒューリスティックなチェック(長さ、構造、キーワードの存在)を使います。これらは完璧ではありません。

戦略2: レイテンシベースのフォールバック

レイテンシベースのフォールバックはよりシンプルです。時間予算を満たす最も高速なモデルにルーティングします:

class LatencyFallback:
    def __init__(self, max_latency: float = 5.0):
        self.max_latency = max_latency
        self.models = [
            {"model": "qwen3-1.7b", "latency": 0.5},
            {"model": "qwen3-8b", "latency": 2.0},
            {"model": "qwen3-32b", "latency": 10.0},
            {"model": "claude-sonnet-4", "latency": 5.0},
        ]

    def route(self, prompt: str) -> str:
        for model_config in sorted(self.models, key=lambda x: x["latency"]):
            if model_config["latency"] <= self.max_latency:
                return self.call_model(model_config["model"], prompt)
        return self.call_model(self.models[0]["model"], prompt)

キャッシング

キャッシングは最も過小評価されているコスト最適化手法です。同一のプロンプトは思っている以上に頻繁に発生します——分類リクエスト、FAQのようなクエリ、繰り返されるツール呼び出しなど。

戦略1: プロンプトキャッシング

完全一致のプロンプトキャッシングはシンプルです:

import hashlib

class PromptCache:
    def __init__(self, max_size: int = 1000):
        self.cache = {}
        self.max_size = max_size

    def get(self, prompt: str) -> str | None:
        key = hashlib.sha256(prompt.encode()).hexdigest()
        return self.cache.get(key)

    def set(self, prompt: str, response: str):
        key = hashlib.sha256(prompt.encode()).hexdigest()
        if len(self.cache) >= self.max_size:
            self.cache.pop(next(iter(self.cache)))
        self.cache[key] = response

戦略2: セマンティックキャッシング

セマンティックキャッシングはより実用的です。意味は同じだがテキストが異なるプロンプトをキャッチします:

from sentence_transformers import SentenceTransformer

class SemanticCache:
    def __init__(self, similarity_threshold: float = 0.95):
        self.model = SentenceTransformer('all-MiniLM-L6-v2')
        self.cache = {}
        self.threshold = similarity_threshold

    def get(self, prompt: str) -> str | None:
        prompt_embedding = self.model.encode([prompt])[0]
        for cached_prompt, cached_response in self.cache.items():
            cached_embedding = self.model.encode([cached_prompt])[0]
            similarity = self.cosine_similarity(
                prompt_embedding, cached_embedding
            )
            if similarity >= self.threshold:
                return cached_response
        return None

    def set(self, prompt: str, response: str):
        self.cache[prompt] = response

閾値は重要です。0.95は厳格で、非常に類似したプロンプトのみが一致します。0.85はより寛容ですが、誤った回答を返すリスクがあります。ミスレートを測定し、調整してください。

一般的なクエリに対するレスポンスキャッシングも価値があります。ユーザーが「天気はどう?」や「何時?」を繰り返し尋ねる場合、正確なプロンプトだけでなくパターンをキャッシュします:

class ResponseCache:
    def __init__(self):
        self.common_queries = {
            "what is the weather": "Check weather API",
            "what is the time": "Check system time",
            "who is the president": "Check current president",
        }

    def get(self, query: str) -> str | None:
        query_lower = query.lower()
        for common_query, response in self.common_queries.items():
            if common_query in query_lower:
                return response
        return None

これは高度ではありませんが、機能します。一般的なクエリは、理由があって一般的なのです。

最適化が役立つ場合

最適化は、大量の処理、ミックスワークロードの実行、または積み上がるAPIコストを負担している場合に重要です。

プロトタイピング中、単一モデルを使用している場合、または少量の処理を行っている場合は関係ありません。1日100リクエストしか行わないシステムに、予算管理、フォールバック、キャッシングの複雑さをかける価値はありません。

まず基本的なフローを動作させましょう。請求書が届いた時点で最適化を追加します。

トレードオフ

戦略 コスト 品質 複雑さ
最適化なし 最高 安定 最低
トークン予算管理 中程度 変動あり 中程度
フォールバックモデル 低-中 変動あり 中程度
キャッシング 最低 高(キャッシュヒット時) 中程度
ハイブリッド 最適化済み 最適化済み 最高

本番システムは通常、ハイブリッド方式で動作します。セッションごとに予算を設定し、品質またはレイテンシでフォールバックし、可能な限りキャッシングします。複雑さは実在しますが、節約効果もまた実在します。

関連項目

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。