LLM-компиляторы: SGLang, TensorRT-LLM, MLC — компиляция для максимальной скорости

llminferenceoptimizationsglangtensorrtmlcgpu
← Back to Blog

Введение: почему интерпретация моделей — это bottleneck

Любая современная LLM — это граф вычислений: матричные умножения, нормализации, активации, attention. Стандартный подход — запускать этот граф через PyTorch, который интерпретирует каждый слой по отдельности. Это работает, но медленно.

PyTorch (интерпретация):
  Layer 1: matmul → copy → add → copy → relu → copy → add → copy → softmax → copy
  Layer 2: matmul → copy → add → copy → relu → copy → add → copy → softmax → copy
  ...
  Каждый operation — отдельный kernel launch на GPU
  Каждый copy — переход между host и device memory
  Результат: 1000+ kernel launches на один forward pass

Компилятор:
  Layer 1-12: fused kernel (один запуск на GPU)
  Layer 13-24: fused kernel
  ...
  Результат: 50-100 kernel launches, граф оптимизирован

Факт: Компиляция модели может дать ускорение в 2-4x по сравнению с нативным PyTorch, особенно для коротких запросов и высоких batch-размеров.


Что такое LLM-компилятор?

Определение

LLM-компилятор — это инструмент, который берёт модель (обычно в формате Hugging Face или GGUF) и компилирует её вычислительный граф в оптимизированную последовательность ядер GPU, минимизируя:

  1. Kernel launches — объединяет последовательные операции в fused kernel
  2. Memory transfers — убирает ненужные копирования между host и device
  3. Memory allocation — pre-allocates всё, что можно предсказать
  4. Compute patterns — подбирает оптимальные tile sizes и blocking strategies
