Cerebras 公开公司知识库搭法:不需要任何人改使用习惯,资料留在原处AI自己去取

上线三个月,每天被提问超过 15000 次;四路打分、讨论串蒸馏、排名融合的具体参数都写了出来。
作者:Cerebras07-18 · 12 分钟⚑ 方法教程
🎧 听播客版0:00 / --:--
0:00 / --:--
深度 · 小互解读
一分钟速览
  • Cerebras 的三名工程师公开了公司内部知识库 Cerebras Knowledge 的搭法,上线三个月,每天被提问超过 15000 次,提问方包括员工、自动化脚本和 Agent。
  • 它不要求任何人改变习惯:资料继续留在 Slack、代码仓库、文档系统里,系统去每个平台取数,全部落进同一张 Postgres 表。
  • Slack 是最难啃的源。同一批候选会被四种打分方式同时检索:全文匹配、向量匹配、稀有词权重、时间衰减,各补各的盲区。
  • 聊天记录不直接拿去做向量。先由模型蒸馏成「一句话问题+摘要+结论+涉及的系统和代码符号」的统一结构,再嵌入。
  • 六个检索器各出一份排名,用倒数排名融合合成一份,常数取 60,效果是「几份榜单都靠前」的文档赢过「一份榜单第一」的文档。
开篇 · 上线三个月

每天 15000 个问题,问的人里还有 Agent

Cerebras 的三名工程师最近在公司博客上把内部知识库 Cerebras Knowledge 的搭法整个拆开公开,从数据怎么采集一路讲到答案怎么排序,每一层用了什么参数都写在里面。

这套系统上线三个月,每天被提问超过 15000 次。提问的不只是员工,还有自动化脚本和 Agent。

触发它的场景很多公司都熟悉:Cerebras 的团队分布在机房运维、芯片设计、硬件、训练、推理、云平台等方向,每年进来几百个新人,沟通频道里被同样的三类问题填满:某个东西在哪里、某件事谁最懂、这个名词是什么意思。

◆值得看的地方在于它不是演示级的 RAG 搭建教程:日均 15000 次查询跑在真实的公司数据上,而每一层的取舍和阈值都被写了出来,包括 3072 维向量、稀有词门槛 IDF 大于等于 4.0、burst 合并后至少 200 字符、排名融合常数取 60、重排后只留 10 条证据。
Cerebras Knowledge 六层架构总览
原图标注为英文,六层自上而下依次是:
① SOURCES 数据源,Slack、Wiki、代码、事故记录 ② DISTILLATION 蒸馏,模型把原始内容抽成统一结构 ③ EMBEDDINGS 向量,存进 Postgres 的 pgvector,3072 维,用 HNSW 这种索引结构快速找到最接近的向量 ④ RETRIEVAL 检索,六份排名并行 ⑤ FUSION + RERANK 融合与重排,先用倒数排名融合再交给小模型重排 ⑥ SYNTHESIS 合成,给出带引用的答案。来源:Cerebras
设计决定 · 数据放在哪

把资料全搬到一个地方,为什么从来没成过

在公司内部找资料难,难点不在搜索本身,在于资料天生就是散的。每过一个季度,总有人提出同一个「绝妙方案」:以后所有信息都记录到一个平台上,做成唯一的真相源。这个设想在实践中几乎没有成功过。

原因是信息在哪里产生方便,就会在哪里产生:文档里的建议修改、Slack 里的讨论串、GitHub 里的代码引用、Jira 里的状态字段。这些平台各自为自己的场景打磨了很多年。在 Google Docs 里讨论一个 pull request 会是很糟糕的体验。

所以 Cerebras 的第一个决定,是设计一套几乎不要求人改变现有习惯的系统。采集这一侧的做法,就是直接去每个平台把数据抽出来。

搬家方案
  • 要求大家以后都写到统一平台
  • 每个人多一道工序,执行几周后回潮
  • 历史资料还留在原处,两边都不全
连接器方案(他们的选择)
  • 人继续在原来最顺手的地方写
  • 每个源写一个连接器去取数
  • 取回来的东西统一格式,落进同一张表

整套东西的核心,是一张表

这个知识库对外提供三件事:一个存内部数据的地方、一个查这些数据的地方,以及一层做身份认证、权限控制、审计和统计的东西。

