LLM-компиляторы: SGLang, TensorRT-LLM, MLC — компиляция для максимальной скорости
Введение: почему интерпретация моделей — это 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, минимизируя:
- Kernel launches — объединяет последовательные операции в fused kernel
- Memory transfers — убирает ненужные копирования между host и device
- Memory allocation — pre-allocates всё, что можно предсказать
- 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. Для прототипирования используйте интерпретируемые решения, для продакшена — компилируйте.
Ресурсы: