最近我有个特别深的感触同一个算子我们的Agent已经第三次写出“新版本”了。不是模型不给力也不是同学不认真而是Agent根本不知道这段逻辑我们上上个月就沉淀过。新来的同学让它生成它就真的从零开始生成一份朴素实现然后我们又在Review里花半小时讨论怎么优化。直到我把CANN知识库正式接进开发流程事情才真正变顺。这篇文章想聊聊这段时间里我是怎么看待“让Agent少写重复算子”这件事的以及知识库到底该怎么建、怎么用、怎么避免变成一堆没人看的文档。1. 为什么同一个算子会被反复重写Agent的“失忆”问题1.1 一次Real Code Review第三次看到类似的朴素实现事情发生得很平常。某次代码评审我打开一个新提交一眼就看到了熟悉的ElementWise算子逻辑——同样的边界处理同样的Tiling思路甚至循环展开的方式都和我们两周前刚合并的那版几乎一模一样。唯一的差别是变量命名风格不同整体结构稍弱一些。我翻了一下提交记录这已经是最近第三次出现类似实现了。第一次是一个老同学为了快速验证想法写的第二次是在另一个模块里当时没留意到仓库里已有完整实现第三次就是这一次由Agent生成新同学甚至不知道我们内部已经有一版性能更好的实现。我专门把三份实现放在一起对比过功能语义基本一致但性能差异不小。有人用了多核并行有人只写了一个最简单层级的标量循环。坦白讲这三份代码各自都能跑但如果我们每次都从零来一遍优化经验根本没法沉淀。这不是个人能力问题而是信息通道问题团队里已经验证过的答案没有在Agent生成代码的那一刻出现在它面前。1.2 Agent不是不会写而是不知道你已经写过很多同学对Agent有个误解以为它和你聊过几次就默认它“了解咱们项目的历史”。实际上完全不是这回事。Agent生成代码时依赖的是预训练知识加上当前对话窗口里的上下文它没有团队级长期记忆。你在这次对话里告诉它的约定、规范、已有实现换一个新对话就全部归零。所以你会发现一个很常见的现象同一个问题你让Agent连续写三次它给出的方案往往是同一套通用解。因为在它眼里“写一个CANN算子”就是一个从预训练经验里抽取通用模式的过程。它不知道你仓库里已经有一版针对特定AI处理器调优过的实现更不知道那版实现里哪些写法是踩过坑之后才改成现在这样的。我试过在Prompt里把仓库结构、已有代码块、评审意见都塞进去效果有一点但撑不了多久。一旦超出上下文长度或者换一个项目目录Agent又回到“失忆”状态。靠对话记住不是办法必须让“已知知识”变成Agent每次生成前都会主动检查的一部分。1.3 大家为什么愿意接受“重写一件旧东西”更深一层的问题是为什么团队成员宁可让Agent重写也不愿意去仓库里找现成的我观察下来有三个原因。第一现有的代码检索方式太弱。按文件名、按注释关键词去搜只能搜到“名字像”的代码搜不到“语义相同但名字不同”的算子。比如一个叫MaxPooling的算子另一个叫Pooling2D的封装功能相近但你按前者找不到后者。第二老代码可读性差。不少实现是性能优化过程中一版一版改出来的变量名简写多注释几乎没有新人根本不敢碰。与其在原地考古不如让Agent重新生成一份自己能看懂的。第三没有权威标志。没有人明确说过“这段代码就是我们推荐的标准实现”所有人都觉得自己写的那版不算数。既然没有标准答案Agent自然每次都会自创一份。所以这本质上不是技术问题是知识管理问题。只要让答案足够显性、足够可信、足够好找绝大多数重复劳动都能消失。2. CANN算子开发的知识壁垒是Agent容易重复做功的根源2.1 CANN算子开发到底在写什么先对齐一下语境。CANN是对接AI处理器的一套异构计算架构做算子开发的人日常写的不只是“用框架层API把网络算子拼起来”而是往更底层走要实现算子的计算逻辑还要把计算映射到AI处理器的执行单元上。这里面最核心的一件事叫Tiling也就是把大的张量计算切分成适合多核并行处理的小份。切得合理计算单元利用率就高切得粗糙代码虽然能跑但可能大量时间浪费在数据搬运上。除了Tiling还有内存管理、指令排布、流水线设计这些事。我见过不少通用写法在GPU上表现不错换到AI处理器上就慢得出奇。原因往往不是算法复杂度问题而是访存方式、指令并行度和硬件调度方式对不上。CANN开发的知识密度是很高的。同一个算子功能实现可能一百行但把它调得快可能要写几百行且每行改动可能都是一个性能经验的浓缩。2.2 硬件特性是隐性知识Prompt里很难说清Agent的预训练语料里有大量通用GPU、CPU优化经验也有不少CANN公开文档内容。但具体到某个AI处理器上哪些Tiling方式最合适、哪些shape组合容易触发访存瓶颈、什么样的循环展开能提升指令流水效率这些极细节的硬件特性很难从公开资料里学全。这类知识更多存在于一个组织内部的profiling报告、代码Review意见、甚至某次线上事故的排查记录里。比如我们曾经遇到过某个算子在小shape下表现不错但大shape下因为共享内存占用过高导致分配失败。这种经验除非写进知识库否则Agent在生成代码时是完全无感的。Prompt能写多少提示词通用约束可以写但“对某个算子里每个分支该用什么tiling策略”这种细致经验靠Prompt既写不全也会让上下文爆炸。所以结论很直接硬件特性是隐性知识隐性知识必须显性化才能让Agent真正用得上。2.3 高性能写法与可读写法并不总是同一个另外一个让Agent“不敢抄”的原因是很多高性能算子代码本身写得很“难看”。为了一点点性能代码里会频繁出现buffer复用、双缓冲、乒乓操作这类技巧。这些写法从工程可读性上讲远不如直接每步都用临时变量来得清楚。我见过不止一次这种情况知识库里明明有性能很好的实现但Agent检索到之后反而把它“优化”成了一种更简单的写法。问它为什么改它说“看起来原实现里有些重复分配可以简化为退出时统一释放”。理论上没错但实际破坏了对缓冲区的复用策略导致性能断崖式下跌。这让我意识到光把代码存进知识库是不够的还要解释“为什么这段代码要写成这样”。如果知识卡片里只写了代码没有注释Agent读不懂背后的设计意图它就倾向于自以为是地“重构”成更通用的形式。这不是模型能力问题是知识表达方式问题。3. 知识库设计的第一原则让Agent能“查得到、读得懂、敢抄用”3.1 最小知识单元算子卡片我把知识库的最小单元定义成“算子卡片”而不是整篇技术文档。原因是Agent处理结构化信息远比处理一篇散文靠谱。卡片把所有关键信息拆成固定字段它只需要按字段去检索和填充。一张算子卡片至少包含下面这些字段字段说明算子名称规范名尽量与代码中统一命名语义描述做什么计算输入输出是什么典型应用场景在哪些模型/网络里会用到已有实现链接指向仓库中某版本代码性能基线在特定shape/硬件配置下的耗时与利用率适用shape范围该实现验证过的shape区间关键决策点什么条件下用哪种Tiling/访存策略已知限制该实现有哪些坑哪些场景不建议使用生效日期与维护人方便追溯与更新其中最重要、也最容易写烂的是“关键决策点”。它必须写成可执行的规则而不是一句抽象的“注意性能”。3.2 打标签与检索字段决定Agent能不能定位卡片建好了还得让Agent能找到。这一步我踩过不少坑一开始只用全文字段去检索结果Agent经常把“算子语义”和“实现细节”混淆召回结果一排全是相关的但不匹配。后来我们给每张卡片打了一套更细的标签包括算子别名、典型模型层类型、输入张量的维度特征比如4维/5维、是否稀疏、性能瓶颈类型访存密集、计算密集、同步开销大等。检索时先做关键词过滤再做向量相似度排序命中率明显上升。举个例子一个二维卷积算子如果只按“conv2d”标签检索可能搜出十几种变体但加上“输入NCHW”“大通道”“访存密集”这些特征后几张真正匹配的卡片就被顶到最前面。Agent生成时能快速锁定正确的已有实现。3.3 经验区该怎么写很多团队写知识库写着写着就写成“这是某某代码的说明文档”。我后来定了一个规矩每张卡片里可以有一个“经验区”但必须用“如果……那么……”的句式。因为这种可决策规则才真正能引导代码生成。举一个当时我们沉淀的真实例子括号里我改成了通用表达“如果输入张量的最后一个维度小于某个阈值那么把tiling切分轴放在倒数第二个维度这样可以减少连续访存时的跨stride访问否则保持默认切分。”这种写法Agent可以直接作为生成代码时的约束条件。而如果写成“注意访存效率”它是一句废话模型不知道该怎么做。我在这方面交过学费现在宁可多花半小时把经验拆成规则也不要写十条正确的空话。3.4 人工审核机制权威知识不能随意覆盖知识库最怕的是把错的代码当成标准答案。Agent一旦引用了错误实现可能在不知不觉中把性能问题扩散到很多模块。所以我在流程里加了一道人工审核。不是所有人提交的知识都能直接进库必须有固定的责任人确认。每条知识卡都要带版本号和生效日期如果有新实现覆盖旧版旧版不能直接删除而是标记为“历史版本”。这样一旦新版本出现问题还能回溯。这道流程看起来多了一步但实际上避免了非常多的连锁返工。我也把一个原则交给了团队知识库不是代码垃圾桶只有经过Review、性能验证过的实现才有资格进库。4. 从零搭建CANN算子知识库的落地路径4.1 盘点从仓库里找出被重复写的算子和模式我们真正动手做知识库不是先买工具而是先盘家底。把维护的几个仓库拉下来让一位同学做了初步聚类分析按函数名、注释、调用关系去粗粒度归类找出那些“语义相似”的算子实现。不用追求完美。第一次盘点能找出最明显的重复项就够了。我当时安排A同学用表格登记记录每个候选算子的代码位置、实现方式、性能表现、是否有人在维护。这一步虽然土但非常关键它让我们知道“该先沉淀哪些知识”。盘点结果比想象中严重我们维护的约两百个算子中有明显语义重叠的超过三成。知识库如果建起来能直接消除的重复工作非常可观。4.2 入库把历史代码改写为可复用的知识卡入库存什么我建议不是把历史代码直接贴进去而是提炼成一张知识卡。历史代码可能变量命名混乱、有大量临时实验分支直接给Agent看它会产生次优理解。所以我们做了改写。注意是“提炼”不是“重写”。我们保留验证过的核心逻辑和性能特征整理出关键决策点、已验证参数和适用shape范围。改写完成后代码部分仍然链接回仓库里的原实现方便同学看上下文。这个过程比较耗时平均一张算子卡要花半天到一天。但它是让知识库真正可用的关键不能省。一开始就想着全量入库是不现实的先挑三到五个最常重写的算子试点跑通链路后再逐步铺开会稳妥很多。4.3 接入Agent工作流让Agent先查后写知识库建好之后的临门一脚是把它接进Agent的生成流程。我实现的不算复杂简单说就是加了“先检索再生成”的步骤Agent要写算子前先去知识库接口做一次相似度检索取回Top K张相关卡片把卡片内容塞进Prompt然后再开始写代码。我在内部工具里给Agent加了一段类似下面的约束“生成算子实现之前必须查询知识库。如果命中知识卡片且代码实现存在应优先基于卡片实现不允许从零编写同语义候选实现。若确认没有命中再按通用最佳实践生成并在完成后提示维护者是否补充知识卡片。”这段约束不复杂但效果立竿见影。Agent开始真正复用已有资产而不是每次生成一个看起来合理但那不是团队沉淀的版本。接入时有个细节知识库检索接口的返回结果要带来源链接和信任等级。Agent如果引用了知识它应该能说清楚“我参考了哪张卡片”这样Review同学也能快速核对而不是凭感觉相信Agent的输出。4.4 反馈闭环让知识库越用越准知识库不能是一次性工程。我设置了月度节奏Review时发现Agent没有复用一个已有知识卡记录下来并分析原因如果是检索没命中就补标签、补别名如果是卡片本身太模糊就重写经验区。另一个很重要的来源是“Agent生成的新实现”。如果某次Agent没有命中已有知识卡但又生成了一版性能不错的实现我们会要求提交者在确认稳定后考虑回填成新卡片。这样知识库会随着使用越长越全而不是上线后半年就陈旧。反馈闭环的核心是让“使用”和“更新”变成同一件事。坐等知识库自己长出来是不可能的它需要被喂。5. 上线后的量化变化与不完全成功的地方5.1 一组抽样对比数据上线大概十周后我让同学做了一次抽样统计。范围不算大主要是我们组经常维护的一组算子。虽然样本有限但几个趋势还是比较清晰的指标上线前抽样上线后4周上线后10周同一语义算子的重复实现数3.4个2.1个1.4个从需求到可运行版本的平均耗时约2.1天1.4天约0.9天性能基线达标率约65%81%88%Review中性能相关返工的比例约30%17%11%数据不算惊艳但方向是对的。尤其“性能基线达标率”这一项过去很多算子第一次能跑但性能不达标还要再花一到两轮去调。现在Agent基于知识卡生成很多调优决策在源头就被考虑到了。5.2 哪些环节的效果超出预期有两个点是我完全没预料到的。第一个是新同学上手效率显著提升。过去新人写算子要么照教材写一个通用实现要么硬着头皮去读老代码。现在知识卡就像一位“不说废话的导师”直接告诉它什么是已验证的方案、有哪些限制。不少新人第一版就能写出非朴素实现。第二个是性能优化经验的传播不再只是某个核心同学脑子里的私有知识。过去讨论个性能问题总要找到当时写代码的人人不在就无从下手。现在知识卡把决策记录摆在那里评审时可以对着卡讨论效率确实不一样。5.3 哪些坑还在等下一轮迭代解决也不能只报喜不报忧。上线十周我们还是遇到几个明显问题需要继续处理。一是检索结果仍然有“看起来相关其实不相关”的噪声。特别是当我们对知识库做了向量化检索后偶尔会把语义相近但使用场景完全不同的算子卡片推给Agent。这会误导代码结构。二是有些历史算子的维护人已经不在组里卡片内容只能对着代码推断准确性打了折扣。这类卡片我们暂时标记为“低信度”不允许Agent直接引用只能做人工参考。三是Agent偶尔会把不合适的卡片硬套进代码。比如某张卡片明确写了适用某个shape范围但Agent为了省事仍然生成了带该Tiling逻辑的代码结果在新shape上性能反而变差。这说明Prompt里的约束只能减少问题不能完全消除。6. 谁适合照搬这套做法边界与调整建议6.1 小团队与个人开发者怎么低成本起步如果你所在的团队只有几个人不要一上来就上重型知识管理平台。我们当时也用过很轻量的方式一个共享目录里面放结构化Markdown文件再加上一个简单的脚本做关键词检索。先把内容质量做起来工具再土都能用。关键是先选“最痛的点”。挑一两个你最常重复编写的算子类型把它沉淀成卡让Agent接入时能看到。如果试运行几周发现效果不错再增加知识卡数量和检索复杂度。小团队最大的优势是反馈链短不需要等复杂的审批流程。个人开发者其实也可以照做。你自己写一个算子过段时间可能也忘了当时的Tiling怎么选的。花半小时写一张自己的知识卡下次让Agent基于这张卡来生成省下的时间远超半小时。6.2 知识库规模扩大后的维护节奏知识库一旦超过一定规模维护节奏就得跟上。我们的经验是每张卡片都要有ownerowner不一定是最初写代码的人但一定要是当前最理解它的人。季度review一次确认卡片是否仍然是最优实现有没有新坑要补。废弃卡片不能删要标记为“废弃”。否则历史引用会断掉Agent拿到残缺卡片反而更误导。设置一个“无效知识率”指标超过一定比例就说明进库审核太松。这里我特别想说的是维护知识库的工时不能被吃干。如果不给同学留出沉淀知识的时间那这个库第一个月还新鲜第二个月就开始枯萎。我们把知识卡更新当作和代码开发同等级的技术债还款来看待而不是“有空再说”。6.3 如果离开CANN这套思路能否迁移完全可以迁移。本质上这套做法的核心是“把隐性经验变成结构化知识再让Agent在生成之前先检索”。换到普通业务代码也一样成立一个订单模块如果被Agent写了五遍也是因为没有人把“已验证的订单处理方案”沉淀成可检索的知识卡。不过CANN这类性能敏感领域收益会更明显。因为算子开发对性能基线和硬件偏好要求在通用Prompt里很难描述清楚而知识卡可以精准补充这些东西。普通业务代码虽然也能减少重复劳动但缺乏“性能基线”这类硬指标知识卡的约束力会弱一些更多是参考性质。最后再分享一个我实际使用下来的体会。知识库上线之后让我最开心的不是“生成速度变快了”而是团队对Agent的信任度变了。以前Agent生成的代码Review时大家总要多问一句“为什么这么写”现在Agent引用知识卡里的内容我们能顺着卡片追溯到代码、性能数据和决策理由。那种“随机发挥”的感觉消失了输出变成可回溯、可解释的存在。如果你也正在被“同一个功能反复生成”折磨我劝你别急着换更强的模型先把你自己写过、验证过、调优过的内容沉淀下来。知识库不一定要多智能能把你过去踩过的坑变成Agent的起点就已经值回票价了。