先选择一种表示
BGE-M3支持稠密、稀疏和多向量表示,本文只提供1024维稠密向量API,避免把三套输出混进一个数据库字段。它适合中文知识库的最小检索链路,但不保证业务准确率。GPU环境使用匹配的PyTorch构建,CPU可以用相同结构试验但耗时不同;首次测试建议控制输入长度和批量。
本服务仅绑定127.0.0.1,没有公网鉴权和租户隔离,不应直接暴露到互联网。生产接入需由网关增加身份、限流与输入审核。业务文档中的个人信息应先脱敏。
安装与离线模型目录
python3 -m venv .bge-env
source .bge-env/bin/activate
# PyTorch按机器驱动选择官方构建,确认安装后再继续
pip install FlagEmbedding fastapi uvicorn requests
pip install 'huggingface_hub[cli]'
mkdir -p /srv/models/bge-m3
hf download BAAI/bge-m3 --local-dir /srv/models/bge-m3
pip freeze > requirements.lock.txtrequirements.lock.txt保存本次成功组合。正式环境应固定所有版本、模型revision及镜像摘要。不要在已运行的服务里使用pip install -U盲目更新,Tokenizer变化可能使新旧向量不再可比。
创建受限API
将以下内容保存为embed_api.py,使用一个进程承载一份模型。多个uvicorn worker通常会各自加载模型,不会自动共享显存。
import threading
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from FlagEmbedding import BGEM3FlagModel
lock = threading.Lock()
model = None
@asynccontextmanager
async def lifespan(app):
global model
model = BGEM3FlagModel('/srv/models/bge-m3', use_fp16=False)
yield
app = FastAPI(lifespan=lifespan)
class Input(BaseModel):
texts: list[str] = Field(min_length=1, max_length=16)
@app.post('/embed')
def embed(x: Input):
if any(not t.strip() or len(t)>6000 for t in x.texts):
raise HTTPException(422,'需要非空文本,单条不超过6000字符')
with lock:
vectors = model.encode(x.texts, batch_size=4,
max_length=512)['dense_vecs']
return {'model':'bge-m3','dimensions':1024,
'vectors':vectors.tolist()}uvicorn embed_api:app --host 127.0.0.1 --port 8020 --workers 1use_fp16=False作为兼顾CPU的最小例子;GPU确认可用后可单独测试半精度,不假设精度和吞吐必然等价。max_length按token截断,6000字符只是请求保护,两者不是同一个限制。长文应先分块并保留页码,不能整篇截断后仍声称全文检索。
验证维度与基本相关性
import requests, numpy as np
texts = ['如何估算大模型显存?','模型权重与KV缓存需要预留显存。',
'液冷机房需要设计供回水温度。']
r = requests.post('http://127.0.0.1:8020/embed',
json={'texts':texts}, timeout=120)
r.raise_for_status()
v = np.asarray(r.json()['vectors'],dtype=np.float32)
assert v.shape == (3,1024)
assert np.isfinite(v).all()
v = v / np.maximum(np.linalg.norm(v,axis=1,keepdims=True),1e-12)
print('相关段落:',float(v[0] @ v[1]))
print('其他段落:',float(v[0] @ v[2]))这个三条测试只能检查链路,不能证明检索质量。正式验收准备至少100个真实问题,标注相关段落ID,用Recall@10检查候选召回,再接重排模型评估前几条结果。相关性分数不是百分比概率,不能统一用0.8作为所有业务的正确阈值。
索引版本与故障处理
数据库同时保存模型ID、revision、分块策略、文本哈希与页码。模型或Tokenizer更换后,新建索引重算,不在旧索引中混入新向量。用双索引切换保留回滚路径,确认新索引质量后再清理旧版本。
故障为维度不一致时,检查数据库字段和返回类型;所有相似度很接近时,检查文本是否被错误截断或清洗为空;OOM先降低batch_size与max_length。接口积压时先观察队列,不通过增加worker复制模型解决。停止服务保留模型与锁定清单,重启后先跑三条链路测试。依据:FlagEmbedding官方项目、BGE-M3模型卡。
请求字符数与实际token上限
中文字符、英文单词、数字串和代码经过分词后长度不同,六千字符限制不能保证不超过五百一十二token。服务端用模型Tokenizer检查真实长度,或者在离线分块时保存token数,超长文本应明确分块或拒绝。静默截断会让后半段条件永远无法被检索,特别是长表格、接口文档和错误日志。
主机内存、磁盘和网络也需要预检。模型下载保留完整配置与词表,提供只读目录,日志中记录revision但不保存敏感文档正文。CPU试验与GPU部署分别建立延迟基线,单进程锁只提供最小串行保护,不是成熟批调度器。输入批量十六条与内部编码批量四条是两个上限,应在界面和接口说明中分别写清楚。
服务健康与索引一致性
进程存活不等于模型加载成功,健康检查应区分启动中、可接收请求和失败。预热使用固定非敏感文本,检查输出维度、有限数值和基本相关性。模型文件被意外替换时停止写入索引,不能继续生成不同版本向量。索引中的revision应与服务真实加载版本对应,不让客户端随意填写一个名字充当证据。
文档、查询使用相同稠密输出路径,不能一边稀疏表示、一边稠密向量。归一化策略与数据库距离类型固定,迁移时抽查一批向量范数和查询结果。向量生成成功但文本清洗错误,同样会降低召回;先对照原段落,而不是反复更换Embedding模型。
批量导入与在线查询隔离
历史文档导入需要任务队列、进度和失败重试,在线查询则更关注尾延迟。共用一张GPU时限制导入批量和活跃任务,把长文档安排到低优先级队列,避免持续阻塞用户。每个导入批次记录文本哈希、输出数量和成功状态,写库失败可以重试写入,不必重新编码所有资料。
建立一百个中文业务问题,包含型号、单位、否定条件与无答案,观察不同分块策略的召回。检索分数只是候选排序,用户最终看到的答案仍需要原文证据。发现异常先判断来源解析、截断、索引版本或服务故障,逐层检查。模型升级使用双索引切换,确认数量、权限和质量后再处理旧版本,不能以删除旧库作为升级的第一步。
嵌入输出也要检查数量,不能假定接口返回与输入永远一一对应。保存每个文本的稳定ID,写库前比较数量、维度与有限数值,任何一项异常停止该批提交。请求取消或进程重启时,导入任务从最后一个完整批次继续,不按界面进度估计哪些向量已经入库。为低频更新建立独立队列,确保查询服务保留资源。
来源:网昱算力学院 · 技术编辑。第三方内容版权归原作者或发布机构所有;本站仅在许可证或明确授权允许时提供本地原文。
- 原文语言
- ZH
- 原文更新时间
- 未提供
- 许可证
- 原创工程内容