核心是一张 Postgres 表,里面同时放着向量、原始摘要和元数据,来源不限。系统持续把公司各处的数据灌进来,维持一个随时可查的状态。数据接口被刻意做得简单:不管来自 Slack 讨论串还是芯片设计用的网表文件,都落进同一张向量表,进了这张表就能被同一套接口查到。

每个数据源只需要定义三件事:这是什么数据、怎么连上去、多久取一次。取回来生成的每一行,字段结构完全一样。

一个源一个连接器,同一张表同一套接口
Slack 频道
Wiki / Confluence
代码仓库
网表文件
内部文档
团队自建数据库
→
EMBEDDINGS(一张表)
document 归一后的正文
embedding 3072 维向量
metadata 频道、作者、系统
source + 时间戳
MCP 工具网页界面Agent
来源:Cerebras(依原文架构图重绘为中文)。这张表是后面所有机制的公共地基:加一个新数据源,查询侧不用改任何东西。

这个设计还留了一个口子给别的团队。有些团队本来就有自己的数据库,不想为了进知识库把数据挪到 Slack 或文档系统里。做法是把这类源当成插件脚本:团队提一个 pull request,交一个小的 Python 模块,负责从自己的系统读数据、吐出符合那张表结构的行,再配一条数据源记录。只要写进同一个库、用同一套字段,剩下的整条链路一行都不用动。

Slack · 混合检索

Slack 里最难的事,是分不清哪句话有用

Slack 是他们最看重、也最难处理的数据源,公司里最新的工程讨论都发生在那里。一开始他们试过最省事的办法:把原始文本直接做成向量去搜。很快发现单靠向量捞不全。

问题出在聊天记录本身的三个特点。第一,信息密度差得极远,「好的收到,谢谢」和一整段讲内核实现的解释,在系统眼里都只是一条消息。第二,消息长短不一,而短消息在向量相似度上经常压过更长、更详细的那条。第三,一条消息的含义往往取决于它前后的对话。

这三个特点合起来意味着什么

你在群里搜「恢复卡在清单加载之后」。真正解决过这个问题的那条回复,可能一个原词都没用,说的是「checkpoint 在 NFS 挂载点上卡住了」;而字面最像的那条,是八个月前一个已经作废的老办法;至于「好的收到,谢谢」,它和几乎所有问句都有点像。

他们的解法是让同一条内容同时被几种检索方式覆盖,每一种补上别的短板。同一个查询、同一批四条候选,切换打分方式,谁被抬起来、谁被压下去就变一次(数据取自原文示例)。

查询:restore hangs after manifest load
(恢复卡在清单加载之后)
按字面对词。工程师直接粘贴一段报错的时候,字面对上就是最硬的证据,语义再像也不该排到它前面。代价是换了一套说法讲同一件事的那条,一个词都对不上。
按意思对。提问的人说「恢复卡住」,回答的人说「checkpoint 在 NFS 挂载点上停住」,两边可能没有一个共同的词,这一层专门接这种情况。代价是寒暄句在向量空间里离所有问句都不远。
按词有多罕见给权重。一条很短但用了生僻配置项名字的消息,值得排上去;而「好的收到,谢谢」一旦按词的稀有程度算分,就接近于零。
按时间打折。两条内容都能回答同一个问题,八个月前那条描述的基础设施可能已经不存在了。在相关度差不多的时候,新的那条赢。
2 周前 · 讨论串
Checkpoint 在 NFS 挂载点上卡住,把 CKPT_PREFETCH 设成 4。
没有一个共同的词 换了说法,意思对上 CKPT_PREFETCH 极罕见 两周内,平局时它赢
1 天前 · 单条消息
ERR_MANIFEST_TIMEOUT:恢复卡在清单加载之后。
原词逐字命中 错误码属于罕见词
3 小时前 · 单条消息
好的收到,谢谢,我试试。
假邻居,跟谁都有点像 全是常见词,得分接近零
8 个月前 · 讨论串
恢复卡在清单加载之后,改用 LEGACY_FETCHER=1。
字面几乎一模一样 8 个月,按时间打折
实心底=被这种打分抬起来 虚线灰=被压下去 白底无标签=这种打分对它没有明显影响 数据取自原文示例。来源:Cerebras
这个组件说明了什么

