RISC-V切入AI芯片的三种路径:主控、RVV向量计算与自定义指令
2026/9/7 21:20:32 网站建设 项目流程

最近跟几个做芯片的朋友聊天,发现RISC-V几乎成了必聊话题。尤其是挂在AI芯片这个大热点下,几乎每个人都觉得“开放指令集和AI肯定能发生点什么”。但再追问一句:RISC-V到底怎么用在AI芯片里?答案就开始五花八门了。有人说拿RISC-V跑Linux做控制,有人说用RISC-V的向量指令直接做矩阵乘,还有人说可以自己往指令集里加加速指令。

这些说法其实恰好对应了RISC-V切入AI芯片的三条路线,我把它总结成三种姿势:做主控、做算力、融算子。这篇文章就把三条路的原理、案例、工具链现状和典型坑一起掰开讲清楚。适合正在做芯片选型或者写立项报告的工程师,也适合不直接写RTL但想搞明白RISC-V和AI芯片到底怎么结合的产品和技术管理者。

1. 切入AI芯片前,先把RISC-V的家底摸清楚

1.1 RISC-V凭什么能跟AI芯片扯上关系

很多不熟悉架构的人会把RISC-V当成“又一种CPU”,稍微进阶一点的理解是“开源CPU”。其实RISC-V准确来说不是一种CPU实现,而是一套指令集架构规范。它规定了机器码长什么样、指令怎么编码、寄存器怎么用,但流水线怎么设计、缓存多大、主频多高,一概不规定。这套规范是开放、免费、可修改的。

RISC-V的指令集是模块化设计的,不是像ARM那样一个大而全的A-profile硬塞给你。基础指令集只有几十条,整数指令、乘法除法、原子操作、浮点、压缩指令、向量扩展,各自是独立模块。做微控制器的可以只选IMC,做应用处理器的可以加上IMAFDC跑Linux,做AI芯片的还能加V向量扩展,甚至往预留的自定义编码空间里塞自己的专有指令。

模块化带来的工程意义不光是“省面积”,更是让芯片公司有了重新发明指令集的可能性。你不需要去问ARM“我能不能改一改指令集”,因为RISC-V的规范本来就是把扩展接口留给你的。这对AI芯片特别重要,因为AI载荷和通用CPU的负载差异太大了,固定指令集的CPU在AI计算面前往往要么性能不够,要么能效太差。

1.2 AI芯片真正缺的是哪块算力

AI芯片里的计算可以粗略分成三个层面:控制面、数据面、计算面。

控制面负责调度,包括任务分发、内核启动、中断响应、DMA搬运、内存管理,这些活的本质是“判断和决策”,对算力密度要求不高,但对通用性要求极高。数据面负责数据流动,比如从DDR搬数据到片上SRAM,做格式转换、量化、padding、激活函数等,这类操作往往是算力无关的搬运。计算面才是你真正砸钱做的部分,大量MAC阵列、脉动阵列、向量ALU堆在这里跑矩阵乘加。

一个容易被忽略的事实是:芯片里大头面积是计算面,但真正决定系统能不能跑起来的是控制面。而控制面恰恰是NPU、GPU这种专用架构的软肋,它们太专了,跑不了操作系统,也处理不了复杂逻辑。所以几乎所有AI加速芯片都需要一个“旁边有个通用CPU”的配置。这个CPU过去多半是ARM核,现在越来越多团队开始用RISC-V核顶上去。

这就是RISC-V切入AI芯片的根本逻辑:AI芯片是异构系统,异构系统里必须有通用控制的角色,而RISC-V正好是最可控的通用角色。再往深走,RISC-V的向量扩展和自定义指令能力又让它从“配角”往“主角”方向延伸。这篇文章后面说的三种姿势,其实就是RISC-V在这个异构系统里不断加戏的三种程度。

2. 姿势一:做大脑,当AI SoC里的主控CPU

2.1 主控核的活儿一点都不轻松

第一种姿势也是最务实的做法:把RISC-V核当作AI芯片里的主控CPU,AI计算交给芯片里的NPU或其它专用加速器。主控核要跑Linux操作系统,负责把整个芯片管起来。

具体工作包括:系统启动时加载固件,运行中和NPU通过驱动打交道,下发计算任务、等待中断、回收结果。数据搬运的调度也在主控这里,比如把输入图像从DDR搬到NPU的内部buffer,算完后再把输出搬回来。这些逻辑看起来不起眼,但系统里80%的软件问题都出在这一层。

