跳转至

RISC-V 与嵌入式系统

一句话总结: RISC-V 是开源、模块化、无需授权的指令集架构,正在重塑芯片产业,也是 TinyML 与 AI 加速器控制核心的首选平台。

RISC-V 是正在重塑芯片产业的开源指令集架构。 本节介绍 RISC-V 设计哲学、V 向量扩展、嵌入式 ML 推理、微控制器上的 TinyML、AI 加速器中的 RISC-V,以及边缘部署的种种限制.

  • 到目前为止我们讲过的所有芯片架构 (x86, ARM) 都需要授权. Intel 和 AMD 为 x86 付钱。 Apple、Qualcomm 以及所有智能手机厂商每年向 ARM 付出数十亿美元。 RISC-V 不一样:它是一个开放标准。 任何人都可以设计、制造、销售 RISC-V 芯片,不用向任何人付版税。 这正在改变芯片设计的经济模型,尤其是对 AI 而言。

RISC-V 设计哲学

  • RISC-V (读作 "risk five") 于 2010 年诞生于加州大学伯克利分校,是一套干净、现代的 RISC 指令集。 核心原则:

    • 开放标准:指令集架构 (Instruction Set Architecture, ISA) 规范免费公开。 你可以在不支付授权费、不签保密协议、不订立法律协议的前提下造出 RISC-V CPU. 这就像 Linux 之于操作系统 —— 任何人都可以使用、修改、并在其之上构建。

    • 模块化设计:基础 ISA (RV32I 或 RV64I) 极简 —— 只有 47 条指令。 其他一切都是可选的扩展: M (乘除法)、A (原子操作)、F/D (浮点)、C (压缩指令)、V (向量处理). 你只挑自己需要的,芯片保持小巧高效。

    • 无历史包袱: x86 背着 45 年的向后兼容包袱。 ARM 背着 35 年。 RISC-V 是从零开始,吸取了两者的经验教训。 没有那些只为兼容 1980 年代软件而存在的冷僻指令。

  • 谁在用 RISC-V: SiFive (通用核)、阿里巴巴 (玄铁服务器核)、Western Digital (存储控制器,出货以十亿计)、乐鑫 (ESP32-C3,流行的物联网芯片),以及数十家 AI 加速器初创公司 —— 它们用 RISC-V 作为管理自家定制计算单元的控制处理器。

RISC-V 基础架构

  • 基础整数 ISA (64 位版本 RV64I) 具备:
    • 32 个通用寄存器 (x0-x31,每个 64 位). x0 硬连线为 0 (方便实现常见模式而无需专门指令).
    • 固定 32 位指令宽度 (C 扩展额外提供 16 位压缩指令以提高代码密度).
    • 加载-存储架构:与 ARM 一样,算术只作用于寄存器。 内存访问必须通过显式的 load/store 指令。
# RISC-V 汇编 (仅为直观感受 —— 实际写代码你会用 C/C++)
add  x3, x1, x2      # x3 = x1 + x2
lw   x4, 0(x5)       # 从 x5 指向的地址加载一个字
sw   x4, 8(x5)       # 存储一个字到地址 x5 + 8
beq  x1, x2, label   # 若 x1 == x2 则跳转
  • ISA 的简洁让 RISC-V 核心又小又省电。 一个最小的 RV32I 核心可以用约 10,000 个门实现 (ARM Cortex-M0 约 12,000). 这对嵌入式系统很重要 —— 每一毫瓦、每一平方毫米硅面积都要算计。

V 扩展:RISC-V 向量处理

  • V 扩展 (RVV) 给 RISC-V 加上了可扩展的向量处理,类似 ARM SVE. 向量寄存器的长度 (VLEN) 可配置,由硬件决定 (128 到 65,536 位). 代码写成向量长度无关的形式:不重新编译就能在任意 VLEN 上跑。
#include <riscv_vector.h>

