用AI Agent辅助搭建QEMU RISC-V AI芯片实验台:从零到RVV验证

发布时间:2026/10/29 19:16:37
用AI Agent辅助搭建QEMU RISC-V AI芯片实验台:从零到RVV验证

一台还没有拿到手的 AI 芯片、一个能用软件模拟出来的指令集、一个会自己写命令的 AGENT——这三样东西组合在一起,就成了我这几天一直在折腾的事:用 AGENT 做 QEMU 环境搭建,拉起一块 RISC-V AI 芯片的实验台。实际操作下来,其实就是让 AI Agent 帮我生成、执行和修正一套能在 x86 电脑上模拟 RISC-V 虚拟机的完整流程。文章不打算写成教程式复读机,而是把整个“人和 Agent 配合搭环境”的过程摊开讲:哪些决策交给 Agent 没问题,哪些必须自己判断,以及最后这台“模拟芯片”到底能干什么。

如果你手上没有 RISC-V 开发板,又想提前跑 AI 推理栈、摸一摸向量扩展指令,这篇应该能帮你省下不少时间。开始之前我默认你用的是 Ubuntu 22.04 或同类发行版,并且知道终端怎么开,后面所有操作都围绕系统级 QEMU 模拟展开。

1. 选型逻辑:RISC-V AI 芯片为什么值得用 QEMU 先“拉起”

很多人听到“AI 芯片实验台”就以为得买开发板、接 JTAG、调驱动。实际上在架构早期验证阶段,虚拟化模拟往往是性价比最高的选择。RISC-V 是一个开放指令集架构,AI 芯片厂商通常会在标准 RISC-V 基础上扩展向量指令(RVV),这一点 QEMU 从 7.0 开始已经能做得比较完整。用 QEMU 拉起一块虚拟 RISC-V 芯片,不需要等待流片,不需要布板子,改一行模拟参数就能改变 CPU 核数、内存大小、向量长度,这种灵活性是真实硬件很难给的。

另一个原因是整个软件栈的验证需要“先跑起来再说”。交叉编译器、内核镜像、根文件系统、AI 推理运行时,这些组件如果直接放在真实芯片上,出了问题很难判断是硬件还是软件的问题。QEMU 的-machine virt会生成一套标准的虚拟设备树,提供 virtio 总线、串口、网卡、磁盘,等于给 RISC-V Linux 一个“标准开发板”。系统能不能启动、驱动能不能加载、向量指令能不能算出正确结果,全在可控的日志范围里,这比在纸上推演靠谱得多。

我这次的目标很明确:在 x86 主机上用 QEMU 跑一个 riscv64 的完整 Linux 系统,然后在这个系统里验证 RVV 向量指令的加法和点积运算,作为后续跑 AI 算子的基线。至于 PyTorch、llama.cpp 这类重量级框架,属于实验台“通电”之后的下一步,不会在第一篇里硬塞。

2. Agent 接手第一步:让 AI Agent 先出依赖树和版本矩阵

搭 QEMU 环境最烦的不是 QEMU 本身,而是它背后一连串依赖和版本匹配问题。我一开始就决定让 AGENT 出方案。我先把主机的信息喂给 Agent:uname -a、lsb_release -a、gcc --version、内存/磁盘情况,然后给一条明确指令。

我现在在 x86_64 Ubuntu 22.04 上,想搭建一个面向 RISC-V AI 实验的 QEMU 系统级模拟环境。 目标: - 模拟器为 QEMU 7.2+,riscv64 softmmu - 能启动最新 Linux 6.x 内核 - 支持 RISC-V Vector Extension v1.0 - 虚拟机内要能运行交叉编译的 C 程序 请输出: 1. 依赖包清单(apt 安装) 2. QEMU 源码编译参数建议 3. 内核/根文件系统获取方案 4. 虚拟机启动命令模板

Agent 给出的第一版方案很标准:安装ninja-build, pkg-config, libglib2.0-dev, libpixman-1-dev, libslirp-dev,下载 QEMU 源码,然后用--target-list=riscv64-softmmu配置。这些都没问题,但有两处我需要亲自修正。

