《llama.cpp性能优化实战:从显存、KV Cache到Token/s》

LlamaAI本地部署实战专栏第9篇

前面我们已经把本地Llama从"能运行"一路升级到了"能提供API、能检索知识、能调用工具"。

但一个真正能用的本地AI,还需要解决一个核心问题:

为什么模型能跑,但是跑得慢?

本篇不再停留在启动参数层面,而是从 GPU显存、KV Cache、Batch、Context、量化、CUDA Offload、Token/s 等底层机制出发,建立一套完整的 llama.cpp 性能优化方法。


一、先搞清楚:到底什么叫"模型快"?

我们经常看到:

复制代码
复制代码
35 tokens/s

或者:

复制代码
复制代码
80 tokens/s

这个指标到底是什么意思?

假设模型输出:

复制代码
复制代码
Hello world, this is a local LLM...

总共生成:

复制代码
复制代码
100 tokens

耗时:

复制代码
复制代码
2秒

那么:

复制代码
复制代码
100 / 2
= 50 tokens/s

也就是:

模型每秒生成50个Token。


二、LLM推理其实包含两个阶段

这是理解性能优化最重要的知识。

一次LLM推理通常可以分成:

复制代码
复制代码
Prompt Processing
        ↓
Token Generation

也叫:

Prefill

和:

Decode


三、Prefill是什么?

假设用户输入:

复制代码
复制代码
请详细介绍CUDA和GPU架构,并解释它们之间的关系......

假设Prompt有:

复制代码
复制代码
2000 tokens

模型首先需要一次性处理这些Token。

这个过程:

Prefill

可以理解为:

复制代码
复制代码
2000 tokens
    ↓
Transformer
    ↓
计算Attention
    ↓
建立KV Cache

Prefill主要特点:

计算密集型。

GPU通常非常擅长。


四、Decode是什么?

Prompt处理完以后:

模型开始一个Token一个Token生成。

例如:

复制代码
复制代码
Token1
 ↓
Token2
 ↓
Token3
 ↓
Token4
 ↓
...

这就是:

Decode

它通常更加受到:

  • 显存带宽
  • KV Cache
  • 模型参数读取

等因素影响。


五、为什么Prompt很长,首Token会很慢?

例如:

复制代码
复制代码
Prompt:

100 tokens

可能:

复制代码
复制代码
首Token:
0.2秒

但如果:

复制代码
复制代码
Prompt:

10000 tokens

可能:

复制代码
复制代码
首Token:
2秒

因为:

复制代码
复制代码
Prompt
 ↓
Prefill
 ↓
生成第一个Token

所以:

首Token延迟(TTFT)和Token生成速度是两个不同指标。


六、两个非常重要的性能指标

1. TTFT

Time To First Token。

也就是:

从发送请求到看到第一个Token的时间。

例如:

复制代码
复制代码
用户发送问题
      ↓
1.2秒
      ↓
第一个Token

那么:

复制代码
复制代码
TTFT = 1.2s

2. TPS

Tokens Per Second。

例如:

复制代码
复制代码
生成100 tokens
耗时2秒

那么:

复制代码
复制代码
TPS = 50 tokens/s

因此:

复制代码
复制代码
LLM性能
=
TTFT
+
Decode TPS

不能只看一个指标。


七、第一优化目标:GPU到底有没有吃满?

首先运行:

复制代码
复制代码
nvidia-smi

观察:

复制代码
复制代码
GPU-Util
Memory-Usage

例如:

复制代码
复制代码
GPU-Util:      95%
Memory Usage:  7GB / 8GB

这是比较理想的情况。

如果:

复制代码
复制代码
GPU-Util: 5%
Memory Usage: 2GB / 8GB

但是模型很慢。

那就需要怀疑:

模型是不是大量计算仍然在CPU?


八、GPU Offload是最重要的优化之一

llama.cpp:

复制代码
复制代码
-ngl

表示:

Number of GPU Layers。

例如:

复制代码
复制代码
-ngl 20

表示:

复制代码
复制代码
Transformer

Layer 0
 ↓ GPU

Layer 1
 ↓ GPU

...

Layer 19
 ↓ GPU

