▼點(diǎn)擊下方名片,關(guān)注公眾號(hào),獲取更多精彩內(nèi)容▼
這里是《賀老師講嵌入式AI》,我是《嵌入式AI:讓單片機(jī)學(xué)會(huì)思考》課程主理人,專注AI在MCU上的落地實(shí)踐。
關(guān)注公眾號(hào)后,回復(fù)【嵌入式AI】,可以免費(fèi)獲取更多賀老師準(zhǔn)備的專業(yè)資料。
模型已經(jīng)部署到 STM32,串口打印“inference time: 3 ms”。項(xiàng)目評(píng)審時(shí)再追問一句:這 3 ms 測的是神經(jīng)網(wǎng)絡(luò),還是包含了 MFCC、緩存搬運(yùn)和后處理?中斷搶占算沒算?第一次運(yùn)行也是 3 ms 嗎?最慢一次是多少?很多計(jì)時(shí)結(jié)果到這里就說不清了。
HAL_GetTick() 通常以毫秒為單位。一次 600 μs 的推理可能被記成 0 ms,也可能因?yàn)榭邕^ SysTick 邊界被記成 1 ms。對(duì)需要判斷任務(wù)余量的 MCU 項(xiàng)目,這種分辨率不夠。
一次端側(cè)識(shí)別通常包含數(shù)據(jù)準(zhǔn)備、特征提取、模型推理和輸出決策。只包住 ai_network_run(),測到的是網(wǎng)絡(luò)運(yùn)行時(shí)間;從 DMA 數(shù)據(jù)就緒一直量到狀態(tài)機(jī)收到事件,得到的才是業(yè)務(wù)鏈路延遲。兩個(gè)數(shù)字都需要,但含義不同。
DMA / 搬運(yùn) | 濾波 / MFCC | ai_network_run | 閾值 / 狀態(tài)機(jī) |
圖 1:模型時(shí)間與端到端響應(yīng)時(shí)間不是同一個(gè)指標(biāo)
建議在代碼里保留兩個(gè)測量區(qū)間:
ai_network_run() 前后,便于比較模型版本、優(yōu)化等級(jí)和內(nèi)存位置。緩存維護(hù)放在哪一側(cè)也要寫清楚。DMA 把輸入寫入可緩存 RAM 后,如果需要執(zhí)行 D-Cache invalidate,這段時(shí)間屬于數(shù)據(jù)準(zhǔn)備;若模型權(quán)重放在外部存儲(chǔ),首次訪問造成的 Cache miss 則屬于真實(shí)推理成本。測量報(bào)告必須說明內(nèi)存位置、I/D Cache 狀態(tài)、編譯優(yōu)化等級(jí)和 CPU 主頻,否則不同工程里的“3 ms”沒有可比性。
Cortex-M3、M4、M7、M33 等內(nèi)核通常可以使用 DWT 的 CYCCNT 寄存器統(tǒng)計(jì)內(nèi)核周期。它的分辨率是一個(gè) CPU cycle,比毫秒 Tick 更適合測短函數(shù)。DWT 屬于可選調(diào)試組件,具體芯片仍要查內(nèi)核配置;不具備周期計(jì)數(shù)器的內(nèi)核可改用硬件 TIM 或 GPIO 方法。
#include "main.h"
#include <stdbool.h>
#include <stdint.h>
static bool dwt_counter_init(void)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0U;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
return (DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) != 0U;
}
在時(shí)鐘初始化完成后調(diào)用一次 dwt_counter_init()。如果返回 false,不要繼續(xù)打印一個(gè)看似正常的 0;應(yīng)改用定時(shí)器或 GPIO,并檢查目標(biāo)內(nèi)核是否實(shí)現(xiàn) DWT。
static uint32_t run_network_cycles(void)
{
__DSB();
__ISB();
const uint32_t start = DWT->CYCCNT;
const ai_i32 batch = ai_network_run(
network,
&ai_input[0],
&ai_output[0]);
__DSB();
__ISB();
const uint32_t elapsed = DWT->CYCCNT - start;
if (batch != 1) {
ai_error error = ai_network_get_error(network);
printf("AI run failed: type=%d code=%d\r\n",
error.type, error.code);
}
return elapsed;
}
uint32_t 無符號(hào)減法可以正確處理一次計(jì)數(shù)器回繞,前提是單次測量小于完整回繞周期。回繞時(shí)間為:
例如 480 MHz 下約為 8.95 秒,測一次 MCU 神經(jīng)網(wǎng)絡(luò)通常足夠。周期轉(zhuǎn)微秒時(shí)使用 64 位中間變量,避免先乘一百萬發(fā)生溢出:
static uint32_t cycles_to_us(uint32_t cycles)
{
return (uint32_t)(((uint64_t)cycles * 1000000ULL)
/ (uint64_t)SystemCoreClock);
}
SystemCoreClock 必須與當(dāng)前時(shí)鐘樹一致。修改 PLL 后沒有更新該變量,會(huì)讓周期數(shù)正確、換算出來的微秒錯(cuò)誤。調(diào)試時(shí)同時(shí)打印 cycles、SystemCoreClock 和 time_us,能更快發(fā)現(xiàn)問題。
DWT 適合代碼內(nèi)部分析,GPIO 更接近外部觀察者。推理開始時(shí)拉高引腳,結(jié)束時(shí)拉低,邏輯分析儀上脈沖寬度就是實(shí)際經(jīng)過時(shí)間。它不依賴串口打印,也不會(huì)因?yàn)?nbsp;printf() 阻塞改變結(jié)果。
#define BENCH_HIGH() \
(GPIOB->BSRR = GPIO_PIN_0)
#define BENCH_LOW() \
(GPIOB->BSRR = ((uint32_t)GPIO_PIN_0 << 16U))
static ai_i32 run_network_with_gpio(void)
{
BENCH_HIGH();
__DSB();
const ai_i32 batch = ai_network_run(
network,
&ai_input[0],
&ai_output[0]);
__DSB();
BENCH_LOW();
return batch;
}
示例使用 PB0,實(shí)際工程換成空閑引腳并在 CubeMX 中配置為推挽輸出。直接寫 BSRR 比 HAL_GPIO_WritePin() 更容易控制測量開銷。若要量端到端時(shí)間,把 BENCH_HIGH() 移到輸入窗口就緒的位置,把 BENCH_LOW() 放到狀態(tài)機(jī)接收結(jié)果之后。
表 1:DWT 與 GPIO 不是互相替代,而是互相校驗(yàn)
不要為了獲得一個(gè)“漂亮數(shù)字”直接關(guān)中斷。關(guān)閉中斷測到的是實(shí)驗(yàn)室里的純函數(shù)時(shí)間,不能代表 RTOS 和真實(shí)外設(shè)工作時(shí)的延遲。更實(shí)用的做法是保留正常中斷,連續(xù)采樣多次;最小值接近無搶占狀態(tài),P95 和最大值反映調(diào)度、Cache miss 及外設(shè)中斷帶來的抖動(dòng)。確實(shí)需要純模型基線時(shí),可以單獨(dú)做一次受控實(shí)驗(yàn),并明確標(biāo)注“中斷關(guān)閉”。
第一次推理可能包含冷 Cache、外部 Flash 首次訪問和運(yùn)行庫初始化影響。先保留第一次結(jié)果,再執(zhí)行若干次暖機(jī),隨后采集一組穩(wěn)定樣本。報(bào)告至少包含首次、最小值、中位數(shù)、P95 和最大值。
最后把耗時(shí)放回業(yè)務(wù)周期。滑動(dòng)窗口模型的截止時(shí)間通常不是窗口長度,而是窗口步長。1 秒音頻窗口每 100 ms 產(chǎn)生一次新結(jié)果,推理鏈路就必須在下一個(gè) 100 ms 周期前完成。
假設(shè)某項(xiàng)目的窗口步長為 20 ms,預(yù)處理 0.8 ms、推理 P95 為 3.4 ms、后處理 0.3 ms、調(diào)度抖動(dòng) 0.7 ms,總計(jì) 5.2 ms,占周期 26%。這只是計(jì)算示例,實(shí)際項(xiàng)目要使用自己的測量數(shù)據(jù)。剩余 74% 也不能全部交給 AI,還要留給采樣、通信、控制任務(wù)和安全余量。
| 最終驗(yàn)收口徑 |
參考資料:
Arm CMSIS-Core:DWT_Type 與 CYCCNT
Arm CMSIS-View:Cortex-M 調(diào)試計(jì)時(shí)器說明
ST UM2526:STM32Cube.AI Validation on Target
ST Wiki:STM32Cube.AI 生成工程的功耗測量

END