// 使用 RVV 内建函数实现向量加法
void vadd_rvv(const float* a, const float* b, float* c, int n) {
    while (n > 0) {
        // vsetvl: 设置向量长度 —— 处理 min(n, hardware_max) 个元素
        size_t vl = __riscv_vsetvl_e32m1(n);

        // 加载 vl 个元素
        vfloat32m1_t va = __riscv_vle32_v_f32m1(a, vl);
        vfloat32m1_t vb = __riscv_vle32_v_f32m1(b, vl);

        // 相加
        vfloat32m1_t vc = __riscv_vfadd_vv_f32m1(va, vb, vl);

        // 存储
        __riscv_vse32_v_f32m1(c, vc, vl);

        // 推进指针
        a += vl; b += vl; c += vl; n -= vl;
    }
}
  • vsetvl 是关键指令。 它告诉硬件"我想处理这么多元素",硬件回答"我这次能处理这么多" (上限由 VLEN 决定). 循环自动适配任何向量宽度,不需要标量收尾代码 (最后一次迭代自然只处理少量元素).

  • LMUL (length multiplier,长度倍数): RVV 可以把多个向量寄存器组合起来 (m1, m2, m4, m8),每条指令处理更多元素,代价是可用寄存器数量减少。 m1 每个向量操作数占一个寄存器;m8 占八个,一次多处理 8 倍元素,但只剩 4 组寄存器可用。

  • 相比 x86 AVX (固定 256/512 位) 与 ARM NEON (固定 128 位), RVV 的可扩展性是应对多样硬件的重大优势:同一份代码既能跑在极小的嵌入式核 (VLEN=128) 上,也能跑在高性能服务器核 (VLEN=1024+) 上。

嵌入式 ML: TinyML

  • TinyML 是跑在微控制器上的机器学习 —— 那些只有几十 KB RAM、MHz 级 CPU、毫瓦级功耗预算的设备。 想想:一个能识别关键词 ("Hey Siri") 的传感器、一个能识别手势的加速度计,或者一个能数人头的摄像头,全都跑在 $0.50 的芯片上,而且不联网。

  • 限制极端严苛:

资源 服务器 GPU 智能手机 微控制器
RAM 80 GB 6 GB 256 KB
存储 TB 128 GB 1 MB
计算 1000 TFLOPS 10 TFLOPS 0.001 TFLOPS
功耗 700 W 5 W 0.001 W
成本 $30,000 $500 $1
  • 能塞进服务器 GPU 的模型 (\(O(10^{10})\) 参数) 装不进微控制器。 TinyML 模型只有 \(O(10^4)\)–\(O(10^6)\) 个参数,并采用 INT8 甚至 INT4 量化。

TensorFlow Lite Micro (TFLM)

  • TFLM 是 Google 面向微控制器的推理框架。 它跑量化后的 TensorFlow Lite 模型,不做动态内存分配,不依赖操作系统 (Operating System, OS),二进制体积只有约 20 KB.
// 微控制器上的 TinyML 推理 (简化版)
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"

// 模型被编译成一个 C 数组 (const unsigned char model_data[])
const tflite::Model* model = tflite::GetModel(model_data);

// 分配一块固定大小的内存区 (不用 malloc!)
constexpr int kArenaSize = 10 * 1024;  // 10 KB
uint8_t tensor_arena[kArenaSize];

// 初始化解释器
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena, kArenaSize);
interpreter.AllocateTensors();

// 设置输入
float* input = interpreter.input(0)->data.f;
input[0] = sensor_reading;

// 执行推理
interpreter.Invoke();

// 读取输出
float* output = interpreter.output(0)->data.f;
if (output[0] > 0.8f) {
    trigger_alert();
}
  • 这段代码里的关键约束:
    • tensor_arena 是静态分配的 —— 没有 malloc,没有堆。 嵌入式系统常常连动态内存分配器都没有。
    • 模型是一个 const 字节数组,存在闪存 (ROM) 里,不是从文件系统加载。
    • 整个框架 + 模型 + 运行时加起来只占几十 KB.

