news 2026/10/4 10:17:24

用Agent构建AI芯片全栈软件地图:从UMD/KMD到知识图谱

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Agent构建AI芯片全栈软件地图:从UMD/KMD到知识图谱

1. 从一颗芯片的诞生说起:为什么全栈软件地图比芯片本身更难画

一颗 AI 芯片从设计到量产,硬件团队交付的是 RTL、网表、GDS,而软件团队要交付的是一整套让这颗硅片真正“跑起来”的软件栈。我做过几个芯片项目的软件适配,最深的体会是:芯片流片回来能点亮只是起点,真正决定这颗芯片能不能被客户用起来、能不能上量,靠的是软件栈的完整度和成熟度。而“全栈软件地图”这件事,本质上就是把这套软件栈的每一层、每个模块、每个接口、每个依赖关系都梳理清楚,让团队知道“我们有什么、缺什么、先做什么、后做什么”。

这次我想聊的是用 Agent 来做这件事——不是让 Agent 去写驱动代码,而是让 Agent 帮我们把一颗 AI 芯片的全栈软件地图给“画”出来。听起来有点抽象,我换个说法:你手里有一堆零散的文档、代码仓库、接口定义、测试用例、团队聊天记录,甚至是一些口口相传的“坑”,你要把这些东西整合成一张结构化的、可查询、可追溯、可演进的软件地图。传统做法是拉一个架构师花几周时间画 PPT,但问题是芯片软件栈太复杂了,从 UMD 到 KMD 到 Runtime 到 Compiler 到 Framework,每一层都在快速迭代,PPT 画完就过时了。

Agent 在这里的价值不是替代架构师,而是做一个“持续在线的地图维护者”。它可以帮你从代码仓库里自动提取模块依赖,从文档里抽取接口定义,从 issue 里归纳已知问题,从测试报告里统计覆盖率,然后把这些信息组织成一张活的、可查询的地图。你问它“UMD 层的内存管理模块依赖哪些 KMD 接口”,它能直接给你答案,而不是让你去翻三个仓库和五个文档。

这篇文章适合几类人看:一是正在做 AI 芯片软件适配的工程师,尤其是负责架构梳理和模块拆解的;二是对 Agent 应用开发感兴趣、想找一个真实复杂场景练手的开发者;三是技术管理者,想了解怎么用 Agent 提升团队的知识管理效率。我会从整体设计思路讲起,然后拆解核心细节,再给出一套可复现的实操方案,最后分享我在这个过程中踩过的坑和总结的技巧。

2. 整体设计思路:为什么用 Agent 而不是传统文档工具

2.1 传统软件地图维护的三大痛点

在讲 Agent 方案之前,先说说传统做法为什么不行。我经历过三种典型的软件地图维护方式,每一种都有硬伤。

第一种是静态文档,比如 Confluence 上的架构图、Word 里的接口说明。问题是更新滞后,芯片软件栈每周都在变,文档三个月不更新就没人看了。而且静态文档是“死”的,你没法查询、没法追溯、没法做影响分析。比如你想知道“如果 KMD 的调度接口改了,哪些 UMD 模块会受影响”,静态文档给不了你答案。

第二种是代码注释加 Doxygen,靠代码里的注释自动生成文档。这个方式比静态文档好一点,至少跟代码同步。但问题是它只能覆盖代码层面,覆盖不了跨模块的依赖关系、设计决策、已知问题、测试覆盖情况。而且 AI 芯片软件栈里有很多“非代码”的知识,比如硬件寄存器的行为、固件的约定、编译器的 pass 顺序,这些都不在代码注释里。

第三种是人肉知识库,靠架构师和资深工程师脑子里的经验。这个最靠谱但也最脆弱,人一走知识就断了。而且人脑的容量有限,一颗 AI 芯片的全栈软件栈涉及几千个模块、上万个接口,没人能全记住。

Agent 方案的核心思路是:把软件地图从“静态文档”变成“可查询的知识图谱”,从“人维护”变成“Agent 持续维护,人审核”。Agent 不替代架构师的判断,但替代架构师的“信息收集和整理”工作。

2.2 Agent 在全栈软件地图中的角色定位

我给 Agent 在这个场景里的角色定了三个边界,这三个边界决定了整个方案的设计。

