多 GPU 与多节点

--tp N 仅选择张量并行,在层内切分权重;--layer-split N 在支持的架构上选择整层放置。两种参数互斥,不支持的请求会在启动时失败。按层切分仅限单节点;只有支持分布式张量并行的架构才能通过 --tp-node-id / --tp-peers 跨节点运行。旧的按层切分命令需将 --tp N 改为 --layer-split N(环境变量:TENSORSHARP_LAYER_SPLIT_DEGREE=N)。

🧭

--tp N 仅选择张量并行,在层内切分权重;--layer-split N 在支持的架构上选择整层放置。两种参数互斥,不支持的请求会在启动时失败。按层切分仅限单节点;只有支持分布式张量并行的架构才能通过 --tp-node-id / --tp-peers 跨节点运行。旧的按层切分命令需将 --tp N 改为 --layer-split N(环境变量:TENSORSHARP_LAYER_SPLIT_DEGREE=N)。

什么时候需要它?

在 GGML 后端上,融合的按 rank block 计算图让 --tp 2 在本来就装得下单卡的模型上也能快于单 GPU —— Gemma 4 E4B Q8_0 上 51.7 对 37.3 tok/s(2× RTX 2000 Ada)。如果你要的是更高的并发吞吐而非更低的单流延迟,请先看 连续批处理——它默认开启,零成本即可尝试。

选择张量并行或按层切分

张量并行(--tp N)在每层内部切分投影,让 N 张 GPU 并行计算并通过集合通信归约结果。按层切分(--layer-split N)把完整层放到 N 张本地 GPU,在设备边界传递隐状态;它增加容量,但不保证吞吐提升。

Qwen 3.8 Flash Next 在 ggml_cuda 上对满足条件的 FFN 量化支持本地 --tp N,在 GGML CUDA/Vulkan 上支持 --layer-split N。DeepSeek V4.1 在 GGML CUDA 上支持 routed-MoE TP(并行度 2–8),注意力与缓存仍按层放置。DeepSeek V4 与 GLM 5.x 也支持本地整层放置,GLM 还有本地原生 TP。Muse Spark 的原生执行器在 GGML CUDA/Vulkan 上使用 --layer-split N,在 GGML CUDA 上也接受 --tp N,通过上游 ggml 的 meta 后端把每一层切分到 N 张 GPU 上;TS_MUSE_SPARK_NATIVE=0 与 GGML Vulkan 仍使用其逐算子路径上的注意力与专家 TP。MiniMax-M3.1 的原生执行器在 GGML CUDA 上两种模式都支持;它的 --tp N 镜像注意力与缓存并切分 FFN。两种参数互斥,不支持的模式会在模型/后端检查时被拒绝,这些整模型路径不支持多节点组。

# 完整层放到两张本地 GPU;服务端接受相同参数
TensorSharp.Cli/bin/TensorSharp.Cli --model qwen38-flash-next.gguf \
    --backend ggml_cuda --layer-split 2

配置文件等价写法:{ "layer-split": 2 }。环境变量:TENSORSHARP_LAYER_SPLIT_DEGREE=2。不能与张量并行或分布式参数组合;跨节点按层切分尚未实现。

张量并行的工作原理

每个 transformer block 被改写为一对互补的切分方式,因此每半个 block 只需要一次集合通信:

  1. 列并行投影

    QKV 与 gate/up 沿输出维度切分——注意力头或 FFN 中间维度分配到各 GPU。此处无需通信:每个 rank 只产出属于自己的那部分激活。

  2. 各 rank 独立计算

    注意力(或激活函数)在每张 GPU 上只针对该 GPU 拥有的头运行,使用该 GPU 自己的 KV 缓存。

  3. 行并行投影 + 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 下运行,Qwen-Image-2.1 的扩散 Transformer 也可以;异构层各有自己的切分策略。--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 使用专家切分。例外是 Bonsai2 经 PRISM 旋转的 qwen35 文件:它们需要单设备 GGML 后端,会拒绝 --tp。
