Skip to Content
ДизайнAdaptive Output Token EscalationПроектирование адаптивного увеличения лимита выходных токенов

Проектирование адаптивного увеличения лимита выходных токенов

Снижает избыточное резервирование GPU-слотов примерно в 4 раза за счёт стратегии «низкий лимит по умолчанию + увеличение при обрезке» для выходных токенов.

Проблема

Каждый API-запрос резервирует фиксированный GPU-слот, пропорциональный max_tokens. Предыдущее значение по умолчанию в 32K токенов означало, что каждый запрос резервировал слот на 32K выходных токенов, хотя 99% ответов содержат менее 5K токенов. Это приводит к избыточному резервированию GPU-ресурсов в 4–6 раз, ограничивая параллелизм сервера и увеличивая затраты.

Решение

Использовать ограниченный лимит по умолчанию в 8K выходных токенов. Если ответ обрезается (модель достигает max_tokens), автоматически выполнить одну повторную попытку с увеличенным лимитом в 64K. Поскольку обрезается менее 1% запросов, это значительно снижает среднее резервирование слотов, сохраняя качество вывода для длинных ответов.

Архитектура

┌─────────────────────────┐ │ Начало запроса │ │ max_tokens = 8K │ └───────────┬─────────────┘ ┌─────────────────────────┐ │ Потоковый ответ │ └───────────┬─────────────┘ ┌─────────┴─────────┐ │ │ finish_reason finish_reason != MAX_TOKENS == MAX_TOKENS │ │ ▼ ▼ ┌───────────┐ ┌─────────────────────┐ │ Готово │ │ Проверка условий: │ └───────────┘ │ - Нет override от │ │ пользователя? │ │ - Нет override от │ │ env? │ │ - Лимит уже не │ │ увеличен? │ └─────────┬───────────┘ ДА │ НЕТ ┌─────────┴────┐ │ │ ▼ ▼ ┌─────────────┐ ┌──────────┐ │ Удалить │ │ Готово │ │ частичный │ │ (обрезан)│ │ ответ модели│ └──────────┘ │ из истории │ │ │ │ Сгенерировать│ │ событие │ │ RETRY │ │ │ │ Повторно │ │ отправить │ │ max_tokens │ │ = 64K │ └─────────────┘

Определение лимита токенов

Эффективное значение max_tokens определяется в следующем порядке приоритета:

ПриоритетИсточникЗначение (известная модель)Значение (неизвестная модель)Поведение при увеличении
1 (высший)Конфигурация пользователя (samplingParams.max_tokens)min(userValue, modelLimit)userValueУвеличение не применяется
2Переменная окружения (QWEN_CODE_MAX_OUTPUT_TOKENS)min(envValue, modelLimit)envValueУвеличение не применяется
3 (низший)Ограниченное значение по умолчаниюmin(modelLimit, 8K)min(32K, 8K) = 8KУвеличивается до 64K при обрезке

«Известная модель» — это модель, имеющая явную запись в OUTPUT_PATTERNS (проверяется через hasExplicitOutputLimit()). Для известных моделей эффективное значение всегда ограничивается заявленным лимитом вывода модели, чтобы избежать ошибок API. Для неизвестных моделей (кастомные развертывания, self-hosted эндпоинты) значение пользователя передается напрямую, так как бэкенд может поддерживать большие лимиты.

Эта логика реализована в трех генераторах контента:

  • DefaultOpenAICompatibleProvider.applyOutputTokenLimit() — OpenAI-совместимые провайдеры
  • DashScopeProvider — наследует applyOutputTokenLimit() от провайдера по умолчанию
  • AnthropicContentGenerator.buildSamplingParameters() — провайдер Anthropic

Механизм увеличения лимита

Логика увеличения лимита находится в geminiChat.ts и размещена за пределами основного цикла повторных попыток. Это сделано намеренно:

  1. Цикл повторных попыток обрабатывает временные ошибки (лимиты запросов, некорректные потоки, валидация контента)
  2. Обрезка не является ошибкой — это успешный ответ, который был прерван
  3. Ошибки из потока с увеличенным лимитом должны напрямую передаваться вызывающей стороне, а не перехватываться логикой повторных попыток

Шаги увеличения лимита (geminiChat.ts)

1. Потоковая передача завершена успешно (lastError === null) 2. Последний чанк имеет finishReason === MAX_TOKENS 3. Проверки guard-условий пройдены: - maxTokensEscalated === false (предотвращение бесконечного увеличения) - hasUserMaxTokensOverride === false (учет намерений пользователя) 4. Удаление частичного ответа модели из истории чата 5. Генерация события RETRY → UI отбрасывает частичный вывод 6. Повторная отправка того же запроса с maxOutputTokens: 64K

Очистка состояния при RETRY (turn.ts)

Когда класс Turn получает событие RETRY, он очищает накопленное состояние для предотвращения несогласованностей:

  • pendingToolCalls — очищается, чтобы избежать дублирования вызовов инструментов, если первый обрезанный ответ содержал завершенные вызовы, которые повторяются в ответе с увеличенным лимитом
  • pendingCitations — очищается, чтобы избежать дублирования цитат
  • debugResponses — очищается, чтобы избежать устаревших отладочных данных
  • finishReason — сбрасывается в undefined, чтобы использовалась причина завершения нового ответа

Константы

Определены в tokenLimits.ts:

КонстантаЗначениеНазначение
CAPPED_DEFAULT_MAX_TOKENS8 000Лимит выходных токенов по умолчанию при отсутствии переопределения пользователем
ESCALATED_MAX_TOKENS64 000Лимит выходных токенов, используемый при повторной попытке после обрезки

Принятые проектные решения

Почему лимит по умолчанию — 8K?

  • 99% ответов содержат менее 5K токенов
  • 8K обеспечивает достаточный запас для немного более длинных ответов без запуска ненужных повторных попыток
  • Снижает среднее резервирование слотов с 32K до 8K (улучшение в 4 раза)

Почему увеличенный лимит — 64K?

  • Покрывает подавляющее большинство длинных выводов, которые обрезались на 8K
  • Соответствует лимиту вывода многих современных моделей (Claude Sonnet, Gemini 3.x, Qwen3.x)
  • Более высокие значения (например, 128K) нивелировали бы преимущества оптимизации слотов для <1% запросов, требующих увеличения лимита

Почему не используется прогрессивное увеличение (8K → 16K → 32K → 64K)?

  • Каждая повторная попытка добавляет задержку (полный ответ должен быть сгенерирован заново)
  • Одна повторная попытка — самый простой подход, покрывающий почти все случаи
  • Частота обрезки <1% при лимите 8K означает, что увеличение лимита требуется почти ни для каких запросов; тем же, кому оно нужно, скорее всего, потребуется значительно больше 16K

Почему увеличение лимита вынесено за пределы цикла повторных попыток?

  • Обрезка — это успешный сценарий, а не ошибка
  • Ошибки из потока с увеличенным лимитом (лимиты запросов, сбои сети) должны передаваться напрямую, а не молча повторяться с некорректными параметрами
  • Позволяет циклу повторных попыток оставаться сфокусированным на своей исходной задаче (восстановление после временных ошибок)
Last updated on