第一,Agent 默认让我直接用发行版仓库的 QEMU,版本是 6.2。这个版本虽然能跑 RISC-V,但对 RVV 1.0 的支持不完整,跑向量指令会偶尔报Illegal instruction。所以我坚持从源码编译 QEMU,版本选 8.1,因为从这一版开始-cpu max才能完整暴露v扩展。

第二,Agent 给的依赖包里漏掉了python3-venv和zlib1g-dev。前者在后续构建 Buildroot 时需要,后者是编译 QEMU 时slirp网络后端必需的。这说明 Agent 给依赖树依赖的是训练数据里的通用答案,并不能感知你本机已经装了什么、缺什么。让 Agent 出“版本矩阵”可以,但真正的“依赖校验”必须靠命令实测。

2.1 版本矩阵怎么定

这里我让 Agent 生成了一张对照表,然后自己标出优先级:

组件版本/分支选择原因
QEMU8.1.0 源码编译RVV 1.0 支持完整,virt 板型稳定
Buildroot2024.02 LTS一条命令生成内核+根文件系统,省去手工制作
Linux Kernel6.6.x(Buildroot内置)virt 平台支持好,AI 所需动态库兼容性高
交叉工具链riscv64-buildroot-linux-gnu-gcc由 Buildroot 内部生成,避免手工配置 sysroot
目标运行时静态编译的 RVV C 程序先验证指令通路,再逐步叠加 AI 框架

这个矩阵并不是 Agent 直接给我的,而是它提供了初稿,我根据“AI 实验台”目标做了两处替换:一是内核版本不能太新,太新设备树变化多;二是工具链不单独装 Linaro 版,直接用 Buildroot 内置工具链,保证后续 rootfs 和内核的 ABI 一致。

3. 真正动手编译 QEMU:三条命令和一次参数修正

依赖装好之后,编译过程反而没什么好讲的。真正有意思的是参数调整。

git clone https://gitlab.com/qemu-project/qemu.git -b v8.1.0 cd qemu ./configure --target-list=riscv64-softmmu --enable-slirp --disable-werror make -j$(nproc)

这里--disable-werror是我自己加的。Agent 给的原始命令没有这一项,但在 Ubuntu 22.04 上编译 QEMU 8.1,会遇到一些头文件告警被当作错误的情况,加上这一项可以避免无谓中断。--enable-slirp则是为了启用 user-mode 网络,让虚拟机可以通过 NAT 访问外网,方便后面apt安装软件。

第一次跑完 configure,Agent 建议我直接qemu-system-riscv64 -machine virt -cpu rv64启动,但我拒绝了,因为我希望虚拟芯片暴露完整的 AI 向量能力。查阅资料后,我在启动参数里加了如下 CPU 描述:

qemu-system-riscv64 \ -machine virt \ -cpu rv64,v=true,vext_spec=v1.0 \ -smp 4 -m 4G \ -nographic \ ...

补充说明一下:QEMU 不同版本对 RISC-V CPU 属性的写法并不一致。8.1 版本里用-cpu rv64,v=true,vext_spec=v1.0能打开标准向量扩展,但到了更早的 7.2,你可能得写成-cpu rv64,x-v=true。如果实在不确定,直接qemu-system-riscv64 -cpu help看当前支持哪些扩展属性,别盲信 Agent 给的参数。这个坑我后面还会具体说。

3.1 为什么用 virt 板型而不是 sifive_u

Agent 第一次给出的参数用的是-machine sifive_u。这板子比较接近真实 SiFive 硬件,但它的设备树是固定的,外设数量和内存上限都受限。我改用了virt。virt是 QEMU 给 RISC-V 设计的通用虚拟平台,类似 ARM 下的virt,优点是会自动生成设备树,通过 PCIe 和 virtio 接入外设,Linux 主线内核对它支持最完善。对 AI 实验台来说,我们后期可能要挂大内存、加模拟 NPU 设备,virt的扩展性明显更好。

3.2 编译完先跑一次“空系统”

编译完成后不要急着找内核,先用无内核方式检查模拟器能起来:

qemu-system-riscv64 -machine virt -cpu rv64,v=true,vext_spec=v1.0 -m 256M -nographic

这条命令会进入 QEMU monitor,显示(qemu)提示符,说明模拟器本身没毛病。这个时候按Ctrl-a x退出。很多人一上来就放内核,结果有问题分不清是内核还是 QEMU 配置问题,先验证模拟器就打好了地基。

