Когда MoE-модели начинают повторяться: проблема зацикливания генераций
Вы запускаете inference на своей красивом MoE-архитектуре, а модель вместо осмысленного ответа выдает одно и то же слово или фразу снова и снова. Знакомо? Команда GigaChat столкнулась с этим в 2025 году при разработке своей последней MoE-модели. Зацикливание - не просто баг, это системная проблема в архитектуре экспертов.
Зацикливание часто возникает из-за того, что определенный эксперт в MoE-архитектуре начинает доминировать и постоянно активироваться, создавая петлю обратной связи в генерации токенов.
В классических dense-моделях такие проблемы решаются через штрафы за повторение, но в MoE все сложнее. Здесь каждый токен может обрабатываться разными экспертами, и если один эксперт "застревает" в определенном паттерне, стандартные методы не работают.
Метрика на BPE-сжатии: как измерить зацикливание
Команда GigaChat разработала новую метрику для обнаружения зацикливания на ранних этапах. Вместо подсчета повторяющихся n-грамм, они используют BPE-сжатие сгенерированного текста.
Они внедрили эту метрику в пайплайн оценки модели и устанавливали порог: если коэффициент сжатия превышает 0.85, генерация считается зацикленной. Это позволило автоматически отсеивать проблемные генерации во время тестирования.
Баг в SGLang: почему ускорение inference иногда ломает генерацию
При оптимизации serving с помощью SGLang, команда обнаружила, что зацикливание усиливается. Оказалось, что в SGLang версии 1.3.2 (актуальной на начало 2025) был баг в кэшировании состояний экспертов для MoE-моделей.
Баг проявлялся только при определенных конфигурациях экспертов и длинных контекстах. SGLang неправильно кэшировал активации экспертов, что приводило к тому, что один эксперт мог быть использован для нескольких токенов, даже если это было неоптимально.
Решение: перейти на исправленную версию SGLang 1.4.0, где этот баг был исправлен. Также они настроили параметры кэширования вручную, чтобы избежать подобных проблем. Если вы используете SGLang для serving MoE-моделей, обязательно обновитесь до последней версии. Более подробно об оптимизации serving massive MoE моделей можно прочитать в нашем benchmark Qwen3.5-397B на 8x H20 с SGLang.
DPO в FP8: когда оптимизация памяти убивает качество
Direct Preference Optimization (DPO) - стандартный метод тонкой настройки LLM по предпочтениям. Но при работе с MoE-моделями, где память критична, переход на FP8 для DPO казался логичным шагом. Однако первоначальная реализация DPO в FP8 приводила к значительной потере качества.
Проблема в том, что градиенты в DPO очень чувствительны к точности вычислений. При использовании стандартного FP8 (через амплификацию типов) накапливались ошибки округления, которые искажали сигнал предпочтений.
Переход на нативный FP8: как сохранить качество
Вместо использования оберток для автоматического преобразования в FP8, команда GigaChat переписала критические части DPO-алгоритма для нативной работы с FP8. Это включало:
- Пересчет функций потерь с учетом динамического диапазона FP8
- Кастомные оптимизаторы, которые работают с масштабированием градиентов
- Использование mixed precision только для определенных операций, а не для всего графа
Результат: потребление памяти уменьшилось на 60%, а качество модели (оцененное по человеческим предпочтениям) упало всего на 0.5% по сравнению с FP16. Для сравнения, стандартный FP8 приводил к падению на 3-5%.
Практический план: как внедрить эти решения в ваш пайплайн
1 Добавьте метрику BPE-сжатия в ваши тесты
Реализуйте функцию, которая вычисляет коэффициент сжатия для сгенерированного текста. Пороговое значение 0.85 - хорошая отправная точка, но его нужно подбирать под вашу модель и задачу.
import zipfile
import io
def bpe_compression_ratio(text: str) -> float:
"""Вычисляет коэффициент сжатия текста с помощью BPE-подобного алгоритма."""
# Упрощенная реализация: используем фактическое сжатие zip
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
zip_file.writestr("text.txt", text.encode('utf-8'))
compressed_size = buffer.tell()
original_size = len(text.encode('utf-8'))
return compressed_size / original_size if original_size > 0 else 1.0
# Использование
text = "повторение повторение повторение повторение"
ratio = bpe_compression_ratio(text)
print(f"Коэффициент сжатия: {ratio:.3f}")
if ratio > 0.85:
print("Вероятно зацикливание!")
2 Обновите SGLang и настройте кэширование
Убедитесь, что используете SGLang версии 1.4.0 или выше. Для MoE-моделей настройте параметры кэширования экспертов:
# Пример конфигурации для SGLang с MoE
config = {
"cache_config": {
"expert_cache_size": 512, # Количество экспертов, которые кэшируются
"enable_expert_aware_caching": True, # Критически важно для MoE
"max_context_length": 8192
}
}
3 Реализуйте нативный FP8 для DPO
Это сложнее. Вот ключевые изменения в функции потерь DPO:
import torch
import torch.nn.functional as F
def dpo_loss_fp8(policy_chosen_logps, policy_rejected_logps,
reference_chosen_logps, reference_rejected_logps,
beta=0.1):
"""DPO loss с нативной поддержкой FP8."""
# Конвертируем логиты в FP8 для вычислений
# Важно: масштабируем перед softmax
scale = 128.0 # Масштабный коэффициент для FP8
# Вычисляем разницы в логарифмических вероятностях
logits = (policy_chosen_logps - policy_rejected_logps) - \
(reference_chosen_logps - reference_rejected_logps)
# Масштабируем для FP8
logits_fp8 = (logits * scale).to(torch.float8_e4m3fn)
logits_scaled = logits_fp8.float() / scale
# Вычисляем потери
losses = -F.logsigmoid(beta * logits_scaled)
return losses.mean()
Это упрощенный пример. В реальности нужно также адаптировать оптимизатор и обратное распространение.
Ошибки, которых стоит избегать
- Не доверяйте автоматическому mixed precision для DPO. Он не учитывает специфику алгоритма и может испортить градиенты.
- Не используйте старые версии SGLang для MoE. Баг с кэшированием экспертов может привести к необъяснимым зацикливаниям.
- Не полагайтесь только на метрики perplexity для обнаружения зацикливания. BPE-сжатие более эффективно для этой конкретной проблемы.
- Не квантуйте все слои модели в FP8. Некоторые эксперты могут требовать более высокой точности. Подробнее о тонкостях квантования MoE-моделей читайте в нашем опыте с Qwen3-Coder-Next.
Что дальше? Будущее MoE-оптимизаций
Опыт GigaChat показывает, что MoE-модели требуют кастомных решений. Стандартные оптимизации часто ломают что-то в архитектуре экспертов. К 2026 году мы видим тенденцию к специализированным фреймворкам для MoE, которые учитывают эти нюансы из коробки.
Совет: если вы разрабатываете MoE-модель, инвестируйте время в создание собственных инструментов мониторинга и оптимизации. Не полагайтесь на общие решения. И следите за развитием таких проектов, как SGLang и новых библиотек для квантования, которые добавляют поддержку MoE.
Для тех, кто хочет глубже погрузиться в тему оптимизации MoE-моделей на потребительском железе, рекомендую наш гайд MoE на RTX 4090: Как выжать максимум из 24 ГБ VRAM.