Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/YOLO-Master-Edge-Deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ examples/YOLO-Master-Edge-Deployment/build-ort/yolo_master_edge_benchmark \
--images /path/to/VisDrone/images/val \
--profile visdrone \
--imgsz 960 \
--threads 4 \
--limit 500 \
--output benchmark_onnx.csv
```
Expand Down Expand Up @@ -107,6 +108,7 @@ examples/YOLO-Master-Edge-Deployment/build-ncnn/yolo_master_edge_benchmark \
--images /path/to/VisDrone/images/val \
--profile visdrone \
--imgsz 960 \
--threads 4 \
--limit 500 \
--output benchmark_ncnn.csv
```
Expand Down Expand Up @@ -137,11 +139,17 @@ examples/YOLO-Master-Edge-Deployment/build-mnn/yolo_master_edge_benchmark \
--images /path/to/VisDrone/images/val \
--profile visdrone \
--imgsz 960 \
--threads 4 \
--limit 500 \
--output benchmark_mnn.csv
```

`--images` accepts either a directory of images or a text file with one image path per line.
`--threads` configures the CPU worker count for ONNX Runtime, NCNN, and MNN. Keep it identical across backends for a
fair CPU comparison. Thread configuration is applied before each runtime loads its model.

For MNN, the input session is resized only when the input tensor shape changes. Fixed-shape benchmark loops therefore
exclude repeated session-resize overhead.

## Benchmark CSV Output

Expand Down
2 changes: 2 additions & 0 deletions examples/YOLO-Master-Edge-Deployment/cpp/backends/backend.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cstdint>
#include <string>
#include <vector>

Expand All @@ -11,6 +12,7 @@ struct Tensor {
class Backend {
public:
virtual ~Backend() = default;
virtual void set_num_threads(int threads) = 0;
virtual void load(const std::string& model_path) = 0;
virtual Tensor infer(const Tensor& input) = 0;
virtual std::string name() const = 0;
Expand Down
16 changes: 13 additions & 3 deletions examples/YOLO-Master-Edge-Deployment/cpp/backends/mnn_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ void validate_input(const Tensor& input) {

MnnBackend::MnnBackend() = default;

void MnnBackend::set_num_threads(int threads) {
if (threads <= 0) {
throw std::invalid_argument("MNN thread count must be positive");
}
num_threads_ = threads;
}

MnnBackend::~MnnBackend() {
#ifdef WITH_MNN
if (interpreter_) {
Expand All @@ -60,7 +67,7 @@ void MnnBackend::load(const std::string& model_path) {

MNN::ScheduleConfig config;
config.type = MNN_FORWARD_CPU;
config.numThread = 1;
config.numThread = num_threads_;
session_ = interpreter_->createSession(config);
if (!session_) {
throw std::runtime_error("failed to create MNN session: " + model_path_);
Expand All @@ -87,8 +94,11 @@ Tensor MnnBackend::infer(const Tensor& input) {
}

std::vector<int> dims(input.shape.begin(), input.shape.end());
interpreter_->resizeTensor(input_tensor_, dims);
interpreter_->resizeSession(session_);
if (input_shape_ != input.shape) {
interpreter_->resizeTensor(input_tensor_, dims);
interpreter_->resizeSession(session_);
input_shape_ = input.shape;
}

auto* tmp_input = MNN::Tensor::create(
dims,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ class MnnBackend final : public Backend {
public:
MnnBackend();
~MnnBackend() override;
void set_num_threads(int threads) override;
void load(const std::string& model_path) override;
Tensor infer(const Tensor& input) override;
std::string name() const override;

private:
std::string model_path_;
int num_threads_ = 4;
std::vector<int64_t> input_shape_;
#ifdef WITH_MNN
MNN::Interpreter* interpreter_ = nullptr;
MNN::Session* session_ = nullptr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ NcnnBackend::NcnnBackend() = default;

NcnnBackend::~NcnnBackend() = default;

void NcnnBackend::set_num_threads(int threads) {
if (threads <= 0) {
throw std::invalid_argument("NCNN thread count must be positive");
}
num_threads_ = threads;
}

void NcnnBackend::load(const std::string& model_path) {
if (model_path.empty()) {
throw std::invalid_argument("NCNN model path is empty");
Expand All @@ -176,7 +183,7 @@ void NcnnBackend::load(const std::string& model_path) {

net_.reset(new ncnn::Net());
net_->opt.use_vulkan_compute = false;
net_->opt.num_threads = 1;
net_->opt.num_threads = num_threads_;

if (net_->load_param(param_path_.c_str()) != 0) {
throw std::runtime_error("failed to load NCNN param file: " + param_path_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ class NcnnBackend final : public Backend {
public:
NcnnBackend();
~NcnnBackend() override;
void set_num_threads(int threads) override;
void load(const std::string& model_path) override;
Tensor infer(const Tensor& input) override;
std::string name() const override;

private:
std::string model_path_;
int num_threads_ = 4;
#ifdef WITH_NCNN
std::string param_path_;
std::string bin_path_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,23 @@ OnnxBackend::OnnxBackend()
{
}

void OnnxBackend::set_num_threads(int threads) {
if (threads <= 0) {
throw std::invalid_argument("ONNX Runtime thread count must be positive");
}
num_threads_ = threads;
}

void OnnxBackend::load(const std::string& model_path) {
if (model_path.empty()) {
throw std::invalid_argument("ONNX model path is empty");
}
model_path_ = model_path;

#ifdef WITH_ONNXRUNTIME
session_options_.SetIntraOpNumThreads(1);
session_options_.SetIntraOpNumThreads(num_threads_);
session_options_.SetInterOpNumThreads(1);
session_options_.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL);
session_options_.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
session_.reset(new Ort::Session(env_, model_path.c_str(), session_options_));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
class OnnxBackend final : public Backend {
public:
OnnxBackend();
void set_num_threads(int threads) override;
void load(const std::string& model_path) override;
Tensor infer(const Tensor& input) override;
std::string name() const override;

private:
std::string model_path_;
int num_threads_ = 4;
#ifdef WITH_ONNXRUNTIME
Ort::Env env_;
Ort::SessionOptions session_options_;
Expand Down
9 changes: 8 additions & 1 deletion examples/YOLO-Master-Edge-Deployment/cpp/edge_benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct Args {
int warmup = 5;
int runs = 1;
int limit = 0;
int threads = 4;
};

struct TimingRow {
Expand All @@ -52,6 +53,7 @@ static void print_usage(const char* program) {
<< "[--warmup 5] "
<< "[--runs 1] "
<< "[--limit 500] "
<< "[--threads 4] "
<< "[--output benchmark.csv]\n";
}

Expand Down Expand Up @@ -100,6 +102,8 @@ static Args parse_args(int argc, char** argv) {
args.runs = std::stoi(value);
} else if (key == "--limit") {
args.limit = std::stoi(value);
} else if (key == "--threads") {
args.threads = std::stoi(value);
} else {
std::cerr << "Unknown argument: " << key << "\n";
print_usage(argv[0]);
Expand All @@ -120,7 +124,8 @@ static Args parse_args(int argc, char** argv) {
std::cerr << "Invalid --profile: " << args.profile << "\n";
std::exit(2);
}
if (args.imgsz <= 0 || args.warmup < 0 || args.runs <= 0 || args.limit < 0) {
if (args.imgsz <= 0 || args.warmup < 0 || args.runs <= 0 || args.limit < 0 ||
args.threads <= 0) {
std::cerr << "Invalid numeric argument\n";
std::exit(2);
}
Expand Down Expand Up @@ -251,6 +256,7 @@ int main(int argc, char** argv) {
const Args args = parse_args(argc, argv);
const auto images = collect_images(args.images, args.limit);
auto backend = create_backend(args.backend);
backend->set_num_threads(args.threads);
backend->load(args.model);

const Tensor warmup_input = preprocess_image(images.front(), args.imgsz, args.imgsz).input;
Expand Down Expand Up @@ -295,6 +301,7 @@ int main(int argc, char** argv) {
<< " model=" << args.model
<< " profile=" << args.profile
<< " imgsz=" << args.imgsz
<< " threads=" << args.threads
<< " conf=" << args.conf
<< " iou=" << args.iou
<< " output=" << args.output << "\n";
Expand Down
62 changes: 62 additions & 0 deletions examples/lora_examples/yolo_master_lora_peft_ema_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# YOLO-Master PEFT LoRA EMA 同步实验报告

本报告记录 `peft_ema_sync_rtx4060_v1` 协议。该协议用于验证 PEFT LoRA 的非张量 `scaling`
状态同步到 EMA 后,在 Brain Tumor 和 VisDrone 垂类场景中的 rank 扫描结果。

六组正式实验基于仓库提交 `a510883` 加本地 PEFT EMA 修复运行;实验完成后,修复提交才重放到
更新后的 `upstream/main`。因此结果应以本报告列出的完整协议为准,不能套用后续默认配置解释。

## 问题与修复

启用 `lora_alpha_warmup` 后,在线模型的 LoRA `scaling` 会随 epoch 增长,但 PEFT 0.19.1 将该状态
保存在普通 Python 字典中,而不是 `state_dict` 张量。标准 EMA 更新因此不会复制它,导致在线模型使用
LoRA、EMA 验证模型却保持零缩放。典型现象是训练 loss 下降,但 mAP 持续下降或归零。

修复在以下生命周期边界同步在线模型与 EMA 的 LoRA `scaling`:

- 每个 epoch 更新 alpha warmup 后;
- 验证前;
- checkpoint 序列化前;
- 断点续训恢复后。

## 实验环境与协议

- GPU:NVIDIA GeForce RTX 4060 Laptop GPU(8188 MiB)
- Python:3.11.15
- PyTorch:2.13.0+cu126
- PEFT:0.19.1
- 模型:YOLO-Master-EsMoE-N 预训练权重
- Rank:`r=4,8,16`,保持 `lora_alpha=2*r`
- Backend:配置为 `auto`,实际解析为 `peft`
- AMP:关闭,避免把数值稳定性问题混入 EMA 修复验证
- Router/gating:不纳入 LoRA 目标模块

Brain Tumor 使用全部训练集、`imgsz=640`、`batch=8`、最多 40 epochs、`patience=15`、
`lora_alpha_warmup=3`。VisDrone 使用 20% 训练集、完整验证集、`imgsz=640`、`batch=2`、
30 epochs、`lora_alpha_warmup=5` 和多尺度训练。

## Rank 扫描结果

| 数据集 | Rank | 完成轮数 | 最佳轮次 | mAP50 | mAP50-95 | 可训练参数 | Adapter 参数 | 时间(分钟) | 日志峰值显存 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Brain Tumor | 4 | 17 | 2 | 0.40754 | 0.26097 | 409,174 | 64,000 | 10.38 | 3.79 GB |
| Brain Tumor | 8 | 40 | 33 | 0.47810 | **0.34647** | 473,174 | 128,000 | 25.38 | 3.83 GB |
| Brain Tumor | 16 | 17 | 2 | 0.47357 | 0.31845 | 601,174 | 256,000 | 10.62 | 3.84 GB |
| VisDrone | 4 | 30 | 20 | 0.09152 | 0.04601 | 410,734 | 64,000 | 73.14 | 8.68 GB |
| VisDrone | 8 | 30 | 20 | 0.09799 | 0.04926 | 474,734 | 128,000 | 69.06 | 8.69 GB |
| VisDrone | 16 | 30 | 28 | 0.11454 | **0.05797** | 602,734 | 256,000 | 76.62 | 8.72 GB |

峰值显存来自训练日志的 `GPU_mem` 最大值;不同 CUDA/PyTorch 版本的内存统计口径可能不同。
完整机器可读结果见 `yolo_master_lora_peft_ema_results.csv`。

## 结论

- Brain Tumor 推荐 `r=8`:mAP50-95 最高,且比 `r=16` 少 128,000 个 Adapter 参数。
- VisDrone 推荐 `r=16`:密集小目标场景从更大的 LoRA 容量中获得了持续收益。
- 两个场景不存在统一最佳 rank,rank 应根据领域复杂度分别选择。
- `best.pt` 重新验证结果与训练记录一致,修复后未再出现 LoRA EMA 缩放为零导致的指标崩溃。

## 可比性限制

本协议不能与仓库中的历史协议直接合并。历史结果可能使用 fallback 后端、AMP、不同 batch、
不同图像尺寸或不同数据比例。跨协议数值只能作为背景参考,rank 结论应在同一协议内部比较。
7 changes: 7 additions & 0 deletions examples/lora_examples/yolo_master_lora_peft_ema_results.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
protocol_id,dataset,rank,alpha,max_epochs,completed_epochs,fraction,amp,batch,imgsz,effective_backend,alpha_warmup,best_epoch,precision,recall,mAP50,mAP50_95,trainable_params,adapter_params,train_time_min,peak_gpu_memory_gb,status
peft_ema_sync_rtx4060_v1,brain_tumor,4,8,40,17,1.0,False,8,640,peft,3,2,0.43386,0.57434,0.40754,0.26097,409174,64000,10.38,3.79,completed_early_stop
peft_ema_sync_rtx4060_v1,brain_tumor,8,16,40,40,1.0,False,8,640,peft,3,33,0.45512,0.77818,0.47810,0.34647,473174,128000,25.38,3.83,completed
peft_ema_sync_rtx4060_v1,brain_tumor,16,32,40,17,1.0,False,8,640,peft,3,2,0.43767,0.75353,0.47357,0.31845,601174,256000,10.62,3.84,completed_early_stop
peft_ema_sync_rtx4060_v1,visdrone,4,8,30,30,0.2,False,2,640,peft,5,20,0.27431,0.14468,0.09152,0.04601,410734,64000,73.14,8.68,completed
peft_ema_sync_rtx4060_v1,visdrone,8,16,30,30,0.2,False,2,640,peft,5,20,0.24676,0.14905,0.09799,0.04926,474734,128000,69.06,8.69,completed
peft_ema_sync_rtx4060_v1,visdrone,16,32,30,30,0.2,False,2,640,peft,5,28,0.30409,0.15179,0.11454,0.05797,602734,256000,76.62,8.72,completed
Loading
Loading