llama-server报错引出的三层缓存理解

在做llama-server压测中遇到高并发与大上下文不能推理直接崩溃问题:

 .\llama-server.exe  -m C:\……models\Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf --host 0.0.0.0 --port 8080  -ctk q4_0 -ctv q4_0 -ngl 999   -a Qwen3.6-35B  -np 32 -c 4500000
0.52.356.143 I slot get_availabl: id 31 | task -1 | selected slot by LRU, t_last = -1
0.52.356.191 I slot launch_slot_: id 31 | task 0 | processing task, is_child = 0
0.56.729.005 I slot print_timing: id 31 | task 0 | n_gen =    179, tg =  59.11 t/s, tg_3s =  59.44 t/s
0.56.866.037 I slot print_timing: id 31 | task 0 | prompt eval time =    1361.68 ms /    11 tokens (  123.79 ms per token,     8.08 tokens per second)
0.56.866.051 I slot print_timing: id 31 | task 0 |        eval time =    3148.22 ms /   187 tokens (   16.93 ms per token,    59.08 tokens per second)
0.56.866.052 I slot print_timing: id 31 | task 0 |       total time =    4509.90 ms /   198 tokens
0.56.866.053 I slot print_timing: id 31 | task 0 |    graphs reused =        186
0.56.866.079 I slot      release: id 31 | task 0 | stop processing: n_tokens = 197, truncated = 0
0.57.834.622 I slot get_availabl: id 30 | task -1 | selected slot by LRU, t_last = -1
0.57.834.674 I slot launch_slot_: id 30 | task 189 | processing task, is_child = 0
D:/a/llama.cpp/llama.cpp/ggml/src/ggml-backend.cpp:359: GGML_ASSERT(tensor->data != NULL && "tensor not allocated") failed

 压测指令 evalscope perf   --api openai   --model qwen3.6-35b-q4   --url http://127.0.0.1:8080/v1   --tokenizer-path /……/Qwen/Qwen3.6-35B-A3B   --dataset random   --min-prompt-length 512 --max-prompt-length 1024   --min-tokens 1024 --max-tokens 2048   --prefix-length 0   --parallel 1 2 4 8 16 32   --number 4 8 16 32 64 128   --warmup-num 8   --stream   --duration 300   --visualizer wandb   --name single-short-in-long-out   --wandb-api-key wandb_v1_Qn6P6W29imIJ836NidR

原因:llama-server 自身在多 slot 场景下做 KV-cache / prompt-cache 状态序列化时,读到了一个从未被分配的 tensor,触发 GGML_ASSERT直接 abort 进程。

日志看时序很清晰:

  1. slot 31 正常完成了 task 0 的生成(59 t/s,198 tokens),说明模型加载、warmup、首次推理都没问题;
  2. 随后 LRU 选中 slot 30、launch task 189,还没来得及输出任何 timing 就立刻崩了
  3. 崩溃点 ggml-backend.cpp:359: GGML_ASSERT(tensor->data != NULL && "tensor not allocated"),这行代码位于 ggml_backend_tensor_get/put,是在读写某个 KV-cache tensor 的数据时发现该 tensor 根本没被分配过

关联issue:https://github.com/ggml-org/llama.cpp/issues/26128 。

llama-server 的 prompt cache 在做 slot 状态保存时走 server_slot::prompt_save()llama_context::state_seq_get_data()ggml_backend_tensor_get()这条序列化路径,而这条路径假设 KV tensor 在主机内存里有直接的 data 指针。当 KV tensor 落在 RPC 后端(无本地指针)这类"非平凡"后端 buffer 上时,assert 在 buf->iface.get_tensor()(本可以正确处理远端 tensor 的接口)被调用之前就先炸了。

触发条件与你的日志完全对得上:issue 明确说 -np = 1时潜伏不炸(没有 idle slot 可保存、对话永远命中自己的 slot),-np ≥ 2 时只要有新任务在其它 slot 空闲时进来就立刻 abort。它还列出了两个调用点:LRU 换占 slot 时(get_available_slot())和每个新任务对所有 idle slot 做缓存保存时(process_single_task())。本地日志里 slot 30 被 LRU 选中、launch task 189 即崩,就是这个模式的翻版。

处理方法

  • --cache-ram 0 完全禁用 prompt cache,崩溃消失,作者在并发负载下验证稳定;我验证也是可用的。
  • 另外一种方式是把--np 改16 也是可以正常运行的。
  • 两个重要澄清:--no-cache-prompt 无效(server prompt cache 是独立机制);--no-cache-idle-slots 只能去掉一个调用点,LRU 路径仍在,不够
  • 关键安慰:slot 内的前缀复用不受 --cache-ram 0影响(实测后续轮次 cache_n=53仍命中),server prompt cache 只在"会话数 > slot 数"时才有收益,对你 32 slot、每 slot 独立请求的压测场景本来就是零收益。