┌──────────────┐    ┌──────────────────┐    ┌──────────────────┐    ┌──────────────┐
│  Model       │───▶│  Graph Analysis  │───▶│  Optimization    │───▶│  Compiled    │
│  (PyTorch /  │    │  - Operations    │    │  - Kernel fusion │    │  Binary /    │
│   ONNX /     │    │  - Memory access │    │  - Memory plan   │    │  CUDA code   │
│   Safetensors│    │  - Dependencies  │    │  - Tile sizes    │    │  + weights   │
└──────────────┘    └──────────────────┘    └──────────────────┘    └──────────────┘

Почему это работает?

Проблема PyTorch для LLM:

1. Kernel launch overhead
   - Каждый matmul — отдельный вызов CUDA kernel
   - 1000 слоёв = 1000+ kernel launches
   - Каждый launch: ~5-10 микросекунд
   - Итого: 5-10 мс пустых задержек на один forward pass

2. Memory fragmentation
   - Каждый intermediate tensor — отдельная аллокация
   - GPU memory manager не знает о паттернах доступа
   - Result: fragmentation, cache misses

3. No cross-layer optimization
   - PyTorch оптимизирует каждый op отдельно
   - Не видит что matmul + add можно объединить в fused kernel
   - Не знает что intermediate tensor можно не сохранять

Решение компилятора:
   - Видит весь граф целиком
   - Fusion: matmul → add → relu → softmax = один kernel
   - Memory planning: знает какие тензоры нужны, какие можно выбросить
   - Register allocation: использует register file GPU вместо global memory

Уровень 1: TensorRT-LLM — компилятор от NVIDIA

Что такое TensorRT-LLM?

TensorRT-LLM — это компилятор от NVIDIA, который использует TensorRT для оптимизации LLM. Работает только на NVIDIA GPU с CUDA.

# Установка
# pip install tensorrt-llm

from polygraphy import cache
from polygraphy.backend.trt import Engine, Profile

# Базовое использование
import tensorrt_llm

class TensorRTLLMCompiler:
    """Компиляция модели через TensorRT-LLM"""
    
    def __init__(
        self,
        model_name: str,
        dtype: str = "float16",
        use_fp8: bool = False,
        use_weight_only: bool = False,
        weight_only_precision: str = "int8"
    ):
        self.model_name = model_name
        self.dtype = dtype
        self.use_fp8 = use_fp8
        self.use_weight_only = use_weight_only
        self.weight_only_precision = weight_only_precision
    
    def build_engine(self, output_dir: str, max_batch_size: int = 8, max_input_len: int = 1024, max_output_len: int = 512):
        """Построение TensorRT engine"""
        from tensorrt_llm.tools.polygraphy import build
        
        config = {
            'builder_config': {
                'name': self.model_name,
                'precision': 'float8' if self.use_fp8 else self.dtype,
                'use_weight_only': self.use_weight_only,
                'weight_only_precision': self.weight_only_precision,
            },
            'build_config': {
                'max_batch_size': max_batch_size,
                'max_input_len': max_input_len,
                'max_output_len': max_output_len,
                'gemm_plugin': 'auto',
            }
        }
        
        # Компиляция может занять от 5 минут до часа
        engine = build(
            model_name=self.model_name,
            config=config,
            output_dir=output_dir
        )
        
        return engine

# Бенчмарк: Llama-3-8B на RTX 4090
# PyTorch:        45 токенов/сек
# vLLM:           140 токенов/сек
# TensorRT-LLM:   200 токенов/сек

Оптимизации TensorRT-LLM

1. Layer fusion
   - Объединяет linear + layernorm + activation в один kernel
   - Убирает промежуточные тензоры из global memory
   - Ускорение: 1.5-2x для коротких последовательностей

2. Weight streaming
   - Не хранит все веса в VRAM одновременно
   - Стримит слои по мере необходимости
   - Позволяет запустить 70B модель на 2x A100 (80 ГБ)

3. Multi-query attention optimization
   - Специализированные kerneы для GQA/MQA
   - Лучше memory bandwidth utilization
   - Ускорение: 1.3-1.5x для длинных контекстов

4. FP8 quantization
   - Динамическое квантование на уровне блока
   - 4x меньше memory bandwidth
   - Минимальная потеря качества (<1% perplexity)

Когда использовать TensorRT-LLM?

✅ Используйте когда:
- У вас NVIDIA GPU (A100, H100, RTX 4090)
- Нужна максимальная производительность
- Вы готовы потратить время на компиляцию
- Работаете в production с предсказуемой нагрузкой

❌ Не используйте когда:
- У вас AMD/Intel GPU
- Вам нужно быстрое прототипирование
- Вы часто меняете модели
- Вам нужна portability

Уровень 2: SGLang — компилятор для структурированного вывода

Что такое SGLang?

SGLang — это фреймворк, который комбинирует LLM-компиляцию с поддержкой структурированного вывода. Его ключевая особенность — Runtime Compilation с EAGLE decoding.

# Установка
# pip install sglang

from sglang import function, system, user, assistant, gen, set_default_backend, Runtime

@function
def multi_turn_questionnaire(hello_topic, followup_topic):
    """Пример структурированного диалога"""
    
    # Компилятор оптимизирует prefix caching
    # для повторяющихся частей промпта
    system("You are a helpful assistant.")
    user(f"Tell me about {hello_topic}.")
    assistant(gen("answer", max_tokens=256))
    user(f"What about {followup_topic}?")
    assistant(gen("followup", max_tokens=256))

# Запуск
backend = Runtime(model_path="meta-llama/Llama-3-8B-Instruct")
set_default_backend(backend)

state = multi_turn_questionnaire(
    hello_topic="climate change",
    followup_topic="renewable energy"
).run()

print(state["answer"])
print(state["followup"])

RadixAttention — ключевая оптимизация SGLang

RadixAttention = prefix tree + KV cache sharing

Обычный KV cache:
  Request 1: "Hello, I want to know about Python"
  Request 2: "Hello, I want to know about Rust"
  Request 3: "Hello, I want to know about Go"
  
  Проблема: "Hello, I want to know about " повторяется 3 раза
  Каждый раз пересчитывается attention для этих токенов

RadixAttention:
  ┌── Tree structure ──┐
  │                     │
  │  [root]             │
  │  ├── "Hello, I want to know about " (SHARED KV cache) │
  │  │   ├── "Python" (unique KV) │
  │  │   ├── "Rust"   (unique KV) │
  │  │   └── "Go"     (unique KV) │
  │                     │
  └─────────────────────┘
  
  Экономия: 31 токен * 2 = 62 токенов не пересчитывается
  Ускорение: 1.5-3x для запросов с общими префиксами

EAGLE — speculative decoding нового поколения

from sglang import Runtime

# SGLang поддерживает EAGLE draft model
runtime = Runtime(
    model_path="meta-llama/Llama-3-8B-Instruct",
    speculative_draft="EAGLE-LLaMA-3-8B",  # Draft model
    speculative_num_steps=4,  # Сколько токенов генерировать за раз
    speculative_eagle_topk=4,  # Сколько кандидатов на шаг
)

# EAGLE vs обычные методы:
# 
# Standard decoding:    140 токенов/сек
# Medusa (head-based): 180 токенов/сек
# EAGLE (model-based): 220 токенов/сек
# 
# EAGLE использует отдельную draft модель, обученную
# предсказывать несколько токенов вперёд. Target модель
# проверяет все токены параллельно (один forward pass).

Structured Generation в SGLang

from sglang import function, gen, system, user, assistant, select

@function
def quiz_bot(question, options):
    """Бот для викторины с выбором из вариантов"""
    
    system("You are a quiz assistant.")
    user(f"Question: {question}\nOptions: {options}")
    
    # select() — это не просто prompt, это constrained decoding!
    # Компилятор генерирует grammar для валидации
    assistant("Answer: " + select(options, name="answer"))

# Компилятор оптимизирует это через:
# 1. Grammar-constrained decoding (только валидные токены)
# 2. Zero hallucination (модель не может выдать несуществующий вариант)
# 3. Faster decoding (меньше токенов нужно проверить)

Уровень 3: MLC LLM — компиляция для любых устройств

Что такое MLC LLC?

MLC LLM использует Apache TVM для компиляции моделей под конкретное железо: мобильные GPU, WebGPU, Raspberry Pi.

# Установка
# pip install mlc-ai mlc-ai-nightly

from mlc_llm import MLCEngine

# Компиляция модели под целевое устройство
# mlc_llm compile Llama-3-8B-Instruct-q4f16_1 \
#   --device cuda \
#   --quantization q4f16_1 \
#   --output dist/Llama-3-8B-Instruct-q4f16_1.json

# Использование
engine = MLCEngine(model="Llama-3-8B-Instruct-q4f16_1", device="cuda")

response, _ = engine.chat.completions.create(
    model="Llama-3-8B-Instruct-q4f16_1",
    messages=[{"role": "user", "content": "What is MLC?"}],
    temperature=0.7,
)

print(response.choices[0].message.content)
engine.terminate()

TVM Compilation Pipeline

┌─────────────────────────────────────────────────────────────┐
│                    MLC Compilation Pipeline                  │
│                                                             │
│  1. Relay IR (High-level)                                   │
│     ┌──────────┐                                            │
│     │ PyTorch  │──▶ Relay graph (symbolic representation)  │
│     │ model    │                                            │
│     └──────────┘                                            │
│          │                                                  │
│          ▼                                                  │
│  2. Optimization Passes                                     │
│     ┌─────────────────────────────────────────────┐         │
│     │ - Operator fusion                           │         │
│     │ - Common subexpression elimination          │         │
│     │ - Memory planning                           │         │
│     │ - Loop tiling & vectorization               │         │
│     └─────────────────────────────────────────────┘         │
│          │                                                  │
│          ▼                                                  │
│  3. Target-specific Codegen                                 │
│     ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│     │ CUDA     │  │ Metal    │  │ WebGPU   │              │
│     │ codegen  │  │ codegen  │  │ codegen  │              │
│     └──────────┘  └──────────┘  └──────────┘              │
│          │                                                  │
│          ▼                                                  │
│  4. Runtime + Model binary                                  │
│     ┌─────────────────────────────────────────┐             │
│     │ Compiled model + TVM runtime (50-200KB) │             │
│     │ Can run on target device standalone     │             │
│     └─────────────────────────────────────────┘             │
└─────────────────────────────────────────────────────────────┘

Бенчмарки MLC на разных устройствах

Модель: Llama-3-8B-Instruct (Q4 quantized)

┌─────────────────────┬───────────────┬──────────────┐
│ Устройство          │ Токенов/сек   │ VRAM/RAM     │
├─────────────────────┼───────────────┼──────────────┤
│ NVIDIA RTX 4090     │ 28            │ 6 ГБ VRAM    │
│ Apple M2 Max        │ 22            │ 6 ГБ unified │
│ iPhone 15 Pro       │ 12            │ 4 ГБ unified │
│ Samsung S24 Ultra   │ 8             │ 3 ГБ GPU     │
│ Raspberry Pi 5      │ 1.5           │ 2 ГБ RAM     │
│ Chrome (WebGPU)     │ 6             │ Browser      │
└─────────────────────┴───────────────┴──────────────┘

Сравнение с llama.cpp:
┌─────────────────────┬───────────────┬──────────────┐
│ Устройство          │ MLC           │ llama.cpp    │
├─────────────────────┼───────────────┼──────────────┤
│ iPhone 15 Pro       │ 12 tok/s      │ 5 tok/s      │
│ Samsung S24 Ultra   │ 8 tok/s       │ 3 tok/s      │
│ Chrome (WebGPU)     │ 6 tok/s       │ N/A          │
│ RTX 4090            │ 28 tok/s      │ 130 tok/s    │
└─────────────────────┴───────────────┴──────────────┘

MLC выигрывает на мобильных (GPU acceleration).
llama.cpp выигрывает на десктопных GPU (CUDA optimization).

WebGPU — LLM в браузере

<!-- MLC можно запустить прямо в браузере -->
<script type="module">
  import { CreateMLCEngineWorker } from 'mlc-ai';
  
  // Создание движка через Web Worker
  const [engine, statusUI] = await CreateMLCEngineWorker(
    "Llama-3-8B-Instruct-q4f16_1",
    {
      initProgressCallback: (report) => {
        console.log(`Loading: ${report.text}`);
      },
    }
  );
  
  // Чат через WebSocket
  const chunks = await engine.chat.completions.create({
    stream: true,
    stream_options: { include_usage: true },
    messages: [
      { role: "user", content: "What can you do?" }
    ],
    max_tokens: 256,
  });
  
  for await (const chunk of chunks) {
    console.log(chunk.choices[0]?.delta?.content || '');
  }
</script>

Уровень 4: Сравнительный анализ компиляторов

Архитектурное сравнение

┌──────────────┬────────────────┬──────────────┬──────────────┐
│ Характеристика │ TensorRT-LLM  │ SGLang       │ MLC LLM      │
├──────────────┼────────────────┼──────────────┼──────────────┤
│ Backend       │ TensorRT/CUDA  │ PyTorch      │ TVM          │
│ GPU поддержка │ NVIDIA only    │ NVIDIA       │ Всё          │
│ Компиляция    │ AOT (ahead-of- │ JIT          │ AOT          │
│               │ time)          │              │              │
│ KV cache      │ PagedAttention │ RadixAttn    │ Standard     │
│ Speculative   │ Нет            │ EAGLE        │ Нет          │
│ Mobile        │ Нет            │ Нет          │ Да           │
│ Web           │ Нет            │ Нет          │ WebGPU       │
│ Структурир.   │ Нет            │ Да (grammar) │ Нет          │
│ FP8           │ Да             │ Нет          │ Эксперимент  │
│ Tensor par.   │ Да             │ Да           │ Нет          │
│ Сложность     │ Высокая        │ Средняя      │ Средняя      │
└──────────────┴────────────────┴──────────────┴──────────────┘

Скорость генерации (Llama-3-8B, RTX 4090)

Batch size = 1 (pre-fill + decode):

┌─────────────────────────────────────────────────────────────┐
│  PyTorch:     ████████████████░░░░░░░░░░░░  45 tok/s       │
│  vLLM:        ████████████████████████░░░░  140 tok/s      │
│  SGLang:      ████████████████████████░░░░  145 tok/s      │
│  TensorRT-LLM:████████████████████████████░░  200 tok/s    │
│  MLC:         ████████████████████░░░░░░░░  120 tok/s      │
└─────────────────────────────────────────────────────────────┘

Batch size = 32 (throughput):

┌─────────────────────────────────────────────────────────────┐
│  PyTorch:     ██████████░░░░░░░░░░░░░░░░░░░░  300 tok/s    │
│  vLLM:        ██████████████████████░░░░░░░░  1200 tok/s   │
│  SGLang:      ████████████████████████░░░░░░  1300 tok/s   │
│  TensorRT-LLM:████████████████████████████░░  1800 tok/s   │
│  MLC:         ██████████████████░░░░░░░░░░░░  800 tok/s    │
└─────────────────────────────────────────────────────────────┘

Выбор компилятора: чек-лист

Шаг 1: Ваше железо?
  ├── NVIDIA A100/H100 → TensorRT-LLM (максимальная производительность)
  ├── NVIDIA RTX 3090/4090 → TensorRT-LLM или SGLang
  ├── Apple Silicon → MLC LLM (Metal backend)
  ├── Мобильное устройство → MLC LLM (Android/iOS)
  ├── Браузер → MLC LLM (WebGPU)
  └── Raspberry Pi / Edge → MLC LLM

Шаг 2: Ваш сценарий?
  ├── Production API, высокая нагрузка → TensorRT-LLM
  ├── RAG с общими префиксами → SGLang (RadixAttention)
  ├── Structured output (JSON, choices) → SGLang (grammar)
  ├── Мобильное приложение → MLC LLM
  ├── Web-приложение без бэкенда → MLC LLM (WebGPU)
  └── Быстрое прототипирование → vLLM (без компиляции)

Шаг 3: Требования к разработке?
  ├── Минимальная настройка → SGLang
  ├── Максимальная скорость → TensorRT-LLM
  ├── Portability → MLC LLM
  └── Python-first → SGLang

Уровень 5: Практическое развёртывание

TensorRT-LLM: Production API

# 1. Компиляция модели (занимает 10-60 минут)
tensorrt_llm build \
  --model_dir meta-llama/Llama-3-8B-Instruct \
  --output_dir ./trt_llm_engine \
  --dtype float16 \
  --max_batch_size 32 \
  --max_num_tokens 8192 \
  --gemm_plugin float16

# 2. Запуск сервера
python -m tensorrt_llm.examples.run_trt_llm \
  --engine_dir ./trt_llm_engine \
  --backend trtllm \
  --endpoint http://0.0.0.0:8000

# 3. Тестирование
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Llama-3-8B-Instruct",
    "messages": [{"role": "user", "content": "Hello!"}],
    "temperature": 0.7,
    "max_tokens": 100
  }'