切一遍四个标签会发现,被抬起来和被压下去的从来不是同一批。那条两周前、换了一套说法、带着罕见参数名的讨论串,恰好是真正解决问题的答案,可它在全文匹配这一栏里一个词都没对上,只有向量匹配、稀有词权重、时间衰减这三栏认它。反过来,八个月前那条字面几乎一模一样的旧答案,在全文匹配里被抬得最高,到时间衰减这一栏才被打折。任何一种打分单独用,要么漏掉真答案,要么把过期答案排到前面。所以四种打分各自出一份排名,到查询时再合并。

Slack · 蒸馏与补捞

聊天记录不直接拿去做向量

原始 Slack 文本一落地就能被关键词搜到,因为他们在 Postgres 里对原文维护了一个全文索引。但要让向量检索真的好用,还得多做一步加工。

这一步叫蒸馏:让模型读完整条讨论串,抽出四样东西,一句工程师真的会拿去搜的问题、一段简短摘要、最后的结论,以及过程中提到的系统和代码符号。被做成向量的是这四样东西,原始的逐字聊天记录不进向量库。实测把讨论串归一成统一格式之后,准确率有明显提升,多出来的元数据也给语义匹配提供了更多线索。

一条讨论串,三段加工
① 原始讨论串
09:14 Maya大集群上恢复卡在清单加载之后,小规模跑没问题。
09:17 Owen128 个分片能复现,日志停在缓存预热之前。
09:18 Sam我笔记本一到周一也卡。
09:21 Maya设成 CKPT_PREFETCH=4 就跑完了,默认值对 NFS 挂载点来说太高。
→
② 抽成结构件
question为什么恢复会卡在清单加载之后?
summary大规模恢复停在缓存预热之前。
resolution对 NFS 挂载点设 CKPT_PREFETCH=4。
systemscheckpoint 恢复、NFS
code_refsCKPT_PREFETCH
→
③ 一行向量记录
归一后的正文问题+摘要+结论+系统+代码符号
sourceslack_thread
source_idthread_8f42
embedding3,072 维
metadata频道+参与者+时间
来源:Cerebras(依原文示意图重绘为中文)。注意第三条消息「我笔记本一到周一也卡」是离题内容,它出现在原始记录里,但没有进入抽出来的结构件。
数据怎么实时进来:Socket Mode 与整串重拉

采集侧他们在工作区装了一个 Slack 机器人,跑在 Socket Mode 下:Slack 通过一条长连接把每条消息事件推过来,不用反复轮询 Web API,也就不会撞上接口调用频率限制。

事件一到就立刻回执,用稳定的事件 ID 去重,然后标记给入库程序。入库这一步有个容易忽略的设计:它不会孤零零地存下这条新消息,而是先定位这条消息属于哪条讨论串,再从 Slack 接口把整条对话重新拉一遍,包括最初那条和后面所有回复,然后把整串当作一行写回去。所以一条新回复会触发父消息和所有同级回复被重新拉取,存下来的正文、参与者名单和最后活跃时间,永远反映的是完整对话的当前状态。

另外,每个 Slack 频道在系统里都是一个独立的数据源,采集频率可以分别调。某个团队可以让自己那个高频的故障频道更新得更勤一些。

长讨论串里,被摘要漏掉的那条消息

做到这一步,Slack 的检索已经可用,但他们反复撞上同一个毛病:长讨论串里那些重要的消息,不一定能在整串的摘要里留下痕迹。

补救办法叫 bursting。一段 burst 指的是同一个人连着发的几条消息。他们把每段 burst 单独做成向量,并且在前面拼上整串的话题当上下文。这样一来,那种答案藏在某条跑题分支里、用词从没进过整串摘要的情况,也能被单独搜到。

为了不让低价值内容涌进数据库,每段 burst 要过一道加权打分,达标才做向量,判据有三条:含有一个在全公司语料里足够罕见的词,稀有度门槛是 IDF 大于等于 4.0;合并之后至少 200 个字符;这段里有消息被人加了表情回应,算一份来自同事的认可。

