RTX 4090 本地运行 Qwen 27B:一次 llama.cpp 性能调优实测
llama, Qwen
llama, Qwen
模型在本地跑起来之后,我最想知道的是:这张 RTX 4090 还能不能更快?调整 batch、换 KV 缓存精度,究竟有没有实际收益?
在本地观察 llama-server 日志和显存,再用 llama-bench 做参数对照等对比后,最终选定的日常配置是 batch=1024、ubatch=512、Q8 KV、Flash Attention、16K 上下文、单并发。
在已测试条件下,输入处理约 3300 tokens/s,短生成约 47 tokens/s;实际一次 8208 tokens 的长回答平均 43.35 tokens/s。提高 ubatch 对输入处理有收益,换成 F16 KV 则没有明显提速。
这是我的个人机器上的参数筛选记录,不是不同显卡或不同模型的横向评测,也没有证明已经达到硬件极限。 本人也只是刚开始捣鼓这些,如果谁还有更好的优化或者测试方案,也可以告诉我。
| 项目 | 本次环境 |
|---|---|
| 测试日期 | 2026-10-18 |
| 系统 | Windows 11 |
| 显卡 | NVIDIA GeForce RTX 4090,24GB |
| 系统内存 | 32GB |
| 推理框架 | llama.cpp,CUDA 后端 |
| benchmark 中显示的模型 | qwen35 27B Q4_K - Medium |
| 参数量 | 27.32B |
| benchmark 显示的模型大小 | 15.32GiB |
| 长上下文测试截图中的构建 | 620d225c(11669) |
| 使用场景 | 个人聊天、小说与代码生成与长文本处理 |
我下载的模型称是 Qwen3.8-27B;但本次截图中的架构标识为 qwen35。这个可能是衍生模型可能沿用基础架构名称(具体不是很清楚)。
本次主要看两类速度:
pp2048 表示处理 2048 tokens 的输入测试,tg256 表示生成 256 tokens 的测试。本文命令使用 -p 2048 -n 256 时,pp 和 tg 是分别进行的测试,不能理解成“每次 tg 都先读入同一段 2048-token 输入”;如需联合测试,应参考官方文档中的 -pg。
-d 8192 用于先填充上下文,再测该深度下的性能。每组 -r 3 重复三次,表中 ± 是工具报告的标准差,不是置信区间。llama-bench 不包含分词与采样时间,因此它不能直接代表客户端完整请求耗时。
另外,token 不等于汉字。本文所有速度都保留 tokens/s 单位。
先观察已经启动的服务。持续输出日志中,前段累计生成速度约为 40~43 tokens/s,最近 3 秒速度约为 40~47 tokens/s。

一次完整长回答的最终日志为:
| 项目 | 实测结果 |
|---|---|
| 输入 token 数 | 381 |
| 输入处理耗时 | 600.22ms |
| 输入处理速度 | 634.76 tokens/s |
| 输出 token 数 | 8208 |
| 输出耗时 | 189339.86ms,约 189.34 秒 |
| 平均输出速度 | 43.35 tokens/s |
| 日志中的 total time | 189940.08ms,约 189.94 秒 |

输出后段的最近 3 秒速度约为 39.7 tokens/s。整体没有突然崩塌式掉速,不过它是一条真实请求,不能用于隔离某个参数的影响。
同一张图后面的请求显示约 2425 tokens/s 的输入处理进度,但它是另一条请求,存在不同输入长度和缓存状态,不能与前面的 634.76 tokens/s 直接计算提升倍数。
显卡状态如下:
| 项目 | 截图值 |
|---|---|
| GPU 利用率 | 93% |
| 专用 GPU 内存 | 17.4 / 24GB |
| 共享 GPU 内存 | 0.2 / 15.9GB |
| GPU 温度 | 69℃ |

