news 2026/9/26 1:44:23

RISC-V AI芯片软件栈从零搭建:从指令集到Agent的六层实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 模型里的一个子类,专门处理文本,核心能力是预测下一个 token;AI 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 循环

我建议所有初学者从 ReAct(Reasoning + Acting)模式开始,不要一上来就接复杂框架。ReAct 的循环只有四步:

  1. 把系统提示词和用户问题拼成消息序列,发给 LLM。
  2. LLM 返回一段文本,里面既包含“思考”,也包含“行动”,例如输出一个 JSON 块,指明要调用哪个工具、传什么参数。
  3. 代码解析这个 JSON,执行工具,拿到返回结果。
  4. 把工具结果作为新的消息追加到对话里,回到第 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 越大,单条指令能处理的数据越多,但寄存器压力也越大。实际调参时,我会把 LMUL=2 或 4 作为起步值,再根据 cache miss 的情况做调整。

GGML 在 llama.cpp 中的矩阵乘 kernel 已经包含了 RVV 的优化分支,但前提是你编译时要开启LLAMA_RVV=ON,并且用支持 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 console=ttyS0" \ -drive file=rootfs.ext2,format=raw,if=virtio \ -nographic

看到buildroot login:提示符,说明这条链路已经通了。下一步是用交叉编译器把 llama.cpp 编出来。

5.2 交叉编译 llama.cpp 到 riscv64

交叉编译的要点是让 CMake 正确找到目标平台工具链,同时关掉本机 CPU 特性优化(LLAMA_NATIVE=OFF)。我用的工具链文件大概长这样:

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_FILE=riscv64-toolchain.cmake \ -DLLAMA_RVV=ON \ -DLLAMA_NATIVE=OFF \ -DCMAKE_BUILD_TYPE=Release 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+ 基本都支持);第二步,用-march=rv64gcv显式指定架构,避免编译器默认使用保守设置;第三步,用qemu -cpu rv64,v=true或真实带 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 扩展的算子跑起来。到那时,我们才算真正开始“造”这块芯片的软件栈。

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

ANSYS Electronics 2024 R2 安装本质:多物理场平台部署指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:44:07

WT2606A芯片实现200条离线命令词与混合多轮语音交互

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:43:35

FPGA与32颗IMU阵列:低成本地震检波器替代方案全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:42:06

Sigmoid函数深度解析:从数学推导到工程实践与梯度消失

1. 从一个被问烂了的问题说起:为什么还要聊Sigmoid每次带新人入门机器学习,讲到神经网络那一章,总有人举手问:“现在大家都用ReLU了,Sigmoid是不是已经淘汰了?”这个问题我大概被问过不下五十遍。我的回答通…

作者头像 李华
网站建设 2026/9/26 1:41:53

从数据库设计到事务并发:学生选课系统实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:40:15

DeepSeek Harness + MCP 实战部署避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华