/*******************************************************************************
* Copyright 2026 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License")* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
#include "gpu/intel/include/conversion.h"
#include "gpu/intel/include/tile_ops.h"
#include "gpu/intel/include/types_interop.h"
#include "gpu/intel/include/utils.h"
#include "gemm_grouped.h"
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#if WITH_BIAS
#define bias_br ugemm_grouped_sg_tile_m
#define bias_bc 1
#define bias_nbr 1
#define bias_nbc 1
DECLARE_2D_TILE(bias_tile_type, float, SUBGROUP_SIZE, bias_br, bias_bc,
bias_nbr, bias_nbc)
DECLARE_2D_TILE_VREDUCE(ugemm_grouped_c_type, SUBGROUP_SIZE,
ugemm_grouped_c_type_block0, ugemm_grouped_c_type_block1,
ugemm_grouped_c_type_nblock0, ugemm_grouped_c_type_nblock1,
bias_tile_type, SUBGROUP_SIZE, bias_br, bias_bc, bias_nbr, bias_nbc)
#ifndef BIA_DT_F32
DECLARE_2D_TILE(bias_in_tile_type, BIA_DATA_T, SUBGROUP_SIZE, bias_br, bias_bc,
bias_nbr, bias_nbc)
#endif
void load_bias(
bias_tile_type *tile, const global BIA_DATA_T *ptr, int n, int sg_i0) {
#if BIA_DT_F32
tile_load(tile, ptr, n, 1, 0, sg_i0, 0)#else
bias_in_tile_type bias_in_tile tile_load(&bias_in_tile, ptr, n, 1, 0, sg_i0, 0) tile_convert(bias_in_tile, (*tile), CONVERT_FLOAT_T)#endif
}
#endif
#if WITH_SPARSE_GROUPS
#define offsets_tile_br SUBGROUP_SIZE
#define offsets_tile_bc 1
#define offsets_tile_nbr \
MAX(1, \
NUM_GROUPS / (ugemm_grouped_sg_per_wg_m * SUBGROUP_SIZE) \
/ ugemm_grouped_sg_per_wg_n)
#define offsets_tile_nbc 1
DECLARE_2D_TILE(offsets_tile_type, int, SUBGROUP_SIZE, offsets_tile_br,
offsets_tile_bc, offsets_tile_nbr, offsets_tile_nbc)
#define slm_src_offsets_size sizeof(off_t) * 2
#define slm_batch_size sizeof(off_t)
#define slm_sg_last_size \
sizeof(int) * ugemm_grouped_sg_per_wg_m *ugemm_grouped_sg_per_wg_n
#define slm_sparse_total_size \
slm_src_offsets_size + slm_batch_size + slm_sg_last_size
void find_sparse_batch(off_t *batch, int2 *src_range,
const global int *src_offsets, off_t flat_token, local char *slm) {
local off_t *slm_src_offset = (local off_t *)slm local off_t *slm_batch = slm_src_offset + 2 local int *slm_sg_last = (local int *)(slm_batch + 1)
offsets_tile_type offsets_tile off_t sg_ij0 = sub_group_broadcast(get_local_linear_id(), 0) int sg_idx = get_sub_group_id() off_t sg_batch0 = sg_ij0 * offsets_tile_nbr
tile_load(&offsets_tile, src_offsets, NUM_GROUPS, 1, 0, sg_batch0, 0)
// Share each subgroup's last tile value for cross-subgroup carry
if (get_sub_group_local_id() == SUBGROUP_SIZE - 1)
slm_sg_last[sg_idx] = offsets_tile.x[offsets_tile_nbr - 1][0] work_group_barrier(CLK_LOCAL_MEM_FENCE)
int sg_carry = (sg_idx > 0) ? slm_sg_last[sg_idx - 1] : 0
// Find the tile containing the current batch, use shuffle_up to
// query the next src_offset.
int carry_b = sg_carry#pragma unroll
for (int b = 0, idx = sg_batch0 + get_sub_group_local_id() b < offsets_tile_nbr off_t curr = offsets_tile.x[b][0] off_t prev = intel_sub_group_shuffle_up(carry_b, curr, 1)
// Only one work-item should satisfy this condition
if (curr > flat_token && prev <= flat_token && idx < NUM_GROUPS) {
slm_src_offset[0] = prev slm_src_offset[1] = curr *slm_batch = idx }
carry_b = sub_group_broadcast(curr, SUBGROUP_SIZE - 1) }
work_group_barrier(CLK_LOCAL_MEM_FENCE)
*batch = *slm_batch *src_range = (int2)(slm_src_offset[0], slm_src_offset[1])}
#else
#define slm_sparse_total_size 0
#endif
#ifndef DST_DT_F32
DECLARE_2D_TILE(c_tile_type_dst, DST_DATA_T, SUBGROUP_SIZE,
ugemm_grouped_c_type_block0, ugemm_grouped_c_type_block1,
ugemm_grouped_c_type_nblock0, ugemm_grouped_c_type_nblock1)
#endif
/* Optional quantization parameters */
#define SRC_SCALE_ARGS \
OPTIONAL(AND(WITH_SRC_SCALES, SRC_SCALES_GROUPED), src_attr_scales)
#define SRC_ZP_ARGS OPTIONAL(WITH_SRC_ZP, src_attr_zp)
#define SRC_LD_ARGS OPTIONAL(OR(WITH_SRC_ZP, SRC_SCALES_GROUPED), ldsrcq)
#define WEI_SCALE_ARGS \
OPTIONAL(AND(WITH_WEI_SCALES, WEI_SCALES_GROUPED), wei_attr_scales)
#define WEI_ZP_ARGS OPTIONAL(WITH_WEI_ZP, wei_attr_zp)
#define WEI_LD_ARGS OPTIONAL(OR(WITH_WEI_ZP, WEI_SCALES_GROUPED), ldweiq)
#define K_PARALLEL_LOCAL_ARGS OPTIONAL(K_PARALLEL_LOCAL, sg_k)
void store_results(ugemm_grouped_c_type *tile, global DST_DATA_T *ptr, int n,
int m, int lddst, int sg_i0, int sg_j0) {
#if DST_DT_F32
tile_store(*tile, ptr, n, m, lddst, sg_i0, sg_j0) //tile_store_t_block2d(c_tile, dst, n, m, lddst, sg_j0, sg_i0)#else
c_tile_type_dst tile_dst tile_convert((*tile), tile_dst, CONVERT_DATA_T) tile_store(tile_dst, ptr, n, m, lddst, sg_i0, sg_j0) //tile_store_block2d(c_tile_dst, dst, n, m, lddst, sg_j0, sg_i0)#endif
}
#if WITH_SRC_SCALES && !SRC_SCALES_GROUPED
#define src_attr_scales_br MAX(SUBGROUP_SIZE, ugemm_grouped_sg_tile_n)
#define src_attr_scales_bc 1
#define src_attr_scales_nbr 1
#define src_attr_scales_nbc 1
DECLARE_2D_TILE(src_attr_scales_tile_type, float, SUBGROUP_SIZE,
src_attr_scales_br, src_attr_scales_bc, src_attr_scales_nbr,
src_attr_scales_nbc)
DECLARE_2D_TILE_HREDUCE(ugemm_grouped_c_type, SUBGROUP_SIZE,
ugemm_grouped_c_type_block0, ugemm_grouped_c_type_block1,
ugemm_grouped_c_type_nblock0, ugemm_grouped_c_type_nblock1,
src_attr_scales_tile_type, SUBGROUP_SIZE, src_attr_scales_br,
src_attr_scales_bc, src_attr_scales_nbr, src_attr_scales_nbc)
#ifndef SRC_SCALES_DT_F32
DECLARE_2D_TILE(src_attr_scales_in_tile_type, SRC_SCALES_DATA_T, SUBGROUP_SIZE,
src_attr_scales_br, src_attr_scales_bc, src_attr_scales_nbr,
src_attr_scales_nbc)
#endif
void load_src_attr_scales(src_attr_scales_tile_type *tile,
const global SRC_SCALES_DATA_T *ptr, int m, int ldsrcq, int sg_j0) {
#if SRC_SCALES_DT_F32
tile_load(tile, ptr, m, 1, ldsrcq, sg_j0, 0)#else
src_attr_scales_in_tile_type src_attr_scales_in_tile tile_load(&src_attr_scales_in_tile, ptr, m, 1, ldsrcq, sg_j0, 0) tile_convert(src_attr_scales_in_tile, (*tile), CONVERT_FLOAT_T)#endif
}
#endif
#if WITH_WEI_SCALES && !WEI_SCALES_GROUPED
#define wei_attr_scales_br ugemm_grouped_sg_tile_m
#define wei_attr_scales_bc 1
#define wei_attr_scales_nbr 1
#define wei_attr_scales_nbc 1
DECLARE_2D_TILE(wei_attr_scales_tile_type, float, SUBGROUP_SIZE,
wei_attr_scales_br, wei_attr_scales_bc, wei_attr_scales_nbr,
wei_attr_scales_nbc)
DECLARE_2D_TILE_VREDUCE(ugemm_grouped_c_type, SUBGROUP_SIZE,
ugemm_grouped_c_type_block0, ugemm_grouped_c_type_block1,
ugemm_grouped_c_type_nblock0, ugemm_grouped_c_type_nblock1,
wei_attr_scales_tile_type, SUBGROUP_SIZE, wei_attr_scales_br,
wei_attr_scales_bc, wei_attr_scales_nbr, wei_attr_scales_nbc)
#ifndef WEI_SCALES_DT_F32
DECLARE_2D_TILE(wei_attr_scales_in_tile_type, WEI_SCALES_DATA_T, SUBGROUP_SIZE,
wei_attr_scales_br, wei_attr_scales_bc, wei_attr_scales_nbr,
wei_attr_scales_nbc)
#endif
void load_wei_attr_scales(wei_attr_scales_tile_type *tile,
const global WEI_SCALES_DATA_T *ptr, int n, int ldweiq, int sg_i0) {
#if WEI_SCALES_DT_F32
tile_load(tile, ptr, n, 1, ldweiq, sg_i0, 0)#else
wei_attr_scales_in_tile_type wei_attr_scales_in_tile tile_load(&wei_attr_scales_in_tile, ptr, n, 1, ldweiq, sg_i0, 0) tile_convert(wei_attr_scales_in_tile, (*tile), CONVERT_FLOAT_T)#endif
}
#endif
__attribute__((intel_reqd_sub_group_size(SUBGROUP_SIZE)))
__attribute__((reqd_work_group_size(ugemm_grouped_sg_per_wg_m * SUBGROUP_SIZE,
ugemm_grouped_sg_per_wg_n, ugemm_grouped_sg_per_wg_k))) kernel void
grouped_micro_gemm(const global SRC_DATA_T *src, long ldsrc,
const global WEI_DATA_T *wei, long4 wei_strides, global DST_DATA_T *dst,
long lddst, const global int *src_offsets,
const global int *dst_offsets,
const global SRC_SCALES_DATA_T *src_attr_scales,
const global SRC_ZP_DATA_T *src_attr_zp, const long ldsrcq,
const global WEI_SCALES_DATA_T *wei_attr_scales,
const global WEI_ZP_DATA_T *wei_attr_zp, const long ldweiq,
const long n, const long k, const global BIA_DATA_T *bias) {
#if WITH_SLM
local char slm[MAX(ugemm_grouped_slm_size, slm_sparse_total_size)]#else
#if WITH_SPARSE_GROUPS
local char slm[slm_sparse_total_size]#else
local char *slm = NULL#endif
#endif
off_t sg_i = sub_group_broadcast(get_local_id(0) / SUBGROUP_SIZE, 0) off_t sg_j = sub_group_broadcast(get_local_id(1), 0)#if K_PARALLEL_LOCAL
off_t sg_k = sub_group_broadcast(get_local_id(2), 0)#endif
off_t wg_i0 = get_group_id(0) * ugemm_grouped_wg_tile_m off_t wg_j0 off_t batch off_t m off_t src_offset
#if WITH_SPARSE_GROUPS
off_t flat_token = get_group_id(2) int2 src_range find_sparse_batch(&batch, &src_range, src_offsets, flat_token, slm) m = (src_range.y - src_range.x) src_offset = src_range.x wg_j0 = flat_token - src_range.x
// Early exit for wg that can be processed by the previous ugemm tile
if (m > 1 && (wg_j0 % ugemm_grouped_wg_tile_n) != 0) { return#else
batch = sub_group_broadcast(get_group_id(2), 0) int2 src_range
= *(global int2 *)(src_offsets + (batch > 0 ? batch - 1 : batch))
m = batch > 0 ? (src_range.y - src_range.x) : src_range.x src_offset = batch > 0 ? src_range.x : 0 wg_j0 = get_group_id(1) * ugemm_grouped_wg_tile_n
if (wg_j0 >= m) return#endif
off_t sg_i0 = wg_i0 + sg_i * ugemm_grouped_sg_tile_m off_t sg_j0 = wg_j0 + sg_j * ugemm_grouped_sg_tile_n
src += src_offset * ldsrc / SRC_ELEMS_PER_BYTE wei += batch * wei_strides[0] / WEI_ELEMS_PER_BYTE dst += src_offset * lddst
off_t ldwei = wei_strides[2] == 1 ? wei_strides[1] : wei_strides[2]
#if WITH_SRC_SCALES
src_attr_scales += src_offset * ldsrcq#endif
#if WITH_SRC_ZP
src_attr_zp += src_offset * ldsrcq / SRC_ZP_ELEMS_PER_BYTE#endif
#if WITH_WEI_SCALES
wei_attr_scales += batch * n * (k / WEI_GROUP_SIZE)#endif
#if WITH_WEI_ZP
wei_attr_zp += batch * n * (k / WEI_GROUP_SIZE) / WEI_ZP_ELEMS_PER_BYTE#endif
ugemm_grouped_c_type c_tile = ugemm_grouped(wei, ldwei, src, ldsrc, n, m, k,
wg_i0, wg_j0, 0, sg_i, sg_j K_PARALLEL_LOCAL_ARGS,
slm WEI_SCALE_ARGS WEI_ZP_ARGS WEI_LD_ARGS SRC_SCALE_ARGS
SRC_ZP_ARGS SRC_LD_ARGS)
#if K_PARALLEL_LOCAL
if (sg_k > 0) return#endif
#if WITH_SRC_SCALES && !SRC_SCALES_GROUPED
src_attr_scales_tile_type src_attr_scales_tile load_src_attr_scales(
&src_attr_scales_tile, src_attr_scales, m, ldsrcq, sg_j0) tile_hbroadcast_mul(&c_tile, src_attr_scales_tile)#endif
#if WITH_WEI_SCALES && !WEI_SCALES_GROUPED
wei_attr_scales_tile_type wei_attr_scales_tile load_wei_attr_scales(
&wei_attr_scales_tile, wei_attr_scales, n, ldweiq, sg_i0) tile_vbroadcast_mul(&c_tile, wei_attr_scales_tile)#endif
#if WITH_BIAS
bias += batch * n bias_tile_type bias_tile load_bias(&bias_tile, bias, n, sg_i0) tile_vbroadcast_add(&c_tile, bias_tile)#endif
store_results(&c_tile, dst, n, m, lddst, sg_i0, sg_j0)}