본문으로 건너뛰기

모델 성능을 평가해 보세요

필드값
범주모델 및 추론
난이도초급
예상 소요 시간5-10 minutes
레이블benchmark, synthetic, latency, throughput, power

1장과 2장에서는 모델을 한 번 실행하고 비동기적으로 실행하는 방법을 보여주었습니다. 이 장에서는 다음 실질적인 질문에 답합니다. "이 모델은 장치에서 얼마나 빠르게 실행됩니까?" 벤치마크 API는 의도적으로 작게 설계되었습니다. 모델을 로드하고, 측정할 샘플 수를 선택한 다음, benchmark(...)를 호출하고 반환된 BenchmarkReport를 읽습니다.

벤치마크는 모델의 input_specs()를 사용하여 결정적인 합성 입력을 생성합니다. 이는 빠른 모델 성능 테스트와 컴파일된 모델 변형을 비교하는 데 유용하지만 카메라 벤치마크는 아닙니다. 카메라 디코딩, 실제 전처리 가변성, 동적 입력 크기 또는 데이터 종속적 후처리 동작이 포함되지 않습니다.

둘러보기​

모델 로드​

이전 모델 튜토리얼에서 사용한 동일한 컴파일된 .tar.gz 아카이브를 사용합니다. 벤치마크는 모델의 선언된 입력 사양에서 합성 텐서를 생성하므로 이미지는 필요하지 않습니다.

아카이브 경로에서 simaai::neat::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를 설정하고, 각 패스가 samples개의 입력 후에 중지하는 빠른 스모크 테스트를 하려면 0으로 설정합니다.

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는 처리량 패스가 실행된 시간을 보고합니다.

전력 값은 Neat 설치 프로그램이 설정하는 보드 텔레메트리 데몬인 Sentinel에서 제공됩니다. report.telemetry에는 처리량 패스 동안의 레일 전력, 다이 및 보드 온도, CPU 사용률, MLA 메모리도 포함됩니다. 각 필드에 대해서는 모델 실행 측정을 참조하십시오. Sentinel이 실행 중이 아닌 경우에도 벤치마크는 지연 시간과 처리량을 보고하고, 전력 필드를 0으로 두며, 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 레이어의 0부터 시작하는 stage_index, name, latency_us를 읽습니다. 이는 해당 레이어만 실행하는 한 번의 프로파일링 실행에서 측정한 하드웨어 실행 시간입니다. 실행 시간이 0인 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)를 호출하면 요청한 MLA 레이어 및 스테이지 통계를 포함한 전체 보고서를 YAML 또는 JSON으로 저장합니다.

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;
}
}

소스​