双卡 VRAM 失衡分析:Qwen3.6-35B-A3B on 2×V100

环境

  • 模型: Huihui-Qwen3.6-35B-A3B-Claude-4.7-Opus-abliterated.i1-Q4_K_M.gguf
  • 架构: qwen35moe(Qwen3.5 MoE Hybrid SSM)
  • 量化: Q4_K(4.88 BPW),文件 19.70 GiB
  • 参数量: 34.66 B(A3B 激活)
  • 显卡: 2× Tesla V100-SXM2-16GB(NVLink)
  • 软件: llama.cpp build 9263(6a257d446)

层分配(tensor-split = 1,1)

  • CUDA0: 层 0-20(共 21 层:16 SSM + 5 full-attn)
  • CUDA1: 层 21-40(共 20 层:15 SSM + 5 full-attn)
  • output.weight: offloaded to GPU(分配到 CUDA1,因为它分到了最后一层)
  • token_embd.weight: CPU(CUDA_Host buffer 不可用),不在 GPU 上

显存分解(nvitop 实测 + 日志)

CUDA0CUDA1差距
模型权重(projected)10206 MiB9696 MiB+510 MiB
compute buffer2073 MiB1410 MiB+663 MiB
SSM 循环状态67 MiB59 MiB+8 MiB
KV cache(空负载)0 MiB0 MiB0 MiB
CUDA 上下文开销~15 MiB0 MiB~15 MiB
合计(实测 nvitop)13924 MiB12728 MiB+1196 MiB

实际差距 ~1.17 GiB(13924 - 12728),与日志中的 projected 数据完全对得上。

根因分析

1. compute buffer 差距(663 MiB)—— 主因

sched_reserve 分配计算图 scratch buffer 时,CUDA0 作为 pipeline 第一卡需要更大工作空间:

  • 处理 embedding 查找转换
  • 第一层 attention/SSM 的中间激活
  • 跨卡通信的 staging buffer

这是 pipeline parallelism 的固有特性,第一卡永远比后续卡多这些开销。

2. 模型权重差距(510 MiB)—— 次因

CUDA0 多分到 1 层(21 vs 20),每层权重 ~510 MiB @ Q4_K。

注意:之前怀疑是 token_embd.weight(~1GB)占了 0 卡,日志实际显示它走了 CPU(CUDA_Host 不可用),不在 GPU 上。

推论

  • embedding 是清白的 —— 不在 GPU,不是差距来源
  • output.weight 在 CUDA1 上(~1GB),反而让 CUDA1 多了一些权重——尽管如此 CUDA0 还是更多,说明前两个因素更强
  • KV cache 分布均等(各 5 层 full-attn),不是因素

优化建议

方案:偏移 tensor-split 补偿

CUDA0 有 ~663 MiB 的 compute buffer 固有开销,因此需要把更多层倾斜到 CUDA1 来平衡:

# 尝试 0.7,1.3   →  0卡 ~28% 层,1卡 ~72% 层
tensor-split = 0.7,1.3

# 如果仍然不平衡,继续加大 1 卡比例:
tensor-split = 0.5,1.5

预期效果:CUDA1 多扛 ~7-8 层 MoE 层后,两卡显存使用趋于相等,单卡能从 ~2220 MiB 空闲释放到各自 ~2800+ MiB。

附录:日志关键行索引

  • 层分配:行 312-352(第一次 fitting)/ 1401-1441(第二次实际加载)
  • embedding CPU fallback:行 1086
  • output layer offload:行 1087
  • model buffer 大小:行 1090-1092
  • KV cache:行 1148-1150
  • SSM RS buffer:行 1193-1194
  • compute buffer:行 1215-1217
  • memory breakdown:行 1222-1223