首页
/ fastest-rag-milvus-groq 实战:Milvus 二值向量检索 + Groq 推理,构建毫秒级 RAG 应用

fastest-rag-milvus-groq 实战:Milvus 二值向量检索 + Groq 推理,构建毫秒级 RAG 应用

2026-09-08 15:32:39作者:邬祺芯Juliet

导读

本文基于 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. 检索侧:稠密向量被符号化压缩为每个维度仅 1 bit 的二进制向量,存储开销相对 float32 降至约 1/32;Milvus 通过 BIN_FLAT 索引 + HAMMING(汉明)距离直接按位计算相似度,无需浮点乘法。
  2. 生成侧:Groq 作为高吞吐推理引擎驱动 LLM,配合 LlamaIndex 的流式接口把首字延迟进一步摊薄。

需要说明:< 15ms 是项目 README 给出的目标指标,实际数值与语料规模、索引类型(本项目 BIN_FLAT 属于精确扫描)、部署硬件(本地 CPU 还是 Beam GPU)强相关,应以你所在环境的实测为准——本项目的 app.py 内置了毫秒级检索计时器,可以直接量化验证。


二、核心原理:Embedding 的二值量化(Binary Quantization)

二值量化是本项目检索侧提速的关键。它把 float32 的稠密向量变成仅含 0/1 的比特串。仓库在 EmbedData 中给出了完整实现,模型默认为 BAAI/bge-large-en-v1.5rag.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,这正是 Milvus BINARY_VECTOR 字段所需的数据形态。

代码中有两个值得注意的工程细节:

  1. 批处理batch_iteratebatch_size=512 分批生成 Embedding 与二进制向量,避免一次处理整个语料造成内存压力;
  2. 模型缓存_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

四个要点值得展开:

  1. 查询也需同源量化get_query_embedding 产生 float32 查询向量后,必须用与建库阶段完全一致的 > 0 → 1 阈值规则打包成 bytes,否则检索结果无意义;
  2. 索引字段对齐anns_field="binary_vector" 指明在二进制向量字段上检索,search_params 中的度量必须是建索引时注册的 HAMMING
  3. 返回原文:通过 output_fields=["context"] 让 Milvus 直接带回命中的文本片段,省去二次查询;
  4. 相似度换算: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 落地中抑制幻觉的常见手段;
  • 双入口 APIquery()llm.stream_complete/llm.completechat_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 把整套流程包装成可视化应用,交互设计可总结为四步:

  1. 侧边栏输入 Groq API Key(密码输入框,支持从环境变量预填);
  2. 上传 PDF:文件进入临时目录后由 LlamaIndex 的 SimpleDirectoryReader(input_dir=..., required_exts=[".pdf"], recursive=True) 抽取文本(app.py);
  3. 三步索引管线:生成 Embedding(进度 40%)→ 创建二进制向量集合并写入 Milvus Lite(60%)→ 构建 RAG 查询引擎(100%),全程用 st.progress 反馈进度;同一会话内已处理的文档存入 st.session_state.file_cache,避免重复索引;
  4. 流式对话 + 计时:用户提问后走检索 → 组装提示词 → 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 上的问答应用。


九、代码路径速览与进一步探索

围绕本主题,可以在仓库中继续阅读以下文件,从“会跑”进阶到“懂实现”:

值得自行验证与思考的边界

  • BIN_FLAT 是精确扫描:源码注释明确其为 “Exact search for binary vectors”,检索耗时随库内向量数量线性增长。当语料达到较大规模时,可在 Milvus 二进制索引体系内进一步调研近似检索(ANN)索引方案来换取舍与速度的平衡;
  • 二值量化是有损压缩:阈值 0 的符号化丢弃了向量幅值信息,换取约 32 倍存储缩减与按位运算。对精度敏感的评测集,建议先用本项目内置的 Retrieval time 计时 + 人工问答验证召回质量;
  • 时延目标存在前提< 15ms 是设计目标,实测值受硬件(Beam T4 / 本地 CPU)、语料量与文本长度影响,务必以应用内展示的毫秒数为准。

综上,这个项目提供了一条“Embedding 二值化 → Milvus 二进制索引 → Groq 快速生成”的低时延 RAG 参考实现。从本地 Streamlit 验证到 Beam 云上发布,链路完整、代码量小,是理解二进制向量检索落地与 LLM 快速推理协同的极佳起点。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525