4. 给虚拟机一块能跑 AI 的“芯”:内核与根文件系统

QEMU 模拟的是硬件本身,里面还得有操作系统。这里我选了 Buildroot,因为手工配内核.config再下载压缩 rootfs 太容易踩版本冲突的坑。Buildroot 对 QEMU virt 平台有现成支持,一条命令就能生成Image(内核镜像)和rootfs.ext2。

git clone https://github.com/buildroot/buildroot.git -b 2024.02 cd buildroot make qemu_riscv64_virt_defconfig make -j$(nproc)

这一步会跑挺久,主要是编译交叉工具链和内核。完成后在output/images/下能看到:

  • Image:Linux 内核镜像,RISC-V 64 位,vmlinuz 格式
  • rootfs.ext2:最小根文件系统,包含 busybox 和基础工具
  • fw_dynamic.elf:OpenSBI 固件

这里有个概念不能省:RISC-V Linux 的启动过程和 ARM 不一样,需要 OpenSBI 作为机器模式固件,把系统切换到管理员模式再引导内核。QEMU virt 板型可以使用-bios default让 QEMU 自动加载自带的 OpenSBI,也可以指定 Buildroot 生成的fw_dynamic.elf。实测下来用-bios default最省事,但如果你要调试 SBI 层,就必须指定自己的固件。

然后启动命令变成:

qemu-system-riscv64 \ -machine virt \ -cpu rv64,v=true,vext_spec=v1.0 \ -smp 4 -m 4G \ -kernel Image \ -drive file=rootfs.ext2,format=raw,if=virtio \ -append "root=/dev/vda rw console=ttyS0" \ -netdev user,id=net0 \ -device virtio-net-device,netdev=net0 \ -nographic

启动后看到Buildroot login:时,用root登录(Buildroot 默认 root 无密码)。进去先跑一句uname -a,如果输出riscv64 GNU/Linux,说明虚拟芯片已经点亮了。

4.1 host 与 guest 之间怎么传文件

后面要在虚拟机里跑 AI 验证程序,必然涉及文件传输。最简单稳定的是用 QEMU 的 virtfs 9p 共享目录,比网络 SSH 省事。启动命令里加上:

-virtfs local,path=/home/user/riscv-share,mount_tag=hostshare,security_model=none

虚拟机内挂载:

mkdir /mnt/share && mount -t 9p -o trans=virtio,version=9p2000.L hostshare /mnt/share

这样宿主机上交叉编译好的二进制文件放到共享目录,guest 里直接就能执行。9p 在虚拟化里性能一般,但传几个小测试程序完全够用。

4.2 交叉编译第一个 RVV 程序

要验证 AI 芯片最关心的向量计算能力,我写了一个很小的 RISC-V Vector C 程序,做两个长度 128 的浮点数组加法。Buildroot 工具链在output/host/bin/下,我用它静态编译:

// rvv_add.c #include <riscv_vector.h> #include <stdio.h> #include <stdint.h> void vec_add(float *a, float *b, float *c, int n) { size_t vl = vsetvlmax_e32m1(); for (size_t i = 0; i < n; i += vl) { vl = vsetvl_e32m1(n - i); vfloat32m1_t va = vle32_v_f32m1(a + i, vl); vfloat32m1_t vb = vle32_v_f32m1(b + i, vl); vfloat32m1_t vc = vfadd_vv_f32m1(va, vb, vl); vse32_v_f32m1(c + i, vc, vl); } } int main() { int n = 128; float a[128], b[128], c[128]; for (int i = 0; i < n; i++) { a[i] = i * 0.5f; b[i] = i * 1.5f; } vec_add(a, b, c, n); printf("c[127] = %f\n", c[127]); return 0; }

编译:

output/host/bin/riscv64-buildroot-linux-gnu-gcc -O2 -march=rv64gcv -mabi=lp64d -static rvv_add.c -o rvv_add

然后把rvv_add放到共享目录,进 guest 执行。得到c[127] = 254.000000,和预期一致。这一步意味着 RISC-V 向量扩展从 CPU 模拟到工具链再到用户态程序,整条链路已经打通。这就是“AI 芯片实验台”最底层的通电测试。

