在4张 A800上跑DeepSeek-V4-Flash-Vision系列总览
在一块没有 FP8 / FP4 Tensor Core的硬件上,把一个按 FP8 / FP4 打包的多模态大模型跑了起来,并让它稳定提供 512K 上下文、单请求 96 张图、单流 ~220 tok/s 的服务。
文章目录
- 在4张 A800上跑DeepSeek-V4-Flash-Vision系列总览
- 开源项目地址
- 一 背景
- 二 最终结果
- 三 系列地图
- 第一编 · 适配与部署(01–04)
- 第二编 · 排查方法论(05)
- 第三编 · 修复(06–10)
- 第四编 性能与复盘(11–12)
- 四 阅读推荐
- 五 贯穿全系列的七条经验
开源项目地址
- gitee开源仓库地址:dsv4-vision-exp-sglang-sm80
一 背景
三个角色:
| 角色 | 是什么 |
|---|---|
| 模型 | DeepSeek-V4-Flash-Vision-Exp——DeepseekV4ForCausalLM,43 层,带 ViT / Aligner 视觉张量 |
| 引擎 | SGLang v0.5.16(commitfdebc938),官方镜像lmsysorg/sglang:v0.5.16-cu130 |
| 硬件 | 4 × A800 80GB,SM80(Ampere,compute capability 8.0),TP=4 |
这三者本来是不成立的:
- 权重里普通线性层是fp8 e4m3 block 量化,MoE experts 是packed MXFP4;
- A800 只有 BF16 / INT8 Tensor Core,没有 FP8 / FP4;
- SGLang 原生的 MXFP4 路径明确拒绝 SM80。
所以结论只有一条:官方镜像直接docker run是起不来的。要么换硬件,要么把整条数据路径重新规划一遍。我们选了后者,并且额外做了第二件事:把官方还没进 v0.5.16 的视觉支持回移植进来。
二 最终结果
| 维度 | 实测结果 |
|---|---|
| 单流解码 | DSPARK 投机解码稳态~220 tok/s(三次复测 217.8 / 219.6 / 220.3);非投机 ~57 tok/s |
| 上下文 | 512K(524288),KV 池容量871,680 token |
| 图片 | 单请求最多96 张;TTFT 亚线性增长:8 张 0.41s → 48 张 4.21s → 96 张 5.32s |
| 并发 | DSPARK 32 并发 0 失败;切非投机可到 192 并发、聚合 ~936 tok/s |
| 显存 | 稳态平线~56.5 / 80 GiB,运行时余量 ~23.5 GiB(这是设计使然,见第 04 篇) |
| 启动 | 4m55s ~ 19m17s,两段独立瓶颈之和(权重读取 + JIT),不是"机器时快时慢" |
| 协议 | OpenAI Chat Completions / OpenAI Responses / Anthropic Messages 三端兼容 |
| 稳定性 | 长跑RestartCount=0,无 crash |
三 系列地图
第一编 · 适配与部署(01–04)
- 把"跑不起来"变成"跑得稳",讲的是方案成立的理由和可复现的落地方式。
| # | 篇目 | 一句话 |
|---|---|---|
| 01 | 适配:当 A800 遇上按 FP8/FP4 打包的权重 | 不是配置错了,是官方数据路径里没有一条能在 SM80 上执行的算子链 |
| 02 | 构建:把补丁叠成可复现的一层 | 官方镜像一份不动,四类改动叠成自包含补丁镜像,再用闸门防漂移 |
| 03 | 部署:启动链路、两种形态与运维 | 起停只需三条命令;就绪的唯一判据是日志 +/v1/models |
| 04 | 容量:512K 上下文与那本显存账 | 显存平线是 KV 池启动即全额预分配的结果,871,680 是怎么算出来的 |
第二编 · 排查方法论(05)
| # | 篇目 | 一句话 |
|---|---|---|
| 05 | 排查:36 个坑的六种形状 | 大多数问题不是"参数写错了",而是版本 / 硬件 / 协议 / 测量口径的错配 |
第三编 · 修复(06–10)
- 每一篇都是同一套结构:症状 → 为什么难查 → 根因在哪一层 → 怎么修 → 数字验证。
| # | 篇目 | 覆盖的问题 |
|---|---|---|
| 06 | 网关与 Responses 协议层 | 浮点created_at丢终态事件、pydantic 懒迭代器致多轮 400、Anthropic 通道 401 |
| 07 | 工具调用与结构化输出 | 历史渲染污染导致参数嵌套、流式丢参、DSPARK 不支持 grammar |
| 08 | 思考等级与推理内容 | 思考被塞进正文、reasoning_tokens恒 0、7 档 effort 的三端映射 |
| 09 | 视觉链路:从能读图到崩不了 | 融合 off-by-one 整机重启、图片数上限、占位符特殊 token 致全会话 400 |
| 10 | 稳定:空回答、推理重复与"字符损坏" | high档推理逐字重复烧完预算、0.35% 采样抖动被误判为管线损坏 |
第四编 性能与复盘(11–12)
| # | 篇目 | 一句话 |
|---|---|---|
| 11 | 性能:数字是怎么测出来的 | 同一套服务,口径不同能从 ~100 变成 ~220 tok/s;单流快与高聚合不可兼得 |
| 12 | 复盘:这套栈教给我的十条经验 | 把散落在各篇的教训收拢成可迁移的判断规则 |
四 阅读推荐
| 目标 | 建议 |
|---|---|
| 理解这套方案为什么成立 | 01 → 02 → 09 → 07 |
| 要把它部署起来 | 01 → 02 → 03 → 04 |
| 线上出问题,快速定位 | 05(症状对照表)→ 对应的修复篇(06–10) |
| 只关心容量与速度 | 04 → 11 |
| 只想接入调用 | 12 |
五 贯穿全系列的七条经验
- 硬拒绝 vs 善后:默认实现偏爱
raise(占位符多了就崩、文本里有特殊 token 就 400)。真正消除故障的往往是"补齐 / 转义"这类带不变式的善后逻辑。 - 历史回放会投毒:工具结果和模型自己的回显被逐字回放进下一轮请求,于是编码器的一个小错会被模型学走、自我强化,"单轮正常、带历史必翻车"是这个模式的指纹。
- 协议层和引擎层必须分开归因:先证明引擎直连是好的,再动网关。
- 口径决定结论:同一套服务,测量口径不同能得出完全相反的结论(~100 vs ~220 tok/s;显存"平线"其实是预分配)。
- 同名不等于同实现:不同引擎里都叫 “DSPARK” 的东西是两套代码,跨引擎类比前先验证。
- 复现性靠闸门:靠人记住"改了哪儿"是不可靠的,要靠可执行的逐字节校验。
- "没坏"也是一种结论:显存平线、档位坍缩、思考模式下采样参数无效。先排除"符合预期",再列真故障。