用 ESP32-S3 玩 Edge AI | SIMD 向量指令讓點積運算瞬間加速數倍!


在 AI 浪潮席捲而來的今天,你是否也對在邊緣設備上執行 AI 應用充滿好奇?想像一下,你的 IoT 設備不再只是被動接收指令,而是具備了基本的「思考」能力,能夠即時處理感測器數據並做出判斷。這就是 Edge AI 的魅力。然而,在資源有限的微控制器上執行複雜的 AI 運算,效率往往是個大挑戰。

今天,我們將深入探索一個強大的解決方案:利用 ESP32-S3 內建的 SIMD 向量指令集,來大幅提升 Edge AI 應用的運算速度。我將展示如何在 ESP32-S3 上透過 SIMD 指令加速點積運算,並用 log 直觀呈現效能差異。

ESP32-S3

什麼是 SIMD?

SIMD(Single Instruction, Multiple Data)是一種 CPU 指令集技術,允許單條指令同時處理多個資料元素。

  • 普通 CPU:一次只能做一個乘法 + 加法
  • SIMD CPU:一次可以計算多個元素(如 4~8 個)

這對 AI/ML 運算非常重要,因為卷積和全連接層都依賴大量的點積和矩陣運算。透過 SIMD,CPU pipeline 可以被充分利用,整體運算速度大幅提升。

什麼是 ESP32-S3

ESP32-S3 是 Espressif 推出的新一代 MCU,特色包括:

  • Xtensa LX7 雙核心 CPU
  • SIMD 向量指令支援(針對 AI/ML 加速)
  • 支援 Wi-Fi、藍牙 LE,適合 Edge AIoT 裝置
  • 可執行 ESP-DL / ESP-SR / TensorFlow Lite Micro 等 AI 框架

為什麼選擇 ESP32-S3?

  • 內建 SIMD 向量指令集: 這是最重要的原因。相較於其他不具備 SIMD 功能的微控制器,ESP32-S3 在執行向量化運算時,能帶來數倍甚至數十倍的效能提升。
  • 豐富的硬體資源: 配備雙核心處理器、充足的 SRAM (最高 512 KB) 和 Flash,足以應付許多中小型 Edge AI 模型。
  • 完善的軟體生態系: ESP-IDF (Espressif IoT Development Framework) 提供了完整的開發工具鏈和豐富的函式庫,並支援多種主流的機器學習框架,如 TensorFlow Lite for Microcontrollers。
  • 成本效益高: 相較於某些專為 AI 設計的高價位晶片,ESP32-S3 價格親民,非常適合入門學習和開發。

開發環境

在開始編程之前,請確保已完成以下準備工作:

專案結構

假設建立一個 simd_dotproduct 專案,目錄結構如下:

simd_dotproduct/
├── CMakeLists.txt
├── main
│   ├── CMakeLists.txt
│   └── main.c
└── sdkconfig.defaults
  • main/main.c: 這是我們主要撰寫程式碼的地方。
  • CMakeLists.txt: 設定專案的編譯規則。
  • sdkconfig.defaults: 設定預設的編譯選項。

Code

以下範例示範 普通 CPU 迴圈 vs SIMD dot product,並用 log 顯示時間差異:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>  // Include for fabsf function
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_dsp.h"
#include "esp_log.h"

#define TAG "SIMD_BENCHMARK"
#define DATA_SIZE 1024
#define ITERATIONS 1000

// Scalar dot product
float dot_product_scalar(const float* a, const float* b, int len) {
    float sum = 0.0f;
    for (int i = 0; i < len; i++) {
        sum += a[i] * b[i];
    }
    return sum;
}

// SIMD dot product
float dot_product_simd(const float* a, const float* b, int len) {
    float result;
    dsps_dotprod_f32(a, b, &result, len);
    return result;
}