我见过不少团队一开始觉得“主控核就是随便放个小核跑RTOS就行”,结果做到后面发现NPU算力很猛,但喂数据的速度跟不上,整个系统跑不起来。问题往往不在NPU,而在主控核的调度能力和总线带宽设计上。拿RISC-V做主控,不是说随便拿个小核就完事,该有的MMU、cache、中断控制器、总线接口一个都不能少。

2.2 指令集选型与启动链路

做这种AI SoC时,RISC-V核的选型大致分两档。一档是跑RTOS的微控制器核,比如拿来做电源管理、传感器采集、安全启动这些简单控制工作,这类核用RV32IMC就够,资源开销极小。另一档是跑Linux的应用级核,需要支持虚拟内存、多核SMP、高性能浮点,这类核要从RV64IMAFDC起步,甚至配上向量扩展。

从启动链路来看,RISC-V和ARM最大的区别是加了OpenSBI这层固件。RISC-V定义了机器模式(M-mode)、监管者模式(S-mode)和用户模式(U-mode),Linux跑在S-mode下,而M-mode下需要一个固件来提供生态服务,这就是OpenSBI。实际项目里的启动流程一般是:BootROM加载SPL,SPL初始化DDR,跳转到U-Boot,U-Boot把OpenSBI和Linux内核一起加载起来,OpenSBI负责切换模式并跳转内核,内核起来之后最后由驱动接管NPU等外设。

这里面交互最频繁的是内存管理。NPU做DMA搬运时,主控要给它分配物理上连续的内存区域,Linux默认的内存分配器分配出来的往往是不连续页,所以驱动里要预留CMA区域或者用DMA池。我们在调试初期经常遇到NPU访问地址不对、cache一致性问题导致计算结果随机错乱,最后都是用dma_alloc_coherent这类接口统一管理才解决。

2.3 这个姿势的代表形态与优缺点

用RISC-V做主控的AI芯片,现在市场上很多。比如大量物联网AIoT芯片就是“RISC-V核加几百G到几T算力的NPU”,跑语音唤醒、人脸识别、关键词检测这些端侧推理任务。这类产品放量很大,大家日常用的智能家居设备里很可能就有。平头哥玄铁系列处理器搭配自研NPU的组合也大量出现在这类SoC里,SiFive的U系列核心也被不少AI加速芯片当管理核用,包括国际大厂的手机SoC里专门负责AI调度和处理的部分。

这个姿势的优点非常明显:软件生态成熟。Linux主线从5.19开始官方支持RISC-V,Buildroot、Yocto都有对应支持,GCC和LLVM的工具链早就稳定。一个熟悉ARM Linux开发的工程师,切到RISC-V平台基本一两天就能上手,驱动开发流程几乎一致。

缺点也很明确:RISC-V核在这里不是算力贡献者,而是管理者的角色。如果芯片的核心竞争力是NPU算力,那RISC-V这部分的价值被很多人低估。另外ARM在移动生态、功耗管理上有多年积累的成熟方案,RISC-V要靠自己优化,尤其在DVFS调频、多核一致性这些进阶议题上,团队的底气明显没那么足。不过对多数AI芯片团队来说,这仍然是风险最低、最容易出片的切入方式。

3. 姿势二:用RVV向量扩展,让CPU自己就是AI引擎

3.1 RVV到底是什么,它跟NEON、SVE有什么不同

第二种姿势就是让RISC-V核心自己干AI计算的活,不依赖独立的NPU。实现这个目标的钥匙是RVV向量扩展,也就是RISC-V的V扩展。向量扩展的思想不是RISC-V首创,ARM有NEON,x86有AVX,但RVV有几个非常不一样的设计点。

第一个点是向量长度不固定。ARM NEON固定128位向量寄存器,意味着一次能处理4个32位浮点数。RVV的向量寄存器长度VLEN由硬件实现决定,可以做成128位、256位、512位甚至更宽。更关键的是,RVV引入了向量长度寄存器vl,这个值由指令按需设置,同样的二进制代码可以跑在不同VLEN的芯片上而不需要重新编译。这套设计思路通过“向量长度无关”的编程模型实现了软件兼容和硬件自由度的统一。

第二个点是LMUL的设计。你可以通过设置LMUL把多个向量寄存器组合成一个逻辑向量,比如LMUL=8时把8个物理向量寄存器当作一个整体来用。这有什么用呢?举个例子,你在做两层循环嵌套的矩阵块乘时,可以把LMUL调大,让一次循环处理更多数据,从而减少循环开销、提高寄存器利用率。这种灵活性是NEON完全给不了的。

