性能参数配置分析(2026-05-23)

硬件平台

  • GPU: 2× Tesla V100-SXM2-16GB (通过双卡显卡坞连接,NVLink 互联)
  • CPU: Intel Xeon E5-2680 v4 @ 2.40GHz (14核/28线程)
  • 内存: 39GB DDR4
  • llama.cpp: v9263 (6a257d446), GCC 13.3.0, CUDA 13.0, Driver 580.126.20

模型配置概要

项目主模型 (Qwen3.5-27B)Embedding 模型
模型文件Huihui-Qwen3.6-35B-A3B-Claude-4.7-Opus-abliterated.i1-Q4_K_M.ggufQwen3-Embedding-0.6B.Q4_K_M.gguf
参数量34.66B595M
文件大小21.2 GiB390 MiB
上下文225,280 tokens (训练上限 262,144)8,192 tokens (per slot 2,816)
GPU 分配全部层 (tensor-split 1,1)❌ 彻底禁用 (device = none)
KV Cacheq8_0, flash-attn, kv-offload, kv-unifiedN/A
量化精度Q4_K_MQ4_K_M
推理模式Reasoning on, budget=1024Pooling=cls

GPU 显存分配

Device=none 前(旧配置)

GPU总量已用占用率说明
GPU 016,384 MiB15,045 MiB91.8%主模型 ~13,980MiB + Embed 734MiB + Xorg
GPU 116,384 MiB13,101 MiB80.0%主模型 ~12,776MiB + Embed 308MiB + Xorg

Device=none 后(预期)

GPU说明
GPU 0预计 ~14,311 MiB(释放 734MiB),空余 ~2 GiB
GPU 1预计 ~12,776 MiB(释放 308MiB),空余 ~3.6 GiB

两卡合计释放约 1 GiB 显存。

推理性能实测

主模型 (Qwen3.5-27B alias) 性能数据

指标请求1(冷启动)请求2(已预热)分析
Prompt Eval13 tokens / 143.59ms264 tokens / 604.91ms
PP 速度90.54 t/s (11.05 ms/tok)436.43 t/s (2.29 ms/tok)⚠ 请求1 被初始推理拉慢
TG 速度98.62 t/s (10.14 ms/tok)102.94 t/s (9.71 ms/tok)稳定 ~100 t/s
Reasoning Budget1024 → 自然结束 (~100 tok)1024 → 自然结束 (~100 tok)推理预算充足
总耗时1,745.67ms (171 tok)2,402.14ms (449 tok)含推理 token
Graphs Reused156 次复用339 次复用计算图缓存生效

关键性能结论

  • Text Generation 稳定在 ~100 t/s:35B 模型在双 V100 上达到 100 t/s 是合理的,NVLink 互联使跨卡通信延迟更低,tensor-split 效率更高。
  • Prompt Processing 预热后达 436 t/s:264 tokens 仅 605ms,说明 flash-attn + q8_0 KV cache 对长上下文 prompt 处理有效。
  • 冷启动性能差:首次请求 PP 仅 90 t/s,因为触发模型加载(从磁盘加载 21GB GGUF 到显存)和推理预算初始化。--no-warmup 跳过了预热步。
  • Reasoning Budget 自然结束:2 次请求推理预算均在 ~100 decoded tokens 时自然结束,未用满 1024 预算,说明模型有能力自行判断推理深度。

KV Cache 量化精度分析:q4 vs q8

KV Cache 显存构成

在当前 225K 上下文的配置下,KV cache 是显存占用的第二大来源(仅次于模型权重 26.8 GiB)。其大小取决于:

KV cache per token = 2 (K+V) × n_layers × d_head × n_kv_heads × dtype_bytes

Qwen3.6-35B 为 GQA 架构,n_kv_heads 远小于 n_heads,KV cache 相对较小。实测 slot checkpoint 中 260 tokens 占 62.813 MiB,其中包含 KV cache 及 slot 管理元数据。

支持的数据类型

类型每元素大小性能表现质量表现适用场景
f162 bytes基线无损调试/质量验证用
q8_01 byte⬇ 50% 显存,速度≈f16极轻微损失(PPL +0.01~0.05)当前配置,推荐作为平衡点
q4_00.5 byte⬇ 75% 显存,速度≈q8轻度损失(PPL +0.1~0.3)显存紧张时的过渡方案
q4_10.5 byte同 q4_0略好于 q4_0(启用 min)相比 q4_0 提升不大
iq4_nl0.5 byte⬇ 75% 显存,稍慢于 q4_0接近 q8_0(PPL +0.05~0.1)q4 中最推荐,非线性量化保留 outlier

