跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • 浅色
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • 深色
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(LCZ-Blue)
  • 不使用皮肤
  • LCZ-Green
  • LCZ-Blue
  • LCZ-Black
折叠
品牌标识

抡锤者

首页 版块 标签
硬件
装备秀
AI 广场
  1. 主页
  2. 版块
  3. AI硬件
  4. X99 不能P2P让我很难受 。 买Pcie switch(260926死缓改判无罪,交换机完美实现P2P 9月27:TP2 Qwen3.8 Q8 60t/s)

X99 不能P2P让我很难受 。 买Pcie switch(260926死缓改判无罪,交换机完美实现P2P 9月27:TP2 Qwen3.8 Q8 60t/s)

已定时 已固定 已锁定 已移动 精华 热议 AI硬件
x99ai-pro-r9700多卡部署
118 帖子 23 发布者 3.7k 浏览 3 关注中
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • L laobenxiong

    @张光璞 说:

    T7910 PCIE SWITCH 双卡推理续篇:自编 ReBAR 内核吃下 32G 大 BAR、7900XTX 解锁 2000MHz,附一整天的实测数据

    期待对 switch 的继续测试. 我感觉 "内部 gen2" 很可疑啊... lspci -tv 看看 topology 到底长什么样? 链路的 bottlenect 在哪里?

    张光璞张
    张光璞张
    张光璞
    技术大牛 劳动模范
    编写于 最后由 编辑
    #108

    @laobenxiong 说:

    @张光璞 说:

    T7910 PCIE SWITCH 双卡推理续篇:自编 ReBAR 内核吃下 32G 大 BAR、7900XTX 解锁 2000MHz,附一整天的实测数据

    期待对 switch 的继续测试. 我感觉 "内部 gen2" 很可疑啊... lspci -tv 看看 topology 到底长什么样? 链路的 bottlenect 在哪里?

    把每一跳的 LnkCtl2(Target Link Speed)和 LnkSta 都 dump 了一遍,结论是**"内部 Gen2"这个说法是我读错了**:

    PEX880xx 会把自己一堆内部虚拟端口(Port #225/#233/#247)也枚举出来,那些行 Target=5GT/s、EqualizationComplete-(没做过均衡)—— 它们只是下行真实端口的镜像值,不能当证据。

    真凶是交换机下行口 ↔ 显卡上游口那一跳:两端 LnkCap 16GT/s x16、Target 16GT/s,但 LnkSta = 5GT/s x16 (downgraded) ⇒ 要求 Gen4、训练后自己回落,属物理层,不是配置。

    宽度全程 x16 没降,所以原文"x8"也写错了,已更正。
    交叉验证:该跳单卡 H2D/D2H = 6.04/6.82 GB/s(Gen2 x16 理论上限 8.0,效率 7585%);直插根口那对是 12.47/13.27(Gen3 x16 上限 15.75,效率 7984%)——效率一致、比值 2.1× ≈ 带宽比 2.0×。

    还不止:两张卡共用一个根口(直插那对是各自独占),所以这段上行本身是 Gen3 x16 共享。按实测单卡 6.0 GB/s,两卡满跑 12 < 15.75,当前瓶颈仍是那个 Gen2 跳。
    下一步按成本排:换线/转接板重插 → 换交换机下行口 → pcie_aspm=off(现在全链路 ASPM L1 开着)→ 最后才考虑 setpci 强制重训(边际链路可能直接掉卡,且绝不能在跑推理时做)。
    卡对卡 P2P 我没下结论:AllReduce 7.04 vs 直连那对 7.19 几乎一样,更像被 RCCL 开销卡住而非 Gen2,要 rocm-bandwidth-test 横幅才定。

    ══ 0000:05:00.0 ★ R9700(直插槽)
    0000:00:02.0 Intel 根口 (Xeon E7v4)
    0000:03:00.0 Navi31 上行口 (卡内) ← 上行链路 Gen3 x16 (能力 Gen5 x16) ✓ 正常:上游平台只到 Gen3
    0000:04:00.0 Navi31 下行口 (卡内) ← 上行链路 Gen5 x16 (能力 Gen5 x16)
    0000:05:00.0 ★ R9700 ← 上行链路 Gen5 x16 (能力 Gen5 x16)

    ══ 0000:08:00.0 ★ R9700(直插槽)
    0000:00:03.0 Intel 根口 (Xeon E7v4)
    0000:06:00.0 Navi31 上行口 (卡内) ← 上行链路 Gen3 x16 (能力 Gen5 x16) ✓ 正常
    0000:07:00.0 Navi31 下行口 (卡内) ← 上行链路 Gen5 x16 (能力 Gen5 x16)
    0000:08:00.0 ★ R9700 ← 上行链路 Gen5 x16 (能力 Gen5 x16)

    ══ 0000:aa:00.0 ★ 7900 XTX(走交换机)
    0000:a0:02.0 Intel 根口 (Xeon E7v4)
    0000:a1:00.0 交换机上游口 ← 上行链路 Gen3 x16 (能力 Gen4 x16) ✓ 正常
    0000:a2:04.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
    0000:a6:00.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
    0000:a7:00.0 交换机下端口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 真实物理链路降速
    0000:a8:00.0 卡上行口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 同一跳的另一端
    0000:a9:00.0 卡下行口 ← 上行链路 Gen4 x16 (能力 Gen4 x16)
    0000:aa:00.0 ★ 7900 XTX ← 上行链路 Gen4 x16 (能力 Gen4 x16)

    ══ 0000:b0:00.0 ★ 7900 XTX(走交换机)
    0000:a0:02.0 Intel 根口 (Xeon E7v4)
    0000:a1:00.0 交换机上游口 ← 上行链路 Gen3 x16 (能力 Gen4 x16) ✓ 正常
    0000:a2:08.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
    0000:ac:00.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
    0000:ad:00.0 交换机下端口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 真实物理链路降速
    0000:ae:00.0 卡上行口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 同一跳的另一端
    0000:af:00.0 卡下行口 ← 上行链路 Gen4 x16 (能力 Gen4 x16)
    0000:b0:00.0 ★ 7900 XTX ← 上行链路 Gen4 x16 (能力 Gen4 x16)

    L 1 条回复 最后回复
    0
    • Grayson RenG
      Grayson RenG
      Grayson Ren
      编写于 最后由 编辑
      #109

      继续加油 持续关注中

      1 条回复 最后回复
      0
      • 张光璞张 张光璞

        @laobenxiong 说:

        @张光璞 说:

        T7910 PCIE SWITCH 双卡推理续篇:自编 ReBAR 内核吃下 32G 大 BAR、7900XTX 解锁 2000MHz,附一整天的实测数据

        期待对 switch 的继续测试. 我感觉 "内部 gen2" 很可疑啊... lspci -tv 看看 topology 到底长什么样? 链路的 bottlenect 在哪里?

        把每一跳的 LnkCtl2(Target Link Speed)和 LnkSta 都 dump 了一遍,结论是**"内部 Gen2"这个说法是我读错了**:

        PEX880xx 会把自己一堆内部虚拟端口(Port #225/#233/#247)也枚举出来,那些行 Target=5GT/s、EqualizationComplete-(没做过均衡)—— 它们只是下行真实端口的镜像值,不能当证据。

        真凶是交换机下行口 ↔ 显卡上游口那一跳:两端 LnkCap 16GT/s x16、Target 16GT/s,但 LnkSta = 5GT/s x16 (downgraded) ⇒ 要求 Gen4、训练后自己回落,属物理层,不是配置。

        宽度全程 x16 没降,所以原文"x8"也写错了,已更正。
        交叉验证:该跳单卡 H2D/D2H = 6.04/6.82 GB/s(Gen2 x16 理论上限 8.0,效率 7585%);直插根口那对是 12.47/13.27(Gen3 x16 上限 15.75,效率 7984%)——效率一致、比值 2.1× ≈ 带宽比 2.0×。

        还不止:两张卡共用一个根口(直插那对是各自独占),所以这段上行本身是 Gen3 x16 共享。按实测单卡 6.0 GB/s,两卡满跑 12 < 15.75,当前瓶颈仍是那个 Gen2 跳。
        下一步按成本排:换线/转接板重插 → 换交换机下行口 → pcie_aspm=off(现在全链路 ASPM L1 开着)→ 最后才考虑 setpci 强制重训(边际链路可能直接掉卡,且绝不能在跑推理时做)。
        卡对卡 P2P 我没下结论:AllReduce 7.04 vs 直连那对 7.19 几乎一样,更像被 RCCL 开销卡住而非 Gen2,要 rocm-bandwidth-test 横幅才定。

        ══ 0000:05:00.0 ★ R9700(直插槽)
        0000:00:02.0 Intel 根口 (Xeon E7v4)
        0000:03:00.0 Navi31 上行口 (卡内) ← 上行链路 Gen3 x16 (能力 Gen5 x16) ✓ 正常:上游平台只到 Gen3
        0000:04:00.0 Navi31 下行口 (卡内) ← 上行链路 Gen5 x16 (能力 Gen5 x16)
        0000:05:00.0 ★ R9700 ← 上行链路 Gen5 x16 (能力 Gen5 x16)

        ══ 0000:08:00.0 ★ R9700(直插槽)
        0000:00:03.0 Intel 根口 (Xeon E7v4)
        0000:06:00.0 Navi31 上行口 (卡内) ← 上行链路 Gen3 x16 (能力 Gen5 x16) ✓ 正常
        0000:07:00.0 Navi31 下行口 (卡内) ← 上行链路 Gen5 x16 (能力 Gen5 x16)
        0000:08:00.0 ★ R9700 ← 上行链路 Gen5 x16 (能力 Gen5 x16)

        ══ 0000:aa:00.0 ★ 7900 XTX(走交换机)
        0000:a0:02.0 Intel 根口 (Xeon E7v4)
        0000:a1:00.0 交换机上游口 ← 上行链路 Gen3 x16 (能力 Gen4 x16) ✓ 正常
        0000:a2:04.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
        0000:a6:00.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
        0000:a7:00.0 交换机下端口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 真实物理链路降速
        0000:a8:00.0 卡上行口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 同一跳的另一端
        0000:a9:00.0 卡下行口 ← 上行链路 Gen4 x16 (能力 Gen4 x16)
        0000:aa:00.0 ★ 7900 XTX ← 上行链路 Gen4 x16 (能力 Gen4 x16)

        ══ 0000:b0:00.0 ★ 7900 XTX(走交换机)
        0000:a0:02.0 Intel 根口 (Xeon E7v4)
        0000:a1:00.0 交换机上游口 ← 上行链路 Gen3 x16 (能力 Gen4 x16) ✓ 正常
        0000:a2:08.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
        0000:ac:00.0 交换机端口 [内] ← 上行链路 Gen2 x16 (能力 Gen4 x16) [镜像值,非物理链路]
        0000:ad:00.0 交换机下端口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 真实物理链路降速
        0000:ae:00.0 卡上行口 ← 上行链路 Gen2 x16 (能力 Gen4 x16) ✗✗ 同一跳的另一端
        0000:af:00.0 卡下行口 ← 上行链路 Gen4 x16 (能力 Gen4 x16)
        0000:b0:00.0 ★ 7900 XTX ← 上行链路 Gen4 x16 (能力 Gen4 x16)

        L
        L
        laobenxiong
        德高望重 劳动模范
        编写于 最后由 编辑
        #110

        @张光璞 如果只能协商到gen2x16, 可以联系一下厂家, 看看能不能调整一下 switch 的 firmware...我觉得这个明显是不科学的。喊他们搞一张 7900xtx 自己复现一下,修一下 firmware...

        1 条回复 最后回复
        0
        • 张光璞张
          张光璞张
          张光璞
          技术大牛 劳动模范
          编写于 最后由 编辑
          #111

          我现在还在测试Q6 模型。并且缺少一个机架 。这一套实验台得慢慢收拾 。

          1 条回复 最后回复
          0
          • 张光璞张
            张光璞张
            张光璞
            技术大牛 劳动模范
            编写于 最后由 编辑
            #112

            T7910 双卡第三篇:Q8 全精度 + 真张量并行跑通(比层切分快 19.7%),以及一套「三档可切换」的本地推理服务

            上篇:《T7910 双卡推理续篇:自编 ReBAR 内核吃下 32G 大 BAR、7900XTX 解锁 2000MHz,附一整天的实测数据》;更早:《双 7900XTX + PCIe 交换机 + SGLang TP=2 实测与掉卡复盘》《T7910 双卡 AI 推理全记录:从 7900XTX 掉总线到 R9700 打通 P2P》。
            本篇只写增量:并行方式与精度档的重新选型 —— 从「层切分 + 中间量化」换成「真张量并行 + Q8 全精度」,以及由此衍生出的一套三档服务、它的固化方式和一批带口径的数据。平台、装机、采购价、大 BAR 补丁上篇都讲过,不再重复。

            TL;DR

            事项 结果 一句话
            双 7900XTX 上跑真张量并行(-sm tensor) ✅ 成了 上游 llama.cpp 在 ROCm 下必崩;换实验 fork(自研内部 all-reduce + P2P)后跑通
            **Q8 全精度(29 GB 权重)**装进双 24 GB 卡 ✅ 成了 关键不是「显存刚好够」,而是张量并行把显存摊平:22.2 / 22.2 GiB
            同二进制 A/B:tensor vs layer ✅ +19.7% 58.16 vs 48.60 tok/s(Q8_0 / 128K / 单流),代码类 +30%
            上下文与并发 ⚠️ 要读对口径 ctx=196608 是两槽共享的池,不是「每槽 192K」;单请求能吃到满
            主机内存提示缓存 48 GiB ⚠️ 超配 淘汰 0 次、命中 83% ⇒ 再加大没用;两个满槽其实 24~33 GiB 就够
            客户端出现空回答 ❌ 踩了 直连推理端口会因「思考通道」把正文吐空 ⇒ 客户端一律走网关别名
            「开机即用」的固化 ⚠️ 只 enable 服务不够 GRUB 默认内核指向另一版 ⇒ 重启后 P2P 能力全丢,必须连内核一起钉住
            拿 R9700 换下单张 7900XTX 跑 ❌ 不划算 单卡 32.05 tok/s / 只能 64K,对比双卡 36.85 tok/s / 192K(详见 §2.6)

            给赶时间的人:这一轮真正改命的是换并行方式(层切分 → 张量并行)——它一次解决了「慢」和「装不下」两件事;而看上去最该调的那几个参数(提示缓存大小、并发槽、草稿深度),实测下来不是已经超配就是本来就不是瓶颈。先量再改,第二次。


            一、想解决的三件事(思路,不是结论)

            1. 精度底线 vs 24 GB 显存

            我们这边的偏好是「尽量不降智」:模型从 27B 级稠密模型起步,量化档只考虑 Q6/Q8 这一档,不接受把主力模型换成 4bit。问题很直接:

            27B 稠密 · Q8_0  →  权重 ~29 GB
            27B 稠密 · Q6_K  →  权重 ~24 GB
            双 7900XTX 可用显存 = 2 × 24 GB = 48 GB(还要扣 KV / 计算缓冲 / 混合 SSM 的 rs cache)
            

            单卡 24 GB 装不下 Q8,双卡必须切分。看起来方案很多,实际能用的很少。

            2. 为什么不能继续用层切分(-sm layer)

            上篇之前我们一直跑的是 SGLang TP=2(int4 权重)和 llama.cpp 的 -sm layer(Q6)。层切分的问题是显存不均:

            切分方式 卡 0 显存 卡 1 显存 后果
            层切分(-sm layer) 19.45 GiB 22.58 GiB(94%) 满的那张先撞墙 ⇒ 它就是瓶颈卡,上下文再加一点就 OOM
            张量并行(-sm tensor) 22.2 GiB 22.2 GiB 完全均分 ⇒ 没有短板卡

            这就是「Q8 装不下」的真正原因:不是总量不够,是层切分把 29 GB 的权重压到了某一张卡上。也顺带解释了以前那种「显存没顶满啊,怎么还 OOM」的困惑 —— 另一张卡满了。

            3. 三条路与筛选判据链

            目标:双 24G 卡上,Q8 精度 + 尽量快
              ├─ 路 A:降精度(int4/4bit)           → ✗ 违背「不降智」底线,先排除
              ├─ 路 B:换卡(单张 32G 卡跑单卡)      → ✗ 带宽/容量实测不划算(§2.6 有数据)
              └─ 路 C:修「张量并行」                 → ✓ 唯一同时解决「慢」和「装不下」的方向
                    ├─ C1:上游 llama.cpp `-sm tensor` → ✗ 实测直接崩(ROCm/meta backend)
                    └─ C2:找已经修通它的实验 fork     → ✓ 本次落点
            

            判据链的关键一步是把「崩」和「慢」分开:上游那份在 -sm tensor 下是启动阶段就崩(不是慢、不是掉精度),所以它不是调参问题,而是必须换实现 —— 这一步想清楚,后面就不会在参数上瞎耗。


            二、速度与显存总账

            口径声明(论坛上最容易被挑的就是这个,先写在前面):本节所有数字,除特别标注外均为
            Q8_0 权重 · f16 KV · ctx 131072 · --parallel 1(单流)· 开投机解码(MTP,草稿深度 3)· 取四类内容各跑一遍的平均。

            2.1 同二进制 A/B(最干净的一组对照)

            同一个二进制、同一份 Q8_0 权重、同一上下文,只换切分方式与草稿策略:

            配置 平均 decode (tok/s) 散文 代码 技术说明 改写复用
            -sm tensor + MTP n-max 3 58.16 49.35 67.61 46.68 69.00
            -sm layer + MTP n-max 3(同二进制) 48.60 42.98 52.04 39.96 59.43
            -sm tensor + adaptive MTP n-max 4 59.33 49.61 67.69 47.30 72.73
            • tensor 比 layer 快 19.7%,且四类内容全部取胜(最夸张的是代码类 +30%)。
            • 换用自适应草稿策略(n-max 4)再涨一点到 59.33 tok/s。
            • 这一组对照的价值在于排除了「模型/量化差异」这个变量 —— 以前跨框架比出来的数字,往往两种东西一起变了。

            2.2 与现役 Q6 档的口径对照(注意口径不同)

            档位 权重 切分 ctx 并发 平均 decode
            旧:Q6 档 Q6_K 系(24 GB)+ 压缩词表草稿 layer 196608 --parallel 2 36.85 tok/s
            新:Q8 档 Q8_0(29 GB)+ 完整词表草稿 tensor 196608 --parallel 2 58~59 tok/s

            ⇒ 精度更高(Q8 对 Q6)、速度还快 57%。这条对照里两档的并发口径一致,但切分方式和量化档同时变了,所以只做参考,别当纯 A/B 用(纯 A/B 见 §2.1)。

            2.3 显存账(同一台机器、同一批卡)

            Q8 档(tensor)  :卡 0 22.2 GiB  |  卡 1 22.2 GiB     ⇐ 完全均分,各剩 ~1.8 GiB
            Q6 档(layer)   :卡 0 19.45 GiB |  卡 1 22.58 GiB    ⇐ 不均,卡 1 已 94%
            权重占比         :Q6 档 ≈ 25 GB / 48 GB = 61%(固定项)
            KV + 计算缓冲    :反推 ≈ 85 KiB/token(f16 KV,含混合 SSM 的 rs cache)
            ⇒ 想上 256K 上下文,需要再多 ≈ 5.6 GB —— 对 layer 档必撞死,对 tensor 档才是「够不够均分」的问题
            

            2.4 上下文与并发(服务端自报,别用猜的)

            load_model: initializing, n_slots = 2, n_ctx_slot = 196608, kv_unified = 'true'
            
            项 值 怎么读
            槽数(并发上限) 2 同时最多 2 个请求
            单槽上下文上限 196608 单个请求可以吃满 192K
            kv_unified true 192K 是两个槽共享的总池:单请求可吃满,双请求之和 ≤ 192K
            实时占用示例 槽 0 = 18,613 tok / 槽 1 = 0 /slots 能直接看到每槽占了多少

            别把它读成「2 × 192K」——这是本轮最容易被自己误导的一条(我也是先写错、再对着 /props 改回来的)。

            2.5 实况读数(真实使用中的累计值,不是跑分)

            跑分是短时的,下面这组是一边用一边攒的服务端累计指标(/metrics):

            指标 值 口径
            平均 decode 73.96 tok/s 48,848 token ÷ 660.48 s(短请求多、含大量缓存命中,故高于跑分)
            平均 prefill 750 tok/s 78,759 新 token ÷ 105.03 s
            历史最大单次 prompt 80,179 token 还没碰到 192K 的边
            草稿接受率 90.8% 38,055 / 41,895
            逐位置接受数 pos0 10,272 → pos1 9,881 → pos2 9,511 → pos3 8,391 第 4 位仍有 ~78% ⇒ 草稿深度 4 是合理值
            平均忙碌槽数 1.0008 基本一直是单槽在解码(没有真并发负载)

            2.6 顺手做的一次对照:拿 32 GB 的 R9700 换下单张 7900XTX

            同为 24/32 GB 级别的卡,很自然会想「单张 32 GB 岂不是更省事」。实测:

            方案 decode (tok/s) 可跑上下文 备注
            单张 R9700(32 GB,RDNA4) 32.05 只能 64K 散文 22.29 / 代码 42.28 / 技术说明 35.20 / 改写 28.43;131072 直接 OOM(cudaMalloc failed ... 1197 MiB)
            双 7900XTX(24 GB × 2,RDNA3) 36.85(Q6)/ 58~59(Q8) 192K 见上

            结论:不换。 原因很硬:decode 是显存带宽受限——R9700 约 640 GB/s,7900XTX 约 960 GB/s,差 1.5 倍不是代际能抹平的。这条也顺手修正了我自己早前的一个误判(当时基于 int4 + 另一个框架的数据,我一度以为 R9700 单卡更强,那是错的)。


            三、几个反直觉的发现

            1. 张量并行的收益不在「更快」,在「装得下」

            +19.7% 当然好,但真正值钱的是显存摊平(22.2 / 22.2 vs 19.45 / 22.58)。对双卡用户,这直接决定了「更大的量化档能不能跑」:Q8 能装下,靠的就是它;而层切分下,你永远要被那张最满的卡卡住。

            2. 「上下文开得不大」是错觉 —— 要看那张最满的卡

            以前问「显存怎么没顶满,是上下文开小了吗」,答案是:总占用不低(41/48 GB),但分布不均——一张 19.45、一张 22.58,瓶颈在后者。换了张量并行之后同样的上下文两颗卡一起 92%,才叫真的顶满。

            3. 提示缓存:淘汰为 0,就说明加它没用

            llama.cpp 的 --cache-ram(默认只有 8 GiB)是主机内存里的提示缓存上限,不是预分配。我们开到 48 GiB,实测:

            项 值
            上限 48 GiB
            实际匿名页占用 ≈ 11.6 GiB(进程 RSS 15.17 GiB)
            淘汰次数 0
            命中率 83%(392,564 缓存命中 / 78,759 新算)

            ⇒ 48 GiB 对「2 个 192K 槽」是超配(按 KV 折算两满槽约 24~33 GiB 就够)。判据:只要淘汰计数是 0,加大它不会有任何收益。

            4. 直连推理端口会吐「空回答」——不是模型坏了

            同一个模型、同一句话:

            走网关别名 → '可用'      (客户端带了「关闭思考」的模板参数)
            裸直连端口 → ''          (思考 token 全进了 reasoning_content,正文为空)
            

            ⇒ 客户端一律走网关别名(顺手还能统一带上 max_input_tokens 之类口径)。别拿裸 curl 的空白答复判模型死刑——这个坑我们当时差点误判成「这个模型不行」。

            5. 内核/GRUB 也是「持久化」的一部分

            把服务 systemctl enable 了,就以为重启后能自动还原 —— 不一定:

            跑通时用的内核:自编 rebar 内核(大 BAR 32G + P2P 可用)
            GRUB 默认项      :另一版内核(p2pdma)  ← 重启后 P2P 能力随内核一起换掉
            ⇒ 服务 enabled 也白搭:起来的是「跑不通那套张量并行」的机器
            

            修法就是把默认项钉成跑通的那一版(GRUB_DEFAULT 的子菜单编号写法、改前备份 /etc/default/grub、update-grub 后核对,并确认没有开 GRUB_SAVEDEFAULT —— 开了会有 saved_entry 悄悄盖掉默认项)。这条不是这次才发现的道理,但这次才真吃了它的亏。


            四、还没打通的墙 / 需要围观的地方(含推测标注)

            事项 现状 推测 / 下一步
            张量模式的 prefill(前置处理)代价 公开的同代双卡实测称 tensor 模式前置比 layer 低约 20%;我们本机没测出退化(8.9K prompt 实测 ~1000 tok/s) 推测:那条数据来自另一个模型/量化档,且前置受工程细节影响大。待验证:固定脚本、同二进制、四种 prompt 长度各跑 3 次取中位
            单请求吃满 192K 后没有并发余量 已确认(共享池语义) 想真正双路长上下文必须把池加一倍;但每卡只剩 ~1.8 GiB ⇒ 只能靠 KV 降精度或更小权重,目前不做
            采样回 CPU tensor 模式的固有行为(服务端自报 3 条无害告警) 观察是否影响高并发下的采样延迟;单流实测无感
            显存温度 ~90°C 风扇曲线对热点有效、对显存无效 推测:需要显存侧的导热改造(背板/风道),软件层已到极限
            交换机路径带宽天花板 卡↔交换机是 Gen4 x16,内部部分跳只跑 Gen2(上篇已记) 与本篇无关,但仍是「跨卡通信上不去」的上游瓶颈

            五、给后来人的清单

            选型之前

            • 先问自己「能不能接受 4bit」:能 ⇒ 路 A 便宜省事;不能 ⇒ 直接看双卡 + 张量并行这条路
            • 别用「显存总量」判断能不能装:先算
              单卡最满那一张(层切分一定不均)
            • 把「启动就崩」和「跑得慢」分开:崩 ⇒ 换实现,慢 ⇒ 调参,两件事别混着耗

            部署时

            • 客户端接
              网关别名(带关闭思考的模板参数),别裸连推理端口
            • 服务用 systemd 管:
              Restart=on-failure + RestartSec(加载慢的模型给 20s 左右,别抖动重启)
            • 同时钉住内核:跑通那版必须是 GRUB 默认项

            调优时

            • 测速一律
              --parallel 1:--parallel 2 的基线会低读约 20%(拿两种口径的数字互比必然得出错误结论)
            • 草稿深度
              试到 4 附近就够(公开数据 2→52.8 / 3→57.3 / 4→57.6 / 6→54.3 tok/s,更深有害)
            • 投机解码用
              服务端自报的接受率验收(接受数 ÷ 草稿数),并有逐位置计数:第 N 位还值不值得试,一眼就能看出来
            • --cache-ram 别按「越大越好」加:淘汰计数 0 就停手

            排障时

            • 空回答 ⇒ 先怀疑「思考通道吃掉了正文」,不是模型坏了
            • 显存「没顶满但 OOM」⇒ 看第二张卡 / 看是不是层切分不均
            • 重启后服务「没起来」⇒ 先看内核是否换了版本,再看服务 enable 状态

            六、当前状态 + 下一步

            • 现在跑的是 Q8 全精度 + 真张量并行档:29 GB 权重 + 完整词表 MTP 草稿,单流 58~59 tok/s,192K 共享池,冷启动约 29 秒(日志自报 listening 在 0:29)。
            • 一台机器上并存三档、互斥切换(引擎/precision/上下文各不同),客户端不用动:网关别名跟着档位走。
            • 开机即用已固化:服务自启 + 崩溃自愈 + 内核默认项钉死。
            • 下一步:① 把 prefill 对比补成正式 A/B(§四 第一条);② 找一个 Q6 档的替代模型做「精度/速度权衡」的第三点;③ 显存散热改造(软件已尽力)。

            附:给 AI / 爬虫的结构化数据段

            路径与用户名已占位(<USER>、<MODELS_DIR>、<FORK_DIR>);公开型号、模型文件名、端口照写,可复现性靠它们。

            A1. 平台与软件栈

            平台: Dell Precision Tower 7910 (BIOS A34)
            CPU: 2 × Xeon E5-2683 v4 @2.1GHz
            RAM: 125 GiB
            GPU_primary: 2 × AMD Radeon RX 7900 XTX 24GB (Navi31 / gfx1100 / RDNA3)   # 现役
            GPU_other:   2 × AMD Radeon AI PRO R9700 32GB (gfx1201 / RDNA4)          # 在场,空载(见 2.6 为何不换)
            互联: 两卡同挂 PLX 交换机下游(Gen4 x16 下行;内部部分跳 Gen2)→ 已实测不影响本方案
            内核: 自编 rebar 内核 7.0.14-rebar(大 BAR 32G + P2P 可用);GRUB 默认项已钉到同一版
            ROCm: 7.2.26015(注意:fork README 写的是 7.14,不可照抄)
            OS: Ubuntu 24.04 系
            

            A2. 引擎:实验 fork 与它改了什么

            repo: nasone32/llama.cpp-RDNA3-7900xtx-opt      # "RDNA3 真张量并行"实验分支
            build_target: gfx1100
            version_string: 0.3.0-dev (build 0, commit unknown)
            关键改动(按对本方案的作用排序):
              - 自研内部 HIP all-reduce + P2P(不走 RCCL)        # 上游 -sm tensor 在 ROCm 崩的正面解法
                  allreduce.cu
              - Q8 线格式量化 + fused residual + P2P issuer
              - adaptive MTP(草稿深度自适应)
                  common/speculative-adaptive.h
              - 修 meta backend(多个提交)
              - Q4_K/Q5_K/Q6_K mmvdq 核 + RDNA WMMA flash-attn + Q6_K MMQ prefill
              - BF16 KV 支持
            上游对照: 官方 llama.cpp 在本平台 -sm tensor 启动即崩 ⇒ 本方案依赖该 fork
            

            A3. 服务启动参数(原文;路径已占位)

            # 现役 Q8 档(张量并行)
            <FORK_DIR>/build-gfx1100/bin/llama-server \
              -m <MODELS_DIR>/Qwen3.8-27B-Uncensored-Q8_0.gguf \
              --model-draft <MODELS_DIR>/Qwen3.8-27B-Uncensored-draft-Q8_0.gguf \
              -sm tensor \
              --host 127.0.0.1 --port 8080 \
              --ctx-size 196608 \
              --parallel 2 --kv-unified --cache-idle-slots \
              --cache-ram 49152 \
              --flash-attn on \
              --cache-type-k f16 --cache-type-v f16 \
              --spec-type draft-mtp --spec-draft-n-max 4 --spec-draft-p-min 0 \
              --no-warmup
            # HIP_VISIBLE_DEVICES=2,3   (按本机枚举,两块 XTX 为 ROCm 2/3)
            

            关键参数 ⇄ 作用对照:

            参数 值 作用 / 为什么
            -sm tensor — 真张量并行:显存摊平 + 卡间只同步必要数据(本篇主线)
            --ctx-size 196608 两槽共享的总池(kv-unified 语义,见 2.4)
            --parallel 2 并发槽数
            --kv-unified — 启用共享池:单请求可吃满整个 ctx
            --cache-idle-slots — 空闲槽的缓存行为
            --cache-ram 49152 (MiB) 主机内存提示缓存上限(默认 8192);48 GiB 实测超配
            --spec-draft-n-max 4 草稿深度;实测逐位置接受率 pos0 95% → pos3 78%,4 是合理点
            --spec-draft-p-min 0 关闭按概率提前截断(配合自适应策略时另调)

            A4. fork 需要的环境变量(一个都别删)

            export GGML_CUDA_ALLREDUCE=internal   # 用 fork 自带的 all-reduce 实现
            export AR_WIRE=q8_0                   # 卡间通信量化格式
            export AR_Q8_THRESHOLD=1048576        # 超过该字节数走 Q8 线格式
            export AR_FUSED_RESIDUAL=1            # 融合残差
            export GDN_CHUNKED_BF16=1             # 混合 SSM 分块 BF16
            export AR_P2P=1                       # 启用 P2P 路径
            export AR_P2P_ISSUER=1                # P2P issuer
            

            A5. 三档服务与端口口径

            档位 引擎 权重 上下文 端口 用途
            sglang SGLang TP=2 int4 + f16 KV + EAGLE 投机 196608 8090 早期 agent 主力档(本轮不涉及)
            q6 上游 llama.cpp / layer 切分 Q6_K 系 24 GB + 压缩词表草稿 196608 8080 严谨档(备用)
            q8(现役) 实验 fork / tensor 并行 Q8_0 29 GB + 完整词表草稿 3.16 GB 196608(2 槽共享) 8080 日常默认
            网关 litellm — — 4000 所有客户端连这里(档位别名跟着走)
            • q6 与 q8 共用 8080、互斥(同一时刻只有一个在跑);切换是一次命令的事(约 15~20 秒)
            • 客户端(两个不同的 agent 前端)都配在 4000,因此换档不用改客户端

            A6. 基准原始数据(JSON)

            {
              "meta": {
                "weights": "Q8_0",
                "kv": "f16/f16",
                "ctx": 131072,
                "parallel": 1,
                "spec_decode": "draft-mtp",
                "content_classes": ["prose", "code", "tech_explain", "rewrite_reuse"],
                "metric": "mean decode tok/s over 4 classes"
              },
              "runs": [
                {"split": "tensor", "spec": "draft-mtp n-max=3",        "mean": 58.16, "per_class": {"prose": 49.35, "code": 67.61, "tech": 46.68, "rewrite": 69.00}},
                {"split": "layer",  "spec": "draft-mtp n-max=3",        "mean": 48.60, "per_class": {"prose": 42.98, "code": 52.04, "tech": 39.96, "rewrite": 59.43}},
                {"split": "tensor", "spec": "draft-mtp-adaptive n-max=4","mean": 59.33, "per_class": {"prose": 49.61, "code": 67.69, "tech": 47.30, "rewrite": 72.73}}
              ],
              "delta_tensor_vs_layer_pct": 19.7,
              "vram_gib": {
                "q8_tensor": {"card0": 22.18, "card1": 22.19, "card_total": 24.0},
                "q6_layer":  {"card0": 19.45, "card1": 22.58, "card_total": 24.0}
              },
              "live_counters": {
                "decode_mean_tok_s": 73.96,
                "decode_tokens": 48848,
                "decode_seconds": 660.48,
                "prefill_mean_tok_s": 750,
                "prompt_tokens": 78759,
                "prefill_seconds": 105.03,
                "max_single_prompt_tokens": 80179,
                "spec_draft_tokens": 41895,
                "spec_accepted_tokens": 38055,
                "spec_accept_rate": 0.908,
                "spec_accepted_per_pos": [10272, 9881, 9511, 8391],
                "busy_slots_per_decode": 1.0008
              },
              "host_prompt_cache": {
                "cap_mib": 49152,
                "anon_gib_used": 11.61,
                "process_rss_gib": 15.17,
                "evictions": 0,
                "cached_prompt_tokens": 392564,
                "new_prompt_tokens": 78759,
                "hit_ratio": 0.83
              },
              "single_card_alt": {
                "gpu": "R9700 32GB",
                "mean": 32.05,
                "per_class": {"prose": 22.29, "code": 42.28, "tech": 35.20, "rewrite": 28.43},
                "ctx_max": 65536,
                "oom_at_ctx": 131072,
                "verdict": "not_worth_swapping"
              }
            }
            

            A7. 日志关键行(原文)

            # 槽与上下文(决定「并发 / 上下文」怎么读)
            load_model: initializing, n_slots = 2, n_ctx_slot = 196608, kv_unified = 'true'
            
            # tensor 模式的三条无害告警(见到别当故障排查)
            llama_params_fit is not implemented for SPLIT_MODE_TENSOR, abort
            set_sampler: backend sampling not supported with SPLIT_MODE_TENSOR; using CPU
            spec common_specu: backend offload failed for seq_id=0; using CPU sampler
            spec common_specu: backend offload failed for seq_id=1; using CPU sampler
            
            # 就绪(冷启动约 29 秒)
            srv llama_server: model loaded
            srv llama_server: listening on http://127.0.0.1:8080
            
            # 编译期的一个坑(无外网时):拉 llama-ui 资源超时只是 Warning
            Timeout was reached   →   no assets available - building without an embedded UI   # 照样编过
            

            A8. 客户端接入(两个前端都走网关别名)

            # 统一口径:客户端 → litellm(:4000) → 具体档位
            gateway:
              aliases:
                q8: {upstream: "http://127.0.0.1:8080/v1", extra_body: {chat_template_kwargs: {enable_thinking: false}}, max_input_tokens: 196608}
                q6: {upstream: "http://127.0.0.1:8080/v1"}
                sglang: {upstream: "http://127.0.0.1:8090/v1"}
            frontend_A: {base_url: "http://<GATEWAY_HOST>:4000/v1", default_model: q8}
            frontend_B: {base_url: "http://<GATEWAY_HOST>:4000/v1", default_model: q8, note: "必须走网关;裸连 8080 会得到空正文"}
            

            A9. 复现命令(关键几步)

            # 1) 取 fork 源码(服务器无外网时:在有代理的机器上下 tarball 再 SFTP,双向 md5 校验)
            #    别覆盖正在用的 llama.cpp 目录
            # 2) 编译(用系统 PATH 里的 hipcc,别照抄作者 README 里他本机的 /opt/rocm/<ver>/... 路径)
            cmake -B build -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1100 -DGGML_HIP_GRAPHS=ON -DCMAKE_BUILD_TYPE=Release
            cmake --build build -j
            
            # 3) 冒烟(先确认能出正文,再谈速度)
            curl -s http://127.0.0.1:8080/v1/chat/completions -H 'Content-Type: application/json' \
              -d '{"model":"local","messages":[{"role":"user","content":"只回复两个字:可用"}],"max_tokens":8}'
            
            # 4) 判据(唯一可信的「服务就绪」)
            curl -s http://127.0.0.1:8080/health          # {"status":"ok"}
            curl -s http://127.0.0.1:8080/props  | grep -E 'total_slots|n_ctx'
            curl -s http://127.0.0.1:8080/slots           # 每槽实时占用
            curl -s http://127.0.0.1:8080/metrics | grep -E 'spec_decode|prompt_tokens|busy_slots'
            
            # 5) 固化:服务自启 + 崩溃自愈 + 内核默认项
            systemctl enable <unit>
            #   unit 内:Restart=on-failure / RestartSec=20
            grep -nE '^(menuentry|submenu)' /boot/grub/grub.cfg   # 先数清子菜单编号再改 GRUB_DEFAULT
            cp -a /etc/default/grub /etc/default/grub.bak-$(date +%Y%m%d-%H%M%S)   # 改前必备份
            update-grub
            

            A10. 判据与禁用项(踩过才知道的)

            • 基准测量:一律 --parallel 1;--parallel 2 的口径会低读约 20%,两种数字不能互比
            • 投机解码验收:只认服务端自报的 接受数 / 草稿数(+ 逐位置计数);别用「看起来快」当验收
            • 上下文读数:以 /props 的 total_slots / n_ctx_slot / kv_unified 为准;kv_unified=true ⇒ 共享池
            • 缓存调优:淘汰计数为 0 ⇒ 停止加大 --cache-ram
            • 服务就绪:只认 /health 200;加载慢的模型先预告「显存会先空后涨」
            • 客户端:不裸连推理端口(思考通道会吃掉正文)
            • 固化:systemctl enable 与「GRUB 默认内核」必须同时到位,否则重启后能力不一致
            • 排序环:单元依赖(显卡限频/OD 服务)没排好时,enabled ≠ 会启动(详见上篇)
            • 测卡对卡带宽:只用 RCCL;别拿拷贝工具的数字当通信能力
            L 1 条回复 最后回复
            0
            • terryT
              terryT
              terry
              超级版主
              编写于 最后由 terry 编辑
              #113

              @stxpnet 说:
              @PhoenixRise2026 怎么不上PCIE 4.0呢?服务器或者AMD的工作站主板,有没有能让两个PCIE 4.0显示跑满X16的? @xiaote 你也用 gpt 6 astra查一下,对于 咱们PCIE4.0显卡的用户,想组双卡或者多卡,有哪些硬件和软件要求?

              他怎么用GPT6?你出钱?
              😂

              油管:https://www.youtube.com/@抡锤者

              1 条回复 最后回复
              0
              • 张光璞张 张光璞

                T7910 双卡第三篇:Q8 全精度 + 真张量并行跑通(比层切分快 19.7%),以及一套「三档可切换」的本地推理服务

                上篇:《T7910 双卡推理续篇:自编 ReBAR 内核吃下 32G 大 BAR、7900XTX 解锁 2000MHz,附一整天的实测数据》;更早:《双 7900XTX + PCIe 交换机 + SGLang TP=2 实测与掉卡复盘》《T7910 双卡 AI 推理全记录:从 7900XTX 掉总线到 R9700 打通 P2P》。
                本篇只写增量:并行方式与精度档的重新选型 —— 从「层切分 + 中间量化」换成「真张量并行 + Q8 全精度」,以及由此衍生出的一套三档服务、它的固化方式和一批带口径的数据。平台、装机、采购价、大 BAR 补丁上篇都讲过,不再重复。

                TL;DR

                事项 结果 一句话
                双 7900XTX 上跑真张量并行(-sm tensor) ✅ 成了 上游 llama.cpp 在 ROCm 下必崩;换实验 fork(自研内部 all-reduce + P2P)后跑通
                **Q8 全精度(29 GB 权重)**装进双 24 GB 卡 ✅ 成了 关键不是「显存刚好够」,而是张量并行把显存摊平:22.2 / 22.2 GiB
                同二进制 A/B:tensor vs layer ✅ +19.7% 58.16 vs 48.60 tok/s(Q8_0 / 128K / 单流),代码类 +30%
                上下文与并发 ⚠️ 要读对口径 ctx=196608 是两槽共享的池,不是「每槽 192K」;单请求能吃到满
                主机内存提示缓存 48 GiB ⚠️ 超配 淘汰 0 次、命中 83% ⇒ 再加大没用;两个满槽其实 24~33 GiB 就够
                客户端出现空回答 ❌ 踩了 直连推理端口会因「思考通道」把正文吐空 ⇒ 客户端一律走网关别名
                「开机即用」的固化 ⚠️ 只 enable 服务不够 GRUB 默认内核指向另一版 ⇒ 重启后 P2P 能力全丢,必须连内核一起钉住
                拿 R9700 换下单张 7900XTX 跑 ❌ 不划算 单卡 32.05 tok/s / 只能 64K,对比双卡 36.85 tok/s / 192K(详见 §2.6)

                给赶时间的人:这一轮真正改命的是换并行方式(层切分 → 张量并行)——它一次解决了「慢」和「装不下」两件事;而看上去最该调的那几个参数(提示缓存大小、并发槽、草稿深度),实测下来不是已经超配就是本来就不是瓶颈。先量再改,第二次。


                一、想解决的三件事(思路,不是结论)

                1. 精度底线 vs 24 GB 显存

                我们这边的偏好是「尽量不降智」:模型从 27B 级稠密模型起步,量化档只考虑 Q6/Q8 这一档,不接受把主力模型换成 4bit。问题很直接:

                27B 稠密 · Q8_0  →  权重 ~29 GB
                27B 稠密 · Q6_K  →  权重 ~24 GB
                双 7900XTX 可用显存 = 2 × 24 GB = 48 GB(还要扣 KV / 计算缓冲 / 混合 SSM 的 rs cache)
                

                单卡 24 GB 装不下 Q8,双卡必须切分。看起来方案很多,实际能用的很少。

                2. 为什么不能继续用层切分(-sm layer)

                上篇之前我们一直跑的是 SGLang TP=2(int4 权重)和 llama.cpp 的 -sm layer(Q6)。层切分的问题是显存不均:

                切分方式 卡 0 显存 卡 1 显存 后果
                层切分(-sm layer) 19.45 GiB 22.58 GiB(94%) 满的那张先撞墙 ⇒ 它就是瓶颈卡,上下文再加一点就 OOM
                张量并行(-sm tensor) 22.2 GiB 22.2 GiB 完全均分 ⇒ 没有短板卡

                这就是「Q8 装不下」的真正原因:不是总量不够,是层切分把 29 GB 的权重压到了某一张卡上。也顺带解释了以前那种「显存没顶满啊,怎么还 OOM」的困惑 —— 另一张卡满了。

                3. 三条路与筛选判据链

                目标:双 24G 卡上,Q8 精度 + 尽量快
                  ├─ 路 A:降精度(int4/4bit)           → ✗ 违背「不降智」底线,先排除
                  ├─ 路 B:换卡(单张 32G 卡跑单卡)      → ✗ 带宽/容量实测不划算(§2.6 有数据)
                  └─ 路 C:修「张量并行」                 → ✓ 唯一同时解决「慢」和「装不下」的方向
                        ├─ C1:上游 llama.cpp `-sm tensor` → ✗ 实测直接崩(ROCm/meta backend)
                        └─ C2:找已经修通它的实验 fork     → ✓ 本次落点
                

                判据链的关键一步是把「崩」和「慢」分开:上游那份在 -sm tensor 下是启动阶段就崩(不是慢、不是掉精度),所以它不是调参问题,而是必须换实现 —— 这一步想清楚,后面就不会在参数上瞎耗。


                二、速度与显存总账

                口径声明(论坛上最容易被挑的就是这个,先写在前面):本节所有数字,除特别标注外均为
                Q8_0 权重 · f16 KV · ctx 131072 · --parallel 1(单流)· 开投机解码(MTP,草稿深度 3)· 取四类内容各跑一遍的平均。

                2.1 同二进制 A/B(最干净的一组对照)

                同一个二进制、同一份 Q8_0 权重、同一上下文,只换切分方式与草稿策略:

                配置 平均 decode (tok/s) 散文 代码 技术说明 改写复用
                -sm tensor + MTP n-max 3 58.16 49.35 67.61 46.68 69.00
                -sm layer + MTP n-max 3(同二进制) 48.60 42.98 52.04 39.96 59.43
                -sm tensor + adaptive MTP n-max 4 59.33 49.61 67.69 47.30 72.73
                • tensor 比 layer 快 19.7%,且四类内容全部取胜(最夸张的是代码类 +30%)。
                • 换用自适应草稿策略(n-max 4)再涨一点到 59.33 tok/s。
                • 这一组对照的价值在于排除了「模型/量化差异」这个变量 —— 以前跨框架比出来的数字,往往两种东西一起变了。

                2.2 与现役 Q6 档的口径对照(注意口径不同)

                档位 权重 切分 ctx 并发 平均 decode
                旧:Q6 档 Q6_K 系(24 GB)+ 压缩词表草稿 layer 196608 --parallel 2 36.85 tok/s
                新:Q8 档 Q8_0(29 GB)+ 完整词表草稿 tensor 196608 --parallel 2 58~59 tok/s

                ⇒ 精度更高(Q8 对 Q6)、速度还快 57%。这条对照里两档的并发口径一致,但切分方式和量化档同时变了,所以只做参考,别当纯 A/B 用(纯 A/B 见 §2.1)。

                2.3 显存账(同一台机器、同一批卡)

                Q8 档(tensor)  :卡 0 22.2 GiB  |  卡 1 22.2 GiB     ⇐ 完全均分,各剩 ~1.8 GiB
                Q6 档(layer)   :卡 0 19.45 GiB |  卡 1 22.58 GiB    ⇐ 不均,卡 1 已 94%
                权重占比         :Q6 档 ≈ 25 GB / 48 GB = 61%(固定项)
                KV + 计算缓冲    :反推 ≈ 85 KiB/token(f16 KV,含混合 SSM 的 rs cache)
                ⇒ 想上 256K 上下文,需要再多 ≈ 5.6 GB —— 对 layer 档必撞死,对 tensor 档才是「够不够均分」的问题
                

                2.4 上下文与并发(服务端自报,别用猜的)

                load_model: initializing, n_slots = 2, n_ctx_slot = 196608, kv_unified = 'true'
                
                项 值 怎么读
                槽数(并发上限) 2 同时最多 2 个请求
                单槽上下文上限 196608 单个请求可以吃满 192K
                kv_unified true 192K 是两个槽共享的总池:单请求可吃满,双请求之和 ≤ 192K
                实时占用示例 槽 0 = 18,613 tok / 槽 1 = 0 /slots 能直接看到每槽占了多少

                别把它读成「2 × 192K」——这是本轮最容易被自己误导的一条(我也是先写错、再对着 /props 改回来的)。

                2.5 实况读数(真实使用中的累计值,不是跑分)

                跑分是短时的,下面这组是一边用一边攒的服务端累计指标(/metrics):

                指标 值 口径
                平均 decode 73.96 tok/s 48,848 token ÷ 660.48 s(短请求多、含大量缓存命中,故高于跑分)
                平均 prefill 750 tok/s 78,759 新 token ÷ 105.03 s
                历史最大单次 prompt 80,179 token 还没碰到 192K 的边
                草稿接受率 90.8% 38,055 / 41,895
                逐位置接受数 pos0 10,272 → pos1 9,881 → pos2 9,511 → pos3 8,391 第 4 位仍有 ~78% ⇒ 草稿深度 4 是合理值
                平均忙碌槽数 1.0008 基本一直是单槽在解码(没有真并发负载)

                2.6 顺手做的一次对照:拿 32 GB 的 R9700 换下单张 7900XTX

                同为 24/32 GB 级别的卡,很自然会想「单张 32 GB 岂不是更省事」。实测:

                方案 decode (tok/s) 可跑上下文 备注
                单张 R9700(32 GB,RDNA4) 32.05 只能 64K 散文 22.29 / 代码 42.28 / 技术说明 35.20 / 改写 28.43;131072 直接 OOM(cudaMalloc failed ... 1197 MiB)
                双 7900XTX(24 GB × 2,RDNA3) 36.85(Q6)/ 58~59(Q8) 192K 见上

                结论:不换。 原因很硬:decode 是显存带宽受限——R9700 约 640 GB/s,7900XTX 约 960 GB/s,差 1.5 倍不是代际能抹平的。这条也顺手修正了我自己早前的一个误判(当时基于 int4 + 另一个框架的数据,我一度以为 R9700 单卡更强,那是错的)。


                三、几个反直觉的发现

                1. 张量并行的收益不在「更快」,在「装得下」

                +19.7% 当然好,但真正值钱的是显存摊平(22.2 / 22.2 vs 19.45 / 22.58)。对双卡用户,这直接决定了「更大的量化档能不能跑」:Q8 能装下,靠的就是它;而层切分下,你永远要被那张最满的卡卡住。

                2. 「上下文开得不大」是错觉 —— 要看那张最满的卡

                以前问「显存怎么没顶满,是上下文开小了吗」,答案是:总占用不低(41/48 GB),但分布不均——一张 19.45、一张 22.58,瓶颈在后者。换了张量并行之后同样的上下文两颗卡一起 92%,才叫真的顶满。

                3. 提示缓存:淘汰为 0,就说明加它没用

                llama.cpp 的 --cache-ram(默认只有 8 GiB)是主机内存里的提示缓存上限,不是预分配。我们开到 48 GiB,实测:

                项 值
                上限 48 GiB
                实际匿名页占用 ≈ 11.6 GiB(进程 RSS 15.17 GiB)
                淘汰次数 0
                命中率 83%(392,564 缓存命中 / 78,759 新算)

                ⇒ 48 GiB 对「2 个 192K 槽」是超配(按 KV 折算两满槽约 24~33 GiB 就够)。判据:只要淘汰计数是 0,加大它不会有任何收益。

                4. 直连推理端口会吐「空回答」——不是模型坏了

                同一个模型、同一句话:

                走网关别名 → '可用'      (客户端带了「关闭思考」的模板参数)
                裸直连端口 → ''          (思考 token 全进了 reasoning_content,正文为空)
                

                ⇒ 客户端一律走网关别名(顺手还能统一带上 max_input_tokens 之类口径)。别拿裸 curl 的空白答复判模型死刑——这个坑我们当时差点误判成「这个模型不行」。

                5. 内核/GRUB 也是「持久化」的一部分

                把服务 systemctl enable 了,就以为重启后能自动还原 —— 不一定:

                跑通时用的内核:自编 rebar 内核(大 BAR 32G + P2P 可用)
                GRUB 默认项      :另一版内核(p2pdma)  ← 重启后 P2P 能力随内核一起换掉
                ⇒ 服务 enabled 也白搭:起来的是「跑不通那套张量并行」的机器
                

                修法就是把默认项钉成跑通的那一版(GRUB_DEFAULT 的子菜单编号写法、改前备份 /etc/default/grub、update-grub 后核对,并确认没有开 GRUB_SAVEDEFAULT —— 开了会有 saved_entry 悄悄盖掉默认项)。这条不是这次才发现的道理,但这次才真吃了它的亏。


                四、还没打通的墙 / 需要围观的地方(含推测标注)

                事项 现状 推测 / 下一步
                张量模式的 prefill(前置处理)代价 公开的同代双卡实测称 tensor 模式前置比 layer 低约 20%;我们本机没测出退化(8.9K prompt 实测 ~1000 tok/s) 推测:那条数据来自另一个模型/量化档,且前置受工程细节影响大。待验证:固定脚本、同二进制、四种 prompt 长度各跑 3 次取中位
                单请求吃满 192K 后没有并发余量 已确认(共享池语义) 想真正双路长上下文必须把池加一倍;但每卡只剩 ~1.8 GiB ⇒ 只能靠 KV 降精度或更小权重,目前不做
                采样回 CPU tensor 模式的固有行为(服务端自报 3 条无害告警) 观察是否影响高并发下的采样延迟;单流实测无感
                显存温度 ~90°C 风扇曲线对热点有效、对显存无效 推测:需要显存侧的导热改造(背板/风道),软件层已到极限
                交换机路径带宽天花板 卡↔交换机是 Gen4 x16,内部部分跳只跑 Gen2(上篇已记) 与本篇无关,但仍是「跨卡通信上不去」的上游瓶颈

                五、给后来人的清单

                选型之前

                • 先问自己「能不能接受 4bit」:能 ⇒ 路 A 便宜省事;不能 ⇒ 直接看双卡 + 张量并行这条路
                • 别用「显存总量」判断能不能装:先算
                  单卡最满那一张(层切分一定不均)
                • 把「启动就崩」和「跑得慢」分开:崩 ⇒ 换实现,慢 ⇒ 调参,两件事别混着耗

                部署时

                • 客户端接
                  网关别名(带关闭思考的模板参数),别裸连推理端口
                • 服务用 systemd 管:
                  Restart=on-failure + RestartSec(加载慢的模型给 20s 左右,别抖动重启)
                • 同时钉住内核:跑通那版必须是 GRUB 默认项

                调优时

                • 测速一律
                  --parallel 1:--parallel 2 的基线会低读约 20%(拿两种口径的数字互比必然得出错误结论)
                • 草稿深度
                  试到 4 附近就够(公开数据 2→52.8 / 3→57.3 / 4→57.6 / 6→54.3 tok/s,更深有害)
                • 投机解码用
                  服务端自报的接受率验收(接受数 ÷ 草稿数),并有逐位置计数:第 N 位还值不值得试,一眼就能看出来
                • --cache-ram 别按「越大越好」加:淘汰计数 0 就停手

                排障时

                • 空回答 ⇒ 先怀疑「思考通道吃掉了正文」,不是模型坏了
                • 显存「没顶满但 OOM」⇒ 看第二张卡 / 看是不是层切分不均
                • 重启后服务「没起来」⇒ 先看内核是否换了版本,再看服务 enable 状态

                六、当前状态 + 下一步

                • 现在跑的是 Q8 全精度 + 真张量并行档:29 GB 权重 + 完整词表 MTP 草稿,单流 58~59 tok/s,192K 共享池,冷启动约 29 秒(日志自报 listening 在 0:29)。
                • 一台机器上并存三档、互斥切换(引擎/precision/上下文各不同),客户端不用动:网关别名跟着档位走。
                • 开机即用已固化:服务自启 + 崩溃自愈 + 内核默认项钉死。
                • 下一步:① 把 prefill 对比补成正式 A/B(§四 第一条);② 找一个 Q6 档的替代模型做「精度/速度权衡」的第三点;③ 显存散热改造(软件已尽力)。

                附:给 AI / 爬虫的结构化数据段

                路径与用户名已占位(<USER>、<MODELS_DIR>、<FORK_DIR>);公开型号、模型文件名、端口照写,可复现性靠它们。

                A1. 平台与软件栈

                平台: Dell Precision Tower 7910 (BIOS A34)
                CPU: 2 × Xeon E5-2683 v4 @2.1GHz
                RAM: 125 GiB
                GPU_primary: 2 × AMD Radeon RX 7900 XTX 24GB (Navi31 / gfx1100 / RDNA3)   # 现役
                GPU_other:   2 × AMD Radeon AI PRO R9700 32GB (gfx1201 / RDNA4)          # 在场,空载(见 2.6 为何不换)
                互联: 两卡同挂 PLX 交换机下游(Gen4 x16 下行;内部部分跳 Gen2)→ 已实测不影响本方案
                内核: 自编 rebar 内核 7.0.14-rebar(大 BAR 32G + P2P 可用);GRUB 默认项已钉到同一版
                ROCm: 7.2.26015(注意:fork README 写的是 7.14,不可照抄)
                OS: Ubuntu 24.04 系
                

                A2. 引擎:实验 fork 与它改了什么

                repo: nasone32/llama.cpp-RDNA3-7900xtx-opt      # "RDNA3 真张量并行"实验分支
                build_target: gfx1100
                version_string: 0.3.0-dev (build 0, commit unknown)
                关键改动(按对本方案的作用排序):
                  - 自研内部 HIP all-reduce + P2P(不走 RCCL)        # 上游 -sm tensor 在 ROCm 崩的正面解法
                      allreduce.cu
                  - Q8 线格式量化 + fused residual + P2P issuer
                  - adaptive MTP(草稿深度自适应)
                      common/speculative-adaptive.h
                  - 修 meta backend(多个提交)
                  - Q4_K/Q5_K/Q6_K mmvdq 核 + RDNA WMMA flash-attn + Q6_K MMQ prefill
                  - BF16 KV 支持
                上游对照: 官方 llama.cpp 在本平台 -sm tensor 启动即崩 ⇒ 本方案依赖该 fork
                

                A3. 服务启动参数(原文;路径已占位)

                # 现役 Q8 档(张量并行)
                <FORK_DIR>/build-gfx1100/bin/llama-server \
                  -m <MODELS_DIR>/Qwen3.8-27B-Uncensored-Q8_0.gguf \
                  --model-draft <MODELS_DIR>/Qwen3.8-27B-Uncensored-draft-Q8_0.gguf \
                  -sm tensor \
                  --host 127.0.0.1 --port 8080 \
                  --ctx-size 196608 \
                  --parallel 2 --kv-unified --cache-idle-slots \
                  --cache-ram 49152 \
                  --flash-attn on \
                  --cache-type-k f16 --cache-type-v f16 \
                  --spec-type draft-mtp --spec-draft-n-max 4 --spec-draft-p-min 0 \
                  --no-warmup
                # HIP_VISIBLE_DEVICES=2,3   (按本机枚举,两块 XTX 为 ROCm 2/3)
                

                关键参数 ⇄ 作用对照:

                参数 值 作用 / 为什么
                -sm tensor — 真张量并行:显存摊平 + 卡间只同步必要数据(本篇主线)
                --ctx-size 196608 两槽共享的总池(kv-unified 语义,见 2.4)
                --parallel 2 并发槽数
                --kv-unified — 启用共享池:单请求可吃满整个 ctx
                --cache-idle-slots — 空闲槽的缓存行为
                --cache-ram 49152 (MiB) 主机内存提示缓存上限(默认 8192);48 GiB 实测超配
                --spec-draft-n-max 4 草稿深度;实测逐位置接受率 pos0 95% → pos3 78%,4 是合理点
                --spec-draft-p-min 0 关闭按概率提前截断(配合自适应策略时另调)

                A4. fork 需要的环境变量(一个都别删)

                export GGML_CUDA_ALLREDUCE=internal   # 用 fork 自带的 all-reduce 实现
                export AR_WIRE=q8_0                   # 卡间通信量化格式
                export AR_Q8_THRESHOLD=1048576        # 超过该字节数走 Q8 线格式
                export AR_FUSED_RESIDUAL=1            # 融合残差
                export GDN_CHUNKED_BF16=1             # 混合 SSM 分块 BF16
                export AR_P2P=1                       # 启用 P2P 路径
                export AR_P2P_ISSUER=1                # P2P issuer
                

                A5. 三档服务与端口口径

                档位 引擎 权重 上下文 端口 用途
                sglang SGLang TP=2 int4 + f16 KV + EAGLE 投机 196608 8090 早期 agent 主力档(本轮不涉及)
                q6 上游 llama.cpp / layer 切分 Q6_K 系 24 GB + 压缩词表草稿 196608 8080 严谨档(备用)
                q8(现役) 实验 fork / tensor 并行 Q8_0 29 GB + 完整词表草稿 3.16 GB 196608(2 槽共享) 8080 日常默认
                网关 litellm — — 4000 所有客户端连这里(档位别名跟着走)
                • q6 与 q8 共用 8080、互斥(同一时刻只有一个在跑);切换是一次命令的事(约 15~20 秒)
                • 客户端(两个不同的 agent 前端)都配在 4000,因此换档不用改客户端

                A6. 基准原始数据(JSON)

                {
                  "meta": {
                    "weights": "Q8_0",
                    "kv": "f16/f16",
                    "ctx": 131072,
                    "parallel": 1,
                    "spec_decode": "draft-mtp",
                    "content_classes": ["prose", "code", "tech_explain", "rewrite_reuse"],
                    "metric": "mean decode tok/s over 4 classes"
                  },
                  "runs": [
                    {"split": "tensor", "spec": "draft-mtp n-max=3",        "mean": 58.16, "per_class": {"prose": 49.35, "code": 67.61, "tech": 46.68, "rewrite": 69.00}},
                    {"split": "layer",  "spec": "draft-mtp n-max=3",        "mean": 48.60, "per_class": {"prose": 42.98, "code": 52.04, "tech": 39.96, "rewrite": 59.43}},
                    {"split": "tensor", "spec": "draft-mtp-adaptive n-max=4","mean": 59.33, "per_class": {"prose": 49.61, "code": 67.69, "tech": 47.30, "rewrite": 72.73}}
                  ],
                  "delta_tensor_vs_layer_pct": 19.7,
                  "vram_gib": {
                    "q8_tensor": {"card0": 22.18, "card1": 22.19, "card_total": 24.0},
                    "q6_layer":  {"card0": 19.45, "card1": 22.58, "card_total": 24.0}
                  },
                  "live_counters": {
                    "decode_mean_tok_s": 73.96,
                    "decode_tokens": 48848,
                    "decode_seconds": 660.48,
                    "prefill_mean_tok_s": 750,
                    "prompt_tokens": 78759,
                    "prefill_seconds": 105.03,
                    "max_single_prompt_tokens": 80179,
                    "spec_draft_tokens": 41895,
                    "spec_accepted_tokens": 38055,
                    "spec_accept_rate": 0.908,
                    "spec_accepted_per_pos": [10272, 9881, 9511, 8391],
                    "busy_slots_per_decode": 1.0008
                  },
                  "host_prompt_cache": {
                    "cap_mib": 49152,
                    "anon_gib_used": 11.61,
                    "process_rss_gib": 15.17,
                    "evictions": 0,
                    "cached_prompt_tokens": 392564,
                    "new_prompt_tokens": 78759,
                    "hit_ratio": 0.83
                  },
                  "single_card_alt": {
                    "gpu": "R9700 32GB",
                    "mean": 32.05,
                    "per_class": {"prose": 22.29, "code": 42.28, "tech": 35.20, "rewrite": 28.43},
                    "ctx_max": 65536,
                    "oom_at_ctx": 131072,
                    "verdict": "not_worth_swapping"
                  }
                }
                

                A7. 日志关键行(原文)

                # 槽与上下文(决定「并发 / 上下文」怎么读)
                load_model: initializing, n_slots = 2, n_ctx_slot = 196608, kv_unified = 'true'
                
                # tensor 模式的三条无害告警(见到别当故障排查)
                llama_params_fit is not implemented for SPLIT_MODE_TENSOR, abort
                set_sampler: backend sampling not supported with SPLIT_MODE_TENSOR; using CPU
                spec common_specu: backend offload failed for seq_id=0; using CPU sampler
                spec common_specu: backend offload failed for seq_id=1; using CPU sampler
                
                # 就绪(冷启动约 29 秒)
                srv llama_server: model loaded
                srv llama_server: listening on http://127.0.0.1:8080
                
                # 编译期的一个坑(无外网时):拉 llama-ui 资源超时只是 Warning
                Timeout was reached   →   no assets available - building without an embedded UI   # 照样编过
                

                A8. 客户端接入(两个前端都走网关别名)

                # 统一口径:客户端 → litellm(:4000) → 具体档位
                gateway:
                  aliases:
                    q8: {upstream: "http://127.0.0.1:8080/v1", extra_body: {chat_template_kwargs: {enable_thinking: false}}, max_input_tokens: 196608}
                    q6: {upstream: "http://127.0.0.1:8080/v1"}
                    sglang: {upstream: "http://127.0.0.1:8090/v1"}
                frontend_A: {base_url: "http://<GATEWAY_HOST>:4000/v1", default_model: q8}
                frontend_B: {base_url: "http://<GATEWAY_HOST>:4000/v1", default_model: q8, note: "必须走网关;裸连 8080 会得到空正文"}
                

                A9. 复现命令(关键几步)

                # 1) 取 fork 源码(服务器无外网时:在有代理的机器上下 tarball 再 SFTP,双向 md5 校验)
                #    别覆盖正在用的 llama.cpp 目录
                # 2) 编译(用系统 PATH 里的 hipcc,别照抄作者 README 里他本机的 /opt/rocm/<ver>/... 路径)
                cmake -B build -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1100 -DGGML_HIP_GRAPHS=ON -DCMAKE_BUILD_TYPE=Release
                cmake --build build -j
                
                # 3) 冒烟(先确认能出正文,再谈速度)
                curl -s http://127.0.0.1:8080/v1/chat/completions -H 'Content-Type: application/json' \
                  -d '{"model":"local","messages":[{"role":"user","content":"只回复两个字:可用"}],"max_tokens":8}'
                
                # 4) 判据(唯一可信的「服务就绪」)
                curl -s http://127.0.0.1:8080/health          # {"status":"ok"}
                curl -s http://127.0.0.1:8080/props  | grep -E 'total_slots|n_ctx'
                curl -s http://127.0.0.1:8080/slots           # 每槽实时占用
                curl -s http://127.0.0.1:8080/metrics | grep -E 'spec_decode|prompt_tokens|busy_slots'
                
                # 5) 固化:服务自启 + 崩溃自愈 + 内核默认项
                systemctl enable <unit>
                #   unit 内:Restart=on-failure / RestartSec=20
                grep -nE '^(menuentry|submenu)' /boot/grub/grub.cfg   # 先数清子菜单编号再改 GRUB_DEFAULT
                cp -a /etc/default/grub /etc/default/grub.bak-$(date +%Y%m%d-%H%M%S)   # 改前必备份
                update-grub
                

                A10. 判据与禁用项(踩过才知道的)

                • 基准测量:一律 --parallel 1;--parallel 2 的口径会低读约 20%,两种数字不能互比
                • 投机解码验收:只认服务端自报的 接受数 / 草稿数(+ 逐位置计数);别用「看起来快」当验收
                • 上下文读数:以 /props 的 total_slots / n_ctx_slot / kv_unified 为准;kv_unified=true ⇒ 共享池
                • 缓存调优:淘汰计数为 0 ⇒ 停止加大 --cache-ram
                • 服务就绪:只认 /health 200;加载慢的模型先预告「显存会先空后涨」
                • 客户端:不裸连推理端口(思考通道会吃掉正文)
                • 固化:systemctl enable 与「GRUB 默认内核」必须同时到位,否则重启后能力不一致
                • 排序环:单元依赖(显卡限频/OD 服务)没排好时,enabled ≠ 会启动(详见上篇)
                • 测卡对卡带宽:只用 RCCL;别拿拷贝工具的数字当通信能力
                L
                L
                laobenxiong
                德高望重 劳动模范
                编写于 最后由 编辑
                #114

                @张光璞 说:

                cache-type-k f16 --cache-type-v f16 \

                kv dtype 到底是 f16 还是 bf16? 我觉得这两个差别不小。
                llama.cpp 上如何验证这个 activation 是:q4/q6/q8 (weight) * f16 (scale) -> f32 -> 参与 gemm 运算 -> 转 bf16 送到下一层?这个应该是 jwc 大神那个 sglang fork 的 gfx1100-support branch 的情况。

                1 条回复 最后回复
                0
                • 张光璞张
                  张光璞张
                  张光璞
                  技术大牛 劳动模范
                  编写于 最后由 编辑
                  #115

                  双 7900 XTX 走 PCIe 交换卡:卡间 P2P 为什么只有 3.6 GB/s,怎么干到 7.2 GB/s

                  一台 2016 年的双路洋垃圾(X99/C612,Dell Precision T7910)+ 一张 PEX88096 四槽交换卡,塞进 2× 7900 XTX + 2× R9700。
                  这篇只讲一件事:交换卡下面那两张 XTX 之间的 P2P 带宽 —— 一开始只有 3.62 GB/s(只比走主机内存快 1.6 倍),最后做到 7.23 GB/s / 双向 14.20 GB/s。
                  全文数字都是同一口径实测,表格带前后对比,判据链每一步排除了什么写在表里;没证实的地方明确标「推测 / 待验证」。


                  TL;DR

                  问题 结论
                  卡间 P2P 实测(改造前) 3.62 GB/s 单向 / 7.15 双向 —— 只比「经主机内存中转」(2.30)快 1.6 倍,等于交换卡白挂
                  为什么这么慢 两层压制:① 交换卡下游端口的目标速率被配成 Gen2(配置层,可以改)② 那张卡插的槽只给 x8 lane(物理层,改不了,只能换槽)
                  到底被谁掐住的 Gen2 x8 理论 4 GB/s,实测 3.62 = 90% ⇒ 不换槽,寄存器怎么调都到不了 7 GB/s
                  最终怎么解决 换槽(3 号槽 x8 → 2 号槽 x16)+ 把交换卡内部 fabric 的目标速率提到 Gen4(对 P2P 零贡献,但排除了「fabric 是瓶颈」)⇒ 7.23 / 14.20 GB/s(×2)
                  还是没解决的 槽那一跳只能 Gen2(两端都要求 Gen4、机制也验证过是好的,就是训不上去)⇒ 天花板 = Gen2 x16 理论 8 GB/s,实测 7.23 = 90%
                  值得折腾吗 卡间带宽在你的关键路径上就值得(真张量并行每层 all-reduce);层拆分/各跑各的则完全没必要(交换卡只为「插得下」)

                  一、平台与为什么盯卡间带宽

                  项 配置
                  主机 Dell Precision T7910(X99/C612,2× Xeon E7 v4,128 GB 四通道 DDR4-2400,双路 NUMA)
                  计算卡 2× Radeon RX 7900 XTX 24 GB(gfx1100),插在 PCIe 交换卡的两个槽上
                  对照卡 2× Radeon AI PRO R9700 32 GB(gfx1201),直连 CPU1 的两个根端口
                  交换卡 Broadcom PEX88096(96 lane / PCIe Gen4,subsystem 1000:c010):上游 x16 金手指 → 板上 4 个 PCIe 槽 + 一个拨码开关(x16 / x8+x8 / x4×4×4)
                  系统 Linux 7.0.x,pci=realloc 已开 ⇒ 四张卡全部拿到 32 GiB BAR0(大 BAR)
                  用途 本地跑 Qwen3.8-27B(Q8),双卡 -sm tensor 真张量并行 —— 每层都要 all-reduce ⇒ 卡间链路就是它的命脉
                  • 为什么先盯这个:层拆分(各跑各的)不依赖卡间带宽,但真张量并行每层都要 all-reduce,卡间链路直接进 decode 路径。
                  • 直觉来自对照:直连根口的那对 R9700 实测 10.27 GB/s,而交换卡下面这对 XTX 只有 3.62 —— 差近 3 倍,这不对劲。
                  • 口径先说明:本贴只测带宽(大块 peer 拷贝),不测延迟。all-reduce 对短消息更吃延迟,但带宽只有 3.6 的链路,得先把带宽修对。

                  二、为什么只有 3.6 GB/s:先量链路,别猜

                  逐跳读数(改造前,两张卡)

                  # 跳 设备 速率 宽度
                  1 根端口 CPU2 根端口 8.0 GT/s(Gen3) x16
                  2 uplink PEX88096 上游口 8.0 GT/s(Gen3) x16
                  3 一层下游 PEX88096 下游端口 5.0 GT/s(Gen2) x16
                  4 二层下游 PEX88096 下游端口 5.0 GT/s(Gen2) x16
                  5 槽口 交换卡槽口 5.0 GT/s(Gen2) x16
                  6 显卡上行口 7900 XTX 板载 1478 5.0 GT/s(Gen2) x16
                  7 显卡下行口 7900 XTX 板载 1479 16.0 GT/s(Gen4) x16
                  8 显卡核心 7900 XTX 16.0 GT/s(Gen4) x16
                  — 另一张卡(3 号槽) 同一条链 5.0 GT/s(Gen2) x8 ⚠️

                  算术上就锁死了

                  • 交换卡所有端口的能力都是 16 GT/s × x16(max_link_speed 全 16 GT/s),但实测只协商 Gen2;
                  • 另一张卡那一路还掉宽成 x8 ⇒ 实际是 Gen2 x8,理论 4 GB/s;
                  • 实测 3.62 GB/s = 90% ⇒ 就是这条链路掐住的,不是卡、不是驱动、不是软件栈。

                  同一时间的对照数据(排除「是不是卡本身不行」)

                  链路 带宽 说明
                  显卡内部 VRAM 拷贝(XTX / R9700) 396 / 288 GB/s 卡本身没问题
                  R9700 ↔ R9700(直连根口) 10.27 GB/s 换条路径就是 3 倍
                  7900 XTX ↔ 7900 XTX(交换卡内) 3.62 GB/s 本文主角
                  7900 XTX 经主机内存中转 2.30 GB/s 交换卡只比它快 1.6×

                  口径:

                  • 工具:自写 HIP 基准,hipMemcpyPeer 512 MiB × 30 次,取每卡独立事件 + 墙钟双口径,每次拷贝都做数据校验(防止测到没真拷的自由返回);
                  • 状态:跑之前停掉 8080 端口的推理服务(显存回落到 28 MB 空闲),否则既有负载污染数据;
                  • 四张卡同机、双路 NUMA,测的卡固定在同一路。

                  三、思路:先把「配置」和「物理」切开

                  判据链(每一步排除了什么、剩下什么)

                  步 动作 判据 / 排除了什么
                  ① 读目标速率寄存器(LnkCtl2,不是 LnkSta) 交换卡一层/二层端口的目标被写成 Gen2 ⇒ 第一嫌疑是配置,不是芯片能力
                  ② 拿空分支(没插任何设备的口)做实验:两端目标一起改 Gen4 链路真上 16 GT/s(lspci 里 (downgraded) 消失)⇒ ①交换卡接受 OS 写入 ②内部 fabric 真有 Gen4 实力 ⇒ 排除「固件无视写入」与「芯片不行」
                  ③ 只改一端 只有那一侧读数变化、链路仍旧档 ⇒ PCIe 速率 = 两端取小,必须两端一起改(最容易白忙的一条)
                  ④ 在挂卡的支路上逐跳升 Gen4(先停服务) 内部两跳真升到 16 GT/s,四张卡全程在、amdgpu 零报错 ⇒ 改档/重协商不打扰 GPU(这条本身是个重要结论:敢做)
                  ⑤ 槽那一跳升不上去 ⇒ 做降级 sweep(目标刻意降到 Gen1 → 再试 Gen3 → Gen4) 降 Gen1 时链路真的跟着降到 2.5 GT/s(机制完全正常)→ 升 Gen3/Gen4 一律回落 5.0 ⇒ 这一段是训练层面只能 Gen2
                  ⑥ 顺手量宽度 3 号槽那张卡 x8、1 号槽 x16 ⇒ 换槽

                  🔑 转折点:「要求更低的速率,看链路跟不跟」

                  之前一直在猜「是不是板子/线材/固件不行」,反复改目标速率都没用。
                  改成要求 Gen1、看到链路乖乖降到 2.5 GT/s 的那一刻就清楚了:机制完全正常,是这条信道训不到 Gen2 以上。这一刀把「配置没生效」和「物理到头了」切开了 —— 比继续猜快得多。

                  另一半转折点是宽度:速率查了半天,回头量 current_link_width 才发现其中一张卡只有 x8 —— 而 Gen2 x8 的理论值恰好把 3.6 GB/s 解释得严丝合缝(90%)。方向一直在「速率」上,答案有一半在「宽度」上。

                  寄存器偏移(这套 PEX88096 端口实测,别照抄网上的)

                  偏移(CAP_EXP+) 寄存器 关键位
                  0x0c LnkCap bits[3:0] 最高速率、[9:4] 最高宽度、[31:24] Port #
                  0x10 LnkCtl bit4 Link Disable、bit5 Link Retrain
                  0x12 LnkSta bits[3:0] 当前速率、[9:4] 当前宽度
                  0x2c LnkCap2 bits[7:1] 支持速率向量(0x1e = 2.5/5/8/16 GT/s 全支持)
                  0x30 LnkCtl2 bits[3:0] 目标速率(1=2.5 / 2=5 / 3=8 / 4=16 GT/s)
                  • 校准方法:LnkCap 的 bit[31:24] 是 Port #,拿它和 lspci 输出的 Port # 对账;LnkCtl 低位对 ASPM …Enabled。网上一大堆写法是 Root Port 的偏移,跟这套 PLX 端口不一致,套了会得到荒谬结论。
                  • 本机实测:写好目标速率后,链路自己就重协商了(不需要再写 retrain 位);个别端口要手动触发才动。
                  • 所有写入都是易失的(setpci 运行时写)⇒ 重启回 EEPROM 默认(Gen2)。想固化得用板厂 EEPROM 工具,或开机早期脚本 —— 本机因为「瓶颈在槽那一跳」,内部 Gen4 对 P2P 零贡献,所以没做开机脚本。

                  四、最终怎么解决的

                  1. 换槽(3 号槽 → 2 号槽):x8 → x16 ⇒ 本轮唯一的实际收益来源,P2P 直接翻倍。
                    • 附带提醒:板上有拨码开关(x16 / x8+x8 / x4×4×4),它决定每个槽拿多少 lane —— 插卡前先对一眼,别像我一样插了才发现掉一半。
                  2. 把交换卡内部 fabric 的目标速率提到 Gen4:对 P2P 没有贡献(瓶颈在槽那一跳),但它彻底排除了「fabric 是不是瓶颈」这个疑问,而且实测不影响 GPU(4 卡无损、零报错)。
                  3. 结论:在「槽那一跳只能 Gen2」的前提下,7.23 GB/s 就是这台机器的天花板(= Gen2 x16 理论 8 GB/s 的 90%)。

                  五、成绩单(同一口径,前后对比)

                  链路 改造前 改造后 理论上限 效率
                  7900 XTX ↔ 7900 XTX(单向) 3.62 GB/s 7.23 GB/s 8 GB/s(Gen2 x16) 90%
                  上面这条(并发双向合计) ≈7.15 GB/s 14.20 GB/s 16 GB/s 89%
                  7900 XTX 经主机内存中转 2.30 GB/s 3.49 GB/s — —
                  R9700 ↔ R9700(直连根口,对照) 10.27 GB/s 10.27 GB/s(没动) 15.75 GB/s(Gen3 x16) 65%
                  被 x8 压住那张卡的 D2H ≈3.6 GB/s 7.26 GB/s — —
                  卡内 VRAM 拷贝(对照) 396 GB/s 396 GB/s — —

                  数字来源:远端落盘日志(p2p_bw7.log 等)逐行核对,不是印象值。唯二非日志直出的是「改造前的双向合计 ≈7.15」与「改造前那张卡的 D2H ≈3.6」——当时没单独存盘,按 x8 链路(理论 4 GB/s、实测 90%)折算,其余全部日志直出。

                  顺手观察到一个对照:R9700 那对只有 65% 效率,而 XTX 这对 90%。R9700 两个根口都在 CPU1 上、P2P 要绕 root complex,而 XTX 这对是在交换卡内部路由 —— 效率差的来源大概率在这(推测,未细查,留个问题给懂行的补充)。


                  六、附带实测:memcpy 快 ≠ 内核能直接读对端

                  同一份基准里我还跑了一个「用 GPU kernel 直接读对端显存」的项(kernel-READ),结果很值得记一笔:

                  卡对 memcpy-P2P kernel 直接读对端 差多少
                  7900 XTX ↔ 7900 XTX 7.23 GB/s 7.06 GB/s 基本一致 ✅
                  R9700 ↔ R9700 10.27 GB/s 1.19 GB/s 差 8.6 倍 ❌
                  • XTX 那对两个数字几乎相同 ⇒ 那条路径的 peer 访问是「真」的;
                  • R9700 那对:memcpy 有 10.27,但 kernel 直接读对端只有 1.19(甚至比走主机内存中转的 6.78-6.79 还慢)⇒ 强烈暗示那个 10.27 是拷贝引擎(CE)跑出来的,不代表 GPU 核心能高效直接访问对端显存。
                  • 为什么这事重要:all-reduce 走的是 GPU 侧的 peer 读写/原子,不是 memcpy。所以「只看 memcpy 带宽」有高估风险 —— 我这份表里 R9700 那对就是典型。
                  • ⚠️ 标注:kernel-READ 是我自己写的一个简单读循环(对端大块顺序读),不是标准 benchmark;R9700 为什么这么低(驱动路径回退?寻址模式?)属于推测,未验证。但这说明多测一项是有必要的。

                  七、为什么槽那一跳只能 Gen2(推测 + 下一步验证)

                  已经排除的:

                  嫌疑 为什么排除
                  「没协商 / 机制没生效」 降 Gen1 的 sweep:链路真的跟着降到 2.5 GT/s ✅ 机制正常
                  「交换卡不接受 OS 写入」 目标速率回读就是写入值,空端口真跑到 Gen4 ✅
                  「卡的速率/宽度不支持」 两端 LnkCap2 都写 2.5-16 GT/s,显卡板载 1479→核心 跑 16 GT/s ✅
                  「宽度不够导致上不去」 同一槽 x16 也一样只跑 Gen2 ✅

                  剩下的推测(未验证):

                  1. 板侧物理裕量:交换卡的槽走线/连接器在这套组合下只稳定到 Gen2(这类扩展卡/背板常见);
                  2. 板侧 EEPROM 里端口 PHY/均衡参数被固化低速:OS 改的是「目标速率」,而 PHY 均衡/预加重参数写在卡的默认配置里 —— 矿场风格固件为了稳定性压速率是常规操作;
                  3. 显卡侧边缘连接器/PHY 限制:能力寄存器说支持,实际训练上不去。

                  下一步(做完会补更):把一张 R9700 插进交换卡的槽里 —— 它在 Gen3 根口下能跑到 8.0 GT/s,说明卡侧至少有 Gen3 能力:

                  实验结果 结论
                  R9700 在槽里也只 Gen2 ⇒ 板侧限制(要动就只剩 EEPROM 工具)
                  R9700 在槽里能跑 Gen3+ ⇒ 卡侧(Sapphire 这些 XTX 的板上行口限制),无解

                  八、给后来人的建议清单

                  买之前

                  • 别只看「Gen4 96 lane」这种营销数字,
                    问清每个槽的 lane 数和速率上限 —— x16 槽和 x8 槽对带宽是 2 倍差;
                  • 有拨码开关的板子,
                    问清/确认出厂默认在哪个模式(x16 / x8+x8 / x4×4×4);
                  • 先明确目标:
                    卡间带宽是否在你的关键路径上?
                    • 层拆分 / 每卡一个模型:交换卡只是「插得下」的手段,带宽无所谓;
                    • 真张量并行 / 频繁 all-reduce:带宽就是命脉,买之前算清这条链能不能跑到 Gen3+(Gen2 x16 = 8 GB/s 就是极限了);
                  • 记得「带宽守恒」:交换卡是
                    透明路由器,总带宽不增,只改拓扑 —— 别指望它变快,它只负责让你插得下。

                  装机时

                  • 卡
                    优先插 x16 的槽;插完立刻量一遍(第 10 节有命令)—— 同型号、同一张卡,换个槽就差一倍;
                  • 记下每个槽的 lane 分配(拍照/记丝印),以后排障不用再拆机;
                  • 分清两件事:
                    大 BAR 看显卡 VBIOS + 主板 BIOS 开关;P2P 看拓扑(两卡要在同一个 switch 的下游,别各挂一个 switch);
                  • lspci -vvv 扫一遍每跳有没有 (downgraded) 标记 —— 有就是没跑到端口能力。

                  调优时

                  • 量链路
                    看 current_link_*,不是 max_link_*(后者只是端口能力,会让你以为跑在 Gen4);
                  • 速率是配置层、可以试着改;宽度是物理层、只能换槽/改拨码 —— 别在软件里找宽度的解;
                  • 改速率
                    两端一起改(PCIe 取小值),一次只动一跳,改完立刻复量;
                  • 先在
                    空端口上做机制验证(零风险),别一上来就动挂卡的端口;
                  • 别碰 uplink(上游口) —— 它牵着整棵子树。

                  排障时

                  • 「降级 sweep」是最快的判据:要求更低的速率,看链路跟不跟 —— 跟 = 机制正常(那升不上去就是物理到头);不跟 = 目标速率根本没被采纳(固件/配置层);
                  • 看到
                    (downgraded) 就同时看速率和宽度(我这次就是只盯速率,绕了远路);
                  • 改档前
                    停掉吃显存的服务/任务,改动做成可回滚(易失写,重启即回默认);
                  • 只信实测:
                    日志沉默 ≠ 链路正常,「lspci 显示 Gen4」也可能是 max 而不是 current。

                  九、当前状态 + 下一步

                  • 当前:两张 XTX 在 1、2 号槽(x16),卡间 7.23 GB/s / 双向 14.20 GB/s;8080 上的 Q8 张量并行服务正常跑;交换卡内部 fabric 的 Gen4 是易失设置,重启回 Gen2(对性能无影响,故不做持久化)。
                  • 下一步:R9700 插槽实验(判「板侧 or 卡侧」)→ 若能跑 Gen3+,就得研究板子的 EEPROM 配置工具;若也只 Gen2,这台机器的卡间 P2P 就到此为止(7.23 = 天花板,接受)。

                  十、附:拓扑 / 复现命令 / 原始数据(给 AI / 给脚本)

                  拓扑(本机实测,BDF 照写便于复现)

                  根端口 a0:02.0 (Gen3 x16)
                  └─ a1:00.0            PEX88096 上游口 (uplink, Gen3 x16)
                     └─ a2:08.0         一层下游(目标速率可写,实测能升 Gen4)
                        ├─ aa:00.0 → ab:00.0(槽1 槽口) → ac:00.0(7900XTX 板载 1478) → ad:00.0(1479) → ae:00.0 核心
                        └─ ab:10.0(槽2 槽口) → af:00.0(7900XTX 板载 1478) → b0:00.0(1479) → b1:00.0 核心
                  
                  R9700 直连根口:00:02.0 → 05:00.0 核心 | 00:03.0 → 08:00.0 核心
                  
                  • 显卡自带上行/下行口为 1002:1478 / 1002:1479(芯片本身 Gen4,核心侧一直 16 GT/s);
                  • 槽口的对端读数是显卡上行口(ab:00.0 ↔ ac:00.0),这两端速度必须一致,不一致说明你看错了链路。

                  量链路(非 root 可读,最省事的判据)

                  BDF=<显卡核心BDF>   # 例如 ae:00.0
                  readlink -f /sys/bus/pci/devices/0000:$BDF | grep -oE '0000:[0-9a-f]{2}:[0-9a-f]{2}\.[0-9]' | while read b; do
                    echo "$b  current=$(cat /sys/bus/pci/devices/$b/current_link_speed) x$(cat /sys/bus/pci/devices/$b/current_link_width)  max=$(cat /sys/bus/pci/devices/$b/max_link_speed) x$(cat /sys/bus/pci/devices/$b/max_link_width)"
                  done
                  

                  读/写目标速率 + 重协商(root;先在空端口上试)

                  # 读目标速率(低 4 位:1=2.5 / 2=5 / 3=8 / 4=16 GT/s)
                  setpci -s <BDF> CAP_EXP+0x30.w
                  # 写 Gen4,保留其余位
                  setpci -s <BDF> CAP_EXP+0x30.w=0x0004:0x000f
                  # 需要时手动触发重训(本机多数端口会自己重协商)
                  setpci -s <BDF> CAP_EXP+0x10.w=0x0020:0x0020
                  # 回读验证
                  setpci -s <BDF> CAP_EXP+0x30.w; lspci -vvv -s <BDF> | grep -E 'LnkSta|LnkCap2'
                  
                  • ⚠️ 易失、重启回默认;别对 uplink 这么干、别在跑任务时干、一次只改一跳并立刻复量。

                  原始数据(改造后,p2p_bw7.log 逐行)

                  {
                    "bench": {"tool": "hip_memcpy_peer", "buffer": "512MiB", "iters": 30, "verify": "每轮数据校验", "service": "已停(显存空闲)"},
                    "intra_card_copy_GBps": {"R9700_05": 288.5, "R9700_08": 288.9, "XTX_ae": 396.3, "XTX_b1": 397.3},
                    "p2p_memcpy_GBps": {"R9700_05<->08": 10.27, "XTX_ae<->b1": 7.23},
                    "p2p_bidir_total_GBps": {"R9700_05<->08": 19.73, "XTX_ae<->b1": 14.20},
                    "p2p_kernel_read_GBps": {"R9700_05->08": 1.19, "XTX_ae->b1": 7.06},
                    "via_host_mem_GBps": {"R9700_05->08": 6.78, "XTX_ae->b1": 3.49},
                    "d2h_GBps": {"R9700": 13.99, "XTX_ae": 7.27, "XTX_b1": 7.26},
                    "before": {"p2p_memcpy_GBps": 3.62, "p2p_bidir_total_GBps": 7.15, "via_host_mem_GBps": 2.30}
                  }
                  

                  环境

                  • 内核:Linux 7.0.x(pci=realloc,四卡大 BAR 32 GiB 就位)
                  • 交换卡:Broadcom PEX88096(96 lane / Gen4,1000:c010),板上 4 槽 + 拨码开关(x16 / x8+x8 / x4×4×4)
                  • 显卡:2× 7900 XTX(gfx1100)+ 2× R9700(gfx1201)
                  • 推理:8080 端口,llama.cpp 系后端 -sm tensor(真张量并行),Qwen3.8-27B Q8
                  • 基准编译:hipcc -O2(ROCm);平台:双路 X99 + 双 NUMA

                  本文所有改动均为运行时 setpci 写入(易失),无任何固件/BIOS 修改;跑基准前均停掉推理服务。

                  1 条回复 最后回复
                  1
                  • 张光璞张
                    张光璞张
                    张光璞
                    技术大牛 劳动模范
                    编写于 最后由 编辑
                    #116

                    双 7900XTX 自制 W8A16:从 6 小时量化到 SGLang 生产栈上线

                    硬件:Dell Precision T7910 · 2× RX 7900 XTX 24G(gfx1100/RDNA3)+ 2× R9700 32G(gfx1201/RDNA4)
                    时间跨度:2026-09-28 深夜 ~ 2026-09-29 傍晚
                    关键词:auto-round · W8A16 · GPTQ · exllama · marlin · SGLang · EAGLE/MTP · HiCache · 显存账

                    TL;DR

                    • 用官方 BF16 底模自己量了个 W8A16(auto-round GPTQ,group_size=128),双 R9700 全 GPU 跑了 约 6 小时,产物 30G。
                    • 在双 7900XTX 上把 SGLang 跑成生产服务,路上三块硬石头:marlin 内核 HIP 编不过、exllama 只吃 fp16、EAGLE 草稿头与主模型同源。
                    • 结果:prefill 全档碾压(比 W4 快 ~80%、比 Q8 快 42~63%)、TTFT 最低、质量无损;代价是 decode 慢 24~49%(权重 30G vs 17G,账对得上)和 EAGLE 把 KV 池从 190K 压到 75.7K。
                    • 顺手推翻了旧结论:HiCache 并没有让 prefill 变慢——真凶是当时自打的 write-through 补丁;write_back 下 8K 冷 prefill 5.44~5.48s,与基线 5.43s 一致,而重复请求提速 12~76 倍、被逐出文档重命中提速 26 倍。

                    一、缘起:为什么要自己量一个 W8A16

                    手上已有两个引擎在跑同一个 27B:

                    档位 引擎 体积 强项 短板
                    Q8_0 llama.cpp(RDNA3 fork,-sm tensor) 29G 单流深上下文 decode 最强 并发弱、prefill 中等
                    W4A16 SGLang(TP=2) 17G decode 快、并发好 prefill 最低

                    缺的是「长文档首答 / 高并发 prefill」这一档——于是自己做 W8A16 补上:体积接近 Q8(质量≈Q8),但要吃到 SGLang 的 continuous batching + radix 缓存红利。


                    二、量化:双 R9700 全 GPU,约 6 小时

                    2.1 配方

                    • 底模:Qwen/Qwen3.8-27B 官方 BF16(52G / 18 分片,含 333 个 visual.* + mtp.*)
                    • 参数:auto-round 0.15.0、bits=8 group_size=128 sym=true、packing_format=auto_round:auto_gptq、--no-quant_lm_head、block_name_to_quantize=model.language_model.layers + mtp.layers

                    2.2 关键取舍:谁保 BF16

                    对象 处理 原因
                    48 个线性注意力层的 in_proj_a / in_proj_b(共 96 条 bits=16 例外) 保 BF16 线性注意力敏感层,量化掉点
                    视觉塔 333 个张量(model.visual.*) 保 BF16 图文能力无损,实测识图通过
                    lm_head 保 BF16(--no-quant_lm_head) 词表头对精度敏感

                    2.3 产物与耗时

                    项 值
                    起止 2026-09-28 22:55:21 → 2026-09-29 04:54:07(约 6 小时)
                    运行方式 双 R9700 全 GPU(用户前夜已断 XTX 供电防跳闸)
                    产物 Qwen3.8-27B-base-w8g128,30G / 10 分片 + mtp_bf16_extra.safetensors + model_extra_tensors.safetensors
                    峰值显存 {0: 25.97GB, 1: 6.56GB}(device_map auto 分得不均,属正常)、peak_ram 14.63GB

                    2.4 量化阶段的三个坑

                    1. 必须用 auto-round 入口命令:python -m auto_round.cli.main 会 runpy 双导入,静默不干活(exit 0 但啥都没跑)。
                    2. RDNA4 上必须 --disable_torch_compile:否则 Inductor 报 CUDA driver error 301。
                    3. GPTQ 格式的正确参数是 auto_round:auto_gptq:写 --format gptq 无效。

                    三、服务化:三块硬石头

                    从「加载就崩」到「能跑」,全程卡了三处,每一处都是可复用判据。

                    3.1 marlin 路径在 HIP 上必崩 → 用 backend: gptq 改路由

                    • 症状:GPTQ 权重加载即崩在 gptq_marlin_repack 的 JIT 编译。
                    • 根因:sglang/srt/layers/quantization/gptq_marlin*.cuh 里残留 CUDA API cudaFuncSetAttribute,HIP 编不过(别去改头文件,直接绕开 marlin)。
                    • 修法:checkpoint config.json 的 quantization_config 加 "backend": "gptq" ⇒ 路由到 exllama GPTQLinearMethod;quant_method 保持 "auto-round",让那 96 条 bits=16 例外原生走 UnquantizedLinearMethod。
                    • 判据:A 卡上 AutoRound/GPTQ「能加载但崩在 repack JIT」= marlin 路径,改路由不改内核。

                    3.2 exllama 内核只吃 fp16 → scales 与激活都要转

                    对象 处理
                    scales SGLANG_RDNA_FP16_GPTQ=1(加载时 bf16 → fp16)
                    激活 补丁 gptq_kernels.py 的 GPTQLinearKernel.apply:每次调用把 bf16 激活转 fp16,输出转回 bf16 再加 bias

                    3.3 EAGLE 草稿头与主模型同源

                    产物自带 mtp.layers(同样量化为 8bit)⇒ 不需要 --speculative-draft-model-path,直接吃同目录草稿头:

                    --speculative-algorithm EAGLE --speculative-num-steps 3 \
                    --speculative-eagle-topk 1 --speculative-num-draft-tokens 4
                    

                    四、首轮基准:三引擎全梯度对照

                    口径:W8 首轮为 无投机解码(speculative_algorithm=None)裸速度,双 XTX TP=2;对照的 W4/Q8 数据来自 09-28 同卡同频基准(同在 SCLK 1500MHz 供电限频下,绝对值偏低约 20%,相对比较公平)。

                    4.1 Prefill(tok/s)——W8 全档碾压

                    上下文 W8A16 W4A16 Q8_0 W8 vs W4 W8 vs Q8
                    8K 1626 904 997 +80% +63%
                    16K 1518 857 1012 +77% +50%
                    64K 1095 607 770 +80% +42%
                    128K 798 438 563 +82% +42%

                    4.2 Decode(tok/s)——W8 全档落后,但账对得上

                    上下文 W8A16 W4A16 差距
                    8K 35.6 69.3 -49%
                    16K 34.5 62.9 -45%
                    64K 29.5 49.5 -40%
                    128K 24.9 38.9 -36%

                    W8 权重 30G vs W4 17G,比值 1.76 ⇒ decode 降幅与体积比吻合,内核效率达理论上限 ~89%,无额外损耗。

                    4.3 TTFT 与并发

                    指标 W8A16 W4A16 Q8_0
                    TTFT @128K 165s 299s 176K 时 387s
                    4 路并发 @16K makespan 77.6s 100.1s 145.2s
                    4 路并发 @16K 聚合吞吐 26.4 tok/s 20.5 14.1

                    4.4 质量

                    类别 W8A16 W4A16 Q8_0
                    NIAH 长上下文检索(27 条) 27/27 27/27 27/27
                    推理(10 题) 8/10 7/10 8/10
                    结构化表格 13 行 0 行 6 行

                    结论:三档质量同档 ⇒ 按负载形态选引擎,别按量化位数选。


                    五、顺手修的两个客户端问题(都是配置,不是模型)

                    症状 根因 修法
                    「雷霆思考」但看不到思考过程 网关配置里硬编码 chat_template_kwargs.enable_thinking: false 客户端请求级带 chat_template_kwargs(enable_thinking)覆盖配置默认值;实测 true → 返回 reasoning_content
                    发图提示「此模型不支持」 客户端 model 的 input 只声明了 text 加 image + requestImagePixelBudget: 1048576 + requestImageMaxBytes: 8388608

                    两个额外认知:

                    • 顶层 reasoning_effort 对本模型无效——模板只认 chat_template_kwargs 内的 enable_thinking + reasoning_effort,且后者只收 low/medium/xhigh(传 high/max/none 直接 400)。
                    • 别为了「清爽」关思考:thinking off 会掉准确性(同一题 9.9 vs 9.11,关思考答错)⇒ 默认档位定 medium。

                    六、HiCache 归因翻案:10 分钟插桩,找回那 15 秒

                    6.1 推翻旧结论

                    • 旧现象:开 HiCache 后 8K 冷 prefill 从 5.3s 涨到 21s。
                    • 真因:当时打的自定义 write-through 补丁(让 chunked prefill 也写回)。
                    • 回到 pristine 源码 + --hicache-write-policy write_back 后:8K 冷 prefill 5.44~5.48s vs 基线 5.43s(一致),GPU 曲线 prefill 期对称 100%/100%、空闲 0%。

                    6.2 驱逐成本结构(方法级探针实测)

                    环节 耗时 占比
                    _evict_components 合计(39 次) 94.20s 100%
                    └ writing_check(阻塞等待) 93.95s 99.7%
                    └ _execute_kv_backup(真正的 D→H 拷贝) 0.81~1.24s(单次 26~28ms) ~1%
                    └ 树遍历 / _demote ≈0 —
                    • 阻塞点在 writing_check(write_back=True) 里的 while ongoing_write_through: ack.finish_event.synchronize()。
                    • 但端到端 wall-time 只 +1.3s——这个等待与排队的 GPU 计算重叠,所以「驱逐成本」不体现在耗时上。

                    6.3 最终 A/B(同电池,EAGLE on,pristine)

                    场景 HiCache OFF HiCache ON (write_back)
                    冷 8K prefill 5.46~5.66s 5.48~5.56s
                    冷 64K prefill 60.76s 60.90s
                    热 64K(在池内) 0.79s 0.75s
                    被逐出的 64K 重命中 63.01s(冷重算) 2.42s(主机池 load_back)= 26×
                    重复请求(不驱逐) — 8K 12×(5.45→0.46s)/ 64K 76×(60.4→0.79s)

                    结论:HiCache write_back 不增加 prefill 成本,唯一差异就是「被逐出文档的二次命中」——从冷重算变成主机层回迁(26 倍)。主机池 418,344 tok/卡。


                    七、意外发现:KV 池缩水的真凶是 EAGLE,不是 HiCache

                    配置 设备 KV 池
                    EAGLE 关 190,645 tok
                    EAGLE 开 75,761 tok
                    • 证据:sglang_mtp2.log(EAGLE + HiCache off)= 75,761;所有非 EAGLE 运行 = 190,645。
                    • 后果:EAGLE 开启时单请求 > ~75.7K token 直接被拒(128K 请求 → HTTP 400 Input length exceeds the maximum allowed length (75755 tokens)),尽管 --context-length 196608。
                    • 认知:投机解码买的是速度,付的是 KV 容量——草稿层 + 打包 KV 层占掉设备池(本机打包后 target_layers=16 + draft_layers=1 = 17)。

                    八、生产栈上线(开机自启)

                    8.1 服务单元

                    [Unit]
                    Description=SGLang Qwen3.8-27B W8A16 + EAGLE + HiCache (双 7900XTX, :8090)
                    Wants=gpu-power-cap.service network-online.target
                    After=gpu-power-cap.service network-online.target
                    Conflicts=sglang.service sglang-p2p.service llama-q6-xtx.service llama-q8-tensor.service
                    
                    [Service]
                    Type=simple
                    User=yalu
                    Group=yalu
                    SupplementaryGroups=render video
                    WorkingDirectory=/home/yalu/sglang
                    ExecStart=/home/yalu/start_sglang_w8.sh
                    Restart=on-failure
                    RestartSec=15
                    TimeoutStartSec=1800
                    LimitMEMLOCK=infinity
                    

                    8.2 引擎配方

                    W8A16 GPTQ(exllama) + EAGLE MTP-3 + HiCache write_back(24G, io-backend kernel) + BF16 KV + TP=2,:8090,served-model-name=qwen3.8-27b-w8a16,--context-length 196608 --mem-fraction-static 0.91 --max-running-requests 4 --max-mamba-cache-size 20。

                    8.3 GPU 枚举顺序漂移 → 按 PCI device id 自探测

                    重启后 0/1 与 2/3 谁在前不固定(本次重启 device0/1=R9700、device2/3=XTX),硬编码索引会加载失败。启动脚本扫 /sys/class/drm/card*/device/device == 0x744c(7900XTX 的 PCI device id),恰好 2 张才用其索引,否则回退 2,3:

                    _xtx=""; _cnt=0
                    for _n in 0 1 2 3 4 5 6 7; do
                      _f=/sys/class/drm/card$_n/device/device
                      [ -f "$_f" ] || continue
                      [ "$(cat "$_f" 2>/dev/null)" = "0x744c" ] && { _xtx="${_xtx:+$_xtx,}$_n"; _cnt=$((_cnt+1)); }
                    done
                    if [ "$_cnt" = "2" ]; then export HIP_VISIBLE_DEVICES="$_xtx"
                    else echo "[WARN] XTX 自动探测异常 (cnt=$_cnt),回退 2,3" >&2; export HIP_VISIBLE_DEVICES=2,3; fi
                    

                    通用规矩:任何写死 GPU 索引的启动脚本都是定时炸弹。

                    8.4 上线验证

                    项 实测
                    8K 冷 prefill 5.41s
                    设备 KV 池 75,761 tok
                    主机 KV 池 418,344 tok / 14.57G
                    网关链路 HTTP 200
                    思考 / 图片端到端 均 OK

                    九、显存账:每卡 22.5G,余量只剩 1.4G

                    组件 每卡占用
                    主模型权重(W8A16, TP=2) 14.69G
                    MTP 草稿头(8bit) 2.82 / 2.78G
                    设备 KV 池 2.46G(75,761 tok BF16)
                    Mamba 状态缓存 1.48G
                    CUDA 图(decode bs 1/2/4) ~1.02G
                    合计 ≈22.5G(实测 22.56 / 22.31G)
                    • 主机侧(HiCache):KV 主机池 418,344 tok / 14.57G + Mamba 主机缓存 9.45G(每卡)。
                    • 24G 卡上余量仅 1.4~1.7G,再调 --hicache-size / max-running-requests / 上下文时要留余量。

                    十、多卡扩展评估

                    10.1 三卡(TP=3)不可行

                    SGLang 支持非 2 幂 TP,但要求头数整除。本模型实测:

                    量 值 被 3 整除
                    num_attention_heads 24 ✓
                    num_key_value_heads 4 ✗(需 pad 到 6)
                    linear_num_key_heads 16 ✗(需 pad 到 18)
                    vocab_size 248320 ✗(需 pad 到 248322)

                    padding 会虚增计算头数并破坏 GPTQ group_size=128 的分组对齐 ⇒ 实际可用档位只有 TP=1 / 2 / 4。

                    10.2 R9700 加入张量并行:不建议

                    • R9700 = gfx1201(RDNA4),SGLang 的 AOT 编译目标含 gfx1201,理论可跑;
                    • 但与 7900XTX(gfx1100)异构通信效率差,且同样受头数整除限制 ⇒ 收益有限。

                    十一、功耗策略:只要 power cap,不要限频

                    单元 状态 说明
                    gpu-power-cap.service enabled + active XTX 2×272W(驱动硬件地板)、R9700 2×210W
                    gpu-clk-limit.service disabled 其 SCLK 1500MHz 锁会把 XTX 从 2371MHz 砍到 1500(-37%)

                    跳闸风险已由 R9700 改外接供电解决 ⇒ 只留 power cap。⚠️ 这与 09-25 的结论(「限频 + 降压才是真杠杆」)方向相反,属前提变化后的有意取舍;若再现跳闸则回退 1500MHz + VDDGFX -100mV。


                    十二、选型总结

                    负载形态 首选 理由
                    长文档首答 / 高并发 prefill W8A16 SGLang prefill +80%、TTFT 最低、并发聚合最高
                    单流长跑(深上下文 decode) Q8_0 llama.cpp TG 最深,128K +60% vs W4
                    多用户 / 多 agent 并发 SGLang(W4/W8) continuous batching + radix/HiCache 前缀缓存红利

                    三条铁律:

                    1. 按负载形态选引擎,别按量化位数选(三档质量同档)。
                    2. 投机解码买速度、付 KV 容量——要长上下文单请求就得在 EAGLE 与容量间取舍。
                    3. 缓存命中率是与「prefill 算力」同级的杠杆——长链 Agent 天然高命中,别只看裸速度。

                    十三、遗留与下一步

                    • W8 挂 EAGLE 后的速度复测(首轮基准是无投机数据,decode 应有明显提升)
                    • KV 池 75.7K 的取舍:EAGLE 加速 vs 128K 单请求容量(或调
                      --speculative-num-steps)
                    • 显存余量 1.4G 偏紧,
                      --hicache-size / max-running-requests 再平衡
                    • W8A16 长文档多轮场景复测(吃满 HiCache 重命中 26 倍的红利)
                    • 功耗策略留观(power cap only vs 限频 + 降压)
                    1 条回复 最后回复
                    1
                    • 张光璞张
                      张光璞张
                      张光璞
                      技术大牛 劳动模范
                      编写于 最后由 编辑
                      #117

                      自己量化W8 模型基本上宣布死刑,双7900xtx 48g 显存 开视觉塔、草稿头之后,上下文只剩下80K。 速度50t/s prefill 1100~1500

                      1 条回复 最后回复
                      1
                      • suboyangS suboyang

                        客服传话:
                        加入了resize BAR功能的是这些型号:X99-4MF 4MT 4MTPLUS 4MFPLUS
                        8D3 8D4 AD3 AD4 BD3 BD4
                        F8D T8D F8DPLUS M-F M-T
                        P4T P4F PD3 PD4

                        最新带ARGB的TF T8 也支持ReBAR

                        tmp tmpT
                        tmp tmpT
                        tmp tmp
                        编写于 最后由 编辑
                        #118

                        @suboyang 说:

                        客服传话:
                        加入了resize BAR功能的是这些型号:X99-4MF 4MT 4MTPLUS 4MFPLUS
                        8D3 8D4 AD3 AD4 BD3 BD4
                        F8D T8D F8DPLUS M-F M-T
                        P4T P4F PD3 PD4

                        最新带ARGB的TF T8 也支持ReBAR

                        我的x99-cd3-gaming, 有印象在bios中看到过reBar字样。不知道是不是一个东西。

                        1 条回复 最后回复
                        0

                        你好!看起来您对这段对话很感兴趣,但您还没有一个账号。

                        厌倦了每次访问都刷到同样的帖子?您注册账号后,您下次访问时都将自动回到上次浏览的位置,并可选择接收新回复的通知(通过电子邮件或推送通知)。您还可以收藏帖子、为帖子点赞,以此向其他社区成员表达您的感谢。

                        有了你的建议,这篇帖子会更精彩哦 💗

                        注册 登录
                        回复
                        • 在新帖中回复
                        登录后回复
                        • 从旧到新
                        • 从新到旧
                        • 最多赞同


                        • 登录

                        • 登录或注册以进行搜索。
                        • 第一个帖子
                          最后一个帖子
                        0
                        • 版块
                        • 最新
                        • 标签
                        • 热门
                        • 用户
                        • 群组