第一个边界:Agent 是信息聚合器,不是决策者。它负责从各种来源收集信息、去重、归类、建立关联,但不负责判断“这个模块该不该拆”“这个接口该不该改”。这些决策还是人来做。Agent 的输出是“事实和关系”,不是“建议和方案”。

第二个边界:Agent 是持续运行的,不是一次性任务。软件地图不是画一次就完了,它需要随着代码提交、文档更新、issue 关闭而持续演进。所以 Agent 要能定期扫描仓库、监听变更、增量更新地图。这跟传统的“跑一次脚本生成文档”有本质区别。

第三个边界:Agent 的输出要可追溯、可验证。地图上的每一条信息都要能追溯到来源,比如“UMD 的内存分配模块依赖 KMD 的kmd_alloc接口”这条信息,要能点进去看到是哪个代码文件、哪一行、哪个 commit。这样人才敢信这张地图。

基于这三个边界,Agent 的架构就清晰了:它需要一个信息采集层(从代码、文档、issue、测试报告里抽信息),一个知识组织层(把信息组织成图谱),一个查询接口层(让人能问问题),一个增量更新层(持续维护)。这四层缺一不可。

2.3 为什么选 UMD 和 KMD 作为地图的核心骨架

AI 芯片的全栈软件栈通常分这么几层:最底下是硬件和固件,往上依次是 KMD、UMD、Runtime、Compiler、Framework、应用层。其中 UMD 和 KMD 是软件栈的“腰”,承上启下,也是最复杂、最容易出问题的地方。

KMD 是内核态驱动,跑在操作系统内核里,负责硬件资源管理、内存映射、中断处理、任务调度。UMD 是用户态驱动,跑在用户空间,负责把上层 Runtime 的请求翻译成 KMD 能理解的命令,同时管理用户态的内存、队列、同步对象。这两层之间的接口是整颗芯片软件栈里最关键的接口,也是最容易出兼容性问题的地方。

我选 UMD 和 KMD 作为地图的核心骨架,有三个原因。一是接口最密集,UMD 和 KMD 之间的 ioctl、共享内存、队列对、事件通知,这些接口的数量和复杂度远超其他层。二是变更最频繁,芯片 bring-up 阶段,UMD 和 KMD 几乎每周都在改,地图必须能跟上。三是影响面最大,UMD 和 KMD 的接口一变,上面的 Runtime、Compiler、Framework 全要跟着改,所以地图要能快速做影响分析。

把 UMD 和 KMD 作为骨架,其他层作为“挂载点”,整个地图就有了主心骨。Agent 在采集信息时,优先采集 UMD 和 KMD 的模块、接口、依赖,然后再往上挂 Runtime、Compiler、Framework 的信息。这样地图的层次感就出来了。

3. 核心细节解析:Agent 怎么把零散信息变成结构化地图

3.1 信息采集:从代码仓库里抽模块和依赖

Agent 要做的第一件事是从代码仓库里抽模块和依赖。这里的关键是“抽什么”和“怎么抽”。

抽什么?我定义了四类核心信息:模块定义(这个模块叫什么、在哪个目录、负责什么)、接口定义(这个模块暴露了哪些函数、ioctl、数据结构)、依赖关系(这个模块依赖了哪些其他模块的接口)、变更历史(这个模块最近改了什么、谁改的、为什么改)。

怎么抽?对于 C/C++ 代码,Agent 可以用 tree-sitter 做语法分析,提取函数定义、结构体定义、宏定义。对于 ioctl 这种特殊的接口,Agent 要能识别_IOW、_IOR、_IOWR这些宏,提取出 ioctl 号、参数类型、方向。对于依赖关系,Agent 要能分析#include、函数调用、符号引用,建立模块之间的依赖图。

这里有个坑:AI 芯片的代码仓库往往很大,几十万行代码,Agent 不可能每次全量扫描。我的做法是让 Agent 先做一次全量扫描建立基线,然后后续只扫描变更的文件。变更检测可以用 git diff,也可以用文件哈希。增量扫描能把每次更新的时间从几十分钟压到几分钟。