q4 vs q8 关键差异分析

  • 显存节省:q4(含 iq4_nl)比 q8 节约 50% 的 KV cache 显存。q8 比 f16 节约 50%。从 f16→q8→q4 是逐级半减的关系。
  • 质量影响
    • q8_0:多数评测中与 f16 无显著差异,对长上下文推理质量几乎无影响
    • q4_0:在短上下文(<8K)下损失可忽略,长上下文(>32K)下累积误差可能影响检索准确率
    • iq4_nl:通过非线性量化保留 KV cache 中的异常值,长上下文下质量显著优于 q4_0,接近 q8_0
  • 速度差异
    • PP(Prompt Processing):q4 因数据量更小,内存带宽压力降低,可能比 q8 略快
    • TG(Text Generation):V100 没有原生 int4 tensor core,q4 反量化开销稍高。实测差距通常在 3-5% 以内
    • flash-attn 对量化精度的敏感性:flash-attn 算法在 q8 和 q4 上的加速比几乎一致
  • 与 flash-attn 的交互:flash-attn 处理的是注意力计算的内存访问模式,不直接涉及 KV cache 的存储格式。q4+q8 结合 flash-attn 兼容性良好。但如果 flash-attn 的 tile size 与 q4 块大小不匹配,可能影响加速效果。

本机推演与建议

配置KV cache 估算占用显存余量提升推荐度
当前 q8_0基线✅ 质量与速度的平衡点
iq4_nl~q8_0 的 50%释放约 1.5-2 GiB/卡(需要更大余量时首选)
q4_0~q8_0 的 50%同上🟡 可接受(长上下文质量略降)
q8_0 + 降低 ctx65K 时约为 225K 的 29%释放 4-6 GiB/卡(与 q4 组合效果更佳)

结论:当前 q8_0 配置是合理的平衡点。如果 device=none 释放 ~1 GiB 后显存仍有压力,优先考虑 iq4_nl(质量损失最小)或 降低 ctx-size 到 65K(按你 50K+8K 的实际用量)。q4_0 在长上下文下的累积误差可能影响 RAG 检索准确率,不推荐。

# 方案A:仅改 kv cache 类型(保质量)
cache-type-k = iq4_nl
cache-type-v = iq4_nl

# 方案B:降低上下文(最有效)
ctx-size = 65536

KV Cache 与显存分析

KV Cache 占用实测

  • 请求2 后保存 idle slot 到 prompt cache:170 tokens 占 64.581 MiB → 0.38 MiB/token
  • checkpoint 创建:260 tokens 占 62.813 MiB → 0.24 MiB/token(slots 内不重复存储)
  • 对比配置说明中声称的 0.031 MiB/token:实际通过 kv-unified 共享后 checkpoint 存储接近此值,但 prompt cache 因存储完整状态略大。

缓存上限 8,192 MiB,最大缓存 225,280 tokens。当前 cache state 仅有 1 prompt 占据 64.581 MiB,远未满。

显存瓶颈

  • 修复前:GPU 0 占用 91.8%,剩余仅 ~1.3 GiB
  • 修复后(device=none):GPU 0 预计 ~87%,空余 ~2 GiB;GPU 1 ~78%,空余 ~3.6 GiB
  • 当前 batch-size 2048 + ubatch-size 512 下,KV cache + 模型权重基本占满
  • 如需增大上下文或并行数,需降低 batch/ubatch 或 KV cache 量化到 iq4_nl

CPU 线程调优参考

线程数Batch / UBPP (50K)TG备注
82048 / 1024基线配置
122048 / 256~70 t/subatch 过小限制 PP
122048 / 512~300 t/s~73 t/s当前最优平衡点
122048 / 1024ubatch 增加拉低 TG
244096 / 512~150 t/s~75 t/sbatch 翻倍但 TG 无明显提升
244096 / 1024~150 t/s~72 t/s更多 CPU 但 TG 反而略降

当前 12 线程 + 2048/512 在 PP/TG 平衡上属于较优选择。PP 主要受 GPU(V100)限制,增加线程对 PP 帮助有限;24 线程时 TG 仅 +2 t/s 但 PP 降一半,得不偿失。

