恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
VLLM+Deepseek+Milvus构建私有知识库RAG问答系统实战详解
首页
资讯中心
/
VLLM+Deepseek+Milvus构建私有知识库RAG问答系统实战详解
VLLM+Deepseek+Milvus构建私有知识库RAG问答系统实战详解
发布时间:2026/10/18 2:46:11
先交代一下背景最近我把本地知识库的整套链路重新搭了一遍最终选定的组合是VLLM Deepseek Milvus。之前也折腾过好几个方案最头疼的是推理性能和检索召回总是顾此失彼要么模型跑得慢要么向量查不准。这一版跑顺之后文档入库、相似度检索、生成回答这三段流程终于在内网环境里稳定工作了。如果你正打算搞一套私有的RAG问答系统或者想给大模型外挂一套可控的知识库这篇内容应该能帮你少踩不少坑。整套方案的定位很明确用 VLLM 扛住 Deepseek 的推理服务用 Milvus 承接向量数据的存储和检索中间再加一层文本切片和 Embedding 转换形成一个“导入文档到获取回答”的完整闭环。下面我按照从架构设计到落地排错的顺序把每个环节的关键细节和实操记录都摊开讲。1. 三件套组合先搞清楚每块组件到底解决什么问题1.1 从真实需求反推架构做本地向量库本质上不是为了“存一堆向量”而是为了给大模型提供可追溯、可控制的外部知识。所以需求通常会落在三个点上能把私有文档PDF、Markdown、Word、TXT切成长短合适的片段每个片段能转换成向量并且支持快速的相似度检索检索到的片段能拼接成上下文喂给大模型生成最终回答。这三个点分别对应数据预处理、向量检索、推理生成。VLLM 负责最后一段Milvus 负责中间一段而 Embedding 模型则负责把文本变成向量。很多人一开始会下意识觉得“VLLM 是干推理的跟向量库有什么关系”实际上 VLLM 在这个架构里有两个身份一是 Deepseek 的托管服务二是 Embedding 模型的高性能推理端口。VLLM 的 OpenAI 兼容接口可以同时兼容 chat/completions 和 embeddings 两种请求这让我不需要额外再部署一套推理框架减少了一个维护点。1.2 组件之间的连接关系整个链路的调用顺序是这样的文档进入系统后先做格式解析和清洗清洗后的文本按照一定策略切分成 chunk每个 chunk 生成向量向量连同原文、来源、元数据一起写入 Milvus用户提问时把问题也转成向量在 Milvus 里做相似度检索拿到 TopK 的原文片段后拼成 prompt交给 VLLM 上托管的 Deepseek 生成回答。这里有一个容易被忽略的地方Embedding 模型和生成模型是两套独立模型。Deepseek 系列权重里不包含向量化能力所以必须单独准备一个 Embedding 模型。我这边用的是 BGE-M3中文场景表现比较稳而且显存占用不算高。标题里只写了 VLLM Deepseek Milvus但实际落地时“Embedding 服务”是不可或缺的第四块拼图它在 VLLM 上同样可以托管。1.3 为什么不是别的组合我对这套组合的信心来自几次对比测试。之前试过用原生 Transformers 直接加载生成模型做推理并发一上来显存马上吃紧响应延迟甚至能拉到十几秒。换到 VLLM 之后同样的模型和硬件吞吐量提升非常明显而且 PagedAttention 对显存的利用效率更高长序列生成时不会再频繁 OOM。向量数据库这块我之前也对比过其他方案Milvus 胜在成熟度和部署可控性。它支持多种索引类型、标量过滤与向量检索混合查询并且在千万级向量规模下依然能保持毫秒级响应。对于本地知识库来说也许几十万条向量用轻量级方案也能扛但是考虑到后续扩展和查询复杂度一开始就上 Milvus 反而省事。2. 环境准备与部署配置从裸机到服务全部跑通2.1 硬件条件先算一笔账不管是推理还是向量检索硬件都是第一道门槛。这套方案的最低建议配置是一张至少 16GB 显存的 GPU用于跑 Deepseek 对话模型和 Embedding 模型32GB 内存用于文档解析和向量化过程中的数据缓冲500GB 可用磁盘Milvus 的底层存储、日志、索引文件都会慢慢占空间。我个人的分配习惯是如果只有一张卡先起 Embedding 模型显存占用比较小再起 Deepseek 时根据显存余量调整 VLLM 的--gpu-memory-utilization。比如 16GB 卡上BGE-M3 大概吃掉 2GB 左右剩余空间分配给 Deepseek 系列小尺寸模型是够的。如果你手里的卡是 24GB 或者更大压力会小很多甚至可以同时上更大尺寸的模型。注意显存不够时不要硬上。VLLM 虽然对显存做了优化但模型权重、KV Cache、中间激活都需要显存。宁可降低并发数也不要让整卡 OOM 导致服务崩溃。2.2 部署 Milvus先用 Standalone 模式保证快速启动Milvus 的部署方式有 Standalone 和 Cluster 两种。本地知识库场景数据量在百万级以内时Standalone 完全够用部署也简单。我用 Docker Compose 起了一套官方推荐配置核心服务包括 etcd、MinIO、Milvus 三个容器分别负责元数据、对象存储和查询节点。启动后默认端口是映射出来的19530。验证部署是否成功最直接的方法是装一下 PyMilvus 客户端然后写一个最简单的连接测试from pymilvus import connections connections.connect( aliasdefault, host127.0.0.1, port19530 ) print(connections.get_connection_addr(default))能正常返回地址信息说明客户端与服务端已经打通。这里有个小经验如果你用的是远程服务器别只开放19530端口Milvus 的 Dashboard 默认端口9091也建议一起开放方便查看集群健康状态和查询延迟。2.3 用 VLLM 托管 Deepseek 模型VLLM 的安装方式很成熟推荐直接通过 Python 的包管理工具安装到独立的虚拟环境里。我用的虚拟环境是 Python 3.10 以上版本CUDA 环境提前配置好。安装完之后启动 Deepseek 模型的核心命令是vllm serve /data/models/deepseek-chat \ --served-model-name deepseek-chat \ --port 8000 \ --max-model-len 32768 \ --gpu-memory-utilization 0.7 \ --dtype auto几个参数值得细说--served-model-name是暴露给客户端的模型名可以自定义方便后续切换模型版本时不用改业务代码--max-model-len控制模型最大上下文长度我这里设置 32K既能覆盖大多数知识库场景又不会因为序列太长导致 KV Cache 占用过多--gpu-memory-utilization非常关键它决定了 VLLM 最多使用多少比例的显存。我设置为 0.7剩下的空间留给后面的 Embedding 模型和系统开销。启动完成后通过一个简单的 OpenAI SDK 调用可以快速验证推理服务是否正常from openai import OpenAI client OpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY # 本地服务通常不校验 key留这个字段只是为了兼容 SDK ) resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: 你好请简单介绍一下向量数据库}] ) print(resp.choices[0].message.content)能打印出回答说明 VLLM 服务端和 Deepseek 模型已经跑通了。2.4 Embedding 模型的部署同样交给 VLLM目前 VLLM 对 Embedding 模型的支持也比较成熟。我直接让 VLLM 再起一个服务端口专门跑 BGE-M3vllm serve /data/models/bge-m3 \ --served-model-name bge-m3 \ --port 8001 \ --max-model-len 8192 \ --gpu-memory-utilization 0.15 \ --task embedding这里特别注意--task embedding没有这个参数VLLM 默认按生成模型处理接口路径和返回格式会跟预期不符。部署完成后调用 Embedding 接口的验证方式resp client.embeddings.create( modelbge-m3, input[Milvus是一个向量数据库] ) print(len(resp.data[0].embedding))正常情况下会输出一个向量维度BGE-M3 的输出维度是 1024。提示如果你手里的显存实在不够也可以把 Embedding 服务放在 CPU 上跑只是入库大批量文本时速度会比较慢。建议项目初期就在 GPU 上把它跑通否则后面调优检索效果时会有很多无用功。3. 数据准备与向量化切片质量决定了检索质量的上限3.1 文档清洗是很容易被跳过但很关键的步骤很多人拿到一批 PDF 就直接切片入库结果检索阶段经常返回一堆乱码或者页眉页脚回答自然也不可读。我的流程是先做两层清洗第一层是格式清洗把 PDF 里的水印、页码、页眉、表格中混入的噪声去掉。PDF 解析我用的开源库解析后按页面转成文本再按规则删除空白行和重复标题。第二层是语义清洗针对一些扫描件或者排版混乱的文档我会人工抽检几个片段确认文本顺序和完整性。清洗后的文档质量直接决定了切片和向量化的输入质量。这一步不要省。3.2 切分策略固定长度加重叠是最省心的起点文本切分没有银弹但是对大多数知识库文档固定长度加重叠Fixed-size with Overlap是最稳的起点。我常用的参数是chunk_size: 500 个字符chunk_overlap: 80 个字符。这样做的原因很简单切得太长一段文本包含多个主题向量表示会被拉平均检索精度下降切得太短语义信息不完整检索时匹配度偏低。而加一段重叠能保证跨越切分点的语义信息不会中断。Python 里实现这个策略并不复杂def split_text_with_overlap(text, chunk_size500, overlap80): chunks [] start 0 while start len(text): end min(start chunk_size, len(text)) chunks.append(text[start:end]) if end len(text): break start end - overlap return chunks如果文档本身已经有明确的章节结构也可以先按 Markdown 标题或者段落边界做一次粗切再对超长段落执行固定长度策略这样检索返回的片段更贴合文档逻辑。3.3 向量化入库批量写入 Milvus 的步骤拆解向量化这一步我的做法是把切好的 chunk 分批请求 Embedding 服务然后组装成 Milvus 需要的实体结构。这里要强调一个点不要一个 chunk 发一次 HTTP 请求太慢。VLLM 的 Embedding 接口原生支持 batch 输入我一次会带 32 到 64 个文本片段。入库前先设计好 Collection 的字段和索引。我用的 Collection Schema 是这样的字段名类型用途idINT64主键textVARCHAR原始文本片段sourceVARCHAR来源文档名chunk_indexINT64切片序号embeddingFLOAT_VECTOR向量字段索引参数我用的是 HNSWmetric 选择 COSINE。HNSW 在百万级数据量下的查询性能很稳定而且召回质量不错。创建索引和写入数据的简化过程from pymilvus import CollectionSchema, FieldSchema, DataType, Collection fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue, auto_idFalse), FieldSchema(nametext, dtypeDataType.VARCHAR, max_length1000), FieldSchema(namesource, dtypeDataType.VARCHAR, max_length200), FieldSchema(namechunk_index, dtypeDataType.INT64), FieldSchema(nameembedding, dtypeDataType.FLOAT_VECTOR, dim1024), ] schema CollectionSchema(fields, descriptionlocal knowledge base) collection Collection(knowledge_base, schema) index { index_type: HNSW, metric_type: COSINE, params: {M: 16, efConstruction: 200} } collection.create_index(embedding, index) collection.load()写入时把之前切片生成的文本和向量批量组装好然后调用insert接口。这里我会分批处理每批 1000 条左右既能保证写入速度又不会让内存压力过大。4. 检索链路与 RAG 调用打通从提问到回答的完整闭环4.1 相似度检索的完整代码实现检索阶段的核心是把用户问题转换成向量然后在 Milvus 里执行相似度搜索。我这个项目的检索函数封装成了下面这个样子def search_knowledge_base(query_text, top_k5): resp embed_client.embeddings.create( modelbge-m3, input[query_text] ) query_vector resp.data[0].embedding search_params { metric_type: COSINE, params: {ef: 64} } results collection.search( data[query_vector], anns_fieldembedding, paramsearch_params, limittop_k, output_fields[text, source, chunk_index] ) return resultsef参数是 HNSW 搜索时的探索范围调大一点召回质量会更好但查询延迟也会上升。我实际使用下来ef64在本地场景下是性价比最高的设置。拿到检索结果后一定要做一层“相关性筛选”不能把 Milvus 返回的所有结果一股脑塞给大模型。我一般会检查相似度分数只保留超过自定义阈值的片段。这个阈值需要根据自己的文档类型多次实验我这边用 0.5 作为默认值效果比较理想。4.2 构造 Prompt 并调用 Deepseek 生成回答检索到的文本片段我会按照“检索内容在前用户问题在后”的顺序拼接 Prompt。同时要求模型只基于提供的上下文回答避免模型回答超出知识库边界。核心代码如下context \n\n.join([hit.entity.text for hit in results]) prompt f请基于以下参考内容回答问题。 参考内容 {context} 问题{query_text} 要求 1. 如果参考内容中没有答案请直接说“知识库中未找到相关信息”。 2. 不要编造事实。 resp chat_client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: prompt}], temperature0.3, max_tokens1024 )这里把 temperature 设置成 0.3 是因为知识问答场景更看重稳定性和准确性过高的随机性会让模型产生不稳定输出。4.3 从检索到大模型调用之间的细节问题实际运行中我发现几个值得优化的细节第一上下文片段不能太多。TopK 设成 5 的时候如果每个 chunk 是 500 字那么上下文差不多 2500 字对最终回答是足够的。继续增加 TopK 并不会明显提升回答质量反而会让模型注意力被无关片段干扰。第二相同的用户问题不要反复向量化。我在应用层加了一层简单缓存把最近一小时的 query 向量存起来命中缓存就直接跳过一次 Embedding 调用延迟和资源消耗都降低不少。第三回答结束时最好让模型输出引用的来源文档。我会在 Prompt 里加上“请标注回答内容对应参考来源的序号”这样用户可以追踪答案出自哪篇文档的哪个片段对于知识库系统来说非常重要。5. 常见问题与排查技巧实录这些坑我替你先踩了5.1 部署阶段的高频报错与解决思路VLLM 启动时最常见的报错是 CUDA 显存不足。解决思路不是盲目调低--gpu-memory-utilization而是先确认当前显存占用用nvidia-smi看进程把残留的 Python 推理进程清理掉再重启。另一个常见问题是模型路径下缺少必要文件VLLM 会直接报找不到 config。建议启动前检查模型权重目录下是否包含config.json、tokenizer.json这些关键文件。Milvus 这边容易踩的坑是 Collection 已经存在重复创建报错。我习惯在初始化代码里先做一次判断处理if collection.has(): collection.drop() collection Collection(knowledge_base, schema)5.2 检索质量不理想时的排查路径如果检索出来的结果跟问题语义不相关我会按下面顺序排查先看 Embedding 模型是不是支持中文。很多英文 Embedding 模型在中文上表现极差再看切片长度是否合适。切片太长了一个 chunk 含多个主题检索精度必然下降再看数据清洗是否干净。文本中有大量噪声字符的话向量表示会严重偏移最后调整 HNSW 的ef参数以及检索阶段的相似度阈值。另外我还有一个小技巧对检索回来的切片做一次关键词重合度校验。如果向量相似度高但关键词完全没重合大概率是语义偏移这个结果可以直接过滤掉。5.3 性能优化与资源控制经验整套系统稳定运行之后我做的优化主要是三个方面第一个是控制 Embedding 请求的并发。VLLM 对大批量请求有一定的排队机制但我在业务层还是加了信号量控制限制同时打向 Embedding 服务的请求数量避免服务端积压。第二个是给 Milvus 设置合理的shards和分区。如果文档类型很多我建议按来源分区比如“产品手册”一个分区“技术文档”一个分区这样查询时可以指定分区减少扫描范围。第三个是定期清理无效数据。我会有定时任务删除掉重复入库的文档对应向量保证 Collection 里没有冗余。具体实现是通过主键删除接口传入待删除的 id 列表。经验谈本地向量库不是搭完就一劳永逸的东西。文档更新、模型升级、检索策略调整都会影响效果。我建议每次新增文档后都抽样跑几条典型问题对比回答质量有没有变化再决定是不是要调整切分或者索引参数。最后再说一个个人习惯每次调整完系统参数后我会把关键配置和测试结果整理成一份变更记录方便回滚。这套 VLLM Deepseek Milvus 的组合目前在我这边的维护成本已经降得很低了如果你也想构建一套私有的知识库问答系统完全可以照这个路线试一试。