Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

LLM зацикливается в продакшене: как локализовать петлю повтора без слепой смены параметров

LLM зацикливается в продакшене: как локализовать петлю повтора без слепой смены параметров

Диагностика зацикливания LLM в продакшене без слепой смены параметров

Представьте ситуацию: сервис на базе языковой модели три недели работает без нареканий, но неожиданно начинает возвращать пользователям отчёты, где с середины текста раз за разом дублируется один и тот же абзац, после чего генерация обрывается. В логах бэкенда нет ни единой ошибки, однако метрика p99 уходит в небеса, GPU полностью загружен фазой декодирования (decode), а затронутые запросы генерируют в разы больше выходных токенов, чем обычно.

В инженерной практике и специализированной литературе подобное поведение называют деградацией текста (text degeneration) или петлёй повтора (repetition loop). Когда происходит такой инцидент, у команды инженеров возникает естественный соблазн сразу изменить параметры сэмплинга — например, повысить штраф за повторы. Однако такой подход часто не только не решает проблему, но и ухудшает работу системы. Давайте подробно разберём, как локализовать причину деградации с позиции эксплуатации продакшен-систем.

Настройка параметров SamplingParams в vLLM
Конфигурирование SamplingParams в vLLM для предотвращения деградации вывода.
Анализ logprobs вероятностей токенов в vLLM
Анализ вероятностей токенов на границе входа в петлю зацикливания.
Оптимизация системного промпта для предотвращения петель
Сравнение структурированного промпта со списками и оптимизированного текстового формата.
Мониторинг метрик деградации LLM в Grafana
Дашборд мониторинга показателей degeneration_rate и wasted_output_tokens.

Разбор видов зацикливания: почему важно отличать типы повторов

Термин «зацикливание» объединяет три совершенно разных сбоя, каждый из которых требует своего метода устранения:

Реклама
  • Токеновый повтор: модель «залипает» на единственном токене или слове и начинает бесконечно воспроизводить его. Этот сбой обнаруживается и задерживается простыми проверками.
  • Повтор фрагментов (текстовый): модель начинает циклично генерировать целые фразы, списки или абзацы. Именно этот тип аномалии приводит к характерному бимодальному распределению длины ответов и перерасходу GPU-ресурсов.
  • Семантическая петля: генератор ходит по кругу смыслов, но переформулирует мысли разными словами. Детекторы n-грамм здесь бессильны. Подобное поведение наиболее характерно для рассуждающих (reasoning) моделей.

В этом материале я сосредоточусь на диагностике и нейтрализации второго типа — циклических повторов текстовых фрагментов.

Исходные условия: что видит дежурный инженер

Для наглядности рассмотрим типичный сервис генерации отчётов. Бэкенд написан на Kotlin, взаимодействие наружу идет по синхронному REST, а внутри организован стриминг. Модель открытого класса 30B развёрнута локально с помощью движка vLLM и доступна через OpenAI-совместимый API. Нагрузка составляет около 8 RPS в пике, а целевая длина нормального ответа лежит в диапазоне 600–900 токенов.

Пример зацикливания ответов LLM и перерасход GPU
График расхода GPU-времени и проявления текстовой деградации при генерации.

Запросы отправляются со следующими базовыми настройками сэмплинга:

SamplingParams(
    temperature=0.7,
    top_p=0.9,
    repetition_penalty=1.0,
    presence_penalty=0.0,
    frequency_penalty=0.0,
    max_tokens=4096,
    stop=None,
)

При анализе 41 200 запросов за сутки инженер видит аномальные сигналы:

  • finish_reason: 6,6% запросов завершились по причиненному лимиту length.
  • Длина аномальных ответов: ровно 4096 токенов без исключений. Это свидетельствует об упоре в жесткий потолок, а не о естественном завершении мысли.
  • Распределение длин: четко бимодальное с двумя пиками — около 740 токенов для здоровых ответов и ровно 4096 токенов для сбойных. Пространство между ними практически пустое.
  • Хвост ответа: многократно повторяющийся один и тот же абзац.
  • Задержка (latency): p50 находится на уровне 4,1 секунды, тогда как p99 вырастает до 38 секунд.

Системный промпт при этом содержит 112 строк с нумерованными списками, вложенными маркерами и таблицей вариантов ответов. Меняться модель или провайдер инференса не могут — проблему необходимо решить на стороне текущей инфраструктуры.

