引言
DeepSeek V4 系列模型(deepseek-v4-flash、deepseek-v4-pro)支持思考模式(Thinking Mode):通过 thinking: {type: enabled|disabled} 开关思考,通过 reasoning_effort: low|high|max 控制思考强度。同时,DeepSeek API 对所有请求默认启用上下文硬盘缓存(Context Caching on Disk):相同前缀的输入只按缓存命中价计费(以 2026 年 8 月官方定价为例,V4 Flash 缓存命中输入 ¥0.02/百万 token,仅为未命中 ¥1/百万 token 的 1/50)。
一个自然的工程问题是:如果应用在同一会话中切换思考模式或思考档位(例如从 effort: none 切到 effort: high),此前建立的提示前缀缓存还能复用吗? 这个问题在官方文档中没有直接答案,市面上的博客与社区讨论也未见专门讲解。
本文通过一组受控实验回答了这个问题,并进一步揭示了缓存本身的组织方式:DeepSeek 的缓存是块级、渐进落盘的——这解释了为何切换档位会完全失效,也解释了为何单字符扰动的影响是状态依赖的。文章给出完整的实验设计、数据与可复现步骤。
背景:DeepSeek 的前缀缓存机制
根据官方文档,DeepSeek 的上下文硬盘缓存规则可以概括为三点:
- 前缀匹配:缓存命中要求后续请求完整匹配一个已落盘的”缓存前缀单元”(用户输入 token 序列的前缀部分)。
- 三种落盘时机:请求结束位置(用户输入结束与模型输出结束处)、公共前缀检测(多次请求间发现公共前缀时)、固定 token 间隔(长输入按间隔截取单元)。
- 尽力而为:缓存构建耗时为秒级,命中率不保证 100%,不用的缓存数小时到数天内自动清理。
缓存命中的计费结果通过 usage 字段返回:prompt_cache_hit_tokens / prompt_cache_miss_tokens(Chat Completions 通道),或 input_tokens_details.cached_tokens(Responses 通道)。
思考模式文档则说明了另一组事实:思考模式默认开启,默认档位为 high;effort 与模型实际推理强度的映射为 low → low、high → high、xhigh → max、max → max。文档没有说明模式切换与缓存的关系。
缓存”前缀单元”按实际发送的输入 token 序列计算,而思考模式很可能改变了实际发送的序列(例如注入指令)——这正是本文要验证的核心假设。
实验设计
研究问题
- RQ1(切换矩阵):在
none / low / high / max之间切换档位,前缀缓存能否复用?切换后需要几次请求才能恢复高命中? - RQ2(注入定位):推理模式是否在输入中注入额外 token?注入量是否与输入长度相关?
- RQ3(机制对照):输入内部不同位置的内容变化,如何影响缓存失效范围?
- RQ4(形态与通道):多轮对话形态(system + 长文档 + user)下结论是否成立?Responses 与 Chat Completions 两个协议通道是否共享缓存池?
控制变量
- 固定 API key、固定模型(
deepseek-v4-flash)、固定输入模板;思考模式下temperature/top_p等采样参数不生效(官方文档),天然受控。 - 每个实验使用全新唯一输入(内容从未发送过,并记录输入内容的 SHA-256 指纹便于审计)。这保证每个序列的首次请求必然 0% 命中(即建缓存请求),实验结果中所有”首次”行都通过该不变量校验。
- 关键序列(切换矩阵)完整执行两轮,验证可复现性。
- 请求间隔 2 秒(官方文档说明缓存落盘耗时为秒级)。
- 测试时间:2026 年 8 月 8 日;直接请求官方端点
https://api.deepseek.com(Responses 与 Chat Completions 两个协议),不经过任何网关或代理。
这里有一个容易被忽视的陷阱:凡报告”首次请求即高命中”的缓存实验,都应怀疑其输入在此之前是否已被发送过(例如测试前的校准请求会悄悄建立缓存)。“首次 0% 命中”是最便宜的实验卫生检查。
观测指标
每个请求记录:输入 token 数(in)、缓存命中 token 数(cached)、未命中 token 数(miss)、命中率(cached/in)、推理 token 数(r_tokens)、输入指纹。命中率为主要指标,in 的绝对值变化用于检测服务端注入的指令 token(同一输入在不同档位下 in 的差值即为注入量)。
输入模板
输入由三段独立主题文本拼接构成(神经语言模型 / 操作系统 / 公钥密码学,各约 590 token),用于探针定位;另有同质长前缀、多轮文档、短文本(117 token)等变体。每实验输入互不相同。
实验与结果
实验 A:模式切换矩阵
方法:同一输入(1476 token,指纹 373fff3b…),按 none → none → high → high → none → low → max → max → none 的顺序依次请求(Responses 通道),完整执行两轮。
结果(第一轮;第二轮完全复现模式):
| 步骤 | 角色 | effort | in | cached | 命中率 | 推理 token |
|---|---|---|---|---|---|---|
| A1 | 首次 | none | 1476 | 0 | 0.0% | 0 |
| A2 | 同档 | none | 1476 | 1408 | 95.4% | 0 |
| A3 | 切换 | high | 1555(+79) | 0 | 0.0% | 302 |
| A4 | 同档 | high | 1555 | 1536 | 98.8% | 857 |
| A5 | 切回 | none | 1476 | 1408 | 95.4% | 0 |
| A6 | 切换 | low | 1476 | 1408 | 95.4% | 611 |
| A7 | 切换 | max | 1568(+92) | 0 | 0.0% | 3558 |
| A8 | 同档 | max | 1568 | 1536 | 98.0% | 11910 |
| A9 | 切回 | none | 1476 | 1408 | 95.4% | 0 |
发现:
- 首次请求命中率为 0%(建缓存),同档第二次请求恢复 95% 以上——这是”干净实验”的基本形态。
none与low共享同一缓存(A6 命中 95.4%,in与 none 完全相同)——low档不注入任何额外 token。high与max各自独立:in分别比基线多 79 与 92 个 token,即服务端向输入中注入了固定长度的指令;切换后首次请求命中率为 0%(连此前 1408 token 的命中块都完全失配)。- 同档位内部稳定复用:
high第二次请求即恢复 98.8%,max为 98.0%。 - 回切
none立即恢复:none 的缓存未被切换操作驱逐,A5/A9 命中率与 A2 一致。 - 第二轮中,因第一轮已建立各档位缓存,
high/max切换行直接命中(98.8%/98.0%),其余行与第一轮一致——同时验证了缓存的分钟级持久性。
实验 B:注入指令的注入量
方法:用三段式输入(1770 token)在 none 档建立缓存后,依次切换 high、max、low,观察命中率与 in 的变化。
结果:
| 步骤 | 角色 | effort | in | cached | 命中率 |
|---|---|---|---|---|---|
| B1 | 首次 | none | 1770 | 0 | 0.0% |
| B2 | 同档 | none | 1770 | 1664 | 94.0% |
| B3 | 切换 | high | 1849(+79) | 0 | 0.0% |
| B4 | 切换 | max | 1862(+92) | 0 | 0.0% |
| B5 | 切换 | low | 1770 | 1664 | 94.0% |
发现:切换 high/max 后,此前全部 1664 个命中块完全失配(cached = 0)。low 档再次确认与 none 完全共享。注入量(+79 / +92)在 117–1770 token 的多种输入长度下均为同一数值,说明它是与输入无关的固定指令模板。
实验 B’:单字符改动的影响(状态依赖)
方法:在 none 档下,对同一输入(1770 token,已建立缓存)的头部、中部、尾部各改动一个字符,观察命中率变化。为观察缓存状态的影响,该实验在公共块落盘前与落盘后各执行一轮(落盘后含多次重复测量)。
结果(公共块落盘后,多次测量完全一致):
| 改动位置 | in | cached | 命中率 |
|---|---|---|---|
| 无(基线) | 1770 | 1664 | 94.0% |
| 头部一个字符 | 1770 | 1664 | 94.0% |
| 中部一个字符 | 1772 | 1664 | 93.9% |
| 尾部一个字符 | 1773 | 1664 | 93.9% |
发现(重要的状态依赖行为):
- 公共块落盘后,单字符改动几乎不影响命中率(94%),无论改动在头部、中部还是尾部——缓存命中不需要整条前缀从头连续匹配。
- 但在缓存建立的早期,同样的改动曾导致命中率崩塌至 0%——原因是”公共前缀检测”落盘是渐进的:第一次遇到变体请求时,服务端只有”完整前缀”单元,改动后无法匹配;随着相似请求反复出现,重复的公共块被单独落盘,此后任意单字符变体都能命中公共块。
- 这一对比恰好解释了官方文档”公共前缀检测落盘”的实际效果:它是按块工作的,且需要多次请求触发。
实验 D:切换后的收敛速度
方法:新输入(698 token)在 none 档建立缓存后,连续 4 次以 high 档请求。
结果:
| 步骤 | effort | in | cached | 命中率 |
|---|---|---|---|---|
| D1 | none(首次) | 698 | 0 | 0.0% |
| D2-1 | high(切换) | 777 | 0 | 0.0% |
| D2-2 | high | 777 | 768 | 98.8% |
| D2-3 | high | 777 | 768 | 98.8% |
| D2-4 | high | 777 | 768 | 98.8% |
发现:切换后仅需 1 次未命中请求即可重建缓存,第 2 次请求恢复满命中(98.8%)。收敛代价 = 一次未命中计费(777 token × ¥1/百万 ≈ ¥0.0008)。
实验 E:多轮对话形态
方法:采用真实 Agent 的多轮形态(system 指令 + 长文档 + user 提问,Chat Completions 通道,510 token),验证单轮结论在带系统提示的场景下是否成立。
结果:
| 步骤 | 角色 | 模式 | in | cached | 命中率 |
|---|---|---|---|---|---|
| E1 | 首次 | disabled | 510 | 0 | 0.0% |
| E2 | 同档 | disabled | 510 | 384 | 75.3% |
| E3 | 切换 | enabled/high | 589(+79) | 0 | 0.0% |
| E4 | 切回 | disabled | 510 | 384 | 75.3% |
发现:多轮形态下结论与单轮完全一致——切换全 miss、回切立即恢复;high 档注入量仍为 +79。(E2 命中率 75.3% 低于长输入实验,与输入较短、尾部未对齐块占比更高有关。)
实验 F:两个协议通道的缓存共享
方法:同一输入(594 token)先用 Responses 通道建立缓存(none 与 high 各一次),随后用 Chat Completions 通道以相同模式请求。
结果:
| 步骤 | 通道 | 角色 | 模式 | in | cached | 命中率 |
|---|---|---|---|---|---|---|
| F1 | responses | 首次 | none | 594 | 0 | 0.0% |
| F2 | responses | 切换 | high | 673 | 0 | 0.0%(建缓存) |
| F3 | chat | 跨通道 | none | 594 | 512 | 86.2% |
| F4 | chat | 跨通道 | high | 673 | 640 | 95.1% |
发现:
- Responses 与 Chat Completions 共享同一前缀缓存池:chat 通道的请求命中了 responses 通道建立的缓存(F3 命中 F1、F4 命中 F2)。
- 两通道注入指令完全一致(均为 +79):说明模式指令注入发生在服务端协议转换的公共层,而非通道特有问题。
实验 G:最短缓存粒度
方法:用 117 token 的短输入验证缓存阈值。
结果:
| 步骤 | effort | in | cached | 命中率 |
|---|---|---|---|---|
| G1 | none(首次) | 117 | 0 | 0.0% |
| G2 | none(同档) | 117 | 0 | 0.0% |
| G3 | high(切换) | 196(+79) | 0 | 0.0% |
发现:117 token 的输入无法建立缓存(第二次仍 0% 命中),而另一组 185 token 的输入则可以命中 128 token。结合所有实验中 cached 值均为 128/256 的倍数,说明缓存的最小粒度约为一个 128-token 块:低于一个块的输入无法命中。注入量仍为 +79,与输入长度无关。
讨论
机制模型
综合所有实验,可以构建一个自洽的块级缓存模型:
- 缓存按块(约 128/256 token)组织:命中不需要整条前缀从头连续匹配;块内容一致即可命中(B’ 实验:头部单字符改动后其余块照常命中)。
- 块渐进落盘:首次请求只落盘”完整前缀”单元;相同或相似请求反复出现后,重复的公共块被单独落盘(官方文档的”公共前缀检测”实际按块工作)。落盘前,任何变体请求都会 miss;落盘后,仅受影响的块及其依赖失效。
- 模式指令注入:思考档位变化时,服务端在输入最前面注入固定长度指令(
high79、max92、low0;none与low等价)。这产生两个后果:输入从第一个 token 起就不同;且因 token 数变化,后续块的对齐边界整体错位。因此切换档位后所有块都失配——这就是实验中稳定复现的 0% 命中。 - 缓存池全局共享:缓存按 API key 组织,Responses 与 Chat Completions 两个协议通道共用(实验 F);
none缓存不会因切换操作被驱逐(实验 A 回切立即恢复)。
与社区观测的关系
- 社区对 V3 的逆向实验(deepseek-harness 项目)观测到”中段改动保留开头若干块”与”约 1024 token 起始阈值”,与本文的块级模型方向一致,但具体参数不同(本文观测到 117 token 不缓存、185 token 可缓存,即块粒度约 128 token)。版本与实现差异无法排除。
- 本文最关键的、此前无人报告过的发现是:推理档位切换使缓存 0% 命中,且原因是固定指令注入导致的块对齐错位——它比”单字符扰动”严重得多,因为扰动只影响单个块,而注入影响所有块的边界。
工程启示
对使用 DeepSeek API 的工程实践(网关、Agent 框架):
- 固定档位:同一会话/工作负载应固定
reasoning_effort,避免在none/high/max之间频繁切换;每次切换的代价是一次全量未命中的输入计费(约输入长度 × ¥1/百万 token)。 none与low可视为同一档位:两者共享缓存且low不注入指令,若需要”轻思考”可放心从none切换。- 微扰动的代价低于直觉:单字符级的输入扰动(如时间戳、ID 变化)在公共块落盘后对命中率影响很小(实验 B’),不需要为”改一个字符”过度焦虑;但改变 token 数目的改动(插入/删除内容)会破坏块对齐,代价接近一次全 miss。
- 切换是显式成本:切换后第 2 次请求即恢复高命中(实验 D),因此”短时切换再切回”的实际成本 = 两次全量未命中。
- 缓存是尽力而为:官方明确不保证 100% 命中,生产设计应以”未命中可接受”为前提,把命中率当作可测量的优化指标而非承诺。
局限
- 所有观测均为黑盒(仅通过
usage字段与输入长度推断),未获取服务端日志;块粒度(128/256)为观测推断,非契约值。 - 测试仅覆盖
deepseek-v4-flash(Responses 通道对deepseek-v4-pro尚未开放);pro的注入量与共享行为待其开放后验证。 - 缓存为”尽力而为”,具体数字可能随服务端实现调整;注入 token 数(79/92)为观测值,非契约值。
- 位置敏感性实验展示了状态依赖行为(公共块落盘前后结论不同),本文如实报告两种状态下的观测。
结论
- 切换推理模式/档位会使前缀缓存完全失效(0% 命中,稳定复现于多轮、双通道、多种输入长度):服务端向输入最前面注入固定长度指令(
high+79、max+92、low0),改变后的输入在块级缓存中无法匹配任何已落盘块。 none与low共享缓存;high、max各自独立;同档位内稳定复用(95–99%)。- 切换后第 2 次请求即恢复高命中;回切原档位立即恢复(缓存不被驱逐)。
- 缓存是块级、渐进落盘的:单字符扰动在公共块落盘后影响很小,但任何改变 token 数量的改动都会破坏块对齐。
- Responses 与 Chat Completions 两个协议通道共享同一缓存池,注入行为一致。
- 工程上应固定档位使用,把切换当作”一次全量未命中计费”的显式成本来管理。
附录:复现
实验脚本、测量数据与复现步骤见 github.com/JiangYingjin/deepseek-cache-experiments。