
Wdrażanie LLM z Modal: od pip install do endpointu produkcyjnego
Większość przewodników dotyczących samodzielnego hostowania modeli LLM pomija najtrudniejszą część: infrastrukturę. Walczysz ze sterownikami CUDA, zarządzasz obrazami Docker, konfigurujesz autoscaling i somehow nadal płacisz za bezczynne GPU o 3 nad ranem. Modal eliminuje wszystkie te problemy. Piszesz kod w Pythonie, wdrażasz go i otrzymujesz URL.
Ten przewodnik przeprowadzi Cię przez proces wdrażania open-source'owego modelu LLM na platformie Modal z wykorzystaniem vLLM jako silnika inferencji. Na końcu będziesz mieć działający endpoint API zgodny z OpenAI, uruchomiony na procesorach H100, który skaluje się do zera, gdy nikt z niego nie korzysta.
Czym jest Modal (i dlaczego warto go używać do LLM)?
Modal to bezserwerowa platforma obliczeniowa stworzona specjalnie dla obciążeń związanych ze sztuczną inteligencją. Pomyśl o AWS Lambda, ale z obsługą GPU, rozliczaniem co sekundę i środowiskiem deweloperskim natywnym dla Pythona. Nie ma plików YAML, nie ma Dockerfile, nie ma Kubernetesa – całą infrastrukturę definiujesz w skrypcie Pythona i wdrażasz jedną komendą.
Oto dlaczego stał się on wyborem nr 1 do wdrażania LLM:
- Rozliczanie scale-to-zero: nie płacisz nic, gdy Twój endpoint nie obsługuje żądań
- Ceny GPU rozliczane co sekundę: H100 za ~3,95 USD/godz., A100 80GB za ~2,50 USD/godz., rozliczane co sekundę
- Uruchamianie w ułamku sekundy (cold starts): kontenery startują szybko, zwłaszcza dzięki migawkom pamięci
- 30 USD miesięcznie darmowych kredytów: wystarczająco, aby eksperymentować bez obciążania karty kredytowej
- Brak DevOps: brak budowania obrazów Docker, brak Terraform, brak zarządzania klastrem
Jeśli uruchamiałeś modele LLM lokalnie i chcesz udostępnić im odpowiednie API bez zarządzania serwerami, Modal jest najkrótszą drogą do celu.
Modal vs. RunPod vs. Lambda
| Cecha | Modal | RunPod | Lambda |
|---|---|---|---|
| Model rozliczeń | Co sekundę, scale-to-zero | Co sekundę, minimalna opłata | Co godzinę, zawsze włączone |
| Cold start | 2-4 sekundy | 6-12 sekund (duże) | N/A (trwałe) |
| Dostępność GPU | H100, A100, L40S, T4 | A100, H100, A6000 | H100, A100 |
| Infrastruktura | Czysty Python, bez plików konfiguracyjnych | Oparte na Docker, większa kontrola | Pełny dostęp do VM |
| Warstwa darmowa | 30 USD/miesiąc kredytów | Brak | Brak |
| Najlepsze dla | Obciążeń skokowych/deweloperskich | Stałego ruchu inferencyjnego | Treningu o wysokim wykorzystaniu |
Podsumowując: Modal wygrywa w przypadku obciążeń skokowych i prac deweloperskich. Jeśli wykorzystanie Twojego GPU będzie konsekwentnie przekraczać 40%, dedykowana instancja na RunPod lub Lambda będzie tańsza. Do wszystkiego innego – prototypowania, przerywanych API, demonstracji – model scale-to-zero Modala oszczędza prawdziwe pieniądze.
Wymagania wstępne
Zanim zaczniesz, potrzebujesz trzech rzeczy:
- Python 3.10+ zainstalowany lokalnie
- Konto Modal, zarejestruj się za darmo na modal.com
- Konto Hugging Face, aby uzyskać dostęp do modeli (większość modeli wymaga rejestracji)
To wszystko. Nie potrzebujesz GPU na swojej maszynie lokalnej, toolkitu CUDA ani Dockera.
Krok 1: Instalacja Modal i uwierzytelnianie
Otwórz terminal i zainstaluj pakiet Pythona Modal:
pip install modalNastępnie uruchom komendę konfiguracji, aby połączyć swoje lokalne środowisko z kontem Modal:
modal setupSpowoduje to otwarcie okna przeglądarki w celu uwierzytelnienia. Po potwierdzeniu Modal zapisze token lokalnie. Nie będziesz musiał robić tego ponownie.
Krok 2: Definiowanie obrazu kontenera
Kontenery Modal są definiowane w Pythonie. Określasz obraz bazowy, instalujesz zależności i ustawiasz zmienne środowiskowe, wszystko jako kod. Utwórz plik o nazwie 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)Warto zwrócić uwagę na kilka rzeczy. Nie ma tutaj pliku Dockerfile – łańcuch modal.Image całkowicie go zastępuje. Obraz bazowy zawiera NVIDIA CUDA 12.8 z Ubuntu 22.04, a na nim instalujemy vLLM oraz klienta Hugging Face Hub.
Krok 3: Konfiguracja przechowywania modelu za pomocą Volume
Wagi modeli LLM są duże (model z 7 miliardami parametrów waży ~14 GB w formacie fp16). Nie chcesz pobierać ich za każdym razem, gdy uruchamia się kontener. Modal Volumes zapewniają trwałą pamięć masową, która montuje się bezpośrednio w Twoich kontenerach:
# 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"Używamy tutaj modelu Qwen3-4B-Thinking (FP8), skwantyzowanego modelu z 4 miliardami parametrów, który jest szybki, wydajny i mieści się na pojedynczym GPU. Możesz zamienić go na dowolny model z Hugging Face: Llama 3.1 8B, Mistral 7B lub cokolwiek, co obsługuje vLLM.
Dlaczego FP8? Zmniejsza zużycie pamięci około dwukrotnie w porównaniu do fp16, co oznacza, że możesz uruchamiać większe modele na tym samym GPU lub mniejsze modele na tańszych GPU. Jeśli interesują Cię kompromisy związane z kwantyzacją, nasz przewodnik po uruchamianiu LLM lokalnie szczegółowo omawia formaty precyzji.
Krok 4: Tworzenie funkcji serwera vLLM
Tutaj dzieje się magia Modala. Dekorujesz funkcję Pythona wymaganiami dotyczącymi GPU, konfiguracją skalowania adnotacją serwera WWW. Modal zajmuje się resztą:
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)Omówmy kluczowe dekoratory:
gpu="H100:1": żąda jednego GPU H100. Zmień na"A100-80GB:1"dla tańszej inferencji lub"H100:2"dla modeli 70B+scaledown_window=15 * MINUTES: utrzymuje kontener „na ciepło” przez 15 minut po ostatnim żądaniu, a następnie skaluje go do zera@modal.concurrent(max_inputs=32): pozwala na obsługę do 32 równoczesnych żądań na kontener (vLLM obsługuje batchowanie wewnętrznie)@modal.web_server(port=8000): udostępnia serwer HTTP vLLM bezpośrednio jako endpoint webowy Modal--enforce-eager: pomija kompilację grafów CUDA dla szybszego cold startu (kompromis: nieco niższa maksymalna przepustowość)
Parametr scaledown_window to Twoje główne narzędzie do kontrolowania kosztów. Ustaw go na 5 minut dla środowiska deweloperskiego, 15-30 minut dla produkcyjnych API, gdzie spodziewasz się regularnego ruchu.
Krok 5: Wdrożenie do produkcji
Jedna komenda. To wszystko:
modal deploy app.pyModal buduje obraz kontenera, wypycha go do swojego rejestru i zwraca działający 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.runPierwsze wdrożenie zajmuje kilka minut, ponieważ pobiera wagi modelu do volume. Kolejne wdrożenia (i cold starty) są znacznie szybsze, ponieważ wagi są buforowane.
Do celów deweloperskich użyj zamiast tego modal serve app.py – przeładowuje kod na gorąco przy zmianach w plikach i daje tymczasowy URL.
Krok 6: Wywołanie swojego endpointu (zgodność z OpenAI)
Twój wdrożony serwer vLLM udostępnia API zgodne z OpenAI pod ścieżką /v1/chat/completions. Możesz użyć standardowego SDK Pythona OpenAI, aby je wywołać, wystarczy skierować base URL na swój endpoint 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)Działa to również z 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
}'Każde narzędzie obsługujące API zgodne z OpenAI będzie działać: LangChain, LlamaIndex, Twoja własna aplikacja. Jeśli kierujesz żądania do wielu endpointów LLM, narzędzie LLM gateway może pomóc w zarządzaniu failover i load balancingiem.
Wskazówki dotyczące optymalizacji kosztów
Rozliczanie co sekundę w Modal jest już bardziej efektywne niż rozliczanie godzinowe, ale możesz wycisnąć z niego jeszcze więcej:
1. Użyj kwantyzacji FP8
Modele FP8 zużywają około połowy VRAM w porównaniu do ich odpowiedników fp16. Qwen3-8B w FP8 zmieści się na jednym H100, podczas gdy wersja fp16 potrzebuje większości z 80 GB tego GPU. Mniejsze zapotrzebowanie na VRAM oznacza, że możesz używać tańszych GPU (A100 40GB, L40S) dla mniejszych modeli.
2. Dostosuj okno skalowania w dół (Scaledown Window)
Parametr scaledown_window kontroluje, jak długo kontener pozostaje „na ciepło” po ostatnim żądaniu:
| Scenariusz | Zalecane okno | Dlaczego |
|---|---|---|
| Development/testy | 5 minut | Oszczędzaj pieniądze, cold starty są akceptowalne |
| Internal API (okazjonalne) | 10-15 minut | Balans między kosztem a latencją |
| Produkcja (regularny ruch) | 20-30 minut | Minimalizuj cold starty |
| Produkcja o dużym ruchu | Użyj min_containers=1 | Zawsze utrzymuj jeden kontener na ciepło |
3. Wybierz odpowiednie GPU
Nie wybieraj domyślnie H100. Mniejsze modele go nie potrzebują:
| Rozmiar modelu | Zalecane GPU | Przybliżony koszt/godz. |
|---|---|---|
| 1-4B parametrów | L4 lub T4 | 0,59 - 0,80 USD |
| 7-8B parametrów | A10 lub L40S | 1,10 - 1,95 USD |
| 13-14B parametrów | A100 40GB | 2,10 USD |
| 30-70B parametrów | A100 80GB lub H100 | 2,50 - 3,95 USD |
| 70B+ parametrów | H100 x2 | 7,90 USD |
4. Włącz buforowanie promptów
Jeśli Twoje obciążenia obejmują powtarzające się system prompty lub wspólne prefiksy, automatyczne buforowanie prefiksów vLLM może znacząco zmniejszyć latencję i obciążenie obliczeniowe. Możesz je włączyć, dodając --enable-prefix-caching do komendy serwera vLLM. Aby głębiej zgłębić działanie buforowania u różnych dostawców, sprawdź nasz przewodnik po buforowaniu promptów LLM.
5. Użyj --enforce-eager do optymalizacji cold startu
Domyślnie vLLM kompiluje grafy CUDA przy starcie, co zajmuje dodatkowe 1-3 minuty. Flaga --enforce-eager pomija tę kompilację. Tracisz ~10-15% maksymalnej przepustowości na rzecz dramatycznie szybszych cold startów. W przypadku obciążeń skokowych, gdzie latencja jest ważniejsza niż surowa przepustowość, jest to prawie zawsze właściwy wybór.
Dalej: Modele fine-tunowane
Gdy już oswoisz się z wdrażaniem modeli bazowych, naturalnym kolejnym krokiem jest wdrożenie własnej, fine-tunowanej wersji. Proces jest identyczny – wystarczy skierować MODEL_NAME na swoje repozytorium Hugging Face lub Modal volume zawierający fine-tunowane wagi.
Modal obsługuje również uruchamianie zadań fine-tuningowych bezpośrednio na swoich GPU. Możesz trenować adapter LoRA na Modal, zapisać go w volume i wdrożyć scalony model, nie opuszczając platformy. Nasz przewodnik po fine-tuningu LLM szczegółowo omawia stronę treningową.
Pełny plik app.py
Oto pełny skrypt wdrożeniowy w jednym bloku gotowym do skopiowania i wklejenia:
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)Wdróż za pomocą modal deploy app.py, zamień MODEL_NAME na dowolny model z Hugging Face i jesteś online.
Często zadawane pytania
Ile kosztuje uruchomienie LLM na Modal?
To zależy od GPU i tego, jak długo Twój endpoint pozostaje „na ciepło”. Qwen3-4B na H100 kosztuje ~3,95 USD/godz. aktywnego użytkowania. Dzięki scale-to-zero i 15-minutowemu oknu scaledown, słabo wykorzystywany endpoint może kosztować 5-15 USD/miesiąc. Darmowy miesięczny kredyt 30 USD pokrywa sporo eksperymentów.
Czy Modal skaluje się do zera?
Tak, to jedna z jego głównych zalet. Gdy przez czas trwania Twojego scaledown_window nie pojawią się żadne żądania, kontener zostaje wyłączony i przestajesz płacić. Następne żądanie uruchamia cold start (zazwyczaj 2-10 sekund, w zależności od rozmiaru modelu i tego, czy używasz --enforce-eager).
Czy mogę wdrożyć Llama 3.1 lub Mistral na Modal?
Absolutnie. Zamień stałą MODEL_NAME na dowolny model obsługiwany przez vLLM: meta-llama/Llama-3.1-8B-Instruct, mistralai/Mistral-7B-Instruct-v0.3 lub setki innych na Hugging Face. Dla modeli 70B+ zmień N_GPU na 2 i użyj gpu="H100:2".
Jak cold starty wypadają w porównaniu do RunPod?
Cold starty Modal zajmują zazwyczaj 2-4 sekundy dla samego kontenera, plus czas ładowania modelu. Przy wagach modelu buforowanych w Volume i włączonym --enforce-eager, całkowity czas dla modelu 7-8B wynosi 10-30 sekund. Bezserwerowe cold starty RunPod wahają się od poniżej 200 ms (buforowane) do 6-12 sekund dla większych kontenerów, choć ich model always-on całkowicie unika cold startów.
Czy endpoint vLLM w Modal jest naprawdę zgodny z OpenAI?
Tak. vLLM implementuje te same endpointy /v1/chat/completions, /v1/completions i /v1/models, których używa OpenAI. Możesz skierować oficjalne SDK Pythona openai na swój URL Modal i działa ono od razu. Streaming, wywoływanie funkcji i tryb JSON działają poprawnie.
Czy potrzebuję GPU na mojej maszynie lokalnej?
Nie. Twoja maszyna lokalna uruchamia tylko CLI Modal. Wszystkie prace GPU odbywają się w chmurze Modal. Mógłbyś wdrażać nawet z Chromebooka, jeśli byś chciał.
Jak dodać uwierzytelnianie do mojego endpointu?
Endpointy webowe Modal są domyślnie publiczne. Do produkcji dodaj prostą kontrolę klucza API w kodzie aplikacji lub użyj wbudowanych funkcji uwierzytelniania webowego Modal. Możesz też skonfigurować warstwę proxy za pomocą LLM gateway, który obsłuży auth, limitowanie ruchu i routing.
Jaka jest różnica między modal serve a modal deploy?
modal serve tworzy tymczasowy endpoint, który przeładowuje się na gorąco podczas edycji kodu, idealny do developmentu. modal deploy tworzy trwały, gotowy do produkcji endpoint ze stabilnym URL. Używaj serve podczas iteracji, deploy gdy jesteś gotowy do publikacji.
Czy mogę użyć SGLang zamiast vLLM?
Tak. Dokumentacja Modal zawiera przykłady SGLang obok vLLM. SGLang ma tendencję do posiadania mniejszego narzutu dla obciążeń heavily decode i mniejszych modeli. vLLM jest generalnie lepszy dla mieszanych obciążeń z heavy prefill. Obie technologie generują endpointy zgodne z OpenAI.
Jak to się ma do wdrażania na Railway lub Render?
Platformy takie jak Railway, Render i Fly.io są świetne do aplikacji webowych, ale nie oferują instancji GPU. Modal jest celowo zbudowany dla obciążeń GPU z rozliczaniem co sekundę i autoscalingiem. Jeśli musisz serwować LLM, Modal (lub RunPod) jest właściwym narzędziem – tradycyjne platformy PaaS tego nie potrafią.