第 11 章 多卡与层切分
MoE 大模型低成本部署框架第 11 章 多卡与层切分
CHAPTER 11

第 11 章 多卡与层切分

13 分钟阅读 · 3,549 字

问题清单 #2 与「双卡三方案」的现场。本章回答:两张 24 GB 的卡,面对一个 76.7 GB 的模型,
到底有几种合作方式
——每种方式的机制在源码哪几行、实测数字是多少、为什么最后选了看起来
"最浪费"的那一种。附带一个完整的翻案故事:solo 9.3 tok/s 的真根因不是显存,是 CPU 抢核。

行号口径:截至 2026-10-19,SSEStraBed/src/program/generate.cpp(下称 generate.cpp)。
引擎行号随注释整理漂移,旧文档里的行号(如互斥检查的 :1738)已不可用,本章用重新核实的值,
并保留旧值作对照。


11.1 先把物理账摆出来

量 数字 出处
权重包 IQ3_XXS 76.7 GB docs/deployment/STRATA-DEPLOYMENT.md §1(2026-10-04 口径)
单卡显存 2 × RTX 4090D,各 24 GB ~/deploy/context.json
专家总数 48 层 × 512 = 24,576 个,每 token 取 top-10 同上
单个专家 blob ~1.74 MB(16.61 GiB ÷ 10,243 槽) docs/plans/方案猜想.md §3.1
每 token 专家查找次数 ~470–590 次(48 层 × top-8 + 投机行,引擎日志实测) 同上
主机 RAM 216 GiB(专家 arena + PLE 28.8 GB 页缓存) 02 章约束 1

一张卡装不下全部 48 层的专家,这是所有讨论的起点。三种合作方式由此展开:

方案 一句话 状态
1 层切分(layer split) 两卡合跑一个实例,各管一半层 可一键回(layout.sh split)
2 双实例 + 钉核 每卡跑一个完整实例,CPU/内存/GPU 全隔离 现生产(layout.sh dual)
3 共享专家池 两卡各跑 dense、专家跨卡共用 已否决(引擎不支持对称形态)

11.2 方案 1:--layer-split,把 48 层切成两段

参数定义在 generate.cpp:423:

std::string layer_split;  // 多 GPU 层切分:"K" / "K1,K2,.." / "auto"(空 = 单卡);只在 --serve 下可用

三种形态:

形态 含义 约束
"24" 层 0–23 在 CUDA0,层 24–47 在另一张卡 需要 --split-device 指定对端卡;K 必须从 2 起递增,每个 K 配一张互不相同的卡,或多个 K 共用 CUDA0(同卡分段)
"12,24,36" 三段切,配多卡 同上,切分点数 ≤ 可见卡数
"auto" 引擎按成本模型自己选切分点 只有一张可见卡时自动退回单卡(:1610-1611 打印 “one GPU visible, so no split”)

参数校验集中在 #多卡切分的参数校验 段(约 :1576-1622):--layer-split 只配 --serve 用,
非法形态直接 exit(:1622 的报错原文要求 “needs --serve, rising K from 2, and one …”)。

--trim-stage-weights 必须配显式切分(:2230):

strata generate: --trim-stage-weights needs an explicit --layer-split (ignored)

它的作用是"每张卡只加载自己那半层的 dense 权重,而不是整份 dense"(选项注释 :394)。
auto 模式不带 trim——这是 10-06 落地时踩过的点:想省显存就得先确定切点。

auto 的成本模型(注释在约 :2767,标定环境是 5080 + 3090 的机器,不是本机):

  • 每层耗时 ∝ 1 /(SM 数 × 主频);
  • 缓存未命中的专家按每单位路由质量约 190 ms;
  • 缓存装哪些专家 = 该 stage 的层里 profile 从热到冷直到显存用完;
  • 第 r 名专家的路由质量取 (r+1)^-1.2(拟合实测命中率);
  • 3 卡以内穷举所有切分点,更多卡按速度比例分配。

层间只传激活值:切分之后两卡之间流动的是每 token 几 KB 的激活(层 23 的输出交给层 24),
不是权重。这是方案 1 能跑快的根本原因,也是理解方案 3 的关键前提。