五条消息,两段 burst,一段被拦下
作者 A短消息
作者 A补一句
作者 B一段技术细节
作者 B给出结论
作者 C收到
→
BURST 01 被拦下
太短,全是常见词,没有人加表情回应,三条判据一条没过。
BURST 02 入库
够长,含罕见词,有人加了表情回应,做成向量,和整串那条记录并排存在同一张表里。
来源:Cerebras(依原文示意图重绘为中文)。作者 C 只有一条「收到」,自成一段,同样过不了门槛。同一条讨论串因此在库里会有两种粒度的记录:整串一条,加上若干段达标的 burst。
代码库 · 切块与增量

40GB 的代码库,怎么保持是最新的

要不要给代码库做向量,他们一开始是犹豫的。有了 Claude Code 这类命令行工具之后,给代码建向量索引看着有点反直觉,感觉有 grep 就够了。跟同行聊过、又读了 Cursor 关于大型代码库语义检索的结论之后,他们决定试一试。

真正的顾虑其实是另一件事:公司内部仓库很多,有的单个超过 40GB,怎么用可接受的代价让索引一直是最新的。

最后落在 CocoIndex 上,一个专门做代码库向量化的开源框架。切块规则按语言的语法边界来,从粗到细依次尝试:先试类这种高层边界,如果切出来的块还是太大,退到方法边界,再不行退到更小的代码块。同一个文件因此会产出好几个粒度的向量,比如文件级一条、函数级各一条。

每 N 个 token 一刀切
  • 切点落在哪儿全看长度
  • 一个函数可能被从中间劈开
  • 切出来的块常常缺头少尾,读不出在干嘛
按语法边界从粗到细
  • 先按类切,太大退到方法,再退到条件块和循环块
  • 每一块都是一个完整的语法单位
  • 同一个文件产出多个粒度,粗细都能被搜到
按语法边界切块的示例代码
原文的切块示例,标注为英文。左侧代码里浅色底标出的是被识别出的语义块,行尾标注写明每一块在第几层被切出来:先试 class,进到 LoadManifest()、WarmShardCache()、Restore() 三个方法,方法内还太大的再退到 if 和 for 这一级,末尾的私有字段区单独成块。右侧两个方块是上面那张对照表的两种切法(01 NAIVE WINDOWS 每 N 个 token 一刀切、02 LANGUAGE-AWARE 按语法边界切,图中选中的是后者);底部图例里 SEMANTIC NODE 指语义块,FIXED SPLIT 指定长切点。来源:Cerebras

更新这一侧是这套方案真正省力的地方。CocoIndex 把同步用的元数据也存在 Postgres 里,每次提交只重新做变动过的那些代码块,不用整个仓库重算。同步状态和向量存在同一个库里,这一点对他们来说尤其合适。

随着接入的代码库变多,仓库接入这件事被改成了配置文件,各团队自己提交,可以按文件路径设白名单和黑名单。

查询侧 · 选工具与取证

一个问题进来之后,系统做了什么

每次收到提问,系统先跑一轮很短的规划:由模型判断这个问题可能需要用到哪些工具、哪些数据源,然后把选中的工具并行发出去。

规划 → 并行取证 → 合成
规划PLANNER
search跨 Slack、Wiki、代码等所有源的统一向量检索,内部已经合并和重排过
search_slack直接查 Slack
search_code对源码仓库跑 ripgrep,命令行里的高速全文搜索工具
subsystem_index按文件生成的逐份摘要
recent_prs与问题相关的近期 pull request
who_knows在某个主题上有过实际贡献的人
合成答案+引用
来源:Cerebras(依原文流程图重绘为中文)。规划这一步看的是一份压缩过的索引说明:有哪些 project、每个 project 下面有哪些源、每个源擅长回答什么问题。它据此选出工具,执行层并行发出,把回来的结果归一成统一的证据格式,再交给最后一轮模型合成答案。

MCP 那一侧,把工具直接交给 Agent 自己编排

网页界面里,上面这条链路是端到端跑完的:规划选工具,执行层并行取证并归一成带分数、时间和来源提示的统一证据,最后一轮模型据此产出带引用和注意事项的答案。对用户来说就是提个问题拿到一个答案。

MCP 集成走的是相反的路子:把检索能力拆成一个个直接可调的工具暴露出去,而不是藏在一个「帮我回答这个问题」的大接口后面。这些工具被刻意做得简单,尽量不掺模型,客户端因此可以查得又快又便宜。每个 MCP 工具对应一件最底层的检索动作,输入输出窄、有结构、稳定,任何客户端或 Agent 都能直接调。大部分工具只跑一条查询链路,按意思检索、按字面检索或者直接跑 ripgrep,套一层轻量打分,然后把原始证据行返回。

