QAnything 阅读优化策略03——查询转换

查询转换(Query Transformation)

查询转换是将用户查询从文本形式转化为检索引擎能处理的格式(如 embedding 向量),同时做必要的清洗和保护。这是让"人类语言"适配"机器引擎"的关键桥梁。


一、是什么

查询转换是将用户查询从文本形式转化为检索引擎能处理的格式(如 embedding 向量),同时做必要的清洗和保护。

核心输入 :改写后的检索查询

核心输出:归一化的 embedding 向量 + 保护后的查询文本


二、为什么重要

检索引擎不认识"人类语言",它只认识向量。

转换质量直接影响检索质量:

  • 图片标记 ![figure] 会干扰 embedding 模型
  • 过长的 query 会导致 Rerank 模型截断
  • 不同组件需要不同的 Token 计数精度

与传统方案的对比

优化点 传统方案 传统方案的问题 QAnything 方案 优势
向量化 平均池化(mean pooling) 所有 token 平均,重要信息被稀释 CLS token 提取 [:,0] 全局语义更聚焦
归一化 不归一化 余弦相似度计算需要除法,长向量主导排序 L2 归一化 余弦相似度 = 点积,加速计算
图片处理 原样发送 ![figure](xxx.jpg) 干扰语义 发送前过滤图片标记 检索向量更纯净
Token 计数 统一用一个 tokenizer tiktoken 计 100 token,实际可能 120 三级 tokenizer + 安全余量 各组件精度匹配
推理框架 PyTorch 直接推理 推理慢,部署体积大,无算子融合 ONNX Runtime + 全图优化 推理速度提升 2-3x
分数输出 原始 logits(无界) 阈值难设,不可解释 Sigmoid + 线性拉伸 0~1 可解释,区分度高

三、怎么做

3.1 Embedding 向量化 + L2 归一化

问题:文本需要转为向量才能在 Milvus 中检索。

方案:提取 CLS token 作为全局语义表示,然后 L2 归一化。

python 复制代码
# Embedding ONNX 模型推理
embedding = outputs_onnx[0][:, 0]  # 提取 CLS token(全局语义)

# L2 归一化:使所有向量落到单位球面
norm_arr = np.linalg.norm(embedding, axis=1, keepdims=True)
embeddings_normalized = embedding / norm_arr

归一化的价值

  • 归一化后余弦相似度 = 点积,加速后续相似度计算
  • 所有向量长度一致,避免"长向量主导排序"的问题

为什么用 CLS token

  • BERT 类模型的 [:, 0] token 经过自注意力聚合,代表整句话的全局语义
  • 比平均池化更能捕捉句子的核心含义

3.2 图片引用过滤

问题 :查询中的 ![figure](xxx.jpg) 会干扰 Embedding 模型的语义理解。

方案:发送前过滤掉图片和公式标记。

python 复制代码
# Embedding 客户端:发送前过滤
def _process_query(query):
    return '\n'.join([line for line in query.split('\n')
                      if not line.strip().startswith('![figure]')
                      and not line.strip().startswith('![equation]')])

为什么不在索引阶段过滤

  • 索引阶段的图片引用是文档内容的一部分
  • 但查询阶段用户不会主动写图片标记,这些通常是系统拼接进来的

3.3 三级 Tokenizer 精度体系

问题:不同组件对 token 计数的精度要求不同,用错 tokenizer 会导致切分不准或预算超支。

Tokenizer 使用场景 为什么用它
tiktoken (GPT) LLM Token 预算计算 直接影响 API 成本,需要精确
embedding_tokenizer 文件切分判断 必须与 Embedding 模型的 max_length 对齐
rerank_tokenizer Rerank 资格判断 必须与 Rerank 模型的 max_length 对齐
python 复制代码
# LLM Token 估算:额外加安全余量
total_tokens *= 1.1   # tiktoken 精度较高,加 10%
total_tokens *= 1.2   # 如果用 cl100k_base 兜底(中文模型偏差大),加 20%

安全余量的原因

  • tiktoken 是 GPT 专用 tokenizer,与国产 LLM(如 Qwen)有偏差
  • cl100k_base 是通用 tokenizer,中文场景偏差更大
  • 加余量是为了防止实际 token 超过预算

3.4 Rerank 查询长度保护

问题:Rerank 模型 max_length = 512,如果 query 太长就没有空间放 doc。

方案:query > 300 token 时跳过 Rerank,给 doc 留出 ~200 token 空间。

python 复制代码
# 三个条件全部满足才执行 Rerank
if rerank and len(source_documents) > 1 and num_tokens_rerank(query) <= 300:
    source_documents = await self.rerank.arerank_documents(...)

为什么是 300 而不是 512

  • Rerank 输入 = query + doc,512 是总长度
  • 给 doc 留 200 token 空间,防止截断

3.5 ONNX Runtime 推理加速

问题:PyTorch 推理速度慢,部署体积大。

方案:使用 ONNX Runtime 进行模型推理。

python 复制代码
sess_options = SessionOptions()
sess_options.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL  # 全图优化
# 自动线程调度 + GPU/CPU 自适应
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']

优化项

  • 算子融合:将多个小算子合并为一个大算子
  • 常量折叠:编译期计算常量表达式
  • 自动线程调度:根据 CPU 核心数分配线程
  • 设备自适应:自动选择 GPU 或 CPU

3.6 Rerank Sigmoid 分数校准

问题:交叉编码器输出 logits(无界值),需要映射为 0~1 之间的可解释分数。

python 复制代码
def sigmoid(x):
    scores = 1 / (1 + np.exp(-x))                       # 标准 sigmoid → (0, 1)
    scores = np.clip(1.5 * (scores - 0.5) + 0.5, 0, 1) # 线性拉伸 → 增强区分度
    return scores

线性拉伸的作用

复制代码
标准 sigmoid 输出:
0.48  0.50  0.52  0.55  → 区分度低,难以设定阈值

拉伸后:
0.47  0.50  0.53  0.575 → 高分更高,低分更低,区分度增强

四、解决的问题汇总

手段 解决的问题
Embedding 归一化 余弦相似度计算效率
图片引用过滤 无效 token 干扰语义
三级 Tokenizer 不同组件精度需求不同
Rerank 长度保护 Rerank 模型输入截断
ONNX 加速 推理速度慢
Sigmoid 校准 分数无界不可解释
相关推荐
爱跳舞的烤冷面16 分钟前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号35 分钟前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
马拉AI1 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者2 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者3 小时前
【征程6】校准量化中HistogramObserver解析
算法
OuO-23 小时前
笔试强训 Day 34:ISBN 号码、kotori 和迷宫、矩阵最长递增路径
java·算法·矩阵
程序喵大人4 小时前
【C++进阶】STL算法与函数对象 - 04 find、count和any_of把查询写成意图
开发语言·c++·算法
豆沙沙包?4 小时前
c++中引用(P7-P11)
java·c++·算法
不会代码的小猴5 小时前
标准模板库(STL)
开发语言·c++·笔记·算法
ZhouDevin5 小时前
算法论文/高效微调4——DoRA:权重分解的低秩适配方法
算法