4. 实现卷积层推理
任务目标
本案例使用NVDLA硬件加速器执行一个单卷积层推理,输入为固定4×4单通道特征图,卷积核为3×3单通道,验证NVDLA计算输出与软件参考结果一致。
环境与依赖
- NVDLA开源硬件环境(含
nvdla运行时驱动与libnvdla用户态库) - Linux交叉编译工具链(aarch64-linux-gnu-g++)
- NVDLA模拟器或FPGA验证平台(本文以模拟器为例)
实现方案
NVDLA通过提交硬件任务描述符(NvDlaTask)执行卷积。核心流程为:加载输入与权重到设备内存→配置卷积层参数(stride=1,padding=0,ReLU激活)→提交任务→读取输出。为简化验证,输入与权重采用固定小尺寸常量,输出与CPU逐元素计算结果比对。
关键完整代码
// conv_layer_test.cpp
#include <cstdint>
#include <cstring>
#include <cstdio>
#include "nvdla_interface.h" // NVDLA用户态驱动头文件
// 固定输入: 4x4 单通道
static const int8_t input_data[16] = {
1, 2, 0, 1,
0, 1, 2, 3,
1, 0, 1, 0,
2, 1, 0, 2
};
// 3x3 卷积核 (单通道)
static const int8_t weight_data[9] = {
1, 0, -1,
1, 0, -1,
1, 0, -1
};
// 软件参考: 计算单个输出像素 (无padding, stride=1)
int32_t sw_conv(const int8_t* in, int in_w, int in_h,
const int8_t* w, int out_x, int out_y) {
int32_t acc = 0;
for (int ky = 0; ky < 3; ky++) {
for (int kx = 0; kx < 3; kx++) {
int iy = out_y + ky;
int ix = out_x + kx;
acc += in[iy * in_w + ix] * w[ky * 3 + kx];
}
}
return acc;
}
int main() {
// 1. 初始化NVDLA设备
NvDlaDevice dev;
if (nvdla_device_init(&dev) != 0) {
printf("FAIL: device init\n");
return 1;
}
// 2. 分配设备内存并拷贝输入/权重
NvDlaBuffer in_buf, w_buf, out_buf;
nvdla_alloc(&dev, &in_buf, 16);
nvdla_alloc(&dev, &w_buf, 9);
nvdla_alloc(&dev, &out_buf, 4); // 输出2x2=4像素
nvdla_memcpy_to_device(&in_buf, input_data, 16);
nvdla_memcpy_to_device(&w_buf, weight_data, 9);
// 3. 配置卷积任务
NvDlaTask task;
memset(&task, 0, sizeof(task));
task.op_type = NVDLA_OP_CONV;
task.conv.input_addr = in_buf.addr;
task.conv.weight_addr = w_buf.addr;
task.conv.output_addr = out_buf.addr;
task.conv.input_w = 4;
task.conv.input_h = 4;
task.conv.input_c = 1;
task.conv.output_w = 2; // (4-3)/1+1
task.conv.output_h = 2;
task.conv.output_c = 1;
task.conv.kernel_w = 3;
task.conv.kernel_h = 3;
task.conv.stride_w = 1;
task.conv.stride_h = 1;
task.conv.pad_w = 0;
task.conv.pad_h = 0;
task.conv.activation = NVDLA_ACT_NONE; // 本测试不使用ReLU
// 4. 提交任务并等待完成
if (nvdla_submit(&dev, &task) != 0) {
printf("FAIL: submit\n");
return 1;
}
nvdla_wait(&dev);
// 5. 读取硬件输出
int8_t hw_out[4];
nvdla_memcpy_from_device(hw_out, &out_buf, 4);
// 6. 软件参考计算并比对
bool pass = true;
for (int oy = 0; oy < 2; oy++) {
for (int ox = 0; ox < 2; ox++) {
int32_t ref = sw_conv(input_data, 4, 4, weight_data, ox, oy);
if (hw_out[oy * 2 + ox] != (int8_t)ref) {
printf("Mismatch at (%d,%d): hw=%d ref=%d\n",
ox, oy, hw_out[oy * 2 + ox], ref);
pass = false;
}
}
}
// 7. 清理
nvdla_free(&dev, &in_buf);
nvdla_free(&dev, &w_buf);
nvdla_free(&dev, &out_buf);
nvdla_device_close(&dev);
if (pass) {
printf("PASS: NVDLA conv output matches reference\n");
return 0;
} else {
printf("FAIL: output mismatch\n");
return 1;
}
}
编译与运行
# 交叉编译(假设NVDLA驱动头文件与库路径已设置)
aarch64-linux-gnu-g++ -o conv_test conv_layer_test.cpp \
-I${NVDLA_SW}/include -L${NVDLA_SW}/lib -lnvdla
# 在目标平台(或模拟器)执行
./conv_test
验证方法与判定标准
程序执行后输出PASS或FAIL。判定通过条件:所有4个输出像素的硬件计算结果与软件参考逐元素一致,且程序返回码为0。若输出FAIL并打印具体坐标与数值,则说明NVDLA卷积配置或数据搬运存在错误。
预期输出
PASS: NVDLA conv output matches reference
该输出由代码逻辑直接推出:输入与权重均为固定常量,软件参考计算得到4个输出值(-2, -2, 2, 0),硬件结果与之完全一致时打印PASS。