停车与切分的相互作用:带 --layer-split 的停车会保存每个 stage 的图像
(SavedConversation::stage_images,注释约 :1567);而"同一张卡分段 + 停车"这个组合被明确拒绝
(:1604-1606,提示用 --conversation-cache-mib 0 关掉停车)。

方案 1 的实测(2026-10-06 落地,gpu:[1,0] + layer_split:"24" + --trim-stage-weights +
--batch-groups 2 + --pool-workers 24;出处 docs/deployment/Strata-双卡部署全景.md §3 方案 1):

指标 双卡 split 单卡基线
单流 solo 107–141 70.6–76
聚合 1 / 2 / 4 / 8 路 106 / 94 / 149 / 153 96.5 / 77.0 / 118.1 / 131.3
专家驻留 / 命中率 84%(20,579/24,576)/ 99.6–100% 42% / ~86%
prefill 6.3–6.7k tok/s(120k 全新约 18–22s) ~3.4k tok/s
显存 两卡各 ~23.8 GB(基本占满) 单卡 23.6 GB

它的致命弱点不在吞吐,在隔离:两卡被一组模型吃满,放不下第二个模型;
所谓"快道"只能靠调度级插队,忙时被同引擎的排队拖到 ~37 tok/s(同一出处)。

11.3 方案 1 的另一种形态:--peer-device 是主从专家层,不是对称池

选项定义(generate.cpp:324-327):

/// Multi-GPU: a second expert tier on CUDA device `peer_device` (-1 = off), `peer_reserve_mib` left free
int peer_device = -1;  // 把另一张卡用作自适应专家缓存层(-1 = 关);与 --layer-split、--expert-cache-device1..3 互斥

帮助文本(:606-607)说清了它的语义:

--peer-device N      a second GPU as an adaptive expert-cache tier (rows over P2P; it also
                     computes its experts' prompt rows).  Not with --layer-split or ...

它是"主卡 + 副卡当缓存层"的从属结构:引擎主线程在 device 0,副卡持有部分专家、
在自己的卡上计算自己持有的那些行(包括 prompt 的行),结果经 P2P 回到主卡的 out。

实现分两个文件,区别值得记清(两个文件的"文件职责"头注释):

文件 机制 关键差别
src/core/peer_experts.cpp CUDA P2P(NVLink 或 PCIe peer access):把激活传过去、在副卡调原生分组专家内核、结果行取回主机 out;按专家使用率在副卡显存内换入换出 副卡直接访存;主线程用 RAII 临时切到 device_ 再切回;每层条目上限 CAP = MAXT × 10(一次上传一个 Meta 块:设备端权重指针、组起始下标、紧凑行号、源 token 下标)
src/core/remote_experts.cpp 该 GPU 不走 P2P 直接访存:激活拷过去、用 pinned 主机缓冲做零拷贝中转、紧凑结果行传回并散射到 out 适合非 P2P 拓扑(PCIe/USB4);多一次主机中转

Portable flag 这个细节(generate.cpp:1563):

strata::core::set_peer_portable(o.peer_device >= 1);   // multi-GPU: the Portable flag on mapped host buffers
                                                        // only with a peer device (before any allocation)

只有存在 peer 时,mapped 锁页内存才额外加 cudaHostAllocPortable——因为副卡也要写这些缓冲
(verify.cpp:85 的注释原话:“the peer card writes its rows into them”;mtp.cpp:71 同理)。
prefill.cpp:134 则相反:if (core::peer_portable()) return false;——有 peer 时保持 MMQ 路径及其缓冲大小。
这类"开关必须在任何分配之前设置"的时序,是引擎里反复出现的坑型(对照 10 章 mtp.cpp:245)。

peer 的默认分流比例(约 :2671 注释):default 0.45 (measured: 0.3-0.6 all better than 0; 0 = off, e.g. for the gate)。
启用前置条件(:3611):--peer-device needs --expert-profile and the expert cache;
启动时会打印副卡驻留量与占比(:3621)。

11.4 为什么 peer 与 layer_split 互斥(源码 + 物理两层理由)

源码事实(generate.cpp 约 :1945-1948,旧文档记作 :1738,已漂移):

if (o.peer_device >= 1 && (!o.layer_split.empty() || o.expert_cache_remote[0] > 0)) {
    std::fprintf(stderr, "strata generate: --peer-device cannot be combined with %s\n",
                 !o.layer_split.empty() ? "--layer-split (use one or the other)" : ...);

物理理由(方案猜想.md §3.2):两者会各放一份同一个专家。
layer split 已经让"专家与层同卡"——每个专家一份、与计算同卡;peer 又在另一张卡上维护一份自适应缓存。
两份拷贝 = 两卡显存被切得更碎,而收益(命中率)早已被 split 本身拿走。
另一处相关报错(约 :1967)指向同一逻辑:某些形态"需要完整会话,而 layer split 没有",
要求"run them without --layer-split"。

11.5 已否决:方案 3「对称共享专家池」——一次框架纠错

方案 3 的原始设想:两卡各跑一组 dense 权重,专家做成跨卡共享池。否决它的是一笔带宽账
(方案猜想.md §3.1,双卡部署全景 §3 方案 3 同表):

"共享"的实现方式 每 token 跨卡流量 判定
搬权重:把对端专家本体(1.74 MB)拉过来算 470–590 次查找 × 1.74 MB ≈ 200–500 MB/token → 仅此一项 ≥8–20 ms/token ❌ 解码上限 <100 tok/s,且 PCIe 被打满(KV、停车、专家流全部饿死)
搬数据:把激活行(~8 KB)送去专家所在卡算 ~2–3 MB/token ✅ 带宽可行——引擎已有这个形态(--peer-device、--expert-cache-device1..3 + `stripe

关键理解纠正(这段值得留在书里,因为它是一次典型的"用错框架否决了正确方向"):
最初按"搬权重"否决方案 3,框架错了;正确机制是数据去找专家。而方案 2 钉核版的实测随后证明:
CPU/RAM 本身就是那个共享专家层——24,576 个专家全在内存 arena 里,未命中时数据找专家、
AVX-512 就地算。方案 3 的可行内核已经被方案 2 吸收,不需要新机制。

不支持的是对称形态:peer 是单主从结构,两个实例各自 --peer-device 互指技术上能启动,
但那只是两份互不共享的拷贝。要做真正的跨卡共享池需要引擎级改动(peer 共享化/可重入 + 每卡 VRAM 分区),
且没有任何实测证据表明它比方案 1 快。→ 记入已否决清单(19 章),别重复踩。

显存账也反对它(方案猜想.md §3.3):现状态每卡 24 GB ≈ dense 半份 ~5 GB + KV/状态/缓冲/MTP ~2–3 GB

  • 专家缓存 ~15.8–16.6 GB。若做"两个整模型实例 + 各自 peer 专家层",每卡要同时容纳 A 的 dense 全量
    (~10 GB)+ B 的 peer 缓存,每实例的本地专家容量反而下降。

11.6 翻案故事:solo 9.3 tok/s 的真根因

这是本项目最重要的一次误判与纠正,完整过程值得逐轮记录(出处:
docs/plans/方案猜想.md §2a、docs/deployment/Strata-双卡部署全景.md §3 方案 2、
docs/reports/双通道测试报告-2026-10-07.md)。

第一轮(2026-10-07):strata-a(GPU0:8091)+ strata-b(GPU1:8092),两份都无 layer split、
各 --pool-workers 24(合计 48 线程抢 32 核),停车各 8192 MiB / 20 槽。

指标 方案 1(split) 每卡一实例(第一轮)
单流解码 107–141 9.3
2 路(每路 / 聚合) ~47 / 94 8.3、8.9 / 16.1
4 路聚合 149 24.0
8 路 153,全活 17.2,6/8 拿不到 token
专家缓存命中 99.6–100% 77.6–85.6%

当时的归因是"单卡装不下全部 48 层的专家,24 GB 只装得下约 1/4–1/3,缺的专家落回 CPU 池"——
这个归因本身没错,但它解释不了 9.3 这个数量级(CPU 池兜底的历史水位是 60–70 tok/s 量级,不是 9)。
于是方案 2 一度被判"否决"。

第二轮:worker 从 24 降到 13(每实例),仍然共享核段 → 仍然崩,6–17 tok/s。
说明"worker 超额"只是部分原因。

第三轮(按用户提示钉核):strata-a 用 taskset -c 0-15(贴 GPU0 的 NUMA0),
strata-b 用 taskset -c 38-53(贴 GPU1 的 NUMA1),各 13 worker → 98–107 tok/s,彻底翻盘。

真根因:两个引擎的 CPU 专家池 worker 在同一批核上互踩——
每层解码的关键路径要等 CPU 池算完未命中专家,两个进程互相把对方的 worker 挤下核,
CFS 调度把等待放大成十倍(对照证据:CPU 池窗口时间从异常水位恢复到正常 59.8 ms/window,
方案猜想.md §重要转折)。不是显存装不下,是核不够分。

钉核后的实测(2026-10-07 晚,无外部流量,直连引擎;双卡部署全景 §3 方案 2 表):

场景 每路解码 聚合 对照:方案 1
单实例 solo 107 / 98 — 130
1+1 89 / 115 139 94
2+2 32–47 109 149
1+3(快 1 + 普 3) 快道 119 · 普道 31–43 93 4 路全 ~37
隔离测试:普道读 60k 长文时快道短请求 快道 93.7 tok/s · TTFT 1.08s — 快道冻结 ~20s
8 路并发(经网关,每通道) 8/8 全活 47–61/通道 —

1+3 那一行是双通道存在的理由:普道满载时快道仍有 119 tok/s,而 split 布局下同一场景所有人 ~37。
最后一行是隔离的价值:split 布局里长 prefill 会把快道冻结约 20 秒(09 章的饿死问题),
双实例布局下快道 TTFT 1.08 秒。

11.7 三方案横向对比与选法

维度 方案 1 split 方案 2 双实例 + 钉核(生产) 方案 3 共享专家
单流 solo 130(107–141) 98–107 —(未实施)
聚合吞吐(4 路) 149 109 —
快道隔离(普道满载) ~37(同引擎排队) 119(完全隔离) —
prefill 6.5k tok/s ~3.4k tok/s —
专家驻留 84%(跨卡分摊) ~1/3 每卡(CPU 池兜底) 设想更高,未验证
RAM 1 份(~120 GiB) 2 份(176/216 GiB) 省显存设想
故障域 1 2 2
运维复杂度 最低 中(钉核纪律) 高(需引擎开发)
适用 单通道高并发 快/普双通道 SLA 隔离 不建议

选法(同一出处的决策图,转述):想"一个模型服务尽量多人" → 方案 1(layout.sh split 一键可回);
想"给 agent 一条不被打扰的快道" → 方案 2(现生产);想"跨卡共享专家省显存" → 方案 3 不支持,
它的可行内核已经在方案 2 的 CPU 池里。若平台再给一张卡(GPU2),最干净的形态是
快道独占整卡、普道回 split 保吞吐(待申请,19 章阶段③)。

纪律(双卡部署全景 §运维):run-iq3_xxs-a.sh 里的 taskset -c 0-15 与
run-iq3_xxs-b.sh 里的 taskset -c 38-53 是命门——改启动脚本先确认 taskset 还在,
否则崩回 9 tok/s(17 章红线第 1 条)。

11.8 本章小结

  • 两张卡有三种合作方式,实测数字都在 11.2/11.6/11.7 的表里;生产选的是方案 2,
    因为它买到的是隔离而不是峰值吞吐。
  • --layer-split 的三种形态(K / K1,K2 / auto)只在 --serve 下可用;
    --trim-stage-weights 必须配显式切分;auto 的成本模型是在别的机器上标定的,本机要复测。
  • --peer-device 是主从专家缓存层(副卡算自己持有的行,P2P 回传),
    与 --layer-split、--expert-cache-device1..3 互斥(约 :1945),因为会各放一份同专家。
  • 方案 3 的否决理由要分清两笔账:搬权重不可行(200–500 MB/token),
    搬激活行可行(2–3 MB/token);后者已由 CPU/RAM 池实现。
  • 9.3 tok/s 的教训:数字崩盘时先怀疑"资源争用"再怀疑"容量不足"——
    容量解释不了数量级,而 CPU 池在关键路径上时,抢核就是十倍的伤害。
全部章节