跳到正文

RAG PROJECT INTERVIEW · Q08 / 25

Chunk Size 和 Overlap 怎么选?不要背 512 和 100

不提供一个适合所有项目的魔法数字,而是用召回、语义完整性、重复率和上下文成本说明参数应该怎样测出来。

作者与复核:吴师兄内容依据:公开 RAG 教程、GitHub 教学代码与工程推导查看关联教程 →查看吴师兄公开作品 →

25 道题目Q08
本页内容14 节

30 秒先说结论

先按文档结构确定候选边界,再把 Chunk Size 作为最大 Token 预算。块太小会丢前提,太大会稀释主题并增加上下文噪声。Overlap 只用于缓解边界断裂,应优先对齐句子或段落边界。最终要在同一批真实问题上比较召回、证据完整性、重复片段和 Token 成本。

面试官真正想听的是参数背后的验证方法

很多回答一上来就是:“我们设置 chunk_size=1024overlap=100。”

面试官接着问“为什么”,回答就变成“网上大多这样配”或者“框架默认值就是这个”。这说明只会填写配置,没有建立评测闭环。

参数选择真正要回答四个问题:

  1. 一个问题的完整证据通常有多长?
  2. 文档里哪些内容最怕被拆开?
  3. 召回结果重复后,会占掉多少候选位和模型上下文?
  4. 参数改变后,怎样证明收益来自切分,而不是其他模块同时变化?

从工程链路看,RAG 可以拆为离线解析、在线召回、上下文生成和查询理解。切分位于离线阶段,但它会沿着整条链路向后影响:块太大,向量里同时包含多个主题,检索得分可能被噪声稀释;块太小,问题所需的前提、条件和结论可能落在不同块里;重叠太小,边界信息可能断开;重叠太大,多个近似块会挤进同一个 Top K。

所以,这不是一个“字符串应该截多长”的问题,而是“证据怎样进入检索单元,并以多大代价被稳定找回”的问题。

Chunk 不是越大或越小越好

参数同时影响语义完整性、检索区分度和上下文成本,最终要由同一批问题验证。

先分清 chunk_sizeoverlap 各自在解决什么

chunk_size 控制的是检索单元的粒度

块变大,不等于信息一定更完整。

例如,一段教学文档可能先讲适用范围,再讲申请条件,最后讲例外情况。如果三个主题全部塞进一个大块,用户只问“申请条件”时,向量表示仍会混入适用范围和例外内容。检索可能命中,但相关证据在块里的密度下降,后续模型还要从更多无关文字中找答案。

块变小,也不等于检索一定更准确。

例如,一段教学材料先写“满足以下任一条件可以申请”,后面紧跟三个列表项。如果前导句和列表项被拆成四个小块,单独召回“条件二”时,模型看见了一个条目,却不知道它属于什么规则。又如材料先写生效条件,下一句写“但以下情况除外”,把两句分开会让答案缺少限制条件。

这里先划清边界:如果切分器还不能识别标题、段落、列表和表格,应该先解决结构切分。本题讨论的是结构基本正确以后,怎样为 chunk_sizeoverlap 找到可验证、可上线的参数。

因此 chunk_size 的核心矛盾是:

  • 大块更容易保留长证据,但主题可能混杂,单次向量表示更粗。
  • 小块主题更集中,但需要更多块才能拼成完整证据,边界风险更高。
  • 同一个知识库里,FAQ、规章条款、表格说明和技术文档的合适粒度可能不同。

overlap 控制的是相邻块之间保留多少共同上下文

重叠窗口的目的,是让切分点附近的前提和结论至少在某一个块中共同出现。

假设块 A 末尾是“申请人应满足以下条件”,块 B 开头是“第一,已完成身份核验”。如果 B 完全不带前文,单独召回 B 时语义会变弱。让 B 带上一小段 A 的尾部,可以恢复“这些条目是什么”的上下文。

但 overlap 不是越大越保险。重叠越多,同一段文字会被多次向量化。查询命中边界区域时,Top K 里可能出现内容高度相似的 A、B、C,表面上召回了三条,实际上只有一份证据。它们还会一起进入重排和提示词,占用候选位、计算和上下文长度。

因此,overlap 解决的是边界连续性,不应该拿它替代正确的结构切分。章节、表格、代码块或列表已经被错误拆散时,盲目扩大重叠只是复制错误。

Overlap 只修边界,不修错误结构

先把结构切对,再在确有断裂风险的边界补最小必要上下文。

为什么不能复制别人的参数

即使两套系统都叫“企业知识库 RAG”,以下差异也会让参数结论完全不同。

第一,文档结构不同。

一问一答式 FAQ 本身就是短而完整的语义单元。制度、合同和技术手册则可能把定义、条件、例外和步骤分散在多个自然段。平均长度相同,并不代表证据跨度相同。

第二,Embedding 模型的输入限制和训练方式不同。

如果块长超过当前 Embedding 模型实际接收的长度,超出部分可能被截断;是否截断、从哪里截断,要以所用模型的文档和实测结果为准。即使没有截断,长块里混入多个主题,也可能让向量表达变得不够聚焦。不能只看数据库保存了多少字,还要确认多少内容真正进入了向量编码。

第三,检索和重排链路不同。

只用向量召回、向量加 BM25 的混合召回、是否有 Cross-Encoder 重排,都会改变“大块”和“小块”的表现。粗召回与精排的职责不同:前者尽量带回候选,后者在较小候选集中做更细判断。切分实验如果同时更换检索器或重排模型,就无法判断收益来自哪里。

第四,问题类型不同。

精确术语问题可能只需要一小段定义;比较题可能同时需要两组证据;流程题需要连续步骤;否定和例外问题必须把限制条件带回来。只用一类问题调出的参数,很容易在另一类问题上失效。

第五,上下文预算不同。

最终能送入模型的不是无限文本。块越大,每次能放入的候选越少;块越小,可能需要更多候选才能补齐证据。参数要和 Top K、重排截断、提示词模板共同考虑,但实验时仍应一次只改一个变量组。

一次合格的参数实验应该怎样设计

第一步:先定义问题,而不是先枚举参数

先从真实文档类型中抽样,再为每类文档准备能够定位到原文证据的问题。评测集至少要覆盖:

  • 单段即可回答的事实题;
  • 前提与结论相隔较远的条件题;
  • 带“除外、不得、仅限”等限制词的问题;
  • 前导句加列表项的流程题;
  • 表头与数据行共同决定含义的表格题;
  • 需要两个片段共同回答的组合题。

这里的“标准答案”不能只有一段自然语言。还要标注支撑答案的原文位置,最好标出最小证据范围。否则系统答对了,也不知道是正确召回、模型常识碰巧答中,还是错误片段碰巧包含相似表述。

第二步:给评测样本分层

把不同风险的样本混成一个平均分,会掩盖问题。

可以按文档形态分层:FAQ、普通段落、长条款、列表、表格、代码或步骤说明。也可以按证据跨度分层:单块证据、跨边界证据、跨多个自然段证据。查询还可分成精确词、口语化表达、否定条件和多约束问题。

分层之后,才看得见某个参数组合到底改善了什么。它可能提高普通事实题的召回,却让表格和列表题变差;也可能让边界问题更完整,却制造大量重复候选。

第三步:固定其他变量

实验期间至少固定这些项目:

  • 同一批原始文档和同一版解析结果;
  • 同一个 Embedding 模型及其输入处理;
  • 相同的向量索引类型与搜索参数;
  • 相同的 BM25 配置和召回路数;
  • 相同的候选数量、重排模型和最终 Top K;
  • 相同的提示词与生成模型,如果要评估最终回答。

唯一改变的是切分配置。若必须同时比较“按段落切”和“固定长度切”,应该把它明确当成另一个实验维度,而不是把算法和参数混在同一张结果表里。

第四步:建立可重跑的配置矩阵

参数值应优先根据当前语料的长度分布和证据跨度来确定,而不是直接复制文章里的数值。实验程序读取一组配置,为每组配置重新切分、建索引、运行同一批查询并记录结果。下面是只说明实验步骤的教学伪代码,不是可直接运行的项目实现:

def evaluate_chunk_config(config, documents, questions, pipeline):
    chunks = pipeline.chunk(
        documents,
        chunk_size=config.chunk_size,
        overlap=config.overlap,
    )
    index = pipeline.build_index(chunks)

    rows = []
    for sample in questions:
        results = pipeline.retrieve(index, sample.query)
        rows.append({
            "query_id": sample.id,
            "retrieved_chunk_ids": [item.id for item in results],
            "evidence_hit": hit_evidence(results, sample.evidence_spans),
            "first_hit_rank": first_hit_rank(results, sample.evidence_spans),
            "duplicate_ratio": duplicate_ratio(results),
        })

    return summarize(
        rows,
        chunk_count=len(chunks),
        index_bytes=index.size_bytes,
    )

关键是每次运行都能追溯配置、索引版本、问题集版本和结果明细。只有一张最终分数截图,而没有逐题结果,出现退化时很难定位。

Chunk 参数应该这样测出来

固定模型、Top K、Rerank 与 Prompt,只比较切分配置带来的真实变化。

指标怎么选,才不会只优化一个漂亮数字

Recall@K:正确证据有没有进入候选集

如果标准证据完全没进前 K,后面的重排和生成通常无从补救。Recall@K 适合观察切分后“该找的内容能不能找到”。

但它也可能被重复结果欺骗。三个高度相似的重叠块都命中同一证据,不能算找回了三份有效信息。因此评估时应按证据或原文区间去重,而不是只按 chunk ID 计数。

MRR 或 nDCG:正确证据排在什么位置

MRR 关注第一个相关结果出现得早不早。nDCG 适合存在多条不同相关度证据时,观察整体排序。这些指标常用于比较召回和排序方案;在切分实验里,它们能帮助判断参数变化是否只把相关块“塞进了候选尾部”,还是让它真正靠前。

证据完整性:召回的块能不能独立支撑回答

传统检索指标只能判断相关,不一定判断完整。

一个块包含“可以申请”,却没有包含后面的“仅限以下情形”;它与问题高度相关,但不能安全支撑答案。可以由人工标注或规则辅助检查:答案所需的前提、主体、条件、结论和例外是否在同一块或允许拼接的少量块中完整出现。

这项指标不必先追求自动化总分。首批高风险样本逐条审查,往往比一个无法解释的模型评分更有价值。

重复率:Top K 里有多少位置被近似内容占据

Overlap 增大后,一个常见的副作用是候选重复。可以比较结果之间的文本重合、来源区间重合或相邻 chunk 关系,统计同一证据占用了多少候选位。

如果 Recall@K 略有提升,但 Top K 中大部分结果都来自同一段相邻文本,后续模型获得的信息覆盖面反而可能下降。

工程成本:索引规模、建库时间、检索延迟与上下文占用

小块和大 overlap 都会增加 chunk 数。chunk 数增加,会影响向量生成量、索引体积、重建时间和候选去重成本。大块则会增加单块 token 数和后续上下文占用。

参数实验还要记录索引更新、在线检索延迟和全链路监控数据。不要预填一个“可接受”的成本数字,应该记录当前项目的真实资源和延迟,再把它作为参数选择的约束。

如何从实验结果里选出真正能上线的一组

不要简单地取 Recall@K 最高的一行。

可以先设业务底线:高风险条件题和否定题不能出现已知退化;正确证据必须在重排候选范围内;重复率、索引规模和延迟不能超过当前系统能承受的边界。在满足底线的配置中,再比较整体排序和成本。

更直观的做法,是画一张“收益与代价”图:横轴是索引或延迟成本,纵轴是证据召回与完整性。位于劣势区域的配置通常可以排除:它比另一个配置更慢、更大,效果还更差。剩下的候选再做人工 bad case 审查。

如果不同文档类型的最优区间差异明显,不必强行全库共用一个数字。可以按明确、稳定的文档类别采用少量策略,例如 FAQ 以问答单元为主,长制度按结构单元再受最大长度约束。前提是分类逻辑可解释、可监控,不要让每份文档都进入一套无法维护的“自动猜参数”流程。

从结果反推参数问题,而不是重复讲结构切分

参数回归应把现象映射到可能原因,先判断它是否真的能由 Size 或 Overlap 修复:

观察到的现象 优先检查的参数问题 不能靠参数掩盖的根因
条件句与结论分开召回 块是否过小,边界处是否缺少最小重叠 解析器没有识别列表与段落结构
Top K 出现多个近似片段 Overlap 是否过大,块是否过碎 重复入库或版本过滤错误
命中正确章节却带回大量无关内容 块是否过大,单块是否混入多个主题 标题路径或结构元数据丢失
表格问题只找到数据行 行组是否过大或过小 表头没有作为结构信息继承
跨页问题长期失败 分页附近的证据跨度是否超过块长 页面阅读顺序在解析阶段已经错误

一组配置即使平均分领先,只要高风险条件题持续退化,也不能直接上线。这个诊断表的作用,是阻止团队把解析、入库和版本问题都错误归因给两个参数。

参数改动上线时,为什么要把索引当成版本化产物

修改切分参数意味着文档会生成一套新的 chunk ID、向量和索引。直接覆盖旧索引,一旦发现回归,很难快速恢复。

更稳妥的流程是:

  1. 保存切分配置、解析版本、Embedding 版本和评测集版本;
  2. 用新配置构建独立索引,不覆盖当前可用版本;
  3. 离线跑完整评测,并逐条检查高风险 bad case;
  4. 在受控流量或影子请求上比较新旧结果,记录每路候选、最终排名与延迟;
  5. 通过后再切换索引别名,保留旧版本作为回退入口;
  6. 切换完成后继续观察无答案率、人工反馈、重复候选和典型问题回归。

若基础设施支持,可以用独立索引、别名切换和受控对照降低切换风险;具体采用哪一种,要按现有存储与发布流程决定。

参数回归还需要固定样本。每次发现新问题,都把原始查询、标准证据位置、当时的切分配置和错误结果加入回归集。下一次优化不只看新问题有没有修好,还要确认旧问题没有重新出现。

Chunk 参数上线前检查

上线前同时检查证据完整、结果重复与变更可控,参数必须能够复现和回归。

面试官继续追问,怎么接

为什么不用一个更大的 chunk,彻底避免信息被切断?

因为大块只降低某些边界风险,却会把多个主题塞进同一个向量,降低证据密度,并占用更多上下文。还要确认 Embedding 模型是否真正编码了整块内容。正确做法是先保护结构边界,再在评测集上验证长度,而不是无限增大。

overlap 越大,Recall@K 是否一定越高?

不一定。它可能让边界附近的证据更容易命中,也可能产生大量近似 chunk,挤占 Top K。即使 Recall@K 上升,也要同时看去重后的证据覆盖、MRR 或 nDCG、重复率和上下文占用。

如果最终答案变好了,能否证明参数选对了?

不能单凭最终答案判断。模型可能依靠自身知识答对,也可能碰巧从错误证据生成正确表述。需要先看标准证据是否被召回、排名在哪里、内容是否完整,再看基于证据的回答质量。

为什么要固定重排模型和 Top K?

因为它们会改变候选进入最终上下文的方式。切分参数、候选数量和重排同时改变,实验就失去归因能力。先控制其他变量找到可解释结论,再做组合实验。

线上出现某类问题,应该先调 chunk_size 还是 overlap?

先定位错误发生在哪一层。正确内容在解析结果里不存在,是解析问题;自然结构被错误拆开,是边界策略问题;只在相邻边界缺少少量上下文,才可能由 overlap 改善;正确块存在却没被召回,则要检查查询、Embedding、检索和排序。不能把所有问题都归结为两个参数。

怎样向面试官说明自己的参数,而不编造项目指标?

说清楚真实语料、测试集来源、对比配置、指标和 bad case。如果还没有完成实验,就明确说“这是候选方案,下一步会按这套方法验证”,不要把教学材料里的数字改成第一人称成果。

如果资料类型差异很大,我还会先问是否真的需要全库共用一组参数。规章正文、表格、FAQ 和代码的自然边界不同,可以按文档类型选择切分器,但每条 Chunk 必须保留统一的来源与版本字段。分类型策略增加了维护成本,因此要用分层评测证明它确实修复了共用参数无法覆盖的错误,而不是为了显得复杂。

另外,评测记录必须保存切分器版本与字符、Token 的计量口径。否则同一个 chunk_size 在模型或分词器升级后可能对应不同的实际长度,旧实验也就无法直接复现。

Chunk 参数没有标准答案,实验和版本才是依据

我不会直接给 chunk_sizeoverlap 一个通用数字。chunk_size 决定检索单元的信息完整性和主题纯度,overlap 用来保护相邻块的边界上下文,但会增加重复索引、重复召回和上下文成本。我的做法是先用当前文档构建带原文证据位置的评测集,并按普通事实、条件例外、列表、表格和跨段问题分层。然后固定解析、Embedding、召回、Top K 和重排,只改变切分配置,对比 Recall@K、MRR 或 nDCG、证据完整性、重复率、索引规模和延迟。最终选择满足高风险样本底线、效果与成本都可接受的配置,而不是只拿最高召回率。上线时新旧索引分版本,先离线回归和受控对照,再切换,并保留旧索引回退。这样参数是由证据和约束共同决定的,也能持续复现和迭代。

INTERVIEW FOLLOW-UPS

面试官会怎样继续追问?

这些追问会继续检查方案取舍、验证方法和项目事实。
  1. Q01

    为什么不同实现会出现不同的 Chunk 起始值?

  2. Q02

    Overlap 过大为什么会制造重复召回?

  3. Q03

    不同文档类型是否应该使用同一组参数?

  4. Q04

    你最终用哪些指标决定上线参数?

PROJECT CHECK

换成你的项目,这几件事能否说清楚?

  • 列出至少三组候选参数
  • 固定评测问题与正确证据
  • 记录召回、重复和 Token 成本
  • 保留参数变化对应的 bad case
用自己的项目经历练一次