Layer 20+
 ↓ CPU

九、显存足够时为什么推荐-ngl 999?

例如:

复制代码
复制代码
-ngl 999

意思并不是:

GPU真的运行999层。

而是:

尽可能把模型层全部放进GPU。

最终可能:

复制代码
复制代码
7B模型

全部Layer
 ↓
GPU

如果显存足够:

复制代码
复制代码
-ngl 999

通常是非常值得尝试的配置。


十、但是显存并不只存模型

这是很多初学者容易忽略的问题。

GPU显存主要可能被:

复制代码
复制代码
模型权重
+
KV Cache
+
计算Buffer
+
CUDA Buffer
+
Batch相关Buffer

占用。

所以:

复制代码
复制代码
8GB显存

并不代表:

复制代码
复制代码
可以运行8GB模型

因为还需要额外空间。


十一、KV Cache到底是什么?

这是本篇最重要的概念之一。

Transformer生成:

复制代码
复制代码
Token1
Token2
Token3
Token4
...

如果每生成一个Token都重新计算之前所有Token:

效率会非常低。

因此模型会缓存Attention中的:

复制代码
复制代码
K
V

也就是:

Key Cache + Value Cache。

简称:

KV Cache


十二、KV Cache可以怎么理解?

假设:

复制代码
复制代码
用户:

你好,我想学习CUDA

模型处理:

复制代码
复制代码
你好
我
想
学习
CUDA

之前计算过的Attention信息:

复制代码
复制代码
K
V

会缓存下来。

下一次生成:

复制代码
复制代码
GPU
 ↓
读取KV Cache
 ↓
只处理新的Token

所以:

KV Cache是LLM高效自回归生成的关键机制。


十三、为什么Context越大,显存越高?

假设:

复制代码
复制代码
Context = 2048

KV Cache需要:

复制代码
复制代码
较少显存

如果:

复制代码
复制代码
Context = 32768

那么:

复制代码
复制代码
KV Cache
      ↓
大幅增加

因此:

复制代码
复制代码
上下文越长
≠
只影响模型输入长度

还会:

直接影响显存占用。


十四、-c参数是什么?

例如:

复制代码
复制代码
-c 4096

表示:

Context Size。

也就是模型允许处理的上下文长度。

例如:

复制代码
复制代码
-c 8192

代表:

复制代码
复制代码
最大Context ≈ 8192 tokens

具体可用长度还受到模型和构建配置等因素影响。


十五、不要盲目把Context开到128K

很多人喜欢:

复制代码
复制代码
-c 131072

然后:

复制代码
复制代码
CUDA out of memory

因为:

复制代码
复制代码
Context
 ↓
KV Cache
 ↓
显存暴涨

如果你的实际应用只是:

复制代码
复制代码
普通聊天

那么:

复制代码
复制代码
-c 4096

或者:

复制代码
复制代码
-c 8192

通常更合理。


十六、Batch Size是什么?

另一个重要参数:

复制代码
复制代码
-b

例如:

复制代码
复制代码
-b 512

它控制:

单次处理的Token批量大小。

简单理解:

复制代码
复制代码
Batch小:

一次处理少量Token


Batch大:

一次处理更多Token

十七、Batch为什么会影响性能?

GPU非常喜欢:

大规模并行计算。

因此:

复制代码
复制代码
Batch = 32

可能:

复制代码
复制代码
GPU利用率较低

而:

复制代码
复制代码
Batch = 512

可能:

复制代码
复制代码
GPU利用率更高

但是:

Batch越大,不代表永远越快。

因为它也会:

复制代码
复制代码
增加显存占用

十八、显存优化其实是在做一个平衡

你可以把显存想成:

复制代码
复制代码
GPU VRAM
│
├── Model
│
├── KV Cache
│
├── Batch
│
└── Runtime Buffer

如果:

复制代码
复制代码
模型占用 6GB

GPU只有:

复制代码
复制代码
8GB

那么:

复制代码
复制代码
KV Cache
+
Batch
+
Runtime

最多只能使用剩余的:

复制代码
复制代码
约2GB

因此:

性能优化本质上就是在显存预算内进行资源分配。


十九、量化:降低模型显存最有效的方法之一

