|
| 1 | +/* |
| 2 | + * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | + * All rights reserved. |
| 4 | + * |
| 5 | + * This source code is licensed under the BSD-style license found in the |
| 6 | + * LICENSE file in the root directory of this source tree. |
| 7 | + */ |
| 8 | + |
| 9 | +/* |
| 10 | + * conv2d_gemm: GEMM step of im2col-backed conv2d. |
| 11 | + * |
| 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]. |
| 16 | + * |
| 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 |
| 22 | + * max_texture2d_dim. IN_STORAGE=texture3d codegen. |
| 23 | + * - buffer: vec4 at offset m*K4 + k4, same K packing. |
| 24 | + * IN_STORAGE=buffer codegen. |
| 25 | + * |
| 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. |
| 29 | + */ |
| 30 | + |
| 31 | +#version 450 core |
| 32 | + |
| 33 | +#define PRECISION ${PRECISION} |
| 34 | + |
| 35 | +$if IN_STORAGE == "buffer" and DTYPE == "half": |
| 36 | + ${define_explicit_type_extensions(DTYPE)} |
| 37 | + |
| 38 | +// VEC4_T is the input storage's natural texel type, which is also the tile type |
| 39 | +// (the linear_fp_*_tile headers default the tile vec4 type to VEC4_T). For the |
| 40 | +// buffer/half path this resolves to f16vec4, so the GEMM inner loop accumulates |
| 41 | +// in true FP16 — the fma emits mad.f16 and the accumulators live in half-width |
| 42 | +// registers. Texture-sampled half always returns vec4, so FP16 accumulation is |
| 43 | +// naturally confined to the buffer (Mali) path; the texture variants (Adreno), |
| 44 | +// where FP16 accumulation regresses, stay vec4 / FP32 with no extra gating. |
| 45 | +#define VEC4_T ${texel_load_type(DTYPE, IN_STORAGE)} |
| 46 | + |
| 47 | +// OUT_VEC4_T is the output surface type. t_out is always texture3d, whose |
| 48 | +// imageStore ABI takes vec4 (fp32) regardless of DTYPE, so the accumulator tile |
| 49 | +// is cast from VEC4_T to OUT_VEC4_T at store time. |
| 50 | +#define OUT_VEC4_T ${texel_load_type(DTYPE, "texture3d")} |
| 51 | + |
| 52 | +#define TILE_M4 ${TILE_M4} |
| 53 | +#define TILE_K4 ${TILE_K4} |
| 54 | +#define TILE_N4 ${TILE_N4} |
| 55 | + |
| 56 | +#define TILE_M ${TILE_M} |
| 57 | +#define TILE_K ${TILE_K4 * 4} |
| 58 | +#define TILE_N ${TILE_N4 * 4} |
| 59 | + |
| 60 | +$if IN_STORAGE == "buffer": |
| 61 | + #define INPUT_BUFFER |
| 62 | +$elif IN_STORAGE == "texture3d": |
| 63 | + #define INPUT_TEXTURE3D |
| 64 | + |
| 65 | +${define_required_extensions("texture3d", DTYPE)} |
| 66 | +$if IN_STORAGE == "buffer": |
| 67 | + ${define_required_extensions("buffer", DTYPE)} |
| 68 | + |
| 69 | +layout(std430) buffer; |
| 70 | + |
| 71 | +#include "common.glslh" |
| 72 | + |
| 73 | +${layout_declare_tensor(B, "w", "t_out", DTYPE, "texture3d")} |
| 74 | +$if IN_STORAGE == "buffer": |
| 75 | + ${layout_declare_tensor(B, "r", "t_in", DTYPE, "buffer", is_scalar_array=False)} |
| 76 | +$else: |
| 77 | + ${layout_declare_tensor(B, "r", "t_in", DTYPE, IN_STORAGE)} |
| 78 | +${layout_declare_tensor(B, "r", "t_weight_packed", DTYPE, "texture2d")} |
| 79 | +${layout_declare_tensor(B, "r", "t_bias", DTYPE, "texture2d")} |
| 80 | + |
| 81 | +${layout_declare_ubo(B, "ivec4", "out_sizes")} |
| 82 | + |
| 83 | +// Push constants are uploaded in 16-byte chunks (one ivec4 each). |
| 84 | +layout(push_constant) uniform restrict Block { |
| 85 | + ivec4 gemm_dims; // (K_total, K4_total, M, _unused) |
| 86 | + vec4 clamp_vals; // (out_min, out_max, _unused, _unused) |
| 87 | +}; |
| 88 | + |
| 89 | +#define K_TOTAL gemm_dims.x |
| 90 | +#define K4_TOTAL gemm_dims.y |
| 91 | +#define M_TOTAL gemm_dims.z |
| 92 | +#define OUT_MIN clamp_vals.x |
| 93 | +#define OUT_MAX clamp_vals.y |
| 94 | + |
| 95 | +layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in; |
| 96 | + |
| 97 | +${layout_declare_spec_const(C, "int", "activation_type", "0")} |
| 98 | + |
| 99 | +#include "linear_fp_input_tile.glslh" |
| 100 | +#include "linear_fp_packed_weight_tile_load.glslh" |
| 101 | +#include "linear_fp_output_tile_fp_compute.glslh" |
| 102 | + |
| 103 | +/* |
| 104 | + * Load TILE_M rows × TILE_K4 K-tiles of the im2col'd input. |
| 105 | + * The im2col output is a contiguous (M, K_total/4) matrix of vec4s, so the |
| 106 | + * load is a plain 2D fetch — no spatial decomposition. |
| 107 | + */ |
| 108 | +void load_input_tile_with_checks( |
| 109 | + out FPInputTile tile, |
| 110 | + const int k4_start, |
| 111 | + const int m_start, |
| 112 | + const int K4, |
| 113 | + const int M, |
| 114 | + const int W_out) { |
| 115 | + // W_out is only consumed by the texture3d variant below. |
| 116 | + [[unroll]] for (int m = 0; m < TILE_M; ++m) { |
| 117 | + [[unroll]] for (int k4 = 0; k4 < TILE_K4; ++k4) { |
| 118 | + if (k4_start + k4 < K4 && m_start + m < M) { |
| 119 | + const int row = m_start + m; |
| 120 | + const int col = k4_start + k4; |
| 121 | +#if defined(INPUT_BUFFER) |
| 122 | + // Cast SSBO texel into the input tile type (f16vec4 for half, vec4 for |
| 123 | + // float). |
| 124 | + tile.data[m][k4] = LINEAR_FP_INPUT_TILE_VEC4_T(t_in[row * K4 + col]); |
| 125 | +#elif defined(INPUT_TEXTURE3D) |
| 126 | + // texture3d layout: row (the flat M index) decomposes into (ow, oh) |
| 127 | + // and K4 is along the Z axis. texelFetch returns vec4 (fp32); cast to |
| 128 | + // the input tile type. |
| 129 | + tile.data[m][k4] = LINEAR_FP_INPUT_TILE_VEC4_T( |
| 130 | + texelFetch(t_in, ivec3(row % W_out, row / W_out, col), 0)); |
| 131 | +#else |
| 132 | + tile.data[m][k4] = |
| 133 | + LINEAR_FP_INPUT_TILE_VEC4_T(texelFetch(t_in, ivec2(col, row), 0)); |
| 134 | +#endif |
| 135 | + } else { |
| 136 | + tile.data[m][k4] = LINEAR_FP_INPUT_TILE_VEC4_T(0.0); |
| 137 | + } |
| 138 | + } |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +void store_output_tile_with_checks( |
| 143 | + const FPOutTile out_tile, |
| 144 | + const int n4_start, |
| 145 | + const int m_start, |
| 146 | + const int N4, |
| 147 | + const int M, |
| 148 | + const int W_out) { |
| 149 | + [[unroll]] for (int m = 0; m < TILE_M; ++m) { |
| 150 | + [[unroll]] for (int n4 = 0; n4 < TILE_N4; ++n4) { |
| 151 | + if (m_start + m < M && n4_start + n4 < N4) { |
| 152 | + const int spatial = m_start + m; |
| 153 | + // Cast the accumulator (f16vec4 for the buffer/half path) to the |
| 154 | + // texture3d output surface type for the activation clamp and store. |
| 155 | + OUT_VEC4_T texel = OUT_VEC4_T(out_tile.data[m][n4]); |
| 156 | + if (activation_type == 1) { |
| 157 | + texel = max(texel, OUT_VEC4_T(0.0)); |
| 158 | + } else if (activation_type == 2) { |
| 159 | + texel = clamp(texel, OUT_VEC4_T(OUT_MIN), OUT_VEC4_T(OUT_MAX)); |
| 160 | + } |
| 161 | + imageStore( |
| 162 | + t_out, ivec3(spatial % W_out, spatial / W_out, n4_start + n4), texel); |
| 163 | + } |
| 164 | + } |
| 165 | + } |
| 166 | +} |
| 167 | + |
| 168 | +void main() { |
| 169 | + const int tile_idx_n = int(gl_GlobalInvocationID.x); |
| 170 | + const int tile_idx_m = int(gl_GlobalInvocationID.y); |
| 171 | + |
| 172 | + const int n4_start = tile_idx_n * TILE_N4; |
| 173 | + const int m_start = tile_idx_m * TILE_M; |
| 174 | + |
| 175 | + const int W_out = out_sizes.x; |
| 176 | + const int H_out = out_sizes.y; |
| 177 | + const int M = M_TOTAL; |
| 178 | + const int K4 = K4_TOTAL; |
| 179 | + const int N = out_sizes.z; |
| 180 | + const int N4 = div_up_4(N); |
| 181 | + |
| 182 | + if (n4_start >= N4 || m_start >= M) { |
| 183 | + return; |
| 184 | + } |
| 185 | + |
| 186 | + FPOutTile out_tile; |
| 187 | + initialize(out_tile); |
| 188 | + |
| 189 | + FPInputTile in_tile; |
| 190 | + FPWeightTile w_tile; |
| 191 | + |
| 192 | + for (int k4 = 0; k4 < K4; k4++) { |
| 193 | + load_input_tile_with_checks(in_tile, k4, m_start, K4, M, W_out); |
| 194 | + load_packed_weight_tile_with_checks(w_tile, n4_start, k4, 0, N4, K4); |
| 195 | + fp_accumulate_with_fp_weight(out_tile, in_tile, w_tile); |
| 196 | + } |
| 197 | + |
| 198 | + // Apply bias |
| 199 | + [[unroll]] for (int m = 0; m < TILE_M; ++m) { |
| 200 | + [[unroll]] for (int n4 = 0; n4 < TILE_N4; ++n4) { |
| 201 | + if (n4_start + n4 < N4) { |
| 202 | + // t_bias is an fp32 texture2d; cast its texel to the accumulator type. |
| 203 | + out_tile.data[m][n4] += LINEAR_FP_OUTPUT_TILE_VEC4_T( |
| 204 | + texelFetch(t_bias, ivec2(n4_start + n4, 0), 0)); |
| 205 | + } |
| 206 | + } |
| 207 | + } |
| 208 | + |
| 209 | + store_output_tile_with_checks(out_tile, n4_start, m_start, N4, M, W_out); |
| 210 | +} |
0 commit comments