面向边缘的模型优化

  • 想让模型跑在微控制器上,需要激进的优化:

    • 量化 (第 18 章):把 float32 权重转成 INT8 (体积小 4 倍,在只有整数运算的硬件上快 2–4 倍). 训练后量化很简单;量化感知训练能保住更多精度。

    • 剪枝:去掉接近零的权重。 结构化剪枝 (整通道/整头砍掉) 比非结构化剪枝 (零星置零) 对硬件更友好,因为它真的减少了计算量,而不仅仅是存储。

    • 知识蒸馏 (第 6 章):训练一个小"学生"模型去模仿大"老师"模型。 学生的精度比从头训练要高,因为它能从老师的软预测中学到更多信息。

    • 神经架构搜索 (Neural Architecture Search, NAS):自动搜索能塞进硬件预算 (延迟、内存、功耗) 的高效架构。 MicroNets 和 MCUNet 就是为特定微控制器搜索出的最优架构。

    • 算子融合:把卷积 + 批归一化 + ReLU 融合成一个操作,省掉中间结果的内存写回 (原理和 GPU 上的核函数融合一样,但在只有 256 KB RAM 时更为关键).

AI 加速器中的 RISC-V

  • 许多 AI 加速器初创公司用 RISC-V 并不是拿它直接跑 ML 模型,而是拿它当控制处理器,负责调度自家定制计算单元:
┌─────────────────────────────────────────┐
│              AI 加速器                   │
│                                         │
│  ┌──────────┐    ┌──────────────────┐   │
│  │  RISC-V  │───→│  定制矩阵         │   │
│  │  控制核   │    │  乘法单元         │   │
│  │          │    │ (脉动阵列,       │   │
│  │          │    │  定制数据流)      │   │
│  └──────────┘    └──────────────────┘   │
│       │                    │            │
│       ▼                    ▼            │
│  ┌──────────┐    ┌──────────────────┐   │
│  │  内存    │    │  片上 SRAM       │   │
│  │  控制     │    │ (激活缓冲区)     │   │
│  │          │    │                  │   │
│  └──────────┘    └──────────────────┘   │
└─────────────────────────────────────────┘
  • RISC-V 核心负责:从外部内存加载模型权重、调度各层执行、管理计算单元之间的数据流,以及与主机通信 (通过 PCIe、USB 或 SPI). 真正的重计算 (矩阵乘、卷积) 由定制硬件完成,不由 RISC-V 核完成。

  • 为什么用 RISC-V 做控制:没有授权费 (对创业公司至关重要)、可定制 (加自家专用指令)、体积小 (控制核不需要 x86 那种复杂度),而且开放生态利于快速原型。

  • 例子: Esperanto Technologies (超过 1000 个 RISC-V 核用于 ML)、Tenstorrent (RISC-V 控制 + 定制 Tensix 核)、SiFive (带向量扩展的 RISC-V 边缘 ML 核).

边缘部署的种种限制

  • 在边缘 (设备端而非云端) 部署 ML 会带来云端部署遇不到的限制:

  • 功耗:电池供电设备的总功耗预算可能只有 100 mW. 模型消耗 50 mW,就只剩 50 mW 给系统其他部分 (传感器、无线电、显示器). 功耗感知推理会调度计算以避免热节流并延长电池寿命。

  • 延迟:边缘推理往往必须实时。 唤醒词检测 ("Hey Siri") 必须在约 200 ms 内响应。 自动驾驶感知系统 (第 11 章) 必须在约 30 ms 内处理完一帧。 云端往返 (50–200 ms) 对这类场景来说太慢了。

  • 隐私:数据在设备端处理意味着敏感数据 (医疗影像、语音录音、私人照片) 从不离开设备。 这在某些司法辖区 (通用数据保护条例,General Data Protection Regulation, GDPR) 是法律要求,到处都是用户信任的要求。

  • 连接性:边缘设备的网络连接可能时断时续或干脆没有。 跑在火星车上 (第 11 章)、潜艇里、乡村农场传感器上的模型必须完全离线工作。

  • 规模化成本:把 ML 部署到十亿部智能手机,每台设备成本为 0 (硬件本来就存在). 部署到十亿个 IoT 传感器则意味着每个传感器上的 ML 硬件预算只有几分钱。 到这个量级,RISC-V 零授权费的意义就异常巨大。