假设一个7B模型:

FP16:

复制代码
复制代码
约14GB

如果使用:

复制代码
复制代码
INT8

大约:

复制代码
复制代码
7GB

如果使用:

复制代码
复制代码
4-bit

大约:

复制代码
复制代码
3.5~4.5GB

实际大小还会受到量化格式、元数据和运行时开销影响。


二十、常见GGUF量化格式

你经常会看到:

复制代码
复制代码
Q4_K_M
Q5_K_M
Q6_K
Q8_0

例如:

复制代码
复制代码
Q4_K_M

通常意味着:

4-bit级别的K-quants量化方案中的一种常用平衡配置。


二十一、Q4和Q8怎么选?

简单理解:

复制代码
复制代码
Q4
 ↓
显存低
速度快
精度损失相对更明显
复制代码
复制代码
Q8
 ↓
显存高
质量更接近高精度模型

通常:

复制代码
复制代码
消费级GPU
 ↓
Q4 / Q5

比较实用。


二十二、为什么不能只追求最低显存?

例如:

复制代码
复制代码
Q2

虽然非常省显存。

但是:

复制代码
复制代码
模型质量
 ↓
可能明显下降

所以量化需要平衡:

复制代码
复制代码
模型质量
     ↕
显存占用
     ↕
推理速度

实践中:

Q4_K_M通常是一个非常常见的质量/大小平衡点。


二十三、Flash Attention是什么?

Attention计算是Transformer的重要瓶颈。

传统:

复制代码
复制代码
Q × Kᵀ
 ↓
Softmax
 ↓
× V

中间可能产生较大的显存访问。

Flash Attention通过:

更高效的Kernel和内存访问方式

减少中间数据的读写。


二十四、llama.cpp中启用Flash Attention

可以尝试:

复制代码
复制代码
--flash-attn

例如:

复制代码
复制代码
llama-server \
-m qwen.gguf \
-ngl 999 \
-c 8192 \
--flash-attn

具体收益:

取决于GPU、模型架构、Context长度和llama.cpp版本。

不要把它理解成:

开启以后一定提升2倍。

正确方式是:

开启前后实测。


二十五、CPU线程数也很重要

如果模型没有全部Offload到GPU:

复制代码
复制代码
GPU
+
CPU

会共同参与推理。

这时:

复制代码
复制代码
--threads

会影响CPU部分计算。

例如:

复制代码
复制代码
--threads 8

或者:

复制代码
复制代码
--threads 16

但是:

线程不是越多越快。

因为还涉及:

  • CPU核心数
  • SMT
  • 内存带宽
  • NUMA
  • GPU/CPU协作开销

二十六、如何找到最佳线程数?

例如你的CPU有:

复制代码
复制代码
8核心 / 16线程

可以测试:

复制代码
复制代码
4线程
8线程
12线程
16线程

分别测:

复制代码
复制代码
tokens/s

例如:

复制代码
复制代码
Threads   TPS

4         8.2
8         10.7
12        11.1
16        10.9

那么:

复制代码
复制代码
12线程

反而比:

复制代码
复制代码
16线程

更好。

所以:

性能参数必须Benchmark,而不是猜。


二十七、真正应该怎么测性能?

不要只问:

"感觉快不快?"

应该建立Benchmark。

例如固定:

复制代码
复制代码
模型
Prompt
Context
GPU Layers
Batch
Threads

然后记录:

复制代码
复制代码
TTFT
Prompt TPS
Generation TPS
总耗时
显存
GPU利用率

二十八、一个简单的性能测试表

例如:

配置 显存 TTFT Generation
CPU --- 3.2s 5 tok/s
GPU 20 layers 5.2GB 1.5s 20 tok/s
GPU 35 layers 7.1GB 0.8s 35 tok/s
GPU全部Offload 7.8GB 0.6s 42 tok/s

从数据可以直接判断:

复制代码
复制代码
GPU Offload
 ↓
性能提升

而不是凭感觉。


二十九、如何查看GPU状态?

运行:

复制代码
复制代码
watch -n 1 nvidia-smi

观察:

复制代码
复制代码
GPU-Util
Memory-Usage
Temperature
Power

