L2 序列化(Serialization / Deserialization)#

LMCache 支持 每个适配器的序列化/反序列化,在 KV Cache 数据与 L2 适配器之间传输时进行转换。典型用途:量化(缩小存储占用)、压缩、加密。

何时使用序列化与反序列化#

  • 节省 L2 存储或带宽。 fp8 量化相比于 bf16 将字节体积减半,且准确度损失较小——非常适合磁盘/远程适配器。

  • 静态加密。 在原始字节写入磁盘之前,使用经过身份验证的加密进行包装。

  • 自定义压缩。 任何无损(lz4/zstd)或有损(CacheGen 风格)都可以通过 Serializer / Deserializer ABC 插入。

Serde 是 按适配器选择 的:一个 --l2-adapter 可以使用 fp8,而另一个则存储原始字节。当省略时,适配器的行为就像 serde 不存在一样(没有额外的分配,没有额外的线程)。

在 L2 适配器上配置 serde#

向任何 --l2-adapter JSON 规范添加一个 "serde" 子字典。type 字段选择一个已注册的 serde;其余键将转发到 serde 工厂。

lmcache server \
    --l1-size-gb 100 \
    --eviction-policy LRU \
    --l2-adapter '{
        "type": "fs",
        "base_path": "/data/lmcache/l2",
        "serde": {"type": "fp8", "fp8_dtype": "float8_e4m3fn"}
    }'
内置 serde 类型#

type

描述

配置字段

fp8

将每个元素量化为 8 位浮点数;在加载时反量化。虽然有损,但高度可压缩。

fp8_dtype``(默认 ``float8_e4m3fn;也接受 float8_e5m2),``max_workers``(线程池大小,默认 1)

编写自定义序列化/反序列化#

实现两个同步 ABC(SerializerDeserializer),并使用您的转换逻辑,然后注册一个以您选择的名称为键的工厂:

# my_project/my_serde.py
from lmcache.v1.distributed.serde import (
    AsyncSerdeProcessor,
    Deserializer,
    Serializer,
    register_serde_factory,
)

class MySerializer(Serializer):
    def serialize(self, src, dst) -> int:
        # Write serialized bytes into dst; return bytes written.
        ...

    def estimate_serialized_size(self, layout_desc) -> int:
        # Upper bound on serialized byte size for this layout.
        ...

class MyDeserializer(Deserializer):
    def deserialize(self, src, dst) -> None:
        # Read serialized bytes from src, write into dst (KV-shaped).
        ...

def _create_mine(config: dict):
    return AsyncSerdeProcessor(MySerializer(), MyDeserializer())

register_serde_factory("mine", _create_mine)

从你的适配器配置中引用它:

{"type": "fs", "base_path": "/data", "serde": {"type": "mine"}}

备注#

  • 缓冲区大小。 estimate_serialized_size(layout) 必须返回实际序列化输出的上限 — 直接在估算中包含任何安全边际(例如,内置的 fp8 序列化器返回 1.5 * num_elements)。

  • 失败处理。 如果任何步骤失败(序列化、存储、加载或反序列化),则整个提交的批次将被报告为失败——在一个批次内的部分成功不会被显示。失败的键会自动清理。

  • 线程池。 AsyncSerdeProcessor(max_workers=N) 控制池的大小。释放全局解释器锁(GIL)的变换(例如,torch 操作)可以从 N > 1 中受益;纯 Python 变换则不然。

示例#

一个端到端的脚本,它在磁盘适配器上启动一个带有 fp8 的 lmcache 服务器,运行 vLLM,清除 L1,然后重新运行相同的请求以触发 L2 预取 + fp8 反序列化路径,位于 examples/serde/fp8/。一个基于 pytest 的文件系统往返测试(不需要 vLLM)位于 tests/v1/distributed/serde/test_serde_fs_e2e.py