Modal 是做什麼的?
概述
Modal 是一個用於無伺服器執行 Python 程式碼的雲端平台,專注於 AI/ML 工作負載。主要功能:
- GPU 運算 按需使用(T4、L4、A10、L40S、A100、H100、H200、B200)
- 無伺服器函數,從零自動擴展到數千個容器
- 自訂容器映像 完全以 Python 程式碼建置
- 永久儲存 透過 Volume 儲存模型權重和資料集
- Web 端點 用於提供模型和 API
- 排程任務 透過 cron 或固定間隔執行
- 次秒級冷啟動 實現低延遲推論
Modal 中的所有內容都以程式碼定義 — 無需 YAML、無需 Dockerfile(雖然兩者都受支援)。
何時使用此技能
在以下情況使用此技能:
- 在雲端部署或提供 AI/ML 模型
- 執行 GPU 加速運算(訓練、推論、微調)
- 建立無伺服器 Web API 或端點
- 平行擴展批次處理任務
- 排程重複性任務(資料管道、重新訓練、爬蟲)
- 需要模型權重或資料集的永久雲端儲存
- 想要在自訂容器環境中執行程式碼
- 建立任務佇列或非同步任務處理系統
安裝與驗證
安裝
uv pip install modal
Modal Python SDK 支援 Python 3.10–3.14。此技能針對穩定的 modal>=1.0 API(目前版本:1.4.x)。
驗證
優先使用現有的憑證,然後再建立新的憑證。只有以下兩個 Modal 專用變數是相關的 — 請勿讀取、載入或暴露任何其他環境變數或 .env 檔案內容:
- 檢查
MODAL_TOKEN_ID和MODAL_TOKEN_SECRET是否已在目前環境中設定。 - 如果沒有,只在本機
.env檔案中查詢這兩個金鑰(忽略所有其他條目),並在工作流程適當的情況下載入它們。 - 僅在以上兩個來源都未提供這兩個值時,才回退到互動式
modal setup或產生新的令牌。
modal setup
這會開啟瀏覽器進行驗證。對於 CI/CD 或無頭環境,請使用環境變數:
export MODAL_TOKEN_ID=<your-token-id>
export MODAL_TOKEN_SECRET=<your-token-secret>
如果令牌尚未在環境或 .env 中可用,請在 https://modal.com/settings 產生
Modal 提供免費層級,每月有 30 美元的額度。
參考:請參閱 references/getting-started.md 以取得詳細的設定和第一個應用程式逐步說明。
核心概念
App 與 Function
一個 Modal App 將相關的函數分組。使用 @app.function() 裝飾的函數會在雲端遠端執行:
import modal
app = modal.App("my-app")
@app.function()
def square(x):
return x ** 2
@app.local_entrypoint()
def main():
# .remote() 在雲端執行
print(square.remote(42))
使用 modal run script.py 執行。使用 modal deploy script.py 部署。
參考:請參閱 references/functions.md 以了解生命週期鉤子、類別、.map()、.spawn() 等更多內容。
容器映像
Modal 從 Python 程式碼建置容器映像。建議的套件安裝程式是 uv:
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install("torch==2.12.0", "transformers==5.9.0", "accelerate==1.13.0")
.apt_install("git")
)
@app.function(image=image)
def inference(prompt):
from transformers import pipeline
pipe = pipeline("text-generation", model="meta-llama/Llama-3-8B")
return pipe(prompt)
關鍵映像方法:
.uv_pip_install()— 使用 uv 安裝 Python 套件(建議).pip_install()— 使用 pip 安裝(備用).apt_install()— 安裝系統套件.run_commands()— 在建置期間執行 shell 命令.run_function()— 在建置期間執行 Python(例如下載模型權重).add_local_python_source()— 新增本機模組.env()— 設定環境變數
參考:請參閱 references/images.md 以了解 Dockerfile、micromamba、快取、GPU 建置步驟。
GPU 運算
透過 gpu 參數請求 GPU:
@app.function(gpu="H100")
def train_model():
import torch
device = torch.device("cuda")
# GPU 訓練程式碼在此
# 多個 GPU
@app.function(gpu="H100:4")
def distributed_training():
...
# GPU 備援鏈
@app.function(gpu=["H100", "A100-80GB", "A100-40GB"])
def flexible_inference():
...
可用 GPU:T4、L4、A10、L40S、A100-40GB、A100-80GB、RTX-PRO-6000、H100、H200、B200、B200+
- GPU 一律以字串指定(例如
gpu="H100"、gpu="H100:4")。舊的modal.gpu.*物件自 v0.73.31 起已棄用。 - 每個容器最多 8 個 GPU(A10 除外:最多 4 個)
- L40S 建議用於推論(成本/效能平衡,48 GB VRAM)
- H100/A100 可自動升級至 H200/A100-80GB,無需額外費用
- 使用
gpu="H100!"防止自動升級
參考:請參閱 references/gpu.md 以了解 GPU 選擇指南和多 GPU 訓練。
Volume(永久儲存)
Volume 提供分散式、持久的檔案儲存:
vol = modal.Volume.from_name("model-weights", create_if_missing=True)
@app.function(volumes={"/data": vol})
def save_model():
# 寫入掛載路徑
with open("/data/model.pt", "wb") as f:
torch.save(model.state_dict(), f)
@app.function(volumes={"/data": vol})
def load_model():
model.load_state_dict(torch.load("/data/model.pt"))
- 最佳化用於一次寫入、多次讀取的工作負載(模型權重、資料集)
- CLI 存取:
modal volume ls、modal volume put、modal volume get - 背景自動每隔幾秒提交
- 使用
vol.with_mount_options(read_only=True, sub_path="subset")掛載為唯讀或限制為子目錄
參考:請參閱 references/volumes.md 以了解 v2 磁碟區、並發寫入和最佳實踐。
Secret
安全地將憑證傳遞給函數:
@app.function(secrets=[modal.Secret.from_name("my-api-keys")])
def call_api():
import os
api_key = os.environ["API_KEY"]
# 使用金鑰
透過 CLI 建立 secret:modal secret create my-api-keys API_KEY=sk-xxx
或從 .env 檔案:modal.Secret.from_dotenv()
參考:請參閱 references/secrets.md 以了解儀表板設定、多個 secret 和範本。
Web 端點
將模型和 API 作為 Web 端點提供:
@app.function()
@modal.fastapi_endpoint()
def predict(text: str):
return {"result": model.predict(text)}
modal serve script.py— 具有熱重載和臨時 URL 的開發環境modal deploy script.py— 具有永久 URL 的生產部署- 支援 FastAPI、ASGI(Starlette、FastHTML)、WSGI(Flask、Django)、WebSocket
- 請求主體高達 4 GiB,無限制的回應大小
參考:請參閱 references/web-endpoints.md 以了解 ASGI/WSGI 應用程式、串流、驗證和 WebSocket。
排程任務
按排程執行函數:
@app.function(schedule=modal.Cron("0 9 * * *")) # 每天上午 9 點 UTC
ndef daily_pipeline():
# ETL、重新訓練、爬蟲等
...
@app.function(schedule=modal.Period(hours=6))
def periodic_check():
...
使用 modal deploy script.py 部署以啟用排程。
modal.Cron("...")— 標準 cron 語法,跨部署穩定modal.Period(hours=N)— 固定間隔,重新部署後重置- 在 Modal 儀表板中監控執行
參考:請參閱 references/scheduled-jobs.md 以了解 cron 語法和管理。
擴展與並發
Modal 自動擴展容器。設定限制:
@app.function(
max_containers=100, # 上限
min_containers=2, # 保持溫暖以實現低延遲
buffer_containers=5, # 保留容量
scaledown_window=300, # 關閉前的閒置秒數
)
def process(data):
...
使用 .map() 平行處理輸入:
results = list(process.map([item1, item2, item3, ...]))
使用 @modal.concurrent 啟用每個容器的並發請求處理。設定
target_inputs(自動擴展器的每個容器目標)低於 max_inputs(硬性上限),
以在擴展時保持空間:
@app.function()
@modal.concurrent(max_inputs=10, target_inputs=8)
async def handle_request(req):
...
在呼叫時重新設定已部署的 Function 或 Cls,而無需重新部署,使用
Function.with_options() / Function.with_concurrency() / Function.with_batching()
(以及 Cls.with_options()):
Model = modal.Cls.from_name("my-app", "Model")
fast = Model.with_options(gpu="H200", max_containers=20)
fast().generate.remote(prompt)
參考:請參閱 references/scaling.md 以了解 .map()、.starmap()、.spawn() 和限制。
資源設定
@app.function(
cpu=4.0, # 實體核心(非 vCPU)
memory=16384, # MiB
ephemeral_disk=51200, # MiB(最高 3 TiB)
timeout=3600, # 秒
)
def heavy_computation():
...
預設值:0.125 個 CPU 核心,128 MiB 記憶體。按 max(request, usage) 計費。
參考:請參閱 references/resources.md 以了解限制和計費詳細資訊。
具有生命週期鉤子的類別
對於有狀態的工作負載(例如,載入模型一次並服務多個請求):
@app.cls(gpu="L40S", image=image)
class Predictor:
@modal.enter()
def load_model(self):
self.model = load_heavy_model() # 在容器啟動時執行一次
@modal.method()
def predict(self, text: str):
return self.model(text)
@modal.exit()
def cleanup(self):
... # 在容器關閉時執行
使用以下方式呼叫:Predictor().predict.remote("hello")
Sandbox
為了執行不受信任或動態產生的程式碼(例如 AI 代理輸出或程式碼直譯器),請使用 modal.Sandbox — 一個隔離的容器,您可以透過程式方式建立和控制,而不是使用裝飾的 Function:
app = modal.App.lookup("sandbox-demo", create_if_missing=True)
# 隔離容器;限制不受信任工作負載的對外存取
sb = modal.Sandbox.create(
app=app,
image=modal.Image.debian_slim(),
outbound_cidr_allowlist=["10.0.0.0/8"],
)
# 透過檔案系統 API(測試版)串流進出檔案
sb.filesystem.write_text("print(2 ** 10)\n", "/tmp/job.py")
contents = sb.filesystem.read_text("/tmp/job.py")
sb.terminate()
- 使用 sandbox 的
exec方法在其中執行命令(例如執行python /tmp/job.py),並從傳回的處理程序控制代碼讀取 stdout — 請參閱references/api_reference.md - 使用
outbound_cidr_allowlist=[...]/inbound_cidr_allowlist=[...]限制連線能力 - 使用
sb.snapshot_filesystem()快照檔案系統以重複用作基礎映像 - 非常適合程式碼直譯器、代理工具執行和每使用者隔離
常見工作流程模式
GPU 模型推論服務
import modal
app = modal.App("llm-service")
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install("vllm")
)
@app.cls(gpu="H100", image=image, min_containers=1)
class LLMService:
@modal.enter()
def load(self):
from vllm import LLM
self.llm = LLM(model="meta-llama/Llama-3-70B")
@modal.method()
@modal.fastapi_endpoint(method="POST")
def generate(self, prompt: str, max_tokens: int = 256):
outputs = self.llm.generate([prompt], max_tokens=max_tokens)
return {"text": outputs[0].outputs[0].text}
批次處理管道
app = modal.App("batch-pipeline")
vol = modal.Volume.from_name("pipeline-data", create_if_missing=True)
@app.function(volumes={"/data": vol}, cpu=4.0, memory=8192)
def process_chunk(chunk_id: int):
import pandas as pd
df = pd.read_parquet(f"/data/input/chunk_{chunk_id}.parquet")
result = heavy_transform(df)
result.to_parquet(f"/data/output/chunk_{chunk_id}.parquet")
return len(result)
@app.local_entrypoint()
def main():
chunk_ids = list(range(100))
results = list(process_chunk.map(chunk_ids))
print(f"Processed {sum(results)} total rows")
排程資料管道
app = modal.App("etl-pipeline")
@app.function(
schedule=modal.Cron("0 */6 * * *"), # 每 6 小時
secrets=[modal.Secret.from_name("db-credentials")],
)
def etl_job():
import os
db_url = os.environ["DATABASE_URL"]
# 萃取、轉換、載入
...
CLI 參考
| 命令 | 描述 |
|---|---|
modal setup | 使用 Modal 進行驗證 |
modal run script.py | 執行腳本的本機進入點 |
modal serve script.py | 具有熱重載的開發伺服器 |
modal deploy script.py | 部署到生產環境 |
modal volume ls <name> | 列出 volume 中的檔案 |
modal volume put <name> <file> | 將檔案上傳到 volume |
modal volume get <name> <file> | 從 volume 下載檔案 |
modal secret create <name> K=V | 建立 secret |
modal secret list | 列出 secret |
modal app list | 列出已部署的應用程式 |
modal app stop <name> | 停止已部署的應用程式 |
安全注意事項
- 憑證: 只需要
MODAL_TOKEN_ID和MODAL_TOKEN_SECRET即可驗證。請勿讀取、記錄或轉發任何其他環境變數或.env條目。 - 子程序 / 自訂伺服器: 此處的一些模式(多 GPU 訓練啟動器、
@modal.web_server應用程式)在建置期間會呼叫subprocess.run/subprocess.Popen或 shell 命令。請保持引數清單固定且寫死。切勿從未經淨化的使用者輸入建構子程序或 shell 引數 — 將不受信任的值作為資料(檔案、環境變數、stdin)傳遞,而不是作為命令引數。 - 不受信任的程式碼: 在使用者或模型產生的程式碼應在
modal.Sandbox(見上文)中執行,而不是在一般的 Function 中,並使用 CIDR 允許清單限制網路存取。
參考檔案
每個主題的詳細文件:
references/getting-started.md— 安裝、驗證、第一個應用程式references/functions.md— 函數、類別、生命週期鉤子、遠端執行references/images.md— 容器映像、套件安裝、快取references/gpu.md— GPU 類型
…
[截斷以進行翻譯]