5.6 节实验:测读输入与逐个生成两段各花多少时间,以及开头插时间戳会不会让前缀缓存失效(API 版与本地 llama.cpp 版)
| 文件 / 目录 | 说明 | 大小 |
|---|---|---|
| 📁 ../ | 返回上级目录 | - |
| 📁 sample_output/ | 子目录 | - |
| 📄 README.md | 4.9 KB | |
| 📄 api_timing.py | 8.1 KB | |
| 📄 local_cache.py | 5.3 KB | |
| 📄 requirements.txt | 13 B |
这份代码回答两个问题。第一,一次请求的时间花在哪:读输入(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 版:
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 标准库,不用装包):
# 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、每轮重新排序的工具列表。