引出--cache-ram 参数,它控制的是 server 的“跨 slot prompt cache”(RAM 侧 KV 状态存档)预算,0 表示完全关闭这套机制;它是独立于 slot 内前缀复用和 --no-cache-prompt的第三个缓存层。

引出了三层缓存机制:

层级

参数 / 开关

作用范围

存储位置

触发场景

Slot 内前缀复用

无(自动启用)

同一 slot 内

GPU 显存

同一对话多轮请求

跨 Slot RAM 状态存档(cache 主体)

--cache‑ram <MB>(默认 8192MiB)

所有 slot 之间

主机内存(RAM)

会话数 > slot 数,LRU 换占 slot 时

GPU 侧上下文检查点

--ctx‑checkpoints <N>(默认 32)

单个 slot 内

GPU 显存

生成过程中按 token 间隔定期保存

--cache-ram 的8192 MiB预算同时服务第二、三层两个机制,设置 --cache-ram 0同时禁用检查点创建和跨slot缓存。

特性

第一层: Slot 内前缀复用

第二层:跨 Slot RAM 存档

第三层:上下文检查点

存储位置

GPU 显存

系统内存

系统内存

控制参数

自动启用

--cache‑ram

--ctx‑checkpoints, --cache‑ram

主要场景

多轮对话

会话数 > slot 数

长上下文 / 上下文移位

存储内容

完整 KV 前缀

完整 KV 状态

部分不可重建状态

需要详细解释的第三层是llama.cpp的上下文检查点系统,用于处理长上下文和上下文移场景。它通过定期保存KV状态的"部分快照"来避免完全重新处理长提示。

第三层检查点参数

参数

作用

默认值

--ctx-checkpoints / -ctxcp

每个 slot 的最大检查点数

32

--checkpoint-min-step / -cms

创建检查点的最小令牌间隔

8192

--cache-ram / -cram

检查点使用的 RAM 预算

8192 MiB

实际应用场景

  1. 长上下文处理:当上下文超过模型限制时,通过检查点移,避免完全重新处理
  2. 代理工具调用:在本地编码代理等场景中,工具调用后恢复状态,大幅减少冗余计算
  3. 混合注意力模型:对Qwen3.5等具有滑动窗口注意力的模型特别有用

第三层是缓存“不可重建的中间状态”

在llama.cpp的上下文检查点系统中,“不可重建的中间状态”是指那些无法通过简单截断KV cache来恢复的模型内部状态

状态类型

完整注意力 KV Cache

不可重建的中间状态

存储形式

每个 token 都有独立的 Key/Value 对

滑动窗口注意力 (SWA) 的 KV 或 递归层 (如 Mamba) 的运行状态

能否截断恢复

可以,llama_memory_seq_rm () 能任意删除

不能,一旦滑动或覆盖,旧数据就丢失

检查点是否存储

不存储(仍在 slot 中,可截断恢复)

必须存储(这是检查点唯一保存的内容)

两种典型不可重建状态

A. 滑动窗口注意力(SWA)的KV Cache

对于采用SWA的模型(如Qwen3.6、Mistral),每个注意力层只维护一个固定大小的“窗口”,窗口外的token的KV会被丢弃。

SWA(滑动窗口注意力)机制

  • 不是“压缩+新内容保留”,而是维护固定大小的窗口
  • 当处理新Token时,窗口外的最旧Token的KV会被永久丢弃
  • 窗口内的Token可能包含新Token和部分旧Token

B. 递归层的运行状态(如Mamba、RWKV)

对于Mamba、RWKV等状态空间模型,它们不存储每个token的KV,而是维护一个单一的“运行状态”,这个状态在每个token处理时都会被原地更新。

递归模型(如Mamba)机制

  • 没有独立的KV对,而是维护一个单一的运行状态
  • 这个状态在每个Token处理时都会被原地更新(覆盖)

检查点设计的主要目标。当处理一个长prompt后,后续请求如果与之前的对话有共同前缀,server会尝试找到匹配的检查点,恢复到某个中间状态,只计算新增的token。

典型场景:多轮对话中,用户发送新请求(包含之前所有对话历史+新问题)。如果没有检查点,server需要重新处理整个对话历史;有了检查点,可以恢复到之前某个状态,只处理新问题部分。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值