还有一个坑:代码里的依赖关系有很多是“隐式”的,比如通过全局变量、通过回调函数、通过共享内存。这些依赖光靠静态分析抽不出来,需要 Agent 结合运行时日志和测试用例来推断。我的做法是让 Agent 先抽显式依赖,然后标记出“疑似隐式依赖”的地方,让人来确认。

3.2 信息采集:从文档和 issue 里抽设计决策和已知问题

代码仓库能告诉你“是什么”,但告诉不了你“为什么”和“有什么坑”。这两类信息在文档和 issue 里。

文档包括设计文档、接口文档、测试文档、用户手册。Agent 要从这些文档里抽三类信息:设计决策(为什么这么设计、当时考虑了哪些方案、为什么选了这个)、约束条件(这个模块有什么限制、什么情况下不能用)、使用示例(怎么调用这个接口、参数怎么填、返回值怎么处理)。

issue 包括 bug 报告、功能请求、讨论记录。Agent 要从 issue 里抽已知问题(这个模块有什么已知 bug、什么条件下会触发、有没有 workaround)、变更原因(这个接口为什么改、改之前是什么样、改之后兼容性如何)、经验教训(这个坑是怎么踩的、怎么避免)。

这里最大的挑战是文档和 issue 往往是非结构化的自然语言,Agent 需要用 NLP 技术来抽信息。我的做法是让 Agent 先做实体识别(识别出模块名、接口名、函数名),然后做关系抽取(识别出“A 依赖 B”“A 导致 B”“A 替代 B”这些关系),最后做摘要生成(把一段讨论压缩成一句话的结论)。

实测下来,Agent 从文档和 issue 里抽信息的准确率大概在 70% 到 80% 之间,剩下的 20% 到 30% 需要人工审核。这个准确率已经比人肉整理高很多了,因为人肉整理往往会漏掉很多细节,而 Agent 至少能做到“全量扫描,不漏”。

3.3 知识组织:用图结构表达模块、接口和依赖

采集到的信息怎么组织?我用的是图结构。图里的节点有三类:模块节点(UMD 的内存管理模块、KMD 的调度模块)、接口节点(kmd_alloc、umd_submit)、问题节点(已知 bug、待办事项)。图里的边有四类:依赖边(A 依赖 B)、调用边(A 调用 B)、影响边(A 变更影响 B)、关联边(A 和 B 相关)。

为什么用图而不是树?因为软件栈的依赖关系不是树状的,是网状的。一个 UMD 模块可能依赖多个 KMD 接口,一个 KMD 接口可能被多个 UMD 模块调用,一个已知问题可能涉及多个模块。树结构表达不了这种多对多的关系,图结构可以。

图建好之后,Agent 就能回答很多传统文档回答不了的问题。比如“UMD 的内存管理模块直接和间接依赖了哪些 KMD 接口”,这是一个图遍历问题,Agent 从 UMD 内存管理节点出发,沿着依赖边走,就能找到所有直接和间接依赖的 KMD 接口。再比如“如果 KMD 的调度接口改了,哪些 UMD 模块会受影响”,这是一个反向图遍历问题,Agent 从 KMD 调度接口出发,沿着影响边反向走,就能找到所有受影响的 UMD 模块。

图结构还有一个好处是可视化。你可以把图渲染成一张网络图,节点是模块和接口,边是依赖和调用,一眼就能看出哪些模块是“枢纽”(连接很多其他模块),哪些模块是“孤岛”(没什么连接)。这对架构师判断模块划分是否合理很有帮助。

3.4 查询接口:让人能用自然语言问地图问题

地图建好了,怎么让人用?最自然的方式是自然语言查询。你问“UMD 的内存管理模块依赖哪些 KMD 接口”,Agent 把这个问题翻译成图查询,然后返回结果。

这里的关键是意图识别和查询生成。Agent 要先识别出你问的是哪类问题:是“依赖查询”(A 依赖什么)、是“影响查询”(A 变更影响什么)、是“路径查询”(A 到 B 的依赖路径是什么)、还是“属性查询”(A 模块的负责人是谁)。识别出意图后,Agent 再生成对应的图查询语句。

