RISC-V AI芯片软件栈从零搭建:从指令集到Agent的六层实践
做 RISC-V AI 芯片的软件栈最难受的地方不是没有处理器而是处理器一上电你发现自己其实站在一片还没铺路的地基上。RISC-V 这两年声音越来越大“指令集开放、可扩展”这些优势固然是前提但真正让 AI Agent 和芯片设计产生交集的是模型推理、工具调用、运行时调度这些原本属于上层应用的东西如今都要从底层重新设计一遍。这个系列就是要把这条漫长的路拆成一个个能落地的步骤而不是给一块已成熟的芯片再补一层软件皮。这篇导读想解决三个问题第一一块 RISC-V AI 芯片的软件栈到底分成几层每层要做什么第二AI Agent、LLM、AI 模型这几个词到底什么关系我们常说的 DeepSeek 在其中扮演什么角色第三从零开始按什么顺序搭出一个最小可运行的环境并在上面跑通一个最简单的 Agent。适合三类人看做芯片工具链或系统软件的工程师、想往 AI 底层走的应用开发者以及打算在 RISC-V 上跑智能体的学生或创业者。1. 系列定位为什么要把 AI Agent 和 RISC-V 放在一起造1.1 软件栈和指令集天生就是一对说“造一块 RISC-V AI 芯片的软件栈”可能有人第一反应是把 Linux、编译器和推理框架移植一下让模型能在上面跑起来。这句话没错但只说对了一半。更准确的说法是软件栈决定了这块芯片到底能承接什么样的 AI 工作负载而 AI Agent 这类应用恰好是考验整个链条是否完整的关键案例。一条完整的推理链路是这样的用户输入一段自然语言Agent 系统把它拆解成“思考、行动、观察结果”的循环每一次行动背后都有一次模型推理推理本身对应大量矩阵乘法和注意力计算这些计算最终要落到 CPU/向量单元的指令上。RISC-V 的价值在于你可以围绕这套负载去定制指令集和微架构但代价是指令集只是第一层后面还需要编译器生成高效代码、运行时管理内存和多核调度、算子库提供可用的 kernel推理框架负责模型加载和量化最上面才是 Agent 应用。换句话说在 x86 或 ARM 上软件栈是“已经铺好路你只管开车”在 RISC-V AI 芯片上软件栈是“边修路边开车”。本系列的每篇文章都会围绕某一个层级展开从指令集出发一路做到 Agent 应用。1.2 哪些人值得读这个系列我默认读者具备一点 Linux 和 C/C 基础但不需要你是编译器专家也不要求你写过深度学习模型。下面三类人都能从系列里找到自己需要的部分系统软件工程师平时在搞交叉编译、内核、BSP想了解 AI 负载对软件栈的新要求尤其是 RVV 向量扩展和算子优化的思路。AI 应用工程师会用 PyTorch、LangChain但对芯片层“黑盒”很好奇想知道模型运行时在目标硬件上经历了什么以及深度求索这类开源大模型怎么部署到本地。学生或创业者正在选型下一台开发板或者规划一个边缘 AI 产品希望知道 RISC-V 这条路能不能走通坑在哪前期的投入到底有多大。这个系列不会只给结论还会给出可以直接照做的命令、配置和代码骨架并在每篇里记录我实际踩过的坑。这样你不需要从零推导照着做能少走很多弯路。2. 整体设计从指令集到 Agent 的六层路线图2.1 软件栈分层模型先画地图再动工造软件栈最忌讳的是上来就编译一个大项目发现缺十个依赖补完又发现架构不支持。我习惯先把整条链路分层每一层只关心和上下层的接口再逐层去打通。这样排查问题的时候也能快速定位是工具链的问题、运行时的问题还是应用的问题。层级这一层解决什么典型组件应用层对话、工具调用、多智能体协作LangGraph、AutoGen、Spring AI推理层模型加载、量化、推理调度llama.cpp、ONNX Runtime、MNN算子层矩阵乘、注意力、量化 kernelGGML、oneDNN、自研 RVV kernel系统层进程调度、内存管理、设备驱动Linux Kernel、OpenSBI、NPU/向量驱动工具链层编译、汇编、链接、调试GCC/LLVM、binutils、QEMU、GDB指令集层架构规范、扩展定义RISC-V IMAFDC、V 扩展、厂商自定义加速指令这张表就是整个系列的目录。命令和代码会变但分层思维不会变。2.2 六个主题怎么拆我计划用六篇主体文章把上面六层走一遍中间穿插两到三篇专题讲怎么用 AI Agent 反过来加速软件开发。目前规划如下第 1 篇系列导读本篇把问题和路线图讲清楚。第 2 篇RISC-V 指令集与向量扩展详解重点看 V 扩展如何支撑矩阵乘和量化算子。第 3 篇交叉编译工具链与系统启动用 QEMU 跑起最小 Linux 环境。第 4 篇推理引擎移植与算子优化把 llama.cpp 在 RISC-V 上跑通并分析几个关键算子的性能。第 5 篇Agent 框架与运行工程设计在边缘设备上实现工具调用和多轮记忆。第 6 篇端到端演示让 RISC-V 虚拟机里的 Agent 帮我完成一个真实的小任务。这样安排的理由很简单每一步都在前一步的基础上做“可运行”的验证。工具链没通后面全是空中楼阁推理引擎没通Agent 就是空壳。2.3 工具选型为什么 QEMU 和 llama.cpp 是起步最优解经常有人在群里问要不要直接买一块几千块的 RISC-V 开发板我的建议是如果你是第一次接触这个方向先别买。开发板的坑太多串口驱动、固件版本、散热、电源随便一个都能耗掉你一个周末。用 QEMU 模拟器起步成本为零环境可复现还能随时 reset。推理引擎方面我首选 llama.cpp而不是 ONNX Runtime 或者 PyTorch。原因有三个第一llama.cpp 对 GGUF 格式的量化支持非常成熟q4_k_m 这种量化级别在内存受限环境里很实用第二它对 RISC-V 向量扩展有对应的编译开关LLAMA_RVV社区活跃度在上海洋第三它自带一个和 OpenAI API 兼容的 HTTP 服务后面跑 Agent 的时候可以少写很多胶水代码。Agent 框架我留到第五篇再深入选型因为早期阶段你只需要一个“循环调用模型”的脚本就够。框架选得太早容易被抽象概念带走反而不理解底层发生了什么。3. 核心概念拆解AI Agent、LLM 和 AI 模型的关系3.1 三个词经常一起出现但边界完全不同我见过不少人把这三个词混用实际上它们是包含关系AI 模型是最大的集合泛指一切深度学习模型包括图像分类、语音识别、推荐系统LLM大语言模型是 AI 模型里的一个子类专门处理文本核心能力是预测下一个 tokenAI Agent 不是一个模型而是一套系统系统里可以包含一个或多个 LLM再加上规划、记忆、工具调用这些模块。打个比方AI 模型是发动机LLM 是涡轮增压发动机AI Agent 是整台带方向盘、导航和传感器的车。你问“deepseek 属于哪个”答案是DeepSeek 是一系列大语言模型的统称属于 LLM 这个类别它可以被当作 Agent 的“大脑”来用但它本身不是一个 Agent。3.2 为什么 Agent 不能只靠模型本身很多人第一次搭建 Agent 时觉得奇怪模型不是能回答问题吗为什么还要额外设计“规划”和“工具调用”原因在于LLM 的知识是静态的它的能力边界是训练数据决定的。让它算 12345 × 6789它可以强答但容易错让它查一个实时价格它根本不知道。Agent 的价值就是给模型加“手”和“眼睛”要么外接计算器、搜索接口、命令行工具要么接入企业内部的数据库和 API。这样一来复杂任务被拆解成多步模型只需要负责每一步的“决策”剩下的具体动作交给代码去执行。这个思路在芯片软件栈的场景里特别有意义。比如让 Agent 帮你编译一个项目编译失败后Agent 可以把报错日志截取出来让模型分析是哪一行代码的问题然后调用工具去定位头文件路径或者修改编译器参数。这个过程不是模型自己会的是靠 Agent 结构设计出来的。3.3 最小可用的 Agent 结构ReAct 循环我建议所有初学者从 ReActReasoning Acting模式开始不要一上来就接复杂框架。ReAct 的循环只有四步把系统提示词和用户问题拼成消息序列发给 LLM。LLM 返回一段文本里面既包含“思考”也包含“行动”例如输出一个 JSON 块指明要调用哪个工具、传什么参数。代码解析这个 JSON执行工具拿到返回结果。把工具结果作为新的消息追加到对话里回到第 1 步。这套循环用 Python 写不到一百行却能让你彻底理解 Agent 的核心机制。等这个循环跑通了你再去看 LangGraph 的状态图、AutoGen 的多智能体聊天才能明白它们是在解决什么问题状态管理、上下文裁剪、异常处理、并发调度。对企业级 Java 生态的读者多说一句Spring AI 其实也是类似思路它把 ReAct 循环和工具调用封装成了一套 Java API。如果你们团队技术栈是 Java用 Spring AI 搭 Agent 平台是合理的但建议先在本系列的模拟器环境里跑通一次最小循环再上框架。4. RISC-V AI 芯片软件栈的硬骨头向量扩展与算子移植策略4.1 RVV 扩展一个可以折腾出花来的指令集特性RISC-V 基础指令集非常精简没有为 AI 准备任何特殊武器。真正的重头戏是 V 扩展RVV一套可变向量长度指令。它跟 ARM 的 NEON 有本质区别NEON 的向量宽度是固定的 128 位而 RVV 的 VLEN向量寄存器长度在架构层面是可配置的实现可以是 128 位也可以是 256 位或更长。这意味着同一套源码跑到 VLEN 不同的芯片上性能特征会完全不一样。以矩阵乘为例这是 AI 推理中最核心的算子。用 RVV 做矩阵乘的几个关键操作包括用vle32_v_f32加载浮点数据、用vfmacc_vv_f32做乘累加、用vredsum做规约。真正写的时候要考虑 LMUL 这个参数它是向量寄存器组倍数表示一次操作占用多少个向量寄存器。LMUL 越大单条指令能处理的数据越多但寄存器压力也越大。实际调参时我会把 LMUL2 或 4 作为起步值再根据 cache miss 的情况做调整。GGML 在 llama.cpp 中的矩阵乘 kernel 已经包含了 RVV 的优化分支但前提是你编译时要开启LLAMA_RVVON并且用支持 V 扩展的编译选项生成代码。这部分是第 4 篇的重点我会把几个核心 kernel 的向量化循环贴出来逐行讲。4.2 运行时与驱动的工程陷阱光有算子还不够整个系统层的复杂程度常常被低估。RISC-V 虚拟化形态下你要打通的软件栈包括OpenSBI机器模式下的固件层、Linux 内核、设备树DTB、根文件系统以及用户态运行库。其中任何一个版本对不上都可能出现“内核起来了但外设没法用”这种让人抓狂的问题。内存子系统是我最想提醒读者的地方。AI 推理和普通嵌入式应用的差别是它对内存带宽和容量的要求高很多。一个 1.5B 参数、Q4_K_M 量化级别的模型大概需要 1 GB 左右内存来存放权重推理过程中的激活值、KV Cache 又额外占用一部分。在 QEMU 里你需要给虚拟机配到至少 4 GB 内存在真实开发板上则要优先考虑带大容量内存或者可以扩展内存的型号。另外动态内存分配也需要留意。Agent 的多轮对话会让上下文不断增长KV Cache 如果按最大长度预分配内存占用会非常高如果按需增长又可能出现碎片化。我的做法是引入一个上下文管理器超过窗口长度后自动截断或者做摘要压缩这在边缘设备上是必备功能。4.3 推理引擎移植先跑通再优化很多团队在移植推理引擎时有一个误区一上来就盯着 benchmark试图把每层算子都调到最优。但如果你连一个完整的模型都还没跑通过性能调优的数据毫无意义。我的移植路径很固定第一步用编译好的 llama.cpp 在目标环境里跑内置测试确认基础功能正常。第二步跑一个 GGUF 格式的小模型确认推理结果合理。第三步开启-t参数测试多线程观察线程扩展性。第四步再进入算子级 profiling找出热点。算下来每一步之间最好间隔一个独立的可验证里程碑。比如第一步的目标是“llama-cli 能正常启动并输出帮助信息”第二步的目标是“llama-cli 能完整生成一句话”。哪怕慢一点也一定要让每个里程碑在真实目标环境上回放一遍不要只在交叉编译机器上测试。5. 实操在 QEMU 里把第一版 Agent 跑起来5.1 环境准备工具链和根文件系统一锅端我推荐用 Buildroot 一步到位地制作 RISC-V 的 Linux 根文件系统因为手动配 glibc、busybox、内核模块的版本兼容性真的很折磨人。Buildroot 里内置了qemu_riscv64_virt_defconfig基本可以直接用。make qemu_riscv64_virt_defconfig make -j$(nproc)这个命令会生成fw_jump.bin、Image、rootfs.ext2三个关键文件分别对应 OpenSBI 固件、Linux 内核镜像和根文件系统。启动命令我习惯写成下面这样qemu-system-riscv64 \ -M virt \ -smp 4 \ -m 4G \ -bios fw_jump.bin \ -kernel Image \ -append root/dev/vda rw consolettyS0 \ -drive filerootfs.ext2,formatraw,ifvirtio \ -nographic看到buildroot login:提示符说明这条链路已经通了。下一步是用交叉编译器把 llama.cpp 编出来。5.2 交叉编译 llama.cpp 到 riscv64交叉编译的要点是让 CMake 正确找到目标平台工具链同时关掉本机 CPU 特性优化LLAMA_NATIVEOFF。我用的工具链文件大概长这样set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR riscv64) set(CMAKE_C_COMPILER riscv64-unknown-linux-gnu-gcc) set(CMAKE_CXX_COMPILER riscv64-unknown-linux-gnu-g) set(CMAKE_FIND_ROOT_PATH /opt/toolchains/riscv64-unknown-linux-gnu) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)然后cmake -B build-riscv \ -DCMAKE_TOOLCHAIN_FILEriscv64-toolchain.cmake \ -DLLAMA_RVVON \ -DLLAMA_NATIVEOFF \ -DCMAKE_BUILD_TYPERelease cmake --build build-riscv -j$(nproc)编译完会产生llama-server可执行文件。把它拷到根文件系统之前建议先在本机用riscv64-unknown-linux-gnu-readelf -A检查一下二进制里是否带有v扩展标记。这一步能省掉很多“为什么代码跑起来就 illegal instruction”的排查时间。5.3 跑一个本地 Agent Demo不用 GPU 也能让人工智能接话把编译好的llama-server和模型文件挂载进 QEMU 后先启动服务./llama-server -m qwen2.5-1.5b-instruct-q4_k_m.gguf \ --host 0.0.0.0 --port 8080 -t 4 --ctx-size 2048这里-t 4是让四核 CPU 都参与推理。在虚拟机内部监听 8080 端口后宿主机上就可以用 Python 脚本访问 OpenAI 兼容的对话接口。一个最简单的 ReAct 循环可以这样写先给模型一段固定的系统提示要求它输出格式为“Action: 工具名Action Input: 参数”的文本代码解析这段文本后执行对应动作比如调用 Python 的内置计算器或者查找文件名把结果拼到上下文里再问一轮。整个过程不依赖 LangChain代码量很小但足以说明 Agent 的核心闭环。如果这一步能跑通就意味着这块“RISC-V AI 芯片”在模拟器里已经把从编译工具链到推理引擎再到 Agent 应用的整条链路走通了。后面再换真机、加 NPU 驱动、优化算子都只是在替换某一条链路而不是重新造轮子。6. 常见问题与排查技巧实录6.1 编译与链接阶段的高频问题最常碰到的报错是 “illegal instruction”特点是程序可以启动但执行到某个特定调用后就崩溃。十有八九是因为编译器生成的代码里带了 V 扩展指令而当前运行环境的 CPU 模拟器没有开启向量扩展或者二进制里混合了本机 CPU 特性。排查方法分三步第一步确认工具链支持 V 扩展GCC 12 基本都支持第二步用-marchrv64gcv显式指定架构避免编译器默认使用保守设置第三步用qemu -cpu rv64,vtrue或真实带 V 扩展的板子验证。如果还是崩就单独写一个五六行的向量加法测试程序这样能最快排除是不是编译参数本身的问题。另外工具链版本和 Buildroot 的用户态版本不匹配也很常见。比如用上游 glibc 2.38 编译的程序拷到一个基于 glibc 2.36 构建的文件系统里可能直接报 GLIBCXX 版本不存在。解决办法是让 Buildroot 自己生成配套工具链或者保证 sysroot 和 rootfs 来自同一套构建流程。6.2 运行时的精度与性能问题量化模型在 RISC-V 上跑出现结果和 x86 上略有差异是正常的但差太多就要警觉。常见原因有两个一是某些算子 fallback 到了纯 C 实现的 FP32 kernel和量化 kernel 的精度特征不一样二是线程竞争导致累积误差这在多线程推理时偶尔会出现。性能上我已经说过先跑通再优化。但有一个点值得提前留意内存映射方式。llama.cpp 默认用 mmap 加载模型在真实嵌入式设备上如果文件系统驱动有问题mmap 的随机读取性能会很差。此时可以加--no-mmap试试数据改为显式读入内存代价是加载时间变长但推理时的 page fault 会少很多。具体怎么取舍建议两个模式都跑一次用-t参数对比 token 生成速率。6.3 Agent 运行时的资源限制与工程化部署Agent 应用比单次模型推理要“贪心”因为上下文会越来越长。出现请求超时或者内存不足时不要只怪模型太大先看看上下文占了多少。我通常会把系统提示词压缩到最小工具结果只保留关键字段并用滑动窗口限制发送给模型的 token 数。还有一个容易被忽略的因素Agent 的 HTTP 服务并发机制。llama-server 在 CPU 环境里最好设置--parallel 1否则同时进来多个请求每个都要复制一份 KV Cache内存会快速见底。在部署形态上把 llama-server 和 Agent 代码封装成 systemd 服务是比较稳妥的做法崩溃了能在三秒内自动拉起。容器方案在真机上可以考虑但在 QEMU 里多一层容器会让调试变得更麻烦起步阶段不建议引入。6.4 用 AI Agent 反过来加速芯片软件开发这个系列标题里的“用 AI Agent 造”还有另一层含义Agent 作为生产力工具帮我们缩短开发周期。我在实际开发中已经用它处理过两类重复劳动编译错误分析交叉编译的报错信息往往很长把报错塞给一个能读上下文的 Agent它可以帮你定位到 CMake 配置或源码里确切的几行节省大量 grep 时间。自动生成算子测试用例针对 RVV 写单元测试时手工构造边界数据很烦。让 Agent 根据 kernel 的参数范围生成若干组输入和预期输出能显著提升覆盖率。不过要提醒一点Agent 生成代码的能力受模型版本影响很大在 RISC-V 这种相对小众的架构上模型经常“一本正经地胡说”。所有代码都必须拿到真实目标环境编译验证不能盲信输出。7. 写在系列开篇的一些提醒我个人在实际操作中的体会是软件栈开发最大的敌人不是技术难度而是“顺序错乱”。如果你一上来就折腾 Agent 框架却发现模型接口都没通那种挫败感会直接劝退自己。反过来把每一步都做成可验证的里程碑每周末都能看到一个东西真正跑起来整个周期反而更顺畅。最后分享一个小技巧一定要把每个组件的版本号记录下来包括 QEMU 版本、工具链 commit、llama.cpp 的 commit、模型文件的哈希值。RISC-V 生态还处于高速变动期很多问题都不是“代码写错了”而是“版本漂移了”。有了版本记录你发帖求助或者自己回溯都能省下大把时间。下一期我会从 RISC-V 向量扩展的基础概念讲起手把手带你把第一个带 V 扩展的算子跑起来。到那时我们才算真正开始“造”这块芯片的软件栈。