第三个点是RVV和循环的配合方式。ARM NEON编程时,你要自己处理数据尾部剩下的部分,写起来非常痛苦。RVV的指令天然支持“按需向量长度”,循环剩余多少就处理多少,tail元素自动置为零或者保持不变,尾部处理逻辑被指令集直接解决了。实际写代码时的体验就是循环边界处理干净利落。

3.2 RVV 1.0的关键参数与代码长什么样

先说一个重要的版本问题。RVV规范在2021年冻结了1.0版本,但在这之前有很长一段时间用的是0.7.1草稿版本。很多早期的芯片和工具链是基于0.7.1开发的,1.0发布后指令编码和语义都变了,0.7.1的代码在1.0的芯片上完全跑不了。所以2024年再立项选型,一定要认准RVV 1.0,找到老代码时也要先确认版本。

写RVV代码有两种方式:一种是用内建函数,在C语言里直接调用RVV的向量接口;另一种是让编译器自动向量化,写普通循环靠GCC或LLVM帮你生成RVV指令。自动向量化理论上很美好,实际上对复杂循环的支持还很有限,想要性能基本得手写内建函数。下面是一个最简单的浮点数组相加,用RVV内建函数实现:

#include <riscv_vector.h> void vec_add(float *a, float *b, float *c, int n) { size_t vl; while (n > 0) { vl = vsetvl_e32m8(n); // 按LMUL=8配置,可处理n个元素 vfloat32m8_t va = vle32_v_f32m8(a, vl); vfloat32m8_t vb = vle32_v_f32m8(b, vl); vfloat32m8_t vc = vfadd_vv_f32m8(va, vb, vl); vse32_v_f32m8(c, vc, vl); a += vl; b += vl; c += vl; n -= vl; } }

vsetvl_e32m8(n)会设置vl为当前硬件VLMAX和n中的较小值,这样即使n不是VLMAX的整数倍,最后一段也能正确处理,不需要额外的尾数处理分支。这里LMUL选8是把多个向量寄存器合并用,实际单核吞吐量决定因素是向量宽度和主频的乘积,代码层面能调的参数就是LMUL和循环展开次数。

3.3 性能定位与适用场景

RVV能跑多大算力?这个问题要看具体实现。以128位向量宽度、1GHz主频的芯片为例,做FP32乘加,理论峰值大约16 GFLOPS,INT8峰值可以再上一个量级。如果芯片做到512位向量宽度、2GHz,单核吞吐能到数百GOPS。这个水平跟专门的大算力NPU比有一定差距,但胜在通用性极强。

我见过合适的应用场景主要包括三类。第一类是算法还在快速迭代的领域,比如具身智能、机器人控制里的多模态模型,模型结构两三个月就换一版,如果是NPU方案,每次模型改动都要重新做算子映射,用RISC-V向量核直接跑C++推理代码,改动成本低得多。第二类是控制加AI混合的场景,比如工业控制器里既要做PLC逻辑控制,又要跑视觉检测算法,用向量核一个核同时搞定两件事,比CPU加NPU双芯片更简单。第三类是实时性要求高的小模型推理,比如动力电池管理系统里的电压预测、电力电子控制里的故障检测,这类任务模型不大但时延要求苛刻,数据就在CPU里,直接用RVV算比搬运到外部NPU快得多。

3.4 实际项目中的几个坑

第一个坑是VLEN不兼容。理论上“同一份代码跑在不同VLEN芯片上”没问题,前提是你不做性能优化。一旦你按照特定VLEN去做极致的循环展开和数据分块,代码就跟硬件绑死了。所以RVV工程上最现实的做法是:运行时检测VLEN,用多套优化kernel做分派。这不是RISC-V特有的问题,SVE、AVX也有类似的优化归档策略。

第二个坑是工具链的自动向量化还远远不够成熟。GCC和LLVM对简单循环能生成不错的RVV代码,但对带有复杂数据依赖、间接寻址、非对齐访问的循环,生成的代码性能可能比手写差十倍以上。指望把ARM上的NEON优化经验直接平移过来是不现实的,团队要有专门做kernel优化的人。

第三个坑是内存带宽很容易成为瓶颈。RVV计算吞吐再高,数据都要经过内存子系统配给。很多芯片设计时把向量单元做得很宽,但L1 cache的带宽没跟上,结果实测性能只有理论峰值的百分之二三十。选型时不能只看TOPS指标,要重点看每周期能加载多少字节的数据,这个数字比算力数字更能说明实际问题。