我实测下来,自然语言查询的准确率取决于两个因素:一是地图本身的质量,如果地图里的模块名、接口名不规范,Agent 就识别不准;二是问题的表述方式,如果问题里用了地图里没有的别名,Agent 也识别不准。所以我在设计时加了一个“别名表”,让人可以把常用的别名映射到地图里的标准名。

除了自然语言查询,Agent 还支持结构化查询,比如直接写图查询语句。这对高级用户很有用,因为自然语言查询有时候不够精确,结构化查询可以精确控制查询逻辑。

4. 实操过程:从零搭建一个 AI 芯片全栈软件地图 Agent

4.1 环境准备与工具选型

先说环境。我用的是一台 32 核 128G 内存的 Linux 服务器,跑 Ubuntu 22.04。为什么需要这么大内存?因为代码仓库全量扫描时,Agent 要把整个仓库的 AST 加载到内存里,几十万行代码的 AST 大概要占几十 G 内存。如果仓库更大,可能还需要分布式扫描。

工具选型上,我用了这几个核心组件:

  • tree-sitter:做代码语法分析,支持 C/C++、Python、Rust 等多种语言。选它是因为它解析速度快、增量解析支持好、API 简单。
  • Neo4j:做图数据库,存模块、接口、依赖关系。选它是因为它查询语言 Cypher 表达力强、可视化工具成熟、社区活跃。
  • LangChain:做 Agent 的编排框架,把信息采集、知识组织、查询接口串起来。选它是因为它生态丰富、文档齐全、上手快。
  • OpenAI API:做自然语言理解和生成。选它是因为效果稳定、接口简单、成本可控。

这里要说明一下,工具选型不是唯一的,你可以用别的图数据库(比如 NebulaGraph)、别的 Agent 框架(比如 AutoGen)、别的 LLM(比如 Claude)。我选这套是因为我用得最熟,而且实测下来稳定。

环境准备好之后,先建一个 Neo4j 数据库,建好节点和边的 schema。节点标签有Module、Interface、Issue,边类型有DEPENDS_ON、CALLS、AFFECTS、RELATES_TO。schema 建好之后,Agent 采集信息时就直接往图里写。

4.2 代码仓库扫描与模块抽取

第一步是扫描代码仓库,抽取模块和接口。我写了一个 Python 脚本,用 tree-sitter 解析每个源文件,提取函数定义、结构体定义、宏定义。

import tree_sitter from tree_sitter import Language, Parser # 加载 C 语言语法 LANGUAGE = Language('build/my-languages.so', 'c') parser = Parser() parser.set_language(LANGUAGE) def extract_functions(file_path): with open(file_path, 'rb') as f: code = f.read() tree = parser.parse(code) functions = [] # 遍历 AST,找函数定义节点 query = LANGUAGE.query('(function_definition declarator: (function_declarator declarator: (identifier) @name))') captures = query.captures(tree.root_node) for node, name in captures: functions.append({ 'name': name.text.decode('utf-8'), 'start_line': node.start_point[0], 'end_line': node.end_point[0] }) return functions

这个脚本能抽出每个文件里的函数定义。对于 ioctl,我加了一个专门的提取逻辑,识别_IOW、_IOR、_IOWR宏,提取出 ioctl 号、参数类型、方向。

import re def extract_ioctls(file_path): with open(file_path, 'r') as f: content = f.read() ioctls = [] # 匹配 _IOW/_IOR/_IOWR 宏 pattern = r'#define\s+(\w+)\s+_IO([WR]?)\s*\(\s*(\w+)\s*,\s*(\d+)\s*,\s*(\w+)\s*\)' for match in re.finditer(pattern, content): ioctls.append({ 'name': match.group(1), 'direction': match.group(2), 'type': match.group(3), 'nr': match.group(4), 'arg_type': match.group(5) }) return ioctls

抽出来的模块和接口信息,直接写到 Neo4j 里。每个模块建一个Module节点,每个接口建一个Interface节点,模块和接口之间建EXPOSES边。

这里有个实操心得:代码仓库里往往有大量自动生成的代码和第三方代码,这些要排除掉。我的做法是维护一个排除列表,把third_party/、build/、generated/这些目录排除掉。如果不排除,地图里会混入大量无关信息,查询时噪音很大。

4.3 依赖关系抽取与图构建

模块和接口抽出来之后,下一步是抽依赖关系。依赖关系分两种:显式依赖和隐式依赖。

