fastest-rag-milvus-groq 实战:Milvus 二值向量检索 + Groq 推理,构建毫秒级 RAG 应用
导读
本文基于 ai-engineering-hub 仓库中的 fastest-rag-milvus-groq 项目,完整拆解一条以“低检索时延”为核心目标的 RAG 技术栈:用 HuggingFace 模型产出稠密向量并做二值量化(Binary Quantization),存入 Milvus 向量库做二进制向量索引与 Hamming 距离检索,再由 Groq 作为推理引擎驱动 Kimi K2 模型生成答案,最后借助 Beam 完成 Serverless 部署。读完本文,你将掌握二值向量检索的原理与落地代码、Milvus BINARY_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 数组,这是 Milvus BINARY_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 快速推理协同的极佳起点。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00