ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Docker和vLLM部署BGE-M3文本嵌入模型的完整指南

用Docker和vLLM部署BGE-M3文本嵌入模型的完整指南 简介面向零基础开发者与研究人员这份PDF以实战方式讲解如何基于Docker与vLLM在本地部署BGE-M3文本嵌入模型。BGE-M3由北京智源研究院推出支持稠密检索、稀疏检索和多向量检索适合跨语言语义匹配与信息检索资源从Docker安装配置讲起逐步覆盖vLLM镜像部署、ModelScope模型下载、共享内存设置及文本嵌入测试并给出可直接运行的Python调用示例可帮助读者绕开依赖冲突与网络障碍快速搭建本地NLP向量服务。资源为1个PDF文件压缩包大小1.35MB篇幅精炼但步骤完整兼顾原理说明与实操命令并针对国内环境做了细节调整。已有644人学习下载。对希望实现隐私保护、定制化与成本可控的本地大模型落地场景这份资料提供了从零到可运行的清晰路径。1. 用 Docker 和 vLLM 跑一个文本嵌入模型没那么玄乎先放一个反直觉的结论vLLM 并不是只能跑生成式大模型像 BGE-M3 这种文本嵌入模型embedding model一样能通过 vLLM 起一个 OpenAI 兼容的 HTTP 服务。这意味着你只要会配 Docker、会写几行docker run就能把北京智源人工智能研究院推出的 BGE-M3 在本地跑起来并且让 LangChain、LlamaIndex 这类框架把它当成text-embedding-3-large的平替来用接口都不用改。这篇笔记要解决的是三件事第一把 Docker、vLLM、BGE-M3 这三者的关系讲透避免你把“推理引擎”和“模型”混在一起第二给出一份能直接照着跑的部署命令包括国内网络环境下从 ModelScope 拉模型、配置 GPU 运行时、处理共享内存这些细节第三把你大概率会踩的坑提前摆出来从模型不加载到向量检索返回空结果每条都给排查路径。这套方案适合两类人一类是搞 RAG 应用的开发者想本地起一个 embedding 服务又不愿意碰sentence-transformers那套依赖另一类是研究人员想在本地验证 BGE-M3 的稠密检索、稀疏检索和多向量能力又不想被 HuggingFace 的下载速度卡住。文章里所有命令我都在 Ubuntu 22.04 Docker 24 NVIDIA GeForce RTX 309024GB 显存上验证过你机器配置低一点也没关系最后会讲显存不够时的替代方案。2. Docker 环境准备不是装完就完事镜像源和 GPU 运行时才是关键2.1 为什么必须用 Docker 而不是裸机装BGE-M3 本身是 PyTorch 模型依赖transformers、torch、sentencepiece这些库版本稍微错位就报错。最常见的是transformers版本太老加载模型时报Some weights of the model checkpoint were not used或者干脆KeyError。vLLM 对torch、cuDNN、CUDA版本更敏感裸机环境一旦之前装过别的深度学习框架版本冲突能把半天时间磨掉。用 Docker 的理由就跟租房一样环境是房东准备好的你只管拎包入住。还有一个实际原因vLLM 官方镜像vllm/vllm-openai把 CUDA 运行时、PyTorch、vLLM 本体都打包好了并且针对 NVIDIA GPU 做了编译优化。你自己pip install vllm当然也行但大概率装到的是预编译 wheel性能上不如官方镜像里针对特定 GPU 架构编译的版本。2.2 安装 Docker 并启动服务如果你是在 Ubuntu 服务器上操作第一步是装 Docker。官方给了安装脚本但我建议你先看一眼系统是否满足要求x86_64 或 arm64 架构、内核版本不低于 3.10。执行下面的命令curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # 启动 Docker 服务 sudo systemctl start docker sudo systemctl enable docker # 验证安装 sudo docker run hello-world这段命令的逻辑是先下载官方安装脚本再用sh执行。脚本会自动检测系统版本配置 apt 源并安装 Docker Engine 和 CLI。systemctl enable docker是设置开机自启服务器重启后不用手动拉服务。最后用hello-world镜像验证 Docker 是否正常工作。注意一点get.docker.com脚本在部分国内服务器上可能拉取不到 Docker 官方 apt 源如果遇到超时可以手动改用清华或阿里云的 Docker CE 镜像源安装这部分属于环境问题不影响后续部署逻辑。2.3 配置国内镜像源和 NVIDIA Container ToolkitDocker 装好之后首先需要处理两个问题拉镜像超时和 GPU 不可用。拉镜像超时是因为默认从 Docker Hub 拉取国内网络不稳定。改法是在/etc/docker/daemon.json里配置registry-mirrors。这里我建议不要把加速器配太多配置两三个稳定的即可配十个反而可能因为某个镜像源超时拖慢整个拉取流程sudo vim /etc/docker/daemon.json配置文件内容如下{ registry-mirrors: [ https://docker.m.daocloud.io/, https://docker.mirrors.ustc.edu.cn, https://docker.nju.edu.cn ], runtimes: { nvidia: { args: [], path: nvidia-container-runtime } } }改完配置后重启 Docker 生效sudo systemctl daemon-reload sudo systemctl restart docker这里runtimes节点是关键它告诉 Docker 有一个名为nvidia的运行时底层可执行文件是nvidia-container-runtime。光装 Docker 不会自动获得这个运行时你还需要单独安装 NVIDIA Container Toolkit安装方式参考 NVIDIA 官方仓库即可装完重启 Docker 服务。没有这一步后面docker run --runtime nvidia会报unknown runtime: nvidia。镜像源的选择上docker.m.daocloud.io是我目前用下来相对稳定的docker.mirrors.ustc.edu.cn偶尔会有访问限制。如果拉 vLLM 镜像时仍然超时多试几个组合这个属于网络玄学看运气。2.4 确认 GPU 对 Docker 可见配置完运行时之后验证一下 GPU 是否能真的被容器使用。跑一个带nvidia-smi的基础测试sudo docker run --rm --runtime nvidia --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi如果输出里能看到显卡型号和显存大小说明 GPU 透传链路是通的。常见的问题有两种一是nvidia-container-runtime没装全报nvidia-smi: command not found二是 Docker 版本太低不支持--gpus参数需要升级 Docker Engine 到 23.0 以上。这一步如果失败不要急着往下走后面所有 vLLM 的部署全都基于 GPU 透传这个环节出了问题浪费的时间会成倍增加。3. 用 vLLM 部署 BGE-M3修改官方示例脚本的三处关键差异3.1 vLLM 官方镜像能做什么vLLM 官方镜像vllm/vllm-openai可以启动一个 OpenAI 兼容的 HTTP 服务支持chat/completions也支持embeddings。很多人的误区是以为 vLLM 只能跑 ChatGLM、Qwen 这类生成模型实际上从 v0.4.0 左右开始vLLM 就支持了 embedding 模型的部署BGE-M3 就是官方支持列表里的常客。镜像标签后面的参数直接传给 vLLM 引擎。你可以把它理解为镜像提供运行时环境参数决定这个环境里跑什么模型、怎么跑。这个理解非常重要因为后面所有部署差异都体现在参数上。3.2 官方示例脚本为什么不能直接用vLLM 官方部署脚本是这样的docker run --runtime nvidia --gpus all \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env HUGGING_FACE_HUB_TOKENsecret \ -p 8000:8000 \ --ipchost \ vllm/vllm-openai:latest \ --model mistralai/Mistral-7B-v0.1这条命令的逻辑是把宿主机的~/.cache/huggingface挂载到容器内这样模型下载后可以缓存到宿主机磁盘下次重启容器不用重新下载。HUGGING_FACE_HUB_TOKEN是访问受限模型用的BGE-M3 是公开模型不需要这个环境变量。--ipchost是让容器共享宿主机内存空间vLLM 底层用 PyTorch张量并行时要用共享内存在进程之间交换数据不设置的话可能出现内存错误。但它有一个实际问题模型默认从 HuggingFace 下载国内网络大概率卡住或者超时。所以需要两个改动一是模型源换成 ModelScope二是显存利用率显式指定。3.3 改写成 ModelScope 源的可用脚本下面是能直接跑的版本注意和官方脚本对比的差异点docker run --name bge-m3 -d --runtime nvidia --gpus all \ -v ~/.cache/modelscope:/root/.cache/modelscope \ --env VLLM_USE_MODELSCOPETrue \ -p 8000:8000 \ --ipchost \ vllm/vllm-openai:latest \ --model BAAI/bge-m3 \ --gpu_memory_utilization 0.9 \ --max-model-len 8192参数说明来拆一下--name bge-m3给容器起名字方便后续docker logs bge-m3查看日志。-d是后台运行模式不加的话终端关闭容器就停了。-v ~/.cache/modelscope:/root/.cache/modelscopeModelScope 下载的模型默认存到/root/.cache/modelscope挂载到宿主机后下次容器重建不用重新下载好几个 GB 的模型文件。--env VLLM_USE_MODELSCOPETrue这个环境变量是 vLLM 项目为国内用户适配的开关设置为True后vLLM 会优先从 ModelScope 解析模型 ID而不是 HuggingFace。--model BAAI/bge-m3在 ModelScope 上模型 ID 就是BAAI/bge-m3跟 HuggingFace 保持了一致这也是这个模型做得比较友好的地方。--gpu_memory_utilization 0.9告诉 vLLM 最多使用 90% 的显存。这个值不能设成 1.0PyTorch 的 CUDA context 本身就要占几百 MB设满会直接 OOM。--max-model-len 8192限制输入最大 token 数。BGE-M3 官方训练时最大长度是 8192但如果你显存只有 16GB建议改成 4096不然长文本场景会显存不足。整个脚本的执行逻辑是先拉vllm/vllm-openai:latest镜像然后创建并启动一个名为bge-m3的容器容器启动后 vLLM 引擎读取--model参数通过 ModelScope 下载模型到挂载目录最后在 8000 端口启动 OpenAI 兼容的 embedding 服务。3.4 不用 ModelScope 的备选方案如果你所在网络访问 HuggingFace 比较流畅也可以不改环境变量只配置国内镜像站docker run --name bge-m3 -d --runtime nvidia --gpus all \ -v ~/.cache/huggingface:/root/.cache/huggingface \ -e HF_ENDPOINThttps://hf-mirror.com \ -p 8000:8000 \ --ipchost \ vllm/vllm-openai:latest \ --model BAAI/bge-m3HF_ENDPOINT是 HuggingFace 官方支持的镜像站环境变量hf-mirror.com在国内算是比较稳定的镜像。两个方案哪个成功率高取决于你服务器所在网络对哪些域名解析更友好。我自己的习惯是优先用 ModelScope因为模型文件的下载稳定性更好而且 ModelScope 没有强制走 S3 的机制断点续传效率高不少。3.5 验证服务是否启动成功服务启动后等待日志输出模型加载完成docker logs -f bge-m3看到类似Application startup complete.或Uvicorn running on http://0.0.0.0:8000的日志说明服务已经就绪。然后用 curl 验证一下 embedding 接口curl -s http://localhost:8000/v1/embeddings \ -H Content-Type: application/json \ -d {model: BAAI/bge-m3, input: 混凝土强度等级}请求参数里model字段填的是你启动时指定的模型 IDinput是任意文本。返回的 JSON 里data[0].embedding就是一个维度为 1024 的向量列表看到这个输出说明模型真正部署成功了。到这一步模型已经以 OpenAI 兼容接口的方式跑起来了接下来要解决的是如何把它接进你的 NLP 管线里。4. 接入 LangChain 做向量检索客户端的三个关键配置4.1 为什么要用 OpenAIEmbeddings 客户端连 vLLMvLLM 提供的是 OpenAI 兼容接口所以客户端不需要任何特殊适配。LangChain 里最方便的就是langchain_openai包里的OpenAIEmbeddings类只需要把默认的api_base指向本地 vLLM 服务地址把api_key设置成任意占位值因为 vLLM 本地服务不校验密钥。这样做的好处是以后如果你想切回云上 OpenAI 的 embedding 服务只需改环境变量业务代码零改动。这是把模型服务化和业务代码解耦的典型做法。4.2 完整的文档向量化流程下面是一段完整的 PDF 加载、切块、向量化、检索代码from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_core.vectorstores import InMemoryVectorStore import os # 设置环境变量 os.environ[OPENAI_BASE_URL] http://localhost:8000/v1 os.environ[OPENAI_API_KEY] EMPTY # 1. 加载 PDF 文档 file_path ../langchain/data/0001.pdf loader PyPDFLoader(file_path) docs loader.load() print(f文档页数{len(docs)} 页) # 2. 文档切块 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, add_start_indexTrue ) all_splits text_splitter.split_documents(docs) print(f切块数量{len(all_splits)}) # 3. 初始化 embeddings 并存入向量库 embeddings OpenAIEmbeddings(modelBAAI/bge-m3) vector_store InMemoryVectorStore(embeddings) ids vector_store.add_documents(documentsall_splits) print(f已入库向量{len(ids)} 条) # 4. 相似度查询 results vector_store.similarity_search(混凝土, k3) for i, doc in enumerate(results): print(f第 {i1} 条结果来源{doc.metadata.get(source, 未知)}) print(doc.page_content[:200])这段代码分了四步第一步加载 PDFPyPDFLoader返回的是一个文档列表每个文档包含page_content和metadata。这里有个常见问题如果 PDF 是扫描件而非文字版loader.load()提取出来的是空字符串需要先用 OCR 预处理这不在本篇范围内但你需要知道边界在哪儿。第二步切块chunk_size500是每块最大字符数chunk_overlap100是相邻块重叠的字符数用于保留上下文边界。add_start_indexTrue会在 metadata 里记录该块在原始文档中的起始位置后续定位引用来源时非常有用。切块参数没有绝对最优解500/100 是混合搜索场景里比较保守的起点如果你的文档专业术语密集可以把 chunk_size 降到 300 试试。第三步是初始化 embeddings 和向量存储。OpenAIEmbeddings(modelBAAI/bge-m3)中的model参数对应 vLLM 服务启动时的--model值必须一致。InMemoryVectorStore是 LangChain 提供的简易向量库数据全存在内存里进程重启即丢失适合测试和小规模数据不要用在生产环境。add_documents返回的是入库文档的 ID 列表数量应该和all_splits一致。第四步查询similarity_search(混凝土, k3)会返回最相似的 3 个文档块。注意查询语句用的是“混凝土”而不是更长的专业术语这是有意为之——短查询更容易暴露 embedding 模型的语义理解能力边界。4.3 客户端的关键参数边界有几个参数需要强调边界第一OPENAI_BASE_URL的环境变量名是 LangChain 和 OpenAI SDK 共用的必须带/v1后缀。如果漏掉/v1会报 404。vLLM 的路由设计是/v1/embeddings不带/v1的客户端请求会直接落到根路径返回Not Found。第二模型 ID 不匹配时不会异步报错而是在调用 embeddings 时才返回类似model not found的错误。所以部署时用什么 ID客户端就填什么 ID建议固定成BAAI/bge-m3不要改。第三InMemoryVectorStore的similarity_search默认用余弦相似度还是内积取决于具体后端实现LangChain 的向量库封装了距离计算你直接调用即可。如果你要替换成 Milvus 或 Chroma 做持久化接口完全一致只要把InMemoryVectorStore换成对应的类即可。4.4 为什么 BGE-M3 检索效果会比关键词搜索好BGE-M3 支持稠密检索、稀疏检索、多向量检索三种模式体现在 embedding 向量上就是稠密向量负责语义相似度比如搜“水泥标号”能匹配到文档里的“混凝土强度”稀疏向量负责词面命中类似 BM25 的加权逻辑多向量检索则是把文本切成 token 级向量做细粒度匹配。vLLM 部署后默认输出的是稠密向量但 BGE-M3 模型本身支持输出三种向量。vLLM 目前对三种模式的支持程度不同BAAI/bge-m3在sentence-transformers里可以分别取不同向量但通过 vLLM 的 OpenAI 兼容接口时返回的是模型的默认 embedding 输出。这也意味着如果你要搭建混合检索稠密加稀疏可能需要同时部署一个sentence-transformers服务或者在业务代码里调用FlagEmbedding库直接生成稀疏向量。后者操作更直接下一章讲混合检索时会展开说。5. 部署避坑清单五个真实翻车记录5.1 vLLM 镜像拉取超时或缓慢现象docker pull vllm/vllm-openai:latest卡住不动或者进度条长时间不变化。原因默认从 Docker Hub 拉取镜像体积约 8-10GB国内连接不稳定。解决确认/etc/docker/daemon.json里registry-mirrors配置生效执行docker info查看 Registry Mirrors 列表。如果加速器仍然无效可以试试配置代理环境变量HTTP_PROXY/HTTPS_PROXY再拉取或者选择在访问海外网络顺畅的时段拉取。5.2 容器启动后模型长时间不加载现象docker logs显示日志停在一个空白页或者一直打印下载进度但不结束。原因vLLM 正在从 ModelScope 下载模型文件BGE-M3 体积约 2.2GB网络慢的话可能需要十几分钟。解决用docker exec -it bge-m3 ls -lh /root/.cache/modelscope/hub查看模型是否在下如果目录为空检查VLLM_USE_MODELSCOPE是否设成了True。如果确认下载失败把容器删掉重新docker run此时挂载目录里已有部分缓存断点续传会接着下。5.3 容器启动后进程崩溃报 CUDA out of memory现象日志末尾出现CUDA out of memory或RuntimeError: CUDA error: out of memory。原因--gpu_memory_utilization设得过高或者--max-model-len太大导致显存预留超量。解决显存 16GB 的卡建议设为--gpu_memory_utilization 0.85和--max-model-len 4096显存 24GB 的卡保持 0.9 和 8192。如果仍然 OOM用nvidia-smi查看是否有其他进程占用显存fuser -v /dev/nvidia*找到占用进程后清理。5.4 调用 embeddings 接口返回 404 错误现象curl 请求返回{detail: Not Found}。原因访问路径不对。vLLM 的路由是/v1/embeddings如果是/embeddings或/api/v1/embeddings都不行。另外检查是不是没写-H Content-Type: application/jsonvLLM 对缺失这个头部的请求会直接拒绝。解决严格使用POST http://localhost:8000/v1/embeddingsbody 里带model和input两个字段。5.5 Docker 宿主机重启后容器起不来现象服务器重启后docker ps -a显示容器存在但docker start bge-m3报错或者模型重新加载了十几分钟。原因vLLM 容器内的进程没有注册为开机自启重启后需要手动启动。另外如果显存里有残留的 CUDA context重新加载可能需要较长时间。解决给容器加--restart unless-stopped参数重新创建这条参数保证 Docker 服务启动时自动拉起容器。如果想保留已有容器直接docker update --restart unless-stopped bge-m3即可。6. 把部署推进一步混合检索、显存不足的兜底方案与验证技巧6.1 验证 BGE-M3 是否真的比通用 embedding 更适合中文长文档部署完成后不要急着接业务先做一次简单的效果验证。准备三份文本一份是建筑工程规范里“混凝土强度等级”的介绍一份是通用的计算机技术文档一份是新闻娱乐内容。然后用同一个 query 分别测试 BGE-M3 和 OpenAI 的text-embedding-3-small对比召回结果。BGE-M3 在中文长文档的召回率上通常有优势因为它是针对多语言预训练的中文语料占比高语义边界学习得更好。6.2 搭建稠密加稀疏的混合检索流程如果你做的是 RAG 应用只用稠密向量会漏掉纯关键词匹配的场景。BGE-M3 设计之初就支持三路检索但 vLLM 接口默认输出的只是稠密向量。我不推荐再起一个 vLLM 服务去折腾输出形式更常见的做法是直接用FlagEmbedding库单独算稀疏向量然后和 vLLM 的稠密向量一起喂给 Milvus 之类的向量库做混合检索。from FlagEmbedding import BGEM3FlagModel # 加载模型指定输出稠密和稀疏向量 model BGEM3FlagModel(BAAI/bge-m3, use_fp16True) sentences [混凝土强度等级是影响结构安全的关键因素, 混凝土, 钢筋] output model.encode(sentences) print(稠密向量维度, output[dense_vecs].shape) print(稀疏向量词命中, output[lexical_weights][0].keys())这段代码的意义在于稀疏向量本身就是 token 到权重的映射可以直接用于传统倒排索引或 Milvus 的稀疏向量检索。use_fp16True可以降低显存占用BGE-M3 模型在 fp16 下推理速度快一倍以上。如果你的机器跑不动这个模型下面提供一个兜底方案。6.3 显存不够时的第三方 API 兜底本地部署最尴尬的场景是显卡显存不够BGE-M3 是 100M 参数级别的模型实际显存占用并不夸张但 6GB 显存的卡跑起来也紧张。这种情况下还有一个比较现实的选择硅基流动SiliconFlow提供了 BGE-M3 的免费 API 服务接口也是 OpenAI 兼容格式。import os from langchain_openai import OpenAIEmbeddings os.environ[OPENAI_BASE_URL] https://api.siliconflow.cn/v1 os.environ[OPENAI_API_KEY] 你的密钥 embeddings OpenAIEmbeddings(modelBAAI/bge-m3)用第三方 API 的好处是本地零部署成本缺点是数据会出网不适合隐私敏感场景。我的建议是测试和原型阶段用 API 快速验证效果验证通过之后再决定是否投入时间本地化部署。6.4 一个值得养成的部署习惯这套流程我部署过很多次踩过上面写的所有坑后来形成了一套固定动作每次重新部署之前先检查nvidia-smi确认显存干净再docker ps -a看有没有同名容器残留然后用docker logs尾部持续观察直到看到Application startup complete最后才用 curl 打一次 embedding 接口确认服务状态再接客户端。这套流程每次走一遍也就两分钟但能把排查时间从半小时压缩到五分钟以内从那以后我每次部署强制走一遍这个流程再也没在环境问题上翻过车。希望这篇笔记能帮你把 BGE-M3 顺利跑起来无论是本地验证能力、集成到 NLP 管线还是转成混合检索方案都留出了足够的操作空间。本文还有配套的精品资源点击获取
返回列表