Перейти к содержанию
Новое AiManual теперь в MAX Подписаться
Публикация AiManual

Оптимизация MTP: Как настройка n_max удваивает производительность инференса

Тонкая настройка n_max в Multi-Token Prediction может удвоить производительность инференса. Бенчмарки на P100+V100 для Gemma, Qwen и практический гайд со скрипт

Коротко

Что будет в материале

  1. 01

    Почему MTP с настройками по умолчанию - это только половина успеха

  2. 02

    Бенчмарки на реальном железе: как ведут себя Gemma, Qwen и другие

  3. 03

    Практический гайд: как за 30 минут подобрать n_max под свою конфигурацию

  4. 04

    Рекомендации по выбору n_max для популярных GPU и моделей

Почему MTP с настройками по умолчанию - это только половина успеха

Multi-Token Prediction (MTP) - техника ускорения инференса, при которой модель за один прямой проход предсказывает не один токен, а сразу несколько. Стандартная авторегрессионная генерация выдаёт токены последовательно: один шаг - один токен. MTP ломает этот порядок. Модель генерирует основной токен и параллельно - n дополнительных. Если черновые токены совпадают с теми, что модель сгенерировала бы поштучно, их принимают. Один проход заменяет несколько.

Параметр n_max управляет количеством предсказываемых токенов. Он же определяет, сколько вычислений модель тратит на спекулятивные гипотезы. Включить MTP с n_max по умолчанию - всё равно что купить спортивный автомобиль и никогда не переключаться выше второй передачи. Едешь, но потенциал не раскрыт.

На тестовом стенде P100 + 2xV100 разница между дефолтным n_max и оптимально подобранным достигала 50–100% прироста токенов в секунду. Модель физически способна выдать вдвое больше, но параметр по умолчанию душит производительность. Проблема не в железе. Проблема в том, что универсального n_max не существует. Каждая связка «модель + GPU» требует своего значения.

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

Бенчмарки на реальном железе: как ведут себя Gemma, Qwen и другие

Стенд: NVIDIA P100 (16 ГБ VRAM) как основная карта, две Tesla V100 (32 ГБ каждая) для разгрузки слоёв. Тестовый промпт - 512 токенов, генерация 256 токенов, три прогона с прогревом. Замерялись токены в секунду на выходе (output t/s).

Три модели - три стратегии поведения. Ниже - детальный разбор каждой.

Gemma-31b: линейное масштабирование и пределы роста

Gemma-31b показала почти линейную зависимость: каждый дополнительный токен в n_max давал пропорциональный прирост скорости.

n_max t/s (output) Прирост к базе
1 (базовый) 34.2
2 58.1 +70%
3 79.3 +132%
4 96.5 +182%
5 98.1 +187%

Рост остановился на n_max=5. При n_max=6 модель вылетела с ошибкой нехватки VRAM - 16 ГБ на P100 оказалось недостаточно для хранения дополнительных тензоров предсказаний. Линейное масштабирование работает, пока хватает памяти. Дальше - стена.

Для Gemma-31b стратегия проста: увеличивайте n_max до тех пор, пока не упрётесь в лимит VRAM. Каждый шаг даёт ощутимый прирост. На GPU с 24 ГБ и выше можно ожидать n_max=6–8 без деградации.

Qwen: почему больше не значит лучше

Qwen (тестировалась Qwen3.6-27B) повела себя иначе. Производительность росла до определённого порога, а затем падала.

n_max t/s (output) Прирост к базе
1 (базовый) 28.7
2 42.3 +47%
3 52.8 +84%
4 48.1 +68%
5 39.4 +37%

Пик - на n_max=3. Дальнейшее увеличение снижало скорость. При n_max=5 прирост к базе составил всего 37% - вдвое меньше пикового значения.

Причина - в механизме принятия спекулятивных токенов. Модель предсказывает n_max токенов, но принимаются только те, что совпадают с результатом строгой авторегрессионной проверки. Qwen на высоких n_max начинает массово ошибаться в предсказаниях. Вычислительные ресурсы тратятся на генерацию токенов, которые тут же отбрасываются. Накладные расходы на проверку и отбраковку съедают выигрыш от параллельной генерации.

Это критически важный инсайт для продакшен-нагрузки, где несколько одновременных запросов могут усугубить эффект. Детальный разбор поведения MTP под конкурентной нагрузкой показывает, что при 12+ одновременных запросах или контексте от 32k токенов преимущество MTP исчезает полностью.

Сюрприз от младшей Gemma: обратная зависимость

Gemma-2b (младшая модель семейства) преподнесла сюрприз. Зависимость оказалась обратной: чем выше n_max, тем ниже скорость.

n_max t/s (output) Прирост к базе
1 (базовый) 112.4
2 98.7 -12%
3 81.2 -28%
4 67.5 -40%

