Оптимизация затрат для систем LLM: куда на самом деле идут деньги
Тратьте токены там, где они действительно важны.
Стоимость использования больших языковых моделей (LLM) растет линейно в зависимости от объема использования. Система, обрабатывающая 10 000 запросов в день по цене $0,01 за запрос, обходится в $100 ежедневно — что составляет $365 в год. В масштабах предприятия эта сумма превышает $10 000.
Оптимизация затрат — это не о том, чтобы экономить на мелочах. Это о том, чтобы тратить токены там, где это действительно важно.
Каждый потраченный впустую токен — это токен, который можно было бы потратить на лучший ответ.

Бюджетирование токенов
Самый простой способ контролировать затраты — устанавливать лимиты. На сессию, на задачу или на день.
Стратегия 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: qwen2.5-1.5b
summarize:
max_tokens: 500
model: qwen2.5-7b
code_review:
max_tokens: 2000
model: qwen2.5-coder-7b
reason:
max_tokens: 4000
model: qwen2.5-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 ($/M токенов) | Локальная стоимость/час | Точка безубыточности |
|---|---|---|---|
| GPT-4o | $2.50 / $10.00 | — | Н/Д |
| Claude Sonnet 4 | $3.00 / $15.00 | — | Н/Д |
| Qwen2.5-72B | $0.50 / $2.00 | ~$0.50 | ~4 часа/день |
| Qwen2.5-32B | $0.30 / $1.20 | ~$0.20 | ~2 часа/день |
| Qwen2.5-7B | $0.10 / $0.40 | ~$0.05 | ~1 час/день |
Математика оборудования:
| Оборудование | Первоначальные затраты | Ежемесячное электричество | Точка безубыточности vs API |
|---|---|---|---|
| RTX 3090 (б/у) | $600 | $15 | ~4 месяца |
| RTX 4090 | $1,500 | $20 | ~6 месяцев |
| RTX 5080 | $1,000 | $18 | ~5 месяцев |
| DGX Spark | $2,000 | $30 | ~8 месяцев |
При умеренном использовании — час или более в день — локальный вывод окупается. При интенсивном использовании экономия становится значительной. Подвох заключается в первоначальных капитальных затратах. 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": "qwen2.5-32b", "cost": 0.001},
{"model": "qwen2.5-7b", "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": "qwen2.5-1.5b", "latency": 0.5},
{"model": "qwen2.5-7b", "latency": 2.0},
{"model": "qwen2.5-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 — более мягкий, но существует риск возврата неверных ответов. Измеряйте уровень промахов и корректируйте порог.
Кэширование ответов для распространенных запросов также стоит того. Если пользователи repeatedly спрашивают «какая погода» или «который час», кэшируйте паттерн, а не только точный промпт:
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, которые накапливаются.
Это не имеет значения, когда вы создаете прототипы, используете одну модель или обрабатываете малые объемы. Сложность бюджетирования, резервного переключения и кэширования не стоит того для системы, которая делает 100 запросов в день.
Сначала заставьте работать базовый поток. Добавьте оптимизацию, когда придет счет.
Компромиссы
| Стратегия | Затраты | Качество | Сложность |
|---|---|---|---|
| Без оптимизации | Наивысшие | Стабильное | Низкая |
| Бюджетирование токенов | Умеренные | Переменное | Средняя |
| Резервные модели | Низкие-Средние | Переменное | Средняя |
| Кэширование | Наинизшие | Высокое (для попаданий в кэш) | Средняя |
| Гибридная | Оптимизированные | Оптимизированные | Наивысшая |
Продакшн-системы обычно работают в гибридном режиме. Устанавливайте бюджет на сессию, используйте резервное переключение на основе качества или задержки, кэшируйте все, что можете. Сложность реальна, но реальна и экономия.
См. также
- Стратегии маршрутизации моделей — маршрутизация на основе возможностей, стоимости и задержки
- Защитные механизмы LLM на практике — валидация входных данных, фильтрация выходных данных, безопасность
- Проектирование многомоделевых систем — архитектура для нескольких моделей
- Архитектура LLM — столп системы: маршрутизация, стоимость, защитные механизмы и оркестрация