TensorZero 实战:使用 Modal + vLLM 一键云端部署 OpenAI gpt-oss 开源推理模型
【免费下载链接】tensorzeroTensorZero is an open-source LLMOps platform that unifies an LLM gateway, observability, evaluation, optimization, and experimentation.项目地址: https://gitcode.com/GitHub_Trending/te/tensorzero
本篇技术指南围绕 TensorZero 仓库中的 vLLM + Modal 部署 gpt-oss 示例 展开,讲解如何把 OpenAI 开源的 gpt-oss-20B / 120B 推理模型以单张 H100 GPU 的规模部署为 HTTP 推理服务。你将掌握:gpt-oss 的核心技术特性(MXFP4 量化、Attention Sinks、Harmony 响应格式)、基于 Modal 的自定义容器镜像构建、模型权重与 vLLM 编译产物的卷缓存策略,以及vllm serve关键启动参数(CUDA Graph 捕获、Tensor Parallel、FAST_BOOT 权衡)的配置方法。该示例是 TensorZero 在 E2E 测试环境中为推理模型提供持久、安全 HTTP 端点的一类部署方案,与仓库中 SGLang 部署示例 等并列存在。
部署入口:一条命令完成全部编排
仓库中该示例的入口文档 README.md 仅一行命令:
uv run modal deploy vllm_gpt_oss.py但这条命令背后是一份高度文档化的完整部署脚本(152 行),它囊括了从模型选择、容器镜像定制、权重缓存到 vLLM 引擎启动参数的完整链路。uv run通过项目内的 pyproject.toml 一类的依赖声明解析 Modal Python SDK;modal deploy则将脚本中定义的 Modal App(此处名为vllm-gpt-oss-20b)及其关联的镜像、卷、GPU 函数发布为常驻云端服务。
说明:该脚本头部带有
pytest: false标记,表明它不会被 pytest 当作测试收集,而是作为部署 fixture 使用——这与仓库中 vllm-inference-qwen-modal、sgl-modal 等部署示例的模式一致(它们的 README 也均只保留一行modal deploy命令)。
gpt-oss 模型背景:为什么要关注这三个特性
脚本注释首先交代了部署对象的技术背景。gpt-oss 是 OpenAI 开源的推理(reasoning)模型,提供gpt-oss-120B与gpt-oss-20B两种规模,两者均为**混合专家(Mixture of Experts, MoE)**架构:总参数量大,但单次推理实际激活的参数量少,从而在保留世界知识与能力的同时获得更快的推理速度。部署该模型需要理解以下三个关键特性:
MXFP4:MoE 层的 4-bit 块量化
gpt-oss 在 MoE 层使用了一种较少见的 4-bitmxfp4浮点格式(MX 格式家族,源于arXiv:2310.10537)。这是一种"块量化"格式:将e2m1浮点数与按块的缩放因子(blockwise scaling factors)结合,在压缩权重大小的同时尽量保留精度;注意 attention 运算不做量化。因此部署镜像必须使用支持 MXFP4 的 vLLM 特供版本(见下文vllm==0.10.1+gptoss)。
Attention Sinks:长上下文的"注意力水槽"
Attention sink 机制允许模型在不牺牲输出质量的前提下支持更长上下文。vLLM 团队为此提前为Flash Attention 3(FA3)加入了 attention sink 支持。这意味着要发挥 gpt-oss 的长上下文能力,依赖链上需要配套的 vLLM 预发布版本与夜间版 PyTorch(用于 Triton 支持),这正是镜像定制环节的由来。
Harmony 响应格式:多通道输出
gpt-oss 使用 OpenAI 的harmony 响应格式训练,使模型能够通过多个通道输出:思维链(chain-of-thought, CoT)通道、工具调用前导(input tool-calling preamble)通道以及常规文本响应通道。示例脚本为简洁起见采用更简单的格式(未显式启用 harmony 多通道),但如果你需要完整的 CoT 与工具调用能力,可以参考 OpenAI 官方 cookbook 中关于 harmony 格式的说明。
构建自定义容器镜像:特供 vLLM + 夜间 PyTorch
脚本用 Modal 的ImageAPI 定义了一个自定义运行环境,这是整套部署的基础:
import modal vllm_image = ( modal.Image.from_registry( "nvidia/cuda:12.8.1-devel-ubuntu22.04", add_python="3.12", ) .entrypoint([]) .uv_pip_install( "vllm==0.10.1+gptoss", "huggingface_hub[hf_transfer]==0.34", pre=True, extra_options="--extra-index-url https://wheels.vllm.ai/gpt-oss/ --extra-index-url https://download.pytorch.org/whl/nightly/cu128 --index-strategy unsafe-best-match", ) )几个要点逐条拆解:
- 基础镜像:
nvidia/cuda:12.8.1-devel-ubuntu22.04是 CUDA 12.8 的开发版镜像,包含编译/运行 vLLM 内核所需的头文件与工具链;add_python="3.12"让 Modal 在镜像上安装 Python 3.12 运行时。 .entrypoint([]):清空基础镜像自带的入口命令,避免与后续启动流程冲突。- 依赖安装:核心是
vllm==0.10.1+gptoss这个特供预发布版本,它专门为 gpt-oss 的 MXFP4 与 attention sink 需求而构建;huggingface_hub[hf_transfer]==0.34启用 HF 的高速传输后端以加速权重下载。 pre=True:声明安装的是预发布版本。extra_options:追加 pip 源配置——--extra-index-url https://wheels.vllm.ai/gpt-oss/指向 gpt-oss 专用 wheel 仓库,https://download.pytorch.org/whl/nightly/cu128指向 CUDA 12.8 的 PyTorch 夜间构建(提供 Triton 支持),--index-strategy unsafe-best-match允许 pip 在多个源之间按版本匹配选择。
这段配置说明了一个重要事实:gpt-oss 的部署不是拿通用 vLLM 即可完成,必须使用配套版本,否则 MXFP4 权重无法被正确加载。
模型选择与权重缓存:Volumes 避免重复下载
模型与版本固定
脚本默认下载 Hugging Face 上的 20B 模型,并固定 revision以保证可复现性:
MODEL_NAME = "openai/gpt-oss-20b" MODEL_REVISION = "f47b95650b3ce7836072fb6457b362a795993484"若显存充裕,可切换到openai/gpt-oss-120b(H100/H200 单卡即可容纳),同时把 revision 换成对应的提交哈希。
两级缓存卷
vLLM 虽然支持按需从 Hugging Face 拉取权重,但如果每次容器冷启动都重新下载,既慢又费流量。脚本用两个Modal Volume(共享磁盘)做持久化缓存:
hf_cache_vol = modal.Volume.from_name("huggingface-cache", create_if_missing=True) vllm_cache_vol = modal.Volume.from_name("vllm-cache", create_if_missing=True)huggingface-cache:挂载到/root/.cache/huggingface,缓存模型权重;vllm-cache:挂载到/root/.cache/vllm,缓存 vLLM 在首次运行时生成的编译产物。
为什么要缓存后者?vLLM 引擎启动时会做若干编译工作(含 CUDA Graph 捕获),这些产物在全新机器上首次生成耗时很长;持久化后,后续扩容或缩容到零再拉起时即可直接复用,显著缩短冷启动时间。
启动性能权衡:FAST_BOOT 与 CUDA Graph
vLLM 的编译配置直接影响"启动延迟 vs 推理性能"这对矛盾。脚本抽象出一个高层开关:
FAST_BOOT = False # slower boots but faster inference并据此推导 CUDA Graph 捕获尺寸列表:
MAX_INPUTS = 32 # how many requests can one replica handle? tune carefully! CUDA_GRAPH_CAPTURE_SIZES = [ # 1, 2, 4, ... MAX_INPUTS 1 << i for i in range((MAX_INPUTS).bit_length()) ]- CUDA Graph:把多次内核启动"录制"为一张图,运行时一次性回放,从而大幅降低 CPU 调度开销。
MAX_INPUTS:单个副本(replica)能同时处理的请求数,直接决定并发上限,脚本注释特别提醒"谨慎调节"。列表按 2 的幂展开(1、2、4、…、32),即针对不同批次规模预生成捕获图,避免未来出现新尺寸时再触发 JIT 捕获的额外延迟。
FAST_BOOT决定引擎以哪种模式启动:
FAST_BOOT = True:追加--enforce-eager,同时禁用 Torch 编译与 CUDA Graph 捕获,启动最快、吞吐最弱,适合快速验证;FAST_BOOT = False:追加--no-enforce-eager与-O.cudagraph_capture_sizes=[1, 2, 4, 8, 16, 32],保留编译与图捕获,推理性能最佳。
定义并启动 vLLM 服务:Modal App 的完整配置
最终的服务函数把以上所有要素组装起来:
app = modal.App("vllm-gpt-oss-20b") N_GPU = 1 MINUTES = 60 # seconds VLLM_PORT = 8000 @app.function( image=vllm_image, gpu=f"H100:{N_GPU}", scaledown_window=5 * MINUTES, # how long should we stay up with no requests? timeout=5 * MINUTES, # how long should we wait for container start? volumes={ "/root/.cache/huggingface": hf_cache_vol, "/root/.cache/vllm": vllm_cache_vol, }, ) @modal.concurrent(max_inputs=MAX_INPUTS) @modal.web_server(port=VLLM_PORT, startup_timeout=5 * MINUTES, requires_proxy_auth=True) def serve(): import subprocess cmd = [ "vllm", "serve", "--uvicorn-log-level=info", MODEL_NAME, "--revision", MODEL_REVISION, "--served-model-name", MODEL_NAME, "llm", "--host", "0.0.0.0", "--port", str(VLLM_PORT), ] # enforce-eager disables both Torch compilation and CUDA graph capture # default is no-enforce-eager. see the --compilation-config flag for tighter control cmd += ["--enforce-eager" if FAST_BOOT else "--no-enforce-eager"] if not FAST_BOOT: # CUDA graph capture is only used with `--enforce-eager` cmd += ["-O.cudagraph_capture_sizes=" + str(CUDA_GRAPH_CAPTURE_SIZES).replace(" ", "")] # assume multiple GPUs are for splitting up large matrix multiplications cmd += ["--tensor-parallel-size", str(N_GPU)] print(cmd) subprocess.Popen(" ".join(cmd), shell=True)各装饰器与参数的含义:
| 配置项 | 值 | 作用 |
|---|---|---|
gpu | H100:1 | 使用 1 张 H100(120B 也可用 H200 单卡);脚本注释说明多卡按"切分大矩阵乘法"的 Tensor Parallel 思路使用 |
scaledown_window | 5 分钟 | 无请求时保持存活的时间,之后 Modal 自动缩容到零以节省成本 |
timeout | 5 分钟 | 等待容器启动的最长时间(含镜像拉取、vLLM 编译) |
volumes | HF 缓存 + vLLM 缓存 | 挂载前述两个缓存卷 |
@modal.concurrent | max_inputs=32 | 单个副本最多并发处理 32 个请求,与 CUDA Graph 捕获尺寸上限保持一致 |
@modal.web_server | 端口 8000,requires_proxy_auth=True | 暴露为 HTTP Web 服务,并要求代理认证——即外部访问需携带有效凭据,这也是 TensorZero 用它充当安全测试后端的关键 |
vllm serve的启动参数同样值得留意:
--revision:锁定 Hugging Face 权重提交哈希,保证行为可复现;--served-model-name:对外暴露的模型名,TensorZero 等客户端用这个名字请求;--host 0.0.0.0 --port 8000:监听所有网卡、固定端口;--tensor-parallel-size 1:单卡场景下值为 1;若改为多卡,vLLM 会把大矩阵乘法切分到多张 GPU 上;-O.cudagraph_capture_sizes:以紧凑形式(无空格)传入捕获尺寸列表。
脚本末尾用subprocess.Popen(..., shell=True)把拼接好的命令行异步拉起,vLLM 随即在 8000 端口提供兼容 OpenAI 的推理 API。启动日志(--uvicorn-log-level=info)会打印实际执行的完整命令,便于排查问题。
从部署到接入:在 TensorZero 中消费该端点
该部署示例在仓库中的定位,是为推理模型提供持久、安全(需 Bearer 认证)、OpenAI 兼容的 HTTP 端点,供 E2E 测试等场景使用。同类模式还出现在 SGLang 部署示例 以及基于 NGINX 做 Bearer Token 认证的 sgl-nginx 与 tgi-nginx 镜像中——后两者通过docker run以环境变量BEARER_TOKEN注入密钥、并把模型路径/ID 作为命令行参数传入,思路与 Modal 方案互补。
部署完成后,该端点即成为符合 OpenAI 协议的服务,可按 TensorZero 配置文档的方式,把base_url指向 Modal 生成的 Web Server 地址并配置对应认证信息,从而让 gpt-oss 以自托管模型的身份参与推理、评估与优化链路。
小结与排障建议
- 版本绑定:gpt-oss 必须配合
vllm==0.10.1+gptoss与 CUDA 12.8 夜间 PyTorch,混用通用 vLLM 会导致 MXFP4 权重加载失败; - 冷启动 vs 性能:首次部署建议
FAST_BOOT = False以获得最佳吞吐;若只是验证链路,可临时设True加速启动; - 缓存复用:保持
huggingface-cache、vllm-cache两个卷存在并挂载,可显著缩短后续扩缩容时的启动时间; - 并发与捕获尺寸:调整
MAX_INPUTS时,CUDA Graph 捕获列表会自动按 2 的幂扩展,二者需与@modal.concurrent(max_inputs=...)保持一致; - 安全访问:
requires_proxy_auth=True意味着访问需经 Modal 代理认证,接入 TensorZero 时需在客户端侧配置相应凭据。
通过这份示例,你可以把 OpenAI 的 20B/120B 开源推理模型以接近零运维的方式部署为可复现、可弹性伸缩的推理服务,并作为 TensorZero 的自托管模型后端投入生产。
【免费下载链接】tensorzeroTensorZero is an open-source LLMOps platform that unifies an LLM gateway, observability, evaluation, optimization, and experimentation.项目地址: https://gitcode.com/GitHub_Trending/te/tensorzero
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考