1. 为什么我要用 Agent 去啃一块 AI 芯片的全栈软件地图
第一次听到“用 Agent 做一块 AI 芯片的全栈软件地图”这个说法,我脑子里冒出来的不是兴奋,而是怀疑。芯片软件栈这东西,向来是人力密集、知识密集、文档散落、版本地狱的重灾区。一个 SoC 从 RTL 冻结到量产,软件团队要维护的东西包括但不限于:启动固件、引导加载、内核移植、设备树、驱动、运行时、编译器后端、算子库、框架适配层、调试工具链、仿真模型、性能分析工具。任何一个人想把这些东西的依赖关系、调用链路、版本对应关系全部理清楚,基本等于用肉眼去读一张几百万节点的图。
我真正开始认真对待这件事,是因为一个很现实的问题:团队里新来的工程师,问“我们的 UMD 和 KMD 到底怎么分工”这种问题,居然要等三个不同的人分别回答,而且答案还不完全一致。这说明知识根本没有沉淀成一张可查询、可追溯、可更新的地图。于是我开始尝试用 Agent 来做这件事——不是让 Agent 替我写驱动,而是让 Agent 帮我把这张全栈软件地图“长”出来,并且持续维护。
这篇文章就是我把这件事从想法推到落地之后的完整记录。我会讲清楚我为什么选 Agent 而不是传统文档工具,Agent 在这件事里到底承担什么角色,UMD 和 KMD 这类核心概念怎么被拆解进地图,实操过程中我踩了哪些坑,以及最后这套东西到底能不能用。适合谁看?如果你在做芯片软件、系统软件、AI 加速器运行时,或者你正在琢磨怎么把 Agent 用到真实的工程知识管理场景里,而不是只让它写写周报,那这篇应该对你有用。
先说结论性的判断:Agent 在这件事里的价值,不是“自动生成文档”,而是“自动维护一张有结构、有来源、有置信度的知识图谱”。这两者差别巨大。前者你用过一次就不想再用,后者才是能长期跑下去的东西。
2. 全栈软件地图到底要画什么:先把边界定死
2.1 一张地图的四个层次
我一开始犯的最大错误,就是想把所有东西都塞进去。结果 Agent 每次输出都像一本电话簿,没人看得下去。后来我强制自己把地图分成四层,每层只回答一个核心问题。
| 层次 | 回答的问题 | 典型内容 | 更新频率 |
|---|---|---|---|
| 硬件抽象层 | 软件看到的硬件长什么样 | 寄存器手册、中断表、内存映射、DMA 描述符 | 低,随流片版本 |
| 内核态层 | 谁在管资源、谁在管调度 | KMD、设备驱动、内存管理、电源管理 | 中,随内核版本 |
| 用户态层 | 应用怎么把活派下去 | UMD、运行时、编译器、算子库 | 高,随框架版本 |
| 工具与验证层 | 怎么知道它跑对了 | 仿真器、性能计数器、调试器、回归测试 | 中高 |
这张表看起来简单,但它解决了一个致命问题:当有人问“这个东西属于哪一层”的时候,有地方可查。Agent 在生成内容时,也被强制要求先判断层次,再写细节。否则它会把 UMD 的调度策略和 KMD 的中断处理混在一段里,读起来像两个人同时在说话。
2.2 UMD 和 KMD 为什么必须分开讲
UMD 是 User Mode Driver,KMD 是 Kernel Mode Driver。这两个词在热词里被反复提到,但很多人其实说不清它们的边界。我用一个生活类比来解释:KMD 像是小区物业,负责水电煤、门禁、消防这些你不能随便碰的东西;UMD 像是你家请的管家,负责安排今天谁做饭、谁打扫、东西放哪里。管家不能自己去改小区总闸,物业也不会管你家沙发怎么摆。
在 AI 芯片场景里,这个边界更关键。KMD 通常负责设备初始化、内存分配、命令队列的提交与完成、中断处理、多进程隔离。UMD 负责把上层框架的算子翻译成硬件能执行的命令、管理显存池、做同步与事件、处理错误恢复。Agent 在构建地图时,我要求它对每一个功能点都标注“归属层”,并且给出判断依据。这个判断依据不是让它编,而是从已有代码、提交记录、设计文档里抽。
注意:如果你让 Agent 自由发挥,它很容易把 UMD 和 KMD 的职责写反,尤其是在错误处理和内存管理这两块。必须用明确的规则约束它。
2.3 地图的“可追溯性”比“完整性”更重要
我见过太多团队花几个月写了一份“全栈软件白皮书”,结果半年后没人知道里面某句话是从哪来的,也不敢改。所以我在设计之初就定了一个原则:地图里的每一个节点,必须能追溯到至少一个来源。来源可以是代码文件路径、提交哈希、设计文档编号、或者某次评审的结论记录。
Agent 在这里的作用是:它不直接“写知识”,而是“抽取并关联来源”。比如它读到某个头文件里定义了ioctl命令号,就会把这个命令号、对应的处理函数、调用它的用户态库函数、以及相关测试用例串成一条链。这条链就是地图的一个边。没有来源的节点,一律标记为“待验证”,不允许进入正式地图。
这个原则听起来很重,但它把 Agent 从“内容生成器”变成了“关系抽取器”,可靠性完全不一样。生成器会幻觉,抽取器只要来源在,就能核对。
3. Agent 在这件事里到底干什么:角色拆解与选型逻辑
3.1 不是一个大 Agent,而是一组小 Agent
我试过用一个“全能 Agent”去读所有代码和文档,结果它上下文爆炸,输出质量断崖式下跌。后来我改成一组职责单一的小 Agent,每个只干一件事,通过一个编排层串起来。这个思路和现在很多 Agent 框架强调的“多 Agent 协作”是一致的,但我不追求花哨的框架,能用脚本串起来就行。
我实际用的 Agent 角色有这么几个:
- 扫描 Agent:负责遍历代码仓库和文档目录,产出文件清单和初步分类。
- 抽取 Agent:针对某一类文件(比如头文件、驱动源码、构建脚本),抽取结构化信息。
- 关联 Agent:把不同来源的抽取结果按规则连边,比如函数调用、数据结构引用、版本对应。
- 校验 Agent:检查关联结果是否自洽,比如某个函数被调用但从未定义,就标红。
- 问答 Agent:面向人的查询接口,把自然语言问题翻译成对地图的查询。
这五个角色里,最容易被低估的是校验 Agent。我一开始觉得抽取准了就没事,后来发现关联阶段才是错误重灾区。比如两个同名函数在不同模块里,关联 Agent 很容易连错。校验 Agent 通过检查签名、命名空间、文件路径来兜底,能拦下大部分低级错误。
3.2 为什么不用传统文档工具
有人会问,这不就是 Confluence 加个搜索吗?我实际对比过。传统文档工具的问题是:它是给人写的,人写的时候会省略、会假设读者有背景、会过期。而 Agent 抽取出来的地图是给人和机器一起读的,每个节点有类型、有来源、有置信度。搜索“UMD 内存分配”在文档里可能返回三篇互相矛盾的文章,在地图里返回的是一条带来源的调用链。
另一个关键差异是更新成本。文档更新靠人记得去改,地图更新可以挂在 CI 上,代码合并后自动触发扫描和抽取。这个差异在芯片软件这种高频迭代的场景里是决定性的。
3.3 Agent 框架选型的几个实际考量
热词里出现了很多 Agent 框架名字,我没有全试,但选型时我关注这几点:
- 是否支持结构化输出:我需要 Agent 输出 JSON 或类似结构,而不是散文。不支持结构化输出的框架直接排除。
- 是否支持工具调用:扫描文件、执行 grep、读数据库,这些都要靠工具。
- 是否支持长任务与断点续跑:全量扫描一次可能几小时,中途失败要能续。
- 是否支持本地模型:芯片代码很多是保密的,不能随便发到外部服务。
- 编排是否透明:出问题时我要能看懂是哪一步错了,黑盒编排很难调。
基于这些,我最后用的是一套偏工程化的方案:用脚本做编排,Agent 只负责它擅长的抽取和判断,重活交给确定性代码。这个选择让整套东西的调试难度下降了一个数量级。
提示:不要为了用 Agent 而用 Agent。凡是能用正则和 AST 稳定解决的事,就不要交给模型。模型只用在“需要理解语义”的地方,比如判断一段注释描述的是哪个硬件模块。
4. 实操:从零把这张地图跑起来
4.1 第一步:定义地图的 Schema
这是最重要的一步,也是最容易被跳过的一步。我定义了一个简单的节点-边模型:
- 节点类型:模块、文件、函数、数据结构、寄存器、命令、版本、测试用例。
- 边类型:调用、引用、定义于、测试于、版本对应、属于层。
- 每个节点必须有:唯一 ID、类型、名称、来源列表、置信度。
- 每条边必须有:起点、终点、类型、来源、置信度。
这个 Schema 用 JSON Schema 描述,Agent 的输出必须符合它。不符合就重试。这一步做完之后,后面所有事情都有了准绳。
4.2 第二步:扫描与分类
扫描 Agent 的工作看起来简单,其实有讲究。芯片软件仓库通常包含大量生成代码、第三方代码、历史遗留目录。如果不加过滤,抽取结果会被噪声淹没。我的做法是维护一个白名单和黑名单:
- 白名单:核心驱动目录、运行时目录、编译器后端目录、关键头文件目录。
- 黑名单:构建产物、第三方库、测试数据、文档图片。
扫描 Agent 按白名单遍历,对每个文件打标签:语言、所属层、是否生成代码、最后修改时间。这些标签后续用来决定抽取策略。比如生成代码只抽接口不抽实现,第三方库只记录版本不深入。
4.3 第三步:抽取结构化信息
抽取是最耗算力的一步。我按文件类型分策略:
- 头文件:抽函数声明、结构体、宏、枚举。
- 驱动源码:抽函数定义、调用关系、寄存器读写。
- 构建脚本:抽目标、依赖、编译选项。
- 设计文档:抽模块描述、接口约定、版本变更。
这里有个实操心得:不要试图一次抽全。我第一版想抽所有东西,结果 Agent 输出又长又乱,校验几乎没法做。第二版改成按需抽取,先抽接口层,再抽实现层,最后抽测试层。每层抽完先校验,通过了再进下一层。这样错误不会累积。
4.4 第四步:关联与建图
关联 Agent 拿到各层的抽取结果后,按规则连边。规则我写成了配置文件,比如:
- 如果函数 A 的函数体里出现函数 B 的名字,且 B 在头文件里声明过,则连一条“调用”边。
- 如果结构体 S 的某个字段类型是 T,且 T 在同一头文件里定义,则连一条“引用”边。
- 如果测试用例 C 的名字里包含模块 M 的名字,则连一条“测试于”边。
这些规则不完美,但可解释、可调整。比让模型自由判断靠谱得多。模型只在规则无法覆盖的地方介入,比如判断一段自然语言注释描述的是哪个模块。
4.5 第五步:校验与置信度打分
校验 Agent 做几件事:
- 检查悬空边:起点或终点不存在的边,直接删。
- 检查循环依赖:某些循环是合法的,某些不是,按规则判断。
- 检查版本冲突:同一个接口在不同版本里签名不一致,标红。
- 给每个节点和边打置信度:来源越多、越权威,置信度越高。
置信度这个设计非常有用。查询的时候可以按置信度过滤,低置信度的结果只作为线索,不作为结论。这避免了“地图看起来什么都有,但不敢信”的尴尬。
4.6 第六步:挂到 CI 上持续更新
地图不是一次性的。我把它挂在代码合并流程上:每次有合并请求,触发增量扫描和抽取,只更新受影响的部分。全量重建一周跑一次,用来发现漂移。这个机制让地图的时效性有了保障,也让团队愿意用它,因为知道它不会过期。
5. 踩过的坑与排查实录
5.1 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方式 |
|---|---|---|---|
| Agent 输出格式错乱 | 提示词约束不够 | 检查输出是否符合 Schema | 加强 Schema 校验,失败重试 |
| 关联结果大量错误 | 同名符号混淆 | 抽样检查边 | 加入命名空间和路径约束 |
| 扫描太慢 | 遍历了黑名单目录 | 看扫描日志 | 收紧白名单 |
| 置信度普遍偏低 | 来源太少 | 统计来源分布 | 补充设计文档和提交记录 |
| 地图更新后查询变差 | 增量更新引入噪声 | 对比更新前后 | 回滚并修正增量规则 |
| 模型成本过高 | 抽取粒度太细 | 统计 token 消耗 | 合并小文件,批量抽取 |
5.2 几个我印象最深的坑
第一个坑是“注释陷阱”。芯片代码里很多注释是历史遗留的,描述的是旧版本行为。Agent 如果直接信注释,就会把过时信息写进地图。我的解决办法是:注释只作为线索,不作为来源。来源必须是代码本身或正式文档。
第二个坑是“宏展开”。很多接口是通过宏生成的,Agent 看到的是宏调用,不是真实函数。如果不做宏展开,地图里会缺一大块。我后来加了一个预处理步骤,用编译器前端做宏展开,再交给 Agent 抽取。
第三个坑是“版本地狱”。同一个模块在不同分支上接口不同,Agent 如果混着读,就会产生矛盾。我的做法是地图按分支隔离,查询时指定分支。跨分支对比作为单独功能,不混进主地图。
注意:如果你的代码仓库有多个长期维护分支,一定要在地图设计之初就考虑分支隔离,否则后期迁移成本极高。
5.3 独家避坑技巧
- 先做小范围试点,选一个模块跑通全流程,再推广。我选的是内存管理模块,因为它边界清晰、依赖少。
- 保留每次抽取的原始输出,出问题时可以回溯。我存了压缩包,按日期和提交哈希命名。
- 给 Agent 的提示词里明确写“不确定就标未知”,不要让它猜。猜出来的东西比没有更危险。
- 定期人工抽检,哪怕只有十分钟。机器校验拦不住语义错误,人的直觉有时候更快。
6. 这套东西跑起来之后,实际改变了什么
最直接的变化是新人的上手时间。以前一个新工程师要花两三周才能搞清楚 UMD 和 KMD 的调用关系,现在他可以直接问问答 Agent,拿到一条带来源的链路,再自己去核对。这不是替代学习,而是把学习路径缩短了。
第二个变化是评审效率。以前评审一个驱动改动,评审人要自己去翻调用方,现在地图直接给出影响范围。这个影响范围不一定全对,但比人肉翻强太多。
第三个变化是知识沉淀的方式。以前知识在人的脑子里和零散的文档里,现在知识在地图里,而且有结构、有来源、有置信度。人走了,地图还在。
我也必须说清楚这套东西的局限。它不能替代设计文档,不能替代代码评审,不能替代人的判断。它只是一个索引,一个帮你快速定位和追溯的索引。把它当成万能药,一定会失望。
7. 如果让我重新做一遍,我会怎么调整
我会更早地定义 Schema,而不是边做边改。Schema 一改,前面所有抽取结果都要重跑,成本很高。
我会更早地引入校验 Agent,而不是等关联出问题才补。校验前置能省下大量返工。
我会把地图的查询接口做得更简单,最好是一条命令就能查。工具越简单,用的人越多,地图的价值才越大。
最后再分享一个小技巧:地图里的每个节点,我都会加一个“最后验证时间”。超过三个月没验证的节点,查询时自动标黄。这个机制逼着团队定期回看,避免地图慢慢腐烂。这个想法是从代码覆盖率工具里借来的,用在地图上效果出奇地好。