跳至主要内容

基準測試您的模型

欄位值
類別模型與推論
難度初級
預估閱讀時間5-10 minutes
標籤benchmark, synthetic, latency, throughput, power

第 001 章和第 002 章展示了如何執行模型一次,然後如何以非同步方式驅動它。本章回答了下一個實際問題:「此模型在設備上的執行速度如何?」基準測試 API 故意設計得較小。您載入模型,選擇要測量的樣本數量,呼叫 benchmark(...),並讀取傳回的 BenchmarkReport。

基準測試使用模型的 input_specs() 來建立確定性的合成輸入。這使得它對於快速的模型初步基準測試以及比較已編譯的模型變體非常有用,但它不是相機基準測試。它不包括相機解碼、實際的預處理變化、動態輸入大小或依賴資料的後處理行為。

操作指南​

載入模型​

從先前模型教程中使用的相同的已編譯 .tar.gz 檔案開始。由於基準測試會根據模型聲明的輸入規格建立合成張量,因此不需要任何圖像。

C++: 從檔案路徑建置 simaai::neat::Model。

Python: 從檔案路徑建置 pyneat.Model。

tutorials/003_benchmark_your_model/benchmark_your_model.cpp
simaai::neat::Model model(model_path);

執行基準測試​

呼叫 benchmark(samples)。API 會預熱模型,測量單次執行(single-flight)延遲,接著在另一個非同步推送/拉取階段中測量吞吐量,將摘要列印到標準輸出,並在 BenchmarkReport 中傳回相同的主要值。

每個階段都會持續執行,直到至少測量了 samples 個輸入且已經過 10 秒,因此吞吐量階段的功耗和溫度讀數會涵蓋多個樣本。設定 BenchmarkOptions::min_duration_ms 可變更此下限;設為 0 則可進行快速的初步檢查,讓每個階段在測量 samples 個輸入後即停止。

其路徑以 BoxDecode 結尾的檢測模型也可以使用 BenchmarkOptions。設定 original_width、original_height 和 resize_mode,以描述源圖像幾何形狀,BoxDecode 在將檢測映射到模型座標時使用該幾何形狀。合成張量仍然保持模型形狀:

simaai::neat::BenchmarkOptions options;
options.num_samples = 100;
options.original_width = 1920;
options.original_height = 1080;
options.resize_mode = simaai::neat::ResizeMode::Letterbox;
auto report = model.benchmark(options);

Python 會透過 pyneat.BenchmarkOptions 公開相同的欄位。設定原始的兩個維度,或省略這兩個維度;如果省略,基準測試會從已解析的模型路徑推斷幾何形狀。每次執行的基準測試幾何形狀優先於 ModelOptions 中已棄用的 BoxDecode 幾何形狀。

tutorials/003_benchmark_your_model/benchmark_your_model.cpp
simaai::neat::BenchmarkOptions options;
options.num_samples = samples;
options.include_mla_layer_stats = has_flag(argc, argv, "--include-mla-layer-stats");
options.include_mla_stage_stats = has_flag(argc, argv, "--include-mla-stage-stats");
simaai::neat::BenchmarkReport report = model.benchmark(options);
if (report.latency_ms <= 0.0 || report.fps <= 0.0)
throw std::runtime_error("benchmark produced no measured latency/fps");

閱讀報告​

傳回的報告以大多數使用者需要的標題欄位開頭:以毫秒為單位的平均端到端延遲、以每秒幀數為單位的吞吐量、以瓦特為單位的平均與峰值 SoM 功耗,以及處理 samples 個輸入所需的能量(以焦耳為單位)。throughput_duration_ms 會回報吞吐量階段執行了多久。

功耗資料來自 Sentinel,也就是 Neat 安裝程式所設定的板子遙測常駐程式。report.telemetry 還包含吞吐量階段的電源軌功耗、晶粒與板子溫度、CPU 使用率以及 MLA 記憶體;各欄位說明請參閱測量模型執行效能。如果 Sentinel 未執行,基準測試仍會報告延遲和吞吐量,將功耗欄位保持為零,並將 report.telemetry.status 設定為原因。

tutorials/003_benchmark_your_model/benchmark_your_model.cpp
std::cout << "report_latency_ms=" << report.latency_ms << "\n";
std::cout << "report_fps=" << report.fps << "\n";
std::cout << "report_avg_power_watts=" << report.avg_power_watts << "\n";
std::cout << "report_energy_joules=" << report.energy_joules << "\n";
std::cout << "report_peak_power_watts=" << report.peak_power_watts << "\n";
std::cout << "report_throughput_duration_ms=" << report.throughput_duration_ms << "\n";
std::cout << "report_telemetry_status=" << report.telemetry.status << "\n";

進階:MLA 層與階段統計資料​

兩個選用選項會在基準測試後執行 mla-rt,測量已編譯 MLA 程式碼的時間。兩者預設皆停用,且需要沒有其他 MLA 工作負載的閒置 devkit。在任一教學命令中加入 --include-mla-layer-stats 或 --include-mla-stage-stats,或透過 API 啟用:

simaai::neat::BenchmarkOptions options;
options.include_mla_layer_stats = true;
options.include_mla_stage_stats = true;
auto report = model.benchmark(options);
options = pyneat.BenchmarkOptions()
options.include_mla_layer_stats = True
options.include_mla_stage_stats = True
report = model.benchmark(options)

從 report.mla_layer_stats.layers 讀取每個已編譯 MLA 層從零開始的 stage_index、name 與 latency_us:在只執行該層的一次剖析執行中測得的硬體執行時間。執行時間為零的 no-op 項目也會保留。各層延遲的總和不等於階段延遲,也不等於整體延遲。

從 report.mla_stage_stats.stages 讀取每個 MLA 階段的 stage_index 與 latency_us:該階段連續不中斷執行 200 次的平均硬體時間。階段延遲包含 MLA 運算,以及程式對 DRAM 的讀取與寫入;不含模型載入、主機與 EV74 處理,以及管線額外負擔。

若找不到 mla-rt、其執行失敗或逾時,benchmark() 仍會傳回主要測量值,清單保持為空,並將原因寫入 report.mla_layer_stats.status 或 report.mla_stage_stats.status。其他情況下,各狀態為 "not requested" 或 "ok"。mla-rt 執行失敗、逾時或當機後,MLA 可能仍有未完成的工作,因此同一行程中之後的剖析會略過並記錄此原因。在確認 MLA 已復原之前,請讓 DevKit 保持閒置。

MLA 層統計會遵循所選的終端政策:只測量所選推論路徑中的 MLA 階段,且每個階段保留其在完整模型中的 stage_index。

tutorials/003_benchmark_your_model/benchmark_your_model.cpp
if (options.include_mla_layer_stats) {
std::cout << "mla_layer_stats.status=" << report.mla_layer_stats.status << "\n";
for (const auto& layer : report.mla_layer_stats.layers) {
std::cout << " stage_index=" << layer.stage_index << " name=" << layer.name
<< " latency_us=" << layer.latency_us << "\n";
}
}
if (options.include_mla_stage_stats) {
std::cout << "mla_stage_stats.status=" << report.mla_stage_stats.status << "\n";
for (const auto& stage : report.mla_stage_stats.stages) {
std::cout << " stage_index=" << stage.stage_index << " latency_us=" << stage.latency_us
<< "\n";
}
}

儲存報告​

呼叫 report.save(path) 即可將完整報告寫入 YAML 或 JSON,包含已要求的 MLA 層與階段統計資料:

auto path = report.save("results/model/yolo_benchmark.yaml");
auto json_path = report.save("results/model/yolo_benchmark.json");
path = report.save("results/model/yolo_benchmark.yaml")
json_path = report.save("results/model/yolo_benchmark.json")

副檔名 .json 選擇 JSON;.yaml 與其他結尾使用 YAML。檔名保持不變。

請指定包含檔名的路徑。不存在的父目錄會自動建立,既有檔案會被取代,並傳回寫入的路徑。儲存需要明確呼叫;benchmark() 不會自動建立報告檔案。

在任一教學命令後加入 --report results/model/yolo_benchmark.yaml。搭配 --include-mla-layer-stats 或 --include-mla-stage-stats,即可同時儲存主要測量值與 MLA 執行時間。

tutorials/003_benchmark_your_model/benchmark_your_model.cpp
std::string report_path;
if (get_arg(argc, argv, "--report", report_path)) {
std::cout << "report_path=" << report.save(report_path).string() << "\n";
}

執行​

執行它,您應該會看到 benchmark() 輸出的基準測試摘要,後面接著從傳回的報告中輸出的相同值。從 Neat 安裝根目錄(包含 share/ 和 lib/ 的目錄)執行 Python 和 C++(預先建置) 命令;從 儲存庫根目錄執行 從原始碼建置 命令。

C++ (prebuilt):

./lib/sima-neat/tutorials/tutorial_003_benchmark_your_model \
--model /tmp/resnet_50.tar.gz --samples 100

C++ (build from source):

./build.sh --all --no-doc
./build/tutorials/tutorial_003_benchmark_your_model \
--model /tmp/resnet_50.tar.gz --samples 100

預期的輸出(確切的數字取決於模型、板載和當前負載;C++ 建置還會輸出尾部的 [OK] 行):