这样一来,编排的活儿交给 Claude Code 这类支持 MCP 的 Agent:调哪些工具、按什么顺序调、结果怎么拼成最终答案或者一次代码修改,都由它自己决定。检索层不依赖这些模型判断也能照常提供服务。

排序 · 排名融合

六份排名,怎么合成一份

一份检索结果排在最前面,可能只是因为它和提问用词撞了,实际回答的是另一件事。更麻烦的是,六个检索器给出的分数互相不能直接比:向量的余弦相似度和全文检索的相关度,根本不是同一把尺子。

他们的做法是先不看分数,只看名次。这个算法叫倒数排名融合,缩写 RRF:一份文档每上一份榜单,就给它加一次分,加的量是 1 除以(60 加上它在这份榜单里的名次),权重默认取 1.0,那个 60 是平滑常数。

六份榜单,各自的前五名
向量
1 CKPT_LOADER.CC
2 CLASS: CHECKPOINTLOADER
3 WIKI: 检查点格式
4 THREAD: NFS 卡住
5 INC-82: 恢复变慢
全文
1 WIKI: 检查点格式
2 CKPT_LOADER.CC
3 RUNBOOK: NFS 挂载
4 INC-82: 恢复变慢
5 THREAD: 缓存预热
讨论串摘要
1 THREAD: NFS 卡住
2 THREAD: 缓存预热
3 CLASS: CHECKPOINTLOADER
4 WIKI: 加载卡住
5 INC-82: 恢复变慢
关系图
1 CLASS: CHECKPOINTLOADER
2 MANIFEST_PARSER.H
3 RUNBOOK: NFS 挂载
4 THREAD: NFS 卡住
5 WIKI: 检查点格式
Wiki 向量
1 WIKI: 检查点格式
2 WIKI: 加载卡住
3 RUNBOOK: NFS 挂载
4 INC-82: 恢复变慢
5 CLASS: CHECKPOINTLOADER
Slack 全文
1 THREAD: NFS 卡住
2 THREAD: 缓存预热
3 BURST: 超时参数
4 INC-82: 恢复变慢
5 CLASS: CHECKPOINTLOADER
合并后的总榜
1 CLASS: CHECKPOINTLOADER0.0792
2 INC-82: 恢复变慢0.0776
3 WIKI: 检查点格式0.0640
4 THREAD: NFS 卡住0.0640
5 THREAD: 缓存预热0.0476
6 RUNBOOK: NFS 挂载0.0476
来源:Cerebras(依原文交互图重绘为中文,条目名和分数为原文数据)。高亮的那份文档在六份榜单里进了五份。

拿总榜第一名算一遍就能看清这个机制。CLASS: CheckpointLoader 在六份榜单里进了五份,名次分别是第 1、2、3、5、5:

CLASS: CheckpointLoader 关系图 第 1 名 1 / (60 + 1) = 0.01639 向量 第 2 名 1 / (60 + 2) = 0.01613 串摘要 第 3 名 1 / (60 + 3) = 0.01587 Wiki 第 5 名 1 / (60 + 5) = 0.01538 Slack 第 5 名 1 / (60 + 5) = 0.01538 总分 0.0792 → 排第 1 INC-82: 恢复变慢(一次第一都没拿到) 第 4、5、5、4、4 名 总分 0.0776 → 排第 2
60 这个常数在做什么

把 60 加在名次前面,第 1 名和第 5 名的得分只差 6%(0.01639 对 0.01538)。如果不加这个常数、直接取名次的倒数,第 5 名就只剩第 1 名的五分之一。所以 60 的作用是把「名次靠前」这件事的收益压平,让共识压过单一强票:一份在好几个检索器里都排在前列的文档,赢过只在其中一个里拿第一的文档。上面那个 INC-82 就是例子,它一次第一都没拿过,靠稳定进入五份榜单排到了第二。

名次带来的得分,加不加这个常数差多少
加上常数 60:第 5 名只少 6% 不加常数:第 5 名只剩五分之一 高 低 1 5 10 文档在某一份榜单里的名次 这一名的得分
纵轴是一份榜单里某个名次能换来的得分,两条线都按各自第 1 名归一。实线是实际采用的 k=60,第 1 名到第 10 名几乎是一条平线;虚线是不加常数直接取名次倒数,第 2 名就掉掉一半。来源:Cerebras(公式与常数取自原文,曲线为按该公式绘制)。

