实战指南:异构显卡集群的 -ot 策略与调优)
ik_llama.cpp 多 GPU 分层卸载Layer Offloading实战指南异构显卡集群的 -ot 策略与调优【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp导读本文基于 ik_llama.cpp 社区讨论 532 - Guidance on GPU Layer Offloading Strategy 整理而成系统讲解在异构多 GPU 集群如 2×RTX 5090 2×RTX 4090上如何制定分层卸载策略。你将掌握--override-tensor-ot正则表达式定向卸载、MoE 模型专家张量的 GPU/CPU 分配、-amb/-fmoe/-mla/-rtr等关键参数的底层行为以及基于llama-sweep-bench的可复现基准测试方法论。所有结论均可在本仓库 common/common.cpp、src/llama.cpp 与 ggml/CMakeLists.txt 中找到源码依据。一、背景异构多卡场景下的卸载难题原讨论发起者 mtcl 的机器为 2×RTX 5090 2×RTX 4090 四卡异构集群目标是运行 Qwen3-235B-A22B 与 DeepSeek-R1-0528 这类巨型 MoE 模型。核心困惑有三点哪些层attention、feed-forward、MoE experts最值得放到 GPU 上如何在 5090 与 4090 之间最优分配ik_llama.cpp 是否有内置机制精确控制张量分布针对如何判断该卸载哪一层维护者 ubergarm 的回答直白且实用Offload early, offload often尽早卸载、尽量多卸载。如果模型能整体放入显存就直接全部卸载装不下时优先把最多的层塞进最快的 GPUKV cache 尽量靠近 attention 张量或统一放在单个主 GPU如--main-gpu 0上。二、核心工具-ot / --override-tensor精确控制张量归属这是 ik_llama.cpp 实现按张量名定向卸载的内置机制也是本讨论最核心的操作工具。其语法为-ot tensor_name_patternbuffer_type[,另一个pattern另一个buffer_type,...]参数解析位于 common/common.cpp实际解析函数为parse_buft_overridescommon/common.cpp。该函数会枚举当前后端注册的所有 buffer type如CUDA0、CUDA1、CPU然后将每个tensor_namebuffer_type对压入tensor_buft_overrides列表。关键行为tensor_name支持正则表达式底层按正则匹配张量名所以可以用blk\.[0-9]\.ffn这类模式批量匹配多个覆盖项用逗号分隔且每一项内部可含多个patternbuffer_type子句buffer type 不合法时会直接报错并列出所有可用类型方便排查拼写。2.1 为什么用正则按块block而非按单层分配对于 MoE 大模型Decoder 层编号从 0 开始每层的权重张量命名形如blk.N.attn_*、blk.N.ffn_*。讨论中的做法是用正则把层号区间切分给不同的 GPU例如把 Qwen3-235B 的 56 层切成四段。由于 5090 显存/算力强于 4090最快 GPU 分到的层段更宽这正是最快 GPU 优先塞满原则的落地。2.2 完整实战示例Qwen3-235B-A22B-128K2×5090 2×4090原讨论给出的完整命令已保留原始参数含注释说明CUDA_VISIBLE_DEVICES2,1,0,3 ./build/bin/llama-server \ --model /home/mukul/dev-ai/models/unsloth/Qwen3-235B-A22B-128K-GGUF/Q4_K_M/Qwen3-235B-A22B-128K-Q4_K_M-00001-of-00003.gguf \ --alias unsloth/Qwen3-235B-A22B-128K-Q4_K_M \ --ctx-size 65536 \ -ctk q8_0 -ctv q8_0 \ -fa \ -b 4096 -ub 4096 \ -fmoe \ --n-gpu-layers 100 \ -ot blk\.([0-9]|1[0-5])\.ffnCUDA0 \ -ot blk\.(1[6-9]|2[0-7])\.ffnCUDA1 \ -ot blk\.(2[8-9]|3[0-9])\.ffnCUDA2 \ -ot blk\.(4[0-9]|5[0-5])\.ffnCUDA3 \ --override-tensor expsCPU \ --parallel 1 \ --threads 56 \ --host 0.0.0.0 \ --port 10002逐项解读CUDA_VISIBLE_DEVICES2,1,0,3重排 CUDA 可见设备顺序。原讨论作者发现nvidia-smi与 CUDA 的设备编号不一致于是显式指定顺序保证CUDA0~CUDA3对应预期的物理卡这里首尾两张为 5090中间两张为 4090。-ctk q8_0 -ctv q8_0KV cache 的 K/V 均使用 q8_0 量化解析见 common/common.cpp显著降低长上下文此处 64K的 KV 显存占用。-fa启用 Flash Attention。-b 4096 -ub 4096batch 与 ubatch 均为 4096。-fmoe融合 MoE 的 up/gate 专家计算默认即开启可用-no-fmoe关闭见 common/common.h 与 common/common.cpp。--n-gpu-layers 100总卸载层数上限解析见 common/common.cpp。四条-ot按层号区间把各 block 的ffn 权重分别钉到CUDA0~CUDA3上。层段宽度不平均——5090 对应blk.0-1516 层与blk.40-5516 层4090 对应blk.16-2712 层与blk.28-3912 层体现快卡多分。--override-tensor expsCPU将 MoE 共享专家shared experts强制留在 CPU。注意这一项仅对含共享专家的模型如 DeepSeek有效Qwen3 没有共享专家ubergarm 明确提醒对于 Qwen 应删除expsCPU这一项否则可能匹配不到张量而产生歧义。2.3 完整实战示例DeepSeek-R1-0528IQ4_KS_R4CUDA_VISIBLE_DEVICES2,1,0,3 ./build/bin/llama-server \ --model /home/mukul/dev-ai/models/ubergarm/DeepSeek-R1-0528-GGUF/IQ4_KS_R4/DeepSeek-R1-0528-IQ4_KS_R4-00001-of-00009.gguf \ --alias ubergarm/DeepSeek-R1-0528-IQ4_KS_R4 \ --ctx-size 40960 \ -ctk q8_0 \ -mla 3 -fa \ -b 4096 -ub 4096 \ -amb 512 \ -fmoe \ -ngl 63 \ -ot blk\.[3-4]\.ffn_.*CUDA0 \ -ot blk\.[5-6]\.ffn_.*CUDA1 \ -ot blk\.[7-8]\.ffn_.*CUDA2 \ -ot blk\.[9]\.ffnCUDA3,blk\.1[0-1]\.ffnCUDA3 \ -ot expsCPU \ --parallel 1 \ --threads 56 \ --host 0.0.0.0 \ --port 10002与 Qwen 命令的差异点-mla 3DeepSeek 使用 MLAMulti-head Latent Attentionmla_attn取值 0~3默认即为 3the best of both worlds见 common/common.h。1 表示 KV^T 缓存2 表示仅 K 缓存。-amb 512限制 self-attention 中间计算缓冲不超过 512 MiB详见下文第四节。-ot blk\.[3-4]\.ffn_.*CUDA0注意正则ffn_.*而非ffn因为 DeepSeek 的专家张量名为ffn_gate_exps、ffn_up_exps、ffn_down_exps等带后缀的形式。最后一条-ot在一个参数内用逗号合并了两个 patternblk.[9].ffn与blk.[10-11].ffn都归 CUDA3演示了逗号分隔多目标的用法。-ot expsCPUDeepSeek 有共享专家shared expert将其留在 CPU与 2.2 节中 Qwen 命令形成对照。三、策略要点哪些层适合 GPU、哪些适合留在 CPUubergarm 在原讨论中给出的分层经验结合 MoE 模型结构attention 与共享专家shexp优先全部放 GPUattention 是每 token 必经的密集计算GPU 收益最高。dense ffn 层通常最先被挤到 CPU因为其权重体积大、占用显存多且主要是大矩阵乘适合 CPU高带宽内存处理前提是你的量化对 CPU 友好如_r4系列行交错量化或使用-rtr运行时重打包。不要让-fmoe拆散 up/gate-fmoefused_moe_up_gate默认开启会把ffn_(gate|up)融合成一次算子merged/repacking up/gate experts逻辑在加载与建图中均有体现llama-sweep-bench的日志过滤列表也包含 merging up/gate in layer、repacking up/gate experts weight in layer见 sweep-bench.cpp。因此不要试图用-ot把ffn_gate和ffn_up分到不同设备这会破坏融合路径。部分卸载时性能受制于内存带宽如果大量专家权重仍在 CPU/RAMtoken 生成速度会被系统内存带宽卡住讨论中多位用户实测 7~15 t/s 量级只有完全 GPU offload 才能绕开该瓶颈。这是能整卡就整卡原则的物理原因。随机卸载也可能有效Currently I have been randomly offloading whatever I can——ubergarm 表示这种方法很多时候效果也不差正式优化前不必焦虑。3.1 辅助手段查看各层权重体积再决定塞什么Ph0rk0z 建议先查看模型各层权重文件大小据此规划显存分配——不是每个 block 的层大小都一样attention 层明显小于 ffn 层MoE 的 experts 权重占比最大。把体积数据打印出来后能塞多少就塞多少通常塞得越满 t/s 越高适当调小-amb可压缩中间缓冲为多塞一层腾出显存。模型加载时 ik_llama.cpp 会打印 Layer sizes 与各层张量信息llama-sweep-bench默认会过滤掉这些刷屏日志见 sweep-bench.cpp而正常运行时可见。3.2 内置的自动适配能力除手动-ot外ik_llama.cpp 还提供基于显存自动卸载的内置能力--fit-margin Nauto-fit 卸载时的安全余量MiB解析见 common/common.cpp-gfm / --gpu-fit-margin gpu,margin,...按 GPU 分别设置余量common/common.cpp-ncmoe / --n-cpu-moe N指定多少层中 MoE 张量保留在 VRAMcommon/common.cpp-cmoe / --cpu-moe等价于把所有 MoE 张量放 CPUncmoe 999common/common.cppauto-fit 当前主要针对 MoE 模型Note: auto-fit currently only works for MoE models见 src/llama.cpp。四、-amb深入解析attention 中间缓冲上限-amb / --attention-max-batch N默认 256见 common/common.h是讨论中花了大量篇幅澄清的参数。维护者 ikawrakow 亲自给出的权威说明逐条整理如下生效范围有限-amb只有在两种情况才有意义——(a) MLA 模型DeepSeek 系(b) 未启用 Flash Attention 且使用 GQA 的模型。对已开 FA 的非 DeepSeek 模型如 Qwen3 -fa完全无效。源码中分块逻辑确实以amb 0 kv_f32_size amb为条件且仅在 MLA 图构建路径is_mla_model()中出现见 src/llama.cpp。不影响精度它只改变 self-attention 的计算分块方式不改变数值结果。原理把 self-attention 计算按 attention head 数切成若干块使K*Q或 MLA 场景下 K-cache ×attn_k_b所需的中间缓冲不超过指定大小。分块下限是一个 head——当 chunk 缩小到单个 head 后无法继续。只约束 self-attention 相关缓冲计算图中其他算子如 ffn、norm的临时缓冲不受-amb控制所以实际 compute buffer 几乎总大于指定值。理论上更慢把一次大矩阵乘拆成多次小矩阵乘理论上必有开销因此该选项默认关闭不传即为不设限但实践中 DeepSeek-R1/V3 用户普遍观察不到可测差异。ikawrakow 自测 DeepSeek-Lite16B同款 attention 机制在完全 GPU 卸载与纯 CPU 两种场景下各约 3% 性能损失。取值建议ubergarm 建议常规-amb 512需要再挤一点显存时可降到-amb 256但一般不要低于此值Ph0rk0z 实测-amb 64也能跑通仅在高上下文时损失少量 t/s且测试 1024/2048 并未带来额外收益。另外注意 common/common.cpp 中有一个下限保护传入大于 0 但小于 128 的值会被强制修正为 128。五、编译期选项面向多卡与特定量化的 CMake 开关ubergarm 推荐的多 GPU 构建命令原讨论原文适用于 DeepSeek 等模型cmake -B ./build -DGGML_CUDAON -DGGML_BLASOFF -DGGML_SCHED_MAX_COPIES1 -DGGML_CUDA_IQK_FORCE_BF161 -DGGML_CUDA_F16ON cmake --build ./build --config Release -j $(nproc)各开关在 ggml/CMakeLists.txt 中的定义与默认值CMake 选项默认值作用说明GGML_SCHED_MAX_COPIES1CACHE STRING见 L110流水线并行下输入张量的最大拷贝份数多卡调度相关保持默认1通常即可GGML_CUDA_IQK_FORCE_BF16OFFL122无可用 MMQ kernel 时强制 CUDA 走 bf16 cuBLAS运行 DeepSeek 模型时建议打开编译宏注入见 ggml/src/CMakeLists.txtGGML_CUDA_F16OFFL125部分计算使用 16 位浮点面向新实验性_kt量化其他场景可不开宏注入见 ggml/src/CMakeLists.txtubergarm 的个人偏好是保持GGML_BLASOFF、不折腾实验性编译器Intel 编译器与 AMX 相关开关。这些是作者个人经验建议读者以自身硬件实测为准。六、设备编号错位CUDA_VISIBLE_DEVICES与CUDA_DEVICE_ORDER原讨论中作者遇到nvidia-smi显示的 GPU 0/3 为 5090但 CUDA 视角下 5090 是 GPU 2/3 的错位问题。解决方案有两层显式重排用CUDA_VISIBLE_DEVICES2,1,0,3让 CUDA 视角的设备顺序符合预期随后-ot中的CUDA0~CUDA3就对应重排后的物理卡。这是讨论中实际采用的做法。改变枚举顺序ubergarm 提到可用CUDA_DEVICE_ORDERPCI_BUS_ID让 CUDA 按 PCIe 总线地址而非默认的枚举顺序排列设备从而对齐nvidia-smi的编号。此外-mg / --main-gpu N默认 0解析见 common/common.cpp语义见 common/common.h用于指定承担 KV 与中间结果等小张量的主 GPU-ts / --tensor-splitcommon/common.cpp则用于按比例把按行切分的张量分发到多卡适合 row 分片模式。七、可复现基准llama-sweep-bench 与实测数据ubergarm 明确建议不要迷信最佳实践用llama-sweep-bench做 A/B 对比找到适合你特定硬件的方案。该工具按 ubatch 窗口扫描整个上下文逐窗口输出 PPprompt processing与 TGtoken generation指标不把整段上下文均值化从而能观察性能随上下文长度N_KV的变化曲线说明见 examples/sweep-bench/README.md。讨论中 kirnat 提供了与提问者相同主板/CPU 配置下的完整实测单张 Blackwell 6000 Pro Xeon 8480 级 ES CPU8×48GB DDR5-4800专家留在 CPUllama-sweep-bench \ -m ./models/unsloth/R1/DeepSeek-R1-0528-UD-Q4_K_XL-00001-of-00008.gguf \ -fa \ -t 52 \ -ngl 61 \ -ot blk\.[0-9]\.ffn_(gate)_exps.CPU \ -ot blk\.1[0-9]\.ffn_(gate)_exps.CPU \ -ot .ffn_(up|down)_exps.CPU \ -mla 1 \ -rtr \ -fmoe \ -ctk q8_0 \ -ctv q8_0 \ -b 1024 \ -ub 1024 \ -c 32768结果截取部分上下文段PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s102425606.705152.7116.38615.62102425610246.743151.8516.55815.46102425620486.811150.3516.60515.42102425640966.962147.0916.69615.33102425681927.241141.4217.05715.011024256163847.833130.7317.54514.591024256245768.464120.9918.18714.081024256317448.979114.0418.45713.87读数要点该场景把所有 experts含 gate/up/down留在 CPU仅 61 层主体上 GPUTG 稳定在 14~15.6 t/s随 N_KV 增长缓慢下滑PP 从 152 t/s 逐步降至 114 t/s。这正是专家在 CPU → TG 受内存带宽限制的典型曲线对照前文 Panchovix 在 5090 上同量级量化约 200~300 t/s PP 的数据。命令里-ot .ffn_(up|down)_exps.CPU用点号开头匹配任意前缀的专家张量名是正则写法的又一实例-rtr--run-time-repack见 common/common.cpp会把 RAM 中的张量重打包为行交错格式以加速 CPU 侧矩阵乘。7.1 基准测试注意点部分卸载 非_r4/_kt量化时-rtr重打包可能让k-quantsQ3_K/Q4_K/Q5_K/Q6_K 等在 CUDA 上无行交错实现导致这些张量的计算被固定到 CPU反而拖慢 PP。项目 README 对此有明确警告If you are running hybrid CPU/GPU inference for MoE models with all or some experts left on the CPU, do not use -rtr unless you know what you are doing见 README.md。测量存在随机方差需多轮观察趋势而非单点值系统内存缓存模型后反复加载试参很方便最终定稿前建议清空缓存复测最优配置Ph0rk0z 经验。八、现实案例192GB 显存下 Qwen 与 DeepSeek 的差异讨论后期 mtcl 升级为 2×Blackwell 6000 Pro合计 192GB VRAM实测结果极具参考价值Qwen3-235B 完全 GPU 卸载PP 超过 1000 t/s生成 50~58 t/s128K 长上下文可跑DeepSeek MoE 部分卸载生成速度仅 12~13 t/s即使换用 ik_llama.cpp 与 IQ3 量化也只到 15 t/s 左右。社区的一致解释DeepSeek 模型体量更大其 IQ2_KT 量化接近 192 GiB纯 2×6000 Pro 放不下需要 IQ1_S IQ2_KT 混合量化才有希望整卡装载因此大量专家权重残留在 CPU/RAMTG 被内存带宽锁死——这与 kirnat 的 sweep-bench 曲线相互印证。这也再次回到核心结论MoE 模型要么尽量整卡要么接受部分卸载带来的 t/s 上限优化的重点是让最快 GPU 塞满、KV/attention 不落地、专家尽量少留 CPU。九、策略总结与操作清单综合讨论与源码异构多 GPU 卸载的推荐流程为确认设备顺序用CUDA_VISIBLE_DEVICES或CUDA_DEVICE_ORDERPCI_BUS_ID统一nvidia-smi与 CUDA 视角快卡排在CUDA0。了解模型结构查看各层张量体积加载日志的 Layer sizes区分 attention、ffn、experts、共享专家。先整卡、再快卡、后 CPU能全卸载就--n-gpu-layers 999不能则用多条-ot正则把层段按快卡多分原则钉到CUDA0~Nattention/shexp 优先 GPUdense ffn 可留 CPU-fmoe保持默认不拆 up/gate。按模型调整参数DeepSeek 系加-mla 3 -amb 512必要时-amb 256Qwen 系删除expsCPUKV cache 用-ctk/-ctv q8_0压缩长上下文开销混合卸载时慎用-rtrk-quants 会绑定 CPU 计算。编译对齐DeepSeek 建议-DGGML_CUDA_IQK_FORCE_BF161_kt新量化可开-DGGML_CUDA_F16ONGGML_SCHED_MAX_COPIES1保持默认。用数据说话llama-sweep-bench逐上下文窗口做 A/B对比不同-ot分布、-amb取值与批量大小观察 N_KV 增长下的 PP/TG 曲线后再定型。相关讨论与工具链接讨论 477DeepSeek-R1-0528 ik quants、llama-sweep-bench 说明、参数解析实现、CMake 编译开关。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考