预计阅读 8 分钟

生产 9:版本管理(SGLang / 模型 / API)

谁该读这一篇? 平台运维 / 上线负责人 —— 处理"换 SGLang 版本怎么平滑"、"模型怎么灰度切换"等问题。 前置阅读: 07-router.md、05-incident-playbook.md。 耗时: 25 分钟 学完能: 1. 区分 SGLang 版本、模型版本、API 版本; 2. 用 router + 双副本灰度切版本; 3. 处理 OpenAI API 兼容协议的版本差异; 4. 写一份发布检查表; 5. 知道权重热更新的几种路径(用于 RL / online learning)。


1. 三个独立的版本维度

维度 频率 影响
SGLang 版本 月度 release 性能 / 修 bug / 偶尔 breaking
模型版本 不定(业务驱动) 输出质量 / 兼容性
API 版本 季度 / 半年 客户端代码兼容

三者独立但要协调。


2. SGLang 版本升级

升级前检查

  • Read release notes(breaking changes / 新特性 / 废弃)。
  • 跑 benchmark 对比新版 vs 旧版。
  • 跑业务 eval 验证模型行为不变。
  • 准备回滚预案。

灰度发布

将旧版和新版作为独立 worker pool,通过 Kubernetes Service、Gateway API 或 sgl-model-gateway 的 worker registry 分阶段切流。当前 router CLI 没有可依赖的 --worker-weights 合同;权重灰度应由流量入口完成,并为两个 pool 分别打上 版本标签以便比较指标。

配置变化

新版常加新参数。配置管理(Helm values / kustomize)要同步更新。


3. 模型版本切换

场景

  • 新训练 checkpoint 发布。
  • 切量化版本(BF16 → FP8)。
  • 换基础模型(Llama-3.1 → Llama-3.3)。

切换方法

方案 A:滚动重启

简单粗暴。逐 Pod 改 --model-path,等加载完再起下一个。 代价:滚动期间容量减半,需 N+1 副本。

方案 B:影子副本

新模型起独立副本组,router 权重灰度切。 代价:双倍 GPU。 优势:随时回滚。

方案 C:热权重更新

模型架构不变(fine-tune),只换权重:

# SGLang Engine API(不重启)
curl -X POST localhost:30000/update_weights_from_disk \
    -H 'Content-Type: application/json' \
    -d '{"model_path": "/models/qwen-7b-v2"}'

weight_updater.py:update_weights_from_disk 实现。


4. RL / Online Learning 场景的权重同步

训练侧每 N step 给推理侧一份新权重。实际请求 schema 由 UpdateWeightsFrom*ReqInput 定义,部署前先查看 /docs 或 io_struct.py,不要把任意 tensor JSON 当作通用接口:

# 训练侧
import torch.distributed as dist
dist.send(weights, dst=inference_rank)

# 推理侧
curl -X POST localhost:30000/update_weights_from_distributed \
    -H 'Content-Type: application/json' \
    -d '{"names":["lm_head.weight"],"dtypes":["float16"],"shapes":[[32000,4096]],"group_name":"weight_update_group"}'

或者用 CUDA IPC(适合同机协同,句柄 schema 由服务端生成):

# 训练侧把 tensor 注册为 IPC handle
handle = tensor.share_memory_().get_handle()
# 推理侧通过 IPC handle 直接读
curl -X POST localhost:30000/update_weights_from_ipc \
    -H 'Content-Type: application/json' -d '{"zmq_handles": {"GPU-...":"tcp://..."}}'

weight_updater.py:update_weights_from_ipc。

详见 srt/weight_sync/。


5. API 版本兼容

SGLang 对外有几套 API:

API 路径 用途
OpenAI 兼容 v1 /v1/chat/completions 主流
原生 /generate DSL / 内部
Embedding /v1/embeddings embedding
Anthropic 兼容 /v1/messages Anthropic SDK
控制 /update_weights* /abort /flush_cache 运维

字段兼容