例如:

复制代码
复制代码
GPU-Util: 95%
Memory:   7.5GB
Power:    110W

说明GPU确实在工作。


三十、如果GPU利用率很低怎么办?

假设:

复制代码
复制代码
GPU-Util = 15%

但是模型非常慢。

优先检查:

1. GPU Offload

复制代码
复制代码
-ngl 999

2. CPU/GPU分工

是否大量Layer仍在CPU?

3. Batch

复制代码
复制代码
-b 512

4. Prompt长度

过大的Prompt会增加Prefill时间。

5. 并发

单请求下GPU可能并没有充分利用。


三十一、单用户速度和多用户吞吐量不是一回事

这是部署AI服务必须理解的概念。

假设:

复制代码
复制代码
单用户:

50 tokens/s

不代表:

复制代码
复制代码
10用户:

500 tokens/s

因为GPU计算资源有限。

真正要看的还有:

Throughput(吞吐量)


三十二、并发请求

llama-server可以通过并行槽位处理多个请求。

例如:

复制代码
复制代码
--parallel 4

可以理解为:

复制代码
复制代码
User1 ─┐
User2 ─┤
User3 ─┼── llama-server ── GPU
User4 ─┘

但是:

并发增加通常会让单请求延迟发生变化。

因此需要在:

复制代码
复制代码
Latency

和:

复制代码
复制代码
Throughput

之间做平衡。


三十三、一个完整的优化命令

假设:

复制代码
复制代码
GPU:

RTX 4060 8GB

模型:

复制代码
复制代码
7B Q4

可以从:

复制代码
复制代码
llama-server \
-m qwen.gguf \
-ngl 999 \
-c 4096 \
-b 512 \
--flash-attn \
--port 8080

开始测试。

然后根据:

复制代码
复制代码
显存
GPU Util
TTFT
TPS

逐项调整。


三十四、如果显存爆了怎么办?

错误:

复制代码
复制代码
CUDA out of memory

不要一上来换GPU。

依次尝试:

第一:

降低Context:

复制代码
复制代码
-c 2048

第二:

降低Batch:

复制代码
复制代码
-b 256

第三:

减少GPU Layers:

复制代码
复制代码
-ngl 30

第四:

换低bit量化:

复制代码
复制代码
Q5
→
Q4

三十五、如果模型速度还是很慢怎么办?

按照下面顺序排查:

复制代码
复制代码
1. GPU驱动
       ↓
2. CUDA
       ↓
3. llama.cpp是否CUDA编译
       ↓
4. GPU Offload
       ↓
5. GPU利用率
       ↓
6. Batch
       ↓
7. Context
       ↓
8. Quantization
       ↓
9. CPU Threads
       ↓
10. Benchmark

不要同时修改10个参数。

否则:

你不知道到底哪个参数产生了效果。


三十六、性能优化最重要的思维方式

假设现在:

复制代码
复制代码
35 tokens/s

你想提升到:

复制代码
复制代码
50 tokens/s

不要直接:

复制代码
复制代码
疯狂改参数

应该:

复制代码
复制代码
建立Baseline
      ↓
测量
      ↓
找到瓶颈
      ↓
修改一个变量
      ↓
重新测量
      ↓
记录

这叫:

Profiling-driven Optimization

也就是:

基于性能分析进行优化。


三十七、最终性能优化思维导图

可以把整个本地LLM性能问题归纳成:

复制代码
复制代码
                 LLM性能
                    │
        ┌───────────┼───────────┐
        ↓           ↓           ↓
      Compute     Memory      Runtime
        │           │           │
       CUDA       VRAM        Batch
       GPU        KV Cache     Threads
       Tensor     Context      Parallel
       Core       Model
        │           │
        └──────┬────┘
               ↓
          Token/s
               ↓
          用户体验

三十八、本篇总结

这一篇最重要的不是记住几个参数,而是建立:

性能分析体系。

核心概念:

GPU Offload

复制代码
复制代码
-ngl 999

尽可能把模型放入GPU。

Context

复制代码
复制代码
-c 4096

影响上下文容量以及KV Cache。

Batch

复制代码
复制代码
-b 512

