#[cfg(all(feature = "metal", target_os = "macos"))]
pub const MSL_CONV2D_F32_IMPLICIT: &str = r#"
#include <metal_stdlib>
using namespace metal;
// Tile / simdgroup geometry (must match dispatch_conv2d_f32_implicit).
// Identical to MSL_GEMM_F32_SIMDGROUP so the matrix-MAC structure carries over.
//
// output tile : CONV_TM x CONV_TN = 64 x 64 (M=C_out, N=P pixels)
// simdgroups/threadgroup: CONV_SIMDGROUPS = 4 (-> 128 threads)
// per-simdgroup quadrant: CONV_SG_M x CONV_SG_N = 32 x 32
// accumulator grid : (CONV_SG_M/8) x (CONV_SG_N/8) = 4 x 4 simdgroup_float8x8
// K step : CONV_TK = 32 (= 4 fragments of 8)
// threadgroup memory : Ash(64*32*4) + Dsh(32*64*4) = 8 + 8 = 16 KiB (float)
constant constexpr uint CONV_TM = 64u;
constant constexpr uint CONV_TN = 64u;
constant constexpr uint CONV_TK = 32u;
constant constexpr uint CONV_SIMDGROUPS = 4u;
constant constexpr uint CONV_THREADS = CONV_SIMDGROUPS * 32u; // 128
constant constexpr uint CONV_SG_M = 32u; // rows per simdgroup quadrant (M=C_out)
constant constexpr uint CONV_SG_N = 32u; // cols per simdgroup quadrant (N=P)
constant constexpr uint CONV_FRAG = 8u; // hardware matrix edge
constant constexpr uint CONV_MFRAGS = CONV_SG_M / CONV_FRAG; // 4 accumulator rows
constant constexpr uint CONV_NFRAGS = CONV_SG_N / CONV_FRAG; // 4 accumulator cols
constant constexpr uint CONV_KFRAGS = CONV_TK / CONV_FRAG; // 4 K-fragments per K-tile
constant constexpr uint CONV_AELEMS = CONV_TM * CONV_TK; // weight floats staged per K-tile (2048)
constant constexpr uint CONV_DELEMS = CONV_TK * CONV_TN; // patch floats staged per K-tile (2048)
kernel void conv2d_f32_implicit(
device const float* weight [[buffer(0)]],
device const float* input [[buffer(1)]],
device float* output [[buffer(2)]],
constant uint& c_out [[buffer(3)]],
constant uint& p [[buffer(4)]],
constant uint& kk_cin [[buffer(5)]],
constant uint& c_in [[buffer(6)]],
constant uint& h [[buffer(7)]],
constant uint& w [[buffer(8)]],
constant uint& k [[buffer(9)]],
constant uint& pad [[buffer(10)]],
constant uint& w_out [[buffer(11)]],
uint2 tgid [[threadgroup_position_in_grid]],
uint lid [[thread_index_in_threadgroup]],
uint sgid [[simdgroup_index_in_threadgroup]])
{
// This threadgroup's output tile origin.
const uint col_base = tgid.x * CONV_TN; // first output pixel (N = P)
const uint row_base = tgid.y * CONV_TM; // first output channel (M = C_out)
// simdgroup -> 32x32 quadrant of the 64x64 tile.
const uint sg_mi = sgid / (CONV_TN / CONV_SG_N); // 0..1 -> quadrant row (M)
const uint sg_ni = sgid % (CONV_TN / CONV_SG_N); // 0..1 -> quadrant col (N)
const uint sg_m0 = sg_mi * CONV_SG_M; // tile-local first M row of this simdgroup
const uint sg_n0 = sg_ni * CONV_SG_N; // tile-local first N col of this simdgroup
const uint k_tiles = (kk_cin + CONV_TK - 1u) / CONV_TK; // number of CONV_TK-wide K-tiles (ceil)
// Threadgroup staging (float — both operands are f32):
// Ash[m][kk] = weight[row_base+m][k_off+kk] (contiguous weight row, ld = CONV_TK)
// Dsh[kk][n] = gathered patch for pixel col_base+n, contraction k_off+kk (ld = CONV_TN)
threadgroup float Ash[CONV_TM * CONV_TK]; // 64 * 32 * 4 = 8 KiB
threadgroup float Dsh[CONV_TK * CONV_TN]; // 32 * 64 * 4 = 8 KiB
// Per-simdgroup accumulators: a 4x4 grid of 8x8 f32 fragments (the 32x32 quadrant).
simdgroup_float8x8 acc[CONV_MFRAGS][CONV_NFRAGS];
for (uint mi = 0u; mi < CONV_MFRAGS; mi++) {
for (uint ni = 0u; ni < CONV_NFRAGS; ni++) {
acc[mi][ni] = make_filled_simdgroup_matrix<float, 8, 8>(0.0f);
}
}
// Boundary extents for this tile (out-of-range rows/cols staged as zero).
const uint valid_m = (row_base < c_out) ? min(CONV_TM, c_out - row_base) : 0u; // valid C_out
const uint valid_n = (col_base < p) ? min(CONV_TN, p - col_base) : 0u; // valid pixels
const uint kc = k * c_in; // for decoding j -> (kh, kw, ci)
const ulong hw_plane = (ulong)h * (ulong)w;
for (uint kt = 0u; kt < k_tiles; kt++) {
const uint k_off = kt * CONV_TK; // global contraction offset
const uint k_span = (k_off < kk_cin) ? min(CONV_TK, kk_cin - k_off) : 0u;
// -- Stage Ash: CONV_TM weight rows x CONV_TK contraction elems.
// All 128 threads cooperate; CONV_AELEMS = 2048 = 16 per thread.
// Ash[a_row][a_kk] = weight[(row_base+a_row) * kk_cin + (k_off+a_kk)].
for (uint i = lid; i < CONV_AELEMS; i += CONV_THREADS) {
const uint a_row = i / CONV_TK; // 0..CONV_TM-1 -> tile-local M (C_out) row
const uint a_kk = i % CONV_TK; // 0..CONV_TK-1 -> contraction within tile
float v = 0.0f;
if (a_row < valid_m && a_kk < k_span) {
const uint w_row = row_base + a_row;
v = weight[(ulong)w_row * (ulong)kk_cin + (ulong)k_off + (ulong)a_kk];
}
Ash[a_row * CONV_TK + a_kk] = v;
}
// -- Stage Dsh: GATHER conv patches (im2col-free).
// Dsh[d_kk][d_n], ld = CONV_TN. d_n -> pixel col_base+d_n -> (oh, ow);
// contraction k_off+d_kk -> (kh, kw, ci); value = in[ci, oh+kh-pad, ow+kw-pad].
// All 128 threads cooperate; CONV_DELEMS = 2048 = 16 per thread.
for (uint i = lid; i < CONV_DELEMS; i += CONV_THREADS) {
const uint d_kk = i / CONV_TN; // 0..CONV_TK-1 -> contraction within tile
const uint d_n = i % CONV_TN; // 0..CONV_TN-1 -> tile-local N (pixel) col
float v = 0.0f;
if (d_n < valid_n && d_kk < k_span) {
const uint j = k_off + d_kk; // global contraction index
// Decode j = (kh*k + kw)*c_in + ci -> (kh, kw, ci).
const uint kh = j / kc;
const uint rem = j % kc;
const uint kw = rem / c_in;
const uint ci = rem % c_in;
// Decode output pixel p_idx = oh*w_out + ow.
const uint p_idx = col_base + d_n;
const uint oh = p_idx / w_out;
const uint ow = p_idx % w_out;
// Padded source coordinates (same geometry as build_im2col).
const uint ih_p = oh + kh;
const uint iw_p = ow + kw;
if (ih_p >= pad && ih_p < h + pad && iw_p >= pad && iw_p < w + pad) {
const uint ih = ih_p - pad;
const uint iw = iw_p - pad;
v = input[(ulong)ci * hw_plane + (ulong)ih * (ulong)w + (ulong)iw];
}
}
Dsh[d_kk * CONV_TN + d_n] = v;
}
threadgroup_barrier(mem_flags::mem_threadgroup);
// -- Matrix MACs: acc[mi][ni] += A_frag * D_frag over the CONV_TK K-tile.
// A fragment : Ash[(sg_m0+mi*8)+ .. ][kf*8 + ..] (row-major, ld = CONV_TK)
// D fragment : Dsh[kf*8 + ..][(sg_n0+ni*8)+ ..] (row-major, ld = CONV_TN)
for (uint kf = 0u; kf < CONV_KFRAGS; kf++) {
simdgroup_float8x8 afrag[CONV_MFRAGS];
simdgroup_float8x8 dfrag[CONV_NFRAGS];
for (uint mi = 0u; mi < CONV_MFRAGS; mi++) {
const uint a_off = (sg_m0 + mi * CONV_FRAG) * CONV_TK + kf * CONV_FRAG;
simdgroup_load(afrag[mi], Ash + a_off, CONV_TK);
}
for (uint ni = 0u; ni < CONV_NFRAGS; ni++) {
const uint d_off = (kf * CONV_FRAG) * CONV_TN + (sg_n0 + ni * CONV_FRAG);
simdgroup_load(dfrag[ni], Dsh + d_off, CONV_TN);
}
for (uint mi = 0u; mi < CONV_MFRAGS; mi++) {
for (uint ni = 0u; ni < CONV_NFRAGS; ni++) {
simdgroup_multiply_accumulate(acc[mi][ni], afrag[mi], dfrag[ni], acc[mi][ni]);
}
}
}
threadgroup_barrier(mem_flags::mem_threadgroup);
}
// -- Write back the 32x32 quadrant. Output is row-major [C_out, P]:
// output[(row_base+m_local) * p + (col_base+n_local)] == NCHW [C_out, H_out, W_out].
// Stage each 8x8 accumulator to threadgroup memory, then store row-major with
// the boundary clamp (the matrix store wants a contiguous destination).
threadgroup float Csh[CONV_SG_M * CONV_SG_N]; // 32 * 32 * 4 = 4 KiB (one simdgroup at a time)
for (uint sg = 0u; sg < CONV_SIMDGROUPS; sg++) {
threadgroup_barrier(mem_flags::mem_threadgroup);
if (sg == sgid) {
for (uint mi = 0u; mi < CONV_MFRAGS; mi++) {
for (uint ni = 0u; ni < CONV_NFRAGS; ni++) {
const uint c_off = (mi * CONV_FRAG) * CONV_SG_N + ni * CONV_FRAG;
simdgroup_store(acc[mi][ni], Csh + c_off, CONV_SG_N);
}
}
}
threadgroup_barrier(mem_flags::mem_threadgroup);
if (sg == sgid) {
// Store Csh[mm][nn] -> output[(row_base+mm) * p + (col_base+nn)] with clamps.
// Iterate so consecutive lanes write consecutive nn (coalesced over pixels).
for (uint idx = lid % 32u; idx < CONV_SG_M * CONV_SG_N; idx += 32u) {
const uint mm = idx / CONV_SG_N; // 0..31 local M (C_out) within quadrant
const uint nn = idx % CONV_SG_N; // 0..31 local N (pixel) within quadrant
const uint m_local = sg_m0 + mm; // tile-local M
const uint n_local = sg_n0 + nn; // tile-local N
if (m_local < valid_m && n_local < valid_n) {
const uint co = row_base + m_local; // output channel
const uint px = col_base + n_local; // output pixel
output[(ulong)co * (ulong)p + (ulong)px] = Csh[mm * CONV_SG_N + nn];
}
}
}
}
}
"#;