OpenAI 协议偶尔加字段(如 response_format、reasoning_effort)。 SGLang 跟进相对快,但有时滞后。 客户端用最新 SDK 时记得验证 SGLang 版本是否支持。

弃用字段

不推荐:直接删字段。 推荐:保留字段 + 加 deprecation warning(log + metric)。


6. 灰度发布检查表

Pre-flight:
[ ] Release notes 已读 + 内部 review
[ ] 新版本镜像 build + push 完成
[ ] 业务 eval 通过
[ ] Benchmark 没退化
[ ] 回滚预案准备好
[ ] 监控面板 ready

Phase 1: 5% canary (24 hr)
[ ] 流量入口将新版比例设为 5%(Kubernetes/Gateway/router 配置)
[ ] 监控 canary 副本: TTFT / TPOT / cache hit / abort
[ ] 业务方观察输出质量
[ ] Postmortem 候选: 任何异常都记

Phase 2: 50% (24 hr)
[ ] 流量入口将新版比例设为 50%
[ ] 监控总体 SLO 不退化

Phase 3: 100%
[ ] 流量入口将新版比例设为 100%
[ ] 旧版本副本下线
[ ] 镜像 retention 至少 30 天

Post-release:
[ ] 写 release report
[ ] Update internal docs

7. 回滚

任何阶段发现 SLO 退化:

  1. 立刻在流量入口将比例切回旧版本。
  2. 通知 oncall 监控。
  3. 不立刻删 canary —— 留着诊断。
  4. 写 incident report。

回滚时间目标:< 5 分钟。


8. 配置版本化

把所有部署相关配置存 git:

ops/
├── helm/
│   ├── values-prod.yaml
│   └── values-staging.yaml
├── kubectl/
│   └── deployments/
└── scripts/
    ├── upgrade.sh
    └── rollback.sh

每次变更 git tag,与镜像 tag 一一对应:

git tag deploy-2026-05-29-sglang-0.6
docker push mycompany/sglang:0.6-2026-05-29

9. 模型权重版本管理

模型 checkpoint 也要版本化:

s3://models/qwen-7b/v1.0/         # 初版
s3://models/qwen-7b/v1.1/         # fine-tune 增量
s3://models/qwen-7b/v1.2/         # bug fix

每个版本:

  • 元信息(训练数据、checkpoint hash、训练 loss)。
  • Eval 结果(GSM8K / MMLU / 业务 eval)。
  • 切换记录(哪天切到这版,切回哪版)。

10. 兼容性矩阵

维护一张表:

SGLang 版本 兼容 model 兼容 client SDK 兼容 router
本教程基线 024a7a1031fd7d789076f90e203e3a51b9656cb3(2026-08-27) 以 sglang/python/pyproject.toml 与 lockfile 为准 同一提交下的 experimental/sgl-router / sgl-model-gateway

不要把历史 0.4/0.5/0.6 小版本号当作能力保证。发布前应锁定 SGLang commit/tag、 镜像 digest、模型 revision、router/gateway commit,并用实际端点做兼容性测试:

python -c 'import sglang; print(sglang.__version__)'
curl -fsS http://localhost:30000/health
curl -fsS http://localhost:30000/v1/models
curl -fsS http://localhost:30000/metrics >/dev/null

每次升级前看这张表。


11. 小结

  • 三个独立版本维度:SGLang / 模型 / API。
  • 升级走灰度 + canary,5% → 50% → 100%。
  • 模型可以热权重更新(disk / distributed / tensor / IPC)。
  • 配置 git 化,与镜像 tag 一一对应。
  • 维护兼容性矩阵防交叉冲突。

12. 自检

  1. SGLang 版本升级前必做的 3 件事?
答案 (1) **Read release notes**:看 breaking changes(参数改名 / 删字段 / 行为变化)、新特性、性能 regression(有时新版个别场景慢)。 (2) **Benchmark 对比**:同硬件 / 同模型 / 同 dataset 跑 `bench_serving.py`,至少 TTFT / TPOT / Throughput 三个指标不退化。 (3) **业务 eval**:用业务私有 eval set 跑两版本,输出质量 / 业务指标不退化(特别是受约束输出、tool calling 行为)。 还有第 4 件强烈推荐:(4) **回滚预案准备**:旧版本镜像保留、Helm values 备份、Router weight 切换脚本就绪。出问题时 5 分钟内能回滚。 不做的话:上线发现 regression 时手忙脚乱、决策迟缓、用户感知差。
  1. 热权重更新有 4 种路径,哪个最快?
