#include "kernels.hpp" #include #include #include #include #include #include #include #include #include namespace { bool VerifyGemm(int rows, int cols, int depth, bool with_bias) { std::vector a(std::size_t(rows) * depth); std::vector b(std::size_t(depth) * cols); std::vector bias(static_cast(cols), 0.F); std::vector output(std::size_t(rows) * cols); std::vector expected(std::size_t(rows) * cols); for (std::size_t i = 0; i < a.size(); ++i) a[i] = float(int(i % 19) - 9) * .0625F; for (std::size_t i = 0; i < b.size(); ++i) b[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 7) - 3) * .125F; for (int row = 0; row < rows; ++row) { for (int col = 0; col < cols; ++col) { float value = with_bias ? bias[std::size_t(col)] : 0.F; for (int k = 0; k < depth; ++k) { value += a[std::size_t(row) * depth + k] * b[std::size_t(k) * cols + col]; } expected[std::size_t(row) * cols + col] = value; } } ppocr::detail::kernels::Gemm(output.data(), a.data(), b.data(), with_bias ? bias.data() : nullptr, rows, cols, depth); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 2e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "GEMM mismatch rows=" << rows << " cols=" << cols << " depth=" << depth << " bias=" << with_bias << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyIdentityRgbToNchw() { // Packed RGB -> NCHW BGR affine, including an odd tail so AVX-512/AVX2 // vector bodies and the scalar remainder share one independent reference. constexpr int width = 37, height = 5, row_width = 40, left = 2, top = 1; constexpr int source_width = 64, source_height = 8; const float scale[3] = {1.F / (255.F * .229F), 1.F / (255.F * .224F), 1.F / (255.F * .225F)}; const float shift[3] = {-.485F / .229F, -.456F / .224F, -.406F / .225F}; std::vector rgb(std::size_t(source_width) * source_height * 3); for (std::size_t i = 0; i < rgb.size(); ++i) { rgb[i] = static_cast((i * 41 + 17) & 255u); } std::vector output(std::size_t(3) * height * row_width, -99.F); std::vector expected(output.size(), -99.F); const std::size_t plane = std::size_t(height) * row_width; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { const auto* pixel = rgb.data() + (std::size_t(top + y) * source_width + left + x) * 3; expected[std::size_t(y) * row_width + x] = static_cast(pixel[2]) * scale[0] + shift[0]; expected[plane + std::size_t(y) * row_width + x] = static_cast(pixel[1]) * scale[1] + shift[1]; expected[2 * plane + std::size_t(y) * row_width + x] = static_cast(pixel[0]) * scale[2] + shift[2]; } } ppocr::detail::kernels::WriteIdentityRgbToNchw( output.data(), rgb.data(), width, height, source_width, left, top, scale, shift, row_width); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-6F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "identity RGB->NCHW mismatch index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyBilinearRgbToNchw(int image_width, int image_height, int left, int top, int source_width, int source_height, int width, int height, int row_width, bool parallel) { // Independent scalar bilinear + uint8 rounding + BGR affine. Covers the // gating detector 720x152->704x160 map, a cropped odd tail, and a 1-pixel // source so gather-safe SIMD tails never hide a scalar edge. const float scale[3] = {1.F / (255.F * .229F), 1.F / (255.F * .224F), 1.F / (255.F * .225F)}; const float shift[3] = {-.485F / .229F, -.456F / .224F, -.406F / .225F}; std::vector rgb(std::size_t(image_width) * image_height * 3); for (std::size_t i = 0; i < rgb.size(); ++i) { rgb[i] = static_cast((i * 41 + 17) & 255u); } std::vector output(std::size_t(3) * height * row_width, -99.F); std::vector expected(output.size(), -99.F); const std::size_t plane = std::size_t(height) * row_width; std::vector x0(static_cast(width)); std::vector x1(static_cast(width)); std::vector dx(static_cast(width)); for (int x = 0; x < width; ++x) { const float fx = (float(x) + .5F) * source_width / width - .5F; const int x_floor = int(std::floor(fx)); x0[static_cast(x)] = std::clamp(x_floor, 0, source_width - 1); x1[static_cast(x)] = std::min(x0[static_cast(x)] + 1, source_width - 1); dx[static_cast(x)] = fx - x_floor; } for (int y = 0; y < height; ++y) { const float fy = (float(y) + .5F) * source_height / height - .5F; const int y_floor = int(std::floor(fy)); const int y0 = std::clamp(y_floor, 0, source_height - 1); const int y1 = std::min(y0 + 1, source_height - 1); const float dy = fy - y_floor; const float inverse_dy = 1.F - dy; const auto* const upper_row = rgb.data() + (std::size_t(top + y0) * image_width + left) * 3; const auto* const lower_row = rgb.data() + (std::size_t(top + y1) * image_width + left) * 3; for (int x = 0; x < width; ++x) { const auto* const upper_left = upper_row + std::size_t(x0[static_cast(x)]) * 3; const auto* const upper_right = upper_row + std::size_t(x1[static_cast(x)]) * 3; const auto* const lower_left = lower_row + std::size_t(x0[static_cast(x)]) * 3; const auto* const lower_right = lower_row + std::size_t(x1[static_cast(x)]) * 3; const float inverse_dx = 1.F - dx[static_cast(x)]; const float upper_red = float(upper_left[0]) * inverse_dx + float(upper_right[0]) * dx[static_cast(x)]; const float lower_red = float(lower_left[0]) * inverse_dx + float(lower_right[0]) * dx[static_cast(x)]; const float sampled_red = static_cast(std::clamp( static_cast(upper_red * inverse_dy + lower_red * dy + .5F), 0, 255)); const float upper_green = float(upper_left[1]) * inverse_dx + float(upper_right[1]) * dx[static_cast(x)]; const float lower_green = float(lower_left[1]) * inverse_dx + float(lower_right[1]) * dx[static_cast(x)]; const float sampled_green = static_cast(std::clamp( static_cast(upper_green * inverse_dy + lower_green * dy + .5F), 0, 255)); const float upper_blue = float(upper_left[2]) * inverse_dx + float(upper_right[2]) * dx[static_cast(x)]; const float lower_blue = float(lower_left[2]) * inverse_dx + float(lower_right[2]) * dx[static_cast(x)]; const float sampled_blue = static_cast(std::clamp( static_cast(upper_blue * inverse_dy + lower_blue * dy + .5F), 0, 255)); expected[std::size_t(y) * row_width + x] = sampled_blue * scale[0] + shift[0]; expected[plane + std::size_t(y) * row_width + x] = sampled_green * scale[1] + shift[1]; expected[2 * plane + std::size_t(y) * row_width + x] = sampled_red * scale[2] + shift[2]; } } ppocr::detail::kernels::WriteBilinearRgbToNchw( output.data(), rgb.data(), image_width, image_height, left, top, source_width, source_height, width, height, scale, shift, row_width, parallel); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-6F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "bilinear RGB->NCHW mismatch " << image_width << "x" << image_height << "->" << width << "x" << height << " parallel=" << parallel << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyLayerNorm(std::size_t rows, std::size_t width) { std::vector input(rows * width), gamma(width), beta(width); std::vector output(input.size()), expected(input.size()); for (std::size_t i = 0; i < input.size(); ++i) { input[i] = float(int(i % 47) - 23) * .0625F; } for (std::size_t column = 0; column < width; ++column) { gamma[column] = float((column % 11) + 1) * .09375F; beta[column] = float((column % 7) - 3) * .125F; } constexpr float epsilon = 1e-5F; for (std::size_t row = 0; row < rows; ++row) { const float* src = input.data() + row * width; float* dst = expected.data() + row * width; float mean = 0.F; for (std::size_t column = 0; column < width; ++column) mean += src[column]; mean /= static_cast(width); float variance = 0.F; for (std::size_t column = 0; column < width; ++column) { const float centered = src[column] - mean; variance += centered * centered; } const float denom = std::sqrt(variance / static_cast(width) + epsilon); for (std::size_t column = 0; column < width; ++column) { dst[column] = ((src[column] - mean) / denom) * gamma[column] + beta[column]; } } ppocr::detail::kernels::LayerNorm(output.data(), input.data(), gamma.data(), beta.data(), rows, width, epsilon); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-6F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "LayerNorm mismatch rows=" << rows << " width=" << width << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifySigmoid(std::size_t n) { std::vector input(n), output(n), expected(n); for (std::size_t i = 0; i < n; ++i) input[i] = float(int(i % 63) - 31) * .0625F; for (std::size_t i = 0; i < n; ++i) expected[i] = 1.F / (1.F + std::exp(-input[i])); ppocr::detail::kernels::Sigmoid(output.data(), input.data(), n); for (std::size_t i = 0; i < n; ++i) { const float tolerance = 3e-6F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "Sigmoid mismatch n=" << n << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyMaxPool2x2Same(int planes, int height, int width) { std::vector input(std::size_t(planes) * height * width); std::vector output(input.size()); std::vector expected(input.size()); for (std::size_t i = 0; i < input.size(); ++i) { input[i] = float(int(i % 29) - 14) * .125F; } const std::size_t plane = std::size_t(height) * width; for (int p = 0; p < planes; ++p) { const float* in = input.data() + std::size_t(p) * plane; float* out = expected.data() + std::size_t(p) * plane; for (int y = 0; y + 1 < height; ++y) { const float* row0 = in + std::size_t(y) * width; const float* row1 = row0 + width; for (int x = 0; x + 1 < width; ++x) { out[std::size_t(y) * width + x] = std::max( std::max(row0[x], row0[x + 1]), std::max(row1[x], row1[x + 1])); } out[std::size_t(y) * width + width - 1] = std::max(row0[width - 1], row1[width - 1]); } if (height == 1) { for (int x = 0; x + 1 < width; ++x) out[x] = std::max(in[x], in[x + 1]); } else { const float* row = in + std::size_t(height - 1) * width; float* out_row = out + std::size_t(height - 1) * width; for (int x = 0; x + 1 < width; ++x) out_row[x] = std::max(row[x], row[x + 1]); } out[plane - 1] = in[plane - 1]; } ppocr::detail::kernels::MaxPool2x2Same(output.data(), input.data(), planes, height, width); for (std::size_t i = 0; i < output.size(); ++i) { if (output[i] != expected[i]) { std::cerr << "MaxPool2x2Same mismatch at " << i << '\n'; return false; } } return true; } bool VerifyMaxPool2x2Valid(int planes, int height, int width) { const int out_h = height - 1, out_w = width - 1; std::vector input(std::size_t(planes) * height * width); std::vector output(std::size_t(planes) * out_h * out_w); std::vector expected(output.size()); for (std::size_t i = 0; i < input.size(); ++i) { input[i] = float(int(i % 29) - 14) * .125F; } for (int plane = 0; plane < planes; ++plane) { const float* in = input.data() + std::size_t(plane) * height * width; float* out = expected.data() + std::size_t(plane) * out_h * out_w; for (int y = 0; y < out_h; ++y) for (int x = 0; x < out_w; ++x) { out[std::size_t(y) * out_w + x] = std::max( std::max(in[std::size_t(y) * width + x], in[std::size_t(y) * width + x + 1]), std::max(in[std::size_t(y + 1) * width + x], in[std::size_t(y + 1) * width + x + 1])); } } ppocr::detail::kernels::MaxPool2x2Valid(output.data(), input.data(), planes, height, width); for (std::size_t i = 0; i < output.size(); ++i) { if (output[i] != expected[i]) { std::cerr << "MaxPool2x2Valid mismatch at " << i << '\n'; return false; } } return true; } bool VerifyExactGelu() { std::vector input(4096 + 13), output(input.size()), expected(input.size()); for (std::size_t i = 0; i < input.size(); ++i) { input[i] = float(int(i % 127) - 63) * .0625F; } constexpr float inv_sqrt2 = 0.7071067811865475244F; for (std::size_t i = 0; i < input.size(); ++i) { expected[i] = input[i] * .5F * (1.F + std::erf(input[i] * inv_sqrt2)); } ppocr::detail::kernels::ExactGelu(output.data(), input.data(), input.size()); for (std::size_t i = 0; i < input.size(); ++i) { const float tolerance = 2e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "exact GELU mismatch index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyRgbByteWiden() { // Covers vector blocks and an odd scalar tail. The helper is shared by the // x86 AVX-512/AVX2 and ARM NEON GPU-upload boundary paths. std::vector input(16 * 1024 + 13); std::vector output(input.size()); for (std::size_t index = 0; index < input.size(); ++index) { input[index] = static_cast((index * 37 + 19) & 255u); } ppocr::detail::kernels::WidenU8ToFloat(output.data(), input.data(), input.size()); for (std::size_t index = 0; index < input.size(); ++index) { if (output[index] != static_cast(input[index])) { std::cerr << "RGB byte widen mismatch index=" << index << '\n'; return false; } } return true; } bool VerifyConv3x3Tile(int input_channels, int output_channels, int height, int width, bool relu) { const std::size_t input_plane = std::size_t(height) * width; const std::size_t output_plane = input_plane; std::vector input(std::size_t(input_channels) * input_plane); std::vector weights(std::size_t(output_channels) * input_channels * 9); std::vector bias(static_cast(output_channels)); std::vector output(std::size_t(output_channels) * output_plane); std::vector expected(output.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 31) - 15) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float value = bias[std::size_t(output_channel)]; for (int input_channel = 0; input_channel < input_channels; ++input_channel) { const float* plane = input.data() + std::size_t(input_channel) * input_plane; const float* filter = weights.data() + (std::size_t(output_channel) * input_channels + input_channel) * 9; for (int ky = 0; ky < 3; ++ky) for (int kx = 0; kx < 3; ++kx) { const int iy = y + ky - 1, ix = x + kx - 1; if (iy >= 0 && iy < height && ix >= 0 && ix < width) { value += plane[std::size_t(iy) * width + ix] * filter[ky * 3 + kx]; } } } expected[(std::size_t(output_channel) * height + y) * width + x] = relu ? std::max(value, 0.F) : value; } } } ppocr::detail::kernels::Conv2d(output.data(), input.data(), weights.data(), bias.data(), input_channels, output_channels, height, width, height, width, 3, 3, 1, 1, 1, 1, relu); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "3x3 convolution mismatch C=" << input_channels << " M=" << output_channels << " H=" << height << " W=" << width << " relu=" << relu << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyConv3x3Stride2(int input_channels, int output_channels, int height, int width, bool relu) { const int pad = 1; const int output_height = (height + 2 * pad - 3) / 2 + 1; const int output_width = (width + 2 * pad - 3) / 2 + 1; const std::size_t input_plane = std::size_t(height) * width; const std::size_t output_plane = std::size_t(output_height) * output_width; std::vector input(std::size_t(input_channels) * input_plane); std::vector weights(std::size_t(output_channels) * input_channels * 9); std::vector bias(static_cast(output_channels)); std::vector output(std::size_t(output_channels) * output_plane); std::vector expected(output.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 31) - 15) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { for (int y = 0; y < output_height; ++y) { for (int x = 0; x < output_width; ++x) { float value = bias[std::size_t(output_channel)]; for (int input_channel = 0; input_channel < input_channels; ++input_channel) { const float* plane = input.data() + std::size_t(input_channel) * input_plane; const float* filter = weights.data() + (std::size_t(output_channel) * input_channels + input_channel) * 9; for (int ky = 0; ky < 3; ++ky) for (int kx = 0; kx < 3; ++kx) { const int iy = y * 2 + ky - pad, ix = x * 2 + kx - pad; if (iy >= 0 && iy < height && ix >= 0 && ix < width) { value += plane[std::size_t(iy) * width + ix] * filter[ky * 3 + kx]; } } } expected[(std::size_t(output_channel) * output_height + y) * output_width + x] = relu ? std::max(value, 0.F) : value; } } } ppocr::detail::kernels::Conv2d(output.data(), input.data(), weights.data(), bias.data(), input_channels, output_channels, height, width, output_height, output_width, 3, 3, 2, 2, pad, pad, relu); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "3x3 stride-2 mismatch C=" << input_channels << " M=" << output_channels << " H=" << height << " W=" << width << " relu=" << relu << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyConcatChannelConv(int source_count, int channels_each, int output_channels, int height, int width, bool relu, int stride_h = 1, int stride_w = 1) { const int total_channels = source_count * channels_each; const int pad = 1; const int output_height = (height + 2 * pad - 3) / stride_h + 1; const int output_width = (width + 2 * pad - 3) / stride_w + 1; const std::size_t plane = std::size_t(height) * width; const std::size_t output_plane = std::size_t(output_height) * output_width; std::vector> sources(static_cast(source_count)); std::vector source_ptrs(static_cast(source_count)); std::vector source_channels(static_cast(source_count), channels_each); std::vector concat(std::size_t(total_channels) * plane); for (int source = 0; source < source_count; ++source) { sources[static_cast(source)].resize(std::size_t(channels_each) * plane); for (std::size_t i = 0; i < sources[static_cast(source)].size(); ++i) { sources[static_cast(source)][i] = float(int((i + std::size_t(source) * 17) % 31) - 15) * .03125F; } source_ptrs[static_cast(source)] = sources[static_cast(source)].data(); std::memcpy(concat.data() + std::size_t(source) * channels_each * plane, sources[static_cast(source)].data(), sources[static_cast(source)].size() * sizeof(float)); } std::vector weights(std::size_t(output_channels) * total_channels * 9); std::vector bias(static_cast(output_channels)); std::vector expected(std::size_t(output_channels) * output_plane); std::vector output(expected.size()); for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { for (int y = 0; y < output_height; ++y) for (int x = 0; x < output_width; ++x) { float value = bias[static_cast(output_channel)]; for (int input_channel = 0; input_channel < total_channels; ++input_channel) { const float* src = concat.data() + std::size_t(input_channel) * plane; const float* filter = weights.data() + (std::size_t(output_channel) * total_channels + input_channel) * 9; for (int ky = 0; ky < 3; ++ky) for (int kx = 0; kx < 3; ++kx) { const int iy = y * stride_h + ky - pad, ix = x * stride_w + kx - pad; if (iy >= 0 && iy < height && ix >= 0 && ix < width) { value += src[std::size_t(iy) * width + ix] * filter[ky * 3 + kx]; } } } expected[(std::size_t(output_channel) * output_height + y) * output_width + x] = relu ? std::max(value, 0.F) : value; } } ppocr::detail::kernels::ConcatChannelConv2d( output.data(), source_ptrs.data(), source_channels.data(), source_count, weights.data(), bias.data(), output_channels, height, width, output_height, output_width, 3, 3, stride_h, stride_w, pad, pad, relu); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "concat-channel conv mismatch sources=" << source_count << " C=" << channels_each << " M=" << output_channels << " stride=" << stride_h << "x" << stride_w << " relu=" << relu << " index=" << i << '\n'; return false; } } return true; } bool VerifyConcatChannelConvMixed(const int* channels, int source_count, int output_channels, int height, int width, bool relu, int stride_h, int stride_w) { const int pad = 1; const int output_height = (height + 2 * pad - 3) / stride_h + 1; const int output_width = (width + 2 * pad - 3) / stride_w + 1; const std::size_t plane = std::size_t(height) * width; const std::size_t output_plane = std::size_t(output_height) * output_width; int total_channels = 0; for (int source = 0; source < source_count; ++source) total_channels += channels[source]; std::vector> sources(static_cast(source_count)); std::vector source_ptrs(static_cast(source_count)); std::vector source_channels(static_cast(source_count)); std::vector concat(std::size_t(total_channels) * plane); int channel0 = 0; for (int source = 0; source < source_count; ++source) { source_channels[static_cast(source)] = channels[source]; sources[static_cast(source)].resize(std::size_t(channels[source]) * plane); for (std::size_t i = 0; i < sources[static_cast(source)].size(); ++i) { sources[static_cast(source)][i] = float(int((i + std::size_t(source) * 17) % 31) - 15) * .03125F; } source_ptrs[static_cast(source)] = sources[static_cast(source)].data(); std::memcpy(concat.data() + std::size_t(channel0) * plane, sources[static_cast(source)].data(), sources[static_cast(source)].size() * sizeof(float)); channel0 += channels[source]; } std::vector weights(std::size_t(output_channels) * total_channels * 9); std::vector bias(static_cast(output_channels)); std::vector expected(std::size_t(output_channels) * output_plane); std::vector output(expected.size()); for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { for (int y = 0; y < output_height; ++y) for (int x = 0; x < output_width; ++x) { float value = bias[static_cast(output_channel)]; for (int input_channel = 0; input_channel < total_channels; ++input_channel) { const float* src = concat.data() + std::size_t(input_channel) * plane; const float* filter = weights.data() + (std::size_t(output_channel) * total_channels + input_channel) * 9; for (int ky = 0; ky < 3; ++ky) for (int kx = 0; kx < 3; ++kx) { const int iy = y * stride_h + ky - pad, ix = x * stride_w + kx - pad; if (iy >= 0 && iy < height && ix >= 0 && ix < width) { value += src[std::size_t(iy) * width + ix] * filter[ky * 3 + kx]; } } } expected[(std::size_t(output_channel) * output_height + y) * output_width + x] = relu ? std::max(value, 0.F) : value; } } ppocr::detail::kernels::ConcatChannelConv2d( output.data(), source_ptrs.data(), source_channels.data(), source_count, weights.data(), bias.data(), output_channels, height, width, output_height, output_width, 3, 3, stride_h, stride_w, pad, pad, relu); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "mixed concat-channel conv mismatch index=" << i << '\n'; return false; } } return true; } bool VerifyConcatConvDualTranspose(int source_count, int channels_each, int conv_oc, int final_oc, int height, int width) { const int pad = 1; const int conv_h = height; const int conv_width = width; const int mid_h = conv_h * 2, mid_w = conv_width * 2; const int out_h = conv_h * 4, out_w = conv_width * 4; const std::size_t plane = std::size_t(height) * width; const int total_channels = source_count * channels_each; std::vector> sources(static_cast(source_count)); std::vector source_ptrs(static_cast(source_count)); std::vector source_channels(static_cast(source_count), channels_each); for (int source = 0; source < source_count; ++source) { sources[static_cast(source)].resize(std::size_t(channels_each) * plane); for (std::size_t i = 0; i < sources[static_cast(source)].size(); ++i) { sources[static_cast(source)][i] = float(int((i + std::size_t(source) * 13) % 29) - 14) * .03125F; } source_ptrs[static_cast(source)] = sources[static_cast(source)].data(); } std::vector conv_weights(std::size_t(conv_oc) * total_channels * 9); std::vector conv_b(static_cast(conv_oc)); std::vector ct0_w(std::size_t(conv_oc) * conv_oc * 4); std::vector ct0_b(static_cast(conv_oc)); std::vector ct1_w(std::size_t(conv_oc) * final_oc * 4); std::vector ct1_b(static_cast(final_oc)); for (std::size_t i = 0; i < conv_weights.size(); ++i) conv_weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < conv_b.size(); ++i) conv_b[i] = float(int(i % 13) - 6) * .0625F; for (std::size_t i = 0; i < ct0_w.size(); ++i) ct0_w[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < ct0_b.size(); ++i) ct0_b[i] = float(int(i % 7) - 3) * .0625F; for (std::size_t i = 0; i < ct1_w.size(); ++i) ct1_w[i] = float(int(i % 19) - 9) * .03125F; for (std::size_t i = 0; i < ct1_b.size(); ++i) ct1_b[i] = float(int(i % 5) - 2) * .0625F; std::vector concat(std::size_t(total_channels) * plane); for (int source = 0; source < source_count; ++source) { std::memcpy(concat.data() + std::size_t(source * channels_each) * plane, sources[static_cast(source)].data(), sources[static_cast(source)].size() * sizeof(float)); } std::vector conv_ref(std::size_t(conv_oc) * conv_h * conv_width); for (int oc = 0; oc < conv_oc; ++oc) { for (int y = 0; y < conv_h; ++y) for (int x = 0; x < conv_width; ++x) { float value = conv_b[static_cast(oc)]; for (int ic = 0; ic < total_channels; ++ic) { const float* src = concat.data() + std::size_t(ic) * plane; const float* filter = conv_weights.data() + (std::size_t(oc) * total_channels + ic) * 9; for (int ky = 0; ky < 3; ++ky) for (int kx = 0; kx < 3; ++kx) { const int iy = y + ky - pad, ix = x + kx - pad; if (iy >= 0 && iy < height && ix >= 0 && ix < width) value += src[std::size_t(iy) * width + ix] * filter[ky * 3 + kx]; } } conv_ref[(std::size_t(oc) * conv_h + y) * conv_width + x] = std::max(value, 0.F); } } std::vector mid_ref(std::size_t(conv_oc) * mid_h * mid_w, 0.F); const std::size_t conv_plane = std::size_t(conv_h) * conv_width; const std::size_t mid_plane = std::size_t(mid_h) * mid_w; for (int oc = 0; oc < conv_oc; ++oc) { float* out = mid_ref.data() + std::size_t(oc) * mid_plane; std::fill_n(out, mid_plane, ct0_b[static_cast(oc)]); for (int ic = 0; ic < conv_oc; ++ic) { const float* src = conv_ref.data() + std::size_t(ic) * conv_plane; const float* w = ct0_w.data() + (std::size_t(ic) * conv_oc + oc) * 4; for (int y = 0; y < conv_h; ++y) { const float* row = src + std::size_t(y) * conv_width; float* dst0 = out + std::size_t(2 * y) * mid_w; float* dst1 = dst0 + mid_w; for (int x = 0; x < conv_width; ++x) { const float value = row[x]; dst0[2 * x] += value * w[0]; dst0[2 * x + 1] += value * w[1]; dst1[2 * x] += value * w[2]; dst1[2 * x + 1] += value * w[3]; } } } for (std::size_t i = 0; i < mid_plane; ++i) out[i] = std::max(out[i], 0.F); } std::vector expected(std::size_t(final_oc) * out_h * out_w, 0.F); const std::size_t out_plane = std::size_t(out_h) * out_w; for (int oc = 0; oc < final_oc; ++oc) { float* out = expected.data() + std::size_t(oc) * out_plane; std::fill_n(out, out_plane, ct1_b[static_cast(oc)]); for (int ic = 0; ic < conv_oc; ++ic) { const float* src = mid_ref.data() + std::size_t(ic) * mid_plane; const float* w = ct1_w.data() + (std::size_t(ic) * final_oc + oc) * 4; for (int y = 0; y < mid_h; ++y) { const float* row = src + std::size_t(y) * mid_w; float* dst0 = out + std::size_t(2 * y) * out_w; float* dst1 = dst0 + out_w; for (int x = 0; x < mid_w; ++x) { const float value = row[x]; dst0[2 * x] += value * w[0]; dst0[2 * x + 1] += value * w[1]; dst1[2 * x] += value * w[2]; dst1[2 * x + 1] += value * w[3]; } } } for (std::size_t i = 0; i < out_plane; ++i) out[i] = 1.F / (1.F + std::exp(-out[i])); } std::vector conv(std::size_t(conv_oc) * conv_h * conv_width); std::vector mid(std::size_t(conv_oc) * mid_h * mid_w); std::vector output(expected.size()); ppocr::detail::kernels::ConcatChannelConv2d( conv.data(), source_ptrs.data(), source_channels.data(), source_count, conv_weights.data(), conv_b.data(), conv_oc, height, width, conv_h, conv_width, 3, 3, 1, 1, pad, pad, true); ppocr::detail::kernels::ConvTranspose2x2(mid.data(), conv.data(), ct0_w.data(), ct0_b.data(), conv_oc, conv_oc, conv_h, conv_width, 1); ppocr::detail::kernels::ConvTranspose2x2(output.data(), mid.data(), ct1_w.data(), ct1_b.data(), conv_oc, final_oc, mid_h, mid_w, 2); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 4e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "concat-conv dual transpose mismatch H=" << height << " W=" << width << " index=" << i << '\n'; return false; } } return true; } bool VerifyDetStemFromNchw(int height, int width) { const int c0_h = (height + 2 - 3) / 2 + 1; const int c0_w = (width + 2 - 3) / 2 + 1; const int stem_h = (c0_h + 2 - 3) / 2 + 1; const int stem_w = (c0_w + 2 - 3) / 2 + 1; const int oc0 = 16, oc1 = 8, oc2 = 16, ocs = 16; std::vector rgb(std::size_t(3) * height * width); std::vector w0(std::size_t(oc0) * 3 * 9), b0(oc0); std::vector w1(std::size_t(oc1) * oc0 * 4), b1(oc1); std::vector w2(std::size_t(oc2) * oc1 * 4), b2(oc2); std::vector ws(std::size_t(ocs) * (oc0 + oc2) * 9), bs(ocs); for (std::size_t i = 0; i < rgb.size(); ++i) rgb[i] = float(int(i % 31) - 15) * .03125F; for (std::size_t i = 0; i < w0.size(); ++i) w0[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < b0.size(); ++i) b0[i] = float(int(i % 13) - 6) * .0625F; for (std::size_t i = 0; i < w1.size(); ++i) w1[i] = float(int(i % 23) - 11) * .015625F; for (std::size_t i = 0; i < b1.size(); ++i) b1[i] = float(int(i % 11) - 5) * .0625F; for (std::size_t i = 0; i < w2.size(); ++i) w2[i] = float(int(i % 19) - 9) * .015625F; for (std::size_t i = 0; i < b2.size(); ++i) b2[i] = float(int(i % 7) - 3) * .0625F; for (std::size_t i = 0; i < ws.size(); ++i) ws[i] = float(int(i % 17) - 8) * .015625F; for (std::size_t i = 0; i < bs.size(); ++i) bs[i] = float(int(i % 5) - 2) * .0625F; std::vector c0(std::size_t(oc0) * c0_h * c0_w); std::vector c1(std::size_t(oc1) * c0_h * c0_w); std::vector c2(std::size_t(oc2) * c0_h * c0_w); std::vector pooled(c0.size()); std::vector expected(std::size_t(ocs) * stem_h * stem_w); std::vector output(expected.size(), -99.F); ppocr::detail::kernels::Conv2d(c0.data(), rgb.data(), w0.data(), b0.data(), 3, oc0, height, width, c0_h, c0_w, 3, 3, 2, 2, 1, 1, true); ppocr::detail::kernels::Conv2d(c1.data(), c0.data(), w1.data(), b1.data(), oc0, oc1, c0_h, c0_w, c0_h, c0_w, 2, 2, 1, 1, 0, 0, true); ppocr::detail::kernels::Conv2d(c2.data(), c1.data(), w2.data(), b2.data(), oc1, oc2, c0_h, c0_w, c0_h, c0_w, 2, 2, 1, 1, 0, 0, true); ppocr::detail::kernels::MaxPool2x2Same(pooled.data(), c0.data(), std::size_t(oc0), c0_h, c0_w); const float* sources[2] = {pooled.data(), c2.data()}; const int chans[2] = {oc0, oc2}; ppocr::detail::kernels::ConcatChannelConv2d(expected.data(), sources, chans, 2, ws.data(), bs.data(), ocs, c0_h, c0_w, stem_h, stem_w, 3, 3, 2, 2, 1, 1, true); ppocr::detail::kernels::DetStemFromNchw(output.data(), rgb.data(), w0.data(), b0.data(), oc0, w1.data(), b1.data(), oc1, w2.data(), b2.data(), oc2, ws.data(), bs.data(), ocs, height, width, true, true); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "det stem mismatch H=" << height << " W=" << width << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyConv2x2SameUpper(int input_channels, int output_channels, int height, int width, bool relu) { const std::size_t plane = std::size_t(height) * width; std::vector input(std::size_t(input_channels) * plane); std::vector weights(std::size_t(output_channels) * input_channels * 4); std::vector bias(static_cast(output_channels)); std::vector output(std::size_t(output_channels) * plane); std::vector expected(output.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) { float value = bias[std::size_t(output_channel)]; for (int input_channel = 0; input_channel < input_channels; ++input_channel) { const float* source = input.data() + std::size_t(input_channel) * plane; const float* kernel = weights.data() + (std::size_t(output_channel) * input_channels + input_channel) * 4; for (int ky = 0; ky < 2; ++ky) for (int kx = 0; kx < 2; ++kx) { const int iy = y + ky, ix = x + kx; if (iy < height && ix < width) { value += source[std::size_t(iy) * width + ix] * kernel[ky * 2 + kx]; } } } expected[(std::size_t(output_channel) * height + y) * width + x] = relu ? std::max(value, 0.F) : value; } } ppocr::detail::kernels::Conv2d(output.data(), input.data(), weights.data(), bias.data(), input_channels, output_channels, height, width, height, width, 2, 2, 1, 1, 0, 0, relu); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "2x2 SAME_UPPER mismatch C=" << input_channels << " M=" << output_channels << " H=" << height << " W=" << width << " relu=" << relu << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyConvWideTile(int input_channels, int output_channels, int height, int width, int kernel) { const int pad = kernel / 2; const std::size_t input_plane = std::size_t(height) * width; std::vector input(std::size_t(input_channels) * input_plane); std::vector weights(std::size_t(output_channels) * input_channels * kernel * kernel); std::vector bias(static_cast(output_channels)); std::vector output(std::size_t(output_channels) * input_plane); std::vector expected(output.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 31) - 15) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) { float value = bias[std::size_t(output_channel)]; for (int input_channel = 0; input_channel < input_channels; ++input_channel) { const float* plane = input.data() + std::size_t(input_channel) * input_plane; const float* filter = weights.data() + (std::size_t(output_channel) * input_channels + input_channel) * kernel * kernel; for (int ky = 0; ky < kernel; ++ky) for (int kx = 0; kx < kernel; ++kx) { const int iy = y + ky - pad, ix = x + kx - pad; if (iy >= 0 && iy < height && ix >= 0 && ix < width) { value += plane[std::size_t(iy) * width + ix] * filter[ky * kernel + kx]; } } } expected[(std::size_t(output_channel) * height + y) * width + x] = value; } } ppocr::detail::kernels::Conv2d(output.data(), input.data(), weights.data(), bias.data(), input_channels, output_channels, height, width, height, width, kernel, kernel, 1, 1, pad, pad, false); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "wide convolution mismatch K=" << kernel << " C=" << input_channels << " M=" << output_channels << " H=" << height << " W=" << width << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyPointwiseConv(int input_channels, int output_channels, std::size_t plane) { std::vector input(std::size_t(input_channels) * plane); std::vector weights(std::size_t(output_channels) * input_channels); std::vector bias(static_cast(output_channels)); std::vector expected(std::size_t(output_channels) * plane); std::vector output(expected.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 37) - 18) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 17) - 8) * .0625F; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { float* dst = expected.data() + std::size_t(output_channel) * plane; const float* filter = weights.data() + std::size_t(output_channel) * input_channels; const float base = bias[static_cast(output_channel)]; for (std::size_t index = 0; index < plane; ++index) { float value = base; for (int input_channel = 0; input_channel < input_channels; ++input_channel) { value += filter[input_channel] * input[std::size_t(input_channel) * plane + index]; } dst[index] = value; } } ppocr::detail::kernels::PointwiseConv(output.data(), input.data(), weights.data(), bias.data(), output_channels, input_channels, plane); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "pointwise conv mismatch C=" << input_channels << " M=" << output_channels << " plane=" << plane << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyPointwiseConvRelu(int input_channels, int output_channels, std::size_t plane) { std::vector input(std::size_t(input_channels) * plane); std::vector weights(std::size_t(output_channels) * input_channels); std::vector bias(static_cast(output_channels)); std::vector expected(std::size_t(output_channels) * plane); std::vector output(expected.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 37) - 18) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 17) - 8) * .0625F; ppocr::detail::kernels::PointwiseConv(expected.data(), input.data(), weights.data(), bias.data(), output_channels, input_channels, plane); ppocr::detail::kernels::Relu(expected.data(), expected.data(), expected.size()); ppocr::detail::kernels::PointwiseConvRelu(output.data(), input.data(), weights.data(), bias.data(), output_channels, input_channels, plane); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "pointwise conv relu mismatch C=" << input_channels << " M=" << output_channels << " plane=" << plane << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyExpandGeluProjectAdd(int channels, int hidden, std::size_t plane) { std::vector input(std::size_t(channels) * plane); std::vector expand_weights(std::size_t(hidden) * channels); std::vector expand_bias(static_cast(hidden)); std::vector project_weights(std::size_t(channels) * hidden); std::vector project_bias(static_cast(channels)); std::vector expected(input.size()); std::vector output(input.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 37) - 18) * .03125F; for (std::size_t i = 0; i < expand_weights.size(); ++i) expand_weights[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < expand_bias.size(); ++i) expand_bias[i] = float(int(i % 17) - 8) * .0625F; for (std::size_t i = 0; i < project_weights.size(); ++i) project_weights[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < project_bias.size(); ++i) project_bias[i] = float(int(i % 13) - 6) * .0625F; constexpr float inv_sqrt2 = 0.7071067811865475244F; std::vector hidden_act(static_cast(hidden)); for (std::size_t spatial = 0; spatial < plane; ++spatial) { for (int hidden_channel = 0; hidden_channel < hidden; ++hidden_channel) { float sum = expand_bias[static_cast(hidden_channel)]; const float* filter = expand_weights.data() + std::size_t(hidden_channel) * channels; for (int channel = 0; channel < channels; ++channel) { sum += filter[channel] * input[std::size_t(channel) * plane + spatial]; } hidden_act[static_cast(hidden_channel)] = sum * .5F * (1.F + std::erf(sum * inv_sqrt2)); } for (int channel = 0; channel < channels; ++channel) { float sum = project_bias[static_cast(channel)] + input[std::size_t(channel) * plane + spatial]; const float* filter = project_weights.data() + std::size_t(channel) * hidden; for (int hidden_channel = 0; hidden_channel < hidden; ++hidden_channel) { sum += filter[hidden_channel] * hidden_act[static_cast(hidden_channel)]; } expected[std::size_t(channel) * plane + spatial] = sum; } } ppocr::detail::kernels::ExpandGeluProjectAdd( output.data(), input.data(), expand_weights.data(), expand_bias.data(), project_weights.data(), project_bias.data(), channels, hidden, plane); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 2e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "expand-GELU-project-add mismatch C=" << channels << " H=" << hidden << " plane=" << plane << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyPointwiseResidualBatch(int batches, int input_channels, int output_channels, std::size_t plane, int activation) { const std::size_t input_batch = std::size_t(input_channels) * plane; const std::size_t output_batch = std::size_t(output_channels) * plane; std::vector input(std::size_t(batches) * input_batch); std::vector weights(std::size_t(output_channels) * input_channels); std::vector bias(static_cast(output_channels)); std::vector residual(std::size_t(batches) * output_batch); std::vector expected(residual.size()); std::vector output(residual.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 37) - 18) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 17) - 8) * .0625F; for (std::size_t i = 0; i < residual.size(); ++i) residual[i] = float(int(i % 29) - 14) * .03125F; for (int batch = 0; batch < batches; ++batch) { float* dst = expected.data() + std::size_t(batch) * output_batch; const float* src = input.data() + std::size_t(batch) * input_batch; const float* add = residual.data() + std::size_t(batch) * output_batch; if (activation == 0) { ppocr::detail::kernels::PointwiseConvAdd(dst, src, weights.data(), bias.data(), add, output_channels, input_channels, plane); } else if (activation == 1) { ppocr::detail::kernels::PointwiseConvAddRelu(dst, src, weights.data(), bias.data(), add, output_channels, input_channels, plane); } else { ppocr::detail::kernels::PointwiseConvAddSwish(dst, src, weights.data(), bias.data(), add, output_channels, input_channels, plane); } } if (activation == 0) { ppocr::detail::kernels::PointwiseConvAddBatch(output.data(), input.data(), weights.data(), bias.data(), residual.data(), batches, output_channels, input_channels, plane); } else if (activation == 1) { ppocr::detail::kernels::PointwiseConvAddReluBatch(output.data(), input.data(), weights.data(), bias.data(), residual.data(), batches, output_channels, input_channels, plane); } else { ppocr::detail::kernels::PointwiseConvAddSwishBatch(output.data(), input.data(), weights.data(), bias.data(), residual.data(), batches, output_channels, input_channels, plane); } for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "batched residual pointwise mismatch N=" << batches << " C=" << input_channels << " M=" << output_channels << " plane=" << plane << " activation=" << activation << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyDepthwisePointwiseFused(int channels, int output_channels, int height, int width, int activation) { const std::size_t in_n = std::size_t(channels) * height * width; const std::size_t out_n = std::size_t(output_channels) * height * width; std::vector input(in_n), dw_w(std::size_t(channels) * 9); std::vector dw_b(static_cast(channels)); std::vector pw_w(std::size_t(output_channels) * channels); std::vector pw_b(static_cast(output_channels)); std::vector intermediate(in_n), expected(out_n), output(out_n, -99.F); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < dw_w.size(); ++i) dw_w[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < dw_b.size(); ++i) dw_b[i] = float(int(i % 13) - 6) * .0625F; for (std::size_t i = 0; i < pw_w.size(); ++i) pw_w[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < pw_b.size(); ++i) pw_b[i] = float(int(i % 17) - 8) * .0625F; ppocr::detail::kernels::DepthwiseConv( intermediate.data(), input.data(), dw_w.data(), dw_b.data(), channels, height, width, height, width, 3, 3, 1, 1, 1, 1); if (activation == 1) { ppocr::detail::kernels::PointwiseConvRelu(expected.data(), intermediate.data(), pw_w.data(), pw_b.data(), output_channels, channels, std::size_t(height) * width); } else if (activation == 2) { ppocr::detail::kernels::PointwiseConvHardSwish(expected.data(), intermediate.data(), pw_w.data(), pw_b.data(), output_channels, channels, std::size_t(height) * width); } else { ppocr::detail::kernels::PointwiseConv(expected.data(), intermediate.data(), pw_w.data(), pw_b.data(), output_channels, channels, std::size_t(height) * width); if (activation == 3) { ppocr::detail::kernels::Gelu(expected.data(), expected.data(), expected.size(), expected.size()); } } const bool fused = ppocr::detail::kernels::DepthwisePointwiseConvFused( output.data(), input.data(), dw_w.data(), dw_b.data(), pw_w.data(), pw_b.data(), 1, channels, output_channels, height, width, 3, 3, 1, 1, 1, 1, activation); if (!fused) { // Scalar/AVX2 hosts skip the fused AVX-512 path; the two-pass kernels // remain the production fallback and are already covered elsewhere. return true; } for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 5e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "fused DW+PW mismatch C=" << channels << " M=" << output_channels << " H=" << height << " W=" << width << " act=" << activation << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyDepthwisePointwiseFused5x5(int channels, int output_channels, int height, int width, int activation) { const std::size_t in_n = std::size_t(channels) * height * width; const std::size_t out_n = std::size_t(output_channels) * height * width; std::vector input(in_n), dw_w(std::size_t(channels) * 25); std::vector dw_b(static_cast(channels)); std::vector pw_w(std::size_t(output_channels) * channels); std::vector pw_b(static_cast(output_channels)); std::vector intermediate(in_n), expected(out_n), output(out_n, -99.F); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < dw_w.size(); ++i) dw_w[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < dw_b.size(); ++i) dw_b[i] = float(int(i % 13) - 6) * .0625F; for (std::size_t i = 0; i < pw_w.size(); ++i) pw_w[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < pw_b.size(); ++i) pw_b[i] = float(int(i % 17) - 8) * .0625F; ppocr::detail::kernels::DepthwiseConv( intermediate.data(), input.data(), dw_w.data(), dw_b.data(), channels, height, width, height, width, 5, 5, 1, 1, 2, 2); if (activation == 1) { ppocr::detail::kernels::PointwiseConvRelu(expected.data(), intermediate.data(), pw_w.data(), pw_b.data(), output_channels, channels, std::size_t(height) * width); } else if (activation == 2) { ppocr::detail::kernels::PointwiseConvHardSwish(expected.data(), intermediate.data(), pw_w.data(), pw_b.data(), output_channels, channels, std::size_t(height) * width); } else { ppocr::detail::kernels::PointwiseConv(expected.data(), intermediate.data(), pw_w.data(), pw_b.data(), output_channels, channels, std::size_t(height) * width); if (activation == 3) { ppocr::detail::kernels::Gelu(expected.data(), expected.data(), expected.size(), expected.size()); } } const bool fused = ppocr::detail::kernels::DepthwisePointwiseConvFused( output.data(), input.data(), dw_w.data(), dw_b.data(), pw_w.data(), pw_b.data(), 1, channels, output_channels, height, width, 5, 5, 1, 1, 2, 2, activation); if (!fused) { return true; } for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 5e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "fused 5x5 DW+PW mismatch C=" << channels << " M=" << output_channels << " H=" << height << " W=" << width << " act=" << activation << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyPointwiseDepthwise(int input_channels, int output_channels, int height, int width, bool relu) { // Tiled 8-OC kernel is ENABLE-only after the 20-run miss versus two-node. #if defined(_WIN32) _putenv("PPOCR_ENABLE_POINTWISE_DEPTHWISE=1"); #else setenv("PPOCR_ENABLE_POINTWISE_DEPTHWISE", "1", 1); #endif const std::size_t in_n = std::size_t(input_channels) * height * width; const std::size_t out_n = std::size_t(output_channels) * height * width; std::vector input(in_n), pw_w(std::size_t(output_channels) * input_channels); std::vector pw_b(static_cast(output_channels)); std::vector dw_w(std::size_t(output_channels) * 9); std::vector dw_b(static_cast(output_channels)); std::vector intermediate(out_n), expected(out_n), output(out_n, -99.F); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < pw_w.size(); ++i) pw_w[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < pw_b.size(); ++i) pw_b[i] = float(int(i % 17) - 8) * .0625F; for (std::size_t i = 0; i < dw_w.size(); ++i) dw_w[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < dw_b.size(); ++i) dw_b[i] = float(int(i % 13) - 6) * .0625F; if (relu) { ppocr::detail::kernels::PointwiseConvRelu( intermediate.data(), input.data(), pw_w.data(), pw_b.data(), output_channels, input_channels, std::size_t(height) * width); } else { ppocr::detail::kernels::PointwiseConv( intermediate.data(), input.data(), pw_w.data(), pw_b.data(), output_channels, input_channels, std::size_t(height) * width); } ppocr::detail::kernels::DepthwiseConv( expected.data(), intermediate.data(), dw_w.data(), dw_b.data(), output_channels, height, width, height, width, 3, 3, 1, 1, 1, 1); const bool fused = ppocr::detail::kernels::PointwiseDepthwiseConvFused( output.data(), input.data(), pw_w.data(), pw_b.data(), dw_w.data(), dw_b.data(), 1, input_channels, output_channels, height, width, relu); if (!fused) { if (height >= 32 && std::getenv("PPOCR_DISABLE_POINTWISE_DEPTHWISE") == nullptr) { std::cerr << "fused PW+DW did not run IC=" << input_channels << " OC=" << output_channels << " H=" << height << " W=" << width << " relu=" << relu << '\n'; return false; } return true; } for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 5e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "fused PW+DW mismatch IC=" << input_channels << " OC=" << output_channels << " H=" << height << " W=" << width << " relu=" << relu << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyDepthwiseExpandGelu(int channels, int hidden, int height, int width) { // Opt-in C*W tiled kernel is ENABLE-only; force it so smoke drives the // shipped AVX-512 function rather than the two-pass fallback. #if defined(_WIN32) _putenv("PPOCR_ENABLE_AVX512_DW_EXPAND_GELU=1"); #else setenv("PPOCR_ENABLE_AVX512_DW_EXPAND_GELU", "1", 1); #endif const std::size_t n = std::size_t(channels) * height * width; std::vector input(n), dw_w(std::size_t(channels) * 9); std::vector dw_b(static_cast(channels)); std::vector expand_w(std::size_t(hidden) * channels); std::vector expand_b(static_cast(hidden)); std::vector project_w(std::size_t(channels) * hidden); std::vector project_b(static_cast(channels)); std::vector intermediate(n), expected(n), output(n, -99.F); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < dw_w.size(); ++i) dw_w[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < dw_b.size(); ++i) dw_b[i] = float(int(i % 13) - 6) * .0625F; for (std::size_t i = 0; i < expand_w.size(); ++i) expand_w[i] = float(int(i % 31) - 15) * .015625F; for (std::size_t i = 0; i < expand_b.size(); ++i) expand_b[i] = float(int(i % 17) - 8) * .0625F; for (std::size_t i = 0; i < project_w.size(); ++i) project_w[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < project_b.size(); ++i) project_b[i] = float(int(i % 11) - 5) * .0625F; ppocr::detail::kernels::DepthwiseConv( intermediate.data(), input.data(), dw_w.data(), dw_b.data(), channels, height, width, height, width, 3, 3, 1, 1, 1, 1); ppocr::detail::kernels::ExpandGeluProjectAdd( expected.data(), intermediate.data(), expand_w.data(), expand_b.data(), project_w.data(), project_b.data(), channels, hidden, std::size_t(height) * width); ppocr::detail::kernels::DepthwiseExpandGeluProjectAdd( output.data(), input.data(), dw_w.data(), dw_b.data(), expand_w.data(), expand_b.data(), project_w.data(), project_b.data(), 1, channels, hidden, height, width, 3, 3, 1, 1, 1, 1); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 8e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "fused DW+ExpandGelu mismatch C=" << channels << " hidden=" << hidden << " H=" << height << " W=" << width << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyDepthwiseBatch(int batches, int channels, int input_height, int input_width, int kernel, int stride, int pad) { const int output_height = (input_height + 2 * pad - kernel) / stride + 1; const int output_width = (input_width + 2 * pad - kernel) / stride + 1; const std::size_t input_batch = std::size_t(channels) * input_height * input_width; const std::size_t output_batch = std::size_t(channels) * output_height * output_width; std::vector input(std::size_t(batches) * input_batch); std::vector weights(std::size_t(channels) * kernel * kernel); std::vector bias(static_cast(channels)); std::vector expected(std::size_t(batches) * output_batch); std::vector output(expected.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int batch = 0; batch < batches; ++batch) { ppocr::detail::kernels::DepthwiseConv( expected.data() + std::size_t(batch) * output_batch, input.data() + std::size_t(batch) * input_batch, weights.data(), bias.data(), channels, input_height, input_width, output_height, output_width, kernel, kernel, stride, stride, pad, pad); } ppocr::detail::kernels::DepthwiseConvBatch( output.data(), input.data(), weights.data(), bias.data(), batches, channels, input_height, input_width, output_height, output_width, kernel, kernel, stride, stride, pad, pad); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "batched depthwise mismatch N=" << batches << " C=" << channels << " H=" << input_height << " W=" << input_width << " K=" << kernel << " stride=" << stride << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyFoldedDepthwiseHardSwish(int channels, int height, int width, int kernel_h, int kernel_w, int pad_h, int pad_w) { const int out_h = height + 2 * pad_h - kernel_h + 1; const int out_w = width + 2 * pad_w - kernel_w + 1; if (out_h <= 0 || out_w <= 0) return false; const std::size_t in_n = std::size_t(channels) * height * width; const std::size_t out_n = std::size_t(channels) * out_h * out_w; std::vector input(in_n), weights(std::size_t(channels) * kernel_h * kernel_w); std::vector bias(static_cast(channels)); std::vector factor(static_cast(channels)); std::vector offset(static_cast(channels)); std::vector expected(out_n), fused(out_n), folded_w(weights.size()), folded_b(bias.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 23) - 11) * .03125F; for (int i = 0; i < channels; ++i) { bias[static_cast(i)] = float(int(i % 13) - 6) * .0625F; factor[static_cast(i)] = 0.75F + float(i % 5) * .05F; offset[static_cast(i)] = float(int(i % 7) - 3) * .03125F; } ppocr::detail::kernels::DepthwiseConv(expected.data(), input.data(), weights.data(), bias.data(), channels, height, width, out_h, out_w, kernel_h, kernel_w, 1, 1, pad_h, pad_w); ppocr::detail::kernels::BatchNormAffine(expected.data(), expected.data(), factor.data(), offset.data(), 1, channels, std::size_t(out_h) * out_w); ppocr::detail::kernels::HardSwish(expected.data(), expected.data(), expected.size()); const int per = kernel_h * kernel_w; for (int c = 0; c < channels; ++c) { for (int k = 0; k < per; ++k) folded_w[std::size_t(c) * per + k] = weights[std::size_t(c) * per + k] * factor[c]; folded_b[static_cast(c)] = bias[static_cast(c)] * factor[c] + offset[c]; } ppocr::detail::kernels::DepthwiseConv(fused.data(), input.data(), folded_w.data(), folded_b.data(), channels, height, width, out_h, out_w, kernel_h, kernel_w, 1, 1, pad_h, pad_w); ppocr::detail::kernels::HardSwish(fused.data(), fused.data(), fused.size()); for (std::size_t i = 0; i < expected.size(); ++i) { const float tolerance = 4e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(fused[i] - expected[i]) > tolerance) { std::cerr << "folded dw+hswish mismatch C=" << channels << " " << height << "x" << width << " k=" << kernel_h << "x" << kernel_w << " index=" << i << '\n'; return false; } } return true; } bool VerifyDepthwiseScalar(int channels, int height, int width, int kernel, int pad) { const int output_height = height; const int output_width = width; const std::size_t plane = std::size_t(height) * width; std::vector input(std::size_t(channels) * plane); std::vector weights(std::size_t(channels) * kernel * kernel); std::vector bias(static_cast(channels)); std::vector expected(input.size()), output(input.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 23) - 11) * .03125F; for (int i = 0; i < channels; ++i) bias[static_cast(i)] = float(int(i % 13) - 6) * .0625F; for (int channel = 0; channel < channels; ++channel) { const float* in = input.data() + std::size_t(channel) * plane; const float* filter = weights.data() + std::size_t(channel) * kernel * kernel; float* out = expected.data() + std::size_t(channel) * plane; const float base = bias[static_cast(channel)]; for (int y = 0; y < output_height; ++y) { for (int x = 0; x < output_width; ++x) { float sum = base; for (int ky = 0; ky < kernel; ++ky) { const int iy = y + ky - pad; if (iy < 0 || iy >= height) continue; for (int kx = 0; kx < kernel; ++kx) { const int ix = x + kx - pad; if (ix >= 0 && ix < width) sum += in[std::size_t(iy) * width + ix] * filter[ky * kernel + kx]; } } out[std::size_t(y) * output_width + x] = sum; } } } ppocr::detail::kernels::DepthwiseConv(output.data(), input.data(), weights.data(), bias.data(), channels, height, width, output_height, output_width, kernel, kernel, 1, 1, pad, pad); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "depthwise scalar mismatch C=" << channels << " H=" << height << " W=" << width << " K=" << kernel << " index=" << i << '\n'; return false; } } return true; } bool VerifyConvBatch(int batches, int input_channels, int output_channels, int input_height, int input_width, int kernel_height, int kernel_width, int stride_height, int stride_width, int pad_top, int pad_left, bool relu) { const int output_height = (input_height + 2 * pad_top - kernel_height) / stride_height + 1; const int output_width = (input_width + 2 * pad_left - kernel_width) / stride_width + 1; const std::size_t input_batch = std::size_t(input_channels) * input_height * input_width; const std::size_t output_batch = std::size_t(output_channels) * output_height * output_width; std::vector input(std::size_t(batches) * input_batch); std::vector weights(std::size_t(output_channels) * input_channels * kernel_height * kernel_width); std::vector bias(static_cast(output_channels)); std::vector expected(std::size_t(batches) * output_batch); std::vector output(expected.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 43) - 21) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 37) - 18) * .015625F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 17) - 8) * .0625F; for (int batch = 0; batch < batches; ++batch) { ppocr::detail::kernels::Conv2d( expected.data() + std::size_t(batch) * output_batch, input.data() + std::size_t(batch) * input_batch, weights.data(), bias.data(), input_channels, output_channels, input_height, input_width, output_height, output_width, kernel_height, kernel_width, stride_height, stride_width, pad_top, pad_left, relu); } ppocr::detail::kernels::Conv2dBatch( output.data(), input.data(), weights.data(), bias.data(), batches, input_channels, output_channels, input_height, input_width, output_height, output_width, kernel_height, kernel_width, stride_height, stride_width, pad_top, pad_left, relu); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "batched 3x3 mismatch N=" << batches << " C=" << input_channels << " M=" << output_channels << " H=" << input_height << " W=" << input_width << " K=" << kernel_height << 'x' << kernel_width << " stride=" << stride_height << 'x' << stride_width << " relu=" << relu << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyPointwiseHardSwishBatch(int batches, int input_channels, int output_channels, std::size_t plane) { const std::size_t input_batch = std::size_t(input_channels) * plane; const std::size_t output_batch = std::size_t(output_channels) * plane; std::vector input(std::size_t(batches) * input_batch); std::vector weights(std::size_t(output_channels) * input_channels); std::vector bias(static_cast(output_channels)); std::vector expected(std::size_t(batches) * output_batch); std::vector output(expected.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 47) - 23) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 29) - 14) * .03125F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 11) - 5) * .125F; for (int batch = 0; batch < batches; ++batch) { ppocr::detail::kernels::PointwiseConvHardSwish( expected.data() + std::size_t(batch) * output_batch, input.data() + std::size_t(batch) * input_batch, weights.data(), bias.data(), output_channels, input_channels, plane); } ppocr::detail::kernels::PointwiseConvHardSwishBatch( output.data(), input.data(), weights.data(), bias.data(), batches, output_channels, input_channels, plane); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "batched pointwise hardswish mismatch N=" << batches << " C=" << input_channels << " M=" << output_channels << " plane=" << plane << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifySqueezeExcitation(int batches, int channels, int reduced, int height, int width, bool residual) { const std::size_t spatial = std::size_t(height) * width; const std::size_t count = std::size_t(batches) * channels * spatial; std::vector values(count), first_w(std::size_t(reduced) * channels); std::vector first_b(static_cast(reduced)); std::vector second_w(std::size_t(channels) * reduced); std::vector second_b(static_cast(channels)); for (std::size_t i = 0; i < values.size(); ++i) values[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < first_w.size(); ++i) first_w[i] = float(int(i % 23) - 11) * .015625F; for (std::size_t i = 0; i < first_b.size(); ++i) first_b[i] = float(int(i % 13) - 6) * .0625F; for (std::size_t i = 0; i < second_w.size(); ++i) second_w[i] = float(int(i % 29) - 14) * .015625F; for (std::size_t i = 0; i < second_b.size(); ++i) second_b[i] = float(int(i % 11) - 5) * .0625F; std::vector expected = values; constexpr float alpha = .2F, beta = .5F; for (int batch = 0; batch < batches; ++batch) { std::vector means(static_cast(channels), 0.F); for (int channel = 0; channel < channels; ++channel) { const float* plane = expected.data() + (std::size_t(batch) * channels + channel) * spatial; float sum = 0.F; for (std::size_t i = 0; i < spatial; ++i) sum += plane[i]; means[static_cast(channel)] = sum / float(spatial); } std::vector hidden(static_cast(reduced)); for (int output = 0; output < reduced; ++output) { float sum = first_b[static_cast(output)]; const float* weights = first_w.data() + std::size_t(output) * channels; for (int input_channel = 0; input_channel < channels; ++input_channel) sum += weights[input_channel] * means[static_cast(input_channel)]; hidden[static_cast(output)] = std::max(0.F, sum); } for (int output = 0; output < channels; ++output) { float sum = second_b[static_cast(output)]; const float* weights = second_w.data() + std::size_t(output) * reduced; for (int hidden_channel = 0; hidden_channel < reduced; ++hidden_channel) sum += weights[hidden_channel] * hidden[static_cast(hidden_channel)]; const float gate = std::clamp(alpha * sum + beta, 0.F, 1.F); const float scale = residual ? (1.F + gate) : gate; float* plane = expected.data() + (std::size_t(batch) * channels + output) * spatial; for (std::size_t i = 0; i < spatial; ++i) plane[i] *= scale; } } auto output = values; ppocr::detail::kernels::SqueezeExcitationGateInplace( output.data(), first_w.data(), first_b.data(), second_w.data(), second_b.data(), batches, channels, reduced, spatial, alpha, beta, residual); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 5e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "SE gate mismatch residual=" << residual << " C=" << channels << " R=" << reduced << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyConvTransposeScalar(int input_channels, int output_channels, int input_height, int input_width) { const int output_height = input_height * 2; const int output_width = input_width * 2; const std::size_t input_plane = std::size_t(input_channels) * input_height * input_width; const std::size_t output_plane = std::size_t(output_channels) * output_height * output_width; std::vector input(input_plane), weights(std::size_t(input_channels) * output_channels * 4); std::vector bias(static_cast(output_channels)); std::vector expected(output_plane), output(output_plane); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 23) - 11) * .03125F; for (int i = 0; i < output_channels; ++i) bias[static_cast(i)] = float(int(i % 13) - 6) * .0625F; const std::size_t in_hw = std::size_t(input_height) * input_width; const std::size_t out_hw = std::size_t(output_height) * output_width; for (int output_channel = 0; output_channel < output_channels; ++output_channel) { float* out = expected.data() + std::size_t(output_channel) * out_hw; std::fill_n(out, out_hw, bias[static_cast(output_channel)]); for (int input_channel = 0; input_channel < input_channels; ++input_channel) { const float* src = input.data() + std::size_t(input_channel) * in_hw; const float* w = weights.data() + (std::size_t(input_channel) * output_channels + output_channel) * 4; for (int y = 0; y < input_height; ++y) { const float* row = src + std::size_t(y) * input_width; float* dst0 = out + std::size_t(2 * y) * output_width; float* dst1 = dst0 + output_width; for (int x = 0; x < input_width; ++x) { const float value = row[x]; const int xx = 2 * x; dst0[xx] += value * w[0]; dst0[xx + 1] += value * w[1]; dst1[xx] += value * w[2]; dst1[xx + 1] += value * w[3]; } } } } ppocr::detail::kernels::ConvTranspose2x2(output.data(), input.data(), weights.data(), bias.data(), input_channels, output_channels, input_height, input_width); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "transpose2x2 scalar mismatch C=" << input_channels << " M=" << output_channels << " H=" << input_height << " W=" << input_width << " index=" << i << '\n'; return false; } } return true; } bool VerifyConvTransposeAct(int input_channels, int output_channels, int input_height, int input_width, int act) { const int output_height = input_height * 2; const int output_width = input_width * 2; const std::size_t input_plane = std::size_t(input_channels) * input_height * input_width; const std::size_t output_count = std::size_t(output_channels) * output_height * output_width; std::vector input(input_plane), weights(std::size_t(input_channels) * output_channels * 4); std::vector bias(static_cast(output_channels)); std::vector expected(output_count), output(output_count); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 23) - 11) * .03125F; for (int i = 0; i < output_channels; ++i) bias[static_cast(i)] = float(int(i % 13) - 6) * .0625F; ppocr::detail::kernels::ConvTranspose2x2(expected.data(), input.data(), weights.data(), bias.data(), input_channels, output_channels, input_height, input_width, 0); if (act == 1) { for (float& value : expected) value = std::max(value, 0.F); } else if (act == 2) { for (float& value : expected) value = 1.F / (1.F + std::exp(-value)); } ppocr::detail::kernels::ConvTranspose2x2(output.data(), input.data(), weights.data(), bias.data(), input_channels, output_channels, input_height, input_width, act); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = (act == 2 ? 3e-5F : 3e-5F) * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "transpose2x2 act=" << act << " mismatch C=" << input_channels << " M=" << output_channels << " H=" << input_height << " W=" << input_width << " index=" << i << '\n'; return false; } } return true; } bool VerifyConvTransposeChain(int input_channels, int mid_channels, int output_channels, int input_height, int input_width) { const int mid_h = input_height * 2, mid_w = input_width * 2; const int out_h = input_height * 4, out_w = input_width * 4; const std::size_t in_n = std::size_t(input_channels) * input_height * input_width; const std::size_t mid_n = std::size_t(mid_channels) * mid_h * mid_w; const std::size_t out_n = std::size_t(output_channels) * out_h * out_w; std::vector input(in_n), w0(std::size_t(input_channels) * mid_channels * 4); std::vector b0(static_cast(mid_channels)); std::vector w1(std::size_t(mid_channels) * output_channels * 4); std::vector b1(static_cast(output_channels)); std::vector mid(mid_n), expected(out_n), output(out_n); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < w0.size(); ++i) w0[i] = float(int(i % 23) - 11) * .03125F; for (int i = 0; i < mid_channels; ++i) b0[static_cast(i)] = float(int(i % 13) - 6) * .0625F; for (std::size_t i = 0; i < w1.size(); ++i) w1[i] = float(int(i % 19) - 9) * .03125F; for (int i = 0; i < output_channels; ++i) b1[static_cast(i)] = float(int(i % 11) - 5) * .0625F; ppocr::detail::kernels::ConvTranspose2x2(mid.data(), input.data(), w0.data(), b0.data(), input_channels, mid_channels, input_height, input_width, 1); ppocr::detail::kernels::ConvTranspose2x2(expected.data(), mid.data(), w1.data(), b1.data(), mid_channels, output_channels, mid_h, mid_w, 2); ppocr::detail::kernels::ConvTranspose2x2Chain(output.data(), input.data(), w0.data(), b0.data(), w1.data(), b1.data(), input_channels, mid_channels, output_channels, input_height, input_width); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 4e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "transpose2x2 chain mismatch C=" << input_channels << " M=" << mid_channels << " O=" << output_channels << " H=" << input_height << " W=" << input_width << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyConvTransposeBatch(int batches, int input_channels, int output_channels, int input_height, int input_width) { const std::size_t input_batch = std::size_t(input_channels) * input_height * input_width; const std::size_t output_batch = std::size_t(output_channels) * (input_height * 2) * (input_width * 2); std::vector input(std::size_t(batches) * input_batch); std::vector weights(std::size_t(input_channels) * output_channels * 4); std::vector bias(static_cast(output_channels)); std::vector expected(std::size_t(batches) * output_batch); std::vector output(expected.size()); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 41) - 20) * .03125F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 23) - 11) * .03125F; for (std::size_t i = 0; i < bias.size(); ++i) bias[i] = float(int(i % 13) - 6) * .0625F; for (int batch = 0; batch < batches; ++batch) { ppocr::detail::kernels::ConvTranspose2x2( expected.data() + std::size_t(batch) * output_batch, input.data() + std::size_t(batch) * input_batch, weights.data(), bias.data(), input_channels, output_channels, input_height, input_width); } ppocr::detail::kernels::ConvTranspose2x2Batch( output.data(), input.data(), weights.data(), bias.data(), batches, input_channels, output_channels, input_height, input_width); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-5F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "batched transpose2x2 mismatch N=" << batches << " C=" << input_channels << " M=" << output_channels << " H=" << input_height << " W=" << input_width << " index=" << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyBatchNormAffine(int batches, int channels, std::size_t spatial) { const std::size_t count = std::size_t(batches) * channels * spatial; std::vector input(count), scale(channels), shift(channels), expected(count), output(count); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 53) - 26) * .03125F; for (int channel = 0; channel < channels; ++channel) { scale[channel] = float((channel % 11) + 1) * .09375F; shift[channel] = float((channel % 7) - 3) * .125F; } for (int batch = 0; batch < batches; ++batch) for (int channel = 0; channel < channels; ++channel) { const auto offset = (std::size_t(batch) * channels + channel) * spatial; for (std::size_t index = 0; index < spatial; ++index) { expected[offset + index] = input[offset + index] * scale[channel] + shift[channel]; } } ppocr::detail::kernels::BatchNormAffine(output.data(), input.data(), scale.data(), shift.data(), batches, channels, spatial); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-6F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "BatchNormAffine output mismatch at " << i << '\n'; return false; } } auto inplace = input; ppocr::detail::kernels::BatchNormAffine(inplace.data(), inplace.data(), scale.data(), shift.data(), batches, channels, spatial); for (std::size_t i = 0; i < inplace.size(); ++i) { if (inplace[i] != expected[i]) { std::cerr << "BatchNormAffine inplace mismatch at " << i << '\n'; return false; } } return true; } bool VerifyNearestResize2xAdd(int batches, int channels, int height, int width) { const int output_width = width * 2; const std::size_t input_count = std::size_t(batches) * channels * height * width; const std::size_t output_count = input_count * 4; std::vector input(input_count), residual(output_count), expected(output_count), output(output_count); for (std::size_t i = 0; i < input.size(); ++i) input[i] = float(int(i % 59) - 29) * .03125F; for (std::size_t i = 0; i < residual.size(); ++i) residual[i] = float(int(i % 43) - 21) * .015625F; for (int plane = 0; plane < batches * channels; ++plane) { const float* src = input.data() + std::size_t(plane) * height * width; const float* add = residual.data() + std::size_t(plane) * height * width * 4; float* dst = expected.data() + std::size_t(plane) * height * width * 4; for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) { const float value = src[std::size_t(y) * width + x]; const auto offset = std::size_t(y * 2) * output_width + std::size_t(x) * 2; dst[offset] = value + add[offset]; dst[offset + 1] = value + add[offset + 1]; dst[offset + output_width] = value + add[offset + output_width]; dst[offset + output_width + 1] = value + add[offset + output_width + 1]; } } ppocr::detail::kernels::NearestResize2xAdd(output.data(), input.data(), residual.data(), batches, channels, height, width); for (std::size_t i = 0; i < output.size(); ++i) { if (output[i] != expected[i]) { std::cerr << "nearest resize-add mismatch N=" << batches << " C=" << channels << " H=" << height << " W=" << width << " index=" << i << '\n'; return false; } } return true; } bool VerifyAveragePool3x2Valid(int planes, int height, int width) { const int output_height = (height - 3) / 3 + 1; const int output_width = (width - 2) / 2 + 1; const std::size_t input_plane = std::size_t(height) * width; const std::size_t output_plane = std::size_t(output_height) * output_width; std::vector input(std::size_t(planes) * input_plane); std::vector expected(std::size_t(planes) * output_plane), output(expected.size()); for (std::size_t i = 0; i < input.size(); ++i) { input[i] = float(int(i % 37) - 18) * .03125F; } for (int plane = 0; plane < planes; ++plane) { const float* source = input.data() + std::size_t(plane) * input_plane; float* destination = expected.data() + std::size_t(plane) * output_plane; for (int y = 0; y < output_height; ++y) for (int x = 0; x < output_width; ++x) { float sum{}; for (int ky = 0; ky < 3; ++ky) for (int kx = 0; kx < 2; ++kx) { sum += source[std::size_t(y * 3 + ky) * width + x * 2 + kx]; } destination[std::size_t(y) * output_width + x] = sum / 6.F; } } ppocr::detail::kernels::AveragePool3x2Valid(output.data(), input.data(), planes, height, width); for (std::size_t i = 0; i < output.size(); ++i) { const float tolerance = 3e-6F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance) { std::cerr << "average pool 3x2 valid mismatch at " << i << " got=" << output[i] << " expected=" << expected[i] << '\n'; return false; } } return true; } bool VerifyCtcTop1(int batches, int steps, int vocab) { const std::size_t rows = std::size_t(batches) * steps; std::vector logits(rows * vocab); std::vector expected_indices(rows), output_indices(rows); std::vector expected_probabilities(rows), output_probabilities(rows); for (std::size_t row = 0; row < rows; ++row) { for (int column = 0; column < vocab; ++column) { logits[row * vocab + column] = float(int((row * 31 + std::size_t(column) * 17) % 127) - 63) * .03125F; } // Cover blank and repeated top-1 decisions, including vector tails. logits[row * vocab + (row % 5 == 0 ? 0 : int((row + 7) % vocab))] = 3.F + float(row % 3); } ppocr::detail::kernels::CtcTop1Scalar(expected_indices.data(), expected_probabilities.data(), logits.data(), rows, steps, vocab); ppocr::detail::kernels::CtcTop1(output_indices.data(), output_probabilities.data(), logits.data(), rows, steps, vocab); for (std::size_t row = 0; row < rows; ++row) { if (output_indices[row] != expected_indices[row]) { std::cerr << "CTC Top1 index mismatch row=" << row << '\n'; return false; } const float tolerance = 3e-6F * std::max(1.F, std::abs(expected_probabilities[row])); if (std::abs(output_probabilities[row] - expected_probabilities[row]) > tolerance) { std::cerr << "CTC Top1 probability mismatch row=" << row << '\n'; return false; } } return true; } bool VerifyCtcTop1FirstMaximum() { // SIMD ArgMax must retain the scalar executor's first-maximum rule across // vector lanes and tails. Use a 6,906-way row so this covers the production // Latin vocabulary geometry as well as a small vector-tail row. for (const int vocab : {31, 6906}) { const int batches = 1, steps = 4; std::vector logits(std::size_t(batches) * steps * vocab, -2.F); for (int step = 0; step < steps; ++step) { float* row = logits.data() + std::size_t(step) * vocab; row[0] = 1.F; row[std::min(vocab - 1, 3)] = 5.F; row[std::min(vocab - 1, 16)] = 5.F; // tie: first maximum must win. row[vocab - 1] = step == 1 ? 6.F : 5.F; } std::vector expected_indices(steps), output_indices(steps); std::vector expected_probabilities(steps), output_probabilities(steps); ppocr::detail::kernels::CtcTop1Scalar(expected_indices.data(), expected_probabilities.data(), logits.data(), steps, steps, vocab); ppocr::detail::kernels::CtcTop1(output_indices.data(), output_probabilities.data(), logits.data(), steps, steps, vocab); for (int row = 0; row < steps; ++row) { if (output_indices[row] != expected_indices[row] || std::abs(output_probabilities[row] - expected_probabilities[row]) > 1e-5F) { std::cerr << "CTC Top1 first-maximum mismatch vocab=" << vocab << " row=" << row << " expected_index=" << expected_indices[row] << " output_index=" << output_indices[row] << " expected_probability=" << expected_probabilities[row] << " output_probability=" << output_probabilities[row] << '\n'; return false; } } } return true; } bool VerifyGemmCtcTop1(int batches, int steps, int vocab, int depth) { const std::size_t rows = std::size_t(batches) * steps; std::vector left(rows * depth), weights(std::size_t(depth) * vocab), bias(vocab), logits(rows * vocab); std::vector expected_indices(rows), output_indices(rows); std::vector expected_probabilities(rows), output_probabilities(rows); for (std::size_t i = 0; i < left.size(); ++i) left[i] = float(int(i % 37) - 18) * .0625F; for (std::size_t i = 0; i < weights.size(); ++i) weights[i] = float(int(i % 41) - 20) * .03125F; for (int i = 0; i < vocab; ++i) bias[std::size_t(i)] = float(i % 11) * .0625F - .25F; ppocr::detail::kernels::Gemm(logits.data(), left.data(), weights.data(), bias.data(), int(rows), vocab, depth); ppocr::detail::kernels::CtcTop1Scalar(expected_indices.data(), expected_probabilities.data(), logits.data(), rows, steps, vocab); ppocr::detail::kernels::GemmCtcTop1(output_indices.data(), output_probabilities.data(), left.data(), weights.data(), bias.data(), int(rows), depth, vocab, steps); for (std::size_t row = 0; row < rows; ++row) { if (output_indices[row] != expected_indices[row]) { std::cerr << "GEMM CTC Top1 index mismatch row=" << row << '\n'; return false; } const float tolerance = 3e-6F * std::max(1.F, std::abs(expected_probabilities[row])); if (std::abs(output_probabilities[row] - expected_probabilities[row]) > tolerance) { std::cerr << "GEMM CTC Top1 probability mismatch row=" << row << '\n'; return false; } } return true; } bool VerifyApproximateGelu() { // The optional x86 path is intentionally a Padé-tanh GELU approximation. // Validate both in-place and separate buffers against its scalar formula so // AVX-512, AVX2 and scalar tails share identical deployment semantics. // Cross the model-specific approximate-GELU threshold and retain a vector // tail, covering the runtime dispatch used by broad medium feature maps. std::vector input(262144 + 13), output(input.size()), inplace(input.size()); for (std::size_t i = 0; i < input.size(); ++i) { input[i] = float(int(i % 127) - 63) * .0625F; inplace[i] = input[i]; } constexpr float c0 = .7978845608028654F, c1 = .044715F; std::vector expected(input.size()); for (std::size_t i = 0; i < input.size(); ++i) { const float x = input[i]; const float z = c0 * (x + c1 * x * x * x); const float z2 = z * z; const float t = std::clamp(z * (27.F + z2) / (27.F + 9.F * z2), -1.F, 1.F); expected[i] = .5F * x * (1.F + t); } ppocr::detail::kernels::Gelu(output.data(), input.data(), input.size(), input.size()); ppocr::detail::kernels::Gelu(inplace.data(), inplace.data(), inplace.size(), inplace.size()); for (std::size_t i = 0; i < input.size(); ++i) { const float tolerance = 3e-6F * std::max(1.F, std::abs(expected[i])); if (std::abs(output[i] - expected[i]) > tolerance || std::abs(inplace[i] - expected[i]) > tolerance) { std::cerr << "approximate GELU mismatch index=" << i << '\n'; return false; } } return true; } } // namespace int main() { // Drive ENABLE-only MaxPoolConcat/CTC kernels from the shipped functions. #if defined(_WIN32) _putenv("PPOCR_ENABLE_AVX512_MAXPOOL_TINY_PARALLEL=1"); _putenv("PPOCR_ENABLE_AVX512_CONCAT_S2_ROW_PF=1"); _putenv("PPOCR_ENABLE_AVX512_GEMM16NARROW=1"); _putenv("PPOCR_ENABLE_AVX512_POINTWISE16_TINY=1"); _putenv("PPOCR_ENABLE_CTC_VOCAB_BLOCK=1"); _putenv("PPOCR_ENABLE_AVX512_CONCAT_ONEPASS=1"); _putenv("PPOCR_ENABLE_AVX512_TRANSPOSE2X2_TILE4=1"); _putenv("PPOCR_ENABLE_AVX512_RESIZE2X_PF=1"); _putenv("PPOCR_ENABLE_POINTWISE_DEPTHWISE=1"); _putenv("PPOCR_FUSED_TERMINAL_CTC_TILE_ROWS=16"); #else setenv("PPOCR_ENABLE_AVX512_MAXPOOL_TINY_PARALLEL", "1", 1); setenv("PPOCR_ENABLE_AVX512_CONCAT_S2_ROW_PF", "1", 1); setenv("PPOCR_ENABLE_AVX512_GEMM16NARROW", "1", 1); setenv("PPOCR_ENABLE_AVX512_POINTWISE16_TINY", "1", 1); setenv("PPOCR_ENABLE_CTC_VOCAB_BLOCK", "1", 1); setenv("PPOCR_ENABLE_AVX512_CONCAT_ONEPASS", "1", 1); setenv("PPOCR_ENABLE_AVX512_TRANSPOSE2X2_TILE4", "1", 1); setenv("PPOCR_ENABLE_AVX512_RESIZE2X_PF", "1", 1); setenv("PPOCR_ENABLE_POINTWISE_DEPTHWISE", "1", 1); setenv("PPOCR_FUSED_TERMINAL_CTC_TILE_ROWS", "16", 1); #endif // Odd row counts exercise the paired-row SIMD loop plus its scalar/SIMD // tail; odd/vector-boundary column counts cover both vector and scalar // write paths. The last case is recognizer-projection sized. if (!VerifyRgbByteWiden() || !VerifyIdentityRgbToNchw() || !VerifyBilinearRgbToNchw(720, 152, 0, 0, 720, 152, 704, 160, 704, true) || !VerifyBilinearRgbToNchw(64, 32, 2, 1, 37, 19, 41, 17, 48, false) || !VerifyBilinearRgbToNchw(8, 8, 0, 0, 1, 1, 5, 5, 8, false) || !VerifyBilinearRgbToNchw(32, 16, 0, 0, 32, 16, 32, 16, 32, true) || !VerifyExactGelu() || !VerifySigmoid(17) || !VerifySigmoid(262144 + 13) || !VerifyMaxPool2x2Valid(16, 41, 39) || !VerifyMaxPool2x2Valid(3, 17, 19) || !VerifyMaxPool2x2Valid(16, 64, 63) || !VerifyMaxPool2x2Valid(8, 128, 127) || !VerifyMaxPool2x2Same(16, 41, 39) || !VerifyMaxPool2x2Same(8, 64, 63) || !VerifyMaxPool2x2Same(3, 17, 19) || !VerifyMaxPool2x2Same(16, 80, 352)) return 1; if (!VerifyConv3x3Stride2(3, 16, 41, 39, true) || !VerifyConv3x3Stride2(3, 16, 65, 63, true) || !VerifyConv3x3Stride2(3, 16, 160, 128, true) || !VerifyConv3x3Stride2(3, 5, 37, 35, true) || !VerifyConv3x3Stride2(11, 9, 39, 41, false) || !VerifyConv3x3Stride2(24, 48, 41, 39, true) || !VerifyConv3x3Stride2(32, 16, 41, 39, true)) return 1; if (!VerifyConcatChannelConv(4, 16, 16, 41, 39, true) || !VerifyConcatChannelConv(4, 16, 16, 40, 176, true) || !VerifyConcatChannelConv(4, 16, 16, 40, 176, false) || !VerifyConcatChannelConv(3, 5, 8, 17, 19, false) || !VerifyConcatChannelConv(2, 7, 9, 33, 31, true) || !VerifyConcatChannelConv(3, 8, 16, 41, 39, true, 2, 2) || !VerifyConcatChannelConv(2, 16, 16, 80, 352, true, 2, 2) || !VerifyConcatChannelConv(2, 7, 9, 33, 31, false, 2, 2)) return 1; { const int stem_channels[] = {16, 8, 16}; if (!VerifyConcatChannelConvMixed(stem_channels, 3, 16, 41, 39, true, 2, 2) || !VerifyConcatChannelConvMixed(stem_channels, 3, 16, 37, 35, false, 2, 2)) { return 1; } } if (!VerifyConcatConvDualTranspose(4, 16, 16, 1, 9, 11) || !VerifyConcatConvDualTranspose(4, 16, 16, 1, 40, 176)) return 1; if (!VerifyLayerNorm(3, 17) || !VerifyLayerNorm(5, 192) || !VerifyLayerNorm(4, 193)) return 1; for (const auto [rows, cols, depth] : { std::array{1, 1, 1}, std::array{2, 7, 5}, std::array{3, 17, 13}, std::array{5, 33, 31}, std::array{7, 65, 47}, std::array{8, 6913, 192}, // Terminal CTC projections for the shipped recognizers. These // wide, four-row cases exercise the AVX-512 four-row tile and its // lower-precision-free vector/tail handling without requiring an // OCR model load in this low-level smoke binary. std::array{4, 6906, 80}, std::array{16, 6906, 80}, std::array{4, 18710, 120}, std::array{4, 18710, 192}, }) { if (!VerifyGemm(rows, cols, depth, false) || !VerifyGemm(rows, cols, depth, true)) return 1; } // A large square map takes the AVX-512 eight-output Conv tile, while the // odd output count exercises its four-channel fallback/tail. ReLU covers // the detector's fused writeback path. if (!VerifyConv3x3Tile(32, 8, 128, 128, false) || !VerifyConv3x3Tile(32, 9, 128, 128, true) || !VerifyConv3x3Tile(64, 16, 65, 63, true)) return 1; if (!VerifyDetStemFromNchw(41, 39) || !VerifyDetStemFromNchw(160, 128) || !VerifyDetStemFromNchw(64, 64)) return 1; if (!VerifyConv2x2SameUpper(32, 8, 65, 63, false) || !VerifyConv2x2SameUpper(32, 9, 64, 64, true) || !VerifyConv2x2SameUpper(16, 8, 41, 39, true) || !VerifyConv2x2SameUpper(16, 8, 80, 352, true) || !VerifyConv2x2SameUpper(8, 16, 41, 39, true) || !VerifyConv2x2SameUpper(8, 16, 80, 352, true) || !VerifyConv2x2SameUpper(8, 12, 17, 15, false)) return 1; // PP-OCRv6 medium uses 9x9 depthwise MobileNet context filters before a // pointwise projection. Exercise a non-square plane and a tail channel so // AVX-512, AVX2, NEON, and scalar dispatches all cover the same geometry. if (!VerifyDepthwiseBatch(2, 257, 37, 41, 9, 1, 4)) return 1; // The opt-in wide tile has an eight-channel SIMD body and a four-channel // tail. These padded 5x5/7x7 cases cover the medium-detector context path. if (!VerifyConvWideTile(32, 8, 64, 63, 5) || !VerifyConvWideTile(32, 9, 64, 63, 7)) return 1; // Exercise the NCHW cross-crop schedule, including non-SIMD tails and a // work size that crosses the persistent-pool threshold. if (!VerifyPointwiseConv(48, 96, 1284) || !VerifyPointwiseConv(96, 48, 1284) || !VerifyPointwiseConv(48, 97, 1000) || !VerifyPointwiseConv(11, 9, 37) || !VerifyPointwiseConv(16, 16, 7040) || !VerifyPointwiseConv(16, 24, 2112) || !VerifyPointwiseConv(32, 64, 7040) || !VerifyPointwiseConv(32, 64, 272) || !VerifyPointwiseConv(64, 16, 7040) || !VerifyPointwiseConv(64, 16, 272) || !VerifyPointwiseConv(16, 32, 7040) || !VerifyPointwiseConv(16, 32, 80) || !VerifyPointwiseConvRelu(16, 32, 7040) || !VerifyPointwiseConvRelu(16, 32, 80) || !VerifyPointwiseConvRelu(32, 64, 7040) || !VerifyPointwiseConvRelu(7, 9, 37)) { return 1; } if (!VerifyExpandGeluProjectAdd(48, 96, 1284) || !VerifyExpandGeluProjectAdd(48, 96, 1000) || !VerifyExpandGeluProjectAdd(11, 9, 37) || !VerifyExpandGeluProjectAdd(8, 16, 16) || !VerifyExpandGeluProjectAdd(4, 5, 17)) { return 1; } for (int activation = 0; activation < 3; ++activation) { if (!VerifyPointwiseResidualBatch(3, 7, 9, 37, activation)) return 1; } if (!VerifyPointwiseResidualBatch(3, 64, 64, 1024, 0)) return 1; if (!VerifyDepthwiseBatch(3, 5, 17, 19, 3, 1, 1) || !VerifyDepthwiseBatch(3, 19, 31, 33, 5, 2, 2) || !VerifyDepthwiseBatch(1, 64, 40, 176, 5, 1, 2) || !VerifyDepthwiseBatch(1, 64, 17, 19, 5, 1, 2) || !VerifyDepthwiseScalar(64, 40, 176, 5, 2) || !VerifyDepthwiseScalar(7, 17, 19, 5, 2) || !VerifyFoldedDepthwiseHardSwish(16, 6, 76, 1, 5, 0, 2) || !VerifyFoldedDepthwiseHardSwish(7, 9, 11, 3, 3, 1, 1)) return 1; for (int activation = 0; activation < 4; ++activation) { if (!VerifyDepthwisePointwiseFused(16, 16, 40, 176, activation) || !VerifyDepthwisePointwiseFused(8, 6, 17, 19, activation) || !VerifyDepthwisePointwiseFused(5, 7, 9, 11, activation)) { return 1; } } for (int activation = 0; activation < 4; ++activation) { if (!VerifyDepthwisePointwiseFused5x5(64, 16, 40, 176, activation) || !VerifyDepthwisePointwiseFused5x5(7, 5, 9, 11, activation) || !VerifyDepthwisePointwiseFused5x5(16, 8, 17, 19, activation)) { return 1; } } if (!VerifyPointwiseDepthwise(16, 32, 40, 176, true) || !VerifyPointwiseDepthwise(16, 32, 40, 176, false) || !VerifyPointwiseDepthwise(16, 32, 33, 17, true) || !VerifyPointwiseDepthwise(8, 8, 32, 16, false) || !VerifyPointwiseDepthwise(7, 9, 9, 11, true)) { return 1; } if (!VerifyDepthwiseExpandGelu(16, 32, 40, 176) || !VerifyDepthwiseExpandGelu(16, 32, 33, 65) || !VerifyDepthwiseExpandGelu(8, 16, 17, 19) || !VerifyDepthwiseExpandGelu(5, 7, 9, 11) || !VerifyDepthwiseExpandGelu(48, 96, 12, 76) || !VerifyDepthwiseExpandGelu(160, 320, 6, 76)) { return 1; } if (!VerifyConvBatch(3, 3, 16, 41, 39, 3, 3, 2, 2, 1, 1, true) || !VerifyConvBatch(3, 7, 9, 37, 35, 3, 3, 1, 1, 1, 1, false) || !VerifyConvBatch(3, 32, 8, 128, 127, 3, 3, 1, 1, 1, 1, true) || !VerifyConvBatch(3, 11, 13, 39, 41, 3, 3, 2, 2, 1, 1, true) || !VerifyConvBatch(3, 11, 9, 39, 41, 2, 2, 1, 1, 0, 0, true) || !VerifyConvBatch(3, 32, 8, 65, 63, 5, 5, 1, 1, 2, 2, false) || !VerifyConvBatch(3, 32, 9, 65, 63, 7, 7, 1, 1, 3, 3, false) || !VerifyConvBatch(3, 13, 9, 39, 41, 1, 5, 1, 1, 0, 2, false) || !VerifyConvBatch(3, 13, 9, 39, 41, 5, 1, 1, 1, 2, 0, false)) return 1; if (!VerifyPointwiseHardSwishBatch(3, 7, 9, 37) || !VerifyPointwiseHardSwishBatch(3, 64, 64, 1024)) return 1; if (!VerifySqueezeExcitation(1, 16, 4, 40, 176, false) || !VerifySqueezeExcitation(1, 16, 4, 40, 176, true) || !VerifySqueezeExcitation(1, 8, 2, 17, 19, true) || !VerifySqueezeExcitation(2, 24, 6, 12, 37, false)) return 1; if (!VerifyConvTransposeBatch(3, 7, 9, 17, 19) || !VerifyConvTransposeBatch(3, 32, 32, 64, 63) || !VerifyConvTransposeScalar(16, 16, 40, 176) || !VerifyConvTransposeScalar(16, 16, 80, 352) || !VerifyConvTransposeAct(16, 16, 40, 176, 1) || !VerifyConvTransposeAct(16, 16, 80, 352, 2) || !VerifyConvTransposeAct(7, 9, 17, 19, 1) || !VerifyConvTransposeChain(16, 16, 1, 9, 11) || !VerifyConvTransposeChain(16, 16, 1, 40, 176) || !VerifyConvTransposeChain(7, 9, 2, 5, 6)) return 1; if (!VerifyBatchNormAffine(3, 7, 37) || !VerifyBatchNormAffine(3, 64, 1024)) return 1; if (!VerifyNearestResize2xAdd(1, 3, 17, 19) || !VerifyNearestResize2xAdd(2, 64, 64, 63) || !VerifyNearestResize2xAdd(1, 16, 20, 88)) return 1; if (!VerifyAveragePool3x2Valid(3, 17, 19) || !VerifyAveragePool3x2Valid(64, 48, 127)) return 1; if (!VerifyCtcTop1(3, 17, 31) || !VerifyCtcTop1(4, 41, 6906) || !VerifyCtcTop1FirstMaximum()) return 1; if (!VerifyGemmCtcTop1(1, 17, 31, 29) || !VerifyGemmCtcTop1(4, 41, 6906, 192) || !VerifyGemmCtcTop1(1, 4, 18710, 192) || !VerifyGemmCtcTop1(1, 16, 6906, 80)) return 1; if (std::getenv("PPOCR_APPROX_GELU") != nullptr && !VerifyApproximateGelu()) return 1; std::cout << "kernel RGB-upload/identity-rgb/LayerNorm/GEMM/2x2/3x3/residual-pointwise/depthwise/conv/hardswish/transpose/resize-add/BN/GELU/exact-GELU/CTC batch tile/tail smoke passed\n"; }