跳到主内容
快讯直播
AI智模界
教程

用 Modal 部署按需伸缩的开源模型推理 API

适用场景

手里有一个开源模型(例如 Llama、Qwen、Mistral 系列),想把它变成 HTTP API 给内部系统或产品调用;流量不稳定,白天高峰、夜里几乎没人用,不想为此长期养一台 GPU 服务器;团队没有 Kubernetes 运维精力,希望把镜像构建、GPU 调度、缩容到零都交给平台。Modal 的定位就是这类 Serverless GPU 场景:用 Python 装饰器定义函数,平台负责按请求拉起容器、跑完再回收。

环境与前置条件

  • 操作系统:macOS、Linux,或 Windows + WSL2。
  • Python:3.10 及以上,具体以 Modal 官方文档当前版本要求为准。
  • 本地工具:pip、git、一个终端。本地不需要 GPU。
  • 账号:Modal 账号,首次使用会生成 token 并写入本地配置文件。
  • 磁盘:本地只需放代码,模型权重存在 Modal Volume 里,不需要下载到本机。
  • 网络:容器内需要能访问 Hugging Face 拉取权重。若网络受限,可在合规前提下配置镜像源,并注意速率限制。
  • 显存规划:7B 级别模型做 FP16 推理通常需要 16GB 以上显存;量化后可以更低。具体占用和模型结构、序列长度有关,以实测为准。

分步骤部署

步骤 1:安装 Modal CLI 并登录

```bash

python -m venv .venv

source .venv/bin/activate # Windows 用 .venv\Scripts\activate

pip install modal

modal setup

```

modal setup 会引导浏览器授权。成功后本地会生成 ~/.modal.toml。可以用下面命令确认:

```bash

modal profile list

```

能看到当前 profile 即登录成功。

步骤 2:创建项目文件

```bash

mkdir modal-llm-api && cd modal-llm-api

touch app.py bench.py

```

步骤 3:定义镜像与模型缓存 Volume

编辑 app.py:

```python

import modal

app = modal.App("llm-inference-api")

MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct" # 换成你要部署的模型,以模型卡说明为准

hf_cache = modal.Volume.from_name("hf-cache", create_if_missing=True)

image = (

modal.Image.debian_slim(python_version="3.11") # 以官方支持的版本为准

.pip_install(

"vllm",

"fastapi",

"uvicorn",

"huggingface_hub",

)

.env({"HF_HOME": "/root/.cache/huggingface"})

)

```

这段在做什么:debian_slim 是基础镜像;pip_install 在镜像构建阶段执行,把 vLLM 等依赖装进去;Volume 用来持久化 Hugging Face 缓存,模型权重只下载一次,后续冷启动直接挂载,省掉重复下载时间。首次构建会花几分钟,输出里能看到镜像层构建日志。

步骤 4:写 GPU 函数,启动 OpenAI 兼容服务

在 app.py 里追加:

```python

@app.function(

image=image,

gpu="A10G", # GPU 型号以官方当前可用列表为准,也可选 T4、L4、A100 等

volumes={"/root/.cache/huggingface": hf_cache},

timeout=60 * 30,

scaledown_window=60 * 5, # 空闲 5 分钟缩容到零;旧版本参数名为 container_idle_timeout

min_containers=0, # 需要常驻预热可调大;旧版本参数名为 keep_warm

)

@modal.web_server(port=8000, startup_timeout=60 * 20)

def serve():

import subprocess

subprocess.Popen(

[

"python", "-m", "vllm.entrypoints.openai.api_server",

"--model", MODEL_NAME,

"--host", "0.0.0.0",

"--port", "8000",

"--max-model-len", "8192",

]

)

```

这里的关键点:

  • gpu 指定容器用什么卡,按官方当前可用型号选择。
  • volumes 把模型缓存挂进容器。
  • scaledown_window 控制空闲多久回收容器。调大能减少冷启动,但会增加空闲成本。
  • min_containers=0 表示可以缩到零;如果对首包延迟敏感,可以设为 1 保持一个热实例。
  • @modal.web_server 把容器内的 8000 端口暴露成 HTTPS 端点。函数体用 Popen 启动 vLLM,自己不阻塞,平台会探测端口是否就绪。
  • vLLM 自带 /v1/chat/completions、/v1/completions、/v1/models,基本兼容 OpenAI 接口。

如果要用 FastAPI 自己写路由,可以把 @modal.web_server 换成 @modal.fastapi_endpoint(旧版本里叫 web_endpoint,以官方文档当前版本为准),然后在函数里返回 FastAPI app。

步骤 5:本地热重载测试

```bash

modal serve app.py

```

终端会打印一个 https://...modal.run 的临时地址。首次运行会构建镜像、拉取模型,等待时间取决于模型大小和网络。看到地址后,另开一个终端:

```bash

curl -s https://<你的临时端点>/v1/models

```

返回 JSON 里包含模型名称,说明服务已经起来。

步骤 6:部署到生产

```bash

modal deploy app.py

```

部署完成后会输出一个持久端点 URL。这个 URL 不会因为本地终端关闭而失效。

步骤 7:用 OpenAI SDK 调用

```python

from openai import OpenAI

client = OpenAI(

base_url="https://<你的持久端点>/v1",

api_key="unused",

若开启了端点鉴权,按官方文档加请求头

default_headers={"Modal-Key": "...", "Modal-Secret": "..."},

)

resp = client.chat.completions.create(

model="Qwen/Qwen2.5-7B-Instruct",

messages=[{"role": "user", "content": "用三句话解释什么是 Serverless。"}],

max_tokens=128,

)

print(resp.choices[0].message.content)

```