合并之后还有三道工序

融合出来的榜单不会直接送去写答案。接下来先把同一个来源被切散的重复块合并回一条,再限制单个文件最多能贡献几条结果,得到一批更分散的前二十名。这二十条连同原始问题一起交给一个小的重排模型,它给每条打一个 0 到 10 分,留下前十条。

融合之后到交出证据,中间还有这几步
STEP 1同一来源被切散的重复块合并回一条
STEP 2限制单个文件最多贡献几条,结果更分散
STEP 3留下前 20 条候选
STEP 4小重排模型逐条打 0 到 10 分,留前 10 条
STEP 5给命中的段落补回前后两段上下文
来源:Cerebras(依原文描述绘制)。前三步做减法和去重,后两步做质量筛选和上下文回填。

排名定下来之后,再把上下文补回来。比如命中的是 wiki 里的某一段,就顺带把它前后两段也拉进来,避免切块时被切散的标题、前置条件和注意事项丢掉。这样交到读者手上的是一段完整的引用,而不是一段缺前提的孤立文字。

走完这一整条,检索这一步交出去的是一包证据:来自不同检索器、经过融合,在来源这一级去过重,针对真正的问题重排过,最后才补上周边上下文。

组织 · 检索范围

搜全公司,反而搜不到有用的

语料越攒越大之后,「什么都搜」这件事很快就不好用了。做编译器的工程师不想在结果里看到机房运维手册,反过来也一样。

他们引入了 project 这一层来解决。一个 project 就是一组数据源的命名包:指定的几个 Slack 频道、几个代码仓库、几个内部数据库和几个文档空间,围绕某个团队或某个项目组织。project 被刻意做得很轻:同一个数据源,比如公司共用的故障频道或者中心平台仓库,可以被多个 project 同时引用,不需要复制一份。

新人入职时会被引导选一个或者建一个跟自己工作最贴近的默认 project,比如训练基础设施、编译器、机房运维。这个默认值存在用户档案里,之后所有查询自动限定在这个范围内。效果是新来的工程师不用先花时间搞清楚哪些 Slack 频道、哪些仓库、哪些文档空间跟自己有关,一上手拿到的结果就已经是相关的。

全公司范围
  • 编译器的问题,结果里混进机房运维手册
  • 相关度靠算法硬扛
  • 新人不知道该信哪一条
限定在一个 project 里
  • 只在这个团队关心的源里检索
  • 相关性由范围先兜住一层
  • 入职选一次,之后自动生效
这套知识库之所以跑得起来,是因为它在信息本来待着的地方就把人接住了,而没有把一切塞进一个僵硬的系统里。 Cerebras,《How we built our knowledge base》
数据一览

全文出现过的具体数字

15000+
每天被提问的次数,提问方含员工、自动化脚本和 Agent
3 个月
上线到现在的时间,已是公司内使用最广的内部工具之一
3,072 维
每条记录的向量维度,存在 Postgres 的 pgvector 里
IDF ≥ 4.0
一段 burst 要入库,至少得含一个这么罕见的词
200 字符
burst 合并后的长度门槛,短于此不入库
k = 60
排名融合的平滑常数,让共识压过单一强票
20 → 10
融合去重后留 20 条候选,重排模型打分后留前 10 条
40 GB+
内部单个代码仓库的规模,只能靠增量重算跟上
✅ 要搭同类系统时,可以逐条对照的八个决定
来源:Cerebras 官方博客《How we built our knowledge base》,作者 Isaac、Daniel、Zenghao。文中数据、参数与示例均出自该文及其配图;架构图与代码切块图为原文配图,其余可视化依原文交互图重绘为中文。Cerebras Knowledge 为公司内部工具,未对外开放。
来源
CerebrasCerebras·查看主材料
↑ 正文结束
✓ 本篇全文免费开放

会员可复制、下载全文 Markdown,还能让你的 AI 订阅和提问。

阅读原文 ↗
📮 新解读第一时间进邮箱

读完觉得有用?留个邮箱,每篇新解读发布就送达,随时退订。