服务重启后想把前缀缓存快速装回来,一个常见做法是把树节点和 KV 数据保存成连续镜像,再用 mmap 映射。读取路径可以变短,文件内容却不会因此自动可信。半写入文件、旧版本布局、损坏的长度字段,都会把“快速恢复”变成越界读取。
前缀复用本来就依赖已有状态。10 月 12 日 vLLM 的双向 KV 传输方案,把跨轮保留状态与后续复用放在同一条链路讨论。官方工程说明 只要状态需要跨时间保存,就需要单独设计它的身份、格式与失效条件;能找到数据和可以使用数据是两步。
文件头是协议,不是 C++ 结构体的照片
直接把内存里的结构体写入磁盘,会把本机字节序、填充和字段大小也带进去。更稳妥的做法是明确规定每个字段的编码。Python 的 struct 文档区分本机布局与固定大小、固定字节序布局,适合用来写一个可检查的最小例子。struct 官方文档
这里定义 20 字节文件头:4 字节魔数、1 字节版本、3 字节保留位、8 字节载荷长度、4 字节 CRC32。教学载荷是普通字节,不是真实 KV 张量。生产格式还需要模型、分词器、张量布局、精度与分片身份。
一个会拒绝坏文件的解码器
import struct import zlib HEADER = struct.Struct("!4sB3xQI") MAX_PAYLOAD = 1024 * 1024 def encode(payload): if len(payload) > MAX_PAYLOAD: raise ValueError("payload too large") return HEADER.pack(b"PFX1", 1, len(payload), zlib.crc32(payload)) + payload def decode(blob): if len(blob) < HEADER.size: raise ValueError("short header") magic, version, size, crc = HEADER.unpack_from(blob) if magic != b"PFX1" or version != 1: raise ValueError("unknown format") if size > MAX_PAYLOAD or size != len(blob) - HEADER.size: raise ValueError("invalid payload length") payload = blob[HEADER.size:] if zlib.crc32(payload) != crc: raise ValueError("checksum mismatch") return payload blob = encode(b"prefix-state-demo") assert decode(blob) == b"prefix-state-demo" for bad in (blob[:8], blob[:-1], blob[:-1] + bytes([blob[-1] ^ 1])): try: decode(bad) except ValueError: pass else: raise AssertionError("corruption was accepted") print("roundtrip and 3 rejection cases passed")先验证长度,再按长度读取,不是先相信一个大整数、分配内存、等校验失败后才回收。CRC32 用于发现意外损坏,不提供身份认证,也挡不住有意修改文件后重新计算 CRC 的人。跨信任边界的快照要另行做认证与访问控制。
mmap 没有承诺零 I/O
mmap 建立的是地址映射。后续访问可能触发缺页和磁盘读取,映射函数返回不等于所有页面已经驻留内存。GPU 还需要可用的设备存储与传输路径。把“省掉对象反序列化”写成“0 微秒恢复完整缓存”,会掩盖最耗时的部分。mmap 官方文档
快照发布也要考虑写入中断:先写临时文件并完成必要的落盘步骤,再按文件系统语义发布最终版本;读取方只接受完整、兼容的镜像。若校验失败,应回退重算并记录原因,不能返回一份半恢复的缓存。
上述程序只在内存字节上测试协议和失败路径,没有映射大文件、读取 SSD 或执行 GPU 恢复。它的价值是让坏数据在边界被拒绝;恢复速度要用冷页缓存、真实文件大小和实际设备另外测量。