Реклама
Схема самоподдерживающегося режима генерации LLM
Принципиальная схема формирования устойчивого состояния повтора в авторегрессионных моделях.

Анализ типовых гипотез и ошибочных решений

Когда на разборе инцидента звучат варианты действий, большинство из них оказываются ошибочными:

  • Гипотеза A: Поднять repetition_penalty до 1.2 и presence_penalty до 1.5. В движке vLLM штрафы presence_penalty и frequency_penalty применяются к сгенерированным токенам, а repetition_penalty наказывает модель в том числе за использование слов из входящего промпта. Кроме того, в структурированных текстах (JSON, таблицы) легитимные повторы ключей и заголовков попадают под штраф, ухудшая итоговое качество.
  • Гипотеза B: Снизить температуру до 0.2. Низкая температура делает выбор детерминированным и прижимает генерацию к наиболе вероятному продолжению. Однако для многих современных моделей (например, Qwen3 в режиме мышления) сжатие распределения или greedy decoding только усиливает устойчивость петли.
  • Гипотеза C: Увеличить max_tokens до 8192. Так как сбойные запросы упираются ровно в потолок 4096 токенов, удвоение лимита не поможет модели «дописать мысль», а лишь вдвое увеличит затраты времени GPU на зацикленные запросы.
  • Гипотеза D: Проверить инфраструктуру. Версия vLLM, параметры квантизации KV-кэша и шаблоны чата действительно могут провоцировать деградацию вывода. Если инцидент совпал с обновлением стека, эту гипотезу проверяют через анализ полного diff конфигурации.
  • Гипотеза E: Зафиксировать артефакты, воспроизвести поведение и снять замер logprobs. Единственно верный первый шаг. Прежде чем меняться параметры, необходимо собрать профиль инцидента.

Механика процесса: как возникает устойчивая петля генерации

Трансформерная модель не имеет внутреннего правила «чем чаще встречается токен, тем выше его вероятность». Вероятность каждого последующего токена вычисляется авторегрессионно на основе текущего состояния и всего накопленного контекста.

Если в процессе генерации контекст попадает в область распределения, где продолжение повторяющегося фрагмента становится математически наиболее вероятным, формируется самоподдерживающийся режим. Каждый новый сгенерированный повтор добавляется в контекст, усиливая вероятность следующего такого же повтора.

В результате параметр max_tokens из защитного предохранителя превращается в единственный реальный фактор остановки генерации. Сбойный запрос производит в разы больше токенов на фазе декодирования, надолго занимая вычислительные мощности и блокируя ресурсы KV-кэша для других пользователей.

Пошаговый алгоритм проверки гипотез

Чтобы найти истинную причину деградации, я рекомендую соблюдать строгую последовательность действий:

  1. Шаг 1. Фиксация окружения и отпечатка инцидента. Сохраните ревизию модели, токенизатора, шаблон чата, стоп-токены, точные параметры сэмплинга, конфигурацию KV-кэша и версию vLLM. Ошибки в шаблоне чата часто приводят к тому, что модель пропускает токен завершения (EOS).
  2. Шаг 2. Воспроизведение аномального поведения. Проведите несколько прогонов сохраненного запроса с фиксированным seed. Обратите внимание, что онлайн-инференс в vLLM не дает стопроцентной побитовой воспроизводимости без режима batch invariance, поэтому целью является фиксация устойчиво повторяющегося характера ошибки.
  3. Шаг 3. Снятие logprobs на границе начала повтора. Сохраните распределение вероятностей top-N токенов непосредственно перед точкой входа в петлю:
from vllm import LLM, SamplingParams

llm = LLM(model="")
params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=4096,
    logprobs=5,
    seed=42,
)
outputs = llm.generate([problem_prompt], params)[0].outputs[0]

for idx, step in enumerate(outputs.logprobs):
    top_tokens = sorted(step.items(), key=lambda item: item[1].logprob, reverse=True)
    print(idx, [(token.decoded_token, round(token.logprob, 3)) for _, token in top_tokens])

О деградации свидетельствует резкая концентрация вероятностей на лидирующем токене повторяющегося фрагмента при одновременном падении вероятности токена EOS.

