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。