4. 姿势三:自定义AI指令扩展,把算子焊进流水线

4.1 自定义指令的编码空间与设计思路

第三种姿势是在RISC-V基础上自己定义AI加速指令。RISC-V规范预留了4个自定义指令编码空间,标准术语叫CUSTOM-0到CUSTOM-3,它们的opcode是保留给用户自行定义的。

有了这些指令槽,你可以定义自己的指令格式、功能单元和寄存器使用方式。举个例子,你可以设计一条matrix_mac指令,让它直接从向量寄存器堆里取两个矩阵的分块数据,做一次矩阵乘累加,结果写回目标寄存器。这么做的效果是,一个本来需要几百条普通指令循环执行的矩阵乘块,被压缩成了一条指令,同时省去了在总线上搬运数据的时间。

为什么要这么激进?因为有些AI算子的计算模式跟通用向量指令配合得并不好。比如卷积里的3x3 kernel滑动窗口,通用RVV实现时要反复加载相邻行数据做lane重排,大量时间花在数据整理上。如果自定义指令可以支持“加载三个相邻行并直接算卷积”这种模式,效率提升立竿见影。

设计自定义指令时要考虑三个层次的问题:指令语义怎么定、硬件怎么实现、编译器怎么支持。语义和硬件设计相对可控,真正的挑战在编译器。指令造出来之后要让程序员能用,你得在编译器后端告诉编译器这条指令长什么样,并定义对应的内建函数。ARM没有这种玩法,x86也没有,只有RISC-V这种开放指令集给你从零定义指令的机会。

4.2 从一条指令到编译器支持,要过几关

第一关是RTL实现。你需要在处理器流水线里挂一个新的执行单元,通常是乘加阵列,或者把自定义指令翻译成微操作送给协处理器。这一步决定指令的延迟和吞吐,以及它跟访存和中断的交互关系。

第二关是汇编器支持。最简单的方式是先用GNU汇编器提供内置的.insn指令编码伪指令来验证概念,比如:

# 定义一个CUSTOM-0编码空间的自定义指令 # 格式:opcode=0x0B, funct3=funct, 目的寄存器rd, 源寄存器rs1和rs2 .insn r 0x0B, 0, 0, a0, a1, a2

这条汇编语句会生成一条机器码,a2、a1的内容经过自定义功能单元计算后写入a0。这种方式适合做功能验证,但要做正式产品,必须在GNU binutils里正式注册指令格式,让反汇编器和开发工具都能识别。

第三关是编译器支持。要让C语言能调用你定义的指令,通常需要在内建函数层面对接。在LLVM里,你要在指令选择阶段(SelectionDAG或GISel)把intrinsic映射到你新增的自定义指令上。不同的funct3和funct7组合都要定义清楚,否则编译器无法编码你想要的变体。再加上自动向量化的匹配规则,工作量一下子从几天变成几周。

整个流程走下来,最大的体会是:硬件加一条指令很快,编译器跟上这条指令很慢。这是很多芯片团队起步时严重低估的工作量。

4.3 核内加速与独立NPU的取舍

自定义AI指令和独立NPU到底怎么选?本质上是在比“把数据送到计算单元旁边”和“把计算单元搬进处理器”之间的效率差异。

独立NPU的好处是算力可以堆得很大,面积和功耗预算可以比较奢侈,但每次计算都要经过DMA搬运数据。数据搬运的时延在百纳秒量级,如果是很小的算子,这个开销就非常可观。自定义指令把AI计算单元放进处理器流水线内,数据和指令都在核内流动,时延可以做到几个时钟周期。这就是为什么很多系统把对时延敏感的算子,比如LayerNorm、Softmax、量化缩放这类紧密耦合进核内的原因。

缺点也很明显。自定义指令吃的是指令带宽,一个周期最多发射那么几条指令,如果算子是数据密集型的大矩阵乘法,指令带宽很快就成为瓶颈,这时候反而是独立NPU按块搬运数据效率更高。所以工程上比较理性的路径是把算子分成两类:一类是数据规整、计算量大的算子放NPU或向量单元,另一类是逻辑复杂但计算体量小、频繁调用的算子用自定义指令加速。

4.4 芯片与Chiplet趋势下的扩展余地

自定义指令的编码空间虽然有限,但配合Chiplet趋势,RISC-V做AI芯片的想象空间更大了。多家厂商开始通过开放指令集去连接异构芯片模块,RISC-V核心既可以作为控制平面协调各个die,也可以通过自定义扩展指令去做die间高速通信和一致性管理。

