Skip to content

Commit 7166324

Browse files
committed
Update
[ghstack-poisoned]
1 parent 101b2b4 commit 7166324

8 files changed

Lines changed: 897 additions & 198 deletions

File tree

backends/vulkan/runtime/graph/ops/glsl/conv2d_gemm.glsl

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,28 @@
99
/*
1010
* conv2d_gemm: GEMM step of im2col-backed conv2d.
1111
*
12-
* Reads the im2col'd input produced by conv2d_im2col.glsl as a 2D matrix
13-
* of shape [M, K_total] (M = H_out * W_out, K_total = Kh*Kw*Cin_padded)
14-
* and writes the conv2d output as texture3D channels-packed
15-
* logical shape [1, C_out, H_out, W_out].
12+
* Reads one tile of the im2col'd input produced by conv2d_im2col.glsl — a 2D
13+
* matrix of shape [M_TILE, K_total] holding OH_TILE output-height rows
14+
* (M_TILE = OH_TILE * W_out, K_total = Kh*Kw*Cin_padded) starting at output
15+
* row OH_OFFSET — and writes the corresponding output rows as texture3D
16+
* channels-packed, logical shape [1, C_out, H_out, W_out]. The full im2col
17+
* matrix is processed OH_TILE rows per dispatch so the scratch tensor is bounded
18+
* to a fixed byte budget regardless of resolution; with tiling disabled the
19+
* caller passes OH_OFFSET = 0 and OH_TILE = H_out (one dispatch covers all M).
1620
*
17-
* The im2col input can be any of:
18-
* - texture2d, width-packed: texel at (k4, m) holds 4 K values for row m.
19-
* IN_STORAGE=texture2d codegen.
20-
* - texture3d, channels-packed: texel at (ow, oh, k4) holds 4 K values
21-
* for output spatial position (oh, ow). Used when M would exceed
21+
* The im2col input tile can be any of:
22+
* - texture2d, width-packed: texel at (k4, r) holds 4 K values for tile-local
23+
* row r. IN_STORAGE=texture2d codegen.
24+
* - texture3d, channels-packed: texel at (ow, oh_local, k4) holds 4 K values
25+
* for tile-local row r = oh_local * W_out + ow. Used when M would exceed
2226
* max_texture2d_dim. IN_STORAGE=texture3d codegen.
23-
* - buffer: vec4 at offset m*K4 + k4, same K packing.
27+
* - buffer: vec4 at offset r*K4 + k4, same K packing.
2428
* IN_STORAGE=buffer codegen.
2529
*
26-
* The matmul interpretation is:
27-
* out[m, n] = sum_k im2col[m, k] * weight[n, k] + bias[n]
28-
* with M = H_out * W_out, K = K_total, N = C_out.
30+
* The matmul interpretation (over this tile's rows) is:
31+
* out[r, n] = sum_k im2col[r, k] * weight[n, k] + bias[n]
32+
* with K = K_total, N = C_out, and r the tile-local row whose global output
33+
* spatial position is (OH_OFFSET + r / W_out, r % W_out).
2934
*/
3035

3136
#version 450 core
@@ -85,14 +90,23 @@ ${layout_declare_ubo(B, "ivec4", "out_sizes")}
8590
// dims), so it is safe to bake at build time even under dynamic shapes.
8691
// M = H_out * W_out IS shape-dependent, so it is derived at runtime from the
8792
// refreshed out_sizes UBO in main() rather than read from here.
93+
//
94+
// This dispatch consumes one tile of the im2col matrix: OH_TILE output-height
95+
// rows starting at output-height row OH_OFFSET. The im2col scratch (t_in) holds
96+
// OH_TILE * W_out tile-local rows; the GEMM reads tile-local rows and writes the
97+
// output at the corresponding global spatial position (OH_OFFSET + oh_local,
98+
// ow). OH_OFFSET / OH_TILE are shape-independent (fixed at build time); the
99+
// global W_out / H_out come from the refreshed out_sizes UBO.
88100
layout(push_constant) uniform restrict Block {
89-
ivec4 gemm_dims; // (K4_total, _unused, _unused, _unused)
101+
ivec4 gemm_dims; // (K4_total, OH_OFFSET, OH_TILE, _unused)
90102
vec4 clamp_vals; // (out_min, out_max, _unused, _unused)
91103
};
92104

93-
#define K4_TOTAL gemm_dims.x
94-
#define OUT_MIN clamp_vals.x
95-
#define OUT_MAX clamp_vals.y
105+
#define K4_TOTAL gemm_dims.x
106+
#define OH_OFFSET gemm_dims.y
107+
#define OH_TILE gemm_dims.z
108+
#define OUT_MIN clamp_vals.x
109+
#define OUT_MAX clamp_vals.y
96110

