Skip to content

Commit d52d9f3

Browse files
authored
Vulkan: take im2col when the gather is cheap, not only when c_out is large
**Updated twice; this is the third rule.** Both earlier versions regressed a model, so the history is worth stating: - A `c_out` gate regressed fast_sam on Mali by 18% (GPU 345 -> 405 ms). Its 80x80 convolutions have `c_out` 64, so the gate pushed them onto the direct shader. - Bounding the gather size regressed fcn_resnet50 by 19% (2938 -> 3505 ms). Its head is a 2048->512 3x3 at 65x65, which wants a 77.9M element gather but reads it back 512 times, so it is exactly the case im2col exists for. Neither variable works alone because they are **independent reasons** to take im2col. A large gather is fine when `c_out` is large, since the GEMM amortizes it `c_out` times. When `c_out` is small it is only worth paying if the matrix is small outright, which is where the direct shader on Mali loses badly. So this keeps upstream's `c_out` rule and adds a cheap-gather disjunct, scoped to Mali so no other device changes behaviour. Mali-G76 (Galaxy S10+), fp16, interleaved GPU timestamps, median. "before" is upstream today: | model | before | c_out gate | gather bound | **this** | | --- | --- | --- | --- | --- | | style_transfer_candy @640 | 1912.15 | 1436.31 | 1410.94 | **1411.67** | | fast_sam s @640 | 345.09 | 404.54 | n/a | **341.61** | | fcn_resnet50 @520 | 2938.13 | 2993.47 | 3505.06 | **2948.10** | fcn gives up 0.3%, at the edge of the run-to-run range. The other two are wins, and neither earlier rule was a win on all three. Regression check (these runners also carry pytorch#22784, which is why the mobilenet models move): | model | before | this | | --- | --- | --- | | lraspp @520 | 71.28 | 67.78 | | deeplabv3-mnv3 @520 | 165.27 | 161.76 | | selfie @256 | 6.49 | 6.18 | | ssdlite320 | 37.79 | 35.80 | 32M is calibrated on this device, not derived. The cases it separates are far apart (~4M and ~30M on one side, ~100M and up on the other), so the exact value is not delicate, but a maintainer with other Mali parts may want to revisit it. Adreno is deliberately untouched. cc @SS-JIA @manuelcandales @digantdesai @cbilgin
1 parent 94a7536 commit d52d9f3

1 file changed

Lines changed: 22 additions & 5 deletions

File tree

‎backends/vulkan/runtime/graph/ops/impl/Convolution.cpp‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,15 +312,24 @@ Conv2dMethod get_conv2d_method(
312312
// - groups == 1
313313
// - dilation == 1 (all dims)
314314
//
315-
// Selection rule: use im2col on Mali universally, or once the output channel
316-
// count is large enough to amortize the fixed ~N*K_total im2col gather cost.
315+
// Selection rule: use im2col once the output channel count is large enough to
316+
// amortize the fixed ~N*K_total im2col gather cost.
317317
constexpr int64_t kIm2colMinCOut = 128;
318318

319+
// A cheap gather is a second, independent reason to take im2col. The gather
320+
// materializes an N x K_total matrix before the GEMM runs, and a large gather
321+
// is fine when c_out is large because the GEMM reads it back c_out times. When
322+
// c_out is small it is only worth paying if the matrix is small outright, which
323+
// is where the direct shader on Mali loses badly: an 80x80 3x3 conv wants ~4M
324+
// elements, a 640x640 9x9 conv wants ~100M.
325+
constexpr int64_t kIm2colMaxCheapGatherElements = 32 * 1024 * 1024;
326+
319327
bool should_use_conv2d_im2col(
320328
ComputeGraph& graph,
321329
const ValueRef weight_data,
322330
const int64_t groups_val,
323-
const Kernel2dParams& kernel_params) {
331+
const Kernel2dParams& kernel_params,
332+
const ValueRef out) {
324333
if (groups_val != 1) {
325334
return false;
326335
}
@@ -329,7 +338,14 @@ bool should_use_conv2d_im2col(
329338
}
330339
const auto weight_sizes = graph.sizes_of(weight_data);
331340
const int64_t c_out = weight_sizes.at(0);
332-
return graph.device_is_mali() || c_out >= kIm2colMinCOut;
341+
const int64_t k_total =
342+
weight_sizes.at(1) * weight_sizes.at(2) * weight_sizes.at(3);
343+
344+
const auto out_sizes = graph.sizes_of(out);
345+
const size_t ndim = out_sizes.size();
346+
const int64_t n = out_sizes.at(ndim - 1) * out_sizes.at(ndim - 2);
347+
return c_out >= kIm2colMinCOut ||
348+
(graph.device_is_mali() && n * k_total <= kIm2colMaxCheapGatherElements);
333349
}
334350

335351
GlobalWorkGrid create_conv2d_gwg(
@@ -539,7 +555,8 @@ void add_conv2d_node(
539555
// device); the default (false) reproduces the production routing exactly.
540556
const bool use_im2col = !force_direct &&
541557
method == Conv2dMethod::SlidingWindow &&
542-
should_use_conv2d_im2col(graph, weight_data, groups_val, kernel_params);
558+
should_use_conv2d_im2col(
559+
graph, weight_data, groups_val, kernel_params, out);
543560
if (use_im2col) {
544561
return conv2d_gemm_impl(
545562
graph,

0 commit comments

Comments
 (0)