关于 serverless-modal
serverless-modal 是用于通过 Python 脚本在 Modal 上运行 GPU 训练、评估和推理任务的技能。它提供一次性任务、API、批处理及分布式训练启动模式,并要求每次运行前估算成本。
主要功能
- 01生成 Modal 启动脚本
- 02支持训练、推理与评估
- 03提供批处理和多 GPU 模式
- 04运行前估算成本
- 05从卷或函数输出收集结果
- 06监控应用和日志
适用场景
- 01无需管理 SSH 实例即可运行 GPU 实验
- 02部署持久运行的推理 API
- 03通过并行 GPU 函数处理数据集
- 04进行微调或多 GPU 训练
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wanshuiyin/ auto-claude-code-research-in-sleep --skill serverless-modal安装后在 Claude Code 中输入/serverless-modal确认已加载。
详细介绍
Modal Cloud GPU — 训练与推理
任务:$ARGUMENTS
概述
Modal 是一个无服务器 GPU 云平台。相比基于 SSH 的平台(vast.ai、远程服务),它的主要优势包括:
- 零配置:无需 SSH、Docker 或端口转发。编写 Python →
modal run→ 完成。 - 自动缩容至零:代码运行结束的瞬间即停止计费。没有闲置实例。
- 本地优先:在笔记本电脑上运行
modal run。代码、数据和结果都保留在本地;只有 GPU 函数在远程运行。 - 环境可复现:依赖项通过代码中的
modal.Image声明,而不是系统级软件包。 将modal.Image链视为../shared-references/compute-env-contract.md中声明式环境规范的渲染形式—— 规范字段相同(基础镜像、按顺序排列的 pip 阶段、 环境变量、烟雾测试探针),.aris/compute/modal.md中的env:<name>@<specHash>账本条目也相同, 长时间运行前的三级验证也相同。
适用场景:没有本地 GPU、需要调试 CUDA 代码、运行小规模测试或快速迭代实验的用户。$5 免费额度(无需银行卡)足以调试代码;$30 免费额度(需绑定银行卡)足以完成大多数小规模实验运行。
权衡:Modal 的每 GPU 小时费用高于某些 GPU 档位下的 vast.ai 或 Lightning,但无需花时间配置,也不会为闲置资源付费,因此短期或中期工作负载通常更便宜。对于长时间训练(>4 小时),可考虑使用 vast.ai,以降低每小时费用。
身份验证
pip install modal
modal setup # Opens browser login, writes token to ~/.modal.toml
# Verify:
modal run -q 'print("ok")'
- 注册:https://modal.com(使用 GitHub/Google 登录)
- 免费(无需银行卡):$5/月——足以进行快速测试
- 免费(需绑定银行卡):$30/月——在 https://modal.com/settings 绑定付款方式,即可享受完整免费额度。设置工作区消费限额,避免意外超额扣费(Settings → Usage → Spending Limit)
- 学术机构:申请 $10k 额度 | 初创公司:申请 $25k 额度
- 密钥:
modal secret create huggingface-secret HF_TOKEN=hf_xxxxx
推荐设置:绑定银行卡以解锁每月 $30 额度,然后立即设置消费限额(例如 $30),确保不会超出免费额度。达到限额时,Modal 会暂停你的工作负载。
安全警告:务必直接在浏览器中访问 https://modal.com/settings,绑定银行卡并设置消费限额。切勿通过 Claude Code 或任何命令行工具输入付款信息、银行卡号或账单详情。只有 Modal 官方网站可安全地用于付款操作。
定价(来源:modal.com/pricing,按秒计费)
| GPU | $/秒 | ≈$/小时 | 显存 | 带宽 GB/s | 免费额度 → 时长 |
|---|---|---|---|---|---|
| T4 | $0.000164 | $0.59 | 16GB | 300 | ~8.5 小时($5)/ 50.8 小时($30) |
| L4 | $0.000222 | $0.80 | 24GB | 300 | ~6.3 小时 / 37.5 小时 |
| A10 | $0.000306 | $1.10 | 24GB | 600 | ~4.5 小时 / 27.3 小时 |
| L40S | $0.000542 | $1.95 | 48GB | 864 | ~2.6 小时 / 15.4 小时 |
| A100-40GB | $0.000583 | $2.10 | 40GB | 1555 | ~2.4 小时 / 14.3 小时 |
| A100-80GB | $0.000694 | $2.50 | 80GB | 2039 | ~2.0 小时 / 12.0 小时 |
| H100 | $0.001097 | $3.95 | 80GB | 3352 | ~1.3 小时 / 7.6 小时 |
| H200 | $0.001261 | $4.54 | 141GB | 4800 | ~1.1 小时 / 6.6 小时 |
| B200 | $0.001736 | $6.25 | 192GB | 8000 | ~0.8 小时 / 4.8 小时 |
CPU:$0.047/核心/小时 | RAM:$0.008/GiB/小时(GPU 通常占总成本的 90% 以上)
!! 必须估算成本 !!
每次运行前,必须估算成本并向用户展示,获得确认后再运行。
要点:
- 推理瓶颈在于内存带宽,而非计算能力 → 高带宽 GPU 通常总体成本更低
- 7-8B BF16 推理需要约 22GB 显存(权重 15G + KV 缓存 1G + 开销),T4(16GB)显存不足
- 用于基准测试时,H100 通常比 L4 更便宜(速度快 11 倍,但只贵 5 倍)
成本估算模板(每次运行前必须使用)
Cost estimate (Modal):
Model: [name] ([params], [precision])
VRAM: ~[X]GB (weights + KV cache + overhead)
GPU: [type] ([VRAM]GB, $[X]/sec = $[X]/hr, bandwidth [X] GB/s)
Estimate: ~[N] min, ~$[X]
7-8B BF16 基准测试成本对比
| GPU | 速度 tok/s | $/小时 | 1000 个样本 x 200tok 的成本 | 时长 |
|---|---|---|---|---|
| H100 | 224 | $3.95 | $0.98 | 15 分钟 |
| A100-40GB | 104 | $2.10 | $1.12 | 32 分钟 |
| L4 | 20 | $0.80 | $2.22 | 167 分钟 |
工作流
步骤 1:分析任务 → 估算成本 → 选择 GPU
与任何 GPU 技能的分析方式相同——根据模型大小确定显存需求,选择 GPU,估算时长并计算成本。请参阅上方的定价表。
显存经验法则:
| 模型大小 | FP16 显存 | 推荐 GPU |
|---|---|---|
| ≤3B | ~8GB | T4、L4 |
| 7-8B | ~22GB | L4、A10、A100-40GB |
| 13B | ~30GB | L40S、A100-40GB |
| 30B | ~65GB | A100-80GB、H100 |
| 70B | ~140GB | H100:2、H200 |
步骤 2:生成 Modal 启动器
根据任务类型生成合适的启动脚本。
模式 A:一次性 GPU 函数(训练、评估、基准测试)
这是 run-experiment 集成中最常见的模式。将现有训练脚本封装起来:
import modal
app = modal.App("experiment-name")
# One .pip_install() call per SPEC PHASE (chained calls install in order, so a
# pinned torch in the first call can't be dragged by packages in the second —
# the rendered form of compute-env-contract.md's ordered pip_phases):
image = (
modal.Image.debian_slim(python_version="3.11")
.pip_install("torch") # phase 1: pins
.pip_install("transformers", "accelerate", "datasets", "wandb") # phase 2
)
# Mount local project code into the container
local_code = modal.Mount.from_local_dir(".", remote_path="/workspace")
# Persistent volume for checkpoints and results
volume = modal.Volume.from_name("experiment-results", create_if_missing=True)
@app.function(
image=image,
gpu="A100-80GB", # Chosen based on Step 1 analysis
mounts=[local_code],
volumes={"/results": volume},
timeout=3600 * 6, # 6 hours max
secrets=[modal.Secret.from_name("wandb-secret")], # Optional
)
def train():
import subprocess
subprocess.run(
["python", "train.py", "--output_dir", "/results/run_001"],
cwd="/workspace",
check=True,
)
volume.commit() # Persist results to volume
@app.local_entrypoint()
def main():
train.remote()
print("Training complete. Results saved to Modal volume 'experiment-results'.")
运行:modal run launcher.py
模式 B:Web API(持久推理服务)
import modal
app = modal.App("inference-api")
image = (
modal.Image.debian_slim(python_version="3.11")
.pip_install("torch") # phase 1: pins
.pip_install("transformers", "accelerate") # phase 2
)
@app.cls(image=image, gpu="L40S")
@modal.concurrent(max_inputs=10)
class InferenceAPI:
@modal.enter()
def load_model(self):
from transformers import AutoModelForCausalLM, AutoTokenizer
self.tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B")
self.model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.2-1B", device_map="auto"
)
@modal.fastapi_endpoint(method="POST")
def generate(self, request: dict):
inputs = self.tokenizer(request.get("prompt", ""), return_tensors="pt").to("cuda")
outputs = self.model.generate(**inputs, max_new_tokens=256)
return {"text": self.tokenizer.decode(outputs[0], skip_special_tokens=True)}
部署:modal deploy app.py
模式 C:vLLM 高性能推理
import modal, subprocess
app = modal.App("vllm-server")
image = modal.Image.debian_slim(python_version="3.11").pip_install("vllm")
VOLUME = modal.Volume.from_name("model-cache", create_if_missing=True)
MODEL = "Qwen/Qwen3-4B"
@app.function(image=image, gpu="H100", volumes={"/models": VOLUME}, timeout=3600)
@modal.concurrent(max_inputs=100)
@modal.web_server(port=8000)
def serve():
subprocess.Popen(["python", "-m", "vllm.entrypoints.openai.api_server",
"--model", MODEL, "--download-dir", "/models", "--port", "8000"])
模式 D:批量并行(遍历数据集)
@app.function(image=image, gpu="T4", timeout=600)
def process_item(item: dict) -> dict:
# ... process one item ...
return {"result": "processed"}
@app.local_entrypoint()
def main():
results = list(process_item.map([{"id": i} for i in range(1000)]))
模式 E:LoRA 微调
@app.function(
image=image, gpu="A100-80GB", volumes={"/output": volume},
timeout=3600 * 6, secrets=[modal.Secret.from_name("huggingface-secret")],
)
def train():
# ... transformers + peft + trl training code ...
trainer.save_model("/output/final")
volume.commit()
模式 F:多 GPU 分布式训练
@app.function(image=image, gpu="H100:4", volumes={"/output": volume}, timeout=3600 * 12)
def train_distributed():
import subprocess
subprocess.run(["accelerate", "launch", "--num_processes", "4",
"--mixed_precision", "bf16", "train.py"], check=True)
步骤 3:运行
modal run launcher.py # One-shot execution (most common for experiments)
modal deploy app.py # Persistent service deployment
步骤 4:验证并监控
modal app list # List running apps
modal app logs <app-name> # Stream logs
步骤 5:收集结果
结果收集方式取决于所用模式:
基于 Volume(推荐用于训练):
# Download results from volume after run completes
# Option A: In the launcher script, copy results to local mount before exit
# Option B: Use modal volume commands
modal volume ls experiment-results
modal volume get experiment-results /run_001/results.json ./results/
基于标准输出/返回值(用于评估/基准测试): 结果会打印到终端或由函数返回——已经位于本地。
步骤 6:清理
Modal 会自动缩容至零——无需手动销毁实例。但请清理未使用的资源:
modal app stop <app-name> # Stop a deployed service
modal volume rm <volume-name> # Delete a volume when done
CLI 参考
modal run app.py # Run once
modal deploy app.py # Deploy persistent service
modal app logs <app> # View logs
modal app list # List apps
modal app stop <app> # Stop
modal volume ls # List volumes
modal volume get <vol> <remote> <local> # Download from volume
modal secret create NAME KEY=VALUE # Create secret
关键提示
- GPU 回退:
gpu=["H100", "A100-80GB", "L40S"]— Modal 会按顺序尝试 - 多 GPU:
gpu="H100:4"(最多 8 个 GPU,成本线性增长) - Volume:使用
modal.Volume.from_name("x", create_if_missing=True)进行持久化存储 @modal.enter()每个容器只加载一次模型 | 使用@modal.concurrent()处理并发请求- 长时间训练:设置
timeout=3600 * N(默认值为 5 分钟) - 本地代码:
modal.Mount.from_local_dir(".", remote_path="/workspace") - W&B 集成:
secrets=[modal.Secret.from_name("wandb-secret")]+ 在脚本中调用wandb.init()
与其他技能组合
/run-experiment "train model" <- detects gpu: modal, calls /serverless-modal
-> /serverless-modal <- analyzes task, generates launcher, runs
-> Results returned locally or to Modal Volume
-> No destroy step needed (auto scale-to-zero)
/serverless-modal <- standalone: any Modal GPU workload
/serverless-modal "deploy vLLM" <- inference service deployment
CLAUDE.md 示例
## Modal
- gpu: modal # tells run-experiment to use Modal serverless
- modal_gpu: A100-80GB # optional: override GPU selection (default: auto-select)
- modal_timeout: 21600 # optional: max seconds (default: 6 hours)
- modal_volume: my-results # optional: named volume for results persistence
无需 SSH 密钥、Docker 镜像或实例管理。只需执行 pip install modal && modal setup。
成本保护:完成
modal setup后,在浏览器中访问 https://modal.com/settings(切勿通过 CLI 操作)→ 绑定付款方式以解锁每月 $30 的免费额度(不绑定银行卡:每月仅 $5)。然后将工作区支出上限设为等同于免费额度的金额——达到上限时,Modal 会自动暂停工作负载,避免产生意外费用。