
Distribuer en LLM med Modal: fra pip install til produksjonsendepunkt
De fleste guider om selvhosting av LLM-er hopper over den vanskeligste delen: infrastrukturen. Man slåss med CUDA-drivere, administrerer Docker-bilder, konfigurerer autoskalering og betaler likevel for inaktive GPU-er klokken 3 om natten. Modal eliminerer alt dette. Du skriver Python, du distribuerer, du får en URL.
Denne guiden tar deg gjennom distribusjon av en open source-LLM på Modal med vLLM som inferensmotor. Til slutt har du et live OpenAI-kompatibelt API-endepunkt på H100-GPU-er som skalerer til null når ingen bruker det.
Hva er Modal (og hvorfor bruke det for LLM-er)?
Modal er en serverløs beregningsplattform bygget spesielt for AI-arbeidsbelastninger. Tenk AWS Lambda, men med GPU-støtte, sekund-fakturering og en Python-nativ utvikleropplevelse. Ingen YAML, ingen Dockerfiles, ingen Kubernetes — du definerer hele infrastrukturen i et Python-skript og distribuerer med én enkelt kommando.
Her er hvorfor det har blitt standardvalget for LLM-distribusjon:
- Scale-to-zero-fakturering — du betaler ingenting når endepunktet ditt ikke håndterer forespørsler
- GPU-priser per sekund — H100-er til ~$3,95/time, A100 80 GB til ~$2,50/time, fakturert per sekund
- Sub-sekunds kaldstarter — containere starter raskt, spesielt med minnebilder
- $30/måned i gratis kreditter — nok til å eksperimentere uten kredittkortgebyr
- Ingen DevOps — ingen Docker-bygg, ingen Terraform, ingen klusteradministrasjon
Hvis du kjører LLM-er lokalt og ønsker å gi dem et ordentlig API uten å administrere servere, er Modal den korteste veien dit.
Modal vs. RunPod vs. Lambda
| Funksjon | Modal | RunPod | Lambda |
|---|---|---|---|
| Faktureringsmodell | Per sekund, scale-to-zero | Per sekund, minimumsavgift | Per time, alltid aktiv |
| Kaldstart | 2-4 sekunder | 6-12 sekunder (stor) | Ikke aktuelt (persistent) |
| GPU-tilgjengelighet | H100, A100, L40S, T4 | A100, H100, A6000 | H100, A100 |
| Infrastruktur | Ren Python, ingen konfig-filer | Docker-basert, mer kontroll | Full VM-tilgang |
| Gratisnivå | $30/måned i kreditter | Ingen | Ingen |
| Best for | Burstig/dev-arbeidsbelastning | Stabil inferenstrafikk | Intensiv trening |
Konklusjon: Modal vinner for burstig arbeidsbelastning og utvikling. Hvis GPU-utnyttelsen din konsekvent overstiger 40%, er en dedikert instans på RunPod eller Lambda billigere. For alt annet — prototyping, intermittente API-er, demoer — sparer Modals scale-to-zero-modell ekte penger.
Forutsetninger
Før du begynner trenger du tre ting:
- Python 3.10+ installert lokalt
- En Modal-konto — registrer deg gratis på modal.com
- En Hugging Face-konto — for modelltilgang (de fleste modeller er begrenset)
Det er alt. Ingen GPU på den lokale maskinen, ingen CUDA-verktøypakke, ingen Docker.
Trinn 1: Installer Modal og autentiser
Åpne en terminal og installer Modal Python-pakken:
pip install modalKjør deretter oppsettkommandoen for å koble det lokale miljøet til Modal-kontoen din:
modal setupDette åpner et nettleservindu for autentisering. Når du bekrefter, lagrer Modal et token lokalt. Du trenger aldri å gjøre dette igjen.
Trinn 2: Definer container-bildet
Modal-containere defineres i Python. Du spesifiserer basisbildet, installerer avhengigheter og setter miljøvariabler — alt som kode. Opprett en fil kalt app.py:
import modal
# Definer container-bildet med CUDA, Python og 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)Noen ting å merke seg. Det er ingen Dockerfile — den modal.Image-kjeden erstatter den fullstendig. Basisbildet inkluderer NVIDIA CUDA 12.8 med Ubuntu 22.04, og vi installerer vLLM og Hugging Face Hub-klienten oppå.
Trinn 3: Konfigurer modelllagring med Volumes
LLM-vekter er store (en modell med 7 milliarder parametere er ~14 GB i fp16). Du vil ikke laste dem ned hver gang en container starter. Modal Volumes gir deg persistent lagring som monteres direkte i containerne dine:
# Persistente volumes for caching av modellvekter
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"Vi bruker Qwen3-4B-Thinking (FP8) her — en kvantisert modell med 4 milliarder parametere som er rask, kapabel og passer på én enkelt GPU. Du kan bytte den ut mot hvilken som helst Hugging Face-modell: Llama 3.1 8B, Mistral 7B, eller hva som helst vLLM støtter.
Hvorfor FP8? Det halverer omtrent minnebruken sammenlignet med fp16, noe som betyr at du kan kjøre større modeller på samme GPU — eller mindre modeller på billigere GPU-er. Hvis du er nysgjerrig på kvantiseringsavveiningene, dekker vår guide om å kjøre LLM-er lokalt presisjonsformatene i detalj.
Trinn 4: Opprett vLLM-serverfunksjonen
Det er her Modals magi skjer. Du dekorerer en Python-funksjon med GPU-krav, skaleringskonfigurasjon og en webserver-annotasjon. Modal håndterer resten:
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", # Raskere kaldstarter
]
subprocess.Popen(" ".join(cmd), shell=True)La oss bryte ned de viktigste dekoratorene:
gpu="H100:1"— ber om én enkelt H100-GPU. Endre til"A100-80GB:1"for billigere inferens, eller"H100:2"for 70B+-modellerscaledown_window=15 * MINUTES— holder containeren varm i 15 minutter etter den siste forespørselen, deretter skalerer den til null@modal.concurrent(max_inputs=32)— tillater opptil 32 samtidige forespørsler per container (vLLM håndterer batching internt)@modal.web_server(port=8000)— eksponerer vLLM HTTP-serveren direkte som et Modal-webbendepunkt--enforce-eager— hopper over CUDA-grafkompilering for raskere kaldstarter (avveining: noe lavere toppgjennomstrømning)
scaledown_window er din viktigste kostnadsbryter. Still den til 5 minutter for utvikling, 15-30 minutter for produksjons-API-er med regelmessig trafikk.
Trinn 5: Distribuer til produksjon
Én kommando. Det er alt:
modal deploy app.pyModal bygger container-bildet, pusher det til registeret sitt og returnerer en live 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.runDen første distribusjonen tar noen minutter fordi den laster ned modellvekter til volumet. Påfølgende distribusjoner (og kaldstarter) er mye raskere siden vektene er cachet.
Bruk modal serve app.py under utvikling i stedet — det laster inn på nytt ved filendringer og gir deg en midlertidig URL.
Trinn 6: Kall endepunktet ditt (OpenAI-kompatibelt)
Din distribuerte vLLM-server eksponerer et OpenAI-kompatibelt API på /v1/chat/completions. Du kan bruke standard OpenAI Python SDK for å kalle det — pek bare base-URL-en mot Modal-endepunktet ditt:
from openai import OpenAI
client = OpenAI(
api_key="not-needed", # vLLM krever ikke autentisering som standard
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)Dette fungerer også med 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
}'Ethvert verktøy som støtter et OpenAI-kompatibelt API vil fungere — LangChain, LlamaIndex, din egen app. Hvis du ruter forespørsler på tvers av flere LLM-endepunkter, kan et LLM-gateway-verktøy hjelpe deg med å administrere failover og lastbalansering.
Tips for kostnadsoptimering
Modals sekund-fakturering er allerede mer effektiv enn timeprislegging, men du kan presse enda mer ut av det:
1. Bruk FP8-kvantiering
FP8-modeller bruker omtrent halvparten av VRAM sammenlignet med fp16-motpartene. En Qwen3-8B i FP8 passer på én enkelt H100, mens fp16-versjonen trenger det meste av den GPU-ens 80 GB. Mindre VRAM betyr at du kan bruke billigere GPU-er (A100 40 GB, L40S) for mindre modeller.
2. Juster nedskaleringsvinduet
scaledown_window-parameteren styrer hvor lenge en container forblir varm etter den siste forespørselen:
| Scenario | Anbefalt vindu | Hvorfor |
|---|---|---|
| Utvikling/testing | 5 minutter | Spar penger, kaldstarter er greit |
| Internt API (av og til) | 10-15 minutter | Balanse kostnad vs latens |
| Produksjon (regelmessig trafikk) | 20-30 minutter | Minimer kaldstarter |
| Høytrafikkproduksjon | Bruk min_containers=1 | Hold én alltid varm |
3. Velg riktig GPU
Velg ikke alltid H100. Mindre modeller trenger den ikke:
| Modellstørrelse | Anbefalt GPU | Omtrentlig kostnad/time |
|---|---|---|
| 1-4B parametere | L4 eller T4 | $0,59 – $0,80 |
| 7-8B parametere | A10 eller L40S | $1,10 – $1,95 |
| 13-14B parametere | A100 40 GB | $2,10 |
| 30-70B parametere | A100 80 GB eller H100 | $2,50 – $3,95 |
| 70B+ parametere | H100 x2 | $7,90 |
4. Aktiver prompt-caching
Hvis arbeidsbelastningene dine involverer gjentatte systemprompts eller delte prefikser, kan vLLMs automatiske prefiks-caching redusere latens og beregning betydelig. Aktiver det ved å legge til --enable-prefix-caching i vLLM serve-kommandoen. For en dypere gjennomgang av hvordan caching fungerer hos ulike leverandører, sjekk ut vår guide om LLM-prompt-caching.
5. Bruk --enforce-eager for kaldstartsoptimering
Som standard kompilerer vLLM CUDA-grafer ved oppstart, noe som tar 1-3 ekstra minutter. --enforce-eager-flagget hopper over denne kompileringen. Du bytter ~10-15% toppgjennomstrømning mot dramatisk raskere kaldstarter. For burstig arbeidsbelastning der latens betyr mer enn rå gjennomstrømning, er det nesten alltid det riktige valget.
Videre: finjusterte modeller
Når du er komfortabel med å distribuere basismodeller, er det naturlige neste steget å distribuere din egne finjusterte versjon. Arbeidsflyten er identisk — du peker bare MODEL_NAME mot Hugging Face-repositoriet ditt eller et Modal-volum som inneholder de finjusterte vektene dine.
Modal støtter også kjøring av finjusteringsjobber direkte på GPU-ene sine. Du kan trene en LoRA-adapter på Modal, lagre den i et volum og distribuere den sammenslåtte modellen — alt uten å forlate plattformen. Vår guide om LLM-finjustering dekker treningssiden i dybden.
Den komplette app.py
Her er det fullstendige distribusjonsskriptet i én klar-til-kopiering-blokk:
import modal
# --- Bilddefinisjon ---
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 modell-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)
# --- Modellkonfigurasjon ---
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)Distribuer med modal deploy app.py, bytt ut MODEL_NAME med hvilken som helst Hugging Face-modell, og du er live.
Ofte stilte spørsmål
Hvor mye koster det å kjøre en LLM på Modal?
Det avhenger av GPU-en og hvor lenge endepunktet ditt forblir varmt. En Qwen3-4B på en H100 koster ~$3,95/time med aktiv bruk. Med scale-to-zero og et 15-minutters nedskaleringsvindu kan et lite brukt endepunkt koste $5-15/måned. De $30 i gratis månedlige kreditter dekker mye eksperimentering.
Skalerer Modal til null?
Ja — det er et av de viktigste salgsargumentene. Når ingen forespørsler ankommer i løpet av scaledown_window-varigheten, slår containeren seg av og du slutter å betale. Den neste forespørselen utløser en kaldstart (vanligvis 2-10 sekunder avhengig av modellstørrelse og om du bruker --enforce-eager).
Kan jeg distribuere Llama 3.1 eller Mistral på Modal?
Absolutt. Bytt ut MODEL_NAME-konstanten med hvilken som helst modell vLLM støtter: meta-llama/Llama-3.1-8B-Instruct, mistralai/Mistral-7B-Instruct-v0.3, eller hundrevis av andre på Hugging Face. For 70B+-modeller, endre N_GPU til 2 og bruk gpu="H100:2".
Hvordan sammenligner kaldstarter seg med RunPod?
Modals kaldstarter er typisk 2-4 sekunder for selve containeren, pluss modellinnlastingstid. Med modellvekter cachet i et Volume og --enforce-eager aktivert, ser du på 10-30 sekunder totalt for en 7-8B-modell. RunPods serverløse kaldstarter varierer fra under 200 ms (cachet) til 6-12 sekunder for større containere — selv om always-on-modellen unngår kaldstarter helt.
Er Modals vLLM-endepunkt virkelig OpenAI-kompatibelt?
Ja. vLLM implementerer de samme /v1/chat/completions-, /v1/completions- og /v1/models-endepunktene som OpenAI bruker. Du kan peke den offisielle openai Python SDK mot Modal-URL-en din og den fungerer rett ut av esken. Streaming, function calling og JSON-modus fungerer alle.
Trenger jeg en GPU på den lokale maskinen min?
Nei. Den lokale maskinen din kjører bare Modal CLI. Alt GPU-arbeid skjer på Modals skyinfrastruktur. Du kan distribuere fra en Chromebook om du vil.
Hvordan legger jeg til autentisering til endepunktet mitt?
Modal-webbendepunkter er offentlige som standard. Legg til en enkel API-nøkkelkontroll i applikasjonskoden din for produksjon, eller bruk Modals innebygde webbautentiseringsfunksjoner. Du kan også sette opp et proxy-lag ved hjelp av en LLM-gateway som håndterer autentisering, hastighetsbegrensning og ruting.
Hva er forskjellen mellom modal serve og modal deploy?
modal serve oppretter et midlertidig endepunkt som laster inn på nytt når du redigerer koden din — perfekt for utvikling. modal deploy oppretter et persistent, produksjonsklart endepunkt med en stabil URL. Bruk serve mens du itererer, deploy når du er klar til å sende.
Kan jeg bruke SGLang i stedet for vLLM?
Ja. Modals dokumentasjon inkluderer SGLang-eksempler ved siden av vLLM. SGLang har en tendens til å ha lavere overhead for decode-tunge arbeidsbelastninger og mindre modeller. vLLM er generelt bedre for blandede arbeidsbelastninger med tung prefill. Begge produserer OpenAI-kompatible endepunkter.
Hvordan sammenligner dette seg med distribusjon på Railway eller Render?
Plattformer som Railway, Render og Fly.io er flotte for nettapplikasjoner, men de tilbyr ikke GPU-instanser. Modal er bygget spesielt for GPU-arbeidsbelastninger med sekund-fakturering og autoskalering. Hvis du trenger å betjene en LLM, er Modal (eller RunPod) det riktige verktøyet — tradisjonelle PaaS-plattformer kan ikke gjøre det.