5. 踩坑实录:三个差点让我放弃的问题和完整排查链路

搭建过程不可能一帆风顺,这里把三个卡我最久的问题列出来,每个都附上排查思路,方便你日后对照。

5.1 坑一:QEMU 8.1 上 RVV 程序报 Illegal instruction

第一次在 guest 里跑rvv_add,直接Illegal instruction (core dumped)。我第一反应是交叉编译参数错了,但程序在 Buildroot 生成的 qemu user-mode 下又能跑。后来我把 guest 的/proc/cpuinfo拉出来看:

isa: rv64imafdc

注意没有v。问题不在编译器,在 QEMU 的 CPU 模型设置。我的启动参数写了-cpu rv64,v=true,但 guest 里内核看到的 ISA 还是没带v。排查中发现,QEMU 8.1 里v=true属性需要在-cpu时同时指定vext_spec,否则内部会把向量扩展降级为“无”。把参数改成-cpu rv64,v=true,vext_spec=v1.0后,再次查看/proc/cpuinfo,isa变成了rv64imafdc_v。Agent 在这一步给了方向,但具体属性写法是我通过对比不同版本文档确认的,所以排错时不要把 Agent 输出当最终答案。

5.2 坑二:rootfs 加载后一直 kernel panic

那是一开始最让人崩溃的现象:内核起来了,但挂载 rootfs 时直接 panic,日志最后一行写着:

VFS: Unable to mount root fs on unknown-block(254,0)

问题出在-drive if=virtio的启动参数和root=/dev/vda对不上。Buildroot 生成的 rootfs.ext2 默认是 ext2 格式,但 virtio 块设备在 Linux 里的命名要看内核配置。我一开始用-drive file=rootfs.ext2,format=raw,if=none再手动加-device virtio-blk-device,drive=hd0,设备名就变成了/dev/vdb。所以要么保持简单,在-drive里直接指定if=virtio,确认为/dev/vda;要么就明确建一个 virtio-blk 设备并改 root 参数。我当时反复改了三次才知道是这个映射关系。

5.3 坑三:user-mode 网络下 apt 更新失败

Buildroot 默认 rootfs 的/etc/resolv.conf是空的,直接apt update会报 DNS 解析失败。QEMU user-mode 网络的默认网关是10.0.2.2,DNS 服务器默认也是10.0.2.3。但 Buildroot 精简系统里根本没写这个配置。手动执行:

echo "nameserver 10.0.2.3" > /etc/resolv.conf

再配合-netdev user,id=net0后网络就通了。这个坑在于 Agent 生成的启动命令里缺了-netdev user这一整段,它默认以为 Buildroot 会自动配置网络,实际上精简 rootfs 不会替你配 DNS。

6. 实验台的可复现清单与后续还能长成什么样

搭建到这里,一台“能通电”的 RISC-V AI 实验台已经立起来了。为了让你也快速复现,把关键动作收敛成一张清单:

步骤命令/参数验证方式
编译 QEMU 8.1./configure --target-list=riscv64-softmmu --enable-slirpqemu-system-riscv64 --version
构建 Buildroot 系统make qemu_riscv64_virt_defconfig && makeoutput/images/Image存在
启动系统带v=true,vext_spec=v1.0的启动命令guestuname -a,/proc/cpuinfo含_v
交叉编译 RVV 程序gcc -march=rv64gcv -staticguest 运行输出c[127]=254.0
挂载共享目录-virtfs local,path=...mount -t 9p成功

下一步有两个方向比较值得投入。第一是把整个 virtio 盘换成 SD/NVMe 模拟,让 I/O 压力更接近真实 AI 盒子;第二是在这里面编译 llama.cpp 或者 ONNX Runtime,真正跑一个 0.1B 的小模型。QEMU 的 TCG 模式比原生慢不少,但用来做软件栈验证和算法迁移,已经足够。

我个人在实际操作中的体会是:AGENT 能从头到尾给你一套看似完整的方案,但“完整”和“正确”之间往往差着一个/proc/cpuinfo。让 Agent 干体力活、生成启动命令、分析 panic 日志,把校验和决策留给自己,这才是用 AGENT 做环境搭建的正确姿态。等后面真的把 llama.cpp 搬进去,我再开一篇把性能数据和踩坑记录补上。