我比较看好的一种做法是,把自定义指令用在“计算效率”和“通信效率”两个方向。前者是给AI算子定义专用加速指令,后者是给数据搬运定义专门的DMA和同步指令。很多芯片团队在通信上的瓶颈比计算更严重,如果能在指令级直接支持数据包收发、同步栅栏和队列管理,系统性能会有质变。

当然,这条路的风险也最高。每家自己定义一套扩展指令,等于走上了碎片化的路。如果没有一个强大的软件生态去统一,最后项目往往变成自家芯片只能跑自家编译器,对外吸引力有限。所以做自定义指令的团队,一定要在立项初期就安排专门的工具链人员,把编译器、汇编器和调试器的维护工作当作产品的一部分来投入。

5. 三种姿势怎么选,以及工具链这条暗河

5.1 三种姿势的选型决策表

三种姿势不是替代关系,更多是产品定位和团队能力的映射。我做了个表,方便对照参考:

对比维度姿势一:主控CPU姿势二:RVV向量计算姿势三:自定义AI指令
核心角色系统调度与管理通用AI计算引擎特定AI算子加速
算力贡献中高
通用灵活性中低
软件生态成熟度最高
工具链维护成本
典型芯片规模1-4核+NPU8-100个向量核少核+深度定制
适用团队所有团队有kernel优化能力有编译器团队支撑
代表产品形态AIoT SoC、终端AI芯片边缘计算、机器人控制器高性能AI加速芯片

选型时建议沿着一条主线思考:你的AI负载是固定的还是不断变化的?固定负载,比如只做几种固定模型的推理,NPU加主控的组合最划算;负载变化频繁,比如要支持各种新模型结构,RVV向量核会给你极高的自由度;负载中有大量细碎小算子,比如Transformer的解码阶段,自定义指令能帮你抠出几个数量级的时延收益。

5.2 通用软件栈:启动固件到推理框架要过几道坎

不管选哪种姿势,落地时都要打通从启动到推理的完整软件栈。RISC-V的启动链路目前已经比较成熟,OpenSBI加U-Boot加Linux主线的组合可以复用。Buildroot和Yocto也都有官方支持,出一个能跑Linux的SDK已经不是难事。

真正的差距在AI推理框架这一层。TensorFlow Lite Micro对RISC-V有基本支持,但算子和优化远不如ARM版本。ONNX Runtime在RISC-V上的RVV后端还在快速迭代中。TVM有RISC-V后端,但很多算子的代码生成质量需要人工调优。如果你有手写的高性能算子库,把CNNs、Transformer里的核心算子用RVV实现,性能可以远超通用自动生成的代码,但这需要团队有很强的体系结构功底。

还有一个不能忽视的环节是调试和性能分析工具。RISC-V的OpenOCD、GDB调试方案能对付一般问题,但性能剖析工具远不如ARM的Streamline、x86的VTune成熟。我们在项目里基本是芯片上的性能计数器加自定义的计时函数,手工分析哪些循环热点占用了时钟。芯片里多做几组性能计数器,后面能省很多事情。

5.3 关于软件成本的一些体会

文章写到这里,最想强调的其实是软件成本这条暗河。很多人谈起RISC-V都会说:指令集免费、开发环境成熟、社区活跃。这些都对,但免费指的是指令集规范,不是把你手上算法跑起来的工程能力。

实际项目里,从拿到一个RISC-V核心到Linux跑起来,大概几周时间;从Linux跑起来到AI模型能推理,可能要几个月;从模型能推理到性能可接受,又是几个月。这个节奏跟用ARM成熟方案有明显差距,因为ARM的AI软件栈是被大量产品和工程验证过的,RISC-V在很多环节还在补课。

所以我的建议很直接:团队在立项时就要把软件投入按硬件投入的一半以上来预算。硬件设计再优秀,如果软件栈没跟上,芯片就是一块昂贵的砖头。尤其是走RVV和自定义指令路线的团队,工具链的坑只能自己踩平,开源社区的帮助需要时间去发酵。

这个领域的现状是变化非常快。RVV 1.0工具链正在快速完善,LLVM和GCC对RISC-V自动向量化的支持一个版本比一个版本强,社区里也有越来越多开源的算子库涌现出来。对做AI芯片的团队来说,RISC-V至少给出了一个从指令集层面进行创新的机会。回到标题说的三种姿势:想稳,从主控做起;想进一步,认真啃下RVV这条向量路线;想构建真正的差异化竞争力,那就早点把自己的编译器团队养起来,往自定义指令的深水区走。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询