97111
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
98112

@@ -104,30 +118,32 @@ ${layout_declare_spec_const(C, "int", "activation_type", "0")}
104118

105119
/*
106120
* Load TILE_M rows × TILE_K4 K-tiles of the im2col'd input.
107-
* The im2col output is a contiguous (M, K_total/4) matrix of vec4s, so the
108-
* load is a plain 2D fetch — no spatial decomposition.
121+
* The im2col scratch holds M_TILE tile-local rows in a contiguous
122+
* (M_TILE, K_total/4) matrix of vec4s; row here is the tile-local index, so the
123+
* load is a plain 2D fetch — no spatial decomposition. (The output store, not
124+
* this load, maps the tile-local row to its global spatial position.)
109125
*/
110126
void load_input_tile_with_checks(
111127
out FPInputTile tile,
112128
const int k4_start,
113129
const int m_start,
114130
const int K4,
115-
const int M,
131+
const int M_TILE,
116132
const int W_out) {
117133
// W_out is only consumed by the texture3d variant below.
118134
[[unroll]] for (int m = 0; m < TILE_M; ++m) {
119135
[[unroll]] for (int k4 = 0; k4 < TILE_K4; ++k4) {
120-
if (k4_start + k4 < K4 && m_start + m < M) {
136+
if (k4_start + k4 < K4 && m_start + m < M_TILE) {
121137
const int row = m_start + m;
122138
const int col = k4_start + k4;
123139
#if defined(INPUT_BUFFER)
124140
// Cast SSBO texel into the input tile type (f16vec4 for half, vec4 for
125141
// float).
126142
tile.data[m][k4] = LINEAR_FP_INPUT_TILE_VEC4_T(t_in[row * K4 + col]);
127143
#elif defined(INPUT_TEXTURE3D)
128-
// texture3d layout: row (the flat M index) decomposes into (ow, oh)
129-
// and K4 is along the Z axis. texelFetch returns vec4 (fp32); cast to
130-
// the input tile type.
144+
// texture3d scratch [1, K_total, OH_TILE, W_out]: the tile-local row
145+
// decomposes into (ow, oh_local) and K4 is along the Z axis. texelFetch
146+
// returns vec4 (fp32); cast to the input tile type.
131147
tile.data[m][k4] = LINEAR_FP_INPUT_TILE_VEC4_T(
132148
texelFetch(t_in, ivec3(row % W_out, row / W_out, col), 0));
133149
#else
@@ -141,17 +157,24 @@ void load_input_tile_with_checks(
141157
}
142158
}
143159

160+
// m_start is a tile-local row offset; the scratch read uses it directly, but
161+
// the output store maps it to the GLOBAL spatial position via oh_global =
162+
// OH_OFFSET + (m_local / W_out). Rows whose global oh lands past H_out (the
163+
// partial trailing tile, or a dynamic shape that shrinks H_out) are skipped.
144164
void store_output_tile_with_checks(
145165
const FPOutTile out_tile,
146166
const int n4_start,
147167
const int m_start,
148168
const int N4,
149-
const int M,
169+
const int M_TILE,
170+
const int H_out,
150171
const int W_out) {
151172
[[unroll]] for (int m = 0; m < TILE_M; ++m) {
152173
[[unroll]] for (int n4 = 0; n4 < TILE_N4; ++n4) {
153-
if (m_start + m < M && n4_start + n4 < N4) {
154-
const int spatial = m_start + m;
174+
const int m_local = m_start + m;
175+
const int ow = m_local % W_out;
176+
const int oh = OH_OFFSET + m_local / W_out;
177+
if (m_local < M_TILE && oh < H_out && n4_start + n4 < N4) {
155178
// Cast the accumulator (f16vec4 for the buffer/half path) to the
156179
// texture3d output surface type for the activation clamp and store.
157180
OUT_VEC4_T texel = OUT_VEC4_T(out_tile.data[m][n4]);
@@ -160,8 +183,7 @@ void store_output_tile_with_checks(
160183
} else if (activation_type == 2) {
161184
texel = clamp(texel, OUT_VEC4_T(OUT_MIN), OUT_VEC4_T(OUT_MAX));
162185
}
163-
imageStore(
164-
t_out, ivec3(spatial % W_out, spatial / W_out, n4_start + n4), texel);
186+
imageStore(t_out, ivec3(ow, oh, n4_start + n4), texel);
165187
}
166188
}
167189
}
@@ -176,14 +198,16 @@ void main() {
176198

177199
const int W_out = out_sizes.x;
178200
const int H_out = out_sizes.y;
179-
// M = H_out * W_out is derived from the refreshed out_sizes UBO so it tracks
201+
// W_out / H_out are derived from the refreshed out_sizes UBO so they track
180202
// dynamic output shapes (out_sizes is virtual_resize'd on trigger_resize).
181-
const int M = W_out * H_out;
203+
// M_TILE = OH_TILE * W_out is the tile-local row count materialized in the
204+
// im2col scratch (t_in); the GEMM reads scratch rows in [0, M_TILE).
205+
const int M_TILE = OH_TILE * W_out;
182206
const int K4 = K4_TOTAL;
183207
const int N = out_sizes.z;
184208
const int N4 = div_up_4(N);
185209

186-
if (n4_start >= N4 || m_start >= M) {
210+
if (n4_start >= N4 || m_start >= M_TILE) {
187211
return;
188212
}
189213

@@ -194,7 +218,7 @@ void main() {
194218
FPWeightTile w_tile;
195219

196220
for (int k4 = 0; k4 < K4; k4 += TILE_K4) {
197-
load_input_tile_with_checks(in_tile, k4, m_start, K4, M, W_out);
221+
load_input_tile_with_checks(in_tile, k4, m_start, K4, M_TILE, W_out);
198222
load_packed_weight_tile_with_checks(w_tile, n4_start, k4, 0, N4, K4);
199223
fp_accumulate_with_fp_weight(out_tile, in_tile, w_tile);
200224
}
@@ -213,5 +237,6 @@ void main() {
213237
}
214238
}
215239

216-
store_output_tile_with_checks(out_tile, n4_start, m_start, N4, M, W_out);
240+
store_output_tile_with_checks(
241+
out_tile, n4_start, m_start, N4, M_TILE, H_out, W_out);
217242
}

backends/vulkan/runtime/graph/ops/glsl/conv2d_im2col.glsl

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,31 @@
99
/*
1010
* Im2col transformation for FP32 / FP16 conv2d.
1111
*
12-
* The output is a 2D matrix of shape [M, K_total] where
12+
* One dispatch materializes a tile of OH_TILE output-height rows (full W_out
13+
* each) of the im2col matrix, starting at output-height row OH_OFFSET. The full
14+
* matrix has logical shape [M, K_total] where
1315
* M = H_out * W_out (number of output spatial positions)
1416
* K_total = Kh * Kw * align_up_4(C_in) (flattened receptive field)
1517
*
18+
* Tiling by output-height rows bounds the scratch tensor to a fixed byte budget
19+
* regardless of resolution: the scratch holds OH_TILE * W_out rows, not M. A
20+
* tile-local row m_local decodes to oh_local = m_local / W_out,
21+
* ow = m_local % W_out; the SOURCE spatial position uses the global output row
22+
* oh = OH_OFFSET + oh_local. Tiling by H rows (rather than flat M rows) keeps
23+
* this row->(oh,ow) decode exact for the spatial texture3d layout too. When
24+
* tiling is disabled the caller passes OH_OFFSET = 0 and OH_TILE = H_out.
25+
*
1626
* K layout (so a 4-tile in K — one vec4 — holds the same kernel position):
1727
* K = (ki * Kw + kj) * Cin_padded + ci
1828
*
1929
* Three codegen'd storage variants of the output tensor:
20-
* - texture2d, width-packed: texel at (k4, m) holds 4 K values for spatial
21-
* position m. Extents = (K_total/4, M).
22-
* - texture3d, channels-packed: texel at (ow, oh, k4) holds 4 K values
23-
* for output spatial position (oh, ow). Extents = (W_out, H_out, K4).
24-
* Used as a fallback when M would exceed max_texture2d_dim.
25-
* - buffer: vec4 at offset (m * K4 + k4), same K packing.
30+
* - texture2d, width-packed: texel at (k4, m_local) holds 4 K values for
31+
* tile-local row m_local. Extents = (K_total/4, OH_TILE * W_out).
32+
* - texture3d, channels-packed: texel at (ow, oh_local, k4) holds 4 K values
33+
* for output spatial position (OH_OFFSET + oh_local, ow). Extents =
34+
* (W_out, OH_TILE, K4). Used as a fallback when M would exceed
35+
* max_texture2d_dim.
36+
* - buffer: vec4 at offset (m_local * K4 + k4), same K packing.
2637
*
2738
* The caller picks storage per device (Mali → buffer; others → texture2d
2839
* when its 2D extents fit, texture3d when its 3D extents fit, else buffer).
@@ -62,7 +73,7 @@ ${layout_declare_ubo(B, "ivec4", "in_sizes")}
6273
layout(push_constant) uniform restrict Block {
6374
ivec4 kernel_stride; // (Kh, Kw, Sh, Sw)
6475
ivec4 padding_dil; // (Ph, Pw, Dh, Dw)
65-
ivec4 dims; // (Cin_padded, _unused, _unused, K4_total)
76+
ivec4 dims; // (Cin_padded, OH_OFFSET, OH_TILE, K4_total)
6677
};
6778

6879
#define KERNEL_H kernel_stride.x
@@ -74,13 +85,17 @@ layout(push_constant) uniform restrict Block {
7485
#define DILATION_H padding_dil.z
7586
#define DILATION_W padding_dil.w
7687
#define CIN_PADDED dims.x
88+
#define OH_OFFSET dims.y
89+
#define OH_TILE dims.z
7790
#define K4_TOTAL dims.w
7891

7992
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
8093

8194
void main() {
8295
const int k4 = int(gl_GlobalInvocationID.x);
83-
const int m = int(gl_GlobalInvocationID.y);
96+
// gl_GlobalInvocationID.y is the tile-local row m_local within this tile's
97+
// OH_TILE * W_out rows; it maps to the global output row via OH_OFFSET.
98+
const int m_local = int(gl_GlobalInvocationID.y);
8499

85100
// Derive the spatial output extents from the (refreshed-on-resize) input
86101
// sizes UBO so the im2col mapping tracks dynamic input shapes. in_sizes is
@@ -92,12 +107,20 @@ void main() {
92107
const int H_OUT =
93108
(in_sizes.y + 2 * PADDING_H - DILATION_H * (KERNEL_H - 1) - 1) / STRIDE_H +
94109
1;
95-
const int M = H_OUT * W_OUT;
110+
// Rows materialized by this tile (capped to the scratch extent).
111+
const int M_TILE = OH_TILE * W_OUT;
96112

97-
if (k4 >= K4_TOTAL || m >= M) {
113+
if (k4 >= K4_TOTAL || m_local >= M_TILE) {
98114
return;
99115
}
100116

117+
// Tile-local row m_local -> (oh_local, ow); global output row oh = OH_OFFSET +
118+
// oh_local. Rows past the real H_OUT (in a partial trailing tile, or when a
119+
// dynamic shape shrinks H_OUT below the build-time OH_OFFSET) write zeros.
120+
const int oh_local = m_local / W_OUT;
121+
const int ow = m_local % W_OUT;
122+
const int oh = OH_OFFSET + oh_local;
123+
101124
const int k_start = k4 * 4;
102125

103126
// K = (ki * Kw + kj) * Cin_padded + ci ; since Cin_padded % 4 == 0, all 4
@@ -109,24 +132,20 @@ void main() {
109132
const int ki = krow_idx / KERNEL_W;
110133
const int ci_blk = ci_start >> 2; // ci_start / 4
111134

112-
// Decompose flat output position m back into (oh, ow).
113-
const int ow = m % W_OUT;
114-
const int oh = m / W_OUT;
115-
116135
// Compute the input spatial position for this (oh, ow, ki, kj).
117136
const int ih = oh * STRIDE_H - PADDING_H + ki * DILATION_H;
118137
const int iw = ow * STRIDE_W - PADDING_W + kj * DILATION_W;
119138

120139
VEC4_T out_texel = VEC4_T(0);
121-
if (ih >= 0 && ih < in_sizes.y && iw >= 0 && iw < in_sizes.x) {
140+
if (oh < H_OUT && ih >= 0 && ih < in_sizes.y && iw >= 0 && iw < in_sizes.x) {
122141
out_texel = texelFetch(t_in, ivec3(iw, ih, ci_blk), 0);
123142
}
124143

125144
#if defined(OUTPUT_BUFFER)
126-
t_out[m * K4_TOTAL + k4] = VEC4_BUF_T(out_texel);
145+
t_out[m_local * K4_TOTAL + k4] = VEC4_BUF_T(out_texel);
127146
#elif defined(OUTPUT_TEXTURE3D)
128-
imageStore(t_out, ivec3(ow, oh, k4), out_texel);
147+
imageStore(t_out, ivec3(ow, oh_local, k4), out_texel);
129148
#else
130-
imageStore(t_out, ivec2(k4, m), out_texel);
149+
imageStore(t_out, ivec2(k4, m_local), out_texel);
131150
#endif
132151
}

0 commit comments

Comments
 (0)