你的 RAG 为什么"看不懂"图片?Multimodal RAG 从原理到生产级实战

MMT 0 次阅读
你的 RAG 为什么"看不懂"图片?Multimodal RAG 从原理到生产级实战

企业文档中 30-60% 的关键信息以图片、图表、表格形式存在,传统文本 RAG 对此完全"失明"。本文带你系统掌握 Multimodal RAG 的三种架构模式和完整落地方法。


开篇:从一个真实业务场景说起

"帮我查一下 Q3 季度毛利率下滑的原因。"

你的 RAG 系统检索了一遍知识库,返回了一段不痛不痒的文字。但你真正需要的信息——那张展示各产品线毛利率对比的瀑布图,在第 34 页上——从未被索引到。

这不是偶然的失败。一个典型的工程企业文档库中,44% 的页面是扫描图像,没有文本层。初始文本 RAG 对文本子集的召回率 Recall@5 为 0.91,对扫描子集仅 0.03——几乎等于看不见。更扎心的是,团队花了三周调试,才发现根因不是分块策略或检索参数,而是管道的输入端就把半数文档丢弃了

这就是我们今天要解决的问题:如何让 RAG 系统真正"看懂"图表、表格、技术图纸和扫描文档?

本文的定位是 Multimodal RAG 的单点深度突破,聚焦三大核心:

  1. 三种架构模式的选型逻辑与优劣对比
  2. ColPali / ColQwen2.5 的底层原理与完整落地代码
  3. 生产环境的 GPU 资源规划、成本控制与踩坑经验

技术背景与核心概念扫盲

什么是 Multimodal RAG?

多模态检索增强生成(Multimodal Retrieval-Augmented Generation) 是传统 RAG 的扩展,核心目标是将文本、图像、表格、音频、视频等多种模态的数据纳入检索与生成流程。

这不是一个"锦上添花"的功能。当你实地审计企业文档库时会发现:

文档类型 含关键视觉元素比例 典型视觉内容
工程图纸 90-100% 接线图、P&ID 流程图、CAD 截图
财务报告 40-60% 营收瀑布图、产品线对比表
技术手册 50-70% 设备示意图、维修流程图
法律合同 10-20% 签名页扫描件、鉴证表格
医疗报告 60-80% 影像学扫描、病理标注图

数据来源:Tensoria 2026 年对 50+ 企业文档库的审计汇总

传统 RAG 的三重"失明"

  1. OCR 错误累积:500 词页面在 80% OCR 准确率下会产生约 100 个乱码 token,嵌入后完全不代表页面真实内容
  2. 表格结构丢失:5条产品线 × 12个月的营收表被压平成数字序列 "Revenue Q1 Q2 Q3 15.2 18.7 21.3",语义关系全部丢失
  3. 多栏布局错乱:双栏学术论文或三栏新闻排版,阅读顺序重排错误率可达 30-40%

这些问题的共同本质是:文本 RAG 假设文档可被无损地线性化为字符串,但这个假设在现实世界中不成立。

Multimodal RAG 三种架构对比图

图1:Multimodal RAG 的三种核心架构模式对比。从左到右:Caption-and-Index(最简单)、Unified Embeddings(最平衡)、Page-as-Image with Late Interaction(最高召回率)。


底层原理深度拆解

三种架构模式

Multimodal RAG 没有"一种正确的架构"。根据视觉内容在文档中的占比和类型,有三种经过生产验证的模式:

架构一:Caption-and-Index(图文标注索引)

工作原理:用视觉语言模型(VLM)为每张图片生成文字描述(Caption),然后将描述文本作为普通文本块嵌入和检索。

[图片] → VLM 生成描述 → [文本描述] → 标准文本嵌入 → 向量检索

优势:零改动接入原有文本 RAG 管道
劣势:有损压缩——Caption 自身的幻觉会污染索引;对图表中的精确数值不可靠

适用场景:文档中图片较少(<10%)、图片内容以自然场景照片为主的企业知识库。快速验证时可用。

架构二:Unified Embeddings(统一嵌入空间)

工作原理:使用多模态嵌入模型(如 Cohere Embed 4、voyage-multimodal-3.5、SigLIP 2)将文本和图像映射到同一个向量空间,实现跨模态检索。

[文本] ─┐
        ├→ 统一嵌入模型 → [768-dim 向量] → 向量数据库
[图像] ─┘

优势:单一索引、跨模态查询、存储成本可控
劣势:单向量表示不如多向量的后交互(Late Interaction)粒度细;有 API 依赖或自托管 GPU 成本

适用场景:电商图片搜索、产品目录检索、混合型文档库。多数生产团队最终选择此架构作为主力。

架构三:Page-as-Image with Late Interaction(页面即图像 + 后交互)

工作原理:跳过所有解析步骤,直接将 PDF 页面渲染为图像,用视觉语言模型生成逐 Patch 的多向量嵌入,通过 MaxSim(最大相似度)后交互评分进行检索。

[PDF 页面] → 渲染为图像 → VLM 编码 → [1030 个 128-dim 向量] → 多向量检索
[用户查询] → 文本编码 → [N 个查询向量] ──→ MaxSim 评分

优势:对图表密集文档召回率最高;无需 OCR/布局分析管道
劣势:每页存储量是文本嵌入的 100-1000 倍;需要支持多向量的向量数据库

适用场景:金融报告(图表/表格密集)、工程图纸(空间关系关键)、科研论文(图文混排)。

ColPali 与后交互原理

ColPali(Faysse et al., ICLR 2025)是"Page-as-Image"架构的代表性模型。核心洞察是:跳过所有解析,让模型像人一样"看"页面

具体流程:

  1. 页面渲染:将 PDF 页面以 150-300 DPI 渲染为图像
  2. Patch 编码:ViT 编码器将 448×448 像素的图像划分为 1030 个 Patch,每个 Patch 覆盖约 14×14 像素区域
  3. 向量投影:每个 Patch 通过线性层投影为 128 维向量——即每页产生 1030 个向量
  4. 查询编码:用户查询文本同样编码为一组查询向量
  5. MaxSim 评分:对每个查询向量,在所有页面 Patch 向量中找到最大余弦相似度并求和
Score(query, page) = Σ_max_j cos(query_i, patch_j)  对每个查询 token i

这比单向量稠密检索的优势在于:传统 RAG 将整页信息压缩为一个向量(均值池化),丢失了空间位置和局部视觉信息。而 MaxSim 后交互可以让查询"Q3 营收数字"精确匹配到表格中对应的单元格 Patch。

ColPali 后交互评分机制示意图

图2:ColPali 的后交互(Late Interaction)评分机制。查询 token 与页面 patch 向量在细粒度上逐一匹配,而非压缩为单向量比较。

ColPali 模型家族选型

截至 2026 年 7 月,ColPali 模型家族已扩展为多个变体:

模型 基础 VLM VRAM (FP16) 每页 Patch 数 ViDoRe nDCG@5 最佳场景
ColPali-3 PaliGemma-3B ~8GB 1030 (448px) ~78% 通用检索,良好基线
ColQwen2.5-7B Qwen2.5-VL-7B ~16GB ~196-1030(可变) ~84% 生产级英文/多语言
ColSmolVLM-256M SmolVLM-256M ~2GB ~196 ~65% 边缘部署、实时批量
ColSmolVLM-500M SmolVLM-500M ~3GB ~196 ~70% 资源受限环境
ColInternVL2-4B InternVL2-4B ~10GB 可变 ~80% 中文/多语言文档

数据来源:ViDoRe Benchmark V2 排行榜(2026 年 4 月更新),nDCG@5 为视觉文档检索标准指标。

对于多数生产环境,ColQwen2.5-7B 是最佳起点——ViDoRe 上最高检索精度,且对混合语言文档支持良好。若 VRAM 预算紧张(如单卡 8GB 内),可降级到 ColPali-3 或 ColSmolVLM。


手把手实战落地

下面我们搭建一个完整的 Multimodal RAG 系统,使用 ColQwen2.5 + Qdrant + vLLM,实现从 PDF 页面索引到视觉问答的端到端流程。

环境准备

# Python 3.10+
pip install torch==2.4.0 --index-url https://download.pytorch.org/whl/cu124
pip install colpali-engine qdrant-client pdf2image pillow
pip install vllm  # 用于部署 VLM 推理服务
pip install anthropic  # 可选:用于 VLM-based 标注方案

⚠️ colpali-engine 是 Vidore 团队提供的统一推理库,支持所有 ColPali 系列模型。

阶段一:PDF 页面渲染

# render_pages.py
from pdf2image import convert_from_path
from pathlib import Path
from PIL import Image
from typing import List


def render_pdf_pages(pdf_path: str, dpi: int = 150) -> List[Image.Image]:
    """
    将 PDF 每一页渲染为 PIL Image 对象。
    
    Args:
        pdf_path: PDF 文件路径
        dpi: 渲染分辨率,150 DPI 是质量和性能的平衡点
        
    Returns:
        页面图像列表
    """
    # convert_from_path 底层调用 poppler,支持大多数 PDF 格式
    pages = convert_from_path(pdf_path, dpi=dpi)
    
    # ColQwen2.5 支持动态分辨率,无需强制 resize
    # 但 ColPali-3 需要 448x448 固定输入
    print(f"  → 已渲染 {len(pages)} 页,首页尺寸: {pages[0].size}")
    return pages

阶段二:批量构建多向量索引

这是最核心的环节——用 ColQwen2.5 为每页生成 Patch 级嵌入,并存入 Qdrant。

# index_pages.py
import uuid
import torch
from colpali_engine.models import ColQwen2_5, ColQwen2_5Processor
from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance, VectorParams, PointStruct,
    MultiVectorConfig, MultiVectorComparator,
)
from PIL import Image
from typing import List
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def create_qdrant_collection(client: QdrantClient, collection_name: str = "multimodal_docs"):
    """
    创建支持多向量的 Qdrant 集合。
    MultiVectorConfig 和 MAX_SIM 比较器是 ColPali 检索的关键。
    """
    # 如果集合已存在,跳过创建
    collections = client.get_collections().collections
    if any(c.name == collection_name for c in collections):
        logger.info(f"集合 '{collection_name}' 已存在,跳过创建")
        return
    
    client.create_collection(
        collection_name=collection_name,
        vectors_config={
            "colpali": VectorParams(
                size=128,                      # ColPali 系列固定维度
                distance=Distance.COSINE,       # 余弦相似度
                multivector_config=MultiVectorConfig(
                    comparator=MultiVectorComparator.MAX_SIM  # 后交互评分器
                ),
            )
        },
    )
    logger.info(f"✅ 已创建多向量集合 '{collection_name}'")


def index_pages(
    pages: List[Image.Image],
    doc_id: str,
    model: ColQwen2_5,
    processor: ColQwen2_5Processor,
    client: QdrantClient,
    collection_name: str = "multimodal_docs",
    start_page: int = 0,
    batch_size: int = 8,
):
    """
    批量索引 PDF 页面。
    
    Args:
        pages: 渲染后的页面图像列表
        doc_id: 文档唯一标识
        model: ColQwen2.5 模型实例
        processor: 对应的 processor
        client: Qdrant 客户端
        start_page: 起始页码
        batch_size: GPU 批处理大小,取决于显存容量
    """
    all_embeddings = []  # 存储所有页面的嵌入
    
    # 分批处理以控制 GPU 显存
    for i in range(0, len(pages), batch_size):
        batch = pages[i : i + batch_size]
        
        # 预处理:图像归一化、padding 等
        inputs = processor.process_images(batch).to(model.device)
        
        with torch.no_grad():
            # 输出形状: (batch_size, n_patches, 128)
            embeddings = model(**inputs)
        
        # 转为 Python list 方便序列化
        all_embeddings.extend(embeddings.cpu().float().numpy().tolist())
        logger.info(f"  → 已处理第 {i+1}-{min(i+batch_size, len(pages))} 页")

    # 构建 Qdrant 数据点
    points = []
    for idx, emb in enumerate(all_embeddings):
        page_num = start_page + idx
        point_id = str(uuid.uuid5(
            uuid.NAMESPACE_DNS, f"{doc_id}::page::{page_num}"
        ))
        points.append(
            PointStruct(
                id=point_id,
                vector={"colpali": emb},  # 多向量字段名必须与集合配置一致
                payload={
                    "doc_id": doc_id,
                    "page": page_num,
                    "source_pdf": doc_id,
                },
            )
        )
    
    # 批量写入 Qdrant
    client.upsert(collection_name=collection_name, points=points)
    logger.info(f"✅ 已写入 {len(points)} 个页面向量到 '{collection_name}'")
    return len(points)


def load_model():
    """加载 ColQwen2.5 模型和 processor"""
    model_name = "vidore/colqwen2.5-v0.2"
    
    model = ColQwen2_5.from_pretrained(
        model_name,
        torch_dtype=torch.bfloat16,  # 节省显存
        device_map="cuda",           # 自动分配到 GPU
    ).eval()  # 推理模式
    
    processor = ColQwen2_5Processor.from_pretrained(model_name)
    logger.info(f"✅ 已加载模型 '{model_name}',设备: {model.device}")
    return model, processor

阶段三:多向量检索

# retrieve.py
import torch
from colpali_engine.models import ColQwen2_5, ColQwen2_5Processor
from qdrant_client import QdrantClient
from typing import List, Dict, Any


def retrieve_pages(
    query_text: str,
    model: ColQwen2_5,
    processor: ColQwen2_5Processor,
    client: QdrantClient,
    top_k: int = 5,
    collection_name: str = "multimodal_docs",
) -> List[Dict[str, Any]]:
    """
    多向量检索:对查询文本编码,通过 MaxSim 后交互在 Qdrant 中检索。
    
    Args:
        query_text: 用户的自然语言查询
        top_k: 返回最相关的 k 个页面
        
    Returns:
        检索到的页面 payload 列表
    """
    # 1️⃣ 编码查询文本
    inputs = processor.process_queries([query_text]).to(model.device)
    with torch.no_grad():
        # 输出形状: (1, n_query_tokens, 128)
        query_embedding = model(**inputs)
    
    # 转为 2D list: [n_query_tokens, 128]
    query_vectors = query_embedding[0].cpu().float().numpy().tolist()
    
    # 2️⃣ 多向量检索(Qdrant 自动执行 MaxSim 评分)
    results = client.query_points(
        collection_name=collection_name,
        query=query_vectors,          # 多向量查询
        using="colpali",              # 使用多向量字段
        limit=top_k,
    )
    
    # 3️⃣ 提取结果
    retrieved = []
    for point in results.points:
        retrieved.append({
            "doc_id": point.payload.get("doc_id"),
            "page": point.payload.get("page"),
            "score": point.score,  # MaxSim 相似度分数
        })
        logger.info(
            f"  → 命中: {point.payload.get('doc_id')} "
            f"第 {point.payload.get('page')} 页 "
            f"(score: {point.score:.4f})"
        )
    
    return retrieved

阶段四:VLM 答案生成

检索到相关页面后,将页面图像发送给视觉语言模型生成最终答案:

# generate_answer.py
import base64
import io
from openai import OpenAI  # vLLM 兼容 OpenAI API
from PIL import Image
from typing import List


def answer_from_retrieved_pages(
    query: str,
    page_images: List[Image.Image],
    vllm_base_url: str = "http://localhost:8000/v1",
    model_name: str = "Qwen/Qwen2.5-VL-7B-Instruct",
) -> str:
    """
    将检索到的页面图像与用户查询一起送入 VLM,生成答案。
    
    Args:
        query: 用户原始查询
        page_images: 检索到的页面图像列表
        vllm_base_url: vLLM 推理服务地址
        model_name: VLM 模型名称
        
    Returns:
        VLM 生成的答案文本
    """
    client = OpenAI(base_url=vllm_base_url, api_key="EMPTY")  # vLLM 无需真实 key
    
    # 构建多模态消息
    content = []
    
    # 先添加用户查询
    content.append({"type": "text", "text": f"根据以下文档页面回答问题:\n\n{query}"})
    
    # 再添加检索到的页面图像
    for i, img in enumerate(page_images):
        buf = io.BytesIO()
        img.save(buf, format="PNG")
        b64 = base64.b64encode(buf.getvalue()).decode()
        
        content.append({
            "type": "image_url",
            "image_url": {
                "url": f"data:image/png;base64,{b64}",
                "detail": "high",  # 高细节模式以看清表格和图表
            },
        })
    
    response = client.chat.completions.create(
        model=model_name,
        messages=[
            {
                "role": "system",
                "content": "你是一个文档分析助手。依据提供的文档页面图像,"
                           "准确回答用户问题。引用具体数据和位置信息。",
            },
            {"role": "user", "content": content},
        ],
        max_tokens=1024,
        temperature=0.1,  # 低温度确保答案确定性
    )
    
    return response.choices[0].message.content

完整的端到端运行脚本

# main.py —— 整合全部流程
import os
from qdrant_client import QdrantClient

# 1. 加载模型
model, processor = load_model()

# 2. 连接 Qdrant(使用内存模式或 Docker 部署)
client = QdrantClient(url=os.getenv("QDRANT_URL", "http://localhost:6333"))
create_qdrant_collection(client)

# 3. 索引一份文档
pages = render_pdf_pages("财报_Q3_2025.pdf")
index_pages(pages, doc_id="财报_Q3_2025", model=model, 
            processor=processor, client=client)

# 4. 检索
query = "Q3 季度毛利率下降的主要原因是什么?"
results = retrieve_pages(query, model=model, processor=processor, 
                         client=client, top_k=3)

# 5. 生成答案
# (实际使用时需要从页面路径加载图像)
# answer = answer_from_retrieved_pages(query, retrieved_images)
# print(f"💡 答案: {answer}")

索引存储成本评估

在投入生产前,务必理解 ColPali 多向量索引的存储规模:

语料规模 ColPali-3 (FP32) ColPali-3 (int8) ColQwen2.5 (FP32) ColQwen2.5 (int8)
1万页 5.3 GB 1.3 GB 1 GB 0.25 GB
10万页 53 GB 13 GB 10 GB 2.5 GB
100万页 527 GB 132 GB 100 GB 25 GB
1000万页 5.3 TB 1.3 TB 1 TB 250 GB

注:ColQwen2.5 默认分辨率产生的 patch 数更少(约196个),因此存储成本仅为 ColPali-3 的约 1/5。int8 量化几乎无损(nDCG@5 下降 <1%)。

多模态RAG完整架构流程图

图3:生产级 Multimodal RAG 的完整架构。包含多路检索(文本 + ColPali)、RRF 融合、VLM 重排序与答案生成。


关键细节与踩坑指南

坑 1:Caption 污染索引

Caption-and-Index 架构中,VLM 生成的图片描述可能产生幻觉。例如,一张瀑布图可能被错误描述为"柱状图",或者数值被错误转录。这些错误描述会被嵌入到索引中,成为永久性污染

解决方案:对 Caption 使用多模型交叉验证,或采用"描述 + 原始图像"的双通道策略——检索时 Caption 负责召回,生成时直接使用原始图像。

坑 2:文档分类先行

"把每一页都送给 VLM 处理,几乎总是错误的第一个步骤。"

生产经验:先运行文档分类器。如果页面有超过 100 个字符的文本层,走标准文本提取。只有文本层不足的页面才路由到 VLM。在企业文档库中,这通常能减少 60-75% 的 VLM 调用量。

def should_route_to_vlm(page_image, pdf_document, page_num) -> bool:
    """判断页面是否需要 VLM 处理"""
    # 尝试直接提取文本
    page = pdf_document[page_num]
    text = page.get_text()
    
    if len(text.strip()) > 100:
        # 文本层足够,走标准文本 RAG
        return False
    else:
        # 扫描页或图像页,需要 VLM
        return True

坑 3:ColPali 检索 + 文本 RAG 的混合不一定更好

从 ViDoRe 基准测试数据看:

方法 金融PDF PPT幻灯片 扫描文档 平均
文本 RAG (BM25) 48% 35% 28% 37%
文本 RAG (BGE-M3 稠密) 62% 44% 31% 46%
ColPali-3 78% 82% 74% 78%
ColQwen2.5-7B 84% 87% 79% 83%
混合(文本 + ColPali) 86% 86% 76% 83%

关键发现:在扫描文档上,OCR 错误导致文本管道的质量拉低了混合结果——混合方案反而低于纯 ColPali。如果你的语料中有大量扫描文档或照片,跳过 OCR 管道,直接用 ColQwen2.5 做单一检索

坑 4:GPU 索引吞吐量受内存带宽限制

索引是内存带宽瓶颈,而非计算瓶颈。Forward pass 中大量 Patch token 的激活张量需要频繁读写 HBM。

GPU HBM 带宽 ColQwen2.5 吞吐量 100万页索引时间 按需成本
A100 80GB 2.0 TB/s ~12 pages/sec ~23 小时 ~$24
H100 SXM5 3.35 TB/s ~20 pages/sec ~14 小时 ~$41
H200 SXM5 4.8 TB/s ~28 pages/sec ~10 小时 ~$45
B200 SXM6 8.0 TB/s ~48 pages/sec ~6 小时 ~$40

建议:一次性大规模索引租用 H200/B200 完成(6-10 小时),日常增量索引用 A100(成本更低)。


生产环境最佳实践

1. 多路检索 + 查询路由

生产环境推荐采用混合延迟融合(Hybrid Late-Fusion) 架构:

                    ┌─ BM25 (关键词检索) ─┐
                    │                     │
[用户查询] ─→ 查询分类器 ─→ 稠密向量检索 ─→ RRF 融合 ─→ VLM 重排序 ─→ 答案生成
                    │                     │
                    └─ ColPali (视觉检索) ─┘

查询路由规则

  • 包含"图表显示""第X页的图""示意图"等视觉关键词 → 优先走 ColPali
  • 文本索引 top-1 置信度 < 0.7 → 回退到 ColPali
  • 纯文本查询 → 标准混合检索(BM25 + 稠密)

2. Docker Compose 生产部署

# docker-compose.yml —— 生产部署
version: "3.9"

services:
  qdrant:
    image: qdrant/qdrant:v1.11.0
    ports:
      - "6333:6333"
      - "6334:6334"
    volumes:
      - qdrant_data:/qdrant/storage
    environment:
      - QDRANT__SERVICE__GRPC_PORT=6334
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]

  colpali-api:
    build: ./colpali-service
    ports:
      - "8080:8080"
    environment:
      - QDRANT_URL=http://qdrant:6333
      - MODEL_ID=vidore/colqwen2.5-v0.2
    depends_on:
      - qdrant
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]

  vllm-serve:
    image: vllm/vllm-openai:latest
    ports:
      - "8000:8000"
    command: >
      --model Qwen/Qwen2.5-VL-7B-Instruct
      --tensor-parallel-size 1
      --max-model-len 16384
      --gpu-memory-utilization 0.35
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]

volumes:
  qdrant_data:

3. 成本优化三板斧

  1. 选择性 VLM 路由:先分文档类型,仅 30-40% 的页面需要 VLM 处理,成本降 60-75%
  2. int8 量化:存储降 4 倍,检索质量下降 <1%
  3. Matryoshka 维度裁剪:1024-dim → 256-dim,存储降 4 倍,召回损失 <2%

横向对比与选型建议

三种方案的全方位对比

维度 Caption-and-Index Unified Embeddings Page-as-Image (ColPali)
检索精度 ⭐⭐(有损) ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
实现复杂度 ⭐(简单) ⭐⭐⭐ ⭐⭐⭐⭐
存储成本 最低 高(100-1000×)
索引速度 最快 慢(GPU 绑定)
查询延迟 中(200-500ms)
是否需 GPU 部署需要,检索不需要 嵌入需要,检索不需要 全程需要
跨模态检索 否(检索在文本空间)
表格精确数值 ✅ 中等 ✅✅ 最佳

选型决策路径

你的文档视觉内容丰富吗?
├── 仅少量图片 (<10%)
│   └── → Caption-and-Index(快速生效)
├── 混合型(文本+图片+表格)
│   └── → Unified Embeddings(Cohere Embed 4 / SigLIP 2)
└── 图表/图纸密集 (>50%)
    └── → Page-as-Image(ColQwen2.5 + Qdrant)

对于大多数生产环境,推荐先部署 Unified Embeddings 作为主力,然后对图表密集的子集叠加 ColPali 作为增强通道。


性能实测与效果验证

ViDoRe V2 基准测试结果

最新的 ViDoRe V2(Macé et al., 2025)是更难的视觉文档检索基准,涵盖多语言和复杂布局:

模型类别 具体模型 nDCG@5 每页存储 检索延迟(100K页)
文本基线 BM25 + OCR 37.2% ~0.5 KB <10ms
文本基线 BGE-M3 + OCR 45.8% ~1 KB <20ms
单向量多模态 Cohere Embed 4 72.3% ~6 KB <20ms
单向量多模态 voyage-multimodal-3.5 75.1% ~6 KB <20ms
多向量后交互 ColPali-3 78.4% 527 KB ~80ms
多向量后交互 ColQwen2.5-7B 83.7% 100 KB ~60ms
多向量后交互 ColInternVL2-4B 80.2% ~200 KB ~70ms
混合方案 Dense + ColPali RRF 84.1% ~100ms

测试环境:A100 80GB,Qdrant 1.11,100K 页面随机采样。数据来源:illuin-tech/vidore-benchmark 排行榜 2026.04。

关键发现

  1. 文本 RAG 的上限清晰可见:即使使用 BGE-M3 这样的强嵌入模型,在包含视觉内容的文档上,上限仅约 46%——因为输入端就已经丢失了大量信息
  2. ColQwen2.5 比 ColPali-3 提升约 5 个百分点,且存储成本低 5 倍(因为动态分辨率产生的 patch 更少)
  3. 混合方案(文本 + ColPali)在纯文本和视觉文档上均表现最佳,但收益主要在文本质量高的文档上;在扫描文档上,OCR 错误反而拖累混合结果

四种方案的检索精度对比

图4:四种方案在金融 PDF、幻灯片、扫描文档上的 nDCG@5 性能对比。ColQwen2.5 在所有子集上均显著超越文本基线。


总结与未来展望

核心要点回顾

  1. 问题认知:企业文档中 30-60% 的关键信息在视觉层,文本 RAG 有结构性的盲区——这是架构问题,不是参数调优问题
  2. 三种架构:Caption-and-Index(快速)、Unified Embeddings(平衡)、Page-as-Image(最强召回),选择取决于文档类型
  3. ColPali 家族:2026 年已成熟,ColQwen2.5-7B 是生产级首选——ViDoRe nDCG@5 达 84%,单页存储仅 100KB(int8)
  4. 成本可控:选择性路由 + int8 量化 + 文档分类前置,可将 Multimodal RAG 的生产成本控制在文本 RAG 的 1.5-3 倍以内

未来趋势

  • 端到端视觉理解:下一代 VLM(如 GPT-5、Gemini 3)原生支持文档级多模态推理,可能简化甚至消除专门的检索层
  • 统一向量标准化:ViDoRe V2 和 REAL-MM-RAG 等基准正在推动多模态检索评估的标准化
  • 边缘部署:ColSmolVLM-256M 仅需 2GB VRAM,可在边缘设备上实时处理文档页面
  • Agentic Multimodal RAG:结合 Function Calling 和工具使用,让 Agent 自主决定何时使用文本检索、何时使用视觉检索

延伸阅读