fastest-rag-milvus-groq 实战:Milvus 二值向量检索 + Groq 推理,构建毫秒级 RAG 应用
【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub
导读
本文基于 ai-engineering-hub 仓库中的 fastest-rag-milvus-groq 项目,完整拆解一条以“低检索时延”为核心目标的 RAG 技术栈:用 HuggingFace 模型产出稠密向量并做二值量化(Binary Quantization),存入 Milvus 向量库做二进制向量索引与 Hamming 距离检索,再由 Groq 作为推理引擎驱动 Kimi K2 模型生成答案,最后借助 Beam 完成 Serverless 部署。读完本文,你将掌握二值向量检索的原理与落地代码、MilvusBINARY_VECTOR集合的建库与索引配置、Groq LLM 的接入方式,以及一条从 PDF 上传到流式问答、并在本地/云端运行完整应用的实操路径。
一、系统全景:这条 RAG 技术栈为什么“快”
该项目在 README.md 中给出了明确的设计目标:构建检索时延 < 15ms的 RAG 应用。这一目标由四个核心组件协同实现:
| 组件 | 在本项目中的角色 | 代码位置 |
|---|---|---|
| LlamaIndex | 编排 RAG 应用,提供文档加载、Embedding 与 LLM 封装 | rag.py |
| Milvus | 二进制向量(BINARY_VECTOR)的索引与存储 | rag.py |
| Groq | 推理引擎,运行 MoonshotAI 的 Kimi K2(moonshotai/kimi-k2-instruct) | rag.py |
| Beam | 极速 Serverless 云端部署 Streamlit 应用 | start_server.py |
速度来自两条主线(依据仓库源码 rag.py 实现推断):
- 检索侧:稠密向量被符号化压缩为每个维度仅 1 bit 的二进制向量,存储开销相对 float32 降至约 1/32;Milvus 通过
BIN_FLAT索引 +HAMMING(汉明)距离直接按位计算相似度,无需浮点乘法。 - 生成侧:Groq 作为高吞吐推理引擎驱动 LLM,配合 LlamaIndex 的流式接口把首字延迟进一步摊薄。
需要说明:< 15ms是项目 README 给出的目标指标,实际数值与语料规模、索引类型(本项目BIN_FLAT属于精确扫描)、部署硬件(本地 CPU 还是 Beam GPU)强相关,应以你所在环境的实测为准——本项目的 app.py 内置了毫秒级检索计时器,可以直接量化验证。
二、核心原理:Embedding 的二值量化(Binary Quantization)
二值量化是本项目检索侧提速的关键。它把 float32 的稠密向量变成仅含 0/1 的比特串。仓库在EmbedData中给出了完整实现,模型默认为BAAI/bge-large-en-v1.5(rag.py):
class EmbedData: def __init__(self, embed_model_name="BAAI/bge-large-en-v1.5", batch_size=512): self.embed_model_name = embed_model_name self.embed_model = self._load_embed_model() # HuggingFaceEmbedding self.batch_size = batch_size def _binary_quantize(self, embeddings): """Convert float32 embeddings to binary vectors""" embeddings_array = np.array(embeddings) binary_embeddings = np.where(embeddings_array > 0, 1, 0).astype(np.uint8) # Pack bits into bytes (8 dimensions per byte) packed_embeddings = np.packbits(binary_embeddings, axis=1) return [vec.tobytes() for vec in packed_embeddings] def embed(self, contexts): for batch_context in batch_iterate(contexts, self.batch_size): batch_embeddings = self.generate_embedding(batch_context) # float32 self.embeddings.extend(batch_embeddings) binary_batch = self._binary_quantize(batch_embeddings) # 二值化 self.binary_embeddings.extend(binary_batch)量化过程分两步:
- 符号化(Sign 编码):
np.where(x > 0, 1, 0)以 0 为阈值,向量各维大于 0 记 1、否则记 0; - 位打包:
np.packbits(binary_embeddings, axis=1)沿向量维度方向把 8 个 bit 打包成 1 个 byte,最终以bytes形式送入 Milvus,这正是 MilvusBINARY_VECTOR字段所需的数据形态。
代码中有两个值得注意的工程细节:
- 批处理:
batch_iterate按batch_size=512分批生成 Embedding 与二进制向量,避免一次处理整个语料造成内存压力; - 模型缓存:
_load_embed_model()将模型缓存在./hf_cache目录(rag.py),首次运行需联网下载bge-large-en-v1.5,之后可离线加载。
值得一提的设计权衡:符号化只用“正/负”信息而丢弃幅值,因此对相似度精度是有损压缩。它换取的是 Milvus 端仅需按位比较的 Hamming 距离与约 32 倍的内存节省——这正是该项目换取检索速度的“代价”。对检索召回率要求极高的场景,可在此基础上用重排序(Re-ranking)阶段补偿(本项目未包含,属于可自行扩展方向)。
三、向量库设计:Milvus 二进制向量集合
3.1 客户端与本地库文件
项目使用MilvusClient(db_file)直接以本地文件的方式运行 Milvus Lite(rag.py),无需单独起 Milvus 服务,非常适合单机/开发期验证:
def define_client(self): try: self.client = MilvusClient(self.db_file) logger.info(f"Initialized Milvus Lite client with database: {self.db_file}") except Exception as e: raise e在 app.py 中,每个浏览器会话会生成独立的库文件milvus_{session_id}.db放在系统临时目录,并在重新索引前先删除旧文件,保证多会话数据隔离。
3.2 集合 Schema 与索引参数
MilvusVDB_BQ.create_collection()定义了二进制向量集合的完整结构(rag.py):
schema = self.client.create_schema( auto_id=True, enable_dynamic_fields=True, ) schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True) schema.add_field(field_name="context", datatype=DataType.VARCHAR, max_length=65535) schema.add_field(field_name="binary_vector", datatype=DataType.BINARY_VECTOR, dim=self.vector_dim) index_params = self.client.prepare_index_params() index_params.add_index( field_name="binary_vector", index_name="binary_vector_index", index_type="BIN_FLAT", # Exact search for binary vectors metric_type="HAMMING" # Hamming distance for binary vectors ) self.client.create_collection( collection_name=self.collection_name, schema=schema, index_params=index_params )关键参数逐项说明:
| 配置项 | 取值 | 含义与影响 |
|---|---|---|
auto_id | True | 主键id由 Milvus 自动生成,插入时无需传 id |
enable_dynamic_fields | True | 允许后续插入未预定义的字段 |
context | VARCHAR(65535) | 存储原始文本片段,检索时随结果一起取出 |
binary_vector | BINARY_VECTOR | 二进制向量类型,维度dim必须与量化前的 Embedding 维度一致(8 的整数倍) |
index_type | BIN_FLAT | 二进制向量的精确(暴力)扫描索引,代码注释明确标注 “Exact search” |
metric_type | HAMMING | 相似度度量采用汉明距离,数值越小表示两个比特串差异越少 |
关于维度,rag.py 默认vector_dim=1024,而 app.py 采用更稳妥的做法:先对一条"test"文本取真实 Embedding 长度作为actual_dim,再传入建库函数,从根上避免维度不匹配的报错。
3.3 批量写入
ingest_data()将文本与二进制向量一一配对、逐批插入(rag.py):
for batch_context, batch_binary_embeddings in zip( batch_iterate(embeddata.contexts, self.batch_size), batch_iterate(embeddata.binary_embeddings, self.batch_size) ): data_batch = [ {"context": context, "binary_vector": binary_embedding} for context, binary_embedding in zip(batch_context, batch_binary_embeddings) ] self.client.insert(collection_name=self.collection_name, data=data_batch)注意binary_vector字段写入的是打包后的bytes对象(_binary_quantize的返回值),而非 0/1 矩阵或 float 数组,这是 MilvusBINARY_VECTOR的输入约定。
四、检索链路:查询二值化 → Hamming 距离 → 相似度换算
Retriever类负责把用户问题映射为同样的二进制空间再执行检索(rag.py):
def _binary_quantize_query(self, query_embedding): embedding_array = np.array([query_embedding]) binary_embedding = np.where(embedding_array > 0, 1, 0).astype(np.uint8) packed_embedding = np.packbits(binary_embedding, axis=1) return packed_embedding[0].tobytes() def search(self, query, top_k=None): if top_k is None: top_k = self.top_k # 默认 5 query_embedding = self.embeddata.embed_model.get_query_embedding(query) binary_query = self._binary_quantize_query(query_embedding) search_results = self.vector_db.client.search( collection_name=self.vector_db.collection_name, data=[binary_query], anns_field="binary_vector", search_params={"metric_type": "HAMMING", "params": {}}, limit=top_k, output_fields=["context"] ) formatted_results = [] for result in search_results[0]: formatted_results.append({ "id": result["id"], "score": 1.0 / (1.0 + result["distance"]), # 汉明距离 → 相似度 "payload": {"context": result["entity"]["context"]}, }) return formatted_results四个要点值得展开:
- 查询也需同源量化:
get_query_embedding产生 float32 查询向量后,必须用与建库阶段完全一致的> 0 → 1阈值规则打包成 bytes,否则检索结果无意义; - 索引字段对齐:
anns_field="binary_vector"指明在二进制向量字段上检索,search_params中的度量必须是建索引时注册的HAMMING; - 返回原文:通过
output_fields=["context"]让 Milvus 直接带回命中的文本片段,省去二次查询; - 相似度换算:Milvus 返回的是汉明距离(越小越相似),项目用
1.0 / (1.0 + distance)将其转换成 “越大越相似” 的分数,方便下游或 UI 直接展示。
五、生成侧:LlamaIndex + Groq 驱动 Kimi K2
检索到的片段由RAG类交给 Groq 上的 LLM 完成生成(rag.py)。初始化核心参数如下:
class RAG: def __init__(self, retriever, llm_model="moonshotai/kimi-k2-instruct", groq_api_key=None): self.llm_model = llm_model self.groq_api_key = groq_api_key or os.getenv("GROQ_API_KEY") self.llm = self._setup_llm() self.retriever = retriever self.prompt_template = ( "CONTEXT: {context}\n" "---------------------\n" "Given the context information above I want you to think step by step to answer the user's query " "in a crisp and concise manner. In case you don't know the answer simply say 'I don't know!'. " "Don't try to make up an answer. Only answer based on facts and contextual information.\n" "QUERY: {query}\n" "ANSWER: " ) def _setup_llm(self): if not self.groq_api_key: raise ValueError("Groq API key is required...") return Groq( model=self.llm_model, api_key=self.groq_api_key, temperature=0.4, max_tokens=1000 )需要特别说明的几处设计:
- 模型选择:
moonshotai/kimi-k2-instruct走 Groq 的推理端点,API Key 通过GROQ_API_KEY环境变量或构造参数传入,缺省时会抛出明确错误提示; - 解码参数:
temperature=0.4(偏低、更保守稳定)与max_tokens=1000(单次回复上限),可结合自身场景调整; - 防幻觉提示词:Prompt 模板要求模型“基于事实与上下文回答、不知道就说 I don't know,绝不编造”,这是 RAG 落地中抑制幻觉的常见手段;
- 双入口 API:
query()走llm.stream_complete/llm.complete,chat_query()走llm.stream_chat/llm.chat,UI 层选用stream_complete实现逐 token 流式输出。
generate_context()会把top_k(默认 5)条检索结果按\n\n---\n\n拼接成单一上下文块(rag.py),再填入上述模板——检索与生成的衔接就在这里完成。
六、前端交互:Streamlit 应用与毫秒级计时
app.py 用 Streamlit 把整套流程包装成可视化应用,交互设计可总结为四步:
- 侧边栏输入 Groq API Key(密码输入框,支持从环境变量预填);
- 上传 PDF:文件进入临时目录后由 LlamaIndex 的
SimpleDirectoryReader(input_dir=..., required_exts=[".pdf"], recursive=True)抽取文本(app.py); - 三步索引管线:生成 Embedding(进度 40%)→ 创建二进制向量集合并写入 Milvus Lite(60%)→ 构建
RAG查询引擎(100%),全程用st.progress反馈进度;同一会话内已处理的文档存入st.session_state.file_cache,避免重复索引; - 流式对话 + 计时:用户提问后走检索 → 组装提示词 →
llm.stream_complete的流水线。
其中计时逻辑是验证“快不快”的关键(app.py):
# Measure retrieval time retrieval_start = time.perf_counter() context_text = query_engine.generate_context(query=prompt) retrieval_time = time.perf_counter() - retrieval_start # ... stream LLM tokens and render ... retrieval_ms = int(retrieval_time * 1000) st.caption(f"⏱️ Retrieval time: {retrieval_ms} ms")这里测量的范围是generate_context()——即查询二值化 + Milvus Hamming 检索 + 上下文拼接的纯检索阶段(不含 LLM 生成),因此该数字能直接刻画向量检索环节的时延,用于验证 README 提出的毫秒级目标。
其余 UI 细节还包括:内置 PDF 的 base64 内联预览、每条会话独立的 Milvus 集合(docs_{uuid8})与“Clear ↺”按钮(重置对话并触发gc.collect()释放内存)。
七、环境准备与本地运行
7.1 安装 uv 与依赖
README 要求Python 3.11 及以上,并推荐用 uv 管理项目与虚拟环境。先安装 uv:
# MacOS/Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"初始化项目并安装依赖:
# Create a new directory for our project uv init fastest-rag cd fastest-rag # Create virtual environment and activate it uv venv source .venv/bin/activate # MacOS/Linux .venv\Scripts\activate # Windows # Install dependencies uv add pymilvus llama-index llama-index-embeddings-huggingface llama-index-llms-groq streamlit beam-client依赖清单与本仓库实际 import 一一对应:pymilvus(Milvus 客户端)、llama-index核心及其 HuggingFace Embedding / Groq LLM 扩展、streamlit(UI)、beam-client(云端部署)。若按仓库目录直接运行,还需python-dotenv以读取.env中的密钥。
7.2 配置 Groq API Key
在 Groq 控制台申请 API Key 后写入项目根目录的.env:
GROQ_API_KEY=<YOUR_GROQ_API_KEY>应用启动时通过load_dotenv()(app.py)自动读取;也可以在页面侧边栏手动填入,二者等价。
7.3 本地启动
streamlit run app.py浏览器打开后上传一个 PDF(仓库fastest-rag-milvus-groq/docs/raft.pdf可作为测试样例),即可看到 “生成 Embeddings → 创建向量索引 → 存入向量库” 的进度条,随后进入流式问答界面,并实时显示每次提问的检索毫秒数。
八、一键上云:Beam Serverless 部署
为了让“毫秒级”在真实服务中可被体验,项目还提供了 Beam 部署脚本 start_server.py,把 Streamlit 应用直接发布到 Beam 云:
from beam import Image, Pod streamlit_server = Pod( image=Image().add_python_packages([ "streamlit", "pymilvus", "llama-index", "llama-index-embeddings-huggingface", "llama-index-llms-groq" ]), ports=[8501], # Default port for streamlit gpu="T4", memory="2Gi", entrypoint=["streamlit", "run", "app.py"], ) res = streamlit_server.create() print("✨ Streamlit server hosted at:", res.url)脚本的资源配置要点如下,便于按需调整:
| 配置 | 取值 | 说明 |
|---|---|---|
ports | [8501] | Streamlit 默认监听端口 |
gpu | "T4" | 指定 T4 GPU(当前被注释的cpu=4表明也可切换为纯 CPU 规格) |
memory | "2Gi" | 容器内存配额 |
entrypoint | ["streamlit", "run", "app.py"] | 容器启动命令 |
image.add_python_packages | 5 个 Python 包 | 云端运行环境依赖 |
部署步骤(README 原文)依次是:
# 1. 注册 Beam:进入控制台,默认 token 会自动生成,然后在终端绑定 beam configure default --token <YOUR_BEAM_TOKEN> # 2. 执行部署脚本,等待 Pod 创建 python start_server.py脚本执行成功后会在终端打印 Streamlit 服务的公网地址,把生成的链接粘贴到浏览器即可直接访问部署在 Beam 上的问答应用。
九、代码路径速览与进一步探索
围绕本主题,可以在仓库中继续阅读以下文件,从“会跑”进阶到“懂实现”:
- fastest-rag-milvus-groq/README.md:项目目标、完整安装与部署命令;
- fastest-rag-milvus-groq/rag.py:二值量化、Milvus 集合建库、检索器与 RAG 引擎的核心实现(重点看
EmbedData、MilvusVDB_BQ、Retriever、RAG四个类); - fastest-rag-milvus-groq/app.py:Streamlit 交互、会话级集合隔离、检索计时与流式输出;
- fastest-rag-milvus-groq/start_server.py:Beam Pod 的云部署声明;
- fastest-rag-milvus-groq/docs/raft.pdf:仓库内置的可直接用于测试的示例 PDF。
值得自行验证与思考的边界
BIN_FLAT是精确扫描:源码注释明确其为 “Exact search for binary vectors”,检索耗时随库内向量数量线性增长。当语料达到较大规模时,可在 Milvus 二进制索引体系内进一步调研近似检索(ANN)索引方案来换取舍与速度的平衡;- 二值量化是有损压缩:阈值 0 的符号化丢弃了向量幅值信息,换取约 32 倍存储缩减与按位运算。对精度敏感的评测集,建议先用本项目内置的
Retrieval time计时 + 人工问答验证召回质量; - 时延目标存在前提:
< 15ms是设计目标,实测值受硬件(Beam T4 / 本地 CPU)、语料量与文本长度影响,务必以应用内展示的毫秒数为准。
综上,这个项目提供了一条“Embedding 二值化 → Milvus 二进制索引 → Groq 快速生成”的低时延 RAG 参考实现。从本地 Streamlit 验证到 Beam 云上发布,链路完整、代码量小,是理解二进制向量检索落地与 LLM 快速推理协同的极佳起点。
【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考