Prompt Cache 行为

  • 启用:首次启动时 prompt cache 为空,后续 idle slot 会被保存到 cache
  • 请求2 的 slot 分配:使用 LRU 选择 slot 2(上次空闲),slot 3 被保存到 cache(64.581 MiB)
  • 缓存命中:第二请求的 prompt 与缓存不相似 (sim=0.000),需完全重新计算
  • 有效场景:多用户重复相同 system prompt 时,缓存可复用首部 token,大幅减少 PP 时间
  • 当前缓存上限 8 GiB,足够存储约 12 万个完整状态 token

Embedding 模型分析

显存泄漏根因:CUDA Primary Context

embedding 模型配置了 main-gpu = -1n-gpu-layers = 0(CPU-only),但仍然在两卡上占用 ~1 GiB 显存。根因在于 llama.cpp router 模式的子进程 spawn 机制:

  • Router(server.cpp:89-95)在启动时调用 llama_backend_init() 加载 CUDA backend,但跳过设备枚举(common_params_print_infoprint_devices=false),router 自身不创建 CUDA primary context
  • 当 embed 请求触发模型加载时,router 通过 server-models.cppget_environment()(第130-151行)捕获全部环境变量,再用 subprocess.h spawn 子进程
  • 子进程完整初始化后,common_params_print_info(params, true) 枚举设备 → CUDA primary context 被创建 → 显存分配(即使模型不加载到 GPU)

从 common.cpp 源码确认:

// common.cpp:386
// device enumeration creates a primary context on CUDA backends,
// skip it when the caller does not own any device

解决方案:device = none

llama.cpp v9000+ 引入了 --device 参数,语义为 "comma-separated list of devices to use for offloading (none = don't offload)"。

配置方式:在 ini 的 [embed] 段落中加入 device = none,ini key 通过 server-model-meta::update_args() 中的 preset.to_args() 转换为 CLI arg --device none 传给子进程。

源码验证(common.cpp:1503-1504):

if (!params.devices.empty()) {
    mparams.devices = params.devices.data();
}
// 空向量 → mparams.devices = nullptr → 完全不接触 GPU

device = none 时,params.devices 为空向量,设备枚举被跳过,CUDA primary context 不会被创建,显存占用归零。

Embedding 模型配置(修复后)

[embed]
model = /home/x99/gguf/Qwen3-Embedding-0.6B.Q4_K_M.gguf
chat-template-file = /home/x99/gguf/Qwen3-Embedding-0.6B.jinja
ctx-size = 8192
parallel = 2
kv-unified = 1
device = none              # 彻底禁用 GPU,跳过 CUDA primary context
no-warmup = 0              # 预热
mlock = 1                  # 锁内存防 swap
threads = 12
batch-size = 512
ubatch-size = 512
pooling = cls
embedding = 1
  • parallel 从 3 降为 2(embedding 并发通常 1-2 路,足够)
  • kv-unified = 1 保持共享上下文
  • device = nonen-gpu-layersmain-gpu 不再需要(保留也无影响)

优化建议

  1. Embedding 显存泄漏 ✅ 已解决:添加 device = none 跳过 CUDA primary context 创建,两卡释放 ~1 GiB。
  2. KV Cache 压缩:如需要更多显存余量,优先尝试 cache-type-k = iq4_nl / cache-type-v = iq4_nl(质量接近 q8_0,显存减半),或直接降低 ctx-size = 65536(按 50K+8K 实际用量)。详见 §"KV Cache 量化精度分析"。
  3. 并行数调优:n_parallel=4(auto)对当前使用场景偏多,可考虑 --parallel 2
  4. 上下文调小:n_ctx=225,280 接近模型上限 262,144,实际 input 50K + output 8K 的场景下设为 65,536 即可释放大量 KV cache 显存。
  5. 启用 warmup:主模型移除 --no-warmup(或设为 0)避免首次请求 PP 慢。

Router 模式注意事项

  • 使用 --models-preset 的多模型路由模式标记为 experimental,但功能稳定可用
  • 子进程绑定 127.0.0.1 而非 0.0.0.0(安全,但如需外部访问需注意)
  • 模型按需加载:首次请求触发加载(~1.16s),后续请求直接服务
  • 子进程间通过 stdin/stdout pipe 与 router 通信,对外统一暴露 8080 端口
  • 子进程通过 get_environment()(server-models.cpp:130-151)继承父进程全部环境变量。要修改子进程环境变量需在 router 启动前设置。