编码任务 (用 g++ 或 riscv64-gcc 交叉编译器编译)

  1. 写一个 C 程序模拟 TinyML 推理流水线:静态分配模型缓冲区,跑一次模拟前向传播,度量资源占用。 这能让你体会到嵌入式约束 (无 malloc、固定内存区).

    // task1_tinyml_sim.cpp
    // 编译: g++ -O2 -o task1 task1_tinyml_sim.cpp
    
    #include <iostream>
    #include <chrono>
    #include <cmath>
    #include <cstring>
    
    // 模拟一个微控制器: 固定内存区, 无动态分配
    static constexpr int ARENA_SIZE = 32 * 1024;  // 总 RAM 预算 32 KB
    static uint8_t arena[ARENA_SIZE];
    
    // 简单的 2 层 MLP: 784 -> 64 -> 10 (类似 MNIST, INT8 权重)
    struct TinyModel {
        int8_t w1[784 * 64];      // 第一层权重: 50,176 字节
        int8_t b1[64];             // 第一层偏置
        int8_t w2[64 * 10];       // 第二层权重: 640 字节
        int8_t b2[10];             // 第二层偏置
        // 合计: 约 51 KB → 必须放到闪存 (ROM), 不放到 RAM
    };
    
    // 检查模型能否装进闪存
    void check_model_fit(int flash_kb) {
        int model_bytes = sizeof(TinyModel);
        std::cout << "Model size: " << model_bytes << " bytes ("
                  << model_bytes / 1024 << " KB)\n";
        std::cout << "Flash: " << flash_kb << " KB → "
                  << (model_bytes <= flash_kb * 1024 ? "FITS" : "TOO LARGE") << "\n";
    }
    
    // 模拟推理, 使用固定内存区存放激活
    void mock_inference(const int8_t* input, int8_t* output) {
        // 激活放在 arena (RAM) 里, 不做动态分配
        int8_t* act1 = (int8_t*)arena;            // 第一层输出占 64 字节
        int8_t* act2 = (int8_t*)(arena + 64);     // 第二层输出占 10 字节
    
        // 第一层: 简化的矩阵乘 (不是真正的量化 matmul, 只演示结构)
        for (int j = 0; j < 64; j++) {
            int32_t sum = 0;  // 用 int32 累加避免溢出
            for (int i = 0; i < 784; i++) {
                sum += (int32_t)input[i] * 1;  // 模拟: 权重 = 1
            }
            act1[j] = (int8_t)std::max(-128, std::min(127, sum / 784));  // 量化回 int8
            act1[j] = act1[j] > 0 ? act1[j] : 0;  // ReLU
        }
    
        // 第二层
        for (int j = 0; j < 10; j++) {
            int32_t sum = 0;
            for (int i = 0; i < 64; i++) {
                sum += (int32_t)act1[i] * 1;
            }
            act2[j] = (int8_t)std::max(-128, std::min(127, sum / 64));
        }
    
        std::memcpy(output, act2, 10);
    }
    
    int main() {
        std::cout << "=== TinyML Resource Budget ===\n";
        std::cout << "Arena (RAM): " << ARENA_SIZE << " bytes ("
                  << ARENA_SIZE / 1024 << " KB)\n";
        check_model_fit(256);  // 典型 MCU 的闪存
    
        // 用掉的激活内存
        int activation_bytes = 64 + 10;  // 第一层 + 第二层输出
        std::cout << "Activation memory: " << activation_bytes
                  << " bytes / " << ARENA_SIZE << " available\n\n";
    
        // 推理基准测试
        int8_t input[784];
        int8_t output[10];
        std::memset(input, 1, 784);
    
        auto start = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < 10000; i++) {
            mock_inference(input, output);
        }
        auto end = std::chrono::high_resolution_clock::now();
        double us = std::chrono::duration<double, std::micro>(end - start).count() / 10000;
    
        std::cout << "Inference latency: " << us << " us\n";
        std::cout << "At 160 MHz MCU (~6.25 ns/cycle): ~"
                  << (int)(us * 160) << " cycles\n";
    
        std::cout << "Output logits: ";
        for (int i = 0; i < 10; i++) std::cout << (int)output[i] << " ";
        std::cout << "\n";
    
        return 0;
    }
    

  2. 写一个 C++ 程序把 float32 权重量化为 INT8,并度量压缩比与量化误差。

    // task2_quantise.cpp
    // 编译: g++ -O3 -o task2 task2_quantise.cpp
    
    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <algorithm>
    #include <numeric>
    
    // 对称量化: 把浮点范围 [-max, +max] 映射到 [-127, +127]
    void quantise_symmetric(const float* input, int8_t* output, int n, float& scale) {
        float max_val = 0.0f;
        for (int i = 0; i < n; i++) {
            max_val = std::max(max_val, std::abs(input[i]));
        }
        scale = max_val / 127.0f;
        for (int i = 0; i < n; i++) {
            float scaled = input[i] / scale;
            output[i] = (int8_t)std::max(-127.0f, std::min(127.0f, std::round(scaled)));
        }
    }
    
    // 反量化: INT8 还原为 float
    void dequantise(const int8_t* input, float* output, int n, float scale) {
        for (int i = 0; i < n; i++) {
            output[i] = (float)input[i] * scale;
        }
    }
    
    int main() {
        const int N = 100000;
    
        // 模拟随机权重 (近似正态分布)
        std::vector<float> weights(N);
        for (int i = 0; i < N; i++) {
            // 简单的伪随机近正态值
            float u1 = (float)(i * 7 % 997 + 1) / 998.0f;
            float u2 = (float)(i * 13 % 991 + 1) / 992.0f;
            weights[i] = std::sqrt(-2.0f * std::log(u1)) * std::cos(6.2832f * u2) * 0.1f;
        }
    
        // 量化
        std::vector<int8_t> quantised(N);
        float scale;
        quantise_symmetric(weights.data(), quantised.data(), N, scale);
    
        // 反量化并测误差
        std::vector<float> reconstructed(N);
        dequantise(quantised.data(), reconstructed.data(), N, scale);
    
        float max_error = 0.0f, total_error = 0.0f;
        for (int i = 0; i < N; i++) {
            float err = std::abs(weights[i] - reconstructed[i]);
            max_error = std::max(max_error, err);
            total_error += err;
        }
    
        std::cout << "=== Quantisation Results ===\n";
        std::cout << "Original:    " << N * 4 << " bytes (float32)\n";
        std::cout << "Quantised:   " << N * 1 << " bytes (int8) + 4 bytes (scale)\n";
        std::cout << "Compression: " << 4.0f << "x\n";
        std::cout << "Scale factor: " << scale << "\n";
        std::cout << "Mean abs error: " << total_error / N << "\n";
        std::cout << "Max abs error:  " << max_error << "\n";
        std::cout << "Max abs error / scale: " << max_error / scale
                  << " (should be <= 0.5 quantisation levels)\n";
    
        return 0;
    }
    

  3. 写一个 C++ 程序执行 INT8 矩阵乘法,使用 INT32 累加 —— 这正是嵌入式 ML 加速器实际跑的计算。

    // task3_int8_matmul.cpp
    // 编译: g++ -O3 -o task3 task3_int8_matmul.cpp
    
    #include <iostream>
    #include <chrono>
    #include <vector>
    #include <cstdint>
    
    // INT8 矩阵乘 + INT32 累加 (Tensor Core 和 MCU 加速器实际的做法)
    void matmul_int8(const int8_t* A, const int8_t* B, int32_t* C,
                     int M, int N, int K) {
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < N; j++) {
                int32_t sum = 0;
                for (int k = 0; k < K; k++) {
                    sum += (int32_t)A[i * K + k] * (int32_t)B[k * N + j];
                }
                C[i * N + j] = sum;
            }
        }
    }
    
    // Float32 矩阵乘作为对照
    void matmul_f32(const float* A, const float* B, float* C,
                    int M, int N, int K) {
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < N; j++) {
                float sum = 0.0f;
                for (int k = 0; k < K; k++) {
                    sum += A[i * K + k] * B[k * N + j];
                }
                C[i * N + j] = sum;
            }
        }
    }
    
    int main() {
        const int M = 128, N = 128, K = 128;
    
        std::vector<int8_t> A_i8(M * K, 1), B_i8(K * N, 1);
        std::vector<int32_t> C_i32(M * N);
    
        std::vector<float> A_f32(M * K, 1.0f), B_f32(K * N, 1.0f);
        std::vector<float> C_f32(M * N);
    
        // 基准测试 INT8
        auto start = std::chrono::high_resolution_clock::now();
        for (int t = 0; t < 100; t++) {
            matmul_int8(A_i8.data(), B_i8.data(), C_i32.data(), M, N, K);
        }
        auto end = std::chrono::high_resolution_clock::now();
        double i8_ms = std::chrono::duration<double, std::milli>(end - start).count() / 100;
    
        // 基准测试 FP32
        start = std::chrono::high_resolution_clock::now();
        for (int t = 0; t < 100; t++) {
            matmul_f32(A_f32.data(), B_f32.data(), C_f32.data(), M, N, K);
        }
        end = std::chrono::high_resolution_clock::now();
        double f32_ms = std::chrono::duration<double, std::milli>(end - start).count() / 100;
    
        double gflops_i8 = 2.0 * M * N * K / i8_ms / 1e6;
        double gflops_f32 = 2.0 * M * N * K / f32_ms / 1e6;
    
        std::cout << "INT8 matmul:  " << i8_ms << " ms (" << gflops_i8 << " GOPS)\n";
        std::cout << "FP32 matmul:  " << f32_ms << " ms (" << gflops_f32 << " GFLOPS)\n";
        std::cout << "INT8 speedup: " << f32_ms / i8_ms << "x\n";
        std::cout << "Memory: INT8 = " << M*K + K*N << " bytes vs FP32 = "
                  << (M*K + K*N) * 4 << " bytes (4x less)\n";
    
        return 0;
    }
    


