第6章:多模态语义记忆与 Embedding 协同存储
embedding 与 blob 双流共生模型 + 列式多模态格式(Lance/Parquet) + 多模态向量库 + CLIP-style 检索 + GPUDirect Storage 直通——长记忆系统里多模态那一档怎么落地
第 5 章把”向量索引”讲透了——但向量索引只是多模态长记忆的索引层,真正占容量、决定带宽的是底下的原始 blob(图像 / 音频 / 视频 / 长文档)。一份多模态长记忆的物理形态是embedding(KB 级,高频检索)+ blob(MB-GB 级,低频访问)的双流共生——两者元数据共享、生命周期一致,但访问模式南辕北辙。本章把多模态长记忆这一档的存储设计讲清:双流共生模型、Lance 这类列式多模态格式、CLIP-style 跨模态检索的存储侧含义、GPUDirect Storage 给”按需取一张图”的低延迟通路、视频/长音频的特殊性。读完本章你能回答两个工程问题:多模态向量库选 LanceDB / Milvus / Weaviate 哪个? 以及 生产 Agent 系统如何让”取一张图”的端到端延迟卡在 50 ms 以下?
📑 目录
- 1. 多模态长记忆的新挑战
- 2. 双流共生模型:embedding 与 blob
- 3. 列式多模态格式:Lance / Parquet / Arrow
- 4. 多模态向量库的工业化方案
- 5. CLIP-style 跨模态检索的存储侧含义
- 6. 大对象的 GPUDirect Storage 直通
- 7. 视频与长音频长记忆的特殊性
- 8. 多模态长记忆的 8 条设计准则
- 9. 给本项目的整合启示
- 自我检验清单
- 参考资料
1. 多模态长记忆的新挑战
1.1 单模态时代的”够用”在多模态时代不够
| 维度 | 单模态长记忆(文本) | 多模态长记忆 |
|---|---|---|
| 单条对象大小 | KB 级 token 序列 | embedding KB + blob MB-GB |
| 总容量(中型 Agent) | GB 级 | TB 级 |
| 每次查询访问数据 | 命中条目 token | embedding 命中 + 偶发拉 blob 给模型看 |
| 写入模式 | 流式追加文本 | 流式 + 批量(用户上传一批照片) |
| 容量增长 | 人类打字速度上限 | 手机相机 / 摄像头 —— 几 GB / 天 |
🌟 核心转变:容量从 GB 级跳到 TB 级——这不是量变,是介质必然要变。再大的 DRAM 也装不下个人级多模态记忆,SSD 是必然落点;同时 embedding 检索路径还要快——这就强迫”embedding 与 blob 分流”。
1.2 三类典型多模态长记忆场景
| 场景 | 数据特征 | 关键约束 |
|---|---|---|
| 个人 Agent 相册记忆 | 图片为主,数千-数十万张 | 按时间/地点/事件检索,大对象偶发拉 |
| 企业多模态知识库 | 文档(含图表)、产品图片、培训视频 | 强权限过滤,跨模态联合检索 |
| 多模态客服 / 监控 Agent | 实时图像/视频流 + 历史归档 | 写入吞吐 + 长期归档分层 |
⭐ 观察:三类场景对存储的要求完全不同——通用方案做不到”一种规格全适配”,分层 + 分模态 + 分场景 是基本设计原则。
2. 双流共生模型:embedding 与 blob
2.1 概念图
一条多模态长记忆 = (embedding, blob, metadata)
│ │ │
│ │ └─ 时间 / 用户 / tag
│ │
│ └─ 原始字节(图/音/视频)
│
└─ 高维向量(检索用)
生命周期相同 | 访问频率不同 | 存储介质不同
2.2 两条流各自的访问模式
| 流 | 大小 | 访问频率 | 访问粒度 | 存储倾向 |
|---|---|---|---|---|
| embedding 流 | KB 级 / 条 | 高(每次召回都用) | 单向量 | DRAM 主导,热点 HBM |
| blob 流 | MB-GB 级 / 条 | 极低(只在需要时拉) | 大块顺序读 | SSD 主导,极热 DRAM |
🍎 直觉比喻:embedding 是一本书的目录,blob 是正文 —— 读者(LLM / Agent)频繁翻目录,只有真正决定看某一章时才翻正文。把目录和正文都印在牛皮纸上太奢侈,把目录也丢柜子里又找不到。
2.3 关键设计原则:metadata 一致 + 物理分流
┌──────────────────────────────────┐
│ 统一 metadata 层 │
│ obj_id → (emb_loc, blob_loc, …) │
└──────────────────────────────────┘
│ │
┌────────┘ └────────┐
▼ ▼
embedding 池 blob 池
(DRAM 主 + HBM 热点) (SSD 主 + DRAM 极热)
按向量索引组织 按 chunk / 列式组织
🌟 核心准则:embedding 与 blob 物理分开存,metadata 共享同一个 ID 空间。这样:
- 检索路径(走 embedding)与原始访问路径(走 blob)各自最优
- 升级 / 重建 embedding 时不影响 blob
- 删除 / 归档时按 ID 一起处理,不丢失对应关系
2.4 反例:错误把它们塞一起的代价
如果把 embedding 和 blob 紧邻存(比如同一个 row 里):
- 检索 cache miss 频繁 —— 每次拉 4 KB embedding 顺带把 5 MB blob 拉到 page cache,DRAM 命中率崩
- 写入放大 —— 加一个新 embedding 要写一个 row,row size 几 MB
- 不能独立量化 —— embedding 想 PQ,blob 想 JPEG,共享 row 没法协同
📍 工程教训:双流物理分开是非协商项。
3. 列式多模态格式:Lance / Parquet / Arrow
3.1 列式格式的核心优势
传统行式格式(JSON Lines, CSV)的问题:所有字段挤在一起,读 embedding 必须扫完整 row。列式把每个字段独立存放,只读 embedding 列就只读 embedding 列对应的物理块——SSD IO 立即降一个量级。
3.2 三种主流列式格式对照
| 格式 | 出身 | 多模态友好度 | 关键特性 |
|---|---|---|---|
| Apache Parquet | Hadoop 生态 | 中 | 强压缩、统计推断,大数据分析标配 |
| Apache Arrow | 内存格式 | 中 | 零拷贝跨语言、Python/Spark/Dask 通用 |
| Lance / LanceDB | 多模态原生 | 高 | 向量列原生支持、版本化、Python 友好 |
🌟 Lance 的关键差异化:它把向量列作为一等公民,在格式层就支持 ANN 索引(IVF / HNSW),不需要外挂向量库——这正好契合多模态长记忆的场景。
3.3 Lance 的多模态长记忆适配点
表结构示例(Lance):
┌──────┬──────────────┬──────────┬──────────┬─────────────┐
│ id │ embedding │ blob_uri │ ts │ tags │
│ │ (float[768]) │ (str) │ (int64) │ (list[str]) │
├──────┼──────────────┼──────────┼──────────┼─────────────┤
│ 001 │ [0.12,…] │ s3://… │ 2025-… │ [photo,kid] │
│ …… │ …… │ …… │ …… │ …… │
└──────┴──────────────┴──────────┴──────────┴─────────────┘
物理上:
embedding 列 ── 独立 chunk,可直接被向量库索引扫
blob_uri 列 ── 紧凑字符串,定位到 SSD/对象存储
ts/tags 列 ── 用于过滤的小列,常驻内存可能
🧠 关键洞察:列式 + 向量列原生 = 检索路径与原始数据路径自然分流。这是 Lance 比”Parquet + 外挂 FAISS”组合更适合长记忆的根本原因。
3.4 列式格式 vs 传统对象存储
| 维度 | 对象存储(S3 / OSS) | 列式格式(Lance) |
|---|---|---|
| 单 blob 访问 | 优(一次 GET 拿到) | 中(要解压 column block) |
| 批量检索 | 极差(N 次 GET) | 优(一次扫一个 chunk) |
| 元数据查询 | 极差(单独索引层) | 优(自带列统计) |
| embedding 检索 | 不支持(要外挂) | 原生支持 |
📍 决策准则:embedding + 小元数据 → Lance;大 blob → 对象存储 / 本地 NVMe。两层各取所长。
4. 多模态向量库的工业化方案
4.1 主流选项速查
| 系统 | 出身 | 多模态原生 | 适合规模 | 关键特性 |
|---|---|---|---|---|
| Milvus | Zilliz 开源 | 部分 | 十亿级 | 分布式、多种索引 |
| Qdrant | Rust 实现 | 部分 | 千万-亿级 | filtering 强、payload 丰富 |
| Weaviate | GraphQL 风格 | 中 | 千万-亿级 | 模块化向量化器 |
| LanceDB | Lance 之上 | 高 | 千万-亿级 | 列式 + 多模态原生 + 版本化 |
| Marqo | 多模态优先 | 高 | 中等规模 | 内置 CLIP / 视觉 embedding |
| Chroma | 轻量 Python | 中 | 千万级 | 嵌入式 / SDK 化 |
4.2 选型决策树
你的场景:
├─ 单机 / 嵌入式 / 快速 prototype
│ → Chroma / LanceDB
│
├─ 中规模 + filter 重 + 多 payload
│ → Qdrant
│
├─ 十亿级 + 分布式
│ → Milvus
│
├─ 多模态原生 + 列式 / 版本化重要
│ → LanceDB
│
└─ 多模态 + 内置 CLIP-style 模型
→ Marqo / Weaviate
🌟 观察:没有”最佳”答案——按场景选。但对长记忆 Agent 系统,LanceDB 是当前最贴的方案(列式 + 多模态原生 + 与 KV / blob 同源思路一致)。
4.3 通用 ANN 库 vs 多模态向量库
| 维度 | FAISS / DiskANN(底层 ANN) | 多模态向量库(Milvus 等) |
|---|---|---|
| 抽象层级 | 算法库 | 服务化 |
| metadata 过滤 | 弱 / 后过滤 | 内置 |
| 多模态 | 不支持(要自己拼) | 原生 |
| 多租户 | 自己做 | 内置 |
| 写入流式 | 自己做 | 内置(部分) |
⭐ 决策建议:项目示范系统用 LanceDB / Milvus 等服务化方案做基线,真正性能瓶颈处下沉到 DiskANN / Vamana 等算法库——分层架构更容易出论文。
5. CLIP-style 跨模态检索的存储侧含义
5.1 跨模态检索基础范式
CLIP(OpenAI, 2021)证明:用大量”图像-文本对”对比学习,可以让两种模态的 embedding 落到同一个向量空间——查文本能找到图片,查图片能找到文本。
文本编码器 ──> emb_text(768维)
\
\── cosine 相似度 ──> 检索
/
图像编码器 ──> emb_image(768维) /
后续:SigLIP, EVA-CLIP, BLIP-2, CoCa, SigLIP2, Qwen2-VL embedding 等持续迭代。
5.2 跨模态检索给存储带来什么
✅ 好消息:所有模态的 embedding 维度统一——可以用同一个向量索引存,写入时只标 modality tag。
❌ 坏消息:距离度量必须严格 —— 跨模态对比学习的几何性质比单模态更敏感,不能随便量化(精度敏感性高)。
⭐ 设计含义:
- embedding 量化(PQ)的档位要保守一些
- 不同模型版本的 embedding 不能混存(几何空间不同)
- 模型升级要考虑已存 embedding 的迁移——本质是”embedding 重建”工程问题
5.3 多向量(multi-vector)与晚期交互
| 范式 | 代表 | 存储成本 |
|---|---|---|
| 单向量(CLIP / dense retrieval) | 一个对象一个 emb | 1× |
| 多向量(ColBERT / late interaction) | 一个对象数十-数百 emb | 数十× |
| 多模态多向量(VL late interaction) | 图像 patch + 文本 token 各一个 emb | 100× 起 |
🌟 关键观察:多向量召回质量更高,但存储 100×——长记忆系统里要不要用,取决于场景。
📍 建议:热数据用单向量保速度,冷归档可考虑多向量保召回——分层策略本身可以驱动检索精度的弹性。
5.4 给本项目的启示
🌟 项目第一模块的统一抽象必须支持:
- 多种 embedding 模型版本共存(不能强制全平台升级)
- 多向量数据结构(列式格式天然友好)
- 量化档位精度感知(模态不同,容忍度不同)
- embedding 重建 pipeline(模型升级时的数据迁移)
6. 大对象的 GPUDirect Storage 直通
6.1 问题:从 SSD 拉一张图给 LLM 看,延迟在哪?
传统路径(无 GPUDirect Storage):
NVMe SSD ─── DMA ───> CPU bounce buffer ─── memcpy ───> GPU HBM
~ 100µs ~ 几百 µs ~ ms
加上 OS page cache、文件系统、可能的网络一跳——端到端可能 50-200 ms,对实时多模态 Agent 来说太慢。
6.2 GPUDirect Storage(GDS)的修复
NVMe SSD ────── DMA(直通) ─────────> GPU HBM
绕开 CPU,无 bounce buffer
~ 100µs + 顺序带宽限制
📌 端到端可压到 几 ms 级别(对几 MB 图像),且 CPU 几乎不参与——CPU 释放出来给其它工作。
6.3 长记忆系统的具体用法
┌──────────────────────────────────┐
│ Agent 决定"看一眼用户上传的图" │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 元数据查找(LanceDB / 自建): │
│ obj_id → blob_uri = "/nvme/…" │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────┐
│ GDS 直读到 GPU HBM │
│ 同时启动 LLM forward 的视觉编码 │
└────────────┬─────────────────────┘
│
▼
Vision encoder 处理 → 给 LLM 当 token
6.4 工程要点
| 点 | 内容 |
|---|---|
| 文件系统支持 | NVMe over PCIe + GDS 兼容 FS(ext4 / xfs 在合适内核版本下 OK) |
| 文件大小友好 | MB 级以上才显著(小文件 syscall overhead 主导) |
| 与图像解码协同 | NVIDIA nvJPEG / DALI 可在 GPU 上做 JPEG 解码 |
| 与多模态 encoder 协同 | 直接喂 ViT / CLIP encoder,链路全 GPU |
⭐ 设计含义:长记忆多模态系统的”取 blob”路径,理想形态是 SSD → GDS → GPU encoder → LLM 全程不经 CPU——这是模块零第 4 章 NIXL / Magnum IO 思路的多模态对应。
7. 视频与长音频长记忆的特殊性
7.1 视频:数据量再上一个数量级
- 单分钟 1080p 视频 ≈ 数百 MB
- 一个 Agent 一天的录像 ≈ TB 级
- 检索粒度:帧 / 镜头 / 事件 / 片段 —— 不是整段视频
7.2 关键设计:多粒度索引
原始视频(SSD)
│
├── 帧采样 → 帧 embedding(每秒一帧)
├── 镜头切分 → 镜头 embedding
├── 字幕 ASR → 文本 chunk + embedding
└── 关键事件 → 事件 embedding + 时间戳
🌟 存储含义:索引层数据 = 几百 MB,原始层 = 几百 GB——比例 1:1000,典型的”索引常驻 + blob 按需取”。
7.3 长音频(会议、播客)
- 比视频小一个量级,但仍是 GB / 小时级
- ASR 转写后文本 chunk + 音频片段双流共生
- 长 Agent 任务里(如”总结这场两小时会议”),需要按时间窗大块顺序读 → SSD 顺序 IO 友好
7.4 给本项目的启示
🌟 视频 / 长音频是多模态长记忆的容量主体——本项目示范系统设计时:
- 多粒度索引作为 design pattern,不只视频用
- 冷归档到对象存储是必要的(本地 NVMe 装不下太久)
- GDS + 流式解码对视频/音频回放至关重要
8. 多模态长记忆的 8 条设计准则
8.1 准则 1:embedding 与 blob 物理分流,metadata 共享 ID
不要塞一起,但要能通过 obj_id 互相定位。
8.2 准则 2:列式格式优先,大 blob 走对象存储 / 本地 NVMe
LanceDB 是当前最贴长记忆场景的选择,但 blob 可以仍然走 S3/OSS,只在 LanceDB 里存 URI。
8.3 准则 3:embedding 不同模型版本不混存
模型升级时新建 namespace,旧 embedding 与新查询不可比较。
8.4 准则 4:跨模态量化要保守
模态间几何关系比单模态敏感——PQ 档位、INT8 量化都要校验。
8.5 准则 5:多向量数据结构上层处理,存储层透明
底层只存 List
8.6 准则 6:大对象走 GPUDirect Storage,CPU 不参与数据搬运
multimodal LLM 推理时,blob 直接 SSD → HBM。
8.7 准则 7:视频 / 长音频用多粒度索引
帧 / 镜头 / 事件 / 文本 各自索引,查询融合。
8.8 准则 8:容量主体进对象存储 / 冷归档,本地 NVMe 是热缓存
TB 级别的多模态原始数据不可能全放本地 NVMe——架构必然是 “本地 NVMe(热)+ 对象存储(冷)“。
9. 给本项目的整合启示
9.1 已有可复用武器
| 武器 | 来源 | 用在哪 |
|---|---|---|
| 列式多模态格式 | Lance / LanceDB | 多模态长记忆主存储 |
| 跨模态 embedding | CLIP / SigLIP / Qwen2-VL | 检索基础模型 |
| GDS 直通 | NVIDIA Magnum IO | blob 取出路径 |
| GPU 图像 / 视频解码 | nvJPEG / NVIDIA DALI | 端到端全 GPU |
| 多模态向量库 | LanceDB / Marqo / Weaviate / Milvus | 服务化基线 |
| 多粒度索引 | 视频检索经典做法 | 视频 / 长音频长记忆 |
9.2 本项目要补的拼图
| 缺口 | 内容 | 难度 |
|---|---|---|
| ⭐⭐⭐ embedding 与 KV 同源 metadata 层 | 一份多模态对象在 KV(若被检索回来用作上下文) + embedding + blob 三处的统一 ID | 高 |
| ⭐⭐⭐ 跨层级演化协同 | 用户 X 的”近期高频图片”集体升 DRAM,Ta 的 KV 也跟着升 | 中-高 |
| ⭐⭐ 多模态量化的精度感知 | 不同模态可承受的量化档位不同,统一抽象要支持 | 中 |
| ⭐⭐ 多向量数据结构 | List | 中 |
| ⭐⭐ embedding 模型升级 pipeline | 平滑迁移已存数据 | 中(工程为主) |
| ⭐ GDS + 多模态 encoder 流水化 | SSD → GDS → encoder → LLM 全 GPU | 中(工程) |
| ⭐ 视频 / 长音频多粒度索引 | 帧 / 镜头 / 事件统一编址 | 中 |
9.3 与第 4 章 KV 武器的协同视图
┌────────────────────────────────────┐
│ 长记忆数据统一抽象 │
│ {KV, 向量索引, 多模态(emb+blob)} │
└────────────────┬───────────────────┘
│
┌─────────────────────────┼──────────────────────────┐
▼ ▼ ▼
KV 武器(Ch4) 向量武器(Ch5) 多模态武器(Ch6)
- PagedAttention - DiskANN/SPANN - LanceDB 列式
- LMCache 跨实例池 - Filtered DiskANN - GDS 直通
- InfiniGen 重要 token - AlayaDB 算子级 - 多粒度索引
- CacheGen 编码 - GPU-ANN
- Pensieve 亲和
│
┌────────────┴───────────┐
▼ ▼
统一 metadata 层 跨层级演化协同
(Ch8) (Ch10)
│
▼
单 token 边际成本(Ch11)
🌟 关键洞察:多模态这一章不能孤立看——它和 KV 战场、向量战场在 统一 metadata 层 相遇。这是 Ch8 的核心命题,也是项目第一模块最硬的科学问题。
✅ 自我检验清单
- 多模态 vs 单模态:能列出至少 3 个容量 / 访问 / 写入维度的差异
- 双流共生:能解释为什么 embedding 和 blob 必须物理分流但 metadata 共享
- 列式格式价值:能说出 Lance 和”Parquet + 外挂 FAISS”对长记忆的差异
- 多模态向量库选型:能给出 LanceDB / Milvus / Marqo / Qdrant 的适用场景
- CLIP-style 范式:能解释统一向量空间对存储设计的好/坏消息
- 多向量代价:能讲清 ColBERT / late interaction 100× 存储代价的来源
- GDS 路径:能画出”SSD → GPU HBM”绕开 CPU bounce buffer 的图
- 视频多粒度索引:能列出帧 / 镜头 / 事件 / 文本四种索引的关系
- 8 条设计准则:至少能默写 6 条
- 三战场协同:能讲清 Ch4(KV)+ Ch5(向量)+ Ch6(多模态)在 Ch8 统一 metadata 上汇合
📚 参考资料
列式 / 多模态格式
- Apache Parquet:parquet.apache.org
- Apache Arrow:arrow.apache.org
- Lance / LanceDB:lancedb.com | github.com/lancedb/lance —— 多模态原生列式
多模态 embedding 模型
- CLIP(OpenAI, ICML 2021):arXiv 2103.00020
- SigLIP(Google, 2023):arXiv 2303.15343
- EVA-CLIP(2023):arXiv 2303.15389
- BLIP-2(Salesforce, ICML 2023):arXiv 2301.12597
- Qwen2-VL embedding / InternVL —— 主流多模态 LLM 自带 embedding
多向量与 late interaction
- ColBERT / ColBERT-v2(Khattab et al., SIGIR 2020/2022) —— late interaction 经典
- PLAID(Santhanam et al., 2022) —— ColBERT 的 SSD 加速
多模态向量库实现
- Milvus:milvus.io | github.com/milvus-io/milvus
- Qdrant:qdrant.tech
- Weaviate:weaviate.io
- Marqo:marqo.ai
- Chroma:trychroma.com
- LanceDB:lancedb.com
NVIDIA 多模态加速
- GPUDirect Storage 文档:docs.nvidia.com/gpudirect-storage/
- NVIDIA DALI:github.com/NVIDIA/DALI —— GPU 数据加载/解码
- nvJPEG:docs.nvidia.com/cuda/nvjpeg/
- NVIDIA NIXL:推理 P2P 传输库(模块零第 4 章已讲)
综述
- A Survey on Multimodal Large Language Models(2024)
- Multimodal Retrieval-Augmented Generation: Survey(2024)
本系列其它模块
- 模块零第 4 章 分布式通信与 I/O 优化 —— Magnum IO / GDS / NIXL
- 模块四第 8 章 推理优化端到端实战 —— 推理服务整合
- 模块十一 Multi-Modal —— 多模态 Agent 上层应用
- 本模块第 5 章 向量索引层次化 —— embedding 索引层