Пошаговый алгоритм диагностики зацикливания LLM
Пошаговая схема локализации причин зацикливания от фиксации отпечатка до слоя защиты.
  1. Шаг 4. Локализация источника методом бинарного поиска. Поочередно сокращайте блоки системного промпта наполовину и измеряйте долю аномальных генераций. Если промпт не влияет на появление петли, переходите к сравнению BF16 и квантизованных сборок модели или настроек инференс-движка.
  2. Шаг 5. Корректировка параметров и внедрение защиты. Корректируйте сэмплинг только после анализа промпта и структуры ответа.

Сторонний практический кейс: почему параметры не помогли

В одном из опубликованных практических кейсов команда столкнулась с 15% деградацией генерации при использовании Qwen3 под управлением vLLM. Первоначально инженеры попытались устранить проблему повышением presence_penalty до 1.5, но это не дало результата, а дальнейшее увеличение штрафов привело к росту доли деградаций до 15% обратно.

Причиной зацикливания оказался сам системный промпт, перенасыщенный нумерованными списками, вложенными буллетами и таблицами. После переработки инструкции: сокращения её объёма почти вдвое (на 46%) и перевода списков в связный текстовый формат — доля деградаций упала до нуля.

Это говорит о том, что избыточно четкие структурные шаблоны в промпте способны смещать вероятностное распределение модели в сторону бесконечных повторов аналогичных структур.

Практические рекомендации для внедрения в продакшен

Для предотвращения и ликвидации зацикливаний в боевых системах следует использовать следующие механизмы:

1. Встроенная детекция на уровне vLLM (начиная с версии 0.17.0)

Движок поддерживает досрочное завершение генерации при обнаружении повторяющихся N-грамм в выходе. Запрос получает статус FINISHED_REPETITION:

from vllm import SamplingParams
from vllm.sampling_params import RepetitionDetectionParams

params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=4096,
    repetition_detection=RepetitionDetectionParams(
        min_pattern_size=3,
        max_pattern_size=20,
        min_count=4,
    ),
)

Обратите внимание: пороги нужно калибровать под ваш домен, иначе в коде, Markdown-таблицах или JSON-ответах валидные структуры будут ложно считываться как петля.

2. Легковесный детектор на уровне приложения/стриминга

График распределения длины ответа языковой модели
Бимодальный график длины ответов: нормальный вывод против аномального упора в max_tokens.

Если требуется кастомная логика остановки, можно обрабатывать скользящее окно токенов в процессе стриминга:

from collections import Counter

def check_repetition_degeneration(tokens, window_size=256, ngram_size=16, threshold=0.65):
    tail = tokens[-window_size:]
    if len(tail) < ngram_size * 3:
        return False
    
    ngrams = [tuple(tail[i:i + ngram_size]) for i in range(len(tail) - ngram_size + 1)]
    counts = Counter(ngrams)
    repeated_count = sum(count - 1 for count in counts.values() if count > 1)
    
    return (repeated_count / len(ngrams)) >= threshold

3. Единая система мониторинга деградации

После внедрения автообрезок метрика finish_reason = length перестанет отражать реальную картину. Формируйте целевую метрику degeneration_rate объединенно по всем факторам (упор в max_tokens с повтором, срабатывание repetition_detected и обрыв детектором приложения). Отдельно отслеживайте показатель wasted_output_tokens — объем бесполезно сгенерированных токенов, сожравших GPU-время.

4. Защита от шторма повторных запросов (retry storm)

Автоматический ретрай сбойного запроса без изменений параметров или промпта приведет к тому, что система начнет зацикливаться снова и снова. Используйте экспоненциальный backoff с джиттером, ограничения по дедлайну и паттерн Circuit Breaker.

Ограничения подхода и краевые случаи

Предложенные методы имеют свои границы применимости:

  • N-граммные детекторы бессильны против семантических петель, когда модель ходит по кругу смыслов без точного повторения токенов.
  • Для кодогенерации и структурированного вывода пороги срабатывания детектора должны быть ощутимо ослаблены.
  • Если модель начинает зацикливаться даже на пустом промпте, проблема кроется в повреждении весов, неверной квантизации или сбое слоя инференса.

Главный вывод для инженера

Системный промпт — такой же полноценный инженерный артефакт, как и программный код. Он должен храниться в системе контроля версий, проходить код-ревью и тестироваться на долю деградации вывода. При возникновении аварий крайне важно сопротивляться импульсивному кручению параметров и начинать с фиксации окружения, замера распределения probabilities и системной проверки гипотез.

Источник: habr.com

01.