Files
ncnn/tests/test_deconvolution.cpp
Stern bbaca40c35 x86: reduce Col2im memory access for non-overlapping deconvolution (#7015)
* Reduce memory access in x86 deconvolution Col2im

The SGEMM path fills the output with bias, then reads and updates each
output value. When kernel tiles do not overlap, each value has one source.
Write the source plus bias once, and fill only the output padding.

Use this path for rectangular kernels with unit dilation and stride equal
to the kernel size. Support packs of 1, 4, and 8. Split input rows between
workers when there are fewer output channel groups than threads. Each
worker writes separate output rows. Keep the existing path for other
cases. Add 32 test cases to test_deconvolution.

Test Plan
---------
These commands passed in the local benchmark workspace:

```bash
ctest --test-dir .tools/ncnn-col2im-build -R '^test_deconvolution$' --output-on-failure
.venv-esp/bin/python results/oss/col2im_transfer_20260920/generalization/test_ncnn.py --parallel
.venv-esp/bin/python results/oss/col2im_transfer_20260920/generalization/sanitize_ncnn.py
```

The complete forward test gave equal output bits for 236 cases and
1,433,578 values. The extracted loops passed ASan and UBSan for 300 cases.
The sanitizer test uses a small Mat substitute, not a full sanitizer build.
The local probe scripts are outside this repository.

The final format check and git diff --check passed. The format changes do
not change the tested operations.

This change was prepared with help from an AI assistant.
2026-10-08 19:44:27 +08:00

609 lines
26 KiB
C++

// Copyright 2019 Tencent
// SPDX-License-Identifier: BSD-3-Clause
#include "testutil.h"
#include "layer_type.h"
#include <limits.h>
static int test_deconvolution(int w, int h, int c, int outch, int kernel, int dilation, int stride, int pad, int bias, int output_pad_right, int output_pad_bottom, int output_w, int output_h, int activation_type = 0)
{
ncnn::Mat a = RandomMat(w, h, c);
if (output_w > 0 && output_h > 0 && pad != -233 && pad != -234)
{
pad = -233;
}
ncnn::ParamDict pd;
pd.set(0, outch);
pd.set(1, kernel);
pd.set(2, dilation);
pd.set(3, stride);
pd.set(4, pad);
pd.set(5, bias);
pd.set(6, outch * c * kernel * kernel);
ncnn::Mat activation_params(2);
activation_params[0] = activation_type == 2 ? 0.1f : -0.5f; // alpha
activation_params[1] = 0.25f; // beta
pd.set(9, activation_type);
pd.set(10, activation_params);
pd.set(18, output_pad_right);
pd.set(19, output_pad_bottom);
pd.set(20, output_w);
pd.set(21, output_h);
std::vector<ncnn::Mat> weights(2);
weights[0] = RandomMat(outch * c * kernel * kernel);
weights[1] = RandomMat(outch);
int ret = test_layer("Deconvolution", pd, weights, a, 0.001);
if (ret != 0)
{
fprintf(stderr, "test_deconvolution failed w=%d h=%d c=%d outch=%d kernel=%d dilation=%d stride=%d pad=%d bias=%d act=%d actparams=[%f,%f] output_pad_right=%d output_pad_bottom=%d output_w=%d output_h=%d\n", w, h, c, outch, kernel, dilation, stride, pad, bias, activation_type, activation_params[0], activation_params[1], output_pad_right, output_pad_bottom, output_w, output_h);
return ret;
}
{
ncnn::Option opt;
opt.num_threads = 1;
opt.use_packing_layout = true;
opt.use_fp16_packed = false;
opt.use_fp16_storage = false;
opt.use_fp16_arithmetic = false;
opt.use_bf16_packed = false;
opt.use_bf16_storage = false;
opt.use_sgemm_convolution = false;
opt.use_winograd_convolution = false;
ret = test_layer_opt("Deconvolution", pd, weights, opt, a, 0.001);
if (ret != 0)
{
fprintf(stderr, "test_deconvolution failed w=%d h=%d c=%d outch=%d kernel=%d dilation=%d stride=%d pad=%d bias=%d act=%d actparams=[%f,%f] output_pad_right=%d output_pad_bottom=%d output_w=%d output_h=%d\n", w, h, c, outch, kernel, dilation, stride, pad, bias, activation_type, activation_params[0], activation_params[1], output_pad_right, output_pad_bottom, output_w, output_h);
return ret;
}
}
{
ncnn::Option opt;
opt.num_threads = 1;
opt.use_packing_layout = true;
opt.use_fp16_packed = true;
opt.use_fp16_storage = true;
opt.use_fp16_arithmetic = true;
opt.use_bf16_packed = false;
opt.use_bf16_storage = false;
opt.use_sgemm_convolution = false;
opt.use_winograd_convolution = false;
ret = test_layer_opt("Deconvolution", pd, weights, opt, a, 0.001);
if (ret != 0)
{
fprintf(stderr, "test_deconvolution failed w=%d h=%d c=%d outch=%d kernel=%d dilation=%d stride=%d pad=%d bias=%d act=%d actparams=[%f,%f] output_pad_right=%d output_pad_bottom=%d output_w=%d output_h=%d\n", w, h, c, outch, kernel, dilation, stride, pad, bias, activation_type, activation_params[0], activation_params[1], output_pad_right, output_pad_bottom, output_w, output_h);
return ret;
}
}
return ret;
}
static int test_deconvolution_0()
{
static const int kdsp[][4] = {
{1, 1, 1, 0}, {1, 1, 2, 0}, {2, 1, 1, 1}, {2, 1, 2, -233}, {3, 1, 1, 1}, {3, 1, 2, 1}, {3, 2, 1, 1}, {4, 1, 1, -233}, {4, 1, 2, -234}, {4, 2, 1, -234}, {5, 1, 1, 2}, {5, 1, 2, 2}, {5, 2, 2, 2}, {7, 1, 1, 3}, {7, 1, 2, 3}, {7, 2, 1, -233}
};
// scalar geometry covers every kernel, stride, dilation and padding boundary
for (size_t i = 0; i < sizeof(kdsp) / sizeof(kdsp[0]); i++)
{
const int* k = kdsp[i];
if (test_deconvolution(9, 7, 1, 1, k[0], k[1], k[2], k[3], 1, 0, 0, 0, 0))
return -1;
}
// large pack16 cases retain specialized kernels and the largest dilation
static const int large_kdsp[][4] = {
{1, 1, 1, 0}, {2, 1, 1, 1}, {3, 1, 1, 1}, {3, 1, 2, 1}, {4, 1, 1, -233}, {4, 1, 2, -234}, {7, 2, 1, -233}
};
for (size_t i = 0; i < sizeof(large_kdsp) / sizeof(large_kdsp[0]); i++)
{
const int* k = large_kdsp[i];
if (test_deconvolution(9, 7, 16, 16, k[0], k[1], k[2], k[3], 0, 0, 2, 7, 5))
return -1;
}
// pack4 geometry keeps generic, strided and dilated representatives
static const int packed_kdsp[][4] = {
{1, 1, 1, 0}, {3, 1, 1, 1}, {3, 1, 2, 1}, {7, 2, 1, -233}
};
for (size_t i = 0; i < sizeof(packed_kdsp) / sizeof(packed_kdsp[0]); i++)
{
const int* k = packed_kdsp[i];
if (test_deconvolution(7, 7, 12, 12, k[0], k[1], k[2], k[3], 1, 0, 1, 0, 0))
return -1;
}
// scalar tails retain each optimized 3x3 and 4x4 kernel in both directions
static const int specialized[][4] = {{3, 1, 1, 1}, {3, 1, 2, 1}, {4, 1, 1, -233}, {4, 1, 2, -234}};
// input channels, output channels, bias, output padding and explicit output size
static const int scalar_tails[][7] = {
{4, 13, 0, 1, 1, 7, 5}, {13, 4, 1, 1, 0, 0, 0}
};
for (size_t i = 0; i < sizeof(specialized) / sizeof(specialized[0]); i++)
{
for (size_t j = 0; j < sizeof(scalar_tails) / sizeof(scalar_tails[0]); j++)
{
const int* k = specialized[i];
const int* a = scalar_tails[j];
if (test_deconvolution(9, 7, a[0], a[1], k[0], k[1], k[2], k[3], a[2], a[3], a[4], a[5], a[6]) != 0)
return -1;
}
}
// mixed pack4 and pack8 directions use one generic kernel representative
static const int packing[][7] = {
{4, 8, 0, 0, 1, 0, 0}, {8, 4, 1, 0, 0, 7, 5}, {8, 13, 0, 2, 2, 0, 0}, {13, 8, 1, 2, 0, 0, 0}
};
for (size_t i = 0; i < sizeof(packing) / sizeof(packing[0]); i++)
{
const int* a = packing[i];
if (test_deconvolution(9, 7, a[0], a[1], 3, 1, 1, 1, a[2], a[3], a[4], a[5], a[6]) != 0)
return -1;
}
// generic packing keeps 1x1 and the largest dilated kernel
static const int generic[][4] = {{1, 1, 1, 0}, {7, 2, 1, -233}};
for (size_t i = 0; i < sizeof(generic) / sizeof(generic[0]); i++)
{
const int* k = generic[i];
if (test_deconvolution(9, 7, 4, 13, k[0], k[1], k[2], k[3], 0, 1, 1, 7, 5)
|| test_deconvolution(9, 7, 13, 8, k[0], k[1], k[2], k[3], 1, 2, 0, 0, 0))
return -1;
}
// every activation is tested directly for scalar and packed output
for (int activation = 0; activation < 5; activation++)
{
if (test_deconvolution(5, 4, 7, 7, 3, 1, 1, 1, 1, 0, 0, 0, 0, activation)
|| test_deconvolution(5, 4, 8, 16, 3, 1, 1, 1, 0, 0, 0, 0, 0, activation))
return -1;
}
// explicit width-only and height-only requests cover independent crop boundaries
if (test_deconvolution(4, 5, 12, 11, 3, 1, 1, 0, 0, 0, 1, 1, 0)
|| test_deconvolution(4, 5, 12, 11, 3, 1, 1, 0, 0, 0, 1, 0, 1))
return -1;
// tier coverage for small outch and various elempack
return 0
|| test_deconvolution(5, 4, 7, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution(6, 7, 2, 3, 2, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(5, 3, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution(8, 6, 8, 2, 3, 2, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(8, 4, 8, 15, 1, 1, 2, 0, 0, 0, 1, 0, 0)
// kernel transform num_input < 4 paths
|| test_deconvolution(4, 5, 1, 8, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 2, 7, 1, 1, 1, 0, 1, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 2, 8, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 5, 2, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 1, 5, 1, 1, 1, 0, 0, 0, 0, 0, 0)
// elempack==1 large inch for inner tier cascading
|| test_deconvolution(3, 3, 14, 2, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(3, 3, 14, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0)
// avx512 tier coverage
|| test_deconvolution(5, 4, 1, 16, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 2, 16, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 8, 16, 3, 1, 2, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(4, 3, 5, 16, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 24, 8, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 20, 8, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 5, 17, 8, 1, 1, 1, 0, 1, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 7, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 3, 24, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 20, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 3, 17, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 3, 24, 2, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 17, 2, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 20, 2, 3, 1, 2, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 3, 7, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 16, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 3, 24, 1, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(5, 4, 20, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution(4, 3, 17, 1, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution(7, 5, 24, 32, 4, 2, 2, 2, 1, 0, 0, 0, 0)
|| test_deconvolution(7, 5, 32, 24, 4, 2, 2, 2, 1, 0, 0, 0, 0)
|| test_deconvolution(7, 5, 28, 32, 4, 2, 2, 2, 1, 0, 0, 0, 0)
|| test_deconvolution(7, 5, 32, 28, 4, 2, 2, 2, 1, 0, 0, 0, 0)
|| test_deconvolution(7, 5, 26, 32, 4, 2, 2, 2, 1, 0, 0, 0, 0)
|| test_deconvolution(7, 5, 32, 26, 4, 2, 2, 2, 1, 0, 0, 0, 0);
}
static int test_deconvolution_dynamic(int w, int h, int c, int outch, int kernel, int dilation, int stride, int pad, int bias, int output_pad_right, int output_pad_bottom, int output_w, int output_h)
{
ncnn::Mat a = RandomMat(w, h, c);
if (output_w > 0 && output_h > 0 && pad != -233 && pad != -234)
{
pad = -233;
}
ncnn::ParamDict pd;
pd.set(0, 0);
pd.set(1, 0);
pd.set(2, dilation);
pd.set(3, stride);
pd.set(4, pad);
pd.set(5, bias);
pd.set(6, 0);
pd.set(28, 1); // dynamic weight
int activation_type = RAND() % 7; // 0 1 2 3 4 5 6
ncnn::Mat activation_params(2);
activation_params[0] = (activation_type == 6) ? RandomFloat(0, 1) : RandomFloat(-1, 0); // alpha
activation_params[1] = RandomFloat(0, 1); // beta
pd.set(9, activation_type);
pd.set(10, activation_params);
pd.set(18, output_pad_right);
pd.set(19, output_pad_bottom);
pd.set(20, output_w);
pd.set(21, output_h);
std::vector<ncnn::Mat> as(bias ? 3 : 2);
as[0] = a;
as[1] = RandomMat(kernel, kernel, outch, c);
if (bias)
as[2] = RandomMat(outch);
std::vector<ncnn::Mat> weights(0);
int ret = test_layer("Deconvolution", pd, weights, as);
if (ret != 0)
{
fprintf(stderr, "test_deconvolution_dynamic failed w=%d h=%d c=%d outch=%d kernel=%d dilation=%d stride=%d pad=%d bias=%d act=%d actparams=[%f,%f] output_pad_right=%d output_pad_bottom=%d output_w=%d output_h=%d\n", w, h, c, outch, kernel, dilation, stride, pad, bias, activation_type, activation_params[0], activation_params[1], output_pad_right, output_pad_bottom, output_w, output_h);
}
return ret;
}
static int test_deconvolution_1()
{
static const int kdsp[7][4] = {
{1, 1, 1, 0},
{1, 1, 2, 0},
{2, 1, 1, 1},
{2, 1, 2, -233},
{3, 1, 1, 1},
{3, 1, 2, 1},
{3, 2, 1, -234},
};
for (int i = 0; i < 7; i++)
{
const int k = kdsp[i][0];
const int d = kdsp[i][1];
const int s = kdsp[i][2];
const int p = kdsp[i][3];
int ret = 0
|| test_deconvolution_dynamic(9, 7, 1, 1, k, d, s, p, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(9, 7, 4, 13, k, d, s, p, 0, 1, 1, 7, 5)
|| test_deconvolution_dynamic(9, 7, 13, 4, k, d, s, p, 1, 1, 0, 0, 0)
|| test_deconvolution_dynamic(9, 7, 4, 8, k, d, s, p, 0, 0, 1, 0, 0)
|| test_deconvolution_dynamic(9, 7, 8, 4, k, d, s, p, 1, 0, 0, 7, 5)
|| test_deconvolution_dynamic(7, 7, 12, 12, k, d, s, p, 1, 0, 1, 0, 0)
|| test_deconvolution_dynamic(4, 5, 12, 11, k, d, s, p, 0, 0, 1, 1, 0)
|| test_deconvolution_dynamic(9, 7, 8, 13, k, d, s, p, 0, 2, 2, 0, 0)
|| test_deconvolution_dynamic(9, 7, 13, 8, k, d, s, p, 1, 2, 0, 0, 0)
|| test_deconvolution_dynamic(9, 7, 16, 16, k, d, s, p, 0, 0, 2, 7, 5);
if (ret != 0)
return -1;
}
// tier coverage for small outch and various elempack
return 0
|| test_deconvolution_dynamic(5, 4, 7, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(6, 7, 2, 3, 2, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 3, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(8, 6, 8, 2, 3, 2, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(8, 4, 8, 15, 1, 1, 2, 0, 0, 0, 1, 0, 0)
// kernel transform num_input < 4 paths
|| test_deconvolution_dynamic(4, 5, 1, 8, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 2, 7, 1, 1, 1, 0, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 2, 8, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 5, 2, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 1, 5, 1, 1, 1, 0, 0, 0, 0, 0, 0)
// elempack==1 large inch for inner tier cascading
|| test_deconvolution_dynamic(3, 3, 14, 2, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(3, 3, 14, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0)
// avx512 tier coverage
|| test_deconvolution_dynamic(5, 4, 1, 16, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 2, 16, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 8, 16, 3, 1, 2, 1, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 3, 5, 16, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 24, 8, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 20, 8, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 5, 17, 8, 1, 1, 1, 0, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 7, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 3, 24, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 20, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 3, 17, 5, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 3, 24, 2, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 17, 2, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 20, 2, 3, 1, 2, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 3, 7, 1, 3, 1, 1, 1, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 16, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 3, 24, 1, 3, 1, 1, 1, 1, 0, 0, 0, 0)
|| test_deconvolution_dynamic(5, 4, 20, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0)
|| test_deconvolution_dynamic(4, 3, 17, 1, 3, 1, 1, 1, 1, 0, 0, 0, 0);
}
static int test_deconvolution_activation_params_text()
{
#if NCNN_STRING
const char* params[] = {"0=1 1=1 6=1 9=3 -23310=2,-1,2", "0=1 1=1 6=1 9=3 -23310=2,-1.0,2.0"};
for (int i = 0; i < 2; i++)
{
TestParamDict pd;
if (pd.load_param(params[i]) != 0 || pd.type(10) != 5 + i)
return -1;
std::vector<ncnn::Mat> weights(1);
weights[0].create(1);
weights[0][0] = 1.f;
ncnn::Mat a(2, 1, 1);
a[0] = -3.f;
a[1] = 3.f;
ncnn::Mat reference(2, 1, 1);
reference[0] = -1.f;
reference[1] = 2.f;
ncnn::Mat b;
int ret = test_layer_naive(ncnn::LayerType::Deconvolution, pd, weights, a, b, 0);
if (ret == 0)
ret = CompareMat(reference, b, 0.f);
if (ret != 0)
{
fprintf(stderr, "test_deconvolution_activation_params_text failed params=%s ret=%d\n", params[i], ret);
return ret;
}
const ncnn::Mat original = pd.get(10, ncnn::Mat());
const int* p = original;
if (i == 0 ? (p[0] != -1 || p[1] != 2) : (original[0] != -1.f || original[1] != 2.f))
{
fprintf(stderr, "test_deconvolution_activation_params_text modified params=%s\n", params[i]);
return -1;
}
}
#endif
return 0;
}
#if NCNN_VALIDATION
static int test_deconvolution_load_param_activation(const ncnn::ParamDict& base, int activation_type, const ncnn::Mat& activation_params, int expected_ret)
{
ncnn::ParamDict pd = base;
pd.set(9, activation_type);
pd.set(10, activation_params);
return test_layer_param(ncnn::LayerType::Deconvolution, pd, expected_ret);
}
static int test_deconvolution_load_param()
{
ncnn::ParamDict base;
base.set(0, 8);
base.set(1, 3);
base.set(6, 216);
if (test_layer_param(ncnn::LayerType::Deconvolution, base, 0) != 0)
return -1;
ncnn::Mat params(2);
params[0] = 0.1f;
params[1] = 0.5f;
int ret = 0
|| test_deconvolution_load_param_activation(base, 0, ncnn::Mat(), 0)
|| test_deconvolution_load_param_activation(base, 0, ncnn::Mat(0), 0)
|| test_deconvolution_load_param_activation(base, 0, params, 0)
|| test_deconvolution_load_param_activation(base, 0, params.range(0, 1), 0)
|| test_deconvolution_load_param_activation(base, 1, ncnn::Mat(), 0)
|| test_deconvolution_load_param_activation(base, 1, ncnn::Mat(0), 0)
|| test_deconvolution_load_param_activation(base, 1, params, 0)
|| test_deconvolution_load_param_activation(base, 1, params.range(0, 1), 0)
|| test_deconvolution_load_param_activation(base, 2, ncnn::Mat(), -1)
|| test_deconvolution_load_param_activation(base, 2, ncnn::Mat(0), -1)
|| test_deconvolution_load_param_activation(base, 2, params, 0)
|| test_deconvolution_load_param_activation(base, 2, params.range(0, 1), 0)
|| test_deconvolution_load_param_activation(base, 3, ncnn::Mat(), -1)
|| test_deconvolution_load_param_activation(base, 3, ncnn::Mat(0), -1)
|| test_deconvolution_load_param_activation(base, 3, params, 0)
|| test_deconvolution_load_param_activation(base, 3, params.range(0, 1), -1)
|| test_deconvolution_load_param_activation(base, 4, ncnn::Mat(), 0)
|| test_deconvolution_load_param_activation(base, 4, ncnn::Mat(0), 0)
|| test_deconvolution_load_param_activation(base, 4, params, 0)
|| test_deconvolution_load_param_activation(base, 4, params.range(0, 1), 0)
|| test_deconvolution_load_param_activation(base, 5, ncnn::Mat(), 0)
|| test_deconvolution_load_param_activation(base, 5, ncnn::Mat(0), 0)
|| test_deconvolution_load_param_activation(base, 5, params, 0)
|| test_deconvolution_load_param_activation(base, 5, params.range(0, 1), 0)
|| test_deconvolution_load_param_activation(base, 6, ncnn::Mat(), -1)
|| test_deconvolution_load_param_activation(base, 6, ncnn::Mat(0), -1)
|| test_deconvolution_load_param_activation(base, 6, params, 0)
|| test_deconvolution_load_param_activation(base, 6, params.range(0, 1), -1)
|| test_deconvolution_load_param_activation(base, 7, ncnn::Mat(), -1);
if (ret != 0)
return ret;
if (test_layer_param(ncnn::LayerType::Deconvolution, base, 10, 1, -1)
|| test_layer_param(ncnn::LayerType::Deconvolution, base, 10, 1.f, -1))
return -1;
ncnn::Mat missing_data(0);
missing_data.w = 1;
ncnn::Mat negative_length(0);
negative_length.w = -1;
const ncnn::Mat bad[] = {ncnn::Mat(2, (size_t)1u), ncnn::Mat(2, (size_t)2u), ncnn::Mat(2, 2), ncnn::Mat(2, (size_t)16u, 4), missing_data, negative_length};
for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++)
{
if (test_layer_param(ncnn::LayerType::Deconvolution, base, 10, bad[i], -1) != 0)
return -1;
}
ret = 0
|| test_layer_param(ncnn::LayerType::Deconvolution, base, 0, 0, -1)
|| test_layer_param(ncnn::LayerType::Deconvolution, base, 1, 0, -1)
|| test_layer_param(ncnn::LayerType::Deconvolution, base, 2, 0, -1)
|| test_layer_param(ncnn::LayerType::Deconvolution, base, 3, 0, -1)
|| test_layer_param(ncnn::LayerType::Deconvolution, base, 1, INT_MAX, -1)
|| test_layer_param(ncnn::LayerType::Deconvolution, base, 6, 217, -1);
if (ret != 0)
return ret;
return 0;
}
static int test_deconvolution_load_param_text()
{
#if NCNN_STRING
const char* params[] = {"0=1 1=1 6=1 9=3 -23310=2,-1,2", "0=1 1=1 6=1 9=3 -23310=2,-1.0,2.0"};
for (int i = 0; i < 2; i++)
{
TestParamDict pd;
if (pd.load_param(params[i]) != 0 || pd.type(10) != 5 + i)
return -1;
if (test_layer_param(ncnn::LayerType::Deconvolution, pd, 0) != 0)
return -1;
}
#endif
return 0;
}
#endif // NCNN_VALIDATION
static int test_deconvolution_nonoverlap_case(int kw, int kh, int outch, int h, int bias, int output_pad_right, int output_pad_bottom, int pad = 0, int activation_type = 0)
{
ncnn::Mat a = RandomMat(7, h, 8);
ncnn::ParamDict pd;
pd.set(0, outch);
pd.set(1, kw);
pd.set(11, kh);
pd.set(3, kw);
pd.set(13, kh);
pd.set(4, pad);
pd.set(5, bias);
pd.set(6, outch * 8 * kw * kh);
pd.set(18, output_pad_right);
pd.set(19, output_pad_bottom);
pd.set(9, activation_type);
ncnn::Mat activation_params(2);
activation_params[0] = activation_type == 2 ? 0.1f : -0.5f;
activation_params[1] = 0.25f;
pd.set(10, activation_params);
std::vector<ncnn::Mat> weights(2);
weights[0] = RandomMat(outch * 8 * kw * kh);
weights[1] = RandomMat(outch);
for (int threaded = 0; threaded < 2; threaded++)
{
const int flag = threaded ? TEST_LAYER_ENABLE_THREADING : 0;
for (int packed = 0; packed < 2; packed++)
{
ncnn::Option opt;
opt.num_threads = 1;
opt.use_packing_layout = packed;
opt.use_sgemm_convolution = true;
opt.use_fp16_packed = false;
opt.use_fp16_storage = false;
opt.use_fp16_arithmetic = false;
opt.use_bf16_packed = false;
opt.use_bf16_storage = false;
if (test_layer_opt("Deconvolution", pd, weights, opt, a, 0.001f, flag) != 0)
{
fprintf(stderr, "test_deconvolution_nonoverlap failed kw=%d kh=%d outch=%d h=%d bias=%d output_pad_right=%d output_pad_bottom=%d pad=%d activation=%d threaded=%d packed=%d\n", kw, kh, outch, h, bias, output_pad_right, output_pad_bottom, pad, activation_type, threaded, packed);
return -1;
}
}
}
return 0;
}
static int test_deconvolution_nonoverlap()
{
const int shapes[][4] = {
{1, 1, 1, 1},
{2, 2, 8, 3},
{2, 3, 4, 1},
{3, 2, 8, 3},
{3, 3, 16, 5},
{4, 4, 16, 7},
{1, 5, 3, 9},
{5, 1, 4, 3}
};
for (size_t i = 0; i < sizeof(shapes) / sizeof(shapes[0]); i++)
{
const int* s = shapes[i];
if (test_deconvolution_nonoverlap_case(s[0], s[1], s[2], s[3], i % 2, i % 2, (i / 2) % 2) != 0)
return -1;
}
// one output group and odd height exercise row splitting with two or more threads
// retain right and bottom padding, then combine crop with activation
return 0
|| test_deconvolution_nonoverlap_case(2, 3, 1, 5, 0, 1, 1)
|| test_deconvolution_nonoverlap_case(2, 3, 4, 5, 1, 1, 1)
|| test_deconvolution_nonoverlap_case(2, 3, 8, 5, 0, 1, 1)
|| test_deconvolution_nonoverlap_case(3, 2, 1, 5, 1, 1, 1, 1, 1)
|| test_deconvolution_nonoverlap_case(3, 2, 4, 5, 0, 1, 1, 1, 3)
|| test_deconvolution_nonoverlap_case(3, 2, 8, 5, 1, 1, 1, 1, 2);
}
static int test_deconvolution_activation_boundaries()
{
// generic kernels exercise activation fusion across input and output packing
for (int activation = 0; activation < 5; activation++)
{
if (test_deconvolution(3, 3, 3, 3, 2, 1, 1, 0, 1, 0, 0, 0, 0, activation)
|| test_deconvolution(3, 3, 4, 3, 2, 1, 1, 0, 1, 0, 0, 0, 0, activation)
|| test_deconvolution(3, 3, 3, 8, 2, 1, 1, 0, 1, 0, 0, 0, 0, activation)
|| test_deconvolution(3, 3, 8, 3, 2, 1, 1, 0, 1, 0, 0, 0, 0, activation))
return -1;
}
// specialized scalar kernels apply activation after the deconvolution
return 0
|| test_deconvolution(3, 3, 1, 1, 3, 1, 2, 0, 0, 0, 0, 0, 0, 1)
|| test_deconvolution(3, 3, 1, 1, 4, 1, 1, 0, 0, 0, 0, 0, 0, 1)
|| test_deconvolution(3, 3, 1, 1, 4, 1, 2, 0, 0, 0, 0, 0, 0, 1);
}
static int test_deconvolution_stride_packing_boundary()
{
// scalar input remainder covers both stride phases in packed output
return test_deconvolution(9, 7, 13, 8, 3, 1, 2, 1, 1, 2, 0, 0, 0);
}
int main()
{
SRAND(7767517);
return 0
|| test_deconvolution_0()
|| test_deconvolution_1()
|| test_deconvolution_nonoverlap()
|| test_deconvolution_activation_params_text()
#if NCNN_VALIDATION
|| test_deconvolution_load_param()
|| test_deconvolution_load_param_text()
#endif // NCNN_VALIDATION
|| test_deconvolution_activation_boundaries()
|| test_deconvolution_stride_packing_boundary();
}