1. 为什么我要用 Agent 来画一张 AI 芯片全栈软件地图第一次听到“用 Agent 做一块 AI 芯片的全栈软件地图”这个说法很多人会以为是在讲芯片设计自动化其实不是。这里的“做”不是流片而是用 Agent 把一颗 AI 芯片从驱动到框架、从运行时到算子库的整条软件栈梳理成一张可执行、可验证、可迭代的地图。我过去几年一直在做异构计算和芯片软件栈相关的工作踩过不少坑也见过太多团队在“软件栈到底包含哪些层、谁依赖谁、哪一层先跑通”这件事上反复扯皮。Agent 在这里的价值不是替你写 Verilog而是替你把散落在文档、代码仓库、issue 和口口相传里的软件栈知识变成结构化的、可查询的、可自动补全的资产。先说清楚这张“全栈软件地图”到底指什么。一颗 AI 芯片要真正跑起一个大模型推理任务软件侧通常要经过这么几层最底下是KMDKernel Mode Driver内核态驱动负责和硬件打交道、管理设备、处理中断和内存映射往上是UMDUser Mode Driver用户态驱动负责命令提交、队列管理、内存分配策略再往上是运行时Runtime比如各种推理引擎的 backend然后是算子库和编译器把高层算子 lowering 到芯片指令最上面才是框架层PyTorch、ONNX、TensorFlow 这些。每一层都有自己的接口、约束和调试手段而“地图”要回答的核心问题是一个算子从框架调用到最终在芯片上执行中间经过了哪些模块、哪些数据结构、哪些同步点。我之所以选择用 Agent 来做这件事是因为传统方式做这张地图有三个绕不开的痛点。第一信息源太分散驱动代码在一个仓库运行时在另一个仓库算子库文档在内部 wiki调试经验在老员工脑子里。第二地图会过期芯片迭代一版UMD 的 ioctl 编号变了KMD 的寄存器定义变了地图如果靠人手维护两周就废。第三验证成本高你画完一张调用链路图怎么证明它是对的只能靠人肉跑一遍费时费力。Agent 恰好擅长这三件事它能跨仓库检索和抽取信息能按固定 schema 重新生成结构化输出还能调用工具去实际验证某条链路是否走得通。提示这里说的 Agent 不是指某个特定产品而是指“具备工具调用能力、能多步推理、能读写外部状态”的智能体。你可以用现成的 Agent 框架搭也可以自己写编排逻辑核心是让它围绕“软件栈地图”这个目标去干活。适合看这篇内容的人我大致分三类。第一类是刚接手 AI 芯片软件栈的工程师你需要快速建立全局观知道每一层大概长什么样、边界在哪。第二类是做 Agent 应用开发的同行你想找一个真实、有深度、非玩具级的场景来练手芯片软件栈这种“知识密集、结构性强、可验证”的领域非常合适。第三类是技术管理者或架构师你在评估“用 Agent 管理复杂技术知识”到底靠不靠谱这篇会给你一个可复现的参考路径。下面我会从整体设计、核心细节、实操过程到问题排查完整讲一遍我是怎么做的。2. 整体设计与思路拆解地图的骨架怎么定2.1 先定义地图的“坐标系”再谈 Agent 怎么用很多人一上来就让 Agent “帮我总结一下芯片软件栈”结果得到一堆正确的废话。问题出在没有给地图定义坐标系。地图不是散文它必须有固定的维度否则每次生成的东西都对不齐。我最后定下来的坐标系是三个维度层级Layer、实体Entity、关系Relation。层级就是前面说的 KMD、UMD、Runtime、算子库、框架这几层但我会再细一点比如 UMD 里拆出“设备管理”“内存管理”“命令提交”三个子域。实体是每一层里的具体对象比如 KMD 里的device_node、dma_bufferUMD 里的command_queue、context。关系则是实体之间的依赖和调用比如“UMD 的 command_queue 通过 ioctl 向 KMD 的 device_node 提交命令”。这三个维度定下来之后Agent 的任务就变得非常明确从代码和文档里抽取实体判断它属于哪一层哪个子域再抽取实体之间的关系。为什么这么设计因为我试过让 Agent 自由发挥结果它会把“PyTorch 的 tensor”和“KMD 的 dma_buffer”混在一起讲层级完全乱掉。有了坐标系之后我可以在 Agent 的 prompt 里强制它输出 JSON每个实体必须带layer、subdomain、name、source字段每条关系必须带from、to、type、evidence字段。这样生成的地图才是机器可读、可对比、可增量更新的。这一点非常关键地图的价值不在于好看而在于能被程序消费否则你没法做自动化验证。2.2 为什么选“抽取-验证-回写”三段式而不是一次性生成我一开始的思路是让 Agent 读一遍所有仓库直接吐出一张完整地图。实测下来问题很大一是上下文塞不下二是 Agent 会在长链路推理里“脑补”出不存在的调用关系三是生成结果没法追溯。后来我改成三段式抽取Extract、验证Verify、回写Write-back。抽取阶段Agent 只做一件事针对某一个子域从指定文件集合里抽出实体和关系输出结构化 JSON。比如这一轮只处理“UMD 命令提交”输入就是 UMD 仓库里cmd_submit.c、queue.c和对应的头文件。验证阶段Agent 拿到抽取结果后不是直接信而是去代码里找证据这条调用关系在哪个函数里、哪一行、有没有条件分支。如果找不到证据这条关系就被标记为unverified。回写阶段把验证过的实体和关系合并进主地图冲突的地方打上conflict标记留给人来裁决。这个设计的核心考量是把 Agent 的“生成能力”和“验证能力”分开。生成的时候允许它发散验证的时候强制它收敛。我踩过的坑是如果让 Agent 一边生成一边自我验证它很容易“自己说服自己”把错的也验证成对的。分开之后验证阶段我会换一个不同的 prompt甚至换一个不同的模型专门做“找茬”准确率明显提升。2.3 地图的存储格式为什么最后选了图数据库而不是 Markdown一开始我用 Markdown 存地图人看着舒服但一旦实体超过两百个查询和更新就崩了。比如我想知道“所有依赖 dma_buffer 的 UMD 实体有哪些”在 Markdown 里只能靠 grep还容易漏。后来换成图数据库我用的是 Neo4j你也可以用 SQLite 加关系表甚至用 JSON 文件加索引实体是节点关系是边每个节点和边都带source、confidence、last_verified属性。这么做的直接好处是Agent 在验证阶段可以直接查图数据库问“从 command_queue 到 device_node 是否已经存在一条路径”避免重复抽取。另一个好处是增量更新变得可行芯片迭代后我只需要让 Agent 重新抽取变化的子域然后做一次图 diff就能知道哪些关系失效了。Markdown 做不到这一点因为它是线性的没有拓扑结构。当然图数据库对小白有门槛如果你只是想先跑通流程用 JSON 文件加一个简单的查询脚本也够用等实体规模上来了再迁移。3. 核心细节解析与实操要点每一层到底抽什么3.1 KMD 层从 ioctl 编号和寄存器定义入手KMD 是整张地图的地基也是最难抽的一层因为它和硬件强相关很多信息藏在寄存器手册和驱动代码的宏定义里。我的做法是以 ioctl 编号为锚点。KMD 通常会暴露一组 ioctl 给 UMD 调用每个 ioctl 对应一个命令比如ALLOC_BUFFER、SUBMIT_CMD、WAIT_FENCE。Agent 的任务是从 KMD 头文件里抽出所有 ioctl 编号和对应的处理函数再从处理函数里抽出它操作的硬件资源寄存器、DMA 通道、中断号。这里有个细节ioctl 编号在不同内核版本里可能变所以 Agent 抽取时必须带上内核版本或 commit hash作为属性。我试过不带版本号结果地图里同一个 ioctl 有两个编号验证阶段直接冲突。另外寄存器定义往往在单独的.h文件里Agent 需要跨文件关联这时候我会在 prompt 里明确告诉它“寄存器宏定义在regs/目录下ioctl 处理函数在drivers/目录下请建立映射”。如果不给这个提示Agent 很容易只抽到函数名抽不到寄存器。注意KMD 层抽取时不要试图一次性抽完所有寄存器。寄存器动辄上千个全抽进来地图会爆炸。我的经验是只抽被 ioctl 处理函数直接引用的寄存器其余的先忽略。等地图主干稳定了再按需扩展。3.2 UMD 层命令队列和内存管理是两条主线UMD 是承上启下的一层往上接运行时往下接 KMD。我把它拆成两条主线来抽命令队列和内存管理。命令队列这条线Agent 要抽的是queue、command buffer、fence、event这些实体以及它们之间的提交和同步关系。内存管理这条线要抽的是buffer、heap、allocation、mapping这些实体以及它们和 KMD 侧dma_buffer的对应关系。为什么强调这两条线因为实际调试中百分之八十的问题出在命令提交和内存映射上。比如一个算子跑出错误结果可能是命令队列的 fence 没等对也可能是 buffer 的 cache 属性设错了。如果地图里这两条线是清晰的排查时就能快速定位到“哦这个 buffer 是从 UMD 的 heap 分配的映射到 KMD 时用了 write-combine但算子需要 coherent所以数据不一致”。Agent 在抽取时我会让它特别标注每个 buffer 的内存属性coherent、cached、write-combine和每个 fence 的同步语义host-device、device-device。3.3 Runtime 与算子库接口契约比实现更重要到了 Runtime 和算子库这一层代码量更大但地图不需要抽到函数级。我的策略是只抽接口契约Runtime 暴露给框架的 API 有哪些每个 API 的输入输出是什么它内部会调用 UMD 的哪些接口。算子库同理只抽“算子名 - 芯片指令序列”的映射关系不抽指令的具体编码。这么做的理由是上层软件栈变化快下层硬件相对稳定。如果地图抽到函数级框架一升级地图就得大改。只抽接口契约的话框架升级通常只影响 API 签名地图的改动量很小。Agent 在这一层的任务是从 Runtime 的头文件里抽 API 列表从实现文件里抽“这个 API 调用了哪些 UMD 接口”然后建立跨层关系。我实测下来这一层用 Agent 抽取的准确率最高因为接口定义通常很规范不像 KMD 那样充满宏和条件编译。3.4 关系抽取的难点如何区分“调用”和“依赖”实体好抽关系难抽。最难的是区分调用关系call和依赖关系depend。比如 UMD 的submit_command调用了 KMD 的ioctl这是调用关系但 UMD 的command_queue依赖 KMD 的device_node存在才能创建这是依赖关系。两者在代码里可能都表现为函数调用但语义完全不同。我的解决办法是在 prompt 里给 Agent 明确的判定规则如果 A 的函数体里直接出现了 B 的函数名且 B 是跨层接口判为 call如果 A 的初始化或生命周期管理里需要 B 先存在判为 depend。同时我会让 Agent 输出evidence字段把判定依据的代码片段贴出来。这样即使判错了人工复核时也能快速纠正。这个规则不是完美的但比让 Agent 自由发挥靠谱得多。踩过的坑是有些关系既是 call 又是 depend这时候我会让 Agent 输出两条边分别标注类型而不是强行合并。4. 实操过程与核心环节实现从零跑通一遍4.1 环境准备与工具选型先说环境。我用的是 Python 3.11Agent 编排用的是 LangGraph你也可以用别的核心是支持多步和状态管理图数据库用 Neo4j 5.x代码检索用 ripgrep 加一个简单的 AST 解析器Python 的ast模块对 C 代码不够用我额外用了pycparser处理头文件。模型方面抽取阶段用了一个上下文窗口较大的模型验证阶段换了一个推理能力更强的模型具体名字不重要你可以根据自己手头的资源选。工具选型上我强烈建议不要一上来就上重型框架。我最初用了一个功能很全的 Agent 平台结果调试成本极高一个 prompt 改动要等半天才能看到效果。后来换成 LangGraph 加自己写的工具函数反而跑得更快。核心工具就四个read_file读代码、search_code正则或 AST 搜索、query_graph查图数据库、write_graph写图数据库。Agent 的每一步推理本质上就是在这四个工具之间切换。提示如果你只是想先验证思路可以跳过图数据库用 JSON 文件加一个networkx内存图。等实体超过五百个再迁移到 Neo4j迁移成本很低因为节点和边的 schema 是一样的。4.2 抽取阶段的完整流程与参数设置抽取阶段我按子域分批跑每个子域一个 Agent 任务。以“UMD 命令提交”为例流程是这样的第一步Agent 调用search_code在 UMD 仓库里搜索submit、queue、fence等关键词拿到候选文件列表。第二步Agent 对每个候选文件调用read_file抽取实体和关系输出 JSON。第三步Agent 调用query_graph检查抽到的实体是否已存在避免重复。第四步Agent 调用write_graph把新实体和关系写入标记confidence为medium等待验证。参数设置上有几个关键点。上下文窗口每个子域的输入代码量控制在模型窗口的百分之六十以内留出空间给 prompt 和输出。温度抽取阶段温度设 0.2允许一点点发散验证阶段设 0强制确定性。重试次数如果 Agent 输出的 JSON 解析失败自动重试两次第二次在 prompt 里附上解析错误信息。我实测下来JSON 解析失败率大概在百分之五左右主要是模型偶尔会加注释或尾逗号重试基本能解决。4.3 验证阶段怎么让 Agent 自己找自己的茬验证阶段是整个流程里最容易被忽视、但价值最高的环节。我的做法是抽取阶段结束后把生成的 JSON 喂给一个全新的 Agent 会话prompt 里明确说“你的任务是找出这些实体和关系里的错误每条关系都必须有代码证据找不到证据的标记为 unverified”。这个 Agent 不允许修改实体只能标记和补充证据。具体验证逻辑分三层。第一层是存在性验证实体名是否真的在代码里出现关系两端的实体是否都存在。第二层是语义验证这条关系是 call 还是 depend类型标对了吗。第三层是一致性验证同一个实体在不同子域里的属性是否冲突比如command_queue在命令提交子域里标记为per-context在内存管理子域里标记为global这就是冲突需要人工裁决。我踩过的一个坑是验证 Agent 有时候会“过度验证”把一些正确的弱关系也标成 unverified因为它找不到直接的代码证据。后来我在 prompt 里加了一条规则“如果关系是通过数据结构成员间接建立的允许标记为inferred但必须说明推断路径”。这样既保留了弱关系又不会让它们污染强关系。4.4 回写与增量更新地图怎么保持不过期回写阶段的核心是冲突处理。新抽取的关系和已有关系冲突时我不会让 Agent 自动覆盖而是写入一个conflict队列附上两边的证据留给人来裁决。裁决之后人工在数据库里更新并记录裁决理由。这个理由很重要因为下次 Agent 再抽到同样的冲突时可以读取历史裁决避免重复提问。增量更新方面我做了两件事。第一给每个实体和关系加last_verified时间戳超过一定时间比如三个月的标记为stale下次抽取时优先重新验证。第二监听代码仓库的 commit如果某个子域的文件有变更自动触发该子域的重新抽取。这样地图就能跟着代码走而不是靠人记得去更新。实测下来一个中等规模的芯片软件栈全量抽取大概需要两到三小时增量更新通常十分钟以内就能跑完。5. 常见问题与排查技巧实录5.1 Agent 抽取结果层级混乱怎么办这是最常见的问题。表现是 Agent 把 UMD 的实体标成了 KMD或者把 Runtime 的 API 标成了算子库。根本原因是层级边界在代码里不是显式标注的Agent 只能靠目录结构和命名习惯猜。我的解决办法是在 prompt 里给一个层级判定表明确列出每一层的目录特征和命名特征。比如“路径包含drivers/kernel/的判为 KMD路径包含drivers/user/的判为 UMD路径包含runtime/的判为 Runtime”。同时我会让 Agent 在不确定时输出layer: unknown而不是硬猜。unknown 的实体后续人工补标比标错了再改成本低得多。5.2 关系抽取漏掉间接调用怎么补间接调用是关系抽取的老大难。比如 A 调用 BB 调用 C但 A 和 C 之间没有直接调用地图里就缺了 A 到 C 的路径。我的做法是在验证阶段加一个“路径补全”步骤Agent 查图数据库找出所有长度为 2 的路径然后去代码里验证这条路径是否真实存在即 B 是否真的在 A 和 C 之间起桥梁作用。如果存在就补一条indirect_call关系。这个步骤能补出不少隐藏的依赖尤其是跨层调用。注意补全的关系要标记inferred和直接抽取的关系区分开。5.3 地图规模大了之后查询变慢怎么优化实体超过一千个之后图数据库的查询开始变慢尤其是多跳查询。我做了三个优化。第一给高频查询建索引比如按layer和subdomain建复合索引。第二预计算常用路径比如“从框架 API 到 KMD ioctl 的所有路径”存成物化视图查询时直接读。第三分层加载Agent 查询时默认只加载当前子域和相邻子域跨子域查询才加载全图。这三个优化下来查询延迟从秒级降到毫秒级。如果你用的是 JSON 文件优化手段类似核心是别每次都全量加载。5.4 常见问题速查表问题现象可能原因排查思路解决办法实体层级标错目录特征不明显检查实体所在文件路径补充层级判定表允许 unknown关系类型混淆call 和 depend 判定规则不清查看 evidence 字段明确判定规则输出两条边JSON 解析失败模型输出格式不稳检查原始输出降低温度加重试附错误信息验证过度标记弱关系找不到直接证据查看 inferred 规则允许 inferred说明推断路径地图更新滞后没有监听代码变更检查 last_verified加 commit 监听自动触发重抽查询变慢全量加载无索引看查询计划建索引预计算路径分层加载5.5 几个我踩过的坑和独家技巧第一个坑是过早追求全自动化。我一开始想让 Agent 从抽取到验证到回写全自动跑结果错误累积地图越跑越乱。后来改成“抽取自动、验证半自动、回写人工确认”准确率大幅提升。第二个坑是忽略代码版本。有一次芯片迭代KMD 的 ioctl 编号变了但地图没更新导致排查问题时对着旧地图找浪费了半天。后来强制每个实体带版本号问题就解决了。独家技巧方面分享两个。第一用“反例”训练 Agent。我会在 prompt 里放几个典型的错误抽取案例告诉 Agent“这种是错的不要这么抽”。这比只给正例效果好得多。第二让 Agent 输出“不确定”。很多 Agent 应用追求“什么都能答”但在软件栈地图这个场景里承认不确定比瞎猜更有价值。我会在 prompt 里明确说“如果你不确定输出 unknown不要编造”。实测下来unknown 的比例大概在百分之十左右这百分之十人工补标的成本远低于纠正错误抽取的成本。6. 地图做出来之后能干什么地图跑通之后最直接的用途是新人 onboarding。以前新人要花两周才能搞清楚软件栈的调用链路现在对着地图查两天就能上手。第二个用途是故障排查。线上出问题时从报错的 API 出发沿着地图往下查能快速定位到是 UMD 的队列问题还是 KMD 的内存问题。第三个用途是变更影响分析。要改 UMD 的某个接口时先查地图看哪些 Runtime API 依赖它评估影响范围。再往远一点这张地图还能喂给 Agent 做自动问答。比如工程师问“我想加一个新的算子需要改哪些层”Agent 查地图就能给出从算子库到 KMD 的完整改动清单。这个场景我还在打磨目前的准确率大概在百分之七十左右主要卡在算子库到指令序列的映射还不够细。但方向是清楚的地图是知识的骨架Agent 是骨架上的肌肉两者结合才能让芯片软件栈这种复杂系统变得可理解、可维护、可演进。最后分享一个我在实际操作中的体会做这张地图最大的阻力不是技术而是信息源的碎片化。很多关键信息不在代码里而在邮件、聊天记录和口头约定里。Agent 能处理代码和文档但处理不了“老张说这个 fence 要等两拍”这种隐性知识。所以我的建议是先把代码和文档里的显性知识抽干净再用人来补隐性知识不要指望 Agent 一步到位。地图的价值不在于一次做完美而在于它能持续迭代每次迭代都比上一版更接近真相。