这几年做芯片相关的项目,最明显的一个感受是:RISC-V 已经不再是嵌入式圈子里的玩具了,它正在往 AI 芯片的腹地挤进去。我最早接触开源指令集是在 MCU 上做控制类应用,当时觉得这东西顶多是 ARM 的平价替代。直到这两年陆续参与了几个和 AI 加速相关的项目,才意识到 RISC-V 切入 AI 芯片的方式远比想象中多元,而且每种姿势踩的坑、吃的红利、依赖的生态完全不一样。这篇文章就从我实际跟项目、读代码、调板子的视角,把 RISC-V 进入 AI 芯片的三种主流姿势掰开揉碎聊一聊,给正在做技术选型或者想转行做 AI 芯片的工程师一个相对完整的参考。
三种姿势说白了就是:第一,在 RISC-V 基础指令集上自己扩展 AI 专用指令,把加速逻辑硬编码进流水线;第二,拥抱 RISC-V 官方标准里的向量扩展 RVV,用通用向量指令吃下 AI 算子的并行计算需求;第三,把 RISC-V 当作 AI SoC 里的控制核心和内存管家,真正的算力交给 NPU,大家各干各的。这三条路线没有绝对的好坏,只有适不适合你的场景、团队、流片成本和软件成熟度。我尽量把每条路的原理、实操细节和翻车经验都讲透。
1. 为什么偏偏是 RISC-V:AI 芯片的指令集历史窗口
1.1 AI 芯片真正的瓶颈从来不只是算力
先聊一个很多人没想清楚的问题:AI 芯片到底缺的是什么。你去看现在市面上的 AI 芯片,从英伟达的 GPU 到谷歌的 TPU,再到地平线、寒武纪这些国产 NPU,其实计算单元本身的峰值算力早就不是最大瓶颈了。反而是两个东西每天都在拖后腿:一个是数据搬运的效率,另一个是算子多样化带来的灵活性问题。
数据搬运这事好理解。AI 计算的本质就是海量数据的规整运算,你以为芯片在算,其实大部分时间都在搬数据。片上的 SRAM 往计算单元搬、片外的 DRAM 往 SRAM 搬、多核之间还要搬。一旦访存带宽跟不上,算力再高也是空转。而算子多样化问题更隐蔽,今天你要跑 Transformer 里的多头注意力,明天要跑卷积里的深度可分离卷积,后天可能又要接一个传感器融合的模型。专用硬件做得越极端,能适应的算子范围就越窄,最后变成一颗只为一两个模型定制的 ASIC,改版迭代的成本高到吓人。
这个时候指令集的重要性就凸显出来了。指令集是软件和硬件之间那个约定好的指令字典,芯片想灵活适配不同算子,指令集必须足够开放、足够可扩展。传统的 x86 和 ARM 虽然也在往 AI 指令的方向走,但那些指令集牢牢攥在少数几家公司手里,你作为芯片设计者只能被动接受他们定好的扩展方式。RISC-V 不一样,它是一个开源指令集,任何人都能基于它做自定义扩展,而且基础 ISA 本身就是模块化的,想切哪块就切哪块。
1.2 RISC-V 踩中了两个历史节拍
RISC-V 能在 AI 芯片里冒头,除了技术上的开放和灵活,其实还踩中了两个历史节拍。第一个节拍是全球芯片产业链重组带来的供应链焦虑,越来越多的公司想把指令集架构的选择权掌握在自己手里,RISC-V 天然就是那个不被任何人卡脖子的选项。第二个节拍是 AI 芯片设计本身进入了碎片化阶段。早期 AI 加速器大家各有各的闭门造车方案,后来发现驱动和工具链才是无底洞,于是行业开始往通用指令集收敛,而 RISC-V 恰好提供了这样一个开放的收拢点。
以前指令集领域的格局基本是:高性能场景看 x86,低功耗移动场景看 ARM,其他场景看 MIPS,但那都是历史遗留问题。现在你去看任何一个新成立的 AI 芯片团队,他们做架构评估时十有八九会把 RISC-V 放进备选列表里,就是因为它在授权模式、定制自由度、软件生态三方面取得了一个不错的平衡点。
2. 姿势一:扩展自定义指令自己做 AI 加速核心
2.1 自定义扩展到底扩在哪儿
第一种姿势,也是 RISC-V 最简单直接的玩法:在基础整数指令集 RVI 或者带浮点的 RVF/RVD 之上,自己加几条 AI 专用指令。我见过不少团队这么干,他们不追求做一个通用 AI 处理器,而是把模型里出现的固定算子做成硬件指令,例如把 4x4 的矩阵乘、16 路的点积、ReLU 加量化,这些操作各自的微码循环是几十条指令,现在用一条自定义指令直接让硬件在几个周期内算完。
关键的问题是这组指令应该扩在哪个位置。RISC-V 规定了不少保留的自定义指令编码空间,从 funct3 到 opcode 都有预留,你可以根据自己的需要把新指令塞进这些萝卜坑里。硬件实现的时候,通常是在现有 ALU 或者浮点流水线旁边挂一个 AI 协处理器单元,主核通过自定义指令把操作数和控制字发过去,协处理器算完再把结果写回寄存器堆。
这里有个设计原则要提醒你:不要试图用自定义指令包揽所有算子。指令集扩展讲究的是克制的覆盖,只做最热、最稳定、编译器最容易生成的那一小撮算子的加速,反而收益最高。如果你把每个没见过的新算子都加一条指令,指令数量爆炸不说,硬件验证的复杂度会直接让你流片周期翻倍。
2.2 一条自定义指令从定义到落地的完整链路
我在实际项目里做过一条用于定点矩阵乘的自定义指令,这里把链路简单捋一遍。第一步是定义指令格式,我选择了 R-Type 格式,一个 rd 存结果,两个 rs 存输入矩阵的基地址,再用 funct7 区分矩阵乘、点积、累加几种操作。第二步写 RTL,把这条指令在译码阶段识别出来,送到一个 8x8 的脉动阵列里,等阵列算完之后生成写回信号。第三步改编译器,这部分最容易被低估,如果你只改了硬件,但没有让编译器知道这条指令的存在,那所有代码还是只能在汇编层手动嵌入,开发效率极低。
好在我们有 LLVM 和 GCC 的 RISC-V 后端可以做自定义指令接入,但坦白讲,定制编译器后端是个硬骨头,光是指令选择匹配规则就要调试很久。所以如果你只是想在项目里快速验证自定义指令的收益,我的建议是先走内联汇编的路子,用asm volatile把新指令直接嵌进 C 代码里做基准测试,确认性能提升足够大之后再投入资源改编译器。这样能避免在架构还没稳定的阶段,就把大量时间耗在编译器开发上。
3. 姿势二:拥抱 RVV 向量扩展走在通用标准上
3.1 RVV 到底解决了什么问题
第二种姿势不再是自己发明轮子,而是拥抱 RISC-V 基金会已经定好的向量扩展标准 RVV。RVV 的设计思想可以理解为:把原来只能同时处理一个数据元素的标量指令,变成能同时处理一堆数据元素的向量指令。比如你想对 1024 个 float 做逐元素相加,标量指令要循环 1024 次,向量指令理论上一条vfadd.vv就能完成,当然实际还要配合加载、存储和掩码操作。
RVV 最早有 0.7.1 版本,后来经过多轮演进,1.0 版本成了真正被工具链和硬件广泛支持的标准。以 SiFive 的 X280 为例,它内置的就是 RVV 1.0,大家用的软件栈也是基于这个版本。用 RVV 有个特别大的好处:你不用自己造轮子,编译器、汇编器、调试器、Linux 内核的向量上下文保存逻辑都有人帮你弄好了,你只需要关注怎么把算法向量化。
做向量化的时候有几个核心概念必须先搞明白:向量长度 VLEN、最大向量长度 MLEN、还有vsetvli这个指令。vsetvli是用来设置向量长度的,它会把vl寄存器设成当前硬件支持的实际向量元素个数。这里要特别提醒一下,RVV 的编程模型是向量长度无关的,你的代码必须能处理vl比预期小的情况,俗称 strip-mining,也就是每次处理一段,直到把所有数据都处理完。
3.2 RVV 的工程成本和隐藏坑
不要以为用了标准 RVV 就万事大吉了。我实际测下来,RVV 的性能表现受数据布局影响非常大。如果你数据的存储方式是数组结构体 AoS,也就是每个元素里面包含多个特征字段,那么向量化时你会频繁做 gather 和 scatter,性能惨不忍睹。如果改成结构体数组 SoA,每个特征字段单独一个连续数组,向量加载就非常干净,性能提升立竿见影。
另一个隐藏坑是寄存器压力。RVV 的向量寄存器一组有 32 个,但每个向量寄存器的长度是从 VLEN 决定的,一般是 128 位或 256 位。如果你的算法需要同时保留多个中间结果,很容易出现寄存器不够用的情况,最终编译器冒着性能风险把中间结果溢出到栈上。这时候你就要考虑重新划分计算分块,或者降低向量化粒度,别死守着一次性处理全部数据。
还有一个工程上的隐性成本,就是硬件实现 RVV 的面积和功耗一点都不低。向量寄存器的数量、宽度,以及运算单元的全连接布局,都会吃掉大量芯片面积。如果你的 AI 芯片目标是低端物联网场景,上完整 RVV 可能不如自己做一个窄位宽的专用加速器划算。
4. 姿势三:RISC-V 做控制 NPU 做算力的异构主流
4.1 异构 SoC 里的任务分工与配合机制
第三种姿势是目前 AI 芯片设计里最常见的一种,也最贴近产业主流:一颗芯片里既有一颗或多颗 RISC-V 处理器核心,又有一个专门的 NPU 或者硬件加速器,两者协同工作,RISC-V 干它最擅长的控制管理,NPU 干它最擅长的矩阵算力。
这个分工听起来很简单,实际操作起来非常考验系统设计能力。RISC-V 核心负责的事情包括:加载模型权重到 NPU 的片上 SRAM、启动 NPU 计算、获取中断状态、做后处理、管理 DMA 搬运数据。为了压榨性能,软件调度和驱动设计通常要把整个流水线拆成多级。我见过一个很经典的做法是把模型推理拆成三个任务:第一个任务做输入数据的预处理和重排,第二个任务启动 NPU 跑主体运算,第三个任务做输出后处理。三个任务在 RISC-V 核上用中断驱动的方式交叉执行,让 NPU 几乎永远处于忙碌状态。
这个设计里面,RISC-V 核不一定要跑 Linux,很多时候为了确定性的实时响应,大家直接用 RTOS 或者裸机代码来跑控制逻辑。只有需要网络协议栈、文件系统、产品级 UI 这些复杂功能时,才会选带 MMU 的更高性能 RISC-V 核,跑一个 Linux 系统。
4.2 这个姿势的典型落地案例和收益
这条路线在产业界已经相当成熟。像 T-Head 的无剑系列、SiFive 的 Intelligence 系列,都是典型的 RISC-V 核加 AI 加速的异构方案。Google 的 TPU 虽然是完全自研的指令集体系,但它主机侧依赖 x86 做控制和调度,如果你把主机侧换成 RISC-V,整个结构就是姿态三的另一个版本。
这种异构方案的好处是开发节奏可以解耦。RISC-V 核心和 NPU 是两个相对独立的模块,团队可以并行开发,RISC-V 核直接用成熟的工具链跑软件,NPU 的编译器可以单独迭代,最后在 SoC 层面做协同验证。对产品团队来说,最大的价值是灵活性:今天模型算法变了,只要 NPU 的可编程能力足够,通常不需要重新流片,只需要更新编译器或者固件就能适配。
5. 三种姿势的核心权衡与一句话选型
为了让你更直观做对比,我整理了下面这张表,分别从指令开放性、能效上限、软件生态、团队门槛、场景适配、扩展风险六个维度做横向评估。这张表不是绝对权威,是我自己和同行在项目复盘时常用的判断框架,你完全可以根据自己团队情况调整权重。
| 评估维度 | 自定义指令扩展 | 标准 RVV 向量扩展 | RISC-V 核 + NPU 异构 |
|---|---|---|---|
| 指令开放性 | 最高,完全自主定义 | 中高,但必须遵循标准 | 中,RISC-V 负责控制,NPU 内部黑盒 |
| 能效上限 | 高,专事专办 | 中高,通用向量执行仍有效率损失 | 最高,NPU 针对算子和数据流深度优化 |
| 软件生态 | 差,编译器工具链要自研 | 较好,RVV 有 LLVM/GCC 支持 | 取决于 NPU 编译器,独立性强 |
| 团队门槛 | 极高,软硬都要熟 | 中,算法工程师要会向量化编程 | 中高,系统软件和驱动经验要求高 |
| 场景适配 | 特定算法长期不变的场景 | 算子范围较宽、产品迭代快的场景 | 大算力、复杂产品、需要跑主流模型场景 |
| 扩展风险 | 风险高,新算子来了不好加 | 中,受制于标准演进和硬件宽度 | 低,NPU 可编程性和可配置空间大 |
一句话选型建议:如果你做的是婴儿监护仪里那种非常固定的图像检测算法,自定义指令扩展可能够用;如果你做的是边缘计算盒子,要跑各种不同模型,RVV 的可编程性会让你舒服很多;如果你做的是自动驾驶域控制器或者服务器推理卡这种重量级产品,不用犹豫,直接走第三种异构方案。
6. 真正的分水岭在工具链和编程模型
6.1 指令集决策背后的软件债务
很多人选 RISC-V 的时候,满脑子想的都是开放、免费、可控这些词,但真正把一颗 AI 芯片推向市场的时候,决定成败的往往不是指令集本身,而是基于这个指令集长出来的软件生态。我见过不止一个团队,指令集和硬件做得漂漂亮亮,结果在编译器适配和算子库移植上整整delay了半年,就是因为低估了软件债务。
所谓软件债务,就是你选择的指令集架构需要配套的编译器、汇编器、链接器、调试器、模拟器、RTOS 支持、Linux 的架构代码、数学库优化。RISC-V 基础指令集的软件生态已经很成熟了,但是一旦你加上自定义扩展,或者选择某个特定版本的 RVV 实现,这套庞大的软件栈就要跟着一起做适配和回归测试。
6.2 为什么说生态比指令集更值钱
以 RVV 为例,光是把它从规范变成编译器里高质量且稳定的向量化代码生成,就耗费了工具链社区好几年时间。你如果决定用 RVV,其实享受的正是这些前人的生态成果。反过来,如果你用自定义指令,就等于把生态建设的成本全部拦回到自己头上。
另一个生态层面的卡点是 AI 框架的适配。最终用户根本不关心指令集是 RISC-V 还是什么,他们只关心 PyTorch、TensorFlow 的模型能不能一键跑起来,精度损失多少,推理速度多快。所以无论是走哪条姿势,你最终都要折腾一层一层往上:算子库、图优化、量化工具、运行时推理引擎。这套软件栈的厚度,往往比硬件本身更决定产品的成败。
7. 实操复现:一条最小的 RISC-V AI 加速路径
7.1 环境准备与最小验证 demo
理论讲再多,不如动手跑一个最小 demo。我建议你从 RISC-V 向量指令入手,因为这条路的标准工具链最容易获得,适合做入门复现。你需要准备的东西有:一个支持 RVV 1.0 的模拟器,比如 QEMU 的最新版本;RISC-V 的 GCC 或 LLVM 工具链,注意版本要支持-march=rv64gcv这类带 v 扩展的编译选项;一个向量点积或向量矩阵乘的 C 语言小程序。
具体操作流程很简单:用向量扩展写一个 1024 长度的 float 向量相加函数,编译反汇编,检查是否生成了vsetvli、vle32.v、vfadd.vv、vse32.v这些指令。然后对比同样逻辑的标量版本,在 QEMU 里统计周期数差异。虽然模拟器的周期数不能代表实际硬件,但你可以通过这个 demo 掌握 RVV 代码的基本结构,这是以后做真实项目的基础功。
7.2 编译、反汇编与性能数据怎么看
反汇编这一步很有价值,因为你能肉眼看到编译器如何处理向量寄存器。我第一次看到vsetvli自动根据vl调整向量长度的时候,才真正理解 strip-mining 到底是什么状态。以前只能抓住 vector length agnostic 这个概念,直到看见寄存器循环才明白动态长度调整的过程。
性能分析的时候,我的做法是把整个计算mock成三块:数据加载、计算、数据存储。实测下来数据加载和存储往往占了大半周期,这一点在QEMU 里可能不明显,但放到真实 SoC 上会被无限放大。所以你在做 RISC-V AI 项目的起步阶段,就要养成“先看访存再谈计算”的思维习惯。
8. 避坑手册:我在 RISC-V AI 项目中踩过的关键坑
8.1 第一个坑:小向量性能不升反降
有段时间我给一个边缘设备做音纹识别,特征向量本身只有 128 维,我觉得用向量指令很容易加速,结果实测比标量循环还慢了一个数量级。后来反汇编分析才发现问题出在向量初始化和向量寄存器搬运的开销上,数据量太小,向量化带来的启动成本完全吃掉了并行计算的收益。
这个坑提醒我:向量化不是数据量越大越好,也不是只要数据范围超过一个向量寄存器长度就值得。一般经验是数据规模至少是硬件向量长度的 4 到 8 倍时,向量化收益才会明显。如果数据量在这个阈值以下,老老实实写标量循环反而是最优解。
8.2 第二个坑:访存带宽比计算问题更痛
有一次我们把一个卷积算子完美向量化,计算单元利用率拉到了 70% 以上,心想这次性能总能冲上去了,结果实测带宽占用直接打满,瓶颈又变成了片外存储的带宽。我们把近一半的时间花在优化数据重排、cache 命中率和 DMA 传输粒度上,最终才把端到端推理延迟降下来。
我后来形成的一个习惯是,做任何 AI 算子在芯片上的优化之前,先做一次访存带宽估算:模型权重有多大,中间激活值有多大,这些数据要搬运几次。算完你就知道,自己是在优化计算还是优化搬运,别一上来就怼着计算单元找问题,否则极易南辕北辙。
8.3 第三个坑:软件生态碎片化带来的维护噩梦
RISC-V 虽说开源,但当开源碰上 AI 的高速迭代,碎片化问题会被放大得很厉害。你用的 NPU 编译器可能是基于某个分支的 TVM,你们的模型部署工程师用的是另一个版本的 ONNX Runtime,两边版本一不匹配,模型转换就会出现各种奇怪错误。
我现在的项目规范是锁死工具链版本,用容器把整个编译和 SDK 环境固化成镜像,任何新同学加入项目的第一件事就是拉镜像,而不是重新从网上下载散装工具。这个小改变帮我省掉了大量“为什么你的编译环境能出这个模型,我的不行”之类的问题。
8.4 第四个坑:任务划分和实时性冲突
走异构路线的时候,RISC-V 控制核往往要同时处理 NPU 推理任务和实时通信任务,如果任务划分没做好,NPU 跑得好好的,中断响应却超时了,整个系统的稳定性立刻崩掉。我在一个多传感器融合项目里,就因为中断处理函数里塞了太多日志打印,导致 NPU 启动命令发送延迟,最后整个推理流水线卡顿。
这个问题的解法还是回到任务优先级设计。中断处理函数只做标志位置位和数据缓冲,真正的调度逻辑放到主循环的高优先级线程里处理。另外,NPU 命令下发用异步队列,不要在中断上下文里直接等完成事件,否则实时性和推理吞吐两个目标都要受牵连。
写在最后的一个小技巧
最后分享一个我在多个项目中验证过的习惯。无论你选哪种姿势,都别在第一天就扎进细节,先把你要面对的模型负载做一次算子画像,算清楚每一层的时间占比、访存流量、并行度。有了这张画像,你再回头选 RISC-V 的切入姿势,会清醒很多。指令集和架构的核心从来不是赶时髦,而是让每一瓦功耗和每一平方毫米芯片面积都花在最值得的地方。