如果你做过多模态检索,大概率遇到过这种尴尬场景:问"这张图里是什么"效果很好,但问"这张图里的关键食材,在另一张图里对应哪个品牌"就崩了。前者是单跳,模型看一眼图就能答;后者是典型的多跳检索,需要从图像跨到文本,再从文本跨回图像,中间任何一环错了,答案就全错了。
我以前处理这类需求时,习惯性做法是加一道"图像描述生成"再进纯文本检索,结果在简单问题上非常能打,一旦遇到跨模态的证据链就暴露出两个硬伤:一是描述生成会丢细节,二是文本检索根本不理解图像组件之间的关系。直到我读到LILaC这一思路——Late Interacting in Layered Component Graph for Open-domain Multimodal Multihop Retrieval,才真正把"多模态多跳检索"拆开揉碎。这篇内容不打算复述论文,而是从工程实践角度聊聊:为什么要用分层组件图,晚期交互到底解决了什么痛点,以及你自己想复现时会在哪些地方踩坑。
1. 多跳检索到底难在哪:一个让两阶段方法破防的场景
1.1 先看一个真实会崩的检索链条
假设知识库里有两类资料:一类是美食图片,一类是菜谱文本。用户的问题长这样:"第一张图里的菜品,它的主料在第二张图里出现过几次?"
这个问题看着不难,但拆开之后是三步推理:
- 从图A识别出菜品,可能是"鱼香肉丝";
- 在图A对应菜谱里找到主料,比如"木耳、胡萝卜、肉丝";
- 拿着主料列表去图B里做视觉匹配,确认哪些主料在画面中出现。
如果你用传统两阶段方案,先把所有图片自动生成文字描述,再喂给纯文本检索模型,大概率倒在第三步。因为图像描述通常只输出"一盘菜",不会细到"画面左下角有木耳、右上角有胡萝卜丝"。可是多跳检索的第三跳恰恰需要这种视觉细节。
也有人会直接用一个图文对齐的双塔模型,把整体图和文本各自编码成一个向量,然后做相似度匹配。单跳场景下这种方案确实不错,可到了多跳就麻烦了:整体图文向量在第一次跳的时候就把信息压扁了,第二次跳根本没有中间证据可以传递。
1.2 "开放域、多模态、多跳"三个词,每个都是坑
开放域意味着你没法预先枚举实体和关系。知识分散在图片、表格、段落、网页、视频字幕里,你不能靠一个固定的知识图谱去做推理。
多模态意味着语义对齐本身就不容易。图像里的"红色"和文本里的"红色",并不是天然落在同一个语义空间里的,需要用大规模对齐训练才能勉强拉近,而拉近之后又会损失模态特有的精细信息。
多跳意味着查询和最终答案之间隔着中间证据。模型不能靠一次匹配搞定,它必须在证据链上走好几步,每走一步都要判断"当前这个中间结果是否值得继续往下走"。错误在这条链上是累积的,第一跳差一点,后面就会越差越远。
1.3 标准RAG方案为什么也搞不定
标准RAG通常把段落作为检索单元,查询转成向量后在段落索引里做近邻搜索。它在单跳开放域问答上非常好用,但多跳场景下,一条证据链往往散落在不同来源、不同模态、不同段落里。你就算把候选段落都召回了,也没有一个结构能告诉你"这些段落应该按什么顺序组成证据链"。
于是有人退回去用Agent方案:让大模型自己决定先检索什么,再检索什么。思路没问题,但工程上很痛苦。每多一次工具调用就多一次延迟和多一层不确定性,而且大模型经常在"该走哪条边"上犯迷糊,检索路径一旦选错,再强的生成能力也救不回来。
LILaC的核心回应很直接:既然知识分散在图谱式的组件里,那就把组件图建出来,把"证据链"这个原本隐式的东西,显式地变成图上的一条路径。检索任务就变成了寻路任务。
2. 为什么是"分层组件图":图结构怎么化解证据链问题
2.1 组件图处理的不再是文档,而是原子组件
我第一次接触组件图的概念时,最先想到的问题是:为什么不直接用整篇文档当节点?那样图谱小很多,关系也少很多。
LILaC给出的答案是:文档太大,不适合当作推理单位。一篇美食文章里,可能只有一句话和当前查询相关,其他内容全是干扰。如果整篇文档是一个节点,检索时每次都要跨过大量无关信息,路径很容易被噪声带偏。
组件图的做法是先把多模态内容拆成原子组件:
- 文本组件:句子级别,甚至是短语级别;
- 视觉组件:图像里的检测框、分割区域、OCR识别出的文字块;
- 混合组件:图文配对区域,比如一个产品图加一句广告语。
每个组件节点都很小,保留自己的模态特征与元信息。这样同一张图片就不会被压成一个单一向量,它可能同时是几十个组件节点,每个节点可以被不同查询命中,也可以出现在不同证据链里。
2.2 分层让图从"一团乱麻"变成"可以上下楼的立体地图"
如果所有组件都堆在同一层,图会非常稠密。图片区域和文本句子之间的边、文本句子和文本句子之间的边、图片区域和图片区域之间的边,全部混在一起。这种图很难检索,因为你不知道该在哪一层走。
LILaC采用分层结构,我理解它本质上解决的是"语义粒度"问题:
- 底层:同模态组件。纯文本句子、纯视觉区域,边主要是模态内的语义相似边;
- 中层:跨模态绑定。把同一来源、同一上下文里的图文组件绑在一起,比如一张新闻图配一段新闻正文;
- 高层:复合事件或主题节点。把多个中层组件聚合成一个事件、一个故事、一个概念。
这样做的好处是,不同查询可以从不同层开始走。有的查询适合直接从视觉区域开始,有的查询适合从文本句子开始,还有的查询应该先定位到高层事件,再沿着隶属边下钻到底层细节。
我用一个生活化类比来理解:低层是"食材",中层是"一盘菜",高层是"一桌宴席"。用户可能问"这桌宴席里哪个菜用到了木耳"——你直接从高层宴席往下找;也可能问"这张图左下角的红颜色食材是什么"——你从底层视觉区域往上查。
2.3 图中的边,定义了"跳"的合法路径
图建好了,接下来关键就是边。边决定了你从A组件能不能走到B组件,LILaC里边的类型大致有这几种:
- 语义相似边:同模态内,编码器向量相似度超过阈值的组件相连;
- 共现边:来自同一文档、同一页面的图文组件相连,表示上下文相关;
- 空间或时间边:视觉区域之间相邻、文本句子顺序相邻、视频帧前后相关;
- 隶属边:底层组件属于哪一个中层组件,中层组件属于哪一个高层事件。
多跳检索的过程,本质上就是从种子节点出发,沿着这些边走出一条路径。路径上的节点共同构成一条证据链。
这也是组件图相比扁平向量的最大优势:图的边天然携带"为什么能跳过去"的信息,检索结果不止是一个分数,还是一串可以给人看的证据路径。
3. Late Interaction:为什么不在编码时拉齐所有模态,而是检索时再算账
3.1 早期融合和后期融合的代价
多模态检索里最常见的设计选择是:要不要在编码阶段就把图像和文本融合起来。
如果做早期融合,也就是把整张图和整段文本一起送进一个跨模态编码器,效果通常不错,但代价也很明显。索引阶段要为每个文档都做一次深度交互计算,而且这种融合是"查询无关"的。换句话说,你提前把图文的交互方式固定死了,可是不同查询希望关注的交互位置完全不同。比如查"颜色"和查"材质",你希望看到的图像区域完全不同,一个预先融合的向量不可能同时满足两种需求。
如果做纯双塔向量,图文各自编码成一个稠密向量,索引和查询都非常快。但这种方案丢失了局部细节,因为整张图被压成一个向量后,多跳所需的中间证据已经溶解在向量里,提不出来。
LILaC选择的路径是晚期交互(Late Interaction):不在编码阶段强行融合,而是让每个组件保持独立表示,等到检索评分阶段再做轻量交互。
3.2 晚期交互的基本思想
文本检索领域里早就有类似思路,把每个文档拆成多个token向量,查询也拆成多个token向量,打分时不使用单一的全局相似度,而是让查询的每个token和文档的每个token做细粒度匹配,最后再聚合起来。
这种做法的好处是文档向量可以离线计算、离线索引,查询时只需要对少量候选做轻量交互,既保留了局部匹配能力,又没有早期融合那么昂贵的在线代价。
LILaC把这套思路从"token级别"推广到了"组件图级别"。每个组件节点就是独立编码的,节点之间不预先做全局融合。查询来了之后,先找到种子节点,再沿着图走路径,每一步路径打分时才计算查询和路径上各节点之间的相关性。
3.3 LILaC在图上怎么实现"late interacting"
具体来说,LILaC里的晚期交互体现在检索链路的多个层面:
- 组件编码阶段:不融合。文本组件用文本编码器,视觉组件用视觉编码器,各自独立,甚至在索引阶段不要求它们落在同一个向量空间;
- 种子检索阶段:轻量交互。查询与组件索引做快速近邻搜索,此时只做表层匹配,目标是把可能的起点找出来,不求精确;
- 路径扩展阶段:局部交互。从种子出发,在图上扩展,每次扩展只看当前节点周围的局部子图,查询与局部子图上的节点计算相关性;
- 路径评分阶段:聚合交互。把路径上所有节点的相关性得分、边权重、跳数等信息聚合起来,给整条路径打分。
所以"Late Interacting"里的Late,指的是直到检索打分阶段,查询才和文档组件发生真正的深度交互;前面所有的离线编码都保持模态隔离。
这带来的直接好处是索引高度可复用。同一个组件图,面对完全不同的查询,不需要重建、不需要重新编码,只需要在不同路径上进行局部打分。这在开放域多模态场景里非常实用。
4. 完整检索链路:建图、编码、寻路、重排
4.1 组件抽取与表示
如果你想按LILaC的思路落地,第一步不是跑模型,而是确定组件抽取器。
文本侧,我建议不要直接用整段文字。先用分句器切成句子,再根据场景决定是否继续切成短语。句子级别相对稳,短语级别更细但噪声大。如果你做的是菜谱、商品描述这类文本,短语级会更好用;如果是新闻长文,句子级更合适。
视觉侧,你需要一个区域检测器,把图像里的物体、文字块、显著区域切出来。每个区域向量化后保留位置坐标和置信度。OCR文本块也单独作为组件,因为它和视觉物体共享空间信息,对路径扩展很有帮助。
组件表示有一个关键点:先不要强行统一到同一个向量空间。文本组件留在文本空间,视觉组件留在视觉空间,OCR文本块可以保留空间位置特征。这样做的原因是,晚期交互本来就不需要你在一开始就做完美对齐,强行拉齐反而丢失细节。
4.2 图的构建与层级组织
组件抽完之后,开始建图。我复现时的做法分成四步:
- 同模态语义边:对文本组件,用文本编码器计算彼此相似度,每个节点保留Top-K个最相似节点作为边;对视觉组件同样处理;
- 跨模态共现边:凡是来自同一个原始文档、同一个页面、同一个视频片段的图文组件,全部建立一条边,权重取对齐模型的相似度;
- 空间与时序边:视觉区域之间如果IOU超过阈值,建立空间邻接边;文本组件如果在原文里相邻,建立顺序边;
- 分层聚合:用聚类或者社区发现的方式,把一组紧密关联的低层组件聚合到中层,中层再聚合到高层,形成隶属边。
建图的时候有几件事一定要控制。第一是边密度,Top-K别给太大,常用5到10条;第二是权重要归一化,因为文本相似度和视觉相似度的分布差异很大,不归一化的话,某类边会主导路径选择;第三是保留元信息,每个组件必须知道它来自哪个原始文档、原始位置在哪,否则后面解释证据链的时候会很痛苦。
4.3 从查询到种子:轻量召回
图建好之后,查询进来先做种子召回。
我建议用混合召回而不是单靠一个向量模型。文本部分用稀疏检索加稠密向量检索混合,视觉部分用区域特征向量做近邻搜索。为什么混着来?因为多跳查询的起始跳往往是开放的,你没法确定查询会从文本侧还是视觉侧命中。只靠稠密向量容易漏,只靠稀疏检索又处理不了视觉组件。
设定一个相对宽松的召回阈值,宁可多召回一些种子,也不要在一开始就把正确答案排除掉。种子质量决定路径上限,如果种子节点里压根没有证据链的起点,后面图算法再厉害也白搭。
4.4 图上的路径扩展与多跳推理
种子确定后,进入核心环节:路径扩展。
我的实现参考了LILaC的做法,在组件图上做受限的广度优先搜索或者光束搜索。从种子节点出发,每次扩展邻居,保留得分最高的若干条候选路径。关键是限制条件:
- 路径最大跳数:通常限制在3到5跳;
- 每层扩展的候选数:光束宽度给32到64;
- 路径必须覆盖至少一个视觉组件和一个文本组件,否则不算跨模态路径。
路径打分时,我用的策略是综合三部分:
- 节点相关分:当前组件和查询的语义相似度;
- 边权重分:沿路径经过的每条边的权重;
- 路径形式分:跳数惩罚、组件类型惩罚、来源多样性奖励。
伪代码如下:
def retrieve_paths(query_vec, component_graph, seeds, max_hops=4, beam_width=32): paths = [{"nodes": [seed], "score": seed.initial_score(query_vec)} for seed in seeds] for hop in range(max_hops - 1): new_paths = [] for path in paths: tail = path["nodes"][-1] for neighbor, edge_weight in component_graph.neighbors(tail): if neighbor in path["nodes"]: continue node_score = neighbor.similarity(query_vec) path_score = path["score"] + edge_weight * 0.3 + node_score new_path = { "nodes": path["nodes"] + [neighbor], "score": path_score, } new_paths.append(new_path) new_paths.sort(key=lambda x: x["score"], reverse=True) paths = new_paths[:beam_width] paths.sort(key=lambda x: x["score"], reverse=True) return paths[:10]注意这里我没有对路径做整齐归一的深度跨模态融合,只用轻量向量相似度在图上寻路,这就是晚期交互的精髓。
4.5 重排与答案组织
路径扩展拿到的是候选证据链,还需要最后一道关卡。
我会把Top-N路径上涉及的所有组件重新组织起来,送给一个更强的跨模态模型做深度核对。因为候选数已经很小,比如10条路径、每条路径十来个组件,深度交互的代价完全可控。
这个重排阶段可以回答一个问题:这条路径上的组件组合在一起,是否真的支撑查询的语义?如果能支撑,再把路径上的文本组件、OCR组件、视觉区域特征的摘要组合成最终答案的上下文,交给生成模型出答案。
路径本身也可以作为可解释性输出:用户可以看到答案是顺着"图A识别菜品 → 菜谱文本找主料 → 图B视觉区域匹配"这条链路得来的,而不是一个黑盒分数。
5. 复现与落地时的关键细节:我踩过的坑和调参建议
5.1 组件粒度:切得多细才算合适
组件粒度对最终效果的影响,比图算法还大。我第一次跑的时候,把文本切成段落,结果每个组件太大,一条路径走两步就跨了整个文档,证据链完全对不上。后来改成句子级,效果好很多。但过犹不及,切到短语级之后,路径跳数明显增加,噪声也变多。
我的经验是:让"一跳"的信息量大致相等。视觉区域通常是画面里几个物体,文本侧对应一两句话,这样从文本跳到视觉时,信息粒度才匹配。
5.2 边质量比节点数量更影响检索效果
组件图里边的质量几乎决定了多跳检索的天花板。有两条边经验特别值得记下来。
第一,语义相似边的阈值要按模态分开调。文本组件的相似度普遍偏高,视觉组件相似度普遍偏低,用一个全局阈值会导致文本边过密、视觉边过稀。我试过把两类边分开归一化,效果立刻改善。
第二,跨模态共现边不要只靠对齐模型的分数,还要加上原始来源的强制性绑定。比如一张图片和一段文字在同一篇文档里,就算对齐分数一般,也必须保留强相关边,因为这是检索路径可解释性的根基。
5.3 路径长度和评分函数的权衡
多跳不是越多越好。我在调试中发现,路径长度限制在3到5跳时效果最好。超过5跳,噪声累积得很严重,而且评分函数的权重会变得极其敏感。
评分方面,加法、最大值、加权归一化几种我都试过。加法对短路径不公平,最大值又容易让单点强相关掩盖整条路径的薄弱环节。折中的办法是用归一化加权平均,给边权相对较大的权重,同时加入跳数惩罚。不同数据集上最优权重差异很大,建议留一个小验证集专门调这个。
5.4 索引与查询速度控制
LILaC这种晚期交互设计,在索引阶段可以并行化得非常好。所有组件独立编码,不需要彼此通信,GPU利用率高。但图本身的存储是另一个问题。
几万组件级别,边数量可能到几十万,内存压力不小。我的优化方式是:
- 向量量化压缩:把组件向量从浮点精度压到8位或者4位,检索损失在可接受范围;
- 边列表紧凑化:只存邻居ID和权重,不存额外对象;
- 分层剪枝:先在高一层定位候选子图,再下潜到底层组件去细化路径,避免全图扫描。
实测下来,几万组件规模在单台小服务器上完全能跑,查询延迟主要花在路径扩展和重排深度交互上。
6. 选型判断:什么时候用LILaC,什么时候别硬上
6.1 和三种常见思路的对比
我用一个表格总结一下LILaC和多模态RAG、单跳双塔、Agent方案的使用边界。
| 方案 | 检索单元 | 多跳能力 | 可解释性 | 在线成本 | 适合场景 |
|---|---|---|---|---|---|
| 标准RAG段落检索 | 段落 | 弱 | 中 | 低 | 纯文本单跳问答 |
| 单跳多模态双塔 | 整图/整段文本 | 弱 | 低 | 低 | 图找图、文找图 |
| Agent多工具检索 | 工具调用结果 | 中 | 中 | 高 | 外部工具少、步骤可编程 |
| LILaC | 组件图路径 | 强 | 高 | 中 | 图文交错多来源、多跳证据链 |
6.2 我认为LILaC最合适的应用场景
有一个很典型的场景:手头有大量新闻网页,每篇新闻都配了至少三张图片,图片里又有OCR文字、有物体、有人物。用户的问题往往是跨图片跨段落的,比如"图中那个人在另一篇报道里说过什么"。这就是分层组件图的主场,因为证据链里图片区域、OCR、文本段落必须交替出现,纯文本检索完全处理不了。
课件检索、商品说明书问答、事故调查报告分析这类场景也合适。尤其是当业务方要求"答案必须能追溯到证据路径"的时候,LILaC的组件图表达几乎是天然匹配。
6.3 不适合的场景
如果你知识库很小,所有内容就几百个文件,且大部分查询是单跳的,别硬上组件图。建图、调边的工程成本比检索本身还高,直接用向量库就好。
如果查询模式非常固定,比如永远就是"根据型号找规格",那结构化元数据过滤比图检索快一个数量级,也不需要组件图。
如果团队没有检索基础设施,没有向量索引和离线计算资源,LILaC的部署成本可能会让你劝退。它的优势要在大规模、多模态、多跳三个条件同时满足时才明显。
6.4 可以继续扩展的方向
我在自己的项目里做过两个方向的扩展,效果都还不错。
第一个是让大模型参与建图。人工设计组件抽取规则太累,可以让大模型先对文档生成结构化组件描述,再映射回视觉区域和文本片段。准确性可能略降,但覆盖度高很多。
第二个是把路径作为上下文直接交给生成模型。检索阶段输出5条候选路径,每条路径按顺序整理成一段上下文,让大模型自己选择哪条路径靠谱,合并生成最终答案。组件的结构信息保留在路径顺序里,生成模型就能"照着路径讲故事"。
最后说点个人体会。我做多模态知识库问答时,最深的感受是:多跳检索真正的瓶颈不是模型能力,而是知识表示方式。扁平向量把证据压碎了,纯文本把视觉丢了,整文档把粒度搞粗了。LILaC给我最大的启发,是把"证据链"当作一级检索对象,用分层组件图把证据链显式地表达出来,再用晚期交互把在线检索成本压下来。
如果你正在做类似的事情,我建议不要一开始就追求全网规模。先拿几百个图文文档,手工清洗组件,把一条完整路径跑通,感受一下"图A识别物体、文本中间桥接、图B视觉区域再匹配"的链路,再考虑扩大规模。这条路线确实比普通向量检索复杂,但它解决的是更高级的问题——答案可解释、证据可追溯、模态可跨越。