constant bool FC_mul_mm_bc_out [[function_constant(FC_MUL_MM + 1)]];
// each block_q contains 16*nl weights
-template<typename S0, typename S0_4x4, typename S0_8x8, typename S1, typename S1_2x4, typename S1_8x8, typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread S0_4x4 &), typename T0, typename T0_4x4, typename T1, typename T1_2x4>
+#ifdef GGML_METAL_HAS_TENSOR
+template<
+ typename SA, typename SA_4x4, typename SA_8x8,
+ typename SB, typename SB_2x4, typename SB_8x8,
+ typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread SA_4x4 &),
+ typename T0, typename T0_4x4, typename T1, typename T1_2x4>
+kernel void kernel_mul_mm(
+ constant ggml_metal_kargs_mul_mm & args,
+ device const char * srcA,
+ device const char * srcB,
+ device char * dst,
+ threadgroup char * shmem [[threadgroup(0)]],
+ uint3 tgpig [[threadgroup_position_in_grid]],
+ ushort tiitg [[thread_index_in_threadgroup]],
+ ushort sgitg [[simdgroup_index_in_threadgroup]]) {
+ (void) sgitg;
+
+ // Matrix dimensions: A(M,K) x B(K,N) -> C(M,N)
+ const int K = args.ne00;
+ const int M = args.ne0;
+ const int N = args.ne1;
+
+ // Batch dimension handling
+ const int im = tgpig.z;
+ const int i12 = im % args.ne12;
+ const int i13 = im / args.ne12;
+
+ // Batch offsets for srcA and srcB
+ const uint64_t offset0 = (i12/args.r2)*args.nb02 + (i13/args.r3)*args.nb03;
+
+ // Tile dimensions
+ constexpr int NRB = SZ_SIMDGROUP * N_MM_BLOCK_X * N_MM_SIMD_GROUP_X;
+ constexpr int NRA = SZ_SIMDGROUP * N_MM_BLOCK_Y * N_MM_SIMD_GROUP_Y;
+
+ // Tile offsets in output matrix
+ const int ra = tgpig.y * NRA;
+ const int rb = tgpig.x * NRB;
+
+ // Threadgroup memory for dequantized A tile only
+ threadgroup SA * sa = (threadgroup SA *)(shmem);
+
+ // Work-item count for A loading
+ constexpr int A_WORK_ITEMS = NRA * N_MM_NK;
+ constexpr int NUM_THREADS = N_SIMDWIDTH * N_MM_SIMD_GROUP_X * N_MM_SIMD_GROUP_Y;
+
+ // tA wraps threadgroup memory
+ auto tA = tensor(sa, dextents<int32_t, 2>(N_MM_NK_TOTAL, NRA));
+
+ // tB wraps device memory directly
+ device T1 * ptrB = (device T1 *)(srcB + args.nb12*i12 + args.nb13*i13);
+ const int strideB = args.nb11 / sizeof(T1);
+ auto tB = tensor(ptrB, dextents<int32_t, 2>(K, N), array<int, 2>({1, strideB}));
+
+ // Configure matmul operation
+ mpp::tensor_ops::matmul2d<
+ mpp::tensor_ops::matmul2d_descriptor(
+ NRB, NRA, N_MM_NK_TOTAL, false, true, true,
+ mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate),
+ execution_simdgroups<N_MM_SIMD_GROUP_X * N_MM_SIMD_GROUP_Y>> mm;
+
+ auto cT = mm.get_destination_cooperative_tensor<decltype(tB), decltype(tA), float>();
+
+ // Accumulate partial results over K dimension
+ for (int loop_k = 0; loop_k < K; loop_k += N_MM_NK_TOTAL) {
+ // === PHASE 1: Dequantization of A into threadgroup memory ===
+ for (int work = tiitg; work < A_WORK_ITEMS; work += NUM_THREADS) {
+ const int row = work / N_MM_NK;
+ const int k_chunk = work % N_MM_NK;
+ const int k_pos = loop_k + k_chunk * 16;
+ const short k_base = k_chunk * 16;
+
+ // Bounds check: skip device read if row is out of matrix bounds
+ if (ra + row < M) {
+ if (is_same<T0_4x4, block_q>::value && FC_mul_mm_bc_inp) {
+ // Element-wise reads when K is not aligned (nb01 not aligned for half4x4/float4x4).
+ // MSL spec Table 2.5: half4x4 requires 8-byte alignment. When K is odd,
+ // nb01 = K*2 is not 8-byte aligned, so odd-row pointers are misaligned.
+ // Mirrors the legacy kernel's existing guard.
+ device const T0 * row_ptr = (device const T0 *)(srcA + args.nb01 * (ra + row) + offset0);
+
+ FOR_UNROLL (short i = 0; i < 16; i++) {
+ sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? (SA) row_ptr[k_pos + i] : (SA)0;
+ }
+ } else {
+ const int block_idx = k_pos / (16 * nl);
+ const short il = (k_pos / 16) % nl;
+
+ device const block_q * row_ptr = (device const block_q *)(srcA + args.nb01 * (ra + row) + offset0);
+
+ SA_4x4 temp_a;
+ dequantize_func(row_ptr + block_idx, il, temp_a);
+
+ FOR_UNROLL (short i = 0; i < 16; i++) {
+ // Zero-pad A for K positions beyond valid range (handles partial K iterations)
+ sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? temp_a[i/4][i%4] : (SA)0;
+ }
+ }
+ } else {
+ // Zero-pad rows beyond matrix bounds
+ FOR_UNROLL (short i = 0; i < 16; i++) {
+ sa[row * N_MM_NK_TOTAL + (k_base + i)] = (SA)0;
+ }
+ }
+ }
+
+ threadgroup_barrier(mem_flags::mem_threadgroup);
+
+ // === PHASE 2: Tensor matmul ===
+ auto mA = tA.slice(0, 0);
+ auto mB = tB.slice(loop_k, rb);
+
+ mm.run(mB, mA, cT);
+
+ threadgroup_barrier(mem_flags::mem_threadgroup);
+ }
+
+ // Store result tile to output matrix (with batch offset)
+ // cT.store handles bounds checking via tD's extents (M, N)
+ device float * dstBatch = (device float *)dst + im * N * M;
+
+ auto tD = tensor(dstBatch, dextents<int32_t, 2>(M, N), array<int, 2>({1, M}));
+ cT.store(tD.slice(ra, rb));
+}
+
+#else
+
+template<
+ typename S0, typename S0_4x4, typename S0_8x8,
+ typename S1, typename S1_2x4, typename S1_8x8,
+ typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread S0_4x4 &),
+ typename T0, typename T0_4x4, typename T1, typename T1_2x4>
kernel void kernel_mul_mm(
constant ggml_metal_kargs_mul_mm & args,
device const char * src0,
threadgroup S0 * sa = (threadgroup S0 *)(shmem);
threadgroup S1 * sb = (threadgroup S1 *)(shmem + 4096);
-#ifdef GGML_METAL_HAS_TENSOR
- threadgroup float * sc = (threadgroup float *)(shmem);
-#endif
-
constexpr int NR0 = 64;
constexpr int NR1 = 32;
+ args.nb11*(r1 + lr1)
+ args.nb10*iy);
-#ifndef GGML_METAL_HAS_TENSOR
S0_8x8 ma[4];
S1_8x8 mb[2];
for (short i = 0; i < 8; i++){
mc[i] = make_filled_simdgroup_matrix<float, 8>(0.f);
}
-#else
- auto tA = tensor<threadgroup S0, dextents<int32_t, 2>, tensor_inline>(sa, dextents<int32_t, 2>(NK, NR0));
- auto tB = tensor<threadgroup S1, dextents<int32_t, 2>, tensor_inline>(sb, dextents<int32_t, 2>(NR1, NK ));
-
- mpp::tensor_ops::matmul2d<
- mpp::tensor_ops::matmul2d_descriptor(NR1, NR0, NK, false, true, false, mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate),
- execution_simdgroups<4>> mm;
-
- auto cT = mm.get_destination_cooperative_tensor<decltype(tA), decltype(tB), float>();
-#endif
for (int loop_k = 0; loop_k < args.ne00; loop_k += NK) {
-#ifndef GGML_METAL_HAS_TENSOR
// load data and store to threadgroup memory
if (is_same<T0_4x4, block_q>::value && FC_mul_mm_bc_inp) {
threadgroup_barrier(mem_flags::mem_threadgroup);
*(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)(*((device T1_2x4 *) y));
}
-#else
- // load data and store to threadgroup memory
- if (is_same<T0_4x4, block_q>::value && FC_mul_mm_bc_inp) {
- threadgroup_barrier(mem_flags::mem_threadgroup);
-
- // no need for dequantization
- for (short i = 0; i < 16; i++) {
- const short sx = 2*il0 + i/8;
- const short sy = (tiitg/NL0)/8;
-
- const short lx = i%8;
- const short ly = (tiitg/NL0)%8;
- //const short lx = (tiitg/NL0)%8;
- //const short ly = i%8;
-
- *(sa + NK*(8*sy + ly) + 8*sx + lx) = loop_k + 16*il + i < args.ne00 ? *((device T0 *) x + i) : 0;
- }
- } else {
- S0_4x4 temp_a;
- dequantize_func(x, il, temp_a);
-
- threadgroup_barrier(mem_flags::mem_threadgroup);
-
- FOR_UNROLL (short i = 0; i < 16; i++) {
- const short sx = 2*il0 + i/8;
- const short sy = (tiitg/NL0)/8;
-
- const short lx = i%8;
- const short ly = (tiitg/NL0)%8;
- //const short lx = (tiitg/NL0)%8;
- //const short ly = i%8;
-
- *(sa + NK*(8*sy + ly) + 8*sx + lx) = temp_a[i/4][i%4];
- }
- }
-
- if (FC_mul_mm_bc_inp) {
- for (short i = 0; i < 8; ++i) {
- const short sx = (tiitg%NL1);
- const short sy = (tiitg/NL1)/8;
-
- const short lx = i;
- const short ly = (tiitg/NL1)%8;
- //const short lx = (tiitg/NL1)%8;
- //const short ly = i;
-
- *(sb + NK*(8*sy + ly) + 8*sx + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0;
- }
- } else {
- const short sx = (tiitg%NL1);
- const short sy = (tiitg/NL1)/8;
-
- //const short lx = i;
- const short ly = (tiitg/NL1)%8;
- //const short lx = (tiitg/NL1)%8;
- //const short ly = i;
-
- *(threadgroup S1_2x4 *)(sb + NK*(8*sy + ly) + 8*sx) = (S1_2x4)(*((device T1_2x4 *) y));
- }
-#endif
il = (il + 2 < nl) ? il + 2 : il % 2;
x = (il < 2) ? x + (2 + nl - 1)/nl : x;
threadgroup_barrier(mem_flags::mem_threadgroup);
-#ifndef GGML_METAL_HAS_TENSOR
// load matrices from threadgroup memory and conduct outer products
threadgroup const S0 * lsma = (sa + 4*64*(sgitg%2));
threadgroup const S1 * lsmb = (sb + 2*64*(sgitg/2));
lsma += 8*64;
lsmb += 4*64;
}
-#else
- auto sA = tA.slice(0, 0);
- auto sB = tB.slice(0, 0);
-
- mm.run(sB, sA, cT);
-#endif
}
if (!FC_mul_mm_bc_out || (r0 + NR0 <= args.ne0 && r1 + NR1 <= args.ne1)) {
// if no bounds checks on the output are needed, we can directly write to device memory
-#ifdef GGML_METAL_HAS_TENSOR
- device float * C = (device float *) dst +
- r0 + \
- r1 * args.ne0 + im*args.ne1*args.ne0;
-
- auto tC = tensor<device float, dextents<int32_t, 2>, tensor_inline>(C, dextents<int32_t, 2>(args.ne0, NR1));
- cT.store(tC);
-#else
device float * C = (device float *) dst +
(r0 + 32*(sgitg & 1)) + \
(r1 + 16*(sgitg >> 1)) * args.ne0 + im*args.ne1*args.ne0;
for (short i = 0; i < 8; i++) {
simdgroup_store(mc[i], C + 8*(i%4) + 8*args.ne0*(i/4), args.ne0, 0, false);
}
-#endif
} else {
// block is smaller than 64x32, we should avoid writing data outside of the matrix
threadgroup_barrier(mem_flags::mem_threadgroup);
threadgroup float * temp_str = ((threadgroup float *) shmem) + 32*(sgitg&1) + (16*(sgitg >> 1))*NR0;
-#ifdef GGML_METAL_HAS_TENSOR
- auto tC = tensor<threadgroup float, dextents<int32_t, 2>, tensor_inline>(sc, dextents<int32_t, 2>(NR0, NR1));
- cT.store(tC);
-#else
for (short i = 0; i < 8; i++) {
simdgroup_store(mc[i], temp_str + 8*(i%4) + 8*NR0*(i/4), NR0, 0, false);
}
-#endif
threadgroup_barrier(mem_flags::mem_threadgroup);
}
}
+#endif // GGML_METAL_HAS_TENSOR
+
template<short ne20> // n_expert_used
kernel void kernel_mul_mm_id_map0(
constant ggml_metal_kargs_mul_mm_id_map0 & args,
const short ib = 8*sx + sy;
- *(sa + 64*ib + 8*ly + lx) = loop_k + 16*il + i < args.ne00 ? *((device T0 *) x + i) : 0;
+ *(sa + 64*ib + 8*ly + lx) = loop_k + 16*il + i < args.ne00 ? (S0) *((device T0 *) x + i) : (S0) 0;
}
} else {
S0_4x4 temp_a;