SGLang: Быстрый старт

# 1. Запуск сервера
python -m sglang.launch_server \
  --model-path meta-llama/Llama-3-8B-Instruct \
  --port 30000 \
  --mem-fraction-static 0.8 \
  --schedule-policy lpm  # longest-prefix-match

# 2. Использование (OpenAI-compatible API)
curl http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Llama-3-8B-Instruct",
    "messages": [{"role": "user", "content": "Write a haiku"}],
    "temperature": 0.7
  }'

# 3. Structured output через JSON schema
curl http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Llama-3-8B-Instruct",
    "messages": [{"role": "user", "content": "Extract info: John, 30, NYC"}],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "schema": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"},
            "city": {"type": "string"}
          },
          "required": ["name", "age", "city"]
        }
      }
    }
  }'

MLC LLM: Мобильное приложение

# 1. Компиляция модели под мобильное устройство
mlc_llm compile \
  Llama-3-8B-Instruct-q4f16_1 \
  --device android \
  --quantization q4f16_1 \
  --output dist/Llama-3-8B-Instruct-android.json

# 2. Билд приложения
mlc_llm app config \
  --model Llama-3-8B-Instruct-q4f16_1 \
  --device android \
  --output ./mlc-package-config.json

# 3. Интеграция в Android (Kotlin)
/*
val engine = MLCEngine(
    model = "Llama-3-8B-Instruct-q4f16_1",
    device = Device.kDGL("android", 0)
)

val response = engine.chatCompletionsCreate(
    model = "Llama-3-8B-Instruct-q4f16_1",
    messages = listOf(
        ChatCompletionMessage(
            role = "user",
            content = "What is AI?"
        )
    ),
    temperature = 0.7,
    maxTokens = 256
)
*/