📌 本节要点

  • RISC-V 开放标准:免授权费的开源 ISA,改变了芯片经济模型,尤其利好 AI 创业与嵌入式。
  • 模块化基础 ISA: RV32I/RV64I 只有 47 条基础指令,M/A/F/D/C/V 全为可选扩展,按需取用。
  • V 扩展与 VLEN: RVV 向量长度硬件可配,代码写成向量长度无关形式,同一份代码通吃嵌入式核到服务器核。
  • LMUL 灵活度:用 m1/m2/m4/m8 合并寄存器,换取每指令更多数据吞吐但可用寄存器减少。
  • TinyML 三重极限:微控制器只有 KB 级 RAM、MHz 级 CPU、mW 级功耗,强迫使用 INT8/INT4 与激进优化。
  • TFLM 关键约束:静态内存区 + 模型驻留闪存 + 无堆分配,二进制体积可压到约 20 KB.
  • 边缘优化组合拳:量化、剪枝、知识蒸馏、神经架构搜索 (NAS)、算子融合协同压缩模型。
  • RISC-V 做控制核: AI 加速器用 RISC-V 调度定制矩阵单元,重计算交给脉动阵列而非 CPU 完成。
  • 边缘部署四大约束:功耗预算、实时延迟、数据隐私、离线可用性缺一不可。
  • 规模化的经济账:十亿级 IoT 传感器上,RISC-V 零授权费的成本优势极其显著。