Skip to content

Commit 96051d4

Browse files
msluszniakSS-JIA
authored andcommitted
Vulkan: hoist the pointwise conv spatial divides out of the K loop
conv2d_pw_tiled recovers the (x, y) texel coordinate from the flattened spatial index with a divide and a modulo by W_out. W_out is a runtime uniform, so neither strength reduces to a shift, and both sat inside the loop over K: once per (m, k4) on the input loads, and again per output texel on the store. The coordinates depend only on m, so a conv with K4 = 4 issued four times as many integer divides as the arithmetic they fed. Compute them once per invocation and pass them down. On a Mali-G76, selfie segmentation fp16 at 256x256 spends 66% of its GPU time in this shader, and that drops from 8.02 ms to 4.18 ms (1.92x); whole-graph GPU time goes 12.2 ms to 8.3 ms and end-to-end inference 14.6 ms to 10.7 ms. Output is bit identical before and after. (cherry picked from commit 2e6e14e)
1 parent aaa31ad commit 96051d4

1 file changed

Lines changed: 57 additions & 28 deletions

File tree

‎backends/vulkan/runtime/graph/ops/glsl/conv2d_pw_tiled.glsl‎

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -51,35 +51,60 @@ ${layout_declare_spec_const(C, "int", "activation_type", "0")}
5151
#include "linear_fp_packed_weight_tile_load.glslh"
5252
#include "linear_fp_output_tile_fp_compute.glslh"
5353

54-
void load_input_tile_with_checks(
55-
out FPInputTile tile,
56-
const int k4_start,
54+
// Texel coordinates for the TILE_M spatial positions this invocation covers.
55+
//
56+
// Recovering (x, y) from the flattened spatial index costs a division and a
57+
// modulo by W_out, which is a runtime uniform, so the compiler cannot strength
58+
// reduce either into a shift. Those coordinates depend only on m, so computing
59+
// them once per invocation keeps the K loop free of integer division; done
60+
// per (m, k4) instead, a conv with K4 = 4 issues four times as many divides as
61+
// the arithmetic they feed.
62+
struct InCoords {
63+
ivec2 pos[TILE_M];
64+
ivec2 out_pos[TILE_M];
65+
bool valid[TILE_M];
66+
};
67+
68+
void compute_in_coords(
69+
out InCoords coords,
5770
const int m_start,
58-
const int K4,
5971
const int M,
6072
const int W_out,
6173
const int W_in,
6274
const int H_in) {
75+
[[unroll]] for (int m = 0; m < TILE_M; ++m) {
76+
const int out_spatial = m_start + m;
77+
if (out_spatial >= M) {
78+
coords.pos[m] = ivec2(0);
79+
coords.out_pos[m] = ivec2(0);
80+
coords.valid[m] = false;
81+
continue;
82+
}
83+
const int out_x = out_spatial % W_out;
84+
const int out_y = out_spatial / W_out;
85+
coords.out_pos[m] = ivec2(out_x, out_y);
86+
if (stride_1_padding_0 != 0) {
87+
coords.pos[m] = ivec2(out_x, out_y);
88+
coords.valid[m] = true;
89+
} else {
90+
const int in_x = out_x * stride_w - padding_w;
91+
const int in_y = out_y * stride_h - padding_h;
92+
coords.pos[m] = ivec2(in_x, in_y);
93+
coords.valid[m] = in_x >= 0 && in_x < W_in && in_y >= 0 && in_y < H_in;
94+
}
95+
}
96+
}
97+
98+
void load_input_tile_with_checks(
99+
out FPInputTile tile,
100+
const InCoords coords,
101+
const int k4_start,
102+
const int K4) {
63103
[[unroll]] for (int m = 0; m < TILE_M; ++m) {
64104
[[unroll]] for (int k4 = 0; k4 < TILE_K4; ++k4) {
65-
if (k4_start + k4 < K4 && m_start + m < M) {
66-
if (stride_1_padding_0 != 0) {
67-
const int spatial = m_start + m;
68-
tile.data[m][k4] =
69-
texelFetch(t_in, ivec3(spatial % W_out, spatial / W_out, k4_start + k4), 0);
70-
} else {
71-
const int out_spatial = m_start + m;
72-
const int out_x = out_spatial % W_out;
73-
const int out_y = out_spatial / W_out;
74-
const int in_x = out_x * stride_w - padding_w;
75-
const int in_y = out_y * stride_h - padding_h;
76-
if (in_x >= 0 && in_x < W_in && in_y >= 0 && in_y < H_in) {
77-
tile.data[m][k4] =
78-
texelFetch(t_in, ivec3(in_x, in_y, k4_start + k4), 0);
79-
} else {
80-
tile.data[m][k4] = VEC4_T(0.0);
81-
}
82-
}
105+
if (k4_start + k4 < K4 && coords.valid[m]) {
106+
tile.data[m][k4] =
107+
texelFetch(t_in, ivec3(coords.pos[m], k4_start + k4), 0);
83108
} else {
84109
tile.data[m][k4] = VEC4_T(0.0);
85110
}
@@ -89,22 +114,23 @@ void load_input_tile_with_checks(
89114

90115
void store_output_tile_with_checks(
91116
const FPOutTile out_tile,
117+
const InCoords coords,
92118
const int n4_start,
93119
const int m_start,
94120
const int N4,
95-
const int M,
96-
const int W_out) {
121+
const int M) {
122+
// Reuse the coordinates computed for this invocation rather than dividing by
123+
// W_out again per output texel.
97124
[[unroll]] for (int m = 0; m < TILE_M; ++m) {
98125
[[unroll]] for (int n4 = 0; n4 < TILE_N4; ++n4) {
99126
if (m_start + m < M && n4_start + n4 < N4) {
100-
const int spatial = m_start + m;
101127
VEC4_T texel = out_tile.data[m][n4];
102128
if (activation_type == 1) {
103129
texel = max(texel, VEC4_T(0.0));
104130
} else if (activation_type == 2) {
105131
texel = clamp(texel, VEC4_T(out_min), VEC4_T(out_max));
106132
}
107-
imageStore(t_out, ivec3(spatial % W_out, spatial / W_out, n4_start + n4), texel);
133+
imageStore(t_out, ivec3(coords.out_pos[m], n4_start + n4), texel);
108134
}
109135
}
110136
}
@@ -138,8 +164,11 @@ void main() {
138164
FPInputTile in_tile;
139165
FPWeightTile w_tile;
140166

167+
InCoords in_coords;
168+
compute_in_coords(in_coords, m_start, M, W_out, W_in, H_in);
169+
141170
for (int k4 = 0; k4 < K4; k4++) {
142-
load_input_tile_with_checks(in_tile, k4, m_start, K4, M, W_out, W_in, H_in);
171+
load_input_tile_with_checks(in_tile, in_coords, k4, K4);
143172
load_packed_weight_tile_with_checks(w_tile, n4_start, k4, 0, N4, K4);
144173
fp_accumulate_with_fp_weight(out_tile, in_tile, w_tile);
145174
}
@@ -154,5 +183,5 @@ void main() {
154183
}
155184
}
156185

157-
store_output_tile_with_checks(out_tile, n4_start, m_start, N4, M, W_out);
186+
store_output_tile_with_checks(out_tile, in_coords, n4_start, m_start, N4, M);
158187
}

0 commit comments

Comments
 (0)