同卡(单张 R9700 32G)来交个作业:radiance 分支,Qwen3.8-27B MXFP4 + DFlash2,附全量实测数据
看到这帖正好对上我们的配置,把这两个月踩出来的数据摆出来,也给还在折腾 radiance 的人一点参照。
一、硬件
| 项 | 配置 |
|---|---|
| 显卡 | 单张 R9700 32G(RDNA4,gfx1201,ReBAR 开) |
| 功耗墙 | 240 W(见下面的取舍实验) |
| CPU | Intel i5-13600K,20 线程 |
| 内存 | 32 GB DDR4 |
| 系统 | Ubuntu,内核 7.0.0-38-generic |
| ROCm | 主机没装 ROCm 用户态,只有内核自带 amdgpu + /dev/kfd,全部靠镜像内 ROCm 7.14 |
二、软件栈
镜像 magiccodingman/vllm-radiance:1.0.394
vLLM 0.30.0 / torch 2.12.0+rocm7.14 / hip 7.14.60850 / libr4d 0.5.0(20 kernels,13/13 queries)
主模型 amd/Qwen3.8-27B-Quark-AWQ-MXFP4 19.03 GiB
草稿 tcclaviger/Qwen3.8-27B-DFlash2-FP8 2.12 GB,spec=7,probabilistic
关键环境(就是 common.sh 那套,加上投机解码栈):
RADIANCE_FAST_DRAFT=1 # int2 draft head + exact rerank
RADIANCE_DRAFT_RERANK=80 # 不是 64:VERIFY_HEAD 需要 4×top_k(20) 和 4×selector_top_k(16)
RADIANCE_VERIFY_HEAD=1 # int2 target verify head
RADIANCE_GDN_FUSED_UPDATE=1 R4D_ATTN_FP8=3 RADIANCE_DYNAMIC_WIDTH=1
三、性能(全部本机实测,同机同权重)
decode(单流,320 token 输出,三次取中位)
| 内容 | ms/step | decode | 接受率 | tok/step |
|---|---|---|---|---|
| 写代码 | 37.5 | 96.4 tok/s | 37.8% | 3.64 |
| 中文散文 | 37.3 | 49.4 tok/s | 12.1% | 1.85 |
| 纯 dense(SPEC=0) | 31.5 | 31–32 tok/s | — | 1.00 |
TTFT / prefill
| 场景 | 结果 |
|---|---|
| 短问题 TTFT p50 | 94 ms(仅 1 个 token 的 prompt 也要 110 ms) |
| 2K prompt | 903 ms → 2,182 tok/s |
| 8K prompt | 3,118 ms → 2,456 tok/s |
| 40K prompt | 17.6 s → 2,281 tok/s |
| 50K prompt | 22.3 s → 2,232 tok/s |
| 157K prompt | 94.5 s → 1,660 tok/s(衰减 26%) |
两个反直觉的数
- 接受率单次抖动极大,ms/step 才是稳定指标。 同一配置、同一道代码题连打三次:接受率 28.0% / 34.2% / 33.2%,decode 66.7 / 76.8 / 74.9 tok/s——但 ms/step 是 43.6 / 43.8 / 43.8,纹丝不动。所以别用单次 tok/s 比配置,会被接受率带走 15%。
- 功耗墙降下去不值。 240 W → 210 W:ms/step 44.1 → 44.3(无变化),2K prefill 2,182 → 2,040(−6%)。我们的卡 300 W 会 108–111℃ 硬关机,所以最终停在 240 W。
四、上下文与并发的取舍(32G 显存的真实账)
权重 19.03 GiB + 图捕获 ~0.6 GB,留给 KV 只有 9 GB,单价 36.5–39.8 KiB/token(fp8 KV + fp16 GDN 状态):
| 并发 | max_model_len | KV 池 | KV 并发 |
|---|---|---|---|
| 8 | 65,536 | 140,036 | 2.14× |
| 2 | 112,640 | 221,029 | 1.96× |
| 2 | 131,072 | 227,191 | 1.73×(不够两条全长) |
| 1 | 245,000 | 246,580 | 1.01× |
所以 2 并发 × 128K 在单卡 32G 上做不到(要 262,144 tokens ≈ 10.4 GB KV)。
看到你的 KV 池 401,454 tokens / 单请求 204,800,比我们多出 60%——你应该就是靠 RADIANCE_LMHEAD_INT4=1 + RADIANCE_EMBED_BITS=4 把权重压下去的。这是我们下一步要试的:1.0.394 里这两个开关有效吗?还是必须 1.5.x?
五、踩过的坑(同卡的人可以直接避)
radiance_verifyhead.py第 97 行会申请 2.37 GiB 临时显存(w.data.abs().max()对 lm_head 全量拷贝)。单卡必 OOM,而且报错的 2.37 GiB 跟 KV 大小、batch、图模式全都无关,很容易误判成 KV 不够。改成分块 4096 行求 max,峰值掉到 ~40 MB 就能起。expandable_segments:True在 ROCm + Triton 上必挂:Memory access fault ... triton_per_fused__to_copy。别开。--max-num-seqs 1时compile_sizes必须传空数组,否则静态形状编译直接断言失败:
AssertionError: Expected exactly one compiled range_entry for static shape compilation, but found N(N = len(compile_sizes)+1)。我们试了 [1]、[1,2,4] 都是这个错,传[]+cudagraph_capture_sizes=[1,8]才过。- 自建 libr4d 是降级,不要碰:拉 codeberg 的 b9e42ab(0.4.0)打 rx10 patch,能编出 22 个 kernel、包含那些 fused/narrow-state 的私货,但 radiance 只解析 10/13 queries(镜像自带的 0.5.0 是 13/13)。而且 rx10 patch 对 main(0.5.0) 打不上。
- fused GDN 不值得追:源码注释自己写着 "in the serve the fused step measured -0.4% … single-stream unchanged",而且它硬性要求 fp32 ssm state(会砍掉一半 KV 池)。我们实测 fp32 state:ms/step 44.2 vs fp16 的 43.8,没差。
FAST_DRAFT=0是错的方向:我们一开始照旧警告关掉了它,结果 ms/step 43.8、decode 79;打开FAST_DRAFT=1 + RERANK=80 + VERIFY_HEAD=1之后 37.5 ms / 96.4 tok/s(+22%)。dflash 分支默认就该是 1。
六、和 GGZ14 单卡基准的对照
| 指标 | 我们 | GGZ14 TP=1 |
|---|---|---|
| TTFT p50 | 94 ms | 95 ms |
| ms/step | 37.5 ms | 35.7 ms |
| decode(代码) | 96.4 tok/s | launcher 注释实测 96.4–101.3 |
| prefill 2K | 2,258 tok/s | 2,737(210W) |
TTFT 和 decode 基本打平,step 差 5%,prefill 差 20%。
顺便说一句:README 里 TP=1 表的 145.2 tok/s(代码)跟他们自己 launcher 注释里的 96.4–101.3 对不上,怀疑是 bench 口径不同(短输出把 prefill 摊进去了?)。建议复现时以 ms/step 和 acc/draft 为准,别拿 t/s 直接比。
七、想请教的两个问题
- Cactus:1.0.394 里有
RADIANCE_CACTUS_DELTA吗?我们日志里没见过,看到你那边 1.5.1 默认开了,代码类 acc 从 6.21 提到 6.39,这个对我们吸引很大。 - lm_head int4 + embed 4bit:实际省了多少显存、对输出质量有没有可测的影响?如果能把 KV 从 246K 推到 400K,那 2 并发 × 128K 就成立了。
那个「连续跑久了 decode 减半、重启恢复」的问题我们也遇到过类似现象(不过我们多是短会话,重启后 3–5 分钟冷启动里 http=000 也容易误判成挂掉)——你贴的 TheRock issue 我记下了,下次复现前先确认 uptime。