显式依赖靠静态分析抽。对于 C/C++ 代码,显式依赖包括#include、函数调用、符号引用。我写了一个脚本,用 tree-sitter 遍历 AST,找call_expression节点,提取出被调用的函数名,然后查这个函数属于哪个模块,建立模块之间的CALLS边。

def extract_calls(file_path, module_name): with open(file_path, 'rb') as f: code = f.read() tree = parser.parse(code) calls = [] query = LANGUAGE.query('(call_expression function: (identifier) @callee)') captures = query.captures(tree.root_node) for node, callee in captures: calls.append({ 'caller_module': module_name, 'callee_name': callee.text.decode('utf-8'), 'line': node.start_point[0] }) return calls

隐式依赖靠运行时日志和测试用例推断。比如两个模块通过共享内存通信,静态分析抽不出来,但运行时日志里能看到两个模块都在读写同一块内存。我的做法是让 Agent 分析运行时日志,找出“同一时间段内访问同一内存地址”的模块对,标记为“疑似隐式依赖”,然后让人来确认。

依赖关系抽出来之后,写到 Neo4j 里,建DEPENDS_ON边和CALLS边。边的属性里记录依赖的类型(显式/隐式)、强度(强/弱)、来源(代码/日志/文档)。

这里有个坑:依赖关系会有环。比如 UMD 模块 A 依赖 KMD 模块 B,KMD 模块 B 又回调 UMD 模块 A。这种环在软件栈里很常见,但图查询时如果不处理,会陷入死循环。我的做法是在图查询时加一个“最大深度”限制,比如最多遍历 5 层,超过就停。

4.4 文档和 issue 的信息抽取

代码仓库扫完之后,下一步是扫文档和 issue。文档我支持 Markdown、Word、PDF 三种格式,issue 我从 Jira、GitHub Issues、GitLab Issues 三个来源拉。

文档抽取用 LangChain 的文档加载器和文本分割器,把长文档切成小段,然后让 LLM 从每段里抽信息。抽取的 prompt 我调了很多次,最后定下来的是这个:

你是一个芯片软件栈的文档分析助手。请从以下文档片段中抽取: 1. 设计决策:为什么这么设计,考虑了哪些方案,为什么选了这个 2. 约束条件:这个模块有什么限制,什么情况下不能用 3. 使用示例:怎么调用这个接口,参数怎么填,返回值怎么处理 输出格式为 JSON,每个信息点包含 type、content、source 三个字段。

issue 抽取类似,但 prompt 不一样:

你是一个芯片软件栈的 issue 分析助手。请从以下 issue 中抽取: 1. 已知问题:这个模块有什么已知 bug,什么条件下会触发,有没有 workaround 2. 变更原因:这个接口为什么改,改之前是什么样,改之后兼容性如何 3. 经验教训:这个坑是怎么踩的,怎么避免 输出格式为 JSON,每个信息点包含 type、content、source 三个字段。

抽出来的信息,写到 Neo4j 里,建Issue节点和RELATES_TO边。Issue节点关联到相关的Module节点和Interface节点。

这里有个实操心得:LLM 抽取的信息一定要人工审核。我实测下来,LLM 抽取的准确率大概 70% 到 80%,剩下的 20% 到 30% 有各种问题,比如把“不推荐”抽成“推荐”,把“已废弃”抽成“可用”。我的做法是让 Agent 把抽取结果标记为“待审核”,然后让人在 Neo4j 的界面上逐条审核,审核通过的标记为“已确认”,审核不通过的标记为“已拒绝”。

4.5 查询接口实现与自然语言问答

地图建好之后,最后一步是实现查询接口。我实现了两种查询方式:自然语言查询和结构化查询。

自然语言查询用 LangChain 的 Agent 实现。用户输入一个问题,Agent 先识别意图,然后生成 Cypher 查询,执行查询,最后把结果用自然语言返回。

from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI def query_graph(cypher): # 执行 Cypher 查询 with driver.session() as session: result = session.run(cypher) return [record.data() for record in result] tools = [ Tool( name="QueryGraph", func=query_graph, description="执行 Cypher 查询,输入是 Cypher 语句,输出是查询结果" ) ] agent = initialize_agent(tools, OpenAI(temperature=0), agent="zero-shot-react-description") response = agent.run("UMD 的内存管理模块依赖哪些 KMD 接口?")