Qwen 3.8 Flash Next本地 GGML CUDA--tp N 切分路由/共享 FFN 的中间通道与输出行,使用 FP32 gather;注意力、GDN、QSA 与 PLE 保持复制。加载时检查量化布局;UD-Q2_K_XL 的 IQ2_XS/IQ3_XXS FFN 会被 TP 拒绝。真机覆盖为 A40 上 UD-IQ4_XS 的 TP2/TP4;TP 下仍禁用前缀检查点。GGML CUDA/Vulkan 上也保留 --layer-split N。范围与模型卡证据 →
GPT OSS✅MoE 专家切分、attention sink、YaRN。GGML 后端同样可运行,但 GGML 上的 MoE 路径目前仍按 token 逐个遍历专家,尚未使用专家并行。
Nemotron-H✅MoE 专家切分;Mamba2 SSM 层在 rank 0 上计算后广播。GGML 上的 MoE 限制与 GPT OSS 相同。
Muse-Glimmer✅稠密模型,但有三处形状需要小心:融合的 [gate|up] 按段切分(连续切分会悄悄把整个 gate 交给一个 rank);逐头的 QK RMSNorm 是一维的 [head_dim] 向量,保持复制(Q norm 还折叠了 qk_scale_factor);注意力输出门按头列并行,并在按 rank 的区域内应用。两次 AllReduce 都作用在 matmul 的原始输出上,位于 1e-8 的 post-norm 之前 —— 在非线性 norm 之后再归约,会产生通顺但错误的输出。2 个 KV 头把并行度上限定为 --tp 2。
Muse Spark✅(仅本地)GGML CUDA:--tp N 运行原生整模型执行器,通过上游 ggml 的 meta 后端(即 llama.cpp -sm tensor 背后的机制)把每一层切分到前 N 张 CUDA GPU 上(用 CUDA_VISIBLE_DEVICES 选择),不修改 ggml。TensorSharp 的切分方案按 KV 组切分注意力(Q/门控与 K/V 的行、K/V 缓存、注意力输出投影的输入列),路由专家沿隐藏维度以 256 个元素为单位切分,输出头按词表行切分,norm、路由器与潜空间投影则在每张 GPU 上各放一份;每层以两次 all-reduce 结束。16 个 KV 组不必均分——--tp 10 时每张 GPU 分到一个或两个,并逐层轮换——但 N 超过 16、GPU 型号不一致、--n-cpu-moe N、TS_MUSE_SPARK_LAYER_SPLIT 与 TS_MUSE_SPARK_PIPELINE=1 都会在加载时被拒绝。在十张 A40 上它的 decode 为 22.63 tok/s,是 llama.cpp -sm tensor 的 1.40×,而长提示的 prefill 则是 --layer-split 10 更快(十张 A40)。TS_MUSE_SPARK_NATIVE=0 保留逐算子路径,GGML Vulkan 上的 --tp N 也走这条路径:按完整的 GQA 头组与整个专家划分到各 rank,路由器、潜空间投影、残差流与输出头在 rank 0 上执行,允许不均匀划分(例如九张 GPU),只要每个 rank 拥有一个 KV 组和一个专家。多节点 TP 会被拒绝。
MiniMax-M3.1✅(仅本地)GGML CUDA:--tp N(2 到 16 张相同型号的 GPU)在上游 ggml 的 meta 后端上用前 N 张 CUDA GPU 运行原生整模型执行器(用 CUDA_VISIBLE_DEVICES 选择;TENSORSHARP_TP_DEVICES 会被拒绝)。由于只有四个 KV 头,注意力、稀疏注意力索引器以及 K/V/索引键缓存在每张 GPU 上镜像,因此每张 GPU 能容纳的上下文更少(八张 A40 上为 45,824 token);稠密、路由与共享专家 FFN 切分,每层一次 all-reduce。--tp 下拒绝 --n-cpu-moe、DSpark 草稿器与 TS_MINIMAX_M3_MSA=kernel。GGML CUDA 上的 --layer-split N 是长提示模式。
DeepSeek V4.1 Flash部分支持,仅本地--layer-split N 选择整层放置。--tp N 在 GGML CUDA 上启用 routed-MoE TP(并行度 2–8);注意力与缓存仍按层放置。不支持完整注意力 TP 与分布式组。两种模式不能组合。历史完整检查点 TP 实测比按层切分更慢。
DeepSeek V4 Flash—(仅按层切分)机制不同:DSV4 的整模型执行器是把模型按层切分到各 GPU,而不是切分每个权重,因此没有逐层 AllReduce。--layer-split N 指定使用 N 张本地 GPU;未配置时默认单设备。切分以单卡最大负载为优化目标,并按各设备固定常驻项的实际落点计入——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。分段快路径由 Flash 这个布尔开关控制,因此 GLM-5.3(非 Flash)始终走组合调度器。对 Flash 而言,CPU MoE、tracing、部分 TS_GLM_TP_SHARD 切分、超额 rank 或缺少原生超连接内核时选择组合调度器回退,其中共享专家只在 rank 0 运行一次。用 --layer-split N 选择整层放置。
Hunyuan Dense—仅单设备;启动时拒绝张量并行、按层切分与分布式组。
DiffusionGemma—仅单设备;启动时拒绝张量并行、按层切分与分布式组。
Qwen-Image-2.1✅(仅本地,扩散 Transformer)在 ggml_cuda / ggml_vulkan 上,--tp N 按 Megatron 方式切分扩散 Transformer:每张 GPU 持有 32/N 个完整注意力头与 12,288/N 个 MLP 列,每个 block 中两次行并行乘积在各 GPU 间求和。LoRA 因子随其权重一起切分,每张 GPU 只缓存自己那部分头的前缀。N 必须整除 32 个头——单机上为 2、4、8 或 16——且切分每个权重时都不能拆开它的量化块,加载时会按权重类型逐一检查;仅实测过 2 张 GPU。文本编码器、视觉编码器与 VAE 留在第一张 GPU 上,多节点并行组会被拒绝。见下文实测。
Wan / MiniMax-H3(视频)—仅单设备;启动时拒绝张量并行、按层切分与分布式组。

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。

要求与约束

