Qwen3.8 Flash Next / 4×A100 完整部署记录
这是 2026-08-30 至 2026-08-31 在现有 A100-SXM4 节点上的实际替换与调优记录:哪些参数来自模型实现约束,运行期 OOM 怎么处理,以及 64K/c8 实测为什么最终选择 16384/0.88。
部署对象与边界
| 项 | 实际值 | 说明 |
|---|---|---|
| GPU | 4× A100-SXM4-80GB | 固定 GPU 0、1、2、3 |
| 公开端口 | 30001 | 沿用原服务端口 |
| 模型 | wtdcode/Qwen3.8-Flash-Next-AWQ-W4A16 | compressed-tensors pack-quantized;routed experts INT4 g128,其余 BF16 |
| 镜像 | lazymio/vllm-backport:latest-sm80 | Ampere / A100 专用 backport |
| 镜像 digest | sha256:9f23c3fe…7a345 | 部署时解析到的 manifest,避免只记录浮动 tag |
| 主 model ID | qwen3.8-flash-next | 新调用方使用 |
| 兼容 model ID | qwen3.8-27b-int8-w8a16 | vLLM 直连兼容;11000 网关另保留 qwen3.8-27b |
为什么是 TP4 + EP
这不是单纯为了把模型分到四张卡。该 AWQ checkpoint 的 routed expert intermediate width 为 640,量化 block 为 128×128。只用普通 TP4 会把 expert intermediate 切成每 rank 160,无法满足 FP8 block 对齐,vLLM 会落到不适合 A100 这条量化路径的 kernel。
--enable-expert-parallel 让 experts 保持完整并在 rank 间分布,权重走 Marlin W4A16 路径。因此这里的 EP 是模型布局约束,不是可有可无的性能开关。
为什么需要 PLE CPU offload
Flash Next 含有约 51B 的 n-gram embedding,FP8 后仍约 51 GiB。若由 TP4 分摊到 GPU,每张卡还要增加约 12.8 GiB,直接压缩 KV cache。设置 VLLM_PLE_CPU_OFFLOAD=1 后,这部分由独立 PleOffloadWorker 放在 pinned host RAM,通过 CUDA IPC 参与推理。
代价是主机需要额外约 51 GiB 锁页内存,因此 Compose 同时保留 unlimited memlock。它不是传统的整层 CPU offload,不能据此推导所有模型都能用同样方法省显存。
实际 Compose
下面去掉了内部 registry 域名和宿主绝对路径,其余关键参数与运行配置一致:
services:
qwen3_8_flash_next:
image: lazymio/vllm-backport:latest-sm80
container_name: vllm_qwen3_8_flash_next
restart: unless-stopped
ports:
- "30001:30001"
volumes:
- /path/to/Qwen3.8-Flash-Next-AWQ-W4A16:/model:ro
- /path/to/vllm-cache:/root/.cache
- /path/to/triton-cache:/root/.triton
- ./chat-template.jinja:/opt/chat-templates/froggeric/chat_template.jinja:ro
environment:
VLLM_PLE_CPU_OFFLOAD: "1"
OMP_NUM_THREADS: "1"
VLLM_WORKER_MULTIPROC_METHOD: spawn
cap_add:
- SYS_PTRACE
ipc: host
shm_size: 32g
ulimits:
memlock: -1
stack: 67108864
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["0", "1", "2", "3"]
capabilities: [gpu]
command:
- /model
- --host=0.0.0.0
- --port=30001
- --served-model-name
- qwen3.8-flash-next
- qwen3.8-27b-int8-w8a16
- --tensor-parallel-size=4
- --enable-expert-parallel
- --max-model-len=262144
- --max-num-seqs=8
- --max-num-batched-tokens=16384
- --gpu-memory-utilization=0.88
- --disable-custom-all-reduce
- --speculative-config={"method":"mtp","num_speculative_tokens":3}
- --compilation-config={"mode":0,"cudagraph_mode":"FULL_DECODE_ONLY"}
- --enable-auto-tool-choice
- --tool-call-parser=qwen3_xml
- --reasoning-parser=qwen3
- --enable-prompt-tokens-details
- --enable-prefix-caching
- --mamba-cache-mode=align
- --chat-template=/opt/chat-templates/froggeric/chat_template.jinja
生产使用模型原生 262,144-token context,不再设置静态 YaRN factor 4。默认采样保持 temperature=1.0, top_p=0.95, top_k=20,默认 reasoning effort 为 medium。
调度与显存容量
当前 vLLM 分配的 KV pool 合计 2,955,692 tokens,可容纳约 11.28 条完整 262,144-token context,高于 scheduler 的 8 条上限。max-num-seqs=8 仍只表示同时管理的 sequence 数量;实际总 context 必须落在同一个 KV pool 内。
max-num-batched-tokens=16384 是本轮 64K/c8 扫描选出的调度甜点。它不是单请求最大上下文:长 prefill 仍会被 chunked prefill 分步调度。
MTP、CUDA graph 与 cache
- MTP:
num_speculative_tokens=3,BF16 MTP draft head 保持未量化。 - CUDA graph:先使用上游在 4×A100 验证过的
FULL_DECODE_ONLY。FULL_AND_PIECEWISE需要单独处理 capture sizes 和 NCCL,不在首次替换里混测。 - Prefix cache:已开启,并配合
mamba-cache-mode=align。该 backport 路径与旧 27B nightly 的临时源码 patch 不是同一套部署,不复用旧 patch wrapper。 - LMCache:L1 实验已回滚;生产只保留 vLLM 原生 GPU prefix cache。
LMCache L1 为什么回滚
LMCache L1 曾以 MP connector 接入 30001:800-token chunks、1 TiB host-memory ceiling、没有 L2。用户随后通过兼容 ID qwen3.8-27b 的正常请求观察到重复输出。该 ID 实际仍路由到同一个 Flash Next upstream,因此不能把异常归因于旧模型。
短的新提示词和 8 路并发合成请求没有稳定复现,尚不能证明具体损坏点;但 HTTP 200 和新 prompt 正常都无法排除 hybrid recurrent state 在外部 cache retrieve 时的静默质量错误。生产质量优先,因此先移除 LMCacheMPConnector、retention 参数和 LMCache container,只保留 vLLM 原生 prefix cache。
| 层级 | 当前值 | 用途 |
|---|---|---|
| GPU prefix cache | 保持开启 | 最低延迟的热前缀命中 |
| LMCache L1 | 关闭 | 等待 cache-hit 质量 A/B |
| LMCache L2 | 关闭 | 不使用 NVMe,不产生 disk2 写放大 |
| 主机 available RAM | 约 1.8 TiB | 移除 L1 后实测 |
回滚前还发现 0.5.5.dev45 即使配置 lazy allocation 和 20 GiB initial,也实际向 container charge 约 1017 GiB;模型 ready 后主机 available 仅约 743 GiB。回滚后恢复到约 1.8 TiB。这也是不能只按参数名判断容量的运维证据。
恢复门槛:固定同一长前缀,分别覆盖 cache store、vLLM 重启后 external retrieve、流式与非流式输出,并逐 token 对比无 L1 基线;未通过前不恢复 L1。
启动失败:不是要 privileged
第一次启动在 PleOffloadWorker 报 pidfd_getfd: Operation not permitted。PLE 需要在 sibling processes 间传递 CUDA tensor,Linux 对 pidfd_getfd 做 ptrace 权限检查。最终只增加 SYS_PTRACE capability,同时移除从旧服务继承的 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True。
没有使用 privileged: true。privileged 不会增加 CUDA core、HBM 或 NVLink 带宽,只会扩大容器权限。问题能由单一 capability 解决,就不应把整个宿主设备和安全边界交给容器。
Chat template 与工具调用
服务继续挂载固定的 Froggeric Qwen3.8 template,默认 reasoning_effort=medium,保留 reasoning history。工具 parser 使用 qwen3_xml,当前生产只承诺 XML 增量工具调用;模板内存在 JSON 分支不代表 parser 已支持 XML/JSON 双格式。
11000 网关如何衔接
外部 OpenAI-compatible 网关新增公开 ID qwen3.8-flash-next,同时保留 qwen3.8-27b。两个 public ID 都路由到 30001 的新 upstream ID,因此旧客户端不需要同步切换。兼容 ID 是接口稳定层,不再表示后端仍运行旧 dense checkpoint。
64K / 8 并发性能报告
客户端使用本地 llama-benchy 0.4.0 直连 30001。每条请求输入 65,536 tokens、输出 512 tokens,并发 8,唯一 prompt、--no-cache、--exact-tg,每个参数点测量一次。表内 prefill 与 decode 都是 8 条请求的 aggregate tok/s。
| batched tokens | GPU util | 结果 | prefill | decode | 平均 TTFT | 最大 TTFT | 峰值 |
|---|---|---|---|---|---|---|---|
| 2,048 | 0.90 | 通过 | 7,545.3 | 56.6 | 39.18s | 69.49s | 306 |
| 8,192 | 0.90 | 通过 | 11,471.8 | 92.3 | 25.31s | 45.70s | 762 |
| 16,384 | 0.90 | 通过 | 13,564.9 | 107.0 | 23.27s | 38.65s | 948 |
| 24,576 | 0.88 | 通过 | 13,858.6 | 113.5 | 23.47s | 37.83s | 879 |
| 32,768 | 0.90 | 运行期 OOM | — | — | — | — | — |
| 32,768 | 0.85 | 通过 | 13,707.0 | 112.5 | 25.17s | 38.25s | 905 |
从 2,048 增加到 16,384 后,prefill 从 7,545 提升到 13,565 tok/s,增幅 79.8%;aggregate decode 从 56.6 提升到 107.0 tok/s,增幅 89.1%;平均 TTFT 从 39.18 秒下降到 23.27 秒,降幅 40.6%。
24,576 相比 16,384 只增加 2.2% prefill,平均 TTFT 反而略高,同时 KV pool 从 11.71 个完整 context 降至 10.20 个。32,768/0.85 能跑,但平均 TTFT 进一步变差,KV pool 只剩 8.46 个完整 context。因此生产没有追逐最高单项吞吐,而是停在 16,384。
运行期 OOM 与显存甜点
| batched tokens | GPU util | KV tokens | 完整 262K context | 观察 |
|---|---|---|---|---|
| 8,192 | 0.90 | 3,350,035 | 12.78 | 通过 |
| 16,384 | 0.90 | 3,069,112 | 11.71 | 通过 |
| 24,576 | 0.88 | 2,674,019 | 10.20 | 通过 |
| 32,768 | 0.90 | 2,501,259 | 9.54 | 执行时 OOM |
| 32,768 | 0.85 | 2,218,834 | 8.46 | 通过,余量很小 |
| 16,384 | 0.88 | 2,955,692 | 11.28 | 当前生产 |
32,768/0.90 在执行阶段需要额外分配 1.19 GiB,当时每张 GPU 仅剩约 0.9–1.0 GiB,因此 OOM。把 gpu-memory-utilization 降到 0.85 后相同 scheduler budget 能完成测试,证明修复方向是少预留 KV、给 activation 和 CUDA graph 留运行期余量,而不是降低模型最大上下文或增加 allocator workaround。
生产甜点:max-num-batched-tokens=16384、gpu-memory-utilization=0.88。前者已经拿到几乎全部吞吐和 TTFT 收益,后者给运行期分配留余量;2,955,692-token KV pool 仍可容纳 11.28 条完整 context,高于 max-8。
--exact-tg 会强制越过自然 EOS,因此重复文本不能用于质量判断。本轮没有采集 MTP acceptance,也不从 decode 吞吐反推 acceptance。最终 16,384/0.88 已确认 API-ready 和 KV 容量;吞吐数字来自相同 scheduler budget 的 16,384/0.90 实测。262K × 8 流实测
选定 16,384/0.88 后又补了一组满上下文测试:每条请求指定 261,600 prompt tokens 和 512 output tokens,并发 8。关闭 warm-up、coherence 和 latency probe,确保服务端只收到一组 8 请求。测试完成,容器没有 OOM 或重启。
19.3 tok/s 不是八条请求都完成 prefill 后的纯 decode 性能。FCFS/chunked prefill 让请求先后进入 decode,512-token 输出与其余请求的超长 prefill 严重重叠;单请求 decode 分布从 2.6 到 64.5 tok/s,已经显示明显的调度不对称。这组数据用于描述 262K 满上下文的实际等待与容量,64K/c8 扫描仍用于选择 scheduler 参数。
回滚边界
旧 Qwen3.8-27B compose 和模型文件仍保留,30000 服务从始至终未动。回滚只需要停止 30001 的 Flash Next container,再从旧 compose 恢复同一端口;不要在回滚时清理模型、编译缓存或修改 GPU 4–7。
当前保留:TP4 + EP、PLE CPU offload、MTP3、262,144 max context、16,384 batched tokens、0.88 GPU memory、FULL_DECODE_ONLY、vLLM 原生 prefix cache、XML tool parser。当前关闭:LMCache L1/L2 和静态 YaRN。待单独 A/B:LMCache cache-hit 质量、FULL_AND_PIECEWISE 与真实业务流量分布。