当时专用显存约有 6.6GB 余量,可以继续尝试参数对照。共享显存的 0.2GB 本身不能证明显存溢出;任务管理器显示的 93% 利用率也不能证明算力或显存带宽已经跑满。
这一轮没有保存完整启动参数,故只作为实际运行参考,不作为严格的优化前基线。
测试前停止 llama-server,避免两份模型同时竞争显存和计算资源。以下 PowerShell 命令在 llama.cpp 目录执行,模型路径替换为实际文件:
$modelFile = "F:\AI\models\你的模型.gguf"
.\llama-bench.exe `
-m $modelFile `
-ngl 99 `
-fa on `
-ctk q8_0 `
-ctv q8_0 `
-b 2048 `
-ub 512 `
-p 2048 `
-n 256 `
-r 3
本轮固定 CUDA、请求 GPU 卸载层数 99、Flash Attention 与 Q8 KV,取得一个统一测试起点。
| batch | ubatch | 输入处理 pp2048 | 生成 tg256 |
|---|---|---|---|
| 2048 | 512 | 3336.12 ± 76.59 | 47.82 ± 0.19 |
单位均为 tokens/s。

这组结果不能与第一轮的长回答直接相减,看起来提速了约 10%。两者的上下文、生成长度和测试开销都不同。
两个参数容易混淆:batch-size 是逻辑批量上限,ubatch-size 是实际计算的微批量。增大它们主要是探索输入处理效率,同时也可能增加运行时内存需求。
本轮保持其他显式参数不变,只比较:
.\llama-bench.exe `
-m $modelFile `
-ngl 99 `
-fa on `
-ctk q8_0 `
-ctv q8_0 `
-b 1024,2048 `
-ub 256,512 `
-p 2048 `
-n 256 `
-r 3
| batch | ubatch | pp2048,tokens/s | tg256,tokens/s |
|---|---|---|---|
| 1024 | 256 | 3063.43 ± 3.20 | 46.18 ± 0.80 |
| 1024 | 512 | 3298.15 ± 22.17 | 47.32 ± 0.19 |
| 2048 | 256 | 3019.81 ± 39.89 | 47.70 ± 0.18 |
| 2048 | 512 | 3297.65 ± 40.52 | 47.59 ± 0.31 |