实测结果

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_02760 / 37.32488 / 51.7
Gemma 4 26B-A4B IQ4_XS1845 / 48.52537 / 51.2
Qwen 3.5-9B Q8_01461 / 23.1399 / 24.4
Qwen 3.5-35B-A3B IQ4_XS装不下184 / 18.1
Muse-Glimmer 30B UD-IQ2_XXS †1171 / 40.21569 / 63.2
Muse-Glimmer 30B Q8_0,28.2 GB †装不下1691 / 34.3

† 测于 2× RTX PRO 4000 Blackwell(各 24 GB,PCIe),而不是其他各行所用的那对 RTX 2000 Ada —— 两套硬件之间不能直接比较,只能各行与自己的单卡列比较。Muse-Glimmer 是这里唯一在两个阶段上都快过单卡的模型(prefill 1.34×、decode 1.57×);它的 Q8_0 没有单卡数字,因为 28.2 GB 的权重根本装不进 24 GB 的卡。

但这并不普适。在 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 能带来多少,取决于互连带宽以及一层里究竟有多少能拆。

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),--layer-split 2 对比单卡:

指标1 GPU--layer-split 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 干脆拒绝加载这个架构。所以这里传 --layer-split N 的理由是权重、缓存与上下文装不进一张卡,而不是吞吐。启动时会打印实际使用的模式与每张 GPU 的层数 / 字节划分。TS_Q4E_LAYER_SPLIT=20,28 可为每张 GPU 指定明确的层数(精神上等同 llama.cpp 的 --tensor-split),遇到无法满足的取值会抛错而不是默默忽略 —— 这很有用,因为自动均衡只按权重计价,看不到稍后才加载、并落在 GPU 0 上的视觉塔。

TP 下的 Qwen-Image-2.1 扩散 Transformer

两张分属不同 CPU 插槽的 NVIDIA A40(各 46 GB),它们之间的 peer access 不可用,NCCL 走的是共享内存传输。前缀缓存开启,步时不含第 1 步:

负载单 GPU 每步双 GPU 每步加速
生成 1024²,40 步1.107 s0.823 s1.34×
编辑,1 张参考图,1024²,40 步1.326 s0.934 s1.42×
生成 2048²,20 步6.817 s4.436 s1.54×
编辑,1 张参考图,2048²,20 步7.612 s4.849 s1.57×
ggml_vulkan 编辑,1024²,20 步(主机归约)2.670 s3.092 s0.86×

在 Vulkan 上归约要经过主机内存,其开销超过了切分省下的时间,因此在这套硬件上 --tp 反而更慢。TP 改变了每个 block 部分和的相加顺序,因此图像与单卡结果并不逐位一致(在 CUDA 上与单卡图像相比 PSNR 为 34.2–51.5 dB):构图与质量保持不变,但细节可能移动。细节见仓库中的 docs/models/qwenimage21_zh-cn.md 卡片。

TP 下的 CUDA 图捕获

一个张量并行 token 是几十次按 rank 的小提交,重放它们值约 45% 的 decode 吞吐 —— 因此 TP 下图捕获保持开启(用 TS_GGML_TP_CUDA_GRAPHS=0 关闭)。4×A40 实测:

模型关闭捕获开启捕获
Qwen 3.5-9B,--tp 488 tok/s128.5 tok/s
Qwen 3.5-35B-A3B,--tp 271.3 tok/s104.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_DEGREE1本机切分使用的 GPU 数(等价于 CLI 与服务端的 --tp)。
TENSORSHARP_TP_DEVICES0..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_SECONDS120节点向 peer 重试连接的时长。编排系统启动节点间隔较大时可调大。
TENSORSHARP_TP_RECV_TIMEOUT_SECONDS300peer 套接字的单次接收超时,让卡住的 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_THRESHOLD262144超过该元素数量时 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_MS10000探测 AllReduce 的完成期限,超时即判定集合通信不可用;0 关闭探测。
GGML_CUDA_AR_BF16_THRESHOLD1 MBggml 在多大载荷以上把 F32 集合通信转成 BF16。TensorSharp 提高了 ggml 自身的默认值(总是转换),使 decode 规模的归约保持精确;设为 0 则完全禁用转换。

启动日志会明确给出拓扑:本地并行组打印 Tensor parallelism: N GPUs (<设备名>),设备对被降级时打印 TP: P2P disabled… 或自检告警,每个节点在网格建立完成后打印一条 [TcpCommunicator] Rank r/N connected to all peers.。

集群内的共享状态

当多个服务端进程共同承载同一批负载时,有一类状态可以放到 Redis,而不是各自的进程内存中:

# Responses 存储放在一个共享 Redis 上
dotnet TensorSharp.Server.Host/bin/TensorSharp.Server.Host.dll --model model.gguf --backend cuda \
    --redis-url localhost:6379

故障排查

现象可能原因与处理
启动失败:请求的 TP 度超过设备数进程看到的 CUDA 设备少于 --tp 所要求的数量。检查 CUDA_VISIBLE_DEVICES 与驱动。
启动时提示不支持的模式请选择实现所请求模式的后端与架构。旧的整层放置命令需改为 --layer-split N;CPU/MLX 与单设备架构不能使用多 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 项目。