Оптимальное значение - n_max=1, то есть отключение MTP вообще. Причина - в соотношении вычислительных затрат и выгоды. Маленькая модель генерирует токены быстро. Накладные расходы на предсказание нескольких токенов (дополнительные линейные слои, операции конкатенации, проверка) сопоставимы с затратами на саму генерацию. Параллельное предсказание не окупается.

Это не баг, а архитектурная особенность. Для моделей с числом параметров менее 7B MTP часто не даёт выигрыша. Ресурсы, которые большая модель тратит на один токен и с лихвой окупает параллелизмом, маленькая модель тратит на сам механизм MTP.

Практический гайд: как за 30 минут подобрать n_max под свою конфигурацию

Универсального значения нет. Есть методика подбора. Ниже - скрипт, который прогоняет модель на массиве значений n_max и выдаёт цифры для принятия решения.

Шаблон скрипта для бенчмаркинга MTP

Скрипт использует vLLM как бэкенд. Параметры для других фреймворков (llama.cpp, TGI) отличаются, но логика та же: прогрев, три итерации замера, усреднение. Код адаптирован для моделей с поддержкой MTP.

from vllm import LLM, SamplingParams
import time
import numpy as np

# Конфигурация
MODEL_NAME = "google/gemma-3-27b"  # Замените на свою модель
N_MAX_VALUES = [1, 2, 3, 4, 5, 6, 7, 8]  # Тестируемый диапазон
WARMUP_ITERATIONS = 2
BENCH_ITERATIONS = 5

# Тестовый промпт - 512 токенов, репрезентативный для вашей нагрузки
TEST_PROMPT = "Объясни архитектуру transformer-моделей, включая механизм внимания, позиционное кодирование и многослойную структуру. " * 8

def benchmark_n_max(n_max):
    llm = LLM(
        model=MODEL_NAME,
        max_num_seqs=1,
        enforce_eager=True,
        speculative_model="mtp",
        num_speculative_tokens=n_max
    )
    sampling_params = SamplingParams(
        temperature=0.7,
        max_tokens=256,
        ignore_eos=False
    )
    
    # Прогрев
    for _ in range(WARMUP_ITERATIONS):
        llm.generate([TEST_PROMPT], sampling_params)
    
    # Замер
    latencies = []
    for _ in range(BENCH_ITERATIONS):
        start = time.perf_counter()
        outputs = llm.generate([TEST_PROMPT], sampling_params)
        end = time.perf_counter()
        
        total_tokens = sum(len(o.outputs[0].token_ids) for o in outputs)
        latency = end - start
        latencies.append(total_tokens / latency)
    
    del llm
    return {
        "mean": np.mean(latencies),
        "p95": np.percentile(latencies, 95),
        "p99": np.percentile(latencies, 99),
        "std": np.std(latencies)
    }

# Прогон
results = {}
for n in N_MAX_VALUES:
    try:
        results[n] = benchmark_n_max(n)
        print(f"n_max={n}: mean={results[n]['mean']:.1f} t/s, p95={results[n]['p95']:.1f}")
    except Exception as e:
        print(f"n_max={n}: FAILED - {e}")
        break

# Вывод лучшего
best = max(results.items(), key=lambda x: x[1]["mean"])
print(f"\nОптимальный n_max: {best[0]} ({best[1]['mean']:.1f} t/s)")

Скрипт последовательно создаёт экземпляры LLM с разными значениями num_speculative_tokens. После каждого прогона модель выгружается из памяти - это критично для чистоты эксперимента, чтобы тензоры предыдущего запуска не влияли на следующий.

Интерпретация результатов: как не попасть в ловушку средних значений

Средняя скорость - обманчивая метрика. Представьте: n_max=5 даёт 95 t/s в среднем, но с просадками до 45 t/s на каждом пятом запросе. n_max=3 даёт стабильные 82 t/s без провалов. В продакшене второй вариант предпочтительнее. Пользователь ждёт ответа по самому медленному запросу, а не по среднему.

Смотрите на три метрики:

  • Средняя (mean) - общая картина, грубый фильтр для отсеивания заведомо плохих значений.
  • P95 - скорость, ниже которой не падают 95% запросов. Характеризует стабильность.
  • Стандартное отклонение - разброс. Если при n_max=4 разброс вдвое выше, чем при n_max=3, выбирайте n_max=3, даже если средняя чуть ниже.

Практический критерий выбора: выбирайте n_max с максимальной средней скоростью при условии, что P95 не ниже 85% от среднего значения. Если такого нет - берите значение с лучшим P95.

Для MoE-моделей, таких как Qwen с активируемыми экспертами, метрики могут вести себя непредсказуемо при смене типа нагрузки. Практические тесты MTP на MoE-моделях в llama.cpp показывают, что для кода и естественного языка оптимальные значения n_max могут различаться в 2–3 раза.

