Files
TheRock/rocm-libraries/projects/rocwmma/samples/community/simple_fusion_gemm.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1344 lines
52 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*******************************************************************************
*
* MIT License
*
* Copyright (C) 2021-2025 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*******************************************************************************/
#include <iostream>
#include <vector>
#include <hip/hip_ext.h>
#include <hip/hip_fp16.h>
#include <hip/hip_fp8.h>
#include <hip/hip_runtime.h>
#include <rocwmma/rocwmma.hpp>
#include <rocwmma/rocwmma_transforms.hpp>
#include "common.hpp"
/* Motivation
*
* For this particular fusion 2 GEMM kernel:
* D = A x B x C
*
* A, B, C= input tiles of MxK, KxN and NxK, respectively
* D = final output tile, MxN
* (M, N and K are block dimensions)
*
* For A x B phase, high performance can be
* achieved through two general principles:
* 1) Data re-use
* 2) Latency hiding
* And for S (the result of AxB) x C, the current performance isn’t particularly good.
* This sample showcases fusing two matmuls and serves as a basic reference for users opting
* to use rocWMMA for attention-style architectures.
*
*
* Performance (GFX9, int8, compared to two separate GEMM kernel invocations):
* +---------+---------+---------+------------+---------------+---------+
* | M | N | K | Fused (ms) | 2x GEMM (ms) | Speedup |
* +=========+=========+=========+============+===============+=========+
* | 8192 | 8192 | 128 | 1.2466 | 1.523 | 1.22x |
* | 1024 | 1024 | 128 | 0.1655 | 0.1863 | 1.13x |
* | 8192 | 8192 | 64 | 0.6523 | 0.8456 | 1.30x |
* | 1024 | 1024 | 64 | 0.0927 | 0.1143 | 1.23x |
* | 2048 | 2048 | 256 | 0.6057 | 0.7000 | 1.16x |
* +---------+---------+---------+------------+---------------+---------+
*
* The fused kernel achieves 10-30% speedup by eliminating the intermediate
* global memory round-trip: the first GEMM result (S = A x B) is kept in
* LDS and consumed directly by the second GEMM (D = S x C), avoiding the
* cost of writing S to global memory and reading it back.
*
* K corresponds to the attention head dimension (typically 64 or 128).
* The baseline measures two consecutive GEMM kernel launches using the
* same grid and rocWMMA tile configuration as the fused version.
*
* Unlike the other samples, we use a 1D grid instead of a 2D grid.
* Since tensors in neural networks are typically 4-D (B, H, N, D or B, N, H, D),
* with batch and head usually serving as the parallel dimensions,
* we parallelize only along the M dimension and handle the N/K dimensions via loops.
*
* In the simple_gemm sample, each warp is responsible for computing
* one output D tile of the final result. In the current sample, each
* warp is now responsible for computing multiple D tiles, what we
* might call a Warp Tile. Because Warp Tile blocks share data locality
* in either the same row or column direction, warps can re-use input
* data from A, B and C as they step through the K/N dimension for each block.
*
* Moreover, Warp Tiles processed by warps in a thread block
* have common locality in the larger Macro Tile. In the Global D layout
* shown below, data re-use opportunities await in D tiles aligned in the
* same rows / columns. These will pass over the same input A/B/C values as
* they march through the K/N dimension.
*
* Block size: (BlockM x BlockN)
* Warp tile size: (BlocksX * BlockSize.x) x (BlocksY * BlockSize.y)
* Macro Tile size: (TBlock.x * WarpTileSize.x) x (TBlock.y * WarpTileSize.y)
*
* Wave data share input A: same row
* Wave data share input B: same col
* Wave data share input C: same col
*
* Global D layout & warp assignment for BlocksX = BlocksY = 2, 2x2 Warps
*
* W (X, Y) = wave row X, col Y
* |--------- Macro Tile Y-------------|
* |-- Wave Tile Y --|
* |-BlockN-|
*
* BlockN x BlocksY BlockN x BlocksY
* |<--------------->|<--------------->|
* _ _ _ _ _ _ ___ ________ ________ ________ ________
* | | | ^ | | | | |
* | Wave| BlockM | BlockM | | W | W |
* | Tile| _|_ x | |__ (0, 0) __|__ (0, 1) __|
* | X | BlocksX | | | |
* Macro | | | | | |
* Tile | _|_ _v_ |________|________|________|________|
* X | ^ | | | | |
* | BlockM | | W | W |
* | x | |__ (1, 0) __|__ (1, 1) __|
* | BlocksX | | | |
* | | | | |
* _|_ _v_ |________|________|________|________|
*
*
* From the above diagram, we can see that input A/B data can be shared within warps,
* as well as between warps in the same threadblock. This means that warps in the same
* thread block can share the input loading responsibilities if they synchronize stepping
* through the K dimension for tiles at the same time.
*
* rocWMMA Cooperative API allows thread blocks to collaboratively move data from
* one location to another. In this case, we will move data from global memory space to
* local storage such that inter-warp data sharing is possible. Maximizing data re-use
* in this way reduces costly access to global memory and improves performance.
*
* To maximize efficiency, we can structure the kernel to maximize bandwidth usage and keep
* the compute resources as busy as possible at the same time. Using a pre-fetch technique,
* we can fetch A/B inputs for the next K-step while keeping the compute resources busy
* processing the current K-step. This helps to hide memory fetching latency.
*
* In general, the process would flow like the following:
*
* Start
* |
* v
* Loop: ab_i = 0:B blocks
* ^ |
* | Pre-Fetch Global A/B for K0
* | |
* | Store LDS buffer0
* | |
* | v
* | Loop: i = 1:K-1
* | ^ |
* | | Fetch Global A/B i+1; store LDS Buffer 1
* | | |
* | | Load LDS buffer0; Accum A x B
* | | |
* | | Swap buffer0, buffer1
* | | |
* | | |
* | end_loop <-
* | |
* | type cast: int32 -> int8
* | |
* | Loop: sc_i = 0:C blocks
* | ^ |
* | | Loop: i = 0:K-1
* | | ^ |
* | | | Fetch Global C i; store LDS Buffer 1
* | | | |
* | | | C Load LDS buffer1; S Load LDS buffer0; Accum S x C
* | | | |
* | | | |
* | | end_loop <-
* | | |
* | | |
* | end_loop <-
* | |
* | |
* -- end_loop <-
* Loop: sc_i = 0:C blocks
* ^ |
* | Write D tile sc_i
* | |
* | |
* -- end_loop <-
* |
* v
* End
*
* Lds Mapping in AxB phase:
* Buffer Width = LDS Width = BlockK
* Matrix geometry for inputs A and B have a common dimension (BlockK).
* We can fix one of the LDS dimensions to BlockK (in this case the width),
* and insert blocks of different heights (BlockM, BlockN) to use the space
* without the need of extra padding.
*
* Fragments of B must be transposed to fit this geometry,
* and both fragments from A and B must accomodate LDS data layout.
*
* Lds Mapping in AxB phase:
* After AxB, we reinterpret_cast LDS width = MACRO_TILE_Y, height = MACRO_TILE_X
* to store result of A block x b block.
* And the rest LDS is used to store GlobalRead Tile
* (In fact, there is some wastage of LDS space.)
*
* Local Layout (LDS):
*
* Non - transposed A fragments [A0 ... AX-1] are placed first and occupy a total height
* of Macro Tile X, where X = number of A blocks and Ck is the kth column of the A block.
*
* Transposed B fragments [B0 (T) ... BY-1 (T)] follow A fragments and occupy a total height of
* Macro Tile Y, where Y = number of B blocks, and Rk is the kth row of the B block.
*
*
* _____________BlockK_____________
* | |
* v v
* (0,0) ----------------------------------->
* --> --> ______________ ... ______
* | | | | | | |
* | | | | | | |
* Macro | BlockM | | C0 | C1 | C2 | Ck-1 | A0
* Tile X | | | | | | |
* | --> |___ |___ |____ ... |______|
* | .
* | . ... ... ... ... AX-1
* -->
* --> --> ______________ ... ______
* | | | | | | |
* | | | | | | |
* Macro | BlockN | | R0 | R1 | R2 | Rk-1 | B0 (T)
* Tile Y | | | | | | |
* | --> |___ |___ |____ ... |______|
* | .
* | . ... ... ... ... BY-1 (T)
* --> (MacroTileX + MacroTileY - 1, BlockK -1)
*
* |--- LDS height = Macro Tile Y-------|
* _ _ _ _ _ _ ___ ________ ________ ________ ________
* | | | ^ | | | | |
* | Wave| BlockM | BlockM | | | | | |
* | Tile| _|_ x | |__ __ _ | _ __ __|__ __ _ | _ __ __|
* | X | BlocksX | | | | | |
* Macro | | | | | | | |
* TileX | _|_ _v_ |________|________|________|________|
* | ^ | | | | |
* | BlockM | | | | | |
* | x | |__ __ _ | _ __ __|__ __ _ | _ __ __|
* | BlocksX | | | | | |
* | | | | | | |
* _|_ _v_ |________|________|________|________|
* | | | ^ | | |
* | Wave| BlockM | BlockM | | | |
* | Tile| _|_ x | |__ __ _ | |
* | X | BlocksX | | | |
* Macro | | | | | Not |
* TileC | _|_ _v_ |________| Used |
* | ^ | | |
* | BlockM | | | |
* | x | |__ __ _ | |
* | BlocksX | | | |
* | | | | |
* _|_ _v_ |________|________ ________ ________|
*
*
* Depending on the locality of the block being processed, warps load the corresponding
* A and B inputs from LDS buffer and use them for the accumulation of AxB calculations.
*/
using namespace rocwmma;
///
/// Parameter configuration
///
/* Depending on the GPU architecture this sample is run on, the following kernel parameters need to
* be modified in order to obtain high performance.
* _________________________________________________________________________________________
*| | | | | | | | |
*| | ROCWMMA_M | ROCWMMA_N | ROCWMMA_K | BLOCKS_X | BLOCKS_Y | TBLOCK_X | TBLOCK_Y |
*|_________|___________|___________|___________|__________|__________|__________|__________|
*| | | | | | | | |
*| GFX_9 | 16 | 16 | 32 | 2 | 2 | 128 | 2 |
*|_________|___________|___________|___________|__________|__________|__________|__________|
*
* __________________________________________
*| | |
*| | WARP_SIZE |
*|_________|________________________________|
*| | |
*| GFX_9 | Constants::AMDGCN_WAVE_SIZE_64 |
*|_________|________________________________|
*/
namespace gfx9Params
{
enum kernelParams : uint32_t
{
ROCWMMA_M = 16u,
ROCWMMA_N = 16u,
ROCWMMA_K = 32u,
BLOCKS_X = 2u,
BLOCKS_Y = 2u,
TBLOCK_X = 128u,
TBLOCK_Y = 2u,
WARP_SIZE = Constants::AMDGCN_WAVE_SIZE_64
};
}
using namespace gfx9Params;
///
/// Types and Data Layouts
///
using InputT = int8_t;
using InputTV = int8_t;
using OutputT = int32_t;
using ComputeT = int32_t;
using LDST = float32_t;
using LDST_new = hip_fp8_e4m3_fnuz;
using DataLayoutA = row_major;
using DataLayoutB = col_major;
using DataLayoutC = row_major;
using DataLayoutLds = col_major;
using DataLayoutV = col_major;
// It's for 2rd GEMM,currently not support modify
// If switching to col_major, code modifications will be required.
using DataLayoutLds_new = row_major;
///
/// Fragment types
///
// Warp tile: computed by each warp
constexpr uint32_t WARP_TILE_X = BLOCKS_X * ROCWMMA_M;
constexpr uint32_t WARP_TILE_Y = BLOCKS_Y * ROCWMMA_N;
constexpr uint32_t els_per_thread = ROCWMMA_M * ROCWMMA_N / WARP_SIZE;
constexpr uint32_t threads_per_row = ROCWMMA_N / els_per_thread;
// Macro Tile: computed by each thread block (workgroup)
// Note: TBLOCK_X must be multiple of WARP_SIZE.
constexpr uint32_t WARPS_X = TBLOCK_X / WARP_SIZE;
constexpr uint32_t WARPS_Y = TBLOCK_Y;
constexpr uint32_t MACRO_TILE_X = WARPS_X * WARP_TILE_X;
constexpr uint32_t MACRO_TILE_Y = WARPS_Y * WARP_TILE_Y;
constexpr uint32_t MACRO_TILE_K = WARPS_Y * BLOCKS_Y * ROCWMMA_K;
// Mfma frags
using MfmaFragA = fragment<matrix_a, ROCWMMA_M, ROCWMMA_N, ROCWMMA_K, InputT, DataLayoutA>;
using MfmaFragB = fragment<matrix_b, ROCWMMA_M, ROCWMMA_N, ROCWMMA_K, InputT, DataLayoutB>;
using MfmaFragC = fragment<accumulator, ROCWMMA_M, ROCWMMA_N, ROCWMMA_K, OutputT, DataLayoutC>;
using MfmaFragD = MfmaFragC;
using MfmaFragAcc = fragment<accumulator, ROCWMMA_M, ROCWMMA_N, ROCWMMA_K, ComputeT, DataLayoutC>;
using MfmaFragF8
= fragment<matrix_a, ROCWMMA_M, ROCWMMA_N, MACRO_TILE_Y, hip_fp8_e4m3_fnuz, DataLayoutC>;
using MfmaFragAcc2 = fragment<accumulator, ROCWMMA_M, ROCWMMA_N, ROCWMMA_K, InputTV, DataLayoutA>;
using MfmaFragS = fragment<matrix_a, ROCWMMA_M, ROCWMMA_N, ROCWMMA_K, InputTV, DataLayoutA>;
using MfmaFragV = fragment<matrix_b, ROCWMMA_M, ROCWMMA_N, ROCWMMA_K, InputTV, DataLayoutV>;
// Global read (macro tile)
using CoopScheduler = fragment_scheduler::coop_row_major_2d<TBLOCK_X, TBLOCK_Y>;
using GRBuffA
= fragment<matrix_a, MACRO_TILE_X, ROCWMMA_N, ROCWMMA_K, InputT, DataLayoutA, CoopScheduler>;
using GRBuffB
= fragment<matrix_b, ROCWMMA_M, MACRO_TILE_Y, ROCWMMA_K, InputT, DataLayoutB, CoopScheduler>;
using GRBuffS
= fragment<matrix_a, MACRO_TILE_X, ROCWMMA_N, ROCWMMA_K, InputTV, DataLayoutA, CoopScheduler>;
using GRBuffV
= fragment<matrix_b, ROCWMMA_M, MACRO_TILE_Y, ROCWMMA_K, InputTV, DataLayoutV, CoopScheduler>;
// Local write of global buffers (macro tile)
// - Must match Lds data layout.
// - Lds has transposed B frags.
using LWBuffA = apply_data_layout_t<GRBuffA, DataLayoutLds>;
using LWBuffB = apply_data_layout_t<apply_transpose_t<GRBuffB>, DataLayoutLds>;
using LWBuffS = apply_data_layout_t<GRBuffS, DataLayoutLds_new>;
using LWBuffV = apply_data_layout_t<apply_transpose_t<GRBuffV>, DataLayoutLds_new>;
// Local read (mfma frags)
// - Must match Lds data layout.
// - Lds has transposed B frags.
using LRFragA = apply_data_layout_t<MfmaFragA, DataLayoutLds>;
using LRFragB = apply_data_layout_t<apply_transpose_t<MfmaFragB>, DataLayoutLds>;
using LRFragS = apply_data_layout_t<MfmaFragS, DataLayoutLds_new>;
using LRFragV = apply_data_layout_t<apply_transpose_t<MfmaFragV>, DataLayoutLds_new>;
using LRFragAcc2 = apply_data_layout_t<MfmaFragAcc2, DataLayoutLds_new>;
// #endif // (ROCWMMA_ARCH_GFX9 || ROCWMMA_ARCH_GFX11)
///
/// Wrapper functions: repeat mfma tile operations across entire warp tile.
///
// Cooperative global read / local write (Macro tile data movement)
// Loads / stores a global data fragment cooperatively across warps. Each participating warp is
// responsible for only a portion of the whole fragment.
//
// The cooperative operation is split into work items (SplitCount). Work items are consumed in
// a round robin fashion by warps in the range of [0, WaveCount). The wave index determines the
// order of the current wave in the collaboration pool.
//
// WaveCount, SplitCount and waveIndex parameters must match successive coop load / store calls
// to ensure the entire fragment remains coherent.
// Global A reads in cooperative mode (macro tile)
ROCWMMA_DEVICE static inline void
globalReadCoopA(GRBuffA& grBuffA, InputT const* gAddrA, uint32_t lda)
{
load_matrix_sync(grBuffA, gAddrA, lda);
}
// Global B reads in cooperative mode (macro tile)
ROCWMMA_DEVICE static inline void
globalReadCoopB(GRBuffB& grBuffB, InputT const* gAddrB, uint32_t ldb)
{
load_matrix_sync(grBuffB, gAddrB, ldb);
}
ROCWMMA_DEVICE static inline void
globalReadCoopV(GRBuffV& grBuffV, InputTV const* gAddrV, uint32_t ldV)
{
load_matrix_sync(grBuffV, gAddrV, ldV);
}
// Local A writes in cooperative mode (macro tile)
ROCWMMA_DEVICE static inline void
localWriteCoopA(InputT* ldsAddr, GRBuffA const& grBuffA, uint32_t ldsld)
{
// No transpose, but apply the lds data layout
store_matrix_sync(ldsAddr, apply_data_layout<DataLayoutLds>(grBuffA), ldsld);
}
// Local B writes in cooperative mode (macro tile)
ROCWMMA_DEVICE static inline void
localWriteCoopB(InputT* ldsAddr, GRBuffB const& grBuffB, uint32_t ldsld)
{
// Transpose B and then apply lds data layout
store_matrix_sync(ldsAddr, apply_data_layout<DataLayoutLds>(apply_transpose(grBuffB)), ldsld);
}
ROCWMMA_DEVICE static inline void
localWriteCoopV(InputTV* ldsAddr, GRBuffV const& grBuffV, uint32_t ldsld)
{
// Transpose B and then apply lds data layout
store_matrix_sync(
ldsAddr, apply_data_layout<DataLayoutLds_new>(apply_transpose(grBuffV)), ldsld);
}
// Local A reads for warp tile gemm, non-cooperative
ROCWMMA_DEVICE static inline void
localReadA(MfmaFragA (&fragsA)[BLOCKS_X], InputT const* ldsAddrA, uint32_t ldsld)
{
using FragShape = GetIOShape_t<LRFragA>;
using Mapper1d = GetDataLayout_t<LRFragA>;
// Each A block is stacked vertically in LDS
auto blockStep = Mapper1d::fromMatrixCoord(make_coord2d(FragShape::BlockHeight, 0u), ldsld);
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
LRFragA tmp;
load_matrix_sync(tmp, ldsAddrA, ldsld);
fragsA[i] = apply_data_layout<DataLayoutA>(tmp);
ldsAddrA += blockStep;
}
}
ROCWMMA_DEVICE static inline void
localReadS(MfmaFragS (&fragsS)[BLOCKS_X], InputTV const* ldsAddrS, uint32_t ldsld)
{
using FragShape = GetIOShape_t<LRFragS>;
using Mapper1d = GetDataLayout_t<LRFragS>;
// Each A block is stacked vertically in LDS
auto blockStep = Mapper1d::fromMatrixCoord(make_coord2d(FragShape::BlockHeight, 0u), ldsld);
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
LRFragS tmp;
load_matrix_sync(tmp, ldsAddrS, ldsld);
fragsS[i] = apply_data_layout<DataLayoutA>(tmp);
ldsAddrS += blockStep;
}
}
// Local B reads for warp tile gemm, non-cooperative
ROCWMMA_DEVICE static inline void
localReadB(MfmaFragB (&fragsB)[BLOCKS_Y], InputT const* ldsAddrB, uint32_t ldsld)
{
using FragShape = GetIOShape_t<LRFragB>;
using Mapper1d = GetDataLayout_t<LRFragB>;
// Each B block is stacked vertically in LDS
auto blockStep = Mapper1d::fromMatrixCoord(make_coord2d(FragShape::BlockHeight, 0u), ldsld);
#pragma unroll
for(int i = 0; i < BLOCKS_Y; i++)
{
LRFragB tmp;
load_matrix_sync(tmp, ldsAddrB, ldsld);
// Transform back to MFMA tile
fragsB[i] = apply_data_layout<DataLayoutB>(apply_transpose(tmp));
ldsAddrB += blockStep;
}
}
ROCWMMA_DEVICE static inline void
localReadV(MfmaFragV (&fragsV)[BLOCKS_Y], InputTV const* ldsAddrV, uint32_t ldsld)
{
using FragShape = GetIOShape_t<LRFragV>;
using Mapper1d = GetDataLayout_t<LRFragV>;
// Each B block is stacked vertically in LDS
auto blockStep = Mapper1d::fromMatrixCoord(make_coord2d(FragShape::BlockHeight, 0u), ldsld);
#pragma unroll
for(int i = 0; i < BLOCKS_Y; i++)
{
LRFragV tmp;
load_matrix_sync(tmp, ldsAddrV, ldsld);
// Transform back to MFMA tile
fragsV[i] = apply_data_layout<DataLayoutV>(apply_transpose(tmp));
ldsAddrV += blockStep;
}
}
// Global C reads for warp tile gemm, non-cooperative
ROCWMMA_DEVICE static inline void
globalReadC(MfmaFragC (&fragC)[BLOCKS_X][BLOCKS_Y], OutputT const* gAddrC, uint32_t ldc)
{
using FragShape = GetIOShape_t<MfmaFragC>;
using Mapper1d = GetDataLayout_t<MfmaFragC>;
// Iterative offsets for each C block in the wave tile
auto blockStepX = Mapper1d::fromMatrixCoord(make_coord2d(FragShape::BlockHeight, 0u), ldc);
auto blockStepY = Mapper1d::fromMatrixCoord(make_coord2d(0u, FragShape::BlockWidth), ldc);
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
auto offsetY = 0u;
#pragma unroll
for(int j = 0; j < BLOCKS_Y; j++)
{
load_matrix_sync(fragC[i][j], gAddrC + offsetY, ldc);
offsetY += blockStepY;
}
gAddrC += blockStepX;
}
}
// Global D writes for warp tile gemm, non-cooperative
ROCWMMA_DEVICE static inline void
globalWriteD(OutputT* gAddrD, MfmaFragAcc const (&fragsD)[BLOCKS_X][BLOCKS_Y], uint32_t ldd)
{
using FragShape = GetIOShape_t<MfmaFragD>;
using Mapper1d = GetDataLayout_t<MfmaFragD>;
// Iterative offsets for each D block in the warp tile
auto blockStepX = Mapper1d::fromMatrixCoord(make_coord2d(FragShape::BlockHeight, 0u), ldd);
auto blockStepY = Mapper1d::fromMatrixCoord(make_coord2d(0u, FragShape::BlockWidth), ldd);
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
auto offsetY = 0u;
#pragma unroll
for(int j = 0; j < BLOCKS_Y; j++)
{
store_matrix_sync(gAddrD + offsetY, fragsD[i][j], ldd);
offsetY += blockStepY;
}
gAddrD += blockStepX;
}
}
// Broadcast value to fragments in warp tile
template <typename FragT>
ROCWMMA_DEVICE static inline void fill(FragT (&frags)[BLOCKS_X][BLOCKS_Y],
GetDataType_t<FragT> value)
{
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
#pragma unroll
for(int j = 0; j < BLOCKS_Y; j++)
{
fill_fragment(frags[i][j], value);
}
}
}
ROCWMMA_DEVICE static inline void
localWriteAcc(MfmaFragAcc2 (&fragsAcc)[BLOCKS_X][BLOCKS_Y], InputT* ldsAddrAcc, uint32_t ldsld)
{
using FragShape = GetIOShape_t<LRFragAcc2>;
using Mapper1d = GetDataLayout_t<LRFragAcc2>;
auto blockStepX = Mapper1d::fromMatrixCoord(make_coord2d(FragShape::BlockHeight, 0u), ldsld);
auto blockStepY = Mapper1d::fromMatrixCoord(make_coord2d(0u, FragShape::BlockWidth), ldsld);
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
auto offsetY = 0u;
#pragma unroll
for(int j = 0; j < BLOCKS_Y; j++)
{
store_matrix_sync(ldsAddrAcc + offsetY, fragsAcc[i][j], ldsld);
offsetY += blockStepY;
}
ldsAddrAcc += blockStepX;
}
}
ROCWMMA_DEVICE static inline void convertI32toI8(MfmaFragAcc (&frags_i32)[BLOCKS_X][BLOCKS_Y],
MfmaFragAcc2 (&frags_i8)[BLOCKS_X][BLOCKS_Y])
{
#pragma unroll
for(int i = 0; i < BLOCKS_X; ++i)
{
#pragma unroll
for(int j = 0; j < BLOCKS_Y; ++j)
{
#pragma unroll
for(int k = 0; k < frags_i32[i][j].num_elements; ++k)
{
int32_t value = frags_i32[i][j].x[k];
if(value > INT8_MAX)
{
frags_i8[i][j].x[k] = INT8_MAX;
}
else if(value < INT8_MIN)
{
frags_i8[i][j].x[k] = INT8_MIN;
}
else
{
frags_i8[i][j].x[k] = static_cast<int8_t>(value);
}
}
}
}
}
ROCWMMA_DEVICE static inline void svgemm(MfmaFragAcc (&fragsAccOut)[BLOCKS_X][BLOCKS_Y],
MfmaFragS const (&fragsA)[BLOCKS_X],
MfmaFragV const (&fragsB)[BLOCKS_Y],
MfmaFragAcc const (&fragsAccIn)[BLOCKS_X][BLOCKS_Y])
{
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
#pragma unroll
for(int j = 0; j < BLOCKS_Y; j++)
{
mma_sync(fragsAccOut[i][j], fragsA[i], fragsB[j], fragsAccIn[i][j]);
}
}
}
// Performs warp tile mfma
ROCWMMA_DEVICE static inline void mfma(MfmaFragAcc (&fragsAccOut)[BLOCKS_X][BLOCKS_Y],
MfmaFragA const (&fragsA)[BLOCKS_X],
MfmaFragB const (&fragsB)[BLOCKS_Y],
MfmaFragAcc const (&fragsAccIn)[BLOCKS_X][BLOCKS_Y])
{
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
#pragma unroll
for(int j = 0; j < BLOCKS_Y; j++)
{
mma_sync(fragsAccOut[i][j], fragsA[i], fragsB[j], fragsAccIn[i][j]);
}
}
}
// Uniform multiply - add (FMA)
// Performs D = alpha * acc + beta * C, where alpha, beta are uniform scalars
ROCWMMA_DEVICE static inline void uniformFma(MfmaFragD (&fragsD)[BLOCKS_X][BLOCKS_Y],
ComputeT alpha,
MfmaFragAcc const (&fragsAcc)[BLOCKS_X][BLOCKS_Y],
ComputeT beta,
MfmaFragC const (&fragsC)[BLOCKS_X][BLOCKS_Y])
{
#pragma unroll
for(int i = 0; i < BLOCKS_X; i++)
{
#pragma unroll
for(int j = 0; j < BLOCKS_Y; j++)
{
for(int k = 0; k < fragsD[i][j].num_elements; k++)
{
// Perform computation in ComputeT and cast back to OutputT
fragsD[i][j].x[k] = static_cast<OutputT>(
alpha * fragsAcc[i][j].x[k] + beta * static_cast<ComputeT>(fragsC[i][j].x[k]));
}
}
}
}
template <int SV_ITERS>
ROCWMMA_KERNEL void __launch_bounds__(256) gemm_rocwmma_d(uint32_t m,
uint32_t n,
uint32_t k,
InputT const* a,
InputT const* b,
InputTV const* v,
OutputT const* c,
OutputT* d,
uint32_t lda,
uint32_t ldb,
uint32_t ldv,
uint32_t ldd)
{
if constexpr(!ROCWMMA_ARCH_HOST)
{
///
/// 2D matrix coordinate setup
///
// Tile Sizes
constexpr auto warpTileSize = make_coord2d(WARP_TILE_X, WARP_TILE_Y); //(64,64)
constexpr auto macroTileSize = make_coord2d(MACRO_TILE_X, MACRO_TILE_Y); //(128,128)
// Local warp coordinate relative to current threadblock (wg).
constexpr auto warpDims = make_coord2d(WARPS_X, WARPS_Y); //(2,2)
auto localWarpCoord = make_coord2d(threadIdx.x / WARP_SIZE, threadIdx.y);
auto localWarpOffset = localWarpCoord * warpTileSize;
using MfmaFragDMap1d = GetDataLayout_t<MfmaFragD>;
const int iterations = (n + MACRO_TILE_Y - 1) / MACRO_TILE_Y;
MfmaFragAcc fragsOut[SV_ITERS][BLOCKS_X][BLOCKS_Y];
for(int i = 0; i < SV_ITERS; i++)
{
fill(fragsOut[i], 0.0f);
}
for(int iter = 0; iter < iterations; iter++)
{
// Global matrix coordinates for C/D
auto macroTileCoord = make_coord2d(blockIdx.x, iter) * macroTileSize;
auto warpTileCoord = macroTileCoord + localWarpOffset;
// Bounds check
auto warpTileBound = warpTileCoord + warpTileSize;
if(get<0>(warpTileBound) > m || get<1>(warpTileBound) > n)
{
return;
}
///
/// 1D global read coordinate setup
///
using GRBuffAMap1d = GetDataLayout_t<GRBuffA>;
using GRBuffBMap1d = GetDataLayout_t<GRBuffB>;
// Initial global read address offsets
auto globalReadOffsetA
= GRBuffAMap1d::fromMatrixCoord(make_coord2d(get<0>(macroTileCoord), 0u), lda);
auto globalReadOffsetB
= GRBuffBMap1d::fromMatrixCoord(make_coord2d(0u, get<1>(macroTileCoord)), ldb);
// Incremental global read address offsets
auto kStepOffsetA = GRBuffAMap1d::fromMatrixCoord(make_coord2d(0u, ROCWMMA_K), lda);
auto kStepOffsetB = GRBuffBMap1d::fromMatrixCoord(make_coord2d(ROCWMMA_K, 0u), ldb);
///
/// Cooperative config for global read A / B
///
// WorkItems will be split up by minimum IOCount to perform either global read or local write.
// These are inputs to cooperative functions.
constexpr auto warpCount = get<0>(warpDims) * get<1>(warpDims);
// Scheduling warp order is analogous to row major priority.
// E.g. Wg = (128, 2) = 2x2 warps
// (0, 0) (0, 1) Share Schedule: w0 = (0, 0), w1 = (0, 1),
// (1, 0) (1, 1) w2 = (1, 0), w3 = (1, 1), count = 4
const auto warpIndex
= get<0>(localWarpCoord) * get<1>(warpDims) + get<1>(localWarpCoord);
///
/// Perform initial global pre-fetch
///
GRBuffA grBuffA;
GRBuffB grBuffB;
globalReadCoopA(grBuffA, a + globalReadOffsetA, lda);
globalReadCoopB(grBuffB, b + globalReadOffsetB, ldb);
globalReadOffsetA += kStepOffsetA;
globalReadOffsetB += kStepOffsetB;
///
/// Setup LDS addressing
/// This kernel will use 2 separate LDS blocks for pipelining
/// the input prefetching during the accumulation loop
///
HIP_DYNAMIC_SHARED(void*, localMemPtr);
using LWBuffAShape = GetIOShape_t<LWBuffA>;
using LWBuffBShape = GetIOShape_t<LWBuffB>;
using LWBuffAMap1d = GetDataLayout_t<LWBuffA>;
using LWBuffBMap1d = GetDataLayout_t<LWBuffB>;
constexpr uint32_t ldsWidth = ROCWMMA_K;
constexpr uint32_t ldsHeight = LWBuffAShape::BlockHeight + LWBuffBShape::BlockHeight;
constexpr uint32_t sizeLds = ldsHeight * ldsWidth;
constexpr uint32_t ldsld
= std::is_same_v<DataLayoutLds, row_major> ? ldsWidth : ldsHeight;
auto* ldsPtrLo = reinterpret_cast<InputT*>(localMemPtr);
auto* ldsPtrHi = ldsPtrLo + sizeLds;
// Local write offsets to start of A / B data
auto ldsWriteOffsetA = 0u;
auto ldsWriteOffsetB
= LWBuffAMap1d::fromMatrixCoord(make_coord2d(LWBuffAShape::BlockHeight, 0u), ldsld);
// Local read offsets for mfma frags
auto ldsReadOffsetA
= ldsWriteOffsetA
+ LWBuffAMap1d::fromMatrixCoord(make_coord2d(get<0>(localWarpOffset), 0u), ldsld);
auto ldsReadOffsetB
= ldsWriteOffsetB
+ LWBuffBMap1d::fromMatrixCoord(make_coord2d(get<1>(localWarpOffset), 0u), ldsld);
///
/// Write prefetch to local
///
localWriteCoopA(ldsPtrLo + ldsWriteOffsetA, grBuffA, ldsld);
localWriteCoopB(ldsPtrLo + ldsWriteOffsetB, grBuffB, ldsld);
///
/// Initialize accumulation frags
///
MfmaFragAcc fragsAcc[BLOCKS_X][BLOCKS_Y];
fill(fragsAcc, 0.0f);
///
/// Synchronize warps and memory
///
synchronize_workgroup();
///
/// Accumulate A * B for all mfma frags in warp tile
///
for(uint32_t currentK = ROCWMMA_K; currentK < k; currentK += ROCWMMA_K)
{
MfmaFragA fragsA[BLOCKS_X];
MfmaFragB fragsB[BLOCKS_Y];
// Local read mfma frags from first LDS buffer
localReadA(fragsA, ldsPtrLo + ldsReadOffsetA, ldsld);
localReadB(fragsB, ldsPtrLo + ldsReadOffsetB, ldsld);
// Prefetch next round of global frags
globalReadCoopA(grBuffA, a + globalReadOffsetA, lda);
globalReadCoopB(grBuffB, b + globalReadOffsetB, ldb);
// Advance offsets to next k step
globalReadOffsetA += kStepOffsetA;
globalReadOffsetB += kStepOffsetB;
// accum(A * B)
mfma(fragsAcc, fragsA, fragsB, fragsAcc);
// Write prefetch to second LDS buffer
localWriteCoopA(ldsPtrHi + ldsWriteOffsetA, grBuffA, ldsld);
localWriteCoopB(ldsPtrHi + ldsWriteOffsetB, grBuffB, ldsld);
// Make sure that all waves have finished reading / writing to lds for currentK.
synchronize_workgroup();
// Swap Lds buffers
auto* tmp = ldsPtrLo;
ldsPtrLo = ldsPtrHi;
ldsPtrHi = tmp;
}
///
/// Clean up tail A * B
///
MfmaFragA fragsA[BLOCKS_X];
MfmaFragB fragsB[BLOCKS_Y];
// Local read mfma frags
localReadA(fragsA, ldsPtrLo + ldsReadOffsetA, ldsld);
localReadB(fragsB, ldsPtrLo + ldsReadOffsetB, ldsld);
mfma(fragsAcc, fragsA, fragsB, fragsAcc); // to do: add write to lds
// Here,we try use store/load_matrix_sync interface to get a row data by reset LDS height
// and width logically.
MfmaFragAcc2 fragsTmp[BLOCKS_X][BLOCKS_Y];
convertI32toI8(fragsAcc, fragsTmp);
auto* ldsPtr = reinterpret_cast<InputTV*>(localMemPtr);
constexpr uint32_t ldsWidth_new = MACRO_TILE_Y;
constexpr uint32_t ldsHeight_new = 8 * MACRO_TILE_X;
constexpr uint32_t ldsld_new
= std::is_same_v<DataLayoutLds_new, row_major> ? ldsWidth_new : ldsHeight_new;
auto ldsReadOffsetAcc = get<0>(localWarpOffset) * ldsld_new + get<1>(localWarpOffset);
localWriteAcc(fragsTmp, ldsPtr + ldsReadOffsetAcc, ldsld_new);
synchronize_workgroup();
//// loop
for(int sv_iter = 0; sv_iter < SV_ITERS; sv_iter++)
{
auto sv_mToffset = sv_iter * get<1>(macroTileSize);
auto sv_wpoffset = sv_mToffset + get<1>(localWarpOffset);
//// load v and calculate sv
using GRBuffVMap1d = GetDataLayout_t<GRBuffV>;
auto globalReadOffsetV
= GRBuffVMap1d::fromMatrixCoord(make_coord2d(0u, get<1>(macroTileCoord)), 1u)
+ sv_iter * MACRO_TILE_Y * ldv;
auto kStepOffsetV = GRBuffVMap1d::fromMatrixCoord(make_coord2d(ROCWMMA_K, 0u), ldv);
GRBuffV grBuffV;
using LWBuffVShape = GetIOShape_t<LWBuffV>;
using LWBuffVMap1d = GetDataLayout_t<LWBuffV>;
auto ldsWriteOffsetV = MACRO_TILE_X * MACRO_TILE_Y;
auto ldsReadOffsetS = get<0>(localWarpOffset) * ldsld_new;
auto ldsReadOffsetV = ldsWriteOffsetV
+ LWBuffVMap1d::fromMatrixCoord(
make_coord2d(get<1>(localWarpOffset), 0u), ldsld_new);
for(uint32_t currentK = 0; currentK < MACRO_TILE_Y; currentK += ROCWMMA_K)
{
globalReadCoopV(grBuffV, v + globalReadOffsetV, ldv);
globalReadOffsetV += kStepOffsetV;
localWriteCoopV(ldsPtr + ldsWriteOffsetV, grBuffV, ldsld_new);
synchronize_workgroup();
//do gemm
MfmaFragS fragsS[BLOCKS_X];
MfmaFragV fragsV[BLOCKS_Y];
// Local read mfma frags from first LDS buffer
localReadS(fragsS, ldsPtr + ldsReadOffsetS, ldsld_new);
ldsReadOffsetS += ROCWMMA_K;
localReadV(fragsV, ldsPtr + ldsReadOffsetV, ldsld_new);
// accum(S * V)
svgemm(fragsOut[sv_iter], fragsS, fragsV, fragsOut[sv_iter]);
// Make sure that all waves have finished reading / writing to lds for currentK.
synchronize_workgroup();
}
}
}
for(int i = 0; i < SV_ITERS; i++)
{
auto Out_macroTileCoord = make_coord2d(blockIdx.x, i) * macroTileSize;
auto Out_warpTileCoord = Out_macroTileCoord + localWarpOffset;
globalWriteD(
d + MfmaFragDMap1d::fromMatrixCoord(Out_warpTileCoord, ldd), fragsOut[i], ldd);
}
}
}
// Simple CPU GEMM implementation, no alpha/bias processing, only do d=a*b
template <typename InputT,
typename OutputT,
typename ComputeT,
typename LayoutA,
typename LayoutB,
typename LayoutC,
typename LayoutD = LayoutC>
ROCWMMA_HOST void gemm_cpu_simple(uint32_t m,
uint32_t n,
uint32_t k,
InputT const* a,
InputT const* b,
OutputT* d,
uint32_t lda,
uint32_t ldb,
uint32_t ldd)
{
auto rowMjr = [](uint32_t row, uint32_t col, uint32_t ld) { return row * ld + col; };
auto colMjr = [](uint32_t row, uint32_t col, uint32_t ld) { return col * ld + row; };
auto aIndex = std::is_same<LayoutA, rocwmma::row_major>::value ? rowMjr : colMjr;
auto bIndex = std::is_same<LayoutB, rocwmma::row_major>::value ? rowMjr : colMjr;
auto dIndex = std::is_same<LayoutD, rocwmma::row_major>::value ? rowMjr : colMjr;
#pragma omp parallel for
for(int i = 0; i < m; ++i)
{
#pragma omp parallel for
for(int j = 0; j < n; ++j)
{
ComputeT accum = static_cast<ComputeT>(0);
for(int h = 0; h < k; ++h)
{
accum += static_cast<ComputeT>(a[aIndex(i, h, lda)])
* static_cast<ComputeT>(b[bIndex(h, j, ldb)]);
}
d[dIndex(i, j, ldd)] = accum;
}
}
}
ROCWMMA_HOST void gemm_test(uint32_t m, uint32_t n, uint32_t k, ComputeT alpha, ComputeT beta)
{
// Runtime checks for host parameters
uint32_t hTBLOCK_X = gfx9Params::TBLOCK_X;
uint32_t hTBLOCK_Y = gfx9Params::TBLOCK_Y;
uint32_t hBLOCKS_X = gfx9Params::BLOCKS_X;
uint32_t hBLOCKS_Y = gfx9Params::BLOCKS_Y;
uint32_t hROCWMMA_M = gfx9Params::ROCWMMA_M;
uint32_t hROCWMMA_N = gfx9Params::ROCWMMA_N;
uint32_t hROCWMMA_K = gfx9Params::ROCWMMA_K;
uint32_t hWARP_TILE_X = hBLOCKS_X * hROCWMMA_M;
uint32_t hWARP_TILE_Y = hBLOCKS_Y * hROCWMMA_N;
// Runtime warp calculation (host code needs to query warpsize dynamically)
auto warpSize = getWarpSize();
auto macroTileSize
= rocwmma::make_coord2d(hTBLOCK_X / warpSize * hWARP_TILE_X, hTBLOCK_Y * hWARP_TILE_Y);
auto MACRO_TILE_Y = get<1>(macroTileSize);
const uint32_t sv_iterations = (k + MACRO_TILE_Y - 1) / MACRO_TILE_Y;
// Device check for supported block and wave sizes
if((isGfx11() || isGfx12()) && (hROCWMMA_M != 16 || hROCWMMA_N != 16))
{
std::cout << "Unsupported block size!\n";
return;
}
if(isGfx9() && (hROCWMMA_M != hROCWMMA_N) || (hROCWMMA_M != 16 && hROCWMMA_M != 32))
{
std::cout << "Unsupported block size!\n";
return;
}
if((isGfx11() || isGfx12()) && getWarpSize() != Constants::AMDGCN_WAVE_SIZE_32)
{
std::cout << "Unsupported wave size!\n";
return;
}
if(isGfx9() && getWarpSize() != Constants::AMDGCN_WAVE_SIZE_64)
{
std::cout << "Unsupported wave size!\n";
return;
}
// Bounds check
if((m < get<0>(macroTileSize) || n < get<1>(macroTileSize) || k < hROCWMMA_K)
|| (m % hROCWMMA_M || n % hROCWMMA_N || k % hROCWMMA_K))
{
std::cout << "Unsupported matrix size!\n";
return;
}
// Layouts leading dims
int lda = std::is_same_v<DataLayoutA, row_major> ? k : m;
int ldb = std::is_same_v<DataLayoutB, row_major> ? n : k;
int ldc = std::is_same_v<DataLayoutC, row_major> ? n : m;
int ldd = std::is_same_v<DataLayoutC, row_major> ? k : m;
int ldv = std::is_same_v<DataLayoutV, row_major> ? k : n;
std::cout << "Initializing host data..." << std::endl;
// Initialize input matrices
std::vector<InputT> matrixA(m * k);
std::vector<InputT> matrixB(k * n);
std::vector<OutputT> matrixC(m * n);
std::vector<InputTV> matrixV(k * n);
// Fill outputs with NaN to catch contamination
std::vector<OutputT> matrixD(m * k, std::numeric_limits<OutputT>::signaling_NaN());
fillRand(matrixA.data(), m, k);
fillRand(matrixB.data(), k, n);
fillRand(matrixC.data(), m, n);
fillRand(matrixV.data(), n, k);
std::cout << "Initializing device data..." << std::endl;
// Allocate and copy device memory
InputT* d_a;
InputT* d_b;
InputTV* d_v;
OutputT* d_c;
OutputT* d_d;
const size_t bytesA = matrixA.size() * sizeof(InputT);
const size_t bytesB = matrixB.size() * sizeof(InputT);
const size_t bytesV = matrixV.size() * sizeof(InputTV);
const size_t bytesC = matrixC.size() * sizeof(OutputT);
const size_t bytesD = matrixD.size() * sizeof(OutputT);
CHECK_HIP_ERROR(hipMalloc(&d_a, bytesA));
CHECK_HIP_ERROR(hipMalloc(&d_b, bytesB));
CHECK_HIP_ERROR(hipMalloc(&d_v, bytesV));
CHECK_HIP_ERROR(hipMalloc(&d_c, bytesC));
CHECK_HIP_ERROR(hipMalloc(&d_d, bytesD));
CHECK_HIP_ERROR(hipMemcpy(d_a, matrixA.data(), bytesA, hipMemcpyHostToDevice));
CHECK_HIP_ERROR(hipMemcpy(d_b, matrixB.data(), bytesB, hipMemcpyHostToDevice));
CHECK_HIP_ERROR(hipMemcpy(d_v, matrixV.data(), bytesV, hipMemcpyHostToDevice));
CHECK_HIP_ERROR(hipMemcpy(d_c, matrixC.data(), bytesC, hipMemcpyHostToDevice));
CHECK_HIP_ERROR(hipMemcpy(d_d, matrixD.data(), bytesD, hipMemcpyHostToDevice));
auto blockDim = dim3(hTBLOCK_X, hTBLOCK_Y);
auto gridDim = dim3(rocwmma::ceil_div(m, get<0>(macroTileSize)));
std::cout << "Launching GEMM kernel..." << std::endl;
std::cout << "gridDim (" << gridDim.x << " " << gridDim.y << ")" << " blockdim (" << blockDim.x
<< " " << blockDim.y << ")" << std::endl;
// Uses 2 lds blocks for prefetch loop (A and B)
int ldsusage
= max(2u * sizeof(InputT) * (get<0>(macroTileSize) + get<1>(macroTileSize)) * hROCWMMA_K,
2u * sizeof(ComputeT) * get<0>(macroTileSize) * get<1>(macroTileSize));
auto rocwmmaKernel = [&]() {
// Instantiate kernel with compile-time SV_ITERS to avoid dynamic alloca/VLA in device code.
switch(sv_iterations)
{
#define LAUNCH(SV) \
case SV: \
{ \
hipEvent_t _start = nullptr, _stop = nullptr; \
hipExtLaunchKernelGGL(gemm_rocwmma_d<SV>, \
gridDim, \
blockDim, \
ldsusage, \
0, \
_start, \
_stop, \
0u, \
m, \
n, \
k, \
d_a, \
d_b, \
d_v, \
d_c, \
d_d, \
lda, \
ldb, \
ldv, \
ldd); \
} \
break;
LAUNCH(1);
LAUNCH(2);
LAUNCH(3);
LAUNCH(4);
LAUNCH(5);
LAUNCH(6);
LAUNCH(7);
LAUNCH(8);
LAUNCH(9);
LAUNCH(10);
LAUNCH(11);
LAUNCH(12);
LAUNCH(13);
LAUNCH(14);
LAUNCH(15);
LAUNCH(16);
LAUNCH(17);
LAUNCH(18);
LAUNCH(19);
LAUNCH(20);
LAUNCH(21);
LAUNCH(22);
LAUNCH(23);
LAUNCH(24);
LAUNCH(25);
LAUNCH(26);
LAUNCH(27);
LAUNCH(28);
LAUNCH(29);
LAUNCH(30);
LAUNCH(31);
LAUNCH(32);
#undef LAUNCH
default:
printf(
"Error: sv_iterations=%u exceeds supported max (32). Increase cases or reduce k.\n",
sv_iterations);
return;
}
};
constexpr uint32_t warmups = 0u;
constexpr uint32_t recordRuns = 1u;
// Warm-up runs, not recorded
for(uint32_t i = 0; i < warmups; ++i)
{
rocwmmaKernel();
}
// Actual recorded runs
hipEvent_t startEvent, stopEvent;
CHECK_HIP_ERROR(hipEventCreate(&startEvent));
CHECK_HIP_ERROR(hipEventCreate(&stopEvent));
CHECK_HIP_ERROR(hipEventRecord(startEvent));
for(uint32_t i = 0; i < recordRuns; ++i)
{
rocwmmaKernel();
CHECK_HIP_ERROR(hipGetLastError());
CHECK_HIP_ERROR(hipStreamSynchronize(0));
}
CHECK_HIP_ERROR(hipEventRecord(stopEvent));
CHECK_HIP_ERROR(hipEventSynchronize(stopEvent));
auto elapsedTimeMs = 0.0f;
CHECK_HIP_ERROR(hipEventElapsedTime(&elapsedTimeMs, startEvent, stopEvent));
auto gFlops = calculateGFlops(m, n, k);
auto tFlopsPerSec
= calculateTFlopsPerSec(m, n, k, static_cast<double>(elapsedTimeMs), recordRuns);
CHECK_HIP_ERROR(hipEventDestroy(startEvent));
CHECK_HIP_ERROR(hipEventDestroy(stopEvent));
// Echo performance
std::cout << "TBlockX, TBlockY, " << "BlocksX, BlocksY, " << "BlkM, BlkN, BlkK, "
<< "MatM, MatN, MatK, " << "alpha, lda, ldb, " << "beta, ldc, ldd, "
<< "elapsedMs, Problem Size(GFlops), TFlops/s" << "," << "sizeof(InputT)"
<< std::endl;
std::cout << hTBLOCK_X << ", " << hTBLOCK_Y << ", " << hBLOCKS_X << ", " << hBLOCKS_Y << ", "
<< hROCWMMA_M << ", " << hROCWMMA_N << ", " << hROCWMMA_K << ", " << m << ", " << n
<< ", " << k << ", " << alpha << ", " << lda << ", " << ldb << ", " << beta << ", "
<< ldc << ", " << ldd << ", " << elapsedTimeMs << ", " << gFlops << ", "
<< tFlopsPerSec << "," << sizeof(InputT) << std::endl;
#if !NDEBUG
std::cout << "Validating result with reference..." << std::endl;
if((uint64_t)m * (uint64_t)n * (uint64_t)k > (2048ull * 2048ull * 2048ull))
{
std::cout << "Please wait. Large sizes can take a while!" << std::endl;
}
// Bring kernel result back to host
CHECK_HIP_ERROR(hipMemcpy(matrixD.data(), d_d, bytesD, hipMemcpyDeviceToHost));
// Setup and run reference computation
std::vector<OutputT> matrixT_ref(m * n, std::numeric_limits<OutputT>::signaling_NaN());
std::vector<InputT> matrixTinput_ref(m * n, std::numeric_limits<OutputT>::signaling_NaN());
std::vector<OutputT> matrixD_ref(m * k, std::numeric_limits<OutputT>::signaling_NaN());
gemm_cpu_simple<InputT, OutputT, ComputeT, DataLayoutA, DataLayoutB, DataLayoutC>(
m, n, k, matrixA.data(), matrixB.data(), matrixT_ref.data(), lda, ldb, ldc);
// Convert int32 to int8
for(int i = 0; i < m; i++)
{
for(int j = 0; j < n; j++)
{
int32_t value = (matrixT_ref[i * n + j]);
if(value > INT8_MAX)
{
matrixTinput_ref[i * n + j] = INT8_MAX;
}
else if(value < INT8_MIN)
{
matrixTinput_ref[i * n + j] = INT8_MIN;
}
else
{
matrixTinput_ref[i * n + j] = static_cast<InputT>(value);
}
}
}
gemm_cpu_simple<InputT, OutputT, ComputeT, DataLayoutA, DataLayoutB, DataLayoutC>(
m, k, n, matrixTinput_ref.data(), matrixV.data(), matrixD_ref.data(), ldc, ldv, ldd);
auto res = compareEqual(matrixD.data(), matrixD_ref.data(), m * k);
if(std::get<0>(res) == false)
{
std::cout << "FAILED\n";
}
else
{
std::cout << "PASSED\n";
}
std::cout << "Max relative error: " << std::get<1>(res) << std::endl;
#endif // !NDEBUG
// Release device memory
CHECK_HIP_ERROR(hipFree(d_a));
CHECK_HIP_ERROR(hipFree(d_b));
CHECK_HIP_ERROR(hipFree(d_c));
CHECK_HIP_ERROR(hipFree(d_v));
CHECK_HIP_ERROR(hipFree(d_d));
std::cout << "Finished!" << std::endl;
}
int main()
{
if(isGfx9())
gemm_test(8192, 8192, 128, 1, 0);
else
std::cout << "This sample Not test on gfx11/gfx12 yet!" << std::endl;
return 0;
}