
Triển khai LLM với Modal: Từ pip install đến Endpoint Production
Hầu hết các hướng dẫn về việc tự host LLM đều lướt qua phần khó nhất: cơ sở hạ tầng. Bạn phải vật lộn với trình điều khiển CUDA, quản lý các Docker image, cấu hình autoscaling và cuối cùng vẫn phải trả tiền cho các GPU nhàn rỗi lúc 3 giờ sáng. Modal loại bỏ tất cả những rắc rối đó. Bạn viết code Python, bạn triển khai, và bạn nhận được một URL.
Hướng dẫn này sẽ đưa bạn qua quy trình triển khai một LLM mã nguồn mở trên Modal với vLLM làm engine suy luận. Khi kết thúc, bạn sẽ có một endpoint API đang hoạt động, tương thích với OpenAI, chạy trên GPU H100 và tự động scale về 0 khi không có ai sử dụng.
Modal là gì (và tại sao nên dùng nó cho LLM)?
Modal là một nền tảng điện toán serverless được xây dựng đặc biệt cho các tác vụ AI. Hãy nghĩ về AWS Lambda, nhưng có hỗ trợ GPU, tính phí theo giây và trải nghiệm phát triển native cho Python. Không có YAML, không có Dockerfiles, không có Kubernetes; bạn định nghĩa toàn bộ cơ sở hạ tầng trong một script Python và triển khai chỉ bằng một lệnh duy nhất.
Dưới đây là lý do tại sao nó đã trở thành lựa chọn hàng đầu để triển khai LLM:
- Tính phí scale-to-zero, bạn không phải trả gì khi endpoint của mình không xử lý yêu cầu
- Giá GPU theo giây, H100 khoảng $3.95/giờ, A100 80GB khoảng $2.50/giờ, tính phí theo từng giây
- Khởi động lạnh dưới một giây, các container khởi động rất nhanh, đặc biệt là với các snapshot bộ nhớ
- $30 tín dụng miễn phí/tháng, đủ để thử nghiệm mà không cần lo lắng về hóa đơn thẻ tín dụng
- Không cần DevOps, không build Docker, không Terraform, không quản lý cluster
Nếu bạn đã từng chạy LLM cục bộ và muốn cung cấp cho chúng một API đàng hoàng mà không phải quản lý máy chủ, Modal là con đường ngắn nhất để đạt được điều đó.
Modal so với RunPod so với Lambda
| Tính năng | Modal | RunPod | Lambda |
|---|---|---|---|
| Mô hình tính phí | Theo giây, scale-to-zero | Theo giây, mức phí tối thiểu | Theo giờ, luôn bật |
| Khởi động lạnh | 2-4 giây | 6-12 giây (lớn) | N/A (liên tục) |
| Khả dụng GPU | H100, A100, L40S, T4 | A100, H100, A6000 | H100, A100 |
| Cơ sở hạ tầng | Pure Python, không file cấu hình | Dựa trên Docker, kiểm soát nhiều hơn | Truy cập VM đầy đủ |
| Gói miễn phí | $30 tín dụng/tháng | Không có | Không có |
| Phù hợp nhất cho | Tải công việc bùng nổ/phát triển | Lưu lượng suy luận ổn định | Huấn luyện với mức sử dụng cao |
Kết luận: Modal thắng thế đối với các tải công việc có tính chất bùng nổ và phát triển. Nếu mức sử dụng GPU của bạn luôn vượt quá 40%, một instance chuyên dụng trên RunPod hoặc Lambda sẽ rẻ hơn. Đối với mọi trường hợp khác như tạo mẫu, API gián đoạn, demo, mô hình scale-to-zero của Modal giúp tiết kiệm tiền thật sự.
Điều kiện tiên quyết
Trước khi bắt đầu, bạn cần ba thứ:
- Python 3.10+ đã được cài đặt cục bộ
- Tài khoản Modal, đăng ký miễn phí tại modal.com
- Tài khoản Hugging Face, để truy cập mô hình (hầu hết các mô hình đều bị giới hạn quyền truy cập)
Chỉ vậy thôi. Không cần GPU trên máy cục bộ, không cần CUDA toolkit, không cần Docker.
Bước 1: Cài đặt Modal và Xác thực
Mở terminal và cài đặt gói Python của Modal:
pip install modalSau đó chạy lệnh setup để liên kết môi trường cục bộ của bạn với tài khoản Modal:
modal setupThao tác này sẽ mở cửa sổ trình duyệt để xác thực. Sau khi bạn xác nhận, Modal sẽ lưu trữ token cục bộ. Bạn sẽ không cần thực hiện lại bước này nữa.
Bước 2: Định nghĩa Container Image
Các container của Modal được định nghĩa bằng Python. Bạn chỉ định image cơ sở, cài đặt các dependency và thiết lập biến môi trường, tất cả đều dưới dạng code. Tạo một file tên là 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)Có vài điểm đáng chú ý. Không có Dockerfile, chuỗi modal.Image thay thế hoàn toàn cho nó. Image cơ sở bao gồm NVIDIA CUDA 12.8 với Ubuntu 22.04, và chúng ta cài đặt vLLM cùng client Hugging Face Hub lên trên đó.
Bước 3: Cấu hình Lưu trữ Mô hình với Volumes
Weights của LLM rất lớn (một mô hình 7 tỷ tham số nặng ~14 GB ở định dạng fp16). Bạn không muốn phải tải xuống chúng mỗi lần một container khởi động. Modal Volumes cung cấp cho bạn bộ nhớ bền vững được mount trực tiếp vào các container của bạn:
# 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"Chúng ta đang sử dụng Qwen3-4B-Thinking (FP8) ở đây, một mô hình 4 tỷ tham số đã được lượng tử hóa, nhanh, mạnh mẽ và vừa vặn trên một GPU duy nhất. Bạn có thể thay thế nó bằng bất kỳ mô hình nào trên Hugging Face: Llama 3.1 8B, Mistral 7B, hoặc bất kỳ thứ gì vLLM hỗ trợ.
Tại sao lại là FP8? Nó giảm gần một nửa mức sử dụng bộ nhớ so với fp16, nghĩa là bạn có thể chạy các mô hình lớn hơn trên cùng một GPU, hoặc chạy các mô hình nhỏ hơn trên các GPU rẻ hơn. Nếu bạn tò mò về sự đánh đổi trong lượng tử hóa, hướng dẫn chạy LLM cục bộ của chúng tôi đề cập chi tiết về các định dạng độ chính xác.
Bước 4: Tạo Hàm Server vLLM
Đây là nơi phép màu của Modal diễn ra. Bạn trang trí một hàm Python với các yêu cầu về GPU, cấu hình scaling và annotation web server. Modal xử lý mọi thứ còn lại:
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)Hãy phân tích các decorator chính:
gpu="H100:1", yêu cầu một GPU H100 duy nhất. Thay đổi thành"A100-80GB:1"để suy luận rẻ hơn, hoặc"H100:2"cho các mô hình 70B+scaledown_window=15 * MINUTES, giữ container ở trạng thái "ấm" trong 15 phút sau yêu cầu cuối cùng, sau đó scale về 0@modal.concurrent(max_inputs=32), cho phép tối đa 32 yêu cầu đồng thời trên mỗi container (vLLM xử lý batching nội bộ)@modal.web_server(port=8000), expose trực tiếp HTTP server của vLLM như một web endpoint của Modal--enforce-eager, bỏ qua biên dịch CUDA graph để khởi động lạnh nhanh hơn (đánh đổi: thông lượng đỉnh thấp hơn một chút)
scaledown_window là đòn bẩy chi phí chính của bạn. Đặt nó thành 5 phút cho môi trường dev, 15-30 phút cho các API production nơi bạn mong đợi lưu lượng truy cập thường xuyên.
Bước 5: Triển khai lên Production
Chỉ một lệnh. Đó là tất cả:
modal deploy app.pyModal xây dựng container image, đẩy nó vào registry của họ và trả về một URL đang hoạt động:
✓ 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.runLần triển khai đầu tiên mất vài phút vì nó tải weights của mô hình vào volume. Các lần triển khai tiếp theo (và khởi động lạnh) sẽ nhanh hơn nhiều vì weights đã được lưu trong bộ nhớ cache.
Để phát triển, hãy sử dụng modal serve app.py thay thế, nó sẽ hot-reload khi có thay đổi file và cung cấp cho bạn một URL tạm thời.
Bước 6: Gọi Endpoint của bạn (Tương thích OpenAI)
Server vLLM đã triển khai của bạn expose một API tương thích OpenAI tại /v1/chat/completions. Bạn có thể sử dụng OpenAI Python SDK tiêu chuẩn để gọi nó, chỉ cần trỏ base URL vào endpoint Modal của bạn:
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)Điều này cũng hoạt động với 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
}'Bất kỳ công cụ nào hỗ trợ API tương thích OpenAI đều sẽ hoạt động, LangChain, LlamaIndex, ứng dụng của riêng bạn. Nếu bạn đang định tuyến yêu cầu qua nhiều endpoint LLM, một công cụ LLM gateway có thể giúp bạn quản lý failover và cân bằng tải.
Mẹo Tối ưu hóa Chi phí
Việc tính phí theo giây của Modal đã hiệu quả hơn so với tính phí theo giờ, nhưng bạn có thể tận dụng tối đa nó:
1. Sử dụng Lượng tử hóa FP8
Các mô hình FP8 sử dụng khoảng một nửa VRAM so với các phiên bản fp16 tương ứng. Qwen3-8B ở định dạng FP8 vừa vặn trên một H100 duy nhất, trong khi phiên bản fp16 cần hầu hết 80 GB của GPU đó. Ít VRAM hơn nghĩa là bạn có thể sử dụng các GPU rẻ hơn (A100 40GB, L40S) cho các mô hình nhỏ hơn.
2. Điều chỉnh Scaledown Window
Tham số scaledown_window kiểm soát thời gian một container giữ trạng thái "ấm" sau yêu cầu cuối cùng:
| Kịch bản | Cửa sổ khuyến nghị | Lý do |
|---|---|---|
| Phát triển/kiểm thử | 5 phút | Tiết kiệm tiền, khởi động lạnh chấp nhận được |
| API nội bộ (thỉnh thoảng) | 10-15 phút | Cân bằng giữa chi phí và độ trễ |
| Production (lưu lượng đều) | 20-30 phút | Giảm thiểu khởi động lạnh |
| Production lưu lượng cao | Sử dụng min_containers=1 | Luôn giữ một cái ấm |
3. Chọn GPU Phù hợp
Đừng mặc định chọn H100. Các mô hình nhỏ hơn không cần nó:
| Kích thước Mô hình | GPU Khuyến nghị | Chi phí xấp xỉ/giờ |
|---|---|---|
| 1-4B tham số | L4 hoặc T4 | $0.59 - $0.80 |
| 7-8B tham số | A10 hoặc L40S | $1.10 - $1.95 |
| 13-14B tham số | A100 40GB | $2.10 |
| 30-70B tham số | A100 80GB hoặc H100 | $2.50 - $3.95 |
| 70B+ tham số | H100 x2 | $7.90 |
4. Bật Prompt Caching
Nếu khối lượng công việc của bạn liên quan đến các system prompts lặp lại hoặc các prefix chia sẻ, tính năng prefix caching tự động của vLLM có thể giảm đáng kể độ trễ và tính toán. Bạn có thể bật nó bằng cách thêm --enable-prefix-caching vào lệnh serve của vLLM. Để tìm hiểu sâu hơn về cách caching hoạt động trên các nhà cung cấp khác nhau, hãy xem hướng dẫn về prompt caching LLM của chúng tôi.
5. Sử dụng --enforce-eager để Tối ưu hóa Khởi động Lạnh
Theo mặc định, vLLM biên dịch CUDA graphs khi khởi động, mất thêm 1-3 phút. Cờ --enforce-eager bỏ qua bước biên dịch này. Bạn đánh đổi ~10-15% thông lượng đỉnh để đổi lấy tốc độ khởi động lạnh nhanh hơn đáng kể. Đối với các tải công việc bùng nổ nơi độ trễ quan trọng hơn thông lượng thô, đây hầu như luôn là lựa chọn đúng đắn.
Vươn xa hơn: Các Mô hình Fine-tuned
Khi bạn đã thoải mái với việc triển khai các mô hình cơ sở, bước tiếp theo tự nhiên là triển khai phiên bản fine-tuned của riêng bạn. Quy trình làm việc giống hệt nhau, bạn chỉ cần trỏ MODEL_NAME vào repo Hugging Face của bạn hoặc một Modal volume chứa các weights đã fine-tuned.
Modal cũng hỗ trợ chạy các job fine-tuning trực tiếp trên GPU của họ. Bạn có thể train một LoRA adapter trên Modal, lưu nó vào volume và triển khai mô hình đã merge, tất cả mà không cần rời khỏi nền tảng. Hướng dẫn fine-tune LLM của chúng tôi đề cập chi tiết về khía cạnh training.
Toàn bộ file app.py
Dưới đây là script triển khai đầy đủ trong một khối sẵn sàng để copy-paste:
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)Triển khai với modal deploy app.py, thay thế MODEL_NAME bằng bất kỳ mô hình Hugging Face nào, và bạn đã sẵn sàng hoạt động.
Câu hỏi Thường gặp
Chạy một LLM trên Modal tốn bao nhiêu tiền?
Nó phụ thuộc vào GPU và thời gian endpoint của bạn giữ trạng thái "ấm". Qwen3-4B trên H100 tốn ~$3.95/giờ sử dụng tích cực. Với scale-to-zero và cửa sổ scaledown 15 phút, một endpoint ít được sử dụng có thể tốn $5-15/tháng. Tín dụng miễn phí $30 hàng tháng bao phủ rất nhiều thử nghiệm.
Modal có scale về 0 không?
Có, đó là một trong những điểm bán hàng chính của nó. Khi không có yêu cầu nào đến trong suốt thời gian của scaledown_window, container sẽ tắt và bạn ngừng trả tiền. Yêu cầu tiếp theo sẽ kích hoạt khởi động lạnh (thường là 2-10 giây tùy thuộc vào kích thước mô hình và việc bạn có sử dụng --enforce-eager hay không).
Tôi có thể triển khai Llama 3.1 hoặc Mistral trên Modal không?
Chắc chắn rồi. Thay đổi hằng số MODEL_NAME thành bất kỳ mô hình nào vLLM hỗ trợ: meta-llama/Llama-3.1-8B-Instruct, mistralai/Mistral-7B-Instruct-v0.3, hoặc hàng trăm mô hình khác trên Hugging Face. Đối với các mô hình 70B+, thay đổi N_GPU thành 2 và sử dụng gpu="H100:2".
Khởi động lạnh so với RunPod như thế nào?
Khởi động lạnh của Modal thường là 2-4 giây cho chính container, cộng thêm thời gian tải mô hình. Với weights mô hình được lưu cache trong Volume và bật --enforce-eager, tổng thời gian là 10-30 giây cho một mô hình 7-8B. Khởi động lạnh serverless của RunPod dao động từ dưới 200ms (đã cache) đến 6-12 giây cho các container lớn hơn, mặc dù mô hình always-on của họ tránh hoàn toàn khởi động lạnh.
Endpoint vLLM của Modal có thực sự tương thích OpenAI không?
Có. vLLM triển khai các endpoint /v1/chat/completions, /v1/completions và /v1/models giống hệt như OpenAI sử dụng. Bạn có thể trỏ official openai Python SDK vào URL Modal của mình và nó hoạt động ngay lập tức. Streaming, function calling và JSON mode đều hoạt động.
Tôi có cần GPU trên máy cục bộ không?
Không. Máy cục bộ của bạn chỉ chạy Modal CLI. Tất cả công việc GPU diễn ra trên cơ sở hạ tầng đám mây của Modal. Bạn thậm chí có thể triển khai từ một Chromebook nếu muốn.
Làm thế nào để thêm xác thực vào endpoint của tôi?
Các web endpoint của Modal là công khai theo mặc định. Cho production, hãy thêm một kiểm tra API key đơn giản trong code ứng dụng của bạn, hoặc sử dụng các tính năng xác thực web tích hợp sẵn của Modal. Bạn cũng có thể thiết lập một lớp proxy sử dụng LLM gateway để xử lý auth, giới hạn tốc độ và định tuyến.
Sự khác biệt giữa modal serve và modal deploy là gì?
modal serve tạo một endpoint tạm thời hot-reload khi bạn chỉnh sửa code, hoàn hảo cho phát triển. modal deploy tạo một endpoint ổn định, sẵn sàng cho production với URL cố định. Sử dụng serve trong khi lặp lại, deploy khi bạn sẵn sàng phát hành.
Tôi có thể sử dụng SGLang thay vì vLLM không?
Có. Tài liệu của Modal bao gồm các ví dụ SGLang bên cạnh vLLM. SGLang có xu hướng có overhead thấp hơn cho các tải công việc nặng về decode và các mô hình nhỏ hơn. vLLM nhìn chung tốt hơn cho các tải công việc hỗn hợp với prefill nặng. Cả hai đều tạo ra các endpoint tương thích OpenAI.
So sánh với việc triển khai trên Railway hoặc Render như thế nào?
Các nền tảng như Railway, Render và Fly.io rất tuyệt vời cho các ứng dụng web, nhưng chúng không cung cấp các instance GPU. Modal được xây dựng đặc biệt cho các tải công việc GPU với tính phí theo giây và autoscaling. Nếu bạn cần phục vụ một LLM, Modal (hoặc RunPod) là công cụ phù hợp, các nền tảng PaaS truyền thống không thể làm được điều này.