Рекомендации по выбору n_max для популярных GPU и моделей

Таблица ниже - эмпирические наблюдения на основе тестов и анализа паттернов. Не догма, а стартовая точка для собственных бенчмарков.

GPU VRAM Рекомендуемый диапазон n_max Примечания
NVIDIA T4 16 ГБ 1–2 Память - главный ограничитель. Модели >13B с MTP могут не поместиться.
NVIDIA P100 16 ГБ 1–3 Аналогично T4, но выше пропускная способность памяти.
NVIDIA V100 32 ГБ 3–5 Достаточно памяти для экспериментов. Для Qwen - искать пик в диапазоне 2–4.
NVIDIA A100 40/80 ГБ 3–6 Для Gemma-31b можно пробовать n_max=6–8. Следить за стабильностью на высоких значениях.
NVIDIA H100 80 ГБ 5–10 Высокая пропускная способность позволяет агрессивные значения. Узкое место - точность предсказаний модели.

Рекомендации по семействам моделей:

  • Gemma (старшие, 27B+) - увеличивайте n_max до упора в VRAM. Модель хорошо предсказывает следующие токены, процент принятия высокий.
  • Qwen (MoE-семейство) - ищите пик в диапазоне 2–4. Выше - почти гарантированная деградация. Для задач с высокими требованиями к точности (код, JSON) можно пробовать n_max=1 и сравнивать с более агрессивными режимами через сравнение DFlash и MTP - иногда альтернативный метод спекулятивного декодирования даёт лучший результат.
  • Младшие модели (<7B) - начинайте с n_max=1. MTP для них часто не окупается. Если модель поддерживает MTP, но бенчмарк показывает деградацию - отключайте, не раздумывая.

Подводные камни и когда MTP может не дать ускорения

MTP - инструмент, а не серебряная пуля. Ситуации, когда настройка n_max не помогает или вредит:

  • Модель не поддерживает MTP. Очевидно, но проверьте документацию. MTP требует наличия дополнительной предсказывающей головы в архитектуре. Модели без неё проигнорируют параметр или упадут с ошибкой.
  • Длинные промпты и большие батчи. При конкурентной нагрузке от 12 одновременных запросов или контексте свыше 32k токенов выигрыш от MTP сходит на нет. Вычисления распараллелены по батчу, и дополнительные спекулятивные токены только забивают compute-юниты.
  • Качество генерации. В тестах на творческих задачах (сторителлинг, диалоги) высокий n_max иногда приводит к повторению фраз и снижению разнообразия лексики. Модель «срезает углы», полагаясь на предсказанные токены, и реже исследует альтернативные продолжения. Количественных данных о деградации качества пока недостаточно для однозначных выводов, но эффект зафиксирован на нескольких конфигурациях.
  • Ограничения фреймворков. vLLM поддерживает MTP начиная с версии 0.6.0. llama.cpp - через флаг --n-max. TGI на момент тестов не имел стабильной поддержки. Проверяйте документацию вашего инференс-сервера перед внедрением.

Отдельный случай - CPU/GPU оффлоуд для MoE-моделей. MTP-голова может использоваться не только для ускорения генерации, но и для предсказания экспертов, которые потребуются на следующем шаге. Исследование предзагрузки экспертов через MTP показывает точность предсказания 78% и потенциальный рост скорости с 35 до 180–200 токенов/с. Это отдельное направление оптимизации, выходящее за рамки подбора n_max.

MTP в контексте других оптимизаций: краткий обзор

MTP не существует в вакууме. Это один из слоёв оптимизации инференса, который комбинируется с другими техниками:

  • Квантизация (FP8, NVFP4, GPTQ, AWQ) снижает требования к памяти и пропускной способности. Меньше бит на вес - больше места для спекулятивных токенов MTP. На A100 квантизованная до INT4 модель может вместить n_max на 2–3 единицы выше, чем FP16-версия.
  • Flash Attention сокращает пиковое потребление памяти на механизме внимания. Освободившаяся VRAM доступна для буферов MTP. Эффект синергетический: Flash Attention экономит память, MTP конвертирует эту память в скорость.
  • Спекулятивная декодировка (draft model) - альтернативный подход, где маленькая модель-черновик предсказывает токены, а большая проверяет. MTP делает то же самое внутри одной модели. В тестах MTP показывает более стабильные результаты (71% принятых токенов против 40–60% у draft-подхода), но draft-модель можно тонко настроить под конкретный домен.

Порядок внедрения оптимизаций для максимального эффекта: сначала квантизация (снижает baseline-потребление памяти), затем Flash Attention (оптимизирует пики), затем подбор n_max для MTP (конвертирует освободившиеся ресурсы в скорость). На выходе - конфигурация, которая на тестовом стенде P100 + 2xV100 давала суммарный прирост до 3.2x против базового инференса без оптимизаций.

Подписаться на канал