server : saving idle slots to the prompt cache stalls all requests for seconds with interleaved KV cells

该问题出现在 llama-server 开启统一 KV( -kvu )并使用 --cache-idle-slots 时,一个新任务到来会触发空闲 slot 的回写保存;交错 KV cell 会让这次保存拆成大量阻塞式 ggml_backend_tensor_get ,并伴随大量 cudaMemcpy

快速结论:该问题出现在 llama-server 开启统一 KV(-kvu)并使用 --cache-idle-slots 时,一个新任务到来会触发空闲 slot 的回写保存;交错 KV cell 会让这次保存拆成大量阻塞式 ggml_backend_tensor_get,并伴随大量 cudaMemcpyAsync 与 cudaStreamSynchronize,导致所有请求和流式输出被卡住数秒。优先排查是否同时启用了统一 KV 与空闲 slot 保存,并关注上游修复 PR。

适用环境:llama.cpp version 11387 (8330e96),GCC 13.3.0,Linux x86_64,CUDA backend,llama-server。Issue 中实际验证过的显卡为 RTX 3070 Ti(WSL2)和 RTX 3090(原生 Linux);模型 Qwen3-8B-Q4_K_M,另一复现使用 Meta-Llama-3.1-8B-Instruct-Q4_K_M。Python、PyTorch、CUDA 具体版本 Issue 未给出。

最快修复方案:Issue 明确给出的修复是上游 PR #31391,它把碎片化的多次 GPU→CPU 拷贝合并处理。请将 llama.cpp 更新到包含该 PR 的版本后重新验证。

注意事项:若暂时无法更新,可优先尝试关闭 --cache-idle-slots 或统一 KV(-kvu)来规避,但这一规避方式并非 Issue 中已验证的修复,属于可优先尝试的临时手段;关闭后会影响空闲 slot 的 prompt cache 复用行为。修改前请评估是否需要该缓存能力。

问题场景

用户在 Linux 上运行 llama-server,加载 GGUF 模型并开启统一 KV(-kvu)、多 slot(-np 8)、Flash Attention(-fa on)、q8_0 的 K/V 缓存(-ctk q8_0 -ctv q8_0)和 --cache-idle-slots。已有多个流式请求在生成时,再发送新请求,服务端会把空闲 slot 保存到 prompt cache,此时其他请求和流被阻塞。

Issue 中的复现步骤是:先启动服务,再起 7 个流式补全并生成几百个 token,然后发送新请求。verbose 日志显示每个空闲 slot 保存约 300 token 耗时 0.7 到 0.9 秒;RTX 3070 Ti(WSL2)下新请求最长等待 5.4 秒,RTX 3090(原生 Linux)下最长等待 2.1 秒。

报错原文

server : saving idle slots to the prompt cache stalls all requests for seconds with interleaved KV cells

20.410333 I slot process_sing: id  2 | task -1 | saving idle slot to prompt cache
20.410627 I srv   prompt_save:  - saving prompt with length 357, total state size = 26.675 MiB (draft: 0.000 MiB)
21.338846 D slot process_sing: id  2 | task -1 | __TEST_TAG_CACHE_IDLE_SLOT__
21.339045 I slot process_sing: id  3 | task -1 | saving idle slot to prompt cache

0.30.721.686 I srv  get_availabl: prompt cache update took 569.17 ms
0.30.721.867 I slot process_sing: id  1 | task -1 | saving idle slot to prompt cache
0.31.282.243 D slot process_sing: id  1 | task -1 | __TEST_TAG_CACHE_IDLE_SLOT__

原因分析

根据 Issue 的定位与日志,最可能的原因是:在统一 KV 与交错 KV cell 布局下,保存空闲 slot 时数据被拆成大量小片段,每个片段触发一次 ggml_backend_tensor_get。Issue 报告中,323 token 的序列触发了 23,256 次阻塞式调用;nsys 分析的一段 kernel 间隙中出现了 82,827 次 cudaMemcpyAsync(其中 82,816 次 device-to-host)以及 82,861 次 cudaStreamSynchronize。这些拷贝和同步发生在 server 线程上,因此其他请求和流需要等待。

Issue 推测首个引入问题的是 50e0ad08fb6906fda9ac2e256e43a4bbf9c85639 (#20993),它默认启用了在新任务开始时保存每个空闲 slot 的循环,而父提交只保存选中的 slot。该提交定位来自源码 diff,不是 timing bisect 结果,因此这里保留“可能原因”的表述。

环境排查

  • 确认 llama.cpp 版本是否为 11387 (8330e96) 或包含 #20993 的版本。
  • 确认启动参数中是否同时启用了 -kvu、--cache-idle-slots 和交错 KV 布局相关配置。
  • 确认是否使用了 CUDA backend,以及 -ngl、-fa on、-ctk q8_0 -ctv q8_0、-np 等参数。
  • 确认运行平台是 WSL2 还是原生 Linux,两者阻塞时长不同,但问题都会出现。
  • Issue 未给出 Python、PyTorch、CUDA 详细版本,如与上游 PR 对比,建议记录实际 CUDA 版本以便核对兼容性。

解决步骤

  1. 记录当前启动命令与版本号,作为对照基线,例如 llama-server -m Qwen3-8B-Q4_K_M.gguf -ngl 99 -fa on -ctk q8_0 -ctv q8_0 -np 8 -kvu -c 20480 -v。
  2. 优先把 llama.cpp 更新到包含 PR #31391 的版本。该 PR 将碎片化设备读取合并,Issue 作者确认它解决了该问题。
  3. 若暂时无法更新,可优先尝试在启动参数中移除 --cache-idle-slots,或在不影响使用的前提下移除 -kvu,以避开空闲 slot 保存路径。这是临时规避,不是已验证修复。
  4. 更新后按原复现步骤重新测试:起 7 个流式补全,让它们生成几百 token,再发送新请求。
  5. 开启 verbose 日志,观察 saving idle slot to prompt cache 与 prompt cache update took 的耗时,确认不再出现秒级阻塞。

验证方法

在相同模型、slot 数、KV 量化与统一 KV 配置下重跑复现步骤,检查 verbose 日志中空闲 slot 保存是否仍出现 0.7 到 0.9 秒级别的耗时;同时观察其他正在流式生成的 slot 是否出现数秒的相邻输出间隔。若使用 nsys,可对比同一时间段内 cudaMemcpyAsync 和 cudaStreamSynchronize 的调用次数是否明显下降。Issue 中提到修复已合并并经复现者用 AIPerf 基准验证,可用同样的基准方式做回归确认。

参考来源

ggml-org/llama.cpp #30252

相关 PR:#31391;疑似引入问题的提交:#20993。

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 32472

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注