第2章:多类型数据的访问规律建模
把四类长记忆数据(KV / 向量 / 多模态 / 推理状态)的访问模式做成可量化的指纹:六维通用框架 + 每类数据的具体模型 + 全栈 profile 工具链建议
第 1 章把四类长记忆数据的”性格”讲清了——但对工程师来说,定性描述不够;放置算法、迁移策略、成本模型都需要可量化的访问规律输入。本章把”性格”翻译成”指纹”:一套适用于任何长记忆数据的六维通用建模框架,然后逐类给出 KV Cache、向量索引、多模态对象、中间推理状态各自的具体模型与典型测量值。最后给一份全栈 profile 工具链建议——告诉你拿到一个生产负载,从哪些层面采数据、怎么把数据汇成本模块后续章节能直接用的输入。本章是后续 Ch8 统一表示、Ch9 分层放置、Ch11 成本建模的”原料供应商”——没有它,所有调度算法都是无源之水。
📑 目录
- 1. 为什么访问规律是放置算法的前提
- 2. 六维通用建模框架
- 3. KV Cache 的访问指纹
- 4. 向量索引的图遍历轨迹
- 5. 多模态对象的 burst-read 模式
- 6. 中间推理状态的写突发与回放概率
- 7. 四类访问模式的横向对比
- 8. 全栈 profile 工具链建议
- 9. 把 profile 数据喂给后续章节
- 自我检验清单
- 参考资料
1. 为什么访问规律是放置算法的前提
1.1 调度算法的”输入-决策-输出”
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 访问规律 (输入) │ → │ 放置/迁移决策 │ → │ 存储介质 (输出) │
│ 本章建模的对象 │ │ Ch9 / Ch10 │ │ HBM / DRAM / SSD │
└──────────────────┘ └──────────────────┘ └──────────────────┘
🌟 关键事实:调度器的所有聪明都是”对工作负载分布的拟合”。给它错的输入,再聪明的算法也是白搭。LRU、LFU、ARC、各种 RL 调度器之所以效果天差地别,核心差异不在算法,在它对工作负载分布的假设是否吻合实际。
1.2 长记忆系统比传统系统难在哪
传统系统(数据库 buffer pool、文件系统 cache、CDN)的访问规律建模已经有几十年研究——为什么不能直接套?
| 传统系统假设 | 长记忆系统现实 |
|---|---|
| 单一数据类型(数据页 / 文件块) | 四类数据混存,各自分布完全不同 |
| 访问模式相对稳定(典型 Zipf 分布) | 强时变、跨用户跨任务差异大 |
| 失效成本对称(都是从磁盘重读) | 失效代价非对称——KV 失效要重 prefill,代价远超磁盘 IO |
| 数据语义对系统透明 | 数据语义可被利用——LLM 对小扰动鲁棒,可做有损压缩 |
⭐ 结论:必须重新建模,不能套传统经验。这正是本章要做的事。
2. 六维通用建模框架
把任何一类长记忆数据的访问规律,都用同一套六维度量描述——这样四类数据之间可比、可统一处理。
2.1 六个维度
| 维度 | 含义 | 用什么测 |
|---|---|---|
| D1 频率分布 | 一段时间窗口内,各对象被访问次数的分布 | 按对象 ID 计数 + 拟合分布(Zipf / 长尾) |
| D2 时序局部性 | 最近被访问的对象,下一刻再被访问的概率 | inter-access time 分布、stack distance |
| D3 工作集大小 | 给定时间窗口内被访问到的不同对象总量 | working-set 曲线 |
| D4 访问粒度 | 每次访问的数据块大小 | 每个 IO 的字节数分布 |
| D5 IO 放大系数 | 一次”逻辑访问”触发的”物理 IO”次数 | 应用层访问 / 存储层 IO 比值 |
| D6 失效代价 | 这次访问 miss 一级缓存,要付出多大代价 | miss 时延 + 重建延迟(如 KV 重 prefill) |
2.2 为什么是六个不是更多
调度器的决策本质是回答四个问题:
- “这个对象多重要?” ← D1 频率 + D2 时序
- “我能装下多少?” ← D3 工作集
- “挪它要花多少钱?” ← D4 粒度 + D5 放大
- “挪错了赔多少?” ← D6 失效代价
🍎 直觉:六个维度像是给数据画的”身份证 + 信用评分”——任何放置策略最终都是在这六维空间里挑落点。
2.3 工程化:把六维做成”指纹向量”
# 概念示意,不是某个具体框架的 API
@dataclass
class AccessProfile:
# D1
frequency_dist: Distribution # 频率分布
# D2
locality_curve: Callable # P(access at t+τ | access at t)
working_set_curve: Callable # working set 大小随窗口
# D3
typical_working_set: int
# D4
io_size_dist: Distribution
# D5
amplification_factor: float # 1 次逻辑访问 = ? 次物理 IO
# D6
miss_penalty: PenaltyModel # miss 时延 + 重建代价
目标:每类数据(乃至每个 session / 每个 model)都有自己的 AccessProfile,调度器对它们一视同仁地处理。
3. KV Cache 的访问指纹
3.1 指纹概览
| 维度 | KV Cache 的特征 |
|---|---|
| D1 频率 | 极度强偏 active session;同一 session 内 token-level Zipf-like(系统 prompt + 早期 token 高频) |
| D2 时序 | 强短期局部性(active session 每 step 都用),长期局部性弱(session 结束后几乎不复用) |
| D3 工作集 | active sessions 数 × 平均 KV 大小;典型几 GB - 几百 GB |
| D4 粒度 | layer-wise 块(每 layer 几 MB);整 KV 几 GB |
| D5 放大 | 每次 token decode = N_layer × 整 sequence 的 KV 扫描——放大系数等于 layer 数 |
| D6 失效代价 | 极高 —— miss 意味着重做整段 prefill,可能数百毫秒到数秒 |
3.2 KV 的两种使用阶段:画像不同
Prefill 阶段: Decode 阶段:
───────────── ─────────────
KV 大批量写入 KV 小步逐次写
访问模式 = 顺序写主导 访问模式 = 每步 attention 全扫
工作集 = 单 prompt 的全部 KV 工作集 = 历史所有 token 的 KV
持续时间 = 一次性 持续时间 = 整轮 decode(几十秒到分钟)
🌟 关键洞察:同一份 KV 数据在 prefill 和 decode 两个阶段呈现不同的访问指纹——这给了”分阶段差异化放置”的空间(对应模块四的 PD 解耦思想)。
3.3 跨实例 / 跨用户的 KV 复用机会
| 复用类型 | 复用率(典型) | 价值 |
|---|---|---|
| 系统 prompt(各租户共享) | 高 | 极高(几 KB - 几 MB,被所有请求复用) |
| RAG 文档前缀 | 中-高 | 高(文档级共享,跨用户) |
| 多轮对话历史 | 中(单用户,多 session) | 中(取决于会话生命周期) |
| 单次工具调用结果 | 低 | 低(几乎不复用) |
📍 设计含义:LMCache、Mooncake 这类系统正是在挖这部分的”复用率红利”——系统 prompt 这一片放 HBM 永驻是高 ROI 决策,因为它几乎对所有请求都有效。
3.4 KV 工作集的”长尾观察”
不止单峰分布,长记忆系统下 KV 工作集是双峰:
访问频率
↑
│ ┌──────┐ 长尾(一次性 / 历史会话)
│ │ HOT │ ▼
│ │ │ ┌─────┐ ┌─────┐ ┌────────────────────────┐
│ │ │ │ │ │ │ │.........................│
│ └──────┘ └─────┘ └─────┘ └─────────────────────────┘
───────────────────────────────────────────────────────► 对象
(active) (recent) (cold)
⭐ 决策启发:HOT 部分应永驻 HBM,长尾部分应该激进卸载到 SSD/远端,中间灰区是放置算法的真正战场。
4. 向量索引的图遍历轨迹
4.1 指纹概览
| 维度 | 向量索引的特征 |
|---|---|
| D1 频率 | 非常强的长尾——少数热点向量(高频内容、热点用户)被反复访问 |
| D2 时序 | 弱(查询彼此独立),但簇局部性强(类似 query 检索同一簇) |
| D3 工作集 | 千万到百亿向量,典型 GB-TB 级 |
| D4 粒度 | 单向量 KB 级、邻居表 KB 级、簇几 MB |
| D5 放大 | 极高 —— 一次 ANN 查询触发几十-几百次邻居展开,每次都是一次随机 IO |
| D6 失效代价 | 中(SSD 读延迟,但是可批量并发) |
4.2 ANN 主流算法的访问模式
图索引(HNSW / Vamana / NSG)
入口点 → (展开邻居 → 计算距离 → 选择继续展开的) × 几十轮 → 返回 top-k
每一步都要读一个邻居表(几 KB)和一组向量(几十 KB)——对存储是高随机 IO + 中等粒度。
聚类索引(IVF / SPANN)
query → (查找 top-N 簇,每簇是几千-几万向量) → 簇内逐一比较 → 返回 top-k
每次访问几个完整簇——对存储是中随机 IO + 大粒度顺序读。
🧠 关键洞察:图索引比聚类索引对SSD 随机 IO 更敏感;聚类索引比图索引对带宽更敏感。在长记忆系统的多类型混存场景下,图 vs 聚类的选择影响存储介质的最佳选型。
4.3 IO 放大的具体测量
应用层视角: "查询用户 X 的相似记忆,top-10"
存储层视角: HNSW 走 50 跳 → 50 次邻居表读 + 200 次向量读
≈ 250 次 SSD random IO
≈ 几 ms - 几十 ms(取决于盘并发)
📍 设计含义:单条向量查询的 IO 放大系数往往是 100-1000 倍——这是为什么传统纯 SSD 方案需要配合 PQ-compressed 的 RAM 副本(参考 DiskANN 的设计)。
4.4 长记忆向量库的特殊性
| 与”通用向量库”的差异 | 影响 |
|---|---|
| 写入流式持续(Agent 不断追加新记忆) | 索引必须支持增量更新(FreshDiskANN 思路) |
| 查询模式与 LLM 推理耦合(attention 触发) | 召回延迟必须卡在毫秒级(参考 AlayaDB) |
| 数据有强 metadata(用户 / 时间窗 / 任务类型) | 过滤式 ANN 是必备(Filtered DiskANN 思路) |
| 多模态混合(文本 + 图像 embedding 共存) | 不同模态的距离度量与索引可能要分开 |
5. 多模态对象的 burst-read 模式
5.1 指纹概览
| 维度 | 多模态原始对象的特征 |
|---|---|
| D1 频率 | 极度长尾,90% 对象一辈子可能就被访问一两次 |
| D2 时序 | 弱时序,但有事件驱动 burst(一个 Agent 任务突然要调一批图片) |
| D3 工作集 | TB 级原始数据 + GB-TB 级 embedding |
| D4 粒度 | embedding KB 级 / 原始对象 MB-GB 级 |
| D5 放大 | 低(对象级直读) |
| D6 失效代价 | 中-高(顺序大块读时延 + 网络成本) |
5.2 双流共生:embedding 与 blob
embedding(KB) blob(MB)
│ │
│ "这是一张包含猫的图" │
│ │
▼ ▼
高频检索路径 低频原始访问
"找最像的 100 张" "把这一张拿来给模型看"
🧠 关键洞察:embedding 和 blob 应该分开放但要 metadata 一致——embedding 进 DRAM 加速检索,blob 留 SSD 等具体被需要时再读。强行把它们放同一级是浪费。
5.3 burst 模式建模
事件驱动的 burst 比平稳访问更难处理:
访问频率
↑
│ ┌──┐ ┌──┐
│ │ │ │ │ ← 突发的 burst
│ ─┘ └──────────────────────────┘ └──── ← 平稳低频
──────────────────────────────────────────► 时间
▲ Agent 启动一个新任务,一次性拉一批历史多模态记忆
📍 设计含义:burst 是 prefetch 的最佳应用场景——根据 Agent 当前任务上下文,提前把可能用到的多模态对象从 SSD 拉到 DRAM。这正是 Agent 长记忆 + 多模态系统的特色优化点。
5.4 顺序 IO 友好的存储格式
多模态对象最不适合零碎 random IO——存储时就应该:
- 用列式 + chunk 格式(Lance / Parquet)
- 把 embedding 和 metadata 紧邻 blob 存放,避免分别 IO
- 大对象优先走 GPUDirect Storage 直 DMA
6. 中间推理状态的写突发与回放概率
6.1 指纹概览(学术界几乎空白)
| 维度 | 中间推理状态的特征 |
|---|---|
| D1 频率 | 写多读少 —— 99% 的 scratchpad 写完就丢 |
| D2 时序 | 强短期局部性(刚写完很可能很快读),长期局部性几乎为零 |
| D3 工作集 | 单任务 MB-GB,跨任务 TB 级(若全保留) |
| D4 粒度 | KB 级零碎写 |
| D5 放大 | 中(写入要 commit,读取偶发但可能是树状回放) |
| D6 失效代价 | 低-中(失败的尝试本来就可丢) |
6.2 三类典型形态
| 形态 | 例子 | 关键特征 |
|---|---|---|
| Linear scratchpad | CoT 推理 | 顺序追加,偶尔回看一两步 |
| Tree-shaped reasoning | MCTS / ReAct tree | 分支多,大部分分支被剪枝 |
| Tool call trace | Function call I/O | 强时序,经常出错重试,关键步骤要持久化 |
6.3 选择性持久化的语义
任务执行中:
关键决策步骤 ── 记入持久化层(SSD 副本)
探索性分支 ── 临时内存(失败就丢)
Tool call I/O ── 全持久化(用于回放/调试)
CoT thoughts ── 短期保留 + 选择性归档
🌟 关键观察:这一类数据是”按语义分级,而不是按访问频率分级”——传统的 LRU 之类规则失效。需要应用层提示(Agent 框架告诉存储层”这个步骤重要”)。
6.4 为什么这一类是项目的”低垂果实”
学术界几乎没有专门的工作研究中间推理状态的存储——但生产 Agent 系统已经撞墙(Letta、Voyager、AutoGPT 等都各自工程地造轮子)。这意味着:
- ⭐ 学术增量大(几乎从零开始)
- ⭐ 工业需求强(已有痛点)
- ⭐ 与第二/第三模块结合度高(分离式池化 + 容错都对它特别重要)
📍 建议:把这一类作为本项目第一模块的特色亮点——KV / 向量 / 多模态都已是热门战场,中间推理状态是无人区。
7. 四类访问模式的横向对比
把六维度量画成一张大表:
| 维度 | KV Cache | 向量索引 | 多模态对象 | 推理状态 |
|---|---|---|---|---|
| 频率分布 | 强偏 active | 长尾 + 簇热点 | 极长尾 | 写多读少 |
| 时序局部性 | 强短期 | 弱 | 事件 burst | 强短期 |
| 工作集 | GB - 数百 GB | GB - TB | TB+ | MB - GB |
| 访问粒度 | layer 块(MB) | 向量(KB)/簇(MB) | 对象(MB-GB) | KB 零碎 |
| IO 放大 | layer 数(几十 ×) | 100-1000 × | 1 × | ~1 × |
| 失效代价 | 极高(重 prefill) | 中 | 中-高 | 低-中 |
| 可有损压缩 | 中 | 高 | 低 | 中-高 |
| 生命周期 | 秒-分钟 | 月-年 | 月-年 | 分钟-小时 |
| 首选介质 | HBM(active)+ DRAM(spill) | DRAM(导航)+ SSD(主体) | DRAM(embedding)+ SSD(blob) | DRAM(active)+ SSD(归档) |
7.1 决策启发
🌟 观察一:没有任何两类数据的”首选介质”完全相同——任何”统一缓存”策略都是错的,必须按类分管 + 在统一抽象下汇总。
🌟 观察二:IO 放大系数差三个数量级(KV 几十、向量 1000、多模态 1)——意味着相同的物理带宽下,不同数据类型能支持的”应用层 QPS”差异巨大。这是容量规划的关键约束。
🌟 观察三:失效代价不对称(KV 极高,scratchpad 低)——意味着调度器在资源紧张时该先牺牲谁有清晰答案。这给了我们写”代价感知的 eviction policy” 的理论基础。
8. 全栈 profile 工具链建议
要把上面的模型在生产负载上具体测出来,需要分层 profile。
8.1 工具链分层
┌──────────────────────────────────────────────────┐
│ 应用层 (Agent / 推理 framework) │
│ - LangGraph / Letta / vLLM / SGLang 内置事件 │
│ - OpenTelemetry trace + 自定义 attribute │
└──────────────────────────────────────────────────┘
↓ 关联
┌──────────────────────────────────────────────────┐
│ 框架层 (PyTorch / NCCL / KV manager) │
│ - PyTorch Profiler + Kineto │
│ - vLLM / Mooncake 自带的 KV trace │
└──────────────────────────────────────────────────┘
↓ 关联
┌──────────────────────────────────────────────────┐
│ 系统层 (kernel / IO) │
│ - eBPF (bpftrace / bcc):IO 路径全捕 │
│ - blktrace + iostat:块层 IO 模式 │
│ - DCGM:GPU 内存带宽 / HBM 占用 │
│ - Nsight Systems:跨 CPU/GPU 时间线 │
└──────────────────────────────────────────────────┘
8.2 关键采集点(对应六维度)
| 维度 | 采集来源 |
|---|---|
| D1 频率 | 应用层日志(对象 ID + 访问计数) |
| D2 时序 | OpenTelemetry trace 时间戳 + inter-access time 直方图 |
| D3 工作集 | 滑窗 unique-key 计数 |
| D4 粒度 | eBPF 抓 read/write syscall + 块层 IO size 分布 |
| D5 放大 | 应用层 / 系统层 IO 计数比值(应用一个 query 对应了多少次 syscall) |
| D6 失效代价 | 应用层 miss 事件 + 重建延迟分布 |
8.3 推荐起步组合(轻量上手)
如果一上来就上 eBPF + Nsight 全套有点重,推荐:
- 应用层:在 vLLM / Mooncake / 你的 Agent 框架里加一组 hook,记录”对象 ID + 时间戳 + 操作类型”,落到 Parquet
- 系统层:
iostat -x 1持续记录 +nvidia-smi dmon持续记录 - 离线分析:用 Pandas / DuckDB 跑 D1-D6 的统计
跑一周生产负载,就能拿到一份完整的 AccessProfile。
📍 关键忠告:先别上 RL 调度器——把六维 profile 跑出来后,你会发现 90% 的优化机会用简单启发式(LRU + 类型感知)就能拿下,RL 是最后一公里的事。
9. 把 profile 数据喂给后续章节
9.1 数据流
┌──────────────────────────┐
│ 生产负载 │
└────────────┬─────────────┘
│ profile
▼
┌──────────────────────────────┐
│ AccessProfile (六维指纹) │ ← 本章产出
└────────────────┬─────────────┘
│
┌───────────────┬─────────┼─────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
Ch3 物理上限 Ch8 统一抽象 Ch9 放置 Ch10 迁移 Ch11 成本建模
(匹配介质) (设计接口) (LP/RL) (触发策略) (token 单位成本)
🌟 本章对后续章节的承诺:给定一份 AccessProfile,后面任何一章的算法都能直接用,不需要重新定义”什么是工作负载”。
9.2 与生产对接
如果你正在做项目示范系统,推荐的工作流:
- 第 2 章(本章):跑 profile 出 AccessProfile
- 第 4-7 章:看看 KV / 向量 / 多模态 / 推理状态各自现成的最优实践
- 第 8 章:把 AccessProfile 接进统一抽象层
- 第 9-10 章:跑放置 / 迁移决策
- 第 11 章:用 token 单位成本做最终评估
⭐ 关键准则:每一轮系统调整后重跑 profile——access pattern 会随着用户行为、模型升级、任务变化而漂移。长记忆系统的调度永远是动态目标,profile 必须是常态化的。
✅ 自我检验清单
- 六维度量:能默写 D1 频率 / D2 时序 / D3 工作集 / D4 粒度 / D5 放大 / D6 失效代价
- 传统经验为何不能直接套:能列举至少 3 条传统系统假设与长记忆系统的差异
- KV 双阶段:能区分 prefill 和 decode 的访问模式差异,以及对放置策略的不同含义
- KV 复用红利:能讲清”系统 prompt 永驻 HBM”的高 ROI 论证
- 向量索引 IO 放大:能粗略估算一次 HNSW 查询的 SSD random IO 次数
- 多模态双流:能解释 embedding 和 blob “metadata 一致但分开放”的设计动机
- 推理状态的语义分级:能解释为什么传统 LRU 在这一类上失效
- 横向对比表:能默写四类数据在六维度量上的核心差异
- profile 工具栈:能列出至少 3 个推荐工具及其对应采集的维度
- 本章承诺:能讲清 AccessProfile 是后续章节算法的统一输入
📚 参考资料
长记忆系统访问规律相关
- Memory in the Age of AI Agents(arXiv 2512.13564, 2025) —— 长记忆访问模式综述
- Towards Efficient Generative LLM Serving: A Survey(CMU, 2023) —— LLM 推理负载特征
- A Survey on Vector Database Management Systems(2024) —— 向量库访问模式
KV Cache 访问规律精读
- vLLM PagedAttention(SOSP’23):arXiv 2309.06180 —— KV 单卡访问模式
- AttentionStore(ATC’24) —— 多轮会话 KV 复用模式
- Mooncake 技术报告(2024):github.com/kvcache-ai/Mooncake —— 集群级 KV 复用
向量索引访问规律
- DiskANN(NeurIPS’19) —— 图索引 SSD 访问模式经典
- SPANN(NeurIPS’21, Microsoft) —— 聚类索引访问模式
- AlayaDB(HKUST, 2025) —— LLM-aware 向量库访问
推理状态(空白领域,可参考)
- Voyager(2023) —— Minecraft Agent 长时任务
- Generative Agents(Park et al., 2023):arXiv 2304.03442 —— 模拟社会 Agent 长记忆
- A-MEM(NeurIPS’25):arXiv 2502.12110 —— Zettelkasten 记忆访问
Profile 工具
- PyTorch Profiler:pytorch.org/tutorials/recipes/recipes/profiler_recipe.html
- NVIDIA Nsight Systems:docs.nvidia.com/nsight-systems/
- NVIDIA DCGM:docs.nvidia.com/datacenter/dcgm/
- eBPF / bpftrace:ebpf.io
调研笔记腹地
- 项目调研 - 长记忆分离式存储 —— 8 篇核心论文精读