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 了解详细设置和第一个应用演练。
核心概念
应用和函数
一个 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 训练。
卷(Volumes,持久化存储)
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 volumes、并发写入和最佳实践。
密钥(Secrets)
安全地将凭据传递给函数:
@app.function(secrets=[modal.Secret.from_name("my-api-keys")])
def call_api():
import os
api_key = os.environ["API_KEY"]
# 使用密钥
通过 CLI 创建密钥:modal secret create my-api-keys API_KEY=sk-xxx
或者从 .env 文件创建:modal.Secret.from_dotenv()
参考:参见 references/secrets.md 了解仪表盘设置、多个密钥和模板。
Web 端点
将模型和 API 作为 Web 端点提供服务:
@app.function()
@modal.fastapi_endpoint()
def predict(text: str):
return {"result": model.predict(text)}
modal serve script.py— 开发模式,支持热重载和临时 URLmodal deploy script.py— 生产部署,使用永久 URL- 支持 FastAPI、ASGI(Starlette、FastHTML)、WSGI(Flask、Django)、WebSockets
- 请求正文最大 4 GiB,响应大小无限制
参考:参见 references/web-endpoints.md 了解 ASGI/WSGI 应用、流式传输、认证和 WebSockets。
定时任务
按计划运行函数:
@app.function(schedule=modal.Cron("0 9 * * *")) # 每天 UTC 时间 9:00
def 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(请求,使用量)计费。
参考:参见 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")
沙箱(Sandboxes)
用于运行不受信任或动态生成的代码(例如,AI agent 输出或代码解释器),使用 modal.Sandbox——一个隔离的容器,通过编程方式创建和控制,而不是使用装饰的函数:
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 流式传输文件(beta)
sb.filesystem.write_text("print(2 ** 10)\n", "/tmp/job.py")
contents = sb.filesystem.read_text("/tmp/job.py")
sb.terminate()
- 使用沙箱的
exec方法在其中运行命令(例如,运行python /tmp/job.py)并从返回的进程句柄读取 stdout——参见references/api_reference.md - 使用
outbound_cidr_allowlist=[...]/inbound_cidr_allowlist=[...]限制连接 - 使用
sb.snapshot_filesystem()快照文件系统以用作基础镜像 - 非常适合代码解释器、Agent 工具执行和按用户隔离
常见工作流模式
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"处理了 {sum(results)} 行总数")
定时数据管道
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> | 列出卷中的文件 |
modal volume put <name> <file> | 上传文件到卷 |
modal volume get <name> <file> | 从卷下载文件 |
modal secret create <name> K=V | 创建密钥 |
modal secret list | 列出密钥 |
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 类型
…
[已截断以供翻译]