Repository: uxlfoundation/oneDNN
Pull request #2882: concat2 internal padding
Author: login: syurkevi
Labels: name: platform:gpu-intel
description: Codeowner: @oneapi-src/onednn-gpu-intel
name: component:tests
description: Codeowner: @oneapi-src/onednn-arch
name: component:common
Created: 2025-03-14T01:56:48Z | Merged: 2025-04-24T17:35:44Z

# Description
This PR improves performance for concat with blocked internal padding, cases such as  `aBc32b:aBc32b      2x33x2:2x32x2 ` that contain padding inside the concat dimension that must be removed. Better bandwidth utilization has been achieved through a new specialized kernel that attempts to form fully utilized write cache lines from overlapping aligned reads. 

Currently, the kernel is specialized to only n=2 inputs and has some restrictions wrt/simd size. The change should address a large portion of the problem layers identified in issue [MFDNN-9174](https://jira.devtools.intel.com/browse/MFDNN-9174). 

Since the approach is fairly generic, a geomean speedup of roughly ~30% is observed across multiple platforms. Peak speedup of certain problem layers reaches ~70%. 

The kernel has been kept within the reusable_simple_concat implementation. This may need to be revisited to determine the optimal code organization.

# Checklist

## General

- [x] Do all unit and benchdnn tests (`make test` and `make test_benchdnn_*`) pass locally for each commit?
- [x] Have you formatted the code using clang-format?

## Performance improvements

- [ ] Have you submitted performance data that demonstrates performance improvements?



Diff:
diff --git a/src/gpu/gpu_concat_list.cpp b/src/gpu/gpu_concat_list.cpp
index 24105ee7b7f..0d47a3d670b 100644
--- a/src/gpu/gpu_concat_list.cpp
+++ b/src/gpu/gpu_concat_list.cpp
@@ -1,5 +1,5 @@
 /*******************************************************************************
-* Copyright 2019-2024 Intel Corporation
+* Copyright 2019-2025 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@
 #include "gpu/intel/ocl/gen9_concat.hpp"
 #include "gpu/intel/ocl/multi_concat.hpp"
 #include "gpu/intel/ocl/reusable_simple_concat.hpp"
-#include "gpu/intel/ocl/simple_concat.hpp"
 #endif
 
 namespace dnnl {
@@ -34,7 +33,6 @@ namespace {
 // clang-format off
 constexpr impl_list_item_t impl_list[] = REG_CONCAT_P({
         GPU_CONCAT_INSTANCE_INTEL(intel::ocl::reusable_simple_concat_t)
-        GPU_CONCAT_INSTANCE_INTEL(intel::ocl::simple_concat_t)
         GPU_CONCAT_INSTANCE_INTEL(intel::ocl::gen9_concat_t)
         GPU_CONCAT_INSTANCE_INTEL(intel::ocl::multi_concat_t)
         GPU_CONCAT_INSTANCE_GENERIC(generic::ref_concat_t)
diff --git a/src/gpu/intel/ocl/concat_utils.hpp b/src/gpu/intel/ocl/concat_utils.hpp
index 8fa60f342f4..27db6c72743 100644
--- a/src/gpu/intel/ocl/concat_utils.hpp
+++ b/src/gpu/intel/ocl/concat_utils.hpp
@@ -135,6 +135,7 @@ class normalization_t {
 
     data_type_t data_type() const { return data_type_; }
     size_t data_type_size() const { return types::data_type_size(data_type_); }
+    void set_pessimistic_chunk_size() { chunk_size_ = 1; }
 
     dim_t max_write_size() const {
         dim_t write_size = 1;
@@ -160,6 +161,10 @@ class normalization_t {
 
     void operator()(memory_desc_t &) const;
 
+    bool has_internal_padding() const {
+        return (padding_type_ == padding_t::internal);
+    }
+
 private:
     static bool striding_ok(striding_t striding) {
         return striding == striding_t::partitioned
diff --git a/src/gpu/intel/ocl/reusable_simple_concat.cl b/src/gpu/intel/ocl/reusable_simple_concat.cl
index 26911049959..6a75a1a3c5a 100644
--- a/src/gpu/intel/ocl/reusable_simple_concat.cl
+++ b/src/gpu/intel/ocl/reusable_simple_concat.cl
@@ -1,5 +1,5 @@
 /*******************************************************************************
-* Copyright 2024 Intel Corporation
+* Copyright 2024-2025 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -234,3 +234,459 @@ reusable_simple_concat(__global DATA_T *dst, const ulong dst_offset0,
     }
 #endif // SIMD == 1
 }
+
+#if BYTES_PER_WORKITEM == 16
+
+inline ulong2 shiftl(ulong2 v, unsigned s) {
+    ulong mm0, mm1;
+    s &= 127;
+    mm0 = ((((s + 127) | s) & 64) >> 6) - 1UL;
+    mm1 = (s >> 6) - 1UL;
+    s &= 63;
+    ulong2 res;
+    res.s1 = (v.s0 << s) & (~mm1);
+    res.s0 = (v.s0 << s) & (mm1);
+    res.s1 |= ((v.s1 << s) | ((v.s0 >> (64 - s)) & mm0)) & mm1;
+    return res;
+}
+
+inline ulong2 shiftr(ulong2 v, unsigned s) {
+    ulong mm0, mm1;
+    s &= 127;
+    mm0 = ((((s + 127) | s) & 64) >> 6) - 1UL;
+    mm1 = (s >> 6) - 1UL;
+    s &= 63;
+    ulong2 res;
+    res.s0 = (v.s1 >> s) & (~mm1);
+    res.s1 = (v.s1 >> s) & (mm1);
+    res.s0 |= ((v.s0 >> s) | ((v.s1 << (64 - s)) & mm0)) & mm1;
+    return res;
+}
+
+#define AS_FULL as_ulong2
+#define COMPUTE_T ulong2
+#define FULLMASK ((ulong2)(0xFFFFFFFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL))
+
+#if DATA_TYPE_SIZE == 8
+#define NPERSG 2
+#define DDATA_T DATA2_T
+#define AS_VEC as_ulong2
+#define BLOCK_READ_UL BLOCK_READ2
+#define BLOCK_WRITE_UL BLOCK_WRITE2
+#elif DATA_TYPE_SIZE == 4
+#define NPERSG 4
+#define DDATA_T DATA4_T
+#define AS_VEC as_uint4
+#define BLOCK_READ_UL BLOCK_READ4
+#define BLOCK_WRITE_UL BLOCK_WRITE4
+#elif DATA_TYPE_SIZE == 2
+#define NPERSG 8
+#define DDATA_T DATA8_T
+#define AS_VEC as_ushort8
+#define BLOCK_READ_UL BLOCK_READ8
+#define BLOCK_WRITE_UL BLOCK_WRITE8
+#elif DATA_TYPE_SIZE == 1
+#define NPERSG 16
+#define DDATA_T DATA16_T
+#define AS_VEC as_uchar16
+#define BLOCK_READ_UL BLOCK_READ16
+#define BLOCK_WRITE_UL BLOCK_WRITE16
+#endif
+
+#define SHUFFLE_UP(p, c, dt) \
+    (ulong2)(intel_sub_group_shuffle_up((p).s0, (c).s0, (dt)), \
+            intel_sub_group_shuffle_up((p).s1, (c).s1, (dt)))
+#define SHUFFLE_DOWN(c, n, dt) \
+    (ulong2)(intel_sub_group_shuffle_down((c).s0, (n).s0, (dt)), \
+            intel_sub_group_shuffle_down((c).s1, (n).s1, (dt)))
+#define SHUFFLE(data, c) \
+    (ulong2)(intel_sub_group_shuffle((data).s0, (c)), \
+            intel_sub_group_shuffle((data).s1, (c)))
+
+#define SHIFTL(v, s) shiftl((v), (s))
+#define SHIFTR(v, s) shiftr((v), (s))
+
+#elif BYTES_PER_WORKITEM == 8
+
+#define AS_FULL as_ulong
+#define COMPUTE_T ulong
+#define FULLMASK 0xFFFFFFFFFFFFFFFFUL
+#define SHUFFLE_UP intel_sub_group_shuffle_up
+#define SHUFFLE_DOWN intel_sub_group_shuffle_down
+#define SHUFFLE intel_sub_group_shuffle
+
+#define DATA1_T DATA_T
+#if DATA_TYPE_SIZE == 8
+#define NPERSG 1
+#define DDATA_T DATA1_T
+#define AS_VEC as_ulong
+#define BLOCK_READ_UL BLOCK_READ
+#define BLOCK_WRITE_UL BLOCK_WRITE
+#elif DATA_TYPE_SIZE == 4
+#define NPERSG 2
+#define DDATA_T DATA2_T
+#define AS_VEC as_uint2
+#define BLOCK_READ_UL BLOCK_READ2
+#define BLOCK_WRITE_UL BLOCK_WRITE2
+#elif DATA_TYPE_SIZE == 2
+#define NPERSG 4
+#define DDATA_T DATA4_T
+#define AS_VEC as_ushort4
+#define BLOCK_READ_UL BLOCK_READ4
+#define BLOCK_WRITE_UL BLOCK_WRITE4
+#elif DATA_TYPE_SIZE == 1
+#define NPERSG 8
+#define DDATA_T DATA8_T
+#define AS_VEC as_uchar8
+#define BLOCK_READ_UL BLOCK_READ8
+#define BLOCK_WRITE_UL BLOCK_WRITE8
+#endif
+
+#define SHIFTL(v, s) ((v) << (s))
+#define SHIFTR(v, s) ((v) >> (s))
+
+#else
+
+#define SKIP_IP_CONCAT2
+
+#endif
+
+/*
+ * This kernel handles internal padding cases by abstracting the problem into 3 cases:
+ *     - Reads and writes fall completely into the first source and can be copied as is
+ *
+ *     - Reads and writes fall across the boundary between two sources, neighboring "read blocks"
+ *       across the concat-axis boundary will need to be combined to form a single "write block" that will
+ *       consist halfway of each of the source blocks
+ *
+ *     - Reads and writes that fall completely within the second source will continue to misalign along
+ *       the halfway cutoff and will likewise require two reads per write, just with both reads within
+ *       the same second source
+ * The kernel will perform the reads/writes on aligned boundaries through additional logic that shifts
+ * misaligned values in registers to form full cache-lines
+ */
+#if SIMD != 1
+__attribute__((intel_reqd_sub_group_size(SIMD)))
+#endif
+__kernel void
+internal_padding_block_concat2(__global DATA_T *dst,
+        const idx_t dst_concat_axis, const idx_t dst_padded_concat_axis,
+        __global const DATA_T *src0, const idx_t offset0,
+        const idx_t padded_offset0, const idx_t src_concat_axis0,
+        const idx_t src_padded_concat_axis0, __global const DATA_T *src1,
+        const idx_t offset1, const idx_t padded_offset1,
+        const idx_t src_concat_axis1, const idx_t src_padded_concat_axis1,
+        const idx_t inner_dim) {
+#ifndef SKIP_IP_CONCAT2
+
+    const int dtsize = DATA_TYPE_SIZE;
+    const int loads_per_sg = NPERSG;
+    const int elems_per_sg = SIMD * loads_per_sg;
+
+#if BLOCK_DEPTH > 0
+    const int B0 = BLOCK_B0;
+#else
+    const int B0 = 1;
+#endif
+
+    const idx_t first_boundary_block
+            = ((DIV_UP(src_concat_axis0, B0) - 1) * inner_dim);
+    const idx_t last_boundary_block = first_boundary_block + inner_dim - 1;
+    const idx_t first_zp_block = (dst_concat_axis / B0 * inner_dim);
+    const idx_t tot_boundary_block
+            = (DIV_UP(dst_padded_concat_axis, B0) * inner_dim);
+
+    const int blocks_per_sg = elems_per_sg
+            / B0; // host side check ensures whole multiple of B0 and elems_per_sg > B0
+
+    __global const DATA_T *src;
+
+    // within block idx, ex: 0-7, 0-15, 0-23, 0-31
+    idx_t blid = get_sub_group_local_id() % B0;
+
+    idx_t id_start = (get_global_id(0) / SIMD) * elems_per_sg
+            + get_sub_group_local_id();
+    idx_t bii = id_start / B0;
+
+    idx_t sg_first_bii = (get_global_id(0) / SIMD) * elems_per_sg / B0;
+
+    // index along concat dimension
+    idx_t ic = (bii / inner_dim) * B0 + blid;
+    idx_t ic_end = ((bii + blocks_per_sg) / inner_dim) * B0 + blid;
+
+    idx_t sg_last_bii = sg_first_bii + blocks_per_sg - 1;
+
+    idx_t ccsz
+            = src_concat_axis0; // concat dimension for calculating batched src
+    idx_t padded_ccsz
+            = padded_offset1; // padded concat dimension for calculating batched src
+
+    idx_t batch_offset0 = inner_dim * padded_ccsz * get_global_id(1);
+    idx_t batch_offset1
+            = inner_dim * src_padded_concat_axis1 * get_global_id(1);
+    idx_t ext_batch_offset
+            = inner_dim * dst_padded_concat_axis * get_global_id(1);
+
+    // completely aligned r/w blocks, no boundary src case nor misaligned reads
+
+    if (sg_last_bii < first_boundary_block) {
+        COMPUTE_T val = AS_FULL(
+                BLOCK_READ_UL(src0 + batch_offset0 + sg_first_bii * B0));
+        BLOCK_WRITE_UL(dst + ext_batch_offset + sg_first_bii * B0, AS_VEC(val));
+    } else if (sg_first_bii > last_boundary_block) {
+        // if sg_bii0 fully within second source, update src_bii+other idx vars to match src1
+
+        idx_t src_blid = (ic - src_concat_axis0) % B0;
+        idx_t src_bii = sg_first_bii
+                - ((padded_offset1 / B0)
+                        * inner_dim); // update inner logical block idx to 0-nblocks read range for src1
+        if (src_bii < 0) { src_bii += inner_dim; }
+
+        idx_t sg_last_src_bii = src_bii + blocks_per_sg - 1;
+
+        const int cutoff = (B0 + (offset1 - padded_offset1) % B0)
+                % B0; // positive modulo(offset / padded_offset, block size) |--|-----|
+        // this cutoff will persist in following misaligned (multi-cacheline / block src)
+        // form full write block by combining two read blocks |-A-|--B---|
+
+        COMPUTE_T aVal
+                = AS_FULL(BLOCK_READ_UL(src1 + batch_offset1 + src_bii * B0));
+
+        idx_t next_block = src_bii
+                + inner_dim; // offset to read next required block for second half of cutoff,
+        idx_t next_block_last = next_block + blocks_per_sg - 1;
+        const idx_t tot_src1_block
+                = (DIV_UP(src_padded_concat_axis1, B0) * inner_dim);
+
+        COMPUTE_T bVal;
+        if (next_block_last > tot_src1_block) { // keep reads in bounds
+            int n_blocks_tail = tot_src1_block - next_block;
+            int rem_elems = n_blocks_tail * B0;
+
+#if NPERSG > 1
+            DDATA_T v = 0;
+            for (int b = get_sub_group_local_id(), vid = 0; b < rem_elems;
+                    b += SIMD, vid++) {
+                v[vid] = src1[batch_offset1 + next_block * B0 + b];
+            }
+            bVal = AS_FULL(v);
+#else
+            for (int b = get_sub_group_local_id(); b < rem_elems; b += SIMD) {
+                bVal = src1[batch_offset1 + next_block * B0 + b];
+            }
+#endif
+        } else {
+            bVal = AS_FULL(BLOCK_READ_UL(src1 + batch_offset1
+                    + next_block
+                            * B0)); //TODO: MAYBE_READ like in simple concat kernel? no perf difference.
+        }
+        bVal = SHUFFLE_UP(bVal, bVal, cutoff);
+
+        // shift both halves of block to match cutoff
+        int sg_shuffle_dt
+                = (B0 - cutoff); // required shift to match block load to cutoff
+
+        COMPUTE_T offset_vec_data = SHUFFLE_DOWN(aVal, aVal, sg_shuffle_dt);
+
+        if ((ic % B0) < cutoff) {
+            aVal = offset_vec_data;
+        } else {
+            aVal = bVal;
+        }
+
+#if NPERSG > 1
+        if (sg_first_bii >= first_zp_block) {
+            if ((ic % B0) >= cutoff) { aVal = 0; }
+        } else if (sg_last_bii >= first_zp_block) {
+            const int blocks_per_simd1 = SIMD / B0;
+            const int leading_boundary_shift = first_zp_block - sg_first_bii;
+            int block_scaled_leading_shift
+                    = DIV_UP(leading_boundary_shift, blocks_per_simd1);
+            int rollover = leading_boundary_shift % blocks_per_simd1;
+
+            if (((get_sub_group_local_id() / B0) + rollover)
+                    >= blocks_per_simd1)
+                block_scaled_leading_shift++;
+
+            if ((ic % B0) >= cutoff) {
+                aVal = aVal
+                        & SHIFTR(FULLMASK,
+                                (NPERSG - block_scaled_leading_shift)
+                                        * DATA_TYPE_SIZE * 8);
+            }
+        }
+#else
+        if (ic >= dst_concat_axis) { aVal = 0; }
+#endif
+
+        if (sg_last_bii >= tot_boundary_block) { // keep writes in bounds
+            int n_blocks_tail = tot_boundary_block - sg_first_bii;
+            int rem_elems = n_blocks_tail * B0;
+
+#if NPERSG > 1
+            for (int b = get_sub_group_local_id(), vid = 0; b < rem_elems;
+                    b += SIMD, vid++) {
+                //TODO: MAYBE_WRITE like in simple concat kernel? nonstandard CL
+                dst[ext_batch_offset + sg_first_bii * B0 + b]
+                        = AS_VEC(aVal)[vid];
+            }
+#else
+            for (int b = get_sub_group_local_id(); b < rem_elems; b += SIMD) {
+                dst[ext_batch_offset + sg_first_bii * B0 + b] = aVal;
+            }
+#endif
+        } else {
+            BLOCK_WRITE_UL(
+                    dst + ext_batch_offset + sg_first_bii * B0, AS_VEC(aVal));
+        }
+    } else { // sg span falls within boundary blocks, handle leading/trailing overlaps
+        // load blocks corresponding to first source
+        COMPUTE_T aVal = AS_FULL(BLOCK_READ_UL(src0 + batch_offset0
+                + sg_first_bii
+                        * B0)); // TODO: does this read past end? logic similar to above
+
+        // since these are "boundary" blocks load corresponding blocks from next source
+        idx_t next_bii = sg_first_bii
+                - ((padded_offset1 / B0)
+                        * inner_dim); // update inner logical block idx to 0-nblocks read range for src1
+        if (next_bii < 0) {
+            next_bii += inner_dim; // bump negative index to next src
+            next_bii = (next_bii < 0)
+                    ? 0
+                    : next_bii; // if next src begins still "negative" clamp to beginning of next src
+            // can happen if sg spans [n-2, n-1] vertical blocks
+        }
+
+        // with certain inner_dim sizes, sg may span before or after border blocks
+        // additional shifting logic will be required in these cases
+        int leading_boundary_shift = (sg_first_bii < first_boundary_block)
+                ? first_boundary_block - sg_first_bii
+                : 0; // sg span begins before boundary span
+        int trailing_boundary_shift = (sg_last_bii > last_boundary_block)
+                ? sg_last_bii - last_boundary_block
+                : 0; // sg span ends after boundary span
+
+        const int cutoff = (B0 + (offset1 - padded_offset1) % B0)
+                % B0; // positive modulo( offset/padded_offset , block size) |-|-------|
+
+        int blocks_per_simd1 = SIMD / B0;
+        COMPUTE_T bVal = AS_FULL(BLOCK_READ_UL(src1 + batch_offset1
+                + next_bii * B0)); // load next blocks across concat boundary
+        if (leading_boundary_shift) {
+            int block_scaled_leading_shift
+                    = leading_boundary_shift / blocks_per_simd1;
+            if (blocks_per_simd1 > 1) {
+                int rollover = leading_boundary_shift % blocks_per_simd1;
+
+                if (((get_sub_group_local_id() / B0) + rollover)
+                        >= blocks_per_simd1)
+                    block_scaled_leading_shift++;
+
+                bVal = SHIFTL(bVal,
+                        (block_scaled_leading_shift * DATA_TYPE_SIZE * 8));
+
+                // cannot directly shift values to corresponding location in sg since each
+                // workitem is responsible for multiple blocks, figure out source block
+                int src_bank
+                        = ((get_sub_group_local_id() / B0)
+                                  + leading_boundary_shift % blocks_per_simd1)
+                        % blocks_per_simd1;
+
+                bVal = SHUFFLE(
+                        bVal, src_bank * B0 + (get_sub_group_local_id() % B0));
+
+            } else {
+                bVal = SHIFTL(bVal,
+                        (block_scaled_leading_shift * DATA_TYPE_SIZE * 8));
+            }
+        }
+
+        int sg_shuffle_dt = cutoff;
+        COMPUTE_T offset_vec_data;
+        const COMPUTE_T zero = 0;
+        offset_vec_data = SHUFFLE_UP(zero, bVal,
+                sg_shuffle_dt); // will move src1 block to cutoff
+
+        COMPUTE_T cVal;
+        if (trailing_boundary_shift) {
+            const idx_t trailing_bii = 0;
+            // trailing portion of sg span across boundary will roll over to beginning portion of next tensor
+            cVal = AS_FULL(
+                    BLOCK_READ_UL(src1 + batch_offset1 + trailing_bii * B0));
+
+            int block_dt = (blocks_per_sg - trailing_boundary_shift);
+            int block_dt_from_src1 = sg_first_bii - first_boundary_block;
+
+            // offset trailing portion to match cutoff
+            cVal = SHUFFLE_DOWN(cVal, zero, (B0 - cutoff));
+
+            // cannot directly shift values to corresponding location in sg since each
+            // workitem is responsible for multiple blocks, figure out source block
+            int src_bank = ((get_sub_group_local_id() / B0)
+                                   + trailing_boundary_shift % blocks_per_simd1)
+                    % blocks_per_simd1;
+
+            int ntrail = 0;
+            for (int i = 0; i < NPERSG; ++i) {
+                if ((bii + i * blocks_per_simd1) > last_boundary_block) {
+                    ntrail = NPERSG - i;
+                    break;
+                }
+            }
+
+            COMPUTE_T trailmask = (ntrail < NPERSG)
+                    ? SHIFTR(SHIFTL(FULLMASK, ntrail * DATA_TYPE_SIZE * 8),
+                            ntrail * DATA_TYPE_SIZE * 8)
+                    : zero;
+
+            if (cutoff > 0 && (ic % B0) >= cutoff) {
+                aVal = aVal
+                        & trailmask; // zero out portions in anticipation of further trailing/cutoff values
+                aVal = aVal | offset_vec_data;
+            }
+
+            // reorder according to source banks
+            cVal = SHUFFLE(cVal, src_bank * B0 + get_sub_group_local_id() % B0);
+            cVal = SHIFTL(cVal, ((NPERSG - ntrail) * DATA_TYPE_SIZE * 8));
+
+            if (cutoff > 0 && (get_sub_group_local_id() % B0) < cutoff) {
+                aVal = (aVal & trailmask) | (cVal & ~trailmask);
+            }
+
+#if NPERSG > 1
+            if (sg_first_bii >= first_zp_block) {
+                if ((ic % B0) >= cutoff) { aVal = 0; }
+            } else if (sg_last_bii >= first_zp_block) {
+                const int blocks_per_simd1 = SIMD / B0;
+                const int leading_boundary_shift
+                        = first_zp_block - sg_first_bii;
+                int block_scaled_leading_shift
+                        = (leading_boundary_shift) / blocks_per_simd1;
+                int rollover = leading_boundary_shift % blocks_per_simd1;
+
+                if (((get_sub_group_local_id() / B0) + rollover)
+                        >= blocks_per_simd1)
+                    block_scaled_leading_shift++;
+
+                if ((ic % B0) >= cutoff) {
+                    aVal = aVal
+                            & SHIFTR(FULLMASK,
+                                    (NPERSG - block_scaled_leading_shift)
+                                            * DATA_TYPE_SIZE * 8);
+                }
+            }
+#else
+            if (ic >= dst_concat_axis) { aVal = 0; }
+#endif
+        } else {
+            // boundary case w/no trailing overlap
+            if (cutoff > 0 && (ic % B0) >= cutoff) {
+                aVal = aVal | offset_vec_data;
+            }
+        }
+        BLOCK_WRITE_UL(
+                dst + ext_batch_offset + sg_first_bii * B0, AS_VEC(aVal));
+    }
+
+#endif
+}
diff --git a/src/gpu/intel/ocl/reusable_simple_concat.cpp b/src/gpu/intel/ocl/reusable_simple_concat.cpp
index 85add60c05c..4700cb6d0f1 100644
--- a/src/gpu/intel/ocl/reusable_simple_concat.cpp
+++ b/src/gpu/intel/ocl/reusable_simple_concat.cpp
@@ -28,24 +28,16 @@ namespace gpu {
 namespace intel {
 namespace ocl {
 
-static status_t init_conf_common(impl::engine_t *engine, const concat_pd_t *pd,
+static status_t normalize_reusable_simple_concat(
         reusable_simple_concat_params_t &conf,
-        reusable_simple_concat_runtime_params_t &rt_conf) {
-    using namespace utils;
-    const memory_desc_t &ref_dst_md = *pd->dst_md();
+        reusable_simple_concat_runtime_params_t &rt_conf,
+        impl::engine_t *engine, const concat_pd_t *pd,
+        const normalization_t &normalize) {
+
     const memory_desc_wrapper ref_dst_mdw = *pd->dst_md();
 
-    if (ref_dst_md.format_kind != format_kind::blocked) {
-        return status::unimplemented;
-    }
     const auto concat_dim = pd->concat_dim();
 
-    normalization_t normalize(ref_dst_md, concat_dim);
-    for (int i = 0; i < pd->n_inputs(); ++i) {
-        const memory_desc_t &src_md = *pd->src_md(i);
-        if (!normalize.add_source(src_md)) { return status::unimplemented; }
-    }
-
     auto *compute_engine = utils::downcast<compute::compute_engine_t *>(engine);
     auto *device_info = compute_engine->device_info();
     dim_t max_write_size = normalize.max_write_size();
@@ -163,10 +155,203 @@ static status_t init_conf_common(impl::engine_t *engine, const concat_pd_t *pd,
     return status::success;
 }
 
+static status_t try_normalize_ip_concat2(reusable_simple_concat_params_t &conf,
+        reusable_simple_concat_runtime_params_t &rt_conf,
+        impl::engine_t *engine, const concat_pd_t *pd,
+        normalization_t normalize) {
+
+    using namespace utils;
+    const memory_desc_wrapper ref_dst_mdw = *pd->dst_md();
+
+    const auto concat_dim = pd->concat_dim();
+
+    auto *compute_engine = utils::downcast<compute::compute_engine_t *>(engine);
+    auto *device_info = compute_engine->device_info();
+
+    normalize.set_pessimistic_chunk_size();
+
+    const int max_sg_size = device_info->max_subgroup_size();
+    const auto data_type_size = normalize.data_type_size();
+    dim_t dst_bytes = ref_dst_mdw.size();
+    dim_t max_bytes = ref_dst_mdw.size();
+
+    conf.read_block = 1;
+    conf.write_block = 1;
+    conf.bytes_per_workitem = 0;
+
+    memory_desc_t dst_md, src_md;
+    int offset = 0, padded_offset = 0, nonempty_inputs = 0;
+    dim_t final_padding = 0;
+    for (int i = 0; i < pd->n_inputs(); ++i) {
+        if (pd->src_md(i)->padded_dims[concat_dim] == 0) continue;
+        memcpy(&src_md, pd->src_md(i), sizeof(memory_desc_t));
+
+        normalize(src_md);
+        dim_t concat_dim = src_md.dims[axis::concat];
+        dim_t concat_pdim = src_md.padded_dims[axis::concat];
+
+        rt_conf.offset[nonempty_inputs] = offset;
+        rt_conf.padded_offset[nonempty_inputs] = padded_offset;
+        final_padding = concat_pdim - concat_dim;
+        offset += concat_dim;
+        padded_offset += concat_pdim;
+
+        if (nonempty_inputs == 0) {
+            rt_conf.src_concat_axis0 = src_md.dims[axis::concat];
+            rt_conf.padded_src_concat_axis0 = src_md.padded_dims[axis::concat];
+        }
+        if (nonempty_inputs == 1) {
+            rt_conf.src_concat_axis1 = src_md.dims[axis::concat];
+            rt_conf.padded_src_concat_axis1 = src_md.padded_dims[axis::concat];
+        }
+        nonempty_inputs++;
+    }
+    memcpy(&dst_md, pd->dst_md(), sizeof(memory_desc_t));
+    normalize(dst_md);
+    const auto &dst_blkg = dst_md.format_desc.blocking;
+    rt_conf.dst_extern_dim_size
+            = dst_blkg.strides[axis::outer] * data_type_size;
+    rt_conf.dst_padded_concat_axis = dst_md.padded_dims[axis::concat];
+    rt_conf.dst_concat_axis
+            = std::min(rt_conf.dst_padded_concat_axis, offset + final_padding);
+
+    conf.n_blocks = 0;
+    dim_t stride = 1;
+    for (int i = dst_blkg.inner_nblks - 1; i >= 0; --i) {
+        auto blk = dst_blkg.inner_blks[i];
+        auto idx = dst_blkg.inner_idxs[i];
+        if (idx == axis::concat) {
+            conf.blocks[conf.n_blocks] = blk;
+            conf.strides[conf.n_blocks] = stride;
+            conf.n_blocks++;
+        }
+        stride *= blk;
+    }
+
+    int max_simd = 1;
+    const size_t preferred_bytes_per_workitem = 16;
+    size_t bytes_per_workitem = preferred_bytes_per_workitem;
+    for (int simd : {32, 16, 8, 1}) {
+        if (simd > max_sg_size) continue;
+        if (simd > 1 && !compute_engine->mayiuse_sub_group(simd)) continue;
+        const dim_t total_elems = dst_bytes / data_type_size;
+        if (simd > total_elems) continue;
+
+        bytes_per_workitem
+                = (data_type_size == 8) ? 8 : preferred_bytes_per_workitem;
+        const dim_t elems_per_simd
+                = simd * (bytes_per_workitem / data_type_size);
+        const bool simd_even_block_multiple
+                = ((elems_per_simd) % conf.blocks[0]) == 0;
+        const bool simd_read_gte_blocksize = (elems_per_simd) >= conf.blocks[0];
+
+        // TODO: too restrictive, kernel will need to be updated with blid replaced w/[blid_start,blid_end]
+        const bool simd_gte_blocksize = (simd >= conf.blocks[0]);
+
+        if (simd_even_block_multiple && simd_read_gte_blocksize
+                && simd_gte_blocksize) {
+            max_simd = simd;
+            break;
+        }
+    }
+    if (max_simd == 1) { return status::unimplemented; }
+
+    dim_t inner_offset = dst_blkg.strides[axis::concat];
+
+    conf.n = nonempty_inputs;
+    conf.simd = max_simd;
+    rt_conf.inner_axis = inner_offset;
+    conf.data_type_size = static_cast<int>(data_type_size);
+
+    conf.use_large_index = (max_bytes > std::numeric_limits<int>::max());
+
+    // attempt to enable internal padding kernel
+    size_t concat2_inner_axis = dst_md.dims[axis::inner];
+    size_t concat2_dtsize = static_cast<size_t>(data_type_size);
+
+    size_t loads_per_thread = bytes_per_workitem / concat2_dtsize;
+
+    // heuristic for problem size too small
+    size_t min_block_read_elements = conf.simd * loads_per_thread;
+    size_t row_inner_elems = conf.blocks[0] * concat2_inner_axis;
+    bool inner_size_sufficient = row_inner_elems > min_block_read_elements;
+    bool problem_size_sufficient = dst_bytes > 5e5;
+
+    // heuristic data misaligned for subgroup loads/stores
+    bool can_subgroup_read_dt = true;
+    size_t min_subgroup_alignment_size = 4;
+    if (concat2_dtsize < min_subgroup_alignment_size) {
+        if (conf.n_blocks > 0) {
+            can_subgroup_read_dt = (concat2_dtsize * conf.blocks[0])
+                    >= min_subgroup_alignment_size;
+        } else {
+            can_subgroup_read_dt = false;
+        }
+    }
+
+    bool supported_block_size = (conf.n_blocks > 0)
+            && ((conf.blocks[0] == 4) || (conf.blocks[0] == 8)
+                    || (conf.blocks[0] == 16) || (conf.blocks[0] == 32));
+
+    bool can_use_internal_padding_concat2 = (conf.n == 2)
+            && can_subgroup_read_dt && inner_size_sufficient
+            && supported_block_size && problem_size_sufficient;
+
+    if (can_use_internal_padding_concat2) {
+        rt_conf.inner_axis = concat2_inner_axis;
+        conf.data_type_size = static_cast<int>(concat2_dtsize);
+        conf.use_internal_padding_kernel = true;
+        conf.bytes_per_workitem = static_cast<int>(bytes_per_workitem);
+
+        const compute::range_t gws
+                = {static_cast<size_t>(
+                           utils::div_up(dst_md.padded_dims[axis::concat]
+                                           * dst_md.dims[axis::inner],
+                                   conf.simd * loads_per_thread)
+                           * conf.simd),
+                        static_cast<size_t>(dst_md.dims[axis::outer]), 1};
+        rt_conf.gws_d = gws;
+        rt_conf.lws_d = compute::get_optimal_lws(
+                rt_conf.gws_d, dim_idx::invalid, device_info->gpu_arch());
+        return status::success;
+    }
+
+    return status::unimplemented;
+}
+
+static status_t init_conf_common(impl::engine_t *engine, const concat_pd_t *pd,
+        reusable_simple_concat_params_t &conf,
+        reusable_simple_concat_runtime_params_t &rt_conf) {
+    using namespace utils;
+    const memory_desc_t &ref_dst_md = *pd->dst_md();
+
+    if (ref_dst_md.format_kind != format_kind::blocked) {
+        return status::unimplemented;
+    }
+    const auto concat_dim = pd->concat_dim();
+
+    normalization_t normalize(ref_dst_md, concat_dim);
+    for (int i = 0; i < pd->n_inputs(); ++i) {
+        const memory_desc_t &src_md = *pd->src_md(i);
+        if (!normalize.add_source(src_md)) { return status::unimplemented; }
+    }
+
+    if (normalize.has_internal_padding()) {
+        status_t s = try_normalize_ip_concat2(
+                conf, rt_conf, engine, pd, normalize);
+        if (s == status::success) { return s; }
+    }
+
+    return normalize_reusable_simple_concat(
+            conf, rt_conf, engine, pd, normalize);
+}
+
 compute::kernel_ctx_t reusable_simple_concat_params_t::get_kernel_ctx() const {
     compute::kernel_ctx_t kernel_ctx;
+
     kernel_ctx.define_int("WRITE_BLOCK", write_block);
     kernel_ctx.define_int("READ_BLOCK", read_block);
+
     kernel_ctx.define_int("N_INPUTS", n);
     kernel_ctx.define_int("BLOCK_DEPTH", n_blocks);
     for (int i = 0; i < n_blocks; ++i) {
@@ -177,6 +362,7 @@ compute::kernel_ctx_t reusable_simple_concat_params_t::get_kernel_ctx() const {
     kernel_ctx.define_int("DATA_TYPE_SIZE", data_type_size);
 
     kernel_ctx.define_int("USE_LARGE_INDEX", use_large_index);
+    kernel_ctx.define_int("BYTES_PER_WORKITEM", bytes_per_workitem);
     return kernel_ctx;
 }
 
@@ -212,12 +398,14 @@ void push_idx_kernel_args(compute::kernel_arg_list_t &partial_list,
         cutoff |= (rt_conf.offset[valid_idx] % rt_conf.read_overlap != 0);
         valid_idx++;
     }
+
     partial_list.append(static_cast<IDX_T>(rt_conf.dst_concat_axis));
     partial_list.append(static_cast<IDX_T>(rt_conf.dst_padded_concat_axis));
 
     partial_list.append(static_cast<IDX_T>(rt_conf.read_overlap));
     partial_list.append(static_cast<IDX_T>(rt_conf.gws0_block));
     partial_list.append(static_cast<IDX_T>(rt_conf.inner_axis));
+
     // Workgroup reads may extend past the concat dimension, so we must also
     // consider the external axis when computing write indices
     bool must_compute_ext_idx
@@ -226,6 +414,42 @@ void push_idx_kernel_args(compute::kernel_arg_list_t &partial_list,
     partial_list.append(static_cast<std::uint8_t>(must_compute_ext_idx));
 }
 
+template <typename IDX_T>
+void push_idx_kernel_args_internal_padding(
+        compute::kernel_arg_list_t &partial_list, const exec_ctx_t &ctx,
+        const reusable_simple_concat_params_t &conf,
+        const reusable_simple_concat_runtime_params_t &rt_conf,
+        const concat_pd_t *pd) {
+    const auto concat_dim = pd->concat_dim();
+
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_concat_axis));
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_padded_concat_axis));
+
+    for (int idx = 0, valid_idx = 0; idx < pd->n_inputs(); ++idx) {
+        // skip invalid inputs
+        if (pd->src_md(idx)->padded_dims[concat_dim] == 0) continue;
+
+        auto &src = CTX_IN_STORAGE(DNNL_ARG_MULTIPLE_SRC + idx);
+        partial_list.append(src);
+
+        partial_list.append(static_cast<IDX_T>(rt_conf.offset[valid_idx]));
+        partial_list.append(
+                static_cast<IDX_T>(rt_conf.padded_offset[valid_idx]));
+
+        if (valid_idx == 0) {
+            partial_list.append(rt_conf.src_concat_axis0);
+            partial_list.append(rt_conf.padded_src_concat_axis0);
+        } else {
+            partial_list.append(rt_conf.src_concat_axis1);
+            partial_list.append(rt_conf.padded_src_concat_axis1);
+        }
+
+        valid_idx++;
+    }
+
+    partial_list.append(static_cast<IDX_T>(rt_conf.inner_axis));
+}
+
 status_t reusable_simple_concat_t::execute_concat(const exec_ctx_t &ctx) const {
     const auto &conf = pd()->conf;
     const auto &rt_conf = pd()->rt_conf;
@@ -233,20 +457,35 @@ status_t reusable_simple_concat_t::execute_concat(const exec_ctx_t &ctx) const {
 
     compute::kernel_arg_list_t arg_list;
     auto &dst = CTX_OUT_STORAGE(DNNL_ARG_DST);
-    arg_list.append(dst);
-    arg_list.append(static_cast<std::uint64_t>(rt_conf.dst_offset0));
-    arg_list.append(static_cast<std::uint64_t>(
-            rt_conf.dst_extern_dim_size / conf.data_type_size));
 
-    if (conf.use_large_index) {
-        push_idx_kernel_args<std::uint64_t>(arg_list, ctx, conf, rt_conf, pd());
-    } else {
-        push_idx_kernel_args<int>(arg_list, ctx, conf, rt_conf, pd());
-    }
+    arg_list.append(dst);
 
     auto nd_range = compute::nd_range_t(rt_conf.gws_d, rt_conf.lws_d);
 
-    status_t status = parallel_for(ctx, nd_range, kernel_, arg_list);
+    status_t status;
+    if (conf.use_internal_padding_kernel) {
+        if (conf.use_large_index) {
+            push_idx_kernel_args_internal_padding<std::uint64_t>(
+                    arg_list, ctx, conf, rt_conf, pd());
+        } else {
+            push_idx_kernel_args_internal_padding<int>(
+                    arg_list, ctx, conf, rt_conf, pd());
+        }
+        status = parallel_for(
+                ctx, nd_range, internal_padding_kernel_, arg_list);
+    } else {
+        arg_list.append(static_cast<std::uint64_t>(rt_conf.dst_offset0));
+        arg_list.append(static_cast<std::uint64_t>(
+                rt_conf.dst_extern_dim_size / conf.data_type_size));
+
+        if (conf.use_large_index) {
+            push_idx_kernel_args<std::uint64_t>(
+                    arg_list, ctx, conf, rt_conf, pd());
+        } else {
+            push_idx_kernel_args<int>(arg_list, ctx, conf, rt_conf, pd());
+        }
+        status = parallel_for(ctx, nd_range, kernel_, arg_list);
+    }
     return status;
 }
 
diff --git a/src/gpu/intel/ocl/reusable_simple_concat.hpp b/src/gpu/intel/ocl/reusable_simple_concat.hpp
index 1a1cdf32285..a2936af2c6b 100644
--- a/src/gpu/intel/ocl/reusable_simple_concat.hpp
+++ b/src/gpu/intel/ocl/reusable_simple_concat.hpp
@@ -1,5 +1,5 @@
 /*******************************************************************************
-* Copyright 2024 Intel Corporation
+* Copyright 2024-2025 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ struct reusable_simple_concat_params_t
 
     const std::vector<const char *> &get_kernel_names() const {
         static const std::vector<const char *> kernel_names
-                = {"reusable_simple_concat"};
+                = {"reusable_simple_concat", "internal_padding_block_concat2"};
         return kernel_names;
     }
 
@@ -57,14 +57,15 @@ struct reusable_simple_concat_params_t
     int n;
     int simd;
     int data_type_size;
+    int bytes_per_workitem;
     bool use_large_index = true;
-    uint8_t padding[3] = {0};
+    bool use_internal_padding_kernel = false;
+    uint8_t padding[6] = {0};
 };
 
 struct reusable_simple_concat_runtime_params_t {
     dim_t dst_extern_dim_size;
     dim_t dst_offset0;
-    dim_t dst_ext_offset;
     dim_t src_extern_dim_sizes[64];
     dim_t offset[64];
     dim_t padded_offset[64];
@@ -75,6 +76,11 @@ struct reusable_simple_concat_runtime_params_t {
     dim_t gws0_block;
     dim_t inner_axis;
 
+    dim_t src_concat_axis0;
+    dim_t src_concat_axis1;
+    dim_t padded_src_concat_axis0;
+    dim_t padded_src_concat_axis1;
+
     compute::range_t gws_d = compute::range_t::one();
     compute::range_t lws_d;
 };
@@ -106,9 +112,11 @@ struct reusable_simple_concat_t : public gpu_primitive_t {
     };
 
     status_t init(impl::engine_t *engine) override {
-        CHECK(create_kernel(
-                engine, kernel_, pd()->conf.get_kernel_names()[0], pd()->conf));
-        if (!kernel_) return status::runtime_error;
+        std::vector<compute::kernel_t> kernels;
+        CHECK(create_kernels(
+                engine, kernels, pd()->conf.get_kernel_names(), pd()->conf));
+        kernel_ = kernels[0];
+        internal_padding_kernel_ = kernels[1];
 
         return status::success;
     }
@@ -122,6 +130,7 @@ struct reusable_simple_concat_t : public gpu_primitive_t {
     const pd_t *pd() const { return (const pd_t *)primitive_t::pd().get(); }
 
     compute::kernel_t kernel_;
+    compute::kernel_t internal_padding_kernel_;
 };
 
 } // namespace ocl
diff --git a/src/gpu/intel/ocl/simple_concat.cl b/src/gpu/intel/ocl/simple_concat.cl
deleted file mode 100644
index 2873e878235..00000000000
--- a/src/gpu/intel/ocl/simple_concat.cl
+++ /dev/null
@@ -1,220 +0,0 @@
-/*******************************************************************************
-* Copyright 2020-2024 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.
-*******************************************************************************/
-
-#define ZERO_PAD_OFFSET CAT(OFFSET, N_INPUTS)
-#define ZERO_PAD_CONCAT_DIM CAT(PADDED_OFFSET, N_INPUTS)
-
-#include "gpu/intel/ocl/concat_common.h"
-
-#define SRC_PTR(n) __global const DATA_T *src##n
-#define SRC_PTRS REDUCE(N_INPUTS, JOIN_COMMA, SRC_PTR)
-
-size_t get_concat_idx(size_t inner_idx) {
-    size_t block = 1;
-    size_t idx = 0;
-#define HANDLE(n) \
-    idx += block * ((inner_idx / (BLOCK_S##n)) % (BLOCK_B##n)); \
-    block *= BLOCK_B##n
-
-    REDUCE(BLOCK_DEPTH, JOIN_SEMICOLON, HANDLE);
-#undef HANDLE
-    return idx + block * (inner_idx / INNER_OFFSET);
-}
-
-size_t get_concat_offset(size_t concat_idx) {
-    size_t block = 1;
-    size_t idx = 0;
-#define HANDLE(n) \
-    idx += (BLOCK_S##n) * ((concat_idx / block) % (BLOCK_B##n)); \
-    block *= BLOCK_B##n
-
-    REDUCE(BLOCK_DEPTH, JOIN_SEMICOLON, HANDLE);
-#undef HANDLE
-    return idx + INNER_OFFSET * (concat_idx / block);
-}
-
-struct write_info_t {
-    size_t idx;
-    bool write;
-};
-
-struct write_info_t get_write_info(size_t src_idx, size_t src_ext_offset,
-        size_t concat_offset, size_t thread_offset, size_t concat_axis) {
-#define LOGICAL_OR(x, y) (x) || (y)
-#define HANDLE(n) CAT(OFFSET, n) % READ_OVERLAP != 0
-#define CUTOFF REDUCE(N_INPUTS, LOGICAL_OR, HANDLE) || HANDLE(N_INPUTS)
-#if READ_OVERLAP * GWS0_BLOCK > INNER_OFFSET || CUTOFF
-    const size_t ext_idx = src_idx / src_ext_offset;
-    const size_t inner_idx = src_idx % src_ext_offset;
-#else
-    const size_t ext_idx = 0;
-    const size_t inner_idx = src_idx;
-#endif
-#undef CUTOFF
-#undef HANDLE
-#undef LOGICAL_OR
-
-    struct write_info_t info;
-#if BLOCK_DEPTH > 0
-    size_t concat_idx = get_concat_idx(inner_idx);
-    bool write_value = concat_offset + concat_idx < concat_axis;
-
-#define CONCAT_AXIS(n) SRC##n##_CONCAT_AXIS
-#define RIGHT(x, y) y
-#if REDUCE(N_INPUTS, RIGHT, CONCAT_AXIS) < ZERO_PAD_CONCAT_DIM
-    size_t zero_pad_offset = ZERO_PAD_OFFSET - concat_axis + thread_offset;
-    bool write_zeropad = zero_pad_offset + concat_idx < ZERO_PAD_CONCAT_DIM;
-
-    size_t write_offset = write_value ? concat_offset : zero_pad_offset;
-    info.write = write_value || write_zeropad;
-#else
-    size_t write_offset = concat_offset;
-    info.write = write_value;
-#endif
-#undef RIGHT
-#undef CONCAT_AXIS
-
-    info.idx = ext_idx * DST_EXT_OFFSET + inner_idx
-            + get_concat_offset(concat_idx + write_offset)
-            - get_concat_offset(concat_idx);
-#else
-    info.write = true;
-    info.idx = ext_idx * DST_EXT_OFFSET + INNER_OFFSET * concat_offset
-            + inner_idx;
-#endif
-    return info;
-}
-
-#if SIMD != 1
-__attribute__((intel_reqd_sub_group_size(SIMD)))
-#endif
-__kernel void
-simple_concat(__global DATA_T *dst, long dst_offset0, SRC_PTRS) {
-    __global const DATA_T *src;
-    size_t src_ext_offset, input_offset;
-    size_t input_padded_offset, concat_axis_size;
-
-#define CHECK_AND_GET(n) \
-    if (get_global_id(2) >= PADDED_OFFSET##n) { \
-        src = src##n; \
-        input_offset = OFFSET##n; \
-        input_padded_offset = PADDED_OFFSET##n; \
-        concat_axis_size = SRC##n##_CONCAT_AXIS; \
-        src_ext_offset = SRC##n##_EXT_OFFSET; \
-    }
-    REDUCE(N_INPUTS, JOIN_ELSE, CHECK_AND_GET);
-#undef CHECK_AND_GET
-
-    const size_t block_offset
-            = GWS0_BLOCK * (get_global_id(2) - input_padded_offset)
-            + (get_global_id(0) / SIMD) * READ_BLOCK;
-    const size_t thr_elems = READ_BLOCK / SIMD;
-    src += READ_OVERLAP * get_global_id(1) * src_ext_offset + block_offset;
-
-#define WRITE_INFO(idx) \
-    get_write_info(block_offset + (idx), src_ext_offset, input_offset, \
-            input_padded_offset, concat_axis_size)
-#if SIMD == 1
-    dst += dst_offset0 + READ_OVERLAP * get_global_id(1) * DST_EXT_OFFSET;
-    for (int i = 0; i < thr_elems; ++i) {
-        struct write_info_t info = WRITE_INFO(i);
-        if (info.write) dst[info.idx] = src[i];
-    }
-#else
-    const size_t lane = get_sub_group_local_id() % SIMD;
-    buffer_t buf;
-
-#if (READ_BLOCK * DATA_TYPE_SIZE % 4 != 0)
-    for (int i = 0; i < thr_elems; ++i)
-        buf.v1[i] = src[i * SIMD + lane];
-#else
-#define MAYBE_BLOCK_READ(n, read, elems) \
-    do { \
-        if ((elems) & (n)) { \
-            const uint rel = (elems) & ~(((n) << 1) - 1); \
-            buf.v##n[rel / (n)] = read(&src[rel * SIMD]); \
-        } \
-    } while (0)
-
-    for (int i = 0; i < thr_elems / 16; ++i)
-        buf.v16[i] = BLOCK_READ16(&src[16 * i * SIMD]);
-    MAYBE_BLOCK_READ(8, BLOCK_READ8, thr_elems);
-    MAYBE_BLOCK_READ(4, BLOCK_READ4, thr_elems);
-    MAYBE_BLOCK_READ(2, BLOCK_READ2, thr_elems);
-    MAYBE_BLOCK_READ(1, BLOCK_READ, thr_elems);
-#undef MAYBE_BLOCK_READ
-#endif // aligned for block reads
-    if (lane < READ_BLOCK % SIMD)
-        buf.v1[thr_elems] = src[thr_elems * SIMD + lane];
-
-    dst += dst_offset0 + READ_OVERLAP * get_global_id(1) * DST_EXT_OFFSET;
-#if (WRITE_BLOCK * DATA_TYPE_SIZE % 16 != 0)
-    for (int i = 0; i < thr_elems; ++i) {
-        struct write_info_t info = WRITE_INFO(i * SIMD + lane);
-        if (info.write) dst[info.idx] = buf.v1[i];
-    }
-    if (lane < READ_BLOCK % SIMD) {
-        struct write_info_t info = WRITE_INFO(thr_elems * SIMD + lane);
-        if (info.write) dst[info.idx] = buf.v1[thr_elems];
-    }
-#else
-    // Break up the data that was read into several blocks that may be written
-    // sequentially. If the block size is not divisible by the subgroup size,
-    // borrow values from the next block(s), if available, to fill a scattered
-    // write.
-    const size_t elems_per_iteration = MAX(SIMD, WRITE_BLOCK);
-    const size_t iterations = DIV_UP(READ_BLOCK, elems_per_iteration);
-
-#pragma unroll
-    for (int j = 0; j < iterations; ++j) {
-        const size_t buf_off = DIV_UP(j * elems_per_iteration, SIMD);
-        const size_t block_off = buf_off * SIMD;
-        // Accounting for any values borrowed from the last iteration, this
-        // block only has `iter_elems` values to write:
-        const size_t iter_elems = WRITE_BLOCK - (block_off % WRITE_BLOCK);
-        const size_t thr_iter_elems = iter_elems / SIMD;
-        struct write_info_t info = WRITE_INFO(block_off);
-        __global DATA_T *iter_dst = dst + info.idx;
-#define MAYBE_BLOCK_WRITE(n, write, elems) \
-    do { \
-        if ((elems) & (n)) { \
-            const uint rel = (elems) & ~(((n) << 1) - 1); \
-            write(&iter_dst[rel * SIMD], load_vec##n(&buf, buf_off + rel)); \
-        } \
-    } while (0)
-
-        if (info.write) {
-            for (int i = 0; i < thr_iter_elems / 16; ++i)
-                BLOCK_WRITE16(&iter_dst[16 * i * SIMD],
-                        load_vec16(&buf, buf_off + i * 16));
-            MAYBE_BLOCK_WRITE(8, BLOCK_WRITE8, thr_iter_elems);
-            MAYBE_BLOCK_WRITE(4, BLOCK_WRITE4, thr_iter_elems);
-            MAYBE_BLOCK_WRITE(2, BLOCK_WRITE2, thr_iter_elems);
-            MAYBE_BLOCK_WRITE(1, BLOCK_WRITE, thr_iter_elems);
-        }
-#undef MAYBE_BLOCK_WRITE
-
-        // Write tail elements + the leading elements of the next block
-        if (iter_elems % SIMD) {
-            const size_t written = block_off + thr_iter_elems * SIMD;
-            struct write_info_t info = WRITE_INFO(written + lane);
-            if (info.write && lane < MIN(SIMD, READ_BLOCK - written))
-                dst[info.idx] = buf.v1[buf_off + thr_iter_elems];
-        }
-    }
-#endif // aligned for block writes
-#endif // SIMD == 1
-}
diff --git a/src/gpu/intel/ocl/simple_concat.cpp b/src/gpu/intel/ocl/simple_concat.cpp
deleted file mode 100644
index f804b2d6ebf..00000000000
--- a/src/gpu/intel/ocl/simple_concat.cpp
+++ /dev/null
@@ -1,237 +0,0 @@
-/*******************************************************************************
-* Copyright 2020-2024 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 <algorithm>
-#include <numeric>
-
-#include "gpu/intel/compute/dispatch.hpp"
-#include "gpu/intel/ocl/concat_utils.hpp"
-#include "gpu/intel/ocl/simple_concat.hpp"
-
-namespace dnnl {
-namespace impl {
-namespace gpu {
-namespace intel {
-namespace ocl {
-
-static status_t init_conf_common(
-        impl::engine_t *engine, concat_conf_t &conf, const concat_pd_t *pd) {
-    using namespace utils;
-    const memory_desc_t &ref_dst_md = *pd->dst_md();
-    if (ref_dst_md.format_kind != format_kind::blocked)
-        return status::unimplemented;
-    const auto concat_dim = pd->concat_dim();
-
-    normalization_t normalize(ref_dst_md, concat_dim);
-    for (int i = 0; i < pd->n_inputs(); ++i) {
-        const memory_desc_t &src_md = *pd->src_md(i);
-        if (!normalize.add_source(src_md)) return status::unimplemented;
-    }
-
-    auto *compute_engine = utils::downcast<compute::compute_engine_t *>(engine);
-    auto *device_info = compute_engine->device_info();
-    dim_t max_write_size = normalize.max_write_size();
-    dim_t max_read_size = normalize.max_read_size();
-
-    // TODO: add proper scales support
-    const bool has_scales = false;
-    const compute::gpu_arch_t hw = device_info->gpu_arch();
-    const int register_bytes = prb_info_t::register_bytes(hw);
-    const int hw_threads = device_info->hw_threads();
-    const int max_sg_size = device_info->max_subgroup_size();
-    const auto data_type_size = normalize.data_type_size();
-    dim_t total_bytes = data_type_size;
-    for (int i = 0; i < pd->dst_md()->ndims; ++i)
-        total_bytes *= pd->dst_md()->padded_dims[i];
-
-    std::vector<prb_info_t> infos;
-    for (int simd : {32, 16, 8, 1}) {
-        if (simd > max_sg_size) continue;
-        if (simd > 1 && !compute_engine->mayiuse_sub_group(simd)) continue;
-        for (int bytes : {8, 4, 2, 1}) {
-            if (has_scales && bytes < (int)data_type_size) break;
-            if (max_write_size % bytes) continue;
-            const dim_t total_elems = total_bytes / bytes;
-            const dim_t concurrent_elems
-                    = utils::div_up(simd * total_elems, hw_threads);
-            const dim_t elems_per_reg = register_bytes / bytes;
-            const dim_t max_elems
-                    = utils::rnd_up(concurrent_elems, elems_per_reg);
-            if (simd > max_elems) continue;
-            infos.emplace_back(simd, bytes, max_elems, max_read_size,
-                    max_write_size, device_info->gpu_arch());
-        }
-    }
-    if (infos.empty() || !infos[0].block) return status::unimplemented;
-    std::sort(infos.begin(), infos.end());
-    const auto &info = infos[0];
-
-    memory_desc_t dst_md, src_md;
-    int offset = 0, padded_offset = 0, nonempty_inputs = 0;
-    dim_t final_padding = 0;
-    for (int i = 0; i < pd->n_inputs(); ++i) {
-        if (pd->src_md(i)->padded_dims[concat_dim] == 0) continue;
-        memcpy(&src_md, pd->src_md(i), sizeof(memory_desc_t));
-        normalize(src_md);
-        const auto &src_blkg = src_md.format_desc.blocking;
-        conf.src_extern_dim_sizes[nonempty_inputs]
-                = src_blkg.strides[axis::outer] * data_type_size;
-        dim_t concat_dim = src_md.dims[axis::concat];
-        dim_t concat_pdim = src_md.padded_dims[axis::concat];
-        conf.offset[nonempty_inputs] = offset;
-        conf.padded_offset[nonempty_inputs] = padded_offset;
-        final_padding = concat_pdim - concat_dim;
-        offset += concat_dim;
-        padded_offset += concat_pdim;
-        nonempty_inputs++;
-    }
-    memcpy(&dst_md, pd->dst_md(), sizeof(memory_desc_t));
-    normalize(dst_md);
-    const auto &dst_blkg = dst_md.format_desc.blocking;
-    conf.dst_extern_dim_size = dst_blkg.strides[axis::outer] * data_type_size;
-    conf.dst_padded_concat_axis = dst_md.padded_dims[axis::concat];
-    conf.dst_concat_axis
-            = std::min(conf.dst_padded_concat_axis, offset + final_padding);
-    dim_t concat_dim_size = padded_offset;
-
-    conf.n_blocks = 0;
-    dim_t stride = 1;
-    for (int i = dst_blkg.inner_nblks - 1; i >= 0; --i) {
-        auto blk = dst_blkg.inner_blks[i];
-        auto idx = dst_blkg.inner_idxs[i];
-        if (i == dst_blkg.inner_nblks - 1)
-            blk = blk * data_type_size / info.type_size;
-        if (idx == axis::concat) {
-            conf.blocks[conf.n_blocks] = blk;
-            conf.strides[conf.n_blocks] = stride;
-            conf.n_blocks++;
-        }
-        stride *= blk;
-    }
-
-    dim_t extern_axis = dst_md.dims[axis::outer];
-    dim_t inner_axis
-            = dst_md.padded_dims[axis::inner] * data_type_size / info.type_size;
-    dim_t inner_offset
-            = dst_blkg.strides[axis::concat] * data_type_size / info.type_size;
-    conf.n = nonempty_inputs;
-    conf.simd = info.simd;
-    conf.inner_axis = inner_offset;
-    conf.data_type_size = info.type_size;
-    conf.dst_offset0 = dst_md.offset0 * data_type_size / info.type_size;
-    conf.read_block = info.block;
-    conf.write_block = std::min(info.block, max_write_size / info.type_size);
-    conf.gws0_block = math::lcm(inner_axis, conf.read_block);
-    conf.read_overlap = conf.gws0_block / inner_axis;
-    conf.gws_d[0] = conf.gws0_block * conf.simd / conf.read_block;
-    conf.gws_d[1] = extern_axis / conf.read_overlap;
-    conf.gws_d[2] = concat_dim_size;
-
-    // Bound estimates based on limited empirical evidence
-    size_t coalesced_writes
-            = ((max_write_size ^ (max_write_size - 1)) >> 1) + 1;
-    size_t extern_axis_bound
-            = 256 * 512 * std::min(coalesced_writes, into<size_t>(8));
-    if (conf.simd == 1 && conf.gws_d[2] > 64) return status::unimplemented;
-    if (conf.simd == 1 && conf.gws_d[1] > extern_axis_bound)
-        return status::unimplemented;
-    if (conf.inner_axis == 1 && 16 * conf.simd <= conf.read_block
-            && (size_t)conf.read_block < conf.gws_d[2])
-        return status::unimplemented;
-    if (conf.n_blocks && conf.write_block * conf.data_type_size == 1)
-        return status::unimplemented;
-    // In this band, this implementation underperforms against either
-    // (1) gen9 concat or (2) ref concat.
-    if (dst_md.dims[axis::inner] > (1l << 20) // (1)
-            && dst_md.dims[axis::inner] <= (1l << 24) // (2)
-            && dst_md.dims[axis::concat] != dst_md.padded_dims[axis::concat]
-            && dst_md.dims[axis::concat] < 8)
-        return status::unimplemented;
-
-    conf.lws_d
-            = compute::get_optimal_lws(conf.gws_d, 0, device_info->gpu_arch());
-    return status::success;
-}
-
-static status_t init_kernel_ctx_common(
-        compute::kernel_ctx_t &kernel_ctx, const concat_conf_t &conf) {
-    kernel_ctx.define_int(
-            "DST_EXT_OFFSET", conf.dst_extern_dim_size / conf.data_type_size);
-    for (int i = 0; i < conf.n; ++i) {
-        kernel_ctx.define_int(utils::format("SRC%d_EXT_OFFSET", i),
-                conf.src_extern_dim_sizes[i] / conf.data_type_size);
-        kernel_ctx.define_int(utils::format("OFFSET%d", i), conf.offset[i]);
-        kernel_ctx.define_int(
-                utils::format("PADDED_OFFSET%d", i), conf.padded_offset[i]);
-        dim_t src_concat_axis
-                = i + 1 < conf.n ? conf.offset[i + 1] : conf.dst_concat_axis;
-        kernel_ctx.define_int(
-                utils::format("SRC%d_CONCAT_AXIS", i), src_concat_axis);
-    }
-    kernel_ctx.define_int("BLOCK_DEPTH", conf.n_blocks);
-    for (int i = 0; i < conf.n_blocks; ++i) {
-        kernel_ctx.define_int(utils::format("BLOCK_B%d", i), conf.blocks[i]);
-        kernel_ctx.define_int(utils::format("BLOCK_S%d", i), conf.strides[i]);
-    }
-    kernel_ctx.define_int(
-            utils::format("OFFSET%d", conf.n), conf.dst_concat_axis);
-    kernel_ctx.define_int(utils::format("PADDED_OFFSET%d", conf.n),
-            conf.dst_padded_concat_axis);
-    kernel_ctx.define_int("INNER_OFFSET", conf.inner_axis);
-    kernel_ctx.define_int("READ_BLOCK", conf.read_block);
-    kernel_ctx.define_int("WRITE_BLOCK", conf.write_block);
-    kernel_ctx.define_int("READ_OVERLAP", conf.read_overlap);
-    kernel_ctx.define_int("GWS0_BLOCK", conf.gws0_block);
-    kernel_ctx.define_int("N_INPUTS", conf.n);
-    kernel_ctx.define_int("SIMD", conf.simd);
-    kernel_ctx.define_int("DATA_TYPE_SIZE", conf.data_type_size);
-    return status::success;
-}
-
-status_t simple_concat_t::pd_t::init_conf(impl::engine_t *engine) {
-    return init_conf_common(engine, conf, this);
-}
-
-status_t simple_concat_t::pd_t::init_kernel_ctx(
-        compute::kernel_ctx_t &kernel_ctx) const {
-    return init_kernel_ctx_common(kernel_ctx, conf);
-}
-
-status_t simple_concat_t::execute_concat(const exec_ctx_t &ctx) const {
-    const auto &conf = pd()->conf;
-    if (conf.n == 0) return status::success;
-    const auto concat_dim = pd()->concat_dim();
-
-    compute::kernel_arg_list_t arg_list;
-    auto &dst = CTX_OUT_STORAGE(DNNL_ARG_DST);
-    arg_list.set(0, dst);
-    arg_list.set(1, conf.dst_offset0);
-    int next_arg = 2;
-    for (int i = 0; i < pd()->n_inputs(); ++i) {
-        if (pd()->src_md(i)->padded_dims[concat_dim] == 0) continue;
-        auto &src = CTX_IN_STORAGE(DNNL_ARG_MULTIPLE_SRC + i);
-        arg_list.set(next_arg++, src);
-    }
-
-    auto nd_range = compute::nd_range_t(conf.gws_d, conf.lws_d);
-
-    status_t status = parallel_for(ctx, nd_range, kernel_, arg_list);
-    return status;
-}
-} // namespace ocl
-} // namespace intel
-} // namespace gpu
-} // namespace impl
-} // namespace dnnl
diff --git a/src/gpu/intel/ocl/simple_concat.hpp b/src/gpu/intel/ocl/simple_concat.hpp
deleted file mode 100644
index 1a6559a33e2..00000000000
--- a/src/gpu/intel/ocl/simple_concat.hpp
+++ /dev/null
@@ -1,86 +0,0 @@
-/*******************************************************************************
-* Copyright 2020-2024 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.
-*******************************************************************************/
-
-#ifndef GPU_INTEL_OCL_SIMPLE_CONCAT_HPP
-#define GPU_INTEL_OCL_SIMPLE_CONCAT_HPP
-
-#include "common/engine.hpp"
-#include "common/primitive.hpp"
-#include "gpu/gpu_concat_pd.hpp"
-#include "gpu/gpu_resource.hpp"
-#include "gpu/intel/gpu_primitive.hpp"
-#include "gpu/intel/primitive_conf.hpp"
-
-namespace dnnl {
-namespace impl {
-namespace gpu {
-namespace intel {
-namespace ocl {
-
-struct simple_concat_t : public gpu_primitive_t {
-    using gpu_primitive_t::gpu_primitive_t;
-    struct pd_t : public gpu_concat_pd_t {
-        using gpu_concat_pd_t::gpu_concat_pd_t;
-
-        DECLARE_CONCAT_PD_T("simple:any", simple_concat_t);
-
-        status_t init(impl::engine_t *engine) {
-            VDISPATCH_CONCAT(n_inputs() <= 64, VERBOSE_BAD_PARAM, "n_inputs");
-            VDISPATCH_CONCAT(
-                    attr()->has_default_values(), VERBOSE_UNSUPPORTED_ATTR);
-            VDISPATCH_CONCAT_SC(set_default_params(), VERBOSE_UNSUPPORTED_TAG);
-
-            VDISPATCH_CONCAT_SC(init_conf(engine),
-                    VERBOSE_PRIMITIVE_CREATION_FAIL, "concat");
-
-            return status::success;
-        }
-
-        status_t init_conf(impl::engine_t *engine);
-        status_t init_kernel_ctx(compute::kernel_ctx_t &kernel_ctx) const;
-        concat_conf_t conf;
-    };
-
-    status_t init(impl::engine_t *engine) override {
-        compute::kernel_ctx_t kernel_ctx;
-
-        status_t status = pd()->init_kernel_ctx(kernel_ctx);
-        CHECK(status);
-
-        CHECK(create_kernel(engine, &kernel_, "simple_concat", kernel_ctx));
-        if (!kernel_) return status::runtime_error;
-
-        return status::success;
-    }
-
-    status_t execute(const exec_ctx_t &ctx) const override {
-        return execute_concat(ctx);
-    }
-
-private:
-    status_t execute_concat(const exec_ctx_t &ctx) const;
-    const pd_t *pd() const { return (const pd_t *)primitive_t::pd().get(); }
-
-    compute::kernel_t kernel_;
-};
-
-} // namespace ocl
-} // namespace intel
-} // namespace gpu
-} // namespace impl
-} // namespace dnnl
-
-#endif
diff --git a/tests/benchdnn/inputs/concat/test_concat_gpu b/tests/benchdnn/inputs/concat/test_concat_gpu
index 661348b0a51..eb6f862ae81 100644
--- a/tests/benchdnn/inputs/concat/test_concat_gpu
+++ b/tests/benchdnn/inputs/concat/test_concat_gpu
@@ -97,3 +97,50 @@
 --dtag=nchw
 --attr-scales=msrc0:common:2+msrc1:common:3+msrc2:common:4+msrc3:common:5+msrc4:common:6+msrc5:common:7+msrc6:common:8+msrc7:common:9+msrc8:common:10+msrc9:common:11+msrc10:common:12+msrc11:common:13+msrc12:common:14+msrc13:common:15+msrc14:common:16+msrc15:common:17+msrc16:common:18
 1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40
+
+# additional tests for internal padding concat kernel
+--reset
+--dtag=aBc32b,aBc16b,aBc8b,aBc4b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc32b:aBc32b,aBc16b:aBc16b,aBc8b:aBc8b,aBc4b:aBc4b
+--axis=1
+4x33x17:4x32x17
+4x34x17:4x32x17
+4x129x17:4x129x17
+4x33x16:4x32x16
+4x33x4096:4x32x4096
+4x129x16:4x129x16
+4x129x4096:4x129x4096
+
+# tests w/small internal padding concat
+--reset
+--dtag=aBc4b
+--stag=aBc4b:aBc4b
+--axis=1
+# src0 < elems_per_sg
+4x2x8:4x2x8
+# sg_size > boundary (no trailing blocks)
+4x34x8:4x4x8
+# boundary_blocks only
+4x2x32:4x2x32
+
+# large internal padding concat for idx_t=ulong
+--reset
+--dtag=aBc32b
+--stag=aBc32b:aBc32b
+--axis=1
+--sdt=bf16 --ddt=bf16 4x337810x992:4x17x992
+
+# test varying internal padding tags/formats
+--reset
+--sdt=u8
+--ddt=u8
+--axis=1
+--stag=aBcd32b:aBcd32b
+--dtag=aBcd32b
+4x33x8x8:4x32x8x8
+
+--stag=ABcd32a32b:ABcd32a32b
+--dtag=ABcd32a32b
+4x33x8x8:4x32x8x8


Reviews:
user: login: dzarukin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T02:23:24Z
user: login: syurkevi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T17:23:28Z
user: login: atkassen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T18:17:19Z
user: login: atkassen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T16:56:35Z
user: login: syurkevi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T16:12:09Z
user: login: atkassen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T22:22:56Z
user: login: atkassen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T22:28:23Z
user: login: atkassen
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T22:49:37Z
user: login: dzarukin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T04:16:15Z
user: login: syurkevi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T16:57:07Z
user: login: dzarukin
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T17:05:16Z

Review comments:
diff_hunk: @@ -0,0 +1,290 @@
+--reset
+
+# b32 internal padding
+--dtag=aBc32b
+--sdt=u8
+--ddt=u8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+4x41x8:4x32x8
+4x42x8:4x32x8
+4x43x8:4x32x8
+4x44x8:4x32x8
+4x45x8:4x32x8
+4x46x8:4x32x8
+4x47x8:4x32x8
+4x48x8:4x32x8
+4x49x8:4x32x8
+4x50x8:4x32x8
+4x51x8:4x32x8
+4x52x8:4x32x8
+4x53x8:4x32x8
+4x54x8:4x32x8
+4x55x8:4x32x8
+4x56x8:4x32x8
+4x57x8:4x32x8
+4x58x8:4x32x8
+4x59x8:4x32x8
+4x60x8:4x32x8
+4x61x8:4x32x8
+4x61x8:4x32x8
+4x62x8:4x32x8
+4x63x8:4x32x8
+4x64x8:4x32x8
+
+# all types b32
+--dtag=aBc32b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b16 internal padding
+--dtag=aBc16b
+--sdt=u8
+--ddt=u8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+4x41x8:4x32x8
+4x42x8:4x32x8
+4x43x8:4x32x8
+4x44x8:4x32x8
+4x45x8:4x32x8
+4x46x8:4x32x8
+4x47x8:4x32x8
+4x48x8:4x32x8
+
+# all types b16
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b8 internal padding
+--dtag=aBc8b
+--sdt=u8
+--ddt=u8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+
+# all types b8
+--dtag=aBc8b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b4 internal padding
+--dtag=aBc4b
+--sdt=u8
+--ddt=u8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x129x8:4x129x8
+
+# all types b4
+--dtag=aBc4b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# increasing inner dim
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x33x9:4x32x9
+4x33x10:4x32x10
+4x33x11:4x32x11
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x8:4x129x8
+4x129x9:4x129x9
+4x129x10:4x129x10
+4x129x11:4x129x11
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+# increasing inner dim
+--dtag=aBc32b
+--sdt=f32,f16,bf16,s8,u8
+--ddt=f32,f16,bf16,s8,u8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x17:4x32x17
+4x33x18:4x32x18
+4x33x19:4x32x19
+4x33x20:4x32x20
+4x33x21:4x32x21
+4x33x22:4x32x22
+4x33x23:4x32x23
+4x33x24:4x32x24
+4x33x25:4x32x25
+4x33x26:4x32x26
+4x33x27:4x32x27
+4x33x28:4x32x28
+4x33x29:4x32x29
+4x33x30:4x32x30
+4x33x31:4x32x31
+4x33x32:4x32x32
+4x33x33:4x32x33
+
+4x129x17:4x129x17
+4x129x18:4x129x18
+4x129x19:4x129x19
+4x129x20:4x129x20
+4x129x21:4x129x21
+4x129x22:4x129x22
+4x129x23:4x129x23
+4x129x24:4x129x24
+4x129x25:4x129x25
+4x129x26:4x129x26
+4x129x27:4x129x27
+4x129x28:4x129x28
+4x129x29:4x129x29
+4x129x30:4x129x30
+4x129x31:4x129x31
+4x129x32:4x129x32
+4x129x33:4x129x33
+
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x33x9:4x32x9
+4x33x10:4x32x10
+4x33x11:4x32x11
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x8:4x129x8
+4x129x9:4x129x9
+4x129x10:4x129x10
+4x129x11:4x129x11
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+--dtag=aBc8b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+--dtag=aBc4b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+# tests w/small concat
+--reset
+--dtag=aBc4b
+--stag=aBc4b:aBc4b
+--axis=1
+# src0 < elems_per_sg
+4x2x8:4x2x8
+# sg_size > boundary (no trailing blocks)
+4x33x8:4x4x8
+# boundary_blocks only
+4x3x32:4x3x32
+
+# large internal padding concat for idx_t=ulong
+--reset
+--dtag=aBc32b
+--stag=aBc32b:aBc32b
+--axis=1
+--sdt=bf16 --ddt=bf16 4x337810x992:4x17x992
+
+# test varying internal padding tags/formats
+--reset
+--sdt=u8
+--ddt=u8
+--axis=1
+--stag=aBcd32b:aBcd32b
+--dtag=aBcd32b
+4x33x8x8:4x32x8x8
+
+--stag=ABcd32a32b:ABcd32a32b
+--dtag=ABcd32a32b
+4x33x8x8:4x32x8x8
path: tests/benchdnn/inputs/concat/test_concat_ip
user: login: dzarukin
body: That's too much to validate a pretty narrow new use case.
Please reduce and try to incorporate into existing files.
And this file won't be picked for GPU validation besides that.

Additionally, would be nice to understand where the blocked layout comes from because, AFAIK, activations must move to nxc format for whole model since Xe2.
created_at: 2025-03-14T02:23:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+--reset
+
+# b32 internal padding
+--dtag=aBc32b
+--sdt=u8
+--ddt=u8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+4x41x8:4x32x8
+4x42x8:4x32x8
+4x43x8:4x32x8
+4x44x8:4x32x8
+4x45x8:4x32x8
+4x46x8:4x32x8
+4x47x8:4x32x8
+4x48x8:4x32x8
+4x49x8:4x32x8
+4x50x8:4x32x8
+4x51x8:4x32x8
+4x52x8:4x32x8
+4x53x8:4x32x8
+4x54x8:4x32x8
+4x55x8:4x32x8
+4x56x8:4x32x8
+4x57x8:4x32x8
+4x58x8:4x32x8
+4x59x8:4x32x8
+4x60x8:4x32x8
+4x61x8:4x32x8
+4x61x8:4x32x8
+4x62x8:4x32x8
+4x63x8:4x32x8
+4x64x8:4x32x8
+
+# all types b32
+--dtag=aBc32b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b16 internal padding
+--dtag=aBc16b
+--sdt=u8
+--ddt=u8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+4x41x8:4x32x8
+4x42x8:4x32x8
+4x43x8:4x32x8
+4x44x8:4x32x8
+4x45x8:4x32x8
+4x46x8:4x32x8
+4x47x8:4x32x8
+4x48x8:4x32x8
+
+# all types b16
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b8 internal padding
+--dtag=aBc8b
+--sdt=u8
+--ddt=u8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+
+# all types b8
+--dtag=aBc8b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b4 internal padding
+--dtag=aBc4b
+--sdt=u8
+--ddt=u8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x129x8:4x129x8
+
+# all types b4
+--dtag=aBc4b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# increasing inner dim
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x33x9:4x32x9
+4x33x10:4x32x10
+4x33x11:4x32x11
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x8:4x129x8
+4x129x9:4x129x9
+4x129x10:4x129x10
+4x129x11:4x129x11
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+# increasing inner dim
+--dtag=aBc32b
+--sdt=f32,f16,bf16,s8,u8
+--ddt=f32,f16,bf16,s8,u8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x17:4x32x17
+4x33x18:4x32x18
+4x33x19:4x32x19
+4x33x20:4x32x20
+4x33x21:4x32x21
+4x33x22:4x32x22
+4x33x23:4x32x23
+4x33x24:4x32x24
+4x33x25:4x32x25
+4x33x26:4x32x26
+4x33x27:4x32x27
+4x33x28:4x32x28
+4x33x29:4x32x29
+4x33x30:4x32x30
+4x33x31:4x32x31
+4x33x32:4x32x32
+4x33x33:4x32x33
+
+4x129x17:4x129x17
+4x129x18:4x129x18
+4x129x19:4x129x19
+4x129x20:4x129x20
+4x129x21:4x129x21
+4x129x22:4x129x22
+4x129x23:4x129x23
+4x129x24:4x129x24
+4x129x25:4x129x25
+4x129x26:4x129x26
+4x129x27:4x129x27
+4x129x28:4x129x28
+4x129x29:4x129x29
+4x129x30:4x129x30
+4x129x31:4x129x31
+4x129x32:4x129x32
+4x129x33:4x129x33
+
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x33x9:4x32x9
+4x33x10:4x32x10
+4x33x11:4x32x11
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x8:4x129x8
+4x129x9:4x129x9
+4x129x10:4x129x10
+4x129x11:4x129x11
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+--dtag=aBc8b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+--dtag=aBc4b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+# tests w/small concat
+--reset
+--dtag=aBc4b
+--stag=aBc4b:aBc4b
+--axis=1
+# src0 < elems_per_sg
+4x2x8:4x2x8
+# sg_size > boundary (no trailing blocks)
+4x33x8:4x4x8
+# boundary_blocks only
+4x3x32:4x3x32
+
+# large internal padding concat for idx_t=ulong
+--reset
+--dtag=aBc32b
+--stag=aBc32b:aBc32b
+--axis=1
+--sdt=bf16 --ddt=bf16 4x337810x992:4x17x992
+
+# test varying internal padding tags/formats
+--reset
+--sdt=u8
+--ddt=u8
+--axis=1
+--stag=aBcd32b:aBcd32b
+--dtag=aBcd32b
+4x33x8x8:4x32x8x8
+
+--stag=ABcd32a32b:ABcd32a32b
+--dtag=ABcd32a32b
+4x33x8x8:4x32x8x8
path: tests/benchdnn/inputs/concat/test_concat_ip
user: login: syurkevi
body: Reduced the number of tests and incorporated into gpu validation. Do let me know if there are still too many. 

There has been some back and forth on the necessity of block support. There were talks it'll still be needed with future platforms but don't recall the details for which customer. Would like to close this narrow case performance gap regardless and will revisit with more improvements(ex: 3+ inputs, large block sizes w/small simd, flexible conf, etc) if there is demand again.
created_at: 2025-03-14T17:23:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,13 +58,13 @@ struct reusable_simple_concat_params_t
     int simd;
     int data_type_size;
     bool use_large_index = true;
+    bool use_internal_padding_kernel = false;
     uint8_t padding[3] = {0};
path: src/gpu/intel/ocl/reusable_simple_concat.hpp
user: login: atkassen
body: ```suggestion
    uint8_t padding[2] = {0};
```
created_at: 2025-03-14T18:17:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,10 +155,210 @@ static status_t init_conf_common(impl::engine_t *engine, const concat_pd_t *pd,
     return status::success;
 }
 
+static status_t attempt_normalize_ip_concat2(
path: src/gpu/intel/ocl/reusable_simple_concat.cpp
user: login: atkassen
body: nit: elsewhere we name these sorts of tentative operations as `maybe_...` or `try_...`.
created_at: 2025-04-03T16:56:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+--reset
+
+# b32 internal padding
+--dtag=aBc32b
+--sdt=u8
+--ddt=u8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+4x41x8:4x32x8
+4x42x8:4x32x8
+4x43x8:4x32x8
+4x44x8:4x32x8
+4x45x8:4x32x8
+4x46x8:4x32x8
+4x47x8:4x32x8
+4x48x8:4x32x8
+4x49x8:4x32x8
+4x50x8:4x32x8
+4x51x8:4x32x8
+4x52x8:4x32x8
+4x53x8:4x32x8
+4x54x8:4x32x8
+4x55x8:4x32x8
+4x56x8:4x32x8
+4x57x8:4x32x8
+4x58x8:4x32x8
+4x59x8:4x32x8
+4x60x8:4x32x8
+4x61x8:4x32x8
+4x61x8:4x32x8
+4x62x8:4x32x8
+4x63x8:4x32x8
+4x64x8:4x32x8
+
+# all types b32
+--dtag=aBc32b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b16 internal padding
+--dtag=aBc16b
+--sdt=u8
+--ddt=u8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+4x41x8:4x32x8
+4x42x8:4x32x8
+4x43x8:4x32x8
+4x44x8:4x32x8
+4x45x8:4x32x8
+4x46x8:4x32x8
+4x47x8:4x32x8
+4x48x8:4x32x8
+
+# all types b16
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b8 internal padding
+--dtag=aBc8b
+--sdt=u8
+--ddt=u8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x37x8:4x32x8
+4x38x8:4x32x8
+4x39x8:4x32x8
+4x40x8:4x32x8
+
+# all types b8
+--dtag=aBc8b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# b4 internal padding
+--dtag=aBc4b
+--sdt=u8
+--ddt=u8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x8:4x32x8
+4x34x8:4x32x8
+4x35x8:4x32x8
+4x36x8:4x32x8
+4x129x8:4x129x8
+
+# all types b4
+--dtag=aBc4b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x8:4x32x8
+4x129x8:4x129x8
+
+# increasing inner dim
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x33x9:4x32x9
+4x33x10:4x32x10
+4x33x11:4x32x11
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x8:4x129x8
+4x129x9:4x129x9
+4x129x10:4x129x10
+4x129x11:4x129x11
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+# increasing inner dim
+--dtag=aBc32b
+--sdt=f32,f16,bf16,s8,u8
+--ddt=f32,f16,bf16,s8,u8
+--stag=aBc32b:aBc32b
+--axis=1
+4x33x17:4x32x17
+4x33x18:4x32x18
+4x33x19:4x32x19
+4x33x20:4x32x20
+4x33x21:4x32x21
+4x33x22:4x32x22
+4x33x23:4x32x23
+4x33x24:4x32x24
+4x33x25:4x32x25
+4x33x26:4x32x26
+4x33x27:4x32x27
+4x33x28:4x32x28
+4x33x29:4x32x29
+4x33x30:4x32x30
+4x33x31:4x32x31
+4x33x32:4x32x32
+4x33x33:4x32x33
+
+4x129x17:4x129x17
+4x129x18:4x129x18
+4x129x19:4x129x19
+4x129x20:4x129x20
+4x129x21:4x129x21
+4x129x22:4x129x22
+4x129x23:4x129x23
+4x129x24:4x129x24
+4x129x25:4x129x25
+4x129x26:4x129x26
+4x129x27:4x129x27
+4x129x28:4x129x28
+4x129x29:4x129x29
+4x129x30:4x129x30
+4x129x31:4x129x31
+4x129x32:4x129x32
+4x129x33:4x129x33
+
+--dtag=aBc16b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc16b:aBc16b
+--axis=1
+4x33x8:4x32x8
+4x33x9:4x32x9
+4x33x10:4x32x10
+4x33x11:4x32x11
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x8:4x129x8
+4x129x9:4x129x9
+4x129x10:4x129x10
+4x129x11:4x129x11
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+--dtag=aBc8b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc8b:aBc8b
+--axis=1
+4x33x12:4x32x12
+4x33x13:4x32x13
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x12:4x129x12
+4x129x13:4x129x13
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+--dtag=aBc4b
+--sdt=f32,f16,bf16,s8
+--ddt=f32,f16,bf16,s8
+--stag=aBc4b:aBc4b
+--axis=1
+4x33x14:4x32x14
+4x33x15:4x32x15
+4x33x16:4x32x16
+4x33x17:4x32x17
+
+4x129x14:4x129x14
+4x129x15:4x129x15
+4x129x16:4x129x16
+4x129x17:4x129x17
+
+# tests w/small concat
+--reset
+--dtag=aBc4b
+--stag=aBc4b:aBc4b
+--axis=1
+# src0 < elems_per_sg
+4x2x8:4x2x8
+# sg_size > boundary (no trailing blocks)
+4x33x8:4x4x8
+# boundary_blocks only
+4x3x32:4x3x32
+
+# large internal padding concat for idx_t=ulong
+--reset
+--dtag=aBc32b
+--stag=aBc32b:aBc32b
+--axis=1
+--sdt=bf16 --ddt=bf16 4x337810x992:4x17x992
+
+# test varying internal padding tags/formats
+--reset
+--sdt=u8
+--ddt=u8
+--axis=1
+--stag=aBcd32b:aBcd32b
+--dtag=aBcd32b
+4x33x8x8:4x32x8x8
+
+--stag=ABcd32a32b:ABcd32a32b
+--dtag=ABcd32a32b
+4x33x8x8:4x32x8x8
path: tests/benchdnn/inputs/concat/test_concat_ip
user: login: syurkevi
body: OpenVINO will continue to support blocked layouts for the near future. After some discussion with @karturov  we've determined this PR introduces a sufficient number of model level improvements (including latest platforms) to warrant merging. Details in perf-report or grafana.
created_at: 2025-04-23T16:12:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -160,6 +161,10 @@ class normalization_t {
 
     void operator()(memory_desc_t &) const;
 
+    bool is_internal_padding_concat() const {
+        return (padding_type_ == padding_t::internal);
+    }
+
path: src/gpu/intel/ocl/concat_utils.hpp
user: login: atkassen
body: nit: `concat` isn't needed in the name here (in a file called concat_utils.hpp). Maybe `has_internal_padding`?
created_at: 2025-04-23T21:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,7 +125,8 @@ class normalization_t {
             padding_type_ = padding_t::external;
 
         if (padding_type_ == padding_t::internal) {
-            chunk_size_ = math::gcd(chunk_size_, source_chunk);
+            // may use different kernel, requires different partition for blocks
+            chunk_size_ = 1;
path: src/gpu/intel/ocl/concat_utils.hpp
user: login: atkassen
body: Can you explain why this change is needed? It changes the behavior for the old kernel as well.
created_at: 2025-04-23T21:48:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -234,3 +234,344 @@ reusable_simple_concat(__global DATA_T *dst, const ulong dst_offset0,
     }
 #endif // SIMD == 1
 }
+
+#define DATA1_T DATA_T
+#if DATA_TYPE_SIZE == 8
+#define NPERSG 1
+#define BBLOCK_READ BLOCK_READ
+#define BBLOCK_WRITE BLOCK_WRITE
+#define DDATA_T DATA1_T
+#define AS_VEC as_ulong
+#elif DATA_TYPE_SIZE == 4
+#define NPERSG 2
+#define BBLOCK_READ BLOCK_READ2
+#define BBLOCK_WRITE BLOCK_WRITE2
+#define DDATA_T DATA2_T
+#define AS_VEC as_uint2
+#elif DATA_TYPE_SIZE == 2
+#define NPERSG 4
+#define BBLOCK_READ BLOCK_READ4
+#define BBLOCK_WRITE BLOCK_WRITE4
+#define DDATA_T DATA4_T
+#define AS_VEC as_ushort4
+#elif DATA_TYPE_SIZE == 1
+#define NPERSG 8
+#define BBLOCK_READ BLOCK_READ8
+#define BBLOCK_WRITE BLOCK_WRITE8
+#define DDATA_T DATA8_T
+#define AS_VEC as_uchar8
+#endif
+
+/*
+ * This kernel handles internal padding cases by abstracting the problem into 3 cases:
+ *     - Reads and writes fall completely into the first source and can be copied as is
+ *
+ *     - Reads and writes fall across the boundary between two sources, neighboring "read blocks"
+ *       across the concat-axis boundary will need to be combined to form a single "write block" that will
+ *       consist halfway of each of the source blocks
+ *
+ *     - Reads and writes that fall completely within the second source will continue to misalign along
+ *       the halfway cutoff and will likewise require two reads per write, just with both reads within
+ *       the same second source
+ */
+#if SIMD != 1
+__attribute__((intel_reqd_sub_group_size(SIMD)))
+#endif
+__kernel void
+internal_padding_block_concat2(__global DATA_T *dst,
+        const idx_t dst_concat_axis, const idx_t dst_padded_concat_axis,
+        __global const DATA_T *src0, const idx_t offset0,
+        const idx_t padded_offset0, const idx_t src_concat_axis0,
+        const idx_t src_padded_concat_axis0, __global const DATA_T *src1,
+        const idx_t offset1, const idx_t padded_offset1,
+        const idx_t src_concat_axis1, const idx_t src_padded_concat_axis1,
+        const idx_t inner_dim) {
+
+    const size_t dtsize = DATA_TYPE_SIZE;
+    const int loads_per_sg = NPERSG;
+    const int elems_per_sg = SIMD * loads_per_sg;
+
+#if BLOCK_DEPTH > 0
+    const unsigned B0 = BLOCK_B0;
+#else
+    const unsigned B0 = 1;
+#endif
+
+    const size_t first_boundary_block
+            = ((DIV_UP(src_concat_axis0, B0) - 1) * inner_dim);
+    const size_t last_boundary_block = first_boundary_block + inner_dim - 1;
+    const size_t tot_boundary_block
+            = (DIV_UP(dst_padded_concat_axis, B0) * inner_dim);
+
+    // TODO: host side check, if blocks_per_sg > #blocks in src0, use legacy method instead
+    unsigned blocks_per_sg;
+    if (B0 > (elems_per_sg)) {
+        blocks_per_sg = B0 / elems_per_sg;
+    } else {
+        blocks_per_sg = elems_per_sg
+                / B0; // 32 / 2 = 16 layout blocks covered by simd
+    }
+
+    __global const DATA_T *src;
+
+    // within block idx, ex: 0-7, 0-15, 0-23, 0-31
+    size_t blid = get_local_id(0) % B0;
+
+    size_t id_start = get_group_id(0) * elems_per_sg + get_local_id(0);
+    long bii = id_start / B0;
+
+    size_t sg_first_bii = get_group_id(0) * elems_per_sg / B0;
+
+    // index along concat dimension
+    long ic = (bii / inner_dim) * B0 + blid;
+    long ic_end = ((bii + blocks_per_sg) / inner_dim) * B0 + blid;
+
+    size_t sg_last_bii = sg_first_bii + blocks_per_sg
+            - 1; //TODO: verify w/B0 > sg_tot_elems
+
+    size_t ccsz
+            = src_concat_axis0; // (31) padded concat dimension for calculating batched src
+    size_t padded_ccsz
+            = padded_offset1; // (32) padded concat dimension for calculating batched src
+
+    size_t cutoff
+            = 0; // determines boundary offset for write sg that spans multiple layout blocks
+    bool boundary = false; // current sg spans boundary between two inputs?
+
+    size_t batch_offset = inner_dim * padded_ccsz * get_global_id(1);
+    size_t batch_offset1
+            = inner_dim * src_padded_concat_axis1 * get_global_id(1);
+    size_t ext_batch_offset
+            = inner_dim * dst_padded_concat_axis * get_global_id(1);
+
+    // completely aligned r/w blocks, no boundary src case nor misaligned reads
+    if (sg_last_bii < first_boundary_block) {
+        //TODO: no concretes src0, bii
+        DDATA_T val = BBLOCK_READ(src0 + batch_offset + sg_first_bii * B0);
+        BBLOCK_WRITE(dst + ext_batch_offset + sg_first_bii * B0, val);
+    } else if (sg_first_bii > last_boundary_block) {
+        // if sg_bii0 fully within second source, update src_bii+other idx vars to match src1
+        long src_blid = (ic - src_concat_axis0) % B0;
+        long src_bii = sg_first_bii
+                - ((padded_offset1 / B0)
+                        * inner_dim); // update inner logical block idx to 0-nblocks read range for src1
+        if (src_bii < 0) { src_bii += inner_dim; }
+
+        long sg_last_src_bii = src_bii + blocks_per_sg - 1;
+        ccsz = src_concat_axis1 - src_concat_axis0;
+        // TODO change dst_padded_concat_axis to padded_offset2? //TODO!!!! THIS IS WRONG
+        padded_ccsz = dst_padded_concat_axis
+                - padded_offset1; // update read padded size for batching
+        batch_offset = inner_dim * padded_ccsz * get_global_id(1);
+
+        cutoff = (B0 + (offset1 - padded_offset1) % B0)
+                % B0; // positive modulo( offset/padded_offset , block size) |-|-------|
+                // this cutoff will continue in following misaligned(multi-cacheline/ block src)
+
+        DDATA_T aVal = BBLOCK_READ(src1 + batch_offset1 + src_bii * B0);
+        long next_block = src_bii
+                + inner_dim; //offset to read next required block for second half of cutoff
+        long next_last_block = next_block + blocks_per_sg - 1;
+        const size_t tot_src1_block
+                = (DIV_UP(src_padded_concat_axis1, B0) * inner_dim);
+        DDATA_T bVal;
+        if (next_last_block > tot_src1_block) {
+            int n_blocks_tail = tot_src1_block - next_block;
+            int rem_elems = n_blocks_tail * B0;
+
+            for (int b = get_local_id(0), vid = 0; b < rem_elems;
+                    b += SIMD, vid++) {
+#if NPERSG > 1
+                bVal[vid] = src1[batch_offset1 + next_block * B0 + b];
+#else
+                bVal = src1[batch_offset1 + next_block * B0 + b];
+#endif
+            }
+        } else {
+            bVal = BBLOCK_READ(src1 + batch_offset1
+                    + next_block
+                            * B0); //TODO: potential read past end? check w/asan?. YES, causes segfault. develop a MAYBE read instead
path: src/gpu/intel/ocl/reusable_simple_concat.cl
user: login: atkassen
body: Should this TODO be resolved before merge?
created_at: 2025-04-23T21:51:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -234,3 +234,344 @@ reusable_simple_concat(__global DATA_T *dst, const ulong dst_offset0,
     }
 #endif // SIMD == 1
 }
+
+#define DATA1_T DATA_T
+#if DATA_TYPE_SIZE == 8
+#define NPERSG 1
+#define BBLOCK_READ BLOCK_READ
+#define BBLOCK_WRITE BLOCK_WRITE
+#define DDATA_T DATA1_T
+#define AS_VEC as_ulong
+#elif DATA_TYPE_SIZE == 4
+#define NPERSG 2
+#define BBLOCK_READ BLOCK_READ2
+#define BBLOCK_WRITE BLOCK_WRITE2
+#define DDATA_T DATA2_T
+#define AS_VEC as_uint2
+#elif DATA_TYPE_SIZE == 2
+#define NPERSG 4
+#define BBLOCK_READ BLOCK_READ4
+#define BBLOCK_WRITE BLOCK_WRITE4
+#define DDATA_T DATA4_T
+#define AS_VEC as_ushort4
+#elif DATA_TYPE_SIZE == 1
+#define NPERSG 8
+#define BBLOCK_READ BLOCK_READ8
+#define BBLOCK_WRITE BLOCK_WRITE8
+#define DDATA_T DATA8_T
+#define AS_VEC as_uchar8
+#endif
+
+/*
+ * This kernel handles internal padding cases by abstracting the problem into 3 cases:
+ *     - Reads and writes fall completely into the first source and can be copied as is
+ *
+ *     - Reads and writes fall across the boundary between two sources, neighboring "read blocks"
+ *       across the concat-axis boundary will need to be combined to form a single "write block" that will
+ *       consist halfway of each of the source blocks
+ *
+ *     - Reads and writes that fall completely within the second source will continue to misalign along
+ *       the halfway cutoff and will likewise require two reads per write, just with both reads within
+ *       the same second source
+ */
+#if SIMD != 1
+__attribute__((intel_reqd_sub_group_size(SIMD)))
+#endif
+__kernel void
+internal_padding_block_concat2(__global DATA_T *dst,
+        const idx_t dst_concat_axis, const idx_t dst_padded_concat_axis,
+        __global const DATA_T *src0, const idx_t offset0,
+        const idx_t padded_offset0, const idx_t src_concat_axis0,
+        const idx_t src_padded_concat_axis0, __global const DATA_T *src1,
+        const idx_t offset1, const idx_t padded_offset1,
+        const idx_t src_concat_axis1, const idx_t src_padded_concat_axis1,
+        const idx_t inner_dim) {
+
+    const size_t dtsize = DATA_TYPE_SIZE;
+    const int loads_per_sg = NPERSG;
+    const int elems_per_sg = SIMD * loads_per_sg;
+
+#if BLOCK_DEPTH > 0
+    const unsigned B0 = BLOCK_B0;
+#else
+    const unsigned B0 = 1;
+#endif
+
+    const size_t first_boundary_block
+            = ((DIV_UP(src_concat_axis0, B0) - 1) * inner_dim);
+    const size_t last_boundary_block = first_boundary_block + inner_dim - 1;
+    const size_t tot_boundary_block
+            = (DIV_UP(dst_padded_concat_axis, B0) * inner_dim);
+
+    // TODO: host side check, if blocks_per_sg > #blocks in src0, use legacy method instead
+    unsigned blocks_per_sg;
+    if (B0 > (elems_per_sg)) {
+        blocks_per_sg = B0 / elems_per_sg;
+    } else {
+        blocks_per_sg = elems_per_sg
+                / B0; // 32 / 2 = 16 layout blocks covered by simd
+    }
+
+    __global const DATA_T *src;
+
+    // within block idx, ex: 0-7, 0-15, 0-23, 0-31
+    size_t blid = get_local_id(0) % B0;
+
+    size_t id_start = get_group_id(0) * elems_per_sg + get_local_id(0);
+    long bii = id_start / B0;
+
+    size_t sg_first_bii = get_group_id(0) * elems_per_sg / B0;
+
+    // index along concat dimension
+    long ic = (bii / inner_dim) * B0 + blid;
+    long ic_end = ((bii + blocks_per_sg) / inner_dim) * B0 + blid;
+
+    size_t sg_last_bii = sg_first_bii + blocks_per_sg
+            - 1; //TODO: verify w/B0 > sg_tot_elems
+
+    size_t ccsz
+            = src_concat_axis0; // (31) padded concat dimension for calculating batched src
+    size_t padded_ccsz
+            = padded_offset1; // (32) padded concat dimension for calculating batched src
+
+    size_t cutoff
+            = 0; // determines boundary offset for write sg that spans multiple layout blocks
+    bool boundary = false; // current sg spans boundary between two inputs?
+
+    size_t batch_offset = inner_dim * padded_ccsz * get_global_id(1);
+    size_t batch_offset1
+            = inner_dim * src_padded_concat_axis1 * get_global_id(1);
+    size_t ext_batch_offset
+            = inner_dim * dst_padded_concat_axis * get_global_id(1);
+
+    // completely aligned r/w blocks, no boundary src case nor misaligned reads
+    if (sg_last_bii < first_boundary_block) {
+        //TODO: no concretes src0, bii
+        DDATA_T val = BBLOCK_READ(src0 + batch_offset + sg_first_bii * B0);
+        BBLOCK_WRITE(dst + ext_batch_offset + sg_first_bii * B0, val);
+    } else if (sg_first_bii > last_boundary_block) {
+        // if sg_bii0 fully within second source, update src_bii+other idx vars to match src1
+        long src_blid = (ic - src_concat_axis0) % B0;
+        long src_bii = sg_first_bii
+                - ((padded_offset1 / B0)
+                        * inner_dim); // update inner logical block idx to 0-nblocks read range for src1
+        if (src_bii < 0) { src_bii += inner_dim; }
+
+        long sg_last_src_bii = src_bii + blocks_per_sg - 1;
+        ccsz = src_concat_axis1 - src_concat_axis0;
+        // TODO change dst_padded_concat_axis to padded_offset2? //TODO!!!! THIS IS WRONG
path: src/gpu/intel/ocl/reusable_simple_concat.cl
user: login: atkassen
body: > THIS IS WRONG

🤔
created_at: 2025-04-23T21:52:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -234,3 +234,344 @@ reusable_simple_concat(__global DATA_T *dst, const ulong dst_offset0,
     }
 #endif // SIMD == 1
 }
+
+#define DATA1_T DATA_T
+#if DATA_TYPE_SIZE == 8
+#define NPERSG 1
+#define BBLOCK_READ BLOCK_READ
+#define BBLOCK_WRITE BLOCK_WRITE
+#define DDATA_T DATA1_T
+#define AS_VEC as_ulong
+#elif DATA_TYPE_SIZE == 4
+#define NPERSG 2
+#define BBLOCK_READ BLOCK_READ2
+#define BBLOCK_WRITE BLOCK_WRITE2
+#define DDATA_T DATA2_T
+#define AS_VEC as_uint2
+#elif DATA_TYPE_SIZE == 2
+#define NPERSG 4
+#define BBLOCK_READ BLOCK_READ4
+#define BBLOCK_WRITE BLOCK_WRITE4
+#define DDATA_T DATA4_T
+#define AS_VEC as_ushort4
+#elif DATA_TYPE_SIZE == 1
+#define NPERSG 8
+#define BBLOCK_READ BLOCK_READ8
+#define BBLOCK_WRITE BLOCK_WRITE8
+#define DDATA_T DATA8_T
+#define AS_VEC as_uchar8
+#endif
+
+/*
+ * This kernel handles internal padding cases by abstracting the problem into 3 cases:
+ *     - Reads and writes fall completely into the first source and can be copied as is
+ *
+ *     - Reads and writes fall across the boundary between two sources, neighboring "read blocks"
+ *       across the concat-axis boundary will need to be combined to form a single "write block" that will
+ *       consist halfway of each of the source blocks
+ *
+ *     - Reads and writes that fall completely within the second source will continue to misalign along
+ *       the halfway cutoff and will likewise require two reads per write, just with both reads within
+ *       the same second source
+ */
+#if SIMD != 1
+__attribute__((intel_reqd_sub_group_size(SIMD)))
+#endif
+__kernel void
+internal_padding_block_concat2(__global DATA_T *dst,
+        const idx_t dst_concat_axis, const idx_t dst_padded_concat_axis,
+        __global const DATA_T *src0, const idx_t offset0,
+        const idx_t padded_offset0, const idx_t src_concat_axis0,
+        const idx_t src_padded_concat_axis0, __global const DATA_T *src1,
+        const idx_t offset1, const idx_t padded_offset1,
+        const idx_t src_concat_axis1, const idx_t src_padded_concat_axis1,
+        const idx_t inner_dim) {
+
+    const size_t dtsize = DATA_TYPE_SIZE;
+    const int loads_per_sg = NPERSG;
+    const int elems_per_sg = SIMD * loads_per_sg;
+
+#if BLOCK_DEPTH > 0
+    const unsigned B0 = BLOCK_B0;
+#else
+    const unsigned B0 = 1;
+#endif
+
+    const size_t first_boundary_block
+            = ((DIV_UP(src_concat_axis0, B0) - 1) * inner_dim);
+    const size_t last_boundary_block = first_boundary_block + inner_dim - 1;
+    const size_t tot_boundary_block
+            = (DIV_UP(dst_padded_concat_axis, B0) * inner_dim);
+
+    // TODO: host side check, if blocks_per_sg > #blocks in src0, use legacy method instead
+    unsigned blocks_per_sg;
+    if (B0 > (elems_per_sg)) {
+        blocks_per_sg = B0 / elems_per_sg;
+    } else {
+        blocks_per_sg = elems_per_sg
+                / B0; // 32 / 2 = 16 layout blocks covered by simd
+    }
+
+    __global const DATA_T *src;
+
+    // within block idx, ex: 0-7, 0-15, 0-23, 0-31
+    size_t blid = get_local_id(0) % B0;
+
+    size_t id_start = get_group_id(0) * elems_per_sg + get_local_id(0);
+    long bii = id_start / B0;
+
+    size_t sg_first_bii = get_group_id(0) * elems_per_sg / B0;
+
+    // index along concat dimension
+    long ic = (bii / inner_dim) * B0 + blid;
+    long ic_end = ((bii + blocks_per_sg) / inner_dim) * B0 + blid;
+
+    size_t sg_last_bii = sg_first_bii + blocks_per_sg
+            - 1; //TODO: verify w/B0 > sg_tot_elems
+
+    size_t ccsz
+            = src_concat_axis0; // (31) padded concat dimension for calculating batched src
+    size_t padded_ccsz
+            = padded_offset1; // (32) padded concat dimension for calculating batched src
+
+    size_t cutoff
+            = 0; // determines boundary offset for write sg that spans multiple layout blocks
+    bool boundary = false; // current sg spans boundary between two inputs?
+
+    size_t batch_offset = inner_dim * padded_ccsz * get_global_id(1);
+    size_t batch_offset1
+            = inner_dim * src_padded_concat_axis1 * get_global_id(1);
+    size_t ext_batch_offset
+            = inner_dim * dst_padded_concat_axis * get_global_id(1);
+
+    // completely aligned r/w blocks, no boundary src case nor misaligned reads
+    if (sg_last_bii < first_boundary_block) {
+        //TODO: no concretes src0, bii
+        DDATA_T val = BBLOCK_READ(src0 + batch_offset + sg_first_bii * B0);
+        BBLOCK_WRITE(dst + ext_batch_offset + sg_first_bii * B0, val);
+    } else if (sg_first_bii > last_boundary_block) {
+        // if sg_bii0 fully within second source, update src_bii+other idx vars to match src1
+        long src_blid = (ic - src_concat_axis0) % B0;
+        long src_bii = sg_first_bii
+                - ((padded_offset1 / B0)
+                        * inner_dim); // update inner logical block idx to 0-nblocks read range for src1
+        if (src_bii < 0) { src_bii += inner_dim; }
+
+        long sg_last_src_bii = src_bii + blocks_per_sg - 1;
+        ccsz = src_concat_axis1 - src_concat_axis0;
+        // TODO change dst_padded_concat_axis to padded_offset2? //TODO!!!! THIS IS WRONG
+        padded_ccsz = dst_padded_concat_axis
+                - padded_offset1; // update read padded size for batching
+        batch_offset = inner_dim * padded_ccsz * get_global_id(1);
+
+        cutoff = (B0 + (offset1 - padded_offset1) % B0)
+                % B0; // positive modulo( offset/padded_offset , block size) |-|-------|
+                // this cutoff will continue in following misaligned(multi-cacheline/ block src)
+
+        DDATA_T aVal = BBLOCK_READ(src1 + batch_offset1 + src_bii * B0);
+        long next_block = src_bii
+                + inner_dim; //offset to read next required block for second half of cutoff
+        long next_last_block = next_block + blocks_per_sg - 1;
+        const size_t tot_src1_block
+                = (DIV_UP(src_padded_concat_axis1, B0) * inner_dim);
+        DDATA_T bVal;
+        if (next_last_block > tot_src1_block) {
+            int n_blocks_tail = tot_src1_block - next_block;
+            int rem_elems = n_blocks_tail * B0;
+
+            for (int b = get_local_id(0), vid = 0; b < rem_elems;
+                    b += SIMD, vid++) {
+#if NPERSG > 1
path: src/gpu/intel/ocl/reusable_simple_concat.cl
user: login: atkassen
body: Can we switch all the `#if NPERSG > 1`s to `if (NPERSG > 1)`? They _should_ compile the same, and looks better in a reusable kernel.
created_at: 2025-04-23T21:53:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -234,3 +234,344 @@ reusable_simple_concat(__global DATA_T *dst, const ulong dst_offset0,
     }
 #endif // SIMD == 1
 }
+
+#define DATA1_T DATA_T
+#if DATA_TYPE_SIZE == 8
+#define NPERSG 1
+#define BBLOCK_READ BLOCK_READ
+#define BBLOCK_WRITE BLOCK_WRITE
+#define DDATA_T DATA1_T
+#define AS_VEC as_ulong
+#elif DATA_TYPE_SIZE == 4
+#define NPERSG 2
+#define BBLOCK_READ BLOCK_READ2
+#define BBLOCK_WRITE BLOCK_WRITE2
+#define DDATA_T DATA2_T
+#define AS_VEC as_uint2
+#elif DATA_TYPE_SIZE == 2
+#define NPERSG 4
+#define BBLOCK_READ BLOCK_READ4
+#define BBLOCK_WRITE BLOCK_WRITE4
+#define DDATA_T DATA4_T
+#define AS_VEC as_ushort4
+#elif DATA_TYPE_SIZE == 1
+#define NPERSG 8
+#define BBLOCK_READ BLOCK_READ8
+#define BBLOCK_WRITE BLOCK_WRITE8
+#define DDATA_T DATA8_T
+#define AS_VEC as_uchar8
+#endif
+
+/*
+ * This kernel handles internal padding cases by abstracting the problem into 3 cases:
+ *     - Reads and writes fall completely into the first source and can be copied as is
+ *
+ *     - Reads and writes fall across the boundary between two sources, neighboring "read blocks"
+ *       across the concat-axis boundary will need to be combined to form a single "write block" that will
+ *       consist halfway of each of the source blocks
+ *
+ *     - Reads and writes that fall completely within the second source will continue to misalign along
+ *       the halfway cutoff and will likewise require two reads per write, just with both reads within
+ *       the same second source
+ */
+#if SIMD != 1
+__attribute__((intel_reqd_sub_group_size(SIMD)))
+#endif
+__kernel void
+internal_padding_block_concat2(__global DATA_T *dst,
+        const idx_t dst_concat_axis, const idx_t dst_padded_concat_axis,
+        __global const DATA_T *src0, const idx_t offset0,
+        const idx_t padded_offset0, const idx_t src_concat_axis0,
+        const idx_t src_padded_concat_axis0, __global const DATA_T *src1,
+        const idx_t offset1, const idx_t padded_offset1,
+        const idx_t src_concat_axis1, const idx_t src_padded_concat_axis1,
+        const idx_t inner_dim) {
+
+    const size_t dtsize = DATA_TYPE_SIZE;
+    const int loads_per_sg = NPERSG;
+    const int elems_per_sg = SIMD * loads_per_sg;
+
+#if BLOCK_DEPTH > 0
+    const unsigned B0 = BLOCK_B0;
+#else
+    const unsigned B0 = 1;
+#endif
+
+    const size_t first_boundary_block
+            = ((DIV_UP(src_concat_axis0, B0) - 1) * inner_dim);
+    const size_t last_boundary_block = first_boundary_block + inner_dim - 1;
+    const size_t tot_boundary_block
+            = (DIV_UP(dst_padded_concat_axis, B0) * inner_dim);
+
+    // TODO: host side check, if blocks_per_sg > #blocks in src0, use legacy method instead
+    unsigned blocks_per_sg;
+    if (B0 > (elems_per_sg)) {
+        blocks_per_sg = B0 / elems_per_sg;
+    } else {
+        blocks_per_sg = elems_per_sg
+                / B0; // 32 / 2 = 16 layout blocks covered by simd
+    }
+
+    __global const DATA_T *src;
+
+    // within block idx, ex: 0-7, 0-15, 0-23, 0-31
+    size_t blid = get_local_id(0) % B0;
+
+    size_t id_start = get_group_id(0) * elems_per_sg + get_local_id(0);
+    long bii = id_start / B0;
+
+    size_t sg_first_bii = get_group_id(0) * elems_per_sg / B0;
+
+    // index along concat dimension
+    long ic = (bii / inner_dim) * B0 + blid;
+    long ic_end = ((bii + blocks_per_sg) / inner_dim) * B0 + blid;
+
+    size_t sg_last_bii = sg_first_bii + blocks_per_sg
+            - 1; //TODO: verify w/B0 > sg_tot_elems
+
+    size_t ccsz
+            = src_concat_axis0; // (31) padded concat dimension for calculating batched src
+    size_t padded_ccsz
+            = padded_offset1; // (32) padded concat dimension for calculating batched src
+
+    size_t cutoff
+            = 0; // determines boundary offset for write sg that spans multiple layout blocks
+    bool boundary = false; // current sg spans boundary between two inputs?
+
+    size_t batch_offset = inner_dim * padded_ccsz * get_global_id(1);
+    size_t batch_offset1
+            = inner_dim * src_padded_concat_axis1 * get_global_id(1);
+    size_t ext_batch_offset
+            = inner_dim * dst_padded_concat_axis * get_global_id(1);
+
+    // completely aligned r/w blocks, no boundary src case nor misaligned reads
+    if (sg_last_bii < first_boundary_block) {
+        //TODO: no concretes src0, bii
+        DDATA_T val = BBLOCK_READ(src0 + batch_offset + sg_first_bii * B0);
+        BBLOCK_WRITE(dst + ext_batch_offset + sg_first_bii * B0, val);
+    } else if (sg_first_bii > last_boundary_block) {
+        // if sg_bii0 fully within second source, update src_bii+other idx vars to match src1
+        long src_blid = (ic - src_concat_axis0) % B0;
+        long src_bii = sg_first_bii
+                - ((padded_offset1 / B0)
+                        * inner_dim); // update inner logical block idx to 0-nblocks read range for src1
+        if (src_bii < 0) { src_bii += inner_dim; }
+
+        long sg_last_src_bii = src_bii + blocks_per_sg - 1;
+        ccsz = src_concat_axis1 - src_concat_axis0;
+        // TODO change dst_padded_concat_axis to padded_offset2? //TODO!!!! THIS IS WRONG
+        padded_ccsz = dst_padded_concat_axis
+                - padded_offset1; // update read padded size for batching
+        batch_offset = inner_dim * padded_ccsz * get_global_id(1);
+
+        cutoff = (B0 + (offset1 - padded_offset1) % B0)
+                % B0; // positive modulo( offset/padded_offset , block size) |-|-------|
+                // this cutoff will continue in following misaligned(multi-cacheline/ block src)
+
+        DDATA_T aVal = BBLOCK_READ(src1 + batch_offset1 + src_bii * B0);
+        long next_block = src_bii
+                + inner_dim; //offset to read next required block for second half of cutoff
+        long next_last_block = next_block + blocks_per_sg - 1;
+        const size_t tot_src1_block
+                = (DIV_UP(src_padded_concat_axis1, B0) * inner_dim);
+        DDATA_T bVal;
+        if (next_last_block > tot_src1_block) {
+            int n_blocks_tail = tot_src1_block - next_block;
+            int rem_elems = n_blocks_tail * B0;
+
+            for (int b = get_local_id(0), vid = 0; b < rem_elems;
+                    b += SIMD, vid++) {
+#if NPERSG > 1
+                bVal[vid] = src1[batch_offset1 + next_block * B0 + b];
+#else
+                bVal = src1[batch_offset1 + next_block * B0 + b];
+#endif
+            }
+        } else {
+            bVal = BBLOCK_READ(src1 + batch_offset1
+                    + next_block
+                            * B0); //TODO: potential read past end? check w/asan?. YES, causes segfault. develop a MAYBE read instead
+        }
+
+        int shift = (B0 - cutoff);
+        int sg_shuffle_dt = shift; // TODO?:vec size per thread
+
+        DDATA_T offset_vec_data;
+        offset_vec_data = AS_VEC(intel_sub_group_shuffle_down(
+                as_ulong(aVal), as_ulong(aVal), sg_shuffle_dt));
+
+        unsigned bshift = (next_block * B0 - src_concat_axis0)
+                % B0; //mismatch between and next_block cutoff? src1
+        bVal = AS_VEC(intel_sub_group_shuffle_up(
+                as_ulong(bVal), as_ulong(bVal), cutoff));
+        if ((ic % B0)
+                < cutoff) { // TODO: should change together with sg_shuffle_dt sizeof(B0)
+            aVal = offset_vec_data;
+        } else {
+            aVal = bVal;
+        }
+
+        if (ic >= dst_concat_axis) {
+            aVal = 0;
+        } //depends todo: depends where each read falls, whether in range or not TODO: match for loop in BB case
+
+        unsigned blocks_per_simd1 = SIMD / B0; //WRONG! TODO: what if SIMD > B0?
+        if (ic_end >= dst_concat_axis) {
+#if NPERSG > 1 // TODO: reformulate as &= 0x00FFFF...
+            for (int i = 0; i < NPERSG; ++i) {
+                if ((((bii + i * blocks_per_simd1) / inner_dim) * B0 + blid)
+                        >= dst_concat_axis) {
+                    aVal[i] = 0; // NOT OK! slow af
+                }
+            }
+#else
+            aVal = 0;
+#endif
+        }
+
+        if (sg_last_bii > tot_boundary_block) {
+            int n_blocks_tail = tot_boundary_block - sg_first_bii;
+            int rem_elems = n_blocks_tail * B0;
+
+            for (int b = get_local_id(0), vid = 0; b < rem_elems;
+                    b += SIMD, vid++) {
+#if NPERSG > 1
+                dst[ext_batch_offset + sg_first_bii * B0 + b] = aVal
+                        [vid]; // TODO: replace with union? non-standard behavior
path: src/gpu/intel/ocl/reusable_simple_concat.cl
user: login: atkassen
body: Can you explain this TODO?
created_at: 2025-04-23T21:56:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -160,6 +160,69 @@ static status_t init_conf_common(impl::engine_t *engine, const concat_pd_t *pd,
             rt_conf.gws_d, dim_idx::invalid, device_info->gpu_arch());
 
     conf.use_large_index = (max_bytes > std::numeric_limits<int>::max());
+    //conf.use_large_index = (total_bytes > std::numeric_limits<int>::max()); //old?
+
+    // attempt to enable internal padding kernel
+    if (normalize.is_internal_padding_concat()) {
+        size_t concat2_inner_axis = dst_md.dims[axis::inner];
+        size_t concat2_dtsize = dnnl_data_type_size(dst_md.data_type);
+
+        size_t min_bytes_per_thread = 8;
+        size_t loads_per_thread = min_bytes_per_thread / concat2_dtsize;
+
+        // heuristic for problem size too small
+        size_t min_block_read_elements = conf.simd * loads_per_thread;
+        size_t src0_inner_elems = rt_conf.offset[1] * concat2_inner_axis;
+        bool src0_size_sufficient = src0_inner_elems > min_block_read_elements;
+
+        // heuristic for data misaligned for subgroup loads/stores
+        bool can_subgroup_read_dt = true;
+        size_t min_subgroup_alignment_size = 4;
+        if (concat2_dtsize < min_subgroup_alignment_size) {
+            if (conf.n_blocks > 0) {
+                can_subgroup_read_dt = (concat2_dtsize * conf.blocks[0])
+                        >= min_subgroup_alignment_size;
+            } else {
+                can_subgroup_read_dt = false;
+            }
+        }
+
+        // TODO: generalize "src_bank" calculation for smaller block sizes
+        bool supported_block_size = (conf.n_blocks > 0)
+                && ((conf.blocks[0] == 8) || (conf.blocks[0] == 16)
+                        || (conf.blocks[0] == 32));
+
+        bool can_use_internal_padding_concat2 = (conf.n == 2)
+                && can_subgroup_read_dt && src0_size_sufficient
+                && supported_block_size;
+        if (can_use_internal_padding_concat2) {
+            rt_conf.inner_axis = concat2_inner_axis;
+            conf.data_type_size = concat2_dtsize;
+            conf.use_internal_padding_kernel = true;
+
+            rt_conf.gws_d[0] = utils::div_up(dst_md.padded_dims[axis::concat]
+                                               * dst_md.dims[axis::inner],
+                                       conf.simd * loads_per_thread)
+                    * conf.simd;
+            rt_conf.gws_d[1] = dst_md.dims[axis::outer];
+            rt_conf.gws_d[2] = 1;
+
+            // TODO: compute::get_optimal_lws( // no emperical diff
+            rt_conf.lws_d[0] = conf.simd;
+            rt_conf.lws_d[1] = 1;
+            rt_conf.lws_d[2] = 1;
+
+            //printf("\n dims[%zu %zu %zu] padded: [%zu %zu %zu]\n gws[%zu %zu %zu] simd:%zu dtsize:%zu\n",
+            //dst_md.dims[axis::outer], dst_md.dims[axis::concat], dst_md.dims[axis::inner],
+            //dst_md.padded_dims[axis::outer], dst_md.padded_dims[axis::concat], dst_md.padded_dims[axis::inner],
+            //rt_conf.gws_d[0], rt_conf.gws_d[1], rt_conf.gws_d[2],
+            //conf.simd, conf.data_type_size);
+            //for(int i=0; i<conf.n_blocks; ++i){
+            //printf("b%d: %d, ", i, conf.blocks[i]);
+            //}
path: src/gpu/intel/ocl/reusable_simple_concat.cpp
user: login: atkassen
body: remove debug stuff?
created_at: 2025-04-23T21:57:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -226,27 +291,85 @@ void push_idx_kernel_args(compute::kernel_arg_list_t &partial_list,
     partial_list.append(static_cast<std::uint8_t>(must_compute_ext_idx));
 }
 
+template <typename IDX_T>
+void push_idx_kernel_args_internal_padding(
+        compute::kernel_arg_list_t &partial_list, const exec_ctx_t &ctx,
+        const reusable_simple_concat_params_t &conf,
+        const reusable_simple_concat_runtime_params_t &rt_conf,
+        const concat_pd_t *pd) {
+    const auto concat_dim = pd->concat_dim();
+
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_concat_axis));
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_padded_concat_axis));
+
+    //printf("dst_concat_axis=%ld dst_padded_concat_axis=%ld\n",
+    //rt_conf.dst_concat_axis, rt_conf.dst_padded_concat_axis);
path: src/gpu/intel/ocl/reusable_simple_concat.cpp
user: login: atkassen
body: remove?
created_at: 2025-04-23T22:10:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -226,27 +291,85 @@ void push_idx_kernel_args(compute::kernel_arg_list_t &partial_list,
     partial_list.append(static_cast<std::uint8_t>(must_compute_ext_idx));
 }
 
+template <typename IDX_T>
+void push_idx_kernel_args_internal_padding(
+        compute::kernel_arg_list_t &partial_list, const exec_ctx_t &ctx,
+        const reusable_simple_concat_params_t &conf,
+        const reusable_simple_concat_runtime_params_t &rt_conf,
+        const concat_pd_t *pd) {
+    const auto concat_dim = pd->concat_dim();
+
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_concat_axis));
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_padded_concat_axis));
+
+    //printf("dst_concat_axis=%ld dst_padded_concat_axis=%ld\n",
+    //rt_conf.dst_concat_axis, rt_conf.dst_padded_concat_axis);
+    for (int idx = 0, valid_idx = 0; idx < pd->n_inputs(); ++idx) {
+        // skip invalid inputs
+        if (pd->src_md(idx)->padded_dims[concat_dim] == 0) continue;
+
+        auto &src = CTX_IN_STORAGE(DNNL_ARG_MULTIPLE_SRC + idx);
+        partial_list.append(src);
+
+        partial_list.append(static_cast<IDX_T>(rt_conf.offset[valid_idx]));
+        partial_list.append(
+                static_cast<IDX_T>(rt_conf.padded_offset[valid_idx]));
+        dim_t src_concat_axis = valid_idx + 1 < conf.n
+                ? rt_conf.offset[valid_idx + 1]
+                : rt_conf.dst_concat_axis;
+        partial_list.append(static_cast<IDX_T>(src_concat_axis));
+
+        partial_list.append(pd->src_md(idx)->padded_dims[concat_dim]);
+
+        //printf("offset%d=%ld padded_offset%d=%ld src_concat_axis%d=%ld "
+        //"padded_src_concat_axis%d=%ld\n",
+        //valid_idx, rt_conf.offset[valid_idx], valid_idx,
+        //rt_conf.padded_offset[valid_idx], valid_idx, src_concat_axis,
+        //valid_idx, pd->src_md(idx)->padded_dims[concat_dim]);
path: src/gpu/intel/ocl/reusable_simple_concat.cpp
user: login: atkassen
body: remove?
created_at: 2025-04-23T22:10:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -226,27 +291,85 @@ void push_idx_kernel_args(compute::kernel_arg_list_t &partial_list,
     partial_list.append(static_cast<std::uint8_t>(must_compute_ext_idx));
 }
 
+template <typename IDX_T>
+void push_idx_kernel_args_internal_padding(
+        compute::kernel_arg_list_t &partial_list, const exec_ctx_t &ctx,
+        const reusable_simple_concat_params_t &conf,
+        const reusable_simple_concat_runtime_params_t &rt_conf,
+        const concat_pd_t *pd) {
+    const auto concat_dim = pd->concat_dim();
+
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_concat_axis));
+    partial_list.append(static_cast<IDX_T>(rt_conf.dst_padded_concat_axis));
+
+    //printf("dst_concat_axis=%ld dst_padded_concat_axis=%ld\n",
+    //rt_conf.dst_concat_axis, rt_conf.dst_padded_concat_axis);
+    for (int idx = 0, valid_idx = 0; idx < pd->n_inputs(); ++idx) {
+        // skip invalid inputs
+        if (pd->src_md(idx)->padded_dims[concat_dim] == 0) continue;
+
+        auto &src = CTX_IN_STORAGE(DNNL_ARG_MULTIPLE_SRC + idx);
+        partial_list.append(src);
+
+        partial_list.append(static_cast<IDX_T>(rt_conf.offset[valid_idx]));
+        partial_list.append(
+                static_cast<IDX_T>(rt_conf.padded_offset[valid_idx]));
+        dim_t src_concat_axis = valid_idx + 1 < conf.n
+                ? rt_conf.offset[valid_idx + 1]
+                : rt_conf.dst_concat_axis;
+        partial_list.append(static_cast<IDX_T>(src_concat_axis));
+
+        partial_list.append(pd->src_md(idx)->padded_dims[concat_dim]);
+
+        //printf("offset%d=%ld padded_offset%d=%ld src_concat_axis%d=%ld "
+        //"padded_src_concat_axis%d=%ld\n",
+        //valid_idx, rt_conf.offset[valid_idx], valid_idx,
+        //rt_conf.padded_offset[valid_idx], valid_idx, src_concat_axis,
+        //valid_idx, pd->src_md(idx)->padded_dims[concat_dim]);
+        valid_idx++;
+    }
+
+    partial_list.append(static_cast<IDX_T>(
+            rt_conf.inner_axis)); //INNERDIM add only for internal_pad kernel
+    //printf("inner_axis=%ld \n", rt_conf.inner_axis);
path: src/gpu/intel/ocl/reusable_simple_concat.cpp
user: login: atkassen
body: remove?
created_at: 2025-04-23T22:10:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,7 +125,8 @@ class normalization_t {
             padding_type_ = padding_t::external;
 
         if (padding_type_ == padding_t::internal) {
-            chunk_size_ = math::gcd(chunk_size_, source_chunk);
+            // may use different kernel, requires different partition for blocks
+            chunk_size_ = 1;
path: src/gpu/intel/ocl/concat_utils.hpp
user: login: atkassen
body: Sorry I was looking at just the first commit. I've resolved all of the out-of-date comments.
created_at: 2025-04-23T22:28:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,3 +97,7 @@
 --dtag=nchw
 --attr-scales=msrc0:common:2+msrc1:common:3+msrc2:common:4+msrc3:common:5+msrc4:common:6+msrc5:common:7+msrc6:common:8+msrc7:common:9+msrc8:common:10+msrc9:common:11+msrc10:common:12+msrc11:common:13+msrc12:common:14+msrc13:common:15+msrc14:common:16+msrc15:common:17+msrc16:common:18
 1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40
+
+# additional tests for internal padding concat kernel
+--reset
+--batch=test_concat_ip
path: tests/benchdnn/inputs/concat/test_concat_gpu
user: login: dzarukin
body: Please convert to harness. Every new `test` file spawns a step under ECMD.
created_at: 2025-04-24T04:16:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,3 +97,7 @@
 --dtag=nchw
 --attr-scales=msrc0:common:2+msrc1:common:3+msrc2:common:4+msrc3:common:5+msrc4:common:6+msrc5:common:7+msrc6:common:8+msrc7:common:9+msrc8:common:10+msrc9:common:11+msrc10:common:12+msrc11:common:13+msrc12:common:14+msrc13:common:15+msrc14:common:16+msrc15:common:17+msrc16:common:18
 1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40:1x1x40x40
+
+# additional tests for internal padding concat kernel
+--reset
+--batch=test_concat_ip
path: tests/benchdnn/inputs/concat/test_concat_gpu
user: login: syurkevi
body: Done, and tests further condensed.
created_at: 2025-04-24T16:57:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: syurkevi
created_at: 2025-03-14T01:58:29Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-03-14T17:24:33Z
body: make test
disable test_device_cpu
disable benchdnn_all
enable benchdnn_concat
author_association: CONTRIBUTOR
user: login: atkassen
created_at: 2025-03-14T17:41:56Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-03-15T01:09:17Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-03-20T01:11:59Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-03-20T02:04:32Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-04-03T03:05:26Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-04-08T03:27:39Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-04-11T00:09:32Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-04-11T03:11:50Z
body: make test
disable test_device_cpu
disable benchdnn_all
enable benchdnn_concat

author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-04-23T21:58:52Z
body: make test perf-gpu
set primitive=concat
author_association: CONTRIBUTOR
user: login: syurkevi
created_at: 2025-04-24T03:08:13Z
body: make test
disable test_device_cpu
disable benchdnn_all
enable benchdnn_concat
author_association: CONTRIBUTOR