如果能打印出模型回复,整条链路就通了。

步骤 8:调冷启动与并发

冷启动优化可以从几处入手:

  • 模型权重走 Volume 缓存,避免每次重新下载。
  • 镜像依赖尽量稳定,减少重建频率。
  • 适当调大 scaledown_window,让空闲容器多留一会儿。
  • 对延迟敏感的接口,设置 min_containers=1 保持热实例。

并发方面,早期版本用 allow_concurrent_inputs 参数控制单容器并发请求数,新版本提供 @modal.concurrent(max_inputs=N) 装饰器,具体写法以官方文档当前版本为准。vLLM 本身支持AI 词典:连续批处理">连续批处理,单容器并发调高通常能提升 GPU 利用率,但要观察显存和尾延迟。

步骤 9:做一次成本压测

编辑 bench.py:

```python

import time

import modal

import httpx

app = modal.App("llm-bench")

@app.function(timeout=600)

def bench(endpoint: str, n: int = 20):

url = f"{endpoint.rstrip('/')}/v1/chat/completions"

payload = {

"model": "Qwen/Qwen2.5-7B-Instruct",

"messages": [{"role": "user", "content": "写一首四行诗"}],

"max_tokens": 128,

}

with httpx.Client(timeout=120) as client:

t0 = time.time()

for i in range(n):

r = client.post(url, json=payload)

r.raise_for_status()

total = time.time() - t0

print(f"{n} 次请求总耗时 {total:.2f}s,平均 {total / n:.2f}s/次")

@app.local_entrypoint()

def main(endpoint: str, n: int = 20):

bench.remote(endpoint, n)

```

运行:

```bash

modal run bench.py --endpoint https://<你的持久端点> --n 20

```

压测时重点看三个数:首次请求耗时(含冷启动)、稳定后单请求耗时、Modal 控制台里的 GPU 秒数和容器启动次数。如果冷启动占比高,就调整 scaledown_window 或 min_containers;如果吞吐上不去,就提高单容器并发或换更强 GPU。成本没有统一公式,和 GPU 型号、请求量、生成长度都相关,以 Modal 控制台账单页为准。

验证部署是否成功

1. 查看应用状态:

```bash

modal app list

```

能看到应用处于 deployed 状态。

2. 检查模型列表:

```bash

curl -s https://<你的持久端点>/v1/models

```

返回 JSON 中包含配置的模型名称。

3. 发起一次对话请求:

```bash

curl -s https://<你的持久端点>/v1/chat/completions \

-H "Content-Type: application/json" \

-d '{"model":"Qwen/Qwen2.5-7B-Instruct","messages":[{"role":"user","content":"你好"}],"max_tokens":32}'

```

返回 JSON 里有 choices 字段和模型输出。

4. 在 Modal 控制台观察容器数量:无请求时缩到零,有请求时拉起,说明按需伸缩生效。

常见报错与解决

报错:torch.cuda.OutOfMemoryError: CUDA out of memory

原因:模型权重、KV 缓存或 max-model-len 超出当前 GPU 显存。

解决:换更大显存的 GPU,或降低 --max-model-len,或使用量化版本模型。改完参数后重新部署:

```bash

modal deploy app.py

```

报错:modal.exception.NotFoundError: Volume 'hf-cache' not found

原因:Volume 还没创建,或者名字写错。

解决:先创建 Volume,再重新部署:

```bash

modal volume create hf-cache

modal deploy app.py

```

报错:端点返回 502 或 Connection refused

原因:容器还没启动完,startup_timeout 太短,或者 vLLM 进程启动失败。

解决:先看日志定位:

```bash

modal app logs <app-id>

```

如果是启动慢,把 startup_timeout 调大;如果是依赖或参数问题,按日志里的报错修正后重新部署。

报错:ModuleNotFoundError: No module named 'vllm'

原因:镜像里没有装对应依赖,或者镜像缓存是旧版本。

解决:确认 image.pip_install 中包含 vllm,然后重新部署触发镜像重建:

```bash

modal deploy app.py

```

报错:401 Unauthorized

原因:端点开启了鉴权,但请求没带凭证。

解决:按官方文档在请求头里加 Modal-Key 和 Modal-Secret,或者在控制台关闭该端点的代理鉴权。

后续维护

  • 备份:代码和配置放 Git 仓库;模型权重放在 Volume 里,即使丢失也可以重新下载。定期确认 Volume 名称和挂载路径没有改动。
  • 升级:升级模型或依赖时,先在本地用 modal serve app.py 验证,再执行 modal deploy app.py。大版本升级建议换一个新应用名灰度,确认无误再切流量。
  • 日志:用 modal app logs <app-id> 看实时日志。把业务侧请求量、错误率、首包延迟也记下来,方便区分是平台问题还是模型问题。
  • 监控:在 Modal 控制台关注 GPU 利用率、容器启动次数、冷启动比例。冷启动比例高就调整保活策略;利用率长期偏低就考虑缩小 GPU 或合并请求。
  • 成本:设置预算提醒,定期检查 scaledown_window 和 min_containers 是否还符合当前流量。压测数据要定期复跑,流量模式变了,最优参数也会变。
  • 安全:不要把密钥硬编码在代码里,使用 Modal Secrets 注入;对外端点建议开启鉴权,并在网关侧加限流。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。