# 5.6 推理的两段与前缀缓存

这份代码回答两个问题。第一，一次请求的时间花在哪：读输入（prefill）和逐个生成输出（decode）是不是同一种开销。第二，前缀缓存在你的写法下有没有生效：同一份长上下文，“每轮只在末尾追加”和“每轮在开头插一个当前时间戳”，差别有多大。

有两个脚本，做的是同一组实验：

- `api_timing.py`：调 AI Builder Space，用流式接口计首 token 延迟。服务端发生了什么只能从时间上推断。
- `local_cache.py`：调你本机的 llama-server。服务端会直接告诉你每次请求重新计算了几个输入 token、复用了几个缓存 token、prefill 和 decode 各用了多少毫秒，看得最清楚。

## 需要什么设备

- `api_timing.py`：只用 API，任何电脑都行。
- `local_cache.py`：Apple Silicon Mac，或一张 NVIDIA 显卡（需要支持 CUDA 的 llama.cpp），或者纯 CPU（会慢很多）。模型是 Qwen3.5-2B 的 4-bit GGUF，约 1.2 GB，8 GB 内存就够。实验 3 用约 5 万 token 的上下文，显存或内存再多留 2 GB 左右。

## 安装与运行

API 版：

```bash
cd 5_6_prefill_cache
uv venv && source .venv/bin/activate      # 没有 uv 就用 python3 -m venv .venv
uv pip install -r requirements.txt
export AI_BUILDER_TOKEN=sk_你的key
python api_timing.py                      # 默认 deepseek，三个实验，约 3 分钟，花费约 0.4 美元
python api_timing.py --model grok-4-fast --only cache
```

模型要支持流式：`deepseek`、`grok-4-fast` 可以，`kimi-k3` 和 Gemini 系列在 AI Builder Space 上不支持流式。

本地版（只用 Python 标准库，不用装包）：

```bash
# 1. 装 llama.cpp。Mac：brew install llama.cpp；NVIDIA：按 llama.cpp 仓库的说明编译 CUDA 版
# 2. 在另一个终端起服务，第一次会自动下载模型（--no-mmproj 表示不下载用不到的看图部分）
llama-server -hf unsloth/Qwen3.5-2B-GGUF:Q4_K_M --no-mmproj -c 65536 -np 1 --port 8080
# 3. 跑实验
python3 local_cache.py                    # 端口不是 8080 就加 --url http://127.0.0.1:端口
```

## 该看到什么

**实验 1（输入变长）**：首 token 延迟随输入变长而变长。本地版能直接看到 prefill 的速度是每秒几千 token。

**实验 2（输出变长）**：总时间随输出长度几乎线性增长，每个输出 token 的时间基本不变。decode 的速度是每秒一两百 token，比 prefill 慢一个数量级以上。这就是“两段是两种计算”：prefill 一次并行处理整段输入，decode 每步只能出一个 token。

**实验 3（前缀缓存）**：只在末尾追加的写法，从第 2 轮起首 token 延迟明显下降；开头插时间戳的写法，每一轮都和第 1 轮一样慢，因为开头一变，后面的缓存全部作废。

我们的样例输出（2026-10-23）在 `sample_output/`：

| | 只在末尾追加（第 2 轮起） | 开头插时间戳（第 2 轮起） |
|---|---|---|
| 本地 Mac Studio（M3 Ultra），Qwen3.5-2B Q4_K_M，每轮约 5.1 万 token | 每轮重算 17–18 个 token、复用约 5.1 万，prefill 0.04 秒 | 每轮重算约 5.1 万 token、复用 0，prefill 约 14.2 秒 |
| API：deepseek，每轮约 10.7 万 token | 首 token 延迟中位数 1.94 秒 | 3.16 秒 |
| API：grok-4-fast，每轮约 10.1 万 token | 1.80 秒 | 3.95 秒 |

本地 6 轮合计重新计算的输入 token：追加写法 51,222，时间戳写法 308,026，差 6 倍。本地 decode 稳定在约 183 token/秒（每 token 5.5 毫秒），prefill 在 1.7 万 token 输入时约 4,900 token/秒。API 上 deepseek 的首 token 延迟从 2 千 token 输入的 1.2 秒涨到 29 万 token 输入的 3.6 秒，每个输出 token 约 3 毫秒。

API 上的数字有网络和排队的波动，同一轮可能差半秒，所以要看中位数和趋势，不要盯单个数字。

**一个限制**：AI Builder Space 的 usage 只返回输入、输出、总 token 三个数，不报缓存命中了多少，计费也不区分命中和未命中，所以 API 版只能从延迟上看到缓存，省下的是时间而不是钱。直接用模型厂商自己的 API 时（例如 DeepSeek 官方 API 在 usage 里返回命中 token 数），命中部分按便宜得多的价格收费，同样的写法差别还会体现在账单上。

## 换成你自己的数据

两个脚本都可以用 `--doc 你的文件.txt` 把实验 3 的长文档换成你自己的材料，比如你的 agent 每轮都会带上的系统提示、工具说明和项目文档。如果你想检查自己的 agent 有没有破坏缓存，最直接的办法是把它每轮实际发出去的完整提示存下来，用本地版逐轮看 `prompt_n`（重算）和 `cache_n`（复用）：从第 2 轮起重算的 token 数如果没有降到几十，说明前缀里有东西在变，常见的是时间戳、随机 ID、每轮重新排序的工具列表。