结构化查询就是直接写 Cypher。比如“UMD 的内存管理模块依赖哪些 KMD 接口”对应的 Cypher 是:

MATCH (umd:Module {name: 'UMD_MEM'})-[:DEPENDS_ON*1..5]->(kmd:Module) WHERE kmd.layer = 'KMD' RETURN kmd.name, kmd.description

这里有个实操心得:自然语言查询的准确率取决于地图的质量和问题的表述。如果地图里的模块名不规范,或者问题里用了别名,Agent 就识别不准。我的做法是维护一个“别名表”,把常用的别名映射到标准名。比如“内存管理”映射到“UMD_MEM”,“调度”映射到“KMD_SCHED”。别名表让自然语言查询的准确率从 60% 提升到了 85%。

5. 常见问题与排查技巧实录

5.1 代码扫描太慢怎么办

这是最常见的问题。全量扫描一个几十万行的代码仓库,tree-sitter 解析加图写入,可能要几十分钟甚至几个小时。我的优化方案分三步。

第一步是增量扫描。用 git diff 找出变更的文件,只扫描变更的文件。增量扫描能把每次更新的时间从几十分钟压到几分钟。但增量扫描有个问题:如果变更涉及接口定义,依赖关系可能变了,需要重新计算依赖。我的做法是让 Agent 在增量扫描时,把变更文件相关的依赖边先删掉,重新扫描后再建。

第二步是并行扫描。把仓库按目录切成多个分片,每个分片用一个进程扫描,最后合并结果。并行扫描能把时间再压一半。但并行扫描有个坑:如果两个分片之间有依赖关系,合并时可能会漏掉跨分片的依赖。我的做法是在合并时,对跨分片的依赖做一次补充扫描。

第三步是缓存 AST。tree-sitter 的 AST 可以序列化缓存,下次扫描时如果文件没变,直接读缓存,不用重新解析。缓存能把重复扫描的时间压到几乎为零。

5.2 依赖关系抽不准怎么办

依赖关系抽不准有两个原因:一是静态分析抽不出隐式依赖,二是 LLM 抽取有误差。

对于隐式依赖,我的做法是多源验证。静态分析抽一遍,运行时日志抽一遍,测试用例抽一遍,三个来源都指向同一个依赖,才标记为“确认依赖”。只有一个来源指向的,标记为“疑似依赖”,让人来确认。

对于 LLM 抽取误差,我的做法是交叉验证。用两个不同的 LLM 抽同一段文档,如果两个 LLM 抽出的结果一致,标记为“高置信度”;如果不一致,标记为“低置信度”,让人来审核。交叉验证能把 LLM 抽取的准确率从 70% 提升到 85%。

还有一个技巧是用结构化信息辅助 LLM。比如抽依赖关系时,先把代码里的#include和函数调用抽出来,作为“提示”给 LLM,让 LLM 在这些提示的基础上抽。这样 LLM 不容易漏掉显式依赖,只需要专注于隐式依赖。

5.3 地图更新后怎么保证一致性

地图是持续更新的,每次更新都可能引入不一致。比如一个模块被删了,但依赖它的边还在;一个接口改了名,但引用它的地方没改。

我的做法是每次更新后跑一致性检查。检查分三类:一是孤儿检查,找出没有入边或出边的节点,这些节点可能是被删了但没清理干净的;二是悬空引用检查,找出引用了不存在节点的边,这些边可能是接口改名后没更新的;三是环检查,找出依赖关系里的环,这些环可能是设计问题,也可能是数据错误。

一致性检查发现问题后,Agent 会生成一个“修复建议”,让人来确认。确认后,Agent 自动修复。实测下来,每次更新后跑一致性检查,能把地图的错误率控制在 5% 以内。

5.4 常见问题速查表

