Три деплоя Gemma 4 на одном T4G за $3: что меняет runtime, а что нет

Соло-разработчик, который поднимает локальный вывод модели рядом с IDE, а не только в управляемом API, обычно сравнивает vLLM, JAX и PyTorch по tok/s из чужих таблиц. В бенчмарке на одном чекпоинте google/gemma-4-E2B-it, одном GPU AWS G5g и едином контуре тестов автор показал обратное: лидер по decode и лидер по cold boot — разные стеки, а весь эксперимент уложился в меньше $3.
Почему один контур тестов важнее трёх красивых цифр
Типичная ловушка при выборе LLM runtime — три разных скрипта, три разных метрики, три разных вывода. Автор изначально гонял vLLM, JAX и PyTorch отдельно и получил несопоставимые числа: у vLLM не было поля usage.decode_tokens_per_second, sweep не мог смотреть на vLLM-rig, а prefix cache с hit rate 94,7% дал невозможный «30×» по TTFT, потому что warmup и repeats переиспользовали один prompt.
Решение — общий клиент sweep.py с флагом --decode-source {auto,usage,stream,both} и портативным decode через разрыв между токенами на wire у OpenAI-compatible серверов. Режим stream считает TPOT так же, как vllm bench serve: (latency - ttft) / (output_len - 1). Калибровка stream/gauge не переносится между rig: JAX 0,9799, PyTorch 0,9543 — cross-rig таблица decode строится только из stream.
Ошибки ловятся измерением на одной статистике, а не рассуждением в трёх разных контурах.
Decode-рейтинг и lifecycle-рейтинг расходятся
На concurrency 1 (stream, 12/12 или 10/12 ячеек) decode tok/s выглядит так:
| Место | Runtime | decode tok/s | % от ceiling 61,4 |
|---|---|---|---|
| 1 | vLLM | 32,53 | 53,0% |
| 2 | JAX | 12,69 | 20,7% |
| 3 | PyTorch | 10,24 | 16,7% |
vLLM в 3,18× быстрее PyTorch по decode. Ни один runtime не упирается в bandwidth ceiling 61,4 tok/s при batch 1.
Cold boot — другая картина (9 cold + 9 warm; старт с момента возврата id из run_instances):
| Место | Runtime | cold boot | warm reload |
|---|---|---|---|
| 1 | PyTorch | 195,2 s | 24,5 s |
| 2 | JAX | 242,2 s | 74,1 s |
| 3 | vLLM | 1417,8 s (23m 38s) | 264,3 s |
PyTorch до serving в 7,3× быстрее vLLM по lifecycle. Смена serving-кода: PyTorch ~25 s против ~67 min rebuild from source у vLLM. Health ≠ ready: JAX отдаёт health 200, но first completion cold — 22,9 s (XLA compile per shape); vLLM — slowest boot, fastest first token (0,5 s cold).
Для агента рядом с IDE это практический выбор: нужен быстрый decode на длинных ответах — vLLM; нужен короткий цикл «поднял — проверил — переключил стек» — PyTorch выигрывает по boot.
Пять гипотез, которые измерение сняло с повестки
Эксперимент «поймал пять неверных утверждений» — не метафора. Ядро — блок про доминирование загрузки весов в cold boot vLLM (468–561 s): четыре гипотезы опровергнуты, пятая оставлена непроверенной.
g5g.2xlargeне нужен swapfile — ускорит boot. Опровергнута трёхкратной кампанией cold boot (23m 38s).- Больший хост «точно» исправит boot. Снята в тот же день — большой хост не измеряли.
- Больший хост «очень правдоподобно» исправит boot. Опровергнута одним boot на
g5g.4xlarge: RAM 11,19→26,49 GiB, загрузка весов 546→468 s, total boot −4,7% (внутри шума). - Виноват loader; auto-prefetch disabled на EXT4. Опровергнута A/B: 76,13 s vs 75,12 s с
--safetensors-load-strategy=prefetch(−1,3%). - EBS лениво гидратирует том из AMI snapshot. Непроверена — автор не повышает до факта без измерения.
Дополнительно: gauge JAX с одним знаком после запятой давал byte-identical 12,8, 12,8, 12,8; после фикса — 12,962 tok/s. Poll health каждые 5 s квантовал два boot в одинаковые 214,4 s и 125,1 s — кампания перезапущена с poll 0,5 s.
MCP-оркестрация деплоя вместо ручного SSH
Пост не ограничивается бенчмарком: набор Python-инструментов MCP упрощает управление каждым деплоем. Named tools в тексте — check_g5g_quotas, get_install_progress, verify_gpu_arch, deploy_torch_server, verify_model_health, terminate_g5g_instance. Remote ops идут через AWS Systems Manager без inbound SSH и private key.
Для соло-workflow с агентами это прямой сигнал: сравнение трёх LLM runtime на облачном GPU можно автоматизировать инструментами MCP, а не ручным циклом «зашёл на инстанс — поставил wheel — забыл убить». Стратегия MCP для сравнения нескольких runtime подтверждена пошаговым подходом, без ручного SSH.
Код контура тестов и sweep — в монорепо gemma4-dev.
Сколько стоит такой эксперимент и где границы вывода
19 инстансов, 4,06 h g5g.2xlarge + 0,38 h g5g.4xlarge — суммарно меньше $3. Верхние границы в cost_derivation.md репозитория: $1,84 all-spot, $2,68 all-on-demand. Spot premium 26–46% на этом железе.
Железо: 1× NVIDIA T4G, SM 7.5, 15 360 MiB; инстанс g5g.2xlarge (8 vCPU Graviton2 aarch64, 16 GiB host RAM). Веса dense — 9,5 GiB float16; чекпоинт ~2B effective из ~5B total. Все три runtime — float16 (Turing без bfloat16/fp8).
Оговорки scope, которые нельзя размывать:
- Только concurrency 1 — latency, не serving throughput; continuous batching vLLM не тестировался.
- PyTorch/JAX в этой setup не умеют concurrent serve.
max_model_len: vLLM 16384, JAX/PyTorch 4096; JAX leg — quantised shipped config против двух dense runtime.- Пост ограничен AWS G5g в
us-east-1; перенос на домашний GPU автор не заявляет.
Если выбираете стек для собственного вывода модели под агента — смотрите не только decode tok/s, но и cold boot, warm reload и стоимость итерации. Три runtime на одном T4G за копейки — дешёвый способ не повторить чужие «five wrong claims».
Источники
- Three Gemma 4 Deployments on One T4G for Under $3: What the Runtime Changes, and What It Doesn't — Dev.to, 2026-09-01
- gemma4-dev — репозиторий автора (harness, sweep, cost_derivation.md)