固定 batch=1024:
(3298.15 / 3063.43 - 1) × 100% ≈ 7.66%
这是本轮最明确的收益。固定 batch=2048 时,同样能看到 ubatch=512 的输入处理更快,方向一致。
固定 ubatch=512,输入处理为 3298.15 与 3297.65 tokens/s,几乎相同。因此日常选择较小的 batch=1024 即可。
生成速度约在 46~48 tokens/s,不能把这几行的全部差异都归因于参数。测试没有随机化执行顺序,也没有逐轮记录 GPU 时钟与功耗。尤其 tg 的结果不能作为“大 batch 必然加速单人逐字输出”的证据。
本轮选择:batch=1024、ubatch=512。这是已测组合中的实用取舍,不是穷举后的全局最优值。
小说场景会不断积累历史,空上下文跑得快还不够。因此增加深度对照:
.\llama-bench.exe `
-m $modelFile `
-ngl 99 `
-fa on `
-ctk q8_0 `
-ctv q8_0 `
-b 1024 `
-ub 512 `
-d 0,8192 `
-p 2048 `
-n 256 `
-r 3
| 测试 | 空上下文 | 已有 8192 tokens | 变化 |
|---|---|---|---|
| pp2048 | 3358.66 ± 53.22 | 3083.06 ± 29.53 | 下降约 8.2% |
| tg256 | 47.22 ± 0.36 | 46.72 ± 0.03 | 下降约 1.1% |
单位为 tokens/s。

本次 8K 深度下,输入处理有一定下降,生成速度则保持得较稳定。这支持当前配置用于该长度附近的实际任务。
Q8 KV 的主要价值是节省缓存占用;它不是保证提速的开关。既然显存还有余量,我就把 K/V 都换成 F16,对照一次。
.\llama-bench.exe `
-m $modelFile `
-ngl 99 `
-fa on `
-ctk f16 `
-ctv f16 `
-b 1024,2048 `
-ub 256,512 `
-p 2048 `
-n 256 `
-r 3
| batch | ubatch | F16 pp2048 | F16 tg256 |
|---|---|---|---|
| 1024 | 256 | 3025.68 ± 59.76 | 47.81 ± 0.24 |
| 1024 | 512 | 3345.68 ± 8.25 | 47.57 ± 0.49 |
| 2048 | 256 | 3037.01 ± 27.64 | 48.08 ± 0.09 |
| 2048 | 512 | 3376.37 ± 37.06 | 47.64 ± 0.81 |
单位为 tokens/s。

以最终选择的 1024/512 配置比较,并采用第四轮的 Q8 空上下文结果:
| 指标 | Q8 KV | F16 KV | F16 相对变化 |
|---|---|---|---|
| pp2048 | 3358.66 | 3345.68 | 约 -0.4% |
| tg256 | 47.22 | 47.57 | 约 +0.7% |
两个值来自不同轮次,只能作近似对照。若采用第三轮的 Q8 结果,则差异约为 +1.4% 与 +0.5%;无论选哪次已记录数据,都没有看到值得为速度切换 F16 的明显收益。
本轮没有记录 F16 显存峰值,也没有加入 -d 8192,因此没有完成 F16 的 8K 对照。测试只测速度,没有测困惑度、小说质量或长文准确率;不能据此声称 Q8 与 F16 输出质量完全相同。
最终保留 Q8 KV,原因是本次没有测出 F16 的实用速度优势,同时希望保留缓存空间余量。
现有脚本保留实际模型路径和程序路径,将参数数组设置为:
$serverArgs = @(
"--model", $modelFile,
"--alias", "qwen-local",
"--host", "127.0.0.1",
"--port", "8080",
"--n-gpu-layers", "99",
"--flash-attn", "on",
"--ctx-size", "16384",
"--parallel", "1",
"--batch-size", "1024",
"--ubatch-size", "512",
"--cache-type-k", "q8_0",
"--cache-type-v", "q8_0",
"--jinja"
)
| 参数 | 最终选择的理由 |
|---|---|
| GPU layers=99 | 请求尽量将模型层放入 GPU;实际卸载结果仍以启动日志为准 |
| Flash Attention=on | 本次统一使用;没有做 on/off 对照,不能量化其独立收益 |
| ctx-size=16384 | 日常服务的上下文预算,输入、历史与输出共用;本次没有验证填满 16K 的表现 |
| parallel=1 | 面向个人单请求使用;没有测试多并发吞吐 |
| batch=1024 | 增至 2048 没有观察到明显输入处理收益 |
| ubatch=512 | 相对 256,对输入处理有较一致收益 |
| K/V=q8_0 | F16 短上下文测试没有明显速度优势 |
| jinja | 使用模型聊天模板,属于服务功能配置,不是本轮测速得出的加速项 |
这些是服务参数;benchmark 的测试上下文由其测试项目和深度决定,不能把 --ctx-size 16384 视为每轮 benchmark 都验证过的条件。
第一,实际有收益的是把 ubatch 从 256 提高到 512。 在 batch=1024 的对照中,输入处理提升约 7.7%;batch 进一步增大则没有显著帮助。
第二,Q8 换 F16 没有带来明显提速。 保留 Q8 是这台机器、这份模型、已测条件下的实用选择,而不是所有模型的通用定律。
第三,8K 深度的生成性能比较稳定。 Q8 KV 下 tg256 从 47.22 降至 46.72 tokens/s,约下降 1.1%。
第四,实际长回答与短 benchmark 应分开理解。 约 43 tokens/s 的真实长输出与约 47 tokens/s 的短测试都是真实记录,但不能直接用它们计算参数优化收益。
当前可以停止围绕 batch 和 KV 精度反复微调,把这套配置作为可用基线。后续如果体验仍不理想,应先确认瓶颈是首字等待、持续输出、输出过长还是内容质量,再决定下一步实验。
本次没有比较其他模型量化、推测解码、不同后端、自行编译选项或 CPU 线程,也没有保存完整模型身份与全部启动日志。因此,准确的结论是“找到了已测试组合中的合理配置”,而不是“4090 的理论最优解”。
本文中的数字来自本次截图;工具含义和参数用法参考以下官方资料。命令参数应同时核对本地程序的 --help,因为主分支文档会更新。
日志还会继续,欢迎常来。
返回首页