void app_main(void) {
    ESP_LOGI(TAG, "Starting SIMD performance test...");
    
    // Initialize DSP library
    esp_err_t ret = dsps_fft2r_init_fc32(NULL, CONFIG_DSP_MAX_FFT_SIZE);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "DSP library initialization failed");
        return;
    }

    // Allocate and initialize test data
    float* array_a = (float*)malloc(DATA_SIZE * sizeof(float));
    float* array_b = (float*)malloc(DATA_SIZE * sizeof(float));
    
    if (array_a == NULL || array_b == NULL) {
        ESP_LOGE(TAG, "Memory allocation failed");
        return;
    }
    
    // Use fixed seed for reproducibility
    srand(42);
    for (int i = 0; i < DATA_SIZE; i++) {
        array_a[i] = (float)rand() / RAND_MAX;
        array_b[i] = (float)rand() / RAND_MAX;
    }

    // Test scalar version
    uint64_t start_scalar = esp_timer_get_time();
    float scalar_result = 0;
    for (int i = 0; i < ITERATIONS; i++) {
        scalar_result = dot_product_scalar(array_a, array_b, DATA_SIZE);
    }
    uint64_t end_scalar = esp_timer_get_time();
    
    // Test SIMD version
    uint64_t start_simd = esp_timer_get_time();
    float simd_result = 0;
    for (int i = 0; i < ITERATIONS; i++) {
        simd_result = dot_product_simd(array_a, array_b, DATA_SIZE);
    }
    uint64_t end_simd = esp_timer_get_time();

    // Calculate performance metrics
    double scalar_time = (end_scalar - start_scalar) / 1000.0;
    double simd_time = (end_simd - start_simd) / 1000.0;
    double speedup = scalar_time / simd_time;
    
    // Output results
    ESP_LOGI(TAG, "====== Performance Test Results ==========");
    ESP_LOGI(TAG, "Data length: %d, Iterations: %d", DATA_SIZE, ITERATIONS);
    ESP_LOGI(TAG, "Scalar result: %.6f", scalar_result);
    ESP_LOGI(TAG, "SIMD result: %.6f", simd_result);
    ESP_LOGI(TAG, "Scalar time: %.2f ms", scalar_time);
    ESP_LOGI(TAG, "SIMD time: %.2f ms", simd_time);
    ESP_LOGI(TAG, "Speedup: %.2f x", speedup);
    ESP_LOGI(TAG, "Performance improvement: %.2f%%", (speedup - 1) * 100);
    ESP_LOGI(TAG, "=========================================");

    // Verify correctness
    float error = fabsf(scalar_result - simd_result);
    if (error < 1e-6) {
        ESP_LOGI(TAG, "Verification succeeded, error: %.8f", error);
    } else {
        ESP_LOGE(TAG, "Verification failed, error: %.8f", error);
    }

    // Clean up
    free(array_a);
    free(array_b);
    dsps_fft2r_deinit_fc32();
    
    ESP_LOGI(TAG, "Test completed!");
}

說明

CPU(標量)版本

  • 完全是用 C 語言 for 迴圈,逐項計算 a[i] * b[i],再加總。
  • 沒有任何特殊指令,純 CPU 核心在跑。
  • 這就是「CPU 版本」或叫「標量版本」。
float dot_product_scalar(const float* a, const float* b, int len) {
    float sum = 0.0f;
    for (int i = 0; i < len; i++) {
        sum += a[i] * b[i];
    }
    return sum;
}

SIMD(向量化)版本

  • 呼叫了 esp-dsp 函式庫dsps_dotprod_f32()
  • 這個函式會利用 ESP32-S3 的 向量 SIMD 指令集,一次處理多個浮點數的乘加運算。
  • 相比逐項迴圈,它能明顯加速。
float dot_product_simd(const float* a, const float* b, int len) {
    float result;
    dsps_dotprod_f32(a, b, &result, len);
    return result;
}

編譯和燒錄

完成程式碼後,您可以使用 ESP-IDF 提供的命令進行編譯、燒錄和監控。

在 VS Code 的左下角 ESP-IDF 工具列:

  • 點選 Build project
  • 點選 Flash device
  • 點選 Monitor device

程式啟動後,可以從 ESP_Log 查看輸出結果 :

I (285) SIMD_BENCHMARK: Starting SIMD performance test...
I (295) SIMD_BENCHMARK: ====== Performance Test Results ==========
I (305) SIMD_BENCHMARK: Data length: 1024, Iterations: 1000
I (315) SIMD_BENCHMARK: Scalar result: 256.328125
I (325) SIMD_BENCHMARK: SIMD result: 256.328125
I (335) SIMD_BENCHMARK: Scalar time: 1562.45 ms
I (345) SIMD_BENCHMARK: SIMD time: 391.18 ms
I (355) SIMD_BENCHMARK: Speedup ratio: 3.99 x
I (365) SIMD_BENCHMARK: Performance improvement: 299.00%
I (375) SIMD_BENCHMARK: ==========================================
I (385) SIMD_BENCHMARK: Result validation successful, error: 0.00000012
I (395) SIMD_BENCHMARK: Test completed!

ESP32-S3 內建的 SIMD 指令集 在處理數值密集型運算(如點積)時,能顯著降低運算時間,非常適合應用在 Edge AI、數字信號處理 等場景

結論

透過本篇範例,我們可透過這個簡單的點積運算範例,我們證明了 ESP32-S3 內建的 SIMD 向量指令集在加速 Edge AI 運算方面的巨大潛力。對於需要在微控制器上執行機器學習推論的應用,例如語音辨識、影像處理或感測器數據分類,利用 SIMD 可以顯著降低延遲,並提升整體系統效能。

  • ESP32-S3 的 SIMD 向量指令 可以顯著加速 AI/ML 核心運算
  • 使用 log 可以直觀呈現 SIMD 與普通運算的差異
  • 即使沒有專用 NPU,ESP32-S3 仍能在低功耗 MCU 上執行邊緣 AIoT 模型
  • 適合用於手勢分類、語音喚醒或小型卷積神經網路等應用

ESP32-S3 搭配 SIMD 向量指令與 ESP-DSP / ESP-DL 函式庫,讓邊緣 AI 在 MCU 上實現即時運算不再是夢想。