ARTICLE DETAIL

资讯详情

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

Docker 与 vLLM 本地部署 BGE-M3 文本嵌入模型实战

Docker 与 vLLM 本地部署 BGE-M3 文本嵌入模型实战 简介这份PDF面向零基础开发者与研究人员讲解如何用Docker与vLLM在本地部署BGE-M3文本嵌入模型。BGE-M3由北京智源人工智能研究院推出支持稠密、稀疏与多向量三种检索模式适用于跨语言语义匹配与信息检索。资源围绕容器化环境搭建、vLLM官方镜像使用、GPU与共享内存配置、模型下载与文本嵌入测试等环节展开帮助读者快速验证模型能力或将其集成到本地NLP pipeline中。压缩包共1个PDF文件约1.35MB内容涵盖Docker安装与镜像源配置、vLLM运行OpenAI兼容服务器、从modelscope拉取bge-m3的脚本调整以及基于LangChain的向量存储与相似度查询示例。目前已有644人学习适合希望掌握大模型本地部署、规避依赖冲突并兼顾隐私与成本控制的开发者参考。1. 零基础实战用 Docker 和 vLLM 本地部署 BGE-M3 文本嵌入模型你手里有一堆文档要做语义检索或者想给自己的 RAG 应用换一个中文效果更好的嵌入模型但一搜教程全是pip install然后一堆 CUDA 版本冲突。BGE-M3 是目前中文社区里综合表现很稳的多语言嵌入模型支持稠密、稀疏、多向量三种检索模式而 vLLM 从 0.5 版本之后对 embedding 模型的支持已经相当成熟。把这两个东西塞进 Docker 里跑最大的好处是环境隔离——你不需要动宿主机上已经装好的 torch也不用担心装 vLLM 的时候把原来的训练环境搞崩。这篇内容面向的是有基本 Linux 命令基础、想在自己机器或公司内网服务器上把 BGE-M3 跑起来的人从拉镜像到发第一个请求每一步都有可复制的命令和参数说明。2. 为什么是 vLLM 而不是 FastAPI 裸跑选型与显存账2.1 嵌入模型的服务化需求与 vLLM 的定位很多人第一次部署 BGE-M3 的做法是写一个 FastAPI 接口里面加载FlagEmbedding或者sentence-transformers然后uvicorn起服务。这个方案在单并发、低 QPS 的场景下没问题但一旦你的 RAG 应用有十几个并发请求同时打过来Python GIL 加上模型前向计算的串行化会让延迟飙升。vLLM 的核心价值在于它把连续批处理continuous batching和 PagedAttention 带到了嵌入模型上——虽然嵌入模型没有 KV Cache 的显存碎片问题但 vLLM 的调度器仍然能把多个请求动态合并成一个 batch 送进 GPU吞吐量比裸跑高出一个数量级。另一个容易被忽略的点是 vLLM 对 embedding 模型输出的标准化。BGE-M3 原生输出包含dense_vecs、sparse_vecs和colbert_vecs三部分不同版本的 FlagEmbedding 返回的字段名和形状有差异。vLLM 的 embedding 接口统一返回 OpenAI 兼容的embedding字段如果你的下游应用比如 Dify、FastGPT、LangChain已经适配了 OpenAI 的 embedding 接口切换过来几乎零改动。显存方面BGE-M3 的模型权重大约 2.2GBFP16vLLM 加载后会额外占用一部分显存做激活值和批处理缓冲区。在 8GB 显存的卡上比如 RTX 3070 或 T4设置--gpu-memory-utilization 0.7可以稳定跑起来留出空间给系统和其他进程。如果你用的是 16GB 以上的卡可以把这个值提到 0.85 到 0.9让 vLLM 尽可能多地缓存 batch。2.2 Docker 镜像选择与 vLLM 版本匹配vLLM 官方提供了vllm/vllm-openai镜像但这个镜像默认是给生成式模型用的里面已经包含了 vLLM 和 CUDA 运行时。对于 BGE-M3 这种 embedding 模型你不需要额外装 FlagEmbedding直接用 vLLM 内置的--task embedding参数就能加载。截至我写这篇内容时vLLM 0.6.x 系列对 BGE-M3 的支持已经比较稳定建议锁定vllm/vllm-openai:v0.6.3或更新的小版本不要用latest因为 vLLM 的 minor 版本之间 API 行为偶尔会有变化。拉镜像之前先确认宿主机的 NVIDIA 驱动和 Docker 的 GPU 支持。运行nvidia-smi能看到显卡信息运行docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi能正常输出就说明 Docker 的 GPU 直通没问题。如果这一步报could not select device driver说明你没装nvidia-container-toolkit需要先补上。# 确认驱动和 Docker GPU 支持 nvidia-smi docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi # 拉取 vLLM 镜像锁定版本避免 latest 带来的不确定性 docker pull vllm/vllm-openai:v0.6.3镜像大小在 8GB 到 10GB 之间取决于 CUDA 版本。国内网络环境下如果拉取慢可以配置 Docker 的 registry mirror但注意不要用已经失效的公开镜像源建议用公司内网或云厂商提供的加速地址。2.3 启动命令与关键参数逐项说明下面这条命令是我在 T4 显卡上跑通 BGE-M3 的最小可用配置docker run -d \ --name bge-m3-server \ --gpus all \ --shm-size 2g \ -p 8000:8000 \ -v /data/models/bge-m3:/models/bge-m3 \ vllm/vllm-openai:v0.6.3 \ --model /models/bge-m3 \ --task embedding \ --dtype float16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.7 \ --port 8000 \ --host 0.0.0.0--task embedding是核心告诉 vLLM 这是一个嵌入模型而不是生成模型vLLM 会据此调整输出层和调度策略。--max-model-len 8192对应 BGE-M3 的最大输入长度如果你实际处理的文本都很短比如 512 token 以内可以把这个值降到 2048 或 4096能省下不少显存给 batch 缓存。--gpu-memory-utilization 0.7控制 vLLM 预分配的显存比例设太高会导致 OOM设太低会限制并发 batch 的大小。--shm-size 2g是给容器内的共享内存vLLM 在多进程加载模型时会用到默认的 64MB 不够。模型文件需要提前下载到宿主机。如果你能访问 HuggingFace可以用huggingface-cli download BAAI/bge-m3 --local-dir /data/models/bge-m3。如果网络受限从 ModelScope 下载BAAI/bge-m3的镜像仓库文件结构是一样的。挂载进容器后vLLM 会从/models/bge-m3读取config.json、pytorch_model.bin和 tokenizer 相关文件。3. 从零跑通第一个嵌入请求验证与压测3.1 用 curl 和 Python 客户端发请求容器启动后先看日志确认模型加载完成docker logs -f bge-m3-server看到Application startup complete和Uvicorn running on http://0.0.0.0:8000就说明服务就绪了。然后用 curl 发一个最简单的请求curl http://localhost:8000/v1/embeddings \ -H Content-Type: application/json \ -d { model: /models/bge-m3, input: [什么是文本嵌入模型, BGE-M3 支持多语言检索] }返回的 JSON 里data数组的每个元素包含embedding字段是一个 1024 维的浮点数列表。注意model字段的值必须和启动命令里的--model参数一致vLLM 不会自动做别名映射。Python 客户端用 OpenAI SDK 就行不需要额外装 vLLM 的客户端库from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keynot-needed) resp client.embeddings.create( model/models/bge-m3, input[什么是文本嵌入模型, BGE-M3 支持多语言检索] ) for i, item in enumerate(resp.data): print(f文本 {i} 的向量维度: {len(item.embedding)}) print(f前 5 个值: {item.embedding[:5]})api_key随便填一个非空字符串即可vLLM 默认不校验鉴权。如果你需要对外暴露服务建议在前面加一层 Nginx 做 API Key 校验或者用 vLLM 的--api-key参数启动。3.2 并发压测与 batch 参数调优单发请求只能验证功能要评估生产可用性得做并发压测。用locust或者简单的asyncio脚本都行我一般用hey或wrk这类 HTTP 压测工具# 安装 hey如果没装 go install github.com/rakyll/heylatest # 发 1000 个请求并发 20 hey -n 1000 -c 20 -m POST \ -H Content-Type: application/json \ -d {model:/models/bge-m3,input:[测试文本嵌入的并发性能]} \ http://localhost:8000/v1/embeddings关注Requests/sec和P99 latency两个指标。在 T4 上--max-model-len 8192且输入长度在 128 token 左右时并发 20 的情况下 QPS 大概在 40 到 60 之间。如果你把--max-model-len降到 2048QPS 能翻倍因为 vLLM 可以缓存更多的 batch 槽位。如果压测时出现CUDA out of memory优先降--gpu-memory-utilization每次降 0.05直到稳定。如果 QPS 上不去但显存还有富余检查--max-num-seqs参数默认是 256适当调大可以让更多请求同时进入 batch。但注意嵌入模型的单条输出很小瓶颈通常在 GPU 计算而不是显存带宽所以--max-num-seqs调到 512 以上收益就不明显了。3.3 多向量与稀疏向量的获取方式BGE-M3 的卖点是三种检索模式但 vLLM 的 OpenAI 兼容接口默认只返回稠密向量。如果你需要稀疏向量用于关键词加权或多向量用于 ColBERT 式精排有两个做法一是用 FlagEmbedding 在客户端本地算把 vLLM 当作纯稠密编码器二是启动 vLLM 时加--task embedding之外再传--override-pooler-config之类的参数但截至 vLLM 0.6.3这个路径对 BGE-M3 的支持还不完整容易翻车。我的建议是如果你的检索链路只需要稠密向量vLLM 方案足够如果需要稀疏或多向量用 vLLM 跑稠密部分稀疏部分用rank_bm25或 SPLADE 单独处理多向量部分用 FlagEmbedding 在 CPU 上算ColBERT 的 late interaction 对延迟不敏感可以异步做。不要强行让 vLLM 输出三种向量社区里踩过这个坑的人不少血泪经验是等 vLLM 官方把 BGE-M3 的 pooler 适配完整再说。4. 避坑与排查部署 BGE-M3 时最容易翻车的 5 个点4.1 容器启动后端口不通或连接被拒现象docker ps显示容器在运行但curl localhost:8000返回Connection refused。原因vLLM 默认绑定127.0.0.1而不是0.0.0.0容器内的 127.0.0.1 和宿主机的 localhost 不是一回事。另外如果宿主机上已经有其他服务占了 8000 端口Docker 的端口映射会静默失败。解决启动命令里显式加--host 0.0.0.0。检查端口占用用ss -tlnp | grep 8000如果被占用就换一个宿主机端口比如-p 18000:8000然后请求localhost:18000。4.2 模型加载时报 “Unsupported model architecture”现象日志里出现ValueError: Unsupported model architecture: XLMRobertaModel或类似信息容器反复重启。原因vLLM 对 embedding 模型的支持依赖于--task embedding参数和模型 config 里的architectures字段。BGE-M3 的config.json里architectures是XLMRobertaModel某些 vLLM 版本需要显式指定--task embedding才会走 embedding 路径否则会尝试用生成模型的加载器去加载直接报错。解决确认启动命令里有--task embedding。如果加了还是报错检查 vLLM 版本0.5.x 早期版本对 XLM-RoBERTa 架构的 embedding 支持有 bug升级到 0.6.x 以上。4.3 显存够但依然 OOM现象nvidia-smi显示显存只用了 60%但 vLLM 报CUDA out of memory。原因vLLM 的--gpu-memory-utilization是预分配比例它会在启动时一次性占掉指定比例的显存。如果你设了 0.9但宿主机上还有其他进程比如 Jupyter、训练脚本占着显存vLLM 启动时就会 OOM。另外--max-model-len设得太大也会导致激活值显存暴涨。解决启动前用nvidia-smi确认空闲显存把--gpu-memory-utilization设成空闲显存 / 总显存再减 0.1。同时把--max-model-len降到实际需要的长度不要盲目设 8192。4.4 请求返回的向量全是 0 或 NaN现象curl 返回的embedding字段里全是 0 或者null。原因输入文本超过了--max-model-len的限制vLLM 对超长输入的处理是截断但如果截断后有效 token 数为 0比如输入全是特殊字符pooler 输出就会异常。另一个可能是--dtype设成了float32但模型权重是float16导致数值溢出。解决检查输入文本长度用 tokenizer 算一下 token 数。--dtype保持float16或bfloat16不要用float32。如果输入确实可能超长在客户端做截断不要依赖服务端处理。4.5 Docker 容器内无法访问宿主机的其他服务现象vLLM 容器跑起来了但你的应用容器比如 Dify连不上它报Connection refused或No route to host。原因Docker 默认的 bridge 网络里容器之间不能通过localhost互相访问。如果你的应用和 vLLM 不在同一个自定义网络里需要用宿主机的 IP 或者 Docker 的内部 DNS。解决创建一个自定义 bridge 网络把两个容器都加进去docker network create llm-net docker network connect llm-net bge-m3-server docker network connect llm-net your-app-container然后在应用容器里用http://bge-m3-server:8000/v1访问。如果不想重建网络用--add-host或者在应用配置里写宿主机的局域网 IP。5. 进阶技巧用 Docker Compose 编排 BGE-M3 与向量数据库5.1 为什么需要 Compose 而不是单容器单容器跑 BGE-M3 只解决了嵌入计算的问题但一个完整的 RAG 链路还需要向量数据库比如 Qdrant、Milvus来存储和检索向量。如果你手动docker run两个容器每次重启都要重新配网络、挂载卷、设环境变量容易漏参数。Docker Compose 把这些配置固化在 YAML 文件里docker compose up -d一键拉起整个链路适合在测试环境和生产环境之间迁移。下面是一个最小化的 Compose 配置包含 BGE-M3 和 Qdrantversion: 3.8 services: bge-m3: image: vllm/vllm-openai:v0.6.3 container_name: bge-m3 runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICESall volumes: - /data/models/bge-m3:/models/bge-m3 command: --model /models/bge-m3 --task embedding --dtype float16 --max-model-len 4096 --gpu-memory-utilization 0.7 --host 0.0.0.0 --port 8000 ports: - 8000:8000 shm_size: 2g networks: - rag-net qdrant: image: qdrant/qdrant:v1.12.0 container_name: qdrant volumes: - /data/qdrant:/qdrant/storage ports: - 6333:6333 - 6334:6334 networks: - rag-net networks: rag-net: driver: bridgeruntime: nvidia是 Compose 里指定 GPU 的方式等价于docker run --gpus all。shm_size对应--shm-size。Qdrant 的 6333 端口是 HTTP API6334 是 gRPC两个都映射出来方便调试。5.2 验证端到端链路从文本到向量检索Compose 拉起后用一段 Python 脚本验证从嵌入到检索的完整流程from openai import OpenAI from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct # 初始化客户端 embed_client OpenAI(base_urlhttp://localhost:8000/v1, api_keynot-needed) qdrant QdrantClient(hostlocalhost, port6333) # 创建集合向量维度 1024 对应 BGE-M3 的稠密向量 qdrant.recreate_collection( collection_namedocs, vectors_configVectorParams(size1024, distanceDistance.COSINE) ) # 准备文档 docs [ Docker 是一种容器化技术, vLLM 支持连续批处理, BGE-M3 支持多语言嵌入, Qdrant 是一个向量数据库 ] # 批量嵌入 resp embed_client.embeddings.create(model/models/bge-m3, inputdocs) vectors [item.embedding for item in resp.data] # 写入 Qdrant points [ PointStruct(idi, vectorvectors[i], payload{text: docs[i]}) for i in range(len(docs)) ] qdrant.upsert(collection_namedocs, pointspoints) # 检索 query 怎么做容器化部署 query_vec embed_client.embeddings.create( model/models/bge-m3, input[query] ).data[0].embedding results qdrant.search( collection_namedocs, query_vectorquery_vec, limit2 ) for r in results: print(f得分: {r.score:.4f}, 文本: {r.payload[text]})这段脚本的关键在于向量维度必须和 Qdrant 集合的size参数一致。BGE-M3 的稠密向量是 1024 维如果你换了其他嵌入模型这个值要跟着改。Distance.COSINE是 BGE-M3 推荐的相似度度量因为它的训练目标就是余弦相似度。5.3 一个容易被忽略的细节模型预热与健康检查vLLM 容器启动后模型加载需要几十秒到几分钟取决于磁盘 IO 和模型大小。如果你的应用容器在 vLLM 还没就绪时就发请求会收到 503 或者连接拒绝。Compose 的depends_on只保证容器启动顺序不保证服务就绪。稳妥的做法是在应用侧加重试逻辑或者给 vLLM 配一个健康检查healthcheck: test: [CMD, curl, -f, http://localhost:8000/health] interval: 10s timeout: 5s retries: 30 start_period: 120sstart_period给模型加载留足时间retries设大一点避免误判。应用容器可以用depends_on的condition: service_healthy来等待 vLLM 真正就绪。我自己在第一次部署时没加健康检查结果应用容器比 vLLM 早就绪了 40 秒所有请求全部失败排查了半天才发现是启动顺序问题。后来养成习惯凡是模型服务必配健康检查这算是用血泪换来的教训。希望帮到你。本文还有配套的精品资源点击获取
返回列表