问题可能原因排查方法解决方案
代码扫描太慢全量扫描、单进程、无缓存看扫描日志,统计每个阶段耗时增量扫描、并行扫描、缓存 AST
依赖关系抽不准隐式依赖、LLM 误差对比多源抽取结果多源验证、交叉验证、结构化提示
地图更新后不一致孤儿节点、悬空引用、环跑一致性检查自动修复、人工确认
自然语言查询不准别名、地图质量差看查询日志,统计失败率维护别名表、规范模块名
图查询太慢图太大、查询太复杂看查询计划,统计遍历节点数加索引、限制遍历深度、分片查询

5.5 独家避坑技巧

技巧一:先建小地图,再扩大地图。不要一上来就扫全仓库,先选一个核心模块(比如 UMD 的内存管理),把它的模块、接口、依赖抽出来,建一个小地图。小地图跑通了,再逐步扩展到其他模块。这样能快速验证方案,也能快速发现问题。

技巧二:地图的 schema 要稳定。节点标签和边类型一旦定了,就不要轻易改。因为 schema 一改,所有查询和可视化都要跟着改。我的做法是 schema 定下来后,写一个 schema 文档,所有团队成员都按这个 schema 来。

技巧三:地图要有人工审核环节。不要指望 Agent 全自动建图,一定要有人工审核。我的做法是让 Agent 把抽取结果标记为“待审核”,然后让人在 Neo4j 的界面上逐条审核。审核通过的标记为“已确认”,审核不通过的标记为“已拒绝”。人工审核能把地图的准确率从 70% 提升到 95%。

技巧四:地图要能导出。地图不能只存在 Neo4j 里,要能导出成各种格式,比如 Markdown、JSON、GraphML。这样团队成员不用装 Neo4j 也能看地图,也能把地图集成到其他工具里。

技巧五:地图要能追溯。地图上的每一条信息都要能追溯到来源。比如“UMD 的内存管理模块依赖 KMD 的kmd_alloc接口”这条信息,要能点进去看到是哪个代码文件、哪一行、哪个 commit。这样人才敢信这张地图。

6. 地图的演进与扩展:从 UMD/KMD 到全栈

6.1 从 UMD/KMD 扩展到 Runtime 和 Compiler

UMD 和 KMD 的地图建好之后,下一步是往上扩展到 Runtime 和 Compiler。Runtime 是芯片软件栈的“调度中心”,负责把上层 Framework 的请求翻译成 UMD 能理解的命令,同时管理任务队列、内存池、同步对象。Compiler 是芯片软件栈的“翻译官”,负责把上层 Framework 的算子翻译成芯片能执行的指令。

扩展的方法跟 UMD/KMD 类似:先扫代码仓库,抽模块和接口;再扫文档和 issue,抽设计决策和已知问题;然后建依赖关系,写到图里。但 Runtime 和 Compiler 有自己的特点,需要特殊处理。

Runtime 的特点是状态机复杂。Runtime 里有很多状态机,比如任务状态机、内存状态机、同步状态机。这些状态机在代码里往往是一堆switch-case和if-else,静态分析抽不出状态转移图。我的做法是让 Agent 分析运行时日志,从日志里抽状态转移,重建状态机。

Compiler 的特点是pass 依赖复杂。Compiler 里有很多 pass,每个 pass 做一种优化,pass 之间有依赖关系。这些依赖关系在代码里往往是一堆注册和调度逻辑,静态分析抽不出完整的 pass 依赖图。我的做法是让 Agent 分析编译日志,从日志里抽 pass 执行顺序,重建 pass 依赖图。

6.2 从静态地图到动态地图

静态地图是“代码里有什么”,动态地图是“运行时发生了什么”。静态地图能告诉你模块和接口的定义,但告诉不了你运行时的行为。比如“UMD 的内存管理模块在什么情况下会调用 KMD 的kmd_alloc接口”,静态地图给不了你答案,动态地图可以。

动态地图的构建方法是分析运行时日志。Agent 从日志里抽事件(比如“UMD 内存管理模块调用了kmd_alloc”),然后建事件之间的时序关系(比如“A 事件在 B 事件之前发生”),最后把事件和静态地图里的模块、接口关联起来。

动态地图的价值在于行为分析。比如你想知道“UMD 的内存管理模块在什么情况下会触发 KMD 的调度”,静态地图只能告诉你“UMD 内存管理模块依赖 KMD 调度接口”,动态地图能告诉你“当内存分配超过阈值时,UMD 内存管理模块会触发 KMD 调度”。这对排查性能问题和稳定性问题很有帮助。