影响批量计算和显存。

KV Cache

复制代码
复制代码
Context
 ↓
KV Cache
 ↓
VRAM

Quantization

复制代码
复制代码
Q8
 ↓
Q5
 ↓
Q4

降低显存需求。

Flash Attention

复制代码
复制代码
更高效Attention Kernel
 ↓
减少内存访问

Benchmark

最终一定要测:

复制代码
复制代码
TTFT
Prompt TPS
Generation TPS
VRAM
GPU Util

三十九、到这里,我们已经完成了什么?

专栏前9篇已经形成完整技术链:

复制代码
复制代码
                 LlamaAI
                    │
                    ↓
              llama.cpp
                    │
        ┌───────────┴───────────┐
        ↓                       ↓
      CUDA                     GGUF
        │                       │
        └───────────┬───────────┘
                    ↓
               LLM Inference
                    │
                    ↓
             llama-server
                    │
        ┌───────────┴───────────┐
        ↓                       ↓
       RAG                    Agent
        │                       │
   BM25 + FAISS              Tools
   Reranker                  File/API
        │                       │
        └───────────┬───────────┘
                    ↓
               Local AI
                    │
                    ↓
             Performance

已经从:

"我怎么在电脑上跑一个大模型?"

进化到了:

"我如何构建一个完整的本地AI系统?"


下一篇:专栏终章

《从0到1打造完整LlamaAI:本地大模型 + RAG + Agent + WebUI》

最后一篇我们不再单独讲某一个技术,而是把前9篇全部串起来,完成一个真正可以作为项目展示的:

LlamaAI本地智能助手

最终架构:

复制代码
复制代码
                    Web UI
                      │
                      ↓
                  FastAPI
                      │
                      ↓
                  AI Agent
                      │
          ┌───────────┼───────────┐
          ↓           ↓           ↓
        RAG         Tools       LLM
          │           │           │
   BM25 + FAISS    File/API    llama.cpp
   + Reranker                   CUDA
          │                       │
          └───────────┬───────────┘
                      ↓
                   GGUF
                      ↓
                 NVIDIA GPU

最终项目将包含:

  • 🧠 本地GGUF大模型
  • ⚡ CUDA GPU加速
  • 🚀 llama-server API
  • 📚 Hybrid RAG知识库
  • 🔎 BM25 + FAISS
  • 🎯 Reranker
  • 🤖 Agent
  • 🔧 Tool Calling
  • 🌐 Web聊天界面
  • 💾 本地数据存储
  • 📊 性能监控
  • 🔒 本地隐私部署

并且会给出完整的项目目录、核心代码、启动脚本、部署流程,本专栏将被完美收束成一个可以放进简历/项目作品集的完整项目。

相关推荐
该用户可能存在4 小时前
【Win11优化】StartAllBack实战:解决任务栏合并、右键折叠等痛点
windows·性能优化·win11·windows 11·startallback·任务栏·开始菜单
灯澜忆梦14 小时前
【MySQL12】进阶篇 | SQL优化
数据库·sql·mysql·性能优化
爱喝水的鱼丶1 天前
SAP-ABAP:性能优化效果验证与回归测试:优化前后性能对比、业务逻辑兼容性校验方案
性能优化·sap·abap·回归测试·开发交流·交流学习
赵庆明老师1 天前
RH7.6安装 llama.cpp(普通用户,无sudo权限)
llama
starzhang1 天前
06 从 Trace 看 Android 启动流程
性能优化
工业设备方案笔记1 天前
RK3588 AI设备性能优化指南:如何释放ARM边缘计算平台真正性能?
arm开发·人工智能·目标跟踪·性能优化·架构·边缘计算
七牛云行业应用1 天前
llama.cpp 本地部署完全指南:从安装到 OpenAI 兼容 API 服务
人工智能·大模型·llama
DO_Community1 天前
AI 应用成本怎么算?从推理费用到完整 TCO 拆解
人工智能·gpt·agent·ai编程·llama·kimi
数据库小学妹1 天前
空间数据库查询慢怎么排查?索引失效、表膨胀、SQL优化实战(附排查命令)
数据库·性能优化·信创·故障排查·索引调优·空间数据库