NEAT Benchmark
Input: synthetic
Samples: 100
Compiled batch: 1
Measurement detail: e2e+throughput-only
Latency: 1.97 ms
FPS: 1457.04 inferences/s
Batch throughput: 1457.04 batches/s
Throughput window: 30.09 s, 43849 inputs
Power: 13.19 W avg, 13.81 W peak (SoM)
MLA power: 3.05 W avg, 3.25 W max
Energy: 0.91 J for 100 inputs
Die temp: 46.34 °C avg, 47.56 °C max
CPU usage: 12.38 % avg, 13.22 % max
MLA memory: 29.65 MiB
Linux memory: 682.88 MiB avg, 697.41 MiB max
Telemetry: Sentinel main:766066b74f49, 15 samples
report_latency_ms=1.96852
report_fps=1457.04
report_avg_power_watts=13.1875
report_energy_joules=0.905085
report_peak_power_watts=13.8125
report_throughput_duration_ms=30094.5
report_telemetry_status=ok
[OK] 003_benchmark_your_model

若要將本章的 C++ 原始碼整合到您自己的專案中,並使用自訂的 CMakeLists.txt(無需額外的資料夾),請參閱登陸頁面上的 如何執行教學。

實務應用​

當您需要快速了解已編譯的模型封存檔時,請使用此基準測試:它是否能正常執行?測得的非同步吞吐量是多少?以及在此目標平台上,主要的板卡功耗是多少?

對於應用程式效能,也請基準測試實際的管線。合成模型輸入是故意設計為穩定的,因此它不代表相機抖動、編解碼器成本、實際的預處理、主機在負載下的排程,或下游應用程式邏輯。若要針對手動建立的非同步執行來調整佇列深度和反壓,請參閱調整吞吐量和佇列深度。

Model::benchmark() 需要具體的 input_specs() 尺寸。如果輸入形狀是動態或非具體的,基準測試將明確失敗,而不是猜測一個形狀。

完整原始碼​

顯示完整原始碼程式
tutorials/003_benchmark_your_model/benchmark_your_model.cpp
// Benchmark a compiled model with deterministic synthetic inputs.
//
// Usage:
// tutorial_003_benchmark_your_model --model /path/to/model.tar.gz [--samples 100]
// [--report results/model/yolo_benchmark.yaml] [--include-mla-layer-stats]
// [--include-mla-stage-stats]

#include "neat.h"

#include <iostream>
#include <stdexcept>
#include <string>

namespace {

bool has_flag(int argc, char** argv, const std::string& key) {
for (int i = 1; i < argc; ++i) {
if (key == argv[i])
return true;
}
return false;
}

bool get_arg(int argc, char** argv, const std::string& key, std::string& out) {
for (int i = 1; i + 1 < argc; ++i) {
if (key == argv[i]) {
out = argv[i + 1];
return true;
}
}
return false;
}

int parse_int_arg(int argc, char** argv, const std::string& key, int def) {
std::string value;
if (!get_arg(argc, argv, key, value))
return def;
return std::stoi(value);
}

} // namespace

int main(int argc, char** argv) {
try {
std::string model_path;
if (!get_arg(argc, argv, "--model", model_path)) {
std::cerr << "Usage: tutorial_003_benchmark_your_model --model <path> [--samples <n>] "
"[--report <file>] [--include-mla-layer-stats] [--include-mla-stage-stats]\n";
return 1;
}
const int samples = parse_int_arg(argc, argv, "--samples", 100);

// CORE LOGIC
simaai::neat::Model model(model_path);

simaai::neat::BenchmarkOptions options;
options.num_samples = samples;
options.include_mla_layer_stats = has_flag(argc, argv, "--include-mla-layer-stats");
options.include_mla_stage_stats = has_flag(argc, argv, "--include-mla-stage-stats");
simaai::neat::BenchmarkReport report = model.benchmark(options);
if (report.latency_ms <= 0.0 || report.fps <= 0.0)
throw std::runtime_error("benchmark produced no measured latency/fps");

std::cout << "report_latency_ms=" << report.latency_ms << "\n";
std::cout << "report_fps=" << report.fps << "\n";
std::cout << "report_avg_power_watts=" << report.avg_power_watts << "\n";
std::cout << "report_energy_joules=" << report.energy_joules << "\n";
std::cout << "report_peak_power_watts=" << report.peak_power_watts << "\n";
std::cout << "report_throughput_duration_ms=" << report.throughput_duration_ms << "\n";
std::cout << "report_telemetry_status=" << report.telemetry.status << "\n";
if (options.include_mla_layer_stats) {
std::cout << "mla_layer_stats.status=" << report.mla_layer_stats.status << "\n";
for (const auto& layer : report.mla_layer_stats.layers) {
std::cout << " stage_index=" << layer.stage_index << " name=" << layer.name
<< " latency_us=" << layer.latency_us << "\n";
}
}
if (options.include_mla_stage_stats) {
std::cout << "mla_stage_stats.status=" << report.mla_stage_stats.status << "\n";
for (const auto& stage : report.mla_stage_stats.stages) {
std::cout << " stage_index=" << stage.stage_index << " latency_us=" << stage.latency_us
<< "\n";
}
}

std::string report_path;
if (get_arg(argc, argv, "--report", report_path)) {
std::cout << "report_path=" << report.save(report_path).string() << "\n";
}

std::cout << "[OK] 003_benchmark_your_model\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}

來源​