答案 (1) `update_weights_from_disk`:磁盘读 safetensors → 反序列化 → CPU → GPU。慢(10s-几分钟,看模型大小)。 (2) `update_weights_from_distributed`:训练侧通过 NCCL broadcast 给推理侧。中速(GPU 间 IB / NVLink,几秒)。 (3) `update_weights_from_tensor`:直接传 torch.Tensor 对象(同进程)。快(仅 device-device copy)。 (4) `update_weights_from_ipc`:CUDA IPC handle 共享同 GPU 内存。**最快**(零拷贝,毫秒级,因为不真复制,是引用)。 排序:IPC > tensor > distributed > disk。 RL online learning 场景一般用 IPC(训练 worker 和推理 worker 同节点);多机训练时用 distributed;冷启动 / 模型切换用 disk。
  1. 模型切换为什么推荐影子副本而非滚动重启?
答案 滚动重启:逐 Pod 改 `--model-path` 重启 → 滚动期间容量减半(已下线的 Pod + 新 Pod 还在 capture cuda graph)→ 需要 N+1 副本扛压力。 影子副本:新模型起独立副本组(双倍资源),router 灰度切流量 → 老副本一直能用,验证完一键切换。 影子的优势: (a) **零容量损失**:滚动期间无能力下降。 (b) **随时回滚**:流量切回老副本秒级生效。 (c) **AB test**:可以对比两版本的业务指标。 (d) **金丝雀**:按流量入口能力选择 1%/5% → 50% → 100% 的渐进切换;这些不是 router CLI 参数。 代价:双倍 GPU 几小时 - 几天(视验证周期)。对核心业务这个成本完全可接受。 滚动重启只用于:(a) 资源极紧无法双倍;(b) 测试 / 内部环境。
  1. 客户端 SDK 比 SGLang 新可能出现什么问题?
答案 openai SDK 跟随 OpenAI API 协议演进,会先于 SGLang 加入新字段: (1) **新字段被忽略**:客户端发 `response_format: {type: "json_schema"}`,老 SGLang 不识别,按 free-form gen 处理;输出可能不符合 schema。 (2) **行为差异**:新 SDK 默认开 `stream_options.include_usage`,老 server 不返回 usage 字段,客户端报错。 (3) **404 端点**:客户端调 `/v1/responses`(新 API),server 没注册路由,404。 (4) **类型不匹配**:新 SDK 用 typed dataclass 接收响应,server 返回字段缺失导致 pydantic 校验失败。 解决: - 客户端和服务端一起做 endpoint contract test,并在 lockfile 中固定经过验证的 SDK 版本。 - 维护兼容性矩阵([§10](#10-兼容性矩阵))。 - 升 SGLang 前先在 staging 用最新客户端跑完整 API 测试。
  1. 回滚时为什么不立刻删 canary?
答案 保留用于诊断: (1) **现场保留**:canary 的容器、日志、metric、core dump 都还在;删了就丢了;重现这种 regression 可能要花几天。 (2) **附加测试**:在 canary 上跑特定输入复现问题,不影响生产。 (3) **变量隔离**:canary 是出问题的版本,停接流量但保留,避免"问题不见了"误判已修复。 (4) **postmortem 证据**:写报告时引用 canary 当时的状态。 做法:rollback 时把流量入口切回 stable,但 canary Pod 不删;postmortem 完成、根因明确后再删(通常 1-7 天)。 K8s 配 PDB 防止 canary 被自动清理;标 `app.kubernetes.io/role: canary-retained` 让运维知道。

13. 下一步

上游源码:sglang/python/sglang/srt/model_executor/model_runner_components/weight_updater.py。