6.3 地图的自动化维护与持续集成

地图建好之后,最大的挑战是持续维护。芯片软件栈每周都在变,地图如果跟不上,很快就没人用了。

我的做法是把地图维护集成到 CI/CD 流程里。每次代码提交,CI 自动跑增量扫描,更新地图。每次文档更新,CI 自动跑文档抽取,更新地图。每次 issue 关闭,CI 自动跑 issue 抽取,更新地图。更新后跑一致性检查,发现问题自动生成修复建议,发到团队群里让人确认。

这样地图就能跟上软件栈的演进,不会变成“死文档”。实测下来,集成到 CI/CD 后,地图的更新延迟从“几周”压到了“几小时”,团队用地图的频率也高了很多。

6.4 地图的查询接口扩展:从问答到 API

自然语言问答适合人用,但不适合工具集成。比如你想把地图集成到 IDE 里,让开发者在写代码时就能看到依赖关系,自然语言问答就不合适了。这时候需要 API。

我实现了一套 REST API,支持几种查询:/modules查模块列表,/interfaces查接口列表,/dependencies查依赖关系,/impact查影响分析。API 返回 JSON,方便工具集成。

API 的价值在于生态扩展。有了 API,你可以把地图集成到 IDE、集成到代码审查工具、集成到监控系统。比如在代码审查时,如果提交的代码改了某个接口,代码审查工具可以调地图的/impactAPI,自动列出受影响的模块,提醒审查者注意。

7. 我个人在实际操作中的体会

做这个项目的过程中,我最大的体会是:Agent 的价值不在于“替代人”,而在于“放大人”。Agent 不能替代架构师判断模块该怎么拆、接口该怎么设计,但 Agent 能把架构师从“信息收集和整理”的苦力活里解放出来,让架构师专注于判断和决策。

第二个体会是:地图的质量取决于信息的质量,信息的质量取决于采集的覆盖度。如果只扫代码不扫文档,地图就只有“是什么”没有“为什么”;如果只扫文档不扫 issue,地图就只有“设计”没有“坑”。只有多源采集,地图才完整。

第三个体会是:人工审核环节不能省。我试过全自动建图,结果地图里混入了大量错误信息,团队用了几次就不敢用了。后来加了人工审核,地图的准确率上去了,团队才敢信、才敢用。

第四个体会是:地图要能追溯。地图上的每一条信息都要能追溯到来源,这样人才敢信。我试过不追溯来源,结果团队用地图时总是问“这个信息哪来的”,用了几次就不用了。后来加了追溯,团队用地图时能直接点进去看来源,信任度就上去了。

最后分享一个小技巧:地图的可视化很重要。人都是视觉动物,一张网络图比一堆文字更容易理解。我用了 Neo4j 的可视化工具,把地图渲染成网络图,节点是模块和接口,边是依赖和调用。团队一看图就能看出哪些模块是“枢纽”,哪些模块是“孤岛”,对架构判断很有帮助。

这个项目后续还可以这样扩展:一是把地图和芯片的硬件设计关联起来,从软件地图追溯到硬件模块;二是把地图和性能数据关联起来,从软件地图追溯到性能瓶颈;三是把地图和测试用例关联起来,从软件地图追溯到测试覆盖。这些扩展能让地图的价值从“知识管理”扩展到“研发效能”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 10:13:20

换热站循环水余热利用:热泵回收系统设计与工程实践

循环水余热利用这个话题,我在换热站项目上摸爬滚打了七八年,见过太多把几十度热水直接排掉的案例了。前两天还有个物业经理找我,说他们小区换热站冬天供完暖之后,回水温度还有四十多度,直接回锅炉房重新加热&#xff0…

作者头像 李华
网站建设 2026/10/4 10:12:57

结构工程、医学、建筑专业的毕业图怎么画?专业图表板块实测

不同学科的论文,对图表的要求完全是两个世界。文科的图能把数据说清楚就行;工科的图要有剖面图、受力图、尺寸标注;医学的图要符合临床规范,直方图、生存曲线、误差棒一个都不能少。我一个学结构工程的学弟跟我吐槽,他…

作者头像 李华