# kb-cpp **Repository Path**: chise0519/kb-cpp ## Basic Information - **Project Name**: kb-cpp - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-20 - **Last Updated**: 2026-08-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # kb — 本地向量知识库 (bge-m3 + HNSW + SQLite) 离线本地语义检索系统:把文档树(PDF / Office / HTML / 纯文本 / 源代码)解析、 切块、embedding 后存入向量索引,支持中英文混合查询。全部本地推理,数据不出机器。 - 实测规模:6,052 文件 / 12.2 万块(56GB 混合文档树) - CPU 全量索引 ~15 小时;GPU (RTX 5060 Ti) 全量重建 **56 分钟** - 增量索引:按 mtime+size 检测变化,夜间 cron 无人值守 ## 架构 ``` 扫描 roots (扩展名白名单 + exclude_dirs + 大小上限) → 按类型抽取文本 (PDF: MuPDF | docx/xlsx/pptx: miniz+pugixml | 文本/代码) → 切块 (文档 ~600 字符 + 120 重叠, CJK 安全; 代码 40 行 + 8 重叠) → bge-m3 embedding (1024 维, L2 归一化, ONNX Runtime CPU/CUDA) → 存储: hnswlib 内积索引 + SQLite (文本/元数据) 增量索引: 状态 = 每文件 mtime+size; 变化→重嵌入, 消失→软删除 ``` --- # 部署 ## 0. 系统要求 | 项 | 要求 | |---|---| | OS | Linux (x86_64 或 aarch64) | | 编译器 | g++ ≥ 11 (C++17)、cmake ≥ 3.16、make、unzip | | 内存 | ≥ 8GB(建索引时建议 16GB) | | 磁盘 | 源码+依赖 ~6GB;索引 ≈ 文档树大小 × 1.5%(12 万块 ≈ 800MB) | | GPU | 可选,NVIDIA(CUDA 12 兼容驱动,见第 8 节) | | 网络 | 仅下载依赖/模型时需要;国内环境已内置镜像回退 | Ubuntu/Debian 装编译工具: ```bash sudo apt install -y build-essential cmake unzip curl ``` ## 1. 获取代码 ```bash git clone https://gitee.com/chise0519/kb-cpp.git cd kb-cpp ``` ## 2. 下载第三方依赖 ```bash chmod +x fetch_deps.sh build_thirdparty.sh build.sh ./fetch_deps.sh # 全部下载到 third_party/downloads/ # GPU 版额外下载 onnxruntime-gpu: FETCH_GPU=1 ./fetch_deps.sh ``` 脚本自动处理:GitHub 直连失败时回退 ghfast.top / ghproxy.net 镜像; 已存在的文件跳过(可安全重跑);下载完逐个校验压缩包完整性。 依赖清单(供排查): | 依赖 | 版本 | 用途 | |---|---|---| | onnxruntime (预编译) | 1.23.2 | embedding 推理 (CPU/GPU) | | mupdf | 1.24.10 | PDF 文本抽取 | | sentencepiece | 0.2.1 | bge-m3 分词 | | abseil-cpp | 20240722.0 | sentencepiece 依赖 | | hnswlib | 0.8.0 | 向量索引 (header-only) | | spdlog | 1.15.0 | 日志 (header-only) | | nlohmann/json | 3.11.3 | 配置解析 (单头文件) | | pugixml | 1.14 | Office XML 解析 | | miniz | 3.0.2 | Office zip 解包 | | sqlite amalgamation | 3.46.1 | 元数据/文本存储 | ## 3. 编译第三方库 ```bash ./build_thirdparty.sh ``` 在 `third_party/` 内本地编译(mupdf / sentencepiece / abseil), 解压 header 库,产物全部留在项目内,**无需 root**。约 10~20 分钟。 ## 4. 下载 bge-m3 模型 官方 BAAI/bge-m3 仓库自带 ONNX 版,国内用 hf-mirror 直链(约 2.2GB): ```bash mkdir -p ~/models/bge-m3-onnx && cd ~/models/bge-m3-onnx base=https://hf-mirror.com/BAAI/bge-m3/resolve/main curl -fLO $base/onnx/model.onnx # ~700KB 计算图 curl -fLO $base/onnx/model.onnx_data # ~2.2GB fp32 权重(必须与 model.onnx 同目录) cd - ``` `sentencepiece.bpe.model` 已随仓库提供(`models/` 下),无需另下。 > CPU 想提速可选做 int8 量化(吞吐 ~2 倍,检索质量损失可忽略): > `pip install onnxruntime` 后 > `python -c "from onnxruntime.quantization import quantize_dynamic, QuantType; quantize_dynamic('model.onnx','model-int8.onnx',weight_type=QuantType.QInt8)"`, > 输出单文件 ~570MB,config 指过去即可。**GPU 部署不要量化**(CUDA EP 对 > 动态量化支持差,直接用 fp32)。 ## 5. 配置 ```bash cp config.example.json config.json vi config.json # 按本机情况修改 ``` 必改项: | 字段 | 说明 | |---|---| | `roots` | 要索引的文档目录列表(绝对路径) | | `model.onnx_dir` | bge-m3 ONNX 目录(含 model.onnx + model.onnx_data) | | `store.db_path` / `store.hnsw_path` | 索引库存放位置(建议绝对路径) | | `model.providers` | `["CPUExecutionProvider"]` 或 `["CUDAExecutionProvider","CPUExecutionProvider"]`(GPU 版) | 其余参数(切块大小、HNSW 的 M/efConstruction/ef_search、排除目录、 扩展名白名单)均有默认值,全 JSON 化、代码零硬编码。 注意:`config.json` 不入库(含本机路径),仓库只提交 `config.example.json`。 ## 6. 编译 kb ```bash ./build.sh # CPU 版 → build/kb + build/libkb.so ``` GPU 版(先完成第 8 节的 CUDA 运行库准备): ```bash cmake -S . -B build-gpu -DCMAKE_BUILD_TYPE=Release -DORT_VARIANT=gpu cmake --build build-gpu -j$(nproc) # → build-gpu/kb ``` ## 7. 首次索引与验证 ```bash ./build/kb index # 首次全量(大文档树建议后台跑) ./build/kb status # 看统计 ./build/kb search "你的问题" -k 5 # 验证检索质量 ./smoke_test.sh # 冒烟测试(可选) ``` 6k 文件量级参考:CPU int8 ~8 块/秒(约 15 小时),GPU ~1800 块/分钟 (约 1 小时,瓶颈转移到 PDF 解析)。 ## 8. GPU 部署(可选但强烈推荐) ### 8.1 宿主机驱动(Secure Boot + Blackwell 适用,其他架构同理) 1. RTX 50 系(Blackwell)**必须用 NVIDIA open 内核模块**——闭源模块能加载 但 `nvidia-smi` 报 "No devices were found",dmesg 有 `NVRM: ... requires use of the NVIDIA open kernel modules`。 2. Secure Boot 开启时,DKMS 自编译模块会被拒(`modprobe: Key was rejected by service`)。解决:装 Canonical 签名的 open 模块包: ```bash sudo apt install linux-modules-nvidia-<驱动系列>-open-$(uname -r) ``` 3. **给每个已安装内核都装**(`ls /lib/modules/` 看一遍)——GRUB 默认启动 最新内核,漏装的话重启后 GPU 失效。 4. 抑制 DKMS 未签名模块抢佔(updates/dkms 目录 depmod 优先级更高): ```bash echo 'AUTOINSTALL="no"' | sudo tee /etc/dkms/nvidia.conf ``` 5. 持久化: ```bash printf 'nvidia\nnvidia_uvm\nnvidia_modeset\n' | sudo tee /etc/modules-load.d/nvidia.conf sudo nvidia-smi -pm 1 ``` ### 8.2 容器内使用 GPU 方式 A(推荐,新容器):装 `nvidia-container-toolkit`,容器加 `--gpus all`。 方式 B(已有 privileged + /dev 挂载的容器,无需重建): 从宿主机拷 3 个库进容器(都在 /usr/lib/x86_64-linux-gnu/,版本必须与 宿主驱动一致),做 `.so.1` 软链 + ldconfig: ``` libcuda.so.<驱动版本> libnvidia-ml.so.<驱动版本> libnvidia-ptxjitcompiler.so.<驱动版本> ← 最容易漏! ``` **libnvidia-ptxjitcompiler 必拷**:预编译 kernel 普遍只到 sm_86/90, Blackwell(sm_120) 走 PTX JIT,缺它就报 `cudaErrorJitCompilerNotFound`。 注意 `cuInit()==0` 不代表可用——必须真正跑一次推理验证。 ### 8.3 kb 的 CUDA 运行库 ORT-GPU 需要 CUDA 12 + cuDNN 9 运行库。从 pip 包提取最省事: ```bash pip download -d /tmp/nvwheels \ nvidia-cublas-cu12 nvidia-cuda-runtime-cu12 nvidia-cudnn-cu12 \ nvidia-cufft-cu12 nvidia-curand-cu12 nvidia-cusparse-cu12 \ nvidia-nvjitlink-cu12 nvidia-cuda-nvrtc-cu12 nvidia-cuda-nvcc-cu12 cd /tmp/nvwheels && for w in *.whl; do unzip -qo "$w" 'nvidia/*/lib/*'; done mkdir -p third_party/cuda-libs cp -a nvidia/*/lib/*.so* third_party/cuda-libs/ ``` > 国内镜像下载大 wheel 容易超时,可先 `pip download` 拿 URL,再用 > `xargs -P4 curl -C - -O` 并行断点续传。 然后 `config.json` 的 `model.providers` 加上 `"CUDAExecutionProvider"` (放第一位,CPU 自动兜底),重新 `kb index` 即可。 ## 9. 定时增量索引(可选) 推荐用一个包装脚本做 cron 入口(无变化时完全静默,有变化才输出一行 摘要——适合消息推送类 cron): ```bash #!/bin/bash # kb-nightly-index.sh cd /path/to/kb-cpp || exit 1 # 日志只留 7 天 find logs -name "kb_*.log" -mtime +7 -delete 2>/dev/null # 每月 1 号压实向量索引(回收软删除的 tombstone, 保持图紧凑) if [ "$(date +%d)" = "01" ]; then ./build-gpu/kb rebuild-hnsw >/dev/null 2>&1 \ && echo "kb 月度向量索引压实完成" \ || echo "kb 月度 rebuild-hnsw 失败" fi out=$(./build-gpu/kb index 2>&1) # 无变化: kb 输出 "nothing to do." 且无 done 行 —— 正常静默退出 echo "$out" | grep -q "nothing to do" && exit 0 done_line=$(echo "$out" | grep "done\." | tail -1) [ -z "$done_line" ] && { echo "kb 夜间索引异常,请检查 $(ls -t logs/kb_*.log | head -1)"; exit 0; } files=$(echo "$done_line" | grep -oE 'files=[0-9]+' | head -1 | cut -d= -f2) added=$(echo "$done_line" | grep -oE 'chunks_added=[0-9]+' | cut -d= -f2) total=$(echo "$done_line" | grep -oE 'collection_total=[0-9]+' | cut -d= -f2) [ "${files:-0}" -gt 0 ] && \ echo "kb 夜间增量索引: 更新 ${files} 文件 (+${added} 块), 当前共 ${total} 块" exit 0 ``` crontab: ```cron 0 0 * * * /path/to/kb-nightly-index.sh ``` ## 10. Web 服务(kb serve) 常驻 HTTP 服务:模型加载一次,之后每次查询 **<50ms**(GPU)。内置精美 单文件网页 UI(搜索/筛选/历史/登录),无需任何前端构建工具。 ### 本地直接运行 ```bash kb serve # 默认 8090,认证与端口来自 config.json 的 serve 段 kb serve --port 8091 # 临时换端口 ``` `config.json` 的 `serve` 段: ```json "serve": { "port": 8090, "user": "kb", "pass": "你的密码" } ``` 环境变量优先:`KB_SERVE_PORT` / `KB_SERVE_USER` / `KB_SERVE_PASS`。 `user` 留空则关闭认证。浏览器访问 `http://:8090`。 ### Docker 部署(推荐) 多阶段 Dockerfile 从源码完整构建;运行时镜像只含二进制 + ORT + CUDA 运行库,`--gpus all` 标准 GPU 接入。 ```bash cp .env.example .env # 或直接写: KB_SERVE_USER=kb / KB_SERVE_PASS=... vi docker-compose.yml # 核对四个 volume 的宿主机路径 docker compose up -d --build ``` compose 挂载(按你的环境改路径): | 容器内 | 内容 | 说明 | |---|---|---| | /opt/kb/store | sqlite + hnsw 索引 | 可与其他容器共享同一份库 | | /opt/kb/models/bge-m3-onnx | bge-m3 ONNX | 只读 | | /home/rk3588/share | 文档根 | 只读;**路径必须与库内固化的 roots 一致** | | /opt/kb/config.json | config.docker.json | 容器路径版配置 | **索引热重载**:serve 会监视 hnsw 文件的修改时间,外部(如另一个容器 的夜间任务)更新索引后,下一次查询自动重载新向量,无需重启服务。 ### API ``` POST /api/login {"user","pass"} → 成功后设 kb_session cookie(HttpOnly, 7天) POST /api/logout 清除会话 GET /api/status → {"chunks","files","roots","model_dir"} GET /api/search?q=关键词&k=8&kind=doc&path=子目录 → {"q","ms","results":[{"score","path","page","kind","text"}]} GET /api/file?path=结果中的path → 原文(PDF 浏览器内打开, 支持 #page=N 定位; 代码/文本 text/plain; Office 下载) GET /view?path=结果中的path → Markdown 渲染阅读页(marked.js 客户端渲染, GitHub 风格暗色排版; 非 md 自动转跳原文); 页面内可切"源码" GET /marked.js → 内嵌 marked v12 库(浏览器缓存 1 天) GET / → 网页 UI(无需认证, 数据接口受保护) ``` 认证双通道:网页端 cookie 会话(登录表单,无浏览器原生弹窗)+ HTTP Basic(curl/API 消费者)。`/api/file` 有路径穿越防护(canonical 化后必须 仍在 roots 内,符号链接逃逸同样拦截)。 ## 11. 日常操作 ### 搜索(最常用,每次约 1 秒) ```bash kb search "电机扭矩校准" # 中文语义搜索 kb search "orbbec force ip" # 英文查询 kb search "PDO 同步" -k 10 # 返回前 10 条(默认 8) kb search "配置" --kind doc # 只搜文档 kb search "配置" --kind code # 只搜代码 kb search "PTP" --path ptp-deploy # 限定路径子目录(匹配 relpath) kb search "标定" --json # JSON 输出(score/path/page/kind/text) ``` - 中英文混合、跨语言同义都能匹配(bge-m3 多语言模型) - `--kind` / `--path` 过滤后若不足 k 条,会自动加深候选窗口重试 - 结果含相关度分数(0~1,内积相似度)与 PDF 页码 ### 索引维护(一般交给夜间任务,无需手动) ```bash kb index # 增量:按 mtime+size 只处理变化文件 kb index --full # 全量重建(约 1 小时 GPU,慎用) kb index --path workNote/电机 # 只索引某路径前缀 kb rm workNote/某目录 # 按路径前缀从索引移除 kb rebuild-hnsw # 从已存文本重建向量(不重新解析文件, # 换模型/修复索引时用,约 1 小时 GPU) kb status # 统计:块数/文件数/模型/库路径 ``` ### 配置修改(改完直接生效) 配置文件 `config.json`(与二进制同目录,或 `KB_CONFIG` 环境变量指定): - 加索引目录:`roots` 数组追加路径。注意:**一个库的 roots 集合是固化 的**,首次 index 后改 roots 会被拒绝(防止身份键错乱);换目录请换新库 (改 `store.db_path`/`hnsw_path`)或删库重建 - 排除目录:`exclude_dirs`(按目录名匹配,如 third_party、node_modules) - 换模型/量化版本:改 `model.onnx_dir` 后必须 `kb rebuild-hnsw` (模型指纹校验会自动拦截混用,见设计要点) ### 故障处理 | 现象 | 处理 | |---|---| | 提示 store inconsistent | `kb rebuild-hnsw` 修复 | | 提示 model/tokenizer changed | `kb rebuild-hnsw` 或换新库 | | 提示 roots changed | 换新库(见上文) | | 搜索结果明显变差 | 先 `kb status` 对数,再 `kb rebuild-hnsw` | | 精确词搜不到 | `kb rebuild-fts` 后重试(正常由增量自动维护) | | 问答提示 LLM 不可用 | `docker ps` 看 kb-llm 是否运行;`docker logs kb-llm` | 配置查找顺序:`KB_CONFIG` 环境变量 > 二进制旁的 `config.json`。 ## 12. 混合检索(语义 + 关键词) 搜索默认走混合模式:bge-m3 稠密向量(语义泛化)+ SQLite FTS5 trigram 关键词索引(精确命中),两路候选用 RRF(Reciprocal Rank Fusion)融合。 - 型号、错误码、函数名、中文短语等精确词不再被语义检索带偏 - trigram 分词对任意语言做子串匹配,无需中文分词器;关键词 token 需 ≥3 字符(更短的由语义路兜底) - 索引时自动同步维护 FTS 表;旧库首次 index 自动引导重建 (12 万块约 9 秒);手动重建:`kb rebuild-fts` - 带 kind/path 过滤且融合结果不足 k 条时,自动回退自适应稠密检索 ## 13. 实时索引(kb watch) ```bash kb watch [--interval 30] # 常驻轮询增量索引,变化秒级进索引 ``` - 每个周期尝试获取写锁(flock LOCK_NB):拿不到(别人在写)就跳过, 与手动 index / 其他容器天然互斥 - 无变化时完全静默(安静模式),有变化输出增量摘要 - Ctrl-C / SIGTERM 优雅退出。生产部署为 kb-indexer 容器(compose 内置) ## 14. RAG 问答(本地 LLM) "问答"模式:混合检索取 top-N 上下文 → 本地 Qwen3-8B (llama.cpp server,GGUF 量化,GPU 推理)→ SSE 流式回答,全程不出机器。 - `POST /api/ask {"q": "问题"}` → SSE 事件流:先 `citations`(引用来源), 随后逐 token `{"t": "..."}`,错误为 `{"error": "..."}` - 网页端:流式 markdown 渲染 + 引用卡片(点击打开原文) - compose 的 llama 服务:llama.cpp 针对 RTX 5060Ti(sm_120) 源码编译, 16k 上下文,KV q8_0 量化省显存;kb-web 经 compose DNS `http://llama:8080` 访问,不对公网暴露 - config.json `llm` 段:base_url / model / max_tokens / timeout_s / rag_chunks - 显存预算(16GB 卡):bge-m3 ~2.3G×2 + 8B q4 ~5G + 16k KV ~1.6G - Qwen3 注意:请求带 `chat_template_kwargs: {enable_thinking: false}` 关闭思考模式;模型仍会吐空 think 标签,kb-web 在 SSE 流头部自动剥离 ## 15. 用户注册(多账号) 登录页底部"注册"入口,自助注册后即可使用(局域网开放注册)。 - 账号存于 `store/accounts.sqlite`(独立于索引库),密码 PBKDF2-HMAC-SHA256(10 万次迭代 + 16 字节随机盐,恒定时间比较, 未知用户也跑一次哈希防时序探测);SHA-256/HMAC/PBKDF2 为内置实现 (third_party/sha256,FIPS 180-4 向量验证过) - 内置管理员 = 环境变量 KB_SERVE_USER/PASS(不可被注册占用), 注册的账号与之并列,Web 登录与 Basic Auth 两个通道都认 - 用户名规则:2-24 字符,字母/数字/_-. /中文;密码 6-128 字符 - `POST /api/register {user, pass, pass2}` → 200 自动登录(下发会话 cookie);409 用户名占用;400 校验失败(返回中文错误消息) - 删除账号:`sqlite3 store/accounts.sqlite "DELETE FROM accounts WHERE username='xxx'"`(kb-web 下次查询即生效,无需重启) --- # 设计要点(踩坑记录) 1. **sentencepiece ↔ tokenizer.json 的 token id 偏移**:bge-m3 的 sentencepiece.bpe.model 词表 `=0,=1,=2`,而 HF tokenizer.json (ONNX 模型训练时用的)是 `=0,=1,=2,=3` —— 所有常规 piece id 差 1。`embedder.cpp` 已在 SPM 编码后对 id>=3 统一 +1。 症状:索引能建、部分搜索看似正常(整个索引在错误空间里自洽),但真正 相关的文档搜不出来、分数挤在窄带里。任何 tokenizer/量化变更都必须 全量重建索引,不能混用空间。 2. **显存 OOM 与自适应子批**:attention 激活 ~ B·heads·L²·4B,长序列大批次 会爆 16GB 显存。embedder 按 `sub = min(B, 15e6/L²)` 自动拆子批。 GPU 上用 fp32(int8 动态量化在 CUDA EP 上支持差)。 3. **存储安全三件套**(索引损坏事故后的加固): - search/status 以只读打开 Store,析构不写索引 - 保存走原子写(.tmp + rename),中断不会留下半截文件 - index/rebuild-hnsw/rm 取独占文件锁(.lock),防并发写坏库 - 已有索引加载失败时大声报错(提示 `kb rebuild-hnsw`),绝不静默换空索引 - sqlite 是文本真源,hnsw 是可重建缓存 —— 最坏情况 `kb rebuild-hnsw` 可从文本重嵌入恢复 4. **C++ ORT CUDA EP 接入**:generic `AppendExecutionProvider("CUDA")` 在 GPU 构建里不认 "CUDA" 名字,要用 C API `SessionOptionsAppendExecutionProvider_CUDA(opts, &OrtCUDAProviderOptions)`; providers_cuda.so 是运行时 dlopen,链接要用 `-Wl,--disable-new-dtags` (DT_RUNPATH 对 dlopen 不传递,DT_RPATH 才传递)。 5. **SIGPIPE**:`kb search | head` 这类管道截断会杀进程,若恰好杀在保存 索引的过程中就会损坏索引。main 里已 `signal(SIGPIPE, SIG_IGN)`。 6. **config.json 热更新**:CMake 用 `file(CREATE_LINK ... SYMBOLIC)` 把根 config 软链进 build 目录;用 configure_file COPYONLY 会产生过时副本, 配置改了不生效。 ## 文件结构 ``` include/kb/ 头文件 (config/embedder/extractor/chunker/store/indexer/searcher) src/ 实现 (抽取器分 pdf/office/text 三个文件) models/ sentencepiece.bpe.model config.example.json 配置模板 (复制为 config.json 后按本机修改) fetch_deps.sh 第三方依赖下载 (含国内镜像回退) build_thirdparty.sh 依赖编译 build.sh CPU 构建 smoke_test.sh 冒烟测试 tests/ 开发期诊断工具 (embedding 一致性/暴力召回对比, 手动 g++ 编译) ```