多 GPU 与多节点
一个模型,多张 GPU——当一台机器不够用时,还可以是多台机器。TensorSharp 按 Megatron-LM 列/行并行范式实现了张量并行,并通过点对点 TCP 网格把它扩展到多台主机。对于不切分权重的架构,同一个 --tp N 参数运行的是按层切分 —— 那是容量特性,而不是速度特性。
一句话上手:加上 --tp N 即可在本机 N 张 GPU 上运行 —— 架构支持时切分每一层,不支持时则按整层把模型摊开;在此基础上再加 --tp-node-id 与 --tp-peers 就能跨机器扩展。TensorSharp.Cli 与 TensorSharp.Server 都支持这些参数,可运行在 Direct cuda 后端以及 GGML CUDA / Vulkan 后端上。
什么时候需要它?
- 模型装不进一张 GPU。每张 GPU 只持有
1/N的切分权重,因此单卡显存放不下的模型,切到两张或四张卡上就能从容运行。 - 模型装不进一台机器。分布式 TP 把多台主机的 GPU 汇聚成一个逻辑并行组——2 节点 × 4 GPU 即全局并行度 8。
- 希望每个 token 获得更多显存带宽。每张 GPU 处理每个投影的自有切片,逐层矩阵乘是被切分的,而不是排队串行执行。
在 GGML 后端上,融合的按 rank block 计算图让 --tp 2 在本来就装得下单卡的模型上也能快于单 GPU —— Gemma 4 E4B Q8_0 上 51.7 对 37.3 tok/s(2× RTX 2000 Ada)。如果你要的是更高的并发吞吐而非更低的单流延迟,请先看 连续批处理——它默认开启,零成本即可尝试。
TP 与按层切分 —— 多卡机器上到底发生了什么
模型占用多张 GPU 有两种完全不同的方式,其中只有一种是 --tp。
- 张量并行(
--tp N)把每一层都放到每个 rank 上,切分的是层内部的权重,于是一次 decode 每张卡只读1/N的字节,各 rank 在每个层边界做 all-reduce。它是按需开启的 —— 不主动要求,就不会有任何张量被切开 —— 下表里的多数(但已不是全部)架构实现了它。 - 按层切分则是把整层放到不同设备上顺序执行:设备 0 算第 0..k 层,把隐状态交给设备 1,依此类推。切点并不是简单的
n_layer/N—— 加载器会测量每张卡的空闲显存,再做装箱以均衡「占用预算的最大比例」,因此配置不一致的一组卡也能被均匀填满。没有集合通信、层内也不切分,因此在慢速互连上不花额外代价,但同一时刻只有一张卡在忙。
按层切分适用于四个架构,它们都走各自的整模型执行器。DeepSeek V4 Flash(deepseek4)、DeepSeek V4.1 Flash(deepseek41)与 GLM 5.x(glm-dsa、glm5next)不加任何参数时就默认如此:它们会摊到所有可见 GPU 上,因为两者都装不进单卡。TS_DSV4_NGPU 与 TS_GLM_NGPU 用来限制使用几张卡。Qwen 3.8 Flash Next(qwen4exp)则是需要你开口的那一个:不传 --tp N 时它只用一张 GPU,而传了之后跑的是按层切分而非张量并行 —— qwen4exp 不切分任何权重,这也是 llama.cpp 为该架构提供的唯一多 GPU 模式(它的 -sm row 拒绝加载这个模型)。GLM-5.2、GLM-5.3 与 GLM-5.3-Flash 不传 --tp 时都继续使用该默认切分;在 GGML GPU 后端上,传入 --tp N 则选择仅支持本地单进程的原生张量并行 —— GLM-5.3(非 Flash)与 5.2 是同一套 79 block 的 glm-dsa 结构,走的就是 GLM-5.2 的加载路径,不需要新代码也不需要新参数。
在其他所有架构上,不加 --tp 就是只用一张 GPU。通用的逐算子路径与融合图路径都没有自动按层切分 —— 装不下单卡的模型会在加载时失败,而不会被悄悄摊开(那条带具体 --n-cpu-moe N 建议的拒绝信息,只有 DeepSeek V4 与 GLM 5.x 的整模型加载器会打印)。两种模式都不支持的架构现在会在 stderr 上明说并只用一张 GPU,而不是接受 --tp 之后默默让其余卡闲置。
所以在一台 3 卡机器上:只写 --backend ggml_cuda,GLM 5.x 与 DeepSeek V4 会用满三张卡(按层切分),Gemma 4 与 Qwen 3.8 Flash Next 只用一张;再加上 --tp 3,GLM-5.2、GLM-5.3 与 GLM-5.3-Flash 都切换到仅支持本地单进程的原生张量并行路径,DeepSeek V4 只是把按层切分限制在这 3 张卡上(那里的 --tp N 仅仅是个设备数,等同 TS_DSV4_NGPU),Gemma 4 通过层内切分用上三张卡,Qwen 3.8 Flash Next 则通过把整层摊开用上三张卡。在 GLM-5.2 上这个切换只会更慢,换来的仅仅是容量;在 GLM-5.3(非 Flash)上它只是一个被接受的模式,在任何大于 1 的并行度下都从未跑过,而且每个 rank 都会复制一份 MLA 与 indexer cache,KV 占用随 N 倍增、能装下的上下文相应变短;在 Qwen 3.8 Flash Next 上按层切分不快也不慢,换来的同样只是容量 —— 见实测结果。
张量并行的工作原理
每个 transformer block 被改写为一对互补的切分方式,因此每半个 block 只需要一次集合通信:
-
列并行投影
QKV 与 gate/up 沿输出维度切分——注意力头或 FFN 中间维度分配到各 GPU。此处无需通信:每个 rank 只产出属于自己的那部分激活。
-
各 rank 独立计算
注意力(或激活函数)在每张 GPU 上只针对该 GPU 拥有的头运行,使用该 GPU 自己的 KV 缓存。
-
行并行投影 + AllReduce
output 与 down 投影沿输入维度切分,因此每个 rank 得到一个部分和。一次 AllReduce 把这些部分和相加,block 结束时每个 rank 都持有完全相同的隐藏状态。
归一化层、词嵌入与 LM head 在各 rank 上复制而非切分——它们体积很小,复制可以从关键路径上省掉一次集合通信。
本地 TP —— 单进程,多张 GPU
本地 TP 运行在单个进程内。在 Direct cuda 后端上,由一个线程顺序向所有 GPU 下发命令,真正的并行由 CUDA stream 提供,AllReduce 通过点对点设备拷贝加逐元素加法内核完成。在 GGML 后端上,则由一个 rank 工作线程池并发驱动各张 GPU —— GGML 的一次算子调用同时完成提交与同步,顺序循环各 rank 会让显卡严格串行执行。
# CLI —— 把模型切分到 2 张 GPU
dotnet TensorSharp.Cli/bin/TensorSharp.Cli.dll --model model.gguf --backend cuda --tp 2
# GGML CUDA 后端上同理
dotnet TensorSharp.Cli/bin/TensorSharp.Cli.dll --model model.gguf --backend ggml_cuda --tp 2
# 指定各 rank 对应的物理 GPU
TENSORSHARP_TP_DEVICES=0,2 dotnet TensorSharp.Cli/bin/TensorSharp.Cli.dll \
--model model.gguf --backend ggml_cuda --tp 2
# 服务端 —— 同样的参数(TENSORSHARP_TP_DEGREE=2 环境变量也可用)
dotnet TensorSharp.Server.Host/bin/TensorSharp.Server.Host.dll \
--model model.gguf --backend ggml_cuda --tp 2
也可以写进配置文件:
{ "model": "model.gguf", "backend": "cuda", "tp": 2 }
分布式 TP —— 多台机器
每个节点在自己的本地 GPU 上运行独立进程,并通过带长度前缀的分帧协议与其他所有节点建立 TCP 连接。AllReduce 是分层的:先在节点内通过 P2P 做本地归约,再由各节点代表之间走 TCP 交换,最后逐层广播回去——因此只有 1/tp_local 的数据需要穿过网络。
# 2 节点 × 每节点 2 GPU = 全局 TP 度 4
# 节点 0:
dotnet TensorSharp.Cli/bin/TensorSharp.Cli.dll --model model.gguf --backend cuda --tp 2 \
--tp-node-id 0 --tp-peers "192.168.1.10:9500,192.168.1.11:9500"
# 节点 1 —— peer 列表相同,节点 ID 不同:
dotnet TensorSharp.Cli/bin/TensorSharp.Cli.dll --model model.gguf --backend cuda --tp 2 \
--tp-node-id 1 --tp-peers "192.168.1.10:9500,192.168.1.11:9500"
服务端可以作为这样一个集群的前端,但只能是节点 0 —— 即负责采样并对外提供 HTTP 的 driver。其余每个节点都运行一个 TensorSharp.Cli worker,使用相同的模型、后端与 peer 列表。TENSORSHARP_TP_* 环境变量可以替代这些参数。
# 节点 0 —— 服务端 / driver:
dotnet TensorSharp.Server.Host/bin/TensorSharp.Server.Host.dll --model model.gguf --backend cuda \
--tp 2 --tp-node-id 0 --tp-peers "192.168.1.10:9500,192.168.1.11:9500"
# 节点 1 —— CLI worker:
dotnet TensorSharp.Cli/bin/TensorSharp.Cli.dll --model model.gguf --backend cuda --tp 2 \
--tp-node-id 1 --tp-peers "192.168.1.10:9500,192.168.1.11:9500"
所有节点必须传入完全相同的 --tp-peers 列表(顺序也要一致),并各自使用唯一的 --tp-node-id(0 起始,对应该列表中的下标)。示例中的端口 9500 不是默认值:请自行选定,并确保所有节点之间可达。节点间流量没有鉴权也没有加密,请把集群放在可信的内网中。
节点通常由人工间隔数秒到数分钟启动,因此先起来的节点会持续重试对外连接(最长 120 秒),而不是在第一次连接被拒绝时就失败。网格建立完成后,每个节点会打印 [TcpCommunicator] Rank r/N connected to all peers.。
支持的架构
TensorSharp 中几乎每一种自回归架构都可以在 TP 下运行;异构层各有自己的切分策略。--tp 含义不同的那几个会单独标注。本表描述本地支持;GLM 5.x 没有任何一个版本声明支持分布式/跨节点 TP —— GLM-5.2、GLM-5.3 与 GLM-5.3-Flash 都会在模型构建之前直接拒绝 --tp-node-id/--tp-peers。
| 架构 | TP | 策略 / 说明 |
|---|---|---|
| Mistral 3 | ✅ | 融合与分离两种 QKV 布局,YaRN RoPE。 |
| Gemma 4 | ✅ | 稠密与 MoE、逐层 head 维度;多模态嵌入会注入 TP 路径,因此视觉 / 音频提示不会丢失。GGML 上融合的整模 MoE 主干在每个专家内部切分(gate/up 列并行、down 行并行),从而保留全局专家 id —— TS_GEMMA4_TP_FUSED_MOE=0 可回退到逐算子的整专家路径。Direct CUDA 使用逐专家切分。 |
| Qwen 3.5 / 3.6 family | ✅ | GatedDeltaNet 循环层采用块循环 V-head 归属 —— 每个 rank 维护自己的、常驻设备的 delta/conv 状态,循环路径无需跨 rank 通信。GGML 上整个 GDN block 作为一个打包的按 rank 内核运行,MoE 采用专家并行(每个 rank 持有整个专家,shared expert 按 Megatron 切分),LM head 为列并行且完全不需要集合通信。Direct CUDA 使用专家切分。 |
| Qwen 3.8 Flash Next | ✅(按层切分) | 机制不同:qwen4exp 不切分任何权重。--tp N 给每张 GPU 一段连续的完整层 —— 整 token 的计算图在设备边界处切开,隐状态在卡之间传递 —— 这也是 llama.cpp 为该架构提供的唯一多 GPU 模式(-sm row 拒绝加载它)。贪心输出与单卡运行逐字节一致、吞吐也没有变化,因此它是容量特性;见按层切分的实测。TS_Q4E_LAYER_SPLIT=20,28 可用明确的每卡层数覆盖自动均衡,遇到无法满足的取值会抛错而不是忽略。 |
| GPT OSS | ✅ | MoE 专家切分、attention sink、YaRN。GGML 后端同样可运行,但 GGML 上的 MoE 路径目前仍按 token 逐个遍历专家,尚未使用专家并行。 |
| Nemotron-H | ✅ | MoE 专家切分;Mamba2 SSM 层在 rank 0 上计算后广播。GGML 上的 MoE 限制与 GPT OSS 相同。 |
| DeepSeek V4.1 Flash | ✅(按层切分,+ 实验性 routed-MoE TP) | 按层放置是默认路径,也是有实测数据的路径,按每张卡的空闲显存分配;--tp N / TS_DSV4_NGPU 限制卡数。TS_DSV41_TP=N(2–8,且必须等于该卡数)还会把路由专家的 gate/up 沿 FFN 中间维、down 沿输入维切分,partial 经主机中转的 F32 缓冲归约;注意力、共享专家与各类 cache 仍按层放置。切分按块对齐且不等宽——2304 的中间维是 9 个 256 元素的 K-quant 块,两 rank 取 1280+1024,四 rank 取 768+512+512+512。首次完整 Q2_K 实测比按层切分更慢。注意力 TP 与分布式组尚未实现。在 --backend cpu 上(纯 C# 的 DeepSeek4CpuExecutor,它是正确性与可移植性路径,而非服务路径),分布式 TP 组以及任何非 0 的 TS_DSV41_TP 都会在读取任何一个权重之前被拒绝。 |
| DeepSeek V4 Flash | ✅(按层切分) | 机制不同:DSV4 的整模型执行器是把模型按层切分到各 GPU,而不是切分每个权重,因此没有逐层 AllReduce。--tp N 只是限制切分使用几张 GPU(等同 TS_DSV4_NGPU);不传参数时使用全部可见设备。切分以单卡最大负载为优化目标,并按各设备固定常驻项的实际落点计入——embedding 表在第一张卡,输出头与整个 DSpark 草稿器在最后一张卡。 |
| GLM 5.x | ✅(仅本地) | 仅限 GGML GPU 后端;整个家族 —— GLM-5.2、GLM-5.3 与 GLM-5.3-Flash —— 的原生 TP 都只支持本地单进程。GLM-5.2 切 MLA head 与每个路由专家内的隐藏行。GLM-5.3(非 Flash)与 5.2 是同一套 79 block 的 glm-dsa 结构 —— 256 个路由专家取 top-8 外加一个共享专家、带 lightning indexer 的 MLA、rope base 8e6 —— 因此它在 GLM-5.2 的路径上原样接受 --tp N,不需要新代码也不需要新参数;它的 MLA 与 indexer cache 是每个 rank 各复制一份,所以 KV 占用随 N 倍增,能装下的上下文也相应变短。它在大于 1 的并行度下从未跑过 —— 唯一有记录的算术是一台 8× A40 46 GB 机器上 --tp 8 每 rank 需要 41.7 GiB、装不下 —— 因此请把它当作一个被接受的模式,而不是一个已验证的配置。GLM-5.3-Flash 还会切 KDA head 并让每个 rank 持有对应的递归状态;其 MLA head 与路由专家隐藏行同样切分。注意力局部输出会在每次非线性 Sinkhorn 超连接之前归约。在 GLM-5.3-Flash 满足条件的分段快路径上,路由 MoE 局部输出先归约,随后每个 rank 在本地计算并加入其复制的共享专家;超连接、池化 indexer、router、norm、稠密层与 embedding 保持不切分并按 rank 执行,output norm / LM head 留在 rank 0。分段快路径与 TS_GLM_TP_FUSED 都由 Flash 这个布尔开关控制,因此 GLM-5.3(非 Flash)始终走组合调度器。对 Flash 而言,CPU MoE、tracing、部分 TS_GLM_TP_SHARD 切分、超额 rank 或缺少原生超连接内核时选择组合调度器回退,其中共享专家只在 rank 0 运行一次;TS_GLM_TP_FUSED=0 可强制该诊断回退。不传 --tp 仍默认按层切分。 |
| DiffusionGemma | — | 不适用(文本扩散采样器,非自回归 decode)。 |
| Qwen-Image-Edit | — | 不适用(MMDiT 图像生成)。 |
MoE 有两种切分方式。专家切分(Direct CUDA,以及各后端上的 GPT OSS / Nemotron-H)让每张 GPU 持有每一个专家权重的 1/tp,router 复制——因此无论 token 选中哪些专家,负载都保持均衡。专家并行(GGML 上的 Qwen 3.5/3.6)则划分整个专家,使每个 rank 每个投影仍然只需一次批量 ggml_mul_mat_id 调度,而不是按 (token, 专家) 循环。GGML 上的 Gemma 4 与 GLM 5.x 用的是第三种:Megatron 切分发生在每个专家内部(gate/up 列并行、down 行并行),于是每个 rank 仍然看得到完整的专家表,一个 token 选中的 id 在全局范围内依旧有效——这正是 ggml_mul_mat_id 的要求,因为它不接受同一 token 的选择里出现重复的专家 id。
要求与约束
- 仅限 GPU 后端。TP 可运行在
cuda、ggml_cuda与ggml_vulkan上;MLX 与 CPU 后端为单设备。 - 整除性。
numHeads、numKVHeads与intermediateSize都必须能被 TP 度整除;量化权重的行并行切分还要求ne0能被tp × blockSize整除。 - TP 下的批处理前向目前实现于 Mistral 3。MoE 模型(Gemma 4、Qwen 3.5/3.6、GPT OSS、Nemotron-H)在 TP 激活时回退到按序列前向,因此连续批处理在这些模型上的增益较小。
- 本地并行度 ≤ 可见 GPU 数。请求的 rank 数超过主机设备数时,启动阶段就会直接失败。
- 多节点仍走逐算子前向。融合的整模 TP 计算图是按 rank、单进程的;分布式运行会回退到逐算子执行。
实测结果
2× RTX 2000 Ada(各 16 GB,PCIe,无 NVLink),--backend ggml_cuda --tp 2,prefill 512 / decode 64,单位 tok/s:
| 模型 | 单 GPU | --tp 2 |
|---|---|---|
| Gemma 4 E4B Q8_0 | 2760 / 37.3 | 2488 / 51.7 |
| Gemma 4 26B-A4B IQ4_XS | 1845 / 48.5 | 2537 / 51.2 |
| Qwen 3.5-9B Q8_0 | 1461 / 23.1 | 399 / 24.4 |
| Qwen 3.5-35B-A3B IQ4_XS | 装不下 | 184 / 18.1 |
Decode —— TP 本该受益的访存瓶颈部分 —— 在 Gemma 4 E4B 上达到单卡的 1.39×,在 Qwen 3.5-9B 上为 1.06×,且两个 Gemma 4 模型的输出与单卡运行逐字节一致。Prefill 受计算约束且要承担集合通信开销,因此在单卡装得下的模型上持平或略低于单卡。Qwen 3.5-35B-A3B 在 16 GB 卡上根本装不下 —— 只有 TP 能跑,两张卡上的显存占用为 9.4 + 8.0 GB。
按层切分的实测
按层切分不切分任何权重,因此它既不该有开销,也不该有收益 —— 实测正是如此。2× A100-80GB,Qwen3.8-Flash-Next-UD-Q2_K_XL(73.4 GiB),--tp 2 对比单卡:
| 指标 | 1 GPU | --tp 2(按层切分) |
|---|---|---|
| 贪心输出 | 逐字节一致 —— SHA-256 相同 | |
| 显存 | 整个模型压在一张卡上 | 24.2 + 26.2 GB |
| Prefill | 约 1520–1550 t/s | 约 1520–1550 t/s |
| Decode | 约 56 t/s | 约 56 t/s |
llama.cpp 在同一台机器上表现相同:-sm layer 把 pp1536 / tg128 从单卡的 1094 / 61.2 提到双卡的 1200 / 61.5 —— prefill 约 10%,decode 几乎为零 —— 而 -sm row 干脆拒绝加载这个架构。所以这里传 --tp N 的理由是权重、缓存与上下文装不进一张卡,而不是吞吐。启动时会打印实际使用的模式与每张 GPU 的层数 / 字节划分。TS_Q4E_LAYER_SPLIT=20,28 可为每张 GPU 指定明确的层数(精神上等同 llama.cpp 的 --tensor-split),遇到无法满足的取值会抛错而不是默默忽略 —— 这很有用,因为自动均衡只按权重计价,看不到稍后才加载、并落在 GPU 0 上的视觉塔。
但这并不普适。在 3× RTX PRO 6000(PCIe,无 NVLink)上,GLM-5.2 UD-IQ2_XXS 以 prefill 2048 / decode 64 测得:按层切分 915.9 / 43.9 tok/s,而 --tp 3 只有 505.6 / 17.6——78 层里每层都要对 [6144, n_tokens] 的隐状态做两次 all-reduce,在 PCIe 主机上这笔开销超过了拆分省下的算力。TP 还要求每个 rank 各自持有一份全长缓存,能装下的上下文因此从 342,272 掉到 91,136 token;它也改变了归约顺序——对着录制的 llama.cpp 金标准,2-bit MoE 在 --tp 3 下复现 3/6 条提示,而按层切分复现 5/6。那里的 TP 是容量特性而非延迟特性。TP 能带来多少,取决于互连带宽以及一层里究竟有多少能拆。
TP 下的 CUDA 图捕获
一个张量并行 token 是几十次按 rank 的小提交,重放它们值约 45% 的 decode 吞吐 —— 因此 TP 下图捕获保持开启(用 TS_GGML_TP_CUDA_GRAPHS=0 关闭)。4×A40 实测:
| 模型 | 关闭捕获 | 开启捕获 |
|---|---|---|
Qwen 3.5-9B,--tp 4 | 88 tok/s | 128.5 tok/s |
Qwen 3.5-35B-A3B,--tp 2 | 71.3 tok/s | 104.1 tok/s —— 这正是 TP 输给还是赢过单卡的分界线 |
集合通信的传输方式同样靠实测而非能力标志位决定:启动时该组会验证所宣称的设备对之间的 peer copy 是否真的把数据送到,以及一次真实的 NCCL AllReduce 能否完成,然后选出通过检验的最快传输。有些主机(常见于虚拟化云实例)宣称支持 peer access 却从不兑现,此时会保留 NCCL 集合通信但禁用 peer 传输,而不是干脆放弃它——这在超过两张卡时尤为重要,因为那里用不上 pinned-host 流水线,替代方案是每个层边界都经主机内存归约(4×A40,Qwen 3.5-9B Q8_0 decode:53.5 → 75.1 tok/s)。
调优与诊断
本地 AllReduce 优先使用 CUDA 点对点(P2P)DMA。启动时,并行组会为每一对报告支持 P2P 的设备启用 peer access,随后做一次往返自检——部分拓扑(挂在某些 PCIe 交换机后的 L4、开启 IOMMU 的主机、BAR1 窗口过小)虽然声称支持 P2P,却会静默传输损坏数据,未通过自检的设备对会被永久降级为主机中转。完全不支持 peer access 的硬件(A16 vGPU 配置、大多数消费级显卡)则从一开始就走主机内存中转。这些回退都是自动的;下面的开关用于人为强制走某条路径以便定位问题。
| 变量 | 默认值 | 作用 |
|---|---|---|
TENSORSHARP_TP_DEGREE | 1 | 本机切分使用的 GPU 数(等价于 CLI 与服务端的 --tp)。 |
TENSORSHARP_TP_DEVICES | 0..tp-1 | 各 rank 使用的 GPU 序号,例如 0,2。用于 GGML 后端。 |
TENSORSHARP_TP_NODE_ID | 未设置 | 本节点的 0 起始编号(等价于 --tp-node-id),需与 peer 列表一起设置。 |
TENSORSHARP_TP_PEERS | 未设置 | 所有节点的 host:port 列表,逗号分隔(等价于 --tp-peers)。 |
TENSORSHARP_TP_CONNECT_TIMEOUT_SECONDS | 120 | 节点向 peer 重试连接的时长。编排系统启动节点间隔较大时可调大。 |
TENSORSHARP_TP_RECV_TIMEOUT_SECONDS | 300 | peer 套接字的单次接收超时,让卡住的 peer 直接让集合通信失败,而不是挂在操作系统 TCP keepalive(往往 2 小时以上)上。 |
TENSORSHARP_TP_DISABLE_P2P | 关闭 | 1 表示所有跨 GPU 传输一律经主机内存中转——与无 P2P 硬件的路径完全一致。用于判断某个多 GPU 缺陷是否出在 P2P DMA 路径上。 |
TENSORSHARP_TP_HOST_ALLREDUCE | 关闭 | 1 表示本地 AllReduce 改为设备→主机、CPU 求和、主机→设备。更慢,但与已知可靠的多节点归约路径完全一致。 |
TS_GGML_TP_DEVICE_AR_THRESHOLD | 262144 | 超过该元素数量时 AllReduce 走 ggml 的设备集合通信,否则在主机内存中归约。GGML 的激活本来就在主机内存里,小载荷在那里求和更划算。 |
TS_GGML_TP_PARALLEL | 开启 | 0 表示顺序而非并发地驱动各 rank —— 诊断用,会显著变慢。 |
TS_GGML_TP_CUDA_GRAPHS | 开启 | 0 关闭多 GPU 运行下的 CUDA 图捕获。默认开启,因为一个张量并行 token 是几十次按 rank 的小提交,重放远比重新下发便宜(见上文)。该开关会在首次调用后端之前被翻译成原生的 GGML_CUDA_DISABLE_GRAPHS,因为 ggml 在首次使用时就会锁定该值。 |
TS_GEMMA4_TP_FUSED_MOE | 开启 | 0 表示 Gemma 4 从融合的 MoE 主干回退到逐算子的整专家路径。 |
GGML_CUDA_ALLREDUCE | 自动 | nccl / internal / none,直接透传给 ggml 的集合通信选择。显式设置会同时跳过 NCCL 启动前探测。 |
TS_GGML_TP_AR_PROBE | 开 | 模型加载前对 NCCL 集合通信做行为探测:一些云主机声称支持 GPU P2P 但数据永远送不到,NCCL 的第一次 AllReduce 会让两块 GPU 永远空转——典型症状是 TP 模型加载卡死在 kernel warmup。探测失败时 TensorSharp 自动改走 ggml 的钉页主机内存 internal 管线。0 跳过探测;force 忽略缓存的按主机判定(~/.cache/tensorsharp/tp-collective-probe)。 |
TS_GGML_TP_AR_PROBE_MS | 10000 | 探测 AllReduce 的完成期限,超时即判定集合通信不可用;0 关闭探测。 |
GGML_CUDA_AR_BF16_THRESHOLD | 1 MB | ggml 在多大载荷以上把 F32 集合通信转成 BF16。TensorSharp 提高了 ggml 自身的默认值(总是转换),使 decode 规模的归约保持精确;设为 0 则完全禁用转换。 |
启动日志会明确给出拓扑:本地并行组打印 Tensor parallelism: N GPUs (<设备名>),设备对被降级时打印 TP: P2P disabled… 或自检告警,每个节点在网格建立完成后打印一条 [TcpCommunicator] Rank r/N connected to all peers.。
集群内的共享状态
当多个服务端进程共同承载同一批负载时,服务端可以选择把两类状态放到 Redis,而不是各自的进程内存中:
- KV 缓存层——分页 KV 块被持久化,因此某个进程算出的前缀可以被另一个进程复用(
--redis-url/--paged-kv-redis-url,环境变量TS_KV_CACHE_REDIS_URL;条目在TS_KV_CACHE_REDIS_TTL_MINUTES后过期,默认 24 小时)。 - Responses API 存储——OpenAI Responses 存储从内存变为持久化且共享(
TS_RESPONSES_STORE_REDIS_URL;--redis-url一次性设置这两项)。
# 两个层共用一个 Redis
dotnet TensorSharp.Server.Host/bin/TensorSharp.Server.Host.dll --model model.gguf --backend cuda \
--redis-url localhost:6379
# 仅 KV 缓存,TTL 12 小时
dotnet TensorSharp.Server.Host/bin/TensorSharp.Server.Host.dll --model model.gguf --backend cuda \
--paged-kv-redis-url localhost:6379 --paged-kv-redis-ttl 720
故障排查
| 现象 | 可能原因与处理 |
|---|---|
| 启动失败:请求的 TP 度超过设备数 | 进程看到的 CUDA 设备少于 --tp 所要求的数量。检查 CUDA_VISIBLE_DEVICES 与驱动。 |
指定了 --tp 2,模型却只加载到一张 GPU | 后端是 mlx、cpu 或 ggml_cpu/ggml_metal。TP 适用于 cuda、ggml_cuda 与 ggml_vulkan。若后端没问题,请看 stderr:既不支持张量并行也不支持按层切分的架构会打印一条提示并只用一张 GPU,而不是直接失败。 |
| 某个维度无法被 TP 度整除 | 选择能整除 numHeads、numKVHeads 与 intermediateSize 的并行度,通常取 2 的幂。 |
| 节点一直等待 peer | 各节点的 peer 列表、顺序或端口不一致,或者防火墙拦截了该端口。若只是节点启动间隔较久,调大 TENSORSHARP_TP_CONNECT_TIMEOUT_SECONDS。 |
| 只有多 GPU 时输出乱码 | 怀疑 P2P DMA 路径。先用 TENSORSHARP_TP_HOST_ALLREDUCE=1 重跑,再试 TENSORSHARP_TP_DISABLE_P2P=1;如果输出恢复正常,问题出在该拓扑的 peer DMA 上。 |
| 多 GPU 比单 GPU 更慢 | Prefill 受计算约束且要承担集合通信开销,在本来就装得下单卡的模型上可能落后于单卡。GGML 后端上的 decode 应当更快 —— 如果不是,请确认融合路径处于开启状态(未设置 TS_GEMMA4_TP_FUSED_MOE 与 TS_GGML_TP_FUSED_MATMUL),并确认是单进程运行,因为多节点会回退到逐算子前向。另外,在没有 NVLink 的 PCIe 主机上,层本身就不好拆的模型无论怎么调都可能更慢——GLM 5.x 每层需要两次 all-reduce,GLM-5.2 在 --tp 3 下实测就是比按层切分慢;GLM-5.3(非 Flash)从未在大于 1 的并行度下跑过,同样的推理对它成立,但背后没有实测数据。那里请把 TP 当作容量手段而不是提速手段。 |
仓库中的参考资料:USAGE_zh-cn.md → 张量并行与分布式推理、FEATURES_zh-cn.md,以及 TensorSharp.Distributed 项目。