跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • 浅色
  • 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 关注中
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • 张光璞张 张光璞

    @Bunsei 说:

    @张光璞 说:

    测试环境

    aed3d331e9215360e3ab3946773aaec8.jpg

    看到我最期待的环节了~ 不过说实话,我决定后面要换平台了。从am5换成老的epic。

    我觉得epyc 平台也做不到4卡全是x16 互联吧。

    A
    A
    applejuice
    技术大牛 劳动模范
    编写于 最后由 编辑
    #107

    @张光璞 可以 4x pcie4x16

    1 条回复 最后回复
    0
    • 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
                          • 版块
                          • 最新
                          • 标签
                          • 热门
                          • 用户
                          • 群组