
Розгортання LLM у Modal: від pip install до продакшен-ендпоінта
Більшість гайдів про самостійне хостування LLM обходять найскладнішу частину: інфраструктуру. Ви боретеся з драйверами CUDA, керуєте Docker-образами, налаштовуєте автомасштабування і все одно чомусь платите за простаючі GPU о третій ночі. Modal усуває всі ці проблеми. Ви пишете Python-код, деплоїте його й отримуєте URL.
Цей гайд проведе вас через процес розгортання відкритої LLM у Modal із використанням vLLM як рушія інференсу. Наприкінці ви матимете працюючий, OpenAI-сумісний API-ендпоінт на GPU H100, який масштабується до нуля, коли ним ніхто не користується.
Що таке Modal (і чому варто використовувати його для LLM)?
Modal — це безсерверна обчислювальна платформа, створена спеціально для AI-навантажень. Уявіть собі AWS Lambda, але з підтримкою GPU, помиттевою тарифікацією та нативним для Python досвідом розробника. Ніяких YAML-файлів, Dockerfile чи Kubernetes: ви визначаєте всю інфраструктуру в Python-скрипті та деплоїте її однією командою.
Ось чому Modal став основним інструментом для розгортання LLM:
- Тарифікація scale-to-zero: ви не платите нічого, коли ваш ендпоінт не обробляє запити
- Помиттева оплата за GPU: H100 коштують ~$3.95/год, A100 80GB — ~$2.50/год, оплата стягується за секунди
- Холодний старт за частки секунди: контейнери швидко запускаються, особливо завдяки снепшотам пам’яті
- $30/місяць безкоштовних кредитів: достатньо для експериментів без списання коштів із картки
- Жодного DevOps: немає Docker-збірок, Terraform чи керування кластерами
Якщо ви вже запускали LLM локально і хочете надати їм повноцінний API без необхідності керувати серверами, Modal — це найкоротший шлях до мети.
Modal проти RunPod проти Lambda
| Функція | Modal | RunPod | Lambda |
|---|---|---|---|
| Модель тарифікації | Помиттево, scale-to-zero | Помиттево, мінімальний заряд | Погодинно, завжди увімкнено |
| Холодний старт | 2–4 секунди | 6–12 секунд (великі) | Н/Д (постійно працює) |
| Доступність GPU | H100, A100, L40S, T4 | A100, H100, A6000 | H100, A100 |
| Інфраструктура | Чистий Python, без конфігураційних файлів | На базі Docker, більше контролю | Повний доступ до VM |
| Безкоштовний тариф | $30/місяць кредитів | Відсутній | Відсутній |
| Найкраще для | Різких навантажень / розробки | Стабільного трафіку інференсу | Тренування з високим завантаженням |
Підсумок: Modal виграє для різких навантажень і розробки. Якщо ваше завантаження GPU стабільно перевищує 40%, виділений інстанс на RunPod або Lambda буде дешевшим. Для всього іншого — прототипування, періодичні API, демо — модель scale-to-zero від Modal реально економить гроші.
Передумови
Перш ніж почати, вам знадобляться три речі:
- Python 3.10+, встановлений локально
- Акаунт Modal, зареєструйтеся безкоштовно на modal.com
- Акаунт Hugging Face, для доступу до моделей (більшість моделей мають обмежений доступ)
Це все. Жодних GPU на вашому локальному комп’ютері, CUDA Toolkit чи Docker.
Крок 1: Встановлення Modal та автентифікація
Відкрийте термінал і встановіть Python-пакет Modal:
pip install modalПотім запустіть команду налаштування, щоб пов’язати ваше локальне середовище з акаунтом Modal:
modal setupЦе відкриє вікно браузера для автентифікації. Після підтвердження Modal збереже токен локально. Більше цього робити не потрібно.
Крок 2: Визначення образу контейнера
Контейнери в Modal описуються мовою Python. Ви вказуєте базовий образ, встановлюєте залежності та задаєте змінні середовища — усе це кодом. Створіть файл app.py:
import modal
# Define the container image with CUDA, Python, and vLLM
vllm_image = (
modal.Image.from_registry(
"nvidia/cuda:12.8.0-devel-ubuntu22.04", add_python="3.12"
)
.entrypoint([])
.pip_install(
"vllm==0.13.0",
"huggingface-hub==0.36.0",
)
)
app = modal.App("llm-endpoint", image=vllm_image)Зверніть увагу на кілька моментів. Тут немає Dockerfile — ланцюжок modal.Image повністю замінює його. Базовий образ включає NVIDIA CUDA 12.8 з Ubuntu 22.04, а поверх нього ми встановлюємо vLLM та клієнт Hugging Face Hub.
Крок 3: Налаштування сховища моделі за допомогою Volume
Ваги LLM займають багато місця (модель із 7 млрд параметрів важить ~14 ГБ у форматі fp16). Ви не захочете завантажувати їх щоразу при запуску контейнера. Modal Volumes надають постійне сховище, яке монтується безпосередньо у ваші контейнери:
# Persistent volumes for caching model weights
hf_cache = modal.Volume.from_name("huggingface-cache", create_if_missing=True)
vllm_cache = modal.Volume.from_name("vllm-cache", create_if_missing=True)
MODEL_NAME = "Qwen/Qwen3-4B-Thinking-2507-FP8"
MODEL_REVISION = "953532f942706930ec4bb870569932ef63038fdf"Тут ми використовуємо Qwen3-4B-Thinking (FP8) — квантизовану модель на 4 мільярди параметрів, яка є швидкою, потужною та поміщається на одному GPU. Ви можете замінити її на будь-яку модель із Hugging Face: Llama 3.1 8B, Mistral 7B або будь-яку іншу, яку підтримує vLLM.
Чому FP8? Це приблизно вдвічі зменшує споживання пам’яті порівняно з fp16, що дозволяє запускати більші моделі на тому самому GPU або менші моделі на дешевших GPU. Якщо вам цікаво дізнатися більше про компроміси квантизації, наш гайд із запуску LLM локально детально розглядає формати точності.
Крок 4: Створення функції сервера vLLM
Саме тут проявляється магія Modal. Ви декоруєте Python-функцію, вказуючи вимоги до GPU, конфігурацію масштабування та анотацію вебсервера. Modal бере на себе все інше:
N_GPU = 1
MINUTES = 60
VLLM_PORT = 8000
@app.function(
gpu=f"H100:{N_GPU}",
scaledown_window=15 * MINUTES,
timeout=10 * MINUTES,
volumes={
"/root/.cache/huggingface": hf_cache,
"/root/.cache/vllm": vllm_cache,
},
)
@modal.concurrent(max_inputs=32)
@modal.web_server(port=VLLM_PORT, startup_timeout=10 * MINUTES)
def serve():
import subprocess
cmd = [
"vllm", "serve",
MODEL_NAME,
"--revision", MODEL_REVISION,
"--served-model-name", MODEL_NAME,
"--host", "0.0.0.0",
"--port", str(VLLM_PORT),
"--tensor-parallel-size", str(N_GPU),
"--enforce-eager", # Faster cold starts
]
subprocess.Popen(" ".join(cmd), shell=True)Розберемо ключові декоратори:
gpu="H100:1"— запитує один GPU H100. Змініть на"A100-80GB:1"для дешевшого інференсу або"H100:2"для моделей понад 70 млрд параметрівscaledown_window=15 * MINUTES— тримає контейнер «теплим» протягом 15 хвилин після останнього запиту, після чого масштабує його до нуля@modal.concurrent(max_inputs=32)— дозволяє до 32 одночасних запитів на контейнер (vLLM обробляє батчинг внутрішньо)@modal.web_server(port=8000)— безпосередньо надає HTTP-сервер vLLM як веб-ендпоінт Modal--enforce-eager— пропускає компіляцію CUDA-графів для швидшого холодного старту (компроміс: дещо нижча пікова пропускна здатність)
Параметр scaledown_window — ваш головний інструмент контролю витрат. Встановіть 5 хвилин для розробки та 15–30 хвилин для продакшен-API, де очікується регулярний трафік.
Крок 5: Деплой у продакшен
Одна команда. І це все:
modal deploy app.pyModal збирає образ контейнера, завантажує його у свій реєстр і повертає активний URL:
✓ Created objects.
├── 🔨 Created mount /app.py
├── 🔨 Created volume huggingface-cache
├── 🔨 Created volume vllm-cache
└── 🔨 Created web function serve => https://your-workspace--llm-endpoint-serve.modal.runПерший деплой займає кілька хвилин, оскільки ваги моделі завантажуються у Volume. Наступні деплої (та холодні старти) відбуваються набагато швидше, адже ваги вже закешовані.
Для розробки використовуйте натомість modal serve app.py: це забезпечить гаряче перезавантаження при зміні файлів і надасть тимчасовий URL.
Крок 6: Виклик вашого ендпоінта (OpenAI-сумісний)
Ваш розгорнутий сервер vLLM надає OpenAI-сумісний API за шляхом /v1/chat/completions. Ви можете використовувати стандартний Python SDK від OpenAI для викликів, просто вказавши базовий URL вашого ендпоінта Modal:
from openai import OpenAI
client = OpenAI(
api_key="not-needed", # vLLM doesn't require auth by default
base_url="https://your-workspace--llm-endpoint-serve.modal.run/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3-4B-Thinking-2507-FP8",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain what vLLM is in two sentences."},
],
temperature=0.7,
max_tokens=256,
)
print(response.choices[0].message.content)Це також працює з curl:
curl -X POST https://your-workspace--llm-endpoint-serve.modal.run/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-4B-Thinking-2507-FP8",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 128
}'Будь-який інструмент, що підтримує OpenAI-сумісний API, працюватиме: LangChain, LlamaIndex, ваша власна aplikacja. Якщо ви маршрутизуєте запити між кількома LLM-ендпоінтами, інструмент LLM-шлюзу допоможе керувати резервуванням і балансуванням навантаження.
Поради щодо оптимізації витрат
Помиттева тарифікація Modal уже ефективніша за погодинну, але можна витиснути ще більше:
1. Використовуйте квантизацію FP8
Моделі FP8 використовують приблизно вдвічі менше VRAM, ніж їхні аналоги fp16. Qwen3-8B у форматі FP8 поміщається на одному H100, тоді як версія fp16 потребує майже всіх 80 ГБ цієї карти. Менше VRAM означає, що для менших моделей можна використовувати дешевші GPU (A100 40GB, L40S).
2. Налаштуйте вікно масштабирования (Scaledown Window)
Параметр scaledown_window контролює, скільки часу контейнер залишається «теплим» після останнього запиту:
| Сценарій | Рекомендоване вікно | Чому |
|---|---|---|
| Розробка/тестування | 5 хвилин | Економія коштів, холодний старт прийнятний |
| Внутрішній API (рідкісне використання) | 10–15 хвилин | Баланс між вартістю та затримкою |
| Продакшен (регулярний трафік) | 20–30 хвилин | Мінімізація холодних стартів |
| Продакшен із високим трафіком | Використовуйте min_containers=1 | Завжди тримати один «теплим» |
3. Обирайте правильний GPU
Не обирайте H100 за замовчуванням. Меншим моделям він не потрібен:
| Розмір моделі | Рекомендований GPU | Приблиз. вартість/год |
|---|---|---|
| 1–4 млрд параметрів | L4 або T4 | $0.59 – $0.80 |
| 7–8 млрд параметрів | A10 або L40S | $1.10 – $1.95 |
| 13–14 млрд параметрів | A100 40GB | $2.10 |
| 30–70 млрд параметрів | A100 80GB або H100 | $2.50 – $3.95 |
| Понад 70 млрд параметрів | H100 x2 | $7.90 |
4. Увімкніть кешування промптів
Якщо ваші робочі процеси передбачають повторювані системні промпти або спільні префікси, автоматичне кешування префіксів у vLLM може значно зменшити затримку та обчислювальні витрати. Ви можете увімкнути його, додавши --enable-prefix-caching до команди запуску vLLM. Щоб глибше зрозуміти, як працює кешування у різних постачальників, ознайомтеся з нашим гайдом із кешування промптів LLM.
5. Використовуйте --enforce-eager для оптимізації холодного старту
За замовчуванням vLLM компілює CUDA-графи під час запуску, що займає додаткові 1–3 хвилини. Прапорець --enforce-eager пропускає цю компіляцію. Ви втрачаєте ~10–15% пікової пропускної здатності заради значно швидшого холодного старту. Для різких навантажень, де затримка важливіша за сиру пропускну здатність, це майже завжди правильний вибір.
Далі більше: донавчені моделі
Коли ви освоїте розгортання базових моделей, природним наступним кроком стане розгортання власної донавченої версії. Робочий процес ідентичний: ви просто вказуєте MODEL_NAME на свій репозиторій Hugging Face або на Volume у Modal, що містить ваги після донавчання.
Modal також підтримує запуск завдань донавчання безпосередньо на своїх GPU. Ви можете навчити LoRA-адаптер у Modal, зберегти його у Volume та розгорнути об’єднану модель, не залишаючи платформи. Наш гайд із донавчання LLM детально охоплює сторону тренування.
Повний файл app.py
Ось повний скрипт деплою в одному блоці, готовому до копіювання:
import modal
# --- Image Definition ---
vllm_image = (
modal.Image.from_registry(
"nvidia/cuda:12.8.0-devel-ubuntu22.04", add_python="3.12"
)
.entrypoint([])
.pip_install("vllm==0.13.0", "huggingface-hub==0.36.0")
)
# --- Volumes for Model Caching ---
hf_cache = modal.Volume.from_name("huggingface-cache", create_if_missing=True)
vllm_cache = modal.Volume.from_name("vllm-cache", create_if_missing=True)
# --- Model Config ---
MODEL_NAME = "Qwen/Qwen3-4B-Thinking-2507-FP8"
MODEL_REVISION = "953532f942706930ec4bb870569932ef63038fdf"
app = modal.App("llm-endpoint", image=vllm_image)
N_GPU = 1
MINUTES = 60
VLLM_PORT = 8000
@app.function(
gpu=f"H100:{N_GPU}",
scaledown_window=15 * MINUTES,
timeout=10 * MINUTES,
volumes={
"/root/.cache/huggingface": hf_cache,
"/root/.cache/vllm": vllm_cache,
},
)
@modal.concurrent(max_inputs=32)
@modal.web_server(port=VLLM_PORT, startup_timeout=10 * MINUTES)
def serve():
import subprocess
cmd = [
"vllm", "serve",
MODEL_NAME,
"--revision", MODEL_REVISION,
"--served-model-name", MODEL_NAME,
"--host", "0.0.0.0",
"--port", str(VLLM_PORT),
"--tensor-parallel-size", str(N_GPU),
"--enforce-eager",
]
subprocess.Popen(" ".join(cmd), shell=True)Запустіть деплой командою modal deploy app.py, замініть MODEL_NAME на будь-яку модель із Hugging Face, і ви в ефірі.
Поширені запитання
Скільки коштує запуск LLM у Modal?
Це залежить від GPU та того, як довго ваш ендпоінт залишається «теплим». Qwen3-4B на H100 коштує ~$3.95/год активного використання. Зі scale-to-zero та вікном масштабирования 15 хвилин слабо завантажений ендпоінт може коштувати $5–15/місяць. Безкоштовних кредитів у розмірі $30 щомісяця вистачає на багато експериментів.
Чи масштабується Modal до нуля?
Так, це одна з його головних переваг. Коли запити не надходять протягом часу, визначеного вашим scaledown_window, контейнер вимикається, і ви перестаєте платити. Наступний запуск ініціює холодний старт (зазвичай 2–10 секунд залежно від розміру моделі та того, чи використовуєте ви --enforce-eager).
Чи можу я розгорнути Llama 3.1 або Mistral у Modal?
Безумовно. Замініть константу MODEL_NAME на будь-яку модель, яку підтримує vLLM: meta-llama/Llama-3.1-8B-Instruct, mistralai/Mistral-7B-Instruct-v0.3 або сотні інших із Hugging Face. Для моделей понад 70 млрд параметрів змініть N_GPU на 2 і використовуйте gpu="H100:2".
Як холодні старти порівнюються з RunPod?
Холодні старти Modal зазвичай становлять 2–4 секунди для самого контейнера плюс час завантаження моделі. Якщо ваги моделі закешовані у Volume та увімкнено --enforce-eager, загальний час для моделі 7–8 млрд параметрів становить 10–30 секунд. Холодні старти безсерверного режиму RunPod варіюються від менш ніж 200 мс (з кешем) до 6–12 секунд для більших контейнерів, хоча їхня модель постійної роботи повністю уникає холодних стартів.
Чи дійсно ендпоінт vLLM у Modal сумісний із OpenAI?
Так. vLLM реалізує ті самі ендпоінти /v1/chat/completions, /v1/completions та /v1/models, що й OpenAI. Ви можете спрямувати офіційний Python SDK openai на ваш URL у Modal, і все працюватиме «з коробки». Підтримуються стрімінг, виклик функцій та JSON-режим.
Чи потрібен мені GPU на локальному комп’ютері?
Ні. Ваш локальний комп’ютер лише запускає CLI Modal. Уся робота з GPU відбувається в хмарній інфраструктурі Modal. Ви могли б здійснити деплой навіть із Chromebook, якщо б хотіли.
Як додати автентифікацію до мого ендпоінта?
Веб-ендпоінти Modal за замовчуванням є публічними. Для продакшену додайте просту перевірку API-ключа у код вашої aplikacja або скористайтеся вбудованими функціями веб-автентифікації Modal. Ви також можете налаштувати проксі-шар за допомогою LLM-шлюзу, який оброблятиме автентифікацію, обмеження частоти запитів та маршрутизацію.
Яка різниця між modal serve та modal deploy?
modal serve створює тимчасовий ендпоінт, який гарячо перезавантажується при редагуванні коду, що ідеально підходить для розробки. modal deploy створює постійний, готовий до продакшену ендпоінт зі стабільним URL. Використовуйте serve під час ітерацій, deploy — коли готові до релізу.
Чи можу я використовувати SGLang замість vLLM?
Так. Документація Modal включає приклади SGLang поряд із vLLM. SGLang, як правило, має менші накладні витрати для навантажень із переважанням декодування та менших моделей. vLLM загалом краще підходить для змішаних навантажень із важким префіллом. Обидва рішення надають OpenAI-сумісні ендпоінти.
Як це порівнюється з розгортанням на Railway або Render?
Платформи на кшталт Railway, Render та Fly.io чудово підходять для вебдодатків, але не пропонують GPU-інстанси. Modal створений спеціально для GPU-навантажень із помиттевою тарифікацією та автомасштабуванням. Якщо вам потрібно обслуговувати LLM, правильним інструментом є Modal (або RunPod); традиційні PaaS-платформи цього не можуть зробити.