Антипаттерны

1. Компилировать модель для каждого запроса

❌ Ошибка:
  for request in requests:
      engine = compile_model(model_path)  # 5-60 минут!
      response = engine.generate(request)

✅ Правильно:
  engine = compile_model(model_path)  # Один раз
  for request in requests:
      response = engine.generate(request)

2. Использовать TensorRT-LLM для прототипирования

❌ Ошибка:
  - Потратить день на компиляцию
  - Обнаружить что модель не подходит
  - Перекомпилировать другую модель

✅ Правильно:
  - Прототип на vLLM или SGLang (секунды на запуск)
  - Оптимизировать на TensorRT-LLM когда модель выбрана

3. Запускать MLC на десктопном GPU

❌ Ошибка:
  - MLC на RTX 4090: 28 tok/s
  - TensorRT-LLM на RTX 4090: 200 tok/s
  - Потеря 7x производительности

✅ Правильно:
  - MLC для мобильных/Web
  - TensorRT-LLM или vLLM для десктопных GPU

4. Игнорировать max_num_tokens при компиляции

❌ Ошибка:
  --max_num_tokens 4096  # Слишком мало для RAG
  # Результат: запросы с длинным контекстом падают

✅ Правильно:
  --max_num_tokens 32768  # Для RAG с длинными документами
  # Компиляция дольше, но работает со всем

Заключение

LLM-компиляторы — это следующий уровень оптимизации после выбора движка. Если vLLM vs llama.cpp — это выбор между GPU и CPU, то компиляторы — это выбор между интерпретацией и компиляцией.

Правило выбора:

Production API на NVIDIA GPU → TensorRT-LLM
RAG / много запросов с общими префиксами → SGLang
Мобильное / Web приложение → MLC LLM
Быстрый старт без компиляции → vLLM

Компиляция стоит времени (от минут до часов), но окупается в production. Для прототипирования используйте интерпретируемые решения, для продакшена — компилируйте.

Ресурсы: