面试-RAG落地的一些难点(分词选型+chunksize+l2相似度+嵌入sentencetransformer)
1 背景
围绕 响应速度 、 健壮性 、准确度与灵活性 这三点展开。
1. 相关知识-RAG的优化:
https://fairy-study.blog.csdn.net/article/details/158778345?spm=1001.2014.3001.5502
2. RAG 基础知识(检索策略):
https://fairy-study.blog.csdn.net/article/details/158744941?spm=1001.2014.3001.5502
2 响应速度
响应速度 这一块主要依赖于 嵌入模型 的选型。大体分为开源 / 闭源两种,如果要用开源模型的话,Sentence-Transformer 无论是科研还是落地都是常用的选项。
2.1 Sentence-Transformer
Sentence-Transformer 的核心作用是将非结构化的文本(句子/段落)转化为稠密的、富含语义信息的向量,从而使得计算机可以“理解”并计算文本之间的语义关系。
2.1.1 特点
稠密检索:
不再依赖关键词匹配,而是根据语义(方向)搜索。
响应速度:
作为粗排阶段,从百万级文档中 快速 (毫秒级)召回 Top-K 个候选,然后再用更精细的 Cross-Encoder 模型进行重排序。
Bi-Encoder 架构:
先独立计算出所有句子的向量并缓存。之后计算相似度只需做简单的矩阵乘法(余弦相似度)。在最耗时间的向量化这块,时间复杂度为 0(n)。
查询时间复杂度:
在实际工程(如 FAISS, Milvus, Annoy)中,我们不会做 O(n) 的暴力扫描。我们会建立索引(如 HNSW, IVF-PQ)利用树结构或图结构快速剪枝,只搜索最可能的一部分区域。过程如下:
预计算(建图):在有了所有句子的“坐标”(向量),你可以把这 100 万个向量全部画在一张图上。而对于 Bi-Encoder 架构 ,一般会建立索引,即在这些点之间连线,如果向量 A 和 B 很近,就在它们之间建立一条“高速公路”。查询(寻路):当新来一个查询句子 Q,你先算出它的向量,然后放到图上,沿着“高速公路”快速跳跃,很快就能找到离它最近的点。特点:时间复杂度为 0(logn)
2.1.2 传统嵌入方法 Bert
相比原始 BERT (Cross-Encoder 模式)
痛点:
原始 BERT 计算两个句子相似度时,必须将 (Sentence A, Sentence B) 一起输入模型。如果要计算 1 万个句子两两之间的相似度,需要计算 10000×9999/2≈5000 万次推理。这在大规模场景下是不可行的。时间复杂度在嵌入的时候,为 0(n2)。
特点:
- 输入为两两配对的形式,导致时间复杂度高;
- 输出为一个标量分数(0-1),不生成句子 A 或句子 B 的独立向量。它利用 Transformer 的 Self-Attention 机制,让 A 中的词和 B 中的词互相“看”对方(Cross-Attention)。因此无法建立索引。
2.1.3 使用
Sentence-Transformer 在保留了 BERT 强大的语义理解能力的同时,通过架构创新解决了其计算效率低下的问题,使得在大规模数据集上进行实时的语义搜索和聚类成为可能。
本质上,类似于 vLLMs 通过 PagedAttention 加速,因为 KV-Cache 那块本身就是 0(n2) 的时间复杂度,因此可以进行优化。
另外 Sentence-Transformer 不需要自己从头训练,只需一行代码即可切换不同的基座模型和微调策略。它支持的模型种类极其丰富:
- 多语言支持:例如 paraphrase-multilingual-MiniLM-L12-v2,支持 50+ 种语言,可以将中文、英文、法文映射到同一向量空间。
- 不同大小的模型(权衡速度与精度):轻量级/高速:all-MiniLM-L6-v2(目前最流行,速度极快,精度不错,适合实时系统)。
- 中等:all-mpnet-base-v2(精度更高,速度适中)。大型/高精度:基于 RoBERTa-Large 或 DeBERTa 的模型。
- 特定领域微调:生物医学:biobert 变体。法律:legal-bert 变体。
- 代码搜索:codebert 或 graphcodebert 变体(用于搜索代码片段)。
- 多模态模型:最新的版本甚至支持图像 - 文本匹配(如 CLIP 的封装),不仅限于纯文本。
例子:
from sentence_transformers import SentenceTransformer
# 1. 直接加载一个现成的模型(自动下载)
model = SentenceTransformer('all-MiniLM-L6-v2')
# 2. 一句话生成向量
sentences = ["一只猫坐在垫子上", "A cat is sitting on the mat"]
embeddings = model.encode(sentences)
# 3. 直接计算相似度
similarity = model.similarity(embeddings[0], embeddings[1])
2.2 选哪一个模型
2.2.1 闭源模型
介绍:
调用 API,比如 OpenAI Embeddings 的 text-embedding-3-small、text-embedding-3-large,特点就是映射的向量维度很高(3072)维,适合精度要求较高的场景。
问题:
- 成本问题,成本难以控制,调用 api 要花钱的;
- 风险问题,api 容易泄露,必须通过环境变量的写法才更为安全(甚至还是不安全,因为环境变量也是能够通过调试工具去捕获的呀);
- 无法微调,无法适用于垂直领域;
- 并发量大的时候容易出现成本、系统崩溃等情况;
- 耗时高,API 调用涉及网络请求(尤其是海外 API,如 OpenAI),单次嵌入耗时可能达 100~500ms,高并发下延迟会放大;
2.2.2 开源模型
介绍:
Embedding 模型部署一般没有要求,CPU 也能很快进行处理,参数量一般在(几十~几百MB),非常适合垂直领域检索。
最常用的为 MiniLM-v12(384维)
特点:
- 响应速度很快,毫秒级别的响应速度,而且支持批处理;
- 支持微调,适合垂直领域;
- 成本低,且支持量化;
用法(基于 sentence-transformers):
{"sentence1": "合同违约的赔偿标准", "sentence2": "民法典第584条违约责任", "label": 1.0} # 高度相似
{"sentence1": "盗窃罪的量刑标准", "sentence2": "公司法股东权利", "label": 0.0} # 不相似
{"sentence1": "医疗事故的举证责任", "sentence2": "侵权责任法医疗损害责任", "label": 0.9} # 较相似
from sentence_transformers import SentenceTransformer, InputExample, losses
from sentence_transformers.evaluation import EmbeddingSimilarityEvaluator
from torch.utils.data import DataLoader
# 1. 加载基础模型
model = SentenceTransformer('BAAI/bge-base-zh-v1.5')
# 2. 加载训练数据(自定义Dataset)
train_examples = []
with open("law_data.jsonl", "r", encoding="utf-8") as f:
import json
for line in f:
data = json.loads(line.strip())
train_examples.append(
InputExample(
texts=[data["sentence1"], data["sentence2"]],
label=float(data["label"])
)
)
# 3. 配置训练参数
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=8)
# 损失函数:余弦相似度损失(适配相似性任务)
train_loss = losses.CosineSimilarityLoss(model=model)
# 评估器(验证微调效果)
evaluator = EmbeddingSimilarityEvaluator.from_input_examples(
train_examples[:100], # 取100条作为验证集
name="law-eval"
)
# 4. 开始微调(核心!)
model.fit(
train_objectives=[(train_dataloader, train_loss)],
evaluator=evaluator,
epochs=3, # 训练轮数(垂直领域3~5轮足够)
evaluation_steps=100, # 每100步评估一次
learning_rate=2e-5, # 学习率(小模型用2e-5,大模型用1e-5)
warmup_steps=100, # 预热步数
output_path="./bge-law-finetuned", # 微调后模型保存路径
show_progress_bar=True
)
# 5. 加载微调后的模型
fine_tuned_model = SentenceTransformer("./bge-law-finetuned")
# 测试微调效果
vec = fine_tuned_model.encode("合同违约的赔偿标准", normalize_embeddings=True)
print(f"微调后向量维度:{len(vec)}")

3 健壮性
3.1 API 健壮性问题
比如以以下代码为例,这是调用 API 时候实现的重试机制:
def _with_retry(self, func, *args, **kwargs):
"""
通用重试装饰器:指数退避 + 错误分类处理
"""
delay = self.retry_delay
for attempt in range(1, self.max_retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
# 🔧 兼容 openai 2.x:安全获取 Retry-After
response = getattr(e, 'response', None)
if isinstance(response, dict):
headers = response.get('headers', {})
retry_after = headers.get('Retry-After') if isinstance(headers, dict) else None
else:
headers = getattr(response, 'headers', {})
retry_after = getattr(headers, 'get', lambda x: None)('Retry-After')
wait = float(retry_after) if retry_after else delay
logger.warning(f"⚠️ 请求失败 (尝试 {attempt}/{self.max_retries}),等待 {wait:.1f}s: {type(e).__name__}")
time.sleep(wait)
delay *= self.retry_backoff
logger.error(f"❌ 重试 {self.max_retries} 次仍失败")
return None
闭源 API 的三大痛点:
- 版本兼容问题: OpenAI SDK 的版本迭代(如 1.x → 2.x)会导致
异常对象的结构变化(1.x 的 e.response 是字典,2.x 是对象),除此以外还有头信息获取方式变化。这会导致代码要不断迭代,维护成本很高; - 服务不可靠问题: 服务降级 / 5xx 错误:需要多次重试才能成功;这些是闭源 API 的 “天然问题”,和版本无关,但必须靠重试逻辑兜底。
3.2 开源模型健壮性问题
开源模型部署在本地,确实不需要处理 API 相关的重试 / 版本兼容(因为没有 网络调用 、没有 第三方 SDK 版本 绑定),解决了 时间问题、成本问题、维护问题:
- ❌
无需处理 Retry-After/ 限流:本地模型的并发仅受自身算力限制,没有第三方 QPS 限制; - ❌
无需处理网络超时:推理在本地完成,无网络波动; - ❌
无需兼容第三方 SDK 版本:直接调用模型库(如 transformers/sentence-transformers),版本由自己控制; - ❌
无需 API 密钥 / 鉴权:本地运行,无密钥泄露 / 计费风险;
4 OpenAI 不同模型对应的分词标准是什么?
Question: 在 RAG 中,不同 OpenAI 模型要使用哪种 tiktoken 编码?
A: OpenAI 每个模型系列固定对应一种 BPE 分词规则,直接决定 token 计数和上下文长度。
- gpt-4o / gpt-4o-mini / gpt-4-turbo
→o200k_base(200k 词表,新版多语言最优) - gpt-4 / gpt-3.5-turbo
→cl100k_base(100k 词表,最常用) - 旧版 davinci / codex 等
→ p50k_base / r50k_base
在 RAG 里的意义:
- 必须用对应编码计算 chunk token,才能准确控制上下文窗口。
- 本地计数必须和 API 计费一致,否则会超长度、报错、成本不准。
5. tiktoken 闭源分词在 RAG 中是什么角色?
Q: tiktoken 是开源还是闭源?在 RAG 里为什么必须用它?
A: tiktoken 是 OpenAI 闭源的、高度优化的 BPE 分词器。
- 核心是:词表和合并规则是 OpenAI 预训练好的黑盒
- 只提供 encode / decode / count 接口,不能重新训练
- 用 Rust 实现,极快,适合大规模文档分块
RAG 中的作用:
- 做 chunk 切分的精确 token 计数
- 保证 chunk 大小不超模型上限
- 唯一能和 OpenAI API 计费完全对齐的工具
一句话:用 OpenAI 模型做 RAG,tiktoken 就是标准尺子。
6 开源分词在 RAG 中怎么用?和 tiktoken 区别?
Q: 本地开源模型(Llama/Qwen/ChatGLM)做 RAG,用什么分词?
A: 用 HuggingFace AutoTokenizer / SentencePiece 等开源分词工具。
特点:
- 每个模型自带专属词表,必须和模型训练时一致
- 完全开源、可查看、可修改、可训练
- 支持 BPE / WordPiece / Unigram 等
RAG 中的用法:
- 开源 LLM + 开源 Embedding → 必须用模型自带 tokenizer
- 切 chunk、算长度、构建 prompt 都要统一分词器
- 否则会出现:本地算 512 token,模型实际 600+,直接超窗
对比 tiktoken:
- tiktoken:闭源、固定、只适配 OpenAI
- 开源 tokenizer:可定制、随模型走、适合本地 RAG
7 什么是感知 Chunk?现在 RAG 主流用静态还是感知?
Q: 什么叫语义感知 chunk(感知chunk)?和固定 token chunk 有什么区别?
A:
- 静态固定 chunk:严格按 512/1024 token 切,不管语义,容易把一句话切断。
- 语义感知 chunk(感知chunk):
先按句号、逗号、换行、段落、标题切分,再把句子合并到接近目标 token 数。
→ 保证语义完整,不硬切句子。
但是 Overlap 的设置已经弥补了
静态Chunk和语义感知Chunk之间的 gap。
Q:感知 chunk 是什么时候提出的?
A:
大约在 2022–2023 年 RAG 工业化落地阶段被广泛提出。
早期 RAG 简单粗暴用固定分块,后来发现语义断裂会大幅降低检索准确率。
Q:现在工业界主流用哪种?
A:
90% 真实 RAG 系统都用「语义感知分块」,不再用纯静态硬切。
常见策略:
- 递归分块(先段落 → 再句子 → 再凑到目标 token)
- 按标点/章节/结构分块
- 保持标题和内容在同一块
- chunk 大小在目标附近浮动,不强求完全相等
对你的实验意义:
你看到厂商日志里 token 数不固定,大概率就是用了语义感知分块,这是正常、合理、更高级的做法。
8 准确度:RAG 中为什么有的业务用 L2 不用余弦相似度?
背景:
大多数厂商 RAG 的实现一般都是用余弦相似度,因为能够将所有向量投影到同一空间上(即同一球面),解决了长度大小对语义方向造成的影响。但落地发现,L2 有时候更适合真实场景,比如,我们检索热门商品,发现商品的介绍都是很长的,而不是短短几个字,一般长的介绍文档更容易被推荐。
Q:向量检索里,余弦相似度和 L2 距离该怎么选?
A:两者关注的重点完全不同,直接影响 RAG 召回结果。
余弦相似度(最常用)
- 只看向量方向,不看长度
- 文本长短不影响分数
- 适合:知识库、问答、纯语义匹配
→ 只要意思对就行,不管内容详不详细
L2 距离(欧氏距离)
- 同时看方向 + 向量长度(模长)
- 文本越长、信息越丰富 → 向量模长通常越大
- L2 会天然偏向长文本、内容详细的文本
Q:为什么商品/评价类 RAG 喜欢用 L2?
A:因为这类业务里:
- 热度高、质量高的评价/商品 → 内容更长、描述更详细
- 简单一句“不错”的短评价 → 模长小
- 用 L2 能自动把更详细、更有价值的内容排前面
RAG 选型总结:
- 做 问答/知识库 → 余弦相似度
- 做 商品/评论/内容质量排序 → L2 更贴合业务
- 你观察到的“用 L2 更适合详细内容”是业界标准结论
整体极简总结
- OpenAI 模型对应固定分词,tiktoken 是官方闭源实现,RAG 中用于精准分块。
- 开源模型必须用自带 tokenizer,保证计数一致。
- 语义感知 chunk 是现在 RAG 主流,按语义边界切分,不再硬切固定 token。
- 余弦相似度重语义,L2 重内容长度/质量,商品/评价类 RAG 常用 L2。
9 准确度:ChunkSize 大小
https://fairy-study.blog.csdn.net/article/details/158778345?spm=1001.2014.3001.5502
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)