Instructions to use replicate/deep-gemm with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Kernels
How to use replicate/deep-gemm with Kernels:
# !pip install kernels from kernels import get_kernel # a version (or an explicit revision) is required; see the "Files and versions" tab for the available ones kernel = get_kernel("replicate/deep-gemm", version=1) - Notebooks
- Google Colab
- Kaggle
Build uploaded using `kernels` (batch 4/10).
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/kernel/sm100_mla_tile_scheduler.hpp +160 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_bwd_reference.hpp +410 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_fwd_gen_reference.hpp +185 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_fwd_reference.hpp +195 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_mla_reference.hpp +201 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/reference_abs_error.hpp +282 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/86_blackwell_mixed_dtype_gemm/mixed_dtype_helper.cuh +269 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/87_blackwell_geforce_gemm_blockwise/utils.h +83 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_bwd_tma_warpspecialized.hpp +863 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_load.hpp +140 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_softmax.hpp +305 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_tma.hpp +526 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_tma_warpspecialized.hpp +560 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_common.hpp +245 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_epilogue.hpp +156 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_epilogue_bwd.hpp +157 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_fusion.hpp +283 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/device/device_universal.hpp +278 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/device/fmha_device_bwd.hpp +299 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_builder.hpp +158 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_bwd_convert.hpp +143 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_bwd_sum_OdO.hpp +134 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_tma.hpp +226 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_tma_warpspecialized.hpp +422 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_options.hpp +83 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_tile_scheduler.hpp +204 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/reference/fmha_bwd_reference.hpp +357 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/reference/fmha_reference.hpp +156 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/reference/reference_abs_error.hpp +129 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/common/dist_gemm_helpers.h +164 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/common/gather_tensor.hpp +215 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/common/helper.h +108 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/cute/tutorial/blackwell/example_utils.hpp +105 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/call_bypass_dlpack.py +166 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/call_from_jit.py +259 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/dynamic_smem_size.py +114 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/elementwise_add.py +403 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/elementwise_apply.py +393 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/flash_attention_v2.py +1334 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/sgemm.py +866 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/smem_allocator.py +200 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/tensorop_gemm.py +1012 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_blockscaled_gemm_persistent.py +2466 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_gemm.py +1890 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_gemm_persistent.py +2193 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_gemm_software_pipeline.py +1831 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/fmha.py +0 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/grouped_gemm.py +2445 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/mamba2_ssd/mamba2_ssd.py +0 -0
- build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/mamba2_ssd/mamba2_ssd_reference.py +397 -0
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/kernel/sm100_mla_tile_scheduler.hpp
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/fast_math.h"
|
| 36 |
+
#include "cutlass/kernel_hardware_info.h"
|
| 37 |
+
|
| 38 |
+
namespace cutlass::fmha::kernel {
|
| 39 |
+
|
| 40 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 41 |
+
|
| 42 |
+
struct Sm100MlaIndividualTileScheduler {
|
| 43 |
+
|
| 44 |
+
struct Params {
|
| 45 |
+
dim3 grid;
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
bool valid_ = true;
|
| 49 |
+
|
| 50 |
+
CUTLASS_DEVICE
|
| 51 |
+
Sm100MlaIndividualTileScheduler(Params const&) {}
|
| 52 |
+
|
| 53 |
+
template<class ProblemShape, class ClusterShape>
|
| 54 |
+
static Params to_underlying_arguments(
|
| 55 |
+
ProblemShape const& problem_shape, KernelHardwareInfo hw_info,
|
| 56 |
+
ClusterShape const& cluster_shape, int const& split_kv) {
|
| 57 |
+
using namespace cute;
|
| 58 |
+
dim3 grid(get<0>(cluster_shape), get<3>(problem_shape) /* Batch */, split_kv /*Maximum Split KV*/);
|
| 59 |
+
return Params{ grid };
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 63 |
+
return params.grid;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
CUTLASS_DEVICE
|
| 67 |
+
bool is_valid() {
|
| 68 |
+
return valid_;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
CUTLASS_DEVICE
|
| 72 |
+
auto get_block_coord() {
|
| 73 |
+
using namespace cute;
|
| 74 |
+
return make_coord(blockIdx.x, _0{}, blockIdx.y, blockIdx.z);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
CUTLASS_DEVICE
|
| 78 |
+
Sm100MlaIndividualTileScheduler& operator++() {
|
| 79 |
+
valid_ = false;
|
| 80 |
+
return *this;
|
| 81 |
+
}
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 85 |
+
|
| 86 |
+
struct Sm100MlaPersistentTileScheduler {
|
| 87 |
+
|
| 88 |
+
struct Params {
|
| 89 |
+
int num_blocks;
|
| 90 |
+
FastDivmod divmod_m_block;
|
| 91 |
+
FastDivmod divmod_b;
|
| 92 |
+
FastDivmod divmod_split_kv;
|
| 93 |
+
KernelHardwareInfo hw_info;
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
int block_idx = 0;
|
| 97 |
+
Params params;
|
| 98 |
+
|
| 99 |
+
CUTLASS_DEVICE
|
| 100 |
+
Sm100MlaPersistentTileScheduler(Params const& params) : block_idx(blockIdx.x), params(params) {}
|
| 101 |
+
|
| 102 |
+
template<class ProblemShape, class ClusterShape>
|
| 103 |
+
static Params to_underlying_arguments(
|
| 104 |
+
ProblemShape const& problem_shape, KernelHardwareInfo hw_info,
|
| 105 |
+
ClusterShape const& cluster_shape, int const& split_kv) {
|
| 106 |
+
using namespace cute;
|
| 107 |
+
// Get SM count if needed, otherwise use user supplied SM count
|
| 108 |
+
int sm_count = hw_info.sm_count;
|
| 109 |
+
if (sm_count <= 1 || sm_count % size<0>(cluster_shape) != 0) {
|
| 110 |
+
CUTLASS_TRACE_HOST(" WARNING: Arguments do not include a valid SM count.\n"
|
| 111 |
+
" For optimal performance, populate the arguments KernelHardwareInfo struct with the SM count.");
|
| 112 |
+
sm_count = KernelHardwareInfo::query_device_multiprocessor_count(hw_info.device_id);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
CUTLASS_TRACE_HOST("to_underlying_arguments(): Setting persistent grid SM count to " << sm_count);
|
| 116 |
+
hw_info.sm_count = sm_count;
|
| 117 |
+
|
| 118 |
+
int num_m_blocks = size<0>(cluster_shape);
|
| 119 |
+
int num_blocks = num_m_blocks * get<3>(problem_shape) /* Batch */;
|
| 120 |
+
num_blocks *= split_kv; /* Maximum Split KV*/
|
| 121 |
+
|
| 122 |
+
return Params {
|
| 123 |
+
num_blocks,
|
| 124 |
+
{ num_m_blocks}, { get<3>(problem_shape) }, {split_kv},
|
| 125 |
+
hw_info
|
| 126 |
+
};
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 130 |
+
dim3 grid(std::min(params.num_blocks, params.hw_info.sm_count), 1, 1);
|
| 131 |
+
return grid;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
CUTLASS_DEVICE
|
| 135 |
+
bool is_valid() {
|
| 136 |
+
return block_idx < params.num_blocks;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
CUTLASS_DEVICE
|
| 140 |
+
auto get_block_coord() {
|
| 141 |
+
using namespace cute;
|
| 142 |
+
int block_decode = block_idx;
|
| 143 |
+
int m_block, bidb, n_split_kv;
|
| 144 |
+
params.divmod_m_block(block_decode, m_block, block_decode);
|
| 145 |
+
params.divmod_split_kv(block_decode, n_split_kv, block_decode);
|
| 146 |
+
params.divmod_b(block_decode, bidb, block_decode);
|
| 147 |
+
return make_coord(m_block, _0{}, bidb, n_split_kv);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
CUTLASS_DEVICE
|
| 151 |
+
Sm100MlaPersistentTileScheduler& operator++() {
|
| 152 |
+
block_idx += gridDim.x;
|
| 153 |
+
return *this;
|
| 154 |
+
}
|
| 155 |
+
};
|
| 156 |
+
|
| 157 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 158 |
+
|
| 159 |
+
} // namespace cutlass::fmha::kernel
|
| 160 |
+
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_bwd_reference.hpp
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2025 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
#pragma once
|
| 34 |
+
|
| 35 |
+
#include "cute/tensor.hpp"
|
| 36 |
+
#include "collective/fmha_fusion.hpp"
|
| 37 |
+
|
| 38 |
+
using namespace cutlass::fmha::collective;
|
| 39 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 40 |
+
|
| 41 |
+
template<
|
| 42 |
+
class ProblemShape,
|
| 43 |
+
class TensorQ, class TensorK, class TensorV,
|
| 44 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 45 |
+
class TensorDQ, /* class TensorDK, class TensorDV, */
|
| 46 |
+
class Fusion
|
| 47 |
+
>
|
| 48 |
+
void __global__ fmha_bwd_reference_dQ_kernel(
|
| 49 |
+
ProblemShape problem_shape_in,
|
| 50 |
+
TensorQ mQ_in, TensorK mK_in, TensorV mV_in,
|
| 51 |
+
TensorO mO_in, TensorLSE mLSE_in, TensorDO mDO_in,
|
| 52 |
+
TensorDQ mDQ_in, /* TensorDK mDK, TensorDV mDV, */
|
| 53 |
+
Fusion fusion) {
|
| 54 |
+
|
| 55 |
+
using namespace cute;
|
| 56 |
+
using namespace cutlass::fmha::collective;
|
| 57 |
+
|
| 58 |
+
using Element = typename TensorO::value_type;
|
| 59 |
+
using ElementAcc = typename TensorLSE::value_type;
|
| 60 |
+
|
| 61 |
+
extern __shared__ char mS_mem[];
|
| 62 |
+
Element* mS = reinterpret_cast<Element*>(mS_mem);
|
| 63 |
+
|
| 64 |
+
ElementAcc softmax_scale = 1.0f / sqrtf(size<2>(problem_shape_in));
|
| 65 |
+
|
| 66 |
+
for (int idx_L = blockIdx.y; idx_L < size<4>(problem_shape_in); idx_L += gridDim.y) {
|
| 67 |
+
auto [problem_shape, offset] = apply_variable_length_offset(
|
| 68 |
+
problem_shape_in,
|
| 69 |
+
make_coord(_0{}, _0{}, _0{}, _0{},idx2crd(idx_L, get<4>(problem_shape_in)))
|
| 70 |
+
);
|
| 71 |
+
// problem_shape = problem_shape_in;
|
| 72 |
+
// offset = repeat_like(problem_shape_in, _0{});
|
| 73 |
+
auto mQ = domain_offset(select<0,2,4>(offset), mQ_in);
|
| 74 |
+
auto mK = domain_offset(select<1,2,4>(offset), mK_in);
|
| 75 |
+
auto mV = domain_offset(select<1,3,4>(offset), mV_in);
|
| 76 |
+
auto mO = domain_offset(select<0,3,4>(offset), mO_in);
|
| 77 |
+
auto mLSE = domain_offset(select<0,4>(offset), mLSE_in);
|
| 78 |
+
auto mDO = domain_offset(select<0,3,4>(offset), mDO_in);
|
| 79 |
+
auto mDQ = domain_offset(select<0,2,4>(offset), mDQ_in);
|
| 80 |
+
for (int idx_Q = blockIdx.x; idx_Q < size<0>(problem_shape); idx_Q += gridDim.x) {
|
| 81 |
+
for (int idx_K = threadIdx.x; idx_K < size<1>(problem_shape); idx_K += blockDim.x) {
|
| 82 |
+
ElementAcc acc_qk = 0;
|
| 83 |
+
ElementAcc acc_dov = 0;
|
| 84 |
+
ElementAcc acc_doo = 0;
|
| 85 |
+
for (int idx_D0 = 0; idx_D0 < size<2>(problem_shape); idx_D0++) {
|
| 86 |
+
acc_qk += mQ(idx_Q, idx_D0, idx_L) * mK(idx_K, idx_D0, idx_L);
|
| 87 |
+
// acc_dov += mDO(idx_Q, idx_D0, idx_L) * mV(idx_K, idx_D0, idx_L);
|
| 88 |
+
// acc_doo += mDO(idx_Q, idx_D0, idx_L) * mO(idx_Q, idx_D0, idx_L);
|
| 89 |
+
} // for idx_D0
|
| 90 |
+
|
| 91 |
+
for (int idx_D1 = 0; idx_D1 < size<3>(problem_shape); idx_D1++) {
|
| 92 |
+
acc_dov += mDO(idx_Q, idx_D1, idx_L) * mV(idx_K, idx_D1, idx_L);
|
| 93 |
+
acc_doo += mDO(idx_Q, idx_D1, idx_L) * mO(idx_Q, idx_D1, idx_L);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 97 |
+
auto frag = make_tensor<ElementAcc>(Shape<_1, _1>{});
|
| 98 |
+
frag(0) = acc_qk;
|
| 99 |
+
fusion.apply_mask(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 100 |
+
acc_qk = frag(0);
|
| 101 |
+
|
| 102 |
+
mS[idx_K] = static_cast<Element>(expf(softmax_scale * acc_qk - mLSE(idx_Q, idx_L)) * softmax_scale * (acc_dov - acc_doo));
|
| 103 |
+
} // for idx_K
|
| 104 |
+
|
| 105 |
+
__syncthreads();
|
| 106 |
+
|
| 107 |
+
for (int idx_D = threadIdx.x; idx_D < size<2>(problem_shape); idx_D += blockDim.x) {
|
| 108 |
+
ElementAcc acc = 0;
|
| 109 |
+
for (int idx_K = 0; idx_K < size<1>(problem_shape); idx_K++) {
|
| 110 |
+
ElementAcc rK = mK(idx_K, idx_D, idx_L);
|
| 111 |
+
ElementAcc rDS = mS[idx_K];
|
| 112 |
+
acc += rDS * rK;
|
| 113 |
+
}
|
| 114 |
+
mDQ(idx_Q, idx_D, idx_L) = static_cast<typename TensorDQ::value_type>(acc);
|
| 115 |
+
} // for idx_D
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 121 |
+
|
| 122 |
+
template<
|
| 123 |
+
class ProblemShape,
|
| 124 |
+
class TensorQ, class TensorK, class TensorV,
|
| 125 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 126 |
+
/* class TensorDQ, */ class TensorDK, /* class TensorDV, */
|
| 127 |
+
class Fusion
|
| 128 |
+
>
|
| 129 |
+
void __global__ fmha_bwd_reference_dK_kernel(
|
| 130 |
+
ProblemShape problem_shape_in,
|
| 131 |
+
TensorQ mQ_in, TensorK mK_in, TensorV mV_in,
|
| 132 |
+
TensorO mO_in, TensorLSE mLSE_in, TensorDO mDO_in,
|
| 133 |
+
/* TensorDQ mDQ_in, */ TensorDK mDK_in, /* TensorDV mDV_in, */
|
| 134 |
+
Fusion fusion) {
|
| 135 |
+
|
| 136 |
+
using namespace cute;
|
| 137 |
+
using namespace cutlass::fmha::collective;
|
| 138 |
+
|
| 139 |
+
using Element = typename TensorO::value_type;
|
| 140 |
+
using ElementAcc = typename TensorLSE::value_type;
|
| 141 |
+
|
| 142 |
+
extern __shared__ char mS_mem[];
|
| 143 |
+
Element* mS = reinterpret_cast<Element*>(mS_mem);
|
| 144 |
+
|
| 145 |
+
ElementAcc softmax_scale = 1.0f / sqrtf(size<2>(problem_shape_in));
|
| 146 |
+
|
| 147 |
+
auto [H, B] = get<4>(problem_shape_in);
|
| 148 |
+
auto [H_R, H_K] = H;
|
| 149 |
+
|
| 150 |
+
for (int idx_HB = blockIdx.y; idx_HB < H_K * B; idx_HB += gridDim.y) {
|
| 151 |
+
auto [idx_H_K, idx_B] = idx2crd(idx_HB, make_shape(H_K, B));
|
| 152 |
+
auto [problem_shape, offset] = apply_variable_length_offset(
|
| 153 |
+
problem_shape_in,
|
| 154 |
+
make_coord(_0{}, _0{}, _0{}, _0{}, make_coord(make_coord(_0{}, idx_H_K), idx_B))
|
| 155 |
+
);
|
| 156 |
+
auto [Q, K, D, D_VO, HB] = problem_shape;
|
| 157 |
+
auto [offset_Q, offset_K, offset_D, offset_D_VO, offset_HB] = offset;
|
| 158 |
+
|
| 159 |
+
auto mQ = domain_offset(make_coord(offset_Q, offset_D, offset_HB), mQ_in);
|
| 160 |
+
auto mK = domain_offset(make_coord(offset_K, offset_D, offset_HB), mK_in);
|
| 161 |
+
auto mV = domain_offset(make_coord(offset_K, offset_D_VO, offset_HB), mV_in);
|
| 162 |
+
auto mO = domain_offset(make_coord(offset_Q, offset_D_VO, offset_HB), mO_in);
|
| 163 |
+
auto mLSE = domain_offset(make_coord(offset_Q, offset_HB), mLSE_in);
|
| 164 |
+
auto mDO = domain_offset(make_coord(offset_Q, offset_D_VO, offset_HB), mDO_in);
|
| 165 |
+
auto mDK = domain_offset(make_coord(offset_K, offset_D, offset_HB), mDK_in);
|
| 166 |
+
|
| 167 |
+
for (int idx_K = blockIdx.x; idx_K < K; idx_K += gridDim.x) {
|
| 168 |
+
ElementAcc acc_dk = 0;
|
| 169 |
+
for (int idx_H_R = 0; idx_H_R < H_R; idx_H_R++) {
|
| 170 |
+
auto coord_HB = make_coord(make_coord(idx_H_R, idx_H_K), idx_B);
|
| 171 |
+
for (int idx_Q = threadIdx.x; idx_Q < Q; idx_Q += blockDim.x) {
|
| 172 |
+
ElementAcc acc_qk = 0;
|
| 173 |
+
ElementAcc acc_dov = 0;
|
| 174 |
+
ElementAcc acc_doo = 0;
|
| 175 |
+
for (int idx_D0 = 0; idx_D0 < D; idx_D0++) {
|
| 176 |
+
ElementAcc rQ = mQ(idx_Q, idx_D0, coord_HB);
|
| 177 |
+
ElementAcc rK = mK(idx_K, idx_D0, coord_HB);
|
| 178 |
+
acc_qk += rQ * rK;
|
| 179 |
+
} // for idx_D0
|
| 180 |
+
|
| 181 |
+
for (int idx_D1 = 0; idx_D1 < D_VO; idx_D1++) {
|
| 182 |
+
ElementAcc rDO = mDO(idx_Q, idx_D1, coord_HB);
|
| 183 |
+
ElementAcc rV = mV(idx_K, idx_D1, coord_HB);
|
| 184 |
+
ElementAcc rO = mO(idx_Q, idx_D1, coord_HB);
|
| 185 |
+
acc_dov += rDO * rV;
|
| 186 |
+
acc_doo += rDO * rO ;
|
| 187 |
+
}
|
| 188 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 189 |
+
auto frag = make_tensor<ElementAcc>(Shape<_1, _1>{});
|
| 190 |
+
frag(0) = acc_qk;
|
| 191 |
+
fusion.apply_mask(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 192 |
+
acc_qk = frag(0);
|
| 193 |
+
|
| 194 |
+
mS[idx_Q] = static_cast<Element>(expf(softmax_scale * acc_qk - mLSE(idx_Q, coord_HB)) * softmax_scale * (acc_dov - acc_doo));
|
| 195 |
+
} // for idx_Q
|
| 196 |
+
|
| 197 |
+
__syncthreads();
|
| 198 |
+
|
| 199 |
+
int idx_D = threadIdx.x;
|
| 200 |
+
if (idx_D < D) {
|
| 201 |
+
for (int idx_Q = 0; idx_Q < Q; idx_Q++) {
|
| 202 |
+
ElementAcc rQ = mQ(idx_Q, idx_D, coord_HB);
|
| 203 |
+
ElementAcc rDS = mS[idx_Q];
|
| 204 |
+
acc_dk += rDS * rQ;
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
__syncthreads();
|
| 208 |
+
} // for idx_H_R
|
| 209 |
+
|
| 210 |
+
int idx_D = threadIdx.x;
|
| 211 |
+
if (idx_D < D) {
|
| 212 |
+
auto coord_HB = make_coord(make_coord(0, idx_H_K), idx_B);
|
| 213 |
+
mDK(idx_K, idx_D, coord_HB) = static_cast<typename TensorDK::value_type>(acc_dk);
|
| 214 |
+
}
|
| 215 |
+
} // for idx_K
|
| 216 |
+
} // for idx_HB
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 220 |
+
|
| 221 |
+
template<
|
| 222 |
+
class ProblemShape,
|
| 223 |
+
class TensorQ, class TensorK, class TensorV,
|
| 224 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 225 |
+
/* class TensorDQ, class TensorDK, */ class TensorDV,
|
| 226 |
+
class Fusion
|
| 227 |
+
>
|
| 228 |
+
void __global__ fmha_bwd_reference_dV_kernel(
|
| 229 |
+
ProblemShape problem_shape_in,
|
| 230 |
+
TensorQ mQ_in, TensorK mK_in, TensorV mV_in,
|
| 231 |
+
TensorO mO_in, TensorLSE mLSE_in, TensorDO mDO_in,
|
| 232 |
+
/* TensorDQ mDQ_in, TensorDK mDK_in, */ TensorDV mDV_in,
|
| 233 |
+
Fusion fusion) {
|
| 234 |
+
|
| 235 |
+
using namespace cute;
|
| 236 |
+
using namespace cutlass::fmha::collective;
|
| 237 |
+
|
| 238 |
+
using Element = typename TensorO::value_type;
|
| 239 |
+
using ElementAcc = typename TensorLSE::value_type;
|
| 240 |
+
|
| 241 |
+
extern __shared__ char mS_mem[];
|
| 242 |
+
Element* mS = reinterpret_cast<Element*>(mS_mem);
|
| 243 |
+
|
| 244 |
+
ElementAcc softmax_scale = 1.0f / sqrtf(size<2>(problem_shape_in));
|
| 245 |
+
|
| 246 |
+
auto [H, B] = get<4>(problem_shape_in);
|
| 247 |
+
auto [H_R, H_K] = H;
|
| 248 |
+
|
| 249 |
+
for (int idx_HB = blockIdx.y; idx_HB < H_K * B; idx_HB += gridDim.y) {
|
| 250 |
+
auto [idx_H_K, idx_B] = idx2crd(idx_HB, make_shape(H_K, B));
|
| 251 |
+
auto [problem_shape, offset] = apply_variable_length_offset(
|
| 252 |
+
problem_shape_in,
|
| 253 |
+
make_coord(_0{}, _0{}, _0{}, _0{}, make_coord(make_coord(_0{}, idx_H_K), idx_B))
|
| 254 |
+
);
|
| 255 |
+
auto [Q, K, D, D_VO, HB] = problem_shape;
|
| 256 |
+
auto [offset_Q, offset_K, offset_D, offset_D_VO, offset_HB] = offset;
|
| 257 |
+
|
| 258 |
+
auto mQ = domain_offset(make_coord(offset_Q, offset_D, offset_HB), mQ_in);
|
| 259 |
+
auto mK = domain_offset(make_coord(offset_K, offset_D, offset_HB), mK_in);
|
| 260 |
+
auto mV = domain_offset(make_coord(offset_K, offset_D_VO, offset_HB), mV_in);
|
| 261 |
+
auto mO = domain_offset(make_coord(offset_Q, offset_D_VO, offset_HB), mO_in);
|
| 262 |
+
auto mLSE = domain_offset(make_coord(offset_Q, offset_HB), mLSE_in);
|
| 263 |
+
auto mDO = domain_offset(make_coord(offset_Q, offset_D_VO, offset_HB), mDO_in);
|
| 264 |
+
auto mDV = domain_offset(make_coord(offset_K, offset_D_VO, offset_HB), mDV_in);
|
| 265 |
+
|
| 266 |
+
for (int idx_K = blockIdx.x; idx_K < K; idx_K += gridDim.x) {
|
| 267 |
+
ElementAcc acc_dv = 0;
|
| 268 |
+
for (int idx_H_R = 0; idx_H_R < H_R; idx_H_R++) {
|
| 269 |
+
auto coord_HB = make_coord(make_coord(idx_H_R, idx_H_K), idx_B);
|
| 270 |
+
for (int idx_Q = threadIdx.x; idx_Q < Q; idx_Q += blockDim.x) {
|
| 271 |
+
ElementAcc acc_qk = 0;
|
| 272 |
+
|
| 273 |
+
for (int idx_D0 = 0; idx_D0 < D; idx_D0++) {
|
| 274 |
+
ElementAcc rQ = mQ(idx_Q, idx_D0, coord_HB);
|
| 275 |
+
ElementAcc rK = mK(idx_K, idx_D0, coord_HB);
|
| 276 |
+
acc_qk += rQ * rK;
|
| 277 |
+
} // for idx_D0
|
| 278 |
+
|
| 279 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 280 |
+
auto frag = make_tensor<ElementAcc>(Shape<_1, _1>{});
|
| 281 |
+
frag(0) = acc_qk;
|
| 282 |
+
fusion.apply_mask(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 283 |
+
acc_qk = frag(0);
|
| 284 |
+
|
| 285 |
+
mS[idx_Q] = static_cast<Element>(expf(softmax_scale * acc_qk - mLSE(idx_Q, coord_HB)));
|
| 286 |
+
} // for idx_Q
|
| 287 |
+
|
| 288 |
+
__syncthreads();
|
| 289 |
+
|
| 290 |
+
int idx_D_VO = threadIdx.x;
|
| 291 |
+
if (idx_D_VO < D_VO) {
|
| 292 |
+
for (int idx_Q = 0; idx_Q < Q; idx_Q++) {
|
| 293 |
+
ElementAcc rDO = mDO(idx_Q, idx_D_VO, coord_HB);
|
| 294 |
+
ElementAcc rP = mS[idx_Q];
|
| 295 |
+
acc_dv += rP * rDO;
|
| 296 |
+
}
|
| 297 |
+
} // for idx_D
|
| 298 |
+
|
| 299 |
+
__syncthreads();
|
| 300 |
+
} // for idx_H_R
|
| 301 |
+
|
| 302 |
+
int idx_D_VO = threadIdx.x;
|
| 303 |
+
if (idx_D_VO < D_VO) {
|
| 304 |
+
auto coord_HB = make_coord(make_coord(0, idx_H_K), idx_B);
|
| 305 |
+
mDV(idx_K, idx_D_VO, coord_HB) = static_cast<typename TensorDV::value_type>(acc_dv);
|
| 306 |
+
}
|
| 307 |
+
} // for idx_K
|
| 308 |
+
} // for idx_L
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 312 |
+
|
| 313 |
+
template<
|
| 314 |
+
class ProblemShape,
|
| 315 |
+
class TensorQ, class TensorK, class TensorV,
|
| 316 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 317 |
+
/**/ class TensorDQ, /** / class TensorDK, / ** / class TensorDV, / **/
|
| 318 |
+
class Fusion
|
| 319 |
+
>
|
| 320 |
+
void fmha_bwd_reference_dQ(
|
| 321 |
+
ProblemShape problem_shape,
|
| 322 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 323 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 324 |
+
/**/ TensorDQ mDQ, /** / TensorDK mDK, / ** / TensorDV mDV, / **/
|
| 325 |
+
Fusion fusion) {
|
| 326 |
+
|
| 327 |
+
using namespace cute;
|
| 328 |
+
|
| 329 |
+
dim3 grid(size<0>(mDQ), size<2>(mDQ), 1);
|
| 330 |
+
dim3 block(256);
|
| 331 |
+
int shared_mem = size<0>(mK) * sizeof(typename TensorDQ::value_type);
|
| 332 |
+
fmha_bwd_reference_dQ_kernel<<<grid, block, shared_mem>>>(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDQ, fusion);
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 336 |
+
|
| 337 |
+
template<
|
| 338 |
+
class ProblemShape,
|
| 339 |
+
class TensorQ, class TensorK, class TensorV,
|
| 340 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 341 |
+
/** / class TensorDQ, / **/ class TensorDK, /** / class TensorDV, / **/
|
| 342 |
+
class Fusion
|
| 343 |
+
>
|
| 344 |
+
void fmha_bwd_reference_dK(
|
| 345 |
+
ProblemShape problem_shape,
|
| 346 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 347 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 348 |
+
/** / TensorDQ mDQ, / **/ TensorDK mDK, /** / TensorDV mDV, / **/
|
| 349 |
+
Fusion fusion) {
|
| 350 |
+
|
| 351 |
+
using namespace cute;
|
| 352 |
+
|
| 353 |
+
auto [K, D, HB] = mDK.shape();
|
| 354 |
+
auto [H, B] = HB;
|
| 355 |
+
auto [H_R, H_K] = H;
|
| 356 |
+
dim3 grid(K, H_K * B, 1);
|
| 357 |
+
dim3 block(std::max(D, 256));
|
| 358 |
+
int shared_mem = size<0>(mDO) * sizeof(typename TensorDK::value_type);
|
| 359 |
+
fmha_bwd_reference_dK_kernel<<<grid, block, shared_mem>>>(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDK, fusion);
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 363 |
+
|
| 364 |
+
template<
|
| 365 |
+
class ProblemShape,
|
| 366 |
+
class TensorQ, class TensorK, class TensorV,
|
| 367 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 368 |
+
/** / class TensorDQ, / ** / class TensorDK, / **/ class TensorDV, /**/
|
| 369 |
+
class Fusion
|
| 370 |
+
>
|
| 371 |
+
void fmha_bwd_reference_dV(
|
| 372 |
+
ProblemShape problem_shape,
|
| 373 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 374 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 375 |
+
/** / TensorDQ mDQ, / ** / TensorDK mDK, / **/ TensorDV mDV, /**/
|
| 376 |
+
Fusion fusion) {
|
| 377 |
+
|
| 378 |
+
using namespace cute;
|
| 379 |
+
|
| 380 |
+
auto [K, D_VO, HB] = mDV.shape();
|
| 381 |
+
auto [H, B] = HB;
|
| 382 |
+
auto [H_R, H_K] = H;
|
| 383 |
+
dim3 grid(K, H_K * B, 1);
|
| 384 |
+
dim3 block(std::max(D_VO, 256));
|
| 385 |
+
int shared_mem = size<0>(mDO) * sizeof(typename TensorDV::value_type);
|
| 386 |
+
fmha_bwd_reference_dV_kernel<<<grid, block, shared_mem>>>(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDV, fusion);
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 390 |
+
|
| 391 |
+
template<
|
| 392 |
+
class ProblemShape,
|
| 393 |
+
class TensorQ, class TensorK, class TensorV,
|
| 394 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 395 |
+
class TensorDQ, class TensorDK, class TensorDV,
|
| 396 |
+
class Fusion
|
| 397 |
+
>
|
| 398 |
+
void fmha_bwd_reference(
|
| 399 |
+
ProblemShape problem_shape,
|
| 400 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 401 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 402 |
+
TensorDQ mDQ, TensorDK mDK, TensorDV mDV,
|
| 403 |
+
Fusion fusion) {
|
| 404 |
+
|
| 405 |
+
fmha_bwd_reference_dQ(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDQ, fusion);
|
| 406 |
+
fmha_bwd_reference_dK(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDK, fusion);
|
| 407 |
+
fmha_bwd_reference_dV(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDV, fusion);
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_fwd_gen_reference.hpp
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
#pragma once
|
| 32 |
+
|
| 33 |
+
#include <vector>
|
| 34 |
+
#include "cute/tensor.hpp"
|
| 35 |
+
|
| 36 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 37 |
+
|
| 38 |
+
template<
|
| 39 |
+
class ElementAcc,
|
| 40 |
+
class ProblemShape,
|
| 41 |
+
class TensorQ,
|
| 42 |
+
class TensorNewK,
|
| 43 |
+
class TensorNewV,
|
| 44 |
+
class TensorCacheK,
|
| 45 |
+
class TensorCacheV,
|
| 46 |
+
class TensorO
|
| 47 |
+
>
|
| 48 |
+
void __global__ fmha_fwd_gen_reference_kernel(
|
| 49 |
+
ProblemShape problem_shape,
|
| 50 |
+
const int* seqlen_kv, const int* cache_batch_idx,
|
| 51 |
+
TensorQ mQ, TensorNewK mNewK, TensorNewV mNewV,
|
| 52 |
+
TensorCacheK mCacheK, TensorCacheV mCacheV, TensorO mO) {
|
| 53 |
+
|
| 54 |
+
using namespace cute;
|
| 55 |
+
extern __shared__ char mS_mem[];
|
| 56 |
+
ElementAcc* mS = reinterpret_cast<ElementAcc*>(mS_mem);
|
| 57 |
+
|
| 58 |
+
float scale = 1.0f / std::sqrt(float(get<2>(problem_shape)));
|
| 59 |
+
|
| 60 |
+
if (mNewK.data() != nullptr) {
|
| 61 |
+
// 1. copy in new_k to cache
|
| 62 |
+
for (int idx_h = blockIdx.x; idx_h < size<3,0,1>(problem_shape); idx_h += gridDim.x) {
|
| 63 |
+
for (int idx_b = blockIdx.z; idx_b < size<3,1>(problem_shape); idx_b += gridDim.z) {
|
| 64 |
+
int idx_b_kv = cache_batch_idx != nullptr ? cache_batch_idx[idx_b] : idx_b;
|
| 65 |
+
for (int idx_d = threadIdx.x; idx_d < size<2>(problem_shape); idx_d += blockDim.x) {
|
| 66 |
+
mCacheK(seqlen_kv[idx_b], idx_d, make_coord(make_coord(_0{}, idx_h), idx_b_kv)) =
|
| 67 |
+
mNewK(_0{}, idx_d, make_coord(make_coord(_0{}, idx_h), idx_b));
|
| 68 |
+
mCacheV(seqlen_kv[idx_b], idx_d, make_coord(make_coord(_0{}, idx_h), idx_b_kv)) =
|
| 69 |
+
mNewV(_0{}, idx_d, make_coord(make_coord(_0{}, idx_h), idx_b));
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// 2. compute attention
|
| 76 |
+
for (int idx_h_kv = blockIdx.x; idx_h_kv < size<3,0,1>(problem_shape); idx_h_kv += gridDim.x) {
|
| 77 |
+
for (int idx_h_qo = blockIdx.y; idx_h_qo < size<3,0,0>(problem_shape); idx_h_qo += gridDim.y) {
|
| 78 |
+
int idx_h = idx_h_qo + size<3,0,0>(problem_shape) * idx_h_kv;
|
| 79 |
+
for (int idx_b = blockIdx.z; idx_b < size<3,1>(problem_shape); idx_b += gridDim.z) {
|
| 80 |
+
int idx_b_kv = cache_batch_idx != nullptr ? cache_batch_idx[idx_b] : idx_b;
|
| 81 |
+
const int kDim = 128;
|
| 82 |
+
ElementAcc reg_o[kDim] = {0};
|
| 83 |
+
ElementAcc row_max = -INFINITY;
|
| 84 |
+
ElementAcc row_sum = 0;
|
| 85 |
+
auto iteration = [&](auto const& tK, auto const& tV) {
|
| 86 |
+
ElementAcc reg_s = 0;
|
| 87 |
+
for (int idx_d = 0; idx_d < kDim; idx_d++) {
|
| 88 |
+
ElementAcc eQ = mQ(_0{}, idx_d, make_coord(idx_h, idx_b));
|
| 89 |
+
ElementAcc eK = tK(idx_d);
|
| 90 |
+
reg_s += eQ * eK;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
ElementAcc old_row_max = row_max;
|
| 94 |
+
row_max = std::max(row_max, reg_s);
|
| 95 |
+
|
| 96 |
+
ElementAcc adjustment = std::exp(scale * (old_row_max - row_max));
|
| 97 |
+
row_sum *= adjustment;
|
| 98 |
+
for (int idx_d = 0; idx_d < kDim; idx_d++) {
|
| 99 |
+
reg_o[idx_d] *= adjustment;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
ElementAcc reg_p = std::exp(scale * (reg_s - row_max));
|
| 103 |
+
row_sum += reg_p;
|
| 104 |
+
|
| 105 |
+
for (int idx_d = 0; idx_d < kDim; idx_d++) {
|
| 106 |
+
ElementAcc eV = tV(idx_d);
|
| 107 |
+
reg_o[idx_d] += reg_p * eV;
|
| 108 |
+
}
|
| 109 |
+
};
|
| 110 |
+
|
| 111 |
+
for (int idx_s = threadIdx.x; idx_s < seqlen_kv[idx_b]; idx_s += blockDim.x) {
|
| 112 |
+
iteration(mCacheK(idx_s, _, make_coord(idx_h, idx_b_kv)), mCacheV(idx_s, _, make_coord(idx_h, idx_b_kv)));
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
if (mNewK.data() != nullptr && threadIdx.x == 0) {
|
| 116 |
+
iteration(mNewK(_0{}, _, make_coord(idx_h, idx_b)), mNewV(_0{}, _, make_coord(idx_h, idx_b)));
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
mS[threadIdx.x] = row_max;
|
| 120 |
+
__syncthreads();
|
| 121 |
+
float old_row_max = row_max;
|
| 122 |
+
for (int i = 0; i < blockDim.x; i++) {
|
| 123 |
+
row_max = std::max(row_max, mS[i]);
|
| 124 |
+
}
|
| 125 |
+
__syncthreads();
|
| 126 |
+
|
| 127 |
+
ElementAcc adjustment = std::exp(scale * (old_row_max - row_max));
|
| 128 |
+
row_sum *= adjustment;
|
| 129 |
+
for (int idx_d = 0; idx_d < kDim; idx_d++) {
|
| 130 |
+
reg_o[idx_d] *= adjustment;
|
| 131 |
+
}
|
| 132 |
+
mS[threadIdx.x] = row_sum;
|
| 133 |
+
__syncthreads();
|
| 134 |
+
|
| 135 |
+
row_sum = 0;
|
| 136 |
+
for (int i = 0; i < blockDim.x; i++) {
|
| 137 |
+
row_sum += mS[i];
|
| 138 |
+
}
|
| 139 |
+
__syncthreads();
|
| 140 |
+
for (int idx_d = 0; idx_d < kDim; idx_d++) {
|
| 141 |
+
mS[idx_d] = 0;
|
| 142 |
+
}
|
| 143 |
+
__syncthreads();
|
| 144 |
+
|
| 145 |
+
for (int idx_d = 0; idx_d < kDim; idx_d++) {
|
| 146 |
+
reg_o[idx_d] /= row_sum;
|
| 147 |
+
atomicAdd(&mS[idx_d], reg_o[idx_d]);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
__syncthreads();
|
| 151 |
+
for (int idx_d = threadIdx.x; idx_d < kDim; idx_d += blockDim.x) {
|
| 152 |
+
mO(_0{}, idx_d, make_coord(idx_h, idx_b)) = static_cast<typename TensorO::value_type>(mS[idx_d]);
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
template<
|
| 160 |
+
class ElementAcc,
|
| 161 |
+
class ProblemShape,
|
| 162 |
+
class TensorQ,
|
| 163 |
+
class TensorNewK,
|
| 164 |
+
class TensorNewV,
|
| 165 |
+
class TensorCacheK,
|
| 166 |
+
class TensorCacheV,
|
| 167 |
+
class TensorO
|
| 168 |
+
>
|
| 169 |
+
void fmha_fwd_gen_reference(
|
| 170 |
+
ProblemShape problem_shape,
|
| 171 |
+
const int* seqlen_kv, const int* cache_batch_idx,
|
| 172 |
+
TensorQ mQ, TensorNewK mNewK, TensorNewV mNewV,
|
| 173 |
+
TensorCacheK mCacheK, TensorCacheV mCacheV, TensorO mO) {
|
| 174 |
+
|
| 175 |
+
using namespace cute;
|
| 176 |
+
|
| 177 |
+
dim3 grid(get<3,0,1>(problem_shape), get<3,0,0>(problem_shape), get<3,1>(problem_shape));
|
| 178 |
+
dim3 block(128);
|
| 179 |
+
int shared_mem = int(sizeof(ElementAcc)) * std::max<int>(128, block.x);
|
| 180 |
+
assert(get<2>(problem_shape) == 128);
|
| 181 |
+
fmha_fwd_gen_reference_kernel<ElementAcc><<<grid, block, shared_mem>>>(
|
| 182 |
+
problem_shape, seqlen_kv, cache_batch_idx,
|
| 183 |
+
mQ, mNewK, mNewV, mCacheK, mCacheV, mO
|
| 184 |
+
);
|
| 185 |
+
}
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_fwd_reference.hpp
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
#pragma once
|
| 32 |
+
|
| 33 |
+
#include "cute/tensor.hpp"
|
| 34 |
+
#include "collective/fmha_fusion.hpp"
|
| 35 |
+
|
| 36 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 37 |
+
|
| 38 |
+
template<
|
| 39 |
+
class ProblemShapeIn,
|
| 40 |
+
class TensorQ,
|
| 41 |
+
class TensorK,
|
| 42 |
+
class TensorV,
|
| 43 |
+
class TensorO,
|
| 44 |
+
class TensorLSE,
|
| 45 |
+
class Mask
|
| 46 |
+
>
|
| 47 |
+
void __global__ fmha_reference_kernel(
|
| 48 |
+
ProblemShapeIn problem_shape_in,
|
| 49 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 50 |
+
TensorO mO, TensorLSE mLSE,
|
| 51 |
+
Mask mask) {
|
| 52 |
+
|
| 53 |
+
using namespace cute;
|
| 54 |
+
using namespace cutlass::fmha::collective;
|
| 55 |
+
|
| 56 |
+
using Element = typename TensorO::value_type;
|
| 57 |
+
using ElementAccumulator = typename TensorLSE::value_type;
|
| 58 |
+
|
| 59 |
+
extern __shared__ char mS_mem[];
|
| 60 |
+
ElementAccumulator* mS = reinterpret_cast<ElementAccumulator*>(mS_mem);
|
| 61 |
+
|
| 62 |
+
ElementAccumulator softmax_scale = static_cast<ElementAccumulator>(1.0 / sqrt(1.0 * size<1>(mQ)));
|
| 63 |
+
|
| 64 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 65 |
+
|
| 66 |
+
for (int idx_L = blockIdx.y; idx_L < size<4>(problem_shape_in); idx_L += gridDim.y) {
|
| 67 |
+
for (int idx_Q = blockIdx.x; idx_Q < size<0>(problem_shape_in); idx_Q += gridDim.x) {
|
| 68 |
+
|
| 69 |
+
auto coord_L = idx2crd(idx_L, shape<4>(problem_shape_in));
|
| 70 |
+
auto get_coord_in = [&]() {
|
| 71 |
+
if constexpr (rank_v<decltype(get<2>(ProblemShapeIn{}))> == 2) {
|
| 72 |
+
return cute::make_tuple(idx_Q, _0{}, cute::make_tuple(_0{}, _0{}), cute::make_tuple(_0{}, _0{}), coord_L);
|
| 73 |
+
} else {
|
| 74 |
+
return cute::make_tuple(idx_Q, _0{}, _0{}, _0{}, coord_L);
|
| 75 |
+
}
|
| 76 |
+
};
|
| 77 |
+
auto coord_in = get_coord_in();
|
| 78 |
+
auto [problem_shape, coord] = apply_variable_length(problem_shape_in, coord_in, get<4,1>(coord_in));
|
| 79 |
+
|
| 80 |
+
int head_qk = 0;
|
| 81 |
+
int head_v = 0;
|
| 82 |
+
if constexpr (rank_v<decltype(get<2>(problem_shape))> == 2) {
|
| 83 |
+
// MLA case: head_qk 192, head_v = 128
|
| 84 |
+
head_qk = size<2, 0>(problem_shape) + size<2, 1>(problem_shape);
|
| 85 |
+
head_v = size<2, 0>(problem_shape);
|
| 86 |
+
} else {
|
| 87 |
+
head_qk = size<3>(problem_shape);
|
| 88 |
+
head_v = head_qk;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
if (get<0,0>(coord) >= get<0>(problem_shape)) continue;
|
| 92 |
+
|
| 93 |
+
int offset_Q = 0;
|
| 94 |
+
if constexpr (rank<0>(decltype(coord){}) == 2) {
|
| 95 |
+
offset_Q = get<0,1>(coord);
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
int offset_K = 0;
|
| 99 |
+
if constexpr (rank<1>(decltype(coord){}) == 2) {
|
| 100 |
+
offset_K = get<1,1>(coord);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
if (get<1>(problem_shape) == 0) {
|
| 104 |
+
for (int idx_D = threadIdx.x; idx_D < head_qk; idx_D += blockDim.x) {
|
| 105 |
+
mO(idx_Q + offset_Q, idx_D, idx_L) = Element(0);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
if (threadIdx.x == 0 && mLSE.data() != nullptr) {
|
| 109 |
+
mLSE(idx_Q + offset_Q, idx_L) = -INFINITY;
|
| 110 |
+
}
|
| 111 |
+
continue;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
for (int idx_K = threadIdx.x; idx_K < size<1>(problem_shape); idx_K += blockDim.x) {
|
| 115 |
+
ElementAccumulator acc = 0;
|
| 116 |
+
for (int idx_D = 0; idx_D < head_qk; idx_D++) {
|
| 117 |
+
ElementAccumulator eQ = mQ(idx_Q + offset_Q, idx_D, idx_L);
|
| 118 |
+
ElementAccumulator eK = mK(idx_K + offset_K, idx_D, idx_L);
|
| 119 |
+
acc += eQ * eK;
|
| 120 |
+
}
|
| 121 |
+
auto frag = make_tensor<ElementAccumulator>(Shape<_1, _1>{});
|
| 122 |
+
frag(0) = acc;
|
| 123 |
+
mask.apply_mask(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 124 |
+
mS[idx_K] = frag(0);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
__syncthreads();
|
| 128 |
+
|
| 129 |
+
ElementAccumulator maxS = -std::numeric_limits<ElementAccumulator>::infinity();
|
| 130 |
+
for (int idx_K = 0; idx_K < size<1>(problem_shape); idx_K++) {
|
| 131 |
+
maxS = std::max<ElementAccumulator>(maxS, mS[idx_K]);
|
| 132 |
+
}
|
| 133 |
+
if (maxS == -std::numeric_limits<ElementAccumulator>::infinity()) maxS = 0;
|
| 134 |
+
|
| 135 |
+
__syncthreads();
|
| 136 |
+
|
| 137 |
+
for (int idx_K = threadIdx.x; idx_K < size<1>(problem_shape); idx_K += blockDim.x) {
|
| 138 |
+
mS[idx_K] = expf(softmax_scale * (mS[idx_K] - maxS));
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
__syncthreads();
|
| 142 |
+
|
| 143 |
+
ElementAccumulator sum = 0;
|
| 144 |
+
for (int idx_K = 0; idx_K < size<1>(problem_shape); idx_K++) {
|
| 145 |
+
sum += mS[idx_K];
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
ElementAccumulator scale = 1.0f / sum;
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
for (int idx_D = threadIdx.x; idx_D < head_v; idx_D += blockDim.x) {
|
| 152 |
+
ElementAccumulator acc = 0;
|
| 153 |
+
for (int idx_K = 0; idx_K < size<1>(problem_shape); idx_K++) {
|
| 154 |
+
ElementAccumulator eV = mV(idx_K + offset_K, idx_D, idx_L);
|
| 155 |
+
ElementAccumulator eK = static_cast<Element>(mS[idx_K]);
|
| 156 |
+
acc += eK * eV;
|
| 157 |
+
}
|
| 158 |
+
mO(idx_Q + offset_Q, idx_D, idx_L) = static_cast<typename TensorO::value_type>(acc * scale);
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
if (threadIdx.x == 0 && mLSE.data() != nullptr) {
|
| 163 |
+
mLSE(idx_Q + offset_Q, idx_L) = log(sum) + softmax_scale * maxS;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 171 |
+
|
| 172 |
+
template<
|
| 173 |
+
class ProblemShapeIn,
|
| 174 |
+
class TensorQ,
|
| 175 |
+
class TensorK,
|
| 176 |
+
class TensorV,
|
| 177 |
+
class TensorO,
|
| 178 |
+
class TensorLSE,
|
| 179 |
+
class Mask
|
| 180 |
+
>
|
| 181 |
+
void fmha_reference(
|
| 182 |
+
ProblemShapeIn problem_shape_in,
|
| 183 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 184 |
+
TensorO mO, TensorLSE mLSE,
|
| 185 |
+
Mask mask) {
|
| 186 |
+
|
| 187 |
+
using namespace cute;
|
| 188 |
+
|
| 189 |
+
dim3 grid(size<0>(mO), size<2>(mO), 1);
|
| 190 |
+
dim3 block(256);
|
| 191 |
+
int shared_mem = size<0>(mK) * int(sizeof(typename TensorLSE::value_type));
|
| 192 |
+
fmha_reference_kernel<<<grid, block, shared_mem>>>(problem_shape_in, mQ, mK, mV, mO, mLSE, mask);
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/fmha_mla_reference.hpp
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
#pragma once
|
| 32 |
+
|
| 33 |
+
#include "cute/tensor.hpp"
|
| 34 |
+
|
| 35 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 36 |
+
|
| 37 |
+
template<
|
| 38 |
+
class ProblemShape,
|
| 39 |
+
class TensorSeq,
|
| 40 |
+
class TensorPageTable,
|
| 41 |
+
class TensorQL,
|
| 42 |
+
class TensorQR,
|
| 43 |
+
class TensorCL,
|
| 44 |
+
class TensorKR,
|
| 45 |
+
class TensorO,
|
| 46 |
+
class TensorLSE,
|
| 47 |
+
class Scale
|
| 48 |
+
>
|
| 49 |
+
void __global__ fmha_mla_reference_kernel(
|
| 50 |
+
ProblemShape problem_shape,
|
| 51 |
+
TensorSeq mSeq, TensorPageTable mPT,
|
| 52 |
+
TensorQL mQL, TensorQR mQR,
|
| 53 |
+
TensorCL mCL, TensorKR mKR,
|
| 54 |
+
TensorO mO, TensorLSE mLSE,
|
| 55 |
+
Scale softmax_scale) {
|
| 56 |
+
|
| 57 |
+
using namespace cute;
|
| 58 |
+
|
| 59 |
+
auto [H, K, D, B] = problem_shape;
|
| 60 |
+
auto [D_latent, D_rope] = D;
|
| 61 |
+
|
| 62 |
+
using Element = typename TensorO::value_type;
|
| 63 |
+
using ElementAcc = typename TensorLSE::value_type;
|
| 64 |
+
|
| 65 |
+
extern __shared__ ElementAcc mS[];
|
| 66 |
+
// ElementAcc* mS = reinterpret_cast<ElementAcc*>(mS_mem);
|
| 67 |
+
|
| 68 |
+
for (int idx_B = blockIdx.y; idx_B < B; idx_B += gridDim.y) {
|
| 69 |
+
if (mSeq.data() != nullptr) {
|
| 70 |
+
K = mSeq(idx_B);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
for (int idx_H = blockIdx.x; idx_H < H; idx_H += gridDim.x) {
|
| 74 |
+
|
| 75 |
+
for (int idx_K = threadIdx.x; idx_K < K; idx_K += blockDim.x) {
|
| 76 |
+
ElementAcc acc = 0;
|
| 77 |
+
|
| 78 |
+
for (int idx_D = 0; idx_D < D_latent; idx_D++) {
|
| 79 |
+
int page_idx_K = idx_K;
|
| 80 |
+
int page_idx_B = idx_B;
|
| 81 |
+
if (mPT.data() != nullptr) {
|
| 82 |
+
page_idx_B = mPT(idx_K / size<0>(mCL), idx_B);
|
| 83 |
+
page_idx_K = idx_K % size<0>(mCL);
|
| 84 |
+
}
|
| 85 |
+
ElementAcc eQ = mQL(idx_H, idx_D, idx_B);
|
| 86 |
+
ElementAcc eK = mCL(page_idx_K, idx_D, page_idx_B);
|
| 87 |
+
acc += eQ * eK;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
for (int idx_D = 0; idx_D < D_rope; idx_D++) {
|
| 91 |
+
int page_idx_K = idx_K;
|
| 92 |
+
int page_idx_B = idx_B;
|
| 93 |
+
if (mPT.data() != nullptr) {
|
| 94 |
+
page_idx_B = mPT(idx_K / size<0>(mCL), idx_B);
|
| 95 |
+
page_idx_K = idx_K % size<0>(mCL);
|
| 96 |
+
}
|
| 97 |
+
ElementAcc eQ = mQR(idx_H, idx_D, idx_B);
|
| 98 |
+
ElementAcc eK = mKR(page_idx_K, idx_D, page_idx_B);
|
| 99 |
+
acc += eQ * eK;
|
| 100 |
+
}
|
| 101 |
+
mS[idx_K] = acc;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
__syncthreads();
|
| 105 |
+
|
| 106 |
+
ElementAcc maxS = -std::numeric_limits<ElementAcc>::infinity();
|
| 107 |
+
for (int idx_K = 0; idx_K < K; idx_K++) {
|
| 108 |
+
maxS = std::max<ElementAcc>(maxS, mS[idx_K]);
|
| 109 |
+
}
|
| 110 |
+
if (maxS == -std::numeric_limits<ElementAcc>::infinity()) maxS = 0;
|
| 111 |
+
|
| 112 |
+
__syncthreads();
|
| 113 |
+
|
| 114 |
+
for (int idx_K = threadIdx.x; idx_K < K; idx_K += blockDim.x) {
|
| 115 |
+
mS[idx_K] = expf(softmax_scale * (mS[idx_K] - maxS));
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
__syncthreads();
|
| 119 |
+
|
| 120 |
+
ElementAcc sum = 0;
|
| 121 |
+
for (int idx_K = 0; idx_K < K; idx_K++) {
|
| 122 |
+
sum += mS[idx_K];
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
ElementAcc o_scale = 1.0f / sum;
|
| 126 |
+
|
| 127 |
+
for (int idx_D = threadIdx.x; idx_D < D_latent; idx_D += blockDim.x) {
|
| 128 |
+
ElementAcc acc = 0;
|
| 129 |
+
for (int idx_K = 0; idx_K < K; idx_K++) {
|
| 130 |
+
int page_idx_K = idx_K;
|
| 131 |
+
int page_idx_B = idx_B;
|
| 132 |
+
if (mPT.data() != nullptr) {
|
| 133 |
+
page_idx_B = mPT(idx_K / size<0>(mCL), idx_B);
|
| 134 |
+
page_idx_K = idx_K % size<0>(mCL);
|
| 135 |
+
}
|
| 136 |
+
ElementAcc eV = mCL(page_idx_K, idx_D, page_idx_B);
|
| 137 |
+
ElementAcc eK = static_cast<Element>(mS[idx_K]);
|
| 138 |
+
acc += eK * eV;
|
| 139 |
+
}
|
| 140 |
+
mO(idx_H, idx_D, idx_B) = static_cast<typename TensorO::value_type>(acc * o_scale);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if (threadIdx.x == 0) {
|
| 144 |
+
mLSE(idx_H, idx_B) = log(sum) + softmax_scale * maxS;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 152 |
+
|
| 153 |
+
template<
|
| 154 |
+
class ProblemShape,
|
| 155 |
+
class TensorSeq,
|
| 156 |
+
class TensorPageTable,
|
| 157 |
+
class TensorQL,
|
| 158 |
+
class TensorQR,
|
| 159 |
+
class TensorCL,
|
| 160 |
+
class TensorKR,
|
| 161 |
+
class TensorO,
|
| 162 |
+
class TensorLSE,
|
| 163 |
+
class Scale
|
| 164 |
+
>
|
| 165 |
+
void fmha_mla_reference(
|
| 166 |
+
ProblemShape problem_shape,
|
| 167 |
+
TensorSeq mSeq, TensorPageTable mPT,
|
| 168 |
+
TensorQL mQL, TensorQR mQR,
|
| 169 |
+
TensorCL mCL, TensorKR mKR,
|
| 170 |
+
TensorO mO, TensorLSE mLSE,
|
| 171 |
+
Scale scale) {
|
| 172 |
+
|
| 173 |
+
using namespace cute;
|
| 174 |
+
|
| 175 |
+
auto [H, K, D, B] = problem_shape;
|
| 176 |
+
auto [D_latent, D_rope] = D;
|
| 177 |
+
|
| 178 |
+
dim3 grid(H, B, 1);
|
| 179 |
+
dim3 block(256);
|
| 180 |
+
int shared_mem = K * int(sizeof(typename TensorLSE::value_type)) + 16;
|
| 181 |
+
cudaError_t result;
|
| 182 |
+
if (shared_mem >= (48 << 10)) {
|
| 183 |
+
result = cudaFuncSetAttribute(
|
| 184 |
+
&fmha_mla_reference_kernel<ProblemShape, TensorSeq, TensorPageTable, TensorQL, TensorQR, TensorCL, TensorKR, TensorO, TensorLSE, Scale>,
|
| 185 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 186 |
+
shared_mem);
|
| 187 |
+
if (cudaSuccess != result) {
|
| 188 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 189 |
+
throw std::runtime_error("couldn't perform smem optin");
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
fmha_mla_reference_kernel<<<grid, block, shared_mem>>>(
|
| 193 |
+
problem_shape, mSeq, mPT, mQL, mQR, mCL, mKR, mO, mLSE, scale);
|
| 194 |
+
cudaDeviceSynchronize();
|
| 195 |
+
result = cudaGetLastError();
|
| 196 |
+
if (cudaSuccess != result) {
|
| 197 |
+
throw std::runtime_error("couldn't execute reference");
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/77_blackwell_fmha/reference/reference_abs_error.hpp
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
#pragma once
|
| 35 |
+
|
| 36 |
+
#include <math.h>
|
| 37 |
+
|
| 38 |
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
| 39 |
+
|
| 40 |
+
template<typename T>
|
| 41 |
+
struct DeviceAllocation {
|
| 42 |
+
T* ptr_ = nullptr;
|
| 43 |
+
size_t offset_ = 0;
|
| 44 |
+
size_t size_ = 0;
|
| 45 |
+
|
| 46 |
+
DeviceAllocation(DeviceAllocation const&) = delete;
|
| 47 |
+
DeviceAllocation& operator=(DeviceAllocation const&) = delete;
|
| 48 |
+
|
| 49 |
+
DeviceAllocation() = default;
|
| 50 |
+
DeviceAllocation(size_t size) { reset(size); }
|
| 51 |
+
~DeviceAllocation() { reset(); }
|
| 52 |
+
|
| 53 |
+
void reset(size_t size, size_t offset=0) {
|
| 54 |
+
reset();
|
| 55 |
+
auto ret = cudaMalloc(&ptr_, sizeof(T) * (size + offset));
|
| 56 |
+
assert(ret == cudaSuccess);
|
| 57 |
+
size_ = size;
|
| 58 |
+
offset_ = offset;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
T* get() {
|
| 62 |
+
return ptr_ + offset_;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
const T* get() const {
|
| 66 |
+
return ptr_ + offset_;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
void reset() {
|
| 70 |
+
if (ptr_ != nullptr) {
|
| 71 |
+
auto ret = cudaFree(ptr_);
|
| 72 |
+
assert(ret == cudaSuccess);
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
size_t size() const { return size_; }
|
| 77 |
+
|
| 78 |
+
size_t get_storage_size() const { return (size_ + offset_) * sizeof(T); }
|
| 79 |
+
|
| 80 |
+
void copy_from_host(const T* ptr, size_t sz) {
|
| 81 |
+
auto ret = cudaMemcpy(ptr_, ptr, sz * sizeof(T), cudaMemcpyDefault);
|
| 82 |
+
assert(ret == cudaSuccess);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
void copy_from_device(const T* ptr, size_t sz) {
|
| 86 |
+
auto ret = cudaMemcpy(ptr_, ptr, sz * sizeof(T), cudaMemcpyDefault);
|
| 87 |
+
assert(ret == cudaSuccess);
|
| 88 |
+
}
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
template<typename Element>
|
| 92 |
+
__global__ void reference_abs_diff_kernel(
|
| 93 |
+
Element* data, Element* data_ref, size_t count,
|
| 94 |
+
double* max_diff, double* sum_diff,
|
| 95 |
+
bool print_diff ) {
|
| 96 |
+
|
| 97 |
+
double thread_max_diff = 0;
|
| 98 |
+
double thread_sum_diff = 0;
|
| 99 |
+
|
| 100 |
+
__shared__ double block_max_diff;
|
| 101 |
+
__shared__ double block_sum_diff;
|
| 102 |
+
|
| 103 |
+
for (size_t i = threadIdx.x + blockIdx.x * blockDim.x; i < count; i += blockDim.x * gridDim.x) {
|
| 104 |
+
if (data[i] == data_ref[i]) {
|
| 105 |
+
continue;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
double diff = fabs(data[i] - data_ref[i]);
|
| 109 |
+
if (print_diff) if (not isfinite(diff) || diff > 0.01f) printf("difference at %lld: %f ... %f vs %f\n", static_cast<long long int>(i), diff, (double)data[i], (double)data_ref[i]);
|
| 110 |
+
thread_max_diff = fmax(diff, thread_max_diff);
|
| 111 |
+
thread_sum_diff += diff;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
for (int i = 0; i < blockDim.x; i++) {
|
| 115 |
+
if (i == threadIdx.x) {
|
| 116 |
+
if (i == 0) {
|
| 117 |
+
block_max_diff = thread_max_diff;
|
| 118 |
+
block_sum_diff = thread_sum_diff;
|
| 119 |
+
}
|
| 120 |
+
else {
|
| 121 |
+
block_max_diff = fmax(block_max_diff, thread_max_diff);
|
| 122 |
+
block_sum_diff += thread_sum_diff;
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
__syncthreads();
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
if (threadIdx.x == 0) {
|
| 129 |
+
atomicAdd(sum_diff, block_sum_diff);
|
| 130 |
+
|
| 131 |
+
for (;;) {
|
| 132 |
+
unsigned long long prev = *reinterpret_cast<unsigned long long*>(max_diff);
|
| 133 |
+
double prev_diff = reinterpret_cast<double const&>(prev);
|
| 134 |
+
double new_max_diff = fmax(block_max_diff, prev_diff);
|
| 135 |
+
unsigned long long found = atomicCAS(reinterpret_cast<unsigned long long*>(max_diff), prev, reinterpret_cast<unsigned long long const&>(new_max_diff));
|
| 136 |
+
if (found == prev) break;
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
template<typename Element>
|
| 142 |
+
void reference_abs_diff(
|
| 143 |
+
DeviceAllocation<Element> const& data,
|
| 144 |
+
DeviceAllocation<Element> const& data_ref,
|
| 145 |
+
double& max_diff, double& mean_diff) {
|
| 146 |
+
|
| 147 |
+
static bool kPrintDiff = getenv("REF_PRINT_DIFF") && atoi(getenv("REF_PRINT_DIFF")) == 1;
|
| 148 |
+
|
| 149 |
+
DeviceAllocation<double> result;
|
| 150 |
+
result.reset(2);
|
| 151 |
+
assert(data.size() == data_ref.size());
|
| 152 |
+
|
| 153 |
+
cudaError_t err = cudaMemset(result.get(), 0, result.size() * sizeof(double));
|
| 154 |
+
if (err != cudaSuccess) {
|
| 155 |
+
std::cerr << "Memset failed. Last CUDA error: "
|
| 156 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 157 |
+
max_diff = mean_diff = 1e20;
|
| 158 |
+
return;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
dim3 block(256, 1, 1);
|
| 162 |
+
dim3 grid(1024, 1, 1);
|
| 163 |
+
reference_abs_diff_kernel<<<block, grid>>>(
|
| 164 |
+
data.get(), data_ref.get(), data.size(),
|
| 165 |
+
result.get(), result.get() + 1, kPrintDiff);
|
| 166 |
+
|
| 167 |
+
err = cudaDeviceSynchronize();
|
| 168 |
+
if (err != cudaSuccess) {
|
| 169 |
+
std::cerr << "Difference kernel failed. Last CUDA error: "
|
| 170 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 171 |
+
max_diff = mean_diff = 1e20;
|
| 172 |
+
return;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
double result_host[2];
|
| 176 |
+
err = cudaMemcpy(result_host, result.get(), result.size() * sizeof(double), cudaMemcpyDefault);
|
| 177 |
+
if (err != cudaSuccess) {
|
| 178 |
+
std::cerr << "Copy failed. Last CUDA error: "
|
| 179 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 180 |
+
max_diff = mean_diff = 1e20;
|
| 181 |
+
return;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
max_diff = result_host[0];
|
| 185 |
+
mean_diff = result_host[1] / static_cast<double>(data.size());
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
template<typename Element>
|
| 189 |
+
__global__ void reference_rel_diff_kernel(
|
| 190 |
+
Element* data, Element* data_ref, size_t count,
|
| 191 |
+
double* max_diff, double* sum_diff,
|
| 192 |
+
bool print_diff ) {
|
| 193 |
+
|
| 194 |
+
double thread_max_diff = 0;
|
| 195 |
+
double thread_sum_diff = 0;
|
| 196 |
+
|
| 197 |
+
__shared__ double block_max_diff;
|
| 198 |
+
__shared__ double block_sum_diff;
|
| 199 |
+
|
| 200 |
+
for (size_t i = threadIdx.x + blockIdx.x * blockDim.x; i < count; i += blockDim.x * gridDim.x) {
|
| 201 |
+
if (data[i] == data_ref[i]) {
|
| 202 |
+
continue;
|
| 203 |
+
}
|
| 204 |
+
double diff = fabs(data[i] - data_ref[i]) / fabs(data_ref[i]);
|
| 205 |
+
if (print_diff) if (not isfinite(diff) || diff > 0.01f) printf("difference at %lld: %f ... %f vs %f\n", static_cast<long long int>(i), diff, (double)data[i], (double)data_ref[i]);
|
| 206 |
+
thread_max_diff = fmax(diff, thread_max_diff);
|
| 207 |
+
thread_sum_diff += diff;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
for (int i = 0; i < blockDim.x; i++) {
|
| 211 |
+
if (i == threadIdx.x) {
|
| 212 |
+
if (i == 0) {
|
| 213 |
+
block_max_diff = thread_max_diff;
|
| 214 |
+
block_sum_diff = thread_sum_diff;
|
| 215 |
+
}
|
| 216 |
+
else {
|
| 217 |
+
block_max_diff = fmax(block_max_diff, thread_max_diff);
|
| 218 |
+
block_sum_diff += thread_sum_diff;
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
__syncthreads();
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
if (threadIdx.x == 0) {
|
| 225 |
+
atomicAdd(sum_diff, block_sum_diff);
|
| 226 |
+
|
| 227 |
+
for (;;) {
|
| 228 |
+
unsigned long long prev = *reinterpret_cast<unsigned long long*>(max_diff);
|
| 229 |
+
double prev_diff = reinterpret_cast<double const&>(prev);
|
| 230 |
+
double new_max_diff = fmax(block_max_diff, prev_diff);
|
| 231 |
+
unsigned long long found = atomicCAS(reinterpret_cast<unsigned long long*>(max_diff), prev, reinterpret_cast<unsigned long long const&>(new_max_diff));
|
| 232 |
+
if (found == prev) break;
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
template<typename Element>
|
| 238 |
+
void reference_rel_diff(
|
| 239 |
+
DeviceAllocation<Element> const& data,
|
| 240 |
+
DeviceAllocation<Element> const& data_ref,
|
| 241 |
+
double& max_diff, double& mean_diff) {
|
| 242 |
+
|
| 243 |
+
static bool kPrintDiff = getenv("REF_PRINT_DIFF") && atoi(getenv("REF_PRINT_DIFF")) == 1;
|
| 244 |
+
|
| 245 |
+
DeviceAllocation<double> result;
|
| 246 |
+
result.reset(2);
|
| 247 |
+
assert(data.size() == data_ref.size());
|
| 248 |
+
|
| 249 |
+
cudaError_t err = cudaMemset(result.get(), 0, result.size() * sizeof(double));
|
| 250 |
+
if (err != cudaSuccess) {
|
| 251 |
+
std::cerr << "Memset failed. Last CUDA error: "
|
| 252 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 253 |
+
max_diff = mean_diff = 1e20;
|
| 254 |
+
return;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
dim3 block(256, 1, 1);
|
| 258 |
+
dim3 grid(1024, 1, 1);
|
| 259 |
+
reference_rel_diff_kernel<<<block, grid>>>(
|
| 260 |
+
data.get(), data_ref.get(), data.size(),
|
| 261 |
+
result.get(), result.get() + 1, kPrintDiff);
|
| 262 |
+
|
| 263 |
+
err = cudaDeviceSynchronize();
|
| 264 |
+
if (err != cudaSuccess) {
|
| 265 |
+
std::cerr << "Difference kernel failed. Last CUDA error: "
|
| 266 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 267 |
+
max_diff = mean_diff = 1e20;
|
| 268 |
+
return;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
double result_host[2];
|
| 272 |
+
err = cudaMemcpy(result_host, result.get(), result.size() * sizeof(double), cudaMemcpyDefault);
|
| 273 |
+
if (err != cudaSuccess) {
|
| 274 |
+
std::cerr << "Copy failed. Last CUDA error: "
|
| 275 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 276 |
+
max_diff = mean_diff = 1e20;
|
| 277 |
+
return;
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
max_diff = result_host[0];
|
| 281 |
+
mean_diff = result_host[1] / static_cast<double>(data.size());
|
| 282 |
+
}
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/86_blackwell_mixed_dtype_gemm/mixed_dtype_helper.cuh
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/gemm/dispatch_policy.hpp"
|
| 36 |
+
#include "cutlass/epilogue/collective/default_epilogue.hpp"
|
| 37 |
+
#include "cutlass/epilogue/collective/collective_builder.hpp"
|
| 38 |
+
#include "cutlass/gemm/device/gemm_universal_adapter.h"
|
| 39 |
+
#include "cutlass/gemm/kernel/gemm_universal.hpp"
|
| 40 |
+
#include "cutlass/util/command_line.h"
|
| 41 |
+
#include "cutlass/util/reference/device/tensor_fill.h"
|
| 42 |
+
#include "cutlass/util/reference/device/tensor_compare.h"
|
| 43 |
+
|
| 44 |
+
#include "cute/tensor.hpp"
|
| 45 |
+
|
| 46 |
+
#include <cuda.h>
|
| 47 |
+
#include <numeric>
|
| 48 |
+
#include "helper.h"
|
| 49 |
+
|
| 50 |
+
enum MixedDtypeGemmMode {
|
| 51 |
+
ConvertOnly,
|
| 52 |
+
ScaleOnly,
|
| 53 |
+
ScaleWithZeroPoint
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
/// Command line options parsing
|
| 57 |
+
struct MixedDtypeOptions {
|
| 58 |
+
|
| 59 |
+
bool help = false;
|
| 60 |
+
bool verify = false;
|
| 61 |
+
|
| 62 |
+
float alpha = 1.0f;
|
| 63 |
+
float beta = 0.0f;
|
| 64 |
+
int iterations = 1000;
|
| 65 |
+
int warmup = 1000;
|
| 66 |
+
int mode = 1;
|
| 67 |
+
int m = 5120, n = 4096, k = 4096;
|
| 68 |
+
int l = 1;
|
| 69 |
+
|
| 70 |
+
// Parses the command line
|
| 71 |
+
void parse(int argc, char const **args) {
|
| 72 |
+
cutlass::CommandLine cmd(argc, args);
|
| 73 |
+
|
| 74 |
+
if (cmd.check_cmd_line_flag("help")) {
|
| 75 |
+
help = true;
|
| 76 |
+
return;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if (cmd.check_cmd_line_flag("verify")) {
|
| 80 |
+
verify = true;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
cmd.get_cmd_line_argument("m", m);
|
| 84 |
+
cmd.get_cmd_line_argument("n", n);
|
| 85 |
+
cmd.get_cmd_line_argument("k", k);
|
| 86 |
+
cmd.get_cmd_line_argument("l", l);
|
| 87 |
+
cmd.get_cmd_line_argument("mode", mode);
|
| 88 |
+
cmd.get_cmd_line_argument("alpha", alpha, 1.f);
|
| 89 |
+
cmd.get_cmd_line_argument("beta", beta, 0.f);
|
| 90 |
+
cmd.get_cmd_line_argument("iterations", iterations);
|
| 91 |
+
cmd.get_cmd_line_argument("warmup", warmup);
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
/// Prints the usage statement.
|
| 95 |
+
std::ostream & print_usage(std::ostream &out) const {
|
| 96 |
+
|
| 97 |
+
out << "86_blackwell_mixed_dtype_gemm\n\n"
|
| 98 |
+
<< " Blackwell Mixed Data Type GEMM using a Warp Specialized kernel.\n\n"
|
| 99 |
+
<< "Options:\n\n"
|
| 100 |
+
<< " --help If specified, displays this usage statement\n\n"
|
| 101 |
+
<< " --m=<int> Sets the M extent of the GEMM\n"
|
| 102 |
+
<< " --n=<int> Sets the N extent of the GEMM\n"
|
| 103 |
+
<< " --k=<int> Sets the K extent of the GEMM\n"
|
| 104 |
+
<< " --l=<int> The number of independent gemm problems with mnk shape\n"
|
| 105 |
+
<< " --mode=<int> The mode to run the gemm. 0 does (A @ B), 1 means A @ (scale * B), 2 means A @ (scale * B + zero-point).\n"
|
| 106 |
+
<< " --alpha=<f32> Epilogue scalar alpha\n"
|
| 107 |
+
<< " --beta=<f32> Epilogue scalar beta\n\n"
|
| 108 |
+
<< " --iterations=<int> Number of profiling iterations to perform.\n\n"
|
| 109 |
+
<< " --warmup=<int> Number of warmup iterations to perform.\n\n"
|
| 110 |
+
<< " --verify=<int> Run verification.\n\n";
|
| 111 |
+
|
| 112 |
+
out
|
| 113 |
+
<< "\n\nExamples:\n\n"
|
| 114 |
+
<< "$ " << "86_blackwell_mixed_dtype_gemm" << " --m=1024 --n=512 --k=1024 --l=10 --alpha=2 --mode=2 --beta=0.707 \n\n";
|
| 115 |
+
|
| 116 |
+
return out;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
/// Compute performance in GFLOP/s
|
| 120 |
+
double gflops(double runtime_s) const
|
| 121 |
+
{
|
| 122 |
+
// Two flops per multiply-add
|
| 123 |
+
uint64_t flop = uint64_t(2) * m * n * k * l;
|
| 124 |
+
double gflop = double(flop) / double(1.0e9);
|
| 125 |
+
return gflop / runtime_s;
|
| 126 |
+
}
|
| 127 |
+
};
|
| 128 |
+
|
| 129 |
+
/// Result structure
|
| 130 |
+
struct MixedDtypeResult
|
| 131 |
+
{
|
| 132 |
+
double avg_runtime_ms = 0.0;
|
| 133 |
+
double gflops = 0.0;
|
| 134 |
+
cutlass::Status status = cutlass::Status::kSuccess;
|
| 135 |
+
cudaError_t error = cudaSuccess;
|
| 136 |
+
bool passed = false;
|
| 137 |
+
|
| 138 |
+
};
|
| 139 |
+
|
| 140 |
+
/// Profiling Loop
|
| 141 |
+
template <class Gemm>
|
| 142 |
+
void mixed_dtype_profiling(
|
| 143 |
+
Gemm& gemm,
|
| 144 |
+
MixedDtypeOptions const& options,
|
| 145 |
+
MixedDtypeResult& result) {
|
| 146 |
+
|
| 147 |
+
if (options.iterations <= 0) return;
|
| 148 |
+
|
| 149 |
+
cudaEvent_t start, stop;
|
| 150 |
+
cudaEventCreate(&start);
|
| 151 |
+
cudaEventCreate(&stop);
|
| 152 |
+
|
| 153 |
+
std::vector<float> runtimes;
|
| 154 |
+
runtimes.reserve(options.iterations);
|
| 155 |
+
|
| 156 |
+
for (int iter = 0; iter < options.warmup + options.iterations; ++iter) {
|
| 157 |
+
cudaEventRecord(start);
|
| 158 |
+
CUTLASS_CHECK(gemm.run());
|
| 159 |
+
cudaEventRecord(stop);
|
| 160 |
+
cudaEventSynchronize(stop);
|
| 161 |
+
|
| 162 |
+
if (iter >= options.warmup) {
|
| 163 |
+
float milliseconds = 0;
|
| 164 |
+
cudaEventElapsedTime(&milliseconds, start, stop);
|
| 165 |
+
runtimes.push_back(milliseconds);
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
cudaEventDestroy(start);
|
| 170 |
+
cudaEventDestroy(stop);
|
| 171 |
+
|
| 172 |
+
// Compute average setup and runtime and GFLOPs.
|
| 173 |
+
result.avg_runtime_ms = std::accumulate(runtimes.begin(), runtimes.end(), 0.0f) / runtimes.size();
|
| 174 |
+
result.gflops = options.gflops(result.avg_runtime_ms / 1000.0);
|
| 175 |
+
|
| 176 |
+
std::cout << " Problem Size: " << options.m << 'x' << options.n << 'x' << options.k << 'x' << options.l << std::endl;
|
| 177 |
+
std::cout << " Avg runtime: " << result.avg_runtime_ms << " ms" << std::endl;
|
| 178 |
+
std::cout << " GFLOPS: " << result.gflops << std::endl;
|
| 179 |
+
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
/// Helpers to initialize a block of device data
|
| 183 |
+
template <class Element>
|
| 184 |
+
bool initialize_tensor(
|
| 185 |
+
cutlass::DeviceAllocation<Element>& block,
|
| 186 |
+
uint64_t seed = 2023) {
|
| 187 |
+
|
| 188 |
+
double scope_max, scope_min;
|
| 189 |
+
int bits_input = cutlass::sizeof_bits<Element>::value;
|
| 190 |
+
int bits_output = cutlass::sizeof_bits<Element>::value;
|
| 191 |
+
|
| 192 |
+
if (bits_input == 1) {
|
| 193 |
+
scope_max = 2;
|
| 194 |
+
scope_min = 0;
|
| 195 |
+
}
|
| 196 |
+
else if (bits_input <= 8) {
|
| 197 |
+
scope_max = 2;
|
| 198 |
+
scope_min = -2;
|
| 199 |
+
}
|
| 200 |
+
else if (bits_output == 16) {
|
| 201 |
+
scope_max = 5;
|
| 202 |
+
scope_min = -5;
|
| 203 |
+
}
|
| 204 |
+
else {
|
| 205 |
+
scope_max = 8;
|
| 206 |
+
scope_min = -8;
|
| 207 |
+
}
|
| 208 |
+
cutlass::reference::device::BlockFillRandomUniform(
|
| 209 |
+
block.get(), block.size(), seed, Element(scope_max), Element(scope_min));
|
| 210 |
+
|
| 211 |
+
return true;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
template <typename Element>
|
| 215 |
+
bool initialize_quant_tensor(
|
| 216 |
+
cutlass::DeviceAllocation<Element>& block,
|
| 217 |
+
uint64_t seed = 2023) {
|
| 218 |
+
|
| 219 |
+
float scope_min = float(cutlass::platform::numeric_limits<Element>::lowest());
|
| 220 |
+
float scope_max = float(cutlass::platform::numeric_limits<Element>::max());
|
| 221 |
+
|
| 222 |
+
cutlass::reference::device::BlockFillRandomUniform(
|
| 223 |
+
block.get(), block.size(), seed, Element(scope_max), Element(scope_min));
|
| 224 |
+
|
| 225 |
+
return true;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
template <class QuantType, class Element>
|
| 229 |
+
bool initialize_scale(
|
| 230 |
+
cutlass::DeviceAllocation<Element>& block,
|
| 231 |
+
MixedDtypeOptions const& options,
|
| 232 |
+
uint64_t seed = 2023) {
|
| 233 |
+
|
| 234 |
+
if (options.mode == MixedDtypeGemmMode::ConvertOnly) {
|
| 235 |
+
// No scales, so just initialize with 1 so we can use the same kernel to dequantize the data.
|
| 236 |
+
std::vector<Element> stage(block.size(), Element(1.0f));
|
| 237 |
+
block.copy_from_host(stage.data());
|
| 238 |
+
}
|
| 239 |
+
else {
|
| 240 |
+
float elt_max_f = float(cutlass::platform::numeric_limits<QuantType>::max());
|
| 241 |
+
const float max_dequant_val = 4.f;
|
| 242 |
+
const float min_dequant_val = 0.5f;
|
| 243 |
+
|
| 244 |
+
float scope_max(max_dequant_val / elt_max_f);
|
| 245 |
+
float scope_min(min_dequant_val / elt_max_f);
|
| 246 |
+
|
| 247 |
+
cutlass::reference::device::BlockFillRandomUniform(
|
| 248 |
+
block.get(), block.size(), seed, Element(scope_max), Element(scope_min));
|
| 249 |
+
}
|
| 250 |
+
return true;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
template <class Element>
|
| 254 |
+
bool initialize_zero(
|
| 255 |
+
cutlass::DeviceAllocation<Element>& block,
|
| 256 |
+
MixedDtypeOptions const& options,
|
| 257 |
+
uint64_t seed = 2023) {
|
| 258 |
+
|
| 259 |
+
if (options.mode == MixedDtypeGemmMode::ScaleWithZeroPoint) {
|
| 260 |
+
cutlass::reference::device::BlockFillRandomUniform(
|
| 261 |
+
block.get(), block.size(), seed, Element(2.0f), Element(-2.0f));
|
| 262 |
+
} else {
|
| 263 |
+
// No bias, so just initialize with 1 so we can use the same kernel to dequantize the data.
|
| 264 |
+
std::vector<Element> stage(block.size(), Element(0.0f));
|
| 265 |
+
block.copy_from_host(stage.data());
|
| 266 |
+
}
|
| 267 |
+
return true;
|
| 268 |
+
}
|
| 269 |
+
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/87_blackwell_geforce_gemm_blockwise/utils.h
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2025 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
/// Helper to initialize a block of device data
|
| 33 |
+
template <class Element, class Layout>
|
| 34 |
+
bool initialize_tensor(
|
| 35 |
+
cutlass::TensorView<Element, Layout> view,
|
| 36 |
+
cutlass::Distribution::Kind dist_kind,
|
| 37 |
+
uint64_t seed) {
|
| 38 |
+
|
| 39 |
+
if (dist_kind == cutlass::Distribution::Uniform) {
|
| 40 |
+
|
| 41 |
+
double scope_max, scope_min;
|
| 42 |
+
int bits_input = cutlass::sizeof_bits<Element>::value;
|
| 43 |
+
|
| 44 |
+
if (bits_input == 1) {
|
| 45 |
+
scope_max = 2;
|
| 46 |
+
scope_min = 0;
|
| 47 |
+
}
|
| 48 |
+
else if (bits_input <= 8) {
|
| 49 |
+
scope_max = 2;
|
| 50 |
+
scope_min = -2;
|
| 51 |
+
}
|
| 52 |
+
else if (bits_input == 16) {
|
| 53 |
+
scope_max = 5;
|
| 54 |
+
scope_min = -5;
|
| 55 |
+
}
|
| 56 |
+
else {
|
| 57 |
+
scope_max = 8;
|
| 58 |
+
scope_min = -8;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
cutlass::reference::host::TensorFillRandomUniform(
|
| 62 |
+
view, seed, scope_max, scope_min, 0);
|
| 63 |
+
}
|
| 64 |
+
else if (dist_kind == cutlass::Distribution::AllZeros) {
|
| 65 |
+
cutlass::reference::host::TensorFill(view);
|
| 66 |
+
}
|
| 67 |
+
else if (dist_kind == cutlass::Distribution::Identity) {
|
| 68 |
+
|
| 69 |
+
cutlass::reference::host::TensorFillIdentity(view);
|
| 70 |
+
}
|
| 71 |
+
else if (dist_kind == cutlass::Distribution::Gaussian) {
|
| 72 |
+
|
| 73 |
+
cutlass::reference::host::TensorFillRandomGaussian(view, seed, 0, 0.5);
|
| 74 |
+
}
|
| 75 |
+
else if (dist_kind == cutlass::Distribution::Sequential) {
|
| 76 |
+
cutlass::reference::host::BlockFillSequential(view.data(), view.capacity());
|
| 77 |
+
}
|
| 78 |
+
else {
|
| 79 |
+
throw std::runtime_error("Not implementated.");
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
return true;
|
| 83 |
+
}
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_bwd_tma_warpspecialized.hpp
ADDED
|
@@ -0,0 +1,863 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/gemm/collective/collective_builder.hpp"
|
| 36 |
+
|
| 37 |
+
#include "../collective/fmha_common.hpp"
|
| 38 |
+
#include "../collective/fmha_collective_load.hpp"
|
| 39 |
+
#include "../collective/fmha_collective_softmax.hpp"
|
| 40 |
+
#include "../kernel/fmha_options.hpp"
|
| 41 |
+
|
| 42 |
+
namespace cutlass::fmha::collective {
|
| 43 |
+
|
| 44 |
+
template<
|
| 45 |
+
typename Element_,
|
| 46 |
+
typename ElementAccumulator_,
|
| 47 |
+
typename TileShape_, // BlockQO, BlockKV, BlockHead
|
| 48 |
+
class Fusion,
|
| 49 |
+
class... Options
|
| 50 |
+
>
|
| 51 |
+
struct FmhaBwdMainloopTmaWarpSpecialized {
|
| 52 |
+
|
| 53 |
+
using Element = Element_;
|
| 54 |
+
using ElementAccumulator = ElementAccumulator_;
|
| 55 |
+
using TileShape = TileShape_;
|
| 56 |
+
|
| 57 |
+
static constexpr bool kIsPersistent = false;
|
| 58 |
+
|
| 59 |
+
static const int NumLoadWarpGroups = 1;
|
| 60 |
+
static constexpr int NumMmaWarpGroups = 2;
|
| 61 |
+
static constexpr int StageCountQ = 2 /*K, V*/ * NumMmaWarpGroups;
|
| 62 |
+
static constexpr int StageCount = 2 /*Q, dO*/ * 2 /* actual stages */;
|
| 63 |
+
|
| 64 |
+
static const int kOuterLoads = 2;
|
| 65 |
+
using StagesQ = cutlass::gemm::collective::StageCount<StageCountQ>;
|
| 66 |
+
using Stages = cutlass::gemm::collective::StageCount<StageCount>;
|
| 67 |
+
using ClusterShape = Shape<_1, _1, _1>;
|
| 68 |
+
static_assert(StagesQ::value >= 2);
|
| 69 |
+
static_assert(Stages::value >= 2 * NumMmaWarpGroups);
|
| 70 |
+
|
| 71 |
+
// 16B alignment lets us use TMA
|
| 72 |
+
static constexpr int Alignment = 16 / sizeof(Element);
|
| 73 |
+
|
| 74 |
+
using TileShapeNM = Shape< // (N,M,D)
|
| 75 |
+
decltype(tuple_element_t<1, TileShape>{} / Int<NumMmaWarpGroups>{}),
|
| 76 |
+
tuple_element_t<0, TileShape>,
|
| 77 |
+
tuple_element_t<2, TileShape>>;
|
| 78 |
+
|
| 79 |
+
using TileShapeND = decltype(select<0,2,1>(TileShapeNM{})); // (N,D,M)
|
| 80 |
+
|
| 81 |
+
using TileShapeMD = decltype(select<2,1,0>(TileShapeND{})); // (M,D,N)
|
| 82 |
+
|
| 83 |
+
using CollectiveMmaNM = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 84 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 85 |
+
Element, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment,
|
| 86 |
+
Element, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment,
|
| 87 |
+
ElementAccumulator,
|
| 88 |
+
TileShapeNM, ClusterShape, Stages,
|
| 89 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 90 |
+
|
| 91 |
+
using CollectiveMmaND = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 92 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 93 |
+
Element, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment, // from register, doesn't matter
|
| 94 |
+
Element, cute::tuple<_1, int, cute::tuple<int, int>>, Alignment,
|
| 95 |
+
ElementAccumulator,
|
| 96 |
+
TileShapeND, ClusterShape, Stages,
|
| 97 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 98 |
+
|
| 99 |
+
using CollectiveMmaND_SS = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 100 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 101 |
+
Element, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment, // from register, doesn't matter
|
| 102 |
+
Element, cute::tuple<_1, int, cute::tuple<int, int>>, Alignment,
|
| 103 |
+
ElementAccumulator,
|
| 104 |
+
TileShapeND, ClusterShape, Stages,
|
| 105 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
using CollectiveMmaMD = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 109 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 110 |
+
Element, cute::tuple<_1, int, cute::tuple<int, int>>, Alignment, // from smem, might matter (?)
|
| 111 |
+
Element, cute::tuple<_1, int, cute::tuple<int, int>>, Alignment,
|
| 112 |
+
ElementAccumulator,
|
| 113 |
+
TileShapeMD, ClusterShape, Stages,
|
| 114 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 115 |
+
|
| 116 |
+
using TiledMmaNM = typename CollectiveMmaNM::TiledMma;
|
| 117 |
+
using TiledMmaND_SS = typename CollectiveMmaND_SS::TiledMma;
|
| 118 |
+
using TiledMmaND_RS = decltype(convert_to_gmma_rs(typename CollectiveMmaND::TiledMma{}));
|
| 119 |
+
using TiledMmaND = TiledMmaND_RS;
|
| 120 |
+
using TiledMmaMD = typename CollectiveMmaMD::TiledMma;
|
| 121 |
+
|
| 122 |
+
using SmemLayoutQ = typename CollectiveMmaNM::SmemLayoutB;
|
| 123 |
+
using SmemLayoutK = typename CollectiveMmaNM::SmemLayoutA;
|
| 124 |
+
using SmemLayoutV = typename CollectiveMmaNM::SmemLayoutA;
|
| 125 |
+
using SmemLayoutDO = typename CollectiveMmaNM::SmemLayoutB;
|
| 126 |
+
|
| 127 |
+
//using SmemLayoutDQ = Layout<
|
| 128 |
+
// Shape<
|
| 129 |
+
// tuple_element_t<0, TileShapeMD>,
|
| 130 |
+
// Shape<_2, _4, decltype(tuple_element_t<1, TileShapeMD>{} / _8{})>,
|
| 131 |
+
// _2
|
| 132 |
+
// >,
|
| 133 |
+
// Stride<
|
| 134 |
+
// _4,
|
| 135 |
+
// Stride<decltype(tuple_element_t<0, TileShapeMD>{} * _4{}), _1, decltype(tuple_element_t<0, TileShapeMD>{} * _8{})>,
|
| 136 |
+
// decltype(tuple_element_t<0, TileShapeMD>{} * tuple_element_t<1, TileShapeMD>{})
|
| 137 |
+
// >>;
|
| 138 |
+
|
| 139 |
+
using SmemLayoutDQ_0 = Layout<
|
| 140 |
+
Shape<
|
| 141 |
+
tuple_element_t<0, TileShapeMD>,
|
| 142 |
+
tuple_element_t<1, TileShapeMD>,
|
| 143 |
+
_2
|
| 144 |
+
>,
|
| 145 |
+
Stride<
|
| 146 |
+
tuple_element_t<1, TileShapeMD>,
|
| 147 |
+
_1,
|
| 148 |
+
decltype(tuple_element_t<0, TileShapeMD>{} * tuple_element_t<1, TileShapeMD>{})
|
| 149 |
+
>>;
|
| 150 |
+
|
| 151 |
+
using SmemAtomDQ = decltype(cutlass::gemm::collective::detail::ss_smem_selector<
|
| 152 |
+
cute::GMMA::Major::K, ElementAccumulator, tuple_element_t<0, TileShapeMD>, tuple_element_t<1, TileShapeMD>>());
|
| 153 |
+
using SmemLayoutDQ_1 = decltype(tile_to_shape(SmemAtomDQ{}, make_shape(get<0>(TileShapeMD{}), get<1>(TileShapeMD{}), _2{}), Step<_2, _1, _3>{}));
|
| 154 |
+
using SmemLayoutDQ = SmemLayoutDQ_1;
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
using PipelineDQ = cutlass::PipelineAsync<2>;
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
using SmemLayoutDS_0 = decltype(unstageSmemLayout(typename CollectiveMmaMD::SmemLayoutA{}, Int<NumMmaWarpGroups>{}));
|
| 161 |
+
|
| 162 |
+
using SmemLayoutDS = decltype(tile_to_shape(GMMA::Layout_MN_INTER_Atom<Element>{}, make_shape(size<0>(SmemLayoutDS_0{}), size<1>(SmemLayoutDS_0{}), size<2>(SmemLayoutDS_0{})), Step<_1, _2, _3>{}));
|
| 163 |
+
using SmemLayoutKp = typename CollectiveMmaMD::SmemLayoutB;
|
| 164 |
+
|
| 165 |
+
using SmemLayoutQp = typename CollectiveMmaND::SmemLayoutB;
|
| 166 |
+
using SmemLayoutDOp = typename CollectiveMmaND::SmemLayoutB;
|
| 167 |
+
|
| 168 |
+
using SmemLayoutLSE = Layout<Shape<tuple_element_t<1, TileShapeNM>, Int<StageCount>>>;
|
| 169 |
+
|
| 170 |
+
using MainloopPipeline = cutlass::PipelineTmaAsync<Stages::value>;
|
| 171 |
+
using MainloopPipelineQ = cutlass::PipelineTmaAsync<StagesQ::value>;
|
| 172 |
+
|
| 173 |
+
using PipelineState = typename cutlass::PipelineState<MainloopPipeline::Stages>;
|
| 174 |
+
using PipelineStateQ = typename cutlass::PipelineState<MainloopPipelineQ::Stages>;
|
| 175 |
+
|
| 176 |
+
using TileShapePV = TileShapeND; // To work with the kernel level
|
| 177 |
+
using TiledMmaPV = TiledMmaND;
|
| 178 |
+
|
| 179 |
+
static constexpr int kInnerLoadBytes = size(SmemLayoutQ{}(_,_,_0{})) * sizeof(Element) + size(SmemLayoutLSE{}(_,_0{})) * sizeof(ElementAccumulator);
|
| 180 |
+
static constexpr int kOuterLoadBytes = size(SmemLayoutK{}(_,_,_0{})) * sizeof(Element);
|
| 181 |
+
|
| 182 |
+
struct SharedStorage {
|
| 183 |
+
// One for each consumer WG
|
| 184 |
+
union {
|
| 185 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutK>> smem_k;
|
| 186 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutKp>> smem_kp;
|
| 187 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutV>> smem_v;
|
| 188 |
+
};
|
| 189 |
+
|
| 190 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutDS>> smem_ds;
|
| 191 |
+
|
| 192 |
+
// Loaded by producer, consumed by both WGs
|
| 193 |
+
union {
|
| 194 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutQ>> smem_q;
|
| 195 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutDO>> smem_do;
|
| 196 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutQp>> smem_qp;
|
| 197 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutDOp>> smem_dop;
|
| 198 |
+
};
|
| 199 |
+
|
| 200 |
+
// Accumulated into by both consumers, potentially loaded, potentially written
|
| 201 |
+
cute::array_aligned<ElementAccumulator, cute::cosize_v<SmemLayoutDQ>> smem_dq;
|
| 202 |
+
|
| 203 |
+
union {
|
| 204 |
+
cute::array_aligned<ElementAccumulator, cute::cosize_v<SmemLayoutLSE>> smem_lse;
|
| 205 |
+
cute::array_aligned<ElementAccumulator, cute::cosize_v<SmemLayoutLSE>> smem_sumOdO;
|
| 206 |
+
};
|
| 207 |
+
};
|
| 208 |
+
|
| 209 |
+
struct Arguments {
|
| 210 |
+
const Element* ptr_Q;
|
| 211 |
+
cute::tuple<int, int, int, _1> dQ;
|
| 212 |
+
const Element* ptr_K;
|
| 213 |
+
cute::tuple<int, int, int, _1> dK;
|
| 214 |
+
const Element* ptr_V;
|
| 215 |
+
cute::tuple<int, int, int, _1> dV;
|
| 216 |
+
|
| 217 |
+
const Element* ptr_dO;
|
| 218 |
+
cute::tuple<int, int, int, _1> dDO;
|
| 219 |
+
|
| 220 |
+
const ElementAccumulator* ptr_LSE;
|
| 221 |
+
cute::tuple<int, int, _1> dLSE;
|
| 222 |
+
const ElementAccumulator* ptr_sum_OdO;
|
| 223 |
+
cute::tuple<int, int, _1> dSumOdO;
|
| 224 |
+
|
| 225 |
+
ElementAccumulator* ptr_dQ;
|
| 226 |
+
cute::tuple<int, int, int, _1> dDQ;
|
| 227 |
+
};
|
| 228 |
+
|
| 229 |
+
using TMA_Q = typename CollectiveMmaNM::Params::TMA_B;
|
| 230 |
+
using TMA_K = typename CollectiveMmaNM::Params::TMA_A;
|
| 231 |
+
using TMA_V = typename CollectiveMmaNM::Params::TMA_A;
|
| 232 |
+
using TMA_DO = typename CollectiveMmaNM::Params::TMA_B;
|
| 233 |
+
|
| 234 |
+
using TMA_LSE = decltype(make_tma_copy(SM90_TMA_LOAD{}, make_tensor((const ElementAccumulator*)nullptr, make_shape(1, 1, 1), make_stride(_1{}, 0, 0)), SmemLayoutLSE{}(_,_0{})));
|
| 235 |
+
using TMA_ODO = TMA_LSE;
|
| 236 |
+
|
| 237 |
+
using TMA_DQ = decltype(make_tma_copy(SM90_TMA_REDUCE_ADD{}, make_tensor((const ElementAccumulator*)nullptr, make_shape(1, 1, 1, 1), make_stride(0, _1{}, 0, 0)), SmemLayoutDQ{}(_,_,_0{})));
|
| 238 |
+
|
| 239 |
+
using LoadQ = CollectiveLoadTma<
|
| 240 |
+
LoadKind::kBwdM,
|
| 241 |
+
MainloopPipeline,
|
| 242 |
+
Element,
|
| 243 |
+
SmemLayoutQ,
|
| 244 |
+
TMA_Q
|
| 245 |
+
>;
|
| 246 |
+
|
| 247 |
+
using LoadK = CollectiveLoadTma<
|
| 248 |
+
LoadKind::kBwdN,
|
| 249 |
+
MainloopPipelineQ,
|
| 250 |
+
Element,
|
| 251 |
+
SmemLayoutK,
|
| 252 |
+
TMA_K
|
| 253 |
+
>;
|
| 254 |
+
|
| 255 |
+
using LoadV = CollectiveLoadTma<
|
| 256 |
+
LoadKind::kBwdN,
|
| 257 |
+
MainloopPipelineQ,
|
| 258 |
+
Element,
|
| 259 |
+
SmemLayoutV,
|
| 260 |
+
TMA_V
|
| 261 |
+
>;
|
| 262 |
+
|
| 263 |
+
using LoadDO = CollectiveLoadTma<
|
| 264 |
+
LoadKind::kBwdM,
|
| 265 |
+
MainloopPipeline,
|
| 266 |
+
Element,
|
| 267 |
+
SmemLayoutDO,
|
| 268 |
+
TMA_DO
|
| 269 |
+
>;
|
| 270 |
+
|
| 271 |
+
using LoadLSE = CollectiveLoadTma<
|
| 272 |
+
LoadKind::kBwdScalar,
|
| 273 |
+
MainloopPipeline,
|
| 274 |
+
ElementAccumulator,
|
| 275 |
+
SmemLayoutLSE,
|
| 276 |
+
TMA_LSE
|
| 277 |
+
>;
|
| 278 |
+
|
| 279 |
+
using LoadODO = CollectiveLoadTma<
|
| 280 |
+
LoadKind::kBwdScalar,
|
| 281 |
+
MainloopPipeline,
|
| 282 |
+
ElementAccumulator,
|
| 283 |
+
SmemLayoutLSE,
|
| 284 |
+
TMA_ODO
|
| 285 |
+
>;
|
| 286 |
+
|
| 287 |
+
struct Params {
|
| 288 |
+
TMA_Q tma_load_q;
|
| 289 |
+
TMA_K tma_load_k;
|
| 290 |
+
TMA_V tma_load_v;
|
| 291 |
+
TMA_DO tma_load_do;
|
| 292 |
+
|
| 293 |
+
TMA_LSE tma_load_lse;
|
| 294 |
+
TMA_ODO tma_load_odo;
|
| 295 |
+
|
| 296 |
+
TMA_DQ tma_red_dq;
|
| 297 |
+
|
| 298 |
+
float scale_softmax;
|
| 299 |
+
float scale_softmax_log2;
|
| 300 |
+
};
|
| 301 |
+
|
| 302 |
+
static_assert(size(TiledMmaNM{}) == size(TiledMmaND{}));
|
| 303 |
+
static_assert(size(TiledMmaNM{}) == size(TiledMmaMD{}));
|
| 304 |
+
|
| 305 |
+
template<class ProblemShape>
|
| 306 |
+
static bool can_implement(ProblemShape const& problem_size, Arguments const& args) {
|
| 307 |
+
return true
|
| 308 |
+
&& (get<4>(problem_size) <= get<2>(TileShape{}))
|
| 309 |
+
&& ((get<4>(problem_size) % Alignment) == 0)
|
| 310 |
+
&& ((get<2>(problem_size) % Alignment) == 0)
|
| 311 |
+
;
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
template<class ProblemShape>
|
| 315 |
+
static Params to_underlying_arguments(ProblemShape const& problem_size, Arguments const& args, void* workspace) {
|
| 316 |
+
auto problem_shape_nm = make_shape(get<3>(problem_size), get<2>(problem_size), get<4>(problem_size), make_shape(get<0>(problem_size), get<1>(problem_size)));
|
| 317 |
+
|
| 318 |
+
auto dK = make_stride(get<2>(args.dK), get<3>(args.dK), make_stride(get<0>(args.dK), get<1>(args.dK)));
|
| 319 |
+
auto dQ = make_stride(get<2>(args.dQ), get<3>(args.dQ), make_stride(get<0>(args.dQ), get<1>(args.dQ)));
|
| 320 |
+
auto params_nm_kq = CollectiveMmaNM::to_underlying_arguments(problem_shape_nm,
|
| 321 |
+
typename CollectiveMmaNM::Arguments {
|
| 322 |
+
args.ptr_K, dK,
|
| 323 |
+
args.ptr_Q, dQ,
|
| 324 |
+
}, /*workspace=*/ nullptr);
|
| 325 |
+
|
| 326 |
+
auto dV = make_stride(get<2>(args.dV), get<3>(args.dV), make_stride(get<0>(args.dV), get<1>(args.dV)));
|
| 327 |
+
auto dDO = make_stride(get<2>(args.dDO), get<3>(args.dDO), make_stride(get<0>(args.dDO), get<1>(args.dDO)));
|
| 328 |
+
auto params_nm_vdo = CollectiveMmaNM::to_underlying_arguments(problem_shape_nm,
|
| 329 |
+
typename CollectiveMmaNM::Arguments {
|
| 330 |
+
args.ptr_V, dV,
|
| 331 |
+
args.ptr_dO, dDO,
|
| 332 |
+
}, /*workspace=*/ nullptr);
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
TMA_LSE tma_load_lse = make_tma_copy(SM90_TMA_LOAD{}, make_tensor(args.ptr_LSE, select<2,0,1>(problem_size), select<2,0,1>(args.dLSE)), SmemLayoutLSE{}(_,_0{}));
|
| 336 |
+
TMA_ODO tma_load_odo = make_tma_copy(SM90_TMA_LOAD{}, make_tensor(args.ptr_sum_OdO, select<2,0,1>(problem_size), select<2,0,1>(args.dSumOdO)), SmemLayoutLSE{}(_,_0{}));
|
| 337 |
+
|
| 338 |
+
TMA_DQ tma_red_dq = make_tma_copy(SM90_TMA_REDUCE_ADD{}, make_tensor(args.ptr_dQ, select<2,4,0,1>(problem_size), select<2,3,0,1>(args.dDQ)), SmemLayoutDQ{}(_,_,_0{}));
|
| 339 |
+
|
| 340 |
+
return Params{
|
| 341 |
+
params_nm_kq.tma_load_b,
|
| 342 |
+
params_nm_kq.tma_load_a,
|
| 343 |
+
params_nm_vdo.tma_load_a,
|
| 344 |
+
params_nm_vdo.tma_load_b,
|
| 345 |
+
tma_load_lse, tma_load_odo,
|
| 346 |
+
tma_red_dq,
|
| 347 |
+
1.0f / (float) std::sqrt(get<4>(problem_size)),
|
| 348 |
+
(float) (std::log2(std::exp(1.0)) / std::sqrt(get<4>(problem_size)))
|
| 349 |
+
};
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
template<class BlkCoord, class ProblemSize>
|
| 353 |
+
CUTLASS_DEVICE
|
| 354 |
+
auto
|
| 355 |
+
get_inner_tile_count(BlkCoord const& blk_coord, ProblemSize const& problem_size) {
|
| 356 |
+
return Fusion{}.get_trip_count(blk_coord, TileShape{}, problem_size);
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
CUTLASS_DEVICE
|
| 360 |
+
static void prefetch_tma_descriptors(Params const& params) {
|
| 361 |
+
cute::prefetch_tma_descriptor(params.tma_load_q.get_tma_descriptor());
|
| 362 |
+
cute::prefetch_tma_descriptor(params.tma_load_k.get_tma_descriptor());
|
| 363 |
+
cute::prefetch_tma_descriptor(params.tma_load_v.get_tma_descriptor());
|
| 364 |
+
cute::prefetch_tma_descriptor(params.tma_load_do.get_tma_descriptor());
|
| 365 |
+
cute::prefetch_tma_descriptor(params.tma_load_odo.get_tma_descriptor());
|
| 366 |
+
cute::prefetch_tma_descriptor(params.tma_load_lse.get_tma_descriptor());
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
template<bool kLoadOuter, class BlkCoord, class ProblemShape, class LoadWarpBarrier>
|
| 370 |
+
CUTLASS_DEVICE void
|
| 371 |
+
load_kv_maybe_q(
|
| 372 |
+
int block_rank_in_cluster,
|
| 373 |
+
BlkCoord const& blk_coord, Params const& params, ProblemShape const& problem_size,
|
| 374 |
+
MainloopPipeline& pipeline_inner, PipelineState& smem_pipe_write_inner,
|
| 375 |
+
MainloopPipelineQ& pipeline_outer, PipelineStateQ& smem_pipe_write_outer,
|
| 376 |
+
SharedStorage& storage,
|
| 377 |
+
LoadWarpBarrier& load_warp_barrier, bool do_barrier)
|
| 378 |
+
{
|
| 379 |
+
// Load pattern:
|
| 380 |
+
// K0 V0 K1 V1
|
| 381 |
+
// Q0 DO0 Q1 DO1 Q2 DO2 ...
|
| 382 |
+
// K0 Q0 V0 K1 DO0 V1 ...
|
| 383 |
+
int lane_predicate = cute::elect_one_sync();
|
| 384 |
+
|
| 385 |
+
int outer_tile_count = NumMmaWarpGroups;
|
| 386 |
+
int inner_tile_count = get_inner_tile_count(blk_coord, problem_size);
|
| 387 |
+
|
| 388 |
+
auto outer_tile_iter = cute::make_coord_iterator(outer_tile_count);
|
| 389 |
+
auto inner_tile_iter = cute::make_coord_iterator(inner_tile_count);
|
| 390 |
+
|
| 391 |
+
uint16_t mcast_mask_b = 0;
|
| 392 |
+
|
| 393 |
+
LoadQ load_q{params.tma_load_q, pipeline_inner, storage.smem_q};
|
| 394 |
+
auto load_state_q = load_q.init_state(block_rank_in_cluster, problem_size, TileShapeNM{}, blk_coord, inner_tile_count);
|
| 395 |
+
|
| 396 |
+
LoadDO load_do{params.tma_load_do, pipeline_inner, storage.smem_do};
|
| 397 |
+
auto load_state_do = load_do.init_state(block_rank_in_cluster, problem_size, TileShapeNM{}, blk_coord, inner_tile_count);
|
| 398 |
+
|
| 399 |
+
LoadK load_k{params.tma_load_k, pipeline_outer, storage.smem_k};
|
| 400 |
+
auto load_state_k = load_k.init_state(_0{}, problem_size, TileShapeNM{}, blk_coord, outer_tile_count);
|
| 401 |
+
|
| 402 |
+
LoadV load_v{params.tma_load_v, pipeline_outer, storage.smem_v};
|
| 403 |
+
auto load_state_v = load_v.init_state(_0{}, problem_size, TileShapeNM{}, blk_coord, outer_tile_count);
|
| 404 |
+
|
| 405 |
+
LoadLSE load_lse{params.tma_load_lse, pipeline_inner, storage.smem_lse};
|
| 406 |
+
auto load_state_lse = load_lse.init_state(_0{}, problem_size, TileShapeNM{}, blk_coord, outer_tile_count);
|
| 407 |
+
|
| 408 |
+
LoadODO load_odo{params.tma_load_odo, pipeline_inner, storage.smem_sumOdO};
|
| 409 |
+
auto load_state_odo = load_odo.init_state(_0{}, problem_size, TileShapeNM{}, blk_coord, outer_tile_count);
|
| 410 |
+
|
| 411 |
+
outer_tile_count *= 2; // K & V
|
| 412 |
+
inner_tile_count *= 4; // Q & dO & LSE & sumOdO
|
| 413 |
+
|
| 414 |
+
while (inner_tile_count > 0) {
|
| 415 |
+
if (Fusion{}.is_contributing(make_coord(*inner_tile_iter, get<1>(blk_coord)), TileShape{}, problem_size)) {
|
| 416 |
+
break;
|
| 417 |
+
}
|
| 418 |
+
inner_tile_count -= 4;
|
| 419 |
+
++inner_tile_iter;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
if constexpr (kLoadOuter) {
|
| 423 |
+
load_k.template step<false>(outer_tile_iter, load_state_k, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
load_q.template step<false,false,true>(inner_tile_iter, load_state_q, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 427 |
+
load_lse.template step<false,true,false>(inner_tile_iter, load_state_lse, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 428 |
+
|
| 429 |
+
if constexpr (! kLoadOuter) {
|
| 430 |
+
if (do_barrier) {
|
| 431 |
+
load_warp_barrier.arrive();
|
| 432 |
+
load_warp_barrier.wait(/*phase=*/ 0);
|
| 433 |
+
do_barrier = false;
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
if constexpr (kLoadOuter) {
|
| 438 |
+
load_v.template step<true>(outer_tile_iter, load_state_v, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 439 |
+
load_k.template step<false>(outer_tile_iter, load_state_k, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
load_do.template step<false,false,true>(inner_tile_iter, load_state_do, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 443 |
+
load_odo.template step<true,true,false>(inner_tile_iter, load_state_odo, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 444 |
+
|
| 445 |
+
if constexpr (kLoadOuter) {
|
| 446 |
+
load_v.template step<true>(outer_tile_iter, load_state_v, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
if constexpr (kLoadOuter) {
|
| 450 |
+
while (outer_tile_count > 0) {
|
| 451 |
+
load_k.template step<false>(outer_tile_iter, load_state_k, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 452 |
+
load_v.template step<true>(outer_tile_iter, load_state_v, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 453 |
+
}
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 457 |
+
while (inner_tile_count > 0) {
|
| 458 |
+
while (inner_tile_count > 0) {
|
| 459 |
+
if (Fusion{}.is_contributing(make_coord(*inner_tile_iter, get<1>(blk_coord)), TileShape{}, problem_size)) {
|
| 460 |
+
break;
|
| 461 |
+
}
|
| 462 |
+
inner_tile_count -= 4;
|
| 463 |
+
++inner_tile_iter;
|
| 464 |
+
}
|
| 465 |
+
load_q.template step<false,false,true>(inner_tile_iter, load_state_q, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 466 |
+
load_lse.template step<false,true,false>(inner_tile_iter, load_state_lse, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 467 |
+
|
| 468 |
+
load_do.template step<false,false,true>(inner_tile_iter, load_state_do, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 469 |
+
load_odo.template step<true,true,false>(inner_tile_iter, load_state_odo, smem_pipe_write_inner, lane_predicate, inner_tile_count, mcast_mask_b);
|
| 470 |
+
}
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
template<class BlkCoord, class ProblemShape, class LoadWarpBarrier>
|
| 474 |
+
CUTLASS_DEVICE void
|
| 475 |
+
load_maybe_q(
|
| 476 |
+
BlkCoord const& blk_coord, Params const& params, ProblemShape const& problem_size,
|
| 477 |
+
MainloopPipelineQ& pipeline_outer, PipelineStateQ& smem_pipe_write_outer,
|
| 478 |
+
SharedStorage& storage,
|
| 479 |
+
LoadWarpBarrier& load_warp_barrier, bool do_barrier)
|
| 480 |
+
{
|
| 481 |
+
// Load pattern:
|
| 482 |
+
// K0 V0 K1 V1
|
| 483 |
+
// Q0 DO0 Q1 DO1 Q2 DO2 ...
|
| 484 |
+
// K0 Q0 V0 K1 DO0 V1 ...
|
| 485 |
+
int lane_predicate = cute::elect_one_sync();
|
| 486 |
+
|
| 487 |
+
int outer_tile_count = NumMmaWarpGroups;
|
| 488 |
+
|
| 489 |
+
auto outer_tile_iter = cute::make_coord_iterator(outer_tile_count);
|
| 490 |
+
|
| 491 |
+
LoadK load_k{params.tma_load_k, pipeline_outer, storage.smem_k};
|
| 492 |
+
auto load_state_k = load_k.init_state(_0{}, problem_size, TileShapeNM{}, blk_coord, outer_tile_count);
|
| 493 |
+
|
| 494 |
+
LoadV load_v{params.tma_load_v, pipeline_outer, storage.smem_v};
|
| 495 |
+
auto load_state_v = load_v.init_state(_0{}, problem_size, TileShapeNM{}, blk_coord, outer_tile_count);
|
| 496 |
+
|
| 497 |
+
outer_tile_count *= 2; // K & V
|
| 498 |
+
|
| 499 |
+
load_k.template step<false>(outer_tile_iter, load_state_k, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 500 |
+
|
| 501 |
+
if (do_barrier) {
|
| 502 |
+
load_warp_barrier.arrive();
|
| 503 |
+
load_warp_barrier.wait(/*phase=*/ 0);
|
| 504 |
+
do_barrier = false;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
load_v.template step<true>(outer_tile_iter, load_state_v, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 508 |
+
|
| 509 |
+
while (outer_tile_count > 0) {
|
| 510 |
+
load_k.template step<false>(outer_tile_iter, load_state_k, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 511 |
+
load_v.template step<true>(outer_tile_iter, load_state_v, smem_pipe_write_outer, lane_predicate, outer_tile_count);
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
template<class BlkCoord, class ProblemShape, class MainloopPipelineReducer, class PipelineStateReducer>
|
| 516 |
+
CUTLASS_DEVICE void
|
| 517 |
+
reduce(
|
| 518 |
+
BlkCoord const& blk_coord, Params const& params, ProblemShape const& problem_size,
|
| 519 |
+
MainloopPipelineReducer& pipeline_reducer, PipelineStateReducer& smem_pipe_read_reducer,
|
| 520 |
+
SharedStorage& storage)
|
| 521 |
+
{
|
| 522 |
+
int lane_predicate = cute::elect_one_sync();
|
| 523 |
+
|
| 524 |
+
Tensor mDQ_full = params.tma_red_dq.get_tma_tensor(select<2,4,0,1>(problem_size));
|
| 525 |
+
Tensor gDQ_full = local_tile(mDQ_full, TileShapeMD{}, make_coord(_, _, _), Step<_1, _1, Underscore>{});
|
| 526 |
+
Tensor gDQ = gDQ_full(_, _, _, _0{}, get<2,0>(blk_coord), get<2,1>(blk_coord));
|
| 527 |
+
Tensor sDQ = make_tensor(make_smem_ptr(storage.smem_dq.data()), SmemLayoutDQ{});
|
| 528 |
+
|
| 529 |
+
auto block_tma = params.tma_red_dq.get_slice(_0{});
|
| 530 |
+
|
| 531 |
+
Tensor tDQsDQ = block_tma.partition_S(sDQ);
|
| 532 |
+
Tensor tDQgDQ = block_tma.partition_D(gDQ);
|
| 533 |
+
|
| 534 |
+
int inner_tile_count = get_inner_tile_count(blk_coord, problem_size);
|
| 535 |
+
int g_index = 0;
|
| 536 |
+
|
| 537 |
+
auto smem_pipe_release_reducer = smem_pipe_read_reducer;
|
| 538 |
+
bool first = true;
|
| 539 |
+
while (inner_tile_count > 0) {
|
| 540 |
+
while (inner_tile_count > 0) {
|
| 541 |
+
if (Fusion{}.is_contributing(make_coord(g_index, get<1>(blk_coord)), TileShape{}, problem_size)) {
|
| 542 |
+
break;
|
| 543 |
+
}
|
| 544 |
+
inner_tile_count -= 1;
|
| 545 |
+
++g_index;
|
| 546 |
+
}
|
| 547 |
+
if (inner_tile_count == 0) break;
|
| 548 |
+
|
| 549 |
+
pipeline_reducer.consumer_wait(smem_pipe_read_reducer);
|
| 550 |
+
if (lane_predicate == 1) {
|
| 551 |
+
tma_store_wait<1>();
|
| 552 |
+
}
|
| 553 |
+
if (! first) {
|
| 554 |
+
pipeline_reducer.consumer_release(smem_pipe_release_reducer);
|
| 555 |
+
++smem_pipe_release_reducer;
|
| 556 |
+
} else {
|
| 557 |
+
first = false;
|
| 558 |
+
}
|
| 559 |
+
if (lane_predicate == 1) {
|
| 560 |
+
copy(params.tma_red_dq, tDQsDQ(_,_,_,smem_pipe_read_reducer.index()), tDQgDQ(_,_,_,g_index));
|
| 561 |
+
tma_store_arrive();
|
| 562 |
+
}
|
| 563 |
+
++smem_pipe_read_reducer;
|
| 564 |
+
--inner_tile_count;
|
| 565 |
+
++g_index;
|
| 566 |
+
}
|
| 567 |
+
if (lane_predicate) {
|
| 568 |
+
tma_store_wait<0>();
|
| 569 |
+
}
|
| 570 |
+
pipeline_reducer.consumer_release(smem_pipe_release_reducer);
|
| 571 |
+
++smem_pipe_release_reducer;
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
template<class BlkCoord, class ProblemShape, class MainloopPipelineReducer, class PipelineStateReducer, class MathWgOrderBarrier>
|
| 575 |
+
CUTLASS_DEVICE auto
|
| 576 |
+
compute(
|
| 577 |
+
BlkCoord const& blk_coord, BlkCoord const& wg_coord,
|
| 578 |
+
Params const& params, ProblemShape const& problem_size,
|
| 579 |
+
MainloopPipeline& pipeline_inner, PipelineState& smem_pipe_read_inner,
|
| 580 |
+
MainloopPipelineQ& pipeline_outer, PipelineStateQ& smem_pipe_read_outer,
|
| 581 |
+
MainloopPipelineReducer& pipeline_reducer, PipelineStateReducer& smem_pipe_write_reducer,
|
| 582 |
+
SharedStorage& storage,
|
| 583 |
+
MathWgOrderBarrier& math_wg_order_barrier)
|
| 584 |
+
{
|
| 585 |
+
TiledMmaND tiled_mma_nd;
|
| 586 |
+
|
| 587 |
+
Tensor acc_DV = partition_fragment_C(tiled_mma_nd, take<0,2>(TileShapeND{}));
|
| 588 |
+
clear(acc_DV);
|
| 589 |
+
|
| 590 |
+
Tensor acc_DK = partition_fragment_C(tiled_mma_nd, take<0,2>(TileShapeND{}));
|
| 591 |
+
clear(acc_DK);
|
| 592 |
+
|
| 593 |
+
int thread_idx = int(threadIdx.x) % cutlass::NumThreadsPerWarpGroup;
|
| 594 |
+
|
| 595 |
+
PipelineState smem_pipe_release_inner = smem_pipe_read_inner;
|
| 596 |
+
|
| 597 |
+
pipeline_outer.consumer_wait(smem_pipe_read_outer);
|
| 598 |
+
PipelineStateQ smem_pipe_read_k = smem_pipe_read_outer;
|
| 599 |
+
++smem_pipe_read_outer;
|
| 600 |
+
pipeline_outer.consumer_wait(smem_pipe_read_outer);
|
| 601 |
+
PipelineStateQ smem_pipe_read_v = smem_pipe_read_outer;
|
| 602 |
+
|
| 603 |
+
int inner_tile_count = get_inner_tile_count(wg_coord, problem_size);
|
| 604 |
+
|
| 605 |
+
TiledMmaNM tiled_mma_nm;
|
| 606 |
+
Tensor sK = make_tensor(make_smem_ptr(storage.smem_k.data()), SmemLayoutK{});
|
| 607 |
+
Tensor sQ = make_tensor(make_smem_ptr(storage.smem_q.data()), SmemLayoutQ{});
|
| 608 |
+
auto thr_mma_nm = tiled_mma_nm.get_thread_slice(thread_idx);
|
| 609 |
+
Tensor tSsK = thr_mma_nm.partition_A(sK);
|
| 610 |
+
Tensor tSsQ = thr_mma_nm.partition_B(sQ);
|
| 611 |
+
Tensor tSrK = thr_mma_nm.make_fragment_A(tSsK);
|
| 612 |
+
Tensor tSrQ = thr_mma_nm.make_fragment_B(tSsQ);
|
| 613 |
+
|
| 614 |
+
Tensor sV = make_tensor(make_smem_ptr(storage.smem_v.data()), SmemLayoutV{});
|
| 615 |
+
Tensor sDO = make_tensor(make_smem_ptr(storage.smem_do.data()), SmemLayoutDO{});
|
| 616 |
+
|
| 617 |
+
Tensor tDPsV = thr_mma_nm.partition_A(sV);
|
| 618 |
+
Tensor tDPsDO = thr_mma_nm.partition_B(sDO);
|
| 619 |
+
Tensor tDPrV = thr_mma_nm.make_fragment_A(tDPsV);
|
| 620 |
+
Tensor tDPrDO = thr_mma_nm.make_fragment_B(tDPsDO);
|
| 621 |
+
|
| 622 |
+
auto thr_mma_nd = tiled_mma_nd.get_thread_slice(thread_idx);
|
| 623 |
+
|
| 624 |
+
Tensor sDOp = make_tensor(make_smem_ptr(storage.smem_dop.data()), SmemLayoutDOp{});
|
| 625 |
+
Tensor tDV_sDO = thr_mma_nd.partition_B(sDOp);
|
| 626 |
+
Tensor tDVrDO = thr_mma_nd.make_fragment_B(tDV_sDO);
|
| 627 |
+
|
| 628 |
+
|
| 629 |
+
Tensor sQp = make_tensor(make_smem_ptr(storage.smem_qp.data()), SmemLayoutQp{});
|
| 630 |
+
Tensor tDK_sQ = thr_mma_nd.partition_B(sQp);
|
| 631 |
+
Tensor tDKrQ = thr_mma_nd.make_fragment_B(tDK_sQ);
|
| 632 |
+
|
| 633 |
+
|
| 634 |
+
int wg_idx = __shfl_sync(0xffffffff, get<1>(wg_coord) % NumMmaWarpGroups, 0);
|
| 635 |
+
|
| 636 |
+
TiledMmaMD tiled_mma_md;
|
| 637 |
+
auto thr_mma_md = tiled_mma_md.get_thread_slice(thread_idx);
|
| 638 |
+
Tensor sDS = make_tensor(make_smem_ptr(storage.smem_ds.data()), SmemLayoutDS{});
|
| 639 |
+
Tensor tDQsDS = thr_mma_md.partition_A(sDS);
|
| 640 |
+
Tensor tDQrDS_full = thr_mma_md.make_fragment_A(tDQsDS);
|
| 641 |
+
Tensor tDQrDS = tDQrDS_full(_,_,_,_);
|
| 642 |
+
Tensor sKp = make_tensor(make_smem_ptr(storage.smem_kp.data()), SmemLayoutKp{});
|
| 643 |
+
Tensor tDQsK = thr_mma_md.partition_B(sKp);
|
| 644 |
+
Tensor tDQrK = thr_mma_md.make_fragment_B(tDQsK);
|
| 645 |
+
|
| 646 |
+
Tensor sLSE = make_tensor(make_smem_ptr(storage.smem_lse.data()), make_shape(get<0>(TileShapeNM{}), get<1>(TileShapeNM{}), Int<StageCount>{}), make_stride(_0{}, _1{}, get<1>(TileShapeNM{})));
|
| 647 |
+
Tensor tSsLSE = thr_mma_nm.partition_C(sLSE);
|
| 648 |
+
|
| 649 |
+
Tensor sODO = make_tensor(make_smem_ptr(storage.smem_sumOdO.data()), make_shape(get<0>(TileShapeNM{}), get<1>(TileShapeNM{}), Int<StageCount>{}), make_stride(_0{}, _1{}, get<1>(TileShapeNM{})));
|
| 650 |
+
Tensor tDPsODO = thr_mma_nm.partition_C(sODO);
|
| 651 |
+
|
| 652 |
+
Tensor cS = make_identity_tensor(take<0,2>(TileShapeNM{}));
|
| 653 |
+
Tensor tScS = thr_mma_nm.partition_C(cS);
|
| 654 |
+
int n_block = get<1>(wg_coord);
|
| 655 |
+
tScS.data() = tScS.data() + E<0>{} * n_block * get<0>(TileShapeNM{});
|
| 656 |
+
|
| 657 |
+
|
| 658 |
+
// Transpose
|
| 659 |
+
Tensor sDSp_full = sDS.compose(make_layout(make_shape(size<1>(sDS), size<0>(sDS), size<2>(sDS)), make_stride(size<0>(sDS), _1{}, size<1>(sDS) * size<0>(sDS))));
|
| 660 |
+
Tensor sDSp = sDSp_full(_,_,_);
|
| 661 |
+
Tensor tDPsDS = thr_mma_nm.partition_C(sDSp);
|
| 662 |
+
|
| 663 |
+
auto thr_mma_nd_ss = TiledMmaND_SS{}.get_thread_slice(thread_idx);
|
| 664 |
+
Tensor tDKsDSp = thr_mma_nd_ss.partition_A(sDSp);
|
| 665 |
+
|
| 666 |
+
Tensor tDKrDSp = thr_mma_nd_ss.make_fragment_A(tDKsDSp);
|
| 667 |
+
|
| 668 |
+
Tensor sDQ = make_tensor(make_smem_ptr(storage.smem_dq.data()), SmemLayoutDQ{});
|
| 669 |
+
auto tDQsDQ_full = thr_mma_md.partition_C(sDQ);
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
auto smem_pipe_read_k_other = smem_pipe_read_k;
|
| 673 |
+
smem_pipe_read_k_other.advance(2);
|
| 674 |
+
|
| 675 |
+
int k_index = 0;
|
| 676 |
+
|
| 677 |
+
while (inner_tile_count > 0) {
|
| 678 |
+
while (inner_tile_count > 0) {
|
| 679 |
+
if (Fusion{}.is_contributing(make_coord(k_index, get<1>(blk_coord)), TileShape{}, problem_size)) {
|
| 680 |
+
break;
|
| 681 |
+
}
|
| 682 |
+
inner_tile_count -= 1;
|
| 683 |
+
tScS.data() = tScS.data() + E<1>{} * get<1>(TileShapeNM{});
|
| 684 |
+
k_index += 1;
|
| 685 |
+
}
|
| 686 |
+
if (inner_tile_count == 0) break;
|
| 687 |
+
|
| 688 |
+
pipeline_inner.consumer_wait(smem_pipe_read_inner);
|
| 689 |
+
PipelineState smem_pipe_read_q = smem_pipe_read_inner;
|
| 690 |
+
++smem_pipe_read_inner;
|
| 691 |
+
PipelineState smem_pipe_read_do = smem_pipe_read_inner;
|
| 692 |
+
++smem_pipe_read_inner;
|
| 693 |
+
|
| 694 |
+
// GEMM KQ -> S
|
| 695 |
+
Tensor acc_S = partition_fragment_C(tiled_mma_nm, take<0,2>(TileShapeNM{}));
|
| 696 |
+
|
| 697 |
+
warpgroup_fence_operand(acc_S);
|
| 698 |
+
warpgroup_arrive();
|
| 699 |
+
gemm_zero_acc(tiled_mma_nm, tSrK(_,_,_,smem_pipe_read_k.index()), tSrQ(_,_,_,smem_pipe_read_q.index()), acc_S);
|
| 700 |
+
warpgroup_commit_batch();
|
| 701 |
+
|
| 702 |
+
pipeline_inner.consumer_wait(smem_pipe_read_do);
|
| 703 |
+
|
| 704 |
+
// GEMM VdO -> dP
|
| 705 |
+
Tensor acc_DP = partition_fragment_C(tiled_mma_nm, take<0,2>(TileShapeNM{}));
|
| 706 |
+
|
| 707 |
+
warpgroup_fence_operand(acc_DP);
|
| 708 |
+
warpgroup_arrive();
|
| 709 |
+
gemm_zero_acc(tiled_mma_nm, tDPrV(_,_,_,smem_pipe_read_v.index()), tDPrDO(_,_,_,smem_pipe_read_do.index()), acc_DP);
|
| 710 |
+
warpgroup_commit_batch();
|
| 711 |
+
|
| 712 |
+
Tensor reg_LSE = make_fragment_like<ElementAccumulator>(acc_S);
|
| 713 |
+
for (int i = 0; i < size(reg_LSE); i++) {
|
| 714 |
+
reg_LSE(i) = ((ElementAccumulator)std::log2(std::exp(1.0))) * tSsLSE(_,_,_,smem_pipe_read_q.index())(i);
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
Tensor reg_ODO = make_fragment_like<ElementAccumulator>(acc_S);
|
| 718 |
+
if constexpr (decltype(get<0>(TileShape{}) != _128{})::value) {
|
| 719 |
+
for (int i = 0; i < size(reg_ODO); i++) {
|
| 720 |
+
reg_ODO(i) = tDPsODO(_,_,_,smem_pipe_read_do.index())(i);
|
| 721 |
+
}
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
warpgroup_wait<1>();
|
| 725 |
+
warpgroup_fence_operand(acc_S);
|
| 726 |
+
|
| 727 |
+
math_wg_order_barrier.wait();
|
| 728 |
+
// Compute S -> P
|
| 729 |
+
Fusion{}.before_softmax(acc_S, tScS, problem_size);
|
| 730 |
+
auto acc_P = make_fragment_like<ElementAccumulator>(acc_S);
|
| 731 |
+
CUTLASS_PRAGMA_UNROLL
|
| 732 |
+
for (int i = 0; i < size(acc_P); i++) {
|
| 733 |
+
acc_P(i) = ::exp2f(params.scale_softmax_log2 * acc_S(i) - reg_LSE(i));
|
| 734 |
+
}
|
| 735 |
+
math_wg_order_barrier.arrive();
|
| 736 |
+
|
| 737 |
+
if constexpr (decltype(get<0>(TileShape{}) == _128{})::value) {
|
| 738 |
+
for (int i = 0; i < size(reg_ODO); i++) {
|
| 739 |
+
reg_ODO(i) = tDPsODO(_,_,_,smem_pipe_read_do.index())(i);
|
| 740 |
+
}
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
warpgroup_wait<0>();
|
| 744 |
+
warpgroup_fence_operand(acc_DP);
|
| 745 |
+
|
| 746 |
+
// Compute dP P -> dS
|
| 747 |
+
auto acc_DS = make_fragment_like<Element>(acc_DP);
|
| 748 |
+
CUTLASS_PRAGMA_UNROLL
|
| 749 |
+
for (int i = 0; i < size(acc_DS); i++) {
|
| 750 |
+
// We could move the scale out and into the respective epilogues (or a final scaling step)
|
| 751 |
+
acc_DS(i) = acc_P(i) * params.scale_softmax * (acc_DP(i) - reg_ODO(i));
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
// GEMM PdO -> dV
|
| 755 |
+
auto op_P = make_acc_into_op<Element>(acc_P, typename TiledMmaND::LayoutA_TV{});
|
| 756 |
+
warpgroup_fence_operand(acc_DV);
|
| 757 |
+
warpgroup_fence_operand(op_P);
|
| 758 |
+
warpgroup_arrive();
|
| 759 |
+
cute::gemm(tiled_mma_nd, op_P, tDVrDO(_,_,_,smem_pipe_read_do.index()), acc_DV);
|
| 760 |
+
warpgroup_commit_batch();
|
| 761 |
+
|
| 762 |
+
// Store dS to smem dS'
|
| 763 |
+
if (wg_idx == 0) math_wg_order_barrier.wait();
|
| 764 |
+
|
| 765 |
+
auto recast_bits = [](auto sz, auto t) {
|
| 766 |
+
return recast<uint_bit_t<decltype(sz)::value>>(t);
|
| 767 |
+
};
|
| 768 |
+
auto tDPsDS_v = recast_bits(Int<sizeof_bits_v<Element> * 2>{}, tDPsDS);
|
| 769 |
+
auto acc_DS_v = recast_bits(Int<sizeof_bits_v<Element> * 2>{}, acc_DS);
|
| 770 |
+
|
| 771 |
+
CUTLASS_PRAGMA_UNROLL
|
| 772 |
+
for (int i = 0; i < size(acc_DS_v); i++) {
|
| 773 |
+
tDPsDS_v(_,_,_,wg_idx)(i) = acc_DS_v(i);
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
cutlass::arch::fence_view_async_shared();
|
| 777 |
+
if (wg_idx == 0) math_wg_order_barrier.arrive();
|
| 778 |
+
|
| 779 |
+
// GEMM dS Q -> dK
|
| 780 |
+
if (wg_idx == 1) {
|
| 781 |
+
|
| 782 |
+
math_wg_order_barrier.wait();
|
| 783 |
+
|
| 784 |
+
// GEMM dS' K -> dQ
|
| 785 |
+
Tensor acc_DQ = partition_fragment_C(tiled_mma_md, take<0,2>(TileShapeMD{}));
|
| 786 |
+
|
| 787 |
+
warpgroup_fence_operand(acc_DQ);
|
| 788 |
+
warpgroup_arrive();
|
| 789 |
+
gemm_zero_acc(tiled_mma_md, tDQrDS(_,_,_,0), tDQrK(_,_,_,smem_pipe_read_k_other.index()), acc_DQ);
|
| 790 |
+
cute::gemm(tiled_mma_md, tDQrDS(_,_,_,1), tDQrK(_,_,_,smem_pipe_read_k.index()), acc_DQ);
|
| 791 |
+
warpgroup_commit_batch();
|
| 792 |
+
|
| 793 |
+
warpgroup_fence_operand(acc_DK);
|
| 794 |
+
warpgroup_arrive();
|
| 795 |
+
cute::gemm(TiledMmaND_SS{}, tDKrDSp(_,_,_,wg_idx), tDKrQ(_,_,_,smem_pipe_read_q.index()), acc_DK);
|
| 796 |
+
warpgroup_commit_batch();
|
| 797 |
+
|
| 798 |
+
warpgroup_wait<1>();
|
| 799 |
+
warpgroup_fence_operand(acc_DK);
|
| 800 |
+
|
| 801 |
+
warpgroup_wait<1>();
|
| 802 |
+
warpgroup_fence_operand(acc_DQ);
|
| 803 |
+
|
| 804 |
+
math_wg_order_barrier.arrive();
|
| 805 |
+
|
| 806 |
+
pipeline_reducer.producer_acquire(smem_pipe_write_reducer);
|
| 807 |
+
auto tDQsDQ = tDQsDQ_full(_,_,_,smem_pipe_write_reducer.index());
|
| 808 |
+
|
| 809 |
+
// Store dQ to smem dQ'
|
| 810 |
+
// Invoke TMA reduce on dQ'
|
| 811 |
+
using Vec = uint_bit_t<sizeof_bits_v<ElementAccumulator> * 2>;
|
| 812 |
+
auto tDQsDQ_v = recast<Vec>(tDQsDQ);
|
| 813 |
+
auto acc_DQ_v = recast<Vec>(acc_DQ);
|
| 814 |
+
|
| 815 |
+
CUTLASS_PRAGMA_UNROLL
|
| 816 |
+
for (int i = 0; i < size(acc_DQ_v); i++) {
|
| 817 |
+
tDQsDQ_v(i) = acc_DQ_v(i);
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
cutlass::arch::fence_view_async_shared();
|
| 821 |
+
|
| 822 |
+
pipeline_reducer.producer_commit(smem_pipe_write_reducer);
|
| 823 |
+
++smem_pipe_write_reducer;
|
| 824 |
+
} else {
|
| 825 |
+
|
| 826 |
+
warpgroup_fence_operand(acc_DK);
|
| 827 |
+
warpgroup_arrive();
|
| 828 |
+
cute::gemm(TiledMmaND_SS{}, tDKrDSp(_,_,_,wg_idx), tDKrQ(_,_,_,smem_pipe_read_q.index()), acc_DK);
|
| 829 |
+
warpgroup_commit_batch();
|
| 830 |
+
|
| 831 |
+
warpgroup_wait<1>();
|
| 832 |
+
warpgroup_fence_operand(acc_DK);
|
| 833 |
+
|
| 834 |
+
pipeline_reducer.producer_acquire(smem_pipe_write_reducer);
|
| 835 |
+
pipeline_reducer.producer_commit(smem_pipe_write_reducer);
|
| 836 |
+
++smem_pipe_write_reducer;
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
--inner_tile_count;
|
| 840 |
+
|
| 841 |
+
pipeline_inner.consumer_release(smem_pipe_release_inner);
|
| 842 |
+
++smem_pipe_release_inner;
|
| 843 |
+
pipeline_inner.consumer_release(smem_pipe_release_inner);
|
| 844 |
+
++smem_pipe_release_inner;
|
| 845 |
+
|
| 846 |
+
tScS.data() = tScS.data() + E<1>{} * get<1>(TileShapeNM{});
|
| 847 |
+
k_index += 1;
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
pipeline_outer.consumer_release(smem_pipe_read_k);
|
| 851 |
+
pipeline_outer.consumer_release(smem_pipe_read_outer);
|
| 852 |
+
pipeline_reducer.producer_tail(smem_pipe_write_reducer);
|
| 853 |
+
++smem_pipe_read_outer;
|
| 854 |
+
|
| 855 |
+
warpgroup_wait<0>();
|
| 856 |
+
warpgroup_fence_operand(acc_DK);
|
| 857 |
+
warpgroup_fence_operand(acc_DV);
|
| 858 |
+
|
| 859 |
+
return make_tuple(acc_DK, acc_DV);
|
| 860 |
+
}
|
| 861 |
+
};
|
| 862 |
+
|
| 863 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_load.hpp
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cute/tensor.hpp"
|
| 36 |
+
|
| 37 |
+
namespace cutlass::fmha::collective {
|
| 38 |
+
|
| 39 |
+
enum class LoadKind {
|
| 40 |
+
kQ, kK, kV,
|
| 41 |
+
kBwdN, kBwdM, kBwdScalar
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
template<
|
| 45 |
+
LoadKind kKind,
|
| 46 |
+
class Pipeline,
|
| 47 |
+
class Element,
|
| 48 |
+
class SmemLayout,
|
| 49 |
+
class TMA
|
| 50 |
+
>
|
| 51 |
+
struct CollectiveLoadTma {
|
| 52 |
+
|
| 53 |
+
using Params = TMA;
|
| 54 |
+
using SharedStorage = cute::array_aligned<Element, cute::cosize_v<SmemLayout>>;
|
| 55 |
+
using PipelineState = typename cutlass::PipelineState<Pipeline::Stages>;
|
| 56 |
+
|
| 57 |
+
Params const& params;
|
| 58 |
+
Pipeline& pipeline;
|
| 59 |
+
SharedStorage& storage;
|
| 60 |
+
|
| 61 |
+
CUTLASS_DEVICE
|
| 62 |
+
CollectiveLoadTma(Params const& params, Pipeline& pipeline, SharedStorage& storage)
|
| 63 |
+
: params(params), pipeline(pipeline), storage(storage) {}
|
| 64 |
+
|
| 65 |
+
template<class ProblemSize, class TileShape, class BlockCoord>
|
| 66 |
+
CUTLASS_DEVICE auto init_g(ProblemSize const& problem_size, TileShape const& tile_shape,
|
| 67 |
+
BlockCoord const& blk_coord, int loop_count
|
| 68 |
+
) {
|
| 69 |
+
using X = Underscore;
|
| 70 |
+
if constexpr (kKind == LoadKind::kK) {
|
| 71 |
+
Tensor mK_full = params.get_tma_tensor(make_shape(get<3>(problem_size), get<4>(problem_size), select<0,1>(problem_size)));
|
| 72 |
+
Tensor gK_full = local_tile(mK_full, tile_shape, make_coord(_, _, _), Step<X, _1, _1>{});
|
| 73 |
+
Tensor gK = gK_full(_, _, _, _0{}, get<2>(blk_coord));
|
| 74 |
+
return gK;
|
| 75 |
+
} else if constexpr (kKind == LoadKind::kQ) {
|
| 76 |
+
Tensor mQ_full = params.get_tma_tensor(make_shape(get<2>(problem_size), get<4>(problem_size), select<0,1>(problem_size)));
|
| 77 |
+
Tensor gQ_full = local_tile(mQ_full, tile_shape, make_coord(_, _, _), Step<_1, X, _1>{});
|
| 78 |
+
Tensor gQ = gQ_full(_, _, _, _0{}, get<2>(blk_coord));
|
| 79 |
+
return make_tensor(gQ.data() + loop_count * get<0>(blk_coord) * stride<2>(gQ), gQ.layout());
|
| 80 |
+
} else if constexpr (kKind == LoadKind::kV) {
|
| 81 |
+
Tensor mV_full = params.get_tma_tensor(make_shape(get<4>(problem_size), get<3>(problem_size), select<0,1>(problem_size)));
|
| 82 |
+
Tensor gV_full = local_tile(mV_full, tile_shape, make_coord(_, _, _), Step<X, _1, _1>{});
|
| 83 |
+
Tensor gV = gV_full(_, _, _0{}, _, get<2>(blk_coord));
|
| 84 |
+
return gV;
|
| 85 |
+
} else if constexpr (kKind == LoadKind::kBwdN) {
|
| 86 |
+
Tensor m_full = params.get_tma_tensor(make_shape(get<3>(problem_size), get<4>(problem_size), select<0,1>(problem_size)));
|
| 87 |
+
Tensor g_full = local_tile(m_full, tile_shape, make_coord(_, _, _), Step<_1, X, _1>{});
|
| 88 |
+
Tensor g = g_full(_, _, _, _0{}, get<2>(blk_coord));
|
| 89 |
+
return make_tensor(g.data() + loop_count * get<1>(blk_coord) * stride<2>(g), g.layout());
|
| 90 |
+
} else if constexpr (kKind == LoadKind::kBwdM) {
|
| 91 |
+
Tensor m_full = params.get_tma_tensor(make_shape(get<2>(problem_size), get<4>(problem_size), select<0,1>(problem_size)));
|
| 92 |
+
Tensor g_full = local_tile(m_full, tile_shape, make_coord(_, _, _), Step<X, _1, _1>{});
|
| 93 |
+
Tensor g = g_full(_, _, _, _0{}, get<2>(blk_coord));
|
| 94 |
+
return g;
|
| 95 |
+
} else if constexpr (kKind == LoadKind::kBwdScalar) {
|
| 96 |
+
Tensor m_full = params.get_tma_tensor(select<2,0,1>(problem_size));
|
| 97 |
+
Tensor g_full = local_tile(m_full, tile_shape, make_coord(_, _, _), Step<X, _1, X>{});
|
| 98 |
+
Tensor g = g_full(_, _, get<2,0>(blk_coord), get<2,1>(blk_coord));
|
| 99 |
+
return g;
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
template<class ClusterRank, class ProblemSize, class TileShape, class BlockCoord>
|
| 104 |
+
CUTLASS_DEVICE auto init_state(ClusterRank const& block_rank_in_cluster,
|
| 105 |
+
ProblemSize const& problem_size, TileShape const& tile_shape,
|
| 106 |
+
BlockCoord const& block_coord, int loop_count
|
| 107 |
+
) {
|
| 108 |
+
Tensor g = init_g(problem_size, tile_shape, block_coord, loop_count);
|
| 109 |
+
Tensor s = make_tensor(make_smem_ptr(storage.data()), SmemLayout{});
|
| 110 |
+
|
| 111 |
+
auto block_tma = params.get_slice(block_rank_in_cluster);
|
| 112 |
+
Tensor ts = block_tma.partition_D(s);
|
| 113 |
+
Tensor tg = block_tma.partition_S(g);
|
| 114 |
+
|
| 115 |
+
return make_tuple(tg, ts);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
template<bool kAdvanceIterator=true, bool kAdvancePipe=true, bool kAcquireBarrier=true, class TileIterator, class State>
|
| 119 |
+
CUTLASS_DEVICE void step(TileIterator& tile_iter, State const& state,
|
| 120 |
+
PipelineState& smem_pipe_write,
|
| 121 |
+
int lane_predicate, int& tile_count, uint16_t mcast_mask = 0
|
| 122 |
+
) {
|
| 123 |
+
if ((lane_predicate == 1) && (tile_count > 0)) {
|
| 124 |
+
if constexpr (kAcquireBarrier) pipeline.producer_acquire(smem_pipe_write);
|
| 125 |
+
using BarrierType = typename Pipeline::ProducerBarrierType;
|
| 126 |
+
BarrierType* tma_barrier = pipeline.producer_get_barrier(smem_pipe_write);
|
| 127 |
+
|
| 128 |
+
if constexpr (kKind == LoadKind::kBwdScalar) {
|
| 129 |
+
copy(params.with(*tma_barrier, mcast_mask), get<0>(state)(_,_,*tile_iter), get<1>(state)(_,_,smem_pipe_write.index()));
|
| 130 |
+
} else {
|
| 131 |
+
copy(params.with(*tma_barrier, mcast_mask), get<0>(state)(_,_,_,*tile_iter), get<1>(state)(_,_,_,smem_pipe_write.index()));
|
| 132 |
+
}
|
| 133 |
+
if constexpr (kAdvancePipe) ++smem_pipe_write;
|
| 134 |
+
if constexpr (kAdvanceIterator) ++tile_iter;
|
| 135 |
+
}
|
| 136 |
+
--tile_count;
|
| 137 |
+
}
|
| 138 |
+
};
|
| 139 |
+
|
| 140 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_softmax.hpp
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cute/tensor.hpp"
|
| 36 |
+
|
| 37 |
+
#include "../collective/fmha_common.hpp"
|
| 38 |
+
|
| 39 |
+
namespace cutlass::fmha::collective {
|
| 40 |
+
|
| 41 |
+
template<
|
| 42 |
+
class ElementAccumulator,
|
| 43 |
+
class Fusion,
|
| 44 |
+
class Params
|
| 45 |
+
>
|
| 46 |
+
struct CollectiveSoftmax {
|
| 47 |
+
Params const& params;
|
| 48 |
+
CUTLASS_DEVICE CollectiveSoftmax(Params const& params) : params(params) {}
|
| 49 |
+
|
| 50 |
+
using SumType = float;
|
| 51 |
+
using MaxType = ElementAccumulator;
|
| 52 |
+
|
| 53 |
+
template<class AccPV, class TiledMmaPV>
|
| 54 |
+
CUTLASS_DEVICE auto init(AccPV const& acc_pv, TiledMmaPV const& tiled_mma_pv) {
|
| 55 |
+
Tensor s_max = make_fragment_like<MaxType>(size<0>(layout_acc_mn(tiled_mma_pv, acc_pv.layout())));
|
| 56 |
+
Tensor a_sum = make_fragment_like<SumType>(s_max);
|
| 57 |
+
return make_tuple(s_max, a_sum);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
CUTLASS_DEVICE float overload_exp2(float f) {
|
| 61 |
+
return ::exp2f(f);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
CUTLASS_DEVICE cutlass::half_t overload_exp2(cutlass::half_t f) {
|
| 65 |
+
auto a = f.raw();
|
| 66 |
+
decltype(a) d;
|
| 67 |
+
asm("ex2.approx.f16 %0, %1;" : "=h"(d) : "h"(a));
|
| 68 |
+
return cutlass::half_t::bitcast(d);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
CUTLASS_DEVICE float overload_max(float a, float b) {
|
| 73 |
+
return ::max(a, b);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
CUTLASS_DEVICE cutlass::half_t overload_max(cutlass::half_t a, cutlass::half_t b) {
|
| 77 |
+
return cutlass::half_t{__hmax_nan(a.to_half(), b.to_half())};
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
CUTLASS_DEVICE half overload_to_native(cutlass::half_t f) {
|
| 81 |
+
return f.to_half();
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
CUTLASS_DEVICE float overload_to_native(float f) {
|
| 85 |
+
return f;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
template<class AccQK, class TiledMmaQK, class CountQK, class State, class ProblemShape>
|
| 89 |
+
CUTLASS_DEVICE auto step(AccQK& acc_qk, TiledMmaQK const& tiled_mma_qk, CountQK const& count_qk, State& state, ProblemShape const& problem_shape) {
|
| 90 |
+
Fusion{}.before_softmax(acc_qk, count_qk, problem_shape);
|
| 91 |
+
Tensor acc_qk_mn = make_tensor(acc_qk.data(), layout_acc_mn(tiled_mma_qk, acc_qk.layout()));
|
| 92 |
+
auto reduction_target_qk = reduction_target_n(tiled_mma_qk);
|
| 93 |
+
constexpr int red_rank = decltype(rank(reduction_target_qk))::value;
|
| 94 |
+
|
| 95 |
+
auto& s_max = get<0>(state);
|
| 96 |
+
auto& a_sum = get<1>(state);
|
| 97 |
+
|
| 98 |
+
// Linear reduction is faster for the first iteration
|
| 99 |
+
CUTLASS_PRAGMA_UNROLL
|
| 100 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 101 |
+
s_max(i) = acc_qk_mn(i, 0);
|
| 102 |
+
}
|
| 103 |
+
CUTLASS_PRAGMA_UNROLL
|
| 104 |
+
for (int j = 1; j < size<1>(acc_qk_mn); j++) {
|
| 105 |
+
CUTLASS_PRAGMA_UNROLL
|
| 106 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 107 |
+
s_max(i) = overload_max(s_max(i), acc_qk_mn(i, j));
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
for_each(make_seq<red_rank>{}, [&](auto r) {
|
| 112 |
+
CUTLASS_PRAGMA_UNROLL
|
| 113 |
+
for (int j = 1; j < shape<r>(reduction_target_qk); j *= 2) {
|
| 114 |
+
CUTLASS_PRAGMA_UNROLL
|
| 115 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 116 |
+
s_max(i) = overload_max(s_max(i), MaxType{__shfl_xor_sync(uint32_t(-1), overload_to_native(s_max(i)), stride<r>(reduction_target_qk) * j)});
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
});
|
| 120 |
+
CUTLASS_PRAGMA_UNROLL
|
| 121 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 122 |
+
MaxType local_max = s_max(i) == static_cast<MaxType>(-INFINITY) ? static_cast<MaxType>(0) : s_max(i);
|
| 123 |
+
MaxType scale = static_cast<MaxType>(params.scale_softmax_log2);
|
| 124 |
+
MaxType scale_max = scale * local_max;
|
| 125 |
+
|
| 126 |
+
CUTLASS_PRAGMA_UNROLL
|
| 127 |
+
for (int j = 0; j < size<1>(acc_qk_mn); j++) {
|
| 128 |
+
acc_qk_mn(i, j) = overload_exp2(scale * acc_qk_mn(i, j) - scale_max);
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
CUTLASS_PRAGMA_UNROLL
|
| 132 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 133 |
+
a_sum(i) = SumType{reduce(acc_qk_mn(i, _), cute::plus{})};
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
template<bool kUseFusion=true, class AccQK, class TiledMmaQK, class CountQK, class State, class AccPV, class TiledMmaPV, class ProblemShape>
|
| 138 |
+
CUTLASS_DEVICE auto step_interleave_begin(AccQK& acc_qk, TiledMmaQK const& tiled_mma_qk, CountQK const& count_qk, State& state, AccPV& acc_pv, TiledMmaPV const& tiled_mma_pv, ProblemShape const& problem_shape) {
|
| 139 |
+
|
| 140 |
+
if constexpr (kUseFusion) {
|
| 141 |
+
Fusion{}.before_softmax(acc_qk, count_qk, problem_shape);
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
Tensor acc_qk_mn = make_tensor(acc_qk.data(), layout_acc_mn(tiled_mma_qk, acc_qk.layout()));
|
| 145 |
+
Tensor acc_pv_mn = make_tensor(acc_pv.data(), layout_acc_mn(tiled_mma_pv, acc_pv.layout()));
|
| 146 |
+
|
| 147 |
+
static_assert(size<0>(acc_qk_mn) == size<0>(acc_pv_mn));
|
| 148 |
+
auto reduction_target_qk = reduction_target_n(tiled_mma_qk);
|
| 149 |
+
constexpr int red_rank = decltype(rank(reduction_target_qk))::value;
|
| 150 |
+
|
| 151 |
+
auto& s_max = get<0>(state);
|
| 152 |
+
auto& a_sum = get<1>(state);
|
| 153 |
+
|
| 154 |
+
Tensor s_max_prev = make_fragment_like(s_max);
|
| 155 |
+
CUTLASS_PRAGMA_UNROLL
|
| 156 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 157 |
+
s_max_prev(i) = s_max(i);
|
| 158 |
+
}
|
| 159 |
+
CUTLASS_PRAGMA_UNROLL
|
| 160 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 161 |
+
// Linear reduction is faster here, as well
|
| 162 |
+
CUTLASS_PRAGMA_UNROLL
|
| 163 |
+
for (int j = 0; j < size<1>(acc_qk_mn); j++) {
|
| 164 |
+
s_max(i) = overload_max(s_max(i), acc_qk_mn(i, j));
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
// reduce max
|
| 168 |
+
for_each(make_seq<red_rank>{}, [&](auto r) {
|
| 169 |
+
CUTLASS_PRAGMA_UNROLL
|
| 170 |
+
for (int j = 1; j < shape<r>(reduction_target_qk); j *= 2) {
|
| 171 |
+
CUTLASS_PRAGMA_UNROLL
|
| 172 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 173 |
+
s_max(i) = overload_max(s_max(i), __shfl_xor_sync(uint32_t(-1), s_max(i), stride<r>(reduction_target_qk) * j));
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
});
|
| 177 |
+
CUTLASS_PRAGMA_UNROLL
|
| 178 |
+
for (int i = 0; i < size<0>(acc_pv_mn); i++) {
|
| 179 |
+
float s_max_cur = s_max(i) == -INFINITY ? 0.0f : s_max(i);
|
| 180 |
+
float scale = ::exp2f((s_max_prev(i) - s_max_cur) * params.scale_softmax_log2);
|
| 181 |
+
a_sum(i) *= scale;
|
| 182 |
+
CUTLASS_PRAGMA_UNROLL
|
| 183 |
+
for (int j = 0; j < size<1>(acc_pv_mn); j++) {
|
| 184 |
+
acc_pv_mn(i, j) *= scale;
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
template<class AccQK_MN, class State>
|
| 190 |
+
CUTLASS_DEVICE auto step_interleave_step(AccQK_MN& acc_qk_mn, State& state) {
|
| 191 |
+
|
| 192 |
+
auto& s_max = get<0>(state);
|
| 193 |
+
auto& a_sum = get<1>(state);
|
| 194 |
+
|
| 195 |
+
CUTLASS_PRAGMA_UNROLL
|
| 196 |
+
for (int j = 0; j < size<0>(acc_qk_mn); j++) {
|
| 197 |
+
float local_max = s_max(j) == -INFINITY ? 0.f : s_max(j);
|
| 198 |
+
float scale_max = params.scale_softmax_log2 * local_max;
|
| 199 |
+
|
| 200 |
+
CUTLASS_PRAGMA_UNROLL
|
| 201 |
+
for (int k = 0; k < size<1>(acc_qk_mn); k++) {
|
| 202 |
+
acc_qk_mn(j, k) = ::exp2f(params.scale_softmax_log2 * acc_qk_mn(j, k) - scale_max);
|
| 203 |
+
a_sum(j) += acc_qk_mn(j, k);
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
template<bool kUseFusion=true, class AccQK, class TiledMmaQK, class CountQK, class State, class AccPV, class TiledMmaPV, class ProblemShape>
|
| 209 |
+
CUTLASS_DEVICE auto step(AccQK& acc_qk, TiledMmaQK const& tiled_mma_qk, CountQK const& count_qk, State& state, AccPV& acc_pv, TiledMmaPV const& tiled_mma_pv, ProblemShape const& problem_shape) {
|
| 210 |
+
|
| 211 |
+
if constexpr (kUseFusion) {
|
| 212 |
+
Fusion{}.before_softmax(acc_qk, count_qk, problem_shape);
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
Tensor acc_qk_mn = make_tensor(acc_qk.data(), layout_acc_mn(tiled_mma_qk, acc_qk.layout()));
|
| 216 |
+
Tensor acc_pv_mn = make_tensor(acc_pv.data(), layout_acc_mn(tiled_mma_pv, acc_pv.layout()));
|
| 217 |
+
|
| 218 |
+
static_assert(size<0>(acc_qk_mn) == size<0>(acc_pv_mn));
|
| 219 |
+
auto reduction_target_qk = reduction_target_n(tiled_mma_qk);
|
| 220 |
+
constexpr int red_rank = decltype(rank(reduction_target_qk))::value;
|
| 221 |
+
|
| 222 |
+
auto& s_max = get<0>(state);
|
| 223 |
+
auto& a_sum = get<1>(state);
|
| 224 |
+
|
| 225 |
+
Tensor s_max_prev = make_fragment_like(s_max);
|
| 226 |
+
CUTLASS_PRAGMA_UNROLL
|
| 227 |
+
for (int i = 0; i < size<0>(acc_qk_mn); i++) {
|
| 228 |
+
s_max_prev(i) = s_max(i);
|
| 229 |
+
|
| 230 |
+
// Linear reduction is faster here, as well
|
| 231 |
+
CUTLASS_PRAGMA_UNROLL
|
| 232 |
+
for (int j = 0; j < size<1>(acc_qk_mn); j++) {
|
| 233 |
+
s_max(i) = overload_max(s_max(i), acc_qk_mn(i, j));
|
| 234 |
+
}
|
| 235 |
+
// reduce max
|
| 236 |
+
for_each(make_seq<red_rank>{}, [&](auto r) {
|
| 237 |
+
CUTLASS_PRAGMA_UNROLL
|
| 238 |
+
for (int j = 1; j < shape<r>(reduction_target_qk); j *= 2) {
|
| 239 |
+
s_max(i) = overload_max(s_max(i), MaxType{__shfl_xor_sync(uint32_t(-1), overload_to_native(s_max(i)), stride<r>(reduction_target_qk) * j)});
|
| 240 |
+
}
|
| 241 |
+
});
|
| 242 |
+
|
| 243 |
+
MaxType local_max = s_max(i) == static_cast<MaxType>(-INFINITY) ? static_cast<MaxType>(0) : s_max(i);
|
| 244 |
+
MaxType scale = static_cast<MaxType>(params.scale_softmax_log2);
|
| 245 |
+
MaxType scale_max = scale * local_max;
|
| 246 |
+
|
| 247 |
+
CUTLASS_PRAGMA_UNROLL
|
| 248 |
+
for (int j = 0; j < size<1>(acc_qk_mn); j++) {
|
| 249 |
+
acc_qk_mn(i, j) = overload_exp2(scale * acc_qk_mn(i, j) - scale_max);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
MaxType s_max_cur = s_max(i) == static_cast<MaxType>(-INFINITY) ? static_cast<MaxType>(0) : s_max(i);
|
| 253 |
+
SumType scale_pv = overload_exp2((s_max_prev(i) - s_max_cur) * scale);
|
| 254 |
+
a_sum(i) *= scale_pv;
|
| 255 |
+
|
| 256 |
+
using ElementPV = typename AccPV::value_type;
|
| 257 |
+
ElementPV scale_pv_ele = static_cast<ElementPV>(scale_pv);
|
| 258 |
+
CUTLASS_PRAGMA_UNROLL
|
| 259 |
+
for (int j = 0; j < size<1>(acc_pv_mn); j++) {
|
| 260 |
+
acc_pv_mn(i, j) *= scale_pv_ele;
|
| 261 |
+
}
|
| 262 |
+
a_sum(i) += SumType{reduce(acc_qk_mn(i, _), cute::plus{})};
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
template<class State, class AccPV, class TiledMmaPV>
|
| 268 |
+
CUTLASS_DEVICE auto tail(State& state, AccPV& acc_pv, TiledMmaPV const& tiled_mma_pv) {
|
| 269 |
+
auto& s_max = get<0>(state);
|
| 270 |
+
auto& a_sum = get<1>(state);
|
| 271 |
+
|
| 272 |
+
Tensor acc_pv_mn = make_tensor(acc_pv.data(), layout_acc_mn(tiled_mma_pv, acc_pv.layout()));
|
| 273 |
+
|
| 274 |
+
auto reduction_target = reduction_target_n(tiled_mma_pv);
|
| 275 |
+
constexpr int red_rank = decltype(rank(reduction_target))::value;
|
| 276 |
+
for_each(make_seq<red_rank>{}, [&](auto r) {
|
| 277 |
+
CUTLASS_PRAGMA_UNROLL
|
| 278 |
+
for (int j = 1; j < shape<r>(reduction_target); j *= 2) {
|
| 279 |
+
CUTLASS_PRAGMA_UNROLL
|
| 280 |
+
for (int i = 0; i < size<0>(acc_pv_mn); i++) {
|
| 281 |
+
a_sum(i) = a_sum(i) + __shfl_xor_sync(uint32_t(-1), a_sum(i), stride<r>(reduction_target) * j);
|
| 282 |
+
}
|
| 283 |
+
}
|
| 284 |
+
});
|
| 285 |
+
|
| 286 |
+
Tensor acc_mn = make_tensor(acc_pv.data(), layout_acc_mn(tiled_mma_pv, acc_pv.layout()));
|
| 287 |
+
|
| 288 |
+
Tensor lse = make_fragment_like(a_sum);
|
| 289 |
+
CUTLASS_PRAGMA_UNROLL
|
| 290 |
+
for (int i = 0; i < size<0>(acc_mn); i++) {
|
| 291 |
+
float sum = a_sum(i);
|
| 292 |
+
float inv_sum = (sum == 0.f || sum != sum) ? 1.f : __frcp_rn(sum);
|
| 293 |
+
lse(i) = (sum == 0.f || sum != sum) ? INFINITY : s_max(i) * params.scale_softmax + __logf(sum);
|
| 294 |
+
float scale = params.rp_dropout * inv_sum;
|
| 295 |
+
CUTLASS_PRAGMA_UNROLL
|
| 296 |
+
for (int j = 0; j < size<1>(acc_mn); j++) {
|
| 297 |
+
acc_mn(i, j) *= scale;
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
return lse;
|
| 302 |
+
}
|
| 303 |
+
};
|
| 304 |
+
|
| 305 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_tma.hpp
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/gemm/collective/collective_builder.hpp"
|
| 36 |
+
|
| 37 |
+
#include "../collective/fmha_common.hpp"
|
| 38 |
+
#include "../collective/fmha_collective_load.hpp"
|
| 39 |
+
#include "../collective/fmha_collective_softmax.hpp"
|
| 40 |
+
#include "../kernel/fmha_options.hpp"
|
| 41 |
+
|
| 42 |
+
namespace cutlass::fmha::collective {
|
| 43 |
+
|
| 44 |
+
using namespace cute;
|
| 45 |
+
using cutlass::fmha::kernel::Tag;
|
| 46 |
+
using cutlass::fmha::kernel::find_option_t;
|
| 47 |
+
|
| 48 |
+
template<
|
| 49 |
+
typename Element_,
|
| 50 |
+
typename ElementAccumulator_,
|
| 51 |
+
typename TileShape_, // BlockQO, BlockKV, BlockHead
|
| 52 |
+
class Fusion,
|
| 53 |
+
class... Options
|
| 54 |
+
>
|
| 55 |
+
struct FmhaMainloopTma {
|
| 56 |
+
|
| 57 |
+
using Element = Element_;
|
| 58 |
+
using ElementAccumulator = ElementAccumulator_;
|
| 59 |
+
using TileShape = TileShape_;
|
| 60 |
+
|
| 61 |
+
// Options
|
| 62 |
+
using kClusterM = find_option_t<Tag::kClusterM, Int<1>, Options...>;
|
| 63 |
+
static constexpr int StageCount = find_option_t<Tag::kStagesKV, Int<4>, Options...>::value;
|
| 64 |
+
static constexpr int StageCountQ = find_option_t<Tag::kStagesQ, Int<1>, Options...>::value;
|
| 65 |
+
|
| 66 |
+
using StagesQ = cutlass::gemm::collective::StageCount<StageCountQ>;
|
| 67 |
+
using Stages = cutlass::gemm::collective::StageCount<StageCount>;
|
| 68 |
+
using ClusterShape = Shape<kClusterM, _1, _1>;
|
| 69 |
+
|
| 70 |
+
// 16B alignment lets us use TMA
|
| 71 |
+
static constexpr int Alignment = 16 / sizeof(Element);
|
| 72 |
+
|
| 73 |
+
using TileShapeQK = TileShape;
|
| 74 |
+
using TileShapePV = decltype(select<0,2,1>(TileShapeQK{}));
|
| 75 |
+
|
| 76 |
+
using LayoutQKV = cute::tuple<int, _1, cute::tuple<int, int>>;
|
| 77 |
+
using LayoutQ = LayoutQKV;
|
| 78 |
+
using LayoutK = LayoutQKV;
|
| 79 |
+
using LayoutV = LayoutQKV;
|
| 80 |
+
|
| 81 |
+
using CollectiveMmaQK = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 82 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 83 |
+
Element, LayoutQ, Alignment,
|
| 84 |
+
Element, LayoutK, Alignment,
|
| 85 |
+
ElementAccumulator,
|
| 86 |
+
TileShapeQK, ClusterShape, Stages,
|
| 87 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 88 |
+
|
| 89 |
+
using CollectiveMmaPV = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 90 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 91 |
+
// the stride for A does not matter since we do not load from smem at all
|
| 92 |
+
Element, LayoutK, Alignment,
|
| 93 |
+
Element, decltype(select<1,0,2>(LayoutV{})), Alignment,
|
| 94 |
+
ElementAccumulator,
|
| 95 |
+
TileShapePV, ClusterShape, Stages,
|
| 96 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 97 |
+
|
| 98 |
+
using TiledMmaQK = typename CollectiveMmaQK::TiledMma;
|
| 99 |
+
using TiledMmaPV = decltype(convert_to_gmma_rs(typename CollectiveMmaPV::TiledMma{}));
|
| 100 |
+
|
| 101 |
+
using SmemLayoutQ = decltype(unstageSmemLayout(typename CollectiveMmaQK::SmemLayoutA{}, Int<StagesQ::value>{}));
|
| 102 |
+
using SmemLayoutK = typename CollectiveMmaQK::SmemLayoutB;
|
| 103 |
+
using SmemLayoutV = typename CollectiveMmaPV::SmemLayoutB;
|
| 104 |
+
|
| 105 |
+
using MainloopPipeline = cutlass::PipelineTmaAsync<Stages::value>;
|
| 106 |
+
using MainloopPipelineQ = cutlass::PipelineTmaAsync<StagesQ::value>;
|
| 107 |
+
|
| 108 |
+
using PipelineState = typename cutlass::PipelineState<MainloopPipeline::Stages>;
|
| 109 |
+
using PipelineStateQ = typename cutlass::PipelineState<MainloopPipelineQ::Stages>;
|
| 110 |
+
|
| 111 |
+
using TileShapeOut = TileShapePV;
|
| 112 |
+
using TiledMmaOut = TiledMmaPV;
|
| 113 |
+
using ElementOut = ElementAccumulator;
|
| 114 |
+
|
| 115 |
+
struct SharedStorage {
|
| 116 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutQ>> smem_q;
|
| 117 |
+
union {
|
| 118 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutK>> smem_k;
|
| 119 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutV>> smem_v;
|
| 120 |
+
};
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
struct Arguments {
|
| 124 |
+
const Element* ptr_Q;
|
| 125 |
+
LayoutQ dQ;
|
| 126 |
+
const Element* ptr_K;
|
| 127 |
+
LayoutK dK;
|
| 128 |
+
const Element* ptr_V;
|
| 129 |
+
LayoutV dV;
|
| 130 |
+
};
|
| 131 |
+
|
| 132 |
+
using TMA_Q = typename CollectiveMmaQK::Params::TMA_A;
|
| 133 |
+
using TMA_K = typename CollectiveMmaQK::Params::TMA_B;
|
| 134 |
+
using TMA_V = typename CollectiveMmaPV::Params::TMA_B;
|
| 135 |
+
|
| 136 |
+
struct Params {
|
| 137 |
+
TMA_Q tma_load_q;
|
| 138 |
+
TMA_K tma_load_k;
|
| 139 |
+
TMA_V tma_load_v;
|
| 140 |
+
|
| 141 |
+
float scale_softmax;
|
| 142 |
+
float scale_softmax_log2;
|
| 143 |
+
float rp_dropout;
|
| 144 |
+
};
|
| 145 |
+
|
| 146 |
+
using LoadQ = cutlass::fmha::collective::CollectiveLoadTma<
|
| 147 |
+
cutlass::fmha::collective::LoadKind::kQ,
|
| 148 |
+
MainloopPipelineQ,
|
| 149 |
+
Element,
|
| 150 |
+
SmemLayoutQ,
|
| 151 |
+
TMA_Q
|
| 152 |
+
>;
|
| 153 |
+
|
| 154 |
+
using LoadK = cutlass::fmha::collective::CollectiveLoadTma<
|
| 155 |
+
cutlass::fmha::collective::LoadKind::kK,
|
| 156 |
+
MainloopPipeline,
|
| 157 |
+
Element,
|
| 158 |
+
SmemLayoutK,
|
| 159 |
+
TMA_K
|
| 160 |
+
>;
|
| 161 |
+
|
| 162 |
+
using LoadV = cutlass::fmha::collective::CollectiveLoadTma<
|
| 163 |
+
cutlass::fmha::collective::LoadKind::kV,
|
| 164 |
+
MainloopPipeline,
|
| 165 |
+
Element,
|
| 166 |
+
SmemLayoutV,
|
| 167 |
+
TMA_V
|
| 168 |
+
>;
|
| 169 |
+
|
| 170 |
+
static_assert(size(typename CollectiveMmaQK::TiledMma{}) == size(typename CollectiveMmaPV::TiledMma{}));
|
| 171 |
+
|
| 172 |
+
static const int MaxThreadsPerBlock = size(typename CollectiveMmaQK::TiledMma{});
|
| 173 |
+
|
| 174 |
+
template<class ProblemShape>
|
| 175 |
+
static bool can_implement(ProblemShape const& problem_size, Arguments const& args) {
|
| 176 |
+
return true
|
| 177 |
+
&& (get<4>(problem_size) <= get<2>(TileShape{}))
|
| 178 |
+
&& ((get<4>(problem_size) % Alignment) == 0)
|
| 179 |
+
&& ((get<2>(problem_size) % Alignment) == 0)
|
| 180 |
+
;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
template<class ProblemShape>
|
| 184 |
+
static Params to_underlying_arguments(ProblemShape const& problem_size, Arguments const& args, void* workspace) {
|
| 185 |
+
|
| 186 |
+
auto problem_shape_qk = make_shape(get<2>(problem_size), get<3>(problem_size), get<4>(problem_size), make_shape(get<0>(problem_size), get<1>(problem_size)));
|
| 187 |
+
auto params_qk = CollectiveMmaQK::to_underlying_arguments(problem_shape_qk,
|
| 188 |
+
typename CollectiveMmaQK::Arguments {
|
| 189 |
+
args.ptr_Q, args.dQ,
|
| 190 |
+
args.ptr_K, args.dK,
|
| 191 |
+
}, /*workspace=*/ nullptr);
|
| 192 |
+
|
| 193 |
+
auto problem_shape_pv = select<0,2,1,3>(problem_shape_qk);
|
| 194 |
+
auto params_pv = CollectiveMmaPV::to_underlying_arguments(problem_shape_pv,
|
| 195 |
+
typename CollectiveMmaPV::Arguments {
|
| 196 |
+
args.ptr_K, args.dK, // never used, dummy
|
| 197 |
+
args.ptr_V, select<1,0,2>(args.dV),
|
| 198 |
+
}, /*workspace=*/ nullptr);
|
| 199 |
+
|
| 200 |
+
return Params{
|
| 201 |
+
params_qk.tma_load_a,
|
| 202 |
+
params_qk.tma_load_b,
|
| 203 |
+
params_pv.tma_load_b,
|
| 204 |
+
1.0f / (float) std::sqrt(get<4>(problem_size)),
|
| 205 |
+
(float) (std::log2(std::exp(1.0)) / std::sqrt(get<4>(problem_size))),
|
| 206 |
+
1.0f
|
| 207 |
+
};
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
CUTLASS_DEVICE
|
| 211 |
+
static void prefetch_tma_descriptors(Params const& params) {
|
| 212 |
+
cute::prefetch_tma_descriptor(params.tma_load_q.get_tma_descriptor());
|
| 213 |
+
cute::prefetch_tma_descriptor(params.tma_load_k.get_tma_descriptor());
|
| 214 |
+
cute::prefetch_tma_descriptor(params.tma_load_v.get_tma_descriptor());
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
template<class BlkCoord, class ProblemShape>
|
| 218 |
+
CUTLASS_DEVICE auto
|
| 219 |
+
compute(
|
| 220 |
+
int block_rank_in_cluster,
|
| 221 |
+
BlkCoord const& blk_coord, Params const& params, ProblemShape const& problem_size,
|
| 222 |
+
MainloopPipeline& pipeline, PipelineState& smem_pipe_read, PipelineState& smem_pipe_write,
|
| 223 |
+
MainloopPipelineQ& pipeline_q, PipelineStateQ& smem_pipe_read_q, PipelineStateQ& smem_pipe_write_q,
|
| 224 |
+
SharedStorage& storage)
|
| 225 |
+
{
|
| 226 |
+
int warp_idx = cutlass::canonical_warp_idx_sync();
|
| 227 |
+
int thread_idx = threadIdx.x;
|
| 228 |
+
|
| 229 |
+
PipelineState smem_pipe_release = smem_pipe_read;
|
| 230 |
+
[[maybe_unused]] PipelineStateQ smem_pipe_release_q = smem_pipe_read_q;
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
int fusion_tile_count = Fusion{}.get_trip_count(blk_coord, TileShape{}, problem_size);
|
| 234 |
+
|
| 235 |
+
LoadQ load_q{params.tma_load_q, pipeline_q, storage.smem_q};
|
| 236 |
+
auto load_state_q = load_q.init_state(_0{}, problem_size, TileShapeQK{}, blk_coord, 1);
|
| 237 |
+
|
| 238 |
+
LoadK load_k{params.tma_load_k, pipeline, storage.smem_k};
|
| 239 |
+
auto load_state_k = load_k.init_state(block_rank_in_cluster, problem_size, TileShapeQK{}, blk_coord, fusion_tile_count);
|
| 240 |
+
|
| 241 |
+
LoadV load_v{params.tma_load_v, pipeline, storage.smem_v};
|
| 242 |
+
auto load_state_v = load_v.init_state(block_rank_in_cluster, problem_size, TileShapePV{}, blk_coord, fusion_tile_count);
|
| 243 |
+
|
| 244 |
+
// Set predicate for the lowest lane_id in the warp
|
| 245 |
+
int lane_predicate = cute::elect_one_sync();
|
| 246 |
+
|
| 247 |
+
// Issue TmaLoads (Prologue fetches)
|
| 248 |
+
if (warp_idx == 0) {
|
| 249 |
+
auto q_tile_iter = cute::make_coord_iterator(1);
|
| 250 |
+
int q_tile_count = 1;
|
| 251 |
+
load_q.step(q_tile_iter, load_state_q, smem_pipe_write_q, lane_predicate, q_tile_count);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
// Loop over K elems
|
| 255 |
+
auto k_tile_iter = cute::make_coord_iterator(fusion_tile_count);
|
| 256 |
+
|
| 257 |
+
int k_tile_count_tma = 2 * fusion_tile_count;
|
| 258 |
+
|
| 259 |
+
uint16_t mcast_mask_b = 0;
|
| 260 |
+
|
| 261 |
+
if (warp_idx == 0 && lane_predicate == 1) {
|
| 262 |
+
if constexpr (cute::is_same_v<typename CollectiveMmaQK::GmemTiledCopyB, SM90_TMA_LOAD_MULTICAST>) {
|
| 263 |
+
auto block_layout = Layout<ClusterShape>{}; // (m,n) -> block_id
|
| 264 |
+
for (int m = 0; m < size<0>(block_layout); ++m) {
|
| 265 |
+
mcast_mask_b |= (uint16_t(1) << block_layout(m,_0{},Int<0>{}));
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
CUTLASS_PRAGMA_UNROLL
|
| 270 |
+
for (int i = 0; i < StageCount; i++) {
|
| 271 |
+
if (i % 2 == 0) {
|
| 272 |
+
load_k.template step<false>(k_tile_iter, load_state_k, smem_pipe_write, lane_predicate, k_tile_count_tma, mcast_mask_b);
|
| 273 |
+
} else {
|
| 274 |
+
load_v.template step<true>(k_tile_iter, load_state_k, smem_pipe_write, lane_predicate, k_tile_count_tma, mcast_mask_b);
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
TiledMmaQK tiled_mma_qk;
|
| 280 |
+
auto thr_mma_qk = tiled_mma_qk.get_thread_slice(thread_idx);
|
| 281 |
+
|
| 282 |
+
// Mainloop setup QK
|
| 283 |
+
Tensor sQ = make_tensor(make_smem_ptr(storage.smem_q.data()), SmemLayoutQ{});
|
| 284 |
+
Tensor sK = make_tensor(make_smem_ptr(storage.smem_k.data()), SmemLayoutK{});
|
| 285 |
+
|
| 286 |
+
Tensor tSsQ = thr_mma_qk.partition_A(sQ); // (MMA,MMA_M,MMA_K,PIPE)
|
| 287 |
+
Tensor tSsK = thr_mma_qk.partition_B(sK); // (MMA,MMA_N,MMA_K,PIPE)
|
| 288 |
+
Tensor tSrQ = thr_mma_qk.make_fragment_A(tSsQ); // (MMA,MMA_N,MMA_K,PIPE)
|
| 289 |
+
Tensor tSrK = thr_mma_qk.make_fragment_B(tSsK); // (MMA,MMA_M,MMA_N,PIPE)
|
| 290 |
+
|
| 291 |
+
// Prepare: MMA PV
|
| 292 |
+
TiledMmaPV tiled_mma_pv;
|
| 293 |
+
auto thr_mma_pv = tiled_mma_pv.get_thread_slice(thread_idx);
|
| 294 |
+
|
| 295 |
+
// Mainloop setup PV
|
| 296 |
+
Tensor sV = make_tensor(make_smem_ptr(storage.smem_v.data()), SmemLayoutV{});
|
| 297 |
+
|
| 298 |
+
Tensor tOsV = thr_mma_pv.partition_B(sV); // (MMA,MMA_N,MMA_K,PIPE)
|
| 299 |
+
Tensor tOrV = thr_mma_pv.make_fragment_B(tOsV); // (MMA,MMA_M,MMA_N,PIPE)
|
| 300 |
+
|
| 301 |
+
int k_tile_count = Fusion{}.get_unmasked_trip_count(blk_coord, TileShape{}, problem_size);
|
| 302 |
+
|
| 303 |
+
pipeline_q.consumer_wait(smem_pipe_read_q);
|
| 304 |
+
|
| 305 |
+
// mapping into QK accumulator
|
| 306 |
+
Tensor cP = make_identity_tensor(take<0,2>(TileShapeQK{}));
|
| 307 |
+
Tensor tPcP = thr_mma_qk.partition_C(cP);
|
| 308 |
+
int m_block = get<0>(blk_coord);
|
| 309 |
+
tPcP.data() = tPcP.data() + E<0>{} * m_block * get<0>(TileShapeQK{});
|
| 310 |
+
|
| 311 |
+
// Allocate PV acc
|
| 312 |
+
Tensor acc_pv = partition_fragment_C(tiled_mma_pv, take<0, 2>(TileShapePV{}));
|
| 313 |
+
|
| 314 |
+
cutlass::fmha::collective::CollectiveSoftmax<ElementAccumulator, Fusion, decltype(params)> softmax{params};
|
| 315 |
+
auto softmax_state = softmax.init(acc_pv, tiled_mma_pv);
|
| 316 |
+
|
| 317 |
+
if (true)
|
| 318 |
+
{
|
| 319 |
+
--k_tile_count;
|
| 320 |
+
// Allocate QK acc
|
| 321 |
+
Tensor acc_qk = partition_fragment_C(tiled_mma_qk, take<0, 2>(TileShapeQK{}));
|
| 322 |
+
|
| 323 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 324 |
+
|
| 325 |
+
// MMA QK
|
| 326 |
+
warpgroup_fence_operand(acc_qk);
|
| 327 |
+
warpgroup_arrive();
|
| 328 |
+
|
| 329 |
+
gemm_zero_acc(tiled_mma_qk, tSrQ(_,_,_,_0{}), tSrK(_,_,_,smem_pipe_read.index()), acc_qk);
|
| 330 |
+
warpgroup_commit_batch();
|
| 331 |
+
|
| 332 |
+
++smem_pipe_read;
|
| 333 |
+
|
| 334 |
+
// Wait for the pipeline MMAs to drain
|
| 335 |
+
warpgroup_wait<0>();
|
| 336 |
+
warpgroup_fence_operand(acc_qk);
|
| 337 |
+
|
| 338 |
+
softmax.step(acc_qk, tiled_mma_qk, tPcP, softmax_state, problem_size);
|
| 339 |
+
|
| 340 |
+
Tensor acc_qk_fixed = make_fragment_like<Element>(convert_c_layout_to_a_layout(acc_qk.layout(), shape<1>(typename decltype(tiled_mma_pv)::LayoutA_TV{})));
|
| 341 |
+
|
| 342 |
+
Tensor acc_qk_input = make_tensor(acc_qk_fixed.data(), acc_qk.layout());
|
| 343 |
+
CUTLASS_PRAGMA_UNROLL
|
| 344 |
+
for (int i = 0; i < size(acc_qk); i++) {
|
| 345 |
+
acc_qk_input(i) = static_cast<Element>(acc_qk(i));
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 349 |
+
|
| 350 |
+
// MMA PV
|
| 351 |
+
warpgroup_fence_operand(acc_pv);
|
| 352 |
+
warpgroup_fence_operand(acc_qk_fixed);
|
| 353 |
+
warpgroup_arrive();
|
| 354 |
+
|
| 355 |
+
gemm_zero_acc(tiled_mma_pv, acc_qk_fixed, tOrV(_,_,_,smem_pipe_read.index()), acc_pv);
|
| 356 |
+
warpgroup_commit_batch();
|
| 357 |
+
|
| 358 |
+
//
|
| 359 |
+
// Advance the pipe
|
| 360 |
+
//
|
| 361 |
+
|
| 362 |
+
// Advance consumer pipeline
|
| 363 |
+
++smem_pipe_read;
|
| 364 |
+
|
| 365 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 366 |
+
++smem_pipe_release;
|
| 367 |
+
|
| 368 |
+
tPcP.data() = tPcP.data() + E<1>{} * get<1>(TileShapeQK{});
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 372 |
+
for ( ; k_tile_count > 0; --k_tile_count)
|
| 373 |
+
{
|
| 374 |
+
// Allocate QK acc
|
| 375 |
+
Tensor acc_qk = partition_fragment_C(tiled_mma_qk, take<0, 2>(TileShapeQK{}));
|
| 376 |
+
|
| 377 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 378 |
+
|
| 379 |
+
// MMA QK
|
| 380 |
+
warpgroup_fence_operand(acc_qk);
|
| 381 |
+
warpgroup_arrive();
|
| 382 |
+
|
| 383 |
+
gemm_zero_acc(tiled_mma_qk, tSrQ(_,_,_,_0{}), tSrK(_,_,_,smem_pipe_read.index()), acc_qk);
|
| 384 |
+
warpgroup_commit_batch();
|
| 385 |
+
|
| 386 |
+
++smem_pipe_read;
|
| 387 |
+
|
| 388 |
+
if (warp_idx == 0) {
|
| 389 |
+
load_k.template step<false>(k_tile_iter, load_state_k, smem_pipe_write, lane_predicate, k_tile_count_tma, mcast_mask_b);
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
// Wait for the pipeline MMAs to drain
|
| 393 |
+
warpgroup_wait<0>();
|
| 394 |
+
warpgroup_fence_operand(acc_qk);
|
| 395 |
+
warpgroup_fence_operand(acc_pv);
|
| 396 |
+
|
| 397 |
+
softmax.template step_interleave_begin<false>(acc_qk, tiled_mma_qk, tPcP, softmax_state, acc_pv, tiled_mma_pv, problem_size);
|
| 398 |
+
|
| 399 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 400 |
+
|
| 401 |
+
++smem_pipe_release;
|
| 402 |
+
|
| 403 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 404 |
+
|
| 405 |
+
// MMA PV
|
| 406 |
+
auto layout_qk_input = convert_c_layout_to_a_layout(acc_qk.layout(), shape<1>(typename decltype(tiled_mma_pv)::LayoutA_TV{}));
|
| 407 |
+
|
| 408 |
+
Tensor acc_qk_input = make_tensor(acc_qk.data(), layout_qk_input);
|
| 409 |
+
|
| 410 |
+
static_assert(decltype(size<1>(layout_qk_input) == _1{})::value);
|
| 411 |
+
CUTLASS_PRAGMA_UNROLL
|
| 412 |
+
for (int i = 0; i < size<2>(tOrV); i++) {
|
| 413 |
+
Tensor acc_qk_element = make_fragment_like<Element>(layout_qk_input(_, _0{}, _0{}));
|
| 414 |
+
Tensor acc_qk_element_mk = tensor_op_mk_v(tiled_mma_pv, acc_qk_element);
|
| 415 |
+
Tensor acc_qk_input_mk = tensor_op_mk_v(tiled_mma_pv, acc_qk_input(_, _0{}, i));
|
| 416 |
+
softmax.step_interleave_step(acc_qk_input_mk, softmax_state);
|
| 417 |
+
CUTLASS_PRAGMA_UNROLL
|
| 418 |
+
for (int j = 0; j < size(acc_qk_element_mk); j++) {
|
| 419 |
+
acc_qk_element_mk(j) = static_cast<Element>(acc_qk_input_mk(j));
|
| 420 |
+
}
|
| 421 |
+
warpgroup_arrive();
|
| 422 |
+
CUTLASS_PRAGMA_UNROLL
|
| 423 |
+
for (int j = 0; j < size<1>(tOrV); j++) {
|
| 424 |
+
cute::gemm(tiled_mma_pv, acc_qk_element, tOrV(_,j,i,smem_pipe_read.index()), acc_pv(_,_0{},j));
|
| 425 |
+
}
|
| 426 |
+
}
|
| 427 |
+
warpgroup_commit_batch();
|
| 428 |
+
|
| 429 |
+
// Wait for the pipeline MMAs to drain
|
| 430 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 431 |
+
++smem_pipe_release;
|
| 432 |
+
|
| 433 |
+
++smem_pipe_read;
|
| 434 |
+
|
| 435 |
+
if (warp_idx == 0) {
|
| 436 |
+
load_v.template step<true>(k_tile_iter, load_state_v, smem_pipe_write, lane_predicate, k_tile_count_tma, mcast_mask_b);
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
tPcP.data() = tPcP.data() + E<1>{} * get<1>(TileShapeQK{});
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
k_tile_count += Fusion{}.get_masked_trip_count(blk_coord, TileShape{}, problem_size);
|
| 443 |
+
|
| 444 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 445 |
+
for ( ; k_tile_count > 0; --k_tile_count)
|
| 446 |
+
{
|
| 447 |
+
// Allocate QK acc
|
| 448 |
+
Tensor acc_qk = partition_fragment_C(tiled_mma_qk, take<0, 2>(TileShapeQK{}));
|
| 449 |
+
|
| 450 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 451 |
+
|
| 452 |
+
// MMA QK
|
| 453 |
+
warpgroup_fence_operand(acc_qk);
|
| 454 |
+
warpgroup_arrive();
|
| 455 |
+
|
| 456 |
+
gemm_zero_acc(tiled_mma_qk, tSrQ(_,_,_,_0{}), tSrK(_,_,_,smem_pipe_read.index()), acc_qk);
|
| 457 |
+
warpgroup_commit_batch();
|
| 458 |
+
|
| 459 |
+
++smem_pipe_read;
|
| 460 |
+
|
| 461 |
+
if (warp_idx == 0) {
|
| 462 |
+
load_k.template step<false>(k_tile_iter, load_state_k, smem_pipe_write, lane_predicate, k_tile_count_tma, mcast_mask_b);
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
// Wait for the pipeline MMAs to drain
|
| 466 |
+
warpgroup_wait<0>();
|
| 467 |
+
warpgroup_fence_operand(acc_qk);
|
| 468 |
+
warpgroup_fence_operand(acc_pv);
|
| 469 |
+
|
| 470 |
+
softmax.step_interleave_begin(acc_qk, tiled_mma_qk, tPcP, softmax_state, acc_pv, tiled_mma_pv, problem_size);
|
| 471 |
+
|
| 472 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 473 |
+
|
| 474 |
+
++smem_pipe_release;
|
| 475 |
+
|
| 476 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 477 |
+
|
| 478 |
+
// MMA PV
|
| 479 |
+
auto layout_qk_input = convert_c_layout_to_a_layout(acc_qk.layout(), shape<1>(typename decltype(tiled_mma_pv)::LayoutA_TV{}));
|
| 480 |
+
|
| 481 |
+
Tensor acc_qk_input = make_tensor(acc_qk.data(), layout_qk_input);
|
| 482 |
+
|
| 483 |
+
static_assert(decltype(size<1>(layout_qk_input) == _1{})::value);
|
| 484 |
+
CUTLASS_PRAGMA_UNROLL
|
| 485 |
+
for (int i = 0; i < size<2>(tOrV); i++) {
|
| 486 |
+
Tensor acc_qk_element = make_fragment_like<Element>(layout_qk_input(_, _0{}, _0{}));
|
| 487 |
+
Tensor acc_qk_element_mk = tensor_op_mk_v(tiled_mma_pv, acc_qk_element);
|
| 488 |
+
Tensor acc_qk_input_mk = tensor_op_mk_v(tiled_mma_pv, acc_qk_input(_, _0{}, i));
|
| 489 |
+
softmax.step_interleave_step(acc_qk_input_mk, softmax_state);
|
| 490 |
+
CUTLASS_PRAGMA_UNROLL
|
| 491 |
+
for (int j = 0; j < size(acc_qk_element_mk); j++) {
|
| 492 |
+
acc_qk_element_mk(j) = static_cast<Element>(acc_qk_input_mk(j));
|
| 493 |
+
}
|
| 494 |
+
warpgroup_arrive();
|
| 495 |
+
CUTLASS_PRAGMA_UNROLL
|
| 496 |
+
for (int j = 0; j < size<1>(tOrV); j++) {
|
| 497 |
+
cute::gemm(tiled_mma_pv, acc_qk_element, tOrV(_,j,i,smem_pipe_read.index()), acc_pv(_,_0{},j));
|
| 498 |
+
}
|
| 499 |
+
}
|
| 500 |
+
warpgroup_commit_batch();
|
| 501 |
+
|
| 502 |
+
// Wait for the pipeline MMAs to drain
|
| 503 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 504 |
+
++smem_pipe_release;
|
| 505 |
+
|
| 506 |
+
++smem_pipe_read;
|
| 507 |
+
|
| 508 |
+
if (warp_idx == 0) {
|
| 509 |
+
load_v.template step<true>(k_tile_iter, load_state_v, smem_pipe_write, lane_predicate, k_tile_count_tma, mcast_mask_b);
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
tPcP.data() = tPcP.data() + E<1>{} * get<1>(TileShapeQK{});
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
// Wait for the pipeline MMAs to drain
|
| 516 |
+
warpgroup_wait<0>();
|
| 517 |
+
warpgroup_fence_operand(acc_pv);
|
| 518 |
+
|
| 519 |
+
Tensor lse = softmax.tail(softmax_state, acc_pv, tiled_mma_pv);
|
| 520 |
+
|
| 521 |
+
return make_tuple(acc_pv, lse);
|
| 522 |
+
}
|
| 523 |
+
};
|
| 524 |
+
|
| 525 |
+
} // namespace cutlass::fmha::collective
|
| 526 |
+
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_collective_tma_warpspecialized.hpp
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/gemm/collective/collective_builder.hpp"
|
| 36 |
+
|
| 37 |
+
#include "../collective/fmha_common.hpp"
|
| 38 |
+
#include "../collective/fmha_collective_load.hpp"
|
| 39 |
+
#include "../collective/fmha_collective_softmax.hpp"
|
| 40 |
+
#include "../kernel/fmha_options.hpp"
|
| 41 |
+
|
| 42 |
+
namespace cutlass::fmha::collective {
|
| 43 |
+
|
| 44 |
+
using namespace cute;
|
| 45 |
+
using cutlass::fmha::kernel::Tag;
|
| 46 |
+
using cutlass::fmha::kernel::find_option_t;
|
| 47 |
+
|
| 48 |
+
template<
|
| 49 |
+
class Element_,
|
| 50 |
+
class ElementAccumulatorQK_,
|
| 51 |
+
class ElementAccumulatorPV_,
|
| 52 |
+
class TileShape_, // SeqQ, SeqKV, Head
|
| 53 |
+
class LayoutQ_, class LayoutK_, class LayoutV_, // SeqX, Head, (Batches)
|
| 54 |
+
class Fusion,
|
| 55 |
+
class... Options
|
| 56 |
+
>
|
| 57 |
+
struct FmhaMainloopTmaWarpSpecialized {
|
| 58 |
+
|
| 59 |
+
using Element = Element_;
|
| 60 |
+
using ElementAccumulatorQK = ElementAccumulatorQK_;
|
| 61 |
+
using ElementAccumulatorPV = ElementAccumulatorPV_;
|
| 62 |
+
using TileShape = TileShape_;
|
| 63 |
+
|
| 64 |
+
using LayoutQ = LayoutQ_;
|
| 65 |
+
using LayoutK = LayoutK_;
|
| 66 |
+
using LayoutV = LayoutV_;
|
| 67 |
+
|
| 68 |
+
// Options
|
| 69 |
+
static constexpr bool kIsPersistent = find_option_t<Tag::kIsPersistent, false_type, Options...>::value;
|
| 70 |
+
static constexpr bool kIsMainloopLocked = find_option_t<Tag::kIsMainloopLocked, false_type, Options...>::value;
|
| 71 |
+
|
| 72 |
+
static constexpr int NumLoadWarpGroups = 1;
|
| 73 |
+
static constexpr int NumMmaWarpGroups = find_option_t<Tag::kNumMmaWarpGroups, Int<2>, Options...>::value;
|
| 74 |
+
static constexpr int StageCount = find_option_t<Tag::kStagesKV, Int<5>, Options...>::value;
|
| 75 |
+
static constexpr int StageCountQ = find_option_t<Tag::kStagesQ, Int<NumMmaWarpGroups>, Options...>::value;
|
| 76 |
+
|
| 77 |
+
static const int kOuterLoads = 1;
|
| 78 |
+
using StagesQ = cutlass::gemm::collective::StageCount<StageCountQ>;
|
| 79 |
+
using Stages = cutlass::gemm::collective::StageCount<StageCount>;
|
| 80 |
+
using ClusterShape = Shape<_1, _1, _1>;
|
| 81 |
+
static_assert(StagesQ::value >= NumMmaWarpGroups);
|
| 82 |
+
static_assert(Stages::value >= 2);
|
| 83 |
+
|
| 84 |
+
// 16B alignment lets us use TMA
|
| 85 |
+
static constexpr int Alignment = 16 / sizeof(Element);
|
| 86 |
+
|
| 87 |
+
using TileShapeQK = Shape<
|
| 88 |
+
decltype(tuple_element_t<0, TileShape>{} / Int<NumMmaWarpGroups>{}),
|
| 89 |
+
tuple_element_t<1, TileShape>,
|
| 90 |
+
tuple_element_t<2, TileShape>>;
|
| 91 |
+
|
| 92 |
+
using TileShapePV = decltype(select<0,2,1>(TileShapeQK{}));
|
| 93 |
+
|
| 94 |
+
using CollectiveMmaQK = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 95 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 96 |
+
Element, LayoutQ, Alignment,
|
| 97 |
+
Element, LayoutK, Alignment,
|
| 98 |
+
ElementAccumulatorQK,
|
| 99 |
+
TileShapeQK, ClusterShape, Stages,
|
| 100 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 101 |
+
|
| 102 |
+
using CollectiveMmaPV = typename cutlass::gemm::collective::CollectiveBuilder<
|
| 103 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 104 |
+
// the stride for A does not matter since we do not load from smem at all
|
| 105 |
+
Element, LayoutK, Alignment,
|
| 106 |
+
Element, decltype(select<1,0,2>(LayoutV{})), Alignment,
|
| 107 |
+
ElementAccumulatorPV,
|
| 108 |
+
TileShapePV, ClusterShape, Stages,
|
| 109 |
+
cutlass::gemm::KernelTmaWarpSpecialized>::CollectiveOp;
|
| 110 |
+
|
| 111 |
+
using TiledMmaQK = typename CollectiveMmaQK::TiledMma;
|
| 112 |
+
using TiledMmaPV = decltype(convert_to_gmma_rs(typename CollectiveMmaPV::TiledMma{}));
|
| 113 |
+
|
| 114 |
+
using SmemLayoutQ = decltype(unstageSmemLayout(typename CollectiveMmaQK::SmemLayoutA{}, Int<StagesQ::value>{}));
|
| 115 |
+
using SmemLayoutK = typename CollectiveMmaQK::SmemLayoutB;
|
| 116 |
+
using SmemLayoutV = typename CollectiveMmaPV::SmemLayoutB;
|
| 117 |
+
|
| 118 |
+
using MainloopPipeline = cutlass::PipelineTmaAsync<Stages::value>;
|
| 119 |
+
using MainloopPipelineQ = cutlass::PipelineTmaAsync<StagesQ::value>;
|
| 120 |
+
|
| 121 |
+
using PipelineState = typename cutlass::PipelineState<MainloopPipeline::Stages>;
|
| 122 |
+
using PipelineStateQ = typename cutlass::PipelineState<MainloopPipelineQ::Stages>;
|
| 123 |
+
|
| 124 |
+
static constexpr int kInnerLoadBytes = size(SmemLayoutK{}(_,_,_0{})) * sizeof(Element);
|
| 125 |
+
static constexpr int kOuterLoadBytes = size(SmemLayoutQ{}(_,_,_0{})) * sizeof(Element);
|
| 126 |
+
|
| 127 |
+
using TileShapeOut = TileShapePV;
|
| 128 |
+
using TiledMmaOut = TiledMmaPV;
|
| 129 |
+
using ElementOut = ElementAccumulatorPV;
|
| 130 |
+
|
| 131 |
+
struct SharedStorage {
|
| 132 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutQ>> smem_q;
|
| 133 |
+
union {
|
| 134 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutK>> smem_k;
|
| 135 |
+
cute::array_aligned<Element, cute::cosize_v<SmemLayoutV>> smem_v;
|
| 136 |
+
};
|
| 137 |
+
};
|
| 138 |
+
|
| 139 |
+
struct Arguments {
|
| 140 |
+
const Element* ptr_Q;
|
| 141 |
+
LayoutQ dQ;
|
| 142 |
+
const Element* ptr_K;
|
| 143 |
+
LayoutK dK;
|
| 144 |
+
const Element* ptr_V;
|
| 145 |
+
LayoutV dV;
|
| 146 |
+
};
|
| 147 |
+
|
| 148 |
+
using TMA_Q = typename CollectiveMmaQK::Params::TMA_A;
|
| 149 |
+
using TMA_K = typename CollectiveMmaQK::Params::TMA_B;
|
| 150 |
+
using TMA_V = typename CollectiveMmaPV::Params::TMA_B;
|
| 151 |
+
|
| 152 |
+
struct Params {
|
| 153 |
+
TMA_Q tma_load_q;
|
| 154 |
+
TMA_K tma_load_k;
|
| 155 |
+
TMA_V tma_load_v;
|
| 156 |
+
|
| 157 |
+
float scale_softmax;
|
| 158 |
+
float scale_softmax_log2;
|
| 159 |
+
float rp_dropout;
|
| 160 |
+
};
|
| 161 |
+
|
| 162 |
+
using LoadQ = cutlass::fmha::collective::CollectiveLoadTma<
|
| 163 |
+
cutlass::fmha::collective::LoadKind::kQ,
|
| 164 |
+
MainloopPipelineQ,
|
| 165 |
+
Element,
|
| 166 |
+
SmemLayoutQ,
|
| 167 |
+
TMA_Q
|
| 168 |
+
>;
|
| 169 |
+
|
| 170 |
+
using LoadK = cutlass::fmha::collective::CollectiveLoadTma<
|
| 171 |
+
cutlass::fmha::collective::LoadKind::kK,
|
| 172 |
+
MainloopPipeline,
|
| 173 |
+
Element,
|
| 174 |
+
SmemLayoutK,
|
| 175 |
+
TMA_K
|
| 176 |
+
>;
|
| 177 |
+
|
| 178 |
+
using LoadV = cutlass::fmha::collective::CollectiveLoadTma<
|
| 179 |
+
cutlass::fmha::collective::LoadKind::kV,
|
| 180 |
+
MainloopPipeline,
|
| 181 |
+
Element,
|
| 182 |
+
SmemLayoutV,
|
| 183 |
+
TMA_V
|
| 184 |
+
>;
|
| 185 |
+
|
| 186 |
+
static_assert(size(typename CollectiveMmaQK::TiledMma{}) == size(typename CollectiveMmaPV::TiledMma{}));
|
| 187 |
+
|
| 188 |
+
template<class ProblemShape>
|
| 189 |
+
static bool can_implement(ProblemShape const& problem_size, Arguments const& args) {
|
| 190 |
+
return true
|
| 191 |
+
&& (get<4>(problem_size) <= get<2>(TileShape{}))
|
| 192 |
+
&& ((get<4>(problem_size) % Alignment) == 0)
|
| 193 |
+
&& ((get<2>(problem_size) % Alignment) == 0)
|
| 194 |
+
;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
template<class ProblemShape>
|
| 198 |
+
static Params to_underlying_arguments(ProblemShape const& problem_size, Arguments const& args, void* workspace) {
|
| 199 |
+
|
| 200 |
+
auto problem_shape_qk = make_shape(get<2>(problem_size), get<3>(problem_size), get<4>(problem_size), make_shape(get<0>(problem_size), get<1>(problem_size)));
|
| 201 |
+
auto params_qk = CollectiveMmaQK::to_underlying_arguments(problem_shape_qk,
|
| 202 |
+
typename CollectiveMmaQK::Arguments {
|
| 203 |
+
args.ptr_Q, args.dQ,
|
| 204 |
+
args.ptr_K, args.dK,
|
| 205 |
+
}, /*workspace=*/ nullptr);
|
| 206 |
+
|
| 207 |
+
auto problem_shape_pv = select<0,2,1,3>(problem_shape_qk);
|
| 208 |
+
auto params_pv = CollectiveMmaPV::to_underlying_arguments(problem_shape_pv,
|
| 209 |
+
typename CollectiveMmaPV::Arguments {
|
| 210 |
+
args.ptr_K, args.dK, // never used, dummy
|
| 211 |
+
args.ptr_V, select<1,0,2>(args.dV),
|
| 212 |
+
}, /*workspace=*/ nullptr);
|
| 213 |
+
|
| 214 |
+
return Params{
|
| 215 |
+
params_qk.tma_load_a,
|
| 216 |
+
params_qk.tma_load_b,
|
| 217 |
+
params_pv.tma_load_b,
|
| 218 |
+
1.0f / (float) std::sqrt(get<4>(problem_size)),
|
| 219 |
+
(float) (std::log2(std::exp(1.0)) / std::sqrt(get<4>(problem_size))),
|
| 220 |
+
1.0f
|
| 221 |
+
};
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
CUTLASS_DEVICE
|
| 225 |
+
static void prefetch_tma_descriptors(Params const& params) {
|
| 226 |
+
cute::prefetch_tma_descriptor(params.tma_load_q.get_tma_descriptor());
|
| 227 |
+
cute::prefetch_tma_descriptor(params.tma_load_k.get_tma_descriptor());
|
| 228 |
+
cute::prefetch_tma_descriptor(params.tma_load_v.get_tma_descriptor());
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
template<bool kLoadQ, class BlkCoord, class ProblemShape, class LoadWarpBarrier>
|
| 232 |
+
CUTLASS_DEVICE void
|
| 233 |
+
load_kv_maybe_q(
|
| 234 |
+
int block_rank_in_cluster,
|
| 235 |
+
BlkCoord const& blk_coord, Params const& params, ProblemShape const& problem_size,
|
| 236 |
+
MainloopPipeline& pipeline, PipelineState& smem_pipe_write,
|
| 237 |
+
MainloopPipelineQ& pipeline_q, PipelineStateQ& smem_pipe_write_q,
|
| 238 |
+
SharedStorage& storage,
|
| 239 |
+
LoadWarpBarrier& load_warp_barrier, bool do_barrier)
|
| 240 |
+
{
|
| 241 |
+
int fusion_tile_count = Fusion{}.get_trip_count(blk_coord, TileShape{}, problem_size);
|
| 242 |
+
|
| 243 |
+
int lane_predicate = cute::elect_one_sync();
|
| 244 |
+
|
| 245 |
+
uint16_t mcast_mask_b = 0;
|
| 246 |
+
|
| 247 |
+
if (lane_predicate == 1) {
|
| 248 |
+
if constexpr (cute::is_same_v<typename CollectiveMmaQK::GmemTiledCopyB, SM90_TMA_LOAD_MULTICAST>) {
|
| 249 |
+
auto block_layout = Layout<ClusterShape>{}; // (m,n) -> block_id
|
| 250 |
+
for (int m = 0; m < size<0>(block_layout); ++m) {
|
| 251 |
+
mcast_mask_b |= (uint16_t(1) << block_layout(m,_0{},Int<0>{}));
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
auto q_tile_iter = cute::make_coord_iterator(Int<NumMmaWarpGroups>{});
|
| 257 |
+
[[maybe_unused]] int q_tile_count = NumMmaWarpGroups;
|
| 258 |
+
|
| 259 |
+
auto k_tile_iter = cute::make_coord_iterator(fusion_tile_count);
|
| 260 |
+
int k_tile_count = 2 * fusion_tile_count;
|
| 261 |
+
|
| 262 |
+
LoadQ load_q{params.tma_load_q, pipeline_q, storage.smem_q};
|
| 263 |
+
auto load_state_q = load_q.init_state(_0{}, problem_size, TileShapeQK{}, blk_coord, NumMmaWarpGroups);
|
| 264 |
+
|
| 265 |
+
LoadK load_k{params.tma_load_k, pipeline, storage.smem_k};
|
| 266 |
+
auto load_state_k = load_k.init_state(block_rank_in_cluster, problem_size, TileShapeQK{}, blk_coord, fusion_tile_count);
|
| 267 |
+
|
| 268 |
+
LoadV load_v{params.tma_load_v, pipeline, storage.smem_v};
|
| 269 |
+
auto load_state_v = load_v.init_state(block_rank_in_cluster, problem_size, TileShapePV{}, blk_coord, fusion_tile_count);
|
| 270 |
+
|
| 271 |
+
if constexpr (kLoadQ) {
|
| 272 |
+
load_q.step(q_tile_iter, load_state_q, smem_pipe_write_q, lane_predicate, q_tile_count);
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
load_k.template step<false>(k_tile_iter, load_state_k, smem_pipe_write, lane_predicate, k_tile_count, mcast_mask_b);
|
| 276 |
+
|
| 277 |
+
if constexpr (kLoadQ) {
|
| 278 |
+
load_q.step(q_tile_iter, load_state_q, smem_pipe_write_q, lane_predicate, q_tile_count);
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
if constexpr (! kLoadQ) {
|
| 282 |
+
if (do_barrier) {
|
| 283 |
+
load_warp_barrier.arrive();
|
| 284 |
+
load_warp_barrier.wait(/*phase=*/ 0);
|
| 285 |
+
do_barrier = false;
|
| 286 |
+
}
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
load_v.template step<true>(k_tile_iter, load_state_v, smem_pipe_write, lane_predicate, k_tile_count, mcast_mask_b);
|
| 290 |
+
|
| 291 |
+
if constexpr (kLoadQ) {
|
| 292 |
+
while (q_tile_count > 0) {
|
| 293 |
+
load_q.step(q_tile_iter, load_state_q, smem_pipe_write_q, lane_predicate, q_tile_count);
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 298 |
+
while (k_tile_count > 0) {
|
| 299 |
+
load_k.template step<false>(k_tile_iter, load_state_k, smem_pipe_write, lane_predicate, k_tile_count, mcast_mask_b);
|
| 300 |
+
load_v.template step<true>(k_tile_iter, load_state_v, smem_pipe_write, lane_predicate, k_tile_count, mcast_mask_b);
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
template<class BlkCoord, class ProblemShape, class LoadWarpBarrier>
|
| 305 |
+
CUTLASS_DEVICE void
|
| 306 |
+
load_maybe_q(
|
| 307 |
+
BlkCoord const& blk_coord, Params const& params, ProblemShape const& problem_size,
|
| 308 |
+
MainloopPipelineQ& pipeline_q, PipelineStateQ& smem_pipe_write_q,
|
| 309 |
+
SharedStorage& storage,
|
| 310 |
+
LoadWarpBarrier& load_warp_barrier, bool do_barrier)
|
| 311 |
+
{
|
| 312 |
+
int lane_predicate = cute::elect_one_sync();
|
| 313 |
+
|
| 314 |
+
LoadQ load_q{params.tma_load_q, pipeline_q, storage.smem_q};
|
| 315 |
+
auto load_state_q = load_q.init_state(_0{}, problem_size, TileShapeQK{}, blk_coord, NumMmaWarpGroups);
|
| 316 |
+
|
| 317 |
+
auto q_tile_iter = cute::make_coord_iterator(Int<NumMmaWarpGroups>{});
|
| 318 |
+
|
| 319 |
+
CUTLASS_PRAGMA_UNROLL
|
| 320 |
+
for (int q_tile_count = 0; q_tile_count < NumMmaWarpGroups; q_tile_count++) {
|
| 321 |
+
int count = 1;
|
| 322 |
+
load_q.step(q_tile_iter, load_state_q, smem_pipe_write_q, lane_predicate, count);
|
| 323 |
+
if (q_tile_count == 0 && do_barrier) {
|
| 324 |
+
load_warp_barrier.arrive();
|
| 325 |
+
load_warp_barrier.wait(/*phase=*/ 0);
|
| 326 |
+
do_barrier = false;
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
template<class BlkCoord, class ProblemShape, class MainloopPipelineReducer, class PipelineStateReducer>
|
| 332 |
+
CUTLASS_DEVICE void
|
| 333 |
+
reduce(
|
| 334 |
+
BlkCoord const& blk_coord, Params const& params, ProblemShape const& problem_size,
|
| 335 |
+
MainloopPipelineReducer& pipeline_reducer, PipelineStateReducer& smem_pipe_write_reducer,
|
| 336 |
+
SharedStorage& storage)
|
| 337 |
+
{ /* no-op */ }
|
| 338 |
+
|
| 339 |
+
template<class BlkCoord, class ProblemShape, class MainloopPipelineReducer, class PipelineStateReducer, class MathWgOrderBarrier>
|
| 340 |
+
CUTLASS_DEVICE auto
|
| 341 |
+
compute(
|
| 342 |
+
BlkCoord const& blk_coord, BlkCoord const& wg_coord,
|
| 343 |
+
Params const& params, ProblemShape const& problem_size,
|
| 344 |
+
MainloopPipeline& pipeline, PipelineState& smem_pipe_read,
|
| 345 |
+
MainloopPipelineQ& pipeline_q, PipelineStateQ& smem_pipe_read_q,
|
| 346 |
+
MainloopPipelineReducer&, PipelineStateReducer&,
|
| 347 |
+
SharedStorage& storage,
|
| 348 |
+
MathWgOrderBarrier& math_wg_order_barrier)
|
| 349 |
+
{
|
| 350 |
+
int thread_idx = int(threadIdx.x);
|
| 351 |
+
|
| 352 |
+
PipelineState smem_pipe_release = smem_pipe_read;
|
| 353 |
+
PipelineStateQ smem_pipe_release_q = smem_pipe_read_q;
|
| 354 |
+
|
| 355 |
+
TiledMmaQK tiled_mma_qk;
|
| 356 |
+
auto thr_mma_qk = tiled_mma_qk.get_thread_slice(thread_idx);
|
| 357 |
+
|
| 358 |
+
// Mainloop setup QK
|
| 359 |
+
Tensor sQ = make_tensor(make_smem_ptr(storage.smem_q.data()), SmemLayoutQ{});
|
| 360 |
+
Tensor sK = make_tensor(make_smem_ptr(storage.smem_k.data()), SmemLayoutK{});
|
| 361 |
+
|
| 362 |
+
Tensor tSsQ = thr_mma_qk.partition_A(sQ); // (MMA,MMA_M,MMA_K,PIPE)
|
| 363 |
+
Tensor tSsK = thr_mma_qk.partition_B(sK); // (MMA,MMA_N,MMA_K,PIPE)
|
| 364 |
+
Tensor tSrQ = thr_mma_qk.make_fragment_A(tSsQ); // (MMA,MMA_N,MMA_K,PIPE)
|
| 365 |
+
Tensor tSrK = thr_mma_qk.make_fragment_B(tSsK); // (MMA,MMA_M,MMA_N,PIPE)
|
| 366 |
+
|
| 367 |
+
// Prepare: MMA PV
|
| 368 |
+
TiledMmaPV tiled_mma_pv;
|
| 369 |
+
auto thr_mma_pv = tiled_mma_pv.get_thread_slice(thread_idx);
|
| 370 |
+
|
| 371 |
+
// Mainloop setup PV
|
| 372 |
+
Tensor sV = make_tensor(make_smem_ptr(storage.smem_v.data()), SmemLayoutV{});
|
| 373 |
+
|
| 374 |
+
Tensor tOsV = thr_mma_pv.partition_B(sV); // (MMA,MMA_N,MMA_K,PIPE)
|
| 375 |
+
Tensor tOrV = thr_mma_pv.make_fragment_B(tOsV); // (MMA,MMA_M,MMA_N,PIPE)
|
| 376 |
+
|
| 377 |
+
int k_tile_count = Fusion{}.get_unmasked_trip_count(blk_coord, TileShape{}, problem_size);
|
| 378 |
+
|
| 379 |
+
pipeline_q.consumer_wait(smem_pipe_read_q);
|
| 380 |
+
|
| 381 |
+
// mapping into QK accumulator
|
| 382 |
+
Tensor cP = make_identity_tensor(take<0,2>(TileShapeQK{}));
|
| 383 |
+
Tensor tPcP = thr_mma_qk.partition_C(cP);
|
| 384 |
+
int m_block = get<0>(wg_coord);
|
| 385 |
+
tPcP.data() = tPcP.data() + E<0>{} * m_block * get<0>(TileShapeQK{});
|
| 386 |
+
|
| 387 |
+
// Allocate PV acc
|
| 388 |
+
Tensor acc_pv = partition_fragment_C(tiled_mma_pv, take<0, 2>(TileShapePV{}));
|
| 389 |
+
|
| 390 |
+
cutlass::fmha::collective::CollectiveSoftmax<ElementAccumulatorQK, Fusion, decltype(params)> softmax{params};
|
| 391 |
+
auto softmax_state = softmax.init(acc_pv, tiled_mma_pv);
|
| 392 |
+
|
| 393 |
+
if (true)
|
| 394 |
+
{
|
| 395 |
+
--k_tile_count;
|
| 396 |
+
// Allocate QK acc
|
| 397 |
+
Tensor acc_qk = partition_fragment_C(tiled_mma_qk, take<0, 2>(TileShapeQK{}));
|
| 398 |
+
|
| 399 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 400 |
+
math_wg_order_barrier.wait();
|
| 401 |
+
|
| 402 |
+
// MMA QK
|
| 403 |
+
warpgroup_fence_operand(acc_qk);
|
| 404 |
+
warpgroup_arrive();
|
| 405 |
+
|
| 406 |
+
gemm_zero_acc(tiled_mma_qk, tSrQ(_,_,_,smem_pipe_read_q.index()), tSrK(_,_,_,smem_pipe_read.index()), acc_qk);
|
| 407 |
+
warpgroup_commit_batch();
|
| 408 |
+
math_wg_order_barrier.arrive();
|
| 409 |
+
|
| 410 |
+
++smem_pipe_read;
|
| 411 |
+
|
| 412 |
+
// Wait for the pipeline MMAs to drain
|
| 413 |
+
warpgroup_wait<0>();
|
| 414 |
+
warpgroup_fence_operand(acc_qk);
|
| 415 |
+
|
| 416 |
+
softmax.step(acc_qk, tiled_mma_qk, tPcP, softmax_state, problem_size);
|
| 417 |
+
|
| 418 |
+
Tensor acc_qk_fixed = make_acc_into_op<Element>(acc_qk, typename TiledMmaPV::LayoutA_TV{});
|
| 419 |
+
|
| 420 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 421 |
+
|
| 422 |
+
// MMA PV
|
| 423 |
+
warpgroup_fence_operand(acc_pv);
|
| 424 |
+
warpgroup_fence_operand(acc_qk_fixed);
|
| 425 |
+
warpgroup_arrive();
|
| 426 |
+
|
| 427 |
+
gemm_zero_acc(tiled_mma_pv, acc_qk_fixed, tOrV(_,_,_,smem_pipe_read.index()), acc_pv);
|
| 428 |
+
warpgroup_commit_batch();
|
| 429 |
+
|
| 430 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 431 |
+
++smem_pipe_release;
|
| 432 |
+
|
| 433 |
+
// Advance consumer pipeline
|
| 434 |
+
++smem_pipe_read;
|
| 435 |
+
tPcP.data() = tPcP.data() + E<1>{} * get<1>(TileShapeQK{});
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 439 |
+
while (k_tile_count > 0)
|
| 440 |
+
{
|
| 441 |
+
--k_tile_count;
|
| 442 |
+
|
| 443 |
+
// Allocate QK acc
|
| 444 |
+
Tensor acc_qk = partition_fragment_C(tiled_mma_qk, take<0, 2>(TileShapeQK{}));
|
| 445 |
+
|
| 446 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 447 |
+
|
| 448 |
+
// MMA QK
|
| 449 |
+
warpgroup_fence_operand(acc_qk);
|
| 450 |
+
warpgroup_arrive();
|
| 451 |
+
|
| 452 |
+
gemm_zero_acc(tiled_mma_qk, tSrQ(_,_,_,smem_pipe_read_q.index()), tSrK(_,_,_,smem_pipe_read.index()), acc_qk);
|
| 453 |
+
warpgroup_commit_batch();
|
| 454 |
+
|
| 455 |
+
++smem_pipe_read;
|
| 456 |
+
auto tok = pipeline.consumer_try_wait(smem_pipe_read);
|
| 457 |
+
|
| 458 |
+
// Wait for the pipeline MMAs to drain
|
| 459 |
+
warpgroup_wait<0>();
|
| 460 |
+
warpgroup_fence_operand(acc_qk);
|
| 461 |
+
warpgroup_fence_operand(acc_pv);
|
| 462 |
+
|
| 463 |
+
if constexpr (kIsMainloopLocked) math_wg_order_barrier.wait();
|
| 464 |
+
softmax.template step<false>(acc_qk, tiled_mma_qk, tPcP, softmax_state, acc_pv, tiled_mma_pv, problem_size);
|
| 465 |
+
if constexpr (kIsMainloopLocked) math_wg_order_barrier.arrive();
|
| 466 |
+
|
| 467 |
+
Tensor acc_qk_fixed = make_acc_into_op<Element>(acc_qk, typename TiledMmaPV::LayoutA_TV{});
|
| 468 |
+
|
| 469 |
+
pipeline.consumer_wait(smem_pipe_read, tok);
|
| 470 |
+
|
| 471 |
+
// MMA PV
|
| 472 |
+
warpgroup_fence_operand(acc_pv);
|
| 473 |
+
warpgroup_fence_operand(acc_qk_fixed);
|
| 474 |
+
warpgroup_arrive();
|
| 475 |
+
|
| 476 |
+
cute::gemm(tiled_mma_pv, acc_qk_fixed, tOrV(_,_,_,smem_pipe_read.index()), acc_pv);
|
| 477 |
+
warpgroup_commit_batch();
|
| 478 |
+
|
| 479 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 480 |
+
++smem_pipe_release;
|
| 481 |
+
|
| 482 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 483 |
+
++smem_pipe_release;
|
| 484 |
+
|
| 485 |
+
++smem_pipe_read;
|
| 486 |
+
tPcP.data() = tPcP.data() + E<1>{} * get<1>(TileShapeQK{});
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
k_tile_count += Fusion{}.get_masked_trip_count(blk_coord, TileShape{}, problem_size);
|
| 490 |
+
|
| 491 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 492 |
+
while (k_tile_count > 0)
|
| 493 |
+
{
|
| 494 |
+
--k_tile_count;
|
| 495 |
+
|
| 496 |
+
// Allocate QK acc
|
| 497 |
+
Tensor acc_qk = partition_fragment_C(tiled_mma_qk, take<0, 2>(TileShapeQK{}));
|
| 498 |
+
|
| 499 |
+
pipeline.consumer_wait(smem_pipe_read);
|
| 500 |
+
|
| 501 |
+
// MMA QK
|
| 502 |
+
warpgroup_fence_operand(acc_qk);
|
| 503 |
+
warpgroup_arrive();
|
| 504 |
+
|
| 505 |
+
gemm_zero_acc(tiled_mma_qk, tSrQ(_,_,_,smem_pipe_read_q.index()), tSrK(_,_,_,smem_pipe_read.index()), acc_qk);
|
| 506 |
+
warpgroup_commit_batch();
|
| 507 |
+
|
| 508 |
+
++smem_pipe_read;
|
| 509 |
+
auto tok = pipeline.consumer_try_wait(smem_pipe_read);
|
| 510 |
+
|
| 511 |
+
// Wait for the pipeline MMAs to drain
|
| 512 |
+
warpgroup_wait<0>();
|
| 513 |
+
warpgroup_fence_operand(acc_qk);
|
| 514 |
+
warpgroup_fence_operand(acc_pv);
|
| 515 |
+
|
| 516 |
+
//if constexpr (kIsPersistent)
|
| 517 |
+
// if (k_tile_count == 0) pipeline_q.consumer_release(smem_pipe_release_q);
|
| 518 |
+
|
| 519 |
+
if constexpr (kIsMainloopLocked) math_wg_order_barrier.wait();
|
| 520 |
+
softmax.step(acc_qk, tiled_mma_qk, tPcP, softmax_state, acc_pv, tiled_mma_pv, problem_size);
|
| 521 |
+
if constexpr (kIsMainloopLocked) math_wg_order_barrier.arrive();
|
| 522 |
+
|
| 523 |
+
Tensor acc_qk_fixed = make_acc_into_op<Element>(acc_qk, typename TiledMmaPV::LayoutA_TV{});
|
| 524 |
+
|
| 525 |
+
pipeline.consumer_wait(smem_pipe_read, tok);
|
| 526 |
+
|
| 527 |
+
// MMA PV
|
| 528 |
+
warpgroup_fence_operand(acc_pv);
|
| 529 |
+
warpgroup_fence_operand(acc_qk_fixed);
|
| 530 |
+
warpgroup_arrive();
|
| 531 |
+
|
| 532 |
+
cute::gemm(tiled_mma_pv, acc_qk_fixed, tOrV(_,_,_,smem_pipe_read.index()), acc_pv);
|
| 533 |
+
warpgroup_commit_batch();
|
| 534 |
+
|
| 535 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 536 |
+
++smem_pipe_release;
|
| 537 |
+
|
| 538 |
+
pipeline.consumer_release(smem_pipe_release);
|
| 539 |
+
++smem_pipe_release;
|
| 540 |
+
|
| 541 |
+
++smem_pipe_read;
|
| 542 |
+
tPcP.data() = tPcP.data() + E<1>{} * get<1>(TileShapeQK{});
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
if (kIsPersistent) pipeline_q.consumer_release(smem_pipe_release_q);
|
| 546 |
+
|
| 547 |
+
// Wait for the pipeline MMAs to drain
|
| 548 |
+
warpgroup_wait<0>();
|
| 549 |
+
warpgroup_fence_operand(acc_pv);
|
| 550 |
+
|
| 551 |
+
if (kIsPersistent) pipeline.consumer_release(smem_pipe_release);
|
| 552 |
+
++smem_pipe_release;
|
| 553 |
+
|
| 554 |
+
Tensor lse = softmax.tail(softmax_state, acc_pv, tiled_mma_pv);
|
| 555 |
+
|
| 556 |
+
return make_tuple(acc_pv, lse);
|
| 557 |
+
}
|
| 558 |
+
};
|
| 559 |
+
|
| 560 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_common.hpp
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/kernel_hardware_info.h"
|
| 35 |
+
#include "cute/tensor.hpp"
|
| 36 |
+
|
| 37 |
+
namespace cutlass::fmha::collective {
|
| 38 |
+
|
| 39 |
+
using namespace cute;
|
| 40 |
+
|
| 41 |
+
template<typename Atom, typename TA, typename TB, typename TC>
|
| 42 |
+
CUTE_DEVICE void gemm_reset_zero_acc(Atom& atom, TA const& tA, TB const& tB, TC&& tC) {
|
| 43 |
+
constexpr int rA = decltype(rank(tA))::value;
|
| 44 |
+
constexpr int rB = decltype(rank(tB))::value;
|
| 45 |
+
constexpr int rC = decltype(rank(tC))::value;
|
| 46 |
+
if constexpr (rA == 2 && rB == 2 && rC == 1) {
|
| 47 |
+
CUTLASS_PRAGMA_UNROLL
|
| 48 |
+
for (int k_block = 0; k_block < size<1>(tA); k_block++) {
|
| 49 |
+
cute::gemm(atom, tA(_,k_block), tB(_,k_block), tC);
|
| 50 |
+
atom.accumulate_ = GMMA::ScaleOut::One;
|
| 51 |
+
}
|
| 52 |
+
} else {
|
| 53 |
+
static_assert(rA == 3 && rB == 3 && rC == 3);
|
| 54 |
+
CUTLASS_PRAGMA_UNROLL
|
| 55 |
+
for (int k_block = 0; k_block < size<2>(tA); k_block++) {
|
| 56 |
+
cute::gemm(atom, tA(_,_,k_block), tB(_,_,k_block), tC);
|
| 57 |
+
atom.accumulate_ = GMMA::ScaleOut::One;
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
template<typename Atom, typename TA, typename TB, typename TC>
|
| 63 |
+
CUTE_DEVICE void gemm_zero_acc(Atom& atom, TA const& tA, TB const& tB, TC&& tC) {
|
| 64 |
+
atom.accumulate_ = GMMA::ScaleOut::Zero;
|
| 65 |
+
gemm_reset_zero_acc(atom, tA, tB, tC);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
template<typename T, typename Fn>
|
| 69 |
+
CUTE_DEVICE constexpr typename T::value_type reduce(T const& t, Fn fn) {
|
| 70 |
+
if constexpr (decltype(size(t) % _2{} == _0{})::value) {
|
| 71 |
+
auto partial = make_tensor<typename T::value_type>(size(t) / _2{});
|
| 72 |
+
CUTE_UNROLL
|
| 73 |
+
for (int i = 0; i < size(partial); i++) {
|
| 74 |
+
partial(i) = fn(t(i), t(i + size(partial)));
|
| 75 |
+
}
|
| 76 |
+
return reduce(partial, fn);
|
| 77 |
+
} else {
|
| 78 |
+
auto result = t(_0{});
|
| 79 |
+
CUTE_UNROLL
|
| 80 |
+
for (int i = 1; i < size(t); i++) {
|
| 81 |
+
result = fn(result, t(i));
|
| 82 |
+
}
|
| 83 |
+
return result;
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
struct fmha_max {
|
| 88 |
+
CUTE_DEVICE float operator()(float a, float b) { return ::max(a, b); }
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
template<typename Threshold, typename Source, typename Reference>
|
| 92 |
+
inline auto __device__ constexpr layout_separate(Threshold const& thr,
|
| 93 |
+
Source const& src, Reference const& ref) {
|
| 94 |
+
auto lt = filter(transform_layout(src, ref, [&](auto const& s, auto const& r) {
|
| 95 |
+
if constexpr(decltype(r < thr)::value) {
|
| 96 |
+
return s;
|
| 97 |
+
} else {
|
| 98 |
+
return make_layout(_1{}, _0{});
|
| 99 |
+
}
|
| 100 |
+
}));
|
| 101 |
+
auto ge = filter(transform_layout(src, ref, [&](auto const& s, auto const& r) {
|
| 102 |
+
if constexpr(decltype(r >= thr)::value) {
|
| 103 |
+
return s;
|
| 104 |
+
} else {
|
| 105 |
+
return make_layout(_1{}, _0{});
|
| 106 |
+
}
|
| 107 |
+
}));
|
| 108 |
+
return make_layout(lt, ge);
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
template<typename TiledMma, typename Acc>
|
| 112 |
+
inline auto __device__ constexpr layout_acc_mn(TiledMma const& tiled_mma, Acc const& acc) {
|
| 113 |
+
auto separated = layout_separate(get<0>(typename TiledMma::Shape_MNK{}),
|
| 114 |
+
get<0>(acc), stride<1>(typename TiledMma::LayoutC_TV{}));
|
| 115 |
+
auto V_M = get<0>(separated);
|
| 116 |
+
auto V_N = get<1>(separated);
|
| 117 |
+
return make_layout(make_layout(V_M, get<1>(acc)), make_layout(V_N, get<2>(acc)));
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
template<typename TiledMma, typename Acc>
|
| 121 |
+
inline auto __device__ constexpr layout_op_mk_v(TiledMma const& tiled_mma, Acc const& acc) {
|
| 122 |
+
return layout_separate(get<0>(typename TiledMma::Shape_MNK{}),
|
| 123 |
+
get<0>(acc), stride<1>(typename TiledMma::LayoutA_TV{}));
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
template<typename TiledMma, typename Acc>
|
| 127 |
+
inline auto __device__ constexpr tensor_op_mk_v(TiledMma const& tiled_mma, Acc&& acc) {
|
| 128 |
+
return make_tensor(acc.data(), layout_op_mk_v(tiled_mma, acc.layout()));
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
template<typename TiledMma>
|
| 132 |
+
inline auto __device__ constexpr reduction_target_n(TiledMma const& tiled_mma) {
|
| 133 |
+
auto separated = layout_separate(get<0>(typename TiledMma::Shape_MNK{}),
|
| 134 |
+
make_layout(shape<0>(typename TiledMma::LayoutC_TV{})),
|
| 135 |
+
stride<0>(typename TiledMma::LayoutC_TV{}));
|
| 136 |
+
return get<1>(separated);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
template<template<cute::GMMA::Major, cute::GMMA::Major, cute::GMMA::ScaleIn, cute::GMMA::ScaleIn> class Primitive, cute::GMMA::Major tA, cute::GMMA::Major tB, cute::GMMA::ScaleIn sA, cute::GMMA::ScaleIn sB>
|
| 141 |
+
inline auto __device__ constexpr convert_to_gmma_rs(cute::MMA_Atom<Primitive<tA, tB, sA, sB>> const& tiled_mma) {
|
| 142 |
+
using Atom = cute::MMA_Atom<Primitive<tA, tB, sA, sB>>;
|
| 143 |
+
using ElementA = typename Atom::ValTypeA;
|
| 144 |
+
using ElementB = typename Atom::ValTypeB;
|
| 145 |
+
using ElementC = typename Atom::ValTypeC;
|
| 146 |
+
using Shape_MNK = typename Atom::Shape_MNK;
|
| 147 |
+
using RS = decltype(cute::GMMA::rs_op_selector<ElementA, ElementB, ElementC, Shape_MNK, tA, tB, sA, sB>());
|
| 148 |
+
return cute::MMA_Atom<RS>{};
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
template<template<cute::GMMA::ScaleIn, cute::GMMA::ScaleIn> class Primitive, cute::GMMA::ScaleIn sA, cute::GMMA::ScaleIn sB>
|
| 152 |
+
inline auto __device__ constexpr convert_to_gmma_rs(cute::MMA_Atom<Primitive<sA, sB>> const& tiled_mma) {
|
| 153 |
+
using Atom = cute::MMA_Atom<Primitive<sA, sB>>;
|
| 154 |
+
using ElementA = typename Atom::ValTypeA;
|
| 155 |
+
using ElementB = typename Atom::ValTypeB;
|
| 156 |
+
using ElementC = typename Atom::ValTypeC;
|
| 157 |
+
using Shape_MNK = typename Atom::Shape_MNK;
|
| 158 |
+
constexpr auto tA = cute::GMMA::Major::K;
|
| 159 |
+
constexpr auto tB = cute::GMMA::Major::K;
|
| 160 |
+
using RS = decltype(cute::GMMA::rs_op_selector<ElementA, ElementB, ElementC, Shape_MNK, tA, tB, sA, sB>());
|
| 161 |
+
return cute::MMA_Atom<RS>{};
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
template<class Atom, class... Args>
|
| 165 |
+
CUTE_DEVICE auto constexpr convert_to_gmma_rs(cute::TiledMMA<Atom, Args...> const& tiled_mma) {
|
| 166 |
+
return cute::TiledMMA<decltype(convert_to_gmma_rs(Atom{})), Args...>{};
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
template<typename CLayout, typename AValueShape>
|
| 170 |
+
CUTE_DEVICE auto constexpr convert_c_layout_to_a_layout(CLayout const& c, AValueShape const& a) {
|
| 171 |
+
return make_layout(
|
| 172 |
+
make_shape(a, shape<1>(c), make_shape(shape<2>(c), size<0>(c) / size(a))),
|
| 173 |
+
make_stride(stride<0>(c), stride<1>(c), make_stride(stride<2>(c), size<2>(a) * stride<0,2>(c))));
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
template<class Layout, class Stages = _1>
|
| 177 |
+
CUTE_DEVICE constexpr auto unstageSmemLayout(Layout const& layout, Stages stages = {}) {
|
| 178 |
+
return composition(layout, make_tuple(_, _, make_layout(stages)));
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
template<class Element, class Accumulator, class OperandLayout_TV>
|
| 182 |
+
CUTE_DEVICE auto make_acc_into_op(Accumulator const& acc, OperandLayout_TV const& operand_layout_tv) {
|
| 183 |
+
Tensor operand = make_fragment_like<Element>(convert_c_layout_to_a_layout(acc.layout(), shape<1>(operand_layout_tv)));
|
| 184 |
+
Tensor operand_as_acc = make_tensor(operand.data(), acc.layout());
|
| 185 |
+
|
| 186 |
+
cute::copy(acc, operand_as_acc);
|
| 187 |
+
|
| 188 |
+
if constexpr (sizeof(Element) == 1) {
|
| 189 |
+
|
| 190 |
+
// 00 11 22 33 00 11 22 33 acc layout
|
| 191 |
+
// 00 00 11 11 22 22 33 33 operand layout
|
| 192 |
+
// BB AA AA BB AA BB BB AA conflict-free exchange pattern
|
| 193 |
+
// 16-bit exchange; so process two at a time potentially
|
| 194 |
+
int tid = threadIdx.x % 4;
|
| 195 |
+
auto values_u32 = recast<uint32_t>(operand);
|
| 196 |
+
|
| 197 |
+
CUTE_UNROLL
|
| 198 |
+
for (int n = 0; n < size<1>(values_u32); n++) {
|
| 199 |
+
CUTE_UNROLL
|
| 200 |
+
for (int k = 0; k < size<2>(values_u32); k++) {
|
| 201 |
+
CUTE_UNROLL
|
| 202 |
+
for (int ii = 0; ii < 8; ii += 4) {
|
| 203 |
+
|
| 204 |
+
uint32_t values_tmp_0 = values_u32(ii / 2 + 0, n, k);
|
| 205 |
+
uint32_t values_tmp_1 = values_u32(ii / 2 + 1, n, k);
|
| 206 |
+
|
| 207 |
+
// step A:
|
| 208 |
+
// t 1 v 0 -> t 0 v 1
|
| 209 |
+
// t 2 v 0 -> t 1 v 0
|
| 210 |
+
// t 0 v 1 -> t 2 v 0
|
| 211 |
+
// t 3 v 1 -> t 3 v 1
|
| 212 |
+
|
| 213 |
+
int v_to_send = tid == 1 || tid == 2 ? 0 : 1;
|
| 214 |
+
int v_to_recv = v_to_send;
|
| 215 |
+
int t_to_recv_from = (0x3021 >> (tid * 4)) & 0xF;
|
| 216 |
+
|
| 217 |
+
uint32_t values_tmp_a = v_to_send == 0 ? values_tmp_0 : values_tmp_1;
|
| 218 |
+
|
| 219 |
+
values_tmp_a = __shfl_sync(0xFFFFFFFF, values_tmp_a, t_to_recv_from, 4);
|
| 220 |
+
|
| 221 |
+
// step B:
|
| 222 |
+
// t 0 v 0 -> t 0 v 0
|
| 223 |
+
// t 3 v 0 -> t 1 v 1
|
| 224 |
+
// t 1 v 1 -> t 2 v 1
|
| 225 |
+
// t 2 v 1 -> t 3 v 0
|
| 226 |
+
|
| 227 |
+
v_to_send = 1 - v_to_send;
|
| 228 |
+
v_to_recv = 1 - v_to_recv;
|
| 229 |
+
t_to_recv_from = (0x2130 >> (tid * 4)) & 0xF;
|
| 230 |
+
|
| 231 |
+
uint32_t values_tmp_b = v_to_send == 0 ? values_tmp_0 : values_tmp_1;
|
| 232 |
+
|
| 233 |
+
values_tmp_b = __shfl_sync(0xFFFFFFFF, values_tmp_b, t_to_recv_from, 4);
|
| 234 |
+
|
| 235 |
+
values_u32(ii / 2 + 0, n, k) = __byte_perm(values_tmp_a, values_tmp_b, v_to_send == 0 ? 0x1054 : 0x5410);
|
| 236 |
+
values_u32(ii / 2 + 1, n, k) = __byte_perm(values_tmp_a, values_tmp_b, v_to_send == 0 ? 0x3276 : 0x7632);
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
return operand;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_epilogue.hpp
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/epilogue/collective/collective_builder.hpp"
|
| 36 |
+
#include "cutlass/epilogue/collective/default_epilogue.hpp"
|
| 37 |
+
#include "cutlass/epilogue/thread/linear_combination.h"
|
| 38 |
+
|
| 39 |
+
#include "../collective/fmha_common.hpp"
|
| 40 |
+
|
| 41 |
+
namespace cutlass::fmha::collective {
|
| 42 |
+
|
| 43 |
+
template<class Element, class ElementAccumulator, class TileShape_WG>
|
| 44 |
+
struct FmhaFwdEpilogue {
|
| 45 |
+
|
| 46 |
+
static constexpr int Alignment = 16 / sizeof(Element);
|
| 47 |
+
|
| 48 |
+
using DefaultOperation = cutlass::epilogue::fusion::LinearCombination<Element, ElementAccumulator, void>;
|
| 49 |
+
using CollectiveEpilogueTMA = typename cutlass::epilogue::collective::CollectiveBuilder<
|
| 50 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 51 |
+
TileShape_WG, Shape<_1,_1,_1>, cutlass::epilogue::collective::EpilogueTileAuto,
|
| 52 |
+
ElementAccumulator, ElementAccumulator,
|
| 53 |
+
void, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment,
|
| 54 |
+
Element, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment,
|
| 55 |
+
cutlass::epilogue::TmaWarpSpecialized,
|
| 56 |
+
DefaultOperation
|
| 57 |
+
>::CollectiveOp;
|
| 58 |
+
|
| 59 |
+
struct Arguments {
|
| 60 |
+
Element* ptr_O;
|
| 61 |
+
cute::tuple<int, cute::_1, cute::tuple<int, int>> dO;
|
| 62 |
+
|
| 63 |
+
ElementAccumulator* ptr_LSE;
|
| 64 |
+
cute::tuple<cute::_1, cute::tuple<int, int>> dLSE;
|
| 65 |
+
};
|
| 66 |
+
|
| 67 |
+
struct Params {
|
| 68 |
+
ElementAccumulator* ptr_LSE;
|
| 69 |
+
cute::tuple<cute::_1, cute::tuple<int, int>> dLSE;
|
| 70 |
+
|
| 71 |
+
typename CollectiveEpilogueTMA::Params epilogue_TMA;
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
using TensorStorage = typename CollectiveEpilogueTMA::TensorStorage;
|
| 75 |
+
using PipelineStorage = typename CollectiveEpilogueTMA::PipelineStorage;
|
| 76 |
+
using LoadPipeline = typename CollectiveEpilogueTMA::LoadPipeline;
|
| 77 |
+
static constexpr int TmaTransactionBytes = CollectiveEpilogueTMA::TmaTransactionBytes;
|
| 78 |
+
|
| 79 |
+
template<class ProblemShape>
|
| 80 |
+
static Params to_underlying_arguments(ProblemShape const& problem_size, Arguments const& args, void* workspace = nullptr) {
|
| 81 |
+
auto problem_size_o = make_shape(get<2>(problem_size), get<4>(problem_size), 1,
|
| 82 |
+
make_shape(get<0>(problem_size), get<1>(problem_size)));
|
| 83 |
+
typename CollectiveEpilogueTMA::Arguments args_tma{{}, args.ptr_O, args.dO, args.ptr_O, args.dO};
|
| 84 |
+
return Params{
|
| 85 |
+
args.ptr_LSE, args.dLSE,
|
| 86 |
+
CollectiveEpilogueTMA::to_underlying_arguments(problem_size_o, args_tma, workspace)
|
| 87 |
+
};
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
template<class TileShape, class BlkCoord, class ResultTuple, class TiledMma, class ProblemShape>
|
| 91 |
+
CUTLASS_DEVICE void operator()(
|
| 92 |
+
TileShape const& tile_shape, BlkCoord const& blk_coord,
|
| 93 |
+
ResultTuple const& result, TiledMma const& tiled_mma,
|
| 94 |
+
ProblemShape const& problem_size, Params const& params,
|
| 95 |
+
LoadPipeline epi_load_pipeline,
|
| 96 |
+
TensorStorage& epi_tensor_storage)
|
| 97 |
+
{
|
| 98 |
+
using X = Underscore;
|
| 99 |
+
|
| 100 |
+
auto acc = get<0>(result);
|
| 101 |
+
auto lse = get<1>(result);
|
| 102 |
+
|
| 103 |
+
auto thr_mma = tiled_mma.get_thread_slice(threadIdx.x);
|
| 104 |
+
|
| 105 |
+
int seqlen_q = get<2>(problem_size);
|
| 106 |
+
int num_batch = get<0>(problem_size);
|
| 107 |
+
int num_heads = get<1>(problem_size);
|
| 108 |
+
// Epilogue for lse
|
| 109 |
+
Tensor mLSE = make_tensor(make_gmem_ptr(params.ptr_LSE),
|
| 110 |
+
make_shape(seqlen_q, get<1>(tile_shape), make_shape(num_batch, num_heads)),
|
| 111 |
+
make_stride(_1{}, _0{}, get<1>(params.dLSE)));
|
| 112 |
+
Tensor gLSE_full = local_tile(mLSE, tile_shape, make_coord(_, _, _), Step<_1, _1, X>{});
|
| 113 |
+
Tensor gLSE = gLSE_full(_, _, get<0>(blk_coord), get<1>(blk_coord), get<2>(blk_coord));
|
| 114 |
+
Tensor tOgLSE = thr_mma.partition_C(gLSE);
|
| 115 |
+
Tensor cO = make_identity_tensor(take<0,2>(tile_shape));
|
| 116 |
+
Tensor tOcO = thr_mma.partition_C(cO);
|
| 117 |
+
if (get<1>(tOcO(_0{})) == 0) {
|
| 118 |
+
auto tOgLSE_mn = make_tensor(tOgLSE.data(), layout_acc_mn(tiled_mma, tOgLSE.layout()));
|
| 119 |
+
auto tOcO_mn = make_tensor(tOcO.data(), layout_acc_mn(tiled_mma, tOcO.layout()));
|
| 120 |
+
CUTLASS_PRAGMA_UNROLL
|
| 121 |
+
for (int i = 0; i < size<0>(tOgLSE_mn); i++) {
|
| 122 |
+
if (get<0>(tOcO_mn(i)) + get<0>(blk_coord) * get<0>(tile_shape) < get<2>(problem_size)) {
|
| 123 |
+
tOgLSE_mn(i, _0{}) = lse(i);
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
auto problem_size_o = make_shape(get<2>(problem_size), get<4>(problem_size), _,
|
| 128 |
+
make_shape(get<0>(problem_size), get<1>(problem_size)));
|
| 129 |
+
|
| 130 |
+
CollectiveEpilogueTMA epilogue_tma(params.epilogue_TMA, epi_tensor_storage);
|
| 131 |
+
|
| 132 |
+
using EpiStorePipeline = typename CollectiveEpilogueTMA::StorePipeline;
|
| 133 |
+
typename EpiStorePipeline::Params epi_store_pipeline_params;
|
| 134 |
+
epi_store_pipeline_params.always_wait = true;
|
| 135 |
+
EpiStorePipeline epi_store_pipeline(epi_store_pipeline_params);
|
| 136 |
+
|
| 137 |
+
typename CollectiveEpilogueTMA::LoadPipelineState epi_load_pipe_consumer_state;
|
| 138 |
+
PipelineState epi_store_pipe_producer_state = cutlass::make_producer_start_state<EpiStorePipeline>();
|
| 139 |
+
|
| 140 |
+
auto [epi_load_pipe_consumer_state_next, epi_store_pipe_producer_state_next] =
|
| 141 |
+
epilogue_tma.store(
|
| 142 |
+
epi_load_pipeline, epi_load_pipe_consumer_state,
|
| 143 |
+
epi_store_pipeline, epi_store_pipe_producer_state,
|
| 144 |
+
problem_size_o, tile_shape, make_coord(get<0>(blk_coord), _0{}, _, get<2>(blk_coord)),
|
| 145 |
+
acc, tiled_mma, threadIdx.x % cutlass::NumThreadsPerWarpGroup,
|
| 146 |
+
epi_tensor_storage
|
| 147 |
+
);
|
| 148 |
+
|
| 149 |
+
epilogue_tma.store_tail(
|
| 150 |
+
epi_load_pipeline, epi_load_pipe_consumer_state_next,
|
| 151 |
+
epi_store_pipeline, epi_store_pipe_producer_state_next
|
| 152 |
+
);
|
| 153 |
+
}
|
| 154 |
+
};
|
| 155 |
+
|
| 156 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_epilogue_bwd.hpp
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/epilogue/collective/default_epilogue.hpp"
|
| 36 |
+
|
| 37 |
+
#include "../collective/fmha_epilogue.hpp"
|
| 38 |
+
|
| 39 |
+
namespace cutlass::fmha::collective {
|
| 40 |
+
|
| 41 |
+
template<class Element, class ElementAccumulator, class TileShape_WG>
|
| 42 |
+
struct FmhaBwdEpilogueKV {
|
| 43 |
+
|
| 44 |
+
static constexpr int Alignment = 16 / sizeof(Element);
|
| 45 |
+
|
| 46 |
+
struct Arguments {
|
| 47 |
+
Element* ptr_K;
|
| 48 |
+
cute::tuple<int, int, int, cute::_1> dK;
|
| 49 |
+
|
| 50 |
+
Element* ptr_V;
|
| 51 |
+
cute::tuple<int, int, int, _1> dV;
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
//using DefaultOperation = cutlass::epilogue::fusion::LinearCombination<Element, ElementAccumulator, void>;
|
| 55 |
+
static constexpr auto RoundStyle = cutlass::FloatRoundStyle::round_to_nearest;
|
| 56 |
+
using DefaultOperation = cutlass::epilogue::fusion::Sm90EVT<
|
| 57 |
+
cutlass::epilogue::fusion::Sm90Compute<cutlass::first, Element, ElementAccumulator, RoundStyle>,
|
| 58 |
+
cutlass::epilogue::fusion::Sm90AccFetch
|
| 59 |
+
>;
|
| 60 |
+
using CollectiveEpilogueTMA = typename cutlass::epilogue::collective::CollectiveBuilder<
|
| 61 |
+
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
| 62 |
+
TileShape_WG, Shape<_1,_1,_1>, cutlass::epilogue::collective::EpilogueTileAuto,
|
| 63 |
+
ElementAccumulator, ElementAccumulator,
|
| 64 |
+
void, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment,
|
| 65 |
+
Element, cute::tuple<int, _1, cute::tuple<int, int>>, Alignment,
|
| 66 |
+
cutlass::epilogue::TmaWarpSpecialized,
|
| 67 |
+
DefaultOperation
|
| 68 |
+
>::CollectiveOp;
|
| 69 |
+
|
| 70 |
+
struct Params {
|
| 71 |
+
typename CollectiveEpilogueTMA::Params epilogue_K;
|
| 72 |
+
typename CollectiveEpilogueTMA::Params epilogue_V;
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
using TensorStorage = typename CollectiveEpilogueTMA::TensorStorage[2];
|
| 77 |
+
using PipelineStorage = typename CollectiveEpilogueTMA::PipelineStorage;
|
| 78 |
+
using LoadPipeline = typename CollectiveEpilogueTMA::LoadPipeline;
|
| 79 |
+
static constexpr int TmaTransactionBytes = CollectiveEpilogueTMA::TmaTransactionBytes;
|
| 80 |
+
|
| 81 |
+
template<class ProblemShape>
|
| 82 |
+
static Params to_underlying_arguments(ProblemShape const& problem_size, Arguments const& args, void* workspace = nullptr) {
|
| 83 |
+
auto dK = make_stride(get<2>(args.dK), get<3>(args.dK),
|
| 84 |
+
make_stride(get<0>(args.dK), get<1>(args.dK)));
|
| 85 |
+
auto dV = make_stride(get<2>(args.dV), get<3>(args.dV),
|
| 86 |
+
make_stride(get<0>(args.dV), get<1>(args.dV)));
|
| 87 |
+
|
| 88 |
+
auto problem_size_kv = make_shape(get<3>(problem_size), get<4>(problem_size), 1,
|
| 89 |
+
make_shape(get<0>(problem_size), get<1>(problem_size)));
|
| 90 |
+
typename CollectiveEpilogueTMA::Arguments args_k{{}, args.ptr_K, dK, args.ptr_K, dK};
|
| 91 |
+
typename CollectiveEpilogueTMA::Arguments args_v{{}, args.ptr_V, dV, args.ptr_V, dV};
|
| 92 |
+
return Params{
|
| 93 |
+
CollectiveEpilogueTMA::to_underlying_arguments(problem_size_kv, args_k, nullptr),
|
| 94 |
+
CollectiveEpilogueTMA::to_underlying_arguments(problem_size_kv, args_v, nullptr)
|
| 95 |
+
};
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
template<class TileShape, class BlkCoord, class ResultTuple, class TiledMma, class ProblemShape>
|
| 99 |
+
CUTLASS_DEVICE void operator()(
|
| 100 |
+
TileShape const& tile_shape, BlkCoord const& blk_coord,
|
| 101 |
+
ResultTuple const& result, TiledMma const& tiled_mma,
|
| 102 |
+
ProblemShape const& problem_size, Params const& params,
|
| 103 |
+
LoadPipeline epi_load_pipeline, TensorStorage& epi_tensor_storage)
|
| 104 |
+
{
|
| 105 |
+
auto acc_k = get<0>(result);
|
| 106 |
+
auto acc_v = get<1>(result);
|
| 107 |
+
|
| 108 |
+
auto problem_size_kv = make_shape(get<3>(problem_size), get<4>(problem_size), _,
|
| 109 |
+
make_shape(get<0>(problem_size), get<1>(problem_size)));
|
| 110 |
+
|
| 111 |
+
using EpiStorePipeline = typename CollectiveEpilogueTMA::StorePipeline;
|
| 112 |
+
typename EpiStorePipeline::Params epi_store_pipeline_params;
|
| 113 |
+
epi_store_pipeline_params.always_wait = true;
|
| 114 |
+
EpiStorePipeline epi_store_pipeline(epi_store_pipeline_params);
|
| 115 |
+
|
| 116 |
+
typename CollectiveEpilogueTMA::LoadPipelineState epi_load_pipe_consumer_state;
|
| 117 |
+
PipelineState epi_store_pipe_producer_state = cutlass::make_producer_start_state<EpiStorePipeline>();
|
| 118 |
+
|
| 119 |
+
CollectiveEpilogueTMA epilogue_k{params.epilogue_K, epi_tensor_storage[0]};
|
| 120 |
+
CollectiveEpilogueTMA epilogue_v{params.epilogue_V, epi_tensor_storage[1]};
|
| 121 |
+
|
| 122 |
+
{
|
| 123 |
+
auto [epi_load_pipe_consumer_state_next, epi_store_pipe_producer_state_next] =
|
| 124 |
+
epilogue_k.store(
|
| 125 |
+
epi_load_pipeline, epi_load_pipe_consumer_state,
|
| 126 |
+
epi_store_pipeline, epi_store_pipe_producer_state,
|
| 127 |
+
problem_size_kv, tile_shape, make_coord(get<1>(blk_coord), _0{}, _, get<2>(blk_coord)),
|
| 128 |
+
acc_k, tiled_mma, threadIdx.x % cutlass::NumThreadsPerWarpGroup,
|
| 129 |
+
epi_tensor_storage[0]
|
| 130 |
+
);
|
| 131 |
+
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
{
|
| 135 |
+
auto [epi_load_pipe_consumer_state_next, epi_store_pipe_producer_state_next] =
|
| 136 |
+
epilogue_v.store(
|
| 137 |
+
epi_load_pipeline, epi_load_pipe_consumer_state,
|
| 138 |
+
epi_store_pipeline, epi_store_pipe_producer_state,
|
| 139 |
+
problem_size_kv, tile_shape, make_coord(get<1>(blk_coord), _0{}, _, get<2>(blk_coord)),
|
| 140 |
+
acc_v, tiled_mma, threadIdx.x % cutlass::NumThreadsPerWarpGroup,
|
| 141 |
+
epi_tensor_storage[1]
|
| 142 |
+
);
|
| 143 |
+
|
| 144 |
+
epilogue_k.store_tail(
|
| 145 |
+
epi_load_pipeline, epi_load_pipe_consumer_state_next,
|
| 146 |
+
epi_store_pipeline, epi_store_pipe_producer_state_next
|
| 147 |
+
);
|
| 148 |
+
|
| 149 |
+
epilogue_v.store_tail(
|
| 150 |
+
epi_load_pipeline, epi_load_pipe_consumer_state_next,
|
| 151 |
+
epi_store_pipeline, epi_store_pipe_producer_state_next
|
| 152 |
+
);
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
};
|
| 156 |
+
|
| 157 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/collective/fmha_fusion.hpp
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cute/tensor.hpp"
|
| 36 |
+
|
| 37 |
+
namespace cutlass::fmha::collective {
|
| 38 |
+
|
| 39 |
+
using namespace cute;
|
| 40 |
+
|
| 41 |
+
struct DefaultFusion {
|
| 42 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 43 |
+
CUTLASS_DEVICE
|
| 44 |
+
int get_trip_count(
|
| 45 |
+
BlkCoord const& blk_coord,
|
| 46 |
+
TileShape const& tile_shape,
|
| 47 |
+
ProblemSize const& problem_size
|
| 48 |
+
) {
|
| 49 |
+
return ceil_div(get<3>(problem_size), get<1>(tile_shape));
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 53 |
+
CUTLASS_DEVICE
|
| 54 |
+
int get_masked_trip_count(
|
| 55 |
+
BlkCoord const& blk_coord,
|
| 56 |
+
TileShape const& tile_shape,
|
| 57 |
+
ProblemSize const& problem_size
|
| 58 |
+
) {
|
| 59 |
+
return get_trip_count(blk_coord, tile_shape, problem_size);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 63 |
+
CUTLASS_DEVICE
|
| 64 |
+
int get_unmasked_trip_count(
|
| 65 |
+
BlkCoord const& blk_coord,
|
| 66 |
+
TileShape const& tile_shape,
|
| 67 |
+
ProblemSize const& problem_size
|
| 68 |
+
) {
|
| 69 |
+
return 0;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
template<class AccQK, class IndexQK, class ProblemSize>
|
| 73 |
+
CUTLASS_DEVICE
|
| 74 |
+
void before_softmax(
|
| 75 |
+
AccQK& acc_qk,
|
| 76 |
+
IndexQK const& index_qk,
|
| 77 |
+
ProblemSize const& problem_size
|
| 78 |
+
|
| 79 |
+
) {
|
| 80 |
+
return;
|
| 81 |
+
}
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
struct ResidualFusion : DefaultFusion {
|
| 85 |
+
|
| 86 |
+
using Base = DefaultFusion;
|
| 87 |
+
|
| 88 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 89 |
+
CUTLASS_DEVICE
|
| 90 |
+
int get_masked_trip_count(
|
| 91 |
+
BlkCoord const& blk_coord,
|
| 92 |
+
TileShape const& tile_shape,
|
| 93 |
+
ProblemSize const& problem_size
|
| 94 |
+
) {
|
| 95 |
+
return 1;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 99 |
+
CUTLASS_DEVICE
|
| 100 |
+
int get_unmasked_trip_count(
|
| 101 |
+
BlkCoord const& blk_coord,
|
| 102 |
+
TileShape const& tile_shape,
|
| 103 |
+
ProblemSize const& problem_size
|
| 104 |
+
) {
|
| 105 |
+
return get_trip_count(blk_coord, tile_shape, problem_size) - 1;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
template<class AccQK, class IndexQK, class ProblemSize>
|
| 109 |
+
CUTLASS_DEVICE
|
| 110 |
+
void before_softmax(
|
| 111 |
+
AccQK& acc_qk,
|
| 112 |
+
IndexQK const& index_qk,
|
| 113 |
+
ProblemSize const& problem_size
|
| 114 |
+
) {
|
| 115 |
+
// This is useful is seqlen_k % kBlockN != 0 since it masks
|
| 116 |
+
// the remaining elements out from softmax.
|
| 117 |
+
// d % kHeadDim != 0 or seqlen_q % kBlockM do not suffer from similar
|
| 118 |
+
// issues as they are transparently taken care of by TMA and the
|
| 119 |
+
// epilogue, if it is instantiated with predication support.
|
| 120 |
+
CUTLASS_PRAGMA_UNROLL
|
| 121 |
+
for (int i = 0; i < size(acc_qk); i++) {
|
| 122 |
+
auto pos = index_qk(i);
|
| 123 |
+
if (get<1>(pos) >= get<3>(problem_size)) {
|
| 124 |
+
acc_qk(i) = -INFINITY;
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
};
|
| 129 |
+
|
| 130 |
+
struct CausalFusion : DefaultFusion {
|
| 131 |
+
|
| 132 |
+
using Base = DefaultFusion;
|
| 133 |
+
|
| 134 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 135 |
+
CUTLASS_DEVICE
|
| 136 |
+
int get_trip_count(
|
| 137 |
+
BlkCoord const& blk_coord,
|
| 138 |
+
TileShape const& tile_shape,
|
| 139 |
+
ProblemSize const& problem_size
|
| 140 |
+
) {
|
| 141 |
+
// See note below on different ways to think about causal attention
|
| 142 |
+
// Again, we'd add the offset_q into the max_blocks_q calculation
|
| 143 |
+
int max_blocks_k = Base::get_trip_count(blk_coord, tile_shape, problem_size);
|
| 144 |
+
int max_blocks_q = ceil_div((get<0>(blk_coord) + 1) * get<0>(tile_shape), get<1>(tile_shape));
|
| 145 |
+
return std::min(max_blocks_k, max_blocks_q);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 149 |
+
CUTLASS_DEVICE
|
| 150 |
+
int get_masked_trip_count(
|
| 151 |
+
BlkCoord const& blk_coord,
|
| 152 |
+
TileShape const& tile_shape,
|
| 153 |
+
ProblemSize const& problem_size
|
| 154 |
+
) {
|
| 155 |
+
return ceil_div(get<0>(tile_shape), get<1>(tile_shape));
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 159 |
+
CUTLASS_DEVICE
|
| 160 |
+
int get_unmasked_trip_count(
|
| 161 |
+
BlkCoord const& blk_coord,
|
| 162 |
+
TileShape const& tile_shape,
|
| 163 |
+
ProblemSize const& problem_size
|
| 164 |
+
) {
|
| 165 |
+
return get_trip_count(blk_coord, tile_shape, problem_size) - get_masked_trip_count(blk_coord, tile_shape, problem_size);
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
template<class AccQK, class IndexQK, class ProblemSize>
|
| 169 |
+
CUTLASS_DEVICE
|
| 170 |
+
void before_softmax(
|
| 171 |
+
AccQK& acc_qk,
|
| 172 |
+
IndexQK const& index_qk,
|
| 173 |
+
ProblemSize const& problem_size
|
| 174 |
+
) {
|
| 175 |
+
// There are two ways to do causal if N_Q != N_K
|
| 176 |
+
// (1) is to assume that the Q is at the beginning of the matrix
|
| 177 |
+
// - this is what we demonstrate here
|
| 178 |
+
// (2) is that it is at the end of the matrix
|
| 179 |
+
// - this is usually what we want for inference settings
|
| 180 |
+
// where we only compute the next row and use cache for the rest
|
| 181 |
+
// - if you'd like this, you only need to add an offset like so:
|
| 182 |
+
// get<0>(pos) + offset_q < get<1>(pos)
|
| 183 |
+
CUTLASS_PRAGMA_UNROLL
|
| 184 |
+
for (int i = 0; i < size(acc_qk); i++) {
|
| 185 |
+
auto pos = index_qk(i);
|
| 186 |
+
if (get<0>(pos) < get<1>(pos)) {
|
| 187 |
+
acc_qk(i) = -INFINITY;
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
};
|
| 193 |
+
|
| 194 |
+
template<class Base>
|
| 195 |
+
struct FusionBwdAdapter {
|
| 196 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 197 |
+
CUTLASS_DEVICE
|
| 198 |
+
int get_trip_count(
|
| 199 |
+
BlkCoord const& blk_coord,
|
| 200 |
+
TileShape const& tile_shape,
|
| 201 |
+
ProblemSize const& problem_size
|
| 202 |
+
) {
|
| 203 |
+
return Base{}.get_trip_count(select<1,0,2>(blk_coord), select<1,0,2>(tile_shape), select<0,1,3,2,4>(problem_size));
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
template<class AccQK, class IndexQK, class ProblemSize>
|
| 207 |
+
CUTLASS_DEVICE
|
| 208 |
+
void before_softmax(
|
| 209 |
+
AccQK& acc_qk,
|
| 210 |
+
IndexQK const& index_qk,
|
| 211 |
+
ProblemSize const& problem_size
|
| 212 |
+
) {
|
| 213 |
+
auto index_base = index_qk(_0{});
|
| 214 |
+
auto index_shape = shape(index_qk);
|
| 215 |
+
auto index_stride = transform_leaf(stride(index_qk), [](auto elem) {
|
| 216 |
+
if constexpr (is_scaled_basis<decltype(elem)>::value) {
|
| 217 |
+
if constexpr(decltype(elem.mode() == _0{})::value) {
|
| 218 |
+
return ScaledBasis<decltype(elem.value()), 1>(elem.value());
|
| 219 |
+
} else {
|
| 220 |
+
return ScaledBasis<decltype(elem.value()), 0>(elem.value());
|
| 221 |
+
}
|
| 222 |
+
} else {
|
| 223 |
+
return elem;
|
| 224 |
+
}
|
| 225 |
+
});
|
| 226 |
+
auto index_qk_bwd = make_tensor(make_inttuple_iter(select<1,0>(index_base)), make_layout(index_shape, index_stride));
|
| 227 |
+
Base{}.before_softmax(acc_qk, index_qk_bwd, problem_size);
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 231 |
+
CUTLASS_DEVICE
|
| 232 |
+
bool is_contributing(
|
| 233 |
+
BlkCoord const& blk_coord,
|
| 234 |
+
TileShape const& tile_shape,
|
| 235 |
+
ProblemSize const& problem_size
|
| 236 |
+
) {
|
| 237 |
+
return true;
|
| 238 |
+
}
|
| 239 |
+
};
|
| 240 |
+
|
| 241 |
+
template<>
|
| 242 |
+
struct FusionBwdAdapter<CausalFusion> {
|
| 243 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 244 |
+
CUTLASS_DEVICE
|
| 245 |
+
int get_trip_count(
|
| 246 |
+
BlkCoord const& blk_coord,
|
| 247 |
+
TileShape const& tile_shape,
|
| 248 |
+
ProblemSize const& problem_size
|
| 249 |
+
) {
|
| 250 |
+
return get<2>(problem_size) / get<0>(TileShape{});
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
template<class AccQK, class IndexQK, class ProblemSize>
|
| 254 |
+
CUTLASS_DEVICE
|
| 255 |
+
void before_softmax(
|
| 256 |
+
AccQK& acc_qk,
|
| 257 |
+
IndexQK const& index_qk,
|
| 258 |
+
ProblemSize const& problem_size
|
| 259 |
+
|
| 260 |
+
) {
|
| 261 |
+
CUTLASS_PRAGMA_UNROLL
|
| 262 |
+
for (int i = 0; i < size(acc_qk); i++) {
|
| 263 |
+
auto pos = index_qk(i);
|
| 264 |
+
if (get<1>(pos) < get<0>(pos)) {
|
| 265 |
+
acc_qk(i) = -INFINITY;
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
template<class BlkCoord, class TileShape, class ProblemSize>
|
| 271 |
+
CUTLASS_DEVICE
|
| 272 |
+
bool is_contributing(
|
| 273 |
+
BlkCoord const& blk_coord,
|
| 274 |
+
TileShape const& tile_shape,
|
| 275 |
+
ProblemSize const& problem_size
|
| 276 |
+
) {
|
| 277 |
+
int max_q = get<0>(blk_coord) * get<0>(tile_shape) + get<0>(tile_shape);
|
| 278 |
+
int min_k = get<1>(blk_coord) * get<1>(tile_shape);
|
| 279 |
+
return min_k <= max_q;
|
| 280 |
+
}
|
| 281 |
+
};
|
| 282 |
+
|
| 283 |
+
} // namespace cutlass::fmha::collective
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/device/device_universal.hpp
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
/*!
|
| 33 |
+
\file
|
| 34 |
+
\brief An universal device layer for cutlass 3.x-style kernels.
|
| 35 |
+
*/
|
| 36 |
+
|
| 37 |
+
#pragma once
|
| 38 |
+
|
| 39 |
+
// common
|
| 40 |
+
#include "cutlass/cutlass.h"
|
| 41 |
+
#include "cutlass/device_kernel.h"
|
| 42 |
+
|
| 43 |
+
#if !defined(__CUDACC_RTC__)
|
| 44 |
+
#include "cutlass/cluster_launch.hpp"
|
| 45 |
+
#include "cutlass/trace.h"
|
| 46 |
+
#endif // !defined(__CUDACC_RTC__)
|
| 47 |
+
|
| 48 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 49 |
+
|
| 50 |
+
namespace cutlass::device {
|
| 51 |
+
|
| 52 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 53 |
+
////////////////////////////// CUTLASS 3.x API /////////////////////////////////
|
| 54 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 55 |
+
|
| 56 |
+
template <class Kernel_>
|
| 57 |
+
class Universal {
|
| 58 |
+
public:
|
| 59 |
+
using Kernel = Kernel_;
|
| 60 |
+
|
| 61 |
+
static int const kThreadCount = Kernel::MaxThreadsPerBlock;
|
| 62 |
+
|
| 63 |
+
/// Argument structure: User API
|
| 64 |
+
using Arguments = typename Kernel::Arguments;
|
| 65 |
+
/// Argument structure: Kernel API
|
| 66 |
+
using Params = typename Kernel::Params;
|
| 67 |
+
|
| 68 |
+
private:
|
| 69 |
+
|
| 70 |
+
/// Kernel API parameters object
|
| 71 |
+
Params params_;
|
| 72 |
+
|
| 73 |
+
bool is_initialized(bool set = false) {
|
| 74 |
+
static bool initialized = false;
|
| 75 |
+
if (set) initialized = true;
|
| 76 |
+
return initialized;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
public:
|
| 80 |
+
|
| 81 |
+
/// Access the Params structure
|
| 82 |
+
Params const& params() const {
|
| 83 |
+
return params_;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/// Determines whether the GEMM can execute the given problem.
|
| 87 |
+
static Status
|
| 88 |
+
can_implement(Arguments const& args) {
|
| 89 |
+
if (Kernel::can_implement(args)) {
|
| 90 |
+
return Status::kSuccess;
|
| 91 |
+
}
|
| 92 |
+
else {
|
| 93 |
+
return Status::kInvalid;
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/// Gets the workspace size
|
| 98 |
+
static size_t
|
| 99 |
+
get_workspace_size(Arguments const& args) {
|
| 100 |
+
size_t workspace_bytes = 0;
|
| 101 |
+
workspace_bytes += Kernel::get_workspace_size(args);
|
| 102 |
+
return workspace_bytes;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
/// Computes the grid shape
|
| 106 |
+
static dim3
|
| 107 |
+
get_grid_shape(Params const& params) {
|
| 108 |
+
return Kernel::get_grid_shape(params);
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
/// Computes the maximum number of active blocks per multiprocessor
|
| 112 |
+
static int maximum_active_blocks(int /* smem_capacity */ = -1) {
|
| 113 |
+
CUTLASS_TRACE_HOST("Universal::maximum_active_blocks()");
|
| 114 |
+
int max_active_blocks = -1;
|
| 115 |
+
int smem_size = Kernel::SharedStorageSize;
|
| 116 |
+
|
| 117 |
+
// first, account for dynamic smem capacity if needed
|
| 118 |
+
cudaError_t result;
|
| 119 |
+
if (smem_size >= (48 << 10)) {
|
| 120 |
+
CUTLASS_TRACE_HOST(" Setting smem size to " << smem_size);
|
| 121 |
+
result = cudaFuncSetAttribute(
|
| 122 |
+
device_kernel<Kernel>,
|
| 123 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 124 |
+
smem_size);
|
| 125 |
+
if (cudaSuccess != result) {
|
| 126 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 127 |
+
CUTLASS_TRACE_HOST(
|
| 128 |
+
" cudaFuncSetAttribute() returned error: "
|
| 129 |
+
<< cudaGetErrorString(result));
|
| 130 |
+
return -1;
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// query occupancy after setting smem size
|
| 135 |
+
result = cudaOccupancyMaxActiveBlocksPerMultiprocessor(
|
| 136 |
+
&max_active_blocks,
|
| 137 |
+
device_kernel<Kernel>,
|
| 138 |
+
Kernel::MaxThreadsPerBlock,
|
| 139 |
+
smem_size);
|
| 140 |
+
|
| 141 |
+
if (cudaSuccess != result) {
|
| 142 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 143 |
+
CUTLASS_TRACE_HOST(
|
| 144 |
+
" cudaOccupancyMaxActiveBlocksPerMultiprocessor() returned error: "
|
| 145 |
+
<< cudaGetErrorString(result));
|
| 146 |
+
return -1;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
CUTLASS_TRACE_HOST(" max_active_blocks: " << max_active_blocks);
|
| 150 |
+
return max_active_blocks;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/// Initializes GEMM state from arguments.
|
| 154 |
+
Status
|
| 155 |
+
initialize(Arguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr) {
|
| 156 |
+
CUTLASS_TRACE_HOST("Universal::initialize() - workspace "
|
| 157 |
+
<< workspace << ", stream: " << (stream ? "non-null" : "null"));
|
| 158 |
+
|
| 159 |
+
// Initialize the workspace
|
| 160 |
+
Status status = Kernel::initialize_workspace(args, workspace, stream);
|
| 161 |
+
if (status != Status::kSuccess) {
|
| 162 |
+
return status;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
// Initialize the Params structure
|
| 166 |
+
params_ = Kernel::to_underlying_arguments(args, workspace);
|
| 167 |
+
|
| 168 |
+
if (is_initialized()) return Status::kSuccess;
|
| 169 |
+
|
| 170 |
+
// account for dynamic smem capacity if needed
|
| 171 |
+
int smem_size = Kernel::SharedStorageSize;
|
| 172 |
+
if (smem_size >= (48 << 10)) {
|
| 173 |
+
CUTLASS_TRACE_HOST(" Setting smem size to " << smem_size);
|
| 174 |
+
cudaError_t result = cudaFuncSetAttribute(
|
| 175 |
+
device_kernel<Kernel>,
|
| 176 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 177 |
+
smem_size);
|
| 178 |
+
if (cudaSuccess != result) {
|
| 179 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 180 |
+
CUTLASS_TRACE_HOST(" cudaFuncSetAttribute() returned error: " << cudaGetErrorString(result));
|
| 181 |
+
return Status::kErrorInternal;
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
is_initialized(true);
|
| 186 |
+
|
| 187 |
+
return Status::kSuccess;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/// Update API is preserved in 3.0, but does not guarantee a lightweight update of params.
|
| 191 |
+
Status
|
| 192 |
+
update(Arguments const& args, void* workspace = nullptr) {
|
| 193 |
+
CUTLASS_TRACE_HOST("Universal()::update() - workspace: " << workspace);
|
| 194 |
+
|
| 195 |
+
size_t workspace_bytes = get_workspace_size(args);
|
| 196 |
+
if (workspace_bytes > 0 && nullptr == workspace) {
|
| 197 |
+
return Status::kErrorWorkspaceNull;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
params_ = Kernel::to_underlying_arguments(args, workspace);
|
| 201 |
+
return Status::kSuccess;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
/// Primary run() entry point API that is static allowing users to create and manage their own params.
|
| 205 |
+
/// Supplied params struct must be construct by calling Kernel::to_underling_arguments()
|
| 206 |
+
static Status
|
| 207 |
+
run(Params& params, cudaStream_t stream = nullptr) {
|
| 208 |
+
CUTLASS_TRACE_HOST("Universal::run()");
|
| 209 |
+
dim3 const block = Kernel::get_block_shape();
|
| 210 |
+
dim3 const grid = get_grid_shape(params);
|
| 211 |
+
|
| 212 |
+
// configure smem size and carveout
|
| 213 |
+
int smem_size = Kernel::SharedStorageSize;
|
| 214 |
+
|
| 215 |
+
Status launch_result;
|
| 216 |
+
// Use extended launch API only for mainloops that use it
|
| 217 |
+
if constexpr(Kernel::ArchTag::kMinComputeCapability >= 90) {
|
| 218 |
+
dim3 cluster(cute::size<0>(typename Kernel::ClusterShape{}),
|
| 219 |
+
cute::size<1>(typename Kernel::ClusterShape{}),
|
| 220 |
+
cute::size<2>(typename Kernel::ClusterShape{}));
|
| 221 |
+
void const* kernel = (void const*) device_kernel<Kernel>;
|
| 222 |
+
void* kernel_params[] = {¶ms};
|
| 223 |
+
launch_result = ClusterLauncher::launch(grid, cluster, block, smem_size, stream, kernel, kernel_params);
|
| 224 |
+
}
|
| 225 |
+
else {
|
| 226 |
+
launch_result = Status::kSuccess;
|
| 227 |
+
cutlass::arch::synclog_setup();
|
| 228 |
+
device_kernel<Kernel><<<grid, block, smem_size, stream>>>(params);
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
cudaError_t result = cudaGetLastError();
|
| 232 |
+
if (cudaSuccess == result && Status::kSuccess == launch_result) {
|
| 233 |
+
return Status::kSuccess;
|
| 234 |
+
}
|
| 235 |
+
else {
|
| 236 |
+
CUTLASS_TRACE_HOST(" Kernel launch failed. Reason: " << result);
|
| 237 |
+
return Status::kErrorInternal;
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
//
|
| 242 |
+
// Non-static launch overloads that first create and set the internal params struct of this kernel handle.
|
| 243 |
+
//
|
| 244 |
+
|
| 245 |
+
/// Launches the kernel after first constructing Params internal state from supplied arguments.
|
| 246 |
+
Status
|
| 247 |
+
run(Arguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr) {
|
| 248 |
+
Status status = initialize(args, workspace, stream);
|
| 249 |
+
if (Status::kSuccess == status) {
|
| 250 |
+
status = run(params_, stream);
|
| 251 |
+
}
|
| 252 |
+
return status;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
/// Launches the kernel after first constructing Params internal state from supplied arguments.
|
| 256 |
+
Status
|
| 257 |
+
operator()(Arguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr) {
|
| 258 |
+
return run(args, workspace, stream);
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
/// Overload that allows a user to re-launch the same kernel without updating internal params struct.
|
| 262 |
+
Status
|
| 263 |
+
run(cudaStream_t stream = nullptr) {
|
| 264 |
+
return run(params_, stream);
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
/// Overload that allows a user to re-launch the same kernel without updating internal params struct.
|
| 268 |
+
Status
|
| 269 |
+
operator()(cudaStream_t stream = nullptr) {
|
| 270 |
+
return run(params_, stream);
|
| 271 |
+
}
|
| 272 |
+
};
|
| 273 |
+
|
| 274 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 275 |
+
|
| 276 |
+
} // namespace cutlass::device
|
| 277 |
+
|
| 278 |
+
////////////////////////////////////////////////////////////////////////////////
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/device/fmha_device_bwd.hpp
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
/*!
|
| 35 |
+
\file
|
| 36 |
+
\brief An universal device layer for cutlass 3.x-style kernels.
|
| 37 |
+
*/
|
| 38 |
+
|
| 39 |
+
// common
|
| 40 |
+
#include "cutlass/cutlass.h"
|
| 41 |
+
|
| 42 |
+
#include "../device/device_universal.hpp"
|
| 43 |
+
#include "../collective/fmha_collective_bwd_tma_warpspecialized.hpp"
|
| 44 |
+
#include "../collective/fmha_fusion.hpp"
|
| 45 |
+
#include "../collective/fmha_epilogue_bwd.hpp"
|
| 46 |
+
#include "../kernel/fmha_kernel_bwd_sum_OdO.hpp"
|
| 47 |
+
#include "../kernel/fmha_kernel_bwd_convert.hpp"
|
| 48 |
+
#include "../kernel/fmha_kernel_tma_warpspecialized.hpp"
|
| 49 |
+
#include "../kernel/fmha_tile_scheduler.hpp"
|
| 50 |
+
|
| 51 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 52 |
+
|
| 53 |
+
namespace cutlass::fmha::device {
|
| 54 |
+
|
| 55 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 56 |
+
////////////////////////////// CUTLASS 3.x API /////////////////////////////////
|
| 57 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 58 |
+
|
| 59 |
+
template<class Element, class ElementAccumulator, class TileShape, class Fusion, class... Options>
|
| 60 |
+
class FmhaBwd {
|
| 61 |
+
public:
|
| 62 |
+
/// Argument structure: User API
|
| 63 |
+
struct Arguments {
|
| 64 |
+
cute::tuple<int, int, int, int, int> problem_size;
|
| 65 |
+
|
| 66 |
+
const Element* ptr_Q;
|
| 67 |
+
cute::tuple<int, int, int, cute::_1> stride_Q;
|
| 68 |
+
const Element* ptr_K;
|
| 69 |
+
cute::tuple<int, int, int, cute::_1> stride_K;
|
| 70 |
+
const Element* ptr_V;
|
| 71 |
+
cute::tuple<int, int, int, cute::_1> stride_V;
|
| 72 |
+
|
| 73 |
+
const Element* ptr_O;
|
| 74 |
+
cute::tuple<int, int, int, cute::_1> stride_O;
|
| 75 |
+
const ElementAccumulator* ptr_LSE;
|
| 76 |
+
cute::tuple<int, int, _1> stride_LSE;
|
| 77 |
+
|
| 78 |
+
const Element* ptr_dO;
|
| 79 |
+
cute::tuple<int, int, int, cute::_1> stride_dO;
|
| 80 |
+
|
| 81 |
+
Element* ptr_dQ;
|
| 82 |
+
cute::tuple<int, int, int, cute::_1> stride_dQ;
|
| 83 |
+
Element* ptr_dK;
|
| 84 |
+
cute::tuple<int, int, int, cute::_1> stride_dK;
|
| 85 |
+
Element* ptr_dV;
|
| 86 |
+
cute::tuple<int, int, int, cute::_1> stride_dV;
|
| 87 |
+
|
| 88 |
+
cutlass::KernelHardwareInfo hw_info;
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
using OperationSumOdO = cutlass::device::Universal<cutlass::fmha::kernel::FmhaKernelBwdSumOdO<Element, ElementAccumulator>>;
|
| 92 |
+
using OperationConvert = cutlass::device::Universal<cutlass::fmha::kernel::FmhaKernelBwdConvert<Element, ElementAccumulator>>;
|
| 93 |
+
|
| 94 |
+
using Mainloop = cutlass::fmha::collective::FmhaBwdMainloopTmaWarpSpecialized<
|
| 95 |
+
Element, ElementAccumulator, TileShape,
|
| 96 |
+
cutlass::fmha::collective::FusionBwdAdapter<Fusion>, Options...>;
|
| 97 |
+
|
| 98 |
+
using Epilogue = cutlass::fmha::collective::FmhaBwdEpilogueKV<Element, ElementAccumulator, typename Mainloop::TileShapePV>;
|
| 99 |
+
|
| 100 |
+
using Operation = cutlass::device::Universal<
|
| 101 |
+
cutlass::fmha::kernel::FmhaKernelTmaWarpSpecialized<
|
| 102 |
+
Mainloop,
|
| 103 |
+
Epilogue,
|
| 104 |
+
cutlass::fmha::kernel::TileSchedulerBwdAdapter<cutlass::fmha::kernel::IndividualTileScheduler>, Options...>>;
|
| 105 |
+
|
| 106 |
+
struct Params {
|
| 107 |
+
OperationSumOdO op_sum_OdO;
|
| 108 |
+
Operation op;
|
| 109 |
+
OperationConvert op_convert;
|
| 110 |
+
ElementAccumulator* dQ_acc;
|
| 111 |
+
size_t dQ_acc_size;
|
| 112 |
+
};
|
| 113 |
+
|
| 114 |
+
private:
|
| 115 |
+
Params params_;
|
| 116 |
+
|
| 117 |
+
static typename OperationSumOdO::Arguments to_sum_OdO_arguments(Arguments const& args, ElementAccumulator* dest = nullptr) {
|
| 118 |
+
auto [B, H, Q, K, D] = args.problem_size;
|
| 119 |
+
D = cutlass::round_up(D, 8); // Alignment
|
| 120 |
+
Q = cutlass::round_up(Q, 8); // Alignment
|
| 121 |
+
auto stride_sum_OdO = make_stride(H*Q, Q, _1{});
|
| 122 |
+
return typename OperationSumOdO::Arguments {
|
| 123 |
+
args.problem_size,
|
| 124 |
+
args.ptr_O, args.stride_O,
|
| 125 |
+
args.ptr_dO, args.stride_dO,
|
| 126 |
+
dest, stride_sum_OdO
|
| 127 |
+
};
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
static typename OperationConvert::Arguments to_convert_arguments(Arguments const& args, ElementAccumulator* src = nullptr) {
|
| 131 |
+
auto [B, H, Q, K, D] = args.problem_size;
|
| 132 |
+
D = cutlass::round_up(D, 8); // Alignment
|
| 133 |
+
Q = cutlass::round_up(Q, 8); // Alignment
|
| 134 |
+
auto stride_src_dQ = make_stride(B == 1 ? 0 : (H*Q*D), Q*D, D, _1{});
|
| 135 |
+
return typename OperationConvert::Arguments {
|
| 136 |
+
args.problem_size,
|
| 137 |
+
src, stride_src_dQ,
|
| 138 |
+
nullptr, stride_src_dQ,
|
| 139 |
+
nullptr, stride_src_dQ,
|
| 140 |
+
args.ptr_dQ, args.stride_dQ,
|
| 141 |
+
nullptr, args.stride_dK,
|
| 142 |
+
nullptr, args.stride_dV
|
| 143 |
+
};
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
static typename Operation::Arguments to_bwd_arguments(
|
| 147 |
+
Arguments const& args,
|
| 148 |
+
ElementAccumulator* sum_OdO = nullptr, cute::tuple<int, int, _1> const& stride_sum_OdO = {},
|
| 149 |
+
ElementAccumulator* dQ_acc = nullptr, cute::tuple<int, int, int, _1> const& stride_dQ = {}
|
| 150 |
+
) {
|
| 151 |
+
return typename Operation::Arguments{
|
| 152 |
+
args.problem_size,
|
| 153 |
+
{ args.ptr_Q, args.stride_Q,
|
| 154 |
+
args.ptr_K, args.stride_K,
|
| 155 |
+
args.ptr_V, args.stride_V,
|
| 156 |
+
args.ptr_dO, args.stride_dO,
|
| 157 |
+
args.ptr_LSE, args.stride_LSE,
|
| 158 |
+
sum_OdO, stride_sum_OdO,
|
| 159 |
+
dQ_acc, stride_dQ },
|
| 160 |
+
{ args.ptr_dK, args.stride_dK,
|
| 161 |
+
args.ptr_dV, args.stride_dV },
|
| 162 |
+
args.hw_info
|
| 163 |
+
};
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
public:
|
| 167 |
+
|
| 168 |
+
/// Determines whether the GEMM can execute the given problem.
|
| 169 |
+
static Status
|
| 170 |
+
can_implement(Arguments const& args) {
|
| 171 |
+
Status status = Status::kSuccess;
|
| 172 |
+
|
| 173 |
+
status = OperationSumOdO::can_implement(to_sum_OdO_arguments(args));
|
| 174 |
+
if (status != Status::kSuccess) {
|
| 175 |
+
return status;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
status = OperationConvert::can_implement(to_convert_arguments(args));
|
| 179 |
+
if (status != Status::kSuccess) {
|
| 180 |
+
return status;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
status = Operation::can_implement(to_bwd_arguments(args));
|
| 184 |
+
if (status != Status::kSuccess) {
|
| 185 |
+
return status;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
return status;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/// Gets the workspace size
|
| 192 |
+
static size_t
|
| 193 |
+
get_workspace_size(Arguments const& args) {
|
| 194 |
+
auto [B, H, Q, K, D] = args.problem_size;
|
| 195 |
+
D = cutlass::round_up(D, 8); // Alignment
|
| 196 |
+
Q = cutlass::round_up(Q, 8); // Alignment
|
| 197 |
+
size_t workspace_bytes = 0;
|
| 198 |
+
// OdO vector
|
| 199 |
+
workspace_bytes += B*H*Q * sizeof(ElementAccumulator);
|
| 200 |
+
// FP32 versions of outputs that are churned (start off with Q only)
|
| 201 |
+
workspace_bytes += B*H*Q*D * sizeof(ElementAccumulator);
|
| 202 |
+
return workspace_bytes;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
/// Initializes state from arguments.
|
| 206 |
+
Status
|
| 207 |
+
initialize_split(Arguments const& args, void* workspace_dQ, void* workspace_sum_OdO, cudaStream_t stream = nullptr) {
|
| 208 |
+
CUTLASS_TRACE_HOST("Universal::initialize_split() - workspace_dQ="
|
| 209 |
+
<< workspace_dQ << ", workspace_sum_OdO=" << workspace_sum_OdO << "stream: " << (stream ? "non-null" : "null"));
|
| 210 |
+
|
| 211 |
+
auto [B, H, Q, K, D] = args.problem_size;
|
| 212 |
+
D = cutlass::round_up(D, 8); // Alignment
|
| 213 |
+
Q = cutlass::round_up(Q, 8); // Alignment
|
| 214 |
+
ElementAccumulator* sum_OdO = reinterpret_cast<ElementAccumulator*>(workspace_sum_OdO);
|
| 215 |
+
ElementAccumulator* dQ_acc = reinterpret_cast<ElementAccumulator*>(workspace_dQ);
|
| 216 |
+
params_.dQ_acc = dQ_acc;
|
| 217 |
+
params_.dQ_acc_size = B*H*Q*D * sizeof(ElementAccumulator);
|
| 218 |
+
auto args_sum_OdO = to_sum_OdO_arguments(args, sum_OdO);
|
| 219 |
+
auto args_convert = to_convert_arguments(args, dQ_acc);
|
| 220 |
+
params_.op_sum_OdO.initialize(args_sum_OdO, nullptr, stream);
|
| 221 |
+
params_.op_convert.initialize(args_convert, nullptr, stream);
|
| 222 |
+
auto args_bwd = to_bwd_arguments(args, sum_OdO, args_sum_OdO.stride_sum_OdO, dQ_acc, args_convert.stride_src_dQ);
|
| 223 |
+
params_.op.initialize(args_bwd, nullptr, stream);
|
| 224 |
+
|
| 225 |
+
return Status::kSuccess;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
/// Initializes state from arguments.
|
| 229 |
+
Status
|
| 230 |
+
initialize(Arguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr) {
|
| 231 |
+
CUTLASS_TRACE_HOST("Universal::initialize() - workspace "
|
| 232 |
+
<< workspace << ", stream: " << (stream ? "non-null" : "null"));
|
| 233 |
+
|
| 234 |
+
auto [B, H, Q, K, D] = args.problem_size;
|
| 235 |
+
D = cutlass::round_up(D, 8); // Alignment
|
| 236 |
+
Q = cutlass::round_up(Q, 8); // Alignment
|
| 237 |
+
char* workspace_chr = reinterpret_cast<char*>(workspace);
|
| 238 |
+
ElementAccumulator* sum_OdO = reinterpret_cast<ElementAccumulator*>(workspace_chr);
|
| 239 |
+
workspace_chr += B*H*Q * sizeof(ElementAccumulator);
|
| 240 |
+
ElementAccumulator* dQ_acc = reinterpret_cast<ElementAccumulator*>(workspace_chr);
|
| 241 |
+
return initialize_split(args, dQ_acc, sum_OdO, stream);
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
/// Primary run() entry point API that is static allowing users to create and manage their own params.
|
| 245 |
+
/// Supplied params struct must be construct by calling Kernel::to_underling_arguments()
|
| 246 |
+
static Status
|
| 247 |
+
run(Params& params, cudaStream_t stream = nullptr) {
|
| 248 |
+
CUTLASS_TRACE_HOST("FmhaDeviceBwd::run()");
|
| 249 |
+
|
| 250 |
+
Status result = Status::kSuccess;
|
| 251 |
+
result = params.op_sum_OdO.run(stream);
|
| 252 |
+
if (result != Status::kSuccess) {
|
| 253 |
+
return result;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
auto cuda_result = cudaMemsetAsync(params.dQ_acc, 0, params.dQ_acc_size, stream);
|
| 257 |
+
if (cuda_result != cudaSuccess) {
|
| 258 |
+
return Status::kErrorInternal;
|
| 259 |
+
}
|
| 260 |
+
result = params.op.run(stream);
|
| 261 |
+
if (result != Status::kSuccess) {
|
| 262 |
+
return result;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
result = params.op_convert.run(stream);
|
| 266 |
+
if (result != Status::kSuccess) {
|
| 267 |
+
return result;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
return Status::kSuccess;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
//
|
| 274 |
+
// Non-static launch overloads that first create and set the internal params struct of this kernel handle.
|
| 275 |
+
//
|
| 276 |
+
|
| 277 |
+
/// Launches the kernel after first constructing Params internal state from supplied arguments.
|
| 278 |
+
Status
|
| 279 |
+
run(Arguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr) {
|
| 280 |
+
Status status = initialize(args, workspace, stream);
|
| 281 |
+
if (Status::kSuccess == status) {
|
| 282 |
+
status = run(params_, stream);
|
| 283 |
+
}
|
| 284 |
+
return status;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
/// Overload that allows a user to re-launch the same kernel without updating internal params struct.
|
| 288 |
+
Status
|
| 289 |
+
run(cudaStream_t stream = nullptr) {
|
| 290 |
+
return run(params_, stream);
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
};
|
| 294 |
+
|
| 295 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 296 |
+
|
| 297 |
+
} // namespace cutlass::fmha::device
|
| 298 |
+
|
| 299 |
+
////////////////////////////////////////////////////////////////////////////////
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_builder.hpp
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "../collective/fmha_collective_tma.hpp"
|
| 35 |
+
#include "../collective/fmha_collective_tma_warpspecialized.hpp"
|
| 36 |
+
#include "../collective/fmha_epilogue.hpp"
|
| 37 |
+
#include "../kernel/fmha_kernel_tma.hpp"
|
| 38 |
+
#include "../kernel/fmha_kernel_tma_warpspecialized.hpp"
|
| 39 |
+
#include "../kernel/fmha_options.hpp"
|
| 40 |
+
|
| 41 |
+
namespace cutlass::fmha::kernel {
|
| 42 |
+
|
| 43 |
+
template<
|
| 44 |
+
class Element_,
|
| 45 |
+
class ElementAccumulatorQK_,
|
| 46 |
+
class ElementAccumulatorPV_,
|
| 47 |
+
class TileShape_, // BlockQO, BlockKV, BlockHead
|
| 48 |
+
class LayoutQ_,
|
| 49 |
+
class LayoutK_,
|
| 50 |
+
class LayoutV_,
|
| 51 |
+
class Fusion,
|
| 52 |
+
class DispatchPolicy,
|
| 53 |
+
class... Options
|
| 54 |
+
>
|
| 55 |
+
struct FmhaBuilder;
|
| 56 |
+
|
| 57 |
+
template<
|
| 58 |
+
class Element,
|
| 59 |
+
class ElementAccumulator,
|
| 60 |
+
class TileShape, // BlockQO, BlockKV, BlockHead
|
| 61 |
+
class Fusion,
|
| 62 |
+
class... Options
|
| 63 |
+
>
|
| 64 |
+
struct FmhaBuilder<
|
| 65 |
+
Element,
|
| 66 |
+
ElementAccumulator,
|
| 67 |
+
ElementAccumulator,
|
| 68 |
+
TileShape,
|
| 69 |
+
cute::tuple<int, _1, cute::tuple<int, int>>,
|
| 70 |
+
cute::tuple<int, _1, cute::tuple<int, int>>,
|
| 71 |
+
cute::tuple<int, _1, cute::tuple<int, int>>,
|
| 72 |
+
Fusion,
|
| 73 |
+
cutlass::gemm::KernelTma,
|
| 74 |
+
Options...
|
| 75 |
+
> {
|
| 76 |
+
|
| 77 |
+
using CollectiveMainloop = cutlass::fmha::collective::FmhaMainloopTma<Element, ElementAccumulator, TileShape, Fusion, Options...>;
|
| 78 |
+
|
| 79 |
+
using CollectiveEpilogue = cutlass::fmha::collective::FmhaFwdEpilogue<
|
| 80 |
+
Element, ElementAccumulator, typename CollectiveMainloop::TileShapePV>;
|
| 81 |
+
|
| 82 |
+
using Kernel = cutlass::fmha::kernel::FmhaKernelTma<CollectiveMainloop, CollectiveEpilogue, Options...>;
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
template<
|
| 86 |
+
class Element,
|
| 87 |
+
class ElementAccumulatorQK,
|
| 88 |
+
class ElementAccumulatorPV,
|
| 89 |
+
class TileShape, // BlockQO, BlockKV, BlockHead
|
| 90 |
+
class LayoutQ,
|
| 91 |
+
class LayoutK,
|
| 92 |
+
class LayoutV,
|
| 93 |
+
class Fusion,
|
| 94 |
+
class... Options
|
| 95 |
+
>
|
| 96 |
+
struct FmhaBuilder<
|
| 97 |
+
Element,
|
| 98 |
+
ElementAccumulatorQK,
|
| 99 |
+
ElementAccumulatorPV,
|
| 100 |
+
TileShape,
|
| 101 |
+
LayoutQ,
|
| 102 |
+
LayoutK,
|
| 103 |
+
LayoutV,
|
| 104 |
+
Fusion,
|
| 105 |
+
cutlass::gemm::KernelTmaWarpSpecializedCooperative,
|
| 106 |
+
Options...
|
| 107 |
+
> {
|
| 108 |
+
|
| 109 |
+
using CollectiveMainloop = cutlass::fmha::collective::FmhaMainloopTmaWarpSpecialized<
|
| 110 |
+
Element, ElementAccumulatorQK, ElementAccumulatorPV,
|
| 111 |
+
TileShape, LayoutQ, LayoutK, LayoutV,
|
| 112 |
+
Fusion, Options...>;
|
| 113 |
+
|
| 114 |
+
using CollectiveEpilogue = cutlass::fmha::collective::FmhaFwdEpilogue<
|
| 115 |
+
Element, ElementAccumulatorPV, typename CollectiveMainloop::TileShapePV>;
|
| 116 |
+
|
| 117 |
+
static constexpr bool kIsPersistent = find_option_t<Tag::kIsPersistent, false_type, Options...>::value;
|
| 118 |
+
using TileScheduler = std::conditional_t<kIsPersistent, cutlass::fmha::kernel::PersistentTileScheduler, cutlass::fmha::kernel::IndividualTileScheduler>;
|
| 119 |
+
|
| 120 |
+
using Kernel = cutlass::fmha::kernel::FmhaKernelTmaWarpSpecialized<CollectiveMainloop, CollectiveEpilogue, TileScheduler, Options...>;
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
template<
|
| 124 |
+
class Element,
|
| 125 |
+
class ElementAccumulatorQK,
|
| 126 |
+
class ElementAccumulatorPV,
|
| 127 |
+
class TileShape, // BlockQO, BlockKV, BlockHead
|
| 128 |
+
class LayoutQ,
|
| 129 |
+
class LayoutK,
|
| 130 |
+
class LayoutV,
|
| 131 |
+
class Fusion,
|
| 132 |
+
class... Options
|
| 133 |
+
>
|
| 134 |
+
struct FmhaBuilder<
|
| 135 |
+
Element,
|
| 136 |
+
ElementAccumulatorQK,
|
| 137 |
+
ElementAccumulatorPV,
|
| 138 |
+
TileShape,
|
| 139 |
+
LayoutQ,
|
| 140 |
+
LayoutK,
|
| 141 |
+
LayoutV,
|
| 142 |
+
Fusion,
|
| 143 |
+
cutlass::gemm::KernelTmaWarpSpecializedPingpong,
|
| 144 |
+
Options...
|
| 145 |
+
> {
|
| 146 |
+
using Kernel = typename FmhaBuilder<
|
| 147 |
+
Element, ElementAccumulatorQK, ElementAccumulatorPV,
|
| 148 |
+
TileShape,
|
| 149 |
+
LayoutQ, LayoutK, LayoutV,
|
| 150 |
+
Fusion,
|
| 151 |
+
cutlass::gemm::KernelTmaWarpSpecializedCooperative,
|
| 152 |
+
Options...,
|
| 153 |
+
Option<Tag::kIsPersistent, true_type>,
|
| 154 |
+
Option<Tag::kLoadsQSeparately, true_type>
|
| 155 |
+
>::Kernel;
|
| 156 |
+
};
|
| 157 |
+
|
| 158 |
+
} // namespace cutlass::fmha::kernel
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_bwd_convert.hpp
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cute/layout.hpp"
|
| 36 |
+
|
| 37 |
+
namespace cutlass::fmha::kernel {
|
| 38 |
+
|
| 39 |
+
using namespace cute;
|
| 40 |
+
|
| 41 |
+
template<class Element, class ElementAccumulator>
|
| 42 |
+
struct FmhaKernelBwdConvert {
|
| 43 |
+
|
| 44 |
+
struct Arguments {
|
| 45 |
+
tuple<int, int, int, int, int> problem_size;
|
| 46 |
+
|
| 47 |
+
const ElementAccumulator* ptr_src_dQ;
|
| 48 |
+
tuple<int, int, int, _1> stride_src_dQ;
|
| 49 |
+
const ElementAccumulator* ptr_src_dK;
|
| 50 |
+
tuple<int, int, int, _1> stride_src_dK;
|
| 51 |
+
const ElementAccumulator* ptr_src_dV;
|
| 52 |
+
tuple<int, int, int, _1> stride_src_dV;
|
| 53 |
+
|
| 54 |
+
Element* ptr_dest_dQ;
|
| 55 |
+
tuple<int, int, int, _1> stride_dest_dQ;
|
| 56 |
+
Element* ptr_dest_dK;
|
| 57 |
+
tuple<int, int, int, _1> stride_dest_dK;
|
| 58 |
+
Element* ptr_dest_dV;
|
| 59 |
+
tuple<int, int, int, _1> stride_dest_dV;
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
+
using Params = Arguments;
|
| 63 |
+
|
| 64 |
+
using ClusterShape = Shape<_1, _1, _1>;
|
| 65 |
+
static constexpr int SharedStorageSize = 0;
|
| 66 |
+
|
| 67 |
+
static const int MinBlocksPerMultiprocessor = 1;
|
| 68 |
+
static const int MaxThreadsPerBlock = 128;
|
| 69 |
+
using ArchTag = cutlass::arch::Sm90;
|
| 70 |
+
|
| 71 |
+
static const int kBlockSeq = 8;
|
| 72 |
+
|
| 73 |
+
static size_t get_workspace_size(Arguments const& args) { return 0; }
|
| 74 |
+
static cutlass::Status initialize_workspace(Arguments const&, void*, cudaStream_t) {
|
| 75 |
+
return cutlass::Status::kSuccess;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
static const int kNumThreadsD = 16;
|
| 79 |
+
static const int kNumThreadsSeq = MaxThreadsPerBlock / kNumThreadsD;
|
| 80 |
+
static const int kElementsPerLoad = 4;
|
| 81 |
+
|
| 82 |
+
static const int kIterationsSeq = kBlockSeq / kNumThreadsSeq;
|
| 83 |
+
|
| 84 |
+
static bool can_implement(Arguments const& args) {
|
| 85 |
+
return get<4>(args.problem_size) % kElementsPerLoad == 0;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 89 |
+
dim3 grid(size<0>(params.problem_size), size<1>(params.problem_size), ceil_div(std::max(size<2>(params.problem_size), size<3>(params.problem_size)), kBlockSeq));
|
| 90 |
+
return grid;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
static dim3 get_block_shape() {
|
| 94 |
+
dim3 block(kNumThreadsD, kNumThreadsSeq, 1);
|
| 95 |
+
return block;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
static Params to_underlying_arguments(Arguments const& args, void* workspace) {
|
| 99 |
+
return args;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
template<class StrideSrc, class StrideDest>
|
| 103 |
+
CUTLASS_DEVICE void copy(Params const& params, const ElementAccumulator* ptr_src, StrideSrc const& stride_src, Element* ptr_dest, StrideDest const& stride_dest, int count) {
|
| 104 |
+
auto ptr_src_bh = ptr_src + get<0>(stride_src) * blockIdx.x + get<1>(stride_src) * blockIdx.y;
|
| 105 |
+
auto ptr_dest_bh = ptr_dest + get<0>(stride_dest) * blockIdx.x + get<1>(stride_dest) * blockIdx.y;
|
| 106 |
+
|
| 107 |
+
for (int idx_s_t = threadIdx.y; idx_s_t < kBlockSeq; idx_s_t += kNumThreadsSeq) {
|
| 108 |
+
int idx_s = idx_s_t + kBlockSeq * blockIdx.z;
|
| 109 |
+
if (idx_s >= count) continue;
|
| 110 |
+
auto ptr_src_bhs = ptr_src_bh + idx_s * get<2>(stride_src);
|
| 111 |
+
auto ptr_dest_bhs = ptr_dest_bh + idx_s * get<2>(stride_dest);
|
| 112 |
+
|
| 113 |
+
for (int idx_d = threadIdx.x * kElementsPerLoad; idx_d < get<4>(params.problem_size); idx_d += kElementsPerLoad * kNumThreadsD) {
|
| 114 |
+
ElementAccumulator value_src[kElementsPerLoad];
|
| 115 |
+
Element value_dest[kElementsPerLoad];
|
| 116 |
+
|
| 117 |
+
using VecSrc = uint_bit_t<sizeof_bits_v<ElementAccumulator> * kElementsPerLoad>;
|
| 118 |
+
using VecDest = uint_bit_t<sizeof_bits_v<Element> * kElementsPerLoad>;
|
| 119 |
+
*reinterpret_cast<VecSrc*>(value_src) = *reinterpret_cast<const VecSrc*>(&ptr_src_bhs[idx_d]);
|
| 120 |
+
|
| 121 |
+
for (int v = 0; v < kElementsPerLoad; v++) {
|
| 122 |
+
value_dest[v] = value_src[v];
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
*reinterpret_cast<VecDest*>(&ptr_dest_bhs[idx_d]) = *reinterpret_cast<const VecDest*>(value_dest);
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
CUTLASS_DEVICE void operator()(const Params ¶ms, char* smem) {
|
| 131 |
+
if (params.ptr_src_dQ != nullptr) {
|
| 132 |
+
copy(params, params.ptr_src_dQ, params.stride_src_dQ, params.ptr_dest_dQ, params.stride_dest_dQ, get<2>(params.problem_size));
|
| 133 |
+
}
|
| 134 |
+
if (params.ptr_src_dK != nullptr) {
|
| 135 |
+
copy(params, params.ptr_src_dK, params.stride_src_dK, params.ptr_dest_dK, params.stride_dest_dK, get<3>(params.problem_size));
|
| 136 |
+
}
|
| 137 |
+
if (params.ptr_src_dV != nullptr) {
|
| 138 |
+
copy(params, params.ptr_src_dV, params.stride_src_dV, params.ptr_dest_dV, params.stride_dest_dV, get<3>(params.problem_size));
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
};
|
| 142 |
+
|
| 143 |
+
} // namespace cutlass::fmha::kernel
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_bwd_sum_OdO.hpp
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cute/layout.hpp"
|
| 36 |
+
|
| 37 |
+
namespace cutlass::fmha::kernel {
|
| 38 |
+
|
| 39 |
+
using namespace cute;
|
| 40 |
+
|
| 41 |
+
template<class Element, class ElementAccumulator>
|
| 42 |
+
struct FmhaKernelBwdSumOdO {
|
| 43 |
+
|
| 44 |
+
struct Arguments {
|
| 45 |
+
cute::tuple<int, int, int, int, int> problem_size;
|
| 46 |
+
|
| 47 |
+
const Element* ptr_O;
|
| 48 |
+
cute::tuple<int, int, int, cute::_1> stride_O;
|
| 49 |
+
const Element* ptr_dO;
|
| 50 |
+
cute::tuple<int, int, int, cute::_1> stride_dO;
|
| 51 |
+
|
| 52 |
+
ElementAccumulator* ptr_sum_OdO;
|
| 53 |
+
cute::tuple<int, int, _1> stride_sum_OdO;
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
using Params = Arguments;
|
| 57 |
+
|
| 58 |
+
using ClusterShape = Shape<_1, _1, _1>;
|
| 59 |
+
static constexpr int SharedStorageSize = 0;
|
| 60 |
+
|
| 61 |
+
static const int MinBlocksPerMultiprocessor = 1;
|
| 62 |
+
static const int MaxThreadsPerBlock = 128;
|
| 63 |
+
using ArchTag = cutlass::arch::Sm90;
|
| 64 |
+
|
| 65 |
+
static size_t get_workspace_size(Arguments const& args) { return 0; }
|
| 66 |
+
static cutlass::Status initialize_workspace(Arguments const&, void*, cudaStream_t) {
|
| 67 |
+
return cutlass::Status::kSuccess;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
static const int kBlockQ = 16;
|
| 71 |
+
|
| 72 |
+
static const int kNumThreadsD = 8;
|
| 73 |
+
static const int kNumThreadsQ = MaxThreadsPerBlock / kNumThreadsD;
|
| 74 |
+
static const int kElementsPerLoad = 2;
|
| 75 |
+
|
| 76 |
+
static const int kIterationsQ = kBlockQ / kNumThreadsQ;
|
| 77 |
+
|
| 78 |
+
static bool can_implement(Arguments const& args) {
|
| 79 |
+
return get<4>(args.problem_size) % kElementsPerLoad == 0;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 83 |
+
dim3 grid(ceil_div(size<2>(params.problem_size), kBlockQ), size<1>(params.problem_size), size<0>(params.problem_size));
|
| 84 |
+
return grid;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
static dim3 get_block_shape() {
|
| 88 |
+
dim3 block(kNumThreadsD, kNumThreadsQ, 1);
|
| 89 |
+
return block;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
static Params to_underlying_arguments(Arguments const& args, void* workspace) {
|
| 93 |
+
return args;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
CUTLASS_DEVICE void operator()(const Params ¶ms, char* smem) {
|
| 97 |
+
auto ptr_O_bh = params.ptr_O + blockIdx.y * get<1>(params.stride_O) + blockIdx.z * get<0>(params.stride_O);
|
| 98 |
+
auto ptr_dO_bh = params.ptr_dO + blockIdx.y * get<1>(params.stride_dO) + blockIdx.z * get<0>(params.stride_dO);
|
| 99 |
+
auto ptr_sum_OdO_bh = params.ptr_sum_OdO + blockIdx.y * get<1>(params.stride_sum_OdO) + blockIdx.z * get<0>(params.stride_sum_OdO);
|
| 100 |
+
|
| 101 |
+
CUTLASS_PRAGMA_UNROLL
|
| 102 |
+
for (int idx_q_t = threadIdx.y; idx_q_t < kBlockQ; idx_q_t += kNumThreadsQ) {
|
| 103 |
+
int idx_q = idx_q_t + kBlockQ * blockIdx.x;
|
| 104 |
+
if (idx_q >= get<2>(params.problem_size)) continue;
|
| 105 |
+
ElementAccumulator acc = 0;
|
| 106 |
+
auto ptr_O_bhq = ptr_O_bh + idx_q * get<2>(params.stride_O);
|
| 107 |
+
auto ptr_dO_bhq = ptr_dO_bh + idx_q * get<2>(params.stride_dO);
|
| 108 |
+
auto ptr_sum_OdO_bhq = ptr_sum_OdO_bh + idx_q * get<2>(params.stride_sum_OdO);
|
| 109 |
+
|
| 110 |
+
for (int idx_d = threadIdx.x * kElementsPerLoad; idx_d < get<4>(params.problem_size); idx_d += kElementsPerLoad * kNumThreadsD) {
|
| 111 |
+
Element value_O[kElementsPerLoad];
|
| 112 |
+
Element value_dO[kElementsPerLoad];
|
| 113 |
+
|
| 114 |
+
using Vec = uint_bit_t<sizeof_bits_v<Element> * kElementsPerLoad>;
|
| 115 |
+
*reinterpret_cast<Vec*>(value_O) = *reinterpret_cast<const Vec*>(&ptr_O_bhq[idx_d]);
|
| 116 |
+
*reinterpret_cast<Vec*>(value_dO) = *reinterpret_cast<const Vec*>(&ptr_dO_bhq[idx_d]);
|
| 117 |
+
|
| 118 |
+
for (int v = 0; v < kElementsPerLoad; v++) {
|
| 119 |
+
acc += value_O[v] * value_dO[v];
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
for (int i = 1; i < kNumThreadsD; i *= 2) {
|
| 124 |
+
acc += __shfl_xor_sync((uint32_t)-1, acc, i, kNumThreadsD);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
if (threadIdx.x == 0) {
|
| 128 |
+
*ptr_sum_OdO_bhq = acc;
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
} // namespace cutlass::fmha::kernel
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_tma.hpp
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/pipeline/pipeline.hpp"
|
| 36 |
+
#include "cutlass/arch/arch.h"
|
| 37 |
+
|
| 38 |
+
#include "../kernel/fmha_tile_scheduler.hpp"
|
| 39 |
+
#include "../kernel/fmha_options.hpp"
|
| 40 |
+
|
| 41 |
+
namespace cutlass::fmha::kernel {
|
| 42 |
+
|
| 43 |
+
template<
|
| 44 |
+
class CollectiveMainloop,
|
| 45 |
+
class CollectiveEpilogue,
|
| 46 |
+
class... Options
|
| 47 |
+
>
|
| 48 |
+
struct FmhaKernelTma {
|
| 49 |
+
|
| 50 |
+
// Options
|
| 51 |
+
static constexpr int kBlocksPerSM = find_option_t<Tag::kBlocksPerSM, Int<2>, Options...>::value;
|
| 52 |
+
|
| 53 |
+
using Element = typename CollectiveMainloop::Element;
|
| 54 |
+
using ElementAccumulator = typename CollectiveMainloop::ElementAccumulator;
|
| 55 |
+
|
| 56 |
+
using TileScheduler = IndividualTileScheduler;
|
| 57 |
+
|
| 58 |
+
using StagesQ = typename CollectiveMainloop::StagesQ;
|
| 59 |
+
using Stages = typename CollectiveMainloop::Stages;
|
| 60 |
+
|
| 61 |
+
using TileShape = typename CollectiveMainloop::TileShape;
|
| 62 |
+
using ClusterShape = typename CollectiveMainloop::ClusterShape;
|
| 63 |
+
|
| 64 |
+
using MainloopPipeline = typename CollectiveMainloop::MainloopPipeline;
|
| 65 |
+
using MainloopPipelineQ = typename CollectiveMainloop::MainloopPipelineQ;
|
| 66 |
+
|
| 67 |
+
using SmemLayoutQ = typename CollectiveMainloop::SmemLayoutQ;
|
| 68 |
+
using SmemLayoutK = typename CollectiveMainloop::SmemLayoutK;
|
| 69 |
+
|
| 70 |
+
struct SharedStorage {
|
| 71 |
+
union {
|
| 72 |
+
typename CollectiveMainloop::SharedStorage mainloop;
|
| 73 |
+
typename CollectiveEpilogue::TensorStorage epilogue;
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
using PipelineStorage = typename MainloopPipeline::SharedStorage;
|
| 77 |
+
using PipelineStorageQ = typename MainloopPipelineQ::SharedStorage;
|
| 78 |
+
alignas(16) PipelineStorage pipeline_storage;
|
| 79 |
+
alignas(16) PipelineStorageQ pipeline_storage_q;
|
| 80 |
+
|
| 81 |
+
using EpiLoadPipelineStorage = typename CollectiveEpilogue::PipelineStorage;
|
| 82 |
+
alignas(16) EpiLoadPipelineStorage epi_load;
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
static constexpr int SharedStorageSize = sizeof(SharedStorage);
|
| 86 |
+
|
| 87 |
+
using ProblemShape = cute::tuple<int, int, int, int, int>;
|
| 88 |
+
|
| 89 |
+
struct Arguments {
|
| 90 |
+
ProblemShape problem_size;
|
| 91 |
+
typename CollectiveMainloop::Arguments mainloop;
|
| 92 |
+
typename CollectiveEpilogue::Arguments epilogue;
|
| 93 |
+
KernelHardwareInfo hw_info;
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
struct Params {
|
| 97 |
+
ProblemShape problem_size;
|
| 98 |
+
typename CollectiveMainloop::Params mainloop;
|
| 99 |
+
typename CollectiveEpilogue::Params epilogue;
|
| 100 |
+
typename TileScheduler::Params tile_scheduler;
|
| 101 |
+
};
|
| 102 |
+
|
| 103 |
+
using PipelineParams = typename MainloopPipeline::Params;
|
| 104 |
+
using PipelineState = typename cutlass::PipelineState<MainloopPipeline::Stages>;
|
| 105 |
+
using PipelineParamsQ = typename MainloopPipelineQ::Params;
|
| 106 |
+
using PipelineStateQ = typename cutlass::PipelineState<MainloopPipelineQ::Stages>;
|
| 107 |
+
|
| 108 |
+
static const int MinBlocksPerMultiprocessor = kBlocksPerSM;
|
| 109 |
+
static const int MaxThreadsPerBlock = CollectiveMainloop::MaxThreadsPerBlock;
|
| 110 |
+
using ArchTag = cutlass::arch::Sm90;
|
| 111 |
+
|
| 112 |
+
static size_t get_workspace_size(Arguments const& args) { return 0; }
|
| 113 |
+
static cutlass::Status initialize_workspace(Arguments const&, void*, cudaStream_t) {
|
| 114 |
+
return cutlass::Status::kSuccess;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
static bool can_implement(Arguments const& args) {
|
| 118 |
+
return CollectiveMainloop::can_implement(args.problem_size, args.mainloop);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 122 |
+
return TileScheduler::get_grid_shape(params.tile_scheduler);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
static dim3 get_block_shape() {
|
| 126 |
+
dim3 block(MaxThreadsPerBlock, 1, 1);
|
| 127 |
+
return block;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
static Params to_underlying_arguments(Arguments const& args, void* workspace) {
|
| 131 |
+
return Params{
|
| 132 |
+
args.problem_size,
|
| 133 |
+
CollectiveMainloop::to_underlying_arguments(args.problem_size, args.mainloop, workspace),
|
| 134 |
+
CollectiveEpilogue::to_underlying_arguments(args.problem_size, args.epilogue, workspace),
|
| 135 |
+
TileScheduler::to_underlying_arguments(args.problem_size, args.hw_info, ClusterShape{}, TileShape{})
|
| 136 |
+
};
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
CUTLASS_DEVICE void operator()(const Params ¶ms, char* smem) {
|
| 140 |
+
#if ! defined(CUTLASS_ARCH_MMA_SM90A_ENABLED)
|
| 141 |
+
printf("ERROR : Arch conditional MMA instruction used without targeting appropriate compute capability. Aborting.\n");
|
| 142 |
+
#else
|
| 143 |
+
TileScheduler tile_scheduler{params.tile_scheduler};
|
| 144 |
+
|
| 145 |
+
// Shared memory.
|
| 146 |
+
auto& storage = *reinterpret_cast<SharedStorage*>(smem);
|
| 147 |
+
|
| 148 |
+
int thread_idx = int(threadIdx.x);
|
| 149 |
+
|
| 150 |
+
uint32_t block_rank_in_cluster = cute::block_rank_in_cluster();
|
| 151 |
+
|
| 152 |
+
int warp_idx = cutlass::canonical_warp_idx_sync();
|
| 153 |
+
int warp_group_thread_idx = thread_idx % cutlass::NumThreadsPerWarpGroup;
|
| 154 |
+
int lane_predicate = cute::elect_one_sync();
|
| 155 |
+
|
| 156 |
+
// Issue Tma Descriptor Prefetch from a single thread
|
| 157 |
+
if ((warp_idx == 0) && lane_predicate) {
|
| 158 |
+
CollectiveMainloop::prefetch_tma_descriptors(params.mainloop);
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
PipelineParamsQ pipeline_params_q;
|
| 163 |
+
pipeline_params_q.transaction_bytes = size(SmemLayoutQ{}(_,_,_0{})) * sizeof(Element); // Q
|
| 164 |
+
pipeline_params_q.role = MainloopPipelineQ::ThreadCategory::ProducerConsumer;
|
| 165 |
+
pipeline_params_q.is_leader = warp_group_thread_idx == 0;
|
| 166 |
+
pipeline_params_q.num_consumers = cutlass::NumThreadsPerWarpGroup;
|
| 167 |
+
|
| 168 |
+
PipelineParams pipeline_params;
|
| 169 |
+
pipeline_params.transaction_bytes = size(SmemLayoutK{}(_,_,_0{})) * sizeof(Element); // KV
|
| 170 |
+
pipeline_params.role = MainloopPipeline::ThreadCategory::ProducerConsumer;
|
| 171 |
+
pipeline_params.is_leader = warp_group_thread_idx == 0;
|
| 172 |
+
pipeline_params.num_consumers = cutlass::NumThreadsPerWarpGroup;
|
| 173 |
+
|
| 174 |
+
MainloopPipelineQ pipeline_q(storage.pipeline_storage_q, pipeline_params_q, Shape<_1, _1, _1>{});
|
| 175 |
+
MainloopPipeline pipeline(storage.pipeline_storage, pipeline_params, ClusterShape{});
|
| 176 |
+
|
| 177 |
+
using EpiLoadPipeline = typename CollectiveEpilogue::LoadPipeline;
|
| 178 |
+
typename EpiLoadPipeline::Params epi_load_pipeline_params;
|
| 179 |
+
epi_load_pipeline_params.role = EpiLoadPipeline::ThreadCategory::ProducerConsumer;
|
| 180 |
+
epi_load_pipeline_params.dst_blockid = cute::block_rank_in_cluster();
|
| 181 |
+
epi_load_pipeline_params.producer_arv_count = NumThreadsPerWarp;
|
| 182 |
+
epi_load_pipeline_params.consumer_arv_count = NumThreadsPerWarpGroup;
|
| 183 |
+
epi_load_pipeline_params.transaction_bytes = CollectiveEpilogue::TmaTransactionBytes;
|
| 184 |
+
EpiLoadPipeline epi_load_pipeline(storage.epi_load, epi_load_pipeline_params);
|
| 185 |
+
|
| 186 |
+
// State variables used for iterating the circular buffer
|
| 187 |
+
// smem_pipe_read / release is used by the consumer of SMEM data - i.e MMA
|
| 188 |
+
// smem_pipe_write is used by the producer of SMEM data - i.e TMA
|
| 189 |
+
PipelineState smem_pipe_read;
|
| 190 |
+
PipelineState smem_pipe_write = cutlass::make_producer_start_state<MainloopPipeline>();
|
| 191 |
+
|
| 192 |
+
PipelineStateQ smem_pipe_read_q;
|
| 193 |
+
PipelineStateQ smem_pipe_write_q = cutlass::make_producer_start_state<MainloopPipelineQ>();
|
| 194 |
+
|
| 195 |
+
// We need this to guarantee that the Pipeline init is visible
|
| 196 |
+
// To all producers and consumer blocks in the Cluster
|
| 197 |
+
// and to finish smem init
|
| 198 |
+
if constexpr (size(ClusterShape{}) > 1) {
|
| 199 |
+
cute::cluster_arrive_relaxed();
|
| 200 |
+
cute::cluster_wait();
|
| 201 |
+
}
|
| 202 |
+
else {
|
| 203 |
+
__syncthreads();
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
auto blk_coord = tile_scheduler.get_block_coord();
|
| 207 |
+
|
| 208 |
+
CollectiveMainloop collective_mainloop;
|
| 209 |
+
auto result = collective_mainloop.compute(
|
| 210 |
+
block_rank_in_cluster,
|
| 211 |
+
blk_coord, params.mainloop, params.problem_size,
|
| 212 |
+
pipeline, smem_pipe_read, smem_pipe_write,
|
| 213 |
+
pipeline_q, smem_pipe_read_q, smem_pipe_write_q,
|
| 214 |
+
storage.mainloop
|
| 215 |
+
);
|
| 216 |
+
|
| 217 |
+
CollectiveEpilogue epilogue;
|
| 218 |
+
epilogue(typename CollectiveMainloop::TileShapePV{}, blk_coord,
|
| 219 |
+
result, typename CollectiveMainloop::TiledMmaPV{},
|
| 220 |
+
params.problem_size, params.epilogue,
|
| 221 |
+
epi_load_pipeline, storage.epilogue);
|
| 222 |
+
#endif
|
| 223 |
+
}
|
| 224 |
+
};
|
| 225 |
+
|
| 226 |
+
} // namespace cutlass::fmha::kernel
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_kernel_tma_warpspecialized.hpp
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/arch/reg_reconfig.h"
|
| 36 |
+
#include "cutlass/pipeline/pipeline.hpp"
|
| 37 |
+
#include "cutlass/arch/arch.h"
|
| 38 |
+
|
| 39 |
+
#include "../kernel/fmha_options.hpp"
|
| 40 |
+
|
| 41 |
+
namespace cutlass::fmha::kernel {
|
| 42 |
+
|
| 43 |
+
using namespace cute;
|
| 44 |
+
|
| 45 |
+
template<
|
| 46 |
+
class CollectiveMainloop,
|
| 47 |
+
class CollectiveEpilogue,
|
| 48 |
+
class TileScheduler,
|
| 49 |
+
class... Options
|
| 50 |
+
>
|
| 51 |
+
struct FmhaKernelTmaWarpSpecialized {
|
| 52 |
+
|
| 53 |
+
// Options
|
| 54 |
+
static constexpr bool kIsEpilogueLocked = find_option_t<Tag::kIsEpilogueLocked, false_type, Options...>::value;
|
| 55 |
+
static constexpr bool kLoadsQSeparately = find_option_t<Tag::kLoadsQSeparately, false_type, Options...>::value;
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
static const int NumLoadWarpGroups = 1;
|
| 59 |
+
static constexpr int NumMmaWarpGroups = CollectiveMainloop::NumMmaWarpGroups;
|
| 60 |
+
|
| 61 |
+
using TileShape = typename CollectiveMainloop::TileShape;
|
| 62 |
+
using ClusterShape = typename CollectiveMainloop::ClusterShape;
|
| 63 |
+
|
| 64 |
+
using MainloopPipelineOuter = typename CollectiveMainloop::MainloopPipelineQ;
|
| 65 |
+
using MainloopPipelineInner = typename CollectiveMainloop::MainloopPipeline;
|
| 66 |
+
using MainloopPipelineReducer = cutlass::PipelineAsync<2>;
|
| 67 |
+
|
| 68 |
+
static constexpr uint32_t StagesPerMathWarpGroup = 2;
|
| 69 |
+
using MathWarpGroupOrderBarrier = cutlass::OrderedSequenceBarrier<
|
| 70 |
+
StagesPerMathWarpGroup, NumMmaWarpGroups>;
|
| 71 |
+
|
| 72 |
+
struct TensorStorageStruct {
|
| 73 |
+
typename CollectiveMainloop::SharedStorage mainloop;
|
| 74 |
+
typename CollectiveEpilogue::TensorStorage epilogue[NumMmaWarpGroups];
|
| 75 |
+
};
|
| 76 |
+
union TensorStorageUnion {
|
| 77 |
+
typename CollectiveMainloop::SharedStorage mainloop;
|
| 78 |
+
typename CollectiveEpilogue::TensorStorage epilogue[NumMmaWarpGroups];
|
| 79 |
+
};
|
| 80 |
+
using TensorStorage = std::conditional_t<CollectiveMainloop::kIsPersistent, TensorStorageStruct, TensorStorageUnion>;
|
| 81 |
+
|
| 82 |
+
struct SharedStorage {
|
| 83 |
+
TensorStorage tensors;
|
| 84 |
+
|
| 85 |
+
using PipelineStorageInner = typename MainloopPipelineInner::SharedStorage;
|
| 86 |
+
using PipelineStorageOuter = typename MainloopPipelineOuter::SharedStorage;
|
| 87 |
+
using PipelineStorageReducer = typename MainloopPipelineReducer::SharedStorage;
|
| 88 |
+
|
| 89 |
+
alignas(16) PipelineStorageInner pipeline_storage_inner;
|
| 90 |
+
alignas(16) PipelineStorageOuter pipeline_storage_outer;
|
| 91 |
+
alignas(16) PipelineStorageReducer pipeline_storage_reducer;
|
| 92 |
+
|
| 93 |
+
using MathWarpGroupOrderBarrierStorage = typename MathWarpGroupOrderBarrier::SharedStorage;
|
| 94 |
+
alignas(16) MathWarpGroupOrderBarrierStorage math_wg_order;
|
| 95 |
+
|
| 96 |
+
alignas(16) cutlass::arch::ClusterBarrier load_warp_barrier;
|
| 97 |
+
|
| 98 |
+
using EpiLoadPipelineStorage = typename CollectiveEpilogue::PipelineStorage;
|
| 99 |
+
alignas(16) EpiLoadPipelineStorage epi_load;
|
| 100 |
+
};
|
| 101 |
+
|
| 102 |
+
static constexpr int SharedStorageSize = sizeof(SharedStorage);
|
| 103 |
+
|
| 104 |
+
using ProblemShape = cute::tuple<int, int, int, int, int>;
|
| 105 |
+
|
| 106 |
+
struct Arguments {
|
| 107 |
+
ProblemShape problem_size;
|
| 108 |
+
typename CollectiveMainloop::Arguments mainloop;
|
| 109 |
+
typename CollectiveEpilogue::Arguments epilogue;
|
| 110 |
+
KernelHardwareInfo hw_info;
|
| 111 |
+
};
|
| 112 |
+
|
| 113 |
+
struct Params {
|
| 114 |
+
ProblemShape problem_size;
|
| 115 |
+
typename CollectiveMainloop::Params mainloop;
|
| 116 |
+
typename CollectiveEpilogue::Params epilogue;
|
| 117 |
+
typename TileScheduler::Params tile_scheduler;
|
| 118 |
+
};
|
| 119 |
+
|
| 120 |
+
using PipelineParamsInner = typename MainloopPipelineInner::Params;
|
| 121 |
+
using PipelineStateInner = typename cutlass::PipelineState<MainloopPipelineInner::Stages>;
|
| 122 |
+
using PipelineParamsOuter = typename MainloopPipelineOuter::Params;
|
| 123 |
+
using PipelineStateOuter = typename cutlass::PipelineState<MainloopPipelineOuter::Stages>;
|
| 124 |
+
using PipelineParamsReducer = typename MainloopPipelineReducer::Params;
|
| 125 |
+
using PipelineStateReducer = typename cutlass::PipelineState<MainloopPipelineReducer::Stages>;
|
| 126 |
+
|
| 127 |
+
static const int MinBlocksPerMultiprocessor = 1;
|
| 128 |
+
static const int MaxThreadsPerBlock = (NumMmaWarpGroups + NumLoadWarpGroups) * cutlass::NumThreadsPerWarpGroup;
|
| 129 |
+
using ArchTag = cutlass::arch::Sm90;
|
| 130 |
+
|
| 131 |
+
static constexpr uint32_t LoadRegisterRequirement = 40 - 2 * 8;
|
| 132 |
+
static constexpr uint32_t TotalRegisterSupply = (64*1024 / MaxThreadsPerBlock / MinBlocksPerMultiprocessor / 8) * 8 * MaxThreadsPerBlock / cutlass::NumThreadsPerWarpGroup;
|
| 133 |
+
static constexpr uint32_t MmaRegisterRequirement = ((TotalRegisterSupply - LoadRegisterRequirement) / NumMmaWarpGroups / 8) * 8;
|
| 134 |
+
|
| 135 |
+
static size_t get_workspace_size(Arguments const& args) { return 0; }
|
| 136 |
+
static cutlass::Status initialize_workspace(Arguments const&, void*, cudaStream_t) {
|
| 137 |
+
return cutlass::Status::kSuccess;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
static bool can_implement(Arguments const& args) {
|
| 141 |
+
return CollectiveMainloop::can_implement(args.problem_size, args.mainloop);
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 145 |
+
return TileScheduler::get_grid_shape(params.tile_scheduler);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
static dim3 get_block_shape() {
|
| 149 |
+
dim3 block(MaxThreadsPerBlock, 1, 1);
|
| 150 |
+
return block;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
static Params to_underlying_arguments(Arguments const& args, void* workspace) {
|
| 154 |
+
return Params{
|
| 155 |
+
args.problem_size,
|
| 156 |
+
CollectiveMainloop::to_underlying_arguments(args.problem_size, args.mainloop, workspace),
|
| 157 |
+
CollectiveEpilogue::to_underlying_arguments(args.problem_size, args.epilogue, workspace),
|
| 158 |
+
TileScheduler::to_underlying_arguments(args.problem_size, args.hw_info, ClusterShape{}, TileShape{})
|
| 159 |
+
};
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
CUTLASS_DEVICE void operator()(const Params ¶ms, char* smem) {
|
| 163 |
+
#if ! defined(CUTLASS_ARCH_MMA_SM90A_ENABLED)
|
| 164 |
+
printf("ERROR : Arch conditional MMA instruction used without targeting appropriate compute capability. Aborting.\n");
|
| 165 |
+
#else
|
| 166 |
+
|
| 167 |
+
enum class WarpGroupRole {
|
| 168 |
+
Producer = 0,
|
| 169 |
+
Consumer0 = 1,
|
| 170 |
+
Consumer1 = 2,
|
| 171 |
+
Consumer2 = 3,
|
| 172 |
+
Consumer3 = 4,
|
| 173 |
+
};
|
| 174 |
+
enum class ProducerWarpRole {
|
| 175 |
+
LoadKV = 1,
|
| 176 |
+
Reducer = 0,
|
| 177 |
+
MaybeLoadQ = 2, // is kLoadsQSeparately is true, this warp loads Q (otherwise warp 0 does it)
|
| 178 |
+
MainloopEpilogue = 3,
|
| 179 |
+
};
|
| 180 |
+
|
| 181 |
+
static constexpr ProducerWarpRole WarpRoleLoadQ = kLoadsQSeparately ? ProducerWarpRole::MaybeLoadQ : ProducerWarpRole::LoadKV;
|
| 182 |
+
|
| 183 |
+
TileScheduler tile_scheduler{params.tile_scheduler};
|
| 184 |
+
|
| 185 |
+
// Shared memory.
|
| 186 |
+
auto& storage = *reinterpret_cast<SharedStorage*>(smem);
|
| 187 |
+
|
| 188 |
+
int lane_idx = cutlass::canonical_lane_idx();
|
| 189 |
+
int warp_idx = cutlass::canonical_warp_idx_sync();
|
| 190 |
+
int warp_idx_in_warp_group = warp_idx % cutlass::NumWarpsPerWarpGroup;
|
| 191 |
+
int warp_group_idx = cutlass::canonical_warp_group_idx();
|
| 192 |
+
auto warp_group_role = WarpGroupRole(warp_group_idx);
|
| 193 |
+
auto producer_warp_role = ProducerWarpRole(warp_idx_in_warp_group);
|
| 194 |
+
int consumer_warp_group_idx = warp_group_idx - (int) WarpGroupRole::Consumer0;
|
| 195 |
+
int lane_predicate = cute::elect_one_sync();
|
| 196 |
+
uint32_t block_rank_in_cluster = cute::block_rank_in_cluster();
|
| 197 |
+
|
| 198 |
+
// Issue Tma Descriptor Prefetch from a single thread
|
| 199 |
+
if ((warp_idx == 0) && lane_predicate) {
|
| 200 |
+
CollectiveMainloop::prefetch_tma_descriptors(params.mainloop);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
PipelineParamsOuter pipeline_params_outer;
|
| 204 |
+
pipeline_params_outer.transaction_bytes = CollectiveMainloop::kOuterLoadBytes;
|
| 205 |
+
pipeline_params_outer.is_leader = lane_predicate && (producer_warp_role == WarpRoleLoadQ);
|
| 206 |
+
pipeline_params_outer.num_consumers = cutlass::NumThreadsPerWarpGroup;
|
| 207 |
+
|
| 208 |
+
PipelineParamsInner pipeline_params_inner;
|
| 209 |
+
pipeline_params_inner.transaction_bytes = CollectiveMainloop::kInnerLoadBytes;
|
| 210 |
+
pipeline_params_inner.is_leader = lane_predicate && (producer_warp_role == ProducerWarpRole::LoadKV);
|
| 211 |
+
pipeline_params_inner.num_consumers = NumMmaWarpGroups * cutlass::NumThreadsPerWarpGroup;
|
| 212 |
+
|
| 213 |
+
PipelineParamsReducer pipeline_params_reducer;
|
| 214 |
+
pipeline_params_reducer.producer_arv_count = NumMmaWarpGroups * cutlass::NumThreadsPerWarpGroup;
|
| 215 |
+
pipeline_params_reducer.consumer_arv_count = cutlass::NumThreadsPerWarp;
|
| 216 |
+
|
| 217 |
+
using EpiLoadPipeline = typename CollectiveEpilogue::LoadPipeline;
|
| 218 |
+
typename EpiLoadPipeline::Params epi_load_pipeline_params;
|
| 219 |
+
|
| 220 |
+
if (warp_group_role == WarpGroupRole::Producer && producer_warp_role == ProducerWarpRole::MainloopEpilogue) {
|
| 221 |
+
epi_load_pipeline_params.role = EpiLoadPipeline::ThreadCategory::Producer;
|
| 222 |
+
}
|
| 223 |
+
if (warp_group_role == WarpGroupRole::Producer && producer_warp_role == ProducerWarpRole::LoadKV) {
|
| 224 |
+
pipeline_params_inner.role = MainloopPipelineInner::ThreadCategory::Producer;
|
| 225 |
+
}
|
| 226 |
+
if (warp_group_role == WarpGroupRole::Producer && producer_warp_role == WarpRoleLoadQ) {
|
| 227 |
+
pipeline_params_outer.role = MainloopPipelineOuter::ThreadCategory::Producer;
|
| 228 |
+
}
|
| 229 |
+
if (warp_group_role == WarpGroupRole::Producer && producer_warp_role == ProducerWarpRole::Reducer) {
|
| 230 |
+
pipeline_params_reducer.role = MainloopPipelineReducer::ThreadCategory::Consumer;
|
| 231 |
+
}
|
| 232 |
+
if (warp_group_role == WarpGroupRole::Consumer0 ||
|
| 233 |
+
warp_group_role == WarpGroupRole::Consumer1 ||
|
| 234 |
+
warp_group_role == WarpGroupRole::Consumer2 ||
|
| 235 |
+
warp_group_role == WarpGroupRole::Consumer3
|
| 236 |
+
) {
|
| 237 |
+
pipeline_params_inner.role = MainloopPipelineInner::ThreadCategory::Consumer;
|
| 238 |
+
pipeline_params_outer.role = MainloopPipelineOuter::ThreadCategory::Consumer;
|
| 239 |
+
pipeline_params_reducer.role = MainloopPipelineReducer::ThreadCategory::Producer;
|
| 240 |
+
epi_load_pipeline_params.role = EpiLoadPipeline::ThreadCategory::Consumer;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
MainloopPipelineOuter pipeline_outer(storage.pipeline_storage_outer, pipeline_params_outer, Shape<_1, _1, _1>{});
|
| 244 |
+
MainloopPipelineInner pipeline_inner(storage.pipeline_storage_inner, pipeline_params_inner, ClusterShape{});
|
| 245 |
+
MainloopPipelineReducer pipeline_reducer(storage.pipeline_storage_reducer, pipeline_params_reducer);
|
| 246 |
+
|
| 247 |
+
// State variables used for iterating the circular buffer
|
| 248 |
+
// smem_pipe_read / release is used by the consumer of SMEM data - i.e MMA
|
| 249 |
+
// smem_pipe_write is used by the producer of SMEM data - i.e TMA
|
| 250 |
+
PipelineStateInner smem_pipe_read_inner;
|
| 251 |
+
PipelineStateInner smem_pipe_write_inner = cutlass::make_producer_start_state<MainloopPipelineInner>();
|
| 252 |
+
|
| 253 |
+
PipelineStateOuter smem_pipe_read_outer;
|
| 254 |
+
PipelineStateOuter smem_pipe_write_outer = cutlass::make_producer_start_state<MainloopPipelineOuter>();
|
| 255 |
+
|
| 256 |
+
PipelineStateReducer smem_pipe_read_reducer;
|
| 257 |
+
PipelineStateReducer smem_pipe_write_reducer = cutlass::make_producer_start_state<MainloopPipelineReducer>();
|
| 258 |
+
|
| 259 |
+
typename MathWarpGroupOrderBarrier::Params params_math_wg_order_barrier;
|
| 260 |
+
// DMA Load WG will not participate in these Ordered Barrier syncs
|
| 261 |
+
params_math_wg_order_barrier.group_id = consumer_warp_group_idx;
|
| 262 |
+
params_math_wg_order_barrier.group_size = cutlass::NumThreadsPerWarpGroup; // Number of threads / participants in a group
|
| 263 |
+
MathWarpGroupOrderBarrier math_wg_order_barrier(storage.math_wg_order, params_math_wg_order_barrier);
|
| 264 |
+
|
| 265 |
+
// Epilogue Load pipeline
|
| 266 |
+
epi_load_pipeline_params.dst_blockid = cute::block_rank_in_cluster();
|
| 267 |
+
epi_load_pipeline_params.producer_arv_count = NumThreadsPerWarp;
|
| 268 |
+
epi_load_pipeline_params.consumer_arv_count = NumThreadsPerWarpGroup;
|
| 269 |
+
epi_load_pipeline_params.transaction_bytes = CollectiveEpilogue::TmaTransactionBytes;
|
| 270 |
+
EpiLoadPipeline epi_load_pipeline(storage.epi_load, epi_load_pipeline_params);
|
| 271 |
+
|
| 272 |
+
if constexpr (kLoadsQSeparately) {
|
| 273 |
+
if ((warp_idx == 0) && lane_predicate) {
|
| 274 |
+
storage.load_warp_barrier.init(2 * cutlass::NumThreadsPerWarp);
|
| 275 |
+
}
|
| 276 |
+
cutlass::arch::fence_barrier_init();
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
// We need this to guarantee that the Pipeline init is visible
|
| 280 |
+
// To all producers and consumer blocks in the Cluster
|
| 281 |
+
// and to finish smem init
|
| 282 |
+
if constexpr (size(ClusterShape{}) > 1) {
|
| 283 |
+
cute::cluster_arrive_relaxed();
|
| 284 |
+
cute::cluster_wait();
|
| 285 |
+
}
|
| 286 |
+
else {
|
| 287 |
+
__syncthreads();
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
CollectiveMainloop collective_mainloop;
|
| 291 |
+
|
| 292 |
+
if (warp_group_role == WarpGroupRole::Producer) {
|
| 293 |
+
cutlass::arch::warpgroup_reg_dealloc<LoadRegisterRequirement>();
|
| 294 |
+
if (producer_warp_role == ProducerWarpRole::LoadKV) {
|
| 295 |
+
bool do_barrier = kLoadsQSeparately;
|
| 296 |
+
|
| 297 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 298 |
+
for (; tile_scheduler.is_valid(); ++tile_scheduler) {
|
| 299 |
+
auto blk_coord = tile_scheduler.get_block_coord();
|
| 300 |
+
collective_mainloop.template load_kv_maybe_q<!kLoadsQSeparately>(
|
| 301 |
+
block_rank_in_cluster,
|
| 302 |
+
blk_coord, params.mainloop, params.problem_size,
|
| 303 |
+
pipeline_inner, smem_pipe_write_inner,
|
| 304 |
+
pipeline_outer, smem_pipe_write_outer,
|
| 305 |
+
storage.tensors.mainloop,
|
| 306 |
+
storage.load_warp_barrier, do_barrier
|
| 307 |
+
);
|
| 308 |
+
do_barrier = false;
|
| 309 |
+
}
|
| 310 |
+
}
|
| 311 |
+
else if (kLoadsQSeparately && (producer_warp_role == ProducerWarpRole::MaybeLoadQ)) {
|
| 312 |
+
bool do_barrier = true;
|
| 313 |
+
|
| 314 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 315 |
+
for (; tile_scheduler.is_valid(); ++tile_scheduler) {
|
| 316 |
+
auto blk_coord = tile_scheduler.get_block_coord();
|
| 317 |
+
collective_mainloop.load_maybe_q(
|
| 318 |
+
blk_coord, params.mainloop, params.problem_size,
|
| 319 |
+
pipeline_outer, smem_pipe_write_outer,
|
| 320 |
+
storage.tensors.mainloop,
|
| 321 |
+
storage.load_warp_barrier, do_barrier
|
| 322 |
+
);
|
| 323 |
+
do_barrier = false;
|
| 324 |
+
}
|
| 325 |
+
} else if (producer_warp_role == ProducerWarpRole::Reducer) {
|
| 326 |
+
for (; tile_scheduler.is_valid(); ++tile_scheduler) {
|
| 327 |
+
auto blk_coord = tile_scheduler.get_block_coord();
|
| 328 |
+
collective_mainloop.reduce(
|
| 329 |
+
blk_coord, params.mainloop, params.problem_size,
|
| 330 |
+
pipeline_reducer, smem_pipe_read_reducer,
|
| 331 |
+
storage.tensors.mainloop
|
| 332 |
+
);
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
}
|
| 336 |
+
else if (
|
| 337 |
+
warp_group_role == WarpGroupRole::Consumer0 ||
|
| 338 |
+
warp_group_role == WarpGroupRole::Consumer1 ||
|
| 339 |
+
warp_group_role == WarpGroupRole::Consumer2 ||
|
| 340 |
+
warp_group_role == WarpGroupRole::Consumer3
|
| 341 |
+
) {
|
| 342 |
+
cutlass::arch::warpgroup_reg_alloc<MmaRegisterRequirement>();
|
| 343 |
+
CUTLASS_PRAGMA_NO_UNROLL
|
| 344 |
+
for (; tile_scheduler.is_valid(); ++tile_scheduler) {
|
| 345 |
+
auto blk_coord = tile_scheduler.get_block_coord();
|
| 346 |
+
auto wg_coord = blk_coord;
|
| 347 |
+
|
| 348 |
+
constexpr int kOuterLoads = CollectiveMainloop::kOuterLoads;
|
| 349 |
+
|
| 350 |
+
if (warp_group_role == WarpGroupRole::Consumer0) {
|
| 351 |
+
smem_pipe_read_outer.advance(0 * kOuterLoads);
|
| 352 |
+
}
|
| 353 |
+
else if (warp_group_role == WarpGroupRole::Consumer1) {
|
| 354 |
+
smem_pipe_read_outer.advance(1 * kOuterLoads);
|
| 355 |
+
}
|
| 356 |
+
else if (warp_group_role == WarpGroupRole::Consumer2) {
|
| 357 |
+
smem_pipe_read_outer.advance(2 * kOuterLoads);
|
| 358 |
+
}
|
| 359 |
+
else if (warp_group_role == WarpGroupRole::Consumer3) {
|
| 360 |
+
smem_pipe_read_outer.advance(3 * kOuterLoads);
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
constexpr int wg_dim = is_constant<0, decltype(get<1>(wg_coord))>::value ? 0 : 1;
|
| 364 |
+
auto& wg_block = get<wg_dim>(wg_coord);
|
| 365 |
+
if (warp_group_role == WarpGroupRole::Consumer0) {
|
| 366 |
+
wg_block = NumMmaWarpGroups * wg_block + 0;
|
| 367 |
+
}
|
| 368 |
+
else if (warp_group_role == WarpGroupRole::Consumer1) {
|
| 369 |
+
wg_block = NumMmaWarpGroups * wg_block + 1;
|
| 370 |
+
}
|
| 371 |
+
else if (warp_group_role == WarpGroupRole::Consumer2) {
|
| 372 |
+
wg_block = NumMmaWarpGroups * wg_block + 2;
|
| 373 |
+
}
|
| 374 |
+
else if (warp_group_role == WarpGroupRole::Consumer3) {
|
| 375 |
+
wg_block = NumMmaWarpGroups * wg_block + 3;
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
auto result = collective_mainloop.compute(
|
| 379 |
+
blk_coord, wg_coord,
|
| 380 |
+
params.mainloop, params.problem_size,
|
| 381 |
+
pipeline_inner, smem_pipe_read_inner,
|
| 382 |
+
pipeline_outer, smem_pipe_read_outer,
|
| 383 |
+
pipeline_reducer, smem_pipe_write_reducer,
|
| 384 |
+
storage.tensors.mainloop,
|
| 385 |
+
math_wg_order_barrier
|
| 386 |
+
);
|
| 387 |
+
|
| 388 |
+
if (warp_group_role == WarpGroupRole::Consumer0) {
|
| 389 |
+
smem_pipe_read_outer.advance(kOuterLoads * (NumMmaWarpGroups - 0));
|
| 390 |
+
}
|
| 391 |
+
if constexpr (NumMmaWarpGroups >= 2) {
|
| 392 |
+
if (warp_group_role == WarpGroupRole::Consumer1) {
|
| 393 |
+
smem_pipe_read_outer.advance(kOuterLoads * (NumMmaWarpGroups - 1));
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
if constexpr (NumMmaWarpGroups >= 3) {
|
| 397 |
+
if (warp_group_role == WarpGroupRole::Consumer2) {
|
| 398 |
+
smem_pipe_read_outer.advance(kOuterLoads * (NumMmaWarpGroups - 2));
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
+
if constexpr (NumMmaWarpGroups >= 4) {
|
| 402 |
+
if (warp_group_role == WarpGroupRole::Consumer3) {
|
| 403 |
+
smem_pipe_read_outer.advance(kOuterLoads * (NumMmaWarpGroups - 3));
|
| 404 |
+
}
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
if constexpr (kIsEpilogueLocked) ; math_wg_order_barrier.wait();
|
| 408 |
+
|
| 409 |
+
CollectiveEpilogue epilogue;
|
| 410 |
+
epilogue(typename CollectiveMainloop::TileShapePV{}, wg_coord,
|
| 411 |
+
result, typename CollectiveMainloop::TiledMmaPV{},
|
| 412 |
+
params.problem_size, params.epilogue,
|
| 413 |
+
epi_load_pipeline, storage.tensors.epilogue[consumer_warp_group_idx]);
|
| 414 |
+
|
| 415 |
+
if constexpr (kIsEpilogueLocked) ; math_wg_order_barrier.arrive();
|
| 416 |
+
}
|
| 417 |
+
}
|
| 418 |
+
#endif
|
| 419 |
+
}
|
| 420 |
+
};
|
| 421 |
+
|
| 422 |
+
} // namespace cutlass::fmha::kernel
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_options.hpp
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
|
| 36 |
+
namespace cutlass::fmha::kernel {
|
| 37 |
+
|
| 38 |
+
template<auto kTag, typename Default, typename... Options>
|
| 39 |
+
struct find_option;
|
| 40 |
+
|
| 41 |
+
template<auto kTag, typename Default>
|
| 42 |
+
struct find_option<kTag, Default> {
|
| 43 |
+
using option_value = Default;
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
template<auto kTag, typename Default, typename Option, typename... Options>
|
| 47 |
+
struct find_option<kTag, Default, Option, Options...> :
|
| 48 |
+
std::conditional_t<
|
| 49 |
+
Option::tag == kTag,
|
| 50 |
+
Option,
|
| 51 |
+
find_option<kTag, Default, Options...>
|
| 52 |
+
>
|
| 53 |
+
{};
|
| 54 |
+
|
| 55 |
+
template<auto kTag, typename Default, typename... Options>
|
| 56 |
+
using find_option_t = typename find_option<kTag, Default, Options...>::option_value;
|
| 57 |
+
|
| 58 |
+
enum class Tag {
|
| 59 |
+
kIsPersistent,
|
| 60 |
+
kNumMmaWarpGroups,
|
| 61 |
+
kLoadsQSeparately,
|
| 62 |
+
|
| 63 |
+
kIsMainloopLocked,
|
| 64 |
+
kIsEpilogueLocked,
|
| 65 |
+
|
| 66 |
+
kStagesQ,
|
| 67 |
+
kStagesKV,
|
| 68 |
+
|
| 69 |
+
kEpilogueKind,
|
| 70 |
+
|
| 71 |
+
kBlocksPerSM,
|
| 72 |
+
kClusterM,
|
| 73 |
+
|
| 74 |
+
kAccQK
|
| 75 |
+
};
|
| 76 |
+
|
| 77 |
+
template<auto kTag, class Value>
|
| 78 |
+
struct Option {
|
| 79 |
+
static constexpr auto tag = kTag;
|
| 80 |
+
using option_value = Value;
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
} // namespace cutlass::fmha::kernel
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/kernel/fmha_tile_scheduler.hpp
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
|
| 34 |
+
#include "cutlass/cutlass.h"
|
| 35 |
+
#include "cutlass/fast_math.h"
|
| 36 |
+
#include "cutlass/kernel_hardware_info.h"
|
| 37 |
+
|
| 38 |
+
namespace cutlass::fmha::kernel {
|
| 39 |
+
|
| 40 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 41 |
+
|
| 42 |
+
struct IndividualTileScheduler {
|
| 43 |
+
|
| 44 |
+
struct Params {
|
| 45 |
+
dim3 grid;
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
bool valid_ = true;
|
| 49 |
+
|
| 50 |
+
CUTLASS_DEVICE
|
| 51 |
+
IndividualTileScheduler(Params const&) {}
|
| 52 |
+
|
| 53 |
+
template<class ProblemSize, class ClusterShape, class TileShape>
|
| 54 |
+
static Params to_underlying_arguments(
|
| 55 |
+
ProblemSize const& problem_size, KernelHardwareInfo hw_info,
|
| 56 |
+
ClusterShape const& cluster_shape, TileShape const& tile_shape)
|
| 57 |
+
{
|
| 58 |
+
using namespace cute;
|
| 59 |
+
dim3 grid(round_up(ceil_div(size<2>(problem_size), size<0>(tile_shape)), size<0>(cluster_shape)), size<0>(problem_size), size<1>(problem_size));
|
| 60 |
+
return Params{ grid };
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 64 |
+
return params.grid;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
CUTLASS_DEVICE
|
| 68 |
+
bool is_valid() {
|
| 69 |
+
return valid_;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
CUTLASS_DEVICE
|
| 73 |
+
auto get_block_coord() {
|
| 74 |
+
using namespace cute;
|
| 75 |
+
return make_coord(blockIdx.x, _0{}, make_coord(blockIdx.y, blockIdx.z));
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
CUTLASS_DEVICE
|
| 79 |
+
IndividualTileScheduler& operator++() {
|
| 80 |
+
valid_ = false;
|
| 81 |
+
return *this;
|
| 82 |
+
}
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 86 |
+
|
| 87 |
+
struct PersistentTileScheduler {
|
| 88 |
+
|
| 89 |
+
struct Params {
|
| 90 |
+
int num_blocks;
|
| 91 |
+
FastDivmod divmod_m_block;
|
| 92 |
+
FastDivmod divmod_b;
|
| 93 |
+
FastDivmod divmod_h;
|
| 94 |
+
|
| 95 |
+
KernelHardwareInfo hw_info;
|
| 96 |
+
};
|
| 97 |
+
|
| 98 |
+
int block_idx = 0;
|
| 99 |
+
Params params;
|
| 100 |
+
|
| 101 |
+
CUTLASS_DEVICE
|
| 102 |
+
PersistentTileScheduler(Params const& params) : block_idx(blockIdx.x), params(params) {}
|
| 103 |
+
|
| 104 |
+
template<class ProblemSize, class ClusterShape, class TileShape>
|
| 105 |
+
static Params to_underlying_arguments(
|
| 106 |
+
ProblemSize const& problem_size, KernelHardwareInfo hw_info,
|
| 107 |
+
ClusterShape const& cluster_shape, TileShape const& tile_shape)
|
| 108 |
+
{
|
| 109 |
+
using namespace cute;
|
| 110 |
+
// Get SM count if needed, otherwise use user supplied SM count
|
| 111 |
+
int sm_count = hw_info.sm_count;
|
| 112 |
+
if (sm_count <= 0) {
|
| 113 |
+
CUTLASS_TRACE_HOST(" WARNING: Arguments do not include a valid SM count.\n"
|
| 114 |
+
" For optimal performance, populate the arguments KernelHardwareInfo struct with the SM count.");
|
| 115 |
+
sm_count = KernelHardwareInfo::query_device_multiprocessor_count(hw_info.device_id);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
CUTLASS_TRACE_HOST("to_underlying_arguments(): Setting persistent grid SM count to " << sm_count);
|
| 119 |
+
hw_info.sm_count = sm_count;
|
| 120 |
+
|
| 121 |
+
int num_m_blocks = cutlass::round_up(ceil_div(size<2>(problem_size), size<0>(tile_shape)), size<0>(cluster_shape));
|
| 122 |
+
int num_blocks = num_m_blocks * size<0>(problem_size) * size<1>(problem_size);
|
| 123 |
+
|
| 124 |
+
return Params {
|
| 125 |
+
num_blocks,
|
| 126 |
+
{ num_m_blocks}, { size<0>(problem_size) }, { size<1>(problem_size) },
|
| 127 |
+
hw_info
|
| 128 |
+
};
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 132 |
+
dim3 grid(std::min(params.num_blocks, params.hw_info.sm_count), 1, 1);
|
| 133 |
+
return grid;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
CUTLASS_DEVICE
|
| 137 |
+
bool is_valid() {
|
| 138 |
+
return block_idx < params.num_blocks;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
CUTLASS_DEVICE
|
| 142 |
+
auto get_block_coord() {
|
| 143 |
+
using namespace cute;
|
| 144 |
+
int block_decode = block_idx;
|
| 145 |
+
int m_block, bidb, bidh;
|
| 146 |
+
params.divmod_m_block(block_decode, m_block, block_decode);
|
| 147 |
+
params.divmod_b(block_decode, bidb, block_decode);
|
| 148 |
+
params.divmod_h(block_decode, bidh, block_decode);
|
| 149 |
+
return make_coord(m_block, _0{}, make_coord(bidb, bidh));
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
CUTLASS_DEVICE
|
| 153 |
+
PersistentTileScheduler& operator++() {
|
| 154 |
+
block_idx += gridDim.x;
|
| 155 |
+
return *this;
|
| 156 |
+
}
|
| 157 |
+
};
|
| 158 |
+
|
| 159 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 160 |
+
|
| 161 |
+
template<typename Base>
|
| 162 |
+
struct TileSchedulerBwdAdapter {
|
| 163 |
+
|
| 164 |
+
using Params = typename Base::Params;
|
| 165 |
+
|
| 166 |
+
Base base_;
|
| 167 |
+
|
| 168 |
+
CUTLASS_DEVICE
|
| 169 |
+
TileSchedulerBwdAdapter(Params const& params) : base_(params) {}
|
| 170 |
+
|
| 171 |
+
template<class ProblemSize, class ClusterShape, class TileShape>
|
| 172 |
+
static Params to_underlying_arguments(
|
| 173 |
+
ProblemSize const& problem_size, KernelHardwareInfo hw_info,
|
| 174 |
+
ClusterShape const& cluster_shape, TileShape const& tile_shape)
|
| 175 |
+
{
|
| 176 |
+
using namespace cute;
|
| 177 |
+
return Base::to_underlying_arguments(select<0,1,3,2,4>(problem_size), hw_info, select<1,0,2>(cluster_shape), select<1,0,2>(tile_shape));
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
static dim3 get_grid_shape(Params const& params) {
|
| 181 |
+
return Base::get_grid_shape(params);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
CUTLASS_DEVICE
|
| 185 |
+
bool is_valid() {
|
| 186 |
+
return base_.is_valid();
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
CUTLASS_DEVICE
|
| 190 |
+
auto get_block_coord() {
|
| 191 |
+
using namespace cute;
|
| 192 |
+
return select<1,0,2>(base_.get_block_coord());
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
CUTLASS_DEVICE
|
| 196 |
+
TileSchedulerBwdAdapter& operator++() {
|
| 197 |
+
++base_;
|
| 198 |
+
return *this;
|
| 199 |
+
}
|
| 200 |
+
};
|
| 201 |
+
|
| 202 |
+
////////////////////////////////////////////////////////////////////////////////
|
| 203 |
+
|
| 204 |
+
} // namespace cutlass::fmha::kernel
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/reference/fmha_bwd_reference.hpp
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2025 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
#pragma once
|
| 34 |
+
|
| 35 |
+
#include "cute/tensor.hpp"
|
| 36 |
+
|
| 37 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 38 |
+
|
| 39 |
+
template<
|
| 40 |
+
class ProblemShape,
|
| 41 |
+
class TensorQ, class TensorK, class TensorV,
|
| 42 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 43 |
+
class TensorDQ, /* class TensorDK, class TensorDV, */
|
| 44 |
+
class Fusion
|
| 45 |
+
>
|
| 46 |
+
void __global__ fmha_bwd_reference_dQ_kernel(
|
| 47 |
+
ProblemShape problem_shape,
|
| 48 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 49 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 50 |
+
TensorDQ mDQ, /* TensorDK mDK, TensorDV mDV, */
|
| 51 |
+
Fusion fusion
|
| 52 |
+
) {
|
| 53 |
+
using namespace cute;
|
| 54 |
+
|
| 55 |
+
using Element = typename TensorO::value_type;
|
| 56 |
+
using ElementAccumulator = typename TensorLSE::value_type;
|
| 57 |
+
|
| 58 |
+
extern __shared__ char mS_mem[];
|
| 59 |
+
Element* mS = reinterpret_cast<Element*>(mS_mem);
|
| 60 |
+
|
| 61 |
+
Element softmax_scale = static_cast<Element>(1.0 / sqrt(1.0 * size<1>(mO)));
|
| 62 |
+
|
| 63 |
+
for (int idx_L = blockIdx.y; idx_L < size<2>(mDQ); idx_L += gridDim.y) {
|
| 64 |
+
for (int idx_Q = blockIdx.x; idx_Q < size<0>(mDQ); idx_Q += gridDim.x) {
|
| 65 |
+
|
| 66 |
+
for (int idx_K = threadIdx.x; idx_K < size<0>(mK); idx_K += blockDim.x) {
|
| 67 |
+
ElementAccumulator acc_qk = 0;
|
| 68 |
+
ElementAccumulator acc_dov = 0;
|
| 69 |
+
ElementAccumulator acc_doo = 0;
|
| 70 |
+
for (int idx_D0 = 0; idx_D0 < size<1>(mK); idx_D0++) {
|
| 71 |
+
acc_qk += mQ(idx_Q, idx_D0, idx_L) * mK(idx_K, idx_D0, idx_L);
|
| 72 |
+
acc_dov += mDO(idx_Q, idx_D0, idx_L) * mV(idx_K, idx_D0, idx_L);
|
| 73 |
+
acc_doo += mDO(idx_Q, idx_D0, idx_L) * mO(idx_Q, idx_D0, idx_L);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 77 |
+
auto frag = make_tensor<ElementAccumulator>(Shape<_1, _1>{});
|
| 78 |
+
frag(0) = acc_qk;
|
| 79 |
+
fusion.before_softmax(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 80 |
+
acc_qk = frag(0);
|
| 81 |
+
|
| 82 |
+
mS[idx_K] = static_cast<Element>(exp(softmax_scale * acc_qk - mLSE(idx_Q, idx_L)) * softmax_scale * (acc_dov - acc_doo));
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
__syncthreads();
|
| 86 |
+
|
| 87 |
+
for (int idx_D = threadIdx.x; idx_D < size<1>(mDQ); idx_D += blockDim.x) {
|
| 88 |
+
ElementAccumulator acc = 0;
|
| 89 |
+
for (int idx_K = 0; idx_K < size<0>(mK); idx_K++) {
|
| 90 |
+
acc += mS[idx_K] * mK(idx_K, idx_D, idx_L);
|
| 91 |
+
}
|
| 92 |
+
mDQ(idx_Q, idx_D, idx_L) = acc;
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 99 |
+
|
| 100 |
+
template<
|
| 101 |
+
class ProblemShape,
|
| 102 |
+
class TensorQ, class TensorK, class TensorV,
|
| 103 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 104 |
+
/* class TensorDQ, */ class TensorDK, /* class TensorDV, */
|
| 105 |
+
class Fusion
|
| 106 |
+
>
|
| 107 |
+
void __global__ fmha_bwd_reference_dK_kernel(
|
| 108 |
+
ProblemShape problem_shape,
|
| 109 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 110 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 111 |
+
/* TensorDQ mDQ, */ TensorDK mDK, /* TensorDV mDV, */
|
| 112 |
+
Fusion fusion
|
| 113 |
+
) {
|
| 114 |
+
using namespace cute;
|
| 115 |
+
|
| 116 |
+
using Element = typename TensorO::value_type;
|
| 117 |
+
using ElementAccumulator = typename TensorLSE::value_type;
|
| 118 |
+
|
| 119 |
+
extern __shared__ char mS_mem[];
|
| 120 |
+
Element* mS = reinterpret_cast<Element*>(mS_mem);
|
| 121 |
+
|
| 122 |
+
Element softmax_scale = static_cast<Element>(1.0 / sqrt(1.0 * size<1>(mO)));
|
| 123 |
+
|
| 124 |
+
for (int idx_L = blockIdx.y; idx_L < size<2>(mDK); idx_L += gridDim.y) {
|
| 125 |
+
for (int idx_K = blockIdx.x; idx_K < size<0>(mDK); idx_K += gridDim.x) {
|
| 126 |
+
|
| 127 |
+
for (int idx_Q = threadIdx.x; idx_Q < size<0>(mDO); idx_Q += blockDim.x) {
|
| 128 |
+
ElementAccumulator acc_qk = 0;
|
| 129 |
+
ElementAccumulator acc_dov = 0;
|
| 130 |
+
ElementAccumulator acc_doo = 0;
|
| 131 |
+
for (int idx_D0 = 0; idx_D0 < size<1>(mK); idx_D0++) {
|
| 132 |
+
acc_qk += mQ(idx_Q, idx_D0, idx_L) * mK(idx_K, idx_D0, idx_L);
|
| 133 |
+
acc_dov += mDO(idx_Q, idx_D0, idx_L) * mV(idx_K, idx_D0, idx_L);
|
| 134 |
+
acc_doo += mDO(idx_Q, idx_D0, idx_L) * mO(idx_Q, idx_D0, idx_L);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 138 |
+
auto frag = make_tensor<ElementAccumulator>(Shape<_1, _1>{});
|
| 139 |
+
frag(0) = acc_qk;
|
| 140 |
+
fusion.before_softmax(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 141 |
+
acc_qk = frag(0);
|
| 142 |
+
|
| 143 |
+
mS[idx_Q] = static_cast<Element>(exp(softmax_scale * acc_qk - mLSE(idx_Q, idx_L)) * softmax_scale * (acc_dov - acc_doo));
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
__syncthreads();
|
| 147 |
+
|
| 148 |
+
for (int idx_D = threadIdx.x; idx_D < size<1>(mDK); idx_D += blockDim.x) {
|
| 149 |
+
ElementAccumulator acc = 0;
|
| 150 |
+
for (int idx_Q = 0; idx_Q < size<0>(mDO); idx_Q++) {
|
| 151 |
+
acc += mS[idx_Q] * mQ(idx_Q, idx_D, idx_L);
|
| 152 |
+
}
|
| 153 |
+
mDK(idx_K, idx_D, idx_L) = acc;
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 160 |
+
|
| 161 |
+
template<
|
| 162 |
+
class ProblemShape,
|
| 163 |
+
class TensorQ, class TensorK, class TensorV,
|
| 164 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 165 |
+
/* class TensorDQ, class TensorDK, */ class TensorDV,
|
| 166 |
+
class Fusion
|
| 167 |
+
>
|
| 168 |
+
void __global__ fmha_bwd_reference_dV_kernel(
|
| 169 |
+
ProblemShape problem_shape,
|
| 170 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 171 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 172 |
+
/* TensorDQ mDQ, TensorDK mDK, */ TensorDV mDV,
|
| 173 |
+
Fusion fusion
|
| 174 |
+
) {
|
| 175 |
+
using namespace cute;
|
| 176 |
+
|
| 177 |
+
using Element = typename TensorO::value_type;
|
| 178 |
+
using ElementAccumulator = typename TensorLSE::value_type;
|
| 179 |
+
|
| 180 |
+
extern __shared__ char mS_mem[];
|
| 181 |
+
Element* mS = reinterpret_cast<Element*>(mS_mem);
|
| 182 |
+
|
| 183 |
+
Element softmax_scale = static_cast<Element>(1.0 / sqrt(1.0 * size<1>(mO)));
|
| 184 |
+
|
| 185 |
+
for (int idx_L = blockIdx.y; idx_L < size<2>(mDV); idx_L += gridDim.y) {
|
| 186 |
+
for (int idx_K = blockIdx.x; idx_K < size<0>(mDV); idx_K += gridDim.x) {
|
| 187 |
+
|
| 188 |
+
for (int idx_Q = threadIdx.x; idx_Q < size<0>(mDO); idx_Q += blockDim.x) {
|
| 189 |
+
ElementAccumulator acc_qk = 0;
|
| 190 |
+
for (int idx_D0 = 0; idx_D0 < size<1>(mK); idx_D0++) {
|
| 191 |
+
acc_qk += mQ(idx_Q, idx_D0, idx_L) * mK(idx_K, idx_D0, idx_L);
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 195 |
+
auto frag = make_tensor<ElementAccumulator>(Shape<_1, _1>{});
|
| 196 |
+
frag(0) = acc_qk;
|
| 197 |
+
fusion.before_softmax(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 198 |
+
acc_qk = frag(0);
|
| 199 |
+
|
| 200 |
+
mS[idx_Q] = static_cast<Element>(exp(softmax_scale * acc_qk - mLSE(idx_Q, idx_L)));
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
__syncthreads();
|
| 204 |
+
|
| 205 |
+
for (int idx_D = threadIdx.x; idx_D < size<1>(mDV); idx_D += blockDim.x) {
|
| 206 |
+
ElementAccumulator acc = 0;
|
| 207 |
+
for (int idx_Q = 0; idx_Q < size<0>(mDO); idx_Q++) {
|
| 208 |
+
acc += mS[idx_Q] * mDO(idx_Q, idx_D, idx_L);
|
| 209 |
+
}
|
| 210 |
+
mDV(idx_K, idx_D, idx_L) = acc;
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 217 |
+
|
| 218 |
+
template<
|
| 219 |
+
class ProblemShape,
|
| 220 |
+
class TensorQ, class TensorK, class TensorV,
|
| 221 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 222 |
+
/**/ class TensorDQ, /** / class TensorDK, / ** / class TensorDV, / **/
|
| 223 |
+
class Fusion
|
| 224 |
+
>
|
| 225 |
+
void fmha_bwd_reference_dQ(
|
| 226 |
+
ProblemShape problem_shape,
|
| 227 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 228 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 229 |
+
/**/ TensorDQ mDQ, /** / TensorDK mDK, / ** / TensorDV mDV, / **/
|
| 230 |
+
Fusion fusion
|
| 231 |
+
) {
|
| 232 |
+
using namespace cute;
|
| 233 |
+
|
| 234 |
+
dim3 grid(size<0>(mDQ), size<2>(mDQ), 1);
|
| 235 |
+
dim3 block(256);
|
| 236 |
+
int shared_mem = size<0>(mK) * sizeof(typename TensorO::value_type);
|
| 237 |
+
|
| 238 |
+
if (shared_mem >= (48 << 10)) {
|
| 239 |
+
CUTLASS_TRACE_HOST(" Setting smem size to " << shared_mem);
|
| 240 |
+
auto result = cudaFuncSetAttribute(
|
| 241 |
+
fmha_bwd_reference_dQ_kernel<ProblemShape, TensorQ, TensorK, TensorV, TensorO, TensorLSE, TensorDO, TensorDQ, Fusion>,
|
| 242 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 243 |
+
shared_mem);
|
| 244 |
+
if (cudaSuccess != result) {
|
| 245 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 246 |
+
CUTLASS_TRACE_HOST(
|
| 247 |
+
" cudaFuncSetAttribute() returned error: "
|
| 248 |
+
<< cudaGetErrorString(result));
|
| 249 |
+
return;
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
fmha_bwd_reference_dQ_kernel<<<grid, block, shared_mem>>>(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDQ, fusion);
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 257 |
+
|
| 258 |
+
template<
|
| 259 |
+
class ProblemShape,
|
| 260 |
+
class TensorQ, class TensorK, class TensorV,
|
| 261 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 262 |
+
/** / class TensorDQ, / **/ class TensorDK, /** / class TensorDV, / **/
|
| 263 |
+
class Fusion
|
| 264 |
+
>
|
| 265 |
+
void fmha_bwd_reference_dK(
|
| 266 |
+
ProblemShape problem_shape,
|
| 267 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 268 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 269 |
+
/** / TensorDQ mDQ, / **/ TensorDK mDK, /** / TensorDV mDV, / **/
|
| 270 |
+
Fusion fusion
|
| 271 |
+
) {
|
| 272 |
+
using namespace cute;
|
| 273 |
+
|
| 274 |
+
dim3 grid(size<0>(mDK), size<2>(mDK), 1);
|
| 275 |
+
dim3 block(256);
|
| 276 |
+
int shared_mem = size<0>(mDO) * sizeof(typename TensorO::value_type);
|
| 277 |
+
|
| 278 |
+
if (shared_mem >= (48 << 10)) {
|
| 279 |
+
CUTLASS_TRACE_HOST(" Setting smem size to " << shared_mem);
|
| 280 |
+
auto result = cudaFuncSetAttribute(
|
| 281 |
+
fmha_bwd_reference_dK_kernel<ProblemShape, TensorQ, TensorK, TensorV, TensorO, TensorLSE, TensorDO, TensorDK, Fusion>,
|
| 282 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 283 |
+
shared_mem);
|
| 284 |
+
if (cudaSuccess != result) {
|
| 285 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 286 |
+
CUTLASS_TRACE_HOST(
|
| 287 |
+
" cudaFuncSetAttribute() returned error: "
|
| 288 |
+
<< cudaGetErrorString(result));
|
| 289 |
+
return;
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
fmha_bwd_reference_dK_kernel<<<grid, block, shared_mem>>>(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDK, fusion);
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 297 |
+
|
| 298 |
+
template<
|
| 299 |
+
class ProblemShape,
|
| 300 |
+
class TensorQ, class TensorK, class TensorV,
|
| 301 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 302 |
+
/** / class TensorDQ, / ** / class TensorDK, / **/ class TensorDV, /**/
|
| 303 |
+
class Fusion
|
| 304 |
+
>
|
| 305 |
+
void fmha_bwd_reference_dV(
|
| 306 |
+
ProblemShape problem_shape,
|
| 307 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 308 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 309 |
+
/** / TensorDQ mDQ, / ** / TensorDK mDK, / **/ TensorDV mDV, /**/
|
| 310 |
+
Fusion fusion
|
| 311 |
+
) {
|
| 312 |
+
using namespace cute;
|
| 313 |
+
|
| 314 |
+
dim3 grid(size<0>(mDV), size<2>(mDV), 1);
|
| 315 |
+
dim3 block(256);
|
| 316 |
+
int shared_mem = size<0>(mDO) * sizeof(typename TensorO::value_type);
|
| 317 |
+
|
| 318 |
+
if (shared_mem >= (48 << 10)) {
|
| 319 |
+
CUTLASS_TRACE_HOST(" Setting smem size to " << shared_mem);
|
| 320 |
+
auto result = cudaFuncSetAttribute(
|
| 321 |
+
fmha_bwd_reference_dV_kernel<ProblemShape, TensorQ, TensorK, TensorV, TensorO, TensorLSE, TensorDO, TensorDV, Fusion>,
|
| 322 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 323 |
+
shared_mem);
|
| 324 |
+
if (cudaSuccess != result) {
|
| 325 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 326 |
+
CUTLASS_TRACE_HOST(
|
| 327 |
+
" cudaFuncSetAttribute() returned error: "
|
| 328 |
+
<< cudaGetErrorString(result));
|
| 329 |
+
return;
|
| 330 |
+
}
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
fmha_bwd_reference_dV_kernel<<<grid, block, shared_mem>>>(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDV, fusion);
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 337 |
+
|
| 338 |
+
template<
|
| 339 |
+
class ProblemShape,
|
| 340 |
+
class TensorQ, class TensorK, class TensorV,
|
| 341 |
+
class TensorO, class TensorLSE, class TensorDO,
|
| 342 |
+
class TensorDQ, class TensorDK, class TensorDV,
|
| 343 |
+
class Fusion
|
| 344 |
+
>
|
| 345 |
+
void fmha_bwd_reference(
|
| 346 |
+
ProblemShape problem_shape,
|
| 347 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 348 |
+
TensorO mO, TensorLSE mLSE, TensorDO mDO,
|
| 349 |
+
TensorDQ mDQ, TensorDK mDK, TensorDV mDV,
|
| 350 |
+
Fusion fusion
|
| 351 |
+
) {
|
| 352 |
+
fmha_bwd_reference_dQ(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDQ, fusion);
|
| 353 |
+
fmha_bwd_reference_dK(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDK, fusion);
|
| 354 |
+
fmha_bwd_reference_dV(problem_shape, mQ, mK, mV, mO, mLSE, mDO, mDV, fusion);
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/reference/fmha_reference.hpp
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
#pragma once
|
| 32 |
+
|
| 33 |
+
#include "cute/tensor.hpp"
|
| 34 |
+
|
| 35 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 36 |
+
|
| 37 |
+
template<
|
| 38 |
+
class ProblemShape,
|
| 39 |
+
class TensorQ,
|
| 40 |
+
class TensorK,
|
| 41 |
+
class TensorV,
|
| 42 |
+
class TensorO,
|
| 43 |
+
class TensorLSE,
|
| 44 |
+
class Fusion
|
| 45 |
+
>
|
| 46 |
+
void __global__ fmha_reference_kernel(
|
| 47 |
+
ProblemShape problem_shape,
|
| 48 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 49 |
+
TensorO mO, TensorLSE mLSE,
|
| 50 |
+
Fusion fusion
|
| 51 |
+
) {
|
| 52 |
+
using namespace cute;
|
| 53 |
+
|
| 54 |
+
using Element = typename TensorO::value_type;
|
| 55 |
+
using ElementAccumulator = typename TensorLSE::value_type;
|
| 56 |
+
|
| 57 |
+
extern __shared__ char mS_mem[];
|
| 58 |
+
Element* mS = reinterpret_cast<Element*>(mS_mem);
|
| 59 |
+
|
| 60 |
+
ElementAccumulator softmax_scale = static_cast<ElementAccumulator>(1.0 / sqrt(1.0 * size<1>(mO)));
|
| 61 |
+
|
| 62 |
+
auto id = make_identity_tensor(make_shape(1, 1));
|
| 63 |
+
for (int idx_L = blockIdx.y; idx_L < size<2>(mO); idx_L += gridDim.y) {
|
| 64 |
+
for (int idx_Q = blockIdx.x; idx_Q < size<0>(mO); idx_Q += gridDim.x) {
|
| 65 |
+
for (int idx_K = threadIdx.x; idx_K < size<0>(mK); idx_K += blockDim.x) {
|
| 66 |
+
ElementAccumulator acc = 0;
|
| 67 |
+
for (int idx_D = 0; idx_D < size<1>(mK); idx_D++) {
|
| 68 |
+
acc += mQ(idx_Q, idx_D, idx_L) * mK(idx_K, idx_D, idx_L);
|
| 69 |
+
}
|
| 70 |
+
auto frag = make_tensor<ElementAccumulator>(Shape<_1, _1>{});
|
| 71 |
+
frag(0) = acc;
|
| 72 |
+
fusion.before_softmax(frag, make_tensor(id.data() + make_arithmetic_tuple(idx_Q, idx_K), id.layout()), problem_shape);
|
| 73 |
+
mS[idx_K] = static_cast<Element>(frag(0) * softmax_scale);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
__syncthreads();
|
| 77 |
+
|
| 78 |
+
ElementAccumulator maxS = -std::numeric_limits<ElementAccumulator>::infinity();
|
| 79 |
+
for (int idx_K = 0; idx_K < size<0>(mK); idx_K++) {
|
| 80 |
+
maxS = std::max<ElementAccumulator>(maxS, mS[idx_K]);
|
| 81 |
+
}
|
| 82 |
+
if (maxS == -std::numeric_limits<ElementAccumulator>::infinity()) maxS = 0;
|
| 83 |
+
|
| 84 |
+
__syncthreads();
|
| 85 |
+
|
| 86 |
+
for (int idx_K = threadIdx.x; idx_K < size<0>(mK); idx_K += blockDim.x) {
|
| 87 |
+
mS[idx_K] = static_cast<Element>(exp(mS[idx_K] - maxS));
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
__syncthreads();
|
| 91 |
+
|
| 92 |
+
ElementAccumulator sum = 0;
|
| 93 |
+
for (int idx_K = 0; idx_K < size<0>(mK); idx_K++) {
|
| 94 |
+
sum += mS[idx_K];
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
Element scale = static_cast<Element>(1.0 / sum);
|
| 98 |
+
|
| 99 |
+
for (int idx_D = threadIdx.x; idx_D < size<1>(mO); idx_D += blockDim.x) {
|
| 100 |
+
ElementAccumulator acc = 0;
|
| 101 |
+
for (int idx_K = 0; idx_K < size<0>(mK); idx_K++) {
|
| 102 |
+
acc += mS[idx_K] * mV(idx_K, idx_D, idx_L) * scale;
|
| 103 |
+
}
|
| 104 |
+
mO(idx_Q, idx_D, idx_L) = static_cast<Element>(acc);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
if (threadIdx.x == 0) {
|
| 108 |
+
mLSE(idx_Q, idx_L) = log(sum) + maxS;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 116 |
+
|
| 117 |
+
template<
|
| 118 |
+
class ProblemShape,
|
| 119 |
+
class TensorQ,
|
| 120 |
+
class TensorK,
|
| 121 |
+
class TensorV,
|
| 122 |
+
class TensorO,
|
| 123 |
+
class TensorLSE,
|
| 124 |
+
class Fusion
|
| 125 |
+
>
|
| 126 |
+
void fmha_reference(
|
| 127 |
+
ProblemShape problem_shape,
|
| 128 |
+
TensorQ mQ, TensorK mK, TensorV mV,
|
| 129 |
+
TensorO mO, TensorLSE mLSE,
|
| 130 |
+
Fusion fusion
|
| 131 |
+
) {
|
| 132 |
+
using namespace cute;
|
| 133 |
+
|
| 134 |
+
dim3 grid(size<0>(mO), size<2>(mO), 1);
|
| 135 |
+
dim3 block(256);
|
| 136 |
+
int shared_mem = size<0>(mK) * sizeof(typename TensorO::value_type);
|
| 137 |
+
|
| 138 |
+
if (shared_mem >= (48 << 10)) {
|
| 139 |
+
CUTLASS_TRACE_HOST(" Setting smem size to " << shared_mem);
|
| 140 |
+
auto result = cudaFuncSetAttribute(
|
| 141 |
+
fmha_reference_kernel<ProblemShape, TensorQ, TensorK, TensorV, TensorO, TensorLSE, Fusion>,
|
| 142 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 143 |
+
shared_mem);
|
| 144 |
+
if (cudaSuccess != result) {
|
| 145 |
+
result = cudaGetLastError(); // to clear the error bit
|
| 146 |
+
CUTLASS_TRACE_HOST(
|
| 147 |
+
" cudaFuncSetAttribute() returned error: "
|
| 148 |
+
<< cudaGetErrorString(result));
|
| 149 |
+
return;
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
fmha_reference_kernel<<<grid, block, shared_mem>>>(problem_shape, mQ, mK, mV, mO, mLSE, fusion);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/88_hopper_fmha/reference/reference_abs_error.hpp
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
#pragma once
|
| 35 |
+
|
| 36 |
+
#include <cmath>
|
| 37 |
+
#include "cutlass/util/device_memory.h"
|
| 38 |
+
|
| 39 |
+
template<typename Element>
|
| 40 |
+
__global__ void reference_abs_diff_kernel(
|
| 41 |
+
Element* data, Element* data_ref, size_t count,
|
| 42 |
+
double* max_diff, double* sum_diff,
|
| 43 |
+
bool print_diff
|
| 44 |
+
) {
|
| 45 |
+
double thread_max_diff = 0;
|
| 46 |
+
double thread_sum_diff = 0;
|
| 47 |
+
|
| 48 |
+
__shared__ double block_max_diff;
|
| 49 |
+
__shared__ double block_sum_diff;
|
| 50 |
+
|
| 51 |
+
for (size_t i = threadIdx.x + blockIdx.x * blockDim.x; i < count; i += blockDim.x * gridDim.x) {
|
| 52 |
+
double diff = fabs(data[i] - data_ref[i]);
|
| 53 |
+
if (print_diff) if (diff != diff || diff > 0.01f) printf("difference at %lld: %f ... %f vs %f\n", static_cast<long long int>(i), diff, (double)data[i], (double)data_ref[i]);
|
| 54 |
+
thread_max_diff = fmax(diff, thread_max_diff);
|
| 55 |
+
thread_sum_diff += diff;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
for (int i = 0; i < blockDim.x; i++) {
|
| 59 |
+
if (i == threadIdx.x) {
|
| 60 |
+
if (i == 0) {
|
| 61 |
+
block_max_diff = thread_max_diff;
|
| 62 |
+
block_sum_diff = thread_sum_diff;
|
| 63 |
+
} else {
|
| 64 |
+
block_max_diff = fmax(block_max_diff, thread_max_diff);
|
| 65 |
+
block_sum_diff += thread_sum_diff;
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
__syncthreads();
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
if (threadIdx.x == 0) {
|
| 72 |
+
atomicAdd(sum_diff, block_sum_diff);
|
| 73 |
+
|
| 74 |
+
for (;;) {
|
| 75 |
+
unsigned long long prev = *reinterpret_cast<unsigned long long*>(max_diff);
|
| 76 |
+
double prev_diff = reinterpret_cast<double const&>(prev);
|
| 77 |
+
double new_max_diff = fmax(block_max_diff, prev_diff);
|
| 78 |
+
unsigned long long found = atomicCAS(reinterpret_cast<unsigned long long*>(max_diff), prev, reinterpret_cast<unsigned long long const&>(new_max_diff));
|
| 79 |
+
if (found == prev) break;
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
template<typename Element>
|
| 85 |
+
void reference_abs_diff(
|
| 86 |
+
cutlass::DeviceAllocation<Element> const& data,
|
| 87 |
+
cutlass::DeviceAllocation<Element> const& data_ref,
|
| 88 |
+
double& max_diff, double& mean_diff
|
| 89 |
+
) {
|
| 90 |
+
static bool kPrintDiff = getenv("REF_PRINT_DIFF") && atoi(getenv("REF_PRINT_DIFF")) == 1;
|
| 91 |
+
|
| 92 |
+
cutlass::DeviceAllocation<double> result;
|
| 93 |
+
result.reset(2);
|
| 94 |
+
assert(data.size() == data_ref.size());
|
| 95 |
+
|
| 96 |
+
cudaError_t err = cudaMemset(result.get(), 0, result.size() * sizeof(double));
|
| 97 |
+
if (err != cudaSuccess) {
|
| 98 |
+
std::cerr << "Memset failed. Last CUDA error: "
|
| 99 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 100 |
+
max_diff = mean_diff = 1e20;
|
| 101 |
+
return;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
dim3 block(256, 1, 1);
|
| 105 |
+
dim3 grid(1024, 1, 1);
|
| 106 |
+
reference_abs_diff_kernel<<<block, grid>>>(
|
| 107 |
+
data.get(), data_ref.get(), data.size(),
|
| 108 |
+
result.get(), result.get() + 1, kPrintDiff);
|
| 109 |
+
|
| 110 |
+
err = cudaDeviceSynchronize();
|
| 111 |
+
if (err != cudaSuccess) {
|
| 112 |
+
std::cerr << "Difference kernel failed. Last CUDA error: "
|
| 113 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 114 |
+
max_diff = mean_diff = 1e20;
|
| 115 |
+
return;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
double result_host[2];
|
| 119 |
+
err = cudaMemcpy(result_host, result.get(), result.size() * sizeof(double), cudaMemcpyDefault);
|
| 120 |
+
if (err != cudaSuccess) {
|
| 121 |
+
std::cerr << "Copy failed. Last CUDA error: "
|
| 122 |
+
<< cudaGetErrorString(err) << std::endl;
|
| 123 |
+
max_diff = mean_diff = 1e20;
|
| 124 |
+
return;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
max_diff = result_host[0];
|
| 128 |
+
mean_diff = result_host[1] / static_cast<double>(data.size());
|
| 129 |
+
}
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/common/dist_gemm_helpers.h
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
/*! \file
|
| 33 |
+
\brief Benchmark helpers for Distributed GEMM
|
| 34 |
+
|
| 35 |
+
A delay kernel to gate all GEMMs across devices, controlled by a flag that
|
| 36 |
+
the host will set off once it launches DistGEMM across all devices.
|
| 37 |
+
|
| 38 |
+
DistGpuTimer extends cutlass's existing cudaEvent-based timer to multiple devices.
|
| 39 |
+
*/
|
| 40 |
+
|
| 41 |
+
#pragma once
|
| 42 |
+
#include "cutlass/cutlass.h"
|
| 43 |
+
#include <iostream>
|
| 44 |
+
#include <cuda/atomic>
|
| 45 |
+
#include CUDA_STD_HEADER(atomic)
|
| 46 |
+
|
| 47 |
+
#include "cute/layout.hpp"
|
| 48 |
+
#include "cute/tensor.hpp"
|
| 49 |
+
#include "cutlass/cuda_host_adapter.hpp"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
namespace cutlass {
|
| 53 |
+
|
| 54 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 55 |
+
/// Delay kernel
|
| 56 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 57 |
+
|
| 58 |
+
using AtomicBoolean = cuda::atomic<bool>;
|
| 59 |
+
|
| 60 |
+
__global__ void delay_kernel(const AtomicBoolean* atomic_flag_ptr) {
|
| 61 |
+
while (not atomic_flag_ptr->load()) {
|
| 62 |
+
__nanosleep(40);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 68 |
+
/// Distributed GPU Timer
|
| 69 |
+
/// Sets up cuda events for multiple processors.
|
| 70 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 71 |
+
template <int NP>
|
| 72 |
+
struct DistGpuTimer {
|
| 73 |
+
int _primary_device;
|
| 74 |
+
cudaEvent_t _start[NP];
|
| 75 |
+
cudaEvent_t _stop[NP];
|
| 76 |
+
|
| 77 |
+
/// Constructor
|
| 78 |
+
DistGpuTimer()
|
| 79 |
+
{
|
| 80 |
+
CUDA_CHECK(cudaGetDevice(&_primary_device));
|
| 81 |
+
for (int device = 0; device < NP; ++device) {
|
| 82 |
+
CUDA_CHECK(cudaSetDevice(device));
|
| 83 |
+
CUDA_CHECK(cudaEventCreate(&_start[device]));
|
| 84 |
+
CUDA_CHECK(cudaEventCreate(&_stop[device]));
|
| 85 |
+
}
|
| 86 |
+
CUDA_CHECK(cudaSetDevice(_primary_device));
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/// Destructor
|
| 90 |
+
~DistGpuTimer()
|
| 91 |
+
{
|
| 92 |
+
for (int device = 0; device < NP; ++device) {
|
| 93 |
+
CUDA_CHECK(cudaSetDevice(device));
|
| 94 |
+
CUDA_CHECK(cudaEventDestroy(_start[device]));
|
| 95 |
+
CUDA_CHECK(cudaEventDestroy(_stop[device]));
|
| 96 |
+
}
|
| 97 |
+
CUDA_CHECK(cudaSetDevice(_primary_device));
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/// Start the timer for a given stream (defaults to the default stream)
|
| 101 |
+
void start(int device, cudaStream_t stream) {
|
| 102 |
+
assert(device >= 0 && device < NP);
|
| 103 |
+
CUDA_CHECK(cudaEventRecord(_start[device], stream));
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
/// Stop the timer
|
| 107 |
+
void stop(int device, cudaStream_t stream) {
|
| 108 |
+
assert(device >= 0 && device < NP);
|
| 109 |
+
CUDA_CHECK(cudaEventRecord(_stop[device], stream));
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
/// Return the elapsed time (in milliseconds)
|
| 113 |
+
float elapsed_millis(int device) {
|
| 114 |
+
assert(device >= 0 && device < NP);
|
| 115 |
+
float elapsed = 0.0;
|
| 116 |
+
CUDA_CHECK(cudaEventSynchronize(_stop[device]));
|
| 117 |
+
CUDA_CHECK(cudaEventElapsedTime(&elapsed, _start[device], _stop[device]));
|
| 118 |
+
return elapsed;
|
| 119 |
+
}
|
| 120 |
+
};
|
| 121 |
+
|
| 122 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 123 |
+
/// Generic device-to-device data movement kernel based for CuTe tensors.
|
| 124 |
+
///
|
| 125 |
+
/// NOTE: this kernel assigns one element copy to every thread, and is by no means
|
| 126 |
+
/// an efficient way of copying tensors. It should only be used for convenience in
|
| 127 |
+
/// reference checks.
|
| 128 |
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
| 129 |
+
|
| 130 |
+
template <typename TensorSource, typename TensorDestination>
|
| 131 |
+
void device_copy(TensorSource tensor_source,
|
| 132 |
+
TensorDestination tensor_destination,
|
| 133 |
+
cudaStream_t stream);
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
template <typename TensorSource, typename TensorDestination>
|
| 137 |
+
__global__ void device_copy_kernel(TensorSource const tensor_source,
|
| 138 |
+
TensorDestination tensor_destination) {
|
| 139 |
+
auto linear_idx = blockIdx.x * blockDim.x + threadIdx.x;
|
| 140 |
+
using ElementSrc = typename TensorSource::value_type;
|
| 141 |
+
using ElementDst = typename TensorDestination::value_type;
|
| 142 |
+
NumericConverter<ElementDst, ElementSrc> converter;
|
| 143 |
+
if (linear_idx < size(tensor_source)) {
|
| 144 |
+
tensor_destination(linear_idx) = converter(tensor_source(linear_idx));
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
template <typename TensorSource, typename TensorDestination>
|
| 149 |
+
void device_copy(TensorSource tensor_source,
|
| 150 |
+
TensorDestination tensor_destination,
|
| 151 |
+
cudaStream_t stream) {
|
| 152 |
+
|
| 153 |
+
assert(tensor_source.size() == tensor_destination.size());
|
| 154 |
+
|
| 155 |
+
auto numel = tensor_source.size();
|
| 156 |
+
static constexpr int NumThreads = 128;
|
| 157 |
+
auto grid_size = cute::ceil_div(numel, NumThreads);
|
| 158 |
+
|
| 159 |
+
dim3 grid(grid_size);
|
| 160 |
+
dim3 block(NumThreads);
|
| 161 |
+
device_copy_kernel<<<grid, block, 0, stream>>>(tensor_source, tensor_destination);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
} //namespace cutlass
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/common/gather_tensor.hpp
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2023 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
#pragma once
|
| 32 |
+
|
| 33 |
+
#include "cute/layout.hpp"
|
| 34 |
+
#include "cute/tensor.hpp"
|
| 35 |
+
#include "cute/util/print.hpp"
|
| 36 |
+
|
| 37 |
+
namespace example {
|
| 38 |
+
|
| 39 |
+
using namespace cute;
|
| 40 |
+
|
| 41 |
+
// Empty type used to disable gather/scatter for a GEMM argument
|
| 42 |
+
struct NoGather
|
| 43 |
+
{
|
| 44 |
+
template<class... Ts>
|
| 45 |
+
NoGather(Ts...) {};
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
/// Function object that applies an index to its argument
|
| 49 |
+
template <class Index>
|
| 50 |
+
struct IndexedGather
|
| 51 |
+
{
|
| 52 |
+
CUTE_HOST_DEVICE constexpr
|
| 53 |
+
IndexedGather(Index const *indices = {}): indices_(indices) {}
|
| 54 |
+
|
| 55 |
+
template <typename I>
|
| 56 |
+
CUTE_HOST_DEVICE constexpr
|
| 57 |
+
Index
|
| 58 |
+
operator()(I i) const { return indices_[i]; }
|
| 59 |
+
|
| 60 |
+
CUTE_HOST_DEVICE friend
|
| 61 |
+
void
|
| 62 |
+
print(IndexedGather const &s) {
|
| 63 |
+
cute::print("Indexed");
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
Index const *indices_;
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
/// Function object that applies a stride to its argument
|
| 70 |
+
/// Example: StridedFunc<int,_2> gathers every other row/column
|
| 71 |
+
template <class Stride>
|
| 72 |
+
struct StridedGather
|
| 73 |
+
{
|
| 74 |
+
CUTE_HOST_DEVICE constexpr
|
| 75 |
+
StridedGather(Stride stride = {}): stride_(stride) {}
|
| 76 |
+
|
| 77 |
+
template <class I>
|
| 78 |
+
CUTE_HOST_DEVICE constexpr
|
| 79 |
+
auto
|
| 80 |
+
operator()(I i) const { return i * stride_; }
|
| 81 |
+
|
| 82 |
+
CUTE_HOST_DEVICE friend
|
| 83 |
+
void
|
| 84 |
+
print(StridedGather const &s) {
|
| 85 |
+
cute::print("Strided{");
|
| 86 |
+
print(s.stride_);
|
| 87 |
+
cute::print("}");
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
Stride stride_;
|
| 91 |
+
};
|
| 92 |
+
|
| 93 |
+
/// Custom stride object that applies a function followed by a stride
|
| 94 |
+
template <class Func, class Stride>
|
| 95 |
+
struct CustomStride
|
| 96 |
+
{
|
| 97 |
+
CUTE_HOST_DEVICE constexpr
|
| 98 |
+
CustomStride(Func const &func, Stride const &stride): func_(func), stride_(stride) {}
|
| 99 |
+
|
| 100 |
+
template <class I>
|
| 101 |
+
CUTE_HOST_DEVICE constexpr friend
|
| 102 |
+
auto
|
| 103 |
+
operator*(I i, CustomStride const &s) { return s.func_(i) * s.stride_; }
|
| 104 |
+
|
| 105 |
+
template <class I>
|
| 106 |
+
CUTE_HOST_DEVICE constexpr friend
|
| 107 |
+
auto
|
| 108 |
+
operator*(CustomStride const &s, I i) { return s.func_(i) * s.stride_; }
|
| 109 |
+
|
| 110 |
+
CUTE_HOST_DEVICE friend
|
| 111 |
+
void
|
| 112 |
+
print(CustomStride const & s) {
|
| 113 |
+
cute::print("Custom{");
|
| 114 |
+
print(s.func_);
|
| 115 |
+
cute::print(",");
|
| 116 |
+
print(s.stride_);
|
| 117 |
+
cute::print("}");
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
template<class Div>
|
| 121 |
+
CUTE_HOST_DEVICE constexpr friend
|
| 122 |
+
auto
|
| 123 |
+
safe_div(CustomStride const &s, Div const &div)
|
| 124 |
+
{
|
| 125 |
+
return CustomStride<Func, decltype(safe_div(s.stride_, div))>(s.func_, safe_div(s.stride_, div));
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
// Circumvent the requirement on make_layout that shape and stride are integral
|
| 129 |
+
template <class Shape>
|
| 130 |
+
CUTE_HOST_DEVICE constexpr friend
|
| 131 |
+
auto
|
| 132 |
+
make_layout(Shape const &shape, CustomStride const &stride)
|
| 133 |
+
{
|
| 134 |
+
return Layout<Shape, CustomStride>(shape, stride);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
Func func_;
|
| 138 |
+
Stride stride_;
|
| 139 |
+
};
|
| 140 |
+
|
| 141 |
+
template<class Stride, class Func>
|
| 142 |
+
CUTLASS_HOST_DEVICE
|
| 143 |
+
auto
|
| 144 |
+
make_custom_stride_layout(Stride const &stride, Func&& func)
|
| 145 |
+
{
|
| 146 |
+
// Use a dummy shape and replace the first non-unit stride with a custom gather stride
|
| 147 |
+
auto idx = find_if(stride, [](auto x){ return not is_constant<1, decltype(x)>{}; });
|
| 148 |
+
constexpr int I = decltype(idx)::value;
|
| 149 |
+
return make_layout(repeat_like(stride, _1{}),
|
| 150 |
+
replace<I>(stride, CustomStride{static_cast<Func&&>(func), get<I>(stride)}));
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/// Helper function to optionally create a gather tensor
|
| 154 |
+
template<class Iterator, class Shape, class Stride, class Func>
|
| 155 |
+
CUTLASS_HOST_DEVICE
|
| 156 |
+
auto
|
| 157 |
+
make_gather_tensor(Iterator iter, Shape const &shape, Stride const &stride, Func &&func)
|
| 158 |
+
{
|
| 159 |
+
if constexpr (not cutlass::platform::is_same<remove_cvref_t<Func>, NoGather>::value) {
|
| 160 |
+
Layout matrix_layout = make_identity_layout(shape);
|
| 161 |
+
auto offset = as_arithmetic_tuple(repeat_like(shape, _0{}));
|
| 162 |
+
Layout gather_layout = make_custom_stride_layout(stride, static_cast<Func&&>(func));
|
| 163 |
+
return make_tensor(iter, ComposedLayout{gather_layout, offset, matrix_layout});
|
| 164 |
+
} else {
|
| 165 |
+
return make_tensor(iter, shape, stride);
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
} // namespace example
|
| 170 |
+
|
| 171 |
+
namespace cute
|
| 172 |
+
{
|
| 173 |
+
|
| 174 |
+
template<int N, int I, class Shape, class Stride>
|
| 175 |
+
CUTE_HOST_DEVICE constexpr
|
| 176 |
+
auto
|
| 177 |
+
upcast(Shape const& shape, Stride const& stride)
|
| 178 |
+
{
|
| 179 |
+
if constexpr (is_tuple<Shape>::value) {
|
| 180 |
+
return transform_layout(shape, stride, [](auto const& s, auto const& d) { return upcast<N,I>(s,d); });
|
| 181 |
+
} else if constexpr (is_scaled_basis<Stride>::value) {
|
| 182 |
+
if constexpr (Stride::mode() == I) {
|
| 183 |
+
return make_layout(ceil_div(shape, Int<N>{}), ceil_div(stride, Int<N>{}));
|
| 184 |
+
} else {
|
| 185 |
+
return make_layout(shape, stride);
|
| 186 |
+
}
|
| 187 |
+
} else {
|
| 188 |
+
return upcast<N>(shape, stride);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
CUTE_GCC_UNREACHABLE;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
template <int N, class OuterShape, class OuterStride, class Offset, class Shape, class Stride>
|
| 195 |
+
CUTE_HOST_DEVICE constexpr
|
| 196 |
+
auto
|
| 197 |
+
upcast(ComposedLayout<Layout<OuterShape,OuterStride>,Offset,Layout<Shape,Stride>> const& layout)
|
| 198 |
+
{
|
| 199 |
+
// Find index of the stride-1 mode - that is the only one that requires updating inner shape and offset
|
| 200 |
+
auto idx = find_if(layout.layout_a().stride(), [](auto x){ return is_constant<1, decltype(x)>{}; });
|
| 201 |
+
constexpr int I = decltype(idx)::value;
|
| 202 |
+
|
| 203 |
+
// Upcast the outer layout (works as expected)
|
| 204 |
+
auto outer = upcast<N>(layout.layout_a());
|
| 205 |
+
|
| 206 |
+
// Upcast the accumulated offset along stride-1 mode
|
| 207 |
+
auto offset = as_arithmetic_tuple(replace<I>(layout.offset(), upcast<N>(get<I>(layout.offset()))));
|
| 208 |
+
|
| 209 |
+
// Upcast the inner layout's shape along stride-1 mode
|
| 210 |
+
auto inner = upcast<N,I>(layout.layout_b().shape(), layout.layout_b().stride());
|
| 211 |
+
|
| 212 |
+
return composition(outer, offset, inner);
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
} // namespace example
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/common/helper.h
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2017 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
#pragma once
|
| 32 |
+
|
| 33 |
+
#include "cuda_runtime.h"
|
| 34 |
+
#include <iostream>
|
| 35 |
+
|
| 36 |
+
/**
|
| 37 |
+
* Panic wrapper for unwinding CUTLASS errors
|
| 38 |
+
*/
|
| 39 |
+
#define CUTLASS_CHECK(status) \
|
| 40 |
+
{ \
|
| 41 |
+
cutlass::Status error = status; \
|
| 42 |
+
if (error != cutlass::Status::kSuccess) { \
|
| 43 |
+
std::cerr << "Got cutlass error: " << cutlassGetStatusString(error) << " at: " << __LINE__ \
|
| 44 |
+
<< std::endl; \
|
| 45 |
+
exit(EXIT_FAILURE); \
|
| 46 |
+
} \
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
/**
|
| 51 |
+
* Panic wrapper for unwinding CUDA runtime errors
|
| 52 |
+
*/
|
| 53 |
+
#define CUDA_CHECK(status) \
|
| 54 |
+
{ \
|
| 55 |
+
cudaError_t error = status; \
|
| 56 |
+
if (error != cudaSuccess) { \
|
| 57 |
+
std::cerr << "Got bad cuda status: " << cudaGetErrorString(error) \
|
| 58 |
+
<< " at line: " << __LINE__ << std::endl; \
|
| 59 |
+
exit(EXIT_FAILURE); \
|
| 60 |
+
} \
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
/**
|
| 65 |
+
* GPU timer for recording the elapsed time across kernel(s) launched in GPU stream
|
| 66 |
+
*/
|
| 67 |
+
struct GpuTimer
|
| 68 |
+
{
|
| 69 |
+
cudaStream_t _stream_id;
|
| 70 |
+
cudaEvent_t _start;
|
| 71 |
+
cudaEvent_t _stop;
|
| 72 |
+
|
| 73 |
+
/// Constructor
|
| 74 |
+
GpuTimer() : _stream_id(0)
|
| 75 |
+
{
|
| 76 |
+
CUDA_CHECK(cudaEventCreate(&_start));
|
| 77 |
+
CUDA_CHECK(cudaEventCreate(&_stop));
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/// Destructor
|
| 81 |
+
~GpuTimer()
|
| 82 |
+
{
|
| 83 |
+
CUDA_CHECK(cudaEventDestroy(_start));
|
| 84 |
+
CUDA_CHECK(cudaEventDestroy(_stop));
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/// Start the timer for a given stream (defaults to the default stream)
|
| 88 |
+
void start(cudaStream_t stream_id = 0)
|
| 89 |
+
{
|
| 90 |
+
_stream_id = stream_id;
|
| 91 |
+
CUDA_CHECK(cudaEventRecord(_start, _stream_id));
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
/// Stop the timer
|
| 95 |
+
void stop()
|
| 96 |
+
{
|
| 97 |
+
CUDA_CHECK(cudaEventRecord(_stop, _stream_id));
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/// Return the elapsed time (in milliseconds)
|
| 101 |
+
float elapsed_millis()
|
| 102 |
+
{
|
| 103 |
+
float elapsed = 0.0;
|
| 104 |
+
CUDA_CHECK(cudaEventSynchronize(_stop));
|
| 105 |
+
CUDA_CHECK(cudaEventElapsedTime(&elapsed, _start, _stop));
|
| 106 |
+
return elapsed;
|
| 107 |
+
}
|
| 108 |
+
};
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/cute/tutorial/blackwell/example_utils.hpp
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/***************************************************************************************************
|
| 2 |
+
* Copyright (c) 2024 - 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
* SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
*
|
| 5 |
+
* Redistribution and use in source and binary forms, with or without
|
| 6 |
+
* modification, are permitted provided that the following conditions are met:
|
| 7 |
+
*
|
| 8 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
* list of conditions and the following disclaimer.
|
| 10 |
+
*
|
| 11 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
* and/or other materials provided with the distribution.
|
| 14 |
+
*
|
| 15 |
+
* 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
* contributors may be used to endorse or promote products derived from
|
| 17 |
+
* this software without specific prior written permission.
|
| 18 |
+
*
|
| 19 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
*
|
| 30 |
+
**************************************************************************************************/
|
| 31 |
+
|
| 32 |
+
#pragma once
|
| 33 |
+
#include <cute/tensor.hpp> // CuTe tensor implementation
|
| 34 |
+
#include <cute/arch/copy_sm90_desc.hpp>
|
| 35 |
+
|
| 36 |
+
template <class AccType,
|
| 37 |
+
class TensorA, class TensorB,
|
| 38 |
+
class TensorC, class TensorD,
|
| 39 |
+
class Alpha, class Beta>
|
| 40 |
+
void
|
| 41 |
+
reference_gemm(TensorA const& tensor_A, TensorB const& tensor_B,
|
| 42 |
+
TensorC const& tensor_C, TensorD & tensor_D,
|
| 43 |
+
Alpha alpha, Beta beta)
|
| 44 |
+
{
|
| 45 |
+
using namespace cute;
|
| 46 |
+
for (int m = 0; m < size<0>(tensor_D); ++m) {
|
| 47 |
+
for (int n = 0; n < size<1>(tensor_D); ++n) {
|
| 48 |
+
AccType c = AccType(0.f);
|
| 49 |
+
for (int k = 0; k < size<1>(tensor_A); ++k) {
|
| 50 |
+
c += tensor_A(m,k) * tensor_B(n,k);
|
| 51 |
+
}
|
| 52 |
+
tensor_D(m,n) = alpha * c + beta * tensor_C(m,n);
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
template <class TensorA, class TensorB,
|
| 58 |
+
class TensorC, class TensorD,
|
| 59 |
+
class RefTensorD>
|
| 60 |
+
bool
|
| 61 |
+
compare_results(TensorA const& tensor_A, TensorB const& tensor_B,
|
| 62 |
+
TensorC const& tensor_C, TensorD const& tensor_D,
|
| 63 |
+
RefTensorD const& ref_tensor_D,
|
| 64 |
+
bool print_diff = false)
|
| 65 |
+
{
|
| 66 |
+
using namespace cute;
|
| 67 |
+
auto norm_A = matrix_inf_norm(tensor_A);
|
| 68 |
+
auto norm_B = matrix_inf_norm(tensor_B);
|
| 69 |
+
auto norm_C = matrix_inf_norm(tensor_C);
|
| 70 |
+
auto norm_D = matrix_inf_norm(tensor_D);
|
| 71 |
+
auto norm_ref_D = matrix_inf_norm(ref_tensor_D);
|
| 72 |
+
auto norm_diff = matrix_diff_inf_norm(tensor_D, ref_tensor_D);
|
| 73 |
+
|
| 74 |
+
if (print_diff) {
|
| 75 |
+
for (int m = 0; m < size<0>(tensor_D); ++m) {
|
| 76 |
+
for (int n = 0; n < size<1>(tensor_D); ++n) {
|
| 77 |
+
std::cout << m << "," << n << " : " << tensor_D(m,n) << " vs. " << ref_tensor_D(m,n) << std::endl;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
std::cout << "norm (A) : " << norm_A.inf_norm << std::endl;
|
| 83 |
+
std::cout << "norm (B) : " << norm_B.inf_norm << std::endl;
|
| 84 |
+
std::cout << "norm (C) : " << norm_C.inf_norm << std::endl;
|
| 85 |
+
std::cout << "norm (D) : " << norm_D.inf_norm << std::endl;
|
| 86 |
+
std::cout << "norm (ref_D) : " << norm_ref_D.inf_norm << std::endl;
|
| 87 |
+
std::cout << "norm (D-ref_D) : " << norm_diff.inf_norm << std::endl;
|
| 88 |
+
|
| 89 |
+
return (!norm_A.found_nan) && (!norm_B.found_nan) &&
|
| 90 |
+
(!norm_C.found_nan) && (!norm_D.found_nan) && (!norm_ref_D.found_nan) && // There are no NaNs
|
| 91 |
+
(norm_A.inf_norm > 0.0) && (norm_B.inf_norm > 0.0) &&
|
| 92 |
+
(norm_C.inf_norm > 0.0) && (norm_D.inf_norm > 0.0) && (norm_ref_D.inf_norm > 0.0) && // Values in tensors aren't zeros
|
| 93 |
+
(norm_diff.inf_norm <= 0.0); // Diff (ref_D-D) == 0
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
template <class Tensor>
|
| 97 |
+
void
|
| 98 |
+
initialize_tensor(Tensor& tensor, cute::tuple<int, int> value_range = {-2, 2})
|
| 99 |
+
{
|
| 100 |
+
using DataType = typename Tensor::element_type;
|
| 101 |
+
auto [min, max] = value_range;
|
| 102 |
+
for (int i = 0; i < cute::size(tensor); i++) {
|
| 103 |
+
tensor(i) = DataType(int((max-min)*(rand() / double(RAND_MAX)) + min));
|
| 104 |
+
}
|
| 105 |
+
}
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/call_bypass_dlpack.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import sys
|
| 30 |
+
import os
|
| 31 |
+
from typing import Tuple
|
| 32 |
+
import torch
|
| 33 |
+
|
| 34 |
+
import cutlass
|
| 35 |
+
import cutlass.cute as cute
|
| 36 |
+
from cutlass.cute.runtime import make_ptr
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
"""
|
| 40 |
+
An Example demonstrating how to call off-the-shelf kernel by-passing dlpack protocol
|
| 41 |
+
|
| 42 |
+
The example shows how to directly pass pointers from PyTorch tensors to off-the-shelf kernels
|
| 43 |
+
written by CuTe DSL with a thin customized wrapper jit function. The jit function will be
|
| 44 |
+
compiled with inline without introducing overhead.
|
| 45 |
+
|
| 46 |
+
To run this example:
|
| 47 |
+
|
| 48 |
+
.. code-block:: bash
|
| 49 |
+
|
| 50 |
+
python examples/ampere/call_bypass_dlpack.py
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
It's worth to mention that by-passing dlpack protocol can resolve the issue that dlpack doesn't handle shape-1
|
| 54 |
+
mode correctly. For example, the following code will fail, because dlpack will convert the shape-1 mode
|
| 55 |
+
with stride-1 which propagate alignment incorrectly.
|
| 56 |
+
|
| 57 |
+
.. code-block:: python
|
| 58 |
+
|
| 59 |
+
@cute.kernel
|
| 60 |
+
def fails_kernel(gX: cute.Tensor):
|
| 61 |
+
bidx, _, _ = cute.arch.block_idx()
|
| 62 |
+
mX = gX[None, bidx, None] # We wish to retain alignment
|
| 63 |
+
# assert mX.iterator.alignment == 16
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@cute.jit
|
| 67 |
+
def fails(gX_: cute.Tensor):
|
| 68 |
+
gX = gX_
|
| 69 |
+
fails_kernel(gX).launch(grid=(1, 1, 1), block=(128, 1, 1))
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
gX_torch = torch.rand((128, 1, 128), device="cuda", dtype=torch.bfloat16)
|
| 73 |
+
fails(from_dlpack(gX_torch, assumed_align=16))
|
| 74 |
+
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
# Add the current directory to sys.path
|
| 78 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 79 |
+
from tensorop_gemm import TensorOpGemm
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@cute.jit
|
| 83 |
+
def tensor_op_gemm_wrapper(
|
| 84 |
+
a_ptr: cute.Pointer,
|
| 85 |
+
b_ptr: cute.Pointer,
|
| 86 |
+
c_ptr: cute.Pointer,
|
| 87 |
+
m: cutlass.Int32,
|
| 88 |
+
n: cutlass.Int32,
|
| 89 |
+
k: cutlass.Int32,
|
| 90 |
+
l: cutlass.Int32,
|
| 91 |
+
):
|
| 92 |
+
print(f"\n[DSL INFO] Input Parameters:")
|
| 93 |
+
print(f"[DSL INFO] mnkl: {(m, n, k, l)}")
|
| 94 |
+
|
| 95 |
+
# Assume alignment of shape to call tensorop_gemm example
|
| 96 |
+
m = cute.assume(m, divby=8)
|
| 97 |
+
n = cute.assume(n, divby=8)
|
| 98 |
+
|
| 99 |
+
# Torch is row major
|
| 100 |
+
a_layout = cute.make_ordered_layout((m, k, l), order=(0, 1, 2))
|
| 101 |
+
b_layout = cute.make_ordered_layout((n, k, l), order=(0, 1, 2))
|
| 102 |
+
c_layout = cute.make_ordered_layout((m, n, l), order=(1, 0, 2))
|
| 103 |
+
mA = cute.make_tensor(a_ptr, layout=a_layout)
|
| 104 |
+
mB = cute.make_tensor(b_ptr, layout=b_layout)
|
| 105 |
+
mC = cute.make_tensor(c_ptr, layout=c_layout)
|
| 106 |
+
|
| 107 |
+
print(f"[DSL INFO] mA: {mA}")
|
| 108 |
+
print(f"[DSL INFO] mB: {mB}")
|
| 109 |
+
print(f"[DSL INFO] mC: {mC}")
|
| 110 |
+
|
| 111 |
+
tensor_op_gemm = TensorOpGemm(
|
| 112 |
+
a_ptr.value_type, c_ptr.value_type, cutlass.Float32, (2, 2, 1)
|
| 113 |
+
)
|
| 114 |
+
print(f"\n[DSL INFO] Created TensorOpGemm instance")
|
| 115 |
+
print(f"[DSL INFO] Input dtype: {a_ptr.value_type}")
|
| 116 |
+
print(f"[DSL INFO] Output dtype: {c_ptr.value_type}")
|
| 117 |
+
print(f"[DSL INFO] Accumulation dtype: {cutlass.Float32}")
|
| 118 |
+
print(f"[DSL INFO] Atom layout: {(2, 2, 1)}")
|
| 119 |
+
|
| 120 |
+
# No need to compile inside jit function
|
| 121 |
+
tensor_op_gemm(mA, mB, mC)
|
| 122 |
+
print(f"\n[DSL INFO] Executed TensorOpGemm")
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def run_tensor_op_gemm_wrapper(mnkl: Tuple[int, int, int, int]):
|
| 126 |
+
print(f"\nRunning TensorOpGemm test with:")
|
| 127 |
+
print(f"Tensor dimensions: {mnkl}")
|
| 128 |
+
|
| 129 |
+
# (M,K,L)
|
| 130 |
+
a = torch.randn(
|
| 131 |
+
mnkl[3], mnkl[2], mnkl[0], dtype=torch.float16, device="cuda"
|
| 132 |
+
).permute(2, 1, 0)
|
| 133 |
+
# (N,K,L)
|
| 134 |
+
b = torch.randn(
|
| 135 |
+
mnkl[3], mnkl[2], mnkl[1], dtype=torch.float16, device="cuda"
|
| 136 |
+
).permute(2, 1, 0)
|
| 137 |
+
# (N,M,L)
|
| 138 |
+
c = torch.randn(
|
| 139 |
+
mnkl[3], mnkl[0], mnkl[1], dtype=torch.float16, device="cuda"
|
| 140 |
+
).permute(1, 2, 0)
|
| 141 |
+
|
| 142 |
+
print(f"Input tensor shapes:")
|
| 143 |
+
print(f"a: {a.shape}, dtype: {a.dtype}")
|
| 144 |
+
print(f"b: {b.shape}, dtype: {b.dtype}")
|
| 145 |
+
print(f"c: {c.shape}, dtype: {c.dtype}\n")
|
| 146 |
+
|
| 147 |
+
a_ptr = make_ptr(
|
| 148 |
+
cutlass.Float16, a.data_ptr(), cute.AddressSpace.gmem, assumed_align=32
|
| 149 |
+
)
|
| 150 |
+
b_ptr = make_ptr(
|
| 151 |
+
cutlass.Float16, b.data_ptr(), cute.AddressSpace.gmem, assumed_align=32
|
| 152 |
+
)
|
| 153 |
+
c_ptr = make_ptr(
|
| 154 |
+
cutlass.Float16, c.data_ptr(), cute.AddressSpace.gmem, assumed_align=32
|
| 155 |
+
)
|
| 156 |
+
tensor_op_gemm_wrapper(a_ptr, b_ptr, c_ptr, *mnkl)
|
| 157 |
+
torch.cuda.synchronize()
|
| 158 |
+
|
| 159 |
+
ref = torch.einsum("mkl,nkl->mnl", a, b)
|
| 160 |
+
torch.testing.assert_close(c, ref, atol=1e-05, rtol=1e-05)
|
| 161 |
+
print(f"\n[DSL INFO] Results verified successfully!")
|
| 162 |
+
print(f"First few elements of result: \n{c[:3, :3, :3]}")
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
if __name__ == "__main__":
|
| 166 |
+
run_tensor_op_gemm_wrapper((512, 256, 128, 16))
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/call_from_jit.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
"""
|
| 30 |
+
Demonstrating JIT GEMM Implementation with Static Shape Wrapper
|
| 31 |
+
|
| 32 |
+
This example illustrates how to invoke a JIT-compiled GEMM implementation through a wrapper function
|
| 33 |
+
with static shapes. It showcases the integration between PyTorch and CuTe tensors in a JIT context.
|
| 34 |
+
|
| 35 |
+
Key features demonstrated:
|
| 36 |
+
1. Seamless conversion between PyTorch and CuTe tensors using the JitArgument protocol
|
| 37 |
+
2. Integration of static shape GEMM operations within a JIT-compiled wrapper function
|
| 38 |
+
|
| 39 |
+
Core components:
|
| 40 |
+
- BufferWithLayout: Handles memory buffer management with configurable stride ordering
|
| 41 |
+
- tensor_op_gemm_wrapper: JIT-compiled entry point that orchestrates the GEMM operation
|
| 42 |
+
|
| 43 |
+
Usage:
|
| 44 |
+
|
| 45 |
+
.. code-block:: bash
|
| 46 |
+
|
| 47 |
+
python examples/ampere/call_from_jit.py
|
| 48 |
+
|
| 49 |
+
Default configuration:
|
| 50 |
+
- Batch dimension (L): 16
|
| 51 |
+
- Matrix dimensions: M=512, N=256, K=128
|
| 52 |
+
- Precision: Float16 inputs with Float32 accumulation
|
| 53 |
+
|
| 54 |
+
Requirements:
|
| 55 |
+
- CUDA-capable GPU
|
| 56 |
+
- PyTorch with CUDA support
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
import os
|
| 60 |
+
import sys
|
| 61 |
+
from typing import Type, Tuple
|
| 62 |
+
|
| 63 |
+
import torch
|
| 64 |
+
|
| 65 |
+
import cutlass
|
| 66 |
+
import cutlass.cute as cute
|
| 67 |
+
from cutlass.torch import dtype as torch_dtype
|
| 68 |
+
from cutlass.cute.runtime import make_ptr
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# Add the current directory to sys.path
|
| 72 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 73 |
+
from tensorop_gemm import TensorOpGemm
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class BufferWithLayout:
|
| 77 |
+
def __init__(self, ptr: cute.Pointer, stride_order: tuple[int, int, int]):
|
| 78 |
+
self.ptr = ptr
|
| 79 |
+
|
| 80 |
+
# static properties
|
| 81 |
+
self.stride_order = stride_order
|
| 82 |
+
|
| 83 |
+
def to_tensor(
|
| 84 |
+
self, shape: tuple[int, int, int], *, loc=None, ip=None
|
| 85 |
+
) -> cute.Tensor:
|
| 86 |
+
assert len(shape) == len(self.stride_order), (
|
| 87 |
+
f"Shape {shape} and stride_order {self.stride_order} must have the "
|
| 88 |
+
"same rank."
|
| 89 |
+
)
|
| 90 |
+
layout = cute.make_ordered_layout(shape, self.stride_order)
|
| 91 |
+
# permute (l, mn, k) -> (mn, k, l)
|
| 92 |
+
res = cute.make_tensor(self.ptr, cute.select(layout, mode=[1, 2, 0]))
|
| 93 |
+
return res
|
| 94 |
+
|
| 95 |
+
# Implement JitArgument Protocol and DynamicExpression Protocol
|
| 96 |
+
|
| 97 |
+
def __c_pointers__(self):
|
| 98 |
+
"""Get the C pointers for the underlying pointer.
|
| 99 |
+
|
| 100 |
+
This method is part of the JitArgument Protocol and returns the C pointers
|
| 101 |
+
from the underlying pointer object.
|
| 102 |
+
|
| 103 |
+
This is required for user to define a custom data type which can pass to JIT function.
|
| 104 |
+
When JIT compiled function is called, JIT executor will call this method to get raw pointers
|
| 105 |
+
to underlying data object.
|
| 106 |
+
|
| 107 |
+
Following condition must be satisfied:
|
| 108 |
+
|
| 109 |
+
len(__c_pointers__()) == len(__get_mlir_types__()) == len(__extract_mlir_values__())
|
| 110 |
+
|
| 111 |
+
:return: The C pointers from the underlying pointer object
|
| 112 |
+
:rtype: Any
|
| 113 |
+
"""
|
| 114 |
+
return self.ptr.__c_pointers__()
|
| 115 |
+
|
| 116 |
+
def __get_mlir_types__(self):
|
| 117 |
+
"""Get the MLIR types for the underlying pointer.
|
| 118 |
+
|
| 119 |
+
This method is part of the JitArgument Protocol and returns the MLIR types
|
| 120 |
+
used for compiler to generate code. It must match the type of the underlying pointers
|
| 121 |
+
returned by __c_pointers__().
|
| 122 |
+
|
| 123 |
+
:return: The MLIR types from the underlying pointer object
|
| 124 |
+
:rtype: Any
|
| 125 |
+
"""
|
| 126 |
+
return self.ptr.__get_mlir_types__()
|
| 127 |
+
|
| 128 |
+
def __extract_mlir_values__(self):
|
| 129 |
+
"""Extract MLIR values from the underlying pointer.
|
| 130 |
+
|
| 131 |
+
This method is part of the DynamicExpression Protocol and extracts MLIR values
|
| 132 |
+
from the underlying pointer object.
|
| 133 |
+
|
| 134 |
+
It is used by compiler to generate function call in MLIR to another JIT function.
|
| 135 |
+
It must match the types returned by __get_mlir_types__().
|
| 136 |
+
|
| 137 |
+
:return: The MLIR values extracted from the underlying pointer object
|
| 138 |
+
:rtype: Any
|
| 139 |
+
"""
|
| 140 |
+
return self.ptr.__extract_mlir_values__()
|
| 141 |
+
|
| 142 |
+
def __new_from_mlir_values__(self, values):
|
| 143 |
+
"""Create a new BufferWithLayout instance from MLIR values.
|
| 144 |
+
|
| 145 |
+
This method is part of the JitArgument & DynamicExpression Protocol and creates a new
|
| 146 |
+
BufferWithLayout instance with pointer initialized from the given MLIR values.
|
| 147 |
+
|
| 148 |
+
It is used by compiler to generate function body in MLIR called by JIT function.
|
| 149 |
+
It must match the types returned by __c_pointers__() and __get_mlir_types__().
|
| 150 |
+
code generator takes function arguments and reconstructs python object which is legal
|
| 151 |
+
inside function body.
|
| 152 |
+
|
| 153 |
+
:param values: MLIR values to initialize the underlying pointer
|
| 154 |
+
:type values: Any
|
| 155 |
+
:return: A new BufferWithLayout instance with pointer initialized from values
|
| 156 |
+
:rtype: BufferWithLayout
|
| 157 |
+
"""
|
| 158 |
+
return BufferWithLayout(
|
| 159 |
+
self.ptr.__new_from_mlir_values__(values), self.stride_order
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
@cute.jit
|
| 164 |
+
def tensor_op_gemm_wrapper(
|
| 165 |
+
buffer_a: BufferWithLayout,
|
| 166 |
+
buffer_b: BufferWithLayout,
|
| 167 |
+
buffer_c: BufferWithLayout,
|
| 168 |
+
mnkl: cutlass.Constexpr[tuple[int, int, int, int]],
|
| 169 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 170 |
+
atom_layout_mnk: cutlass.Constexpr[tuple[int, int, int]],
|
| 171 |
+
):
|
| 172 |
+
print(f"\n[DSL INFO] Input Parameters:")
|
| 173 |
+
print(f"[DSL INFO] mnkl: {mnkl}")
|
| 174 |
+
print(f"[DSL INFO] buffer_a: {buffer_a}")
|
| 175 |
+
print(f"[DSL INFO] buffer_b: {buffer_b}")
|
| 176 |
+
print(f"[DSL INFO] buffer_c: {buffer_c}")
|
| 177 |
+
print(f"[DSL INFO] acc_dtype: {acc_dtype}")
|
| 178 |
+
print(f"[DSL INFO] atom_layout_mnk: {atom_layout_mnk}")
|
| 179 |
+
|
| 180 |
+
mA = buffer_a.to_tensor(cute.select(mnkl, mode=[3, 0, 2]))
|
| 181 |
+
mB = buffer_b.to_tensor(cute.select(mnkl, mode=[3, 1, 2]))
|
| 182 |
+
mC = buffer_c.to_tensor(cute.select(mnkl, mode=[3, 0, 1]))
|
| 183 |
+
|
| 184 |
+
print(f"\n[DSL INFO] Created Tensors:")
|
| 185 |
+
print(f"[DSL INFO] mA = {mA}")
|
| 186 |
+
print(f"[DSL INFO] mB = {mB}")
|
| 187 |
+
print(f"[DSL INFO] mC = {mC}")
|
| 188 |
+
|
| 189 |
+
tensor_op_gemm = TensorOpGemm(
|
| 190 |
+
buffer_a.ptr.value_type,
|
| 191 |
+
buffer_c.ptr.value_type,
|
| 192 |
+
acc_dtype,
|
| 193 |
+
atom_layout_mnk,
|
| 194 |
+
)
|
| 195 |
+
print(f"\n[DSL INFO] Created TensorOpGemm instance")
|
| 196 |
+
print(f"[DSL INFO] Input dtype: {buffer_a.ptr.value_type}")
|
| 197 |
+
print(f"[DSL INFO] Output dtype: {buffer_c.ptr.value_type}")
|
| 198 |
+
print(f"[DSL INFO] Accumulation dtype: {acc_dtype}")
|
| 199 |
+
print(f"[DSL INFO] Atom layout: {atom_layout_mnk}")
|
| 200 |
+
|
| 201 |
+
# No need to compile inside jit function
|
| 202 |
+
tensor_op_gemm(mA, mB, mC)
|
| 203 |
+
print(f"\n[DSL INFO] Executed TensorOpGemm")
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def run_tensor_op_gemm_wrapper(mnkl: Tuple[int, int, int, int]):
|
| 207 |
+
print(f"\nRunning TensorOpGemm test with:")
|
| 208 |
+
print(f"Tensor dimensions: {mnkl}")
|
| 209 |
+
|
| 210 |
+
ab_dtype = cutlass.Float16
|
| 211 |
+
c_dtype = cutlass.Float16
|
| 212 |
+
|
| 213 |
+
a = torch.randn(
|
| 214 |
+
mnkl[3], mnkl[0], mnkl[2], dtype=torch_dtype(ab_dtype), device="cuda"
|
| 215 |
+
)
|
| 216 |
+
b = torch.randn(
|
| 217 |
+
mnkl[3], mnkl[1], mnkl[2], dtype=torch_dtype(ab_dtype), device="cuda"
|
| 218 |
+
)
|
| 219 |
+
c = torch.randn(
|
| 220 |
+
mnkl[3], mnkl[0], mnkl[1], dtype=torch_dtype(c_dtype), device="cuda"
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
print(f"Input tensor shapes:")
|
| 224 |
+
print(f"a: {a.shape}, dtype: {a.dtype}")
|
| 225 |
+
print(f"b: {b.shape}, dtype: {b.dtype}")
|
| 226 |
+
print(f"c: {c.shape}, dtype: {c.dtype}\n")
|
| 227 |
+
|
| 228 |
+
buffer_a = BufferWithLayout(
|
| 229 |
+
make_ptr(ab_dtype, a.data_ptr(), cute.AddressSpace.gmem, assumed_align=32),
|
| 230 |
+
(2, 1, 0),
|
| 231 |
+
)
|
| 232 |
+
buffer_b = BufferWithLayout(
|
| 233 |
+
make_ptr(ab_dtype, b.data_ptr(), cute.AddressSpace.gmem, assumed_align=32),
|
| 234 |
+
(2, 1, 0),
|
| 235 |
+
)
|
| 236 |
+
buffer_c = BufferWithLayout(
|
| 237 |
+
make_ptr(c_dtype, c.data_ptr(), cute.AddressSpace.gmem, assumed_align=32),
|
| 238 |
+
(2, 1, 0),
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
tensor_op_gemm_wrapper(
|
| 242 |
+
buffer_a,
|
| 243 |
+
buffer_b,
|
| 244 |
+
buffer_c,
|
| 245 |
+
mnkl, # pass shape as static value
|
| 246 |
+
# no stride passing
|
| 247 |
+
cutlass.Float32,
|
| 248 |
+
(2, 2, 1),
|
| 249 |
+
)
|
| 250 |
+
torch.cuda.synchronize()
|
| 251 |
+
|
| 252 |
+
ref = torch.einsum("lmk,lnk->lmn", a, b)
|
| 253 |
+
torch.testing.assert_close(c, ref, atol=1e-05, rtol=1e-05)
|
| 254 |
+
print(f"\n[DSL INFO] Results verified successfully!")
|
| 255 |
+
print(f"First few elements of result: \n{c[:3, :3, :3]}")
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
if __name__ == "__main__":
|
| 259 |
+
run_tensor_op_gemm_wrapper((512, 256, 128, 16))
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/dynamic_smem_size.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import cutlass.cute as cute
|
| 30 |
+
import cutlass
|
| 31 |
+
|
| 32 |
+
"""
|
| 33 |
+
Example of automatic shared memory size computation for configuring kernel launch
|
| 34 |
+
|
| 35 |
+
This example demonstrates how to let the DSL automatically set shared memory
|
| 36 |
+
size for a kernel launch rather explicitly configuring it at launch time,
|
| 37 |
+
provided that developers are using `SmemAllocator` for all allocations.
|
| 38 |
+
|
| 39 |
+
Usage:
|
| 40 |
+
python dynamic_smem_size.py # Show auto inference
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@cute.struct
|
| 45 |
+
class SharedData:
|
| 46 |
+
"""A struct to demonstrate shared memory allocation."""
|
| 47 |
+
|
| 48 |
+
values: cute.struct.MemRange[cutlass.Float32, 64] # 256 bytes
|
| 49 |
+
counter: cutlass.Int32 # 4 bytes
|
| 50 |
+
flag: cutlass.Int8 # 1 byte
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@cute.kernel
|
| 54 |
+
def kernel():
|
| 55 |
+
"""
|
| 56 |
+
Example kernel that allocates shared memory.
|
| 57 |
+
The total allocation will be automatically calculated when smem=None.
|
| 58 |
+
"""
|
| 59 |
+
allocator = cutlass.utils.SmemAllocator()
|
| 60 |
+
|
| 61 |
+
# Allocate various types of shared memory
|
| 62 |
+
shared_data = allocator.allocate(SharedData)
|
| 63 |
+
raw_buffer = allocator.allocate(512, byte_alignment=64)
|
| 64 |
+
int_array = allocator.allocate_array(element_type=cutlass.Int32, num_elems=128)
|
| 65 |
+
tensor_smem = allocator.allocate_tensor(
|
| 66 |
+
element_type=cutlass.Float16,
|
| 67 |
+
layout=cute.make_layout((32, 16)),
|
| 68 |
+
byte_alignment=16,
|
| 69 |
+
swizzle=None,
|
| 70 |
+
)
|
| 71 |
+
return
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
@cute.kernel
|
| 75 |
+
def kernel_no_smem():
|
| 76 |
+
"""
|
| 77 |
+
Example kernel that does not allocates shared memory.
|
| 78 |
+
The total allocation will be automatically calculated as 0 when smem=None.
|
| 79 |
+
"""
|
| 80 |
+
tidx, _, _ = cute.arch.block_idx()
|
| 81 |
+
if tidx == 0:
|
| 82 |
+
cute.printf("Hello world")
|
| 83 |
+
return
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
# Initialize CUDA context
|
| 88 |
+
cutlass.cuda.initialize_cuda_context()
|
| 89 |
+
|
| 90 |
+
print("Launching kernel with auto smem size. (launch config `smem=None`)")
|
| 91 |
+
|
| 92 |
+
# Compile the example
|
| 93 |
+
@cute.jit
|
| 94 |
+
def launch_kernel1():
|
| 95 |
+
k = kernel()
|
| 96 |
+
k.launch(
|
| 97 |
+
grid=(1, 1, 1),
|
| 98 |
+
block=(1, 1, 1),
|
| 99 |
+
)
|
| 100 |
+
print(f"Kernel recorded internal smem usage: {k.smem_usage()}")
|
| 101 |
+
|
| 102 |
+
@cute.jit
|
| 103 |
+
def launch_kernel2():
|
| 104 |
+
k = kernel_no_smem()
|
| 105 |
+
k.launch(
|
| 106 |
+
grid=(1, 1, 1),
|
| 107 |
+
block=(1, 1, 1),
|
| 108 |
+
)
|
| 109 |
+
print(f"Kernel recorded internal smem usage: {k.smem_usage()}")
|
| 110 |
+
|
| 111 |
+
cute.compile(launch_kernel1)
|
| 112 |
+
cute.compile(launch_kernel2)
|
| 113 |
+
|
| 114 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/elementwise_add.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
import argparse
|
| 31 |
+
import time
|
| 32 |
+
from typing import Type
|
| 33 |
+
|
| 34 |
+
import cuda.bindings.driver as cuda
|
| 35 |
+
import torch
|
| 36 |
+
|
| 37 |
+
import cutlass
|
| 38 |
+
import cutlass.cute as cute
|
| 39 |
+
import cutlass.cute.testing as testing
|
| 40 |
+
import cutlass.torch as cutlass_torch
|
| 41 |
+
from cutlass.cute.runtime import from_dlpack
|
| 42 |
+
|
| 43 |
+
"""
|
| 44 |
+
An Elementwise Addition Example using CuTe DSL.
|
| 45 |
+
|
| 46 |
+
This example kernel copies data from global memory to register memory (rmem), performs the elementwise
|
| 47 |
+
addition operation, and stores the result back to global memory.
|
| 48 |
+
|
| 49 |
+
Primary goals of this example are to demonstrate how basic global memory copies can be expressed in
|
| 50 |
+
CuTe DSL and illustrate canonical partitioning patterns in CuTe. It also implements canonical
|
| 51 |
+
predication for tensors whose shape is not multiple of tile size to guard OOB reads.
|
| 52 |
+
|
| 53 |
+
Thread-value (or TV) layouts are central to canonical partitioning patterns in CuTe. They provide a
|
| 54 |
+
mapping from thread and a thread's value to the set of coordinates within a tile that we have sliced
|
| 55 |
+
out from a data tensor.
|
| 56 |
+
|
| 57 |
+
The input tensors are row-major layout, that leading dimension is the right most dimension. In order
|
| 58 |
+
to efficiently copy data from global memory, we must map threads contiguously on row dimension.
|
| 59 |
+
|
| 60 |
+
Thread ID mapping to 2D coordinates with layout `(4,32):(32,1)`:
|
| 61 |
+
|
| 62 |
+
+----+----+----+----+-----+----+
|
| 63 |
+
| | 0 | 1 | 2 | ... | 31 |
|
| 64 |
+
+----+----+----+----+-----+----+
|
| 65 |
+
| 0 | T0 | T1 | T2 | ... | T31|
|
| 66 |
+
+----+----+----+----+-----+----+
|
| 67 |
+
| 1 |T32 |T33 |T34 | ... |T63 |
|
| 68 |
+
+----+----+----+----+-----+----+
|
| 69 |
+
| 2 |T64 |T65 |T66 | ... |T95 |
|
| 70 |
+
+----+----+----+----+-----+----+
|
| 71 |
+
| 3 |T96 |T97 |T98 | ... |T127|
|
| 72 |
+
+----+----+----+----+-----+----+
|
| 73 |
+
|
| 74 |
+
As Ampere GPU supports a maximum of 128bit per load/store instruction and each element is 32bit, we
|
| 75 |
+
can load 4 elements per instruction. Having additional contiguous values allows for vectorization
|
| 76 |
+
across threads (coalesced accesses) and is required for saturating the memory bandwidth.
|
| 77 |
+
|
| 78 |
+
We use `(4,4):(4,1)` as the val layout in this example. Notice that the major mode is the same as
|
| 79 |
+
the major mode of the input tensor - without which vectorization would not be possible.
|
| 80 |
+
|
| 81 |
+
If you already know the TV layout you want to use for your tiled copy, CuTe DSL provides utility
|
| 82 |
+
`cute.make_layout_tv` to build the tiled copy type around it and the atom of your choice.
|
| 83 |
+
|
| 84 |
+
.. code-block:: python
|
| 85 |
+
|
| 86 |
+
thr_layout = cute.make_layout((4, 32), stride=(32, 1))
|
| 87 |
+
val_layout = cute.make_layout((4, 4), stride=(4, 1))
|
| 88 |
+
tiler_mn, tv_layout = cute.make_layout_tv(thr_layout, val_layout)
|
| 89 |
+
|
| 90 |
+
# Tile input tensor to thread blocks: ((TileM,TileN),(RestM,RestN))
|
| 91 |
+
gA = cute.zipped_divide(mA, tiler_mn)
|
| 92 |
+
|
| 93 |
+
Then we can build tiled copy for input and output tensors with `cute.make_tiled_copy_tv` utility, which
|
| 94 |
+
infers the tiler and tv layout for the tiled copy automatically, where `tiler` is the tile size per thread
|
| 95 |
+
block and `tv_layout` is the TV layout which maps thread index and inter-thread index of data array per
|
| 96 |
+
thread to logical coordinates of elements in input and output tensors.
|
| 97 |
+
|
| 98 |
+
.. code-block:: python
|
| 99 |
+
|
| 100 |
+
blkA = gA[((None, None), bidx)] # (TileM,TileN)
|
| 101 |
+
|
| 102 |
+
copy_atom_load = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), gA.element_type)
|
| 103 |
+
tiled_copy_A = cute.make_tiled_copy_tv(copy_atom_load, thr_layout, val_layout)
|
| 104 |
+
|
| 105 |
+
# get slice of tiled_copy_A for current thread
|
| 106 |
+
thr_copy_A = tiled_copy_A.get_slice(tidx)
|
| 107 |
+
|
| 108 |
+
# partition per thread block tensor as source of tiled copy
|
| 109 |
+
thrA = thr_copy_A.partition_S(blkA)
|
| 110 |
+
|
| 111 |
+
# allocate fragment for gmem->rmem
|
| 112 |
+
frgA = cute.make_fragment_like(thrA)
|
| 113 |
+
|
| 114 |
+
# copy data from global memory to register memory
|
| 115 |
+
cute.copy(copy_atom_load, thrA, frgA)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
To run this example:
|
| 119 |
+
|
| 120 |
+
.. code-block:: bash
|
| 121 |
+
|
| 122 |
+
python examples/ampere/elementwise_add.py --M 3 --N 12
|
| 123 |
+
python examples/ampere/elementwise_add.py --M 1024 --N 512
|
| 124 |
+
python examples/ampere/elementwise_add.py --M 1024 --N 1024 --benchmark --warmup_iterations 2 --iterations 1000
|
| 125 |
+
|
| 126 |
+
To collect performance with NCU profiler:
|
| 127 |
+
|
| 128 |
+
.. code-block:: bash
|
| 129 |
+
|
| 130 |
+
# Don't iterate too many times when profiling with ncu
|
| 131 |
+
ncu python examples/ampere/elementwise_add.py --M 2048 --N 2048 --benchmark --iterations 10 --skip_ref_check
|
| 132 |
+
"""
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
@cute.kernel
|
| 136 |
+
def elementwise_add_kernel(
|
| 137 |
+
gA: cute.Tensor,
|
| 138 |
+
gB: cute.Tensor,
|
| 139 |
+
gC: cute.Tensor,
|
| 140 |
+
cC: cute.Tensor, # coordinate tensor
|
| 141 |
+
shape: cute.Shape,
|
| 142 |
+
thr_layout: cute.Layout,
|
| 143 |
+
val_layout: cute.Layout,
|
| 144 |
+
):
|
| 145 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 146 |
+
bidx, _, _ = cute.arch.block_idx()
|
| 147 |
+
|
| 148 |
+
# slice for CTAs
|
| 149 |
+
# logical id -> address
|
| 150 |
+
blk_coord = ((None, None), bidx)
|
| 151 |
+
blkA = gA[blk_coord] # (TileM,TileN)
|
| 152 |
+
blkB = gB[blk_coord] # (TileM,TileN)
|
| 153 |
+
blkC = gC[blk_coord] # (TileM,TileN)
|
| 154 |
+
blkCrd = cC[blk_coord] # (TileM, TileN)
|
| 155 |
+
|
| 156 |
+
# Note: these prints only run at compile/jit time
|
| 157 |
+
print(f"[DSL INFO] Sliced Tensors per thread block:")
|
| 158 |
+
print(f"[DSL INFO] blkA = {blkA.type}")
|
| 159 |
+
print(f"[DSL INFO] blkB = {blkB.type}")
|
| 160 |
+
print(f"[DSL INFO] blkC = {blkC.type}")
|
| 161 |
+
print(f"[DSL INFO] blkCrd = {blkCrd.type}")
|
| 162 |
+
|
| 163 |
+
# # declare the atoms which will be used later for memory copy
|
| 164 |
+
copy_atom_load = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), gA.element_type)
|
| 165 |
+
copy_atom_store = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), gC.element_type)
|
| 166 |
+
|
| 167 |
+
tiled_copy_A = cute.make_tiled_copy_tv(copy_atom_load, thr_layout, val_layout)
|
| 168 |
+
tiled_copy_B = cute.make_tiled_copy_tv(copy_atom_load, thr_layout, val_layout)
|
| 169 |
+
tiled_copy_C = cute.make_tiled_copy_tv(copy_atom_store, thr_layout, val_layout)
|
| 170 |
+
|
| 171 |
+
thr_copy_A = tiled_copy_A.get_slice(tidx)
|
| 172 |
+
thr_copy_B = tiled_copy_B.get_slice(tidx)
|
| 173 |
+
thr_copy_C = tiled_copy_C.get_slice(tidx)
|
| 174 |
+
|
| 175 |
+
thrA = thr_copy_A.partition_S(blkA)
|
| 176 |
+
thrB = thr_copy_B.partition_S(blkB)
|
| 177 |
+
thrC = thr_copy_C.partition_S(blkC)
|
| 178 |
+
|
| 179 |
+
# allocate fragments for gmem->rmem
|
| 180 |
+
frgA = cute.make_fragment_like(thrA)
|
| 181 |
+
frgB = cute.make_fragment_like(thrB)
|
| 182 |
+
frgC = cute.make_fragment_like(thrC)
|
| 183 |
+
|
| 184 |
+
thrCrd = thr_copy_C.partition_S(blkCrd)
|
| 185 |
+
frgPred = cute.make_fragment(thrCrd.shape, cutlass.Boolean)
|
| 186 |
+
|
| 187 |
+
print(f"[DSL INFO] Sliced Tensors per thread:")
|
| 188 |
+
print(f"[DSL INFO] thrA = {thrA.type}")
|
| 189 |
+
print(f"[DSL INFO] thrB = {thrB.type}")
|
| 190 |
+
print(f"[DSL INFO] thrC = {thrC.type}")
|
| 191 |
+
print(f"[DSL INFO] thrCrd = {thrCrd.type}")
|
| 192 |
+
|
| 193 |
+
for i in range(0, cute.size(frgPred), 1):
|
| 194 |
+
val = cute.elem_less(thrCrd[i], shape)
|
| 195 |
+
frgPred[i] = val
|
| 196 |
+
|
| 197 |
+
# Print per thread predicate mask
|
| 198 |
+
# if tidx == 0 and bidx == 0:
|
| 199 |
+
# cute.printf("block_dim = {}", cute.arch.grid_dim())
|
| 200 |
+
# cute.printf("shape = {}", shape)
|
| 201 |
+
# cute.print_tensor(thrA)
|
| 202 |
+
# cute.print_tensor(thrB)
|
| 203 |
+
# cute.print_tensor(frgPred)
|
| 204 |
+
|
| 205 |
+
##########################################################
|
| 206 |
+
# Move data to reg address space
|
| 207 |
+
##########################################################
|
| 208 |
+
|
| 209 |
+
cute.copy(copy_atom_load, thrA, frgA, pred=frgPred)
|
| 210 |
+
cute.copy(copy_atom_load, thrB, frgB, pred=frgPred)
|
| 211 |
+
|
| 212 |
+
# if tidx == 0 and bidx == 0:
|
| 213 |
+
# cute.print_tensor(frgA)
|
| 214 |
+
# cute.print_tensor(frgB)
|
| 215 |
+
|
| 216 |
+
# Load data before use. The compiler will optimize the copy and load
|
| 217 |
+
# operations to convert some memory ld/st into register uses.
|
| 218 |
+
result = frgA.load() + frgB.load()
|
| 219 |
+
|
| 220 |
+
# Save the results back to registers. Here we reuse b's registers.
|
| 221 |
+
frgC.store(result)
|
| 222 |
+
|
| 223 |
+
# Copy the results back to c
|
| 224 |
+
cute.copy(copy_atom_store, frgC, thrC, pred=frgPred)
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@cute.jit
|
| 228 |
+
def elementwise_add(mA, mB, mC, copy_bits: cutlass.Constexpr = 128):
|
| 229 |
+
dtype = mA.element_type
|
| 230 |
+
vector_size = copy_bits // dtype.width
|
| 231 |
+
|
| 232 |
+
thr_layout = cute.make_ordered_layout((4, 32), order=(1, 0))
|
| 233 |
+
val_layout = cute.make_ordered_layout((4, vector_size), order=(1, 0))
|
| 234 |
+
tiler_mn, tv_layout = cute.make_layout_tv(thr_layout, val_layout)
|
| 235 |
+
|
| 236 |
+
print(f"[DSL INFO] Input Tensors:")
|
| 237 |
+
print(f"[DSL INFO] mA = {mA.type}")
|
| 238 |
+
print(f"[DSL INFO] mB = {mB.type}")
|
| 239 |
+
|
| 240 |
+
print(f"[DSL INFO] Tiling Parameters:")
|
| 241 |
+
print(f"[DSL INFO] tiler_mn = {tiler_mn} per thread block")
|
| 242 |
+
print(f"[DSL INFO] tv_layout = {tv_layout}")
|
| 243 |
+
|
| 244 |
+
gA = cute.zipped_divide(mA, tiler_mn) # ((TileM,TileN),(RestM,RestN))
|
| 245 |
+
gB = cute.zipped_divide(mB, tiler_mn) # ((TileM,TileN),(RestM,RestN))
|
| 246 |
+
gC = cute.zipped_divide(mC, tiler_mn) # ((TileM,TileN),(RestM,RestN))
|
| 247 |
+
print(f"[DSL INFO] Tiled Tensors:")
|
| 248 |
+
print(f"[DSL INFO] gA = {gA.type}")
|
| 249 |
+
print(f"[DSL INFO] gB = {gB.type}")
|
| 250 |
+
print(f"[DSL INFO] gC = {gC.type}")
|
| 251 |
+
|
| 252 |
+
idC = cute.make_identity_tensor(mC.shape)
|
| 253 |
+
cC = cute.zipped_divide(idC, tiler=tiler_mn)
|
| 254 |
+
print(f"[DSL INFO] coord tensor = {cC.type}")
|
| 255 |
+
|
| 256 |
+
elementwise_add_kernel(gA, gB, gC, cC, mC.shape, thr_layout, val_layout).launch(
|
| 257 |
+
grid=[cute.size(gC, mode=[1]), 1, 1],
|
| 258 |
+
block=[cute.size(tv_layout, mode=[0]), 1, 1],
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def run_elementwise_add(
|
| 263 |
+
M,
|
| 264 |
+
N,
|
| 265 |
+
dtype: Type[cutlass.Numeric],
|
| 266 |
+
is_a_dynamic_layout=False,
|
| 267 |
+
is_b_dynamic_layout=False,
|
| 268 |
+
is_result_dynamic_layout=False,
|
| 269 |
+
skip_ref_check=False,
|
| 270 |
+
benchmark=True,
|
| 271 |
+
warmup_iterations=2,
|
| 272 |
+
iterations=200,
|
| 273 |
+
):
|
| 274 |
+
print(f"\nRunning Elementwise Add test with:")
|
| 275 |
+
print(f"Tensor dimensions: [{M}, {N}]")
|
| 276 |
+
print(f"Input and Output Data type: {dtype}")
|
| 277 |
+
|
| 278 |
+
torch_dtype = cutlass_torch.dtype(dtype)
|
| 279 |
+
if dtype.is_integer:
|
| 280 |
+
a = torch.randint(0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype)
|
| 281 |
+
b = torch.randint(0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype)
|
| 282 |
+
else:
|
| 283 |
+
a = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
|
| 284 |
+
b = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
|
| 285 |
+
|
| 286 |
+
c = torch.zeros_like(a)
|
| 287 |
+
|
| 288 |
+
print(f"Input tensor shapes:")
|
| 289 |
+
print(f"a: {a.shape}, dtype: {a.dtype}")
|
| 290 |
+
print(f"b: {b.shape}, dtype: {b.dtype}")
|
| 291 |
+
print(f"c: {c.shape}, dtype: {c.dtype}\n")
|
| 292 |
+
|
| 293 |
+
if not is_a_dynamic_layout:
|
| 294 |
+
a_tensor = from_dlpack(a).mark_layout_dynamic()
|
| 295 |
+
else:
|
| 296 |
+
a_tensor = a
|
| 297 |
+
|
| 298 |
+
if not is_b_dynamic_layout:
|
| 299 |
+
b_tensor = from_dlpack(b).mark_layout_dynamic()
|
| 300 |
+
else:
|
| 301 |
+
b_tensor = b
|
| 302 |
+
|
| 303 |
+
if not is_result_dynamic_layout:
|
| 304 |
+
c_tensor = from_dlpack(c).mark_layout_dynamic()
|
| 305 |
+
else:
|
| 306 |
+
c_tensor = c
|
| 307 |
+
|
| 308 |
+
print("Compiling kernel with cute.compile ...")
|
| 309 |
+
start_time = time.time()
|
| 310 |
+
compiled_func = cute.compile(elementwise_add, a_tensor, b_tensor, c_tensor)
|
| 311 |
+
compilation_time = time.time() - start_time
|
| 312 |
+
print(f"Compilation time: {compilation_time:.4f} seconds")
|
| 313 |
+
|
| 314 |
+
print("Executing vector add kernel...")
|
| 315 |
+
|
| 316 |
+
# Get current CUstream from torch
|
| 317 |
+
current_stream = cutlass_torch.current_stream()
|
| 318 |
+
|
| 319 |
+
if not skip_ref_check:
|
| 320 |
+
compiled_func(a_tensor, b_tensor, c_tensor)
|
| 321 |
+
print("Verifying results...")
|
| 322 |
+
torch.testing.assert_close(a + b, c)
|
| 323 |
+
print("Results verified successfully!")
|
| 324 |
+
|
| 325 |
+
if not benchmark:
|
| 326 |
+
return
|
| 327 |
+
|
| 328 |
+
def generate_tensors():
|
| 329 |
+
if dtype.is_integer:
|
| 330 |
+
a = torch.randint(
|
| 331 |
+
0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype
|
| 332 |
+
)
|
| 333 |
+
b = torch.randint(
|
| 334 |
+
0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype
|
| 335 |
+
)
|
| 336 |
+
else:
|
| 337 |
+
a = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
|
| 338 |
+
b = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
|
| 339 |
+
|
| 340 |
+
c = torch.zeros_like(a)
|
| 341 |
+
|
| 342 |
+
if not is_a_dynamic_layout:
|
| 343 |
+
a_tensor = from_dlpack(a).mark_layout_dynamic()
|
| 344 |
+
else:
|
| 345 |
+
a_tensor = a
|
| 346 |
+
|
| 347 |
+
if not is_b_dynamic_layout:
|
| 348 |
+
b_tensor = from_dlpack(b).mark_layout_dynamic()
|
| 349 |
+
else:
|
| 350 |
+
b_tensor = b
|
| 351 |
+
|
| 352 |
+
if not is_result_dynamic_layout:
|
| 353 |
+
c_tensor = from_dlpack(c).mark_layout_dynamic()
|
| 354 |
+
else:
|
| 355 |
+
c_tensor = c
|
| 356 |
+
|
| 357 |
+
return testing.JitArguments(a_tensor, b_tensor, c_tensor)
|
| 358 |
+
|
| 359 |
+
avg_time_us = testing.benchmark(
|
| 360 |
+
compiled_func,
|
| 361 |
+
workspace_generator=generate_tensors,
|
| 362 |
+
workspace_count=10,
|
| 363 |
+
warmup_iterations=warmup_iterations,
|
| 364 |
+
iterations=iterations,
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
# Print execution results
|
| 368 |
+
print(f"Kernel execution time: {avg_time_us / 1e3:.4f} ms")
|
| 369 |
+
print(
|
| 370 |
+
f"Achieved memory throughput: {(3 * a.numel() * dtype.width // 8) / (avg_time_us / 1e6) / 1e9:.2f} GB/s"
|
| 371 |
+
)
|
| 372 |
+
print(f"First few elements of result: \n{c[:3, :3]}")
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
if __name__ == "__main__":
|
| 376 |
+
parser = argparse.ArgumentParser(
|
| 377 |
+
description="example of elementwise add to demonstrate the numpy/pytorch as input for kernels"
|
| 378 |
+
)
|
| 379 |
+
parser.add_argument("--M", default=1024, type=int)
|
| 380 |
+
parser.add_argument("--N", default=1024, type=int)
|
| 381 |
+
parser.add_argument("--warmup_iterations", default=2, type=int)
|
| 382 |
+
parser.add_argument("--iterations", default=100, type=int)
|
| 383 |
+
parser.add_argument("--skip_ref_check", action="store_true")
|
| 384 |
+
parser.add_argument("--benchmark", action="store_true")
|
| 385 |
+
|
| 386 |
+
args = parser.parse_args()
|
| 387 |
+
|
| 388 |
+
if not torch.cuda.is_available():
|
| 389 |
+
raise RuntimeError(f"Ampere GPU is required to run this example!")
|
| 390 |
+
|
| 391 |
+
run_elementwise_add(
|
| 392 |
+
args.M,
|
| 393 |
+
args.N,
|
| 394 |
+
dtype=cutlass.Float32,
|
| 395 |
+
is_a_dynamic_layout=True,
|
| 396 |
+
is_b_dynamic_layout=True,
|
| 397 |
+
is_result_dynamic_layout=True,
|
| 398 |
+
skip_ref_check=args.skip_ref_check,
|
| 399 |
+
benchmark=args.benchmark,
|
| 400 |
+
warmup_iterations=args.warmup_iterations,
|
| 401 |
+
iterations=args.iterations,
|
| 402 |
+
)
|
| 403 |
+
print("\nPASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/elementwise_apply.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
import argparse
|
| 31 |
+
import operator
|
| 32 |
+
import time
|
| 33 |
+
from typing import Type, List
|
| 34 |
+
|
| 35 |
+
import cuda.bindings.driver as cuda
|
| 36 |
+
import torch
|
| 37 |
+
|
| 38 |
+
import cutlass
|
| 39 |
+
import cutlass.cute as cute
|
| 40 |
+
import cutlass.cute.testing as testing
|
| 41 |
+
import cutlass.torch as cutlass_torch
|
| 42 |
+
from cutlass.cute.runtime import from_dlpack
|
| 43 |
+
|
| 44 |
+
"""
|
| 45 |
+
An Elementwise Apply Example using CuTe DSL.
|
| 46 |
+
|
| 47 |
+
This example kernel demonstrates the meta-programming capability of the CuTe DSL by allowing
|
| 48 |
+
customization of elementwise operations through lambda functions. The kernel copies data from
|
| 49 |
+
global memory to register memory (rmem), applies a user-defined operation to the elements,
|
| 50 |
+
and stores the result back to global memory.
|
| 51 |
+
|
| 52 |
+
Primary goals of this example:
|
| 53 |
+
1. Demonstrate meta-programming capability by passing lambda functions to customize elementwise operations
|
| 54 |
+
2. Show how to apply different operations (add, multiply, etc.) using the same kernel structure
|
| 55 |
+
3. Illustrate how to parameterize CUDA kernels with operation types at compile time
|
| 56 |
+
|
| 57 |
+
To run this example:
|
| 58 |
+
|
| 59 |
+
.. code-block:: bash
|
| 60 |
+
|
| 61 |
+
# Run with addition operation
|
| 62 |
+
python examples/ampere/elementwise_apply.py --M 1024 --N 512 --op add
|
| 63 |
+
|
| 64 |
+
# Run with multiplication operation
|
| 65 |
+
python examples/ampere/elementwise_apply.py --M 1024 --N 512 --op mul
|
| 66 |
+
|
| 67 |
+
# Run with subtraction operation
|
| 68 |
+
python examples/ampere/elementwise_apply.py --M 1024 --N 512 --op sub
|
| 69 |
+
|
| 70 |
+
# Benchmark performance
|
| 71 |
+
python examples/ampere/elementwise_apply.py --M 2048 --N 2048 --op add --benchmark --warmup_iterations 2 --iterations 10
|
| 72 |
+
|
| 73 |
+
The example demonstrates how to express complex CUDA kernels with customizable operations
|
| 74 |
+
while maintaining high performance through efficient memory access patterns.
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@cute.kernel
|
| 79 |
+
def elementwise_apply_kernel(
|
| 80 |
+
op: cutlass.Constexpr,
|
| 81 |
+
inputs: List[cute.Tensor],
|
| 82 |
+
gC: cute.Tensor,
|
| 83 |
+
cC: cute.Tensor, # coordinate tensor
|
| 84 |
+
shape: cute.Shape,
|
| 85 |
+
tv_layout: cute.Layout, # (tid, vid) -> logic coord
|
| 86 |
+
):
|
| 87 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 88 |
+
bidx, _, _ = cute.arch.block_idx()
|
| 89 |
+
|
| 90 |
+
# slice for CTAs
|
| 91 |
+
cta_coord = ((None, None), bidx)
|
| 92 |
+
# logical coord -> address
|
| 93 |
+
# Leverage the meta-programming capability of the DSL to slice the tensors for each input
|
| 94 |
+
# All for loops below on input tensors would be fully unrolled automatically at compile time
|
| 95 |
+
ctaInputs = [t[cta_coord] for t in inputs] # (TileM, TileN)
|
| 96 |
+
ctaC = gC[cta_coord] # (TileM, TileN)
|
| 97 |
+
ctaCrd = cC[cta_coord] # (TileM, TileN)
|
| 98 |
+
|
| 99 |
+
print(f"[DSL INFO] Sliced Tensors per thread block:")
|
| 100 |
+
for i in cutlass.range_constexpr(len(ctaInputs)):
|
| 101 |
+
print(f"[DSL INFO] ctaInputs{i} = {ctaInputs[i].type}")
|
| 102 |
+
print(f"[DSL INFO] ctaC = {ctaC.type}")
|
| 103 |
+
print(f"[DSL INFO] ctaCrd = {ctaCrd.type}")
|
| 104 |
+
|
| 105 |
+
# compose with CTA TV layout
|
| 106 |
+
# (tid, vid) -> address
|
| 107 |
+
tidfrgInputs = [cute.composition(t, tv_layout) for t in ctaInputs]
|
| 108 |
+
tidfrgC = cute.composition(ctaC, tv_layout)
|
| 109 |
+
tidfrgCrd = cute.composition(ctaCrd, tv_layout)
|
| 110 |
+
# print(f"{tv_layout = }")
|
| 111 |
+
# print(f"{tidfrgAB[0] = }")
|
| 112 |
+
|
| 113 |
+
thr_coord = (tidx, (None, None))
|
| 114 |
+
|
| 115 |
+
# slice for threads
|
| 116 |
+
# vid -> address
|
| 117 |
+
thrInputs = [t[thr_coord] for t in tidfrgInputs] # (V)
|
| 118 |
+
thrC = tidfrgC[thr_coord] # (V)
|
| 119 |
+
thrCrd = tidfrgCrd[thr_coord]
|
| 120 |
+
|
| 121 |
+
print(f"[DSL INFO] Sliced Tensors per thread:")
|
| 122 |
+
for i in cutlass.range_constexpr(len(thrInputs)):
|
| 123 |
+
print(f"[DSL INFO] thrInputs{i} = {thrInputs[i].type}")
|
| 124 |
+
print(f"[DSL INFO] thrC = {thrC.type}")
|
| 125 |
+
print(f"[DSL INFO] thrCrd = {thrCrd.type}")
|
| 126 |
+
|
| 127 |
+
# allocate fragments for gmem->rmem
|
| 128 |
+
frgInputs = [cute.make_fragment_like(t, t.element_type) for t in thrInputs]
|
| 129 |
+
frgC = cute.make_fragment_like(thrC, gC.element_type)
|
| 130 |
+
frgPred = cute.make_fragment(thrCrd.shape, cutlass.Boolean)
|
| 131 |
+
|
| 132 |
+
for i in cutlass.range(cute.size(frgPred), unroll=1):
|
| 133 |
+
frgPred[i] = cute.elem_less(thrCrd[i], shape)
|
| 134 |
+
|
| 135 |
+
# if tidx == 0 and bidx == 0:
|
| 136 |
+
# cute.print_tensor(frgPred)
|
| 137 |
+
|
| 138 |
+
##########################################################
|
| 139 |
+
# Move data to reg address space
|
| 140 |
+
##########################################################
|
| 141 |
+
|
| 142 |
+
# declare the atoms which will be used later for memory copy
|
| 143 |
+
# Compile time validation: expect same element type for all input tensors so as to reuse the copy atom for load
|
| 144 |
+
assert all(t.element_type == inputs[0].element_type for t in inputs)
|
| 145 |
+
|
| 146 |
+
copy_atom_load = cute.make_copy_atom(
|
| 147 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 148 |
+
inputs[0].element_type,
|
| 149 |
+
num_bits_per_copy=inputs[0].element_type.width,
|
| 150 |
+
)
|
| 151 |
+
copy_atom_store = cute.make_copy_atom(
|
| 152 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 153 |
+
gC.element_type,
|
| 154 |
+
num_bits_per_copy=gC.element_type.width,
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
for thrInput, frgInput in zip(thrInputs, frgInputs):
|
| 158 |
+
cute.copy(copy_atom_load, thrInput, frgInput, pred=frgPred)
|
| 159 |
+
|
| 160 |
+
# Load data before use. The compiler will optimize the copy and load
|
| 161 |
+
# operations to convert some memory ld/st into register uses.
|
| 162 |
+
result = op(*[frgInput.load() for frgInput in frgInputs])
|
| 163 |
+
|
| 164 |
+
# Save the results back to registers. Here we reuse b's registers.
|
| 165 |
+
frgC.store(result)
|
| 166 |
+
|
| 167 |
+
# Copy the results back to c
|
| 168 |
+
cute.copy(copy_atom_store, frgC, thrC, pred=frgPred)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
@cute.jit
|
| 172 |
+
def elementwise_apply(
|
| 173 |
+
op: cutlass.Constexpr,
|
| 174 |
+
a: cute.Tensor,
|
| 175 |
+
b: cute.Tensor,
|
| 176 |
+
result: cute.Tensor,
|
| 177 |
+
stream: cuda.CUstream,
|
| 178 |
+
):
|
| 179 |
+
"""CUDA kernel applying binary operator on each element of two n-D input tensors in
|
| 180 |
+
CuTe Python and store to result tensor.
|
| 181 |
+
|
| 182 |
+
:param op: Binary operator or lambda function to apply element-wise
|
| 183 |
+
:type op: cutlass.Constexpr
|
| 184 |
+
:param a: First input tensor
|
| 185 |
+
:type a: cute.Tensor
|
| 186 |
+
:param b: Second input tensor
|
| 187 |
+
:type b: cute.Tensor
|
| 188 |
+
:param result: Output tensor to store the results of op(a, b)
|
| 189 |
+
:type result: cute.Tensor
|
| 190 |
+
:return: None
|
| 191 |
+
:rtype: None
|
| 192 |
+
|
| 193 |
+
.. code-block:: python
|
| 194 |
+
|
| 195 |
+
# Example 1: Adding two tensors
|
| 196 |
+
x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32, device="cuda")
|
| 197 |
+
y = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32, device="cuda")
|
| 198 |
+
result = torch.empty_like(x)
|
| 199 |
+
elementwise_apply(operator.add, from_dlpack(x), from_dlpack(y), from_dlpack(result))
|
| 200 |
+
# result:
|
| 201 |
+
# tensor([[6.0, 8.0],
|
| 202 |
+
# [10.0, 12.0]], device='cuda:0')
|
| 203 |
+
|
| 204 |
+
# Example 2: Using a lambda function
|
| 205 |
+
elementwise_apply(lambda a, b: a * a + b * b, from_dlpack(x), from_dlpack(y), from_dlpack(result))
|
| 206 |
+
# result:
|
| 207 |
+
# tensor([[ 2., 8.],
|
| 208 |
+
# [ 54., 512.]], device='cuda:0')
|
| 209 |
+
"""
|
| 210 |
+
|
| 211 |
+
# Baseline: naive TV layout
|
| 212 |
+
# * mA layout: (4096, 4096):(4096, 1)
|
| 213 |
+
# * TV layout map to (512, 4) tile
|
| 214 |
+
# * tidx maps to mode-0 but input layout is contiguous on mode-1, performance will be bad
|
| 215 |
+
# tv_layout = cute.make_layout((128, (4, 4)), stride=(4, (512, 1)))
|
| 216 |
+
# cta_tiler = (512, 4)
|
| 217 |
+
|
| 218 |
+
# Opt-1: better TV layout with better 1D thread layout (SOL with 1D thread layout)
|
| 219 |
+
# * mA layout: (4096, 4096):(4096, 1)
|
| 220 |
+
# * TV layout map to (4, 512) tile
|
| 221 |
+
# * tidx maps to mode-1 which is leading mode of input tensor for coalesced load
|
| 222 |
+
# tv_layout = cute.make_layout((128, (4, 4)), stride=(16, (4, 1)))
|
| 223 |
+
# cta_tiler = (4, 512)
|
| 224 |
+
|
| 225 |
+
# Opt-2: 2D tile but worse
|
| 226 |
+
# * mA layout: (4096, 4096):(4096, 1)
|
| 227 |
+
# * TV layout map to (128, 16) logical tile
|
| 228 |
+
# * V layout is bad as contiguous mode is not on right-most
|
| 229 |
+
# * `cute.copy` only supports vectorize when stride-1 of v-layout on right-most )
|
| 230 |
+
# tv_layout = cute.make_layout(((32, 4), (4, 4)), stride=((4, 512), (1, 128)))
|
| 231 |
+
# cta_tiler = (128, 16)
|
| 232 |
+
|
| 233 |
+
# Opt-3: SOL with 2D thread tile
|
| 234 |
+
# * mA layout: (4096, 4096):(4096, 1)
|
| 235 |
+
# * TV layout map to (16, 128) logical tile
|
| 236 |
+
# * tidx maps to mode-1 and input layout is contiguous on mode-1 for coalesced load-store
|
| 237 |
+
thr_layout = cute.make_layout((4, 32), stride=(32, 1))
|
| 238 |
+
val_layout = cute.make_layout((4, 4), stride=(4, 1))
|
| 239 |
+
tiler_mn, tv_layout = cute.make_layout_tv(thr_layout, val_layout)
|
| 240 |
+
|
| 241 |
+
print(f"[DSL INFO] Input Tensors:")
|
| 242 |
+
print(f"[DSL INFO] a = {a.type}")
|
| 243 |
+
print(f"[DSL INFO] b = {b.type}")
|
| 244 |
+
print(f"[DSL INFO] result = {result.type}")
|
| 245 |
+
|
| 246 |
+
print(f"[DSL INFO] Tiling Parameters:")
|
| 247 |
+
print(f"[DSL INFO] tiler_mn = {tiler_mn} per thread block")
|
| 248 |
+
print(f"[DSL INFO] tv_layout = {tv_layout}")
|
| 249 |
+
|
| 250 |
+
gA = cute.zipped_divide(a, tiler_mn) # ((TileM, TileN), (RestM, RestN))
|
| 251 |
+
gB = cute.zipped_divide(b, tiler_mn) # ((TileM, TileN), (RestM, RestN))
|
| 252 |
+
gC = cute.zipped_divide(result, tiler_mn) # ((TileM, TileN), (RestM, RestN))
|
| 253 |
+
|
| 254 |
+
print(f"[DSL INFO] Tiled Tensors:")
|
| 255 |
+
print(f"[DSL INFO] gA = {gA.type}")
|
| 256 |
+
print(f"[DSL INFO] gB = {gB.type}")
|
| 257 |
+
print(f"[DSL INFO] gC = {gC.type}")
|
| 258 |
+
|
| 259 |
+
idC = cute.make_identity_tensor(result.shape)
|
| 260 |
+
cC = cute.zipped_divide(idC, tiler=tiler_mn)
|
| 261 |
+
print(f"[DSL INFO] coord tensor = {cC.type}")
|
| 262 |
+
|
| 263 |
+
# Launch the kernel asynchronously
|
| 264 |
+
# Async token(s) can also be specified as dependencies
|
| 265 |
+
elementwise_apply_kernel(
|
| 266 |
+
op,
|
| 267 |
+
[gA, gB], # Group input tensors into a list as a single argument
|
| 268 |
+
gC,
|
| 269 |
+
cC,
|
| 270 |
+
result.shape,
|
| 271 |
+
tv_layout,
|
| 272 |
+
).launch(
|
| 273 |
+
grid=[cute.size(gC, mode=[1]), 1, 1],
|
| 274 |
+
block=[cute.size(tv_layout, mode=[0]), 1, 1],
|
| 275 |
+
stream=stream,
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def run_elementwise_apply_and_verify(
|
| 280 |
+
op,
|
| 281 |
+
M,
|
| 282 |
+
N,
|
| 283 |
+
dtype: Type[cutlass.Numeric],
|
| 284 |
+
skip_ref_check=False,
|
| 285 |
+
benchmark=True,
|
| 286 |
+
warmup_iterations=2,
|
| 287 |
+
iterations=100,
|
| 288 |
+
):
|
| 289 |
+
if not torch.cuda.is_available():
|
| 290 |
+
raise RuntimeError(f"Ampere GPU is required to run this example!")
|
| 291 |
+
|
| 292 |
+
# Create non default CUDA stream from PyTorch
|
| 293 |
+
torch_stream = torch.cuda.Stream()
|
| 294 |
+
# Get the raw stream pointer as a CUstream
|
| 295 |
+
current_stream = cuda.CUstream(torch_stream.cuda_stream)
|
| 296 |
+
|
| 297 |
+
print(f"\nRunning Elementwise Apply test with:")
|
| 298 |
+
print(f"Tensor dimensions: [{M}, {N}]")
|
| 299 |
+
print(f"Input and Output Data type: {dtype}")
|
| 300 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 301 |
+
print(f"Measurement iterations: {iterations}\n")
|
| 302 |
+
|
| 303 |
+
torch_dtype = cutlass_torch.dtype(dtype)
|
| 304 |
+
|
| 305 |
+
# Allocate tensors with random values.
|
| 306 |
+
a = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
|
| 307 |
+
b = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
|
| 308 |
+
c = torch.zeros_like(a)
|
| 309 |
+
|
| 310 |
+
print(f"Input tensor shapes:")
|
| 311 |
+
print(f"a: {a.shape}, dtype: {a.dtype}")
|
| 312 |
+
print(f"b: {b.shape}, dtype: {b.dtype}")
|
| 313 |
+
print(f"c: {c.shape}, dtype: {c.dtype}\n")
|
| 314 |
+
|
| 315 |
+
epsilon = 1.2
|
| 316 |
+
if op in (operator.truediv, operator.floordiv):
|
| 317 |
+
b = torch.where(b == 0, torch.tensor(epsilon), b)
|
| 318 |
+
|
| 319 |
+
print("Executing elementwise apply kernel...")
|
| 320 |
+
|
| 321 |
+
if not skip_ref_check:
|
| 322 |
+
elementwise_apply(
|
| 323 |
+
op,
|
| 324 |
+
from_dlpack(a),
|
| 325 |
+
from_dlpack(b),
|
| 326 |
+
from_dlpack(c).mark_layout_dynamic(),
|
| 327 |
+
current_stream,
|
| 328 |
+
)
|
| 329 |
+
print("Verifying results...")
|
| 330 |
+
torch.testing.assert_close(op(a, b), c)
|
| 331 |
+
print("Results verified successfully!")
|
| 332 |
+
|
| 333 |
+
if not benchmark:
|
| 334 |
+
return
|
| 335 |
+
|
| 336 |
+
compiled_func = cute.compile(
|
| 337 |
+
elementwise_apply,
|
| 338 |
+
op,
|
| 339 |
+
from_dlpack(a),
|
| 340 |
+
from_dlpack(b),
|
| 341 |
+
from_dlpack(c).mark_layout_dynamic(),
|
| 342 |
+
current_stream,
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
# When compiled we inlined op in the kernel, so we do not pass it when benchmarking
|
| 346 |
+
|
| 347 |
+
avg_time_us = testing.benchmark(
|
| 348 |
+
compiled_func,
|
| 349 |
+
kernel_arguments=testing.JitArguments(
|
| 350 |
+
from_dlpack(a),
|
| 351 |
+
from_dlpack(b),
|
| 352 |
+
from_dlpack(c).mark_layout_dynamic(),
|
| 353 |
+
current_stream,
|
| 354 |
+
),
|
| 355 |
+
warmup_iterations=warmup_iterations,
|
| 356 |
+
iterations=iterations,
|
| 357 |
+
use_cuda_graphs=True,
|
| 358 |
+
stream=current_stream,
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
avg_time = avg_time_us / 1e3
|
| 362 |
+
|
| 363 |
+
# Print execution results
|
| 364 |
+
print(f"Kernel execution time: {avg_time:.4f} ms")
|
| 365 |
+
print(
|
| 366 |
+
f"Achieved memory throughput: {(3 * a.numel() * dtype.width // 8) / (avg_time / 1000) / 1e9:.2f} GB/s"
|
| 367 |
+
)
|
| 368 |
+
print(f"First few elements of result: \n{c[:3, :3]}")
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
if __name__ == "__main__":
|
| 372 |
+
parser = argparse.ArgumentParser(
|
| 373 |
+
description="example of elementwise apply to demonstrate building elementwise kernels"
|
| 374 |
+
)
|
| 375 |
+
parser.add_argument("--M", default=128, type=int)
|
| 376 |
+
parser.add_argument("--N", default=128, type=int)
|
| 377 |
+
parser.add_argument("--op", default="add", type=str)
|
| 378 |
+
parser.add_argument("--warmup_iterations", default=2, type=int)
|
| 379 |
+
parser.add_argument("--iterations", default=100, type=int)
|
| 380 |
+
parser.add_argument("--skip_ref_check", action="store_true")
|
| 381 |
+
parser.add_argument("--benchmark", action="store_true")
|
| 382 |
+
args = parser.parse_args()
|
| 383 |
+
run_elementwise_apply_and_verify(
|
| 384 |
+
getattr(operator, args.op),
|
| 385 |
+
args.M,
|
| 386 |
+
args.N,
|
| 387 |
+
dtype=cutlass.Float32,
|
| 388 |
+
warmup_iterations=args.warmup_iterations,
|
| 389 |
+
iterations=args.iterations,
|
| 390 |
+
skip_ref_check=args.skip_ref_check,
|
| 391 |
+
benchmark=args.benchmark,
|
| 392 |
+
)
|
| 393 |
+
print("\nPASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/flash_attention_v2.py
ADDED
|
@@ -0,0 +1,1334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
from types import SimpleNamespace
|
| 31 |
+
from typing import Type, Union, Callable
|
| 32 |
+
|
| 33 |
+
import torch
|
| 34 |
+
import cuda.bindings.driver as cuda
|
| 35 |
+
import cutlass.cute.testing as testing
|
| 36 |
+
import cutlass
|
| 37 |
+
import cutlass.cute as cute
|
| 38 |
+
from cutlass.cute.nvgpu import cpasync, warp
|
| 39 |
+
import cutlass.torch as cutlass_torch
|
| 40 |
+
from cutlass.cute.runtime import from_dlpack
|
| 41 |
+
import cutlass.utils as utils
|
| 42 |
+
|
| 43 |
+
"""
|
| 44 |
+
A flash attention v2 forward pass example for NVIDIA Ampere SM80 architecture using CUTE DSL.
|
| 45 |
+
|
| 46 |
+
- Matrix Q is BxSqxNxH, B is batch dimension, Sq is query sequence length, N is number of heads, H is head dimension
|
| 47 |
+
- Matrix K is BxSkxNxH, B is batch dimension, Sk is key sequence length, N is number of heads, H is head dimension
|
| 48 |
+
- Matrix V is BxSkxNxH, B is batch dimension, Sk is key sequence length, N is number of heads, H is head dimension
|
| 49 |
+
- Matrix O is BxSqxNxH, B is batch dimension, Sq is query sequence length, N is number of heads, H is head dimension
|
| 50 |
+
|
| 51 |
+
This kernel supports the following features:
|
| 52 |
+
- Utilizes CpAsync for efficient memory operations
|
| 53 |
+
- Utilizes Ampere's tensor core for matrix multiply-accumulate (MMA) operations
|
| 54 |
+
- Utilizes register pipeline to overlap shared memory-to-register transfers with computations.
|
| 55 |
+
- Leverages DSL to implement an integrated online softmax fusion pattern.
|
| 56 |
+
|
| 57 |
+
This kernel works as follows:
|
| 58 |
+
1. Load Q and K matrices from global memory (GMEM) to shared memory (SMEM) using CpAsync operations.
|
| 59 |
+
2. Perform matrix multiply-accumulate (MMA) operations using tensor core instructions to compute intermediate result S.
|
| 60 |
+
3. Apply padding mask or causal mask to S during initial iterations.
|
| 61 |
+
4. Apply online softmax to S and rescale O using results from previous iteration.
|
| 62 |
+
5. Load V matrices and perform matrix multiply-accumulate (MMA) operations to compute final result O.
|
| 63 |
+
6. Normalize O after all iterations complete and store result back to global memory (GMEM).
|
| 64 |
+
|
| 65 |
+
To run this example:
|
| 66 |
+
|
| 67 |
+
.. code-block:: bash
|
| 68 |
+
|
| 69 |
+
python examples/ampere/flash_attention_v2.py \
|
| 70 |
+
--dtype Float16 --head_dim 128 --m_block_size 128 --n_block_size 128 \
|
| 71 |
+
--num_threads 128 --batch_size 1 --seqlen_q 1280 --seqlen_k 1536 \
|
| 72 |
+
--num_head 16 --softmax_scale 1.0 --is_causal
|
| 73 |
+
|
| 74 |
+
The above command configures the model to use float16 for inputs and outputs. The problem dimensions
|
| 75 |
+
are set to: batch size of 1, query sequence length of 1280, key sequence length of 1536, head dimension
|
| 76 |
+
of 128, and 16 attention heads. The softmax scale is set to 1.0 and causal masking is enabled. The computation
|
| 77 |
+
uses tiles of size 128x128 for m and n dimensions, and utilizes 128 parallel threads.
|
| 78 |
+
|
| 79 |
+
To collect the performance with NCU profiler:
|
| 80 |
+
|
| 81 |
+
.. code-block:: bash
|
| 82 |
+
|
| 83 |
+
ncu python examples/ampere/flash_attention_v2.py \
|
| 84 |
+
--dtype Float16 --head_dim 128 --m_block_size 128 --n_block_size 128 \
|
| 85 |
+
--num_threads 128 --batch_size 1 --seqlen_q 1280 --seqlen_k 1536 \
|
| 86 |
+
--num_head 16 --softmax_scale 1.0 --is_causal --skip_ref_check
|
| 87 |
+
|
| 88 |
+
There are some constraints for this example:
|
| 89 |
+
* Only fp16 and bf16 data types are supported.
|
| 90 |
+
* The contiguous dimension of each tensor must be at least 16 bytes aligned.
|
| 91 |
+
* The log-sum-exp(for training) is not computed in the kernel.
|
| 92 |
+
* The values of `m_block_size`, `n_block_size`, and `head_dim` must be selected to stay within shared memory capacity limits.
|
| 93 |
+
* `m_block_size * 2` must be divisible by `num_threads`, otherwise the kernel will not be able to get the correct result.
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class FlashAttentionForwardAmpere:
|
| 98 |
+
def __init__(
|
| 99 |
+
self,
|
| 100 |
+
head_dim: int,
|
| 101 |
+
m_block_size: int = 128,
|
| 102 |
+
n_block_size: int = 128,
|
| 103 |
+
num_threads: int = 128,
|
| 104 |
+
is_causal: bool = False,
|
| 105 |
+
):
|
| 106 |
+
"""Initializes the configuration for a flash attention v2 kernel.
|
| 107 |
+
|
| 108 |
+
All contiguous dimensions must be at least 16 bytes aligned which indicates the head dimension
|
| 109 |
+
should be a multiple of 8.
|
| 110 |
+
|
| 111 |
+
:param head_dim: head dimension
|
| 112 |
+
:type head_dim: int
|
| 113 |
+
:param m_block_size: m block size
|
| 114 |
+
:type m_block_size: int
|
| 115 |
+
:param n_block_size: n block size
|
| 116 |
+
:type n_block_size: int
|
| 117 |
+
:param num_threads: number of threads
|
| 118 |
+
:type num_threads: int
|
| 119 |
+
:param is_causal: is causal
|
| 120 |
+
"""
|
| 121 |
+
self._head_dim = head_dim
|
| 122 |
+
self._m_block_size = m_block_size
|
| 123 |
+
self._n_block_size = n_block_size
|
| 124 |
+
# padding head_dim to a multiple of 32 as k_block_size
|
| 125 |
+
self._head_dim_padded = (head_dim + 31) // 32 * 32
|
| 126 |
+
self._num_threads = num_threads
|
| 127 |
+
self._is_causal = is_causal
|
| 128 |
+
|
| 129 |
+
@staticmethod
|
| 130 |
+
def can_implement(
|
| 131 |
+
dtype, head_dim, m_block_size, n_block_size, num_threads, is_causal
|
| 132 |
+
) -> bool:
|
| 133 |
+
"""Check if the kernel can be implemented with the given parameters.
|
| 134 |
+
|
| 135 |
+
:param dtype: data type
|
| 136 |
+
:type dtype: cutlass.Numeric
|
| 137 |
+
:param head_dim: head dimension
|
| 138 |
+
:type head_dim: int
|
| 139 |
+
:param m_block_size: m block size
|
| 140 |
+
:type m_block_size: int
|
| 141 |
+
:param n_block_size: n block size
|
| 142 |
+
:type n_block_size: int
|
| 143 |
+
:param num_threads: number of threads
|
| 144 |
+
:type num_threads: int
|
| 145 |
+
:param is_causal: is causal
|
| 146 |
+
:type is_causal: bool
|
| 147 |
+
|
| 148 |
+
:return: True if the kernel can be implemented, False otherwise
|
| 149 |
+
:rtype: bool
|
| 150 |
+
"""
|
| 151 |
+
# Check if data type is fp16 or bf16
|
| 152 |
+
if dtype != cutlass.Float16 and dtype != cutlass.BFloat16:
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
# Check if head dimension is a multiple of 8
|
| 156 |
+
if head_dim % 8 != 0:
|
| 157 |
+
return False
|
| 158 |
+
|
| 159 |
+
# Check if number of threads is a multiple of 32
|
| 160 |
+
if num_threads % 32 != 0:
|
| 161 |
+
return False
|
| 162 |
+
|
| 163 |
+
# Check if block size setting is out of shared memory capacity
|
| 164 |
+
# Shared memory usage: Q tile + (K tile + V tile) where K and V use the same tile size
|
| 165 |
+
smem_usage = (m_block_size * head_dim + n_block_size * head_dim * 2) * 2
|
| 166 |
+
smem_capacity = utils.get_smem_capacity_in_bytes("sm_80")
|
| 167 |
+
if smem_usage > smem_capacity:
|
| 168 |
+
return False
|
| 169 |
+
|
| 170 |
+
# Check if twice the block size is divisible by the number of threads
|
| 171 |
+
if (m_block_size * 2) % num_threads != 0:
|
| 172 |
+
return False
|
| 173 |
+
|
| 174 |
+
return True
|
| 175 |
+
|
| 176 |
+
@cute.jit
|
| 177 |
+
def __call__(
|
| 178 |
+
self,
|
| 179 |
+
mQ: cute.Tensor,
|
| 180 |
+
mK: cute.Tensor,
|
| 181 |
+
mV: cute.Tensor,
|
| 182 |
+
mO: cute.Tensor,
|
| 183 |
+
softmax_scale: cutlass.Float32,
|
| 184 |
+
stream: cuda.CUstream,
|
| 185 |
+
):
|
| 186 |
+
"""Configures and launches the flash attention v2 kernel.
|
| 187 |
+
|
| 188 |
+
mQ/mK/mV/mO has same data types(supports fp16 and bf16) and same layout:
|
| 189 |
+
(batch_size, seqlen_q, num_head, head_dim):(seqlen_q * num_head * head_dim, num_head * head_dim, head_dim, 1)
|
| 190 |
+
|
| 191 |
+
Prepares the shared memory layout, tiled copy atoms, tiled mma and shared memory storage.
|
| 192 |
+
Then launches the kernel function with the prepared parameters.
|
| 193 |
+
|
| 194 |
+
:param mQ: query tensor
|
| 195 |
+
:type mQ: cute.Tensor
|
| 196 |
+
:param mK: key tensor
|
| 197 |
+
:type mK: cute.Tensor
|
| 198 |
+
:param mV: value tensor
|
| 199 |
+
:type mV: cute.Tensor
|
| 200 |
+
:param mO: output tensor
|
| 201 |
+
:type mO: cute.Tensor
|
| 202 |
+
:param softmax_scale: softmax scale
|
| 203 |
+
:type softmax_scale: cutlass.Float32
|
| 204 |
+
"""
|
| 205 |
+
# Get the data type and check if it is fp16 or bf16
|
| 206 |
+
if cutlass.const_expr(
|
| 207 |
+
not (
|
| 208 |
+
mQ.element_type == mK.element_type == mV.element_type == mO.element_type
|
| 209 |
+
)
|
| 210 |
+
):
|
| 211 |
+
raise TypeError("All tensors must have the same data type")
|
| 212 |
+
if cutlass.const_expr(
|
| 213 |
+
not (
|
| 214 |
+
mQ.element_type == cutlass.Float16
|
| 215 |
+
or mQ.element_type == cutlass.BFloat16
|
| 216 |
+
)
|
| 217 |
+
):
|
| 218 |
+
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 219 |
+
self._dtype: Type[cutlass.Numeric] = mQ.element_type
|
| 220 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 221 |
+
# Shared memory layout: Q/K/V
|
| 222 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 223 |
+
smem_k_block_size = 64 if self._head_dim_padded % 64 == 0 else 32
|
| 224 |
+
swizzle_bits = 3 if smem_k_block_size == 64 else 2
|
| 225 |
+
sQ_layout_atom = cute.make_composed_layout(
|
| 226 |
+
cute.make_swizzle(swizzle_bits, 3, 3),
|
| 227 |
+
0,
|
| 228 |
+
cute.make_layout((8, smem_k_block_size), stride=(smem_k_block_size, 1)),
|
| 229 |
+
)
|
| 230 |
+
sQ_layout = cute.tile_to_shape(
|
| 231 |
+
sQ_layout_atom,
|
| 232 |
+
(self._m_block_size, self._head_dim_padded),
|
| 233 |
+
(0, 1),
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
sKV_layout_atom = sQ_layout_atom
|
| 237 |
+
sKV_layout = cute.tile_to_shape(
|
| 238 |
+
sKV_layout_atom,
|
| 239 |
+
(self._n_block_size, self._head_dim_padded),
|
| 240 |
+
(0, 1),
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
sO_layout = sQ_layout
|
| 244 |
+
|
| 245 |
+
@cute.struct
|
| 246 |
+
class SharedStorage:
|
| 247 |
+
sQ: cute.struct.Align[
|
| 248 |
+
cute.struct.MemRange[self._dtype, cute.cosize(sQ_layout)], 1024
|
| 249 |
+
]
|
| 250 |
+
sK: cute.struct.Align[
|
| 251 |
+
cute.struct.MemRange[self._dtype, cute.cosize(sKV_layout)], 1024
|
| 252 |
+
]
|
| 253 |
+
sV: cute.struct.Align[
|
| 254 |
+
cute.struct.MemRange[self._dtype, cute.cosize(sKV_layout)], 1024
|
| 255 |
+
]
|
| 256 |
+
|
| 257 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 258 |
+
# GMEM Tiled copy:
|
| 259 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 260 |
+
# Thread layouts for copies
|
| 261 |
+
universal_copy_bits = 128
|
| 262 |
+
async_copy_elems = universal_copy_bits // self._dtype.width
|
| 263 |
+
# atom_async_copy: async copy atom for QKV load
|
| 264 |
+
atom_async_copy = cute.make_copy_atom(
|
| 265 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 266 |
+
self._dtype,
|
| 267 |
+
num_bits_per_copy=universal_copy_bits,
|
| 268 |
+
)
|
| 269 |
+
# atom_universal_copy: universal copy atom for O store
|
| 270 |
+
atom_universal_copy = cute.make_copy_atom(
|
| 271 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 272 |
+
self._dtype,
|
| 273 |
+
num_bits_per_copy=universal_copy_bits,
|
| 274 |
+
)
|
| 275 |
+
# tQKV_layout: thread layout for QKV load
|
| 276 |
+
tQKV_shape_dim_1 = sQ_layout_atom.outer.shape[1] // async_copy_elems
|
| 277 |
+
tQKV_layout = cute.make_layout(
|
| 278 |
+
(self._num_threads // tQKV_shape_dim_1, tQKV_shape_dim_1),
|
| 279 |
+
stride=(tQKV_shape_dim_1, 1),
|
| 280 |
+
)
|
| 281 |
+
# tO_layout: thread layout for O store
|
| 282 |
+
tO_layout = tQKV_layout
|
| 283 |
+
|
| 284 |
+
# Value layouts for copies
|
| 285 |
+
vQKV_layout = cute.make_layout((1, async_copy_elems))
|
| 286 |
+
vO_layout = vQKV_layout
|
| 287 |
+
|
| 288 |
+
# gmem_tiled_copy_QKV: tiled copy for QKV load
|
| 289 |
+
gmem_tiled_copy_QKV = cute.make_tiled_copy_tv(
|
| 290 |
+
atom_async_copy, tQKV_layout, vQKV_layout
|
| 291 |
+
)
|
| 292 |
+
# gmem_tiled_copy_O: tiled copy for O store
|
| 293 |
+
gmem_tiled_copy_O = cute.make_tiled_copy_tv(
|
| 294 |
+
atom_universal_copy, tO_layout, vO_layout
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 298 |
+
# Tiled mma
|
| 299 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 300 |
+
tiled_mma = cute.make_tiled_mma(
|
| 301 |
+
warp.MmaF16BF16Op(self._dtype, cutlass.Float32, (16, 8, 16)),
|
| 302 |
+
(self._num_threads // 32, 1, 1),
|
| 303 |
+
permutation_mnk=(self._num_threads // 32 * 16, 16, 16),
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
# grid_dim: (m_block, batch_size, num_head)
|
| 307 |
+
grid_dim = (
|
| 308 |
+
cute.ceil_div(mQ.shape[1], self._m_block_size),
|
| 309 |
+
cute.size(mQ.shape[0]),
|
| 310 |
+
cute.size(mQ.shape[2]),
|
| 311 |
+
)
|
| 312 |
+
LOG2_E = 1.4426950408889634074
|
| 313 |
+
softmax_scale_log2 = softmax_scale * LOG2_E
|
| 314 |
+
self.kernel(
|
| 315 |
+
mQ,
|
| 316 |
+
mK,
|
| 317 |
+
mV,
|
| 318 |
+
mO,
|
| 319 |
+
softmax_scale_log2,
|
| 320 |
+
sQ_layout,
|
| 321 |
+
sKV_layout,
|
| 322 |
+
sO_layout,
|
| 323 |
+
gmem_tiled_copy_QKV,
|
| 324 |
+
gmem_tiled_copy_O,
|
| 325 |
+
tiled_mma,
|
| 326 |
+
SharedStorage,
|
| 327 |
+
).launch(
|
| 328 |
+
grid=grid_dim,
|
| 329 |
+
block=[self._num_threads, 1, 1],
|
| 330 |
+
stream=stream,
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
@cute.kernel
|
| 334 |
+
def kernel(
|
| 335 |
+
self,
|
| 336 |
+
mQ: cute.Tensor,
|
| 337 |
+
mK: cute.Tensor,
|
| 338 |
+
mV: cute.Tensor,
|
| 339 |
+
mO: cute.Tensor,
|
| 340 |
+
softmax_scale_log2: cutlass.Float32,
|
| 341 |
+
sQ_layout: cute.ComposedLayout,
|
| 342 |
+
sKV_layout: cute.ComposedLayout,
|
| 343 |
+
sO_layout: cute.ComposedLayout,
|
| 344 |
+
gmem_tiled_copy_QKV: cute.TiledCopy,
|
| 345 |
+
gmem_tiled_copy_O: cute.TiledCopy,
|
| 346 |
+
tiled_mma: cute.TiledMma,
|
| 347 |
+
SharedStorage: cutlass.Constexpr,
|
| 348 |
+
):
|
| 349 |
+
"""Kernel function for flash attention v2.
|
| 350 |
+
|
| 351 |
+
:param mQ: query tensor
|
| 352 |
+
:type mQ: cute.Tensor
|
| 353 |
+
:param mK: key tensor
|
| 354 |
+
:type mK: cute.Tensor
|
| 355 |
+
:param mV: value tensor
|
| 356 |
+
:type mV: cute.Tensor
|
| 357 |
+
:param mO: output tensor
|
| 358 |
+
:type mO: cute.Tensor
|
| 359 |
+
:param softmax_scale_log2: softmax scale log2
|
| 360 |
+
:type softmax_scale_log2: cutlass.Float32
|
| 361 |
+
:param sQ_layout: query layout
|
| 362 |
+
:type sQ_layout: cute.ComposedLayout
|
| 363 |
+
:param sKV_layout: key/value layout
|
| 364 |
+
:type sKV_layout: cute.ComposedLayout
|
| 365 |
+
:param sO_layout: output layout
|
| 366 |
+
:type sO_layout: cute.ComposedLayout
|
| 367 |
+
:param gmem_tiled_copy_QKV: tiled copy for QKV load
|
| 368 |
+
:type gmem_tiled_copy_QKV: cute.TiledCopy
|
| 369 |
+
:param gmem_tiled_copy_O: tiled copy for O store
|
| 370 |
+
:type gmem_tiled_copy_O: cute.TiledCopy
|
| 371 |
+
:param tiled_mma: tiled mma
|
| 372 |
+
:type tiled_mma: cute.TiledMma
|
| 373 |
+
:param SharedStorage: shared storage
|
| 374 |
+
:type SharedStorage: cutlass.Constexpr
|
| 375 |
+
"""
|
| 376 |
+
# Thread index, block index
|
| 377 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 378 |
+
m_block, batch_size, num_head = cute.arch.block_idx()
|
| 379 |
+
|
| 380 |
+
n_block_max = cute.ceil_div(mK.shape[1], self._n_block_size)
|
| 381 |
+
if self._is_causal:
|
| 382 |
+
n_block_max = min(
|
| 383 |
+
cute.ceil_div(
|
| 384 |
+
(m_block + 1) * self._m_block_size,
|
| 385 |
+
self._n_block_size,
|
| 386 |
+
),
|
| 387 |
+
n_block_max,
|
| 388 |
+
)
|
| 389 |
+
n_block = n_block_max - 1
|
| 390 |
+
|
| 391 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 392 |
+
# Get the appropriate tiles for this thread block.
|
| 393 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 394 |
+
# (m_block_size, head_dim)
|
| 395 |
+
gQ = cute.local_tile(
|
| 396 |
+
mQ[batch_size, None, num_head, None],
|
| 397 |
+
(self._m_block_size, self._head_dim_padded),
|
| 398 |
+
(m_block, 0),
|
| 399 |
+
)
|
| 400 |
+
# (n_block_size, head_dim, n_block)
|
| 401 |
+
gK = cute.local_tile(
|
| 402 |
+
mK[batch_size, None, num_head, None],
|
| 403 |
+
(self._n_block_size, self._head_dim_padded),
|
| 404 |
+
(None, 0),
|
| 405 |
+
)
|
| 406 |
+
# (n_block_size, head_dim, n_block)
|
| 407 |
+
gV = cute.local_tile(
|
| 408 |
+
mV[batch_size, None, num_head, None],
|
| 409 |
+
(self._n_block_size, self._head_dim_padded),
|
| 410 |
+
(None, 0),
|
| 411 |
+
)
|
| 412 |
+
|
| 413 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 414 |
+
# Get shared memory buffer
|
| 415 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 416 |
+
smem = cutlass.utils.SmemAllocator()
|
| 417 |
+
|
| 418 |
+
storage = smem.allocate(SharedStorage)
|
| 419 |
+
sQ = storage.sQ.get_tensor(sQ_layout)
|
| 420 |
+
sK = storage.sK.get_tensor(sKV_layout)
|
| 421 |
+
sV = storage.sV.get_tensor(sKV_layout)
|
| 422 |
+
|
| 423 |
+
# Transpose view of V to tensor with layout (head_dim, n_block_size) for tiled mma
|
| 424 |
+
sVt = cute.composition(
|
| 425 |
+
sV,
|
| 426 |
+
cute.make_layout(
|
| 427 |
+
(self._head_dim_padded, self._n_block_size),
|
| 428 |
+
stride=(self._n_block_size, 1),
|
| 429 |
+
),
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
gmem_thr_copy_QKV = gmem_tiled_copy_QKV.get_slice(tidx)
|
| 433 |
+
# (CPY_Atom, CPY_M, CPY_K)
|
| 434 |
+
tQgQ = gmem_thr_copy_QKV.partition_S(gQ)
|
| 435 |
+
tQsQ = gmem_thr_copy_QKV.partition_D(sQ)
|
| 436 |
+
# (CPY_Atom, CPY_N, CPY_K, n_block)
|
| 437 |
+
tKgK = gmem_thr_copy_QKV.partition_S(gK)
|
| 438 |
+
tKsK = gmem_thr_copy_QKV.partition_D(sK)
|
| 439 |
+
# (CPY_Atom, CPY_N, CPY_K, n_block)
|
| 440 |
+
tVgV = gmem_thr_copy_QKV.partition_S(gV)
|
| 441 |
+
tVsV = gmem_thr_copy_QKV.partition_D(sV)
|
| 442 |
+
|
| 443 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 444 |
+
# Tile MMA compute thread partitions and allocate accumulators
|
| 445 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 446 |
+
thr_mma = tiled_mma.get_slice(tidx)
|
| 447 |
+
tSrQ = thr_mma.make_fragment_A(thr_mma.partition_A(sQ))
|
| 448 |
+
tSrK = thr_mma.make_fragment_B(thr_mma.partition_B(sK))
|
| 449 |
+
tOrVt = thr_mma.make_fragment_B(thr_mma.partition_B(sVt))
|
| 450 |
+
acc_shape_O = thr_mma.partition_shape_C(
|
| 451 |
+
(self._m_block_size, self._head_dim_padded)
|
| 452 |
+
)
|
| 453 |
+
acc_O = cute.make_fragment(acc_shape_O, cutlass.Float32)
|
| 454 |
+
acc_O.fill(0.0)
|
| 455 |
+
|
| 456 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 457 |
+
# Smem copy atom tiling
|
| 458 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 459 |
+
smem_copy_atom_Q = cute.make_copy_atom(
|
| 460 |
+
warp.LdMatrix8x8x16bOp(transpose=False, num_matrices=4),
|
| 461 |
+
self._dtype,
|
| 462 |
+
)
|
| 463 |
+
smem_copy_atom_K = cute.make_copy_atom(
|
| 464 |
+
warp.LdMatrix8x8x16bOp(transpose=False, num_matrices=4),
|
| 465 |
+
self._dtype,
|
| 466 |
+
)
|
| 467 |
+
smem_copy_atom_V = cute.make_copy_atom(
|
| 468 |
+
warp.LdMatrix8x8x16bOp(transpose=True, num_matrices=4),
|
| 469 |
+
self._dtype,
|
| 470 |
+
)
|
| 471 |
+
smem_tiled_copy_Q = cute.make_tiled_copy_A(smem_copy_atom_Q, tiled_mma)
|
| 472 |
+
smem_tiled_copy_K = cute.make_tiled_copy_B(smem_copy_atom_K, tiled_mma)
|
| 473 |
+
smem_tiled_copy_V = cute.make_tiled_copy_B(smem_copy_atom_V, tiled_mma)
|
| 474 |
+
|
| 475 |
+
smem_thr_copy_Q = smem_tiled_copy_Q.get_slice(tidx)
|
| 476 |
+
smem_thr_copy_K = smem_tiled_copy_K.get_slice(tidx)
|
| 477 |
+
smem_thr_copy_V = smem_tiled_copy_V.get_slice(tidx)
|
| 478 |
+
|
| 479 |
+
tSsQ = smem_thr_copy_Q.partition_S(sQ)
|
| 480 |
+
tSrQ_copy_view = smem_thr_copy_Q.retile(tSrQ)
|
| 481 |
+
tSsK = smem_thr_copy_K.partition_S(sK)
|
| 482 |
+
tSrK_copy_view = smem_thr_copy_K.retile(tSrK)
|
| 483 |
+
tOsVt = smem_thr_copy_V.partition_S(sVt)
|
| 484 |
+
tOrVt_copy_view = smem_thr_copy_V.retile(tOrVt)
|
| 485 |
+
|
| 486 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 487 |
+
# Predicate: Mark indices that need to copy when problem_shape isn't a multiple
|
| 488 |
+
# of tile_shape
|
| 489 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 490 |
+
# Construct identity layout for Q and KV
|
| 491 |
+
mcQ = cute.make_identity_tensor(mQ.layout.shape)
|
| 492 |
+
mcKV = cute.make_identity_tensor(mK.layout.shape)
|
| 493 |
+
cQ = cute.local_tile(
|
| 494 |
+
mcQ[batch_size, None, num_head, None],
|
| 495 |
+
(self._m_block_size, self._head_dim_padded),
|
| 496 |
+
(m_block, 0),
|
| 497 |
+
)
|
| 498 |
+
cKV = cute.local_tile(
|
| 499 |
+
mcKV[batch_size, None, num_head, None],
|
| 500 |
+
(self._n_block_size, self._head_dim_padded),
|
| 501 |
+
(n_block, 0),
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
# Repeat the partitioning with identity layouts
|
| 505 |
+
tQcQ = gmem_thr_copy_QKV.partition_S(cQ)
|
| 506 |
+
tKVcKV = gmem_thr_copy_QKV.partition_S(cKV)
|
| 507 |
+
# Allocate predicate tensors for m and n, here we only allocate the tile of k, and do special process for mn.
|
| 508 |
+
# This is to reduce register pressure and gets 2-3% performance gain compared with allocating the whole tile.
|
| 509 |
+
tQpQ = cute.make_fragment(
|
| 510 |
+
cute.make_layout(
|
| 511 |
+
(
|
| 512 |
+
tQsQ.shape[0][1],
|
| 513 |
+
cute.size(tQsQ, mode=[1]),
|
| 514 |
+
cute.size(tQsQ, mode=[2]),
|
| 515 |
+
),
|
| 516 |
+
stride=(cute.size(tQsQ, mode=[2]), 0, 1),
|
| 517 |
+
),
|
| 518 |
+
cutlass.Boolean,
|
| 519 |
+
)
|
| 520 |
+
tKVpKV = cute.make_fragment(
|
| 521 |
+
cute.make_layout(
|
| 522 |
+
(
|
| 523 |
+
tKsK.shape[0][1],
|
| 524 |
+
cute.size(tKsK, mode=[1]),
|
| 525 |
+
cute.size(tKsK, mode=[2]),
|
| 526 |
+
),
|
| 527 |
+
stride=(cute.size(tKsK, mode=[2]), 0, 1),
|
| 528 |
+
),
|
| 529 |
+
cutlass.Boolean,
|
| 530 |
+
)
|
| 531 |
+
# Set predicates for head_dim bounds, seqlen_q/k bounds is processed at the first tile.
|
| 532 |
+
for rest_v in cutlass.range_constexpr(tQpQ.shape[0]):
|
| 533 |
+
for rest_k in cutlass.range_constexpr(tQpQ.shape[2]):
|
| 534 |
+
tQpQ[rest_v, 0, rest_k] = cute.elem_less(
|
| 535 |
+
tQcQ[(0, rest_v), 0, rest_k][3], mQ.layout.shape[3]
|
| 536 |
+
)
|
| 537 |
+
for rest_v in cutlass.range_constexpr(tKVpKV.shape[0]):
|
| 538 |
+
for rest_k in cutlass.range_constexpr(tKVpKV.shape[2]):
|
| 539 |
+
tKVpKV[rest_v, 0, rest_k] = cute.elem_less(
|
| 540 |
+
tKVcKV[(0, rest_v), 0, rest_k][3], mK.layout.shape[3]
|
| 541 |
+
)
|
| 542 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 543 |
+
# Prefetch Prologue
|
| 544 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 545 |
+
# Start async loads of the last mn-tile, where we take care of the mn residue
|
| 546 |
+
for m in cutlass.range_constexpr(cute.size(tQsQ.shape[1])):
|
| 547 |
+
if cute.elem_less(tQcQ[0, m, 0][1], mQ.layout.shape[1]):
|
| 548 |
+
cute.copy(
|
| 549 |
+
gmem_tiled_copy_QKV,
|
| 550 |
+
tQgQ[None, m, None],
|
| 551 |
+
tQsQ[None, m, None],
|
| 552 |
+
pred=tQpQ[None, m, None],
|
| 553 |
+
)
|
| 554 |
+
else:
|
| 555 |
+
# Clear the smem tiles to account for predicated off loads
|
| 556 |
+
tQsQ[None, m, None].fill(0)
|
| 557 |
+
for n in cutlass.range_constexpr(cute.size(tKsK.shape[1])):
|
| 558 |
+
if cute.elem_less(tKVcKV[0, n, 0][1], mK.layout.shape[1]):
|
| 559 |
+
cute.copy(
|
| 560 |
+
gmem_tiled_copy_QKV,
|
| 561 |
+
tKgK[None, n, None, n_block],
|
| 562 |
+
tKsK[None, n, None],
|
| 563 |
+
pred=tKVpKV[None, n, None],
|
| 564 |
+
)
|
| 565 |
+
else:
|
| 566 |
+
# Clear the smem tiles to account for predicated off loads
|
| 567 |
+
tKsK[None, n, None].fill(0)
|
| 568 |
+
|
| 569 |
+
cute.arch.cp_async_commit_group()
|
| 570 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 571 |
+
# Softmax intermediate result: row_max and row_sum
|
| 572 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 573 |
+
# shape: (atom_v_m * rest_m)
|
| 574 |
+
row_max = cute.make_fragment(
|
| 575 |
+
(acc_O.shape[0][0] * acc_O.shape[1]), cutlass.Float32
|
| 576 |
+
)
|
| 577 |
+
# shape: (atom_v_m * rest_m)
|
| 578 |
+
row_sum = cute.make_fragment(
|
| 579 |
+
(acc_O.shape[0][0] * acc_O.shape[1]), cutlass.Float32
|
| 580 |
+
)
|
| 581 |
+
row_max.fill(-cutlass.Float32.inf)
|
| 582 |
+
row_sum.fill(0.0)
|
| 583 |
+
|
| 584 |
+
# group parameters for compute_one_n_block
|
| 585 |
+
basic_params = SimpleNamespace(
|
| 586 |
+
m_block=m_block,
|
| 587 |
+
n_block=n_block,
|
| 588 |
+
mQ=mQ,
|
| 589 |
+
mK=mK,
|
| 590 |
+
batch_size=batch_size,
|
| 591 |
+
num_head=num_head,
|
| 592 |
+
)
|
| 593 |
+
mma_params = SimpleNamespace(
|
| 594 |
+
thr_mma=thr_mma,
|
| 595 |
+
tiled_mma=tiled_mma,
|
| 596 |
+
tSrQ=tSrQ,
|
| 597 |
+
tSrK=tSrK,
|
| 598 |
+
tOrVt=tOrVt,
|
| 599 |
+
acc_O=acc_O,
|
| 600 |
+
)
|
| 601 |
+
gmem_copy_params = SimpleNamespace(
|
| 602 |
+
gmem_tiled_copy_QKV=gmem_tiled_copy_QKV,
|
| 603 |
+
tKVcKV=tKVcKV,
|
| 604 |
+
tKgK=tKgK,
|
| 605 |
+
tKsK=tKsK,
|
| 606 |
+
tVgV=tVgV,
|
| 607 |
+
tVsV=tVsV,
|
| 608 |
+
tKVpKV=tKVpKV,
|
| 609 |
+
)
|
| 610 |
+
smem_copy_params = SimpleNamespace(
|
| 611 |
+
smem_tiled_copy_Q=smem_tiled_copy_Q,
|
| 612 |
+
smem_tiled_copy_K=smem_tiled_copy_K,
|
| 613 |
+
smem_tiled_copy_V=smem_tiled_copy_V,
|
| 614 |
+
tSsQ=tSsQ,
|
| 615 |
+
tSrQ_copy_view=tSrQ_copy_view,
|
| 616 |
+
tSsK=tSsK,
|
| 617 |
+
tSrK_copy_view=tSrK_copy_view,
|
| 618 |
+
tOsVt=tOsVt,
|
| 619 |
+
tOrVt_copy_view=tOrVt_copy_view,
|
| 620 |
+
)
|
| 621 |
+
softmax_params = SimpleNamespace(
|
| 622 |
+
row_max=row_max,
|
| 623 |
+
row_sum=row_sum,
|
| 624 |
+
softmax_scale_log2=softmax_scale_log2,
|
| 625 |
+
)
|
| 626 |
+
|
| 627 |
+
# Start processing of the first n-block.
|
| 628 |
+
# For performance reason, we separate out two kinds of iterations:
|
| 629 |
+
# those that need masking on S, and those that don't.
|
| 630 |
+
# We need masking on S for the very last block when K and V has length not multiple of n_block_size.
|
| 631 |
+
# We also need masking on S if it's causal, for the last ceil_div(m_block_size, n_block_size) blocks.
|
| 632 |
+
# We will have at least 1 "masking" iteration.
|
| 633 |
+
mask_steps = 1
|
| 634 |
+
if cutlass.const_expr(self._is_causal):
|
| 635 |
+
mask_steps = cute.ceil_div(self._m_block_size, self._n_block_size)
|
| 636 |
+
|
| 637 |
+
for n_tile in cutlass.range_constexpr(mask_steps):
|
| 638 |
+
n_block = n_block_max - n_tile - 1
|
| 639 |
+
basic_params.n_block = n_block
|
| 640 |
+
if cutlass.const_expr(self._is_causal):
|
| 641 |
+
if n_block >= 0:
|
| 642 |
+
self.compute_one_n_block(
|
| 643 |
+
basic_params,
|
| 644 |
+
mma_params,
|
| 645 |
+
gmem_copy_params,
|
| 646 |
+
smem_copy_params,
|
| 647 |
+
softmax_params,
|
| 648 |
+
is_first_n_block=(n_tile == 0),
|
| 649 |
+
in_mask_steps=True,
|
| 650 |
+
)
|
| 651 |
+
else:
|
| 652 |
+
self.compute_one_n_block(
|
| 653 |
+
basic_params,
|
| 654 |
+
mma_params,
|
| 655 |
+
gmem_copy_params,
|
| 656 |
+
smem_copy_params,
|
| 657 |
+
softmax_params,
|
| 658 |
+
is_first_n_block=True,
|
| 659 |
+
in_mask_steps=True,
|
| 660 |
+
)
|
| 661 |
+
|
| 662 |
+
# Start async loads of rest k-tiles in reverse order, no k-residue handling needed
|
| 663 |
+
for n_tile in range(mask_steps, n_block_max, 1):
|
| 664 |
+
n_block = n_block_max - n_tile - 1
|
| 665 |
+
basic_params.n_block = n_block
|
| 666 |
+
self.compute_one_n_block(
|
| 667 |
+
basic_params,
|
| 668 |
+
mma_params,
|
| 669 |
+
gmem_copy_params,
|
| 670 |
+
smem_copy_params,
|
| 671 |
+
softmax_params,
|
| 672 |
+
is_first_n_block=False,
|
| 673 |
+
in_mask_steps=False,
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 677 |
+
# Epilogue
|
| 678 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 679 |
+
# normalize acc_O by row_sum and calculate the lse
|
| 680 |
+
self.normalize_softmax(acc_O, row_sum)
|
| 681 |
+
# store acc_O
|
| 682 |
+
rO = cute.make_fragment_like(acc_O, self._dtype)
|
| 683 |
+
rO.store(acc_O.load().to(self._dtype))
|
| 684 |
+
# reuse sQ's data iterator
|
| 685 |
+
sO = cute.make_tensor(sQ.iterator, sO_layout)
|
| 686 |
+
|
| 687 |
+
# smem copy atom for O
|
| 688 |
+
smem_copy_atom_O = cute.make_copy_atom(
|
| 689 |
+
cute.nvgpu.CopyUniversalOp(), self._dtype
|
| 690 |
+
)
|
| 691 |
+
# tiled copy atom for O
|
| 692 |
+
smem_tiled_copy_O = cute.make_tiled_copy_C(smem_copy_atom_O, tiled_mma)
|
| 693 |
+
smem_thr_copy_O = smem_tiled_copy_O.get_slice(tidx)
|
| 694 |
+
taccOrO = smem_thr_copy_O.retile(rO)
|
| 695 |
+
taccOsO = smem_thr_copy_O.partition_D(sO)
|
| 696 |
+
# copy acc O from rmem to smem with the smem copy atom
|
| 697 |
+
cute.copy(
|
| 698 |
+
smem_copy_atom_O,
|
| 699 |
+
taccOrO,
|
| 700 |
+
taccOsO,
|
| 701 |
+
)
|
| 702 |
+
gO = cute.local_tile(
|
| 703 |
+
mO[batch_size, None, num_head, None],
|
| 704 |
+
(self._m_block_size, self._head_dim_padded),
|
| 705 |
+
(m_block, 0),
|
| 706 |
+
)
|
| 707 |
+
|
| 708 |
+
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 709 |
+
tOsO = gmem_thr_copy_O.partition_S(sO)
|
| 710 |
+
tOgO = gmem_thr_copy_O.partition_D(gO)
|
| 711 |
+
tOrO = cute.make_fragment_like(tOgO, self._dtype)
|
| 712 |
+
# sync before all smem stores are done.
|
| 713 |
+
cute.arch.barrier()
|
| 714 |
+
# load acc O from smem to rmem for wider vectorization
|
| 715 |
+
cute.copy(
|
| 716 |
+
gmem_tiled_copy_O,
|
| 717 |
+
tOsO,
|
| 718 |
+
tOrO,
|
| 719 |
+
)
|
| 720 |
+
mcO = cute.make_identity_tensor(mO.layout.shape)
|
| 721 |
+
cO = cute.local_tile(
|
| 722 |
+
mcO[batch_size, None, num_head, None],
|
| 723 |
+
(self._m_block_size, self._head_dim_padded),
|
| 724 |
+
(m_block, 0),
|
| 725 |
+
)
|
| 726 |
+
tOcO = gmem_thr_copy_O.partition_D(cO)
|
| 727 |
+
tOpO = cute.make_fragment(
|
| 728 |
+
cute.make_layout(
|
| 729 |
+
(tOgO.shape[0][1], tOgO.shape[1], tOgO.shape[2]),
|
| 730 |
+
stride=(tOgO.shape[2], 0, 1),
|
| 731 |
+
),
|
| 732 |
+
cutlass.Boolean,
|
| 733 |
+
)
|
| 734 |
+
for rest_v in cutlass.range_constexpr(tOpO.shape[0]):
|
| 735 |
+
for rest_n in cutlass.range_constexpr(cute.size(tOpO.shape[2])):
|
| 736 |
+
tOpO[rest_v, 0, rest_n] = cute.elem_less(
|
| 737 |
+
tOcO[(0, rest_v), 0, rest_n][3], mO.layout.shape[3]
|
| 738 |
+
)
|
| 739 |
+
# copy acc O from rmem to gmem
|
| 740 |
+
for rest_m in cutlass.range_constexpr(cute.size(tOpO.shape[1])):
|
| 741 |
+
if cute.elem_less(tOcO[0, rest_m, 0][1], mO.layout.shape[1]):
|
| 742 |
+
cute.copy(
|
| 743 |
+
gmem_tiled_copy_O,
|
| 744 |
+
tOrO[None, rest_m, None],
|
| 745 |
+
tOgO[None, rest_m, None],
|
| 746 |
+
pred=tOpO[None, rest_m, None],
|
| 747 |
+
)
|
| 748 |
+
|
| 749 |
+
@cute.jit
|
| 750 |
+
def compute_one_n_block(
|
| 751 |
+
self,
|
| 752 |
+
basic_params: SimpleNamespace,
|
| 753 |
+
mma_params: SimpleNamespace,
|
| 754 |
+
gmem_copy_params: SimpleNamespace,
|
| 755 |
+
smem_copy_params: SimpleNamespace,
|
| 756 |
+
softmax_params: SimpleNamespace,
|
| 757 |
+
is_first_n_block: cutlass.Constexpr,
|
| 758 |
+
in_mask_steps: cutlass.Constexpr,
|
| 759 |
+
):
|
| 760 |
+
"""Compute one n_block of S/O.
|
| 761 |
+
|
| 762 |
+
This function provides different variants for processing the first n block versus subsequent blocks,
|
| 763 |
+
as well as variants for handling masked and unmasked steps.
|
| 764 |
+
|
| 765 |
+
:param basic_params: basic parameters
|
| 766 |
+
:type basic_params: SimpleNamespace
|
| 767 |
+
:param mma_params: mma parameters
|
| 768 |
+
:type mma_params: SimpleNamespace
|
| 769 |
+
:param gmem_copy_params: gmem copy parameters
|
| 770 |
+
:type gmem_copy_params: SimpleNamespace
|
| 771 |
+
:param smem_copy_params: smem copy parameters
|
| 772 |
+
:type smem_copy_params: SimpleNamespace
|
| 773 |
+
:param softmax_params: softmax parameters
|
| 774 |
+
:type softmax_params: SimpleNamespace
|
| 775 |
+
:param is_first_n_block: is first n block
|
| 776 |
+
:type is_first_n_block: cutlass.Constexpr
|
| 777 |
+
"""
|
| 778 |
+
acc_shape_S = mma_params.thr_mma.partition_shape_C(
|
| 779 |
+
(self._m_block_size, self._n_block_size)
|
| 780 |
+
)
|
| 781 |
+
acc_S = cute.make_fragment(acc_shape_S, cutlass.Float32)
|
| 782 |
+
acc_S.fill(0.0)
|
| 783 |
+
|
| 784 |
+
# wait for smem tile QK before mma calculation for S
|
| 785 |
+
cute.arch.cp_async_wait_group(0)
|
| 786 |
+
cute.arch.barrier()
|
| 787 |
+
# load smem tile V for O, special process for the first tile to avoid loading nan.
|
| 788 |
+
# The `if` here is a constexpr, won't be generated in the IR.
|
| 789 |
+
if is_first_n_block:
|
| 790 |
+
for n in cutlass.range_constexpr(cute.size(gmem_copy_params.tVsV.shape[1])):
|
| 791 |
+
if cute.elem_less(
|
| 792 |
+
gmem_copy_params.tKVcKV[0, n, 0][1],
|
| 793 |
+
basic_params.mK.layout.shape[1],
|
| 794 |
+
):
|
| 795 |
+
cute.copy(
|
| 796 |
+
gmem_copy_params.gmem_tiled_copy_QKV,
|
| 797 |
+
gmem_copy_params.tVgV[None, n, None, basic_params.n_block],
|
| 798 |
+
gmem_copy_params.tVsV[None, n, None],
|
| 799 |
+
pred=gmem_copy_params.tKVpKV[None, n, None],
|
| 800 |
+
)
|
| 801 |
+
else:
|
| 802 |
+
gmem_copy_params.tVsV[None, n, None].fill(0.0)
|
| 803 |
+
else:
|
| 804 |
+
cute.copy(
|
| 805 |
+
gmem_copy_params.gmem_tiled_copy_QKV,
|
| 806 |
+
gmem_copy_params.tVgV[None, None, None, basic_params.n_block],
|
| 807 |
+
gmem_copy_params.tVsV,
|
| 808 |
+
pred=gmem_copy_params.tKVpKV,
|
| 809 |
+
)
|
| 810 |
+
|
| 811 |
+
cute.arch.cp_async_commit_group()
|
| 812 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 813 |
+
# S gemm calculation
|
| 814 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 815 |
+
# load first QK k-block from smem to rmem for mma
|
| 816 |
+
cute.copy(
|
| 817 |
+
smem_copy_params.smem_tiled_copy_Q,
|
| 818 |
+
smem_copy_params.tSsQ[None, None, 0],
|
| 819 |
+
smem_copy_params.tSrQ_copy_view[None, None, 0],
|
| 820 |
+
)
|
| 821 |
+
cute.copy(
|
| 822 |
+
smem_copy_params.smem_tiled_copy_K,
|
| 823 |
+
smem_copy_params.tSsK[None, None, 0],
|
| 824 |
+
smem_copy_params.tSrK_copy_view[None, None, 0],
|
| 825 |
+
)
|
| 826 |
+
# mma for S
|
| 827 |
+
for k in cutlass.range_constexpr(cute.size(smem_copy_params.tSsQ.shape[2])):
|
| 828 |
+
# load next QK k-block from smem to rmem for mma
|
| 829 |
+
k_next = (k + 1) % cute.size(smem_copy_params.tSsQ.shape[2])
|
| 830 |
+
cute.copy(
|
| 831 |
+
smem_copy_params.smem_tiled_copy_Q,
|
| 832 |
+
smem_copy_params.tSsQ[None, None, k_next],
|
| 833 |
+
smem_copy_params.tSrQ_copy_view[None, None, k_next],
|
| 834 |
+
)
|
| 835 |
+
cute.copy(
|
| 836 |
+
smem_copy_params.smem_tiled_copy_K,
|
| 837 |
+
smem_copy_params.tSsK[None, None, k_next],
|
| 838 |
+
smem_copy_params.tSrK_copy_view[None, None, k_next],
|
| 839 |
+
)
|
| 840 |
+
cute.gemm(
|
| 841 |
+
mma_params.tiled_mma,
|
| 842 |
+
acc_S,
|
| 843 |
+
mma_params.tSrQ[None, None, k],
|
| 844 |
+
mma_params.tSrK[None, None, k],
|
| 845 |
+
acc_S,
|
| 846 |
+
)
|
| 847 |
+
|
| 848 |
+
# wait for smem tile V for O
|
| 849 |
+
cute.arch.cp_async_wait_group(0)
|
| 850 |
+
cute.arch.barrier()
|
| 851 |
+
|
| 852 |
+
if basic_params.n_block > 0:
|
| 853 |
+
cute.copy(
|
| 854 |
+
gmem_copy_params.gmem_tiled_copy_QKV,
|
| 855 |
+
gmem_copy_params.tKgK[None, None, None, basic_params.n_block - 1],
|
| 856 |
+
gmem_copy_params.tKsK,
|
| 857 |
+
pred=gmem_copy_params.tKVpKV,
|
| 858 |
+
)
|
| 859 |
+
cute.arch.cp_async_commit_group()
|
| 860 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 861 |
+
# online softmax
|
| 862 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 863 |
+
self.softmax_rescale_O(
|
| 864 |
+
basic_params,
|
| 865 |
+
mma_params,
|
| 866 |
+
softmax_params,
|
| 867 |
+
acc_S,
|
| 868 |
+
is_first_n_block,
|
| 869 |
+
in_mask_steps,
|
| 870 |
+
)
|
| 871 |
+
|
| 872 |
+
rP = cute.make_fragment_like(acc_S, self._dtype)
|
| 873 |
+
rP.store(acc_S.load().to(self._dtype))
|
| 874 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 875 |
+
# O gemm calculation
|
| 876 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 877 |
+
# Convert layout of acc_S to gemm O accept layout.
|
| 878 |
+
# Due to the mma instruction shape is 16x8x16, we need to convert from (4, MMA_M, MMA_N) to ((4, 2), MMA_M, MMA_N / 2)
|
| 879 |
+
# (4, MMA_M, MMA_N) -> (4, MMA_M, (2, MMA_N / 2))
|
| 880 |
+
rP_layout_divided = cute.logical_divide(rP.layout, (None, None, 2))
|
| 881 |
+
rP_mma_view = cute.make_layout(
|
| 882 |
+
(
|
| 883 |
+
(rP_layout_divided.shape[0], rP_layout_divided.shape[2][0]),
|
| 884 |
+
rP_layout_divided.shape[1],
|
| 885 |
+
rP_layout_divided.shape[2][1],
|
| 886 |
+
),
|
| 887 |
+
stride=(
|
| 888 |
+
(rP_layout_divided.stride[0], rP_layout_divided.stride[2][0]),
|
| 889 |
+
rP_layout_divided.stride[1],
|
| 890 |
+
rP_layout_divided.stride[2][1],
|
| 891 |
+
),
|
| 892 |
+
)
|
| 893 |
+
tOrS = cute.make_tensor(rP.iterator, rP_mma_view)
|
| 894 |
+
|
| 895 |
+
# load first V k-block from smem to rmem for mma
|
| 896 |
+
cute.copy(
|
| 897 |
+
smem_copy_params.smem_tiled_copy_V,
|
| 898 |
+
smem_copy_params.tOsVt[None, None, 0],
|
| 899 |
+
smem_copy_params.tOrVt_copy_view[None, None, 0],
|
| 900 |
+
)
|
| 901 |
+
# mma for O
|
| 902 |
+
for k in cutlass.range_constexpr(cute.size(tOrS.shape[2])):
|
| 903 |
+
# load next V k-block from smem to rmem for mma
|
| 904 |
+
k_next = (k + 1) % cute.size(tOrS.shape[2])
|
| 905 |
+
cute.copy(
|
| 906 |
+
smem_copy_params.smem_tiled_copy_V,
|
| 907 |
+
smem_copy_params.tOsVt[None, None, k_next],
|
| 908 |
+
smem_copy_params.tOrVt_copy_view[None, None, k_next],
|
| 909 |
+
)
|
| 910 |
+
cute.gemm(
|
| 911 |
+
mma_params.tiled_mma,
|
| 912 |
+
mma_params.acc_O,
|
| 913 |
+
tOrS[None, None, k],
|
| 914 |
+
mma_params.tOrVt[None, None, k],
|
| 915 |
+
mma_params.acc_O,
|
| 916 |
+
)
|
| 917 |
+
|
| 918 |
+
@cute.jit
|
| 919 |
+
def softmax_rescale_O(
|
| 920 |
+
self,
|
| 921 |
+
basic_params: SimpleNamespace,
|
| 922 |
+
mma_params: SimpleNamespace,
|
| 923 |
+
softmax_params: SimpleNamespace,
|
| 924 |
+
acc_S: cute.Tensor,
|
| 925 |
+
is_first_n_block: cutlass.Constexpr,
|
| 926 |
+
in_mask_steps: cutlass.Constexpr,
|
| 927 |
+
):
|
| 928 |
+
"""Apply online softmax and rescale acc_O.
|
| 929 |
+
|
| 930 |
+
This function provides different variants for processing the first n block versus subsequent blocks,
|
| 931 |
+
as well as variants for handling masked and unmasked steps.
|
| 932 |
+
|
| 933 |
+
:param basic_params: basic parameters
|
| 934 |
+
:type basic_params: SimpleNamespace
|
| 935 |
+
:param mma_params: mma parameters
|
| 936 |
+
:type mma_params: SimpleNamespace
|
| 937 |
+
:param softmax_params: softmax parameters
|
| 938 |
+
:type softmax_params: SimpleNamespace
|
| 939 |
+
:param acc_S: acc_S tensor
|
| 940 |
+
:type acc_S: cute.Tensor
|
| 941 |
+
:param is_first_n_block: is first n_block
|
| 942 |
+
:type is_first_n_block: cutlass.Constexpr
|
| 943 |
+
:param in_mask_steps: in mask steps
|
| 944 |
+
:type in_mask_steps: cutlass.Constexpr
|
| 945 |
+
"""
|
| 946 |
+
# Change acc_S to M,N layout view.
|
| 947 |
+
acc_S_mn = self._make_acc_tensor_mn_view(acc_S)
|
| 948 |
+
acc_O_mn = self._make_acc_tensor_mn_view(mma_params.acc_O)
|
| 949 |
+
row_max_prev = None
|
| 950 |
+
# if it is not the first tile, load the row r of previous row_max and compare with row_max_cur_row.
|
| 951 |
+
if cutlass.const_expr(not is_first_n_block):
|
| 952 |
+
row_max_prev = cute.make_fragment_like(
|
| 953 |
+
softmax_params.row_max, cutlass.Float32
|
| 954 |
+
)
|
| 955 |
+
cute.basic_copy(softmax_params.row_max, row_max_prev)
|
| 956 |
+
# if it is the first tile, create a mask for residual of S to -inf for softmax.
|
| 957 |
+
tScS_mn = None
|
| 958 |
+
if cutlass.const_expr(in_mask_steps):
|
| 959 |
+
mcS = cute.make_identity_tensor(
|
| 960 |
+
(
|
| 961 |
+
basic_params.mQ.shape[0],
|
| 962 |
+
basic_params.mQ.shape[1],
|
| 963 |
+
basic_params.mQ.shape[2],
|
| 964 |
+
basic_params.mK.shape[1],
|
| 965 |
+
)
|
| 966 |
+
)
|
| 967 |
+
cS = cute.local_tile(
|
| 968 |
+
mcS[basic_params.batch_size, None, basic_params.num_head, None],
|
| 969 |
+
(self._m_block_size, self._n_block_size),
|
| 970 |
+
(basic_params.m_block, basic_params.n_block),
|
| 971 |
+
)
|
| 972 |
+
tScS = mma_params.thr_mma.partition_C(cS)
|
| 973 |
+
tScS_mn = self._make_acc_tensor_mn_view(tScS)
|
| 974 |
+
|
| 975 |
+
# Each iteration processes one row of acc_S
|
| 976 |
+
for r in cutlass.range_constexpr(cute.size(softmax_params.row_max)):
|
| 977 |
+
# mask residual of S with -inf
|
| 978 |
+
if cutlass.const_expr(in_mask_steps):
|
| 979 |
+
if cutlass.const_expr(not self._is_causal):
|
| 980 |
+
# traverse column index.
|
| 981 |
+
for c in cutlass.range_constexpr(cute.size(tScS_mn.shape[1])):
|
| 982 |
+
if cute.elem_less(
|
| 983 |
+
basic_params.mK.shape[1], tScS_mn[0, c][3] + 1
|
| 984 |
+
):
|
| 985 |
+
acc_S_mn[r, c] = -cutlass.Float32.inf
|
| 986 |
+
else:
|
| 987 |
+
# get the column index limit based on current row. Only consider the row index, so the column index sets to 0.
|
| 988 |
+
col_idx_limit = cutlass.min(
|
| 989 |
+
tScS_mn[r, 0][1] + 1, basic_params.mK.shape[1]
|
| 990 |
+
)
|
| 991 |
+
# traverse column index.
|
| 992 |
+
for c in cutlass.range_constexpr(cute.size(tScS_mn.shape[1])):
|
| 993 |
+
# only consider the column index, so the row index sets to 0.
|
| 994 |
+
if cute.elem_less(col_idx_limit, tScS_mn[0, c][3] + 1):
|
| 995 |
+
acc_S_mn[r, c] = -cutlass.Float32.inf
|
| 996 |
+
|
| 997 |
+
# (n_block_size)
|
| 998 |
+
acc_S_row = acc_S_mn[r, None].load()
|
| 999 |
+
# row_max_cur_row => f32
|
| 1000 |
+
row_max_cur_row = acc_S_row.reduce(
|
| 1001 |
+
cute.ReductionOp.MAX, -cutlass.Float32.inf, 0
|
| 1002 |
+
)
|
| 1003 |
+
# quad reduction for row_max
|
| 1004 |
+
row_max_cur_row = self._threadquad_reduce_max(row_max_cur_row)
|
| 1005 |
+
row_max_prev_row = None
|
| 1006 |
+
# if it is not the first tile, load the row r of previous row_max and compare with row_max_cur_row.
|
| 1007 |
+
if cutlass.const_expr(not is_first_n_block):
|
| 1008 |
+
row_max_prev_row = row_max_prev[r]
|
| 1009 |
+
row_max_cur_row = cute.arch.fmax(row_max_prev_row, row_max_cur_row)
|
| 1010 |
+
if cutlass.const_expr(self._is_causal):
|
| 1011 |
+
row_max_cur_row = (
|
| 1012 |
+
0.0 if row_max_cur_row == -cutlass.Float32.inf else row_max_cur_row
|
| 1013 |
+
)
|
| 1014 |
+
|
| 1015 |
+
# compute exp(x - max) using exp2(x * log_2(e) - max * log_2(e))
|
| 1016 |
+
acc_S_row_exp = cute.math.exp2(
|
| 1017 |
+
acc_S_row * softmax_params.softmax_scale_log2
|
| 1018 |
+
- row_max_cur_row * softmax_params.softmax_scale_log2,
|
| 1019 |
+
fastmath=True,
|
| 1020 |
+
)
|
| 1021 |
+
# acc_S_row_sum => f32
|
| 1022 |
+
acc_S_row_sum = acc_S_row_exp.reduce(
|
| 1023 |
+
cute.ReductionOp.ADD, cutlass.Float32.zero, 0
|
| 1024 |
+
)
|
| 1025 |
+
# if it is not the first tile, load the row r of previous row_max and minus row_max_cur_row to update row_sum.
|
| 1026 |
+
if cutlass.const_expr(not is_first_n_block):
|
| 1027 |
+
prev_minus_cur_exp = cute.math.exp2(
|
| 1028 |
+
row_max_prev_row * softmax_params.softmax_scale_log2
|
| 1029 |
+
- row_max_cur_row * softmax_params.softmax_scale_log2,
|
| 1030 |
+
fastmath=True,
|
| 1031 |
+
)
|
| 1032 |
+
acc_S_row_sum = (
|
| 1033 |
+
acc_S_row_sum + softmax_params.row_sum[r] * prev_minus_cur_exp
|
| 1034 |
+
)
|
| 1035 |
+
acc_O_mn[r, None] = acc_O_mn[r, None].load() * prev_minus_cur_exp
|
| 1036 |
+
# update row_max, row_sum and acc_S
|
| 1037 |
+
softmax_params.row_max[r] = row_max_cur_row
|
| 1038 |
+
softmax_params.row_sum[r] = acc_S_row_sum
|
| 1039 |
+
acc_S_mn[r, None] = acc_S_row_exp
|
| 1040 |
+
|
| 1041 |
+
@cute.jit
|
| 1042 |
+
def normalize_softmax(
|
| 1043 |
+
self,
|
| 1044 |
+
acc_O: cute.Tensor,
|
| 1045 |
+
row_sum: cute.Tensor,
|
| 1046 |
+
):
|
| 1047 |
+
"""Normalize acc_O by row_sum.
|
| 1048 |
+
|
| 1049 |
+
:param acc_O: input tensor
|
| 1050 |
+
:type acc_O: cute.Tensor
|
| 1051 |
+
:param row_sum: row_sum tensor
|
| 1052 |
+
:type row_sum: cute.Tensor
|
| 1053 |
+
"""
|
| 1054 |
+
# do quad reduction for row_sum.
|
| 1055 |
+
acc_O_mn = self._make_acc_tensor_mn_view(acc_O)
|
| 1056 |
+
for r in cutlass.range_constexpr(cute.size(row_sum)):
|
| 1057 |
+
row_sum[r] = self._threadquad_reduce_sum(row_sum[r])
|
| 1058 |
+
# if row_sum is zero or nan, set acc_O_mn_row to 1.0
|
| 1059 |
+
acc_O_mn_row_is_zero_or_nan = row_sum[r] == 0.0 or row_sum[r] != row_sum[r]
|
| 1060 |
+
|
| 1061 |
+
scale = (
|
| 1062 |
+
1.0 if acc_O_mn_row_is_zero_or_nan else cute.arch.rcp_approx(row_sum[r])
|
| 1063 |
+
)
|
| 1064 |
+
|
| 1065 |
+
acc_O_mn[r, None] = acc_O_mn[r, None].load() * scale
|
| 1066 |
+
|
| 1067 |
+
def _make_acc_tensor_mn_view(self, acc: cute.Tensor) -> cute.Tensor:
|
| 1068 |
+
"""make acc tensor as mn layout view
|
| 1069 |
+
|
| 1070 |
+
:param acc: input tensor
|
| 1071 |
+
:type acc: cute.Tensor
|
| 1072 |
+
:return: acc tensor mn layout view
|
| 1073 |
+
:rtype: cute.Tensor
|
| 1074 |
+
"""
|
| 1075 |
+
acc_layout_col_major = cute.make_layout(acc.layout.shape)
|
| 1076 |
+
acc_layout_mn = cute.make_layout(
|
| 1077 |
+
(
|
| 1078 |
+
(
|
| 1079 |
+
acc_layout_col_major.shape[0][1],
|
| 1080 |
+
acc_layout_col_major.shape[1],
|
| 1081 |
+
), # MMA_M
|
| 1082 |
+
(
|
| 1083 |
+
acc_layout_col_major.shape[0][0],
|
| 1084 |
+
acc_layout_col_major.shape[2],
|
| 1085 |
+
), # MMA_N
|
| 1086 |
+
),
|
| 1087 |
+
stride=(
|
| 1088 |
+
(
|
| 1089 |
+
acc_layout_col_major.stride[0][1],
|
| 1090 |
+
acc_layout_col_major.stride[1],
|
| 1091 |
+
), # MMA_M
|
| 1092 |
+
(
|
| 1093 |
+
acc_layout_col_major.stride[0][0],
|
| 1094 |
+
acc_layout_col_major.stride[2],
|
| 1095 |
+
), # MMA_N
|
| 1096 |
+
),
|
| 1097 |
+
)
|
| 1098 |
+
acc_layout_mn = cute.composition(acc.layout, acc_layout_mn)
|
| 1099 |
+
return cute.make_tensor(acc.iterator, acc_layout_mn)
|
| 1100 |
+
|
| 1101 |
+
def _threadquad_reduce(self, val: cutlass.Float32, op: Callable) -> cutlass.Float32:
|
| 1102 |
+
"""thread quad reduction
|
| 1103 |
+
|
| 1104 |
+
:param val: register value
|
| 1105 |
+
:type val: cutlass.Float32
|
| 1106 |
+
:param op: binary operator
|
| 1107 |
+
:type op: Callable
|
| 1108 |
+
:return: reduced value
|
| 1109 |
+
:rtype: cutlass.Float32
|
| 1110 |
+
"""
|
| 1111 |
+
val = op(
|
| 1112 |
+
val,
|
| 1113 |
+
cute.arch.shuffle_sync_bfly(val, offset=2, mask=-1, mask_and_clamp=31),
|
| 1114 |
+
)
|
| 1115 |
+
val = op(
|
| 1116 |
+
val,
|
| 1117 |
+
cute.arch.shuffle_sync_bfly(val, offset=1, mask=-1, mask_and_clamp=31),
|
| 1118 |
+
)
|
| 1119 |
+
return val
|
| 1120 |
+
|
| 1121 |
+
def _threadquad_reduce_max(self, val: cutlass.Float32) -> cutlass.Float32:
|
| 1122 |
+
"""thread quad reduction max
|
| 1123 |
+
|
| 1124 |
+
:param val: register value
|
| 1125 |
+
:type val: cutlass.Float32
|
| 1126 |
+
:return: max value
|
| 1127 |
+
:rtype: cutlass.Float32
|
| 1128 |
+
"""
|
| 1129 |
+
return self._threadquad_reduce(val, lambda x, y: cute.arch.fmax(x, y))
|
| 1130 |
+
|
| 1131 |
+
def _threadquad_reduce_sum(self, val: cutlass.Float32) -> cutlass.Float32:
|
| 1132 |
+
"""thread quad reduction sum
|
| 1133 |
+
|
| 1134 |
+
:param val: register value
|
| 1135 |
+
:type val: cutlass.Float32
|
| 1136 |
+
:return: sum value
|
| 1137 |
+
:rtype: cutlass.Float32
|
| 1138 |
+
"""
|
| 1139 |
+
return self._threadquad_reduce(val, lambda x, y: x + y)
|
| 1140 |
+
|
| 1141 |
+
|
| 1142 |
+
def run(
|
| 1143 |
+
dtype: Type[cutlass.Numeric],
|
| 1144 |
+
batch_size: int,
|
| 1145 |
+
seqlen_q: int,
|
| 1146 |
+
seqlen_k: int,
|
| 1147 |
+
num_head: int,
|
| 1148 |
+
head_dim: int,
|
| 1149 |
+
softmax_scale: float = 1.0,
|
| 1150 |
+
m_block_size: int = 128,
|
| 1151 |
+
n_block_size: int = 128,
|
| 1152 |
+
num_threads: int = 128,
|
| 1153 |
+
is_causal: bool = False,
|
| 1154 |
+
warmup_iterations: int = 0,
|
| 1155 |
+
iterations: int = 1,
|
| 1156 |
+
skip_ref_check: bool = False,
|
| 1157 |
+
use_cold_l2: bool = False,
|
| 1158 |
+
**kwargs,
|
| 1159 |
+
):
|
| 1160 |
+
# Skip unsupported testcase
|
| 1161 |
+
if not FlashAttentionForwardAmpere.can_implement(
|
| 1162 |
+
dtype,
|
| 1163 |
+
head_dim,
|
| 1164 |
+
m_block_size,
|
| 1165 |
+
n_block_size,
|
| 1166 |
+
num_threads,
|
| 1167 |
+
is_causal,
|
| 1168 |
+
):
|
| 1169 |
+
raise TypeError(
|
| 1170 |
+
f"Unsupported testcase {dtype}, {head_dim}, {m_block_size}, {n_block_size}, {num_threads}, {is_causal}"
|
| 1171 |
+
)
|
| 1172 |
+
|
| 1173 |
+
print(f"Running Ampere SM80 FlashAttentionForward test with:")
|
| 1174 |
+
print(f" dtype: {dtype}")
|
| 1175 |
+
print(f" batch_size: {batch_size}")
|
| 1176 |
+
print(f" seqlen_q: {seqlen_q}")
|
| 1177 |
+
print(f" seqlen_k: {seqlen_k}")
|
| 1178 |
+
print(f" num_head: {num_head}")
|
| 1179 |
+
print(f" head_dim: {head_dim}")
|
| 1180 |
+
print(f" softmax_scale: {softmax_scale}")
|
| 1181 |
+
print(f" m_block_size: {m_block_size}")
|
| 1182 |
+
print(f" n_block_size: {n_block_size}")
|
| 1183 |
+
print(f" num_threads: {num_threads}")
|
| 1184 |
+
print(f" is_causal: {is_causal}")
|
| 1185 |
+
print(f" warmup_iterations: {warmup_iterations}")
|
| 1186 |
+
print(f" iterations: {iterations}")
|
| 1187 |
+
print(f" skip_ref_check: {skip_ref_check}")
|
| 1188 |
+
print(f" use_cold_l2: {use_cold_l2}")
|
| 1189 |
+
|
| 1190 |
+
# Create tensor Q/K/V/O
|
| 1191 |
+
def create_tensor(
|
| 1192 |
+
batch_size: int,
|
| 1193 |
+
seqlen: int,
|
| 1194 |
+
num_head: int,
|
| 1195 |
+
head_dim: int,
|
| 1196 |
+
dtype: Type[cutlass.Numeric],
|
| 1197 |
+
) -> cute.Tensor:
|
| 1198 |
+
# (batch_size, seqlen, num_head, head_dim)
|
| 1199 |
+
shape = (batch_size, seqlen, num_head, head_dim)
|
| 1200 |
+
torch_tensor = (
|
| 1201 |
+
torch.empty(*shape, dtype=torch.int32)
|
| 1202 |
+
.random_(-2, 2)
|
| 1203 |
+
.to(dtype=cutlass_torch.dtype(dtype))
|
| 1204 |
+
.cuda()
|
| 1205 |
+
)
|
| 1206 |
+
# assume input is 16B aligned.
|
| 1207 |
+
cute_tensor = (
|
| 1208 |
+
from_dlpack(torch_tensor, assumed_align=16)
|
| 1209 |
+
.mark_layout_dynamic(leading_dim=3)
|
| 1210 |
+
.mark_compact_shape_dynamic(
|
| 1211 |
+
mode=3,
|
| 1212 |
+
stride_order=torch_tensor.dim_order(),
|
| 1213 |
+
divisibility=(128 // dtype.width),
|
| 1214 |
+
)
|
| 1215 |
+
)
|
| 1216 |
+
return cute_tensor, torch_tensor
|
| 1217 |
+
|
| 1218 |
+
q, q_torch = create_tensor(batch_size, seqlen_q, num_head, head_dim, dtype)
|
| 1219 |
+
k, k_torch = create_tensor(batch_size, seqlen_k, num_head, head_dim, dtype)
|
| 1220 |
+
v, v_torch = create_tensor(batch_size, seqlen_k, num_head, head_dim, dtype)
|
| 1221 |
+
o, o_torch = create_tensor(batch_size, seqlen_q, num_head, head_dim, dtype)
|
| 1222 |
+
|
| 1223 |
+
fa2_fwd = FlashAttentionForwardAmpere(
|
| 1224 |
+
head_dim,
|
| 1225 |
+
m_block_size,
|
| 1226 |
+
n_block_size,
|
| 1227 |
+
num_threads,
|
| 1228 |
+
is_causal,
|
| 1229 |
+
)
|
| 1230 |
+
|
| 1231 |
+
# Get current CUDA stream from PyTorch
|
| 1232 |
+
torch_stream = torch.cuda.current_stream()
|
| 1233 |
+
# Get the raw stream pointer as a CUstream
|
| 1234 |
+
current_stream = cuda.CUstream(torch_stream.cuda_stream)
|
| 1235 |
+
# compile the fa2 forward pass
|
| 1236 |
+
compiled_fa2_fwd = cute.compile(fa2_fwd, q, k, v, o, softmax_scale, current_stream)
|
| 1237 |
+
|
| 1238 |
+
if not skip_ref_check:
|
| 1239 |
+
compiled_fa2_fwd(q, k, v, o, softmax_scale, current_stream)
|
| 1240 |
+
torch.cuda.synchronize()
|
| 1241 |
+
q_ref = q_torch.permute(0, 2, 1, 3)
|
| 1242 |
+
k_ref = k_torch.permute(0, 2, 1, 3)
|
| 1243 |
+
v_ref = v_torch.permute(0, 2, 1, 3)
|
| 1244 |
+
torch.backends.cuda.enable_flash_sdp(enabled=True)
|
| 1245 |
+
ref_o = torch.nn.functional.scaled_dot_product_attention(
|
| 1246 |
+
q_ref, k_ref, v_ref, scale=softmax_scale, is_causal=is_causal
|
| 1247 |
+
).permute(0, 2, 1, 3)
|
| 1248 |
+
torch.testing.assert_close(o_torch.cpu(), ref_o.cpu(), atol=1e-02, rtol=1e-04)
|
| 1249 |
+
print("Results verified successfully!")
|
| 1250 |
+
|
| 1251 |
+
def generate_tensors():
|
| 1252 |
+
q_workspace, _ = create_tensor(batch_size, seqlen_q, num_head, head_dim, dtype)
|
| 1253 |
+
k_workspace, _ = create_tensor(batch_size, seqlen_k, num_head, head_dim, dtype)
|
| 1254 |
+
v_workspace, _ = create_tensor(batch_size, seqlen_k, num_head, head_dim, dtype)
|
| 1255 |
+
o_workspace, _ = create_tensor(batch_size, seqlen_q, num_head, head_dim, dtype)
|
| 1256 |
+
return testing.JitArguments(
|
| 1257 |
+
q_workspace,
|
| 1258 |
+
k_workspace,
|
| 1259 |
+
v_workspace,
|
| 1260 |
+
o_workspace,
|
| 1261 |
+
softmax_scale,
|
| 1262 |
+
current_stream,
|
| 1263 |
+
)
|
| 1264 |
+
|
| 1265 |
+
workspace_count = 1
|
| 1266 |
+
if use_cold_l2:
|
| 1267 |
+
one_workspace_bytes = (
|
| 1268 |
+
q_torch.numel() * q_torch.element_size()
|
| 1269 |
+
+ k_torch.numel() * k_torch.element_size()
|
| 1270 |
+
+ v_torch.numel() * v_torch.element_size()
|
| 1271 |
+
+ o_torch.numel() * o_torch.element_size()
|
| 1272 |
+
)
|
| 1273 |
+
workspace_count = testing.get_workspace_count(
|
| 1274 |
+
one_workspace_bytes, warmup_iterations, iterations
|
| 1275 |
+
)
|
| 1276 |
+
|
| 1277 |
+
avg_time_us = testing.benchmark(
|
| 1278 |
+
compiled_fa2_fwd,
|
| 1279 |
+
workspace_generator=generate_tensors,
|
| 1280 |
+
workspace_count=workspace_count,
|
| 1281 |
+
stream=current_stream,
|
| 1282 |
+
warmup_iterations=warmup_iterations,
|
| 1283 |
+
iterations=iterations,
|
| 1284 |
+
)
|
| 1285 |
+
|
| 1286 |
+
return avg_time_us # Return execution time in microseconds
|
| 1287 |
+
|
| 1288 |
+
if __name__ == "__main__":
|
| 1289 |
+
parser = argparse.ArgumentParser(
|
| 1290 |
+
description="example of flash attention v2 with CuTe on GPU"
|
| 1291 |
+
)
|
| 1292 |
+
parser.add_argument("--dtype", type=cutlass.dtype, default=cutlass.BFloat16)
|
| 1293 |
+
parser.add_argument("--batch_size", type=int, default=4)
|
| 1294 |
+
parser.add_argument("--seqlen_q", type=int, default=8192)
|
| 1295 |
+
parser.add_argument("--seqlen_k", type=int, default=8192)
|
| 1296 |
+
parser.add_argument("--num_head", type=int, default=16)
|
| 1297 |
+
parser.add_argument("--head_dim", type=int, default=128)
|
| 1298 |
+
parser.add_argument("--softmax_scale", type=float, default=0.5)
|
| 1299 |
+
parser.add_argument("--m_block_size", type=int, default=128)
|
| 1300 |
+
parser.add_argument("--n_block_size", type=int, default=64)
|
| 1301 |
+
parser.add_argument("--num_threads", type=int, default=128)
|
| 1302 |
+
parser.add_argument("--is_causal", action="store_true", help="Enable causal mask")
|
| 1303 |
+
parser.add_argument("--warmup_iterations", type=int, default=3)
|
| 1304 |
+
parser.add_argument("--iterations", type=int, default=10)
|
| 1305 |
+
parser.add_argument(
|
| 1306 |
+
"--skip_ref_check", action="store_true", help="Skip reference check"
|
| 1307 |
+
)
|
| 1308 |
+
parser.add_argument(
|
| 1309 |
+
"--use_cold_l2",
|
| 1310 |
+
action="store_true",
|
| 1311 |
+
default=False,
|
| 1312 |
+
help="Use circular buffer tensor sets to ensure L2 cold cache",
|
| 1313 |
+
)
|
| 1314 |
+
|
| 1315 |
+
args = parser.parse_args()
|
| 1316 |
+
run(
|
| 1317 |
+
args.dtype,
|
| 1318 |
+
args.batch_size,
|
| 1319 |
+
args.seqlen_q,
|
| 1320 |
+
args.seqlen_k,
|
| 1321 |
+
args.num_head,
|
| 1322 |
+
args.head_dim,
|
| 1323 |
+
args.softmax_scale,
|
| 1324 |
+
args.m_block_size,
|
| 1325 |
+
args.n_block_size,
|
| 1326 |
+
args.num_threads,
|
| 1327 |
+
args.is_causal,
|
| 1328 |
+
args.warmup_iterations,
|
| 1329 |
+
args.iterations,
|
| 1330 |
+
args.skip_ref_check,
|
| 1331 |
+
args.use_cold_l2,
|
| 1332 |
+
)
|
| 1333 |
+
|
| 1334 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/sgemm.py
ADDED
|
@@ -0,0 +1,866 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
import time
|
| 31 |
+
from typing import Tuple
|
| 32 |
+
|
| 33 |
+
import cuda.bindings.driver as cuda
|
| 34 |
+
import torch
|
| 35 |
+
|
| 36 |
+
import cutlass
|
| 37 |
+
import cutlass.cute as cute
|
| 38 |
+
import cutlass.cute.testing as testing
|
| 39 |
+
import cutlass.torch as cutlass_torch
|
| 40 |
+
import cutlass.utils as utils
|
| 41 |
+
from cutlass.cute.runtime import from_dlpack
|
| 42 |
+
|
| 43 |
+
"""
|
| 44 |
+
A dense FP32 SIMT GEMM (C = A * B) example using CUTE DSL.
|
| 45 |
+
- Matrix A is MxK, A can be row-major("K") or column-major("M")
|
| 46 |
+
- Matrix B is NxK, B can be row-major("N") or column-major("K")
|
| 47 |
+
- Matrix C is MxN, C can be row-major("N") or column-major("M")
|
| 48 |
+
|
| 49 |
+
This GEMM kernel supports the following features:
|
| 50 |
+
- Utilizes FPU for matrix multiply-accumulate (MMA) operations
|
| 51 |
+
- Use multistage pipeline to overlap computation and memory access
|
| 52 |
+
* Shared memory pipeline: hides gmem-to-smem latency.
|
| 53 |
+
* Register pipeline: overlaps shared memory-to-register transfers with
|
| 54 |
+
computations and eliminates false data dependencies for
|
| 55 |
+
better parallelism.
|
| 56 |
+
- Use vectorized copies
|
| 57 |
+
- Add padding to reduce bank conflicts in global -> shared memory copies
|
| 58 |
+
- Use predication to avoid unnecessary copies or copies of stale data
|
| 59 |
+
|
| 60 |
+
This GEMM works as follows:
|
| 61 |
+
1. Load A and B matrices from global memory (GMEM) to shared memory (SMEM) using asynchronous copies.
|
| 62 |
+
2. Perform matrix multiply-accumulate (MMA) operations using simple fused multiply-add atomics.
|
| 63 |
+
3. Store results from registers (RMEM) to global memory (GMEM).
|
| 64 |
+
|
| 65 |
+
To run this example:
|
| 66 |
+
|
| 67 |
+
.. code-block:: bash
|
| 68 |
+
|
| 69 |
+
python examples/ampere/sgemm.py \
|
| 70 |
+
--mnk 8192,8192,8192 \
|
| 71 |
+
--a_major m --b_major n --c_major n
|
| 72 |
+
|
| 73 |
+
To collect performance with NCU profiler:
|
| 74 |
+
|
| 75 |
+
.. code-block:: bash
|
| 76 |
+
|
| 77 |
+
ncu python examples/ampere/sgemm.py \
|
| 78 |
+
--mnk 8192,8192,8192 \
|
| 79 |
+
--a_major m --b_major n --c_major n \
|
| 80 |
+
--skip_ref_check --iterations 2
|
| 81 |
+
|
| 82 |
+
Constraints:
|
| 83 |
+
* Supported input, output, and accumulator data types: fp32
|
| 84 |
+
* Default tile shape is set to be 128x128x8
|
| 85 |
+
* The contiguous dimension of A/B/C tensors must be at least 16 bytes aligned
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class SGemm:
|
| 90 |
+
def __init__(
|
| 91 |
+
self,
|
| 92 |
+
cta_tiler: Tuple[int, int, int] = (128, 128, 8),
|
| 93 |
+
num_stages: int = 3,
|
| 94 |
+
num_threads: int = 256,
|
| 95 |
+
):
|
| 96 |
+
self._cta_tiler = cta_tiler
|
| 97 |
+
self._num_stages = num_stages
|
| 98 |
+
self._num_threads = num_threads
|
| 99 |
+
assert num_threads > 0, "needs at least one thread"
|
| 100 |
+
assert num_threads % 16 == 0, "multiples of 16 required for MMA thread layout"
|
| 101 |
+
|
| 102 |
+
self._bM, self._bN, self._bK = self._cta_tiler
|
| 103 |
+
assert self._bM % 16 == 0, "multiple of 16 required for tile dimension M"
|
| 104 |
+
assert self._bN % 16 == 0, "multiple of 16 required for tile dimension N"
|
| 105 |
+
assert self._num_stages >= 3, "num_stages must be greater than or equal to 3"
|
| 106 |
+
|
| 107 |
+
@cute.jit
|
| 108 |
+
def __call__(
|
| 109 |
+
self,
|
| 110 |
+
mA: cute.Tensor,
|
| 111 |
+
mB: cute.Tensor,
|
| 112 |
+
mC: cute.Tensor,
|
| 113 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 114 |
+
stream: cuda.CUstream = cuda.CUstream(cuda.CUstream_flags.CU_STREAM_DEFAULT),
|
| 115 |
+
):
|
| 116 |
+
self.a_major_mode = utils.LayoutEnum.from_tensor(mA)
|
| 117 |
+
self.b_major_mode = utils.LayoutEnum.from_tensor(mB)
|
| 118 |
+
self.c_major_mode = utils.LayoutEnum.from_tensor(mC)
|
| 119 |
+
|
| 120 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 121 |
+
# Create layouts for shared memory for A and B:
|
| 122 |
+
# - sA/sB is m/n-major to vectorized copies from shared
|
| 123 |
+
# memory to registers. This is because the MMA layouts
|
| 124 |
+
# for sA/sB are also m/n-major
|
| 125 |
+
# - When gA/gB is k-major, pad 4 elements to reduce bank conflicts
|
| 126 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 127 |
+
|
| 128 |
+
padding_a = 4 if self.a_major_mode == utils.LayoutEnum.ROW_MAJOR else 0
|
| 129 |
+
padding_b = 4 if self.b_major_mode == utils.LayoutEnum.ROW_MAJOR else 0
|
| 130 |
+
sA_layout = cute.make_layout(
|
| 131 |
+
(self._bM, self._bK, self._num_stages),
|
| 132 |
+
stride=(1, (self._bM + padding_a), self._bK * (self._bM + padding_a)),
|
| 133 |
+
)
|
| 134 |
+
sB_layout = cute.make_layout(
|
| 135 |
+
(self._bN, self._bK, self._num_stages),
|
| 136 |
+
stride=(1, (self._bN + padding_b), self._bK * (self._bN + padding_b)),
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 140 |
+
# Create copy layouts that will be used for asynchronous
|
| 141 |
+
# global memory -> shared memory copies:
|
| 142 |
+
# - The majorness of tA/tB follows the majorness of gA/gB
|
| 143 |
+
# - For k-major, these layouts will copy values one-by-one from
|
| 144 |
+
# from global memory, without vectorizing
|
| 145 |
+
# - For m/n-major, it will vectorize to a 128bit copy for faster
|
| 146 |
+
# data transfer between global and shared memory, as long
|
| 147 |
+
# as the alignment of the tensor allows it. Otherwise, it
|
| 148 |
+
# defaults to a non-vectorized copy
|
| 149 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 150 |
+
|
| 151 |
+
tA = cute.make_layout(
|
| 152 |
+
(self._num_threads // self._bK, self._bK), stride=(self._bK, 1)
|
| 153 |
+
)
|
| 154 |
+
tB = cute.make_layout(
|
| 155 |
+
(self._num_threads // self._bK, self._bK), stride=(self._bK, 1)
|
| 156 |
+
)
|
| 157 |
+
vA = cute.make_layout((1, 1))
|
| 158 |
+
vB = cute.make_layout((1, 1))
|
| 159 |
+
atom_async_copy_A = cute.make_copy_atom(
|
| 160 |
+
cute.nvgpu.cpasync.CopyG2SOp(),
|
| 161 |
+
mA.element_type,
|
| 162 |
+
num_bits_per_copy=mA.element_type.width,
|
| 163 |
+
)
|
| 164 |
+
atom_async_copy_B = cute.make_copy_atom(
|
| 165 |
+
cute.nvgpu.cpasync.CopyG2SOp(),
|
| 166 |
+
mA.element_type,
|
| 167 |
+
num_bits_per_copy=mB.element_type.width,
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
if cutlass.const_expr(self.a_major_mode == utils.LayoutEnum.COL_MAJOR):
|
| 171 |
+
num_vectorized = 4 if (mA.layout.max_alignment % 16 == 0) else 1
|
| 172 |
+
atom_async_copy_A = cute.make_copy_atom(
|
| 173 |
+
cute.nvgpu.cpasync.CopyG2SOp(),
|
| 174 |
+
mA.element_type,
|
| 175 |
+
num_bits_per_copy=mA.element_type.width * num_vectorized,
|
| 176 |
+
)
|
| 177 |
+
major_mode_size = self._bM // num_vectorized
|
| 178 |
+
tA = cute.make_layout(
|
| 179 |
+
(major_mode_size, self._num_threads // major_mode_size),
|
| 180 |
+
stride=(1, major_mode_size),
|
| 181 |
+
)
|
| 182 |
+
vA = cute.make_layout((num_vectorized, 1))
|
| 183 |
+
|
| 184 |
+
if cutlass.const_expr(self.b_major_mode == utils.LayoutEnum.COL_MAJOR):
|
| 185 |
+
num_vectorized = 4 if (mB.layout.max_alignment % 16 == 0) else 1
|
| 186 |
+
atom_async_copy_B = cute.make_copy_atom(
|
| 187 |
+
cute.nvgpu.cpasync.CopyG2SOp(),
|
| 188 |
+
mA.element_type,
|
| 189 |
+
num_bits_per_copy=mB.element_type.width * num_vectorized,
|
| 190 |
+
)
|
| 191 |
+
major_mode_size = self._bN // num_vectorized
|
| 192 |
+
tB = cute.make_layout(
|
| 193 |
+
(major_mode_size, self._num_threads // major_mode_size),
|
| 194 |
+
stride=(1, major_mode_size),
|
| 195 |
+
)
|
| 196 |
+
vB = cute.make_layout((num_vectorized, 1))
|
| 197 |
+
|
| 198 |
+
tiled_copy_A = cute.make_tiled_copy_tv(atom_async_copy_A, tA, vA)
|
| 199 |
+
tiled_copy_B = cute.make_tiled_copy_tv(atom_async_copy_B, tB, vB)
|
| 200 |
+
|
| 201 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 202 |
+
# Create layouts for GEMM:
|
| 203 |
+
# We tile an MMA atom across a tensor. `atoms_layout` is the layout
|
| 204 |
+
# of atoms in the tiled MMA. (Because we use an `MmaUniversalOp`,
|
| 205 |
+
# which has a trivial 1x1x1 MMA trait, `atoms_layout` is also
|
| 206 |
+
# simply the thread layout for C.) `permutation_tiler` reorders the
|
| 207 |
+
# elements of the tensor that the tiled MMA is applied to.
|
| 208 |
+
# Different combinations of `atoms_layout` and `permutation_tiler`
|
| 209 |
+
# values can create different MMA thread-value patterns.
|
| 210 |
+
#
|
| 211 |
+
# Here, the MMA layout is set so that each thread copies four
|
| 212 |
+
# consecutive elements from shared memory to registers.
|
| 213 |
+
# `permutation_tiler_M/N` maps the elements handled by each thread
|
| 214 |
+
# to the permuted element in the tensor.
|
| 215 |
+
# For increasing indices in the tensor, the thread ID that reads it is:
|
| 216 |
+
# - (without permutation) ==>
|
| 217 |
+
# 0 1 2 ... 15 0 1 2 ... 15 0 1 2 ... 15 0 1 2 ... 15 ......
|
| 218 |
+
# - (with permutation) ==>
|
| 219 |
+
# 0 0 0 0 1 1 1 1 2 2 2 2 ... 15 15 15 15 0 0 0 0 1 1 1 1 ......
|
| 220 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 221 |
+
atoms_layout = cute.make_layout(
|
| 222 |
+
(self._num_threads // 16, 16, 1), stride=(16, 1, 0)
|
| 223 |
+
)
|
| 224 |
+
if cutlass.const_expr(self.c_major_mode == utils.LayoutEnum.COL_MAJOR):
|
| 225 |
+
atoms_layout = cute.make_layout(
|
| 226 |
+
(16, self._num_threads // 16, 1), stride=(1, 16, 0)
|
| 227 |
+
)
|
| 228 |
+
op = cute.nvgpu.MmaUniversalOp(cutlass.Float32)
|
| 229 |
+
permutation_tiler_M = cute.make_layout(
|
| 230 |
+
(atoms_layout.shape[0], 4), stride=(4, 1)
|
| 231 |
+
)
|
| 232 |
+
permutation_tiler_N = cute.make_layout(
|
| 233 |
+
(atoms_layout.shape[1], 4), stride=(4, 1)
|
| 234 |
+
)
|
| 235 |
+
tiled_mma = cute.make_tiled_mma(
|
| 236 |
+
op,
|
| 237 |
+
atoms_layout,
|
| 238 |
+
permutation_mnk=(permutation_tiler_M, permutation_tiler_N, None),
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
# grid_dim: ((m + BLK_M - 1) // BLK_M, (n + BLK_N - 1) // BLK_N, 1)
|
| 242 |
+
grid_dim = *cute.ceil_div(mC.shape, (self._bM, self._bN)), 1
|
| 243 |
+
|
| 244 |
+
self.kernel(
|
| 245 |
+
mA,
|
| 246 |
+
mB,
|
| 247 |
+
mC,
|
| 248 |
+
sA_layout,
|
| 249 |
+
sB_layout,
|
| 250 |
+
tiled_copy_A,
|
| 251 |
+
tiled_copy_B,
|
| 252 |
+
tiled_mma,
|
| 253 |
+
epilogue_op,
|
| 254 |
+
).launch(
|
| 255 |
+
grid=grid_dim,
|
| 256 |
+
block=[cute.size(atoms_layout), 1, 1],
|
| 257 |
+
stream=stream,
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
@cute.kernel
|
| 261 |
+
def kernel(
|
| 262 |
+
self,
|
| 263 |
+
mA: cute.Tensor,
|
| 264 |
+
mB: cute.Tensor,
|
| 265 |
+
mC: cute.Tensor,
|
| 266 |
+
sA_layout: cute.Layout,
|
| 267 |
+
sB_layout: cute.Layout,
|
| 268 |
+
tiled_copy_A: cute.TiledCopy,
|
| 269 |
+
tiled_copy_B: cute.TiledCopy,
|
| 270 |
+
tiled_mma: cute.TiledMma,
|
| 271 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 272 |
+
):
|
| 273 |
+
# Thread and block indices
|
| 274 |
+
tidx, tidy, tidz = cute.arch.thread_idx()
|
| 275 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 276 |
+
tiler_coord = (bidx, bidy, None)
|
| 277 |
+
thr_mma = tiled_mma.get_slice(tidx)
|
| 278 |
+
|
| 279 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 280 |
+
# Get the appropriate tiles for this thread block.
|
| 281 |
+
# gA: (BLK_M, BLK_K, k), gB: (BLK_N, BLK_K, k), gC: (BLK_M, BLK_N)
|
| 282 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 283 |
+
gA = cute.local_tile(
|
| 284 |
+
mA, tiler=self._cta_tiler, coord=tiler_coord, proj=(1, None, 1)
|
| 285 |
+
)
|
| 286 |
+
gB = cute.local_tile(
|
| 287 |
+
mB, tiler=self._cta_tiler, coord=tiler_coord, proj=(None, 1, 1)
|
| 288 |
+
)
|
| 289 |
+
gC = cute.local_tile(
|
| 290 |
+
mC, tiler=self._cta_tiler, coord=tiler_coord, proj=(1, 1, None)
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
# Move the pointer of gA/gB in the `-k`` direction, making the first
|
| 294 |
+
# tile (instead of the last one) irregular in shape when k is irregular.
|
| 295 |
+
# We first handle the irregular tile to avoid checking for this
|
| 296 |
+
# condition within the mainloop.
|
| 297 |
+
residue_k = mA.shape[1] - cutlass.Int32(self._bK) * gA.shape[2]
|
| 298 |
+
gA = cute.domain_offset((0, residue_k, 0), gA)
|
| 299 |
+
gB = cute.domain_offset((0, residue_k, 0), gB)
|
| 300 |
+
|
| 301 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 302 |
+
# Get the appropriate tiles for this thread.
|
| 303 |
+
# sA: (BLK_M, BLK_K, PIPE) , sB: (BLK_N, BLK_K, PIPE)
|
| 304 |
+
# tAgA: (CPY, CPY_M, CPY_K, k) , tBgB: (CPY, CPY_N, CPY_K, k)
|
| 305 |
+
# tAsA: (CPY, CPY_M, CPY_K, PIPE) , tBsB: (CPY, CPY_N, CPY_K, PIPE)
|
| 306 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 307 |
+
# Create shared memory buffer
|
| 308 |
+
smem = cutlass.utils.SmemAllocator()
|
| 309 |
+
sA = smem.allocate_tensor(mA.element_type, sA_layout, 16)
|
| 310 |
+
sB = smem.allocate_tensor(mB.element_type, sB_layout, 16)
|
| 311 |
+
thr_copy_A = tiled_copy_A.get_slice(tidx)
|
| 312 |
+
thr_copy_B = tiled_copy_B.get_slice(tidx)
|
| 313 |
+
tAgA = thr_copy_A.partition_S(gA)
|
| 314 |
+
tAsA = thr_copy_A.partition_D(sA)
|
| 315 |
+
tBgB = thr_copy_B.partition_S(gB)
|
| 316 |
+
tBsB = thr_copy_B.partition_D(sB)
|
| 317 |
+
|
| 318 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 319 |
+
# Predicate: Mark indices that need to copy when the problem shape
|
| 320 |
+
# isn't a multiple of the tile shape. If tApA/B[i] is 0, then do not
|
| 321 |
+
# do the copy atom associated with index i.
|
| 322 |
+
# cA: (BLK_M, BLK_K) => (blk_m, blk_k)
|
| 323 |
+
# cB: (BLK_N, BLK_K) => (blk_n, blk_k)
|
| 324 |
+
# tAcA: (CPY, CPY_M, CPY_K) => (blk_m, blk_k)
|
| 325 |
+
# tBcB: (CPY, CPY_N, CPY_K) => (blk_n, blk_k)
|
| 326 |
+
# tApA: (rest_v, CPY_M, CPY_K), stride=(..., ..., 0)
|
| 327 |
+
# tBpB: (rest_v, CPY_N, CPY_K), stride=(..., ..., 0)
|
| 328 |
+
# CPY = (atom_v, rest_v)
|
| 329 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 330 |
+
# Construct identity layout for sA and sB, used for predication
|
| 331 |
+
mcA = cute.make_identity_tensor(mA.shape)
|
| 332 |
+
mcB = cute.make_identity_tensor(mB.shape)
|
| 333 |
+
cA = cute.local_tile(
|
| 334 |
+
mcA, tiler=self._cta_tiler, coord=tiler_coord, proj=(1, None, 1)
|
| 335 |
+
)
|
| 336 |
+
cB = cute.local_tile(
|
| 337 |
+
mcB, tiler=self._cta_tiler, coord=tiler_coord, proj=(None, 1, 1)
|
| 338 |
+
)
|
| 339 |
+
cA = cute.domain_offset((0, residue_k, 0), cA)
|
| 340 |
+
cB = cute.domain_offset((0, residue_k, 0), cB)
|
| 341 |
+
# Repeat the partitioning with identity layouts
|
| 342 |
+
tAcA = thr_copy_A.partition_S(cA)
|
| 343 |
+
tBcB = thr_copy_B.partition_S(cB)
|
| 344 |
+
# Allocate predicate tensors for m and n
|
| 345 |
+
tApA = cute.make_fragment(
|
| 346 |
+
cute.make_layout(
|
| 347 |
+
(
|
| 348 |
+
tAsA.shape[0][1],
|
| 349 |
+
cute.size(tAsA, mode=[1]),
|
| 350 |
+
cute.size(tAsA, mode=[2]),
|
| 351 |
+
),
|
| 352 |
+
stride=(cute.size(tAsA, mode=[1]), 1, 0),
|
| 353 |
+
),
|
| 354 |
+
cutlass.Boolean,
|
| 355 |
+
)
|
| 356 |
+
tBpB = cute.make_fragment(
|
| 357 |
+
cute.make_layout(
|
| 358 |
+
(
|
| 359 |
+
tBsB.shape[0][1],
|
| 360 |
+
cute.size(tBsB, mode=[1]),
|
| 361 |
+
cute.size(tBsB, mode=[2]),
|
| 362 |
+
),
|
| 363 |
+
stride=(cute.size(tBsB, mode=[1]), 1, 0),
|
| 364 |
+
),
|
| 365 |
+
cutlass.Boolean,
|
| 366 |
+
)
|
| 367 |
+
# Allocate predicate tensors for m, n and k for residue k-tile
|
| 368 |
+
tApA_residue_k = cute.make_fragment(
|
| 369 |
+
cute.make_layout(
|
| 370 |
+
(
|
| 371 |
+
tAsA.shape[0][1],
|
| 372 |
+
cute.size(tAsA, mode=[1]),
|
| 373 |
+
cute.size(tAsA, mode=[2]),
|
| 374 |
+
),
|
| 375 |
+
stride=(
|
| 376 |
+
cute.size(tAsA, mode=[1]) * cute.size(tAsA, mode=[2]),
|
| 377 |
+
cute.size(tAsA, mode=[2]),
|
| 378 |
+
1,
|
| 379 |
+
),
|
| 380 |
+
),
|
| 381 |
+
cutlass.Boolean,
|
| 382 |
+
)
|
| 383 |
+
tBpB_residue_k = cute.make_fragment(
|
| 384 |
+
cute.make_layout(
|
| 385 |
+
(
|
| 386 |
+
tBsB.shape[0][1],
|
| 387 |
+
cute.size(tBsB, mode=[1]),
|
| 388 |
+
cute.size(tBsB, mode=[2]),
|
| 389 |
+
),
|
| 390 |
+
stride=(
|
| 391 |
+
cute.size(tBsB, mode=[1]) * cute.size(tBsB, mode=[2]),
|
| 392 |
+
cute.size(tBsB, mode=[2]),
|
| 393 |
+
1,
|
| 394 |
+
),
|
| 395 |
+
),
|
| 396 |
+
cutlass.Boolean,
|
| 397 |
+
)
|
| 398 |
+
# Set predicates for m/n bounds for mainloop
|
| 399 |
+
for rest_v in range(tApA.shape[0]):
|
| 400 |
+
for m in range(tApA.shape[1]):
|
| 401 |
+
tApA[rest_v, m, 0] = cute.elem_less(
|
| 402 |
+
tAcA[(0, rest_v), m, 0, 0][0], mA.shape[0]
|
| 403 |
+
)
|
| 404 |
+
for rest_v in range(tBpB.shape[0]):
|
| 405 |
+
for n in range(tBpB.shape[1]):
|
| 406 |
+
tBpB[rest_v, n, 0] = cute.elem_less(
|
| 407 |
+
tBcB[(0, rest_v), n, 0, 0][0], mB.shape[0]
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
# Set predicates for m/n/k bounds for residue k tile
|
| 411 |
+
for rest_v in range(tApA_residue_k.shape[0]):
|
| 412 |
+
for m in range(tApA_residue_k.shape[1]):
|
| 413 |
+
for k in range(tApA_residue_k.shape[2]):
|
| 414 |
+
coord_A = tAcA[(0, rest_v), m, k, 0]
|
| 415 |
+
tApA_residue_k[rest_v, m, k] = cute.elem_less(
|
| 416 |
+
(coord_A[0], cutlass.Int32(-1)), (mA.shape[0], coord_A[1])
|
| 417 |
+
)
|
| 418 |
+
for rest_v in range(tBpB_residue_k.shape[0]):
|
| 419 |
+
for n in range(tBpB_residue_k.shape[1]):
|
| 420 |
+
for k in range(tBpB_residue_k.shape[2]):
|
| 421 |
+
coord_B = tBcB[(0, rest_v), n, k, 0]
|
| 422 |
+
tBpB_residue_k[rest_v, n, k] = cute.elem_less(
|
| 423 |
+
(coord_B[0], cutlass.Int32(-1)), (mB.shape[0], coord_B[1])
|
| 424 |
+
)
|
| 425 |
+
|
| 426 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 427 |
+
# Prefetch Prologue
|
| 428 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 429 |
+
# Start async loads for 0th k-tile, where we take care of the k-residue
|
| 430 |
+
k_pipe_max = cute.size(tAsA, mode=[3])
|
| 431 |
+
k_tile_count = cute.size(tAgA, mode=[3])
|
| 432 |
+
gmem_pipe_read = cutlass.Int32(0)
|
| 433 |
+
cute.copy(
|
| 434 |
+
tiled_copy_A,
|
| 435 |
+
tAgA[None, None, None, gmem_pipe_read],
|
| 436 |
+
tAsA[None, None, None, 0],
|
| 437 |
+
pred=tApA_residue_k,
|
| 438 |
+
)
|
| 439 |
+
cute.copy(
|
| 440 |
+
tiled_copy_B,
|
| 441 |
+
tBgB[None, None, None, gmem_pipe_read],
|
| 442 |
+
tBsB[None, None, None, 0],
|
| 443 |
+
pred=tBpB_residue_k,
|
| 444 |
+
)
|
| 445 |
+
cute.arch.cp_async_commit_group()
|
| 446 |
+
gmem_pipe_read = (
|
| 447 |
+
gmem_pipe_read + 1
|
| 448 |
+
if gmem_pipe_read + 1 < k_tile_count
|
| 449 |
+
else cutlass.Int32(0)
|
| 450 |
+
)
|
| 451 |
+
# Start async loads for 1st k-tile onwards, no k-residue handling needed
|
| 452 |
+
for k_tile in range(1, k_pipe_max - 1):
|
| 453 |
+
if k_tile < k_tile_count:
|
| 454 |
+
cute.copy(
|
| 455 |
+
tiled_copy_A,
|
| 456 |
+
tAgA[None, None, None, gmem_pipe_read],
|
| 457 |
+
tAsA[None, None, None, k_tile],
|
| 458 |
+
pred=tApA,
|
| 459 |
+
)
|
| 460 |
+
cute.copy(
|
| 461 |
+
tiled_copy_B,
|
| 462 |
+
tBgB[None, None, None, gmem_pipe_read],
|
| 463 |
+
tBsB[None, None, None, k_tile],
|
| 464 |
+
pred=tBpB,
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
gmem_pipe_read = (
|
| 468 |
+
gmem_pipe_read + 1
|
| 469 |
+
if gmem_pipe_read + 1 < k_tile_count
|
| 470 |
+
else cutlass.Int32(0)
|
| 471 |
+
)
|
| 472 |
+
cute.arch.cp_async_commit_group()
|
| 473 |
+
|
| 474 |
+
# all tiles have been copied from global memory, so clear the
|
| 475 |
+
# predicate tensor
|
| 476 |
+
if k_tile_count < k_pipe_max:
|
| 477 |
+
for rest_v in range(tApA.shape[0]):
|
| 478 |
+
for m in range(tApA.shape[1]):
|
| 479 |
+
tApA[rest_v, m, 0] = cutlass.Boolean(0)
|
| 480 |
+
for rest_v in range(tBpB.shape[0]):
|
| 481 |
+
for n in range(tBpB.shape[1]):
|
| 482 |
+
tBpB[rest_v, n, 0] = cutlass.Boolean(0)
|
| 483 |
+
|
| 484 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 485 |
+
# Define A/B partitioning and C accumulators.
|
| 486 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 487 |
+
tCsA = thr_mma.partition_A(sA)
|
| 488 |
+
tCsB = thr_mma.partition_B(sB)
|
| 489 |
+
tCgC = thr_mma.partition_C(gC)
|
| 490 |
+
tCrA = tiled_mma.make_fragment_A(tCsA[None, None, None, 0])
|
| 491 |
+
tCrB = tiled_mma.make_fragment_B(tCsB[None, None, None, 0])
|
| 492 |
+
tCrC = tiled_mma.make_fragment_C(tCgC)
|
| 493 |
+
# Clear the accumulator
|
| 494 |
+
tCrC.fill(0.0)
|
| 495 |
+
|
| 496 |
+
# Current pipe index in smem to read from / write to
|
| 497 |
+
smem_pipe_read = cutlass.Int32(0)
|
| 498 |
+
smem_pipe_write = cutlass.Int32(k_pipe_max - 1)
|
| 499 |
+
|
| 500 |
+
tCsA_p = tCsA[None, None, None, smem_pipe_read]
|
| 501 |
+
tCsB_p = tCsB[None, None, None, smem_pipe_read]
|
| 502 |
+
|
| 503 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 504 |
+
# PREFETCH register pipeline
|
| 505 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 506 |
+
k_block_max = cute.size(tCrA, mode=[2])
|
| 507 |
+
|
| 508 |
+
if k_block_max > 1:
|
| 509 |
+
# Wait until our first prefetched tile is loaded in
|
| 510 |
+
cute.arch.cp_async_wait_group(k_pipe_max - 2)
|
| 511 |
+
cute.arch.barrier()
|
| 512 |
+
# Prefetch the first rmem from the first k-tile
|
| 513 |
+
cute.autovec_copy(tCsA_p[None, None, 0], tCrA[None, None, 0])
|
| 514 |
+
cute.autovec_copy(tCsB_p[None, None, 0], tCrB[None, None, 0])
|
| 515 |
+
|
| 516 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 517 |
+
# Mainloop
|
| 518 |
+
# 1. Shared memory pipeline (gmem -> smem):
|
| 519 |
+
# The default smem pipeline depth is 3, meaning that for shared
|
| 520 |
+
# memory buffers, we allocate three times the size described by the
|
| 521 |
+
# CTA tiler. We prefetch 2 of these buffers before entering the main
|
| 522 |
+
# loop. Considering only the transfer from global memory to shared
|
| 523 |
+
# memory, the general structure of the mainloop is:
|
| 524 |
+
# (1) copy k-tile from gmem to smem;
|
| 525 |
+
# (2) perform gemm computation on k-tile;
|
| 526 |
+
# (3) wait for the next copy to finish.
|
| 527 |
+
# The `cute.arch.cp_async_wait_group(num_smem_stages - 2)` command
|
| 528 |
+
# waits for the number of unfinished 'copy' to be <= 1. The advantage
|
| 529 |
+
# of this approach is that it allows for simultaneous production
|
| 530 |
+
# (i.e., step (1)) and consumption (i.e., step (2)) of smem.
|
| 531 |
+
# A common misconception is to prefetch N buffers and rewrite
|
| 532 |
+
# the pipeline logic to wait on N-1 pending copies. The disadvantage
|
| 533 |
+
# of this approach is that it requires fully consuming a buffer in
|
| 534 |
+
# order to open an empty buffer for the next copy.
|
| 535 |
+
# 2. Register pipeline (smem -> register):
|
| 536 |
+
# Similarly, the register pipeline produces i+1, consumes i, and
|
| 537 |
+
# produces i+2... Notably, i and i+1 do not use the same register,
|
| 538 |
+
# eliminating dependencies on the same register for better parallelism.
|
| 539 |
+
# 3. Combining the smem and register pipelines results in the mainloop.
|
| 540 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 541 |
+
|
| 542 |
+
for _ in range(k_tile_count):
|
| 543 |
+
for k_block in range(k_block_max, unroll_full=True):
|
| 544 |
+
if k_block == k_block_max - 1:
|
| 545 |
+
tCsA_p = tCsA[None, None, None, smem_pipe_read]
|
| 546 |
+
tCsB_p = tCsB[None, None, None, smem_pipe_read]
|
| 547 |
+
cute.arch.cp_async_wait_group(k_pipe_max - 2)
|
| 548 |
+
cute.arch.barrier()
|
| 549 |
+
|
| 550 |
+
# Load A, B from shared memory to registers for k_block + 1
|
| 551 |
+
k_block_next = (k_block + 1) % k_block_max # static
|
| 552 |
+
cute.autovec_copy(
|
| 553 |
+
tCsA_p[None, None, k_block_next],
|
| 554 |
+
tCrA[None, None, k_block_next],
|
| 555 |
+
)
|
| 556 |
+
cute.autovec_copy(
|
| 557 |
+
tCsB_p[None, None, k_block_next],
|
| 558 |
+
tCrB[None, None, k_block_next],
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
# Fetch next A: To better interleave global memory access and
|
| 562 |
+
# compute instructions, we intentionally use the sequence:
|
| 563 |
+
# copy A, perform GEMM, then copy B.
|
| 564 |
+
if k_block == 0:
|
| 565 |
+
cute.copy(
|
| 566 |
+
tiled_copy_A,
|
| 567 |
+
tAgA[None, None, None, gmem_pipe_read],
|
| 568 |
+
tAsA[None, None, None, smem_pipe_write],
|
| 569 |
+
# Use predicates because the m-mode may be irregular
|
| 570 |
+
pred=tApA,
|
| 571 |
+
)
|
| 572 |
+
|
| 573 |
+
# Thread-level register gemm for k_block
|
| 574 |
+
cute.gemm(
|
| 575 |
+
tiled_mma,
|
| 576 |
+
tCrC,
|
| 577 |
+
tCrA[None, None, k_block],
|
| 578 |
+
tCrB[None, None, k_block],
|
| 579 |
+
tCrC,
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
# Fetch next B and update smem pipeline read/write
|
| 583 |
+
if k_block == 0:
|
| 584 |
+
cute.copy(
|
| 585 |
+
tiled_copy_B,
|
| 586 |
+
tBgB[None, None, None, gmem_pipe_read],
|
| 587 |
+
tBsB[None, None, None, smem_pipe_write],
|
| 588 |
+
# Use predicates because the n-mode may be irregular
|
| 589 |
+
pred=tBpB,
|
| 590 |
+
)
|
| 591 |
+
cute.arch.cp_async_commit_group()
|
| 592 |
+
smem_pipe_write = smem_pipe_read
|
| 593 |
+
smem_pipe_read = smem_pipe_read + 1
|
| 594 |
+
if smem_pipe_read == k_pipe_max:
|
| 595 |
+
smem_pipe_read = cutlass.Int32(0)
|
| 596 |
+
# After copying all tiles, we avoid clearing the predicate
|
| 597 |
+
# tensor in the `mainloop` to prevent increasing its
|
| 598 |
+
# instruction count. Instead, we continue copying the
|
| 599 |
+
# first tile, though it won't be used. The 0-th tile is not
|
| 600 |
+
# copied due to its irregular shape, which could lead to
|
| 601 |
+
# illegal memory accesses.
|
| 602 |
+
gmem_pipe_read = (
|
| 603 |
+
gmem_pipe_read + 1
|
| 604 |
+
if gmem_pipe_read + 1 < k_tile_count
|
| 605 |
+
else cutlass.Int32(1)
|
| 606 |
+
)
|
| 607 |
+
|
| 608 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 609 |
+
# Epilogue
|
| 610 |
+
# Applies the epilogue operation to the accumulated results and copies
|
| 611 |
+
# them without vectorization.
|
| 612 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 613 |
+
cute.arch.cp_async_wait_group(0)
|
| 614 |
+
cute.arch.barrier()
|
| 615 |
+
tCrC.store(epilogue_op(tCrC.load()))
|
| 616 |
+
|
| 617 |
+
# predicate
|
| 618 |
+
cC = cute.make_identity_tensor(gC.shape)
|
| 619 |
+
tCpC = thr_mma.partition_C(cC)
|
| 620 |
+
predC = cute.make_fragment(tCrC.layout, cutlass.Boolean)
|
| 621 |
+
residue_m = mC.shape[0] - cutlass.Int32(self._bM) * bidx
|
| 622 |
+
residue_n = mC.shape[1] - cutlass.Int32(self._bN) * bidy
|
| 623 |
+
for i in range(cute.size(tCrC.shape)):
|
| 624 |
+
predC[i] = cute.elem_less(tCpC[i], (residue_m, residue_n))
|
| 625 |
+
numIterM = cute.size(tCrC, mode=[1])
|
| 626 |
+
numIterN = cute.size(tCrC, mode=[2])
|
| 627 |
+
atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), mC.element_type)
|
| 628 |
+
cute.copy(atom, tCrC, tCgC, pred=predC)
|
| 629 |
+
return
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
def run(
|
| 633 |
+
mnk: Tuple[int, int, int],
|
| 634 |
+
a_major: str,
|
| 635 |
+
b_major: str,
|
| 636 |
+
c_major: str,
|
| 637 |
+
static_shape: bool = False,
|
| 638 |
+
warmup_iterations: int = 2,
|
| 639 |
+
iterations: int = 100,
|
| 640 |
+
skip_ref_check: bool = False,
|
| 641 |
+
use_cold_l2: bool = False,
|
| 642 |
+
**kwargs,
|
| 643 |
+
):
|
| 644 |
+
"""Execute SIMT GEMM operation and benchmark performance.
|
| 645 |
+
|
| 646 |
+
:param mnk: GEMM problem size (M, N, K, L)
|
| 647 |
+
:type mnk: Tuple[int, int, int, int]
|
| 648 |
+
:param a_major: Memory layout of tensor A
|
| 649 |
+
:type a_major: str
|
| 650 |
+
:param b_major: Memory layout of tensor B
|
| 651 |
+
:type b_major: str
|
| 652 |
+
:param c_major: Memory layout of tensor C
|
| 653 |
+
:type c_major: str
|
| 654 |
+
:param static_shape: Whether to use static shape optimization, defaults to False
|
| 655 |
+
:type static_shape: bool, optional
|
| 656 |
+
:param warmup_iterations: Number of warmup iterations before benchmarking, defaults to 2
|
| 657 |
+
:type warmup_iterations: int, optional
|
| 658 |
+
:param iterations: Number of benchmark iterations to run, defaults to 100
|
| 659 |
+
:type iterations: int, optional
|
| 660 |
+
:param skip_ref_check: Skip validation against reference implementation, defaults to False
|
| 661 |
+
:type skip_ref_check: bool, optional
|
| 662 |
+
:param use_cold_l2: Whether to use circular buffer strategy to ensure cold L2 cache, defaults to False
|
| 663 |
+
:type use_cold_l2: bool, optional
|
| 664 |
+
:return: Execution time of the GEMM kernel in microseconds
|
| 665 |
+
:rtype: float
|
| 666 |
+
"""
|
| 667 |
+
print(f"Running Ampere SIMT GEMM example:")
|
| 668 |
+
print(f"mnk: {mnk}")
|
| 669 |
+
print(f"A major: {a_major}, B major: {b_major}, C major: {c_major}")
|
| 670 |
+
print(f"Static shape: {static_shape}")
|
| 671 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 672 |
+
print(f"Iterations: {iterations}")
|
| 673 |
+
print(f"Skip reference checking: {skip_ref_check}")
|
| 674 |
+
print(f"Use cold L2: {use_cold_l2}")
|
| 675 |
+
M, N, K = mnk
|
| 676 |
+
|
| 677 |
+
# Create and permute tensor A/B/C
|
| 678 |
+
def create_and_permute_tensor(mode0, mode1, is_mode0_major, dtype):
|
| 679 |
+
# is_mode0_major: (mode1, mode0) -> (mode0, mode1)
|
| 680 |
+
# else: (mode0, mode1) -> (mode0, mode1)
|
| 681 |
+
shape = (mode1, mode0) if is_mode0_major else (mode0, mode1)
|
| 682 |
+
permute_order = (1, 0) if is_mode0_major else (0, 1)
|
| 683 |
+
|
| 684 |
+
return (
|
| 685 |
+
torch.empty(*shape, dtype=torch.int32)
|
| 686 |
+
.random_(-5, 5)
|
| 687 |
+
.to(dtype=dtype)
|
| 688 |
+
.permute(permute_order)
|
| 689 |
+
.cuda()
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
+
a = create_and_permute_tensor(M, K, a_major == "m", torch.float32)
|
| 693 |
+
b = create_and_permute_tensor(N, K, b_major == "n", torch.float32)
|
| 694 |
+
c = create_and_permute_tensor(M, N, c_major == "m", torch.float32)
|
| 695 |
+
|
| 696 |
+
divisibility_a = a.shape[1] if a_major == "k" else a.shape[0]
|
| 697 |
+
divisibility_b = b.shape[1] if b_major == "k" else b.shape[0]
|
| 698 |
+
divisibility_c = c.shape[1] if c_major == "n" else c.shape[0]
|
| 699 |
+
|
| 700 |
+
a_tensor = (
|
| 701 |
+
from_dlpack(a, assumed_align=16)
|
| 702 |
+
.mark_layout_dynamic(leading_dim=(1 if a_major == "k" else 0))
|
| 703 |
+
.mark_compact_shape_dynamic(
|
| 704 |
+
mode=(1 if a_major == "k" else 0),
|
| 705 |
+
divisibility=divisibility_a,
|
| 706 |
+
)
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
b_tensor = (
|
| 710 |
+
from_dlpack(b, assumed_align=16)
|
| 711 |
+
.mark_layout_dynamic(leading_dim=(1 if b_major == "k" else 0))
|
| 712 |
+
.mark_compact_shape_dynamic(
|
| 713 |
+
mode=(1 if b_major == "k" else 0),
|
| 714 |
+
divisibility=divisibility_b,
|
| 715 |
+
)
|
| 716 |
+
)
|
| 717 |
+
|
| 718 |
+
c_tensor = (
|
| 719 |
+
from_dlpack(c, assumed_align=16)
|
| 720 |
+
.mark_layout_dynamic(leading_dim=(1 if c_major == "n" else 0))
|
| 721 |
+
.mark_compact_shape_dynamic(
|
| 722 |
+
mode=(1 if c_major == "n" else 0),
|
| 723 |
+
divisibility=divisibility_c,
|
| 724 |
+
)
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
sgemm = SGemm()
|
| 728 |
+
|
| 729 |
+
# Get current CUDA stream from PyTorch
|
| 730 |
+
torch_stream = torch.cuda.current_stream()
|
| 731 |
+
# Get the raw stream pointer as a CUstream
|
| 732 |
+
current_stream = cuda.CUstream(torch_stream.cuda_stream)
|
| 733 |
+
|
| 734 |
+
print("Compiling kernel with cute.compile ...")
|
| 735 |
+
start_time = time.time()
|
| 736 |
+
compiled_fn = cute.compile(
|
| 737 |
+
sgemm,
|
| 738 |
+
a_tensor,
|
| 739 |
+
b_tensor,
|
| 740 |
+
c_tensor,
|
| 741 |
+
stream=current_stream,
|
| 742 |
+
)
|
| 743 |
+
compilation_time = time.time() - start_time
|
| 744 |
+
print(f"Compilation time: {compilation_time:.4f} seconds")
|
| 745 |
+
|
| 746 |
+
print("Executing GEMM kernel...")
|
| 747 |
+
|
| 748 |
+
if not skip_ref_check:
|
| 749 |
+
compiled_fn(a_tensor, b_tensor, c_tensor)
|
| 750 |
+
torch.cuda.synchronize()
|
| 751 |
+
print("Verifying results...")
|
| 752 |
+
ref = torch.einsum("mk,nk->mn", a, b)
|
| 753 |
+
torch.testing.assert_close(c.cpu(), ref.cpu(), atol=1e-03, rtol=1e-05)
|
| 754 |
+
print("Results verified successfully!")
|
| 755 |
+
|
| 756 |
+
def generate_tensors():
|
| 757 |
+
# Create new tensors for each workspace to ensure cold L2 cache
|
| 758 |
+
a_workspace = create_and_permute_tensor(M, K, a_major == "m", torch.float32)
|
| 759 |
+
b_workspace = create_and_permute_tensor(N, K, b_major == "n", torch.float32)
|
| 760 |
+
c_workspace = create_and_permute_tensor(M, N, c_major == "m", torch.float32)
|
| 761 |
+
|
| 762 |
+
if static_shape:
|
| 763 |
+
a_tensor_workspace = (
|
| 764 |
+
from_dlpack(a_workspace, assumed_align=16)
|
| 765 |
+
.mark_layout_dynamic(leading_dim=(1 if a_major == "k" else 0))
|
| 766 |
+
.mark_compact_shape_dynamic(
|
| 767 |
+
mode=(1 if a_major == "k" else 0),
|
| 768 |
+
divisibility=divisibility_a,
|
| 769 |
+
)
|
| 770 |
+
)
|
| 771 |
+
else:
|
| 772 |
+
a_tensor_workspace = from_dlpack(a_workspace, assumed_align=16)
|
| 773 |
+
|
| 774 |
+
b_tensor_workspace = (
|
| 775 |
+
from_dlpack(b_workspace, assumed_align=16)
|
| 776 |
+
.mark_layout_dynamic(leading_dim=(1 if b_major == "k" else 0))
|
| 777 |
+
.mark_compact_shape_dynamic(
|
| 778 |
+
mode=(1 if b_major == "k" else 0),
|
| 779 |
+
divisibility=divisibility_b,
|
| 780 |
+
)
|
| 781 |
+
)
|
| 782 |
+
|
| 783 |
+
c_tensor_workspace = (
|
| 784 |
+
from_dlpack(c_workspace, assumed_align=16)
|
| 785 |
+
.mark_layout_dynamic(leading_dim=(1 if c_major == "n" else 0))
|
| 786 |
+
.mark_compact_shape_dynamic(
|
| 787 |
+
mode=(1 if c_major == "n" else 0),
|
| 788 |
+
divisibility=divisibility_c,
|
| 789 |
+
)
|
| 790 |
+
)
|
| 791 |
+
|
| 792 |
+
return testing.JitArguments(
|
| 793 |
+
a_tensor_workspace, b_tensor_workspace, c_tensor_workspace, current_stream
|
| 794 |
+
)
|
| 795 |
+
|
| 796 |
+
workspace_count = 1
|
| 797 |
+
if use_cold_l2:
|
| 798 |
+
one_workspace_bytes = (
|
| 799 |
+
a.numel() * a.element_size()
|
| 800 |
+
+ b.numel() * b.element_size()
|
| 801 |
+
+ c.numel() * c.element_size()
|
| 802 |
+
)
|
| 803 |
+
workspace_count = testing.get_workspace_count(
|
| 804 |
+
one_workspace_bytes, warmup_iterations, iterations
|
| 805 |
+
)
|
| 806 |
+
|
| 807 |
+
avg_time_us = testing.benchmark(
|
| 808 |
+
compiled_fn,
|
| 809 |
+
workspace_generator=generate_tensors,
|
| 810 |
+
workspace_count=workspace_count,
|
| 811 |
+
stream=current_stream,
|
| 812 |
+
warmup_iterations=warmup_iterations,
|
| 813 |
+
iterations=iterations,
|
| 814 |
+
)
|
| 815 |
+
|
| 816 |
+
# Print execution results
|
| 817 |
+
print(f"Kernel execution time: {avg_time_us / 1e3:.4f} ms")
|
| 818 |
+
|
| 819 |
+
return avg_time_us # Return execution time in microseconds
|
| 820 |
+
|
| 821 |
+
|
| 822 |
+
if __name__ == "__main__":
|
| 823 |
+
|
| 824 |
+
def parse_comma_separated_ints(s: str) -> Tuple[int, ...]:
|
| 825 |
+
try:
|
| 826 |
+
return tuple(int(x.strip()) for x in s.split(","))
|
| 827 |
+
except ValueError:
|
| 828 |
+
raise argparse.ArgumentTypeError(
|
| 829 |
+
"Invalid format. Expected comma-separated integers."
|
| 830 |
+
)
|
| 831 |
+
|
| 832 |
+
parser = argparse.ArgumentParser()
|
| 833 |
+
parser.add_argument(
|
| 834 |
+
"--mnk", type=parse_comma_separated_ints, default=(256, 256, 64)
|
| 835 |
+
)
|
| 836 |
+
parser.add_argument("--a_major", choices=["k", "m"], default="k")
|
| 837 |
+
parser.add_argument("--b_major", choices=["k", "n"], default="k")
|
| 838 |
+
parser.add_argument("--c_major", choices=["n", "m"], default="n")
|
| 839 |
+
parser.add_argument("--warmup_iterations", default=2, type=int)
|
| 840 |
+
parser.add_argument("--iterations", default=100, type=int)
|
| 841 |
+
parser.add_argument("--static_shape", action="store_true")
|
| 842 |
+
parser.add_argument("--skip_ref_check", action="store_true")
|
| 843 |
+
parser.add_argument(
|
| 844 |
+
"--use_cold_l2",
|
| 845 |
+
action="store_true",
|
| 846 |
+
default=False,
|
| 847 |
+
help="Use circular buffer tensor sets to ensure L2 cold cache",
|
| 848 |
+
)
|
| 849 |
+
|
| 850 |
+
args = parser.parse_args()
|
| 851 |
+
print("Running SIMT GEMM example:")
|
| 852 |
+
|
| 853 |
+
torch.manual_seed(1024)
|
| 854 |
+
|
| 855 |
+
run(
|
| 856 |
+
args.mnk,
|
| 857 |
+
args.a_major,
|
| 858 |
+
args.b_major,
|
| 859 |
+
args.c_major,
|
| 860 |
+
args.static_shape,
|
| 861 |
+
args.warmup_iterations,
|
| 862 |
+
args.iterations,
|
| 863 |
+
args.skip_ref_check,
|
| 864 |
+
args.use_cold_l2,
|
| 865 |
+
)
|
| 866 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/smem_allocator.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import cutlass.cute as cute
|
| 30 |
+
import cutlass
|
| 31 |
+
import torch
|
| 32 |
+
import numpy as np
|
| 33 |
+
from cutlass.cute.runtime import from_dlpack
|
| 34 |
+
|
| 35 |
+
"""
|
| 36 |
+
A Shared Memory Allocator Example on NVIDIA Ampere architecture using CuTe DSL.
|
| 37 |
+
|
| 38 |
+
This example demonstrates how to allocate and manage shared memory in JIT kernels by using the SmemAllocator in CuTe DSL.
|
| 39 |
+
It shows various ways to allocate different data structures in shared memory:
|
| 40 |
+
|
| 41 |
+
1. Struct allocation with natural and strict alignment
|
| 42 |
+
2. Raw memory block allocation with custom alignment
|
| 43 |
+
3. Array allocation with automatic alignment
|
| 44 |
+
4. Tensor allocation with layout specification
|
| 45 |
+
|
| 46 |
+
The example includes:
|
| 47 |
+
- Shared storage struct with mixed alignment requirements
|
| 48 |
+
- Memory allocation patterns for different data types
|
| 49 |
+
- Tensor operations on allocated memory
|
| 50 |
+
|
| 51 |
+
To run this example:
|
| 52 |
+
|
| 53 |
+
.. code-block:: bash
|
| 54 |
+
|
| 55 |
+
python examples/ampere/smem_allocator.py
|
| 56 |
+
|
| 57 |
+
The example will allocate shared memory, perform tensor operations, and verify the results.
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@cute.struct
|
| 62 |
+
class complex:
|
| 63 |
+
real: cutlass.Float32
|
| 64 |
+
imag: cutlass.Float32
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# SharedStorage size is 512, alignment is 128
|
| 68 |
+
@cute.struct
|
| 69 |
+
class SharedStorage:
|
| 70 |
+
# struct elements with natural alignment
|
| 71 |
+
a: cute.struct.MemRange[cutlass.Float32, 32] # array
|
| 72 |
+
b: cutlass.Int64 # saclar
|
| 73 |
+
c: complex # nested struct
|
| 74 |
+
# struct elements with strict alignment
|
| 75 |
+
x: cute.struct.Align[
|
| 76 |
+
cute.struct.MemRange[cutlass.Float32, 32],
|
| 77 |
+
128,
|
| 78 |
+
]
|
| 79 |
+
y: cute.struct.Align[cutlass.Int32, 8]
|
| 80 |
+
z: cute.struct.Align[complex, 16]
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@cute.kernel
|
| 84 |
+
def kernel(
|
| 85 |
+
const_a: cutlass.Constexpr,
|
| 86 |
+
dst_a: cute.Tensor,
|
| 87 |
+
const_b: cutlass.Constexpr,
|
| 88 |
+
dst_b: cute.Tensor,
|
| 89 |
+
const_c: cutlass.Constexpr,
|
| 90 |
+
dst_c: cute.Tensor,
|
| 91 |
+
):
|
| 92 |
+
# Note: SMEM_SIZE bytes (specified in kernel().launch(smem=...)) can be reserved for developer to utilize
|
| 93 |
+
# Note: alignment of inital allocator base ptr is 1024
|
| 94 |
+
allocator = cutlass.utils.SmemAllocator()
|
| 95 |
+
# base ptr of allocator points at: SMEM_ADDR_START (the starting address of available shared memory)
|
| 96 |
+
|
| 97 |
+
# -- Allocate a struct --
|
| 98 |
+
# Note: when specified alignment, max(alignment, alignof(struct)) will be applied
|
| 99 |
+
# reserves the section of struct in smem, elements in the struct can be accessed by ptr
|
| 100 |
+
struct_in_smem = allocator.allocate(SharedStorage)
|
| 101 |
+
# base ptr of allocator now points at: SMEM_ADDR_AFTER_STRUCT = SMEM_ADDR_START + aligned_size(struct)
|
| 102 |
+
|
| 103 |
+
# -- Allocate a block of memory --
|
| 104 |
+
# reserves a section of 64 bytes in smem, align to 128 bytes, returns the section base ptr
|
| 105 |
+
section_in_smem = allocator.allocate(64, byte_alignment=128)
|
| 106 |
+
# base ptr of allocator now points at: SMEM_ADDR_AFTER_SECTION = SMEM_ADDR_AFTER_STRUCT + aligned_size(section)
|
| 107 |
+
|
| 108 |
+
# -- Allocate an array --
|
| 109 |
+
# reserves an int64 array of size 14 in smem, returns the array base ptr
|
| 110 |
+
array_in_smem = allocator.allocate_array(element_type=cutlass.Int64, num_elems=14)
|
| 111 |
+
# base ptr of allocator now points at: SMEM_ADDR_AFTER_ARRAY = SMEM_ADDR_AFTER_SECTION + aligned_size(array)
|
| 112 |
+
|
| 113 |
+
# -- Allocate a tensor --
|
| 114 |
+
# Note: use cute.ComposedLayout or cute.Layout to specify layout of tensor
|
| 115 |
+
# Note: iterator swizzle with swizzle layout is currently not supported
|
| 116 |
+
layout = cute.make_layout((16, 2))
|
| 117 |
+
tensor_in_smem = allocator.allocate_tensor(
|
| 118 |
+
element_type=cutlass.Float32, layout=layout, byte_alignment=32, swizzle=None
|
| 119 |
+
)
|
| 120 |
+
# base ptr of allocator now points at: SMEM_ADDR_AFTER_TENSOR = SMEM_ADDR_AFTER_ARRAY + aligned_size(tensor)
|
| 121 |
+
|
| 122 |
+
# ptr<f16, smem, align<1024>>
|
| 123 |
+
# ptr<i64, smem, align<128>>
|
| 124 |
+
# ptr<f32, smem, align<8>>
|
| 125 |
+
print(struct_in_smem.a.data_ptr())
|
| 126 |
+
print(struct_in_smem.b)
|
| 127 |
+
print(struct_in_smem.c.real)
|
| 128 |
+
# ptr<i8, smem, align<512>>
|
| 129 |
+
print(section_in_smem)
|
| 130 |
+
# ptr<i64, smem, align<64>>
|
| 131 |
+
print(array_in_smem)
|
| 132 |
+
# tensor<ptr<f16, smem, align<32>> o (16,4):(1,16)>
|
| 133 |
+
print(tensor_in_smem)
|
| 134 |
+
|
| 135 |
+
# fill MemRange tensor in struct and copy to dst
|
| 136 |
+
a_tensor = struct_in_smem.a.get_tensor(cute.make_layout((8, 4)))
|
| 137 |
+
a_tensor.fill(const_a)
|
| 138 |
+
cute.printf("cute.struct.MemRange: {}", a_tensor)
|
| 139 |
+
dst_a.store(a_tensor.load())
|
| 140 |
+
|
| 141 |
+
# convert block of smem to fill tensor and copy to dst
|
| 142 |
+
layout = cute.make_layout((8, 2))
|
| 143 |
+
sec_ptr = cute.recast_ptr(section_in_smem, dtype=cutlass.Float32)
|
| 144 |
+
sec_tensor = cute.make_tensor(sec_ptr, layout)
|
| 145 |
+
sec_tensor.fill(const_b)
|
| 146 |
+
cute.printf("block of memory: {}", sec_tensor)
|
| 147 |
+
dst_b.store(sec_tensor.load())
|
| 148 |
+
|
| 149 |
+
# fill allocated tensor in smem and copy to dst
|
| 150 |
+
tensor_in_smem.fill(const_c)
|
| 151 |
+
cute.printf("tensor in smem: {}", tensor_in_smem)
|
| 152 |
+
dst_c.store(tensor_in_smem.load())
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@cute.jit
|
| 156 |
+
def run_allocation_kernel(
|
| 157 |
+
const_a: cutlass.Constexpr,
|
| 158 |
+
dst_a: cute.Tensor,
|
| 159 |
+
const_b: cutlass.Constexpr,
|
| 160 |
+
dst_b: cute.Tensor,
|
| 161 |
+
const_c: cutlass.Constexpr,
|
| 162 |
+
dst_c: cute.Tensor,
|
| 163 |
+
):
|
| 164 |
+
# additional size for the example, 64(section) + 112(array) + 128(tensor) < 384
|
| 165 |
+
addtional_bytes = 384
|
| 166 |
+
# Note: launch shared memory size is: SMEM_SIZE = 512 + 384 = 896 bytes
|
| 167 |
+
kernel(const_a, dst_a, const_b, dst_b, const_c, dst_c).launch(
|
| 168 |
+
grid=(1, 1, 1),
|
| 169 |
+
block=(1, 1, 1),
|
| 170 |
+
smem=SharedStorage.size_in_bytes() + addtional_bytes,
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def veify_allocation_kernel(const_a, const_b, const_c):
|
| 175 |
+
dst_a = torch.zeros((8, 4), dtype=torch.float32, device="cuda")
|
| 176 |
+
dst_b = torch.zeros((8, 2), dtype=torch.float32, device="cuda")
|
| 177 |
+
dst_c = torch.zeros((16, 2), dtype=torch.float32, device="cuda")
|
| 178 |
+
|
| 179 |
+
run_allocation_kernel(
|
| 180 |
+
const_a,
|
| 181 |
+
from_dlpack(dst_a),
|
| 182 |
+
const_b,
|
| 183 |
+
from_dlpack(dst_b),
|
| 184 |
+
const_c,
|
| 185 |
+
from_dlpack(dst_c),
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
np.testing.assert_equal(const_a, dst_a.detach().cpu().numpy()[0])
|
| 189 |
+
np.testing.assert_equal(const_b, dst_b.detach().cpu().numpy()[0])
|
| 190 |
+
np.testing.assert_equal(const_c, dst_c.detach().cpu().numpy()[0])
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
if __name__ == "__main__":
|
| 194 |
+
# prepare cuda context
|
| 195 |
+
cutlass.cuda.initialize_cuda_context()
|
| 196 |
+
# An example for shared memory allocation
|
| 197 |
+
const_a = 0.5
|
| 198 |
+
const_b = 1.0
|
| 199 |
+
const_c = 2.0
|
| 200 |
+
veify_allocation_kernel(const_a, const_b, const_c)
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/ampere/tensorop_gemm.py
ADDED
|
@@ -0,0 +1,1012 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
import math
|
| 31 |
+
import time
|
| 32 |
+
from typing import Tuple, Type
|
| 33 |
+
|
| 34 |
+
import cuda.bindings.driver as cuda
|
| 35 |
+
import torch
|
| 36 |
+
|
| 37 |
+
import cutlass
|
| 38 |
+
import cutlass.cute as cute
|
| 39 |
+
import cutlass.cute.testing as testing
|
| 40 |
+
import cutlass.torch as cutlass_torch
|
| 41 |
+
import cutlass.utils as utils
|
| 42 |
+
from cutlass.cute.runtime import from_dlpack
|
| 43 |
+
|
| 44 |
+
"""
|
| 45 |
+
A dense GEMM (C = A * B) example for the NVIDIA Ampere architecture using CUTE DSL.
|
| 46 |
+
- Matrix A is MxKxL, L is batch dimension, A can be row-major("K") or column-major("M")
|
| 47 |
+
- Matrix B is NxKxL, L is batch dimension, B can be row-major("N") or column-major("K")
|
| 48 |
+
- Matrix C is MxNxL, L is batch dimension, C can be row-major("N") or column-major("M")
|
| 49 |
+
|
| 50 |
+
This GEMM kernel supports the following features:
|
| 51 |
+
- Utilizes Ampere's tensor cores for matrix multiply-accumulate (MMA) operations
|
| 52 |
+
- Threadblock rasterization to improve data re-use
|
| 53 |
+
- Supports multi-stage pipeline to overlap computation and memory access
|
| 54 |
+
- Implements shared memory buffering for epilogue to increase coalesced global memory access
|
| 55 |
+
|
| 56 |
+
This GEMM works as follows:
|
| 57 |
+
1. Load A and B matrices from global memory (GMEM) to shared memory (SMEM) using asynchronous copies.
|
| 58 |
+
2. Perform matrix multiply-accumulate (MMA) operations.
|
| 59 |
+
3. Store results from registers (RMEM) to shared memory (SMEM), then to global memory (GMEM).
|
| 60 |
+
|
| 61 |
+
The Ampere tensor core instruction used operates as follows:
|
| 62 |
+
- Read matrix A from SMEM
|
| 63 |
+
- Read matrix B from SMEM
|
| 64 |
+
- Perform MMA operation and store the result in Accumulator(register)
|
| 65 |
+
|
| 66 |
+
To run this example:
|
| 67 |
+
|
| 68 |
+
.. code-block:: bash
|
| 69 |
+
|
| 70 |
+
python examples/ampere/tensorop_gemm.py \
|
| 71 |
+
--mnkl 8192,8192,8192,1 --atom_layout_mnk 2,2,1 \
|
| 72 |
+
--ab_dtype Float16 \
|
| 73 |
+
--c_dtype Float16 --acc_dtype Float32 \
|
| 74 |
+
--a_major m --b_major n --c_major n
|
| 75 |
+
|
| 76 |
+
The above example command computes with M=8192, N=8192, K=8192,
|
| 77 |
+
batch_count=1. The atom layout's shape is 2x2x1 and the input, mma
|
| 78 |
+
accumulator, and output data type are set as fp16, fp32 and fp16,
|
| 79 |
+
respectively.
|
| 80 |
+
|
| 81 |
+
To collect performance with NCU profiler:
|
| 82 |
+
|
| 83 |
+
.. code-block:: bash
|
| 84 |
+
|
| 85 |
+
ncu python examples/ampere/tensorop_gemm.py \
|
| 86 |
+
--mnkl 8192,8192,8192,1 --atom_layout_mnk 2,2,1 \
|
| 87 |
+
--ab_dtype Float16 \
|
| 88 |
+
--c_dtype Float16 --acc_dtype Float32 \
|
| 89 |
+
--a_major m --b_major n --c_major n \
|
| 90 |
+
--skip_ref_check --iterations 2
|
| 91 |
+
|
| 92 |
+
Constraints:
|
| 93 |
+
* Supported input and output data types: fp16
|
| 94 |
+
* Support accumulator data types: f32
|
| 95 |
+
* Default tile shape is set to be 128x128x32
|
| 96 |
+
* Atom layout's MNK shape is set so that tile shape can be divided by MMA
|
| 97 |
+
instruction shape
|
| 98 |
+
* The contiguous dimension of A/B/C tensors must be at least 16 bytes aligned,
|
| 99 |
+
i.e, number of elements is a multiple of 8
|
| 100 |
+
"""
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class TensorOpGemm:
|
| 104 |
+
def __init__(
|
| 105 |
+
self,
|
| 106 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 107 |
+
c_dtype: Type[cutlass.Numeric],
|
| 108 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 109 |
+
atom_layout_mnk: Tuple[int, int, int],
|
| 110 |
+
):
|
| 111 |
+
self.ab_dtype = ab_dtype
|
| 112 |
+
self.c_dtype = c_dtype
|
| 113 |
+
self.acc_dtype = acc_dtype
|
| 114 |
+
self.cta_tiler = (128, 128, 32)
|
| 115 |
+
self.num_stages = 3
|
| 116 |
+
self.atom_layout_mnk = atom_layout_mnk
|
| 117 |
+
atom_lay_M, atom_lay_N, atom_lay_K = self.atom_layout_mnk
|
| 118 |
+
self.num_threads = atom_lay_M * atom_lay_N * atom_lay_K * 32
|
| 119 |
+
|
| 120 |
+
self.bM, self.bN, self.bK = self.cta_tiler
|
| 121 |
+
self.mma_inst_shape = (16, 8, 16)
|
| 122 |
+
mmaM, mmaN, mmaK = self.mma_inst_shape
|
| 123 |
+
|
| 124 |
+
assert (
|
| 125 |
+
self.bM % (atom_lay_M * mmaM) == 0
|
| 126 |
+
), "bM must be divisible by MMA instruction"
|
| 127 |
+
assert (
|
| 128 |
+
self.bN % (atom_lay_N * mmaN) == 0
|
| 129 |
+
), "bN must be divisible by MMA instruction"
|
| 130 |
+
assert atom_lay_K == 1, "this example does not support atom layout K > 1"
|
| 131 |
+
assert self.bK % mmaK == 0, "bK must be divisible by MMA instruction"
|
| 132 |
+
assert self.num_stages >= 3, "num_stages must be greater than or equal to 3"
|
| 133 |
+
|
| 134 |
+
@cute.jit
|
| 135 |
+
def __call__(
|
| 136 |
+
self,
|
| 137 |
+
mA: cute.Tensor,
|
| 138 |
+
mB: cute.Tensor,
|
| 139 |
+
mC: cute.Tensor,
|
| 140 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 141 |
+
):
|
| 142 |
+
# The grid divides the problems's M, N, and L dimensions by the
|
| 143 |
+
# respective modes of the tile shape (bM, bN, 1). The K dimension is
|
| 144 |
+
# handled within a block via a multistage process.
|
| 145 |
+
|
| 146 |
+
self.a_major_mode = utils.LayoutEnum.from_tensor(mA)
|
| 147 |
+
self.b_major_mode = utils.LayoutEnum.from_tensor(mB)
|
| 148 |
+
self.c_major_mode = utils.LayoutEnum.from_tensor(mC)
|
| 149 |
+
|
| 150 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 151 |
+
# Shared memory layout:
|
| 152 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 153 |
+
|
| 154 |
+
# Creates a layout with the size required for the provided tile
|
| 155 |
+
# size and num stages (stages are used for K dimension) that is also
|
| 156 |
+
# sectioned into 64x8 or 8x32 layout atoms. The swizzle is set so that
|
| 157 |
+
# the atom for the shared memory -> register copy does not encounter
|
| 158 |
+
# bank conflicts
|
| 159 |
+
|
| 160 |
+
# assume the input is 16B align
|
| 161 |
+
ab_copy_bits = 128
|
| 162 |
+
sA_layout = self._make_smem_layout_AB(
|
| 163 |
+
mA.element_type,
|
| 164 |
+
self.a_major_mode,
|
| 165 |
+
ab_copy_bits,
|
| 166 |
+
(self.cta_tiler[0], self.cta_tiler[2], self.num_stages),
|
| 167 |
+
)
|
| 168 |
+
sB_layout = self._make_smem_layout_AB(
|
| 169 |
+
mB.element_type,
|
| 170 |
+
self.b_major_mode,
|
| 171 |
+
ab_copy_bits,
|
| 172 |
+
(self.cta_tiler[1], self.cta_tiler[2], self.num_stages),
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
# Creates a similar layout but without num_stages or layout atoms
|
| 176 |
+
sC_layout = self._make_smem_layout_C(
|
| 177 |
+
mC.element_type,
|
| 178 |
+
self.c_major_mode,
|
| 179 |
+
ab_copy_bits,
|
| 180 |
+
(self.cta_tiler[0], self.cta_tiler[1]),
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# Shared memory allocated for operations with A, B will be
|
| 184 |
+
# overwritten for operations on C. This is to improve performance
|
| 185 |
+
# by reducing the size of shared memory requested by each block
|
| 186 |
+
smem_size = max(
|
| 187 |
+
cute.size_in_bytes(mC.element_type, sC_layout),
|
| 188 |
+
cute.size_in_bytes(mA.element_type, sA_layout)
|
| 189 |
+
+ cute.size_in_bytes(mB.element_type, sB_layout),
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 193 |
+
# Tiled copy:
|
| 194 |
+
# The majorness of tA/tB/tC follows the majorness of gA/gB/gC,
|
| 195 |
+
# enabling merged accesses to global memory for faster data
|
| 196 |
+
# transfer between global and shared memory.
|
| 197 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 198 |
+
|
| 199 |
+
# Create a copy atom for a global to shared memory asynchronous copy
|
| 200 |
+
atom_async_copy = cute.make_copy_atom(
|
| 201 |
+
cute.nvgpu.cpasync.CopyG2SOp(
|
| 202 |
+
cache_mode=cute.nvgpu.cpasync.LoadCacheMode.GLOBAL
|
| 203 |
+
),
|
| 204 |
+
mA.element_type,
|
| 205 |
+
num_bits_per_copy=ab_copy_bits,
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
# Create thread layouts for tiled copy from the copy atom where the
|
| 209 |
+
# thread layout simply follows the leading dimension of the tensor
|
| 210 |
+
tiled_copy_A = self._make_gmem_tiled_copy_AB(
|
| 211 |
+
atom_async_copy, mA.element_type, self.a_major_mode, ab_copy_bits
|
| 212 |
+
)
|
| 213 |
+
tiled_copy_B = self._make_gmem_tiled_copy_AB(
|
| 214 |
+
atom_async_copy, mB.element_type, self.b_major_mode, ab_copy_bits
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
# Creates a synchronous copy atom and thread layouts for the epilogue
|
| 218 |
+
c_copy_bits = 128
|
| 219 |
+
atom_sync_copy = cute.make_copy_atom(
|
| 220 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 221 |
+
mC.element_type,
|
| 222 |
+
num_bits_per_copy=c_copy_bits,
|
| 223 |
+
)
|
| 224 |
+
tiled_copy_C = self._make_gmem_tiled_copy_C(
|
| 225 |
+
atom_sync_copy, mC.element_type, self.c_major_mode, c_copy_bits
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 229 |
+
# Tiled MMA
|
| 230 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 231 |
+
|
| 232 |
+
# Creates a mma atom with 16x8x16 shape for MNK
|
| 233 |
+
op = cute.nvgpu.warp.MmaF16BF16Op(
|
| 234 |
+
self.ab_dtype, self.acc_dtype, self.mma_inst_shape
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
permutation_mnk = (
|
| 238 |
+
self.atom_layout_mnk[0] * self.mma_inst_shape[0],
|
| 239 |
+
# if atom layout's N-mode is 1, to leverage the largest coalesced
|
| 240 |
+
# shared memory -> register copy, set the tiled mma's N mode to 16
|
| 241 |
+
self.atom_layout_mnk[1] * self.mma_inst_shape[1] * 2,
|
| 242 |
+
self.atom_layout_mnk[2] * self.mma_inst_shape[2],
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
# Created a tiled mma that tiles the atom according to specified layout.
|
| 246 |
+
# For a 2x2x1 atom layout, the mma atom is duplicated 4 times, twice
|
| 247 |
+
# across M and twice across N
|
| 248 |
+
tC = cute.make_layout(self.atom_layout_mnk)
|
| 249 |
+
tiled_mma = cute.make_tiled_mma(
|
| 250 |
+
op,
|
| 251 |
+
tC,
|
| 252 |
+
permutation_mnk=permutation_mnk,
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
# grid_dim: ((m + BLK_M - 1) // BLK_M, (n + BLK_N - 1) // BLK_N, l)
|
| 256 |
+
grid_dim = cute.ceil_div(mC.shape, (self.bM, self.bN, 1))
|
| 257 |
+
|
| 258 |
+
# Add threadblock rasterization to improve re-use of data
|
| 259 |
+
raster_factor = 1
|
| 260 |
+
grid_dim_n = cute.size(grid_dim[1])
|
| 261 |
+
# Thresholds picked so that it doesn't cause too many no-op CTAs
|
| 262 |
+
if grid_dim_n > 5:
|
| 263 |
+
raster_factor = 8
|
| 264 |
+
elif grid_dim_n > 2:
|
| 265 |
+
raster_factor = 4
|
| 266 |
+
elif grid_dim_n > 1:
|
| 267 |
+
raster_factor = 2
|
| 268 |
+
rasterization_remap_grid_dim = (
|
| 269 |
+
cute.size(grid_dim[0]) * raster_factor,
|
| 270 |
+
(cute.size(grid_dim[1]) + raster_factor - 1) // raster_factor,
|
| 271 |
+
cute.size(grid_dim[2]),
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
self.kernel(
|
| 275 |
+
mA,
|
| 276 |
+
mB,
|
| 277 |
+
mC,
|
| 278 |
+
sA_layout,
|
| 279 |
+
sB_layout,
|
| 280 |
+
sC_layout,
|
| 281 |
+
tiled_copy_A,
|
| 282 |
+
tiled_copy_B,
|
| 283 |
+
tiled_copy_C,
|
| 284 |
+
tiled_mma,
|
| 285 |
+
raster_factor,
|
| 286 |
+
epilogue_op,
|
| 287 |
+
).launch(
|
| 288 |
+
grid=rasterization_remap_grid_dim,
|
| 289 |
+
block=[self.num_threads, 1, 1],
|
| 290 |
+
smem=smem_size,
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
@cute.kernel
|
| 294 |
+
def kernel(
|
| 295 |
+
self,
|
| 296 |
+
mA: cute.Tensor,
|
| 297 |
+
mB: cute.Tensor,
|
| 298 |
+
mC: cute.Tensor,
|
| 299 |
+
sA_layout: cute.ComposedLayout,
|
| 300 |
+
sB_layout: cute.ComposedLayout,
|
| 301 |
+
sC_layout: cute.ComposedLayout,
|
| 302 |
+
tiled_copy_A: cute.TiledCopy,
|
| 303 |
+
tiled_copy_B: cute.TiledCopy,
|
| 304 |
+
tiled_copy_C: cute.TiledCopy,
|
| 305 |
+
tiled_mma: cute.TiledMma,
|
| 306 |
+
rasterization_factor: cutlass.Int32,
|
| 307 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 308 |
+
):
|
| 309 |
+
# Thread index, block index
|
| 310 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 311 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 312 |
+
grid_dim = cute.ceil_div(mC.shape, (self.bM, self.bN, 1))
|
| 313 |
+
offset_tile_x, offset_tile_y = self.raster_tile(
|
| 314 |
+
bidx, bidy, rasterization_factor
|
| 315 |
+
)
|
| 316 |
+
# Early exit if CTA is out of range
|
| 317 |
+
if grid_dim[0] <= offset_tile_x or grid_dim[1] <= offset_tile_y:
|
| 318 |
+
pass
|
| 319 |
+
else:
|
| 320 |
+
tiler_coord = (offset_tile_x, offset_tile_y, None)
|
| 321 |
+
|
| 322 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 323 |
+
# Get the appropriate tiles for this thread block.
|
| 324 |
+
# gA: (BLK_M, BLK_N, k), gB: (BLK_N, BLK_K, k), gC: (BLK_M, BLK_N)
|
| 325 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 326 |
+
gA = cute.local_tile(
|
| 327 |
+
mA[None, None, bidz],
|
| 328 |
+
tiler=self.cta_tiler,
|
| 329 |
+
coord=tiler_coord,
|
| 330 |
+
proj=(1, None, 1),
|
| 331 |
+
)
|
| 332 |
+
gB = cute.local_tile(
|
| 333 |
+
mB[None, None, bidz],
|
| 334 |
+
tiler=self.cta_tiler,
|
| 335 |
+
coord=tiler_coord,
|
| 336 |
+
proj=(None, 1, 1),
|
| 337 |
+
)
|
| 338 |
+
gC = cute.local_tile(
|
| 339 |
+
mC[None, None, bidz],
|
| 340 |
+
tiler=self.cta_tiler,
|
| 341 |
+
coord=tiler_coord,
|
| 342 |
+
proj=(1, 1, None),
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
# By default, if the tensor k mode does not divide into the tile k
|
| 346 |
+
# size, then last tiles in the k dimension are irregular.
|
| 347 |
+
# Instead, make the first tiles irregular when k is irregular.
|
| 348 |
+
# This allows us to handle the irregular tile first to avoid
|
| 349 |
+
# checking for this condition within the mainloop.
|
| 350 |
+
|
| 351 |
+
# residual_k is a negative number indicating the amount needed to
|
| 352 |
+
# shift the pointer by in dimension k
|
| 353 |
+
residual_k = cute.size(mA, mode=[1]) - cutlass.Int32(self.bK) * cute.size(
|
| 354 |
+
gA, mode=[2]
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
# move the pointer of gA/gB in the `-k` direction
|
| 358 |
+
gA = cute.domain_offset((0, residual_k, 0), gA)
|
| 359 |
+
gB = cute.domain_offset((0, residual_k, 0), gB)
|
| 360 |
+
# input is 16B aligned
|
| 361 |
+
gA = cute.make_tensor(gA.iterator.align(16), gA.layout)
|
| 362 |
+
gB = cute.make_tensor(gB.iterator.align(16), gB.layout)
|
| 363 |
+
|
| 364 |
+
# Construct identity layout for sA and sB (mirrors global tensors,
|
| 365 |
+
# used for predication only)
|
| 366 |
+
mcA = cute.make_identity_tensor(mA.layout.shape)
|
| 367 |
+
mcB = cute.make_identity_tensor(mB.layout.shape)
|
| 368 |
+
cA = cute.local_tile(
|
| 369 |
+
mcA[None, None, bidz],
|
| 370 |
+
tiler=self.cta_tiler,
|
| 371 |
+
coord=tiler_coord,
|
| 372 |
+
proj=(1, None, 1),
|
| 373 |
+
)
|
| 374 |
+
cB = cute.local_tile(
|
| 375 |
+
mcB[None, None, bidz],
|
| 376 |
+
tiler=self.cta_tiler,
|
| 377 |
+
coord=tiler_coord,
|
| 378 |
+
proj=(None, 1, 1),
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
cA = cute.domain_offset((0, residual_k, 0), cA)
|
| 382 |
+
cB = cute.domain_offset((0, residual_k, 0), cB)
|
| 383 |
+
|
| 384 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 385 |
+
# Create shared memory buffers and get the appropriate fragments for this thread.
|
| 386 |
+
# sA: (BLK_M, BLK_K, PIPE) , sB: (BLK_N, BLK_K, PIPE)
|
| 387 |
+
# tAgA: (CPY, CPY_M, CPY_K, k) , tBgB: (CPY, CPY_N, CPY_K, k)
|
| 388 |
+
# tAsA: (CPY, CPY_M, CPY_K, PIPE) , tBsB: (CPY, CPY_N, CPY_K, PIPE)
|
| 389 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 390 |
+
# Shared memory buffer
|
| 391 |
+
smem = cutlass.utils.SmemAllocator()
|
| 392 |
+
|
| 393 |
+
sA = smem.allocate_tensor(mA.element_type, sA_layout, 16)
|
| 394 |
+
sB = smem.allocate_tensor(mB.element_type, sB_layout, 16)
|
| 395 |
+
sC = cute.make_tensor(
|
| 396 |
+
cute.recast_ptr(sA.iterator, dtype=self.c_dtype), sC_layout
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
thr_copy_A = tiled_copy_A.get_slice(tidx)
|
| 400 |
+
thr_copy_B = tiled_copy_B.get_slice(tidx)
|
| 401 |
+
thr_copy_C = tiled_copy_C.get_slice(tidx)
|
| 402 |
+
tAgA = thr_copy_A.partition_S(gA)
|
| 403 |
+
tAsA = thr_copy_A.partition_D(sA)
|
| 404 |
+
tBgB = thr_copy_B.partition_S(gB)
|
| 405 |
+
tBsB = thr_copy_B.partition_D(sB)
|
| 406 |
+
tCsC_epilogue = thr_copy_C.partition_S(sC)
|
| 407 |
+
tCgC_epilogue = thr_copy_C.partition_D(gC)
|
| 408 |
+
|
| 409 |
+
# Repeat the partitioning with identity layouts
|
| 410 |
+
tAcA = thr_copy_A.partition_S(cA)
|
| 411 |
+
tBcB = thr_copy_B.partition_S(cB)
|
| 412 |
+
|
| 413 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 414 |
+
# Predicate: Mark indices that need to copy when problem_shape isn't a multiple
|
| 415 |
+
# of tile_shape
|
| 416 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 417 |
+
|
| 418 |
+
# For predication over the tensors A (M/K), B (N/K), and (in the
|
| 419 |
+
# epilogue) C (M/N), we will compute it in a fashion similar to an
|
| 420 |
+
# outer product. The predication along one of the dimensions is
|
| 421 |
+
# evaluated and stored in a predication tensor. Then, the
|
| 422 |
+
# predication for the remaining dimension is handled later via an
|
| 423 |
+
# if/else branch at the copy.
|
| 424 |
+
# For A and B, predication booleans along M/N are stored in a
|
| 425 |
+
# predication tensor and along K is handled via a if/else branch.
|
| 426 |
+
|
| 427 |
+
# Allocate predicate tensors for M and N. Predication is checked
|
| 428 |
+
# at the granularity of a copy atom, so the predicate tensor does not
|
| 429 |
+
# need separate booleans for individual elements within a copy
|
| 430 |
+
# atom (for example, the elements of tAgA.shape[0][0].)
|
| 431 |
+
tApA = cute.make_fragment(
|
| 432 |
+
cute.make_layout(
|
| 433 |
+
(
|
| 434 |
+
tAgA.shape[0][1],
|
| 435 |
+
cute.size(tAgA, mode=[1]),
|
| 436 |
+
cute.size(tAgA, mode=[2]),
|
| 437 |
+
),
|
| 438 |
+
stride=(cute.size(tAgA, mode=[1]), 1, 0),
|
| 439 |
+
),
|
| 440 |
+
cutlass.Boolean,
|
| 441 |
+
)
|
| 442 |
+
tBpB = cute.make_fragment(
|
| 443 |
+
cute.make_layout(
|
| 444 |
+
(
|
| 445 |
+
tBsB.shape[0][1],
|
| 446 |
+
cute.size(tBsB, mode=[1]),
|
| 447 |
+
cute.size(tBsB, mode=[2]),
|
| 448 |
+
),
|
| 449 |
+
stride=(cute.size(tBsB, mode=[1]), 1, 0),
|
| 450 |
+
),
|
| 451 |
+
cutlass.Boolean,
|
| 452 |
+
)
|
| 453 |
+
# Set predicates for M/N bounds
|
| 454 |
+
for rest_v in range(tApA.shape[0]):
|
| 455 |
+
for m in range(tApA.shape[1]):
|
| 456 |
+
tApA[rest_v, m, 0] = cute.elem_less(
|
| 457 |
+
tAcA[(0, rest_v), m, 0, 0][0], mA.shape[0]
|
| 458 |
+
)
|
| 459 |
+
for rest_v in range(tBpB.shape[0]):
|
| 460 |
+
for n in range(tBpB.shape[1]):
|
| 461 |
+
tBpB[rest_v, n, 0] = cute.elem_less(
|
| 462 |
+
tBcB[(0, rest_v), n, 0, 0][0], mB.shape[0]
|
| 463 |
+
)
|
| 464 |
+
|
| 465 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 466 |
+
# Prefetch Prologue
|
| 467 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 468 |
+
# Clear the smem tiles to account for predicated off loads
|
| 469 |
+
tAsA.fill(0)
|
| 470 |
+
tBsB.fill(0)
|
| 471 |
+
cute.arch.sync_threads()
|
| 472 |
+
# Start async loads for the first k-tile. Here we take care of the k residue
|
| 473 |
+
# via if/else check along the k dimension. Because we shifted the identity tensor
|
| 474 |
+
# by the residue_k and because the identity tensor is a coord tensor, the
|
| 475 |
+
# values of any identity tensor element that is poison is less than -1
|
| 476 |
+
num_smem_stages = cute.size(tAsA, mode=[3])
|
| 477 |
+
k_tile_count = cute.size(tAgA, mode=[3])
|
| 478 |
+
k_tile_index = cutlass.Int32(0)
|
| 479 |
+
|
| 480 |
+
for k in range(tApA.shape[2]):
|
| 481 |
+
if cute.elem_less(cutlass.Int32(-1), tAcA[0, 0, k, 0][1]):
|
| 482 |
+
cute.copy(
|
| 483 |
+
tiled_copy_A,
|
| 484 |
+
tAgA[None, None, k, k_tile_index],
|
| 485 |
+
tAsA[None, None, k, 0],
|
| 486 |
+
pred=tApA[None, None, k],
|
| 487 |
+
)
|
| 488 |
+
for k in range(tBpB.shape[2]):
|
| 489 |
+
if cute.elem_less(cutlass.Int32(-1), tBcB[0, 0, k, 0][1]):
|
| 490 |
+
cute.copy(
|
| 491 |
+
tiled_copy_B,
|
| 492 |
+
tBgB[None, None, k, k_tile_index],
|
| 493 |
+
tBsB[None, None, k, 0],
|
| 494 |
+
pred=tBpB[None, None, k],
|
| 495 |
+
)
|
| 496 |
+
k_tile_index = k_tile_index + 1
|
| 497 |
+
cute.arch.cp_async_commit_group()
|
| 498 |
+
|
| 499 |
+
# Start async loads for rest of the k-tiles
|
| 500 |
+
for k_tile in range(1, num_smem_stages - 1):
|
| 501 |
+
if k_tile == k_tile_count:
|
| 502 |
+
tApA.fill(0)
|
| 503 |
+
tBpB.fill(0)
|
| 504 |
+
cute.copy(
|
| 505 |
+
tiled_copy_A,
|
| 506 |
+
tAgA[None, None, None, k_tile_index],
|
| 507 |
+
tAsA[None, None, None, k_tile],
|
| 508 |
+
pred=tApA,
|
| 509 |
+
)
|
| 510 |
+
cute.copy(
|
| 511 |
+
tiled_copy_B,
|
| 512 |
+
tBgB[None, None, None, k_tile_index],
|
| 513 |
+
tBsB[None, None, None, k_tile],
|
| 514 |
+
pred=tBpB,
|
| 515 |
+
)
|
| 516 |
+
k_tile_index = k_tile_index + 1
|
| 517 |
+
cute.arch.cp_async_commit_group()
|
| 518 |
+
|
| 519 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 520 |
+
# Tile MMA compute thread partitions and allocate accumulators
|
| 521 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 522 |
+
thr_mma = tiled_mma.get_slice(tidx)
|
| 523 |
+
tCsA = thr_mma.partition_A(sA)
|
| 524 |
+
tCsB = thr_mma.partition_B(sB)
|
| 525 |
+
tCsC = thr_mma.partition_C(sC)
|
| 526 |
+
tCgC = thr_mma.partition_C(gC)
|
| 527 |
+
tCrA = tiled_mma.make_fragment_A(tCsA[None, None, None, 0])
|
| 528 |
+
tCrB = tiled_mma.make_fragment_B(tCsB[None, None, None, 0])
|
| 529 |
+
tCrC = tiled_mma.make_fragment_C(tCgC)
|
| 530 |
+
# Clear the accumulator
|
| 531 |
+
tCrC.fill(0.0)
|
| 532 |
+
|
| 533 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 534 |
+
# Copy Atom A/B retiling
|
| 535 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 536 |
+
|
| 537 |
+
# Create the copy atoms for the copy from shared memory to register
|
| 538 |
+
atom_copy_s2r_A = cute.make_copy_atom(
|
| 539 |
+
cute.nvgpu.warp.LdMatrix8x8x16bOp(
|
| 540 |
+
self.a_major_mode != utils.LayoutEnum.ROW_MAJOR, 4
|
| 541 |
+
),
|
| 542 |
+
mA.element_type,
|
| 543 |
+
)
|
| 544 |
+
atom_copy_s2r_B = cute.make_copy_atom(
|
| 545 |
+
cute.nvgpu.warp.LdMatrix8x8x16bOp(
|
| 546 |
+
self.b_major_mode != utils.LayoutEnum.ROW_MAJOR, 4
|
| 547 |
+
),
|
| 548 |
+
mB.element_type,
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
# Creates the tiled copy so that it matches the thread-value layout
|
| 552 |
+
# expected by the tiled mma
|
| 553 |
+
tiled_copy_s2r_A = cute.make_tiled_copy_A(atom_copy_s2r_A, tiled_mma)
|
| 554 |
+
tiled_copy_s2r_B = cute.make_tiled_copy_B(atom_copy_s2r_B, tiled_mma)
|
| 555 |
+
|
| 556 |
+
thr_copy_ldmatrix_A = tiled_copy_s2r_A.get_slice(tidx)
|
| 557 |
+
thr_copy_ldmatrix_B = tiled_copy_s2r_B.get_slice(tidx)
|
| 558 |
+
tCsA_copy_view = thr_copy_ldmatrix_A.partition_S(sA)
|
| 559 |
+
tCrA_copy_view = thr_copy_ldmatrix_A.retile(tCrA)
|
| 560 |
+
tCsB_copy_view = thr_copy_ldmatrix_B.partition_S(sB)
|
| 561 |
+
tCrB_copy_view = thr_copy_ldmatrix_B.retile(tCrB)
|
| 562 |
+
|
| 563 |
+
# Current pipe index in smem to read from / write to
|
| 564 |
+
smem_pipe_read = 0
|
| 565 |
+
smem_pipe_write = num_smem_stages - 1
|
| 566 |
+
|
| 567 |
+
tCsA_p = tCsA_copy_view[None, None, None, smem_pipe_read]
|
| 568 |
+
tCsB_p = tCsB_copy_view[None, None, None, smem_pipe_read]
|
| 569 |
+
|
| 570 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 571 |
+
# PREFETCH register pipeline
|
| 572 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 573 |
+
num_k_block = cute.size(tCrA, mode=[2])
|
| 574 |
+
if num_k_block > 1:
|
| 575 |
+
# Wait until our first prefetched tile is loaded in
|
| 576 |
+
cute.arch.cp_async_wait_group(num_smem_stages - 2)
|
| 577 |
+
cute.arch.sync_threads()
|
| 578 |
+
# Prefetch the first k-block rmem from the first k-tile
|
| 579 |
+
cute.copy(
|
| 580 |
+
tiled_copy_s2r_A,
|
| 581 |
+
tCsA_p[None, None, 0],
|
| 582 |
+
tCrA_copy_view[None, None, 0],
|
| 583 |
+
)
|
| 584 |
+
cute.copy(
|
| 585 |
+
tiled_copy_s2r_B,
|
| 586 |
+
tCsB_p[None, None, 0],
|
| 587 |
+
tCrB_copy_view[None, None, 0],
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 591 |
+
# Mainloop
|
| 592 |
+
# 1. Shared memory pipeline (gmem -> smem):
|
| 593 |
+
# The default smem pipeline depth is 3, meaning that for shared
|
| 594 |
+
# memory buffers, we allocate three times the size described by the
|
| 595 |
+
# CTA tiler. We prefetch 2 of these buffers before entering the main
|
| 596 |
+
# loop. Considering only the transfer from global memory to shared
|
| 597 |
+
# memory, the general structure of the mainloop is:
|
| 598 |
+
# (1) copy k-tile from gmem to smem;
|
| 599 |
+
# (2) perform gemm computation on k-tile;
|
| 600 |
+
# (3) wait for the next copy to finish.
|
| 601 |
+
# The `cute.arch.cp_async_wait_group(num_smem_stages - 2)` command
|
| 602 |
+
# waits for the number of unfinished 'copy' to be <= 1. The advantage
|
| 603 |
+
# of this approach is that it allows for simultaneous production
|
| 604 |
+
# (i.e., step (1)) and consumption (i.e., step (2)) of smem.
|
| 605 |
+
# A common misconception is to prefetch N buffers and rewrite
|
| 606 |
+
# the pipeline logic to wait on N-1 pending copies. The disadvantage
|
| 607 |
+
# of this approach is that it requires fully consuming a buffer in
|
| 608 |
+
# order to open an empty buffer for the next copy.
|
| 609 |
+
# 2. Register pipeline (smem -> register):
|
| 610 |
+
# Similarly, the register pipeline produces i+1, consumes i, and
|
| 611 |
+
# produces i+2... Notably, i and i+1 do not use the same register,
|
| 612 |
+
# eliminating dependencies on the same register for better parallelism.
|
| 613 |
+
# 3. Combining the smem and register pipelines results in the mainloop.
|
| 614 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 615 |
+
for k_tile in range(k_tile_count):
|
| 616 |
+
for k_block in cutlass.range(num_k_block, unroll_full=True):
|
| 617 |
+
if k_block == num_k_block - 1:
|
| 618 |
+
tCsA_p = tCsA_copy_view[None, None, None, smem_pipe_read]
|
| 619 |
+
tCsB_p = tCsB_copy_view[None, None, None, smem_pipe_read]
|
| 620 |
+
cute.arch.cp_async_wait_group(num_smem_stages - 2)
|
| 621 |
+
cute.arch.sync_threads()
|
| 622 |
+
|
| 623 |
+
# Load A, B from shared memory to registers for k_block + 1
|
| 624 |
+
k_block_next = (k_block + 1) % num_k_block # static
|
| 625 |
+
cute.copy(
|
| 626 |
+
tiled_copy_s2r_A,
|
| 627 |
+
tCsA_p[None, None, k_block_next],
|
| 628 |
+
tCrA_copy_view[None, None, k_block_next],
|
| 629 |
+
)
|
| 630 |
+
cute.copy(
|
| 631 |
+
tiled_copy_s2r_B,
|
| 632 |
+
tCsB_p[None, None, k_block_next],
|
| 633 |
+
tCrB_copy_view[None, None, k_block_next],
|
| 634 |
+
)
|
| 635 |
+
|
| 636 |
+
# Fetch next A: To better interleave global memory access and compute
|
| 637 |
+
# instructions, we intentionally use the sequence: copy A, perform GEMM,
|
| 638 |
+
# then copy B.
|
| 639 |
+
if k_block == 0:
|
| 640 |
+
if k_tile + num_smem_stages - 1 < k_tile_count:
|
| 641 |
+
cute.copy(
|
| 642 |
+
tiled_copy_A,
|
| 643 |
+
tAgA[None, None, None, k_tile_index],
|
| 644 |
+
tAsA[None, None, None, smem_pipe_write],
|
| 645 |
+
pred=tApA,
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
# Thread-level register gemm for k_block
|
| 649 |
+
cute.gemm(
|
| 650 |
+
tiled_mma,
|
| 651 |
+
tCrC,
|
| 652 |
+
tCrA[None, None, k_block],
|
| 653 |
+
tCrB[None, None, k_block],
|
| 654 |
+
tCrC,
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
+
# Fetch next B and update smem pipeline read/write
|
| 658 |
+
if k_block == 0:
|
| 659 |
+
if k_tile + num_smem_stages - 1 < k_tile_count:
|
| 660 |
+
cute.copy(
|
| 661 |
+
tiled_copy_B,
|
| 662 |
+
tBgB[None, None, None, k_tile_index],
|
| 663 |
+
tBsB[None, None, None, smem_pipe_write],
|
| 664 |
+
pred=tBpB,
|
| 665 |
+
)
|
| 666 |
+
k_tile_index = k_tile_index + 1
|
| 667 |
+
cute.arch.cp_async_commit_group()
|
| 668 |
+
smem_pipe_write = smem_pipe_read
|
| 669 |
+
smem_pipe_read = smem_pipe_read + 1
|
| 670 |
+
if smem_pipe_read == num_smem_stages:
|
| 671 |
+
smem_pipe_read = 0
|
| 672 |
+
|
| 673 |
+
# Sync before epilogue
|
| 674 |
+
cute.arch.cp_async_wait_group(0)
|
| 675 |
+
cute.arch.sync_threads()
|
| 676 |
+
|
| 677 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 678 |
+
# Epilogue with fusion
|
| 679 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 680 |
+
tCrD = cute.make_fragment_like(tCrC, self.c_dtype)
|
| 681 |
+
tCrD[None] = epilogue_op(tCrC.load()).to(self.c_dtype)
|
| 682 |
+
|
| 683 |
+
# Copy results of D back to shared memory
|
| 684 |
+
cute.autovec_copy(tCrD, tCsC)
|
| 685 |
+
|
| 686 |
+
# Create coord tensor for C
|
| 687 |
+
ceilM, ceilN, _ = cute.ceil_div(mC.shape, (self.bM, self.bN, 1))
|
| 688 |
+
mcC = cute.make_identity_tensor(
|
| 689 |
+
(
|
| 690 |
+
cute.size(ceilM) * self.cta_tiler[0],
|
| 691 |
+
cute.size(ceilN) * self.cta_tiler[1],
|
| 692 |
+
1,
|
| 693 |
+
)
|
| 694 |
+
)
|
| 695 |
+
cC = cute.local_tile(
|
| 696 |
+
mcC[None, None, bidz],
|
| 697 |
+
tiler=self.cta_tiler,
|
| 698 |
+
coord=tiler_coord,
|
| 699 |
+
proj=(1, 1, None),
|
| 700 |
+
)
|
| 701 |
+
tCcC = thr_copy_C.partition_S(cC)
|
| 702 |
+
|
| 703 |
+
tCrC_epilogue = cute.make_fragment_like(tCsC_epilogue)
|
| 704 |
+
# Wait for all writes to shared memory to finish before starting copies
|
| 705 |
+
# using the new layouts
|
| 706 |
+
cute.arch.sync_threads()
|
| 707 |
+
cute.autovec_copy(tCsC_epilogue, tCrC_epilogue)
|
| 708 |
+
|
| 709 |
+
# Create predication tensor for m
|
| 710 |
+
tCpC = cute.make_fragment(
|
| 711 |
+
cute.make_layout(
|
| 712 |
+
(
|
| 713 |
+
tCgC_epilogue.shape[0][1],
|
| 714 |
+
cute.size(tCgC_epilogue, mode=[1]),
|
| 715 |
+
cute.size(tCgC_epilogue, mode=[2]),
|
| 716 |
+
),
|
| 717 |
+
stride=(cute.size(tCgC_epilogue, mode=[1]), 1, 0),
|
| 718 |
+
),
|
| 719 |
+
cutlass.Boolean,
|
| 720 |
+
)
|
| 721 |
+
for rest_v in range(tCpC.shape[0]):
|
| 722 |
+
for m in range(tCpC.shape[1]):
|
| 723 |
+
tCpC[rest_v, m, 0] = cute.elem_less(
|
| 724 |
+
tCcC[(0, rest_v), m, 0][0], mC.shape[0]
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
# Copy to global memory using better vectorization
|
| 728 |
+
for rest_v in range(tCpC.shape[0]):
|
| 729 |
+
for n in range(tCpC.shape[2]):
|
| 730 |
+
if cute.elem_less(tCcC[(0, rest_v), 0, n][1], mC.shape[1]):
|
| 731 |
+
cute.copy(
|
| 732 |
+
tiled_copy_C,
|
| 733 |
+
tCrC_epilogue[None, None, n],
|
| 734 |
+
tCgC_epilogue[None, None, n],
|
| 735 |
+
pred=tCpC[None, None, n],
|
| 736 |
+
)
|
| 737 |
+
return
|
| 738 |
+
|
| 739 |
+
def _make_smem_layout_AB(self, dtype, major_mode, copy_bits, smem_tiler):
|
| 740 |
+
major_mode_size = (
|
| 741 |
+
smem_tiler[1] if major_mode == utils.LayoutEnum.ROW_MAJOR else smem_tiler[0]
|
| 742 |
+
)
|
| 743 |
+
major_mode_size = 64 if major_mode_size >= 64 else major_mode_size
|
| 744 |
+
|
| 745 |
+
swizzle_bits = int(math.log2(major_mode_size * dtype.width // copy_bits))
|
| 746 |
+
swizzle_bits = min(swizzle_bits, 3)
|
| 747 |
+
|
| 748 |
+
layout_atom_outer = (
|
| 749 |
+
cute.make_layout((8, major_mode_size), stride=(major_mode_size, 1))
|
| 750 |
+
if major_mode == utils.LayoutEnum.ROW_MAJOR
|
| 751 |
+
else cute.make_layout((major_mode_size, 8), stride=(1, major_mode_size))
|
| 752 |
+
)
|
| 753 |
+
layout_atom = cute.make_composed_layout(
|
| 754 |
+
cute.make_swizzle(swizzle_bits, 3, 3),
|
| 755 |
+
0,
|
| 756 |
+
layout_atom_outer,
|
| 757 |
+
)
|
| 758 |
+
layout = cute.tile_to_shape(layout_atom, smem_tiler, (0, 1, 2))
|
| 759 |
+
return layout
|
| 760 |
+
|
| 761 |
+
def _make_smem_layout_C(self, dtype, major_mode, copy_bits, smem_tiler):
|
| 762 |
+
major_mode_size = (
|
| 763 |
+
smem_tiler[1] if major_mode == utils.LayoutEnum.ROW_MAJOR else smem_tiler[0]
|
| 764 |
+
)
|
| 765 |
+
|
| 766 |
+
swizzle_bits = int(math.log2(major_mode_size * dtype.width // copy_bits))
|
| 767 |
+
swizzle_bits = min(swizzle_bits, 3)
|
| 768 |
+
|
| 769 |
+
layout_atom_outer = (
|
| 770 |
+
cute.make_layout((8, major_mode_size), stride=(major_mode_size, 1))
|
| 771 |
+
if major_mode == utils.LayoutEnum.ROW_MAJOR
|
| 772 |
+
else cute.make_layout((major_mode_size, 8), stride=(1, major_mode_size))
|
| 773 |
+
)
|
| 774 |
+
layout_atom = cute.make_composed_layout(
|
| 775 |
+
cute.make_swizzle(swizzle_bits, 3, 4),
|
| 776 |
+
0,
|
| 777 |
+
layout_atom_outer,
|
| 778 |
+
)
|
| 779 |
+
|
| 780 |
+
# Due to the thread layout of the mma, remove swizzle in C to
|
| 781 |
+
# prevent shared memory fragments owned by an single thread from
|
| 782 |
+
# holding swizzles
|
| 783 |
+
if major_mode == utils.LayoutEnum.COL_MAJOR:
|
| 784 |
+
layout_atom = cute.make_composed_layout(
|
| 785 |
+
cute.make_swizzle(0, 3, 4), 0, layout_atom_outer
|
| 786 |
+
)
|
| 787 |
+
layout = cute.tile_to_shape(
|
| 788 |
+
layout_atom,
|
| 789 |
+
smem_tiler,
|
| 790 |
+
(0, 1),
|
| 791 |
+
)
|
| 792 |
+
return layout
|
| 793 |
+
|
| 794 |
+
def _make_gmem_tiled_copy_AB(self, atom_copy, dtype, major_mode, copy_bits):
|
| 795 |
+
copy_elems = copy_bits // dtype.width
|
| 796 |
+
shape_dim_1 = cute.size(self.bK) // copy_elems
|
| 797 |
+
# thread layout for copy
|
| 798 |
+
thread_layout = cute.make_layout(
|
| 799 |
+
(self.num_threads // shape_dim_1, shape_dim_1), stride=(shape_dim_1, 1)
|
| 800 |
+
)
|
| 801 |
+
if major_mode != utils.LayoutEnum.ROW_MAJOR:
|
| 802 |
+
shape_dim_0 = cute.size(self.bM) // copy_elems
|
| 803 |
+
thread_layout = cute.make_layout(
|
| 804 |
+
(shape_dim_0, self.num_threads // shape_dim_0), stride=(1, shape_dim_0)
|
| 805 |
+
)
|
| 806 |
+
# Value layout for copy
|
| 807 |
+
value_layout = (
|
| 808 |
+
cute.make_layout((1, copy_elems))
|
| 809 |
+
if major_mode == utils.LayoutEnum.ROW_MAJOR
|
| 810 |
+
else cute.make_layout((copy_elems, 1))
|
| 811 |
+
)
|
| 812 |
+
return cute.make_tiled_copy_tv(atom_copy, thread_layout, value_layout)
|
| 813 |
+
|
| 814 |
+
def _make_gmem_tiled_copy_C(self, atom_copy, dtype, major_mode, copy_bits):
|
| 815 |
+
copy_elems = copy_bits // dtype.width
|
| 816 |
+
shape_dim_1 = cute.size(self.bN) // copy_elems
|
| 817 |
+
# thread layout for copy
|
| 818 |
+
thread_layout = cute.make_layout(
|
| 819 |
+
(self.num_threads // shape_dim_1, shape_dim_1), stride=(shape_dim_1, 1)
|
| 820 |
+
)
|
| 821 |
+
if major_mode != utils.LayoutEnum.ROW_MAJOR:
|
| 822 |
+
shape_dim_0 = cute.size(self.bM) // copy_elems
|
| 823 |
+
thread_layout = cute.make_layout(
|
| 824 |
+
(shape_dim_0, self.num_threads // shape_dim_0), stride=(1, shape_dim_0)
|
| 825 |
+
)
|
| 826 |
+
value_layout = (
|
| 827 |
+
cute.make_layout((1, copy_elems))
|
| 828 |
+
if major_mode == utils.LayoutEnum.ROW_MAJOR
|
| 829 |
+
else cute.make_layout((copy_elems, 1))
|
| 830 |
+
)
|
| 831 |
+
return cute.make_tiled_copy_tv(atom_copy, thread_layout, value_layout)
|
| 832 |
+
|
| 833 |
+
def raster_tile(self, i, j, f):
|
| 834 |
+
new_i = i // f
|
| 835 |
+
new_j = (i % f) + (j * f)
|
| 836 |
+
return (new_i, new_j)
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
def run(
|
| 840 |
+
a_major: str,
|
| 841 |
+
b_major: str,
|
| 842 |
+
c_major: str,
|
| 843 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 844 |
+
c_dtype: Type[cutlass.Numeric],
|
| 845 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 846 |
+
mnkl: Tuple[int, int, int, int],
|
| 847 |
+
atom_layout_mnk: Tuple[int, int, int],
|
| 848 |
+
warmup_iterations: int = 2,
|
| 849 |
+
iterations: int = 100,
|
| 850 |
+
skip_ref_check: bool = False,
|
| 851 |
+
use_cold_l2: bool = False,
|
| 852 |
+
**kwargs,
|
| 853 |
+
):
|
| 854 |
+
print(f"Running Ampere tensor core GEMM example:")
|
| 855 |
+
print(f"mnkl: {mnkl}")
|
| 856 |
+
print(
|
| 857 |
+
f"A dtype: {ab_dtype}, B dtype: {ab_dtype}, C dtype: {c_dtype}, Acc dtype: {acc_dtype}"
|
| 858 |
+
)
|
| 859 |
+
print(f"Matrix majors - A: {a_major}, B: {b_major}, C: {c_major}")
|
| 860 |
+
print(f"Atoms layout: {atom_layout_mnk}")
|
| 861 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 862 |
+
print(f"Iterations: {iterations}")
|
| 863 |
+
print(f"Skip reference checking: {skip_ref_check}")
|
| 864 |
+
print(f"Use cold L2: {use_cold_l2}")
|
| 865 |
+
M, N, K, L = mnkl
|
| 866 |
+
|
| 867 |
+
# Create and permute tensor A/B/C
|
| 868 |
+
def create_and_permute_tensor(l, mode0, mode1, is_mode0_major, dtype):
|
| 869 |
+
# is_mode0_major: (l, mode1, mode0) -> (mode0, mode1, l)
|
| 870 |
+
# else: (l, mode0, mode1) -> (mode0, mode1, l)
|
| 871 |
+
shape = (l, mode1, mode0) if is_mode0_major else (l, mode0, mode1)
|
| 872 |
+
permute_order = (2, 1, 0) if is_mode0_major else (1, 2, 0)
|
| 873 |
+
torch_tensor = (
|
| 874 |
+
torch.empty(*shape, dtype=torch.int32)
|
| 875 |
+
.random_(-2, 2)
|
| 876 |
+
.to(dtype=cutlass_torch.dtype(dtype))
|
| 877 |
+
.permute(permute_order)
|
| 878 |
+
.cuda()
|
| 879 |
+
)
|
| 880 |
+
# assume input is 16B aligned
|
| 881 |
+
cute_tensor = (
|
| 882 |
+
from_dlpack(torch_tensor, assumed_align=16)
|
| 883 |
+
.mark_layout_dynamic(leading_dim=(1 if not is_mode0_major else 0))
|
| 884 |
+
.mark_compact_shape_dynamic(
|
| 885 |
+
mode=(1 if not is_mode0_major else 0),
|
| 886 |
+
stride_order=(2, 0, 1) if not is_mode0_major else (2, 1, 0),
|
| 887 |
+
divisibility=(128 // dtype.width),
|
| 888 |
+
)
|
| 889 |
+
)
|
| 890 |
+
return cute_tensor, torch_tensor
|
| 891 |
+
|
| 892 |
+
mA, a_torch = create_and_permute_tensor(L, M, K, a_major == "m", ab_dtype)
|
| 893 |
+
mB, b_torch = create_and_permute_tensor(L, N, K, b_major == "n", ab_dtype)
|
| 894 |
+
mC, c_torch = create_and_permute_tensor(L, M, N, c_major == "m", c_dtype)
|
| 895 |
+
|
| 896 |
+
tensor_op_gemm = TensorOpGemm(
|
| 897 |
+
ab_dtype,
|
| 898 |
+
c_dtype,
|
| 899 |
+
acc_dtype,
|
| 900 |
+
atom_layout_mnk,
|
| 901 |
+
)
|
| 902 |
+
|
| 903 |
+
print("Compiling kernel with cute.compile ...")
|
| 904 |
+
compiled_gemm = cute.compile(tensor_op_gemm, mA, mB, mC)
|
| 905 |
+
|
| 906 |
+
print("Executing GEMM kernel...")
|
| 907 |
+
|
| 908 |
+
if not skip_ref_check:
|
| 909 |
+
ref = torch.einsum(
|
| 910 |
+
"mkl,nkl->mnl",
|
| 911 |
+
a_torch.to(dtype=torch.float32),
|
| 912 |
+
b_torch.to(dtype=torch.float32),
|
| 913 |
+
).to(cutlass_torch.dtype(c_dtype))
|
| 914 |
+
compiled_gemm(mA, mB, mC)
|
| 915 |
+
print("Verifying results...")
|
| 916 |
+
torch.testing.assert_close(c_torch.cpu(), ref.cpu(), atol=1e-03, rtol=1e-05)
|
| 917 |
+
print("Results verified successfully!")
|
| 918 |
+
|
| 919 |
+
def generate_tensors():
|
| 920 |
+
a_workspace, _ = create_and_permute_tensor(L, M, K, a_major == "m", ab_dtype)
|
| 921 |
+
b_workspace, _ = create_and_permute_tensor(L, N, K, b_major == "n", ab_dtype)
|
| 922 |
+
c_workspace, _ = create_and_permute_tensor(L, M, N, c_major == "m", c_dtype)
|
| 923 |
+
return testing.JitArguments(a_workspace, b_workspace, c_workspace)
|
| 924 |
+
|
| 925 |
+
workspace_count = 1
|
| 926 |
+
if use_cold_l2:
|
| 927 |
+
one_workspace_bytes = (
|
| 928 |
+
a_torch.numel() * a_torch.element_size()
|
| 929 |
+
+ b_torch.numel() * b_torch.element_size()
|
| 930 |
+
+ c_torch.numel() * c_torch.element_size()
|
| 931 |
+
)
|
| 932 |
+
workspace_count = testing.get_workspace_count(
|
| 933 |
+
one_workspace_bytes, warmup_iterations, iterations
|
| 934 |
+
)
|
| 935 |
+
|
| 936 |
+
avg_time_us = testing.benchmark(
|
| 937 |
+
compiled_gemm,
|
| 938 |
+
workspace_generator=generate_tensors,
|
| 939 |
+
workspace_count=workspace_count,
|
| 940 |
+
warmup_iterations=warmup_iterations,
|
| 941 |
+
iterations=iterations,
|
| 942 |
+
use_cuda_graphs=False,
|
| 943 |
+
)
|
| 944 |
+
|
| 945 |
+
return avg_time_us # Return execution time in microseconds
|
| 946 |
+
|
| 947 |
+
if __name__ == "__main__":
|
| 948 |
+
|
| 949 |
+
def parse_comma_separated_ints(s: str) -> Tuple[int, ...]:
|
| 950 |
+
try:
|
| 951 |
+
return tuple(int(x.strip()) for x in s.split(","))
|
| 952 |
+
except ValueError:
|
| 953 |
+
raise argparse.ArgumentTypeError(
|
| 954 |
+
"Invalid format. Expected comma-separated integers."
|
| 955 |
+
)
|
| 956 |
+
|
| 957 |
+
parser = argparse.ArgumentParser(
|
| 958 |
+
description="example of multistage block matmul with CuTe on GPU"
|
| 959 |
+
)
|
| 960 |
+
parser.add_argument(
|
| 961 |
+
"--mnkl", type=parse_comma_separated_ints, default=(112, 136, 40, 1)
|
| 962 |
+
)
|
| 963 |
+
parser.add_argument(
|
| 964 |
+
"--atom_layout_mnk", type=parse_comma_separated_ints, default=(2, 2, 1)
|
| 965 |
+
)
|
| 966 |
+
parser.add_argument(
|
| 967 |
+
"--ab_dtype",
|
| 968 |
+
type=cutlass.dtype,
|
| 969 |
+
choices=[cutlass.Float16],
|
| 970 |
+
default=cutlass.Float16,
|
| 971 |
+
)
|
| 972 |
+
parser.add_argument(
|
| 973 |
+
"--acc_dtype",
|
| 974 |
+
type=cutlass.dtype,
|
| 975 |
+
choices=[cutlass.Float32],
|
| 976 |
+
default=cutlass.Float32,
|
| 977 |
+
)
|
| 978 |
+
parser.add_argument(
|
| 979 |
+
"--c_dtype",
|
| 980 |
+
type=cutlass.dtype,
|
| 981 |
+
choices=[cutlass.Float16],
|
| 982 |
+
default=cutlass.Float16,
|
| 983 |
+
)
|
| 984 |
+
parser.add_argument("--a_major", choices=["k", "m"], default="m")
|
| 985 |
+
parser.add_argument("--b_major", choices=["k", "n"], default="n")
|
| 986 |
+
parser.add_argument("--c_major", choices=["n", "m"], default="n")
|
| 987 |
+
parser.add_argument("--warmup_iterations", default=2, type=int)
|
| 988 |
+
parser.add_argument("--iterations", default=100, type=int)
|
| 989 |
+
parser.add_argument("--skip_ref_check", action="store_true")
|
| 990 |
+
parser.add_argument(
|
| 991 |
+
"--use_cold_l2",
|
| 992 |
+
action="store_true",
|
| 993 |
+
default=False,
|
| 994 |
+
help="Use circular buffer tensor sets to ensure L2 cold cache",
|
| 995 |
+
)
|
| 996 |
+
|
| 997 |
+
args = parser.parse_args()
|
| 998 |
+
run(
|
| 999 |
+
args.a_major,
|
| 1000 |
+
args.b_major,
|
| 1001 |
+
args.c_major,
|
| 1002 |
+
args.ab_dtype,
|
| 1003 |
+
args.c_dtype,
|
| 1004 |
+
args.acc_dtype,
|
| 1005 |
+
args.mnkl,
|
| 1006 |
+
args.atom_layout_mnk,
|
| 1007 |
+
args.warmup_iterations,
|
| 1008 |
+
args.iterations,
|
| 1009 |
+
args.skip_ref_check,
|
| 1010 |
+
args.use_cold_l2,
|
| 1011 |
+
)
|
| 1012 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_blockscaled_gemm_persistent.py
ADDED
|
@@ -0,0 +1,2466 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
from typing import Optional, Type, Tuple, Union
|
| 31 |
+
|
| 32 |
+
import cuda.bindings.driver as cuda
|
| 33 |
+
import torch
|
| 34 |
+
|
| 35 |
+
import cutlass
|
| 36 |
+
import cutlass.cute as cute
|
| 37 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 38 |
+
import cutlass.torch as cutlass_torch
|
| 39 |
+
import cutlass.utils as utils
|
| 40 |
+
import cutlass.pipeline as pipeline
|
| 41 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 42 |
+
import cutlass.utils.blockscaled_layout as blockscaled_utils
|
| 43 |
+
from cutlass.cute.runtime import from_dlpack
|
| 44 |
+
|
| 45 |
+
"""
|
| 46 |
+
This example provides an experimental implementation of the SM100 batched dense blockscaled GEMM kernel, please note that the APIs and implementation details related to this kernel may change in future releases.
|
| 47 |
+
|
| 48 |
+
A high-performance persistent batched dense blockscaled GEMM example for the NVIDIA Blackwell SM100 architecture
|
| 49 |
+
using CUTE DSL.
|
| 50 |
+
- Matrix A is MxKxL, L is batch dimension, A can be row-major("K") or column-major("M") for MXF8 input type and can only be row-major("K") for MXF4/NVF4 input type
|
| 51 |
+
- Matrix B is NxKxL, L is batch dimension, B can be row-major("N") or column-major("K") for MXF8 input type and can only be row-major("K") for MXF4/NVF4 input type
|
| 52 |
+
- Matrix C is MxNxL, L is batch dimension, C can be row-major("N") or column-major("M")
|
| 53 |
+
- Matrix SFA layout is filled internally according to A shape and BlockScaledBasicChunk, which has M×ceil_div(K, sf_vec_size)×L elements respectively
|
| 54 |
+
- Matrix SFB layout is filled internally according to B shape and BlockScaledBasicChunk, which has N×ceil_div(K, sf_vec_size)×L elements respectively
|
| 55 |
+
|
| 56 |
+
This GEMM kernel supports the following features:
|
| 57 |
+
- Utilizes Tensor Memory Access (TMA) for efficient memory operations
|
| 58 |
+
- Utilizes Blackwell's tcgen05.mma for matrix multiply-accumulate (MMA) operations (including 2cta mma instructions)
|
| 59 |
+
- Implements TMA multicast with cluster to reduce L2 memory traffic
|
| 60 |
+
- Support persistent tile scheduling to better overlap memory load/store with mma between tiles
|
| 61 |
+
- Support warp specialization to avoid explicit pipelining between mainloop load and mma
|
| 62 |
+
|
| 63 |
+
This GEMM works as follows:
|
| 64 |
+
1. DMA warp: Load A and B matrices from global memory (GMEM) to shared memory (SMEM) using TMA operations.
|
| 65 |
+
2. MMA warp:
|
| 66 |
+
- Load scale factor A/B from shared memory (SMEM) to tensor memory (TMEM) using tcgen05.cp instruction.
|
| 67 |
+
- Perform matrix multiply-accumulate (MMA) operations using tcgen05.mma instruction.
|
| 68 |
+
3. EPILOGUE warp:
|
| 69 |
+
- Load completed accumulator from tensor memory (TMEM) to registers (RMEM) using tcgen05.ld.
|
| 70 |
+
- Type convert C matrix to output type.
|
| 71 |
+
- Optionally store C matrix from registers (RMEM) to shared memory (SMEM) to global memory (GMEM) with TMA operations,
|
| 72 |
+
or directly store C matrix from registers (RMEM) to global memory (GMEM) without TMA operations.
|
| 73 |
+
- Optionally accept an elementwise lambda function epilogue_op to apply to the output tensor:
|
| 74 |
+
e.g., relu can set epilogue_op = lambda x: cute.where(x > 0, x, cute.full_like(x, 0))
|
| 75 |
+
|
| 76 |
+
SM100 tcgen05.mma.kind.block_scale instructions operate as follows:
|
| 77 |
+
- Read matrix A from SMEM
|
| 78 |
+
- Read matrix B from SMEM
|
| 79 |
+
- Read scalefactor A from TMEM
|
| 80 |
+
- Read scalefactor B from TMEM
|
| 81 |
+
- Write accumulator to TMEM
|
| 82 |
+
The accumulator in TMEM must then be loaded to registers before writing back to GMEM.
|
| 83 |
+
|
| 84 |
+
Input arguments to this example is shown below:
|
| 85 |
+
|
| 86 |
+
.. code-block:: bash
|
| 87 |
+
|
| 88 |
+
python examples/blackwell/dense_blockscaled_gemm_persistent.py \
|
| 89 |
+
--ab_dtype Float4E2M1FN --sf_dtype Float8E8M0FNU --sf_vec_size 16 \
|
| 90 |
+
--c_dtype Float16 \
|
| 91 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 92 |
+
--mnkl 8192,8192,1024,1
|
| 93 |
+
|
| 94 |
+
To collect performance with NCU profiler:
|
| 95 |
+
|
| 96 |
+
.. code-block:: bash
|
| 97 |
+
|
| 98 |
+
ncu python examples/blackwell/dense_blockscaled_gemm_persistent.py \
|
| 99 |
+
--ab_dtype Float4E2M1FN --sf_dtype Float8E8M0FNU --sf_vec_size 16 \
|
| 100 |
+
--c_dtype Float16 \
|
| 101 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 102 |
+
--mnkl 8192,8192,1024,1 \
|
| 103 |
+
--warmup_iterations 1 --iterations 10 --skip_ref_check
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
Constraints:
|
| 107 |
+
* Supported input data types: mxf8, mxf4, nvf4
|
| 108 |
+
see detailed valid dtype combinations in below Sm100BlockScaledPersistentDenseGemmKernel class documentation
|
| 109 |
+
* A/B tensor must have the same data type, mixed data type is not supported (e.g., mxf8 x mxf4)
|
| 110 |
+
* Mma tiler M must be 128 or 256(use_2cta_instrs)
|
| 111 |
+
* Mma tiler N must be 128 or 256
|
| 112 |
+
* Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 113 |
+
* Cluster shape M must be multiple of 2 if Mma tiler M is 256(use_2cta_instrs)
|
| 114 |
+
* The contiguous dimension of A/B/C tensors must be at least 16 bytes aligned,
|
| 115 |
+
i.e, number of elements is a multiple of 16 and 32 for Float8 and Float4, respectively.
|
| 116 |
+
"""
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class Sm100BlockScaledPersistentDenseGemmKernel:
|
| 120 |
+
"""This class implements batched matrix multiplication (C = A x SFA x B x SFB) with support for various data types
|
| 121 |
+
and architectural features specific to Blackwell GPUs with persistent tile scheduling and warp specialization.
|
| 122 |
+
|
| 123 |
+
:param sf_vec_size: Scalefactor vector size.
|
| 124 |
+
:type sf_vec_size: int
|
| 125 |
+
:param mma_tiler_mn: Shape of the Matrix Multiply-Accumulate (MMA) tile (M,N)
|
| 126 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 127 |
+
:param cluster_shape_mn: Cluster dimensions (M,N) for parallel processing
|
| 128 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 129 |
+
|
| 130 |
+
:note: In current version, A and B tensor must have the same data type
|
| 131 |
+
- i.e., Float8E4M3FN for A and Float8E5M2 for B is not supported
|
| 132 |
+
|
| 133 |
+
:note: Supported combinations of A/B data types, SF data typs and SF vector size:
|
| 134 |
+
- MXF8: A/B: Float8E5M2/Float8E4M3FN + SF: Float8E8M0FNU + sf_vec_size: 32
|
| 135 |
+
- MXF4: A/B: Float4E2M1FN + SF: Float8E8M0FNU + sf_vec_size: 32
|
| 136 |
+
- NVF4: A/B: Float4E2M1FN + SF: Float8E8M0FNU/Float8E4M3FN + sf_vec_size: 16
|
| 137 |
+
|
| 138 |
+
:note: Supported accumulator data types:
|
| 139 |
+
- Float32
|
| 140 |
+
|
| 141 |
+
:note: Supported C data types:
|
| 142 |
+
- Float32
|
| 143 |
+
- Float16/BFloat16
|
| 144 |
+
- Float8E4M3FN/Float8E5M2
|
| 145 |
+
:note: Constraints:
|
| 146 |
+
- MMA tiler M must be 128 or 256 (use_2cta_instrs)
|
| 147 |
+
- MMA tiler N must be 128/256
|
| 148 |
+
- Cluster shape M must be multiple of 2 if Mma tiler M is 256
|
| 149 |
+
- Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 150 |
+
- Also, Cluster shape M/N must be <= 4 for scale factor multicasts due to limited size of scale factors
|
| 151 |
+
|
| 152 |
+
Example:
|
| 153 |
+
>>> gemm = Sm100BlockScaledPersistentDenseGemmKernel(
|
| 154 |
+
... sf_vec_size=16,
|
| 155 |
+
... mma_tiler_mn=(256, 128),
|
| 156 |
+
... cluster_shape_mn=(2, 1)
|
| 157 |
+
... )
|
| 158 |
+
>>> gemm(a_tensor, b_tensor, sfa_tensor, sfb_tensor, c_tensor, max_active_clusters, stream)
|
| 159 |
+
"""
|
| 160 |
+
|
| 161 |
+
def __init__(
|
| 162 |
+
self,
|
| 163 |
+
sf_vec_size: int,
|
| 164 |
+
mma_tiler_mn: Tuple[int, int],
|
| 165 |
+
cluster_shape_mn: Tuple[int, int],
|
| 166 |
+
):
|
| 167 |
+
"""Initializes the configuration for a Blackwell dense GEMM kernel.
|
| 168 |
+
|
| 169 |
+
This configuration includes several key aspects:
|
| 170 |
+
|
| 171 |
+
1. MMA Instruction Settings (tcgen05):
|
| 172 |
+
- acc_dtype: Data types for MMA accumulator, always set to Float32
|
| 173 |
+
- sf_vec_size: Scalefactor A/B vector size.
|
| 174 |
+
- mma_tiler_mn: The (M, N) shape of the MMA instruction tiler.
|
| 175 |
+
|
| 176 |
+
2. Cluster Shape:
|
| 177 |
+
- cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster.
|
| 178 |
+
|
| 179 |
+
:param sf_vec_size: Scalefactor vector size.
|
| 180 |
+
:type sf_vec_size: int
|
| 181 |
+
:param mma_tiler_mn: Tuple (M, N) shape of the MMA instruction.
|
| 182 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 183 |
+
:param cluster_shape_mn: Tuple (ClusterM, ClusterN) shape of the cluster.
|
| 184 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 185 |
+
"""
|
| 186 |
+
|
| 187 |
+
self.acc_dtype = cutlass.Float32
|
| 188 |
+
self.sf_vec_size = sf_vec_size
|
| 189 |
+
self.use_2cta_instrs = mma_tiler_mn[0] == 256
|
| 190 |
+
self.cluster_shape_mn = cluster_shape_mn
|
| 191 |
+
# K dimension is deferred in _setup_attributes
|
| 192 |
+
self.mma_tiler = (*mma_tiler_mn, 1)
|
| 193 |
+
|
| 194 |
+
self.cta_group = (
|
| 195 |
+
tcgen05.CtaGroup.TWO if self.use_2cta_instrs else tcgen05.CtaGroup.ONE
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
self.occupancy = 1
|
| 199 |
+
# Set specialized warp ids
|
| 200 |
+
self.epilog_warp_id = (
|
| 201 |
+
0,
|
| 202 |
+
1,
|
| 203 |
+
2,
|
| 204 |
+
3,
|
| 205 |
+
)
|
| 206 |
+
self.mma_warp_id = 4
|
| 207 |
+
self.tma_warp_id = 5
|
| 208 |
+
self.threads_per_cta = 32 * len(
|
| 209 |
+
(self.mma_warp_id, self.tma_warp_id, *self.epilog_warp_id)
|
| 210 |
+
)
|
| 211 |
+
# Set barrier id for cta sync, epilogue sync and tmem ptr sync
|
| 212 |
+
self.cta_sync_bar_id = 0
|
| 213 |
+
self.epilog_sync_bar_id = 1
|
| 214 |
+
self.tmem_ptr_sync_bar_id = 2
|
| 215 |
+
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
| 216 |
+
SM100_TMEM_CAPACITY_COLUMNS = 512
|
| 217 |
+
self.num_tmem_alloc_cols = SM100_TMEM_CAPACITY_COLUMNS
|
| 218 |
+
|
| 219 |
+
def _setup_attributes(self):
|
| 220 |
+
"""Set up configurations that are dependent on GEMM inputs
|
| 221 |
+
|
| 222 |
+
This method configures various attributes based on the input tensor properties
|
| 223 |
+
(data types, leading dimensions) and kernel settings:
|
| 224 |
+
- Configuring tiled MMA
|
| 225 |
+
- Computing MMA/cluster/tile shapes
|
| 226 |
+
- Computing cluster layout
|
| 227 |
+
- Computing multicast CTAs for A/B/SFA/SFB
|
| 228 |
+
- Computing epilogue subtile
|
| 229 |
+
- Setting up A/B/SFA/SFB/C stage counts in shared memory
|
| 230 |
+
- Computing A/B/SFA/SFB/C shared memory layout
|
| 231 |
+
- Computing tensor memory allocation columns
|
| 232 |
+
"""
|
| 233 |
+
# Compute mma instruction shapes
|
| 234 |
+
mma_inst_bits_k = 256
|
| 235 |
+
# (MMA_Tile_Shape_M, MMA_Tile_Shape_N, MMA_Inst_Shape_K)
|
| 236 |
+
self.mma_inst_shape_mnk = (
|
| 237 |
+
self.mma_tiler[0],
|
| 238 |
+
self.mma_tiler[1],
|
| 239 |
+
mma_inst_bits_k // self.a_dtype.width,
|
| 240 |
+
)
|
| 241 |
+
# (CTA_Tile_Shape_M, Round_Up(MMA_Tile_Shape_N, 128), MMA_Inst_Shape_K)
|
| 242 |
+
self.mma_inst_shape_mnk_sfb = (
|
| 243 |
+
self.mma_inst_shape_mnk[0] // (2 if self.use_2cta_instrs else 1),
|
| 244 |
+
cute.round_up(self.mma_inst_shape_mnk[1], 128),
|
| 245 |
+
self.mma_inst_shape_mnk[2],
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
tiled_mma = sm100_utils.make_blockscaled_trivial_tiled_mma(
|
| 249 |
+
self.a_dtype,
|
| 250 |
+
self.a_major_mode,
|
| 251 |
+
self.b_major_mode,
|
| 252 |
+
self.sf_dtype,
|
| 253 |
+
self.sf_vec_size,
|
| 254 |
+
self.cta_group,
|
| 255 |
+
self.mma_inst_shape_mnk[:2],
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
tiled_mma_sfb = sm100_utils.make_blockscaled_trivial_tiled_mma(
|
| 259 |
+
self.a_dtype,
|
| 260 |
+
self.a_major_mode,
|
| 261 |
+
self.b_major_mode,
|
| 262 |
+
self.sf_dtype,
|
| 263 |
+
self.sf_vec_size,
|
| 264 |
+
cute.nvgpu.tcgen05.CtaGroup.ONE,
|
| 265 |
+
self.mma_inst_shape_mnk_sfb[:2],
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
# Compute mma/cluster/tile shapes
|
| 269 |
+
mma_inst_tile_k = 4
|
| 270 |
+
self.mma_tiler = (
|
| 271 |
+
self.mma_inst_shape_mnk[0],
|
| 272 |
+
self.mma_inst_shape_mnk[1],
|
| 273 |
+
self.mma_inst_shape_mnk[2] * mma_inst_tile_k,
|
| 274 |
+
)
|
| 275 |
+
self.mma_tiler_sfb = (
|
| 276 |
+
self.mma_inst_shape_mnk_sfb[0],
|
| 277 |
+
self.mma_inst_shape_mnk_sfb[1],
|
| 278 |
+
self.mma_inst_shape_mnk_sfb[2] * mma_inst_tile_k,
|
| 279 |
+
)
|
| 280 |
+
self.cta_tile_shape_mnk = (
|
| 281 |
+
self.mma_tiler[0] // cute.size(tiled_mma.thr_id.shape),
|
| 282 |
+
self.mma_tiler[1],
|
| 283 |
+
self.mma_tiler[2],
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
# Compute cluster layout
|
| 287 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 288 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 289 |
+
(tiled_mma.thr_id.shape,),
|
| 290 |
+
)
|
| 291 |
+
self.cluster_layout_sfb_vmnk = cute.tiled_divide(
|
| 292 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 293 |
+
(tiled_mma_sfb.thr_id.shape,),
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
# Compute number of multicast CTAs for A/B
|
| 297 |
+
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
| 298 |
+
self.num_mcast_ctas_b = cute.size(self.cluster_layout_vmnk.shape[1])
|
| 299 |
+
self.num_mcast_ctas_sfb = cute.size(self.cluster_layout_sfb_vmnk.shape[1])
|
| 300 |
+
self.is_a_mcast = self.num_mcast_ctas_a > 1
|
| 301 |
+
self.is_b_mcast = self.num_mcast_ctas_b > 1
|
| 302 |
+
self.is_sfb_mcast = self.num_mcast_ctas_sfb > 1
|
| 303 |
+
|
| 304 |
+
# Compute epilogue subtile
|
| 305 |
+
self.epi_tile = sm100_utils.compute_epilogue_tile_shape(
|
| 306 |
+
self.cta_tile_shape_mnk,
|
| 307 |
+
self.use_2cta_instrs,
|
| 308 |
+
self.c_layout,
|
| 309 |
+
self.c_dtype,
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
# Setup A/B/C stage count in shared memory and ACC stage count in tensor memory
|
| 313 |
+
self.num_acc_stage, self.num_ab_stage, self.num_c_stage = self._compute_stages(
|
| 314 |
+
tiled_mma,
|
| 315 |
+
self.mma_tiler,
|
| 316 |
+
self.a_dtype,
|
| 317 |
+
self.a_major_mode,
|
| 318 |
+
self.b_dtype,
|
| 319 |
+
self.b_major_mode,
|
| 320 |
+
self.epi_tile,
|
| 321 |
+
self.c_dtype,
|
| 322 |
+
self.c_layout,
|
| 323 |
+
self.sf_dtype,
|
| 324 |
+
self.sf_vec_size,
|
| 325 |
+
self.smem_capacity,
|
| 326 |
+
self.occupancy,
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
# Compute A/B/SFA/SFB/C shared memory layout
|
| 330 |
+
self.a_smem_layout_staged = sm100_utils.make_smem_layout_a(
|
| 331 |
+
tiled_mma,
|
| 332 |
+
self.mma_tiler,
|
| 333 |
+
self.a_dtype,
|
| 334 |
+
self.num_ab_stage,
|
| 335 |
+
)
|
| 336 |
+
self.b_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
| 337 |
+
tiled_mma,
|
| 338 |
+
self.mma_tiler,
|
| 339 |
+
self.b_dtype,
|
| 340 |
+
self.num_ab_stage,
|
| 341 |
+
)
|
| 342 |
+
self.sfa_smem_layout_staged = blockscaled_utils.make_smem_layout_sfa(
|
| 343 |
+
tiled_mma,
|
| 344 |
+
self.mma_tiler,
|
| 345 |
+
self.sf_vec_size,
|
| 346 |
+
self.num_ab_stage,
|
| 347 |
+
)
|
| 348 |
+
self.sfb_smem_layout_staged = blockscaled_utils.make_smem_layout_sfb(
|
| 349 |
+
tiled_mma,
|
| 350 |
+
self.mma_tiler,
|
| 351 |
+
self.sf_vec_size,
|
| 352 |
+
self.num_ab_stage,
|
| 353 |
+
)
|
| 354 |
+
self.c_smem_layout_staged = sm100_utils.make_smem_layout_epi(
|
| 355 |
+
self.c_dtype,
|
| 356 |
+
self.c_layout,
|
| 357 |
+
self.epi_tile,
|
| 358 |
+
self.num_c_stage,
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
@cute.jit
|
| 362 |
+
def __call__(
|
| 363 |
+
self,
|
| 364 |
+
a_tensor: cute.Tensor,
|
| 365 |
+
b_tensor: cute.Tensor,
|
| 366 |
+
sfa_tensor: cute.Tensor,
|
| 367 |
+
sfb_tensor: cute.Tensor,
|
| 368 |
+
c_tensor: cute.Tensor,
|
| 369 |
+
max_active_clusters: cutlass.Constexpr,
|
| 370 |
+
stream: cuda.CUstream,
|
| 371 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 372 |
+
):
|
| 373 |
+
"""Execute the GEMM operation in steps:
|
| 374 |
+
- Setup static attributes before smem/grid/tma computation
|
| 375 |
+
- Setup TMA load/store atoms and tensors
|
| 376 |
+
- Compute grid size with regard to hardware constraints
|
| 377 |
+
- Define shared storage for kernel
|
| 378 |
+
- Launch the kernel synchronously
|
| 379 |
+
|
| 380 |
+
:param a_tensor: Input tensor A
|
| 381 |
+
:type a_tensor: cute.Tensor
|
| 382 |
+
:param b_tensor: Input tensor B
|
| 383 |
+
:type b_tensor: cute.Tensor
|
| 384 |
+
:param sfa_tensor: Scale factor tensor A
|
| 385 |
+
:type sfa_tensor: cute.Tensor
|
| 386 |
+
:param sfb_tensor: Scale factor tensor B
|
| 387 |
+
:type sfb_tensor: cute.Tensor
|
| 388 |
+
:param c_tensor: Output tensor C
|
| 389 |
+
:type c_tensor: cute.Tensor
|
| 390 |
+
:param max_active_clusters: Maximum number of active clusters
|
| 391 |
+
:type max_active_clusters: cutlass.Constexpr
|
| 392 |
+
:param stream: CUDA stream for asynchronous execution
|
| 393 |
+
:type stream: cuda.CUstream
|
| 394 |
+
:param epilogue_op: Optional elementwise lambda function to apply to the output tensor
|
| 395 |
+
:type epilogue_op: cutlass.Constexpr
|
| 396 |
+
:raises TypeError: If input data types are incompatible with the MMA instruction.
|
| 397 |
+
"""
|
| 398 |
+
# Setup static attributes before smem/grid/tma computation
|
| 399 |
+
self.a_dtype: Type[cutlass.Numeric] = a_tensor.element_type
|
| 400 |
+
self.b_dtype: Type[cutlass.Numeric] = b_tensor.element_type
|
| 401 |
+
self.sf_dtype: Type[cutlass.Numeric] = sfa_tensor.element_type
|
| 402 |
+
self.c_dtype: Type[cutlass.Numeric] = c_tensor.element_type
|
| 403 |
+
self.a_major_mode = utils.LayoutEnum.from_tensor(a_tensor).mma_major_mode()
|
| 404 |
+
self.b_major_mode = utils.LayoutEnum.from_tensor(b_tensor).mma_major_mode()
|
| 405 |
+
self.c_layout = utils.LayoutEnum.from_tensor(c_tensor)
|
| 406 |
+
|
| 407 |
+
# Check if input data types are compatible with MMA instruction
|
| 408 |
+
if cutlass.const_expr(self.a_dtype != self.b_dtype):
|
| 409 |
+
raise TypeError(f"Type must match: {self.a_dtype} != {self.b_dtype}")
|
| 410 |
+
|
| 411 |
+
# Setup attributes that dependent on gemm inputs
|
| 412 |
+
self._setup_attributes()
|
| 413 |
+
|
| 414 |
+
# Setup sfa/sfb tensor by filling A/B tensor to scale factor atom layout
|
| 415 |
+
# ((Atom_M, Rest_M),(Atom_K, Rest_K),RestL)
|
| 416 |
+
sfa_layout = blockscaled_utils.tile_atom_to_shape_SF(
|
| 417 |
+
a_tensor.shape, self.sf_vec_size
|
| 418 |
+
)
|
| 419 |
+
sfa_tensor = cute.make_tensor(sfa_tensor.iterator, sfa_layout)
|
| 420 |
+
|
| 421 |
+
# ((Atom_N, Rest_N),(Atom_K, Rest_K),RestL)
|
| 422 |
+
sfb_layout = blockscaled_utils.tile_atom_to_shape_SF(
|
| 423 |
+
b_tensor.shape, self.sf_vec_size
|
| 424 |
+
)
|
| 425 |
+
sfb_tensor = cute.make_tensor(sfb_tensor.iterator, sfb_layout)
|
| 426 |
+
|
| 427 |
+
tiled_mma = sm100_utils.make_blockscaled_trivial_tiled_mma(
|
| 428 |
+
self.a_dtype,
|
| 429 |
+
self.a_major_mode,
|
| 430 |
+
self.b_major_mode,
|
| 431 |
+
self.sf_dtype,
|
| 432 |
+
self.sf_vec_size,
|
| 433 |
+
self.cta_group,
|
| 434 |
+
self.mma_inst_shape_mnk[:2],
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
tiled_mma_sfb = sm100_utils.make_blockscaled_trivial_tiled_mma(
|
| 438 |
+
self.a_dtype,
|
| 439 |
+
self.a_major_mode,
|
| 440 |
+
self.b_major_mode,
|
| 441 |
+
self.sf_dtype,
|
| 442 |
+
self.sf_vec_size,
|
| 443 |
+
cute.nvgpu.tcgen05.CtaGroup.ONE,
|
| 444 |
+
self.mma_inst_shape_mnk_sfb[:2],
|
| 445 |
+
)
|
| 446 |
+
atom_thr_size = cute.size(tiled_mma.thr_id.shape)
|
| 447 |
+
|
| 448 |
+
# Setup TMA load for A
|
| 449 |
+
a_op = sm100_utils.cluster_shape_to_tma_atom_A(
|
| 450 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 451 |
+
)
|
| 452 |
+
a_smem_layout = cute.slice_(self.a_smem_layout_staged, (None, None, None, 0))
|
| 453 |
+
tma_atom_a, tma_tensor_a = cute.nvgpu.make_tiled_tma_atom_A(
|
| 454 |
+
a_op,
|
| 455 |
+
a_tensor,
|
| 456 |
+
a_smem_layout,
|
| 457 |
+
self.mma_tiler,
|
| 458 |
+
tiled_mma,
|
| 459 |
+
self.cluster_layout_vmnk.shape,
|
| 460 |
+
)
|
| 461 |
+
|
| 462 |
+
# Setup TMA load for B
|
| 463 |
+
b_op = sm100_utils.cluster_shape_to_tma_atom_B(
|
| 464 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 465 |
+
)
|
| 466 |
+
b_smem_layout = cute.slice_(self.b_smem_layout_staged, (None, None, None, 0))
|
| 467 |
+
tma_atom_b, tma_tensor_b = cute.nvgpu.make_tiled_tma_atom_B(
|
| 468 |
+
b_op,
|
| 469 |
+
b_tensor,
|
| 470 |
+
b_smem_layout,
|
| 471 |
+
self.mma_tiler,
|
| 472 |
+
tiled_mma,
|
| 473 |
+
self.cluster_layout_vmnk.shape,
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
# Setup TMA load for SFA
|
| 477 |
+
sfa_op = sm100_utils.cluster_shape_to_tma_atom_A(
|
| 478 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 479 |
+
)
|
| 480 |
+
sfa_smem_layout = cute.slice_(
|
| 481 |
+
self.sfa_smem_layout_staged, (None, None, None, 0)
|
| 482 |
+
)
|
| 483 |
+
tma_atom_sfa, tma_tensor_sfa = cute.nvgpu.make_tiled_tma_atom_A(
|
| 484 |
+
sfa_op,
|
| 485 |
+
sfa_tensor,
|
| 486 |
+
sfa_smem_layout,
|
| 487 |
+
self.mma_tiler,
|
| 488 |
+
tiled_mma,
|
| 489 |
+
self.cluster_layout_vmnk.shape,
|
| 490 |
+
internal_type=cutlass.Int16,
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
# Setup TMA load for SFB
|
| 494 |
+
sfb_op = sm100_utils.cluster_shape_to_tma_atom_SFB(
|
| 495 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 496 |
+
)
|
| 497 |
+
sfb_smem_layout = cute.slice_(
|
| 498 |
+
self.sfb_smem_layout_staged, (None, None, None, 0)
|
| 499 |
+
)
|
| 500 |
+
tma_atom_sfb, tma_tensor_sfb = cute.nvgpu.make_tiled_tma_atom_B(
|
| 501 |
+
sfb_op,
|
| 502 |
+
sfb_tensor,
|
| 503 |
+
sfb_smem_layout,
|
| 504 |
+
self.mma_tiler_sfb,
|
| 505 |
+
tiled_mma_sfb,
|
| 506 |
+
self.cluster_layout_sfb_vmnk.shape,
|
| 507 |
+
internal_type=cutlass.Int16,
|
| 508 |
+
)
|
| 509 |
+
|
| 510 |
+
a_copy_size = cute.size_in_bytes(self.a_dtype, a_smem_layout)
|
| 511 |
+
b_copy_size = cute.size_in_bytes(self.b_dtype, b_smem_layout)
|
| 512 |
+
sfa_copy_size = cute.size_in_bytes(self.sf_dtype, sfa_smem_layout)
|
| 513 |
+
sfb_copy_size = cute.size_in_bytes(self.sf_dtype, sfb_smem_layout)
|
| 514 |
+
self.num_tma_load_bytes = (
|
| 515 |
+
a_copy_size + b_copy_size + sfa_copy_size + sfb_copy_size
|
| 516 |
+
) * atom_thr_size
|
| 517 |
+
|
| 518 |
+
# Setup TMA store for C
|
| 519 |
+
epi_smem_layout = cute.slice_(self.c_smem_layout_staged, (None, None, 0))
|
| 520 |
+
tma_atom_c, tma_tensor_c = cpasync.make_tiled_tma_atom(
|
| 521 |
+
cpasync.CopyBulkTensorTileS2GOp(),
|
| 522 |
+
c_tensor,
|
| 523 |
+
epi_smem_layout,
|
| 524 |
+
self.epi_tile,
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
# Compute grid size
|
| 528 |
+
self.tile_sched_params, grid = self._compute_grid(
|
| 529 |
+
c_tensor,
|
| 530 |
+
self.cta_tile_shape_mnk,
|
| 531 |
+
self.cluster_shape_mn,
|
| 532 |
+
max_active_clusters,
|
| 533 |
+
)
|
| 534 |
+
|
| 535 |
+
self.buffer_align_bytes = 1024
|
| 536 |
+
|
| 537 |
+
# Define shared storage for kernel
|
| 538 |
+
@cute.struct
|
| 539 |
+
class SharedStorage:
|
| 540 |
+
ab_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 541 |
+
ab_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 542 |
+
acc_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 543 |
+
acc_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 544 |
+
tmem_dealloc_mbar_ptr: cutlass.Int64
|
| 545 |
+
tmem_holding_buf: cutlass.Int32
|
| 546 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 547 |
+
sC: cute.struct.Align[
|
| 548 |
+
cute.struct.MemRange[
|
| 549 |
+
self.c_dtype,
|
| 550 |
+
cute.cosize(self.c_smem_layout_staged.outer),
|
| 551 |
+
],
|
| 552 |
+
self.buffer_align_bytes,
|
| 553 |
+
]
|
| 554 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 555 |
+
sA: cute.struct.Align[
|
| 556 |
+
cute.struct.MemRange[
|
| 557 |
+
self.a_dtype, cute.cosize(self.a_smem_layout_staged.outer)
|
| 558 |
+
],
|
| 559 |
+
self.buffer_align_bytes,
|
| 560 |
+
]
|
| 561 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 562 |
+
sB: cute.struct.Align[
|
| 563 |
+
cute.struct.MemRange[
|
| 564 |
+
self.b_dtype, cute.cosize(self.b_smem_layout_staged.outer)
|
| 565 |
+
],
|
| 566 |
+
self.buffer_align_bytes,
|
| 567 |
+
]
|
| 568 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 569 |
+
sSFA: cute.struct.Align[
|
| 570 |
+
cute.struct.MemRange[
|
| 571 |
+
self.sf_dtype, cute.cosize(self.sfa_smem_layout_staged)
|
| 572 |
+
],
|
| 573 |
+
self.buffer_align_bytes,
|
| 574 |
+
]
|
| 575 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 576 |
+
sSFB: cute.struct.Align[
|
| 577 |
+
cute.struct.MemRange[
|
| 578 |
+
self.sf_dtype, cute.cosize(self.sfb_smem_layout_staged)
|
| 579 |
+
],
|
| 580 |
+
self.buffer_align_bytes,
|
| 581 |
+
]
|
| 582 |
+
|
| 583 |
+
self.shared_storage = SharedStorage
|
| 584 |
+
|
| 585 |
+
# Launch the kernel synchronously
|
| 586 |
+
self.kernel(
|
| 587 |
+
tiled_mma,
|
| 588 |
+
tiled_mma_sfb,
|
| 589 |
+
tma_atom_a,
|
| 590 |
+
tma_tensor_a,
|
| 591 |
+
tma_atom_b,
|
| 592 |
+
tma_tensor_b,
|
| 593 |
+
tma_atom_sfa,
|
| 594 |
+
tma_tensor_sfa,
|
| 595 |
+
tma_atom_sfb,
|
| 596 |
+
tma_tensor_sfb,
|
| 597 |
+
tma_atom_c,
|
| 598 |
+
tma_tensor_c,
|
| 599 |
+
self.cluster_layout_vmnk,
|
| 600 |
+
self.cluster_layout_sfb_vmnk,
|
| 601 |
+
self.a_smem_layout_staged,
|
| 602 |
+
self.b_smem_layout_staged,
|
| 603 |
+
self.sfa_smem_layout_staged,
|
| 604 |
+
self.sfb_smem_layout_staged,
|
| 605 |
+
self.c_smem_layout_staged,
|
| 606 |
+
self.epi_tile,
|
| 607 |
+
self.tile_sched_params,
|
| 608 |
+
epilogue_op,
|
| 609 |
+
).launch(
|
| 610 |
+
grid=grid,
|
| 611 |
+
block=[self.threads_per_cta, 1, 1],
|
| 612 |
+
cluster=(*self.cluster_shape_mn, 1),
|
| 613 |
+
stream=stream,
|
| 614 |
+
)
|
| 615 |
+
return
|
| 616 |
+
|
| 617 |
+
# GPU device kernel
|
| 618 |
+
@cute.kernel
|
| 619 |
+
def kernel(
|
| 620 |
+
self,
|
| 621 |
+
tiled_mma: cute.TiledMma,
|
| 622 |
+
tiled_mma_sfb: cute.TiledMma,
|
| 623 |
+
tma_atom_a: cute.CopyAtom,
|
| 624 |
+
mA_mkl: cute.Tensor,
|
| 625 |
+
tma_atom_b: cute.CopyAtom,
|
| 626 |
+
mB_nkl: cute.Tensor,
|
| 627 |
+
tma_atom_sfa: cute.CopyAtom,
|
| 628 |
+
mSFA_mkl: cute.Tensor,
|
| 629 |
+
tma_atom_sfb: cute.CopyAtom,
|
| 630 |
+
mSFB_nkl: cute.Tensor,
|
| 631 |
+
tma_atom_c: Optional[cute.CopyAtom],
|
| 632 |
+
mC_mnl: cute.Tensor,
|
| 633 |
+
cluster_layout_vmnk: cute.Layout,
|
| 634 |
+
cluster_layout_sfb_vmnk: cute.Layout,
|
| 635 |
+
a_smem_layout_staged: cute.ComposedLayout,
|
| 636 |
+
b_smem_layout_staged: cute.ComposedLayout,
|
| 637 |
+
sfa_smem_layout_staged: cute.Layout,
|
| 638 |
+
sfb_smem_layout_staged: cute.Layout,
|
| 639 |
+
c_smem_layout_staged: Union[cute.Layout, cute.ComposedLayout, None],
|
| 640 |
+
epi_tile: cute.Tile,
|
| 641 |
+
tile_sched_params: utils.PersistentTileSchedulerParams,
|
| 642 |
+
epilogue_op: cutlass.Constexpr,
|
| 643 |
+
):
|
| 644 |
+
"""
|
| 645 |
+
GPU device kernel performing the Persistent batched GEMM computation.
|
| 646 |
+
"""
|
| 647 |
+
warp_idx = cute.arch.warp_idx()
|
| 648 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 649 |
+
|
| 650 |
+
#
|
| 651 |
+
# Prefetch tma desc
|
| 652 |
+
#
|
| 653 |
+
if warp_idx == self.tma_warp_id:
|
| 654 |
+
cpasync.prefetch_descriptor(tma_atom_a)
|
| 655 |
+
cpasync.prefetch_descriptor(tma_atom_b)
|
| 656 |
+
cpasync.prefetch_descriptor(tma_atom_sfa)
|
| 657 |
+
cpasync.prefetch_descriptor(tma_atom_sfb)
|
| 658 |
+
cpasync.prefetch_descriptor(tma_atom_c)
|
| 659 |
+
|
| 660 |
+
use_2cta_instrs = cute.size(tiled_mma.thr_id.shape) == 2
|
| 661 |
+
|
| 662 |
+
#
|
| 663 |
+
# Setup cta/thread coordinates
|
| 664 |
+
#
|
| 665 |
+
# Coords inside cluster
|
| 666 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 667 |
+
mma_tile_coord_v = bidx % cute.size(tiled_mma.thr_id.shape)
|
| 668 |
+
is_leader_cta = mma_tile_coord_v == 0
|
| 669 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(
|
| 670 |
+
cute.arch.block_idx_in_cluster()
|
| 671 |
+
)
|
| 672 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(
|
| 673 |
+
cta_rank_in_cluster
|
| 674 |
+
)
|
| 675 |
+
block_in_cluster_coord_sfb_vmnk = cluster_layout_sfb_vmnk.get_flat_coord(
|
| 676 |
+
cta_rank_in_cluster
|
| 677 |
+
)
|
| 678 |
+
# Coord inside cta
|
| 679 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 680 |
+
|
| 681 |
+
#
|
| 682 |
+
# Alloc and init: a+b full/empty, accumulator full/empty, tensor memory dealloc barrier
|
| 683 |
+
#
|
| 684 |
+
smem = utils.SmemAllocator()
|
| 685 |
+
storage = smem.allocate(self.shared_storage)
|
| 686 |
+
|
| 687 |
+
tmem_dealloc_mbar_ptr = storage.tmem_dealloc_mbar_ptr
|
| 688 |
+
tmem_holding_buf = storage.tmem_holding_buf
|
| 689 |
+
|
| 690 |
+
# Initialize mainloop ab_pipeline (barrier) and states
|
| 691 |
+
ab_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 692 |
+
num_tma_producer = self.num_mcast_ctas_a + self.num_mcast_ctas_b - 1
|
| 693 |
+
ab_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 694 |
+
pipeline.Agent.Thread, num_tma_producer
|
| 695 |
+
)
|
| 696 |
+
ab_pipeline = pipeline.PipelineTmaUmma.create(
|
| 697 |
+
barrier_storage=storage.ab_full_mbar_ptr.data_ptr(),
|
| 698 |
+
num_stages=self.num_ab_stage,
|
| 699 |
+
producer_group=ab_pipeline_producer_group,
|
| 700 |
+
consumer_group=ab_pipeline_consumer_group,
|
| 701 |
+
tx_count=self.num_tma_load_bytes,
|
| 702 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 703 |
+
)
|
| 704 |
+
|
| 705 |
+
# Initialize acc_pipeline (barrier) and states
|
| 706 |
+
acc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 707 |
+
num_acc_consumer_threads = len(self.epilog_warp_id) * (
|
| 708 |
+
2 if use_2cta_instrs else 1
|
| 709 |
+
)
|
| 710 |
+
acc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 711 |
+
pipeline.Agent.Thread, num_acc_consumer_threads
|
| 712 |
+
)
|
| 713 |
+
acc_pipeline = pipeline.PipelineUmmaAsync.create(
|
| 714 |
+
barrier_storage=storage.acc_full_mbar_ptr.data_ptr(),
|
| 715 |
+
num_stages=self.num_acc_stage,
|
| 716 |
+
producer_group=acc_pipeline_producer_group,
|
| 717 |
+
consumer_group=acc_pipeline_consumer_group,
|
| 718 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
# Tensor memory dealloc barrier init
|
| 722 |
+
if use_2cta_instrs:
|
| 723 |
+
if warp_idx == self.tma_warp_id:
|
| 724 |
+
num_tmem_dealloc_threads = 32
|
| 725 |
+
with cute.arch.elect_one():
|
| 726 |
+
cute.arch.mbarrier_init(
|
| 727 |
+
tmem_dealloc_mbar_ptr, num_tmem_dealloc_threads
|
| 728 |
+
)
|
| 729 |
+
cute.arch.mbarrier_init_fence()
|
| 730 |
+
|
| 731 |
+
# Cluster arrive after barrier init
|
| 732 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 733 |
+
cute.arch.cluster_arrive_relaxed()
|
| 734 |
+
|
| 735 |
+
#
|
| 736 |
+
# Setup smem tensor A/B/SFA/SFB/C
|
| 737 |
+
#
|
| 738 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 739 |
+
sC = storage.sC.get_tensor(
|
| 740 |
+
c_smem_layout_staged.outer, swizzle=c_smem_layout_staged.inner
|
| 741 |
+
)
|
| 742 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 743 |
+
sA = storage.sA.get_tensor(
|
| 744 |
+
a_smem_layout_staged.outer, swizzle=a_smem_layout_staged.inner
|
| 745 |
+
)
|
| 746 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 747 |
+
sB = storage.sB.get_tensor(
|
| 748 |
+
b_smem_layout_staged.outer, swizzle=b_smem_layout_staged.inner
|
| 749 |
+
)
|
| 750 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 751 |
+
sSFA = storage.sSFA.get_tensor(sfa_smem_layout_staged)
|
| 752 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 753 |
+
sSFB = storage.sSFB.get_tensor(sfb_smem_layout_staged)
|
| 754 |
+
|
| 755 |
+
#
|
| 756 |
+
# Compute multicast mask for A/B/SFA/SFB buffer full
|
| 757 |
+
#
|
| 758 |
+
a_full_mcast_mask = None
|
| 759 |
+
b_full_mcast_mask = None
|
| 760 |
+
sfa_full_mcast_mask = None
|
| 761 |
+
sfb_full_mcast_mask = None
|
| 762 |
+
if cutlass.const_expr(self.is_a_mcast or self.is_b_mcast or use_2cta_instrs):
|
| 763 |
+
a_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 764 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 765 |
+
)
|
| 766 |
+
b_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 767 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=1
|
| 768 |
+
)
|
| 769 |
+
sfa_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 770 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 771 |
+
)
|
| 772 |
+
sfb_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 773 |
+
cluster_layout_sfb_vmnk, block_in_cluster_coord_sfb_vmnk, mcast_mode=1
|
| 774 |
+
)
|
| 775 |
+
|
| 776 |
+
#
|
| 777 |
+
# Local_tile partition global tensors
|
| 778 |
+
#
|
| 779 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 780 |
+
gA_mkl = cute.local_tile(
|
| 781 |
+
mA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None)
|
| 782 |
+
)
|
| 783 |
+
# (bN, bK, RestN, RestK, RestL)
|
| 784 |
+
gB_nkl = cute.local_tile(
|
| 785 |
+
mB_nkl, cute.slice_(self.mma_tiler, (0, None, None)), (None, None, None)
|
| 786 |
+
)
|
| 787 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 788 |
+
gSFA_mkl = cute.local_tile(
|
| 789 |
+
mSFA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None)
|
| 790 |
+
)
|
| 791 |
+
# (bN, bK, RestN, RestK, RestL)
|
| 792 |
+
gSFB_nkl = cute.local_tile(
|
| 793 |
+
mSFB_nkl, cute.slice_(self.mma_tiler, (0, None, None)), (None, None, None)
|
| 794 |
+
)
|
| 795 |
+
# (bM, bN, RestM, RestN, RestL)
|
| 796 |
+
gC_mnl = cute.local_tile(
|
| 797 |
+
mC_mnl, cute.slice_(self.mma_tiler, (None, None, 0)), (None, None, None)
|
| 798 |
+
)
|
| 799 |
+
k_tile_cnt = cute.size(gA_mkl, mode=[3])
|
| 800 |
+
|
| 801 |
+
#
|
| 802 |
+
# Partition global tensor for TiledMMA_A/B/C
|
| 803 |
+
#
|
| 804 |
+
thr_mma = tiled_mma.get_slice(mma_tile_coord_v)
|
| 805 |
+
thr_mma_sfb = tiled_mma_sfb.get_slice(mma_tile_coord_v)
|
| 806 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 807 |
+
tCgA = thr_mma.partition_A(gA_mkl)
|
| 808 |
+
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
|
| 809 |
+
tCgB = thr_mma.partition_B(gB_nkl)
|
| 810 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 811 |
+
tCgSFA = thr_mma.partition_A(gSFA_mkl)
|
| 812 |
+
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
|
| 813 |
+
tCgSFB = thr_mma_sfb.partition_B(gSFB_nkl)
|
| 814 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 815 |
+
tCgC = thr_mma.partition_C(gC_mnl)
|
| 816 |
+
|
| 817 |
+
#
|
| 818 |
+
# Partition global/shared tensor for TMA load A/B
|
| 819 |
+
#
|
| 820 |
+
# TMA load A partition_S/D
|
| 821 |
+
a_cta_layout = cute.make_layout(
|
| 822 |
+
cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape
|
| 823 |
+
)
|
| 824 |
+
# ((atom_v, rest_v), STAGE)
|
| 825 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 826 |
+
tAsA, tAgA = cpasync.tma_partition(
|
| 827 |
+
tma_atom_a,
|
| 828 |
+
block_in_cluster_coord_vmnk[2],
|
| 829 |
+
a_cta_layout,
|
| 830 |
+
cute.group_modes(sA, 0, 3),
|
| 831 |
+
cute.group_modes(tCgA, 0, 3),
|
| 832 |
+
)
|
| 833 |
+
# TMA load B partition_S/D
|
| 834 |
+
b_cta_layout = cute.make_layout(
|
| 835 |
+
cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape
|
| 836 |
+
)
|
| 837 |
+
# ((atom_v, rest_v), STAGE)
|
| 838 |
+
# ((atom_v, rest_v), RestN, RestK, RestL)
|
| 839 |
+
tBsB, tBgB = cpasync.tma_partition(
|
| 840 |
+
tma_atom_b,
|
| 841 |
+
block_in_cluster_coord_vmnk[1],
|
| 842 |
+
b_cta_layout,
|
| 843 |
+
cute.group_modes(sB, 0, 3),
|
| 844 |
+
cute.group_modes(tCgB, 0, 3),
|
| 845 |
+
)
|
| 846 |
+
|
| 847 |
+
# TMA load SFA partition_S/D
|
| 848 |
+
sfa_cta_layout = a_cta_layout
|
| 849 |
+
# ((atom_v, rest_v), STAGE)
|
| 850 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 851 |
+
tAsSFA, tAgSFA = cute.nvgpu.cpasync.tma_partition(
|
| 852 |
+
tma_atom_sfa,
|
| 853 |
+
block_in_cluster_coord_vmnk[2],
|
| 854 |
+
sfa_cta_layout,
|
| 855 |
+
cute.group_modes(sSFA, 0, 3),
|
| 856 |
+
cute.group_modes(tCgSFA, 0, 3),
|
| 857 |
+
)
|
| 858 |
+
tAsSFA = cute.filter_zeros(tAsSFA)
|
| 859 |
+
tAgSFA = cute.filter_zeros(tAgSFA)
|
| 860 |
+
|
| 861 |
+
# TMA load SFB partition_S/D
|
| 862 |
+
sfb_cta_layout = cute.make_layout(
|
| 863 |
+
cute.slice_(cluster_layout_sfb_vmnk, (0, None, 0, 0)).shape
|
| 864 |
+
)
|
| 865 |
+
# ((atom_v, rest_v), STAGE)
|
| 866 |
+
# ((atom_v, rest_v), RestN, RestK, RestL)
|
| 867 |
+
tBsSFB, tBgSFB = cute.nvgpu.cpasync.tma_partition(
|
| 868 |
+
tma_atom_sfb,
|
| 869 |
+
block_in_cluster_coord_sfb_vmnk[1],
|
| 870 |
+
sfb_cta_layout,
|
| 871 |
+
cute.group_modes(sSFB, 0, 3),
|
| 872 |
+
cute.group_modes(tCgSFB, 0, 3),
|
| 873 |
+
)
|
| 874 |
+
tBsSFB = cute.filter_zeros(tBsSFB)
|
| 875 |
+
tBgSFB = cute.filter_zeros(tBgSFB)
|
| 876 |
+
|
| 877 |
+
#
|
| 878 |
+
# Partition shared/tensor memory tensor for TiledMMA_A/B/C
|
| 879 |
+
#
|
| 880 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 881 |
+
tCrA = tiled_mma.make_fragment_A(sA)
|
| 882 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 883 |
+
tCrB = tiled_mma.make_fragment_B(sB)
|
| 884 |
+
# (MMA, MMA_M, MMA_N)
|
| 885 |
+
acc_shape = tiled_mma.partition_shape_C(self.mma_tiler[:2])
|
| 886 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 887 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(
|
| 888 |
+
cute.append(acc_shape, self.num_acc_stage)
|
| 889 |
+
)
|
| 890 |
+
|
| 891 |
+
#
|
| 892 |
+
# Cluster wait before tensor memory alloc
|
| 893 |
+
#
|
| 894 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 895 |
+
cute.arch.cluster_wait()
|
| 896 |
+
else:
|
| 897 |
+
cute.arch.barrier(
|
| 898 |
+
barrier_id=self.cta_sync_bar_id, number_of_threads=self.threads_per_cta
|
| 899 |
+
)
|
| 900 |
+
|
| 901 |
+
#
|
| 902 |
+
# Specialized TMA load warp
|
| 903 |
+
#
|
| 904 |
+
if warp_idx == self.tma_warp_id:
|
| 905 |
+
#
|
| 906 |
+
# Persistent tile scheduling loop
|
| 907 |
+
#
|
| 908 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 909 |
+
tile_sched_params, cute.arch.block_idx(), cute.arch.grid_dim()
|
| 910 |
+
)
|
| 911 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 912 |
+
|
| 913 |
+
ab_producer_state = pipeline.make_pipeline_state(
|
| 914 |
+
pipeline.PipelineUserType.Producer, self.num_ab_stage
|
| 915 |
+
)
|
| 916 |
+
|
| 917 |
+
while work_tile.is_valid_tile:
|
| 918 |
+
|
| 919 |
+
# Get tile coord from tile scheduler
|
| 920 |
+
cur_tile_coord = work_tile.tile_idx
|
| 921 |
+
mma_tile_coord_mnl = (
|
| 922 |
+
cur_tile_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 923 |
+
cur_tile_coord[1],
|
| 924 |
+
cur_tile_coord[2],
|
| 925 |
+
)
|
| 926 |
+
|
| 927 |
+
#
|
| 928 |
+
# Slice to per mma tile index
|
| 929 |
+
#
|
| 930 |
+
# ((atom_v, rest_v), RestK)
|
| 931 |
+
tAgA_slice = tAgA[
|
| 932 |
+
(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])
|
| 933 |
+
]
|
| 934 |
+
# ((atom_v, rest_v), RestK)
|
| 935 |
+
tBgB_slice = tBgB[
|
| 936 |
+
(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])
|
| 937 |
+
]
|
| 938 |
+
|
| 939 |
+
# ((atom_v, rest_v), RestK)
|
| 940 |
+
tAgSFA_slice = tAgSFA[
|
| 941 |
+
(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])
|
| 942 |
+
]
|
| 943 |
+
# ((atom_v, rest_v), RestK)
|
| 944 |
+
tBgSFB_slice = tBgSFB[
|
| 945 |
+
(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])
|
| 946 |
+
]
|
| 947 |
+
|
| 948 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt
|
| 949 |
+
ab_producer_state.reset_count()
|
| 950 |
+
peek_ab_empty_status = cutlass.Boolean(1)
|
| 951 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 952 |
+
peek_ab_empty_status = ab_pipeline.producer_try_acquire(
|
| 953 |
+
ab_producer_state
|
| 954 |
+
)
|
| 955 |
+
#
|
| 956 |
+
# Tma load loop
|
| 957 |
+
#
|
| 958 |
+
for k_tile in cutlass.range(0, k_tile_cnt, 1, unroll=1):
|
| 959 |
+
# Conditionally wait for AB buffer empty
|
| 960 |
+
ab_pipeline.producer_acquire(
|
| 961 |
+
ab_producer_state, peek_ab_empty_status
|
| 962 |
+
)
|
| 963 |
+
|
| 964 |
+
# TMA load A/B/SFA/SFB
|
| 965 |
+
cute.copy(
|
| 966 |
+
tma_atom_a,
|
| 967 |
+
tAgA_slice[(None, ab_producer_state.count)],
|
| 968 |
+
tAsA[(None, ab_producer_state.index)],
|
| 969 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 970 |
+
mcast_mask=a_full_mcast_mask,
|
| 971 |
+
)
|
| 972 |
+
cute.copy(
|
| 973 |
+
tma_atom_b,
|
| 974 |
+
tBgB_slice[(None, ab_producer_state.count)],
|
| 975 |
+
tBsB[(None, ab_producer_state.index)],
|
| 976 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 977 |
+
mcast_mask=b_full_mcast_mask,
|
| 978 |
+
)
|
| 979 |
+
cute.copy(
|
| 980 |
+
tma_atom_sfa,
|
| 981 |
+
tAgSFA_slice[(None, ab_producer_state.count)],
|
| 982 |
+
tAsSFA[(None, ab_producer_state.index)],
|
| 983 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 984 |
+
mcast_mask=sfa_full_mcast_mask,
|
| 985 |
+
)
|
| 986 |
+
cute.copy(
|
| 987 |
+
tma_atom_sfb,
|
| 988 |
+
tBgSFB_slice[(None, ab_producer_state.count)],
|
| 989 |
+
tBsSFB[(None, ab_producer_state.index)],
|
| 990 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 991 |
+
mcast_mask=sfb_full_mcast_mask,
|
| 992 |
+
)
|
| 993 |
+
|
| 994 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt + k_tile + 1
|
| 995 |
+
ab_producer_state.advance()
|
| 996 |
+
peek_ab_empty_status = cutlass.Boolean(1)
|
| 997 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 998 |
+
peek_ab_empty_status = ab_pipeline.producer_try_acquire(
|
| 999 |
+
ab_producer_state
|
| 1000 |
+
)
|
| 1001 |
+
|
| 1002 |
+
#
|
| 1003 |
+
# Advance to next tile
|
| 1004 |
+
#
|
| 1005 |
+
tile_sched.advance_to_next_work()
|
| 1006 |
+
work_tile = tile_sched.get_current_work()
|
| 1007 |
+
|
| 1008 |
+
#
|
| 1009 |
+
# Wait A/B buffer empty
|
| 1010 |
+
#
|
| 1011 |
+
ab_pipeline.producer_tail(ab_producer_state)
|
| 1012 |
+
|
| 1013 |
+
#
|
| 1014 |
+
# Specialized MMA warp
|
| 1015 |
+
#
|
| 1016 |
+
if warp_idx == self.mma_warp_id:
|
| 1017 |
+
#
|
| 1018 |
+
# Bar sync for retrieve tensor memory ptr from shared mem
|
| 1019 |
+
#
|
| 1020 |
+
tmem_ptr_read_threads = 32 * len((self.mma_warp_id, *self.epilog_warp_id))
|
| 1021 |
+
cute.arch.barrier(
|
| 1022 |
+
barrier_id=self.tmem_ptr_sync_bar_id,
|
| 1023 |
+
number_of_threads=tmem_ptr_read_threads,
|
| 1024 |
+
)
|
| 1025 |
+
|
| 1026 |
+
#
|
| 1027 |
+
# Retrieving tensor memory ptr and make accumulator/SFA/SFB tensor
|
| 1028 |
+
#
|
| 1029 |
+
# Make accumulator tmem tensor
|
| 1030 |
+
acc_tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 1031 |
+
self.acc_dtype,
|
| 1032 |
+
alignment=16,
|
| 1033 |
+
ptr_to_buffer_holding_addr=tmem_holding_buf,
|
| 1034 |
+
)
|
| 1035 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 1036 |
+
tCtAcc_base = cute.make_tensor(acc_tmem_ptr, tCtAcc_fake.layout)
|
| 1037 |
+
|
| 1038 |
+
# Make SFA tmem tensor
|
| 1039 |
+
sfa_tmem_ptr = cute.recast_ptr(
|
| 1040 |
+
acc_tmem_ptr + tcgen05.find_tmem_tensor_col_offset(tCtAcc_base),
|
| 1041 |
+
dtype=self.sf_dtype,
|
| 1042 |
+
)
|
| 1043 |
+
# (MMA, MMA_M, MMA_K)
|
| 1044 |
+
tCtSFA_layout = blockscaled_utils.make_tmem_layout_sfa(
|
| 1045 |
+
tiled_mma,
|
| 1046 |
+
self.mma_tiler,
|
| 1047 |
+
self.sf_vec_size,
|
| 1048 |
+
cute.slice_(sfa_smem_layout_staged, (None, None, None, 0)),
|
| 1049 |
+
)
|
| 1050 |
+
tCtSFA = cute.make_tensor(sfa_tmem_ptr, tCtSFA_layout)
|
| 1051 |
+
|
| 1052 |
+
# Make SFB tmem tensor
|
| 1053 |
+
sfb_tmem_ptr = cute.recast_ptr(
|
| 1054 |
+
acc_tmem_ptr
|
| 1055 |
+
+ tcgen05.find_tmem_tensor_col_offset(tCtAcc_base)
|
| 1056 |
+
+ tcgen05.find_tmem_tensor_col_offset(tCtSFA),
|
| 1057 |
+
dtype=self.sf_dtype,
|
| 1058 |
+
)
|
| 1059 |
+
# (MMA, MMA_N, MMA_K)
|
| 1060 |
+
tCtSFB_layout = blockscaled_utils.make_tmem_layout_sfb(
|
| 1061 |
+
tiled_mma,
|
| 1062 |
+
self.mma_tiler,
|
| 1063 |
+
self.sf_vec_size,
|
| 1064 |
+
cute.slice_(sfb_smem_layout_staged, (None, None, None, 0)),
|
| 1065 |
+
)
|
| 1066 |
+
tCtSFB = cute.make_tensor(sfb_tmem_ptr, tCtSFB_layout)
|
| 1067 |
+
#
|
| 1068 |
+
# Partition for S2T copy of SFA/SFB
|
| 1069 |
+
#
|
| 1070 |
+
tiled_copy_s2t_sfa, tCsSFA_compact_s2t, tCtSFA_compact_s2t = (
|
| 1071 |
+
self.mainloop_s2t_copy_and_partition(sSFA, tCtSFA)
|
| 1072 |
+
)
|
| 1073 |
+
tiled_copy_s2t_sfb, tCsSFB_compact_s2t, tCtSFB_compact_s2t = (
|
| 1074 |
+
self.mainloop_s2t_copy_and_partition(sSFB, tCtSFB)
|
| 1075 |
+
)
|
| 1076 |
+
|
| 1077 |
+
#
|
| 1078 |
+
# Persistent tile scheduling loop
|
| 1079 |
+
#
|
| 1080 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 1081 |
+
tile_sched_params, cute.arch.block_idx(), cute.arch.grid_dim()
|
| 1082 |
+
)
|
| 1083 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 1084 |
+
|
| 1085 |
+
ab_consumer_state = pipeline.make_pipeline_state(
|
| 1086 |
+
pipeline.PipelineUserType.Consumer, self.num_ab_stage
|
| 1087 |
+
)
|
| 1088 |
+
acc_producer_state = pipeline.make_pipeline_state(
|
| 1089 |
+
pipeline.PipelineUserType.Producer, self.num_acc_stage
|
| 1090 |
+
)
|
| 1091 |
+
|
| 1092 |
+
while work_tile.is_valid_tile:
|
| 1093 |
+
# Get tile coord from tile scheduler
|
| 1094 |
+
cur_tile_coord = work_tile.tile_idx
|
| 1095 |
+
mma_tile_coord_mnl = (
|
| 1096 |
+
cur_tile_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 1097 |
+
cur_tile_coord[1],
|
| 1098 |
+
cur_tile_coord[2],
|
| 1099 |
+
)
|
| 1100 |
+
|
| 1101 |
+
# Set tensor memory buffer for current tile
|
| 1102 |
+
# (MMA, MMA_M, MMA_N)
|
| 1103 |
+
tCtAcc = tCtAcc_base[(None, None, None, acc_producer_state.index)]
|
| 1104 |
+
|
| 1105 |
+
# Peek (try_wait) AB buffer full for k_tile = 0
|
| 1106 |
+
ab_consumer_state.reset_count()
|
| 1107 |
+
peek_ab_full_status = cutlass.Boolean(1)
|
| 1108 |
+
if ab_consumer_state.count < k_tile_cnt and is_leader_cta:
|
| 1109 |
+
peek_ab_full_status = ab_pipeline.consumer_try_wait(
|
| 1110 |
+
ab_consumer_state
|
| 1111 |
+
)
|
| 1112 |
+
|
| 1113 |
+
#
|
| 1114 |
+
# Wait for accumulator buffer empty
|
| 1115 |
+
#
|
| 1116 |
+
if is_leader_cta:
|
| 1117 |
+
acc_pipeline.producer_acquire(acc_producer_state)
|
| 1118 |
+
|
| 1119 |
+
#
|
| 1120 |
+
# Reset the ACCUMULATE field for each tile
|
| 1121 |
+
#
|
| 1122 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 1123 |
+
|
| 1124 |
+
#
|
| 1125 |
+
# Mma mainloop
|
| 1126 |
+
#
|
| 1127 |
+
for k_tile in range(k_tile_cnt):
|
| 1128 |
+
if is_leader_cta:
|
| 1129 |
+
# Conditionally wait for AB buffer full
|
| 1130 |
+
ab_pipeline.consumer_wait(
|
| 1131 |
+
ab_consumer_state, peek_ab_full_status
|
| 1132 |
+
)
|
| 1133 |
+
|
| 1134 |
+
# Copy SFA/SFB from smem to tmem
|
| 1135 |
+
s2t_stage_coord = (
|
| 1136 |
+
None,
|
| 1137 |
+
None,
|
| 1138 |
+
None,
|
| 1139 |
+
None,
|
| 1140 |
+
ab_consumer_state.index,
|
| 1141 |
+
)
|
| 1142 |
+
tCsSFA_compact_s2t_staged = tCsSFA_compact_s2t[s2t_stage_coord]
|
| 1143 |
+
tCsSFB_compact_s2t_staged = tCsSFB_compact_s2t[s2t_stage_coord]
|
| 1144 |
+
cute.copy(
|
| 1145 |
+
tiled_copy_s2t_sfa,
|
| 1146 |
+
tCsSFA_compact_s2t_staged,
|
| 1147 |
+
tCtSFA_compact_s2t,
|
| 1148 |
+
)
|
| 1149 |
+
cute.copy(
|
| 1150 |
+
tiled_copy_s2t_sfb,
|
| 1151 |
+
tCsSFB_compact_s2t_staged,
|
| 1152 |
+
tCtSFB_compact_s2t,
|
| 1153 |
+
)
|
| 1154 |
+
|
| 1155 |
+
# tCtAcc += tCrA * tCrSFA * tCrB * tCrSFB
|
| 1156 |
+
num_kblocks = cute.size(tCrA, mode=[2])
|
| 1157 |
+
for kblock_idx in cutlass.range(num_kblocks, unroll_full=True):
|
| 1158 |
+
kblock_coord = (
|
| 1159 |
+
None,
|
| 1160 |
+
None,
|
| 1161 |
+
kblock_idx,
|
| 1162 |
+
ab_consumer_state.index,
|
| 1163 |
+
)
|
| 1164 |
+
|
| 1165 |
+
# Set SFA/SFB tensor to tiled_mma
|
| 1166 |
+
sf_kblock_coord = (None, None, kblock_idx)
|
| 1167 |
+
tiled_mma.set(
|
| 1168 |
+
tcgen05.Field.SFA,
|
| 1169 |
+
tCtSFA[sf_kblock_coord].iterator,
|
| 1170 |
+
)
|
| 1171 |
+
tiled_mma.set(
|
| 1172 |
+
tcgen05.Field.SFB,
|
| 1173 |
+
tCtSFB[sf_kblock_coord].iterator,
|
| 1174 |
+
)
|
| 1175 |
+
|
| 1176 |
+
cute.gemm(
|
| 1177 |
+
tiled_mma,
|
| 1178 |
+
tCtAcc,
|
| 1179 |
+
tCrA[kblock_coord],
|
| 1180 |
+
tCrB[kblock_coord],
|
| 1181 |
+
tCtAcc,
|
| 1182 |
+
)
|
| 1183 |
+
|
| 1184 |
+
# Enable accumulate on tCtAcc after first kblock
|
| 1185 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1186 |
+
|
| 1187 |
+
# Async arrive AB buffer empty
|
| 1188 |
+
ab_pipeline.consumer_release(ab_consumer_state)
|
| 1189 |
+
|
| 1190 |
+
# Peek (try_wait) AB buffer full for k_tile = k_tile + 1
|
| 1191 |
+
ab_consumer_state.advance()
|
| 1192 |
+
peek_ab_full_status = cutlass.Boolean(1)
|
| 1193 |
+
if ab_consumer_state.count < k_tile_cnt:
|
| 1194 |
+
if is_leader_cta:
|
| 1195 |
+
peek_ab_full_status = ab_pipeline.consumer_try_wait(
|
| 1196 |
+
ab_consumer_state
|
| 1197 |
+
)
|
| 1198 |
+
|
| 1199 |
+
#
|
| 1200 |
+
# Async arrive accumulator buffer full
|
| 1201 |
+
#
|
| 1202 |
+
if is_leader_cta:
|
| 1203 |
+
acc_pipeline.producer_commit(acc_producer_state)
|
| 1204 |
+
acc_producer_state.advance()
|
| 1205 |
+
|
| 1206 |
+
#
|
| 1207 |
+
# Advance to next tile
|
| 1208 |
+
#
|
| 1209 |
+
tile_sched.advance_to_next_work()
|
| 1210 |
+
work_tile = tile_sched.get_current_work()
|
| 1211 |
+
|
| 1212 |
+
#
|
| 1213 |
+
# Wait for accumulator buffer empty
|
| 1214 |
+
#
|
| 1215 |
+
acc_pipeline.producer_tail(acc_producer_state)
|
| 1216 |
+
#
|
| 1217 |
+
# Specialized epilogue warps
|
| 1218 |
+
#
|
| 1219 |
+
if warp_idx < self.mma_warp_id:
|
| 1220 |
+
#
|
| 1221 |
+
# Alloc tensor memory buffer
|
| 1222 |
+
#
|
| 1223 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1224 |
+
cute.arch.alloc_tmem(
|
| 1225 |
+
self.num_tmem_alloc_cols,
|
| 1226 |
+
tmem_holding_buf,
|
| 1227 |
+
is_two_cta=use_2cta_instrs,
|
| 1228 |
+
)
|
| 1229 |
+
|
| 1230 |
+
#
|
| 1231 |
+
# Bar sync for retrieve tensor memory ptr from shared memory
|
| 1232 |
+
#
|
| 1233 |
+
tmem_ptr_read_threads = 32 * len((self.mma_warp_id, *self.epilog_warp_id))
|
| 1234 |
+
cute.arch.barrier(
|
| 1235 |
+
barrier_id=self.tmem_ptr_sync_bar_id,
|
| 1236 |
+
number_of_threads=tmem_ptr_read_threads,
|
| 1237 |
+
)
|
| 1238 |
+
|
| 1239 |
+
#
|
| 1240 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 1241 |
+
#
|
| 1242 |
+
acc_tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 1243 |
+
self.acc_dtype,
|
| 1244 |
+
alignment=16,
|
| 1245 |
+
ptr_to_buffer_holding_addr=tmem_holding_buf,
|
| 1246 |
+
)
|
| 1247 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 1248 |
+
tCtAcc_base = cute.make_tensor(acc_tmem_ptr, tCtAcc_fake.layout)
|
| 1249 |
+
|
| 1250 |
+
#
|
| 1251 |
+
# Partition for epilogue
|
| 1252 |
+
#
|
| 1253 |
+
epi_tidx = tidx
|
| 1254 |
+
tiled_copy_t2r, tTR_tAcc_base, tTR_rAcc = (
|
| 1255 |
+
self.epilog_tmem_copy_and_partition(
|
| 1256 |
+
epi_tidx, tCtAcc_base, tCgC, epi_tile, use_2cta_instrs
|
| 1257 |
+
)
|
| 1258 |
+
)
|
| 1259 |
+
|
| 1260 |
+
tTR_rC = cute.make_fragment(tTR_rAcc.shape, self.c_dtype)
|
| 1261 |
+
tiled_copy_r2s, tRS_rC, tRS_sC = self.epilog_smem_copy_and_partition(
|
| 1262 |
+
tiled_copy_t2r, tTR_rC, epi_tidx, sC
|
| 1263 |
+
)
|
| 1264 |
+
tma_atom_c, bSG_sC, bSG_gC_partitioned = (
|
| 1265 |
+
self.epilog_gmem_copy_and_partition(
|
| 1266 |
+
epi_tidx, tma_atom_c, tCgC, epi_tile, sC
|
| 1267 |
+
)
|
| 1268 |
+
)
|
| 1269 |
+
|
| 1270 |
+
#
|
| 1271 |
+
# Persistent tile scheduling loop
|
| 1272 |
+
#
|
| 1273 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 1274 |
+
tile_sched_params, cute.arch.block_idx(), cute.arch.grid_dim()
|
| 1275 |
+
)
|
| 1276 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 1277 |
+
|
| 1278 |
+
acc_consumer_state = pipeline.make_pipeline_state(
|
| 1279 |
+
pipeline.PipelineUserType.Consumer, self.num_acc_stage
|
| 1280 |
+
)
|
| 1281 |
+
|
| 1282 |
+
# Threads/warps participating in tma store pipeline
|
| 1283 |
+
c_producer_group = pipeline.CooperativeGroup(
|
| 1284 |
+
pipeline.Agent.Thread,
|
| 1285 |
+
32 * len(self.epilog_warp_id),
|
| 1286 |
+
32 * len(self.epilog_warp_id),
|
| 1287 |
+
)
|
| 1288 |
+
c_pipeline = pipeline.PipelineTmaStore.create(
|
| 1289 |
+
num_stages=self.num_c_stage,
|
| 1290 |
+
producer_group=c_producer_group,
|
| 1291 |
+
)
|
| 1292 |
+
|
| 1293 |
+
while work_tile.is_valid_tile:
|
| 1294 |
+
|
| 1295 |
+
# Get tile coord from tile scheduler
|
| 1296 |
+
cur_tile_coord = work_tile.tile_idx
|
| 1297 |
+
mma_tile_coord_mnl = (
|
| 1298 |
+
cur_tile_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 1299 |
+
cur_tile_coord[1],
|
| 1300 |
+
cur_tile_coord[2],
|
| 1301 |
+
)
|
| 1302 |
+
|
| 1303 |
+
#
|
| 1304 |
+
# Slice to per mma tile index
|
| 1305 |
+
#
|
| 1306 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1307 |
+
bSG_gC = bSG_gC_partitioned[
|
| 1308 |
+
(
|
| 1309 |
+
None,
|
| 1310 |
+
None,
|
| 1311 |
+
None,
|
| 1312 |
+
*mma_tile_coord_mnl,
|
| 1313 |
+
)
|
| 1314 |
+
]
|
| 1315 |
+
|
| 1316 |
+
# Set tensor memory buffer for current tile
|
| 1317 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M)
|
| 1318 |
+
tTR_tAcc = tTR_tAcc_base[
|
| 1319 |
+
(None, None, None, None, None, acc_consumer_state.index)
|
| 1320 |
+
]
|
| 1321 |
+
|
| 1322 |
+
#
|
| 1323 |
+
# Wait for accumulator buffer full
|
| 1324 |
+
#
|
| 1325 |
+
acc_pipeline.consumer_wait(acc_consumer_state)
|
| 1326 |
+
|
| 1327 |
+
tTR_tAcc = cute.group_modes(tTR_tAcc, 3, cute.rank(tTR_tAcc))
|
| 1328 |
+
bSG_gC = cute.group_modes(bSG_gC, 1, cute.rank(bSG_gC))
|
| 1329 |
+
|
| 1330 |
+
#
|
| 1331 |
+
# Store accumulator to global memory in subtiles
|
| 1332 |
+
#
|
| 1333 |
+
subtile_cnt = cute.size(tTR_tAcc.shape, mode=[3])
|
| 1334 |
+
num_prev_subtiles = tile_sched.num_tiles_executed * subtile_cnt
|
| 1335 |
+
for subtile_idx in cutlass.range(subtile_cnt):
|
| 1336 |
+
#
|
| 1337 |
+
# Load accumulator from tensor memory buffer to register
|
| 1338 |
+
#
|
| 1339 |
+
tTR_tAcc_mn = tTR_tAcc[(None, None, None, subtile_idx)]
|
| 1340 |
+
cute.copy(tiled_copy_t2r, tTR_tAcc_mn, tTR_rAcc)
|
| 1341 |
+
|
| 1342 |
+
#
|
| 1343 |
+
# Convert to C type
|
| 1344 |
+
#
|
| 1345 |
+
acc_vec = tiled_copy_r2s.retile(tTR_rAcc).load()
|
| 1346 |
+
acc_vec = epilogue_op(acc_vec.to(self.c_dtype))
|
| 1347 |
+
tRS_rC.store(acc_vec)
|
| 1348 |
+
|
| 1349 |
+
#
|
| 1350 |
+
# Store C to shared memory
|
| 1351 |
+
#
|
| 1352 |
+
c_buffer = (num_prev_subtiles + subtile_idx) % self.num_c_stage
|
| 1353 |
+
cute.copy(
|
| 1354 |
+
tiled_copy_r2s,
|
| 1355 |
+
tRS_rC,
|
| 1356 |
+
tRS_sC[(None, None, None, c_buffer)],
|
| 1357 |
+
)
|
| 1358 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 1359 |
+
cute.arch.fence_proxy(
|
| 1360 |
+
cute.arch.ProxyKind.async_shared,
|
| 1361 |
+
space=cute.arch.SharedSpace.shared_cta,
|
| 1362 |
+
)
|
| 1363 |
+
epilog_threads = 32 * len(self.epilog_warp_id)
|
| 1364 |
+
cute.arch.barrier(
|
| 1365 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 1366 |
+
number_of_threads=epilog_threads,
|
| 1367 |
+
)
|
| 1368 |
+
|
| 1369 |
+
#
|
| 1370 |
+
# TMA store C to global memory
|
| 1371 |
+
#
|
| 1372 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1373 |
+
cute.copy(
|
| 1374 |
+
tma_atom_c,
|
| 1375 |
+
bSG_sC[(None, c_buffer)],
|
| 1376 |
+
bSG_gC[(None, subtile_idx)],
|
| 1377 |
+
)
|
| 1378 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 1379 |
+
c_pipeline.producer_commit()
|
| 1380 |
+
c_pipeline.producer_acquire()
|
| 1381 |
+
cute.arch.barrier(
|
| 1382 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 1383 |
+
number_of_threads=epilog_threads,
|
| 1384 |
+
)
|
| 1385 |
+
|
| 1386 |
+
#
|
| 1387 |
+
# Async arrive accumulator buffer empty
|
| 1388 |
+
#
|
| 1389 |
+
with cute.arch.elect_one():
|
| 1390 |
+
acc_pipeline.consumer_release(acc_consumer_state)
|
| 1391 |
+
acc_consumer_state.advance()
|
| 1392 |
+
|
| 1393 |
+
#
|
| 1394 |
+
# Advance to next tile
|
| 1395 |
+
#
|
| 1396 |
+
tile_sched.advance_to_next_work()
|
| 1397 |
+
work_tile = tile_sched.get_current_work()
|
| 1398 |
+
|
| 1399 |
+
#
|
| 1400 |
+
# Dealloc the tensor memory buffer
|
| 1401 |
+
#
|
| 1402 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1403 |
+
cute.arch.relinquish_tmem_alloc_permit(is_two_cta=use_2cta_instrs)
|
| 1404 |
+
epilog_threads = 32 * len(self.epilog_warp_id)
|
| 1405 |
+
cute.arch.barrier(
|
| 1406 |
+
barrier_id=self.epilog_sync_bar_id, number_of_threads=epilog_threads
|
| 1407 |
+
)
|
| 1408 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1409 |
+
if use_2cta_instrs:
|
| 1410 |
+
cute.arch.mbarrier_arrive(
|
| 1411 |
+
tmem_dealloc_mbar_ptr, cta_rank_in_cluster ^ 1
|
| 1412 |
+
)
|
| 1413 |
+
cute.arch.mbarrier_wait(tmem_dealloc_mbar_ptr, 0)
|
| 1414 |
+
cute.arch.dealloc_tmem(
|
| 1415 |
+
acc_tmem_ptr, self.num_tmem_alloc_cols, is_two_cta=use_2cta_instrs
|
| 1416 |
+
)
|
| 1417 |
+
#
|
| 1418 |
+
# Wait for C store complete
|
| 1419 |
+
#
|
| 1420 |
+
c_pipeline.producer_tail()
|
| 1421 |
+
|
| 1422 |
+
def mainloop_s2t_copy_and_partition(
|
| 1423 |
+
self,
|
| 1424 |
+
sSF: cute.Tensor,
|
| 1425 |
+
tSF: cute.Tensor,
|
| 1426 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1427 |
+
"""
|
| 1428 |
+
Make tiledCopy for smem to tmem load for scale factor tensor, then use it to partition smem memory (source) and tensor memory (destination).
|
| 1429 |
+
|
| 1430 |
+
:param sSF: The scale factor tensor in smem
|
| 1431 |
+
:type sSF: cute.Tensor
|
| 1432 |
+
:param tSF: The scale factor tensor in tmem
|
| 1433 |
+
:type tSF: cute.Tensor
|
| 1434 |
+
|
| 1435 |
+
:return: A tuple containing (tiled_copy_s2t, tCsSF_compact_s2t, tCtSF_compact_s2t) where:
|
| 1436 |
+
- tiled_copy_s2t: The tiled copy operation for smem to tmem load for scale factor tensor(s2t)
|
| 1437 |
+
- tCsSF_compact_s2t: The partitioned scale factor tensor in smem
|
| 1438 |
+
- tSF_compact_s2t: The partitioned scale factor tensor in tmem
|
| 1439 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1440 |
+
"""
|
| 1441 |
+
# (MMA, MMA_MN, MMA_K, STAGE)
|
| 1442 |
+
tCsSF_compact = cute.filter_zeros(sSF)
|
| 1443 |
+
# (MMA, MMA_MN, MMA_K)
|
| 1444 |
+
tCtSF_compact = cute.filter_zeros(tSF)
|
| 1445 |
+
|
| 1446 |
+
# Make S2T CopyAtom and tiledCopy
|
| 1447 |
+
copy_atom_s2t = cute.make_copy_atom(
|
| 1448 |
+
tcgen05.Cp4x32x128bOp(self.cta_group),
|
| 1449 |
+
self.sf_dtype,
|
| 1450 |
+
)
|
| 1451 |
+
tiled_copy_s2t = tcgen05.make_s2t_copy(copy_atom_s2t, tCtSF_compact)
|
| 1452 |
+
thr_copy_s2t = tiled_copy_s2t.get_slice(0)
|
| 1453 |
+
|
| 1454 |
+
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K, STAGE)
|
| 1455 |
+
tCsSF_compact_s2t_ = thr_copy_s2t.partition_S(tCsSF_compact)
|
| 1456 |
+
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K, STAGE)
|
| 1457 |
+
tCsSF_compact_s2t = tcgen05.get_s2t_smem_desc_tensor(
|
| 1458 |
+
tiled_copy_s2t, tCsSF_compact_s2t_
|
| 1459 |
+
)
|
| 1460 |
+
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K)
|
| 1461 |
+
tCtSF_compact_s2t = thr_copy_s2t.partition_D(tCtSF_compact)
|
| 1462 |
+
|
| 1463 |
+
return tiled_copy_s2t, tCsSF_compact_s2t, tCtSF_compact_s2t
|
| 1464 |
+
|
| 1465 |
+
def epilog_tmem_copy_and_partition(
|
| 1466 |
+
self,
|
| 1467 |
+
tidx: cutlass.Int32,
|
| 1468 |
+
tAcc: cute.Tensor,
|
| 1469 |
+
gC_mnl: cute.Tensor,
|
| 1470 |
+
epi_tile: cute.Tile,
|
| 1471 |
+
use_2cta_instrs: Union[cutlass.Boolean, bool],
|
| 1472 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1473 |
+
"""
|
| 1474 |
+
Make tiledCopy for tensor memory load, then use it to partition tensor memory (source) and register array (destination).
|
| 1475 |
+
|
| 1476 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1477 |
+
:type tidx: cutlass.Int32
|
| 1478 |
+
:param tAcc: The accumulator tensor to be copied and partitioned
|
| 1479 |
+
:type tAcc: cute.Tensor
|
| 1480 |
+
:param gC_mnl: The global tensor C
|
| 1481 |
+
:type gC_mnl: cute.Tensor
|
| 1482 |
+
:param epi_tile: The epilogue tiler
|
| 1483 |
+
:type epi_tile: cute.Tile
|
| 1484 |
+
:param use_2cta_instrs: Whether use_2cta_instrs is enabled
|
| 1485 |
+
:type use_2cta_instrs: bool
|
| 1486 |
+
|
| 1487 |
+
:return: A tuple containing (tiled_copy_t2r, tTR_tAcc, tTR_rAcc) where:
|
| 1488 |
+
- tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1489 |
+
- tTR_tAcc: The partitioned accumulator tensor
|
| 1490 |
+
- tTR_rAcc: The accumulated tensor in register used to hold t2r results
|
| 1491 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1492 |
+
"""
|
| 1493 |
+
# Make tiledCopy for tensor memory load
|
| 1494 |
+
copy_atom_t2r = sm100_utils.get_tmem_load_op(
|
| 1495 |
+
self.cta_tile_shape_mnk,
|
| 1496 |
+
self.c_layout,
|
| 1497 |
+
self.c_dtype,
|
| 1498 |
+
self.acc_dtype,
|
| 1499 |
+
epi_tile,
|
| 1500 |
+
use_2cta_instrs,
|
| 1501 |
+
)
|
| 1502 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, STAGE)
|
| 1503 |
+
tAcc_epi = cute.flat_divide(
|
| 1504 |
+
tAcc[((None, None), 0, 0, None)],
|
| 1505 |
+
epi_tile,
|
| 1506 |
+
)
|
| 1507 |
+
# (EPI_TILE_M, EPI_TILE_N)
|
| 1508 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(
|
| 1509 |
+
copy_atom_t2r, tAcc_epi[(None, None, 0, 0, 0)]
|
| 1510 |
+
)
|
| 1511 |
+
|
| 1512 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1513 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M, STAGE)
|
| 1514 |
+
tTR_tAcc = thr_copy_t2r.partition_S(tAcc_epi)
|
| 1515 |
+
|
| 1516 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1517 |
+
gC_mnl_epi = cute.flat_divide(
|
| 1518 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1519 |
+
)
|
| 1520 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1521 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_mnl_epi)
|
| 1522 |
+
# (T2R, T2R_M, T2R_N)
|
| 1523 |
+
tTR_rAcc = cute.make_fragment(
|
| 1524 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.acc_dtype
|
| 1525 |
+
)
|
| 1526 |
+
return tiled_copy_t2r, tTR_tAcc, tTR_rAcc
|
| 1527 |
+
|
| 1528 |
+
def epilog_smem_copy_and_partition(
|
| 1529 |
+
self,
|
| 1530 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 1531 |
+
tTR_rC: cute.Tensor,
|
| 1532 |
+
tidx: cutlass.Int32,
|
| 1533 |
+
sC: cute.Tensor,
|
| 1534 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1535 |
+
"""
|
| 1536 |
+
Make tiledCopy for shared memory store, then use it to partition register array (source) and shared memory (destination).
|
| 1537 |
+
|
| 1538 |
+
:param tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1539 |
+
:type tiled_copy_t2r: cute.TiledCopy
|
| 1540 |
+
:param tTR_rC: The partitioned accumulator tensor
|
| 1541 |
+
:type tTR_rC: cute.Tensor
|
| 1542 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1543 |
+
:type tidx: cutlass.Int32
|
| 1544 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1545 |
+
:type sC: cute.Tensor
|
| 1546 |
+
:type sepi: cute.Tensor
|
| 1547 |
+
|
| 1548 |
+
:return: A tuple containing (tiled_copy_r2s, tRS_rC, tRS_sC) where:
|
| 1549 |
+
- tiled_copy_r2s: The tiled copy operation for register to smem copy(r2s)
|
| 1550 |
+
- tRS_rC: The partitioned tensor C (register source)
|
| 1551 |
+
- tRS_sC: The partitioned tensor C (smem destination)
|
| 1552 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1553 |
+
"""
|
| 1554 |
+
copy_atom_r2s = sm100_utils.get_smem_store_op(
|
| 1555 |
+
self.c_layout, self.c_dtype, self.acc_dtype, tiled_copy_t2r
|
| 1556 |
+
)
|
| 1557 |
+
tiled_copy_r2s = cute.make_tiled_copy_D(copy_atom_r2s, tiled_copy_t2r)
|
| 1558 |
+
# (R2S, R2S_M, R2S_N, PIPE_D)
|
| 1559 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx)
|
| 1560 |
+
tRS_sC = thr_copy_r2s.partition_D(sC)
|
| 1561 |
+
# (R2S, R2S_M, R2S_N)
|
| 1562 |
+
tRS_rC = tiled_copy_r2s.retile(tTR_rC)
|
| 1563 |
+
return tiled_copy_r2s, tRS_rC, tRS_sC
|
| 1564 |
+
|
| 1565 |
+
def epilog_gmem_copy_and_partition(
|
| 1566 |
+
self,
|
| 1567 |
+
tidx: cutlass.Int32,
|
| 1568 |
+
atom: Union[cute.CopyAtom, cute.TiledCopy],
|
| 1569 |
+
gC_mnl: cute.Tensor,
|
| 1570 |
+
epi_tile: cute.Tile,
|
| 1571 |
+
sC: cute.Tensor,
|
| 1572 |
+
) -> Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]:
|
| 1573 |
+
"""Make tiledCopy for global memory store, then use it to:
|
| 1574 |
+
partition shared memory (source) and global memory (destination) for TMA store version.
|
| 1575 |
+
|
| 1576 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1577 |
+
:type tidx: cutlass.Int32
|
| 1578 |
+
:param atom: The copy_atom_c to be used for TMA store version, or tiled_copy_t2r for none TMA store version
|
| 1579 |
+
:type atom: cute.CopyAtom or cute.TiledCopy
|
| 1580 |
+
:param gC_mnl: The global tensor C
|
| 1581 |
+
:type gC_mnl: cute.Tensor
|
| 1582 |
+
:param epi_tile: The epilogue tiler
|
| 1583 |
+
:type epi_tile: cute.Tile
|
| 1584 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1585 |
+
:type sC: cute.Tensor
|
| 1586 |
+
|
| 1587 |
+
:return: A tuple containing (tma_atom_c, bSG_sC, bSG_gC) where:
|
| 1588 |
+
- tma_atom_c: The TMA copy atom
|
| 1589 |
+
- bSG_sC: The partitioned shared memory tensor C
|
| 1590 |
+
- bSG_gC: The partitioned global tensor C
|
| 1591 |
+
:rtype: Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]
|
| 1592 |
+
"""
|
| 1593 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1594 |
+
gC_epi = cute.flat_divide(
|
| 1595 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1596 |
+
)
|
| 1597 |
+
|
| 1598 |
+
tma_atom_c = atom
|
| 1599 |
+
sC_for_tma_partition = cute.group_modes(sC, 0, 2)
|
| 1600 |
+
gC_for_tma_partition = cute.group_modes(gC_epi, 0, 2)
|
| 1601 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1602 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1603 |
+
bSG_sC, bSG_gC = cpasync.tma_partition(
|
| 1604 |
+
tma_atom_c,
|
| 1605 |
+
0,
|
| 1606 |
+
cute.make_layout(1),
|
| 1607 |
+
sC_for_tma_partition,
|
| 1608 |
+
gC_for_tma_partition,
|
| 1609 |
+
)
|
| 1610 |
+
return tma_atom_c, bSG_sC, bSG_gC
|
| 1611 |
+
|
| 1612 |
+
@staticmethod
|
| 1613 |
+
def _compute_stages(
|
| 1614 |
+
tiled_mma: cute.TiledMma,
|
| 1615 |
+
mma_tiler_mnk: Tuple[int, int, int],
|
| 1616 |
+
a_dtype: Type[cutlass.Numeric],
|
| 1617 |
+
a_major_mode: tcgen05.OperandMajorMode,
|
| 1618 |
+
b_dtype: Type[cutlass.Numeric],
|
| 1619 |
+
b_major_mode: tcgen05.OperandMajorMode,
|
| 1620 |
+
epi_tile: cute.Tile,
|
| 1621 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1622 |
+
c_layout: utils.LayoutEnum,
|
| 1623 |
+
sf_dtype: Type[cutlass.Numeric],
|
| 1624 |
+
sf_vec_size: int,
|
| 1625 |
+
smem_capacity: int,
|
| 1626 |
+
occupancy: int,
|
| 1627 |
+
) -> Tuple[int, int, int]:
|
| 1628 |
+
"""Computes the number of stages for A/B/C operands based on heuristics.
|
| 1629 |
+
|
| 1630 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1631 |
+
:type tiled_mma: cute.TiledMma
|
| 1632 |
+
:param mma_tiler_mnk: The shape (M, N, K) of the MMA tiler.
|
| 1633 |
+
:type mma_tiler_mnk: tuple[int, int, int]
|
| 1634 |
+
:param a_dtype: Data type of operand A.
|
| 1635 |
+
:type a_dtype: type[cutlass.Numeric]
|
| 1636 |
+
:param a_major_mode: Major mode of operand A.
|
| 1637 |
+
:type a_major_mode: tcgen05.OperandMajorMode
|
| 1638 |
+
:param b_dtype: Data type of operand B.
|
| 1639 |
+
:type b_dtype: type[cutlass.Numeric]
|
| 1640 |
+
:param b_major_mode: Major mode of operand B.
|
| 1641 |
+
:type b_major_mode: tcgen05.OperandMajorMode
|
| 1642 |
+
:param epi_tile: The epilogue tile shape.
|
| 1643 |
+
:type epi_tile: cute.Tile
|
| 1644 |
+
:param c_dtype: Data type of operand C (output).
|
| 1645 |
+
:type c_dtype: type[cutlass.Numeric]
|
| 1646 |
+
:param c_layout: Layout enum of operand C.
|
| 1647 |
+
:type c_layout: utils.LayoutEnum
|
| 1648 |
+
:param sf_dtype: Data type of Scale factor.
|
| 1649 |
+
:type sf_dtype: type[cutlass.Numeric]
|
| 1650 |
+
:param sf_vec_size: Scale factor vector size.
|
| 1651 |
+
:type sf_vec_size: int
|
| 1652 |
+
:param smem_capacity: Total available shared memory capacity in bytes.
|
| 1653 |
+
:type smem_capacity: int
|
| 1654 |
+
:param occupancy: Target number of CTAs per SM (occupancy).
|
| 1655 |
+
:type occupancy: int
|
| 1656 |
+
|
| 1657 |
+
:return: A tuple containing the computed number of stages for:
|
| 1658 |
+
(ACC stages, A/B operand stages, C stages)
|
| 1659 |
+
:rtype: tuple[int, int, int]
|
| 1660 |
+
"""
|
| 1661 |
+
# ACC stages
|
| 1662 |
+
num_acc_stage = 1 if mma_tiler_mnk[1] == 256 else 2
|
| 1663 |
+
|
| 1664 |
+
# Default C stages
|
| 1665 |
+
num_c_stage = 2
|
| 1666 |
+
|
| 1667 |
+
# Calculate smem layout and size for one stage of A, B, SFA, SFB and C
|
| 1668 |
+
a_smem_layout_stage_one = sm100_utils.make_smem_layout_a(
|
| 1669 |
+
tiled_mma,
|
| 1670 |
+
mma_tiler_mnk,
|
| 1671 |
+
a_dtype,
|
| 1672 |
+
1, # a tmp 1 stage is provided
|
| 1673 |
+
)
|
| 1674 |
+
b_smem_layout_staged_one = sm100_utils.make_smem_layout_b(
|
| 1675 |
+
tiled_mma,
|
| 1676 |
+
mma_tiler_mnk,
|
| 1677 |
+
b_dtype,
|
| 1678 |
+
1, # a tmp 1 stage is provided
|
| 1679 |
+
)
|
| 1680 |
+
sfa_smem_layout_staged_one = blockscaled_utils.make_smem_layout_sfa(
|
| 1681 |
+
tiled_mma,
|
| 1682 |
+
mma_tiler_mnk,
|
| 1683 |
+
sf_vec_size,
|
| 1684 |
+
1, # a tmp 1 stage is provided
|
| 1685 |
+
)
|
| 1686 |
+
sfb_smem_layout_staged_one = blockscaled_utils.make_smem_layout_sfb(
|
| 1687 |
+
tiled_mma,
|
| 1688 |
+
mma_tiler_mnk,
|
| 1689 |
+
sf_vec_size,
|
| 1690 |
+
1, # a tmp 1 stage is provided
|
| 1691 |
+
)
|
| 1692 |
+
|
| 1693 |
+
c_smem_layout_staged_one = sm100_utils.make_smem_layout_epi(
|
| 1694 |
+
c_dtype,
|
| 1695 |
+
c_layout,
|
| 1696 |
+
epi_tile,
|
| 1697 |
+
1,
|
| 1698 |
+
)
|
| 1699 |
+
|
| 1700 |
+
ab_bytes_per_stage = (
|
| 1701 |
+
cute.size_in_bytes(a_dtype, a_smem_layout_stage_one)
|
| 1702 |
+
+ cute.size_in_bytes(b_dtype, b_smem_layout_staged_one)
|
| 1703 |
+
+ cute.size_in_bytes(sf_dtype, sfa_smem_layout_staged_one)
|
| 1704 |
+
+ cute.size_in_bytes(sf_dtype, sfb_smem_layout_staged_one)
|
| 1705 |
+
)
|
| 1706 |
+
mbar_helpers_bytes = 1024
|
| 1707 |
+
c_bytes_per_stage = cute.size_in_bytes(c_dtype, c_smem_layout_staged_one)
|
| 1708 |
+
c_bytes = c_bytes_per_stage * num_c_stage
|
| 1709 |
+
|
| 1710 |
+
# Calculate A/B/SFA/SFB stages:
|
| 1711 |
+
# Start with total smem per CTA (capacity / occupancy)
|
| 1712 |
+
# Subtract reserved bytes and initial C stages bytes
|
| 1713 |
+
# Divide remaining by bytes needed per A/B/SFA/SFB stage
|
| 1714 |
+
num_ab_stage = (
|
| 1715 |
+
smem_capacity // occupancy - (mbar_helpers_bytes + c_bytes)
|
| 1716 |
+
) // ab_bytes_per_stage
|
| 1717 |
+
|
| 1718 |
+
# Refine epilogue stages:
|
| 1719 |
+
# Calculate remaining smem after allocating for A/B/SFA/SFB stages and reserved bytes
|
| 1720 |
+
# Add remaining unused smem to epilogue
|
| 1721 |
+
num_c_stage += (
|
| 1722 |
+
smem_capacity
|
| 1723 |
+
- occupancy * ab_bytes_per_stage * num_ab_stage
|
| 1724 |
+
- occupancy * (mbar_helpers_bytes + c_bytes)
|
| 1725 |
+
) // (occupancy * c_bytes_per_stage)
|
| 1726 |
+
|
| 1727 |
+
return num_acc_stage, num_ab_stage, num_c_stage
|
| 1728 |
+
|
| 1729 |
+
@staticmethod
|
| 1730 |
+
def _compute_grid(
|
| 1731 |
+
c: cute.Tensor,
|
| 1732 |
+
cta_tile_shape_mnk: Tuple[int, int, int],
|
| 1733 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1734 |
+
max_active_clusters: cutlass.Constexpr,
|
| 1735 |
+
) -> Tuple[utils.PersistentTileSchedulerParams, Tuple[int, int, int]]:
|
| 1736 |
+
"""Use persistent tile scheduler to compute the grid size for the output tensor C.
|
| 1737 |
+
|
| 1738 |
+
:param c: The output tensor C
|
| 1739 |
+
:type c: cute.Tensor
|
| 1740 |
+
:param cta_tile_shape_mnk: The shape (M, N, K) of the CTA tile.
|
| 1741 |
+
:type cta_tile_shape_mnk: tuple[int, int, int]
|
| 1742 |
+
:param cluster_shape_mn: Shape of each cluster in M, N dimensions.
|
| 1743 |
+
:type cluster_shape_mn: tuple[int, int]
|
| 1744 |
+
:param max_active_clusters: Maximum number of active clusters.
|
| 1745 |
+
:type max_active_clusters: cutlass.Constexpr
|
| 1746 |
+
|
| 1747 |
+
:return: A tuple containing:
|
| 1748 |
+
- tile_sched_params: Parameters for the persistent tile scheduler.
|
| 1749 |
+
- grid: Grid shape for kernel launch.
|
| 1750 |
+
:rtype: Tuple[utils.PersistentTileSchedulerParams, tuple[int, int, int]]
|
| 1751 |
+
"""
|
| 1752 |
+
c_shape = cute.slice_(cta_tile_shape_mnk, (None, None, 0))
|
| 1753 |
+
gc = cute.zipped_divide(c, tiler=c_shape)
|
| 1754 |
+
num_ctas_mnl = gc[(0, (None, None, None))].shape
|
| 1755 |
+
cluster_shape_mnl = (*cluster_shape_mn, 1)
|
| 1756 |
+
|
| 1757 |
+
tile_sched_params = utils.PersistentTileSchedulerParams(
|
| 1758 |
+
num_ctas_mnl, cluster_shape_mnl
|
| 1759 |
+
)
|
| 1760 |
+
grid = utils.StaticPersistentTileScheduler.get_grid_shape(
|
| 1761 |
+
tile_sched_params, max_active_clusters
|
| 1762 |
+
)
|
| 1763 |
+
|
| 1764 |
+
return tile_sched_params, grid
|
| 1765 |
+
|
| 1766 |
+
@staticmethod
|
| 1767 |
+
def is_valid_dtypes_and_scale_factor_vec_size(
|
| 1768 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1769 |
+
sf_dtype: Type[cutlass.Numeric],
|
| 1770 |
+
sf_vec_size: int,
|
| 1771 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1772 |
+
) -> bool:
|
| 1773 |
+
"""
|
| 1774 |
+
Check if the dtypes and sf_vec_size are valid combinations
|
| 1775 |
+
|
| 1776 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1777 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1778 |
+
:param sf_dtype: The data type of the scale factor
|
| 1779 |
+
:type sf_dtype: Type[cutlass.Numeric]
|
| 1780 |
+
:param sf_vec_size: The vector size of the scale factor
|
| 1781 |
+
:type sf_vec_size: int
|
| 1782 |
+
:param c_dtype: The data type of the output tensor
|
| 1783 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1784 |
+
|
| 1785 |
+
:return: True if the dtypes and sf_vec_size are valid, False otherwise
|
| 1786 |
+
:rtype: bool
|
| 1787 |
+
"""
|
| 1788 |
+
is_valid = True
|
| 1789 |
+
|
| 1790 |
+
# Check valid ab_dtype
|
| 1791 |
+
if ab_dtype not in {
|
| 1792 |
+
cutlass.Float4E2M1FN,
|
| 1793 |
+
cutlass.Float8E5M2,
|
| 1794 |
+
cutlass.Float8E4M3FN,
|
| 1795 |
+
}:
|
| 1796 |
+
is_valid = False
|
| 1797 |
+
|
| 1798 |
+
# Check valid sf_vec_size
|
| 1799 |
+
if sf_vec_size not in {16, 32}:
|
| 1800 |
+
is_valid = False
|
| 1801 |
+
|
| 1802 |
+
# Check valid sf_dtype
|
| 1803 |
+
if sf_dtype not in {cutlass.Float8E8M0FNU, cutlass.Float8E4M3FN}:
|
| 1804 |
+
is_valid = False
|
| 1805 |
+
|
| 1806 |
+
# Check valid sf_dtype and sf_vec_size combinations
|
| 1807 |
+
if sf_dtype == cutlass.Float8E4M3FN and sf_vec_size == 32:
|
| 1808 |
+
is_valid = False
|
| 1809 |
+
if ab_dtype in {cutlass.Float8E5M2, cutlass.Float8E4M3FN} and sf_vec_size == 16:
|
| 1810 |
+
is_valid = False
|
| 1811 |
+
|
| 1812 |
+
# Check valid c_dtype
|
| 1813 |
+
if c_dtype not in {
|
| 1814 |
+
cutlass.Float32,
|
| 1815 |
+
cutlass.Float16,
|
| 1816 |
+
cutlass.BFloat16,
|
| 1817 |
+
cutlass.Float8E5M2,
|
| 1818 |
+
cutlass.Float8E4M3FN,
|
| 1819 |
+
}:
|
| 1820 |
+
is_valid = False
|
| 1821 |
+
|
| 1822 |
+
return is_valid
|
| 1823 |
+
|
| 1824 |
+
@staticmethod
|
| 1825 |
+
def is_valid_layouts(
|
| 1826 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1827 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1828 |
+
a_major: str,
|
| 1829 |
+
b_major: str,
|
| 1830 |
+
c_major: str,
|
| 1831 |
+
) -> bool:
|
| 1832 |
+
"""
|
| 1833 |
+
Check if the dtypes and sf_vec_size are valid combinations
|
| 1834 |
+
|
| 1835 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1836 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1837 |
+
:param c_dtype: The data type of the output tensor
|
| 1838 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1839 |
+
:param a_major: The major dimension of the A tensor
|
| 1840 |
+
:type a_major: str
|
| 1841 |
+
:param b_major: The major dimension of the B tensor
|
| 1842 |
+
:type b_major: str
|
| 1843 |
+
:param c_major: The major dimension of the C tensor
|
| 1844 |
+
:type c_major: str
|
| 1845 |
+
|
| 1846 |
+
:return: True if the layouts are valid, False otherwise
|
| 1847 |
+
:rtype: bool
|
| 1848 |
+
"""
|
| 1849 |
+
is_valid = True
|
| 1850 |
+
|
| 1851 |
+
if ab_dtype is cutlass.Float4E2M1FN and not (a_major == "k" and b_major == "k"):
|
| 1852 |
+
is_valid = False
|
| 1853 |
+
return is_valid
|
| 1854 |
+
|
| 1855 |
+
@staticmethod
|
| 1856 |
+
def is_valid_mma_tiler_and_cluster_shape(
|
| 1857 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1858 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1859 |
+
) -> bool:
|
| 1860 |
+
"""
|
| 1861 |
+
Check if the mma tiler and cluster shape are valid
|
| 1862 |
+
|
| 1863 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1864 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1865 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1866 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1867 |
+
|
| 1868 |
+
:return: True if the mma tiler and cluster shape are valid, False otherwise
|
| 1869 |
+
:rtype: bool
|
| 1870 |
+
"""
|
| 1871 |
+
is_valid = True
|
| 1872 |
+
# Skip invalid mma tile shape
|
| 1873 |
+
if not mma_tiler_mn[0] in [128, 256]:
|
| 1874 |
+
is_valid = False
|
| 1875 |
+
if not mma_tiler_mn[1] in [128, 256]:
|
| 1876 |
+
is_valid = False
|
| 1877 |
+
# Skip illegal cluster shape
|
| 1878 |
+
if cluster_shape_mn[0] % (2 if mma_tiler_mn[0] == 256 else 1) != 0:
|
| 1879 |
+
is_valid = False
|
| 1880 |
+
# Skip invalid cluster shape
|
| 1881 |
+
is_power_of_2 = lambda x: x > 0 and (x & (x - 1)) == 0
|
| 1882 |
+
if (
|
| 1883 |
+
cluster_shape_mn[0] * cluster_shape_mn[1] > 16
|
| 1884 |
+
or cluster_shape_mn[0] <= 0
|
| 1885 |
+
or cluster_shape_mn[1] <= 0
|
| 1886 |
+
# Special cluster shape check for scale factor multicasts.
|
| 1887 |
+
# Due to limited size of scale factors, we can't multicast among more than 4 CTAs.
|
| 1888 |
+
or cluster_shape_mn[0] > 4
|
| 1889 |
+
or cluster_shape_mn[1] > 4
|
| 1890 |
+
or not is_power_of_2(cluster_shape_mn[0])
|
| 1891 |
+
or not is_power_of_2(cluster_shape_mn[1])
|
| 1892 |
+
):
|
| 1893 |
+
is_valid = False
|
| 1894 |
+
return is_valid
|
| 1895 |
+
|
| 1896 |
+
@staticmethod
|
| 1897 |
+
def is_valid_tensor_alignment(
|
| 1898 |
+
m: int,
|
| 1899 |
+
n: int,
|
| 1900 |
+
k: int,
|
| 1901 |
+
l: int,
|
| 1902 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1903 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1904 |
+
a_major: str,
|
| 1905 |
+
b_major: str,
|
| 1906 |
+
c_major: str,
|
| 1907 |
+
) -> bool:
|
| 1908 |
+
"""
|
| 1909 |
+
Check if the tensor alignment is valid
|
| 1910 |
+
|
| 1911 |
+
:param m: The number of rows in the A tensor
|
| 1912 |
+
:type m: int
|
| 1913 |
+
:param n: The number of columns in the B tensor
|
| 1914 |
+
:type n: int
|
| 1915 |
+
:param k: The number of columns in the A tensor
|
| 1916 |
+
:type k: int
|
| 1917 |
+
:param l: The number of columns in the C tensor
|
| 1918 |
+
:type l: int
|
| 1919 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1920 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1921 |
+
:param c_dtype: The data type of the output tensor
|
| 1922 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1923 |
+
:param a_major: The major axis of the A tensor
|
| 1924 |
+
:type a_major: str
|
| 1925 |
+
:param b_major: The major axis of the B tensor
|
| 1926 |
+
:type b_major: str
|
| 1927 |
+
:param c_major: The major axis of the C tensor
|
| 1928 |
+
:type c_major: str
|
| 1929 |
+
|
| 1930 |
+
:return: True if the problem shape is valid, False otherwise
|
| 1931 |
+
:rtype: bool
|
| 1932 |
+
"""
|
| 1933 |
+
is_valid = True
|
| 1934 |
+
|
| 1935 |
+
def check_contigous_16B_alignment(dtype, is_mode0_major, tensor_shape):
|
| 1936 |
+
major_mode_idx = 0 if is_mode0_major else 1
|
| 1937 |
+
num_major_elements = tensor_shape[major_mode_idx]
|
| 1938 |
+
num_contiguous_elements = 16 * 8 // dtype.width
|
| 1939 |
+
return num_major_elements % num_contiguous_elements == 0
|
| 1940 |
+
|
| 1941 |
+
if (
|
| 1942 |
+
not check_contigous_16B_alignment(ab_dtype, a_major == "m", (m, k, l))
|
| 1943 |
+
or not check_contigous_16B_alignment(ab_dtype, b_major == "n", (n, k, l))
|
| 1944 |
+
or not check_contigous_16B_alignment(c_dtype, c_major == "m", (m, n, l))
|
| 1945 |
+
):
|
| 1946 |
+
is_valid = False
|
| 1947 |
+
return is_valid
|
| 1948 |
+
|
| 1949 |
+
@staticmethod
|
| 1950 |
+
def can_implement(
|
| 1951 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1952 |
+
sf_dtype: Type[cutlass.Numeric],
|
| 1953 |
+
sf_vec_size: int,
|
| 1954 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1955 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1956 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1957 |
+
m: int,
|
| 1958 |
+
n: int,
|
| 1959 |
+
k: int,
|
| 1960 |
+
l: int,
|
| 1961 |
+
a_major: str,
|
| 1962 |
+
b_major: str,
|
| 1963 |
+
c_major: str,
|
| 1964 |
+
) -> bool:
|
| 1965 |
+
"""
|
| 1966 |
+
Check if the gemm can be implemented
|
| 1967 |
+
|
| 1968 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1969 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1970 |
+
:param sf_dtype: The data type of the scale factor tensor
|
| 1971 |
+
:type sf_dtype: Type[cutlass.Numeric]
|
| 1972 |
+
:param sf_vec_size: The vector size
|
| 1973 |
+
:type sf_vec_size: int
|
| 1974 |
+
:param c_dtype: The data type of the output tensor
|
| 1975 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1976 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1977 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1978 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1979 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1980 |
+
:param m: The number of rows in the A tensor
|
| 1981 |
+
:type m: int
|
| 1982 |
+
:param n: The number of columns in the B tensor
|
| 1983 |
+
:type n: int
|
| 1984 |
+
:param k: The number of columns in the A tensor
|
| 1985 |
+
:type k: int
|
| 1986 |
+
:param l: The number of columns in the C tensor
|
| 1987 |
+
:type l: int
|
| 1988 |
+
:param a_major: The major axis of the A tensor
|
| 1989 |
+
:type a_major: str
|
| 1990 |
+
:param b_major: The major axis of the B tensor
|
| 1991 |
+
:type b_major: str
|
| 1992 |
+
:param c_major: The major axis of the C tensor
|
| 1993 |
+
:type c_major: str
|
| 1994 |
+
|
| 1995 |
+
:return: True if the gemm can be implemented, False otherwise
|
| 1996 |
+
:rtype: bool
|
| 1997 |
+
"""
|
| 1998 |
+
can_implement = True
|
| 1999 |
+
# Skip unsupported types
|
| 2000 |
+
if not Sm100BlockScaledPersistentDenseGemmKernel.is_valid_dtypes_and_scale_factor_vec_size(
|
| 2001 |
+
ab_dtype, sf_dtype, sf_vec_size, c_dtype
|
| 2002 |
+
):
|
| 2003 |
+
can_implement = False
|
| 2004 |
+
# Skip unsupported layouts
|
| 2005 |
+
if not Sm100BlockScaledPersistentDenseGemmKernel.is_valid_layouts(
|
| 2006 |
+
ab_dtype, c_dtype, a_major, b_major, c_major
|
| 2007 |
+
):
|
| 2008 |
+
can_implement = False
|
| 2009 |
+
# Skip invalid mma tile shape and cluster shape
|
| 2010 |
+
if not Sm100BlockScaledPersistentDenseGemmKernel.is_valid_mma_tiler_and_cluster_shape(
|
| 2011 |
+
mma_tiler_mn, cluster_shape_mn
|
| 2012 |
+
):
|
| 2013 |
+
can_implement = False
|
| 2014 |
+
# Skip illegal problem shape for load/store alignment
|
| 2015 |
+
if not Sm100BlockScaledPersistentDenseGemmKernel.is_valid_tensor_alignment(
|
| 2016 |
+
m, n, k, l, ab_dtype, c_dtype, a_major, b_major, c_major
|
| 2017 |
+
):
|
| 2018 |
+
can_implement = False
|
| 2019 |
+
return can_implement
|
| 2020 |
+
|
| 2021 |
+
|
| 2022 |
+
@cute.jit
|
| 2023 |
+
def cvt_sf_MKL_to_M32x4xrm_K4xrk_L(
|
| 2024 |
+
sf_ref_tensor: cute.Tensor,
|
| 2025 |
+
sf_mma_tensor: cute.Tensor,
|
| 2026 |
+
):
|
| 2027 |
+
"""Convert scale factor tensor from MKL layout to mma specification M(32x4xrest_m)xK(4xrest_k)xL layout"""
|
| 2028 |
+
# sf_mma_tensor has flatten shape (32, 4, rest_m, 4, rest_k, l)
|
| 2029 |
+
# group to ((32, 4, rest_m), (4, rest_k), l)
|
| 2030 |
+
sf_mma_tensor = cute.group_modes(sf_mma_tensor, 0, 3)
|
| 2031 |
+
sf_mma_tensor = cute.group_modes(sf_mma_tensor, 1, 3)
|
| 2032 |
+
for i in cutlass.range(cute.size(sf_ref_tensor)):
|
| 2033 |
+
mkl_coord = sf_ref_tensor.layout.get_hier_coord(i)
|
| 2034 |
+
sf_mma_tensor[mkl_coord] = sf_ref_tensor[mkl_coord]
|
| 2035 |
+
|
| 2036 |
+
|
| 2037 |
+
def run(
|
| 2038 |
+
mnkl: Tuple[int, int, int, int],
|
| 2039 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 2040 |
+
sf_dtype: Type[cutlass.Numeric],
|
| 2041 |
+
sf_vec_size: int,
|
| 2042 |
+
c_dtype: Type[cutlass.Numeric],
|
| 2043 |
+
a_major: str,
|
| 2044 |
+
b_major: str,
|
| 2045 |
+
c_major: str,
|
| 2046 |
+
mma_tiler_mn: Tuple[int, int],
|
| 2047 |
+
cluster_shape_mn: Tuple[int, int],
|
| 2048 |
+
tolerance: float = 1e-01,
|
| 2049 |
+
warmup_iterations: int = 0,
|
| 2050 |
+
iterations: int = 1,
|
| 2051 |
+
skip_ref_check: bool = False,
|
| 2052 |
+
use_cold_l2: bool = False,
|
| 2053 |
+
**kwargs,
|
| 2054 |
+
):
|
| 2055 |
+
"""Execute a persistent batched dense blockscaled GEMM operation on Blackwell architecture with performance benchmarking.
|
| 2056 |
+
|
| 2057 |
+
This function prepares input tensors, configures and launches the persistent GEMM kernel,
|
| 2058 |
+
optionally performs reference validation, and benchmarks the execution performance.
|
| 2059 |
+
|
| 2060 |
+
:param mnkl: Problem size (M, N, K, L)
|
| 2061 |
+
:type mnkl: Tuple[int, int, int, int]
|
| 2062 |
+
:param ab_dtype: Data type for input tensors A and B
|
| 2063 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 2064 |
+
:param sf_dtype: Data type for scale factor tensor
|
| 2065 |
+
:type sf_dtype: Type[cutlass.Numeric]
|
| 2066 |
+
:param sf_vec_size: Vector size for scale factor tensor
|
| 2067 |
+
:type sf_vec_size: int
|
| 2068 |
+
:param c_dtype: Data type for output tensor C
|
| 2069 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 2070 |
+
:param a_major/b_major/c_major: Memory layout of tensor A/B/C
|
| 2071 |
+
:type a_major/b_major/c_major: str
|
| 2072 |
+
:param mma_tiler_mn: MMA tiling size.
|
| 2073 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 2074 |
+
:param cluster_shape_mn: Cluster shape.
|
| 2075 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 2076 |
+
:param tolerance: Tolerance value for reference validation comparison, defaults to 1e-01
|
| 2077 |
+
:type tolerance: float, optional
|
| 2078 |
+
:param warmup_iterations: Number of warmup iterations before benchmarking, defaults to 0
|
| 2079 |
+
:type warmup_iterations: int, optional
|
| 2080 |
+
:param iterations: Number of benchmark iterations to run, defaults to 1
|
| 2081 |
+
:type iterations: int, optional
|
| 2082 |
+
:param skip_ref_check: Whether to skip reference result validation, defaults to False
|
| 2083 |
+
:type skip_ref_check: bool, optional
|
| 2084 |
+
:param use_cold_l2: Whether to use circular buffer strategy to ensure cold L2 cache, defaults to False
|
| 2085 |
+
:type use_cold_l2: bool, optional
|
| 2086 |
+
:raises RuntimeError: If CUDA GPU is not available
|
| 2087 |
+
:raises ValueError: If the configuration is invalid or unsupported by the kernel
|
| 2088 |
+
:return: Execution time of the GEMM kernel
|
| 2089 |
+
:rtype: float
|
| 2090 |
+
"""
|
| 2091 |
+
print(f"Running Sm100 Persistent Dense BlockScaled GEMM test with:")
|
| 2092 |
+
print(f"mnkl: {mnkl}")
|
| 2093 |
+
print(f"AB dtype: {ab_dtype}, SF dtype: {sf_dtype}, SF Vec size: {sf_vec_size}")
|
| 2094 |
+
print(f"C dtype: {c_dtype}")
|
| 2095 |
+
print(f"Matrix majors - A: {a_major}, B: {b_major}, C: {c_major}")
|
| 2096 |
+
print(f"Mma Tiler (M, N): {mma_tiler_mn}, Cluster Shape (M, N): {cluster_shape_mn}")
|
| 2097 |
+
print(f"Tolerance: {tolerance}")
|
| 2098 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 2099 |
+
print(f"Iterations: {iterations}")
|
| 2100 |
+
print(f"Skip reference checking: {skip_ref_check}")
|
| 2101 |
+
print(f"Use cold L2: {'True' if use_cold_l2 else 'False'}")
|
| 2102 |
+
|
| 2103 |
+
# Unpack parameters
|
| 2104 |
+
m, n, k, l = mnkl
|
| 2105 |
+
|
| 2106 |
+
# Skip unsupported testcase
|
| 2107 |
+
if not Sm100BlockScaledPersistentDenseGemmKernel.can_implement(
|
| 2108 |
+
ab_dtype,
|
| 2109 |
+
sf_dtype,
|
| 2110 |
+
sf_vec_size,
|
| 2111 |
+
c_dtype,
|
| 2112 |
+
mma_tiler_mn,
|
| 2113 |
+
cluster_shape_mn,
|
| 2114 |
+
m,
|
| 2115 |
+
n,
|
| 2116 |
+
k,
|
| 2117 |
+
l,
|
| 2118 |
+
a_major,
|
| 2119 |
+
b_major,
|
| 2120 |
+
c_major,
|
| 2121 |
+
):
|
| 2122 |
+
raise TypeError(
|
| 2123 |
+
f"Unsupported testcase {ab_dtype}, {sf_dtype}, {sf_vec_size}, {c_dtype}, {mma_tiler_mn}, {cluster_shape_mn}, {m}, {n}, {k}, {l}, {a_major}, {b_major}, {c_major}"
|
| 2124 |
+
)
|
| 2125 |
+
|
| 2126 |
+
if not torch.cuda.is_available():
|
| 2127 |
+
raise RuntimeError("GPU is required to run this example!")
|
| 2128 |
+
|
| 2129 |
+
torch.manual_seed(1111)
|
| 2130 |
+
|
| 2131 |
+
# Create tensor A/B/C
|
| 2132 |
+
a_ref = cutlass_torch.matrix(l, m, k, a_major == "m", cutlass.Float32)
|
| 2133 |
+
b_ref = cutlass_torch.matrix(l, n, k, b_major == "n", cutlass.Float32)
|
| 2134 |
+
c_ref = cutlass_torch.matrix(l, m, n, c_major == "m", cutlass.Float32)
|
| 2135 |
+
|
| 2136 |
+
a_tensor, a_torch = cutlass_torch.cute_tensor_like(
|
| 2137 |
+
a_ref, ab_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2138 |
+
)
|
| 2139 |
+
b_tensor, b_torch = cutlass_torch.cute_tensor_like(
|
| 2140 |
+
b_ref, ab_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2141 |
+
)
|
| 2142 |
+
c_tensor, c_torch = cutlass_torch.cute_tensor_like(
|
| 2143 |
+
c_ref, c_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2144 |
+
)
|
| 2145 |
+
|
| 2146 |
+
# Mark tensor to be byte aligned
|
| 2147 |
+
a_tensor.mark_compact_shape_dynamic(
|
| 2148 |
+
mode=1 if a_major == "k" else 0,
|
| 2149 |
+
stride_order=(2, 0, 1) if a_major == "k" else (2, 1, 0),
|
| 2150 |
+
divisibility=2 if ab_dtype == cutlass.Float4E2M1FN else 1,
|
| 2151 |
+
)
|
| 2152 |
+
b_tensor.mark_compact_shape_dynamic(
|
| 2153 |
+
mode=1 if b_major == "k" else 0,
|
| 2154 |
+
stride_order=(2, 0, 1) if b_major == "k" else (2, 1, 0),
|
| 2155 |
+
divisibility=2 if ab_dtype == cutlass.Float4E2M1FN else 1,
|
| 2156 |
+
)
|
| 2157 |
+
c_tensor.mark_compact_shape_dynamic(
|
| 2158 |
+
mode=1 if c_major == "n" else 0,
|
| 2159 |
+
stride_order=(2, 0, 1) if c_major == "n" else (2, 1, 0),
|
| 2160 |
+
divisibility=2 if c_dtype == cutlass.Float4E2M1FN else 1,
|
| 2161 |
+
)
|
| 2162 |
+
|
| 2163 |
+
# Create scale factor tensor SFA/SFB
|
| 2164 |
+
def create_scale_factor_tensor(l, mn, k, sf_vec_size, dtype):
|
| 2165 |
+
def ceil_div(a, b):
|
| 2166 |
+
return (a + b - 1) // b
|
| 2167 |
+
|
| 2168 |
+
sf_k = ceil_div(k, sf_vec_size)
|
| 2169 |
+
ref_shape = (l, mn, sf_k)
|
| 2170 |
+
|
| 2171 |
+
atom_m = (32, 4)
|
| 2172 |
+
atom_k = 4
|
| 2173 |
+
mma_shape = (
|
| 2174 |
+
l,
|
| 2175 |
+
ceil_div(mn, atom_m[0] * atom_m[1]),
|
| 2176 |
+
ceil_div(sf_k, atom_k),
|
| 2177 |
+
atom_m[0],
|
| 2178 |
+
atom_m[1],
|
| 2179 |
+
atom_k,
|
| 2180 |
+
)
|
| 2181 |
+
|
| 2182 |
+
ref_permute_order = (1, 2, 0)
|
| 2183 |
+
mma_permute_order = (3, 4, 1, 5, 2, 0)
|
| 2184 |
+
|
| 2185 |
+
# Create f32 ref torch tensor (cpu)
|
| 2186 |
+
ref_f32_torch_tensor_cpu = cutlass_torch.create_and_permute_torch_tensor(
|
| 2187 |
+
ref_shape,
|
| 2188 |
+
torch.float32,
|
| 2189 |
+
permute_order=ref_permute_order,
|
| 2190 |
+
init_type=cutlass_torch.TensorInitType.RANDOM,
|
| 2191 |
+
init_config=cutlass_torch.RandomInitConfig(
|
| 2192 |
+
min_val=1,
|
| 2193 |
+
max_val=3,
|
| 2194 |
+
),
|
| 2195 |
+
)
|
| 2196 |
+
|
| 2197 |
+
# Create f32 cute torch tensor (cpu)
|
| 2198 |
+
cute_f32_torch_tensor_cpu = cutlass_torch.create_and_permute_torch_tensor(
|
| 2199 |
+
mma_shape,
|
| 2200 |
+
torch.float32,
|
| 2201 |
+
permute_order=mma_permute_order,
|
| 2202 |
+
init_type=cutlass_torch.TensorInitType.RANDOM,
|
| 2203 |
+
init_config=cutlass_torch.RandomInitConfig(
|
| 2204 |
+
min_val=0,
|
| 2205 |
+
max_val=1,
|
| 2206 |
+
),
|
| 2207 |
+
)
|
| 2208 |
+
|
| 2209 |
+
# convert ref f32 tensor to cute f32 tensor
|
| 2210 |
+
cvt_sf_MKL_to_M32x4xrm_K4xrk_L(
|
| 2211 |
+
from_dlpack(ref_f32_torch_tensor_cpu),
|
| 2212 |
+
from_dlpack(cute_f32_torch_tensor_cpu),
|
| 2213 |
+
)
|
| 2214 |
+
cute_f32_torch_tensor = cute_f32_torch_tensor_cpu.cuda()
|
| 2215 |
+
|
| 2216 |
+
# reshape makes memory contiguous
|
| 2217 |
+
ref_f32_torch_tensor_cpu = (
|
| 2218 |
+
ref_f32_torch_tensor_cpu.permute(2, 0, 1)
|
| 2219 |
+
.unsqueeze(-1)
|
| 2220 |
+
.expand(l, mn, sf_k, sf_vec_size)
|
| 2221 |
+
.reshape(l, mn, sf_k * sf_vec_size)
|
| 2222 |
+
.permute(*ref_permute_order)
|
| 2223 |
+
)
|
| 2224 |
+
# prune to mkl for reference check.
|
| 2225 |
+
ref_f32_torch_tensor_cpu = ref_f32_torch_tensor_cpu[:, :k, :]
|
| 2226 |
+
|
| 2227 |
+
# Create dtype cute torch tensor (cpu)
|
| 2228 |
+
cute_tensor, cute_torch_tensor = cutlass_torch.cute_tensor_like(
|
| 2229 |
+
cute_f32_torch_tensor_cpu,
|
| 2230 |
+
dtype,
|
| 2231 |
+
is_dynamic_layout=True,
|
| 2232 |
+
assumed_align=16,
|
| 2233 |
+
)
|
| 2234 |
+
|
| 2235 |
+
# Convert f32 cute tensor to dtype cute tensor
|
| 2236 |
+
cute_tensor = cutlass_torch.convert_cute_tensor(
|
| 2237 |
+
cute_f32_torch_tensor,
|
| 2238 |
+
cute_tensor,
|
| 2239 |
+
dtype,
|
| 2240 |
+
is_dynamic_layout=True,
|
| 2241 |
+
)
|
| 2242 |
+
return ref_f32_torch_tensor_cpu, cute_tensor, cute_torch_tensor
|
| 2243 |
+
|
| 2244 |
+
sfa_ref, sfa_tensor, sfa_torch = create_scale_factor_tensor(
|
| 2245 |
+
l, m, k, sf_vec_size, sf_dtype
|
| 2246 |
+
)
|
| 2247 |
+
sfb_ref, sfb_tensor, sfb_torch = create_scale_factor_tensor(
|
| 2248 |
+
l, n, k, sf_vec_size, sf_dtype
|
| 2249 |
+
)
|
| 2250 |
+
|
| 2251 |
+
# Configure gemm kernel
|
| 2252 |
+
gemm = Sm100BlockScaledPersistentDenseGemmKernel(
|
| 2253 |
+
sf_vec_size,
|
| 2254 |
+
mma_tiler_mn,
|
| 2255 |
+
cluster_shape_mn,
|
| 2256 |
+
)
|
| 2257 |
+
|
| 2258 |
+
# Compute max active clusters on current device
|
| 2259 |
+
hardware_info = cutlass.utils.HardwareInfo()
|
| 2260 |
+
max_active_clusters = hardware_info.get_max_active_clusters(
|
| 2261 |
+
cluster_shape_mn[0] * cluster_shape_mn[1]
|
| 2262 |
+
)
|
| 2263 |
+
|
| 2264 |
+
# Initialize Stream
|
| 2265 |
+
current_stream = cutlass_torch.default_stream()
|
| 2266 |
+
|
| 2267 |
+
# Compile gemm kernel
|
| 2268 |
+
compiled_gemm = cute.compile(
|
| 2269 |
+
gemm,
|
| 2270 |
+
a_tensor,
|
| 2271 |
+
b_tensor,
|
| 2272 |
+
sfa_tensor,
|
| 2273 |
+
sfb_tensor,
|
| 2274 |
+
c_tensor,
|
| 2275 |
+
max_active_clusters,
|
| 2276 |
+
current_stream,
|
| 2277 |
+
)
|
| 2278 |
+
|
| 2279 |
+
# Compute reference result
|
| 2280 |
+
if not skip_ref_check:
|
| 2281 |
+
# Execute kernel once for reference checking
|
| 2282 |
+
compiled_gemm(
|
| 2283 |
+
a_tensor, b_tensor, sfa_tensor, sfb_tensor, c_tensor, current_stream
|
| 2284 |
+
)
|
| 2285 |
+
print("Verifying results...")
|
| 2286 |
+
res_a = torch.einsum("mkl,mkl->mkl", a_ref, sfa_ref)
|
| 2287 |
+
res_b = torch.einsum("nkl,nkl->nkl", b_ref, sfb_ref)
|
| 2288 |
+
ref = torch.einsum("mkl,nkl->mnl", res_a, res_b)
|
| 2289 |
+
|
| 2290 |
+
# Convert c back to f32 for comparison.
|
| 2291 |
+
c_ref_device = c_ref.cuda()
|
| 2292 |
+
cute.testing.convert(
|
| 2293 |
+
c_tensor,
|
| 2294 |
+
from_dlpack(c_ref_device, assumed_align=16).mark_layout_dynamic(
|
| 2295 |
+
leading_dim=(1 if c_major == "n" else 0)
|
| 2296 |
+
),
|
| 2297 |
+
)
|
| 2298 |
+
c_ref = c_ref_device.cpu()
|
| 2299 |
+
|
| 2300 |
+
if c_dtype in (cutlass.Float32, cutlass.Float16, cutlass.BFloat16):
|
| 2301 |
+
torch.testing.assert_close(c_ref, ref, atol=tolerance, rtol=1e-02)
|
| 2302 |
+
elif c_dtype in (cutlass.Float8E5M2, cutlass.Float8E4M3FN):
|
| 2303 |
+
# Convert ref : f32 -> f8 -> f32
|
| 2304 |
+
ref_f8_ = torch.empty(*(l, m, n), dtype=torch.uint8, device="cuda").permute(
|
| 2305 |
+
1, 2, 0
|
| 2306 |
+
)
|
| 2307 |
+
ref_f8 = from_dlpack(ref_f8_, assumed_align=16).mark_layout_dynamic(
|
| 2308 |
+
leading_dim=1
|
| 2309 |
+
)
|
| 2310 |
+
ref_f8.element_type = c_dtype
|
| 2311 |
+
ref_device = ref.permute(2, 0, 1).contiguous().permute(1, 2, 0).cuda()
|
| 2312 |
+
ref_tensor = from_dlpack(ref_device, assumed_align=16).mark_layout_dynamic(
|
| 2313 |
+
leading_dim=1
|
| 2314 |
+
)
|
| 2315 |
+
cute.testing.convert(ref_tensor, ref_f8)
|
| 2316 |
+
cute.testing.convert(ref_f8, ref_tensor)
|
| 2317 |
+
ref = ref_device.cpu()
|
| 2318 |
+
torch.testing.assert_close(c_ref, ref, atol=tolerance, rtol=1e-02)
|
| 2319 |
+
def generate_tensors():
|
| 2320 |
+
a_tensor, _ = cutlass_torch.cute_tensor_like(
|
| 2321 |
+
a_ref, ab_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2322 |
+
)
|
| 2323 |
+
b_tensor, _ = cutlass_torch.cute_tensor_like(
|
| 2324 |
+
b_ref, ab_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2325 |
+
)
|
| 2326 |
+
c_tensor, _ = cutlass_torch.cute_tensor_like(
|
| 2327 |
+
c_ref, c_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2328 |
+
)
|
| 2329 |
+
|
| 2330 |
+
# Mark tensor to be byte aligned
|
| 2331 |
+
a_tensor.mark_compact_shape_dynamic(
|
| 2332 |
+
mode=1 if a_major == "k" else 0,
|
| 2333 |
+
stride_order=(2, 0, 1) if a_major == "k" else (2, 1, 0),
|
| 2334 |
+
divisibility=2 if ab_dtype == cutlass.Float4E2M1FN else 1,
|
| 2335 |
+
)
|
| 2336 |
+
b_tensor.mark_compact_shape_dynamic(
|
| 2337 |
+
mode=1 if b_major == "k" else 0,
|
| 2338 |
+
stride_order=(2, 0, 1) if b_major == "k" else (2, 1, 0),
|
| 2339 |
+
divisibility=2 if ab_dtype == cutlass.Float4E2M1FN else 1,
|
| 2340 |
+
)
|
| 2341 |
+
c_tensor.mark_compact_shape_dynamic(
|
| 2342 |
+
mode=1 if c_major == "n" else 0,
|
| 2343 |
+
stride_order=(2, 0, 1) if c_major == "n" else (2, 1, 0),
|
| 2344 |
+
divisibility=2 if c_dtype == cutlass.Float4E2M1FN else 1,
|
| 2345 |
+
)
|
| 2346 |
+
|
| 2347 |
+
_, sfa_tensor, _ = create_scale_factor_tensor(l, m, k, sf_vec_size, sf_dtype)
|
| 2348 |
+
_, sfb_tensor, _ = create_scale_factor_tensor(l, n, k, sf_vec_size, sf_dtype)
|
| 2349 |
+
return cute.testing.JitArguments(
|
| 2350 |
+
a_tensor, b_tensor, sfa_tensor, sfb_tensor, c_tensor, current_stream
|
| 2351 |
+
)
|
| 2352 |
+
|
| 2353 |
+
workspace_count = 1
|
| 2354 |
+
if use_cold_l2:
|
| 2355 |
+
one_workspace_bytes = (
|
| 2356 |
+
a_torch.numel() * a_torch.element_size()
|
| 2357 |
+
+ b_torch.numel() * b_torch.element_size()
|
| 2358 |
+
+ sfa_torch.numel() * sfa_torch.element_size()
|
| 2359 |
+
+ sfb_torch.numel() * sfb_torch.element_size()
|
| 2360 |
+
+ c_torch.numel() * c_torch.element_size()
|
| 2361 |
+
)
|
| 2362 |
+
workspace_count = cute.testing.get_workspace_count(
|
| 2363 |
+
one_workspace_bytes, warmup_iterations, iterations
|
| 2364 |
+
)
|
| 2365 |
+
|
| 2366 |
+
exec_time = cute.testing.benchmark(
|
| 2367 |
+
compiled_gemm,
|
| 2368 |
+
workspace_generator=generate_tensors,
|
| 2369 |
+
workspace_count=workspace_count,
|
| 2370 |
+
stream=current_stream,
|
| 2371 |
+
warmup_iterations=warmup_iterations,
|
| 2372 |
+
iterations=iterations,
|
| 2373 |
+
)
|
| 2374 |
+
|
| 2375 |
+
return exec_time # Return execution time in microseconds
|
| 2376 |
+
|
| 2377 |
+
if __name__ == "__main__":
|
| 2378 |
+
|
| 2379 |
+
def parse_comma_separated_ints(s: str) -> Tuple[int, ...]:
|
| 2380 |
+
try:
|
| 2381 |
+
return tuple(int(x.strip()) for x in s.split(","))
|
| 2382 |
+
except ValueError:
|
| 2383 |
+
raise argparse.ArgumentTypeError(
|
| 2384 |
+
"Invalid format. Expected comma-separated integers."
|
| 2385 |
+
)
|
| 2386 |
+
|
| 2387 |
+
parser = argparse.ArgumentParser(
|
| 2388 |
+
description="Example of Sm100 Dense Persistent BlockScaled GEMM."
|
| 2389 |
+
)
|
| 2390 |
+
|
| 2391 |
+
parser.add_argument(
|
| 2392 |
+
"--mnkl",
|
| 2393 |
+
type=parse_comma_separated_ints,
|
| 2394 |
+
default=(512, 256, 256, 1),
|
| 2395 |
+
help="mnkl dimensions (comma-separated)",
|
| 2396 |
+
)
|
| 2397 |
+
parser.add_argument(
|
| 2398 |
+
"--mma_tiler_mn",
|
| 2399 |
+
type=parse_comma_separated_ints,
|
| 2400 |
+
default=(128, 128),
|
| 2401 |
+
help="Mma tile shape (comma-separated)",
|
| 2402 |
+
)
|
| 2403 |
+
parser.add_argument(
|
| 2404 |
+
"--cluster_shape_mn",
|
| 2405 |
+
type=parse_comma_separated_ints,
|
| 2406 |
+
default=(1, 1),
|
| 2407 |
+
help="Cluster shape (comma-separated)",
|
| 2408 |
+
)
|
| 2409 |
+
parser.add_argument("--ab_dtype", type=cutlass.dtype, default=cutlass.Float4E2M1FN)
|
| 2410 |
+
parser.add_argument("--sf_dtype", type=cutlass.dtype, default=cutlass.Float8E8M0FNU)
|
| 2411 |
+
parser.add_argument("--sf_vec_size", type=int, default=16)
|
| 2412 |
+
parser.add_argument("--c_dtype", type=cutlass.dtype, default=cutlass.Float16)
|
| 2413 |
+
parser.add_argument("--a_major", choices=["k", "m"], type=str, default="k")
|
| 2414 |
+
parser.add_argument("--b_major", choices=["k", "n"], type=str, default="k")
|
| 2415 |
+
parser.add_argument("--c_major", choices=["n", "m"], type=str, default="n")
|
| 2416 |
+
parser.add_argument(
|
| 2417 |
+
"--tolerance", type=float, default=1e-01, help="Tolerance for validation"
|
| 2418 |
+
)
|
| 2419 |
+
parser.add_argument(
|
| 2420 |
+
"--warmup_iterations", type=int, default=0, help="Warmup iterations"
|
| 2421 |
+
)
|
| 2422 |
+
parser.add_argument(
|
| 2423 |
+
"--iterations",
|
| 2424 |
+
type=int,
|
| 2425 |
+
default=1,
|
| 2426 |
+
help="Number of iterations to run the kernel",
|
| 2427 |
+
)
|
| 2428 |
+
parser.add_argument(
|
| 2429 |
+
"--skip_ref_check", action="store_true", help="Skip reference checking"
|
| 2430 |
+
)
|
| 2431 |
+
parser.add_argument(
|
| 2432 |
+
"--use_cold_l2",
|
| 2433 |
+
action="store_true",
|
| 2434 |
+
default=False,
|
| 2435 |
+
help="Use circular buffer tensor sets to ensure L2 cold cache",
|
| 2436 |
+
)
|
| 2437 |
+
|
| 2438 |
+
args = parser.parse_args()
|
| 2439 |
+
|
| 2440 |
+
if len(args.mnkl) != 4:
|
| 2441 |
+
parser.error("--mnkl must contain exactly 4 values")
|
| 2442 |
+
|
| 2443 |
+
if len(args.mma_tiler_mn) != 2:
|
| 2444 |
+
parser.error("--mma_tiler_mn must contain exactly 2 values")
|
| 2445 |
+
|
| 2446 |
+
if len(args.cluster_shape_mn) != 2:
|
| 2447 |
+
parser.error("--cluster_shape_mn must contain exactly 2 values")
|
| 2448 |
+
|
| 2449 |
+
run(
|
| 2450 |
+
args.mnkl,
|
| 2451 |
+
args.ab_dtype,
|
| 2452 |
+
args.sf_dtype,
|
| 2453 |
+
args.sf_vec_size,
|
| 2454 |
+
args.c_dtype,
|
| 2455 |
+
args.a_major,
|
| 2456 |
+
args.b_major,
|
| 2457 |
+
args.c_major,
|
| 2458 |
+
args.mma_tiler_mn,
|
| 2459 |
+
args.cluster_shape_mn,
|
| 2460 |
+
args.tolerance,
|
| 2461 |
+
args.warmup_iterations,
|
| 2462 |
+
args.iterations,
|
| 2463 |
+
args.skip_ref_check,
|
| 2464 |
+
args.use_cold_l2,
|
| 2465 |
+
)
|
| 2466 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_gemm.py
ADDED
|
@@ -0,0 +1,1890 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
from typing import Optional, Type, Tuple, Union
|
| 31 |
+
import cuda.bindings.driver as cuda
|
| 32 |
+
|
| 33 |
+
import torch
|
| 34 |
+
|
| 35 |
+
import cutlass
|
| 36 |
+
import cutlass.cute as cute
|
| 37 |
+
import cutlass.utils as utils
|
| 38 |
+
import cutlass.pipeline as pipeline
|
| 39 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 40 |
+
import cutlass.torch as cutlass_torch
|
| 41 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 42 |
+
from cutlass.cute.runtime import from_dlpack
|
| 43 |
+
|
| 44 |
+
"""
|
| 45 |
+
A high-performance batched dense GEMM (C = A * B) example for the NVIDIA Blackwell SM100 architecture
|
| 46 |
+
using CUTE DSL.
|
| 47 |
+
- Matrix A is MxKxL, L is batch dimension, A can be row-major("K") or column-major("M")
|
| 48 |
+
- Matrix B is NxKxL, L is batch dimension, B can be row-major("N") or column-major("K")
|
| 49 |
+
- Matrix C is MxNxL, L is batch dimension, C can be row-major("N") or column-major("M")
|
| 50 |
+
|
| 51 |
+
This GEMM kernel supports the following features:
|
| 52 |
+
- Utilizes Tensor Memory Access (TMA) for efficient memory operations
|
| 53 |
+
- Utilizes Blackwell's tcgen05.mma for matrix multiply-accumulate (MMA) operations (including 2cta mma instructions)
|
| 54 |
+
- Implements TMA multicast with cluster to reduce L2 memory traffic
|
| 55 |
+
- Supports multi-stage pipeline to overlap computation and memory access
|
| 56 |
+
|
| 57 |
+
This GEMM works as follows:
|
| 58 |
+
1. Load A and B matrices from global memory (GMEM) to shared memory (SMEM) using TMA operations.
|
| 59 |
+
2. Perform matrix multiply-accumulate (MMA) operations using tcgen05.mma instruction.
|
| 60 |
+
3. Load completed accumulator from tensor memory (TMEM) to registers (RMEM) using tcgen05.ld.
|
| 61 |
+
4. Type convert C matrix to output type.
|
| 62 |
+
5. Optionally store C matrix from registers (RMEM) to shared memory (SMEM) to global memory (GMEM) with TMA operations,
|
| 63 |
+
or directly store C matrix from registers (RMEM) to global memory (GMEM) without TMA operations.
|
| 64 |
+
6. Optionally accept an elementwise lambda function epilogue_op to apply to the output tensor:
|
| 65 |
+
e.g., relu can set epilogue_op = lambda x: cute.where(x > 0, x, cute.full_like(x, 0))
|
| 66 |
+
|
| 67 |
+
SM100 tcgen05.mma instructions operate as follows:
|
| 68 |
+
- Read matrix A from SMEM
|
| 69 |
+
- Read matrix B from SMEM
|
| 70 |
+
- Write accumulator to TMEM
|
| 71 |
+
The accumulator in TMEM must then be loaded to registers before writing back to GMEM.
|
| 72 |
+
|
| 73 |
+
To run this example:
|
| 74 |
+
|
| 75 |
+
.. code-block:: bash
|
| 76 |
+
|
| 77 |
+
python examples/blackwell/dense_gemm.py \
|
| 78 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 79 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 80 |
+
--mnkl 8192,8192,8192,1 \
|
| 81 |
+
--use_tma_store --use_2cta_instrs
|
| 82 |
+
|
| 83 |
+
The above example command compute batched gemm with M=8192, N=8192, K=8192,
|
| 84 |
+
batch_count=1. The Blackwell tcgen05 MMA tile shape used 2 cta with 256x128
|
| 85 |
+
MMA tile and the cluster shape is (2,1). The input, mma accumulator and output
|
| 86 |
+
data type are set as fp16, fp32 and fp16, respectively.
|
| 87 |
+
|
| 88 |
+
To collect performance with NCU profiler:
|
| 89 |
+
|
| 90 |
+
.. code-block:: bash
|
| 91 |
+
|
| 92 |
+
ncu python examples/blackwell/dense_gemm.py \
|
| 93 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 94 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 95 |
+
--mnkl 8192,8192,8192,1 \
|
| 96 |
+
--use_tma_store --use_2cta_instrs
|
| 97 |
+
|
| 98 |
+
Constraints:
|
| 99 |
+
* Supported input data types: fp16, bf16, tf32, int8, uint8, fp8 (e4m3fn, e5m2),
|
| 100 |
+
see detailed valid dtype combinations in below DenseGemmKernel class documentation
|
| 101 |
+
* A/B tensor must have the same data type
|
| 102 |
+
* Mma tiler M must be 64/128 (use_2cta_instrs=False) or 128/256 (use_2cta_instrs=True)
|
| 103 |
+
* Mma tiler N must be 32-256, step 32
|
| 104 |
+
* Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 105 |
+
* Cluster shape M must be multiple of 2 if use_2cta_instrs=True
|
| 106 |
+
* The contiguous dimension of A/B/C tensors must be at least 16 bytes aligned,
|
| 107 |
+
i.e, number of elements is a multiple of 4, 8, and 16 for TFloat32,
|
| 108 |
+
Float16/BFloat16, and Int8/Uint8/Float8, respectively.
|
| 109 |
+
* OOB tiles are not allowed when TMA store is disabled
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class DenseGemmKernel:
|
| 114 |
+
"""
|
| 115 |
+
This class implements batched matrix multiplication (C = A x B) with support for various data types
|
| 116 |
+
and architectural features specific to Blackwell GPUs.
|
| 117 |
+
|
| 118 |
+
:param acc_dtype: Data type for accumulation during computation
|
| 119 |
+
:type acc_dtype: type[cutlass.Numeric]
|
| 120 |
+
:param use_2cta_instrs: Whether to use CTA group 2 for advanced thread cooperation
|
| 121 |
+
:type use_2cta_instrs: bool
|
| 122 |
+
:param mma_tiler_mn: Shape of the Matrix Multiply-Accumulate (MMA) tiler (M,N)
|
| 123 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 124 |
+
:param cluster_shape_mn: Cluster dimensions (M,N) for parallel processing
|
| 125 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 126 |
+
:param use_tma_store: Whether to use Tensor Memory Access (TMA) for storing results
|
| 127 |
+
:type use_tma_store: bool
|
| 128 |
+
|
| 129 |
+
:note: In current version, A and B tensor must have the same data type
|
| 130 |
+
- i.e., Float8E4M3FN for A and Float8E5M2 for B is not supported
|
| 131 |
+
|
| 132 |
+
:note: Supported A/B data types:
|
| 133 |
+
- TFloat32
|
| 134 |
+
- Float16/BFloat16
|
| 135 |
+
- Int8/Uint8
|
| 136 |
+
- Float8E4M3FN/Float8E5M2
|
| 137 |
+
|
| 138 |
+
:note: Supported accumulator data types:
|
| 139 |
+
- Float32 (for all floating point A/B data types)
|
| 140 |
+
- Float16 (only for fp16 and fp8 A/B data types)
|
| 141 |
+
- Int32 (only for uint8/int8 A/B data types)
|
| 142 |
+
|
| 143 |
+
:note: Supported C data types:
|
| 144 |
+
- Float32 (for float32 and int32 accumulator data types)
|
| 145 |
+
- Int32 (for float32 and int32 accumulator data types)
|
| 146 |
+
- Float16/BFloat16 (for fp16 and fp8 accumulator data types)
|
| 147 |
+
- Int8/Uint8 (for uint8/int8 accumulator data types)
|
| 148 |
+
- Float8E4M3FN/Float8E5M2 (for float32 accumulator data types)
|
| 149 |
+
|
| 150 |
+
:note: Constraints:
|
| 151 |
+
- MMA tiler M must be 64/128 (use_2cta_instrs=False) or 128/256 (use_2cta_instrs=True)
|
| 152 |
+
- MMA tiler N must be 32-256, step 32
|
| 153 |
+
- Cluster shape M must be multiple of 2 if use_2cta_instrs=True
|
| 154 |
+
- Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 155 |
+
|
| 156 |
+
Example:
|
| 157 |
+
>>> gemm = DenseGemmKernel(
|
| 158 |
+
... acc_dtype=cutlass.Float32,
|
| 159 |
+
... use_2cta_instrs=True,
|
| 160 |
+
... mma_tiler_mn=(128, 128),
|
| 161 |
+
... cluster_shape_mn=(2, 2)
|
| 162 |
+
... )
|
| 163 |
+
>>> gemm(a_tensor, b_tensor, c_tensor, stream)
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(
|
| 167 |
+
self,
|
| 168 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 169 |
+
use_2cta_instrs: bool,
|
| 170 |
+
mma_tiler_mn: Tuple[int, int],
|
| 171 |
+
cluster_shape_mn: Tuple[int, int],
|
| 172 |
+
use_tma_store: bool,
|
| 173 |
+
):
|
| 174 |
+
"""Initializes the configuration for a Blackwell dense GEMM kernel.
|
| 175 |
+
|
| 176 |
+
This configuration includes several key aspects:
|
| 177 |
+
|
| 178 |
+
1. MMA Instruction Settings (tcgen05):
|
| 179 |
+
- acc_dtype: Data types for MMA accumulator.
|
| 180 |
+
- mma_tiler_mn: The (M, N) shape of the MMA instruction tiler.
|
| 181 |
+
- use_2cta_instrs: Boolean indicating if the tcgen05 MMA variant
|
| 182 |
+
with cta_group=2 should be used.
|
| 183 |
+
|
| 184 |
+
2. Cluster Shape:
|
| 185 |
+
- cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster.
|
| 186 |
+
|
| 187 |
+
3. Output C tensor store mode:
|
| 188 |
+
- use_tma_store: Boolean indicating whether to use Tensor Memory Access (TMA) for storing results.
|
| 189 |
+
|
| 190 |
+
:param acc_dtype: Data type of the accumulator.
|
| 191 |
+
:type acc_dtype: type[cutlass.Numeric]
|
| 192 |
+
:param mma_tiler_mn: Tuple (M, N) shape of the MMA instruction.
|
| 193 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 194 |
+
:param use_2cta_instrs: Boolean, True to use cta_group=2 MMA variant.
|
| 195 |
+
:type use_2cta_instrs: bool
|
| 196 |
+
:param cluster_shape_mn: Tuple (ClusterM, ClusterN) shape of the cluster.
|
| 197 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 198 |
+
:param use_tma_store: Use Tensor Memory Access (TMA) or normal store for output C tensor.
|
| 199 |
+
:type use_tma_store: bool
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
self.acc_dtype: Type[cutlass.Numeric] = acc_dtype
|
| 203 |
+
self.use_2cta_instrs = use_2cta_instrs
|
| 204 |
+
self.cluster_shape_mn = cluster_shape_mn
|
| 205 |
+
# K dimension is deferred in _setup_attributes
|
| 206 |
+
self.mma_tiler = (*mma_tiler_mn, 1)
|
| 207 |
+
self.use_tma_store = use_tma_store
|
| 208 |
+
|
| 209 |
+
self.cta_group = (
|
| 210 |
+
tcgen05.CtaGroup.TWO if self.use_2cta_instrs else tcgen05.CtaGroup.ONE
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
self.occupancy = 1
|
| 214 |
+
self.threads_per_cta = 128
|
| 215 |
+
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
| 216 |
+
|
| 217 |
+
def _setup_attributes(self):
|
| 218 |
+
"""Set up configurations that are dependent on GEMM inputs
|
| 219 |
+
|
| 220 |
+
This method configures various attributes based on the input tensor properties
|
| 221 |
+
(data types, leading dimensions) and kernel settings:
|
| 222 |
+
- Configuring tiled MMA
|
| 223 |
+
- Computing MMA/cluster/tile shapes
|
| 224 |
+
- Computing cluster layout
|
| 225 |
+
- Computing multicast CTAs for A/B
|
| 226 |
+
- Computing epilogue subtile
|
| 227 |
+
- Setting up A/B/C stage counts in shared memory
|
| 228 |
+
- Computing A/B/C shared memory layout
|
| 229 |
+
- Computing tensor memory allocation columns
|
| 230 |
+
"""
|
| 231 |
+
# Configure tiled mma
|
| 232 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 233 |
+
self.a_dtype,
|
| 234 |
+
self.a_major_mode,
|
| 235 |
+
self.b_major_mode,
|
| 236 |
+
self.acc_dtype,
|
| 237 |
+
self.cta_group,
|
| 238 |
+
self.mma_tiler[:2],
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
# Compute mma/cluster/tile shapes
|
| 242 |
+
mma_inst_shape_k = cute.size(tiled_mma.shape_mnk, mode=[2])
|
| 243 |
+
mma_inst_tile_k = 4
|
| 244 |
+
self.mma_tiler = (
|
| 245 |
+
self.mma_tiler[0],
|
| 246 |
+
self.mma_tiler[1],
|
| 247 |
+
mma_inst_shape_k * mma_inst_tile_k,
|
| 248 |
+
)
|
| 249 |
+
self.cta_tile_shape_mnk = (
|
| 250 |
+
self.mma_tiler[0] // cute.size(tiled_mma.thr_id.shape),
|
| 251 |
+
self.mma_tiler[1],
|
| 252 |
+
self.mma_tiler[2],
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
# Compute cluster layout
|
| 256 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 257 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 258 |
+
(tiled_mma.thr_id.shape,),
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
# Compute number of multicast CTAs for A/B
|
| 262 |
+
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
| 263 |
+
self.num_mcast_ctas_b = cute.size(self.cluster_layout_vmnk.shape[1])
|
| 264 |
+
self.is_a_mcast = self.num_mcast_ctas_a > 1
|
| 265 |
+
self.is_b_mcast = self.num_mcast_ctas_b > 1
|
| 266 |
+
|
| 267 |
+
# Compute epilogue subtile
|
| 268 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 269 |
+
self.epi_tile = sm100_utils.compute_epilogue_tile_shape(
|
| 270 |
+
self.cta_tile_shape_mnk,
|
| 271 |
+
self.use_2cta_instrs,
|
| 272 |
+
self.c_layout,
|
| 273 |
+
self.c_dtype,
|
| 274 |
+
)
|
| 275 |
+
else:
|
| 276 |
+
self.epi_tile = self.cta_tile_shape_mnk[:2]
|
| 277 |
+
|
| 278 |
+
# Setup A/B/C stage count in shared memory
|
| 279 |
+
self.num_acc_stage, self.num_ab_stage, self.num_c_stage = self._compute_stages(
|
| 280 |
+
tiled_mma,
|
| 281 |
+
self.mma_tiler,
|
| 282 |
+
self.a_dtype,
|
| 283 |
+
self.b_dtype,
|
| 284 |
+
self.epi_tile,
|
| 285 |
+
self.c_dtype,
|
| 286 |
+
self.c_layout,
|
| 287 |
+
self.smem_capacity,
|
| 288 |
+
self.occupancy,
|
| 289 |
+
self.use_tma_store,
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
# Compute A/B/C shared memory layout
|
| 293 |
+
self.a_smem_layout_staged = sm100_utils.make_smem_layout_a(
|
| 294 |
+
tiled_mma,
|
| 295 |
+
self.mma_tiler,
|
| 296 |
+
self.a_dtype,
|
| 297 |
+
self.num_ab_stage,
|
| 298 |
+
)
|
| 299 |
+
self.b_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
| 300 |
+
tiled_mma,
|
| 301 |
+
self.mma_tiler,
|
| 302 |
+
self.b_dtype,
|
| 303 |
+
self.num_ab_stage,
|
| 304 |
+
)
|
| 305 |
+
self.c_smem_layout_staged = (
|
| 306 |
+
sm100_utils.make_smem_layout_epi(
|
| 307 |
+
self.c_dtype,
|
| 308 |
+
self.c_layout,
|
| 309 |
+
self.epi_tile,
|
| 310 |
+
self.num_c_stage,
|
| 311 |
+
)
|
| 312 |
+
if self.use_tma_store
|
| 313 |
+
else None
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
# Compute the number of tensor memory allocation columns
|
| 317 |
+
self.num_tmem_alloc_cols = self._compute_num_tmem_alloc_cols(
|
| 318 |
+
tiled_mma, self.mma_tiler
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
@cute.jit
|
| 322 |
+
def __call__(
|
| 323 |
+
self,
|
| 324 |
+
a: cute.Tensor,
|
| 325 |
+
b: cute.Tensor,
|
| 326 |
+
c: cute.Tensor,
|
| 327 |
+
stream: cuda.CUstream,
|
| 328 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 329 |
+
):
|
| 330 |
+
"""Execute the GEMM operation in steps:
|
| 331 |
+
- Setup static attributes
|
| 332 |
+
- Setup TMA load/store atoms and tensors
|
| 333 |
+
- Compute grid size
|
| 334 |
+
- Define shared storage for kernel
|
| 335 |
+
- Launch the kernel synchronously
|
| 336 |
+
|
| 337 |
+
:param a: Input tensor A
|
| 338 |
+
:type a: cute.Tensor
|
| 339 |
+
:param b: Input tensor B
|
| 340 |
+
:type b: cute.Tensor
|
| 341 |
+
:param c: Output tensor C
|
| 342 |
+
:type c: cute.Tensor
|
| 343 |
+
:param stream: CUDA stream for asynchronous execution
|
| 344 |
+
:type stream: cuda.CUstream
|
| 345 |
+
:param epilogue_op: Optional elementwise lambda function to apply to the output tensor
|
| 346 |
+
:type epilogue_op: cutlass.Constexpr
|
| 347 |
+
:raises TypeError: If input data types are incompatible with the MMA instruction.
|
| 348 |
+
:raises AssertionError: If OOB (Out-Of-Bounds) tiles are present when TMA store is disabled.
|
| 349 |
+
"""
|
| 350 |
+
# Setup static attributes before smem/grid/tma computation
|
| 351 |
+
self.a_dtype: Type[cutlass.Numeric] = a.element_type
|
| 352 |
+
self.b_dtype: Type[cutlass.Numeric] = b.element_type
|
| 353 |
+
self.c_dtype: Type[cutlass.Numeric] = c.element_type
|
| 354 |
+
self.a_major_mode = utils.LayoutEnum.from_tensor(a).mma_major_mode()
|
| 355 |
+
self.b_major_mode = utils.LayoutEnum.from_tensor(b).mma_major_mode()
|
| 356 |
+
self.c_layout = utils.LayoutEnum.from_tensor(c)
|
| 357 |
+
|
| 358 |
+
# Check if input data types are compatible with MMA instruction
|
| 359 |
+
if cutlass.const_expr(self.a_dtype != self.b_dtype):
|
| 360 |
+
raise TypeError(f"Type must match: {self.a_dtype} != {self.b_dtype}")
|
| 361 |
+
|
| 362 |
+
# Setup attributes that dependent on gemm inputs
|
| 363 |
+
self._setup_attributes()
|
| 364 |
+
|
| 365 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 366 |
+
self.a_dtype,
|
| 367 |
+
self.a_major_mode,
|
| 368 |
+
self.b_major_mode,
|
| 369 |
+
self.acc_dtype,
|
| 370 |
+
self.cta_group,
|
| 371 |
+
self.mma_tiler[:2],
|
| 372 |
+
)
|
| 373 |
+
atom_thr_size = cute.size(tiled_mma.thr_id.shape)
|
| 374 |
+
|
| 375 |
+
# Setup TMA load for A
|
| 376 |
+
a_op = sm100_utils.cluster_shape_to_tma_atom_A(
|
| 377 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 378 |
+
)
|
| 379 |
+
a_smem_layout = cute.slice_(self.a_smem_layout_staged, (None, None, None, 0))
|
| 380 |
+
tma_atom_a, tma_tensor_a = cute.nvgpu.make_tiled_tma_atom_A(
|
| 381 |
+
a_op,
|
| 382 |
+
a,
|
| 383 |
+
a_smem_layout,
|
| 384 |
+
self.mma_tiler,
|
| 385 |
+
tiled_mma,
|
| 386 |
+
self.cluster_layout_vmnk.shape,
|
| 387 |
+
internal_type=(
|
| 388 |
+
cutlass.TFloat32 if a.element_type is cutlass.Float32 else None
|
| 389 |
+
),
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
# Setup TMA load for B
|
| 393 |
+
b_op = sm100_utils.cluster_shape_to_tma_atom_B(
|
| 394 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 395 |
+
)
|
| 396 |
+
b_smem_layout = cute.slice_(self.b_smem_layout_staged, (None, None, None, 0))
|
| 397 |
+
tma_atom_b, tma_tensor_b = cute.nvgpu.make_tiled_tma_atom_B(
|
| 398 |
+
b_op,
|
| 399 |
+
b,
|
| 400 |
+
b_smem_layout,
|
| 401 |
+
self.mma_tiler,
|
| 402 |
+
tiled_mma,
|
| 403 |
+
self.cluster_layout_vmnk.shape,
|
| 404 |
+
internal_type=(
|
| 405 |
+
cutlass.TFloat32 if b.element_type is cutlass.Float32 else None
|
| 406 |
+
),
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
a_copy_size = cute.size_in_bytes(self.a_dtype, a_smem_layout)
|
| 410 |
+
b_copy_size = cute.size_in_bytes(self.b_dtype, b_smem_layout)
|
| 411 |
+
self.num_tma_load_bytes = (a_copy_size + b_copy_size) * atom_thr_size
|
| 412 |
+
|
| 413 |
+
# Setup store for C
|
| 414 |
+
tma_atom_c = None
|
| 415 |
+
tma_tensor_c = None
|
| 416 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 417 |
+
c_cta_v_layout = cute.composition(
|
| 418 |
+
cute.make_identity_layout(c.shape), self.epi_tile
|
| 419 |
+
)
|
| 420 |
+
epi_smem_layout = cute.slice_(self.c_smem_layout_staged, (None, None, 0))
|
| 421 |
+
tma_atom_c, tma_tensor_c = cpasync.make_tiled_tma_atom(
|
| 422 |
+
cpasync.CopyBulkTensorTileS2GOp(),
|
| 423 |
+
c,
|
| 424 |
+
epi_smem_layout,
|
| 425 |
+
c_cta_v_layout,
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
# Compute grid size
|
| 429 |
+
grid = self._compute_grid(c, self.cta_tile_shape_mnk, self.cluster_shape_mn)
|
| 430 |
+
|
| 431 |
+
self.buffer_align_bytes = 1024
|
| 432 |
+
|
| 433 |
+
c_smem_size = (
|
| 434 |
+
cute.cosize(self.c_smem_layout_staged.outer) if self.use_tma_store else 0
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
# Define shared storage for kernel
|
| 438 |
+
@cute.struct
|
| 439 |
+
class SharedStorage:
|
| 440 |
+
ab_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 441 |
+
ab_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 442 |
+
acc_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 443 |
+
tmem_dealloc_mbar_ptr: cutlass.Int64
|
| 444 |
+
tmem_holding_buf: cutlass.Int32
|
| 445 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 446 |
+
sC: cute.struct.Align[
|
| 447 |
+
cute.struct.MemRange[
|
| 448 |
+
self.c_dtype,
|
| 449 |
+
c_smem_size,
|
| 450 |
+
],
|
| 451 |
+
self.buffer_align_bytes,
|
| 452 |
+
]
|
| 453 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 454 |
+
sA: cute.struct.Align[
|
| 455 |
+
cute.struct.MemRange[
|
| 456 |
+
self.a_dtype, cute.cosize(self.a_smem_layout_staged.outer)
|
| 457 |
+
],
|
| 458 |
+
self.buffer_align_bytes,
|
| 459 |
+
]
|
| 460 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 461 |
+
sB: cute.struct.Align[
|
| 462 |
+
cute.struct.MemRange[
|
| 463 |
+
self.b_dtype, cute.cosize(self.b_smem_layout_staged.outer)
|
| 464 |
+
],
|
| 465 |
+
self.buffer_align_bytes,
|
| 466 |
+
]
|
| 467 |
+
|
| 468 |
+
self.shared_storage = SharedStorage
|
| 469 |
+
|
| 470 |
+
# Launch the kernel synchronously
|
| 471 |
+
self.kernel(
|
| 472 |
+
tiled_mma,
|
| 473 |
+
tma_atom_a,
|
| 474 |
+
tma_tensor_a,
|
| 475 |
+
tma_atom_b,
|
| 476 |
+
tma_tensor_b,
|
| 477 |
+
tma_atom_c,
|
| 478 |
+
tma_tensor_c if self.use_tma_store else c,
|
| 479 |
+
self.cluster_layout_vmnk,
|
| 480 |
+
self.a_smem_layout_staged,
|
| 481 |
+
self.b_smem_layout_staged,
|
| 482 |
+
self.c_smem_layout_staged,
|
| 483 |
+
self.epi_tile,
|
| 484 |
+
epilogue_op,
|
| 485 |
+
).launch(
|
| 486 |
+
grid=grid,
|
| 487 |
+
block=[self.threads_per_cta, 1, 1],
|
| 488 |
+
cluster=(*self.cluster_shape_mn, 1),
|
| 489 |
+
stream=stream,
|
| 490 |
+
)
|
| 491 |
+
return
|
| 492 |
+
|
| 493 |
+
# GPU device kernel
|
| 494 |
+
@cute.kernel
|
| 495 |
+
def kernel(
|
| 496 |
+
self,
|
| 497 |
+
tiled_mma: cute.TiledMma,
|
| 498 |
+
tma_atom_a: cute.CopyAtom,
|
| 499 |
+
mA_mkl: cute.Tensor,
|
| 500 |
+
tma_atom_b: cute.CopyAtom,
|
| 501 |
+
mB_nkl: cute.Tensor,
|
| 502 |
+
tma_atom_c: Optional[cute.CopyAtom],
|
| 503 |
+
mC_mnl: cute.Tensor,
|
| 504 |
+
cluster_layout_vmnk: cute.Layout,
|
| 505 |
+
a_smem_layout_staged: cute.ComposedLayout,
|
| 506 |
+
b_smem_layout_staged: cute.ComposedLayout,
|
| 507 |
+
c_smem_layout_staged: Union[cute.Layout, cute.ComposedLayout, None],
|
| 508 |
+
epi_tile: cute.Tile,
|
| 509 |
+
epilogue_op: cutlass.Constexpr,
|
| 510 |
+
):
|
| 511 |
+
"""
|
| 512 |
+
GPU device kernel performing the batched GEMM computation.
|
| 513 |
+
"""
|
| 514 |
+
warp_idx = cute.arch.warp_idx()
|
| 515 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 516 |
+
|
| 517 |
+
#
|
| 518 |
+
# Prefetch tma descriptor
|
| 519 |
+
#
|
| 520 |
+
if warp_idx == 0:
|
| 521 |
+
cpasync.prefetch_descriptor(tma_atom_a)
|
| 522 |
+
cpasync.prefetch_descriptor(tma_atom_b)
|
| 523 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 524 |
+
cpasync.prefetch_descriptor(tma_atom_c)
|
| 525 |
+
|
| 526 |
+
use_2cta_instrs = cute.size(tiled_mma.thr_id.shape) == 2
|
| 527 |
+
|
| 528 |
+
#
|
| 529 |
+
# Setup cta/thread coordinates
|
| 530 |
+
#
|
| 531 |
+
# Coords inside cluster
|
| 532 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 533 |
+
mma_tile_coord_v = bidx % cute.size(tiled_mma.thr_id.shape)
|
| 534 |
+
is_leader_cta = mma_tile_coord_v == 0
|
| 535 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(
|
| 536 |
+
cute.arch.block_idx_in_cluster()
|
| 537 |
+
)
|
| 538 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(
|
| 539 |
+
cta_rank_in_cluster
|
| 540 |
+
)
|
| 541 |
+
# Coords outside cluster
|
| 542 |
+
cta_coord = (bidx, bidy, bidz)
|
| 543 |
+
mma_tile_coord_mnl = (
|
| 544 |
+
cta_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 545 |
+
cta_coord[1],
|
| 546 |
+
cta_coord[2],
|
| 547 |
+
)
|
| 548 |
+
# Coords inside cta
|
| 549 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 550 |
+
|
| 551 |
+
#
|
| 552 |
+
# Alloc and init: a+b full/empty, accumulator full, tensor memory dealloc barrier
|
| 553 |
+
#
|
| 554 |
+
smem = utils.SmemAllocator()
|
| 555 |
+
storage = smem.allocate(self.shared_storage)
|
| 556 |
+
|
| 557 |
+
tmem_dealloc_mbar_ptr = storage.tmem_dealloc_mbar_ptr
|
| 558 |
+
tmem_holding_buf = storage.tmem_holding_buf
|
| 559 |
+
|
| 560 |
+
# Initialize mainloop ab_pipeline (barrier) and states
|
| 561 |
+
ab_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 562 |
+
num_tma_producer = self.num_mcast_ctas_a + self.num_mcast_ctas_b - 1
|
| 563 |
+
ab_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 564 |
+
pipeline.Agent.Thread, num_tma_producer
|
| 565 |
+
)
|
| 566 |
+
ab_pipeline = pipeline.PipelineTmaUmma.create(
|
| 567 |
+
barrier_storage=storage.ab_full_mbar_ptr.data_ptr(),
|
| 568 |
+
num_stages=self.num_ab_stage,
|
| 569 |
+
producer_group=ab_pipeline_producer_group,
|
| 570 |
+
consumer_group=ab_pipeline_consumer_group,
|
| 571 |
+
tx_count=self.num_tma_load_bytes,
|
| 572 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 573 |
+
)
|
| 574 |
+
ab_producer_state = pipeline.make_pipeline_state(
|
| 575 |
+
pipeline.PipelineUserType.Producer, self.num_ab_stage
|
| 576 |
+
)
|
| 577 |
+
ab_consumer_state = pipeline.make_pipeline_state(
|
| 578 |
+
pipeline.PipelineUserType.Consumer, self.num_ab_stage
|
| 579 |
+
)
|
| 580 |
+
|
| 581 |
+
# Initialize acc_pipeline (barrier) and states
|
| 582 |
+
acc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 583 |
+
acc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 584 |
+
pipeline.Agent.Thread, self.threads_per_cta, self.threads_per_cta
|
| 585 |
+
)
|
| 586 |
+
acc_pipeline = pipeline.PipelineUmmaAsync.create(
|
| 587 |
+
barrier_storage=storage.acc_full_mbar_ptr.data_ptr(),
|
| 588 |
+
num_stages=self.num_acc_stage,
|
| 589 |
+
producer_group=acc_pipeline_producer_group,
|
| 590 |
+
consumer_group=acc_pipeline_consumer_group,
|
| 591 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 592 |
+
)
|
| 593 |
+
acc_producer_state = pipeline.make_pipeline_state(
|
| 594 |
+
pipeline.PipelineUserType.Producer, self.num_acc_stage
|
| 595 |
+
)
|
| 596 |
+
acc_consumer_state = pipeline.make_pipeline_state(
|
| 597 |
+
pipeline.PipelineUserType.Consumer, self.num_acc_stage
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
# Tensor memory dealloc barrier init
|
| 601 |
+
if use_2cta_instrs:
|
| 602 |
+
if warp_idx == 0:
|
| 603 |
+
num_tmem_dealloc_threads = 32
|
| 604 |
+
with cute.arch.elect_one():
|
| 605 |
+
cute.arch.mbarrier_init(
|
| 606 |
+
tmem_dealloc_mbar_ptr, num_tmem_dealloc_threads
|
| 607 |
+
)
|
| 608 |
+
cute.arch.mbarrier_init_fence()
|
| 609 |
+
|
| 610 |
+
# Cluster arrive after barrier init
|
| 611 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 612 |
+
cute.arch.cluster_arrive_relaxed()
|
| 613 |
+
|
| 614 |
+
#
|
| 615 |
+
# Setup smem tensor A/B/C
|
| 616 |
+
#
|
| 617 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 618 |
+
sC = (
|
| 619 |
+
storage.sC.get_tensor(
|
| 620 |
+
c_smem_layout_staged.outer, swizzle=c_smem_layout_staged.inner
|
| 621 |
+
)
|
| 622 |
+
if self.use_tma_store
|
| 623 |
+
else None
|
| 624 |
+
)
|
| 625 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 626 |
+
sA = storage.sA.get_tensor(
|
| 627 |
+
a_smem_layout_staged.outer, swizzle=a_smem_layout_staged.inner
|
| 628 |
+
)
|
| 629 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 630 |
+
sB = storage.sB.get_tensor(
|
| 631 |
+
b_smem_layout_staged.outer, swizzle=b_smem_layout_staged.inner
|
| 632 |
+
)
|
| 633 |
+
|
| 634 |
+
#
|
| 635 |
+
# Compute multicast mask for A/B buffer full
|
| 636 |
+
#
|
| 637 |
+
a_full_mcast_mask = None
|
| 638 |
+
b_full_mcast_mask = None
|
| 639 |
+
if cutlass.const_expr(self.is_a_mcast or self.is_b_mcast or use_2cta_instrs):
|
| 640 |
+
a_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 641 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 642 |
+
)
|
| 643 |
+
b_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 644 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=1
|
| 645 |
+
)
|
| 646 |
+
|
| 647 |
+
#
|
| 648 |
+
# Local_tile partition global tensors
|
| 649 |
+
#
|
| 650 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 651 |
+
gA_mkl = cute.local_tile(
|
| 652 |
+
mA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None)
|
| 653 |
+
)
|
| 654 |
+
# (bN, bK, RestN, RestK, RestL)
|
| 655 |
+
gB_nkl = cute.local_tile(
|
| 656 |
+
mB_nkl, cute.slice_(self.mma_tiler, (0, None, None)), (None, None, None)
|
| 657 |
+
)
|
| 658 |
+
# (bM, bN, RestM, RestN, RestL)
|
| 659 |
+
gC_mnl = cute.local_tile(
|
| 660 |
+
mC_mnl, cute.slice_(self.mma_tiler, (None, None, 0)), (None, None, None)
|
| 661 |
+
)
|
| 662 |
+
k_tile_cnt = cute.size(gA_mkl, mode=[3])
|
| 663 |
+
|
| 664 |
+
#
|
| 665 |
+
# Partition global tensor for TiledMMA_A/B/C
|
| 666 |
+
#
|
| 667 |
+
thr_mma = tiled_mma.get_slice(mma_tile_coord_v)
|
| 668 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 669 |
+
tCgA = thr_mma.partition_A(gA_mkl)
|
| 670 |
+
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
|
| 671 |
+
tCgB = thr_mma.partition_B(gB_nkl)
|
| 672 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 673 |
+
tCgC = thr_mma.partition_C(gC_mnl)
|
| 674 |
+
|
| 675 |
+
#
|
| 676 |
+
# Partition global/shared tensor for TMA load A/B
|
| 677 |
+
#
|
| 678 |
+
# TMA load A partition_S/D
|
| 679 |
+
a_cta_layout = cute.make_layout(
|
| 680 |
+
cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape
|
| 681 |
+
)
|
| 682 |
+
# ((atom_v, rest_v), STAGE)
|
| 683 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 684 |
+
tAsA, tAgA = cpasync.tma_partition(
|
| 685 |
+
tma_atom_a,
|
| 686 |
+
block_in_cluster_coord_vmnk[2],
|
| 687 |
+
a_cta_layout,
|
| 688 |
+
cute.group_modes(sA, 0, 3),
|
| 689 |
+
cute.group_modes(tCgA, 0, 3),
|
| 690 |
+
)
|
| 691 |
+
# TMA load B partition_S/D
|
| 692 |
+
b_cta_layout = cute.make_layout(
|
| 693 |
+
cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape
|
| 694 |
+
)
|
| 695 |
+
# ((atom_v, rest_v), STAGE)
|
| 696 |
+
# ((atom_v, rest_v), RestN, RestK, RestL)
|
| 697 |
+
tBsB, tBgB = cpasync.tma_partition(
|
| 698 |
+
tma_atom_b,
|
| 699 |
+
block_in_cluster_coord_vmnk[1],
|
| 700 |
+
b_cta_layout,
|
| 701 |
+
cute.group_modes(sB, 0, 3),
|
| 702 |
+
cute.group_modes(tCgB, 0, 3),
|
| 703 |
+
)
|
| 704 |
+
|
| 705 |
+
#
|
| 706 |
+
# Partition shared/tensor memory tensor for TiledMMA_A/B/C
|
| 707 |
+
#
|
| 708 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 709 |
+
tCrA = tiled_mma.make_fragment_A(sA)
|
| 710 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 711 |
+
tCrB = tiled_mma.make_fragment_B(sB)
|
| 712 |
+
# (MMA, MMA_M, MMA_N)
|
| 713 |
+
acc_shape = tiled_mma.partition_shape_C(self.mma_tiler[:2])
|
| 714 |
+
# (MMA, MMA_M, MMA_N)
|
| 715 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(acc_shape)
|
| 716 |
+
|
| 717 |
+
#
|
| 718 |
+
# Cluster wait before tensor memory alloc
|
| 719 |
+
#
|
| 720 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 721 |
+
cute.arch.cluster_wait()
|
| 722 |
+
|
| 723 |
+
#
|
| 724 |
+
# Alloc tensor memory buffer
|
| 725 |
+
#
|
| 726 |
+
if warp_idx == 0:
|
| 727 |
+
cute.arch.alloc_tmem(
|
| 728 |
+
self.num_tmem_alloc_cols, tmem_holding_buf, is_two_cta=use_2cta_instrs
|
| 729 |
+
)
|
| 730 |
+
|
| 731 |
+
#
|
| 732 |
+
# Bar sync for retrieve tensor memory ptr from shared memory
|
| 733 |
+
#
|
| 734 |
+
cute.arch.barrier()
|
| 735 |
+
|
| 736 |
+
#
|
| 737 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 738 |
+
#
|
| 739 |
+
tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 740 |
+
self.acc_dtype, alignment=16, ptr_to_buffer_holding_addr=tmem_holding_buf
|
| 741 |
+
)
|
| 742 |
+
# (MMA, MMA_M, MMA_N)
|
| 743 |
+
tCtAcc = cute.make_tensor(tmem_ptr, tCtAcc_fake.layout)
|
| 744 |
+
|
| 745 |
+
#
|
| 746 |
+
# Partition for epilogue
|
| 747 |
+
#
|
| 748 |
+
tiled_copy_t2r, tTR_tAcc, tTR_rAcc = self.epilog_tmem_copy_and_partition(
|
| 749 |
+
tidx, tCtAcc, tCgC, epi_tile, use_2cta_instrs
|
| 750 |
+
)
|
| 751 |
+
|
| 752 |
+
tTR_rC = None
|
| 753 |
+
tiled_copy_r2s = None
|
| 754 |
+
simt_atom = None
|
| 755 |
+
tRS_rC = None
|
| 756 |
+
tRS_sC = None
|
| 757 |
+
bSG_sC = None
|
| 758 |
+
bSG_gC = None
|
| 759 |
+
tTR_gC = None
|
| 760 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 761 |
+
tTR_rC = cute.make_fragment(tTR_rAcc.shape, self.c_dtype)
|
| 762 |
+
tiled_copy_r2s, tRS_rC, tRS_sC = self.epilog_smem_copy_and_partition(
|
| 763 |
+
tiled_copy_t2r, tTR_rC, tidx, sC
|
| 764 |
+
)
|
| 765 |
+
tma_atom_c, bSG_sC, bSG_gC = self.epilog_gmem_copy_and_partition(
|
| 766 |
+
tidx, tma_atom_c, tCgC, epi_tile, sC
|
| 767 |
+
)
|
| 768 |
+
else:
|
| 769 |
+
simt_atom, tTR_rC, tTR_gC = self.epilog_gmem_copy_and_partition(
|
| 770 |
+
tidx, tiled_copy_t2r, tCgC, epi_tile, sC
|
| 771 |
+
)
|
| 772 |
+
|
| 773 |
+
#
|
| 774 |
+
# Slice to per mma tile index
|
| 775 |
+
#
|
| 776 |
+
# ((atom_v, rest_v), RestK)
|
| 777 |
+
tAgA = tAgA[(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])]
|
| 778 |
+
# ((atom_v, rest_v), RestK)
|
| 779 |
+
tBgB = tBgB[(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])]
|
| 780 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 781 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 782 |
+
bSG_gC = bSG_gC[(None, None, None, *mma_tile_coord_mnl)]
|
| 783 |
+
else:
|
| 784 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N)
|
| 785 |
+
tTR_gC = tTR_gC[(None, None, None, None, None, *mma_tile_coord_mnl)]
|
| 786 |
+
|
| 787 |
+
#
|
| 788 |
+
# Pipelining TMA load A/B and MMA mainloop
|
| 789 |
+
#
|
| 790 |
+
prefetch_k_tile_cnt = cutlass.min(self.num_ab_stage - 2, k_tile_cnt)
|
| 791 |
+
|
| 792 |
+
if warp_idx == 0:
|
| 793 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt
|
| 794 |
+
peek_ab_empty_status = cutlass.Boolean(1)
|
| 795 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 796 |
+
peek_ab_empty_status = ab_pipeline.producer_try_acquire(
|
| 797 |
+
ab_producer_state
|
| 798 |
+
)
|
| 799 |
+
#
|
| 800 |
+
# Prefetch TMA load A/B
|
| 801 |
+
#
|
| 802 |
+
for prefetch_idx in cutlass.range(prefetch_k_tile_cnt, unroll=1):
|
| 803 |
+
# Conditionally wait for AB buffer empty
|
| 804 |
+
ab_pipeline.producer_acquire(ab_producer_state, peek_ab_empty_status)
|
| 805 |
+
|
| 806 |
+
# TMA load A/B
|
| 807 |
+
cute.copy(
|
| 808 |
+
tma_atom_a,
|
| 809 |
+
tAgA[(None, ab_producer_state.count)],
|
| 810 |
+
tAsA[(None, ab_producer_state.index)],
|
| 811 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 812 |
+
mcast_mask=a_full_mcast_mask,
|
| 813 |
+
)
|
| 814 |
+
cute.copy(
|
| 815 |
+
tma_atom_b,
|
| 816 |
+
tBgB[(None, ab_producer_state.count)],
|
| 817 |
+
tBsB[(None, ab_producer_state.index)],
|
| 818 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 819 |
+
mcast_mask=b_full_mcast_mask,
|
| 820 |
+
)
|
| 821 |
+
|
| 822 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt + k_tile + 1
|
| 823 |
+
ab_producer_state.advance()
|
| 824 |
+
peek_ab_empty_status = cutlass.Boolean(1)
|
| 825 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 826 |
+
peek_ab_empty_status = ab_pipeline.producer_try_acquire(
|
| 827 |
+
ab_producer_state
|
| 828 |
+
)
|
| 829 |
+
|
| 830 |
+
# Peek (try_wait) AB buffer full for k_tile = 0
|
| 831 |
+
peek_ab_full_status = cutlass.Boolean(1)
|
| 832 |
+
if ab_consumer_state.count < k_tile_cnt and is_leader_cta:
|
| 833 |
+
peek_ab_full_status = ab_pipeline.consumer_try_wait(ab_consumer_state)
|
| 834 |
+
|
| 835 |
+
#
|
| 836 |
+
# MMA mainloop
|
| 837 |
+
#
|
| 838 |
+
for k_tile in range(k_tile_cnt):
|
| 839 |
+
# Conditionally wait for AB buffer empty
|
| 840 |
+
ab_pipeline.producer_acquire(ab_producer_state, peek_ab_empty_status)
|
| 841 |
+
|
| 842 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 843 |
+
# TMA load A/B
|
| 844 |
+
cute.copy(
|
| 845 |
+
tma_atom_a,
|
| 846 |
+
tAgA[(None, ab_producer_state.count)],
|
| 847 |
+
tAsA[(None, ab_producer_state.index)],
|
| 848 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 849 |
+
mcast_mask=a_full_mcast_mask,
|
| 850 |
+
)
|
| 851 |
+
cute.copy(
|
| 852 |
+
tma_atom_b,
|
| 853 |
+
tBgB[(None, ab_producer_state.count)],
|
| 854 |
+
tBsB[(None, ab_producer_state.index)],
|
| 855 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 856 |
+
mcast_mask=b_full_mcast_mask,
|
| 857 |
+
)
|
| 858 |
+
|
| 859 |
+
if is_leader_cta:
|
| 860 |
+
# Conditionally wait for AB buffer full
|
| 861 |
+
ab_pipeline.consumer_wait(ab_consumer_state, peek_ab_full_status)
|
| 862 |
+
|
| 863 |
+
# tCtAcc += tCrA * tCrB
|
| 864 |
+
num_kblocks = cute.size(tCrA, mode=[2])
|
| 865 |
+
for kblock_idx in cutlass.range(num_kblocks, unroll_full=True):
|
| 866 |
+
kblock_coord = (None, None, kblock_idx, ab_consumer_state.index)
|
| 867 |
+
|
| 868 |
+
cute.gemm(
|
| 869 |
+
tiled_mma,
|
| 870 |
+
tCtAcc,
|
| 871 |
+
tCrA[kblock_coord],
|
| 872 |
+
tCrB[kblock_coord],
|
| 873 |
+
tCtAcc,
|
| 874 |
+
)
|
| 875 |
+
# Enable accumulate on tCtAcc after first kblock
|
| 876 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 877 |
+
|
| 878 |
+
# Async arrive AB buffer empty
|
| 879 |
+
ab_pipeline.consumer_release(ab_consumer_state)
|
| 880 |
+
|
| 881 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt + k_tile + 1
|
| 882 |
+
ab_producer_state.advance()
|
| 883 |
+
peek_ab_empty_status = cutlass.Boolean(1)
|
| 884 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 885 |
+
peek_ab_empty_status = ab_pipeline.producer_try_acquire(
|
| 886 |
+
ab_producer_state
|
| 887 |
+
)
|
| 888 |
+
|
| 889 |
+
# Peek (try_wait) AB buffer full for k_tile = k_tile + 1
|
| 890 |
+
ab_consumer_state.advance()
|
| 891 |
+
peek_ab_full_status = cutlass.Boolean(1)
|
| 892 |
+
if ab_consumer_state.count < k_tile_cnt:
|
| 893 |
+
if is_leader_cta:
|
| 894 |
+
peek_ab_full_status = ab_pipeline.consumer_try_wait(
|
| 895 |
+
ab_consumer_state
|
| 896 |
+
)
|
| 897 |
+
|
| 898 |
+
# Async arrive accumulator buffer full
|
| 899 |
+
if is_leader_cta:
|
| 900 |
+
acc_pipeline.producer_commit(acc_producer_state)
|
| 901 |
+
|
| 902 |
+
#
|
| 903 |
+
# Epilogue
|
| 904 |
+
#
|
| 905 |
+
|
| 906 |
+
# Release tensor memory allocation lock
|
| 907 |
+
if warp_idx == 0:
|
| 908 |
+
cute.arch.relinquish_tmem_alloc_permit(is_two_cta=use_2cta_instrs)
|
| 909 |
+
|
| 910 |
+
# Wait for accumulator buffer full
|
| 911 |
+
acc_pipeline.consumer_wait(acc_consumer_state)
|
| 912 |
+
|
| 913 |
+
tTR_tAcc = cute.group_modes(tTR_tAcc, 3, cute.rank(tTR_tAcc))
|
| 914 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 915 |
+
bSG_gC = cute.group_modes(bSG_gC, 1, cute.rank(bSG_gC))
|
| 916 |
+
else:
|
| 917 |
+
tTR_gC = cute.group_modes(tTR_gC, 3, cute.rank(tTR_gC))
|
| 918 |
+
|
| 919 |
+
c_pipeline = None
|
| 920 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 921 |
+
# Initialize tma store c_pipeline
|
| 922 |
+
c_producer_group = pipeline.CooperativeGroup(
|
| 923 |
+
pipeline.Agent.Thread, self.threads_per_cta, self.threads_per_cta
|
| 924 |
+
)
|
| 925 |
+
c_pipeline = pipeline.PipelineTmaStore.create(
|
| 926 |
+
num_stages=self.num_c_stage,
|
| 927 |
+
producer_group=c_producer_group,
|
| 928 |
+
)
|
| 929 |
+
|
| 930 |
+
#
|
| 931 |
+
# Store accumulator to global memory in subtiles
|
| 932 |
+
#
|
| 933 |
+
subtile_cnt = cute.size(tTR_tAcc.shape, mode=[3])
|
| 934 |
+
for subtile_idx in range(subtile_cnt):
|
| 935 |
+
#
|
| 936 |
+
# Load accumulator from tensor memory buffer to register
|
| 937 |
+
#
|
| 938 |
+
tTR_tAcc_mn = tTR_tAcc[(None, None, None, subtile_idx)]
|
| 939 |
+
cute.copy(tiled_copy_t2r, tTR_tAcc_mn, tTR_rAcc)
|
| 940 |
+
|
| 941 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 942 |
+
#
|
| 943 |
+
# Perform epilogue op on accumulator and convert to C type
|
| 944 |
+
#
|
| 945 |
+
acc_vec = tiled_copy_r2s.retile(tTR_rAcc).load()
|
| 946 |
+
acc_vec = epilogue_op(acc_vec.to(self.c_dtype))
|
| 947 |
+
tRS_rC.store(acc_vec)
|
| 948 |
+
|
| 949 |
+
#
|
| 950 |
+
# Store C to shared memory
|
| 951 |
+
#
|
| 952 |
+
c_buffer = subtile_idx % self.num_c_stage
|
| 953 |
+
cute.copy(tiled_copy_r2s, tRS_rC, tRS_sC[(None, None, None, c_buffer)])
|
| 954 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 955 |
+
cute.arch.fence_proxy(
|
| 956 |
+
cute.arch.ProxyKind.async_shared,
|
| 957 |
+
space=cute.arch.SharedSpace.shared_cta,
|
| 958 |
+
)
|
| 959 |
+
cute.arch.barrier()
|
| 960 |
+
|
| 961 |
+
#
|
| 962 |
+
# TMA store C to global memory
|
| 963 |
+
#
|
| 964 |
+
if warp_idx == 0:
|
| 965 |
+
cute.copy(
|
| 966 |
+
tma_atom_c,
|
| 967 |
+
bSG_sC[(None, c_buffer)],
|
| 968 |
+
bSG_gC[(None, subtile_idx)],
|
| 969 |
+
)
|
| 970 |
+
# Fence and barrier to make sure TMA store is completed to recollect C buffer
|
| 971 |
+
c_pipeline.producer_commit()
|
| 972 |
+
c_pipeline.producer_acquire()
|
| 973 |
+
cute.arch.barrier()
|
| 974 |
+
else:
|
| 975 |
+
#
|
| 976 |
+
# Perform epilogue op on accumulator and convert to C type
|
| 977 |
+
#
|
| 978 |
+
acc_vec = tTR_rAcc.load()
|
| 979 |
+
acc_vec = epilogue_op(acc_vec.to(self.c_dtype))
|
| 980 |
+
tTR_rC.store(acc_vec)
|
| 981 |
+
|
| 982 |
+
#
|
| 983 |
+
# Store C to global memory
|
| 984 |
+
#
|
| 985 |
+
cute.copy(simt_atom, tTR_rC, tTR_gC[(None, None, None, subtile_idx)])
|
| 986 |
+
|
| 987 |
+
#
|
| 988 |
+
# Dealloc the tensor memory buffer
|
| 989 |
+
#
|
| 990 |
+
cute.arch.barrier()
|
| 991 |
+
if warp_idx == 0:
|
| 992 |
+
if use_2cta_instrs:
|
| 993 |
+
cute.arch.mbarrier_arrive(
|
| 994 |
+
tmem_dealloc_mbar_ptr, cta_rank_in_cluster ^ 1
|
| 995 |
+
)
|
| 996 |
+
cute.arch.mbarrier_wait(tmem_dealloc_mbar_ptr, 0)
|
| 997 |
+
cute.arch.dealloc_tmem(
|
| 998 |
+
tmem_ptr, self.num_tmem_alloc_cols, is_two_cta=use_2cta_instrs
|
| 999 |
+
)
|
| 1000 |
+
|
| 1001 |
+
#
|
| 1002 |
+
# Wait for C store complete
|
| 1003 |
+
#
|
| 1004 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1005 |
+
c_pipeline.producer_tail()
|
| 1006 |
+
|
| 1007 |
+
#
|
| 1008 |
+
# Wait A/B buffer empty
|
| 1009 |
+
#
|
| 1010 |
+
if warp_idx == 0:
|
| 1011 |
+
# Reverse prefetch_k_tile_cnt times to next available buffer
|
| 1012 |
+
for i in range(prefetch_k_tile_cnt):
|
| 1013 |
+
ab_producer_state.reverse()
|
| 1014 |
+
ab_pipeline.producer_tail(ab_producer_state)
|
| 1015 |
+
return
|
| 1016 |
+
|
| 1017 |
+
def epilog_tmem_copy_and_partition(
|
| 1018 |
+
self,
|
| 1019 |
+
tidx: cutlass.Int32,
|
| 1020 |
+
tAcc: cute.Tensor,
|
| 1021 |
+
gC_mnl: cute.Tensor,
|
| 1022 |
+
epi_tile: cute.Tile,
|
| 1023 |
+
use_2cta_instrs: Union[cutlass.Boolean, bool],
|
| 1024 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1025 |
+
"""
|
| 1026 |
+
Make tiledCopy for tensor memory load, then use it to partition tensor memory (source) and register array (destination).
|
| 1027 |
+
|
| 1028 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1029 |
+
:type tidx: cutlass.Int32
|
| 1030 |
+
:param tAcc: The accumulator tensor to be copied and partitioned
|
| 1031 |
+
:type tAcc: cute.Tensor
|
| 1032 |
+
:param gC_mnl: The global tensor C
|
| 1033 |
+
:type gC_mnl: cute.Tensor
|
| 1034 |
+
:param epi_tile: The epilogue tiler
|
| 1035 |
+
:type epi_tile: cute.Tile
|
| 1036 |
+
:param use_2cta_instrs: Whether use_2cta_instrs is enabled
|
| 1037 |
+
:type use_2cta_instrs: bool
|
| 1038 |
+
|
| 1039 |
+
:return: A tuple containing (tiled_copy_t2r, tTR_tAcc, tTR_rAcc) where:
|
| 1040 |
+
- tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1041 |
+
- tTR_tAcc: The partitioned accumulator tensor
|
| 1042 |
+
- tTR_rAcc: The accumulated tensor in register used to hold t2r results
|
| 1043 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1044 |
+
"""
|
| 1045 |
+
# Make tiledCopy for tensor memory load
|
| 1046 |
+
copy_atom_t2r = sm100_utils.get_tmem_load_op(
|
| 1047 |
+
self.cta_tile_shape_mnk,
|
| 1048 |
+
self.c_layout,
|
| 1049 |
+
self.c_dtype,
|
| 1050 |
+
self.acc_dtype,
|
| 1051 |
+
epi_tile,
|
| 1052 |
+
use_2cta_instrs,
|
| 1053 |
+
)
|
| 1054 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N)
|
| 1055 |
+
tAcc_epi = cute.flat_divide(
|
| 1056 |
+
tAcc[((None, None), 0, 0)],
|
| 1057 |
+
epi_tile,
|
| 1058 |
+
)
|
| 1059 |
+
# (EPI_TILE_M, EPI_TILE_N)
|
| 1060 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(
|
| 1061 |
+
copy_atom_t2r, tAcc_epi[(None, None, 0, 0)]
|
| 1062 |
+
)
|
| 1063 |
+
|
| 1064 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1065 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M)
|
| 1066 |
+
tTR_tAcc = thr_copy_t2r.partition_S(tAcc_epi)
|
| 1067 |
+
|
| 1068 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1069 |
+
gC_mnl_epi = cute.flat_divide(
|
| 1070 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1071 |
+
)
|
| 1072 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1073 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_mnl_epi)
|
| 1074 |
+
# (T2R, T2R_M, T2R_N)
|
| 1075 |
+
tTR_rAcc = cute.make_fragment(
|
| 1076 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.acc_dtype
|
| 1077 |
+
)
|
| 1078 |
+
return tiled_copy_t2r, tTR_tAcc, tTR_rAcc
|
| 1079 |
+
|
| 1080 |
+
def epilog_smem_copy_and_partition(
|
| 1081 |
+
self,
|
| 1082 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 1083 |
+
tTR_rC: cute.Tensor,
|
| 1084 |
+
tidx: cutlass.Int32,
|
| 1085 |
+
sC: cute.Tensor,
|
| 1086 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1087 |
+
"""
|
| 1088 |
+
Make tiledCopy for shared memory store, then use it to partition register array (source) and shared memory (destination).
|
| 1089 |
+
|
| 1090 |
+
:param tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1091 |
+
:type tiled_copy_t2r: cute.TiledCopy
|
| 1092 |
+
:param tTR_rC: The partitioned accumulator tensor
|
| 1093 |
+
:type tTR_rC: cute.Tensor
|
| 1094 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1095 |
+
:type tidx: cutlass.Int32
|
| 1096 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1097 |
+
:type sC: cute.Tensor
|
| 1098 |
+
|
| 1099 |
+
:return: A tuple containing (tiled_copy_r2s, tRS_rC, tRS_sC) where:
|
| 1100 |
+
- tiled_copy_r2s: The tiled copy operation for register to smem copy(r2s)
|
| 1101 |
+
- tRS_rC: The partitioned tensor C (register source)
|
| 1102 |
+
- tRS_sC: The partitioned tensor C (smem destination)
|
| 1103 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1104 |
+
"""
|
| 1105 |
+
copy_atom_r2s = sm100_utils.get_smem_store_op(
|
| 1106 |
+
self.c_layout, self.c_dtype, self.acc_dtype, tiled_copy_t2r
|
| 1107 |
+
)
|
| 1108 |
+
tiled_copy_r2s = cute.make_tiled_copy_D(copy_atom_r2s, tiled_copy_t2r)
|
| 1109 |
+
# (R2S, R2S_M, R2S_N, PIPE_D)
|
| 1110 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx)
|
| 1111 |
+
tRS_sC = thr_copy_r2s.partition_D(sC)
|
| 1112 |
+
# (R2S, R2S_M, R2S_N)
|
| 1113 |
+
tRS_rC = tiled_copy_r2s.retile(tTR_rC)
|
| 1114 |
+
return tiled_copy_r2s, tRS_rC, tRS_sC
|
| 1115 |
+
|
| 1116 |
+
def epilog_gmem_copy_and_partition(
|
| 1117 |
+
self,
|
| 1118 |
+
tidx: cutlass.Int32,
|
| 1119 |
+
atom: Union[cute.CopyAtom, cute.TiledCopy],
|
| 1120 |
+
gC_mnl: cute.Tensor,
|
| 1121 |
+
epi_tile: cute.Tile,
|
| 1122 |
+
sC: cute.Tensor,
|
| 1123 |
+
) -> Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]:
|
| 1124 |
+
"""Make tiledCopy for global memory store, then use it to:
|
| 1125 |
+
- partition register array (source) and global memory (destination) for none TMA store version;
|
| 1126 |
+
- partition shared memory (source) and global memory (destination) for TMA store version.
|
| 1127 |
+
|
| 1128 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1129 |
+
:type tidx: cutlass.Int32
|
| 1130 |
+
:param atom: The copy_atom_c to be used for TMA store version, or tiled_copy_t2r for none TMA store version
|
| 1131 |
+
:type atom: cute.CopyAtom or cute.TiledCopy
|
| 1132 |
+
:param gC_mnl: The global tensor C
|
| 1133 |
+
:type gC_mnl: cute.Tensor
|
| 1134 |
+
:param epi_tile: The epilogue tiler
|
| 1135 |
+
:type epi_tile: cute.Tile
|
| 1136 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1137 |
+
:type sC: cute.Tensor
|
| 1138 |
+
|
| 1139 |
+
:return: A tuple containing either:
|
| 1140 |
+
- For TMA store: (tma_atom_c, bSG_sC, bSG_gC) where:
|
| 1141 |
+
- tma_atom_c: The TMA copy atom
|
| 1142 |
+
- bSG_sC: The partitioned shared memory tensor C
|
| 1143 |
+
- bSG_gC: The partitioned global tensor C
|
| 1144 |
+
- For non-TMA store: (simt_atom, tTR_rC, tTR_gC) where:
|
| 1145 |
+
- simt_atom: The SIMT copy atom
|
| 1146 |
+
- tTR_rC: The register tensor C
|
| 1147 |
+
- tTR_gC: The partitioned global tensor C
|
| 1148 |
+
:rtype: Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]
|
| 1149 |
+
"""
|
| 1150 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1151 |
+
gC_epi = cute.flat_divide(
|
| 1152 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1153 |
+
)
|
| 1154 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1155 |
+
tma_atom_c = atom
|
| 1156 |
+
sC_for_tma_partition = cute.group_modes(sC, 0, 2)
|
| 1157 |
+
gC_for_tma_partition = cute.group_modes(gC_epi, 0, 2)
|
| 1158 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1159 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1160 |
+
bSG_sC, bSG_gC = cpasync.tma_partition(
|
| 1161 |
+
tma_atom_c,
|
| 1162 |
+
0,
|
| 1163 |
+
cute.make_layout(1),
|
| 1164 |
+
sC_for_tma_partition,
|
| 1165 |
+
gC_for_tma_partition,
|
| 1166 |
+
)
|
| 1167 |
+
return tma_atom_c, bSG_sC, bSG_gC
|
| 1168 |
+
else:
|
| 1169 |
+
tiled_copy_t2r = atom
|
| 1170 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1171 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1172 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_epi)
|
| 1173 |
+
# (T2R, T2R_M, T2R_N)
|
| 1174 |
+
tTR_rC = cute.make_fragment(
|
| 1175 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.c_dtype
|
| 1176 |
+
)
|
| 1177 |
+
simt_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), self.c_dtype)
|
| 1178 |
+
return simt_atom, tTR_rC, tTR_gC
|
| 1179 |
+
|
| 1180 |
+
@staticmethod
|
| 1181 |
+
def _compute_stages(
|
| 1182 |
+
tiled_mma: cute.TiledMma,
|
| 1183 |
+
mma_tiler_mnk: Tuple[int, int, int],
|
| 1184 |
+
a_dtype: Type[cutlass.Numeric],
|
| 1185 |
+
b_dtype: Type[cutlass.Numeric],
|
| 1186 |
+
epi_tile: cute.Tile,
|
| 1187 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1188 |
+
c_layout: utils.LayoutEnum,
|
| 1189 |
+
smem_capacity: int,
|
| 1190 |
+
occupancy: int,
|
| 1191 |
+
use_tma_store: bool,
|
| 1192 |
+
) -> Tuple[int, int, int]:
|
| 1193 |
+
"""Computes the number of stages for A/B/C operands based on heuristics.
|
| 1194 |
+
|
| 1195 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1196 |
+
:type tiled_mma: cute.TiledMma
|
| 1197 |
+
:param mma_tiler_mnk: The shape (M, N, K) of the MMA tile.
|
| 1198 |
+
:type mma_tiler_mnk: tuple[int, int, int]
|
| 1199 |
+
:param a_dtype: Data type of operand A.
|
| 1200 |
+
:type a_dtype: type[cutlass.Numeric]
|
| 1201 |
+
:param b_dtype: Data type of operand B.
|
| 1202 |
+
:type b_dtype: type[cutlass.Numeric]
|
| 1203 |
+
:param epi_tile: The epilogue tile shape.
|
| 1204 |
+
:type epi_tile: cute.Tile
|
| 1205 |
+
:param c_dtype: Data type of operand C (output).
|
| 1206 |
+
:type c_dtype: type[cutlass.Numeric]
|
| 1207 |
+
:param c_layout: Layout enum of operand C in global memory.
|
| 1208 |
+
:type c_layout: utils.LayoutEnum
|
| 1209 |
+
:param smem_capacity: Total available shared memory capacity in bytes.
|
| 1210 |
+
:type smem_capacity: int
|
| 1211 |
+
:param occupancy: Target number of CTAs per SM (occupancy).
|
| 1212 |
+
:type occupancy: int
|
| 1213 |
+
:param use_tma_store: Whether TMA store is enabled.
|
| 1214 |
+
:type use_tma_store: bool
|
| 1215 |
+
|
| 1216 |
+
:return: A tuple containing the computed number of stages for:
|
| 1217 |
+
(ACC stages, A/B operand stages, epilogue stages)
|
| 1218 |
+
:rtype: tuple[int, int, int]
|
| 1219 |
+
"""
|
| 1220 |
+
# Default ACC stages
|
| 1221 |
+
num_acc_stage = 1
|
| 1222 |
+
# Default C stages
|
| 1223 |
+
num_c_stage = 2 if use_tma_store else 0
|
| 1224 |
+
|
| 1225 |
+
# Calculate smem layout and size for one stage of A, B, and C
|
| 1226 |
+
a_smem_layout_stage_one = sm100_utils.make_smem_layout_a(
|
| 1227 |
+
tiled_mma,
|
| 1228 |
+
mma_tiler_mnk,
|
| 1229 |
+
a_dtype,
|
| 1230 |
+
1, # a tmp 1 stage is provided
|
| 1231 |
+
)
|
| 1232 |
+
b_smem_layout_staged_one = sm100_utils.make_smem_layout_b(
|
| 1233 |
+
tiled_mma,
|
| 1234 |
+
mma_tiler_mnk,
|
| 1235 |
+
b_dtype,
|
| 1236 |
+
1, # a tmp 1 stage is provided
|
| 1237 |
+
)
|
| 1238 |
+
c_smem_layout_staged_one = (
|
| 1239 |
+
sm100_utils.make_smem_layout_epi(
|
| 1240 |
+
c_dtype,
|
| 1241 |
+
c_layout,
|
| 1242 |
+
epi_tile,
|
| 1243 |
+
1,
|
| 1244 |
+
)
|
| 1245 |
+
if use_tma_store
|
| 1246 |
+
else None
|
| 1247 |
+
)
|
| 1248 |
+
ab_bytes_per_stage = cute.size_in_bytes(
|
| 1249 |
+
a_dtype, a_smem_layout_stage_one
|
| 1250 |
+
) + cute.size_in_bytes(b_dtype, b_smem_layout_staged_one)
|
| 1251 |
+
mbar_helpers_bytes = 1024
|
| 1252 |
+
c_bytes_per_stage = (
|
| 1253 |
+
cute.size_in_bytes(c_dtype, c_smem_layout_staged_one)
|
| 1254 |
+
if use_tma_store
|
| 1255 |
+
else 0
|
| 1256 |
+
)
|
| 1257 |
+
c_bytes = c_bytes_per_stage * num_c_stage
|
| 1258 |
+
|
| 1259 |
+
# Calculate A/B stages:
|
| 1260 |
+
# Start with total smem per CTA (capacity / occupancy)
|
| 1261 |
+
# Subtract reserved bytes and initial C stages bytes
|
| 1262 |
+
# Divide remaining by bytes needed per A/B stage
|
| 1263 |
+
num_ab_stage = (
|
| 1264 |
+
smem_capacity - (occupancy + 1) * (mbar_helpers_bytes + c_bytes)
|
| 1265 |
+
) // ab_bytes_per_stage
|
| 1266 |
+
|
| 1267 |
+
# Refine epilogue stages:
|
| 1268 |
+
# Calculate remaining smem after allocating for A/B stages and reserved bytes
|
| 1269 |
+
# Add remaining unused smem to epilogue
|
| 1270 |
+
if use_tma_store:
|
| 1271 |
+
num_c_stage += (
|
| 1272 |
+
smem_capacity
|
| 1273 |
+
- ab_bytes_per_stage * num_ab_stage
|
| 1274 |
+
- (occupancy + 1) * (mbar_helpers_bytes + c_bytes)
|
| 1275 |
+
) // ((occupancy + 1) * c_bytes_per_stage)
|
| 1276 |
+
return num_acc_stage, num_ab_stage, num_c_stage
|
| 1277 |
+
|
| 1278 |
+
@staticmethod
|
| 1279 |
+
def _compute_grid(
|
| 1280 |
+
c: cute.Tensor,
|
| 1281 |
+
cta_tile_shape_mnk: Tuple[int, int, int],
|
| 1282 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1283 |
+
) -> Tuple[int, int, int]:
|
| 1284 |
+
"""Compute grid shape for the output tensor C.
|
| 1285 |
+
|
| 1286 |
+
:param c: The output tensor C
|
| 1287 |
+
:type c: cute.Tensor
|
| 1288 |
+
:param cta_tile_shape_mnk: The shape (M, N, K) of the CTA tile.
|
| 1289 |
+
:type cta_tile_shape_mnk: tuple[int, int, int]
|
| 1290 |
+
:param cluster_shape_mn: Shape of each cluster in M, N dimensions.
|
| 1291 |
+
:type cluster_shape_mn: tuple[int, int]
|
| 1292 |
+
|
| 1293 |
+
:return: Grid shape for kernel launch.
|
| 1294 |
+
:rtype: tuple[int, int, int]
|
| 1295 |
+
"""
|
| 1296 |
+
|
| 1297 |
+
cluster_shape_mnl = (*cluster_shape_mn, 1)
|
| 1298 |
+
|
| 1299 |
+
grid = cute.round_up(
|
| 1300 |
+
(
|
| 1301 |
+
cute.ceil_div(c.layout.shape[0], cta_tile_shape_mnk[0]),
|
| 1302 |
+
cute.ceil_div(c.layout.shape[1], cta_tile_shape_mnk[1]),
|
| 1303 |
+
c.layout.shape[2],
|
| 1304 |
+
),
|
| 1305 |
+
cluster_shape_mnl,
|
| 1306 |
+
)
|
| 1307 |
+
|
| 1308 |
+
return grid
|
| 1309 |
+
|
| 1310 |
+
@staticmethod
|
| 1311 |
+
def _compute_num_tmem_alloc_cols(
|
| 1312 |
+
tiled_mma: cute.TiledMma, mma_tiler: Tuple[int, int, int]
|
| 1313 |
+
) -> int:
|
| 1314 |
+
"""
|
| 1315 |
+
Compute the number of tensor memory allocation columns.
|
| 1316 |
+
|
| 1317 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1318 |
+
:type tiled_mma: cute.TiledMma
|
| 1319 |
+
:param mma_tiler: The shape (M, N, K) of the MMA tile.
|
| 1320 |
+
:type mma_tiler: tuple[int, int, int]
|
| 1321 |
+
|
| 1322 |
+
:return: The number of tensor memory allocation columns.
|
| 1323 |
+
:rtype: int
|
| 1324 |
+
"""
|
| 1325 |
+
acc_shape = tiled_mma.partition_shape_C(mma_tiler[:2])
|
| 1326 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(acc_shape)
|
| 1327 |
+
return sm100_utils.get_num_tmem_alloc_cols(tCtAcc_fake)
|
| 1328 |
+
|
| 1329 |
+
@staticmethod
|
| 1330 |
+
def is_valid_dtypes(
|
| 1331 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1332 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1333 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1334 |
+
) -> bool:
|
| 1335 |
+
"""
|
| 1336 |
+
Check if the dtypes are valid
|
| 1337 |
+
|
| 1338 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1339 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1340 |
+
:param acc_dtype: The data type of the accumulator
|
| 1341 |
+
:type acc_dtype: Type[cutlass.Numeric]
|
| 1342 |
+
:param c_dtype: The data type of the output tensor
|
| 1343 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1344 |
+
|
| 1345 |
+
:return: True if the dtypes are valid, False otherwise
|
| 1346 |
+
:rtype: bool
|
| 1347 |
+
"""
|
| 1348 |
+
is_valid = True
|
| 1349 |
+
if ab_dtype not in {
|
| 1350 |
+
cutlass.Float16,
|
| 1351 |
+
cutlass.BFloat16,
|
| 1352 |
+
cutlass.TFloat32,
|
| 1353 |
+
cutlass.Uint8,
|
| 1354 |
+
cutlass.Int8,
|
| 1355 |
+
cutlass.Float8E4M3FN,
|
| 1356 |
+
cutlass.Float8E5M2,
|
| 1357 |
+
}:
|
| 1358 |
+
is_valid = False
|
| 1359 |
+
if (
|
| 1360 |
+
acc_dtype not in {cutlass.Float32, cutlass.Float16, cutlass.Int32}
|
| 1361 |
+
or acc_dtype == cutlass.Float16
|
| 1362 |
+
and ab_dtype
|
| 1363 |
+
not in {cutlass.Float16, cutlass.Float8E4M3FN, cutlass.Float8E5M2}
|
| 1364 |
+
or acc_dtype == cutlass.Int32
|
| 1365 |
+
and ab_dtype not in {cutlass.Uint8, cutlass.Int8}
|
| 1366 |
+
):
|
| 1367 |
+
is_valid = False
|
| 1368 |
+
if (
|
| 1369 |
+
acc_dtype == cutlass.Float32
|
| 1370 |
+
and c_dtype
|
| 1371 |
+
not in {
|
| 1372 |
+
cutlass.Float32,
|
| 1373 |
+
cutlass.Float16,
|
| 1374 |
+
cutlass.BFloat16,
|
| 1375 |
+
cutlass.Float8E4M3FN,
|
| 1376 |
+
cutlass.Float8E5M2,
|
| 1377 |
+
cutlass.Int32,
|
| 1378 |
+
cutlass.Int8,
|
| 1379 |
+
cutlass.Uint8,
|
| 1380 |
+
}
|
| 1381 |
+
or acc_dtype == cutlass.Float16
|
| 1382 |
+
and c_dtype
|
| 1383 |
+
not in {
|
| 1384 |
+
cutlass.BFloat16,
|
| 1385 |
+
cutlass.Float16,
|
| 1386 |
+
}
|
| 1387 |
+
or acc_dtype == cutlass.Int32
|
| 1388 |
+
and c_dtype
|
| 1389 |
+
not in {
|
| 1390 |
+
cutlass.BFloat16,
|
| 1391 |
+
cutlass.Float16,
|
| 1392 |
+
cutlass.Float32,
|
| 1393 |
+
cutlass.Int32,
|
| 1394 |
+
cutlass.Int8,
|
| 1395 |
+
cutlass.Uint8,
|
| 1396 |
+
}
|
| 1397 |
+
):
|
| 1398 |
+
is_valid = False
|
| 1399 |
+
return is_valid
|
| 1400 |
+
|
| 1401 |
+
@staticmethod
|
| 1402 |
+
def is_valid_mma_tiler_and_cluster_shape(
|
| 1403 |
+
use_2cta_instrs: bool,
|
| 1404 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1405 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1406 |
+
) -> bool:
|
| 1407 |
+
"""
|
| 1408 |
+
Check if the mma tiler and cluster shape are valid
|
| 1409 |
+
|
| 1410 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1411 |
+
:type use_2cta_instrs: bool
|
| 1412 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1413 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1414 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1415 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1416 |
+
|
| 1417 |
+
:return: True if the mma tiler and cluster shape are valid, False otherwise
|
| 1418 |
+
:rtype: bool
|
| 1419 |
+
"""
|
| 1420 |
+
is_valid = True
|
| 1421 |
+
# Skip invalid mma tile shape
|
| 1422 |
+
if not (
|
| 1423 |
+
(not use_2cta_instrs and mma_tiler_mn[0] in [64, 128])
|
| 1424 |
+
or (use_2cta_instrs and mma_tiler_mn[0] in [128, 256])
|
| 1425 |
+
):
|
| 1426 |
+
is_valid = False
|
| 1427 |
+
if mma_tiler_mn[1] not in range(32, 257, 32):
|
| 1428 |
+
is_valid = False
|
| 1429 |
+
# Skip illegal cluster shape
|
| 1430 |
+
if cluster_shape_mn[0] % (2 if use_2cta_instrs else 1) != 0:
|
| 1431 |
+
is_valid = False
|
| 1432 |
+
# Skip invalid cluster shape
|
| 1433 |
+
is_power_of_2 = lambda x: x > 0 and (x & (x - 1)) == 0
|
| 1434 |
+
if (
|
| 1435 |
+
cluster_shape_mn[0] * cluster_shape_mn[1] > 16
|
| 1436 |
+
or cluster_shape_mn[0] <= 0
|
| 1437 |
+
or cluster_shape_mn[1] <= 0
|
| 1438 |
+
or not is_power_of_2(cluster_shape_mn[0])
|
| 1439 |
+
or not is_power_of_2(cluster_shape_mn[1])
|
| 1440 |
+
):
|
| 1441 |
+
is_valid = False
|
| 1442 |
+
return is_valid
|
| 1443 |
+
|
| 1444 |
+
@staticmethod
|
| 1445 |
+
def is_valid_tensor_alignment(
|
| 1446 |
+
m: int,
|
| 1447 |
+
n: int,
|
| 1448 |
+
k: int,
|
| 1449 |
+
l: int,
|
| 1450 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1451 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1452 |
+
a_major: str,
|
| 1453 |
+
b_major: str,
|
| 1454 |
+
c_major: str,
|
| 1455 |
+
) -> bool:
|
| 1456 |
+
"""
|
| 1457 |
+
Check if the tensor alignment is valid
|
| 1458 |
+
|
| 1459 |
+
:param m: The number of rows in the A tensor
|
| 1460 |
+
:type m: int
|
| 1461 |
+
:param n: The number of columns in the B tensor
|
| 1462 |
+
:type n: int
|
| 1463 |
+
:param k: The number of columns in the A tensor
|
| 1464 |
+
:type k: int
|
| 1465 |
+
:param l: The number of columns in the C tensor
|
| 1466 |
+
:type l: int
|
| 1467 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1468 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1469 |
+
:param c_dtype: The data type of the output tensor
|
| 1470 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1471 |
+
:param a_major: The major axis of the A tensor
|
| 1472 |
+
:type a_major: str
|
| 1473 |
+
:param b_major: The major axis of the B tensor
|
| 1474 |
+
:type b_major: str
|
| 1475 |
+
:param c_major: The major axis of the C tensor
|
| 1476 |
+
:type c_major: str
|
| 1477 |
+
|
| 1478 |
+
:return: True if the problem shape is valid, False otherwise
|
| 1479 |
+
:rtype: bool
|
| 1480 |
+
"""
|
| 1481 |
+
is_valid = True
|
| 1482 |
+
|
| 1483 |
+
def check_contigous_16B_alignment(dtype, is_mode0_major, tensor_shape):
|
| 1484 |
+
major_mode_idx = 0 if is_mode0_major else 1
|
| 1485 |
+
num_major_elements = tensor_shape[major_mode_idx]
|
| 1486 |
+
num_contiguous_elements = 16 * 8 // dtype.width
|
| 1487 |
+
return num_major_elements % num_contiguous_elements == 0
|
| 1488 |
+
|
| 1489 |
+
if (
|
| 1490 |
+
not check_contigous_16B_alignment(ab_dtype, a_major == "m", (m, k, l))
|
| 1491 |
+
or not check_contigous_16B_alignment(ab_dtype, b_major == "n", (n, k, l))
|
| 1492 |
+
or not check_contigous_16B_alignment(c_dtype, c_major == "m", (m, n, l))
|
| 1493 |
+
):
|
| 1494 |
+
is_valid = False
|
| 1495 |
+
return is_valid
|
| 1496 |
+
|
| 1497 |
+
@staticmethod
|
| 1498 |
+
def is_valid_epilog_store_option(
|
| 1499 |
+
use_2cta_instrs: bool,
|
| 1500 |
+
use_tma_store: bool,
|
| 1501 |
+
m: int,
|
| 1502 |
+
n: int,
|
| 1503 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1504 |
+
) -> bool:
|
| 1505 |
+
"""
|
| 1506 |
+
Check if the epilogue store option is valid
|
| 1507 |
+
|
| 1508 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1509 |
+
:type use_2cta_instrs: bool
|
| 1510 |
+
:param use_tma_store: Whether to use TMA store
|
| 1511 |
+
:type use_tma_store: bool
|
| 1512 |
+
:param m: The number of rows in the A tensor
|
| 1513 |
+
:type m: int
|
| 1514 |
+
:param n: The number of columns in the B tensor
|
| 1515 |
+
:type n: int
|
| 1516 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1517 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1518 |
+
|
| 1519 |
+
:return: True if the epilogue store option is valid, False otherwise
|
| 1520 |
+
:rtype: bool
|
| 1521 |
+
"""
|
| 1522 |
+
|
| 1523 |
+
is_valid = True
|
| 1524 |
+
# None TMA store version does not have predication, can not support OOB tiles
|
| 1525 |
+
cta_tile_shape_mn = (
|
| 1526 |
+
mma_tiler_mn[0] // (2 if use_2cta_instrs else 1),
|
| 1527 |
+
mma_tiler_mn[1],
|
| 1528 |
+
)
|
| 1529 |
+
if not use_tma_store:
|
| 1530 |
+
if not (m % cta_tile_shape_mn[0] == 0 and n % cta_tile_shape_mn[1] == 0):
|
| 1531 |
+
is_valid = False
|
| 1532 |
+
return is_valid
|
| 1533 |
+
|
| 1534 |
+
@staticmethod
|
| 1535 |
+
def can_implement(
|
| 1536 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1537 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1538 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1539 |
+
use_2cta_instrs: bool,
|
| 1540 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1541 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1542 |
+
use_tma_store: bool,
|
| 1543 |
+
m: int,
|
| 1544 |
+
n: int,
|
| 1545 |
+
k: int,
|
| 1546 |
+
l: int,
|
| 1547 |
+
a_major: str,
|
| 1548 |
+
b_major: str,
|
| 1549 |
+
c_major: str,
|
| 1550 |
+
) -> bool:
|
| 1551 |
+
"""
|
| 1552 |
+
Check if the gemm can be implemented
|
| 1553 |
+
|
| 1554 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1555 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1556 |
+
:param acc_dtype: The data type of the accumulator
|
| 1557 |
+
:type acc_dtype: Type[cutlass.Numeric]
|
| 1558 |
+
:param c_dtype: The data type of the output tensor
|
| 1559 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1560 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1561 |
+
:type use_2cta_instrs: bool
|
| 1562 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1563 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1564 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1565 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1566 |
+
:param use_tma_store: Whether to use TMA store
|
| 1567 |
+
:type use_tma_store: bool
|
| 1568 |
+
:param m: The number of rows in the A tensor
|
| 1569 |
+
:type m: int
|
| 1570 |
+
:param n: The number of columns in the B tensor
|
| 1571 |
+
:type n: int
|
| 1572 |
+
:param k: The number of columns in the A tensor
|
| 1573 |
+
:type k: int
|
| 1574 |
+
:param l: The number of columns in the C tensor
|
| 1575 |
+
:type l: int
|
| 1576 |
+
:param a_major: The major axis of the A tensor
|
| 1577 |
+
:type a_major: str
|
| 1578 |
+
:param b_major: The major axis of the B tensor
|
| 1579 |
+
:type b_major: str
|
| 1580 |
+
:param c_major: The major axis of the C tensor
|
| 1581 |
+
:type c_major: str
|
| 1582 |
+
|
| 1583 |
+
:return: True if the gemm can be implemented, False otherwise
|
| 1584 |
+
:rtype: bool
|
| 1585 |
+
"""
|
| 1586 |
+
can_implement = True
|
| 1587 |
+
# Skip unsupported types
|
| 1588 |
+
if not DenseGemmKernel.is_valid_dtypes(ab_dtype, acc_dtype, c_dtype):
|
| 1589 |
+
can_implement = False
|
| 1590 |
+
# Skip invalid mma tile shape and cluster shape
|
| 1591 |
+
if not DenseGemmKernel.is_valid_mma_tiler_and_cluster_shape(
|
| 1592 |
+
use_2cta_instrs, mma_tiler_mn, cluster_shape_mn
|
| 1593 |
+
):
|
| 1594 |
+
can_implement = False
|
| 1595 |
+
# Skip illegal problem shape for load/store alignment
|
| 1596 |
+
if not DenseGemmKernel.is_valid_tensor_alignment(
|
| 1597 |
+
m, n, k, l, ab_dtype, c_dtype, a_major, b_major, c_major
|
| 1598 |
+
):
|
| 1599 |
+
can_implement = False
|
| 1600 |
+
# Skip invalid epilogue store option
|
| 1601 |
+
if not DenseGemmKernel.is_valid_epilog_store_option(
|
| 1602 |
+
use_2cta_instrs, use_tma_store, m, n, mma_tiler_mn
|
| 1603 |
+
):
|
| 1604 |
+
can_implement = False
|
| 1605 |
+
return can_implement
|
| 1606 |
+
|
| 1607 |
+
|
| 1608 |
+
def run_dense_gemm(
|
| 1609 |
+
mnkl: Tuple[int, int, int, int],
|
| 1610 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1611 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1612 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1613 |
+
a_major: str,
|
| 1614 |
+
b_major: str,
|
| 1615 |
+
c_major: str,
|
| 1616 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1617 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1618 |
+
use_2cta_instrs: bool,
|
| 1619 |
+
use_tma_store: bool,
|
| 1620 |
+
tolerance: float,
|
| 1621 |
+
warmup_iterations: int = 0,
|
| 1622 |
+
iterations: int = 1,
|
| 1623 |
+
skip_ref_check: bool = False,
|
| 1624 |
+
measure_launch_overhead=False,
|
| 1625 |
+
):
|
| 1626 |
+
"""
|
| 1627 |
+
Prepare A/B/C tensors, launch GPU kernel, and reference checking.
|
| 1628 |
+
"""
|
| 1629 |
+
print(f"Running B100 Dense GEMM test with:")
|
| 1630 |
+
print(f"mnkl: {mnkl}")
|
| 1631 |
+
print(f"AB dtype: {ab_dtype}, C dtype: {c_dtype}, Acc dtype: {acc_dtype}")
|
| 1632 |
+
print(f"Matrix majors - A: {a_major}, B: {b_major}, C: {c_major}")
|
| 1633 |
+
print(f"Mma Tiler (M, N): {mma_tiler_mn}, Cluster Shape (M, N): {cluster_shape_mn}")
|
| 1634 |
+
print(f"2CTA MMA instructions: {'True' if use_2cta_instrs else 'False'}")
|
| 1635 |
+
print(f"Use TMA Store: {'True' if use_tma_store else 'False'}")
|
| 1636 |
+
print(f"Tolerance: {tolerance}")
|
| 1637 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 1638 |
+
print(f"Iterations: {iterations}")
|
| 1639 |
+
print(f"Skip reference checking: {skip_ref_check}")
|
| 1640 |
+
|
| 1641 |
+
# Unpack parameters
|
| 1642 |
+
m, n, k, l = mnkl
|
| 1643 |
+
|
| 1644 |
+
# Skip unsupported testcase
|
| 1645 |
+
if not DenseGemmKernel.can_implement(
|
| 1646 |
+
ab_dtype,
|
| 1647 |
+
acc_dtype,
|
| 1648 |
+
c_dtype,
|
| 1649 |
+
use_2cta_instrs,
|
| 1650 |
+
mma_tiler_mn,
|
| 1651 |
+
cluster_shape_mn,
|
| 1652 |
+
use_tma_store,
|
| 1653 |
+
m,
|
| 1654 |
+
n,
|
| 1655 |
+
k,
|
| 1656 |
+
l,
|
| 1657 |
+
a_major,
|
| 1658 |
+
b_major,
|
| 1659 |
+
c_major,
|
| 1660 |
+
):
|
| 1661 |
+
raise TypeError(
|
| 1662 |
+
f"Unsupported testcase {ab_dtype}, {acc_dtype}, {c_dtype}, {use_2cta_instrs}, {mma_tiler_mn}, {cluster_shape_mn}, {use_tma_store}, {m}, {n}, {k}, {l}, {a_major}, {b_major}, {c_major}"
|
| 1663 |
+
)
|
| 1664 |
+
|
| 1665 |
+
if not torch.cuda.is_available():
|
| 1666 |
+
raise RuntimeError("GPU is required to run this example!")
|
| 1667 |
+
|
| 1668 |
+
torch.manual_seed(1111)
|
| 1669 |
+
|
| 1670 |
+
# Create and permute tensor A/B/C
|
| 1671 |
+
def create_and_permute_tensor(
|
| 1672 |
+
l, mode0, mode1, is_mode0_major, dtype, is_dynamic_layout=True
|
| 1673 |
+
):
|
| 1674 |
+
# is_mode0_major: (l, mode1, mode0) -> (mode0, mode1, l)
|
| 1675 |
+
# else: (l, mode0, mode1) -> (mode0, mode1, l)
|
| 1676 |
+
shape = (l, mode1, mode0) if is_mode0_major else (l, mode0, mode1)
|
| 1677 |
+
permute_order = (2, 1, 0) if is_mode0_major else (1, 2, 0)
|
| 1678 |
+
is_unsigned = dtype in {cutlass.Uint8}
|
| 1679 |
+
# Temporarily use uint8 as torch does not support fp8 type
|
| 1680 |
+
torch_dtype = (
|
| 1681 |
+
cutlass_torch.dtype(dtype)
|
| 1682 |
+
if dtype not in {cutlass.Float8E5M2, cutlass.Float8E4M3FN}
|
| 1683 |
+
else torch.uint8
|
| 1684 |
+
)
|
| 1685 |
+
|
| 1686 |
+
# Create dtype torch tensor (cpu)
|
| 1687 |
+
torch_tensor_cpu = cutlass_torch.create_and_permute_torch_tensor(
|
| 1688 |
+
shape,
|
| 1689 |
+
torch_dtype,
|
| 1690 |
+
permute_order=permute_order,
|
| 1691 |
+
init_type=cutlass_torch.TensorInitType.RANDOM,
|
| 1692 |
+
init_config=cutlass_torch.RandomInitConfig(
|
| 1693 |
+
min_val=0 if is_unsigned else -2, max_val=4 if is_unsigned else 2
|
| 1694 |
+
),
|
| 1695 |
+
)
|
| 1696 |
+
# Create dtype torch tensor (gpu)
|
| 1697 |
+
torch_tensor = torch_tensor_cpu.cuda()
|
| 1698 |
+
|
| 1699 |
+
# Create f32 torch tensor (cpu)
|
| 1700 |
+
f32_torch_tensor = torch_tensor_cpu.to(dtype=torch.float32)
|
| 1701 |
+
|
| 1702 |
+
# Create dtype cute tensor (gpu)
|
| 1703 |
+
cute_tensor = from_dlpack(torch_tensor, assumed_align=16)
|
| 1704 |
+
cute_tensor.element_type = dtype
|
| 1705 |
+
if is_dynamic_layout:
|
| 1706 |
+
cute_tensor = cute_tensor.mark_layout_dynamic(
|
| 1707 |
+
leading_dim=(0 if is_mode0_major else 1)
|
| 1708 |
+
)
|
| 1709 |
+
cute_tensor = cutlass_torch.convert_cute_tensor(
|
| 1710 |
+
f32_torch_tensor,
|
| 1711 |
+
cute_tensor,
|
| 1712 |
+
dtype,
|
| 1713 |
+
is_dynamic_layout=is_dynamic_layout,
|
| 1714 |
+
)
|
| 1715 |
+
|
| 1716 |
+
return f32_torch_tensor, cute_tensor, torch_tensor
|
| 1717 |
+
|
| 1718 |
+
a_ref, a_tensor, a_torch = create_and_permute_tensor(
|
| 1719 |
+
l, m, k, a_major == "m", ab_dtype, is_dynamic_layout=True
|
| 1720 |
+
)
|
| 1721 |
+
b_ref, b_tensor, b_torch = create_and_permute_tensor(
|
| 1722 |
+
l, n, k, b_major == "n", ab_dtype, is_dynamic_layout=True
|
| 1723 |
+
)
|
| 1724 |
+
c_ref, c_tensor, c_torch = create_and_permute_tensor(
|
| 1725 |
+
l, m, n, c_major == "m", c_dtype, is_dynamic_layout=True
|
| 1726 |
+
)
|
| 1727 |
+
|
| 1728 |
+
# Configure gemm kernel
|
| 1729 |
+
gemm = DenseGemmKernel(
|
| 1730 |
+
acc_dtype,
|
| 1731 |
+
use_2cta_instrs,
|
| 1732 |
+
mma_tiler_mn,
|
| 1733 |
+
cluster_shape_mn,
|
| 1734 |
+
use_tma_store,
|
| 1735 |
+
)
|
| 1736 |
+
|
| 1737 |
+
torch_stream = torch.cuda.Stream()
|
| 1738 |
+
stream = cuda.CUstream(torch_stream.cuda_stream)
|
| 1739 |
+
# Compile gemm kernel
|
| 1740 |
+
compiled_gemm = cute.compile(gemm, a_tensor, b_tensor, c_tensor, stream)
|
| 1741 |
+
|
| 1742 |
+
# Launch GPU kernel
|
| 1743 |
+
# Warm up
|
| 1744 |
+
for i in range(warmup_iterations):
|
| 1745 |
+
compiled_gemm(a_tensor, b_tensor, c_tensor, stream)
|
| 1746 |
+
# Execution
|
| 1747 |
+
for i in range(iterations):
|
| 1748 |
+
compiled_gemm(a_tensor, b_tensor, c_tensor, stream)
|
| 1749 |
+
|
| 1750 |
+
# Compute reference result
|
| 1751 |
+
if not skip_ref_check:
|
| 1752 |
+
if ab_dtype in {
|
| 1753 |
+
cutlass.Int8,
|
| 1754 |
+
cutlass.Uint8,
|
| 1755 |
+
cutlass.Float8E4M3FN,
|
| 1756 |
+
cutlass.Float8E5M2,
|
| 1757 |
+
}:
|
| 1758 |
+
ref = torch.einsum("mkl,nkl->mnl", a_ref.cpu(), b_ref.cpu())
|
| 1759 |
+
else:
|
| 1760 |
+
ref = (torch.einsum("mkl,nkl->mnl", a_ref, b_ref)).cpu()
|
| 1761 |
+
|
| 1762 |
+
# Copy gpu result back
|
| 1763 |
+
gpu_c = c_torch.cpu()
|
| 1764 |
+
|
| 1765 |
+
# Convert ref to c_type
|
| 1766 |
+
if c_dtype == cutlass.Float32:
|
| 1767 |
+
ref_c = ref
|
| 1768 |
+
elif c_dtype in {cutlass.Float8E5M2, cutlass.Float8E4M3FN}:
|
| 1769 |
+
# m major: (l, n, m) -> (m, n, l)
|
| 1770 |
+
# n major: (l, m, n) -> (m, n, l)
|
| 1771 |
+
permute_order = (1, 2, 0) if c_major == "n" else (2, 1, 0)
|
| 1772 |
+
shape = (l, m, n) if c_major == "n" else (l, n, m)
|
| 1773 |
+
f8_torch_tensor = cutlass_torch.create_and_permute_torch_tensor(
|
| 1774 |
+
shape,
|
| 1775 |
+
torch.uint8,
|
| 1776 |
+
permute_order=permute_order,
|
| 1777 |
+
init_type=cutlass_torch.TensorInitType.SKIP,
|
| 1778 |
+
).cuda()
|
| 1779 |
+
# Create dtype cute tensor (gpu)
|
| 1780 |
+
ref_c_tensor = from_dlpack(
|
| 1781 |
+
f8_torch_tensor, assumed_align=16
|
| 1782 |
+
).mark_layout_dynamic(leading_dim=(1 if c_major == "n" else 0))
|
| 1783 |
+
ref_c_tensor.element_type = c_dtype
|
| 1784 |
+
ref_c_tensor = cutlass_torch.convert_cute_tensor(
|
| 1785 |
+
ref,
|
| 1786 |
+
ref_c_tensor,
|
| 1787 |
+
c_dtype,
|
| 1788 |
+
is_dynamic_layout=True,
|
| 1789 |
+
)
|
| 1790 |
+
|
| 1791 |
+
ref_c = f8_torch_tensor.cpu()
|
| 1792 |
+
else:
|
| 1793 |
+
ref_c = ref.to(cutlass_torch.dtype(c_dtype))
|
| 1794 |
+
|
| 1795 |
+
# Reference checking ref_c and gpu_c
|
| 1796 |
+
torch.testing.assert_close(
|
| 1797 |
+
gpu_c,
|
| 1798 |
+
ref_c,
|
| 1799 |
+
atol=tolerance,
|
| 1800 |
+
rtol=1e-05,
|
| 1801 |
+
)
|
| 1802 |
+
|
| 1803 |
+
|
| 1804 |
+
if __name__ == "__main__":
|
| 1805 |
+
|
| 1806 |
+
def parse_comma_separated_ints(s: str) -> Tuple[int, ...]:
|
| 1807 |
+
try:
|
| 1808 |
+
return tuple(int(x.strip()) for x in s.split(","))
|
| 1809 |
+
# or: return tuple([int(x.strip()) for x in s.split(",")])
|
| 1810 |
+
except ValueError:
|
| 1811 |
+
raise argparse.ArgumentTypeError(
|
| 1812 |
+
"Invalid format. Expected comma-separated integers."
|
| 1813 |
+
)
|
| 1814 |
+
|
| 1815 |
+
parser = argparse.ArgumentParser(
|
| 1816 |
+
description="Example of MxNxKxL GEMM on Blackwell."
|
| 1817 |
+
)
|
| 1818 |
+
|
| 1819 |
+
parser.add_argument(
|
| 1820 |
+
"--mnkl",
|
| 1821 |
+
type=parse_comma_separated_ints,
|
| 1822 |
+
default=(256, 256, 512, 1),
|
| 1823 |
+
help="mnkl dimensions (comma-separated)",
|
| 1824 |
+
)
|
| 1825 |
+
parser.add_argument(
|
| 1826 |
+
"--mma_tiler_mn",
|
| 1827 |
+
type=parse_comma_separated_ints,
|
| 1828 |
+
default=(128, 128),
|
| 1829 |
+
help="Mma tiler (comma-separated)",
|
| 1830 |
+
)
|
| 1831 |
+
parser.add_argument(
|
| 1832 |
+
"--cluster_shape_mn",
|
| 1833 |
+
type=parse_comma_separated_ints,
|
| 1834 |
+
default=(1, 1),
|
| 1835 |
+
help="Cluster shape (comma-separated)",
|
| 1836 |
+
)
|
| 1837 |
+
parser.add_argument("--ab_dtype", type=cutlass.dtype, default=cutlass.TFloat32)
|
| 1838 |
+
parser.add_argument("--c_dtype", type=cutlass.dtype, default=cutlass.Float32)
|
| 1839 |
+
parser.add_argument("--acc_dtype", type=cutlass.dtype, default=cutlass.Float32)
|
| 1840 |
+
parser.add_argument(
|
| 1841 |
+
"--use_2cta_instrs",
|
| 1842 |
+
action="store_true",
|
| 1843 |
+
help="Enable 2CTA MMA instructions feature",
|
| 1844 |
+
)
|
| 1845 |
+
parser.add_argument("--a_major", choices=["k", "m"], type=str, default="k")
|
| 1846 |
+
parser.add_argument("--b_major", choices=["k", "n"], type=str, default="k")
|
| 1847 |
+
parser.add_argument("--c_major", choices=["n", "m"], type=str, default="n")
|
| 1848 |
+
parser.add_argument(
|
| 1849 |
+
"--use_tma_store", action="store_true", help="Use tma store or not"
|
| 1850 |
+
)
|
| 1851 |
+
parser.add_argument(
|
| 1852 |
+
"--tolerance", type=float, default=1e-01, help="Tolerance for validation"
|
| 1853 |
+
)
|
| 1854 |
+
parser.add_argument(
|
| 1855 |
+
"--warmup_iterations", type=int, default=0, help="Warmup iterations"
|
| 1856 |
+
)
|
| 1857 |
+
parser.add_argument("--iterations", type=int, default=1, help="Iterations")
|
| 1858 |
+
parser.add_argument(
|
| 1859 |
+
"--skip_ref_check", action="store_true", help="Skip reference checking"
|
| 1860 |
+
)
|
| 1861 |
+
|
| 1862 |
+
args = parser.parse_args()
|
| 1863 |
+
|
| 1864 |
+
if len(args.mnkl) != 4:
|
| 1865 |
+
parser.error("--mnkl must contain exactly 4 values")
|
| 1866 |
+
|
| 1867 |
+
if len(args.mma_tiler_mn) != 2:
|
| 1868 |
+
parser.error("--mma_tiler_mn must contain exactly 2 values")
|
| 1869 |
+
|
| 1870 |
+
if len(args.cluster_shape_mn) != 2:
|
| 1871 |
+
parser.error("--cluster_shape_mn must contain exactly 2 values")
|
| 1872 |
+
|
| 1873 |
+
run_dense_gemm(
|
| 1874 |
+
args.mnkl,
|
| 1875 |
+
args.ab_dtype,
|
| 1876 |
+
args.c_dtype,
|
| 1877 |
+
args.acc_dtype,
|
| 1878 |
+
args.a_major,
|
| 1879 |
+
args.b_major,
|
| 1880 |
+
args.c_major,
|
| 1881 |
+
args.mma_tiler_mn,
|
| 1882 |
+
args.cluster_shape_mn,
|
| 1883 |
+
args.use_2cta_instrs,
|
| 1884 |
+
args.use_tma_store,
|
| 1885 |
+
args.tolerance,
|
| 1886 |
+
args.warmup_iterations,
|
| 1887 |
+
args.iterations,
|
| 1888 |
+
args.skip_ref_check,
|
| 1889 |
+
)
|
| 1890 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_gemm_persistent.py
ADDED
|
@@ -0,0 +1,2193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
from typing import Optional, Type, Tuple, Union
|
| 31 |
+
|
| 32 |
+
import cuda.bindings.driver as cuda
|
| 33 |
+
import torch
|
| 34 |
+
|
| 35 |
+
import cutlass
|
| 36 |
+
import cutlass.cute as cute
|
| 37 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 38 |
+
import cutlass.torch as cutlass_torch
|
| 39 |
+
import cutlass.utils as utils
|
| 40 |
+
import cutlass.pipeline as pipeline
|
| 41 |
+
import cutlass.cute.testing as testing
|
| 42 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 43 |
+
from cutlass.cute.runtime import from_dlpack
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
"""
|
| 47 |
+
A high-performance persistent batched dense GEMM example for the NVIDIA Blackwell SM100 architecture
|
| 48 |
+
using CUTE DSL.
|
| 49 |
+
- Matrix A is MxKxL, L is batch dimension, A can be row-major("K") or column-major("M")
|
| 50 |
+
- Matrix B is NxKxL, L is batch dimension, B can be row-major("N") or column-major("K")
|
| 51 |
+
- Matrix C is MxNxL, L is batch dimension, C can be row-major("N") or column-major("M")
|
| 52 |
+
|
| 53 |
+
This GEMM kernel supports the following features:
|
| 54 |
+
- Utilizes Tensor Memory Access (TMA) for efficient memory operations
|
| 55 |
+
- Utilizes Blackwell's tcgen05.mma for matrix multiply-accumulate (MMA) operations (including 2cta mma instructions)
|
| 56 |
+
- Implements TMA multicast with cluster to reduce L2 memory traffic
|
| 57 |
+
- Support persistent tile scheduling to better overlap memory load/store with mma between tiles
|
| 58 |
+
- Support warp specialization to avoid explicit pipelining between mainloop load and mma
|
| 59 |
+
|
| 60 |
+
This GEMM works as follows:
|
| 61 |
+
1. DMA warp: Load A and B matrices from global memory (GMEM) to shared memory (SMEM) using TMA operations.
|
| 62 |
+
2. MMA warp: Perform matrix multiply-accumulate (MMA) operations using tcgen05.mma instruction.
|
| 63 |
+
3. EPILOGUE warp:
|
| 64 |
+
- Load completed accumulator from tensor memory (TMEM) to registers (RMEM) using tcgen05.ld.
|
| 65 |
+
- Type convert C matrix to output type.
|
| 66 |
+
- Optionally store C matrix from registers (RMEM) to shared memory (SMEM) to global memory (GMEM) with TMA operations,
|
| 67 |
+
or directly store C matrix from registers (RMEM) to global memory (GMEM) without TMA operations.
|
| 68 |
+
- Optionally accept an elementwise lambda function epilogue_op to apply to the output tensor:
|
| 69 |
+
e.g., relu can set epilogue_op = lambda x: cute.where(x > 0, x, cute.full_like(x, 0))
|
| 70 |
+
|
| 71 |
+
SM100 tcgen05.mma instructions operate as follows:
|
| 72 |
+
- Read matrix A from SMEM
|
| 73 |
+
- Read matrix B from SMEM
|
| 74 |
+
- Write accumulator to TMEM
|
| 75 |
+
The accumulator in TMEM must then be loaded to registers before writing back to GMEM.
|
| 76 |
+
|
| 77 |
+
Input arguments to this example is same as dense_gemm.py.
|
| 78 |
+
|
| 79 |
+
.. code-block:: bash
|
| 80 |
+
|
| 81 |
+
python examples/blackwell/dense_gemm_persistent.py \
|
| 82 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 83 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 84 |
+
--mnkl 8192,8192,8192,1 \
|
| 85 |
+
--use_tma_store --use_2cta_instrs
|
| 86 |
+
|
| 87 |
+
To collect performance with NCU profiler:
|
| 88 |
+
|
| 89 |
+
.. code-block:: bash
|
| 90 |
+
|
| 91 |
+
ncu python examples/blackwell/dense_gemm_persistent.py \
|
| 92 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 93 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 94 |
+
--mnkl 8192,8192,8192,1 \
|
| 95 |
+
--use_tma_store --use_2cta_instrs \
|
| 96 |
+
--warmup_iterations 1 --iterations 10 --skip_ref_check
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
Constraints are same as dense_gemm.py:
|
| 100 |
+
* Supported input data types: fp16, bf16, tf32, int8, uint8, fp8 (e4m3fn, e5m2),
|
| 101 |
+
see detailed valid dtype combinations in below PersistentDenseGemmKernel class documentation
|
| 102 |
+
* A/B tensor must have the same data type
|
| 103 |
+
* Mma tiler M must be 64/128 (use_2cta_instrs=False) or 128/256 (use_2cta_instrs=True)
|
| 104 |
+
* Mma tiler N must be 32-256, step 32
|
| 105 |
+
* Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 106 |
+
* Cluster shape M must be multiple of 2 if use_2cta_instrs=True
|
| 107 |
+
* The contiguous dimension of A/B/C tensors must be at least 16 bytes aligned,
|
| 108 |
+
i.e, number of elements is a multiple of 4, 8, and 16 for TFloat32,
|
| 109 |
+
Float16/BFloat16, and Int8/Uint8/Float8, respectively.
|
| 110 |
+
* OOB tiles are not allowed when TMA store is disabled
|
| 111 |
+
"""
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class PersistentDenseGemmKernel:
|
| 115 |
+
"""This class implements batched matrix multiplication (C = A x B) with support for various data types
|
| 116 |
+
and architectural features specific to Blackwell GPUs with persistent tile scheduling and warp specialization.
|
| 117 |
+
|
| 118 |
+
:param acc_dtype: Data type for accumulation during computation
|
| 119 |
+
:type acc_dtype: type[cutlass.Numeric]
|
| 120 |
+
:param use_2cta_instrs: Whether to use CTA group 2 for advanced thread cooperation
|
| 121 |
+
:type use_2cta_instrs: bool
|
| 122 |
+
:param mma_tiler_mn: Shape of the Matrix Multiply-Accumulate (MMA) tile (M,N)
|
| 123 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 124 |
+
:param cluster_shape_mn: Cluster dimensions (M,N) for parallel processing
|
| 125 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 126 |
+
:param use_tma_store: Whether to use Tensor Memory Access (TMA) for storing results
|
| 127 |
+
:type use_tma_store: bool
|
| 128 |
+
|
| 129 |
+
:note: In current version, A and B tensor must have the same data type
|
| 130 |
+
- i.e., Float8E4M3FN for A and Float8E5M2 for B is not supported
|
| 131 |
+
|
| 132 |
+
:note: Supported A/B data types:
|
| 133 |
+
- TFloat32
|
| 134 |
+
- Float16/BFloat16
|
| 135 |
+
- Int8/Uint8
|
| 136 |
+
- Float8E4M3FN/Float8E5M2
|
| 137 |
+
|
| 138 |
+
:note: Supported accumulator data types:
|
| 139 |
+
- Float32 (for all floating point A/B data types)
|
| 140 |
+
- Float16 (only for fp16 and fp8 A/B data types)
|
| 141 |
+
- Int32 (only for uint8/int8 A/B data types)
|
| 142 |
+
|
| 143 |
+
:note: Supported C data types:
|
| 144 |
+
- Float32 (for float32 and int32 accumulator data types)
|
| 145 |
+
- Int32 (for float32 and int32 accumulator data types)
|
| 146 |
+
- Float16/BFloat16 (for fp16 and fp8 accumulator data types)
|
| 147 |
+
- Int8/Uint8 (for uint8/int8 accumulator data types)
|
| 148 |
+
- Float8E4M3FN/Float8E5M2 (for float32 accumulator data types)
|
| 149 |
+
|
| 150 |
+
:note: Constraints:
|
| 151 |
+
- MMA tiler M must be 64/128 (use_2cta_instrs=False) or 128/256 (use_2cta_instrs=True)
|
| 152 |
+
- MMA tiler N must be 32-256, step 32
|
| 153 |
+
- Cluster shape M must be multiple of 2 if use_2cta_instrs=True
|
| 154 |
+
- Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 155 |
+
|
| 156 |
+
Example:
|
| 157 |
+
>>> gemm = PersistentDenseGemmKernel(
|
| 158 |
+
... acc_dtype=cutlass.Float32,
|
| 159 |
+
... use_2cta_instrs=True,
|
| 160 |
+
... mma_tiler_mn=(128, 128),
|
| 161 |
+
... cluster_shape_mn=(2, 2)
|
| 162 |
+
... )
|
| 163 |
+
>>> gemm(a_tensor, b_tensor, c_tensor, max_active_clusters, stream)
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(
|
| 167 |
+
self,
|
| 168 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 169 |
+
use_2cta_instrs: bool,
|
| 170 |
+
mma_tiler_mn: Tuple[int, int],
|
| 171 |
+
cluster_shape_mn: Tuple[int, int],
|
| 172 |
+
use_tma_store: bool,
|
| 173 |
+
):
|
| 174 |
+
"""Initializes the configuration for a Blackwell dense GEMM kernel.
|
| 175 |
+
|
| 176 |
+
This configuration includes several key aspects:
|
| 177 |
+
|
| 178 |
+
1. MMA Instruction Settings (tcgen05):
|
| 179 |
+
- acc_dtype: Data types for MMA accumulator.
|
| 180 |
+
- mma_tiler_mn: The (M, N) shape of the MMA instruction tiler.
|
| 181 |
+
- use_2cta_instrs: Boolean indicating if the tcgen05 MMA variant
|
| 182 |
+
with cta_group=2 should be used.
|
| 183 |
+
|
| 184 |
+
2. Cluster Shape:
|
| 185 |
+
- cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster.
|
| 186 |
+
|
| 187 |
+
3. Output C tensor store mode:
|
| 188 |
+
- use_tma_store: Boolean indicating whether to use Tensor Memory Access (TMA) for storing results.
|
| 189 |
+
|
| 190 |
+
:param acc_dtype: Data type of the accumulator.
|
| 191 |
+
:type acc_dtype: type[cutlass.Numeric]
|
| 192 |
+
:param mma_tiler_mn: Tuple (M, N) shape of the MMA instruction.
|
| 193 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 194 |
+
:param use_2cta_instrs: Boolean, True to use cta_group=2 MMA variant.
|
| 195 |
+
:type use_2cta_instrs: bool
|
| 196 |
+
:param cluster_shape_mn: Tuple (ClusterM, ClusterN) shape of the cluster.
|
| 197 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 198 |
+
:param use_tma_store: Use Tensor Memory Access (TMA) or normal store for output C tensor.
|
| 199 |
+
:type use_tma_store: bool
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
self.acc_dtype: Type[cutlass.Numeric] = acc_dtype
|
| 203 |
+
self.use_2cta_instrs = use_2cta_instrs
|
| 204 |
+
self.cluster_shape_mn = cluster_shape_mn
|
| 205 |
+
# K dimension is deferred in _setup_attributes
|
| 206 |
+
self.mma_tiler = (*mma_tiler_mn, 1)
|
| 207 |
+
self.use_tma_store = use_tma_store
|
| 208 |
+
|
| 209 |
+
self.cta_group = (
|
| 210 |
+
tcgen05.CtaGroup.TWO if use_2cta_instrs else tcgen05.CtaGroup.ONE
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
self.occupancy = 1
|
| 214 |
+
# Set specialized warp ids
|
| 215 |
+
self.epilog_warp_id = (
|
| 216 |
+
0,
|
| 217 |
+
1,
|
| 218 |
+
2,
|
| 219 |
+
3,
|
| 220 |
+
)
|
| 221 |
+
self.mma_warp_id = 4
|
| 222 |
+
self.tma_warp_id = 5
|
| 223 |
+
self.threads_per_cta = 32 * len(
|
| 224 |
+
(self.mma_warp_id, self.tma_warp_id, *self.epilog_warp_id)
|
| 225 |
+
)
|
| 226 |
+
# Set barrier id for cta sync, epilogue sync and tmem ptr sync
|
| 227 |
+
self.cta_sync_bar_id = 0
|
| 228 |
+
self.epilog_sync_bar_id = 1
|
| 229 |
+
self.tmem_ptr_sync_bar_id = 2
|
| 230 |
+
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
| 231 |
+
|
| 232 |
+
def _setup_attributes(self):
|
| 233 |
+
"""Set up configurations that are dependent on GEMM inputs
|
| 234 |
+
|
| 235 |
+
This method configures various attributes based on the input tensor properties
|
| 236 |
+
(data types, leading dimensions) and kernel settings:
|
| 237 |
+
- Configuring tiled MMA
|
| 238 |
+
- Computing MMA/cluster/tile shapes
|
| 239 |
+
- Computing cluster layout
|
| 240 |
+
- Computing multicast CTAs for A/B
|
| 241 |
+
- Computing epilogue subtile
|
| 242 |
+
- Setting up A/B/C stage counts in shared memory
|
| 243 |
+
- Computing A/B/C shared memory layout
|
| 244 |
+
- Computing tensor memory allocation columns
|
| 245 |
+
"""
|
| 246 |
+
# Configure tiled mma
|
| 247 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 248 |
+
self.a_dtype,
|
| 249 |
+
self.a_major_mode,
|
| 250 |
+
self.b_major_mode,
|
| 251 |
+
self.acc_dtype,
|
| 252 |
+
self.cta_group,
|
| 253 |
+
self.mma_tiler[:2],
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
# Compute mma/cluster/tile shapes
|
| 257 |
+
mma_inst_shape_k = cute.size(tiled_mma.shape_mnk, mode=[2])
|
| 258 |
+
mma_inst_tile_k = 4
|
| 259 |
+
self.mma_tiler = (
|
| 260 |
+
self.mma_tiler[0],
|
| 261 |
+
self.mma_tiler[1],
|
| 262 |
+
mma_inst_shape_k * mma_inst_tile_k,
|
| 263 |
+
)
|
| 264 |
+
self.cta_tile_shape_mnk = (
|
| 265 |
+
self.mma_tiler[0] // cute.size(tiled_mma.thr_id.shape),
|
| 266 |
+
self.mma_tiler[1],
|
| 267 |
+
self.mma_tiler[2],
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
# Compute cluster layout
|
| 271 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 272 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 273 |
+
(tiled_mma.thr_id.shape,),
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
# Compute number of multicast CTAs for A/B
|
| 277 |
+
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
| 278 |
+
self.num_mcast_ctas_b = cute.size(self.cluster_layout_vmnk.shape[1])
|
| 279 |
+
self.is_a_mcast = self.num_mcast_ctas_a > 1
|
| 280 |
+
self.is_b_mcast = self.num_mcast_ctas_b > 1
|
| 281 |
+
|
| 282 |
+
# Compute epilogue subtile
|
| 283 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 284 |
+
self.epi_tile = sm100_utils.compute_epilogue_tile_shape(
|
| 285 |
+
self.cta_tile_shape_mnk,
|
| 286 |
+
self.use_2cta_instrs,
|
| 287 |
+
self.c_layout,
|
| 288 |
+
self.c_dtype,
|
| 289 |
+
)
|
| 290 |
+
else:
|
| 291 |
+
self.epi_tile = self.cta_tile_shape_mnk[:2]
|
| 292 |
+
|
| 293 |
+
# Setup A/B/C stage count in shared memory and ACC stage count in tensor memory
|
| 294 |
+
self.num_acc_stage, self.num_ab_stage, self.num_c_stage = self._compute_stages(
|
| 295 |
+
tiled_mma,
|
| 296 |
+
self.mma_tiler,
|
| 297 |
+
self.a_dtype,
|
| 298 |
+
self.b_dtype,
|
| 299 |
+
self.epi_tile,
|
| 300 |
+
self.c_dtype,
|
| 301 |
+
self.c_layout,
|
| 302 |
+
self.smem_capacity,
|
| 303 |
+
self.occupancy,
|
| 304 |
+
self.use_tma_store,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
# Compute A/B/C shared memory layout
|
| 308 |
+
self.a_smem_layout_staged = sm100_utils.make_smem_layout_a(
|
| 309 |
+
tiled_mma,
|
| 310 |
+
self.mma_tiler,
|
| 311 |
+
self.a_dtype,
|
| 312 |
+
self.num_ab_stage,
|
| 313 |
+
)
|
| 314 |
+
self.b_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
| 315 |
+
tiled_mma,
|
| 316 |
+
self.mma_tiler,
|
| 317 |
+
self.b_dtype,
|
| 318 |
+
self.num_ab_stage,
|
| 319 |
+
)
|
| 320 |
+
self.c_smem_layout_staged = (
|
| 321 |
+
sm100_utils.make_smem_layout_epi(
|
| 322 |
+
self.c_dtype,
|
| 323 |
+
self.c_layout,
|
| 324 |
+
self.epi_tile,
|
| 325 |
+
self.num_c_stage,
|
| 326 |
+
)
|
| 327 |
+
if cutlass.const_expr(self.use_tma_store)
|
| 328 |
+
else None
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
# Compute the number of tensor memory allocation columns
|
| 332 |
+
self.num_tmem_alloc_cols = self._compute_num_tmem_alloc_cols(
|
| 333 |
+
tiled_mma, self.mma_tiler, self.num_acc_stage
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
@cute.jit
|
| 337 |
+
def __call__(
|
| 338 |
+
self,
|
| 339 |
+
a: cute.Tensor,
|
| 340 |
+
b: cute.Tensor,
|
| 341 |
+
c: cute.Tensor,
|
| 342 |
+
max_active_clusters: cutlass.Constexpr,
|
| 343 |
+
stream: cuda.CUstream,
|
| 344 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 345 |
+
):
|
| 346 |
+
"""Execute the GEMM operation in steps:
|
| 347 |
+
- Setup static attributes before smem/grid/tma computation
|
| 348 |
+
- Setup TMA load/store atoms and tensors
|
| 349 |
+
- Compute grid size with regard to hardware constraints
|
| 350 |
+
- Define shared storage for kernel
|
| 351 |
+
- Launch the kernel synchronously
|
| 352 |
+
|
| 353 |
+
:param a: Input tensor A
|
| 354 |
+
:type a: cute.Tensor
|
| 355 |
+
:param b: Input tensor B
|
| 356 |
+
:type b: cute.Tensor
|
| 357 |
+
:param c: Output tensor C
|
| 358 |
+
:type c: cute.Tensor
|
| 359 |
+
:param max_active_clusters: Maximum number of active clusters
|
| 360 |
+
:type max_active_clusters: cutlass.Constexpr
|
| 361 |
+
:param stream: CUDA stream for asynchronous execution
|
| 362 |
+
:type stream: cuda.CUstream
|
| 363 |
+
:param epilogue_op: Optional elementwise lambda function to apply to the output tensor
|
| 364 |
+
:type epilogue_op: cutlass.Constexpr
|
| 365 |
+
:raises TypeError: If input data types are incompatible with the MMA instruction.
|
| 366 |
+
:raises AssertionError: If OOB (Out-Of-Bounds) tiles are present when TMA store is disabled.
|
| 367 |
+
"""
|
| 368 |
+
# Setup static attributes before smem/grid/tma computation
|
| 369 |
+
self.a_dtype: Type[cutlass.Numeric] = a.element_type
|
| 370 |
+
self.b_dtype: Type[cutlass.Numeric] = b.element_type
|
| 371 |
+
self.c_dtype: Type[cutlass.Numeric] = c.element_type
|
| 372 |
+
self.a_major_mode = utils.LayoutEnum.from_tensor(a).mma_major_mode()
|
| 373 |
+
self.b_major_mode = utils.LayoutEnum.from_tensor(b).mma_major_mode()
|
| 374 |
+
self.c_layout = utils.LayoutEnum.from_tensor(c)
|
| 375 |
+
|
| 376 |
+
# Check if input data types are compatible with MMA instruction
|
| 377 |
+
if cutlass.const_expr(self.a_dtype != self.b_dtype):
|
| 378 |
+
raise TypeError(f"Type must match: {self.a_dtype} != {self.b_dtype}")
|
| 379 |
+
|
| 380 |
+
# Setup attributes that dependent on gemm inputs
|
| 381 |
+
self._setup_attributes()
|
| 382 |
+
|
| 383 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 384 |
+
self.a_dtype,
|
| 385 |
+
self.a_major_mode,
|
| 386 |
+
self.b_major_mode,
|
| 387 |
+
self.acc_dtype,
|
| 388 |
+
self.cta_group,
|
| 389 |
+
self.mma_tiler[:2],
|
| 390 |
+
)
|
| 391 |
+
atom_thr_size = cute.size(tiled_mma.thr_id.shape)
|
| 392 |
+
|
| 393 |
+
# Setup TMA load for A
|
| 394 |
+
a_op = sm100_utils.cluster_shape_to_tma_atom_A(
|
| 395 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 396 |
+
)
|
| 397 |
+
a_smem_layout = cute.slice_(self.a_smem_layout_staged, (None, None, None, 0))
|
| 398 |
+
tma_atom_a, tma_tensor_a = cute.nvgpu.make_tiled_tma_atom_A(
|
| 399 |
+
a_op,
|
| 400 |
+
a,
|
| 401 |
+
a_smem_layout,
|
| 402 |
+
self.mma_tiler,
|
| 403 |
+
tiled_mma,
|
| 404 |
+
self.cluster_layout_vmnk.shape,
|
| 405 |
+
internal_type=(
|
| 406 |
+
cutlass.TFloat32 if a.element_type is cutlass.Float32 else None
|
| 407 |
+
),
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
# Setup TMA load for B
|
| 411 |
+
b_op = sm100_utils.cluster_shape_to_tma_atom_B(
|
| 412 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 413 |
+
)
|
| 414 |
+
b_smem_layout = cute.slice_(self.b_smem_layout_staged, (None, None, None, 0))
|
| 415 |
+
tma_atom_b, tma_tensor_b = cute.nvgpu.make_tiled_tma_atom_B(
|
| 416 |
+
b_op,
|
| 417 |
+
b,
|
| 418 |
+
b_smem_layout,
|
| 419 |
+
self.mma_tiler,
|
| 420 |
+
tiled_mma,
|
| 421 |
+
self.cluster_layout_vmnk.shape,
|
| 422 |
+
internal_type=(
|
| 423 |
+
cutlass.TFloat32 if b.element_type is cutlass.Float32 else None
|
| 424 |
+
),
|
| 425 |
+
)
|
| 426 |
+
|
| 427 |
+
a_copy_size = cute.size_in_bytes(self.a_dtype, a_smem_layout)
|
| 428 |
+
b_copy_size = cute.size_in_bytes(self.b_dtype, b_smem_layout)
|
| 429 |
+
self.num_tma_load_bytes = (a_copy_size + b_copy_size) * atom_thr_size
|
| 430 |
+
|
| 431 |
+
# Setup TMA store for C
|
| 432 |
+
tma_atom_c = None
|
| 433 |
+
tma_tensor_c = None
|
| 434 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 435 |
+
c_cta_v_layout = cute.composition(
|
| 436 |
+
cute.make_identity_layout(c.shape), self.epi_tile
|
| 437 |
+
)
|
| 438 |
+
epi_smem_layout = cute.slice_(self.c_smem_layout_staged, (None, None, 0))
|
| 439 |
+
tma_atom_c, tma_tensor_c = cpasync.make_tiled_tma_atom(
|
| 440 |
+
cpasync.CopyBulkTensorTileS2GOp(),
|
| 441 |
+
c,
|
| 442 |
+
epi_smem_layout,
|
| 443 |
+
c_cta_v_layout,
|
| 444 |
+
)
|
| 445 |
+
|
| 446 |
+
# Compute grid size
|
| 447 |
+
self.tile_sched_params, grid = self._compute_grid(
|
| 448 |
+
c, self.cta_tile_shape_mnk, self.cluster_shape_mn, max_active_clusters
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
self.buffer_align_bytes = 1024
|
| 452 |
+
|
| 453 |
+
c_smem_size = (
|
| 454 |
+
cute.cosize(self.c_smem_layout_staged.outer)
|
| 455 |
+
if cutlass.const_expr(self.use_tma_store)
|
| 456 |
+
else 0
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
# Define shared storage for kernel
|
| 460 |
+
@cute.struct
|
| 461 |
+
class SharedStorage:
|
| 462 |
+
ab_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 463 |
+
ab_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 464 |
+
acc_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 465 |
+
acc_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 466 |
+
tmem_dealloc_mbar_ptr: cutlass.Int64
|
| 467 |
+
tmem_holding_buf: cutlass.Int32
|
| 468 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 469 |
+
sC: cute.struct.Align[
|
| 470 |
+
cute.struct.MemRange[
|
| 471 |
+
self.c_dtype,
|
| 472 |
+
c_smem_size,
|
| 473 |
+
],
|
| 474 |
+
self.buffer_align_bytes,
|
| 475 |
+
]
|
| 476 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 477 |
+
sA: cute.struct.Align[
|
| 478 |
+
cute.struct.MemRange[
|
| 479 |
+
self.a_dtype, cute.cosize(self.a_smem_layout_staged.outer)
|
| 480 |
+
],
|
| 481 |
+
self.buffer_align_bytes,
|
| 482 |
+
]
|
| 483 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 484 |
+
sB: cute.struct.Align[
|
| 485 |
+
cute.struct.MemRange[
|
| 486 |
+
self.b_dtype, cute.cosize(self.b_smem_layout_staged.outer)
|
| 487 |
+
],
|
| 488 |
+
self.buffer_align_bytes,
|
| 489 |
+
]
|
| 490 |
+
|
| 491 |
+
self.shared_storage = SharedStorage
|
| 492 |
+
|
| 493 |
+
# Launch the kernel synchronously
|
| 494 |
+
self.kernel(
|
| 495 |
+
tiled_mma,
|
| 496 |
+
tma_atom_a,
|
| 497 |
+
tma_tensor_a,
|
| 498 |
+
tma_atom_b,
|
| 499 |
+
tma_tensor_b,
|
| 500 |
+
tma_atom_c,
|
| 501 |
+
tma_tensor_c if cutlass.const_expr(self.use_tma_store) else c,
|
| 502 |
+
self.cluster_layout_vmnk,
|
| 503 |
+
self.a_smem_layout_staged,
|
| 504 |
+
self.b_smem_layout_staged,
|
| 505 |
+
self.c_smem_layout_staged,
|
| 506 |
+
self.epi_tile,
|
| 507 |
+
self.tile_sched_params,
|
| 508 |
+
epilogue_op,
|
| 509 |
+
).launch(
|
| 510 |
+
grid=grid,
|
| 511 |
+
block=[self.threads_per_cta, 1, 1],
|
| 512 |
+
cluster=(*self.cluster_shape_mn, 1),
|
| 513 |
+
stream=stream,
|
| 514 |
+
)
|
| 515 |
+
return
|
| 516 |
+
|
| 517 |
+
# GPU device kernel
|
| 518 |
+
@cute.kernel
|
| 519 |
+
def kernel(
|
| 520 |
+
self,
|
| 521 |
+
tiled_mma: cute.TiledMma,
|
| 522 |
+
tma_atom_a: cute.CopyAtom,
|
| 523 |
+
mA_mkl: cute.Tensor,
|
| 524 |
+
tma_atom_b: cute.CopyAtom,
|
| 525 |
+
mB_nkl: cute.Tensor,
|
| 526 |
+
tma_atom_c: Optional[cute.CopyAtom],
|
| 527 |
+
mC_mnl: cute.Tensor,
|
| 528 |
+
cluster_layout_vmnk: cute.Layout,
|
| 529 |
+
a_smem_layout_staged: cute.ComposedLayout,
|
| 530 |
+
b_smem_layout_staged: cute.ComposedLayout,
|
| 531 |
+
c_smem_layout_staged: Union[cute.Layout, cute.ComposedLayout, None],
|
| 532 |
+
epi_tile: cute.Tile,
|
| 533 |
+
tile_sched_params: utils.PersistentTileSchedulerParams,
|
| 534 |
+
epilogue_op: cutlass.Constexpr,
|
| 535 |
+
):
|
| 536 |
+
"""
|
| 537 |
+
GPU device kernel performing the Persistent batched GEMM computation.
|
| 538 |
+
"""
|
| 539 |
+
warp_idx = cute.arch.warp_idx()
|
| 540 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 541 |
+
|
| 542 |
+
#
|
| 543 |
+
# Prefetch tma desc
|
| 544 |
+
#
|
| 545 |
+
if warp_idx == self.tma_warp_id:
|
| 546 |
+
cpasync.prefetch_descriptor(tma_atom_a)
|
| 547 |
+
cpasync.prefetch_descriptor(tma_atom_b)
|
| 548 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 549 |
+
cpasync.prefetch_descriptor(tma_atom_c)
|
| 550 |
+
|
| 551 |
+
use_2cta_instrs = cute.size(tiled_mma.thr_id.shape) == 2
|
| 552 |
+
|
| 553 |
+
#
|
| 554 |
+
# Setup cta/thread coordinates
|
| 555 |
+
#
|
| 556 |
+
# Coords inside cluster
|
| 557 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 558 |
+
mma_tile_coord_v = bidx % cute.size(tiled_mma.thr_id.shape)
|
| 559 |
+
is_leader_cta = mma_tile_coord_v == 0
|
| 560 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(
|
| 561 |
+
cute.arch.block_idx_in_cluster()
|
| 562 |
+
)
|
| 563 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(
|
| 564 |
+
cta_rank_in_cluster
|
| 565 |
+
)
|
| 566 |
+
# Coord inside cta
|
| 567 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 568 |
+
|
| 569 |
+
#
|
| 570 |
+
# Alloc and init: a+b full/empty, accumulator full/empty, tensor memory dealloc barrier
|
| 571 |
+
#
|
| 572 |
+
smem = utils.SmemAllocator()
|
| 573 |
+
storage = smem.allocate(self.shared_storage)
|
| 574 |
+
|
| 575 |
+
tmem_dealloc_mbar_ptr = storage.tmem_dealloc_mbar_ptr
|
| 576 |
+
tmem_holding_buf = storage.tmem_holding_buf
|
| 577 |
+
|
| 578 |
+
# Initialize mainloop ab_pipeline (barrier) and states
|
| 579 |
+
ab_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 580 |
+
num_tma_producer = self.num_mcast_ctas_a + self.num_mcast_ctas_b - 1
|
| 581 |
+
ab_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 582 |
+
pipeline.Agent.Thread, num_tma_producer
|
| 583 |
+
)
|
| 584 |
+
ab_pipeline = pipeline.PipelineTmaUmma.create(
|
| 585 |
+
barrier_storage=storage.ab_full_mbar_ptr.data_ptr(),
|
| 586 |
+
num_stages=self.num_ab_stage,
|
| 587 |
+
producer_group=ab_pipeline_producer_group,
|
| 588 |
+
consumer_group=ab_pipeline_consumer_group,
|
| 589 |
+
tx_count=self.num_tma_load_bytes,
|
| 590 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 591 |
+
)
|
| 592 |
+
|
| 593 |
+
# Initialize acc_pipeline (barrier) and states
|
| 594 |
+
acc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 595 |
+
num_acc_consumer_threads = len(self.epilog_warp_id) * (
|
| 596 |
+
2 if use_2cta_instrs else 1
|
| 597 |
+
)
|
| 598 |
+
acc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 599 |
+
pipeline.Agent.Thread, num_acc_consumer_threads
|
| 600 |
+
)
|
| 601 |
+
acc_pipeline = pipeline.PipelineUmmaAsync.create(
|
| 602 |
+
barrier_storage=storage.acc_full_mbar_ptr.data_ptr(),
|
| 603 |
+
num_stages=self.num_acc_stage,
|
| 604 |
+
producer_group=acc_pipeline_producer_group,
|
| 605 |
+
consumer_group=acc_pipeline_consumer_group,
|
| 606 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 607 |
+
)
|
| 608 |
+
|
| 609 |
+
# Tensor memory dealloc barrier init
|
| 610 |
+
if use_2cta_instrs:
|
| 611 |
+
if warp_idx == self.tma_warp_id:
|
| 612 |
+
num_tmem_dealloc_threads = 32
|
| 613 |
+
with cute.arch.elect_one():
|
| 614 |
+
cute.arch.mbarrier_init(
|
| 615 |
+
tmem_dealloc_mbar_ptr, num_tmem_dealloc_threads
|
| 616 |
+
)
|
| 617 |
+
cute.arch.mbarrier_init_fence()
|
| 618 |
+
|
| 619 |
+
# Cluster arrive after barrier init
|
| 620 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 621 |
+
cute.arch.cluster_arrive_relaxed()
|
| 622 |
+
|
| 623 |
+
#
|
| 624 |
+
# Setup smem tensor A/B/C
|
| 625 |
+
#
|
| 626 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 627 |
+
sC = (
|
| 628 |
+
storage.sC.get_tensor(
|
| 629 |
+
c_smem_layout_staged.outer, swizzle=c_smem_layout_staged.inner
|
| 630 |
+
)
|
| 631 |
+
if cutlass.const_expr(self.use_tma_store)
|
| 632 |
+
else None
|
| 633 |
+
)
|
| 634 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 635 |
+
sA = storage.sA.get_tensor(
|
| 636 |
+
a_smem_layout_staged.outer, swizzle=a_smem_layout_staged.inner
|
| 637 |
+
)
|
| 638 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 639 |
+
sB = storage.sB.get_tensor(
|
| 640 |
+
b_smem_layout_staged.outer, swizzle=b_smem_layout_staged.inner
|
| 641 |
+
)
|
| 642 |
+
|
| 643 |
+
#
|
| 644 |
+
# Compute multicast mask for A/B buffer full
|
| 645 |
+
#
|
| 646 |
+
a_full_mcast_mask = None
|
| 647 |
+
b_full_mcast_mask = None
|
| 648 |
+
if cutlass.const_expr(self.is_a_mcast or self.is_b_mcast or use_2cta_instrs):
|
| 649 |
+
a_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 650 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 651 |
+
)
|
| 652 |
+
b_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 653 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=1
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
+
#
|
| 657 |
+
# Local_tile partition global tensors
|
| 658 |
+
#
|
| 659 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 660 |
+
gA_mkl = cute.local_tile(
|
| 661 |
+
mA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None)
|
| 662 |
+
)
|
| 663 |
+
# (bN, bK, RestN, RestK, RestL)
|
| 664 |
+
gB_nkl = cute.local_tile(
|
| 665 |
+
mB_nkl, cute.slice_(self.mma_tiler, (0, None, None)), (None, None, None)
|
| 666 |
+
)
|
| 667 |
+
# (bM, bN, RestM, RestN, RestL)
|
| 668 |
+
gC_mnl = cute.local_tile(
|
| 669 |
+
mC_mnl, cute.slice_(self.mma_tiler, (None, None, 0)), (None, None, None)
|
| 670 |
+
)
|
| 671 |
+
k_tile_cnt = cute.size(gA_mkl, mode=[3])
|
| 672 |
+
|
| 673 |
+
#
|
| 674 |
+
# Partition global tensor for TiledMMA_A/B/C
|
| 675 |
+
#
|
| 676 |
+
thr_mma = tiled_mma.get_slice(mma_tile_coord_v)
|
| 677 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 678 |
+
tCgA = thr_mma.partition_A(gA_mkl)
|
| 679 |
+
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
|
| 680 |
+
tCgB = thr_mma.partition_B(gB_nkl)
|
| 681 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 682 |
+
tCgC = thr_mma.partition_C(gC_mnl)
|
| 683 |
+
|
| 684 |
+
#
|
| 685 |
+
# Partition global/shared tensor for TMA load A/B
|
| 686 |
+
#
|
| 687 |
+
# TMA load A partition_S/D
|
| 688 |
+
a_cta_layout = cute.make_layout(
|
| 689 |
+
cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape
|
| 690 |
+
)
|
| 691 |
+
# ((atom_v, rest_v), STAGE)
|
| 692 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 693 |
+
tAsA, tAgA = cpasync.tma_partition(
|
| 694 |
+
tma_atom_a,
|
| 695 |
+
block_in_cluster_coord_vmnk[2],
|
| 696 |
+
a_cta_layout,
|
| 697 |
+
cute.group_modes(sA, 0, 3),
|
| 698 |
+
cute.group_modes(tCgA, 0, 3),
|
| 699 |
+
)
|
| 700 |
+
# TMA load B partition_S/D
|
| 701 |
+
b_cta_layout = cute.make_layout(
|
| 702 |
+
cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape
|
| 703 |
+
)
|
| 704 |
+
# ((atom_v, rest_v), STAGE)
|
| 705 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 706 |
+
tBsB, tBgB = cpasync.tma_partition(
|
| 707 |
+
tma_atom_b,
|
| 708 |
+
block_in_cluster_coord_vmnk[1],
|
| 709 |
+
b_cta_layout,
|
| 710 |
+
cute.group_modes(sB, 0, 3),
|
| 711 |
+
cute.group_modes(tCgB, 0, 3),
|
| 712 |
+
)
|
| 713 |
+
|
| 714 |
+
#
|
| 715 |
+
# Partition shared/tensor memory tensor for TiledMMA_A/B/C
|
| 716 |
+
#
|
| 717 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 718 |
+
tCrA = tiled_mma.make_fragment_A(sA)
|
| 719 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 720 |
+
tCrB = tiled_mma.make_fragment_B(sB)
|
| 721 |
+
# (MMA, MMA_M, MMA_N)
|
| 722 |
+
acc_shape = tiled_mma.partition_shape_C(self.mma_tiler[:2])
|
| 723 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 724 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(
|
| 725 |
+
cute.append(acc_shape, self.num_acc_stage)
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
+
#
|
| 729 |
+
# Cluster wait before tensor memory alloc
|
| 730 |
+
#
|
| 731 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 732 |
+
cute.arch.cluster_wait()
|
| 733 |
+
else:
|
| 734 |
+
cute.arch.barrier(
|
| 735 |
+
barrier_id=self.cta_sync_bar_id, number_of_threads=self.threads_per_cta
|
| 736 |
+
)
|
| 737 |
+
|
| 738 |
+
#
|
| 739 |
+
# Specialized TMA load warp
|
| 740 |
+
#
|
| 741 |
+
|
| 742 |
+
if warp_idx == self.tma_warp_id:
|
| 743 |
+
#
|
| 744 |
+
# Persistent tile scheduling loop
|
| 745 |
+
#
|
| 746 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 747 |
+
tile_sched_params, cute.arch.block_idx(), cute.arch.grid_dim()
|
| 748 |
+
)
|
| 749 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 750 |
+
|
| 751 |
+
ab_producer_state = pipeline.make_pipeline_state(
|
| 752 |
+
pipeline.PipelineUserType.Producer, self.num_ab_stage
|
| 753 |
+
)
|
| 754 |
+
|
| 755 |
+
while work_tile.is_valid_tile:
|
| 756 |
+
# Get tile coord from tile scheduler
|
| 757 |
+
cur_tile_coord = work_tile.tile_idx
|
| 758 |
+
mma_tile_coord_mnl = (
|
| 759 |
+
cur_tile_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 760 |
+
cur_tile_coord[1],
|
| 761 |
+
cur_tile_coord[2],
|
| 762 |
+
)
|
| 763 |
+
|
| 764 |
+
#
|
| 765 |
+
# Slice to per mma tile index
|
| 766 |
+
#
|
| 767 |
+
# ((atom_v, rest_v), RestK)
|
| 768 |
+
tAgA_slice = tAgA[
|
| 769 |
+
(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])
|
| 770 |
+
]
|
| 771 |
+
# ((atom_v, rest_v), RestK)
|
| 772 |
+
tBgB_slice = tBgB[
|
| 773 |
+
(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])
|
| 774 |
+
]
|
| 775 |
+
|
| 776 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt
|
| 777 |
+
ab_producer_state.reset_count()
|
| 778 |
+
peek_ab_empty_status = cutlass.Boolean(1)
|
| 779 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 780 |
+
peek_ab_empty_status = ab_pipeline.producer_try_acquire(
|
| 781 |
+
ab_producer_state
|
| 782 |
+
)
|
| 783 |
+
#
|
| 784 |
+
# Tma load loop
|
| 785 |
+
#
|
| 786 |
+
for k_tile in cutlass.range(0, k_tile_cnt, 1, unroll=1):
|
| 787 |
+
# Conditionally wait for AB buffer empty
|
| 788 |
+
ab_pipeline.producer_acquire(
|
| 789 |
+
ab_producer_state, peek_ab_empty_status
|
| 790 |
+
)
|
| 791 |
+
|
| 792 |
+
# TMA load A/B
|
| 793 |
+
cute.copy(
|
| 794 |
+
tma_atom_a,
|
| 795 |
+
tAgA_slice[(None, ab_producer_state.count)],
|
| 796 |
+
tAsA[(None, ab_producer_state.index)],
|
| 797 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 798 |
+
mcast_mask=a_full_mcast_mask,
|
| 799 |
+
)
|
| 800 |
+
cute.copy(
|
| 801 |
+
tma_atom_b,
|
| 802 |
+
tBgB_slice[(None, ab_producer_state.count)],
|
| 803 |
+
tBsB[(None, ab_producer_state.index)],
|
| 804 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 805 |
+
mcast_mask=b_full_mcast_mask,
|
| 806 |
+
)
|
| 807 |
+
|
| 808 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt + k_tile + 1
|
| 809 |
+
ab_producer_state.advance()
|
| 810 |
+
peek_ab_empty_status = cutlass.Boolean(1)
|
| 811 |
+
if ab_producer_state.count < k_tile_cnt:
|
| 812 |
+
peek_ab_empty_status = ab_pipeline.producer_try_acquire(
|
| 813 |
+
ab_producer_state
|
| 814 |
+
)
|
| 815 |
+
|
| 816 |
+
#
|
| 817 |
+
# Advance to next tile
|
| 818 |
+
#
|
| 819 |
+
tile_sched.advance_to_next_work()
|
| 820 |
+
work_tile = tile_sched.get_current_work()
|
| 821 |
+
|
| 822 |
+
#
|
| 823 |
+
# Wait A/B buffer empty
|
| 824 |
+
#
|
| 825 |
+
ab_pipeline.producer_tail(ab_producer_state)
|
| 826 |
+
|
| 827 |
+
#
|
| 828 |
+
# Specialized MMA warp
|
| 829 |
+
#
|
| 830 |
+
if warp_idx == self.mma_warp_id:
|
| 831 |
+
#
|
| 832 |
+
# Bar sync for retrieve tensor memory ptr from shared mem
|
| 833 |
+
#
|
| 834 |
+
tmem_ptr_read_threads = 32 * len((self.mma_warp_id, *self.epilog_warp_id))
|
| 835 |
+
cute.arch.barrier(
|
| 836 |
+
barrier_id=self.tmem_ptr_sync_bar_id,
|
| 837 |
+
number_of_threads=tmem_ptr_read_threads,
|
| 838 |
+
)
|
| 839 |
+
|
| 840 |
+
#
|
| 841 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 842 |
+
#
|
| 843 |
+
tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 844 |
+
self.acc_dtype,
|
| 845 |
+
alignment=16,
|
| 846 |
+
ptr_to_buffer_holding_addr=tmem_holding_buf,
|
| 847 |
+
)
|
| 848 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 849 |
+
tCtAcc_base = cute.make_tensor(tmem_ptr, tCtAcc_fake.layout)
|
| 850 |
+
|
| 851 |
+
#
|
| 852 |
+
# Persistent tile scheduling loop
|
| 853 |
+
#
|
| 854 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 855 |
+
tile_sched_params, cute.arch.block_idx(), cute.arch.grid_dim()
|
| 856 |
+
)
|
| 857 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 858 |
+
|
| 859 |
+
ab_consumer_state = pipeline.make_pipeline_state(
|
| 860 |
+
pipeline.PipelineUserType.Consumer, self.num_ab_stage
|
| 861 |
+
)
|
| 862 |
+
acc_producer_state = pipeline.make_pipeline_state(
|
| 863 |
+
pipeline.PipelineUserType.Producer, self.num_acc_stage
|
| 864 |
+
)
|
| 865 |
+
|
| 866 |
+
while work_tile.is_valid_tile:
|
| 867 |
+
# Get tile coord from tile scheduler
|
| 868 |
+
cur_tile_coord = work_tile.tile_idx
|
| 869 |
+
mma_tile_coord_mnl = (
|
| 870 |
+
cur_tile_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 871 |
+
cur_tile_coord[1],
|
| 872 |
+
cur_tile_coord[2],
|
| 873 |
+
)
|
| 874 |
+
|
| 875 |
+
# Set tensor memory buffer for current tile
|
| 876 |
+
# (MMA, MMA_M, MMA_N)
|
| 877 |
+
tCtAcc = tCtAcc_base[(None, None, None, acc_producer_state.index)]
|
| 878 |
+
|
| 879 |
+
# Peek (try_wait) AB buffer full for k_tile = 0
|
| 880 |
+
ab_consumer_state.reset_count()
|
| 881 |
+
peek_ab_full_status = cutlass.Boolean(1)
|
| 882 |
+
if ab_consumer_state.count < k_tile_cnt and is_leader_cta:
|
| 883 |
+
peek_ab_full_status = ab_pipeline.consumer_try_wait(
|
| 884 |
+
ab_consumer_state
|
| 885 |
+
)
|
| 886 |
+
|
| 887 |
+
#
|
| 888 |
+
# Wait for accumulator buffer empty
|
| 889 |
+
#
|
| 890 |
+
if is_leader_cta:
|
| 891 |
+
acc_pipeline.producer_acquire(acc_producer_state)
|
| 892 |
+
|
| 893 |
+
#
|
| 894 |
+
# Reset the ACCUMULATE field for each tile
|
| 895 |
+
#
|
| 896 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 897 |
+
|
| 898 |
+
#
|
| 899 |
+
# Mma mainloop
|
| 900 |
+
#
|
| 901 |
+
for k_tile in range(k_tile_cnt):
|
| 902 |
+
if is_leader_cta:
|
| 903 |
+
# Conditionally wait for AB buffer full
|
| 904 |
+
ab_pipeline.consumer_wait(
|
| 905 |
+
ab_consumer_state, peek_ab_full_status
|
| 906 |
+
)
|
| 907 |
+
|
| 908 |
+
# tCtAcc += tCrA * tCrB
|
| 909 |
+
num_kblocks = cute.size(tCrA, mode=[2])
|
| 910 |
+
for kblock_idx in cutlass.range(num_kblocks, unroll_full=True):
|
| 911 |
+
kblock_coord = (
|
| 912 |
+
None,
|
| 913 |
+
None,
|
| 914 |
+
kblock_idx,
|
| 915 |
+
ab_consumer_state.index,
|
| 916 |
+
)
|
| 917 |
+
|
| 918 |
+
cute.gemm(
|
| 919 |
+
tiled_mma,
|
| 920 |
+
tCtAcc,
|
| 921 |
+
tCrA[kblock_coord],
|
| 922 |
+
tCrB[kblock_coord],
|
| 923 |
+
tCtAcc,
|
| 924 |
+
)
|
| 925 |
+
# Enable accumulate on tCtAcc after first kblock
|
| 926 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 927 |
+
|
| 928 |
+
# Async arrive AB buffer empty
|
| 929 |
+
ab_pipeline.consumer_release(ab_consumer_state)
|
| 930 |
+
|
| 931 |
+
# Peek (try_wait) AB buffer full for k_tile = k_tile + 1
|
| 932 |
+
ab_consumer_state.advance()
|
| 933 |
+
peek_ab_full_status = cutlass.Boolean(1)
|
| 934 |
+
if ab_consumer_state.count < k_tile_cnt:
|
| 935 |
+
if is_leader_cta:
|
| 936 |
+
peek_ab_full_status = ab_pipeline.consumer_try_wait(
|
| 937 |
+
ab_consumer_state
|
| 938 |
+
)
|
| 939 |
+
|
| 940 |
+
#
|
| 941 |
+
# Async arrive accumulator buffer full
|
| 942 |
+
#
|
| 943 |
+
if is_leader_cta:
|
| 944 |
+
acc_pipeline.producer_commit(acc_producer_state)
|
| 945 |
+
acc_producer_state.advance()
|
| 946 |
+
|
| 947 |
+
#
|
| 948 |
+
# Advance to next tile
|
| 949 |
+
#
|
| 950 |
+
tile_sched.advance_to_next_work()
|
| 951 |
+
work_tile = tile_sched.get_current_work()
|
| 952 |
+
|
| 953 |
+
#
|
| 954 |
+
# Wait for accumulator buffer empty
|
| 955 |
+
#
|
| 956 |
+
acc_pipeline.producer_tail(acc_producer_state)
|
| 957 |
+
#
|
| 958 |
+
# Specialized epilogue warps
|
| 959 |
+
#
|
| 960 |
+
if warp_idx < self.mma_warp_id:
|
| 961 |
+
#
|
| 962 |
+
# Alloc tensor memory buffer
|
| 963 |
+
#
|
| 964 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 965 |
+
cute.arch.alloc_tmem(
|
| 966 |
+
self.num_tmem_alloc_cols,
|
| 967 |
+
tmem_holding_buf,
|
| 968 |
+
is_two_cta=use_2cta_instrs,
|
| 969 |
+
)
|
| 970 |
+
|
| 971 |
+
#
|
| 972 |
+
# Bar sync for retrieve tensor memory ptr from shared memory
|
| 973 |
+
#
|
| 974 |
+
tmem_ptr_read_threads = 32 * len((self.mma_warp_id, *self.epilog_warp_id))
|
| 975 |
+
cute.arch.barrier(
|
| 976 |
+
barrier_id=self.tmem_ptr_sync_bar_id,
|
| 977 |
+
number_of_threads=tmem_ptr_read_threads,
|
| 978 |
+
)
|
| 979 |
+
|
| 980 |
+
#
|
| 981 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 982 |
+
#
|
| 983 |
+
tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 984 |
+
self.acc_dtype,
|
| 985 |
+
alignment=16,
|
| 986 |
+
ptr_to_buffer_holding_addr=tmem_holding_buf,
|
| 987 |
+
)
|
| 988 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 989 |
+
tCtAcc_base = cute.make_tensor(tmem_ptr, tCtAcc_fake.layout)
|
| 990 |
+
|
| 991 |
+
#
|
| 992 |
+
# Partition for epilogue
|
| 993 |
+
#
|
| 994 |
+
epi_tidx = tidx
|
| 995 |
+
(
|
| 996 |
+
tiled_copy_t2r,
|
| 997 |
+
tTR_tAcc_base,
|
| 998 |
+
tTR_rAcc,
|
| 999 |
+
) = self.epilog_tmem_copy_and_partition(
|
| 1000 |
+
epi_tidx, tCtAcc_base, tCgC, epi_tile, use_2cta_instrs
|
| 1001 |
+
)
|
| 1002 |
+
|
| 1003 |
+
tTR_rC = None
|
| 1004 |
+
tiled_copy_r2s = None
|
| 1005 |
+
simt_atom = None
|
| 1006 |
+
tRS_rC = None
|
| 1007 |
+
tRS_sC = None
|
| 1008 |
+
bSG_sC = None
|
| 1009 |
+
bSG_gC_partitioned = None
|
| 1010 |
+
tTR_gC_partitioned = None
|
| 1011 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1012 |
+
tTR_rC = cute.make_fragment(tTR_rAcc.shape, self.c_dtype)
|
| 1013 |
+
tiled_copy_r2s, tRS_rC, tRS_sC = self.epilog_smem_copy_and_partition(
|
| 1014 |
+
tiled_copy_t2r, tTR_rC, epi_tidx, sC
|
| 1015 |
+
)
|
| 1016 |
+
(
|
| 1017 |
+
tma_atom_c,
|
| 1018 |
+
bSG_sC,
|
| 1019 |
+
bSG_gC_partitioned,
|
| 1020 |
+
) = self.epilog_gmem_copy_and_partition(
|
| 1021 |
+
epi_tidx, tma_atom_c, tCgC, epi_tile, sC
|
| 1022 |
+
)
|
| 1023 |
+
else:
|
| 1024 |
+
(
|
| 1025 |
+
simt_atom,
|
| 1026 |
+
tTR_rC,
|
| 1027 |
+
tTR_gC_partitioned,
|
| 1028 |
+
) = self.epilog_gmem_copy_and_partition(
|
| 1029 |
+
epi_tidx, tiled_copy_t2r, tCgC, epi_tile, sC
|
| 1030 |
+
)
|
| 1031 |
+
|
| 1032 |
+
#
|
| 1033 |
+
# Persistent tile scheduling loop
|
| 1034 |
+
#
|
| 1035 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 1036 |
+
tile_sched_params, cute.arch.block_idx(), cute.arch.grid_dim()
|
| 1037 |
+
)
|
| 1038 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 1039 |
+
|
| 1040 |
+
acc_consumer_state = pipeline.make_pipeline_state(
|
| 1041 |
+
pipeline.PipelineUserType.Consumer, self.num_acc_stage
|
| 1042 |
+
)
|
| 1043 |
+
|
| 1044 |
+
c_pipeline = None
|
| 1045 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1046 |
+
# Threads/warps participating in tma store pipeline
|
| 1047 |
+
c_producer_group = pipeline.CooperativeGroup(
|
| 1048 |
+
pipeline.Agent.Thread,
|
| 1049 |
+
32 * len(self.epilog_warp_id),
|
| 1050 |
+
32 * len(self.epilog_warp_id),
|
| 1051 |
+
)
|
| 1052 |
+
c_pipeline = pipeline.PipelineTmaStore.create(
|
| 1053 |
+
num_stages=self.num_c_stage,
|
| 1054 |
+
producer_group=c_producer_group,
|
| 1055 |
+
)
|
| 1056 |
+
|
| 1057 |
+
while work_tile.is_valid_tile:
|
| 1058 |
+
# Get tile coord from tile scheduler
|
| 1059 |
+
cur_tile_coord = work_tile.tile_idx
|
| 1060 |
+
mma_tile_coord_mnl = (
|
| 1061 |
+
cur_tile_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 1062 |
+
cur_tile_coord[1],
|
| 1063 |
+
cur_tile_coord[2],
|
| 1064 |
+
)
|
| 1065 |
+
|
| 1066 |
+
#
|
| 1067 |
+
# Slice to per mma tile index
|
| 1068 |
+
#
|
| 1069 |
+
bSG_gC = None
|
| 1070 |
+
tTR_gC = None
|
| 1071 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1072 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1073 |
+
bSG_gC = bSG_gC_partitioned[
|
| 1074 |
+
(
|
| 1075 |
+
None,
|
| 1076 |
+
None,
|
| 1077 |
+
None,
|
| 1078 |
+
*mma_tile_coord_mnl,
|
| 1079 |
+
)
|
| 1080 |
+
]
|
| 1081 |
+
else:
|
| 1082 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N)
|
| 1083 |
+
tTR_gC = tTR_gC_partitioned[
|
| 1084 |
+
(
|
| 1085 |
+
None,
|
| 1086 |
+
None,
|
| 1087 |
+
None,
|
| 1088 |
+
None,
|
| 1089 |
+
None,
|
| 1090 |
+
*mma_tile_coord_mnl,
|
| 1091 |
+
)
|
| 1092 |
+
]
|
| 1093 |
+
|
| 1094 |
+
# Set tensor memory buffer for current tile
|
| 1095 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M)
|
| 1096 |
+
tTR_tAcc = tTR_tAcc_base[
|
| 1097 |
+
(None, None, None, None, None, acc_consumer_state.index)
|
| 1098 |
+
]
|
| 1099 |
+
|
| 1100 |
+
#
|
| 1101 |
+
# Wait for accumulator buffer full
|
| 1102 |
+
#
|
| 1103 |
+
acc_pipeline.consumer_wait(acc_consumer_state)
|
| 1104 |
+
|
| 1105 |
+
tTR_tAcc = cute.group_modes(tTR_tAcc, 3, cute.rank(tTR_tAcc))
|
| 1106 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1107 |
+
bSG_gC = cute.group_modes(bSG_gC, 1, cute.rank(bSG_gC))
|
| 1108 |
+
else:
|
| 1109 |
+
tTR_gC = cute.group_modes(tTR_gC, 3, cute.rank(tTR_gC))
|
| 1110 |
+
|
| 1111 |
+
#
|
| 1112 |
+
# Store accumulator to global memory in subtiles
|
| 1113 |
+
#
|
| 1114 |
+
subtile_cnt = cute.size(tTR_tAcc.shape, mode=[3])
|
| 1115 |
+
num_prev_subtiles = tile_sched.num_tiles_executed * subtile_cnt
|
| 1116 |
+
for subtile_idx in cutlass.range(subtile_cnt):
|
| 1117 |
+
#
|
| 1118 |
+
# Load accumulator from tensor memory buffer to register
|
| 1119 |
+
#
|
| 1120 |
+
tTR_tAcc_mn = tTR_tAcc[(None, None, None, subtile_idx)]
|
| 1121 |
+
cute.copy(tiled_copy_t2r, tTR_tAcc_mn, tTR_rAcc)
|
| 1122 |
+
|
| 1123 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1124 |
+
#
|
| 1125 |
+
# Convert to C type
|
| 1126 |
+
#
|
| 1127 |
+
acc_vec = tiled_copy_r2s.retile(tTR_rAcc).load()
|
| 1128 |
+
acc_vec = epilogue_op(acc_vec.to(self.c_dtype))
|
| 1129 |
+
tRS_rC.store(acc_vec)
|
| 1130 |
+
|
| 1131 |
+
#
|
| 1132 |
+
# Store C to shared memory
|
| 1133 |
+
#
|
| 1134 |
+
c_buffer = (num_prev_subtiles + subtile_idx) % self.num_c_stage
|
| 1135 |
+
cute.copy(
|
| 1136 |
+
tiled_copy_r2s,
|
| 1137 |
+
tRS_rC,
|
| 1138 |
+
tRS_sC[(None, None, None, c_buffer)],
|
| 1139 |
+
)
|
| 1140 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 1141 |
+
cute.arch.fence_proxy(
|
| 1142 |
+
cute.arch.ProxyKind.async_shared,
|
| 1143 |
+
space=cute.arch.SharedSpace.shared_cta,
|
| 1144 |
+
)
|
| 1145 |
+
epilog_threads = 32 * len(self.epilog_warp_id)
|
| 1146 |
+
cute.arch.barrier(
|
| 1147 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 1148 |
+
number_of_threads=epilog_threads,
|
| 1149 |
+
)
|
| 1150 |
+
|
| 1151 |
+
#
|
| 1152 |
+
# TMA store C to global memory
|
| 1153 |
+
#
|
| 1154 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1155 |
+
cute.copy(
|
| 1156 |
+
tma_atom_c,
|
| 1157 |
+
bSG_sC[(None, c_buffer)],
|
| 1158 |
+
bSG_gC[(None, subtile_idx)],
|
| 1159 |
+
)
|
| 1160 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 1161 |
+
c_pipeline.producer_commit()
|
| 1162 |
+
c_pipeline.producer_acquire()
|
| 1163 |
+
cute.arch.barrier(
|
| 1164 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 1165 |
+
number_of_threads=epilog_threads,
|
| 1166 |
+
)
|
| 1167 |
+
else:
|
| 1168 |
+
#
|
| 1169 |
+
# Convert to C type
|
| 1170 |
+
#
|
| 1171 |
+
acc_vec = tTR_rAcc.load()
|
| 1172 |
+
acc_vec = epilogue_op(acc_vec.to(self.c_dtype))
|
| 1173 |
+
tTR_rC.store(acc_vec)
|
| 1174 |
+
|
| 1175 |
+
#
|
| 1176 |
+
# Store C to global memory
|
| 1177 |
+
#
|
| 1178 |
+
cute.copy(
|
| 1179 |
+
simt_atom, tTR_rC, tTR_gC[(None, None, None, subtile_idx)]
|
| 1180 |
+
)
|
| 1181 |
+
|
| 1182 |
+
#
|
| 1183 |
+
# Async arrive accumulator buffer empty
|
| 1184 |
+
#
|
| 1185 |
+
with cute.arch.elect_one():
|
| 1186 |
+
acc_pipeline.consumer_release(acc_consumer_state)
|
| 1187 |
+
acc_consumer_state.advance()
|
| 1188 |
+
|
| 1189 |
+
#
|
| 1190 |
+
# Advance to next tile
|
| 1191 |
+
#
|
| 1192 |
+
tile_sched.advance_to_next_work()
|
| 1193 |
+
work_tile = tile_sched.get_current_work()
|
| 1194 |
+
|
| 1195 |
+
#
|
| 1196 |
+
# Dealloc the tensor memory buffer
|
| 1197 |
+
#
|
| 1198 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1199 |
+
cute.arch.relinquish_tmem_alloc_permit(is_two_cta=use_2cta_instrs)
|
| 1200 |
+
epilog_threads = 32 * len(self.epilog_warp_id)
|
| 1201 |
+
cute.arch.barrier(
|
| 1202 |
+
barrier_id=self.epilog_sync_bar_id, number_of_threads=epilog_threads
|
| 1203 |
+
)
|
| 1204 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1205 |
+
if use_2cta_instrs:
|
| 1206 |
+
cute.arch.mbarrier_arrive(
|
| 1207 |
+
tmem_dealloc_mbar_ptr, cta_rank_in_cluster ^ 1
|
| 1208 |
+
)
|
| 1209 |
+
cute.arch.mbarrier_wait(tmem_dealloc_mbar_ptr, 0)
|
| 1210 |
+
cute.arch.dealloc_tmem(
|
| 1211 |
+
tmem_ptr, self.num_tmem_alloc_cols, is_two_cta=use_2cta_instrs
|
| 1212 |
+
)
|
| 1213 |
+
#
|
| 1214 |
+
# Wait for C store complete
|
| 1215 |
+
#
|
| 1216 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1217 |
+
c_pipeline.producer_tail()
|
| 1218 |
+
|
| 1219 |
+
def epilog_tmem_copy_and_partition(
|
| 1220 |
+
self,
|
| 1221 |
+
tidx: cutlass.Int32,
|
| 1222 |
+
tAcc: cute.Tensor,
|
| 1223 |
+
gC_mnl: cute.Tensor,
|
| 1224 |
+
epi_tile: cute.Tile,
|
| 1225 |
+
use_2cta_instrs: Union[cutlass.Boolean, bool],
|
| 1226 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1227 |
+
"""
|
| 1228 |
+
Make tiledCopy for tensor memory load, then use it to partition tensor memory (source) and register array (destination).
|
| 1229 |
+
|
| 1230 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1231 |
+
:type tidx: cutlass.Int32
|
| 1232 |
+
:param tAcc: The accumulator tensor to be copied and partitioned
|
| 1233 |
+
:type tAcc: cute.Tensor
|
| 1234 |
+
:param gC_mnl: The global tensor C
|
| 1235 |
+
:type gC_mnl: cute.Tensor
|
| 1236 |
+
:param epi_tile: The epilogue tiler
|
| 1237 |
+
:type epi_tile: cute.Tile
|
| 1238 |
+
:param use_2cta_instrs: Whether use_2cta_instrs is enabled
|
| 1239 |
+
:type use_2cta_instrs: bool
|
| 1240 |
+
|
| 1241 |
+
:return: A tuple containing (tiled_copy_t2r, tTR_tAcc, tTR_rAcc) where:
|
| 1242 |
+
- tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1243 |
+
- tTR_tAcc: The partitioned accumulator tensor
|
| 1244 |
+
- tTR_rAcc: The accumulated tensor in register used to hold t2r results
|
| 1245 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1246 |
+
"""
|
| 1247 |
+
# Make tiledCopy for tensor memory load
|
| 1248 |
+
copy_atom_t2r = sm100_utils.get_tmem_load_op(
|
| 1249 |
+
self.cta_tile_shape_mnk,
|
| 1250 |
+
self.c_layout,
|
| 1251 |
+
self.c_dtype,
|
| 1252 |
+
self.acc_dtype,
|
| 1253 |
+
epi_tile,
|
| 1254 |
+
use_2cta_instrs,
|
| 1255 |
+
)
|
| 1256 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, STAGE)
|
| 1257 |
+
tAcc_epi = cute.flat_divide(
|
| 1258 |
+
tAcc[((None, None), 0, 0, None)],
|
| 1259 |
+
epi_tile,
|
| 1260 |
+
)
|
| 1261 |
+
# (EPI_TILE_M, EPI_TILE_N)
|
| 1262 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(
|
| 1263 |
+
copy_atom_t2r, tAcc_epi[(None, None, 0, 0, 0)]
|
| 1264 |
+
)
|
| 1265 |
+
|
| 1266 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1267 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M, STAGE)
|
| 1268 |
+
tTR_tAcc = thr_copy_t2r.partition_S(tAcc_epi)
|
| 1269 |
+
|
| 1270 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1271 |
+
gC_mnl_epi = cute.flat_divide(
|
| 1272 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1273 |
+
)
|
| 1274 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1275 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_mnl_epi)
|
| 1276 |
+
# (T2R, T2R_M, T2R_N)
|
| 1277 |
+
tTR_rAcc = cute.make_fragment(
|
| 1278 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.acc_dtype
|
| 1279 |
+
)
|
| 1280 |
+
return tiled_copy_t2r, tTR_tAcc, tTR_rAcc
|
| 1281 |
+
|
| 1282 |
+
def epilog_smem_copy_and_partition(
|
| 1283 |
+
self,
|
| 1284 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 1285 |
+
tTR_rC: cute.Tensor,
|
| 1286 |
+
tidx: cutlass.Int32,
|
| 1287 |
+
sC: cute.Tensor,
|
| 1288 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1289 |
+
"""
|
| 1290 |
+
Make tiledCopy for shared memory store, then use it to partition register array (source) and shared memory (destination).
|
| 1291 |
+
|
| 1292 |
+
:param tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1293 |
+
:type tiled_copy_t2r: cute.TiledCopy
|
| 1294 |
+
:param tTR_rC: The partitioned accumulator tensor
|
| 1295 |
+
:type tTR_rC: cute.Tensor
|
| 1296 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1297 |
+
:type tidx: cutlass.Int32
|
| 1298 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1299 |
+
:type sC: cute.Tensor
|
| 1300 |
+
:type sepi: cute.Tensor
|
| 1301 |
+
|
| 1302 |
+
:return: A tuple containing (tiled_copy_r2s, tRS_rC, tRS_sC) where:
|
| 1303 |
+
- tiled_copy_r2s: The tiled copy operation for register to smem copy(r2s)
|
| 1304 |
+
- tRS_rC: The partitioned tensor C (register source)
|
| 1305 |
+
- tRS_sC: The partitioned tensor C (smem destination)
|
| 1306 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1307 |
+
"""
|
| 1308 |
+
copy_atom_r2s = sm100_utils.get_smem_store_op(
|
| 1309 |
+
self.c_layout, self.c_dtype, self.acc_dtype, tiled_copy_t2r
|
| 1310 |
+
)
|
| 1311 |
+
tiled_copy_r2s = cute.make_tiled_copy_D(copy_atom_r2s, tiled_copy_t2r)
|
| 1312 |
+
# (R2S, R2S_M, R2S_N, PIPE_D)
|
| 1313 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx)
|
| 1314 |
+
tRS_sC = thr_copy_r2s.partition_D(sC)
|
| 1315 |
+
# (R2S, R2S_M, R2S_N)
|
| 1316 |
+
tRS_rC = tiled_copy_r2s.retile(tTR_rC)
|
| 1317 |
+
return tiled_copy_r2s, tRS_rC, tRS_sC
|
| 1318 |
+
|
| 1319 |
+
def epilog_gmem_copy_and_partition(
|
| 1320 |
+
self,
|
| 1321 |
+
tidx: cutlass.Int32,
|
| 1322 |
+
atom: Union[cute.CopyAtom, cute.TiledCopy],
|
| 1323 |
+
gC_mnl: cute.Tensor,
|
| 1324 |
+
epi_tile: cute.Tile,
|
| 1325 |
+
sC: cute.Tensor,
|
| 1326 |
+
) -> Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]:
|
| 1327 |
+
"""Make tiledCopy for global memory store, then use it to:
|
| 1328 |
+
- partition register array (source) and global memory (destination) for none TMA store version;
|
| 1329 |
+
- partition shared memory (source) and global memory (destination) for TMA store version.
|
| 1330 |
+
|
| 1331 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1332 |
+
:type tidx: cutlass.Int32
|
| 1333 |
+
:param atom: The copy_atom_c to be used for TMA store version, or tiled_copy_t2r for none TMA store version
|
| 1334 |
+
:type atom: cute.CopyAtom or cute.TiledCopy
|
| 1335 |
+
:param gC_mnl: The global tensor C
|
| 1336 |
+
:type gC_mnl: cute.Tensor
|
| 1337 |
+
:param epi_tile: The epilogue tiler
|
| 1338 |
+
:type epi_tile: cute.Tile
|
| 1339 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1340 |
+
:type sC: cute.Tensor
|
| 1341 |
+
|
| 1342 |
+
:return: A tuple containing either:
|
| 1343 |
+
- For TMA store: (tma_atom_c, bSG_sC, bSG_gC) where:
|
| 1344 |
+
- tma_atom_c: The TMA copy atom
|
| 1345 |
+
- bSG_sC: The partitioned shared memory tensor C
|
| 1346 |
+
- bSG_gC: The partitioned global tensor C
|
| 1347 |
+
- For non-TMA store: (simt_atom, tTR_rC, tTR_gC) where:
|
| 1348 |
+
- simt_atom: The SIMT copy atom
|
| 1349 |
+
- tTR_rC: The register tensor C
|
| 1350 |
+
- tTR_gC: The partitioned global tensor C
|
| 1351 |
+
:rtype: Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]
|
| 1352 |
+
"""
|
| 1353 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1354 |
+
gC_epi = cute.flat_divide(
|
| 1355 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1356 |
+
)
|
| 1357 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1358 |
+
tma_atom_c = atom
|
| 1359 |
+
sC_for_tma_partition = cute.group_modes(sC, 0, 2)
|
| 1360 |
+
gC_for_tma_partition = cute.group_modes(gC_epi, 0, 2)
|
| 1361 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1362 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1363 |
+
bSG_sC, bSG_gC = cpasync.tma_partition(
|
| 1364 |
+
tma_atom_c,
|
| 1365 |
+
0,
|
| 1366 |
+
cute.make_layout(1),
|
| 1367 |
+
sC_for_tma_partition,
|
| 1368 |
+
gC_for_tma_partition,
|
| 1369 |
+
)
|
| 1370 |
+
return tma_atom_c, bSG_sC, bSG_gC
|
| 1371 |
+
else:
|
| 1372 |
+
tiled_copy_t2r = atom
|
| 1373 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1374 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1375 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_epi)
|
| 1376 |
+
# (T2R, T2R_M, T2R_N)
|
| 1377 |
+
tTR_rC = cute.make_fragment(
|
| 1378 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.c_dtype
|
| 1379 |
+
)
|
| 1380 |
+
simt_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), self.c_dtype)
|
| 1381 |
+
return simt_atom, tTR_rC, tTR_gC
|
| 1382 |
+
|
| 1383 |
+
@staticmethod
|
| 1384 |
+
def _compute_stages(
|
| 1385 |
+
tiled_mma: cute.TiledMma,
|
| 1386 |
+
mma_tiler_mnk: Tuple[int, int, int],
|
| 1387 |
+
a_dtype: Type[cutlass.Numeric],
|
| 1388 |
+
b_dtype: Type[cutlass.Numeric],
|
| 1389 |
+
epi_tile: cute.Tile,
|
| 1390 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1391 |
+
c_layout: utils.LayoutEnum,
|
| 1392 |
+
smem_capacity: int,
|
| 1393 |
+
occupancy: int,
|
| 1394 |
+
use_tma_store: bool,
|
| 1395 |
+
) -> Tuple[int, int, int]:
|
| 1396 |
+
"""Computes the number of stages for A/B/C operands based on heuristics.
|
| 1397 |
+
|
| 1398 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1399 |
+
:type tiled_mma: cute.TiledMma
|
| 1400 |
+
:param mma_tiler_mnk: The shape (M, N, K) of the MMA tiler.
|
| 1401 |
+
:type mma_tiler_mnk: tuple[int, int, int]
|
| 1402 |
+
:param a_dtype: Data type of operand A.
|
| 1403 |
+
:type a_dtype: type[cutlass.Numeric]
|
| 1404 |
+
:param b_dtype: Data type of operand B.
|
| 1405 |
+
:type b_dtype: type[cutlass.Numeric]
|
| 1406 |
+
:param epi_tile: The epilogue tile shape.
|
| 1407 |
+
:type epi_tile: cute.Tile
|
| 1408 |
+
:param c_dtype: Data type of operand C (output).
|
| 1409 |
+
:type c_dtype: type[cutlass.Numeric]
|
| 1410 |
+
:param c_layout: Layout enum of operand C.
|
| 1411 |
+
:type c_layout: utils.LayoutEnum
|
| 1412 |
+
:param smem_capacity: Total available shared memory capacity in bytes.
|
| 1413 |
+
:type smem_capacity: int
|
| 1414 |
+
:param occupancy: Target number of CTAs per SM (occupancy).
|
| 1415 |
+
:type occupancy: int
|
| 1416 |
+
:param use_tma_store: Whether TMA store is enabled.
|
| 1417 |
+
:type use_tma_store: bool
|
| 1418 |
+
|
| 1419 |
+
:return: A tuple containing the computed number of stages for:
|
| 1420 |
+
(ACC stages, A/B operand stages, C stages)
|
| 1421 |
+
:rtype: tuple[int, int, int]
|
| 1422 |
+
"""
|
| 1423 |
+
# Default ACC stages
|
| 1424 |
+
num_acc_stage = 2
|
| 1425 |
+
|
| 1426 |
+
# Default C stages
|
| 1427 |
+
num_c_stage = 2 if use_tma_store else 0
|
| 1428 |
+
|
| 1429 |
+
# Calculate smem layout and size for one stage of A, B, and C
|
| 1430 |
+
a_smem_layout_stage_one = sm100_utils.make_smem_layout_a(
|
| 1431 |
+
tiled_mma,
|
| 1432 |
+
mma_tiler_mnk,
|
| 1433 |
+
a_dtype,
|
| 1434 |
+
1, # a tmp 1 stage is provided
|
| 1435 |
+
)
|
| 1436 |
+
b_smem_layout_staged_one = sm100_utils.make_smem_layout_b(
|
| 1437 |
+
tiled_mma,
|
| 1438 |
+
mma_tiler_mnk,
|
| 1439 |
+
b_dtype,
|
| 1440 |
+
1, # a tmp 1 stage is provided
|
| 1441 |
+
)
|
| 1442 |
+
c_smem_layout_staged_one = (
|
| 1443 |
+
sm100_utils.make_smem_layout_epi(
|
| 1444 |
+
c_dtype,
|
| 1445 |
+
c_layout,
|
| 1446 |
+
epi_tile,
|
| 1447 |
+
1,
|
| 1448 |
+
)
|
| 1449 |
+
if use_tma_store
|
| 1450 |
+
else None
|
| 1451 |
+
)
|
| 1452 |
+
ab_bytes_per_stage = cute.size_in_bytes(
|
| 1453 |
+
a_dtype, a_smem_layout_stage_one
|
| 1454 |
+
) + cute.size_in_bytes(b_dtype, b_smem_layout_staged_one)
|
| 1455 |
+
mbar_helpers_bytes = 1024
|
| 1456 |
+
c_bytes_per_stage = (
|
| 1457 |
+
cute.size_in_bytes(c_dtype, c_smem_layout_staged_one)
|
| 1458 |
+
if use_tma_store
|
| 1459 |
+
else 0
|
| 1460 |
+
)
|
| 1461 |
+
c_bytes = c_bytes_per_stage * num_c_stage
|
| 1462 |
+
|
| 1463 |
+
# Calculate A/B stages:
|
| 1464 |
+
# Start with total smem per CTA (capacity / occupancy)
|
| 1465 |
+
# Subtract reserved bytes and initial C stages bytes
|
| 1466 |
+
# Divide remaining by bytes needed per A/B stage
|
| 1467 |
+
num_ab_stage = (
|
| 1468 |
+
smem_capacity // occupancy - (mbar_helpers_bytes + c_bytes)
|
| 1469 |
+
) // ab_bytes_per_stage
|
| 1470 |
+
|
| 1471 |
+
# Refine epilogue stages:
|
| 1472 |
+
# Calculate remaining smem after allocating for A/B stages and reserved bytes
|
| 1473 |
+
# Add remaining unused smem to epilogue
|
| 1474 |
+
if use_tma_store:
|
| 1475 |
+
num_c_stage += (
|
| 1476 |
+
smem_capacity
|
| 1477 |
+
- occupancy * ab_bytes_per_stage * num_ab_stage
|
| 1478 |
+
- occupancy * (mbar_helpers_bytes + c_bytes)
|
| 1479 |
+
) // (occupancy * c_bytes_per_stage)
|
| 1480 |
+
return num_acc_stage, num_ab_stage, num_c_stage
|
| 1481 |
+
|
| 1482 |
+
@staticmethod
|
| 1483 |
+
def _compute_grid(
|
| 1484 |
+
c: cute.Tensor,
|
| 1485 |
+
cta_tile_shape_mnk: Tuple[int, int, int],
|
| 1486 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1487 |
+
max_active_clusters: cutlass.Constexpr,
|
| 1488 |
+
) -> Tuple[utils.PersistentTileSchedulerParams, Tuple[int, int, int]]:
|
| 1489 |
+
"""Use persistent tile scheduler to compute the grid size for the output tensor C.
|
| 1490 |
+
|
| 1491 |
+
:param c: The output tensor C
|
| 1492 |
+
:type c: cute.Tensor
|
| 1493 |
+
:param cta_tile_shape_mnk: The shape (M, N, K) of the CTA tile.
|
| 1494 |
+
:type cta_tile_shape_mnk: tuple[int, int, int]
|
| 1495 |
+
:param cluster_shape_mn: Shape of each cluster in M, N dimensions.
|
| 1496 |
+
:type cluster_shape_mn: tuple[int, int]
|
| 1497 |
+
:param max_active_clusters: Maximum number of active clusters.
|
| 1498 |
+
:type max_active_clusters: cutlass.Constexpr
|
| 1499 |
+
|
| 1500 |
+
:return: A tuple containing:
|
| 1501 |
+
- tile_sched_params: Parameters for the persistent tile scheduler.
|
| 1502 |
+
- grid: Grid shape for kernel launch.
|
| 1503 |
+
:rtype: Tuple[utils.PersistentTileSchedulerParams, tuple[int, int, int]]
|
| 1504 |
+
"""
|
| 1505 |
+
c_shape = cute.slice_(cta_tile_shape_mnk, (None, None, 0))
|
| 1506 |
+
gc = cute.zipped_divide(c, tiler=c_shape)
|
| 1507 |
+
num_ctas_mnl = gc[(0, (None, None, None))].shape
|
| 1508 |
+
cluster_shape_mnl = (*cluster_shape_mn, 1)
|
| 1509 |
+
|
| 1510 |
+
tile_sched_params = utils.PersistentTileSchedulerParams(
|
| 1511 |
+
num_ctas_mnl, cluster_shape_mnl
|
| 1512 |
+
)
|
| 1513 |
+
grid = utils.StaticPersistentTileScheduler.get_grid_shape(
|
| 1514 |
+
tile_sched_params, max_active_clusters
|
| 1515 |
+
)
|
| 1516 |
+
|
| 1517 |
+
return tile_sched_params, grid
|
| 1518 |
+
|
| 1519 |
+
@staticmethod
|
| 1520 |
+
def _compute_num_tmem_alloc_cols(
|
| 1521 |
+
tiled_mma: cute.TiledMma,
|
| 1522 |
+
mma_tiler: Tuple[int, int, int],
|
| 1523 |
+
num_acc_stage: int,
|
| 1524 |
+
) -> int:
|
| 1525 |
+
"""
|
| 1526 |
+
Compute the number of tensor memory allocation columns.
|
| 1527 |
+
|
| 1528 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1529 |
+
:type tiled_mma: cute.TiledMma
|
| 1530 |
+
:param mma_tiler: The shape (M, N, K) of the MMA tile.
|
| 1531 |
+
:type mma_tiler: tuple[int, int, int]
|
| 1532 |
+
:param num_acc_stage: The stage of the accumulator tensor.
|
| 1533 |
+
:type num_acc_stage: int
|
| 1534 |
+
|
| 1535 |
+
:return: The number of tensor memory allocation columns.
|
| 1536 |
+
:rtype: int
|
| 1537 |
+
"""
|
| 1538 |
+
acc_shape = tiled_mma.partition_shape_C(mma_tiler[:2])
|
| 1539 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(cute.append(acc_shape, num_acc_stage))
|
| 1540 |
+
num_tmem_alloc_cols = utils.get_num_tmem_alloc_cols(tCtAcc_fake)
|
| 1541 |
+
|
| 1542 |
+
return num_tmem_alloc_cols
|
| 1543 |
+
|
| 1544 |
+
@staticmethod
|
| 1545 |
+
def is_valid_dtypes(
|
| 1546 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1547 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1548 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1549 |
+
) -> bool:
|
| 1550 |
+
"""
|
| 1551 |
+
Check if the dtypes are valid
|
| 1552 |
+
|
| 1553 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1554 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1555 |
+
:param acc_dtype: The data type of the accumulator
|
| 1556 |
+
:type acc_dtype: Type[cutlass.Numeric]
|
| 1557 |
+
:param c_dtype: The data type of the output tensor
|
| 1558 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1559 |
+
|
| 1560 |
+
:return: True if the dtypes are valid, False otherwise
|
| 1561 |
+
:rtype: bool
|
| 1562 |
+
"""
|
| 1563 |
+
is_valid = True
|
| 1564 |
+
if ab_dtype not in {
|
| 1565 |
+
cutlass.Float16,
|
| 1566 |
+
cutlass.BFloat16,
|
| 1567 |
+
cutlass.TFloat32,
|
| 1568 |
+
cutlass.Uint8,
|
| 1569 |
+
cutlass.Int8,
|
| 1570 |
+
cutlass.Float8E4M3FN,
|
| 1571 |
+
cutlass.Float8E5M2,
|
| 1572 |
+
}:
|
| 1573 |
+
is_valid = False
|
| 1574 |
+
if (
|
| 1575 |
+
acc_dtype not in {cutlass.Float32, cutlass.Float16, cutlass.Int32}
|
| 1576 |
+
or acc_dtype == cutlass.Float16
|
| 1577 |
+
and ab_dtype
|
| 1578 |
+
not in {cutlass.Float16, cutlass.Float8E4M3FN, cutlass.Float8E5M2}
|
| 1579 |
+
or acc_dtype == cutlass.Int32
|
| 1580 |
+
and ab_dtype not in {cutlass.Uint8, cutlass.Int8}
|
| 1581 |
+
):
|
| 1582 |
+
is_valid = False
|
| 1583 |
+
if (
|
| 1584 |
+
acc_dtype == cutlass.Float32
|
| 1585 |
+
and c_dtype
|
| 1586 |
+
not in {
|
| 1587 |
+
cutlass.Float32,
|
| 1588 |
+
cutlass.Float16,
|
| 1589 |
+
cutlass.BFloat16,
|
| 1590 |
+
cutlass.Float8E4M3FN,
|
| 1591 |
+
cutlass.Float8E5M2,
|
| 1592 |
+
cutlass.Int32,
|
| 1593 |
+
cutlass.Int8,
|
| 1594 |
+
cutlass.Uint8,
|
| 1595 |
+
}
|
| 1596 |
+
or acc_dtype == cutlass.Float16
|
| 1597 |
+
and c_dtype
|
| 1598 |
+
not in {
|
| 1599 |
+
cutlass.BFloat16,
|
| 1600 |
+
cutlass.Float16,
|
| 1601 |
+
}
|
| 1602 |
+
or acc_dtype == cutlass.Int32
|
| 1603 |
+
and c_dtype
|
| 1604 |
+
not in {
|
| 1605 |
+
cutlass.BFloat16,
|
| 1606 |
+
cutlass.Float16,
|
| 1607 |
+
cutlass.Float32,
|
| 1608 |
+
cutlass.Int32,
|
| 1609 |
+
cutlass.Int8,
|
| 1610 |
+
cutlass.Uint8,
|
| 1611 |
+
}
|
| 1612 |
+
):
|
| 1613 |
+
is_valid = False
|
| 1614 |
+
return is_valid
|
| 1615 |
+
|
| 1616 |
+
@staticmethod
|
| 1617 |
+
def is_valid_mma_tiler_and_cluster_shape(
|
| 1618 |
+
use_2cta_instrs: bool,
|
| 1619 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1620 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1621 |
+
) -> bool:
|
| 1622 |
+
"""
|
| 1623 |
+
Check if the mma tiler and cluster shape are valid
|
| 1624 |
+
|
| 1625 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1626 |
+
:type use_2cta_instrs: bool
|
| 1627 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1628 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1629 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1630 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1631 |
+
|
| 1632 |
+
:return: True if the mma tiler and cluster shape are valid, False otherwise
|
| 1633 |
+
:rtype: bool
|
| 1634 |
+
"""
|
| 1635 |
+
is_valid = True
|
| 1636 |
+
# Skip invalid mma tile shape
|
| 1637 |
+
if not (
|
| 1638 |
+
(not use_2cta_instrs and mma_tiler_mn[0] in [64, 128])
|
| 1639 |
+
or (use_2cta_instrs and mma_tiler_mn[0] in [128, 256])
|
| 1640 |
+
):
|
| 1641 |
+
is_valid = False
|
| 1642 |
+
if mma_tiler_mn[1] not in range(32, 257, 32):
|
| 1643 |
+
is_valid = False
|
| 1644 |
+
# Skip illegal cluster shape
|
| 1645 |
+
if cluster_shape_mn[0] % (2 if use_2cta_instrs else 1) != 0:
|
| 1646 |
+
is_valid = False
|
| 1647 |
+
# Skip invalid cluster shape
|
| 1648 |
+
is_power_of_2 = lambda x: x > 0 and (x & (x - 1)) == 0
|
| 1649 |
+
if (
|
| 1650 |
+
cluster_shape_mn[0] * cluster_shape_mn[1] > 16
|
| 1651 |
+
or cluster_shape_mn[0] <= 0
|
| 1652 |
+
or cluster_shape_mn[1] <= 0
|
| 1653 |
+
or not is_power_of_2(cluster_shape_mn[0])
|
| 1654 |
+
or not is_power_of_2(cluster_shape_mn[1])
|
| 1655 |
+
):
|
| 1656 |
+
is_valid = False
|
| 1657 |
+
return is_valid
|
| 1658 |
+
|
| 1659 |
+
@staticmethod
|
| 1660 |
+
def is_valid_tensor_alignment(
|
| 1661 |
+
m: int,
|
| 1662 |
+
n: int,
|
| 1663 |
+
k: int,
|
| 1664 |
+
l: int,
|
| 1665 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1666 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1667 |
+
a_major: str,
|
| 1668 |
+
b_major: str,
|
| 1669 |
+
c_major: str,
|
| 1670 |
+
) -> bool:
|
| 1671 |
+
"""
|
| 1672 |
+
Check if the tensor alignment is valid
|
| 1673 |
+
|
| 1674 |
+
:param m: The number of rows in the A tensor
|
| 1675 |
+
:type m: int
|
| 1676 |
+
:param n: The number of columns in the B tensor
|
| 1677 |
+
:type n: int
|
| 1678 |
+
:param k: The number of columns in the A tensor
|
| 1679 |
+
:type k: int
|
| 1680 |
+
:param l: The number of columns in the C tensor
|
| 1681 |
+
:type l: int
|
| 1682 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1683 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1684 |
+
:param c_dtype: The data type of the output tensor
|
| 1685 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1686 |
+
:param a_major: The major axis of the A tensor
|
| 1687 |
+
:type a_major: str
|
| 1688 |
+
:param b_major: The major axis of the B tensor
|
| 1689 |
+
:type b_major: str
|
| 1690 |
+
:param c_major: The major axis of the C tensor
|
| 1691 |
+
:type c_major: str
|
| 1692 |
+
|
| 1693 |
+
:return: True if the problem shape is valid, False otherwise
|
| 1694 |
+
:rtype: bool
|
| 1695 |
+
"""
|
| 1696 |
+
is_valid = True
|
| 1697 |
+
|
| 1698 |
+
def check_contigous_16B_alignment(dtype, is_mode0_major, tensor_shape):
|
| 1699 |
+
major_mode_idx = 0 if is_mode0_major else 1
|
| 1700 |
+
num_major_elements = tensor_shape[major_mode_idx]
|
| 1701 |
+
num_contiguous_elements = 16 * 8 // dtype.width
|
| 1702 |
+
return num_major_elements % num_contiguous_elements == 0
|
| 1703 |
+
|
| 1704 |
+
if (
|
| 1705 |
+
not check_contigous_16B_alignment(ab_dtype, a_major == "m", (m, k, l))
|
| 1706 |
+
or not check_contigous_16B_alignment(ab_dtype, b_major == "n", (n, k, l))
|
| 1707 |
+
or not check_contigous_16B_alignment(c_dtype, c_major == "m", (m, n, l))
|
| 1708 |
+
):
|
| 1709 |
+
is_valid = False
|
| 1710 |
+
return is_valid
|
| 1711 |
+
|
| 1712 |
+
@staticmethod
|
| 1713 |
+
def is_valid_epilog_store_option(
|
| 1714 |
+
use_2cta_instrs: bool,
|
| 1715 |
+
use_tma_store: bool,
|
| 1716 |
+
m: int,
|
| 1717 |
+
n: int,
|
| 1718 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1719 |
+
) -> bool:
|
| 1720 |
+
"""
|
| 1721 |
+
Check if the epilogue store option is valid
|
| 1722 |
+
|
| 1723 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1724 |
+
:type use_2cta_instrs: bool
|
| 1725 |
+
:param use_tma_store: Whether to use TMA store
|
| 1726 |
+
:type use_tma_store: bool
|
| 1727 |
+
:param m: The number of rows in the A tensor
|
| 1728 |
+
:type m: int
|
| 1729 |
+
:param n: The number of columns in the B tensor
|
| 1730 |
+
:type n: int
|
| 1731 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1732 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1733 |
+
|
| 1734 |
+
:return: True if the epilogue store option is valid, False otherwise
|
| 1735 |
+
:rtype: bool
|
| 1736 |
+
"""
|
| 1737 |
+
|
| 1738 |
+
is_valid = True
|
| 1739 |
+
# None TMA store version does not have predication, can not support OOB tiles
|
| 1740 |
+
cta_tile_shape_mn = (
|
| 1741 |
+
mma_tiler_mn[0] // (2 if use_2cta_instrs else 1),
|
| 1742 |
+
mma_tiler_mn[1],
|
| 1743 |
+
)
|
| 1744 |
+
if not use_tma_store:
|
| 1745 |
+
if not (m % cta_tile_shape_mn[0] == 0 and n % cta_tile_shape_mn[1] == 0):
|
| 1746 |
+
is_valid = False
|
| 1747 |
+
return is_valid
|
| 1748 |
+
|
| 1749 |
+
@staticmethod
|
| 1750 |
+
def can_implement(
|
| 1751 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1752 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1753 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1754 |
+
use_2cta_instrs: bool,
|
| 1755 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1756 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1757 |
+
use_tma_store: bool,
|
| 1758 |
+
m: int,
|
| 1759 |
+
n: int,
|
| 1760 |
+
k: int,
|
| 1761 |
+
l: int,
|
| 1762 |
+
a_major: str,
|
| 1763 |
+
b_major: str,
|
| 1764 |
+
c_major: str,
|
| 1765 |
+
) -> bool:
|
| 1766 |
+
"""
|
| 1767 |
+
Check if the gemm can be implemented
|
| 1768 |
+
|
| 1769 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1770 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1771 |
+
:param acc_dtype: The data type of the accumulator
|
| 1772 |
+
:type acc_dtype: Type[cutlass.Numeric]
|
| 1773 |
+
:param c_dtype: The data type of the output tensor
|
| 1774 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1775 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1776 |
+
:type use_2cta_instrs: bool
|
| 1777 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1778 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1779 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1780 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1781 |
+
:param use_tma_store: Whether to use TMA store
|
| 1782 |
+
:type use_tma_store: bool
|
| 1783 |
+
:param m: The number of rows in the A tensor
|
| 1784 |
+
:type m: int
|
| 1785 |
+
:param n: The number of columns in the B tensor
|
| 1786 |
+
:type n: int
|
| 1787 |
+
:param k: The number of columns in the A tensor
|
| 1788 |
+
:type k: int
|
| 1789 |
+
:param l: The number of columns in the C tensor
|
| 1790 |
+
:type l: int
|
| 1791 |
+
:param a_major: The major axis of the A tensor
|
| 1792 |
+
:type a_major: str
|
| 1793 |
+
:param b_major: The major axis of the B tensor
|
| 1794 |
+
:type b_major: str
|
| 1795 |
+
:param c_major: The major axis of the C tensor
|
| 1796 |
+
:type c_major: str
|
| 1797 |
+
|
| 1798 |
+
:return: True if the gemm can be implemented, False otherwise
|
| 1799 |
+
:rtype: bool
|
| 1800 |
+
"""
|
| 1801 |
+
can_implement = True
|
| 1802 |
+
# Skip unsupported types
|
| 1803 |
+
if not PersistentDenseGemmKernel.is_valid_dtypes(ab_dtype, acc_dtype, c_dtype):
|
| 1804 |
+
can_implement = False
|
| 1805 |
+
# Skip invalid mma tile shape and cluster shape
|
| 1806 |
+
if not PersistentDenseGemmKernel.is_valid_mma_tiler_and_cluster_shape(
|
| 1807 |
+
use_2cta_instrs, mma_tiler_mn, cluster_shape_mn
|
| 1808 |
+
):
|
| 1809 |
+
can_implement = False
|
| 1810 |
+
# Skip illegal problem shape for load/store alignment
|
| 1811 |
+
if not PersistentDenseGemmKernel.is_valid_tensor_alignment(
|
| 1812 |
+
m, n, k, l, ab_dtype, c_dtype, a_major, b_major, c_major
|
| 1813 |
+
):
|
| 1814 |
+
can_implement = False
|
| 1815 |
+
# Skip invalid epilogue store option
|
| 1816 |
+
if not PersistentDenseGemmKernel.is_valid_epilog_store_option(
|
| 1817 |
+
use_2cta_instrs, use_tma_store, m, n, mma_tiler_mn
|
| 1818 |
+
):
|
| 1819 |
+
can_implement = False
|
| 1820 |
+
return can_implement
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
def run(
|
| 1824 |
+
mnkl: Tuple[int, int, int, int],
|
| 1825 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1826 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1827 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1828 |
+
a_major: str,
|
| 1829 |
+
b_major: str,
|
| 1830 |
+
c_major: str,
|
| 1831 |
+
mma_tiler_mn: Tuple[int, int] = (256, 256),
|
| 1832 |
+
cluster_shape_mn: Tuple[int, int] = (2, 1),
|
| 1833 |
+
use_2cta_instrs: bool = True,
|
| 1834 |
+
use_tma_store: bool = True,
|
| 1835 |
+
tolerance: float = 1e-01,
|
| 1836 |
+
warmup_iterations: int = 0,
|
| 1837 |
+
iterations: int = 1,
|
| 1838 |
+
skip_ref_check: bool = False,
|
| 1839 |
+
use_cold_l2: bool = False,
|
| 1840 |
+
**kwargs,
|
| 1841 |
+
):
|
| 1842 |
+
"""Execute a persistent batched dense GEMM operation on Blackwell architecture with performance benchmarking.
|
| 1843 |
+
|
| 1844 |
+
This function prepares input tensors, configures and launches the persistent GEMM kernel,
|
| 1845 |
+
optionally performs reference validation, and benchmarks the execution performance.
|
| 1846 |
+
|
| 1847 |
+
:param mnkl: Problem size (M, N, K, L)
|
| 1848 |
+
:type mnkl: Tuple[int, int, int, int]
|
| 1849 |
+
:param ab_dtype: Data type for input tensors A and B
|
| 1850 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1851 |
+
:param c_dtype: Data type for output tensor C
|
| 1852 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1853 |
+
:param acc_dtype: Data type for accumulation during matrix multiplication
|
| 1854 |
+
:type acc_dtype: Type[cutlass.Numeric]
|
| 1855 |
+
:param a_major/b_major/c_major: Memory layout of tensor A/B/C
|
| 1856 |
+
:type a_major/b_major/c_major: str
|
| 1857 |
+
:param mma_tiler_mn: MMA tiling size. If not specified in the decorator parameters, the autotuner will use the
|
| 1858 |
+
default value of (256, 256). Otherwise, the autotuner will use the value specified in the decorator parameters.
|
| 1859 |
+
:type mma_tiler_mn: Tuple[int, int], optional
|
| 1860 |
+
:param cluster_shape_mn: Cluster shape. If not specified in the decorator parameters, the autotuner will use the
|
| 1861 |
+
default value of (2, 1). Otherwise, the autotuner will use the value specified in the decorator parameters.
|
| 1862 |
+
:type cluster_shape_mn: Tuple[int, int], optional
|
| 1863 |
+
:param use_2cta_instrs: Whether to use 2CTA instructions. If not specified in the decorator parameters, the autotuner
|
| 1864 |
+
will use the default value of True. Otherwise, the autotuner will use the value specified in the decorator parameters.
|
| 1865 |
+
:type use_2cta_instrs: bool, optional
|
| 1866 |
+
:param use_tma_store: Whether to use TMA store. If not specified in the decorator parameters, the autotuner will use
|
| 1867 |
+
the default value of True. Otherwise, the autotuner will use the value specified in the decorator parameters.
|
| 1868 |
+
:type use_tma_store: bool, optional
|
| 1869 |
+
:param tolerance: Tolerance value for reference validation comparison, defaults to 1e-01
|
| 1870 |
+
:type tolerance: float, optional
|
| 1871 |
+
:param warmup_iterations: Number of warmup iterations before benchmarking, defaults to 0
|
| 1872 |
+
:type warmup_iterations: int, optional
|
| 1873 |
+
:param iterations: Number of benchmark iterations to run, defaults to 1
|
| 1874 |
+
:type iterations: int, optional
|
| 1875 |
+
:param skip_ref_check: Whether to skip reference result validation, defaults to False
|
| 1876 |
+
:type skip_ref_check: bool, optional
|
| 1877 |
+
:param use_cold_l2: Whether to use circular buffer strategy to ensure cold L2 cache, defaults to False
|
| 1878 |
+
:type use_cold_l2: bool, optional
|
| 1879 |
+
:raises RuntimeError: If CUDA GPU is not available
|
| 1880 |
+
:raises ValueError: If the configuration is invalid or unsupported by the kernel
|
| 1881 |
+
:return: Execution time of the GEMM kernel
|
| 1882 |
+
:rtype: float
|
| 1883 |
+
"""
|
| 1884 |
+
print(f"Running Blackwell Persistent Dense GEMM test with:")
|
| 1885 |
+
print(f"mnkl: {mnkl}")
|
| 1886 |
+
print(f"AB dtype: {ab_dtype}, C dtype: {c_dtype}, Acc dtype: {acc_dtype}")
|
| 1887 |
+
print(f"Matrix majors - A: {a_major}, B: {b_major}, C: {c_major}")
|
| 1888 |
+
print(f"Mma Tiler (M, N): {mma_tiler_mn}, Cluster Shape (M, N): {cluster_shape_mn}")
|
| 1889 |
+
print(f"2CTA MMA instructions: {'True' if use_2cta_instrs else 'False'}")
|
| 1890 |
+
print(f"Use TMA Store: {'True' if use_tma_store else 'False'}")
|
| 1891 |
+
print(f"Tolerance: {tolerance}")
|
| 1892 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 1893 |
+
print(f"Iterations: {iterations}")
|
| 1894 |
+
print(f"Skip reference checking: {skip_ref_check}")
|
| 1895 |
+
print(f"Use cold L2: {'True' if use_cold_l2 else 'False'}")
|
| 1896 |
+
|
| 1897 |
+
# Unpack parameters
|
| 1898 |
+
m, n, k, l = mnkl
|
| 1899 |
+
|
| 1900 |
+
# Skip unsupported testcase
|
| 1901 |
+
if not PersistentDenseGemmKernel.can_implement(
|
| 1902 |
+
ab_dtype,
|
| 1903 |
+
acc_dtype,
|
| 1904 |
+
c_dtype,
|
| 1905 |
+
use_2cta_instrs,
|
| 1906 |
+
mma_tiler_mn,
|
| 1907 |
+
cluster_shape_mn,
|
| 1908 |
+
use_tma_store,
|
| 1909 |
+
m,
|
| 1910 |
+
n,
|
| 1911 |
+
k,
|
| 1912 |
+
l,
|
| 1913 |
+
a_major,
|
| 1914 |
+
b_major,
|
| 1915 |
+
c_major,
|
| 1916 |
+
):
|
| 1917 |
+
raise TypeError(
|
| 1918 |
+
f"Unsupported testcase {ab_dtype}, {acc_dtype}, {c_dtype}, {use_2cta_instrs}, {mma_tiler_mn}, {cluster_shape_mn}, {use_tma_store}, {m}, {n}, {k}, {l}, {a_major}, {b_major}, {c_major}"
|
| 1919 |
+
)
|
| 1920 |
+
|
| 1921 |
+
if not torch.cuda.is_available():
|
| 1922 |
+
raise RuntimeError("GPU is required to run this example!")
|
| 1923 |
+
|
| 1924 |
+
torch.manual_seed(1111)
|
| 1925 |
+
|
| 1926 |
+
# Create and permute tensor A/B/C
|
| 1927 |
+
def create_and_permute_tensor(
|
| 1928 |
+
l, mode0, mode1, is_mode0_major, dtype, is_dynamic_layout=True
|
| 1929 |
+
):
|
| 1930 |
+
# is_mode0_major: (l, mode1, mode0) -> (mode0, mode1, l)
|
| 1931 |
+
# else: (l, mode0, mode1) -> (mode0, mode1, l)
|
| 1932 |
+
shape = (l, mode1, mode0) if is_mode0_major else (l, mode0, mode1)
|
| 1933 |
+
permute_order = (2, 1, 0) if is_mode0_major else (1, 2, 0)
|
| 1934 |
+
is_unsigned = dtype in {cutlass.Uint8}
|
| 1935 |
+
# Temporarily use uint8 as torch does not support fp8 type
|
| 1936 |
+
torch_dtype = (
|
| 1937 |
+
cutlass_torch.dtype(dtype)
|
| 1938 |
+
if dtype not in {cutlass.Float8E5M2, cutlass.Float8E4M3FN}
|
| 1939 |
+
else torch.uint8
|
| 1940 |
+
)
|
| 1941 |
+
|
| 1942 |
+
# Create dtype torch tensor (cpu)
|
| 1943 |
+
torch_tensor_cpu = cutlass_torch.create_and_permute_torch_tensor(
|
| 1944 |
+
shape,
|
| 1945 |
+
torch_dtype,
|
| 1946 |
+
permute_order=permute_order,
|
| 1947 |
+
init_type=cutlass_torch.TensorInitType.RANDOM,
|
| 1948 |
+
init_config=cutlass_torch.RandomInitConfig(
|
| 1949 |
+
min_val=0 if is_unsigned else -2, max_val=4 if is_unsigned else 2
|
| 1950 |
+
),
|
| 1951 |
+
)
|
| 1952 |
+
# Create dtype torch tensor (gpu)
|
| 1953 |
+
torch_tensor = torch_tensor_cpu.cuda()
|
| 1954 |
+
|
| 1955 |
+
# Create f32 torch tensor (cpu)
|
| 1956 |
+
f32_torch_tensor = torch_tensor_cpu.to(dtype=torch.float32)
|
| 1957 |
+
|
| 1958 |
+
# Create dtype cute tensor (gpu)
|
| 1959 |
+
cute_tensor = from_dlpack(torch_tensor, assumed_align=16)
|
| 1960 |
+
cute_tensor.element_type = dtype
|
| 1961 |
+
if is_dynamic_layout:
|
| 1962 |
+
cute_tensor = cute_tensor.mark_layout_dynamic(
|
| 1963 |
+
leading_dim=(0 if is_mode0_major else 1)
|
| 1964 |
+
)
|
| 1965 |
+
cute_tensor = cutlass_torch.convert_cute_tensor(
|
| 1966 |
+
f32_torch_tensor,
|
| 1967 |
+
cute_tensor,
|
| 1968 |
+
dtype,
|
| 1969 |
+
is_dynamic_layout=is_dynamic_layout,
|
| 1970 |
+
)
|
| 1971 |
+
|
| 1972 |
+
return f32_torch_tensor, cute_tensor, torch_tensor, torch_tensor_cpu
|
| 1973 |
+
|
| 1974 |
+
a_ref, a_tensor, a_torch, a_torch_cpu = create_and_permute_tensor(
|
| 1975 |
+
l, m, k, a_major == "m", ab_dtype, is_dynamic_layout=True
|
| 1976 |
+
)
|
| 1977 |
+
b_ref, b_tensor, b_torch, b_torch_cpu = create_and_permute_tensor(
|
| 1978 |
+
l, n, k, b_major == "n", ab_dtype, is_dynamic_layout=True
|
| 1979 |
+
)
|
| 1980 |
+
c_ref, c_tensor, c_torch, c_torch_cpu = create_and_permute_tensor(
|
| 1981 |
+
l, m, n, c_major == "m", c_dtype, is_dynamic_layout=True
|
| 1982 |
+
)
|
| 1983 |
+
|
| 1984 |
+
# Configure gemm kernel
|
| 1985 |
+
gemm = PersistentDenseGemmKernel(
|
| 1986 |
+
acc_dtype,
|
| 1987 |
+
use_2cta_instrs,
|
| 1988 |
+
mma_tiler_mn,
|
| 1989 |
+
cluster_shape_mn,
|
| 1990 |
+
use_tma_store,
|
| 1991 |
+
)
|
| 1992 |
+
|
| 1993 |
+
# Compute max active clusters on current device
|
| 1994 |
+
hardware_info = cutlass.utils.HardwareInfo()
|
| 1995 |
+
max_active_clusters = hardware_info.get_max_active_clusters(
|
| 1996 |
+
cluster_shape_mn[0] * cluster_shape_mn[1]
|
| 1997 |
+
)
|
| 1998 |
+
|
| 1999 |
+
# Get current CUDA stream from PyTorch
|
| 2000 |
+
torch_stream = torch.cuda.current_stream()
|
| 2001 |
+
# Get the raw stream pointer as a CUstream
|
| 2002 |
+
current_stream = cuda.CUstream(torch_stream.cuda_stream)
|
| 2003 |
+
# Compile gemm kernel
|
| 2004 |
+
compiled_gemm = cute.compile(
|
| 2005 |
+
gemm, a_tensor, b_tensor, c_tensor, max_active_clusters, current_stream
|
| 2006 |
+
)
|
| 2007 |
+
|
| 2008 |
+
if not skip_ref_check:
|
| 2009 |
+
compiled_gemm(a_tensor, b_tensor, c_tensor, current_stream)
|
| 2010 |
+
if ab_dtype in {
|
| 2011 |
+
cutlass.Int8,
|
| 2012 |
+
cutlass.Uint8,
|
| 2013 |
+
cutlass.Float8E4M3FN,
|
| 2014 |
+
cutlass.Float8E5M2,
|
| 2015 |
+
}:
|
| 2016 |
+
ref = torch.einsum("mkl,nkl->mnl", a_ref.cpu(), b_ref.cpu())
|
| 2017 |
+
else:
|
| 2018 |
+
ref = (torch.einsum("mkl,nkl->mnl", a_ref, b_ref)).cpu()
|
| 2019 |
+
|
| 2020 |
+
# Copy gpu result back
|
| 2021 |
+
gpu_c = c_torch.cpu()
|
| 2022 |
+
|
| 2023 |
+
# Convert ref to c_type
|
| 2024 |
+
if c_dtype == cutlass.Float32:
|
| 2025 |
+
ref_c = ref
|
| 2026 |
+
elif c_dtype in {cutlass.Float8E5M2, cutlass.Float8E4M3FN}:
|
| 2027 |
+
# m major: (l, n, m) -> (m, n, l)
|
| 2028 |
+
# n major: (l, m, n) -> (m, n, l)
|
| 2029 |
+
permute_order = (1, 2, 0) if c_major == "n" else (2, 1, 0)
|
| 2030 |
+
shape = (l, m, n) if c_major == "n" else (l, n, m)
|
| 2031 |
+
f8_torch_tensor = cutlass_torch.create_and_permute_torch_tensor(
|
| 2032 |
+
shape,
|
| 2033 |
+
torch.uint8,
|
| 2034 |
+
permute_order=permute_order,
|
| 2035 |
+
init_type=cutlass_torch.TensorInitType.SKIP,
|
| 2036 |
+
).cuda()
|
| 2037 |
+
# Create dtype cute tensor (gpu)
|
| 2038 |
+
ref_c_tensor = from_dlpack(
|
| 2039 |
+
f8_torch_tensor, assumed_align=16
|
| 2040 |
+
).mark_layout_dynamic(leading_dim=(1 if c_major == "n" else 0))
|
| 2041 |
+
ref_c_tensor.element_type = c_dtype
|
| 2042 |
+
ref_c_tensor = cutlass_torch.convert_cute_tensor(
|
| 2043 |
+
ref,
|
| 2044 |
+
ref_c_tensor,
|
| 2045 |
+
c_dtype,
|
| 2046 |
+
is_dynamic_layout=True,
|
| 2047 |
+
)
|
| 2048 |
+
|
| 2049 |
+
ref_c = f8_torch_tensor.cpu()
|
| 2050 |
+
else:
|
| 2051 |
+
ref_c = ref.to(cutlass_torch.dtype(c_dtype))
|
| 2052 |
+
|
| 2053 |
+
# Reference checking ref_c and gpu_c
|
| 2054 |
+
torch.testing.assert_close(
|
| 2055 |
+
gpu_c,
|
| 2056 |
+
ref_c,
|
| 2057 |
+
atol=tolerance,
|
| 2058 |
+
rtol=1e-05,
|
| 2059 |
+
)
|
| 2060 |
+
|
| 2061 |
+
def generate_tensors():
|
| 2062 |
+
a_tensor, _ = cutlass_torch.cute_tensor_like(
|
| 2063 |
+
a_torch_cpu, ab_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2064 |
+
)
|
| 2065 |
+
b_tensor, _ = cutlass_torch.cute_tensor_like(
|
| 2066 |
+
b_torch_cpu, ab_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2067 |
+
)
|
| 2068 |
+
c_tensor, _ = cutlass_torch.cute_tensor_like(
|
| 2069 |
+
c_torch_cpu, c_dtype, is_dynamic_layout=True, assumed_align=16
|
| 2070 |
+
)
|
| 2071 |
+
return testing.JitArguments(a_tensor, b_tensor, c_tensor, current_stream)
|
| 2072 |
+
|
| 2073 |
+
workspace_count = 1
|
| 2074 |
+
if use_cold_l2:
|
| 2075 |
+
one_workspace_bytes = (
|
| 2076 |
+
a_torch_cpu.numel() * a_torch_cpu.element_size()
|
| 2077 |
+
+ b_torch_cpu.numel() * b_torch_cpu.element_size()
|
| 2078 |
+
+ c_torch_cpu.numel() * c_torch_cpu.element_size()
|
| 2079 |
+
)
|
| 2080 |
+
workspace_count = testing.get_workspace_count(
|
| 2081 |
+
one_workspace_bytes, warmup_iterations, iterations
|
| 2082 |
+
)
|
| 2083 |
+
|
| 2084 |
+
exec_time = testing.benchmark(
|
| 2085 |
+
compiled_gemm,
|
| 2086 |
+
workspace_generator=generate_tensors,
|
| 2087 |
+
workspace_count=workspace_count,
|
| 2088 |
+
stream=current_stream,
|
| 2089 |
+
warmup_iterations=warmup_iterations,
|
| 2090 |
+
iterations=iterations,
|
| 2091 |
+
)
|
| 2092 |
+
|
| 2093 |
+
return exec_time # Return execution time in microseconds
|
| 2094 |
+
|
| 2095 |
+
|
| 2096 |
+
if __name__ == "__main__":
|
| 2097 |
+
|
| 2098 |
+
def parse_comma_separated_ints(s: str) -> Tuple[int, ...]:
|
| 2099 |
+
try:
|
| 2100 |
+
return tuple(int(x.strip()) for x in s.split(","))
|
| 2101 |
+
except ValueError:
|
| 2102 |
+
raise argparse.ArgumentTypeError(
|
| 2103 |
+
"Invalid format. Expected comma-separated integers."
|
| 2104 |
+
)
|
| 2105 |
+
|
| 2106 |
+
parser = argparse.ArgumentParser(
|
| 2107 |
+
description="Example of Dense Persistent GEMM on Blackwell."
|
| 2108 |
+
)
|
| 2109 |
+
|
| 2110 |
+
parser.add_argument(
|
| 2111 |
+
"--mnkl",
|
| 2112 |
+
type=parse_comma_separated_ints,
|
| 2113 |
+
default=(256, 256, 512, 1),
|
| 2114 |
+
help="mnkl dimensions (comma-separated)",
|
| 2115 |
+
)
|
| 2116 |
+
parser.add_argument(
|
| 2117 |
+
"--mma_tiler_mn",
|
| 2118 |
+
type=parse_comma_separated_ints,
|
| 2119 |
+
default=(128, 128),
|
| 2120 |
+
help="Mma tile shape (comma-separated)",
|
| 2121 |
+
)
|
| 2122 |
+
parser.add_argument(
|
| 2123 |
+
"--cluster_shape_mn",
|
| 2124 |
+
type=parse_comma_separated_ints,
|
| 2125 |
+
default=(1, 1),
|
| 2126 |
+
help="Cluster shape (comma-separated)",
|
| 2127 |
+
)
|
| 2128 |
+
parser.add_argument("--ab_dtype", type=cutlass.dtype, default=cutlass.TFloat32)
|
| 2129 |
+
parser.add_argument("--c_dtype", type=cutlass.dtype, default=cutlass.Float32)
|
| 2130 |
+
parser.add_argument("--acc_dtype", type=cutlass.dtype, default=cutlass.Float32)
|
| 2131 |
+
parser.add_argument(
|
| 2132 |
+
"--use_2cta_instrs",
|
| 2133 |
+
action="store_true",
|
| 2134 |
+
help="Enable 2CTA MMA instructions feature",
|
| 2135 |
+
)
|
| 2136 |
+
parser.add_argument("--a_major", choices=["k", "m"], type=str, default="k")
|
| 2137 |
+
parser.add_argument("--b_major", choices=["k", "n"], type=str, default="k")
|
| 2138 |
+
parser.add_argument("--c_major", choices=["n", "m"], type=str, default="n")
|
| 2139 |
+
parser.add_argument(
|
| 2140 |
+
"--use_tma_store", action="store_true", help="Use tma store or not"
|
| 2141 |
+
)
|
| 2142 |
+
parser.add_argument(
|
| 2143 |
+
"--tolerance", type=float, default=1e-01, help="Tolerance for validation"
|
| 2144 |
+
)
|
| 2145 |
+
parser.add_argument(
|
| 2146 |
+
"--warmup_iterations", type=int, default=0, help="Warmup iterations"
|
| 2147 |
+
)
|
| 2148 |
+
parser.add_argument(
|
| 2149 |
+
"--iterations",
|
| 2150 |
+
type=int,
|
| 2151 |
+
default=1,
|
| 2152 |
+
help="Number of iterations to run the kernel",
|
| 2153 |
+
)
|
| 2154 |
+
parser.add_argument(
|
| 2155 |
+
"--skip_ref_check", action="store_true", help="Skip reference checking"
|
| 2156 |
+
)
|
| 2157 |
+
parser.add_argument(
|
| 2158 |
+
"--use_cold_l2",
|
| 2159 |
+
action="store_true",
|
| 2160 |
+
default=False,
|
| 2161 |
+
help="Use circular buffer tensor sets to ensure L2 cold cache",
|
| 2162 |
+
)
|
| 2163 |
+
|
| 2164 |
+
args = parser.parse_args()
|
| 2165 |
+
|
| 2166 |
+
if len(args.mnkl) != 4:
|
| 2167 |
+
parser.error("--mnkl must contain exactly 4 values")
|
| 2168 |
+
|
| 2169 |
+
if len(args.mma_tiler_mn) != 2:
|
| 2170 |
+
parser.error("--mma_tiler_mn must contain exactly 2 values")
|
| 2171 |
+
|
| 2172 |
+
if len(args.cluster_shape_mn) != 2:
|
| 2173 |
+
parser.error("--cluster_shape_mn must contain exactly 2 values")
|
| 2174 |
+
|
| 2175 |
+
run(
|
| 2176 |
+
args.mnkl,
|
| 2177 |
+
args.ab_dtype,
|
| 2178 |
+
args.c_dtype,
|
| 2179 |
+
args.acc_dtype,
|
| 2180 |
+
args.a_major,
|
| 2181 |
+
args.b_major,
|
| 2182 |
+
args.c_major,
|
| 2183 |
+
args.mma_tiler_mn,
|
| 2184 |
+
args.cluster_shape_mn,
|
| 2185 |
+
args.use_2cta_instrs,
|
| 2186 |
+
args.use_tma_store,
|
| 2187 |
+
args.tolerance,
|
| 2188 |
+
args.warmup_iterations,
|
| 2189 |
+
args.iterations,
|
| 2190 |
+
args.skip_ref_check,
|
| 2191 |
+
args.use_cold_l2,
|
| 2192 |
+
)
|
| 2193 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/dense_gemm_software_pipeline.py
ADDED
|
@@ -0,0 +1,1831 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
from typing import Optional, Type, Tuple, Union
|
| 31 |
+
import cuda.bindings.driver as cuda
|
| 32 |
+
|
| 33 |
+
import torch
|
| 34 |
+
|
| 35 |
+
import cutlass
|
| 36 |
+
import cutlass.cute as cute
|
| 37 |
+
import cutlass.utils as utils
|
| 38 |
+
import cutlass.pipeline as pipeline
|
| 39 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 40 |
+
import cutlass.torch as cutlass_torch
|
| 41 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 42 |
+
from cutlass.cute.runtime import from_dlpack
|
| 43 |
+
|
| 44 |
+
"""
|
| 45 |
+
A high-performance batched dense GEMM (C = A * B) example for the NVIDIA Blackwell SM100 architecture
|
| 46 |
+
using CUTE DSL with compiler generated software pipeline.
|
| 47 |
+
- Matrix A is MxKxL, L is batch dimension, A can be row-major("K") or column-major("M")
|
| 48 |
+
- Matrix B is NxKxL, L is batch dimension, B can be row-major("N") or column-major("K")
|
| 49 |
+
- Matrix C is MxNxL, L is batch dimension, C can be row-major("N") or column-major("M")
|
| 50 |
+
|
| 51 |
+
This GEMM kernel supports the following features:
|
| 52 |
+
- Utilizes Tensor Memory Access (TMA) for efficient memory operations
|
| 53 |
+
- Utilizes Blackwell's tcgen05.mma for matrix multiply-accumulate (MMA) operations (including 2cta mma instructions)
|
| 54 |
+
- Implements TMA multicast with cluster to reduce L2 memory traffic
|
| 55 |
+
- Supports multi-stage pipeline to overlap computation and memory access
|
| 56 |
+
|
| 57 |
+
This GEMM works as follows:
|
| 58 |
+
1. Load A and B matrices from global memory (GMEM) to shared memory (SMEM) using TMA operations.
|
| 59 |
+
2. Perform matrix multiply-accumulate (MMA) operations using tcgen05.mma instruction.
|
| 60 |
+
3. Load completed accumulator from tensor memory (TMEM) to registers (RMEM) using tcgen05.ld.
|
| 61 |
+
4. Type convert C matrix to output type.
|
| 62 |
+
5. Optionally store C matrix from registers (RMEM) to shared memory (SMEM) to global memory (GMEM) with TMA operations,
|
| 63 |
+
or directly store C matrix from registers (RMEM) to global memory (GMEM) without TMA operations.
|
| 64 |
+
6. Optionally accept an elementwise lambda function epilogue_op to apply to the output tensor:
|
| 65 |
+
e.g., relu can set epilogue_op = lambda x: cute.where(x > 0, x, cute.full_like(x, 0))
|
| 66 |
+
|
| 67 |
+
SM100 tcgen05.mma instructions operate as follows:
|
| 68 |
+
- Read matrix A from SMEM
|
| 69 |
+
- Read matrix B from SMEM
|
| 70 |
+
- Write accumulator to TMEM
|
| 71 |
+
The accumulator in TMEM must then be loaded to registers before writing back to GMEM.
|
| 72 |
+
|
| 73 |
+
To run this example:
|
| 74 |
+
|
| 75 |
+
.. code-block:: bash
|
| 76 |
+
|
| 77 |
+
python examples/blackwell/dense_gemm_software_pipeline.py \
|
| 78 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 79 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 80 |
+
--mnkl 8192,8192,8192,1 \
|
| 81 |
+
--use_tma_store --use_2cta_instrs
|
| 82 |
+
|
| 83 |
+
The above example command compute batched gemm with M=8192, N=8192, K=8192,
|
| 84 |
+
batch_count=1. The Blackwell tcgen05 MMA tile shape used 2 cta with 256x128
|
| 85 |
+
MMA tile and the cluster shape is (2,1). The input, mma accumulator and output
|
| 86 |
+
data type are set as fp16, fp32 and fp16, respectively.
|
| 87 |
+
|
| 88 |
+
To collect performance with NCU profiler:
|
| 89 |
+
|
| 90 |
+
.. code-block:: bash
|
| 91 |
+
|
| 92 |
+
ncu python examples/blackwell/dense_gemm_software_pipeline.py \
|
| 93 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 94 |
+
--mma_tiler_mn 256,128 --cluster_shape_mn 2,1 \
|
| 95 |
+
--mnkl 8192,8192,8192,1 \
|
| 96 |
+
--use_tma_store --use_2cta_instrs
|
| 97 |
+
|
| 98 |
+
Constraints:
|
| 99 |
+
* Supported input data types: fp16, bf16, tf32, int8, uint8, fp8 (e4m3fn, e5m2),
|
| 100 |
+
see detailed valid dtype combinations in below DenseGemmKernel class documentation
|
| 101 |
+
* A/B tensor must have the same data type
|
| 102 |
+
* Mma tiler M must be 64/128 (use_2cta_instrs=False) or 128/256 (use_2cta_instrs=True)
|
| 103 |
+
* Mma tiler N must be 32-256, step 32
|
| 104 |
+
* Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 105 |
+
* Cluster shape M must be multiple of 2 if use_2cta_instrs=True
|
| 106 |
+
* The contiguous dimension of A/B/C tensors must be at least 16 bytes aligned,
|
| 107 |
+
i.e, number of elements is a multiple of 4, 8, and 16 for TFloat32,
|
| 108 |
+
Float16/BFloat16, and Int8/Uint8/Float8, respectively.
|
| 109 |
+
* OOB tiles are not allowed when TMA store is disabled
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class DenseGemmKernel:
|
| 114 |
+
"""
|
| 115 |
+
This class implements batched matrix multiplication (C = A x B) with support for various data types
|
| 116 |
+
and architectural features specific to Blackwell GPUs.
|
| 117 |
+
|
| 118 |
+
:param acc_dtype: Data type for accumulation during computation
|
| 119 |
+
:type acc_dtype: type[cutlass.Numeric]
|
| 120 |
+
:param use_2cta_instrs: Whether to use CTA group 2 for advanced thread cooperation
|
| 121 |
+
:type use_2cta_instrs: bool
|
| 122 |
+
:param mma_tiler_mn: Shape of the Matrix Multiply-Accumulate (MMA) tiler (M,N)
|
| 123 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 124 |
+
:param cluster_shape_mn: Cluster dimensions (M,N) for parallel processing
|
| 125 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 126 |
+
:param use_tma_store: Whether to use Tensor Memory Access (TMA) for storing results
|
| 127 |
+
:type use_tma_store: bool
|
| 128 |
+
|
| 129 |
+
:note: In current version, A and B tensor must have the same data type
|
| 130 |
+
- i.e., Float8E4M3FN for A and Float8E5M2 for B is not supported
|
| 131 |
+
|
| 132 |
+
:note: Supported A/B data types:
|
| 133 |
+
- TFloat32
|
| 134 |
+
- Float16/BFloat16
|
| 135 |
+
- Int8/Uint8
|
| 136 |
+
- Float8E4M3FN/Float8E5M2
|
| 137 |
+
|
| 138 |
+
:note: Supported accumulator data types:
|
| 139 |
+
- Float32 (for all floating point A/B data types)
|
| 140 |
+
- Float16 (only for fp16 and fp8 A/B data types)
|
| 141 |
+
- Int32 (only for uint8/int8 A/B data types)
|
| 142 |
+
|
| 143 |
+
:note: Supported C data types:
|
| 144 |
+
- Float32 (for float32 and int32 accumulator data types)
|
| 145 |
+
- Int32 (for float32 and int32 accumulator data types)
|
| 146 |
+
- Float16/BFloat16 (for fp16 and fp8 accumulator data types)
|
| 147 |
+
- Int8/Uint8 (for uint8/int8 accumulator data types)
|
| 148 |
+
- Float8E4M3FN/Float8E5M2 (for float32 accumulator data types)
|
| 149 |
+
|
| 150 |
+
:note: Constraints:
|
| 151 |
+
- MMA tiler M must be 64/128 (use_2cta_instrs=False) or 128/256 (use_2cta_instrs=True)
|
| 152 |
+
- MMA tiler N must be 32-256, step 32
|
| 153 |
+
- Cluster shape M must be multiple of 2 if use_2cta_instrs=True
|
| 154 |
+
- Cluster shape M/N must be positive and power of 2, total cluster size <= 16
|
| 155 |
+
|
| 156 |
+
Example:
|
| 157 |
+
>>> gemm = DenseGemmKernel(
|
| 158 |
+
... acc_dtype=cutlass.Float32,
|
| 159 |
+
... use_2cta_instrs=True,
|
| 160 |
+
... mma_tiler_mn=(128, 128),
|
| 161 |
+
... cluster_shape_mn=(2, 2)
|
| 162 |
+
... )
|
| 163 |
+
>>> gemm(a_tensor, b_tensor, c_tensor, stream)
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(
|
| 167 |
+
self,
|
| 168 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 169 |
+
use_2cta_instrs: bool,
|
| 170 |
+
mma_tiler_mn: Tuple[int, int],
|
| 171 |
+
cluster_shape_mn: Tuple[int, int],
|
| 172 |
+
use_tma_store: bool,
|
| 173 |
+
):
|
| 174 |
+
"""Initializes the configuration for a Blackwell dense GEMM kernel.
|
| 175 |
+
|
| 176 |
+
This configuration includes several key aspects:
|
| 177 |
+
|
| 178 |
+
1. MMA Instruction Settings (tcgen05):
|
| 179 |
+
- acc_dtype: Data types for MMA accumulator.
|
| 180 |
+
- mma_tiler_mn: The (M, N) shape of the MMA instruction tiler.
|
| 181 |
+
- use_2cta_instrs: Boolean indicating if the tcgen05 MMA variant
|
| 182 |
+
with cta_group=2 should be used.
|
| 183 |
+
|
| 184 |
+
2. Cluster Shape:
|
| 185 |
+
- cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster.
|
| 186 |
+
|
| 187 |
+
3. Output C tensor store mode:
|
| 188 |
+
- use_tma_store: Boolean indicating whether to use Tensor Memory Access (TMA) for storing results.
|
| 189 |
+
|
| 190 |
+
:param acc_dtype: Data type of the accumulator.
|
| 191 |
+
:type acc_dtype: type[cutlass.Numeric]
|
| 192 |
+
:param mma_tiler_mn: Tuple (M, N) shape of the MMA instruction.
|
| 193 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 194 |
+
:param use_2cta_instrs: Boolean, True to use cta_group=2 MMA variant.
|
| 195 |
+
:type use_2cta_instrs: bool
|
| 196 |
+
:param cluster_shape_mn: Tuple (ClusterM, ClusterN) shape of the cluster.
|
| 197 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 198 |
+
:param use_tma_store: Use Tensor Memory Access (TMA) or normal store for output C tensor.
|
| 199 |
+
:type use_tma_store: bool
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
self.acc_dtype: Type[cutlass.Numeric] = acc_dtype
|
| 203 |
+
self.use_2cta_instrs = use_2cta_instrs
|
| 204 |
+
self.cluster_shape_mn = cluster_shape_mn
|
| 205 |
+
# K dimension is deferred in _setup_attributes
|
| 206 |
+
self.mma_tiler = (*mma_tiler_mn, 1)
|
| 207 |
+
self.use_tma_store = use_tma_store
|
| 208 |
+
|
| 209 |
+
self.cta_group = (
|
| 210 |
+
tcgen05.CtaGroup.TWO if self.use_2cta_instrs else tcgen05.CtaGroup.ONE
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
self.occupancy = 1
|
| 214 |
+
self.threads_per_cta = 128
|
| 215 |
+
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
| 216 |
+
|
| 217 |
+
def _setup_attributes(self):
|
| 218 |
+
"""Set up configurations that are dependent on GEMM inputs
|
| 219 |
+
|
| 220 |
+
This method configures various attributes based on the input tensor properties
|
| 221 |
+
(data types, leading dimensions) and kernel settings:
|
| 222 |
+
- Configuring tiled MMA
|
| 223 |
+
- Computing MMA/cluster/tile shapes
|
| 224 |
+
- Computing cluster layout
|
| 225 |
+
- Computing multicast CTAs for A/B
|
| 226 |
+
- Computing epilogue subtile
|
| 227 |
+
- Setting up A/B/C stage counts in shared memory
|
| 228 |
+
- Computing A/B/C shared memory layout
|
| 229 |
+
- Computing tensor memory allocation columns
|
| 230 |
+
"""
|
| 231 |
+
# Configure tiled mma
|
| 232 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 233 |
+
self.a_dtype,
|
| 234 |
+
self.a_major_mode,
|
| 235 |
+
self.b_major_mode,
|
| 236 |
+
self.acc_dtype,
|
| 237 |
+
self.cta_group,
|
| 238 |
+
self.mma_tiler[:2],
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
# Compute mma/cluster/tile shapes
|
| 242 |
+
mma_inst_shape_k = cute.size(tiled_mma.shape_mnk, mode=[2])
|
| 243 |
+
mma_inst_tile_k = 4
|
| 244 |
+
self.mma_tiler = (
|
| 245 |
+
self.mma_tiler[0],
|
| 246 |
+
self.mma_tiler[1],
|
| 247 |
+
mma_inst_shape_k * mma_inst_tile_k,
|
| 248 |
+
)
|
| 249 |
+
self.cta_tile_shape_mnk = (
|
| 250 |
+
self.mma_tiler[0] // cute.size(tiled_mma.thr_id.shape),
|
| 251 |
+
self.mma_tiler[1],
|
| 252 |
+
self.mma_tiler[2],
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
# Compute cluster layout
|
| 256 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 257 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 258 |
+
(tiled_mma.thr_id.shape,),
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
# Compute number of multicast CTAs for A/B
|
| 262 |
+
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
| 263 |
+
self.num_mcast_ctas_b = cute.size(self.cluster_layout_vmnk.shape[1])
|
| 264 |
+
self.is_a_mcast = self.num_mcast_ctas_a > 1
|
| 265 |
+
self.is_b_mcast = self.num_mcast_ctas_b > 1
|
| 266 |
+
|
| 267 |
+
# Compute epilogue subtile
|
| 268 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 269 |
+
self.epi_tile = sm100_utils.compute_epilogue_tile_shape(
|
| 270 |
+
self.cta_tile_shape_mnk,
|
| 271 |
+
self.use_2cta_instrs,
|
| 272 |
+
self.c_layout,
|
| 273 |
+
self.c_dtype,
|
| 274 |
+
)
|
| 275 |
+
else:
|
| 276 |
+
self.epi_tile = self.cta_tile_shape_mnk[:2]
|
| 277 |
+
|
| 278 |
+
# Setup A/B/C stage count in shared memory
|
| 279 |
+
self.num_acc_stage, self.num_ab_stage, self.num_c_stage = self._compute_stages(
|
| 280 |
+
tiled_mma,
|
| 281 |
+
self.mma_tiler,
|
| 282 |
+
self.a_dtype,
|
| 283 |
+
self.b_dtype,
|
| 284 |
+
self.epi_tile,
|
| 285 |
+
self.c_dtype,
|
| 286 |
+
self.c_layout,
|
| 287 |
+
self.smem_capacity,
|
| 288 |
+
self.occupancy,
|
| 289 |
+
self.use_tma_store,
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
# Compute A/B/C shared memory layout
|
| 293 |
+
self.a_smem_layout_staged = sm100_utils.make_smem_layout_a(
|
| 294 |
+
tiled_mma,
|
| 295 |
+
self.mma_tiler,
|
| 296 |
+
self.a_dtype,
|
| 297 |
+
self.num_ab_stage,
|
| 298 |
+
)
|
| 299 |
+
self.b_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
| 300 |
+
tiled_mma,
|
| 301 |
+
self.mma_tiler,
|
| 302 |
+
self.b_dtype,
|
| 303 |
+
self.num_ab_stage,
|
| 304 |
+
)
|
| 305 |
+
self.c_smem_layout_staged = (
|
| 306 |
+
sm100_utils.make_smem_layout_epi(
|
| 307 |
+
self.c_dtype,
|
| 308 |
+
self.c_layout,
|
| 309 |
+
self.epi_tile,
|
| 310 |
+
self.num_c_stage,
|
| 311 |
+
)
|
| 312 |
+
if self.use_tma_store
|
| 313 |
+
else None
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
# Compute the number of tensor memory allocation columns
|
| 317 |
+
self.num_tmem_alloc_cols = self._compute_num_tmem_alloc_cols(
|
| 318 |
+
tiled_mma, self.mma_tiler
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
@cute.jit
|
| 322 |
+
def __call__(
|
| 323 |
+
self,
|
| 324 |
+
a: cute.Tensor,
|
| 325 |
+
b: cute.Tensor,
|
| 326 |
+
c: cute.Tensor,
|
| 327 |
+
stream: cuda.CUstream,
|
| 328 |
+
epilogue_op: cutlass.Constexpr = lambda x: x,
|
| 329 |
+
):
|
| 330 |
+
"""Execute the GEMM operation in steps:
|
| 331 |
+
- Setup static attributes
|
| 332 |
+
- Setup TMA load/store atoms and tensors
|
| 333 |
+
- Compute grid size
|
| 334 |
+
- Define shared storage for kernel
|
| 335 |
+
- Launch the kernel synchronously
|
| 336 |
+
|
| 337 |
+
:param a: Input tensor A
|
| 338 |
+
:type a: cute.Tensor
|
| 339 |
+
:param b: Input tensor B
|
| 340 |
+
:type b: cute.Tensor
|
| 341 |
+
:param c: Output tensor C
|
| 342 |
+
:type c: cute.Tensor
|
| 343 |
+
:param stream: CUDA stream for asynchronous execution
|
| 344 |
+
:type stream: cuda.CUstream
|
| 345 |
+
:param epilogue_op: Optional elementwise lambda function to apply to the output tensor
|
| 346 |
+
:type epilogue_op: cutlass.Constexpr
|
| 347 |
+
:raises TypeError: If input data types are incompatible with the MMA instruction.
|
| 348 |
+
:raises AssertionError: If OOB (Out-Of-Bounds) tiles are present when TMA store is disabled.
|
| 349 |
+
"""
|
| 350 |
+
# Setup static attributes before smem/grid/tma computation
|
| 351 |
+
self.a_dtype: Type[cutlass.Numeric] = a.element_type
|
| 352 |
+
self.b_dtype: Type[cutlass.Numeric] = b.element_type
|
| 353 |
+
self.c_dtype: Type[cutlass.Numeric] = c.element_type
|
| 354 |
+
self.a_major_mode = utils.LayoutEnum.from_tensor(a).mma_major_mode()
|
| 355 |
+
self.b_major_mode = utils.LayoutEnum.from_tensor(b).mma_major_mode()
|
| 356 |
+
self.c_layout = utils.LayoutEnum.from_tensor(c)
|
| 357 |
+
|
| 358 |
+
# Check if input data types are compatible with MMA instruction
|
| 359 |
+
if cutlass.const_expr(self.a_dtype != self.b_dtype):
|
| 360 |
+
raise TypeError(f"Type must match: {self.a_dtype} != {self.b_dtype}")
|
| 361 |
+
|
| 362 |
+
# Setup attributes that dependent on gemm inputs
|
| 363 |
+
self._setup_attributes()
|
| 364 |
+
|
| 365 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 366 |
+
self.a_dtype,
|
| 367 |
+
self.a_major_mode,
|
| 368 |
+
self.b_major_mode,
|
| 369 |
+
self.acc_dtype,
|
| 370 |
+
self.cta_group,
|
| 371 |
+
self.mma_tiler[:2],
|
| 372 |
+
)
|
| 373 |
+
atom_thr_size = cute.size(tiled_mma.thr_id.shape)
|
| 374 |
+
|
| 375 |
+
# Setup TMA load for A
|
| 376 |
+
a_op = sm100_utils.cluster_shape_to_tma_atom_A(
|
| 377 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 378 |
+
)
|
| 379 |
+
a_smem_layout = cute.slice_(self.a_smem_layout_staged, (None, None, None, 0))
|
| 380 |
+
tma_atom_a, tma_tensor_a = cute.nvgpu.make_tiled_tma_atom_A(
|
| 381 |
+
a_op,
|
| 382 |
+
a,
|
| 383 |
+
a_smem_layout,
|
| 384 |
+
self.mma_tiler,
|
| 385 |
+
tiled_mma,
|
| 386 |
+
self.cluster_layout_vmnk.shape,
|
| 387 |
+
internal_type=(
|
| 388 |
+
cutlass.TFloat32 if a.element_type is cutlass.Float32 else None
|
| 389 |
+
),
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
# Setup TMA load for B
|
| 393 |
+
b_op = sm100_utils.cluster_shape_to_tma_atom_B(
|
| 394 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 395 |
+
)
|
| 396 |
+
b_smem_layout = cute.slice_(self.b_smem_layout_staged, (None, None, None, 0))
|
| 397 |
+
tma_atom_b, tma_tensor_b = cute.nvgpu.make_tiled_tma_atom_B(
|
| 398 |
+
b_op,
|
| 399 |
+
b,
|
| 400 |
+
b_smem_layout,
|
| 401 |
+
self.mma_tiler,
|
| 402 |
+
tiled_mma,
|
| 403 |
+
self.cluster_layout_vmnk.shape,
|
| 404 |
+
internal_type=(
|
| 405 |
+
cutlass.TFloat32 if b.element_type is cutlass.Float32 else None
|
| 406 |
+
),
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
a_copy_size = cute.size_in_bytes(self.a_dtype, a_smem_layout)
|
| 410 |
+
b_copy_size = cute.size_in_bytes(self.b_dtype, b_smem_layout)
|
| 411 |
+
self.num_tma_load_bytes = (a_copy_size + b_copy_size) * atom_thr_size
|
| 412 |
+
|
| 413 |
+
# Setup store for C
|
| 414 |
+
tma_atom_c = None
|
| 415 |
+
tma_tensor_c = None
|
| 416 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 417 |
+
c_cta_v_layout = cute.composition(
|
| 418 |
+
cute.make_identity_layout(c.shape), self.epi_tile
|
| 419 |
+
)
|
| 420 |
+
epi_smem_layout = cute.slice_(self.c_smem_layout_staged, (None, None, 0))
|
| 421 |
+
tma_atom_c, tma_tensor_c = cpasync.make_tiled_tma_atom(
|
| 422 |
+
cpasync.CopyBulkTensorTileS2GOp(),
|
| 423 |
+
c,
|
| 424 |
+
epi_smem_layout,
|
| 425 |
+
c_cta_v_layout,
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
# Compute grid size
|
| 429 |
+
grid = self._compute_grid(c, self.cta_tile_shape_mnk, self.cluster_shape_mn)
|
| 430 |
+
|
| 431 |
+
self.buffer_align_bytes = 1024
|
| 432 |
+
|
| 433 |
+
c_smem_size = (
|
| 434 |
+
cute.cosize(self.c_smem_layout_staged.outer) if self.use_tma_store else 0
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
# Define shared storage for kernel
|
| 438 |
+
@cute.struct
|
| 439 |
+
class SharedStorage:
|
| 440 |
+
ab_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 441 |
+
ab_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 442 |
+
acc_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 443 |
+
tmem_dealloc_mbar_ptr: cutlass.Int64
|
| 444 |
+
tmem_holding_buf: cutlass.Int32
|
| 445 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 446 |
+
sC: cute.struct.Align[
|
| 447 |
+
cute.struct.MemRange[
|
| 448 |
+
self.c_dtype,
|
| 449 |
+
c_smem_size,
|
| 450 |
+
],
|
| 451 |
+
self.buffer_align_bytes,
|
| 452 |
+
]
|
| 453 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 454 |
+
sA: cute.struct.Align[
|
| 455 |
+
cute.struct.MemRange[
|
| 456 |
+
self.a_dtype, cute.cosize(self.a_smem_layout_staged.outer)
|
| 457 |
+
],
|
| 458 |
+
self.buffer_align_bytes,
|
| 459 |
+
]
|
| 460 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 461 |
+
sB: cute.struct.Align[
|
| 462 |
+
cute.struct.MemRange[
|
| 463 |
+
self.b_dtype, cute.cosize(self.b_smem_layout_staged.outer)
|
| 464 |
+
],
|
| 465 |
+
self.buffer_align_bytes,
|
| 466 |
+
]
|
| 467 |
+
|
| 468 |
+
self.shared_storage = SharedStorage
|
| 469 |
+
|
| 470 |
+
# Launch the kernel synchronously
|
| 471 |
+
self.kernel(
|
| 472 |
+
tiled_mma,
|
| 473 |
+
tma_atom_a,
|
| 474 |
+
tma_tensor_a,
|
| 475 |
+
tma_atom_b,
|
| 476 |
+
tma_tensor_b,
|
| 477 |
+
tma_atom_c,
|
| 478 |
+
tma_tensor_c if self.use_tma_store else c,
|
| 479 |
+
self.cluster_layout_vmnk,
|
| 480 |
+
self.a_smem_layout_staged,
|
| 481 |
+
self.b_smem_layout_staged,
|
| 482 |
+
self.c_smem_layout_staged,
|
| 483 |
+
self.epi_tile,
|
| 484 |
+
epilogue_op,
|
| 485 |
+
).launch(
|
| 486 |
+
grid=grid,
|
| 487 |
+
block=[self.threads_per_cta, 1, 1],
|
| 488 |
+
cluster=(*self.cluster_shape_mn, 1),
|
| 489 |
+
stream=stream,
|
| 490 |
+
)
|
| 491 |
+
return
|
| 492 |
+
|
| 493 |
+
# GPU device kernel
|
| 494 |
+
@cute.kernel
|
| 495 |
+
def kernel(
|
| 496 |
+
self,
|
| 497 |
+
tiled_mma: cute.TiledMma,
|
| 498 |
+
tma_atom_a: cute.CopyAtom,
|
| 499 |
+
mA_mkl: cute.Tensor,
|
| 500 |
+
tma_atom_b: cute.CopyAtom,
|
| 501 |
+
mB_nkl: cute.Tensor,
|
| 502 |
+
tma_atom_c: Optional[cute.CopyAtom],
|
| 503 |
+
mC_mnl: cute.Tensor,
|
| 504 |
+
cluster_layout_vmnk: cute.Layout,
|
| 505 |
+
a_smem_layout_staged: cute.ComposedLayout,
|
| 506 |
+
b_smem_layout_staged: cute.ComposedLayout,
|
| 507 |
+
c_smem_layout_staged: Union[cute.Layout, cute.ComposedLayout, None],
|
| 508 |
+
epi_tile: cute.Tile,
|
| 509 |
+
epilogue_op: cutlass.Constexpr,
|
| 510 |
+
):
|
| 511 |
+
"""
|
| 512 |
+
GPU device kernel performing the batched GEMM computation.
|
| 513 |
+
"""
|
| 514 |
+
warp_idx = cute.arch.warp_idx()
|
| 515 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 516 |
+
|
| 517 |
+
#
|
| 518 |
+
# Prefetch tma descriptor
|
| 519 |
+
#
|
| 520 |
+
if warp_idx == 0:
|
| 521 |
+
cpasync.prefetch_descriptor(tma_atom_a)
|
| 522 |
+
cpasync.prefetch_descriptor(tma_atom_b)
|
| 523 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 524 |
+
cpasync.prefetch_descriptor(tma_atom_c)
|
| 525 |
+
|
| 526 |
+
use_2cta_instrs = cute.size(tiled_mma.thr_id.shape) == 2
|
| 527 |
+
|
| 528 |
+
#
|
| 529 |
+
# Setup cta/thread coordinates
|
| 530 |
+
#
|
| 531 |
+
# Coords inside cluster
|
| 532 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 533 |
+
mma_tile_coord_v = bidx % cute.size(tiled_mma.thr_id.shape)
|
| 534 |
+
is_leader_cta = mma_tile_coord_v == 0
|
| 535 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(
|
| 536 |
+
cute.arch.block_idx_in_cluster()
|
| 537 |
+
)
|
| 538 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(
|
| 539 |
+
cta_rank_in_cluster
|
| 540 |
+
)
|
| 541 |
+
# Coords outside cluster
|
| 542 |
+
cta_coord = (bidx, bidy, bidz)
|
| 543 |
+
mma_tile_coord_mnl = (
|
| 544 |
+
cta_coord[0] // cute.size(tiled_mma.thr_id.shape),
|
| 545 |
+
cta_coord[1],
|
| 546 |
+
cta_coord[2],
|
| 547 |
+
)
|
| 548 |
+
# Coords inside cta
|
| 549 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 550 |
+
|
| 551 |
+
#
|
| 552 |
+
# Alloc and init: a+b full/empty, accumulator full, tensor memory dealloc barrier
|
| 553 |
+
#
|
| 554 |
+
smem = utils.SmemAllocator()
|
| 555 |
+
storage = smem.allocate(self.shared_storage)
|
| 556 |
+
|
| 557 |
+
tmem_dealloc_mbar_ptr = storage.tmem_dealloc_mbar_ptr
|
| 558 |
+
tmem_holding_buf = storage.tmem_holding_buf
|
| 559 |
+
|
| 560 |
+
# Initialize mainloop ab_pipeline (barrier) and states
|
| 561 |
+
ab_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 562 |
+
num_tma_producer = self.num_mcast_ctas_a + self.num_mcast_ctas_b - 1
|
| 563 |
+
ab_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 564 |
+
pipeline.Agent.Thread, num_tma_producer
|
| 565 |
+
)
|
| 566 |
+
ab_pipeline = pipeline.PipelineTmaUmma.create(
|
| 567 |
+
barrier_storage=storage.ab_full_mbar_ptr.data_ptr(),
|
| 568 |
+
num_stages=self.num_ab_stage,
|
| 569 |
+
producer_group=ab_pipeline_producer_group,
|
| 570 |
+
consumer_group=ab_pipeline_consumer_group,
|
| 571 |
+
tx_count=self.num_tma_load_bytes,
|
| 572 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 573 |
+
)
|
| 574 |
+
ab_producer_state = pipeline.make_pipeline_state(
|
| 575 |
+
pipeline.PipelineUserType.Producer, self.num_ab_stage
|
| 576 |
+
)
|
| 577 |
+
ab_consumer_state = pipeline.make_pipeline_state(
|
| 578 |
+
pipeline.PipelineUserType.Consumer, self.num_ab_stage
|
| 579 |
+
)
|
| 580 |
+
|
| 581 |
+
# Initialize acc_pipeline (barrier) and states
|
| 582 |
+
acc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 583 |
+
acc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 584 |
+
pipeline.Agent.Thread, self.threads_per_cta, self.threads_per_cta
|
| 585 |
+
)
|
| 586 |
+
acc_pipeline = pipeline.PipelineUmmaAsync.create(
|
| 587 |
+
barrier_storage=storage.acc_full_mbar_ptr.data_ptr(),
|
| 588 |
+
num_stages=self.num_acc_stage,
|
| 589 |
+
producer_group=acc_pipeline_producer_group,
|
| 590 |
+
consumer_group=acc_pipeline_consumer_group,
|
| 591 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 592 |
+
)
|
| 593 |
+
acc_producer_state = pipeline.make_pipeline_state(
|
| 594 |
+
pipeline.PipelineUserType.Producer, self.num_acc_stage
|
| 595 |
+
)
|
| 596 |
+
acc_consumer_state = pipeline.make_pipeline_state(
|
| 597 |
+
pipeline.PipelineUserType.Consumer, self.num_acc_stage
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
# Tensor memory dealloc barrier init
|
| 601 |
+
if use_2cta_instrs:
|
| 602 |
+
if warp_idx == 0:
|
| 603 |
+
num_tmem_dealloc_threads = 32
|
| 604 |
+
with cute.arch.elect_one():
|
| 605 |
+
cute.arch.mbarrier_init(
|
| 606 |
+
tmem_dealloc_mbar_ptr, num_tmem_dealloc_threads
|
| 607 |
+
)
|
| 608 |
+
cute.arch.mbarrier_init_fence()
|
| 609 |
+
|
| 610 |
+
# Cluster arrive after barrier init
|
| 611 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 612 |
+
cute.arch.cluster_arrive_relaxed()
|
| 613 |
+
|
| 614 |
+
#
|
| 615 |
+
# Setup smem tensor A/B/C
|
| 616 |
+
#
|
| 617 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 618 |
+
sC = (
|
| 619 |
+
storage.sC.get_tensor(
|
| 620 |
+
c_smem_layout_staged.outer, swizzle=c_smem_layout_staged.inner
|
| 621 |
+
)
|
| 622 |
+
if self.use_tma_store
|
| 623 |
+
else None
|
| 624 |
+
)
|
| 625 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 626 |
+
sA = storage.sA.get_tensor(
|
| 627 |
+
a_smem_layout_staged.outer, swizzle=a_smem_layout_staged.inner
|
| 628 |
+
)
|
| 629 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 630 |
+
sB = storage.sB.get_tensor(
|
| 631 |
+
b_smem_layout_staged.outer, swizzle=b_smem_layout_staged.inner
|
| 632 |
+
)
|
| 633 |
+
|
| 634 |
+
#
|
| 635 |
+
# Compute multicast mask for A/B buffer full
|
| 636 |
+
#
|
| 637 |
+
a_full_mcast_mask = None
|
| 638 |
+
b_full_mcast_mask = None
|
| 639 |
+
if cutlass.const_expr(self.is_a_mcast or self.is_b_mcast or use_2cta_instrs):
|
| 640 |
+
a_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 641 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 642 |
+
)
|
| 643 |
+
b_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 644 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=1
|
| 645 |
+
)
|
| 646 |
+
|
| 647 |
+
#
|
| 648 |
+
# Local_tile partition global tensors
|
| 649 |
+
#
|
| 650 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 651 |
+
gA_mkl = cute.local_tile(
|
| 652 |
+
mA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None)
|
| 653 |
+
)
|
| 654 |
+
# (bN, bK, RestN, RestK, RestL)
|
| 655 |
+
gB_nkl = cute.local_tile(
|
| 656 |
+
mB_nkl, cute.slice_(self.mma_tiler, (0, None, None)), (None, None, None)
|
| 657 |
+
)
|
| 658 |
+
# (bM, bN, RestM, RestN, RestL)
|
| 659 |
+
gC_mnl = cute.local_tile(
|
| 660 |
+
mC_mnl, cute.slice_(self.mma_tiler, (None, None, 0)), (None, None, None)
|
| 661 |
+
)
|
| 662 |
+
k_tile_cnt = cute.size(gA_mkl, mode=[3])
|
| 663 |
+
|
| 664 |
+
#
|
| 665 |
+
# Partition global tensor for TiledMMA_A/B/C
|
| 666 |
+
#
|
| 667 |
+
thr_mma = tiled_mma.get_slice(mma_tile_coord_v)
|
| 668 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 669 |
+
tCgA = thr_mma.partition_A(gA_mkl)
|
| 670 |
+
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
|
| 671 |
+
tCgB = thr_mma.partition_B(gB_nkl)
|
| 672 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 673 |
+
tCgC = thr_mma.partition_C(gC_mnl)
|
| 674 |
+
|
| 675 |
+
#
|
| 676 |
+
# Partition global/shared tensor for TMA load A/B
|
| 677 |
+
#
|
| 678 |
+
# TMA load A partition_S/D
|
| 679 |
+
a_cta_layout = cute.make_layout(
|
| 680 |
+
cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape
|
| 681 |
+
)
|
| 682 |
+
# ((atom_v, rest_v), STAGE)
|
| 683 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 684 |
+
tAsA, tAgA = cpasync.tma_partition(
|
| 685 |
+
tma_atom_a,
|
| 686 |
+
block_in_cluster_coord_vmnk[2],
|
| 687 |
+
a_cta_layout,
|
| 688 |
+
cute.group_modes(sA, 0, 3),
|
| 689 |
+
cute.group_modes(tCgA, 0, 3),
|
| 690 |
+
)
|
| 691 |
+
# TMA load B partition_S/D
|
| 692 |
+
b_cta_layout = cute.make_layout(
|
| 693 |
+
cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape
|
| 694 |
+
)
|
| 695 |
+
# ((atom_v, rest_v), STAGE)
|
| 696 |
+
# ((atom_v, rest_v), RestN, RestK, RestL)
|
| 697 |
+
tBsB, tBgB = cpasync.tma_partition(
|
| 698 |
+
tma_atom_b,
|
| 699 |
+
block_in_cluster_coord_vmnk[1],
|
| 700 |
+
b_cta_layout,
|
| 701 |
+
cute.group_modes(sB, 0, 3),
|
| 702 |
+
cute.group_modes(tCgB, 0, 3),
|
| 703 |
+
)
|
| 704 |
+
|
| 705 |
+
#
|
| 706 |
+
# Partition shared/tensor memory tensor for TiledMMA_A/B/C
|
| 707 |
+
#
|
| 708 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 709 |
+
tCrA = tiled_mma.make_fragment_A(sA)
|
| 710 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 711 |
+
tCrB = tiled_mma.make_fragment_B(sB)
|
| 712 |
+
# (MMA, MMA_M, MMA_N)
|
| 713 |
+
acc_shape = tiled_mma.partition_shape_C(self.mma_tiler[:2])
|
| 714 |
+
# (MMA, MMA_M, MMA_N)
|
| 715 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(acc_shape)
|
| 716 |
+
|
| 717 |
+
#
|
| 718 |
+
# Cluster wait before tensor memory alloc
|
| 719 |
+
#
|
| 720 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 721 |
+
cute.arch.cluster_wait()
|
| 722 |
+
|
| 723 |
+
#
|
| 724 |
+
# Alloc tensor memory buffer
|
| 725 |
+
#
|
| 726 |
+
if warp_idx == 0:
|
| 727 |
+
cute.arch.alloc_tmem(
|
| 728 |
+
self.num_tmem_alloc_cols, tmem_holding_buf, is_two_cta=use_2cta_instrs
|
| 729 |
+
)
|
| 730 |
+
|
| 731 |
+
#
|
| 732 |
+
# Bar sync for retrieve tensor memory ptr from shared memory
|
| 733 |
+
#
|
| 734 |
+
cute.arch.barrier()
|
| 735 |
+
|
| 736 |
+
#
|
| 737 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 738 |
+
#
|
| 739 |
+
tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 740 |
+
self.acc_dtype, alignment=16, ptr_to_buffer_holding_addr=tmem_holding_buf
|
| 741 |
+
)
|
| 742 |
+
# (MMA, MMA_M, MMA_N)
|
| 743 |
+
tCtAcc = cute.make_tensor(tmem_ptr, tCtAcc_fake.layout)
|
| 744 |
+
|
| 745 |
+
#
|
| 746 |
+
# Partition for epilogue
|
| 747 |
+
#
|
| 748 |
+
tiled_copy_t2r, tTR_tAcc, tTR_rAcc = self.epilog_tmem_copy_and_partition(
|
| 749 |
+
tidx, tCtAcc, tCgC, epi_tile, use_2cta_instrs
|
| 750 |
+
)
|
| 751 |
+
|
| 752 |
+
tTR_rC = None
|
| 753 |
+
tiled_copy_r2s = None
|
| 754 |
+
simt_atom = None
|
| 755 |
+
tRS_rC = None
|
| 756 |
+
tRS_sC = None
|
| 757 |
+
bSG_sC = None
|
| 758 |
+
bSG_gC = None
|
| 759 |
+
tTR_gC = None
|
| 760 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 761 |
+
tTR_rC = cute.make_fragment(tTR_rAcc.shape, self.c_dtype)
|
| 762 |
+
tiled_copy_r2s, tRS_rC, tRS_sC = self.epilog_smem_copy_and_partition(
|
| 763 |
+
tiled_copy_t2r, tTR_rC, tidx, sC
|
| 764 |
+
)
|
| 765 |
+
tma_atom_c, bSG_sC, bSG_gC = self.epilog_gmem_copy_and_partition(
|
| 766 |
+
tidx, tma_atom_c, tCgC, epi_tile, sC
|
| 767 |
+
)
|
| 768 |
+
else:
|
| 769 |
+
simt_atom, tTR_rC, tTR_gC = self.epilog_gmem_copy_and_partition(
|
| 770 |
+
tidx, tiled_copy_t2r, tCgC, epi_tile, sC
|
| 771 |
+
)
|
| 772 |
+
|
| 773 |
+
#
|
| 774 |
+
# Slice to per mma tile index
|
| 775 |
+
#
|
| 776 |
+
# ((atom_v, rest_v), RestK)
|
| 777 |
+
tAgA = tAgA[(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])]
|
| 778 |
+
# ((atom_v, rest_v), RestK)
|
| 779 |
+
tBgB = tBgB[(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])]
|
| 780 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 781 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 782 |
+
bSG_gC = bSG_gC[(None, None, None, *mma_tile_coord_mnl)]
|
| 783 |
+
else:
|
| 784 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N)
|
| 785 |
+
tTR_gC = tTR_gC[(None, None, None, None, None, *mma_tile_coord_mnl)]
|
| 786 |
+
|
| 787 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 788 |
+
# MAINLOOP
|
| 789 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 790 |
+
prefetch_k_tile_cnt = cutlass.min(self.num_ab_stage - 2, k_tile_cnt)
|
| 791 |
+
if warp_idx == 0:
|
| 792 |
+
for k_tile in cutlass.range(
|
| 793 |
+
k_tile_cnt,
|
| 794 |
+
prefetch_stages=self.num_ab_stage - 2,
|
| 795 |
+
):
|
| 796 |
+
# wait for AB buffer empty
|
| 797 |
+
ab_pipeline.producer_acquire(ab_producer_state)
|
| 798 |
+
|
| 799 |
+
# TMA load A/B
|
| 800 |
+
cute.copy(
|
| 801 |
+
tma_atom_a,
|
| 802 |
+
tAgA[(None, ab_producer_state.count)],
|
| 803 |
+
tAsA[(None, ab_producer_state.index)],
|
| 804 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 805 |
+
mcast_mask=a_full_mcast_mask,
|
| 806 |
+
)
|
| 807 |
+
cute.copy(
|
| 808 |
+
tma_atom_b,
|
| 809 |
+
tBgB[(None, ab_producer_state.count)],
|
| 810 |
+
tBsB[(None, ab_producer_state.index)],
|
| 811 |
+
tma_bar_ptr=ab_pipeline.producer_get_barrier(ab_producer_state),
|
| 812 |
+
mcast_mask=b_full_mcast_mask,
|
| 813 |
+
)
|
| 814 |
+
|
| 815 |
+
if is_leader_cta:
|
| 816 |
+
# Wait for AB buffer full
|
| 817 |
+
ab_pipeline.consumer_wait(ab_consumer_state)
|
| 818 |
+
|
| 819 |
+
# tCtAcc += tCrA * tCrB
|
| 820 |
+
num_kblocks = cute.size(tCrA, mode=[2])
|
| 821 |
+
for kblock_idx in cutlass.range(num_kblocks, unroll_full=True):
|
| 822 |
+
kblock_coord = (None, None, kblock_idx, ab_consumer_state.index)
|
| 823 |
+
|
| 824 |
+
cute.gemm(
|
| 825 |
+
tiled_mma,
|
| 826 |
+
tCtAcc,
|
| 827 |
+
tCrA[kblock_coord],
|
| 828 |
+
tCrB[kblock_coord],
|
| 829 |
+
tCtAcc,
|
| 830 |
+
)
|
| 831 |
+
# Enable accumulate on tCtAcc after first kblock
|
| 832 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 833 |
+
|
| 834 |
+
# Async arrive AB buffer empty
|
| 835 |
+
ab_pipeline.consumer_release(ab_consumer_state)
|
| 836 |
+
|
| 837 |
+
ab_producer_state.advance()
|
| 838 |
+
ab_consumer_state.advance()
|
| 839 |
+
|
| 840 |
+
# Async arrive accumulator buffer full
|
| 841 |
+
if is_leader_cta:
|
| 842 |
+
acc_pipeline.producer_commit(acc_producer_state)
|
| 843 |
+
|
| 844 |
+
#
|
| 845 |
+
# Epilogue
|
| 846 |
+
#
|
| 847 |
+
|
| 848 |
+
# Release tensor memory allocation lock
|
| 849 |
+
if warp_idx == 0:
|
| 850 |
+
cute.arch.relinquish_tmem_alloc_permit(is_two_cta=use_2cta_instrs)
|
| 851 |
+
|
| 852 |
+
# Wait for accumulator buffer full
|
| 853 |
+
acc_pipeline.consumer_wait(acc_consumer_state)
|
| 854 |
+
|
| 855 |
+
tTR_tAcc = cute.group_modes(tTR_tAcc, 3, cute.rank(tTR_tAcc))
|
| 856 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 857 |
+
bSG_gC = cute.group_modes(bSG_gC, 1, cute.rank(bSG_gC))
|
| 858 |
+
else:
|
| 859 |
+
tTR_gC = cute.group_modes(tTR_gC, 3, cute.rank(tTR_gC))
|
| 860 |
+
|
| 861 |
+
c_pipeline = None
|
| 862 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 863 |
+
# Initialize tma store c_pipeline
|
| 864 |
+
c_producer_group = pipeline.CooperativeGroup(
|
| 865 |
+
pipeline.Agent.Thread, self.threads_per_cta, self.threads_per_cta
|
| 866 |
+
)
|
| 867 |
+
c_pipeline = pipeline.PipelineTmaStore.create(
|
| 868 |
+
num_stages=self.num_c_stage,
|
| 869 |
+
producer_group=c_producer_group,
|
| 870 |
+
)
|
| 871 |
+
|
| 872 |
+
#
|
| 873 |
+
# Store accumulator to global memory in subtiles
|
| 874 |
+
#
|
| 875 |
+
subtile_cnt = cute.size(tTR_tAcc.shape, mode=[3])
|
| 876 |
+
for subtile_idx in range(subtile_cnt):
|
| 877 |
+
#
|
| 878 |
+
# Load accumulator from tensor memory buffer to register
|
| 879 |
+
#
|
| 880 |
+
tTR_tAcc_mn = tTR_tAcc[(None, None, None, subtile_idx)]
|
| 881 |
+
cute.copy(tiled_copy_t2r, tTR_tAcc_mn, tTR_rAcc)
|
| 882 |
+
|
| 883 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 884 |
+
#
|
| 885 |
+
# Perform epilogue op on accumulator and convert to C type
|
| 886 |
+
#
|
| 887 |
+
acc_vec = tiled_copy_r2s.retile(tTR_rAcc).load()
|
| 888 |
+
acc_vec = epilogue_op(acc_vec.to(self.c_dtype))
|
| 889 |
+
tRS_rC.store(acc_vec)
|
| 890 |
+
|
| 891 |
+
#
|
| 892 |
+
# Store C to shared memory
|
| 893 |
+
#
|
| 894 |
+
c_buffer = subtile_idx % self.num_c_stage
|
| 895 |
+
cute.copy(tiled_copy_r2s, tRS_rC, tRS_sC[(None, None, None, c_buffer)])
|
| 896 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 897 |
+
cute.arch.fence_proxy(
|
| 898 |
+
cute.arch.ProxyKind.async_shared,
|
| 899 |
+
space=cute.arch.SharedSpace.shared_cta,
|
| 900 |
+
)
|
| 901 |
+
cute.arch.barrier()
|
| 902 |
+
|
| 903 |
+
#
|
| 904 |
+
# TMA store C to global memory
|
| 905 |
+
#
|
| 906 |
+
if warp_idx == 0:
|
| 907 |
+
cute.copy(
|
| 908 |
+
tma_atom_c,
|
| 909 |
+
bSG_sC[(None, c_buffer)],
|
| 910 |
+
bSG_gC[(None, subtile_idx)],
|
| 911 |
+
)
|
| 912 |
+
# Fence and barrier to make sure TMA store is completed to recollect C buffer
|
| 913 |
+
c_pipeline.producer_commit()
|
| 914 |
+
c_pipeline.producer_acquire()
|
| 915 |
+
cute.arch.barrier()
|
| 916 |
+
else:
|
| 917 |
+
#
|
| 918 |
+
# Perform epilogue op on accumulator and convert to C type
|
| 919 |
+
#
|
| 920 |
+
acc_vec = tTR_rAcc.load()
|
| 921 |
+
acc_vec = epilogue_op(acc_vec.to(self.c_dtype))
|
| 922 |
+
tTR_rC.store(acc_vec)
|
| 923 |
+
|
| 924 |
+
#
|
| 925 |
+
# Store C to global memory
|
| 926 |
+
#
|
| 927 |
+
cute.copy(simt_atom, tTR_rC, tTR_gC[(None, None, None, subtile_idx)])
|
| 928 |
+
|
| 929 |
+
#
|
| 930 |
+
# Dealloc the tensor memory buffer
|
| 931 |
+
#
|
| 932 |
+
cute.arch.barrier()
|
| 933 |
+
if warp_idx == 0:
|
| 934 |
+
if use_2cta_instrs:
|
| 935 |
+
cute.arch.mbarrier_arrive(
|
| 936 |
+
tmem_dealloc_mbar_ptr, cta_rank_in_cluster ^ 1
|
| 937 |
+
)
|
| 938 |
+
cute.arch.mbarrier_wait(tmem_dealloc_mbar_ptr, 0)
|
| 939 |
+
cute.arch.dealloc_tmem(
|
| 940 |
+
tmem_ptr, self.num_tmem_alloc_cols, is_two_cta=use_2cta_instrs
|
| 941 |
+
)
|
| 942 |
+
|
| 943 |
+
#
|
| 944 |
+
# Wait for C store complete
|
| 945 |
+
#
|
| 946 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 947 |
+
c_pipeline.producer_tail()
|
| 948 |
+
|
| 949 |
+
#
|
| 950 |
+
# Wait A/B buffer empty
|
| 951 |
+
#
|
| 952 |
+
if warp_idx == 0:
|
| 953 |
+
# Reverse prefetch_k_tile_cnt times to next available buffer
|
| 954 |
+
for i in range(prefetch_k_tile_cnt):
|
| 955 |
+
ab_producer_state.reverse()
|
| 956 |
+
ab_pipeline.producer_tail(ab_producer_state)
|
| 957 |
+
return
|
| 958 |
+
|
| 959 |
+
def epilog_tmem_copy_and_partition(
|
| 960 |
+
self,
|
| 961 |
+
tidx: cutlass.Int32,
|
| 962 |
+
tAcc: cute.Tensor,
|
| 963 |
+
gC_mnl: cute.Tensor,
|
| 964 |
+
epi_tile: cute.Tile,
|
| 965 |
+
use_2cta_instrs: Union[cutlass.Boolean, bool],
|
| 966 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 967 |
+
"""
|
| 968 |
+
Make tiledCopy for tensor memory load, then use it to partition tensor memory (source) and register array (destination).
|
| 969 |
+
|
| 970 |
+
:param tidx: The thread index in epilogue warp groups
|
| 971 |
+
:type tidx: cutlass.Int32
|
| 972 |
+
:param tAcc: The accumulator tensor to be copied and partitioned
|
| 973 |
+
:type tAcc: cute.Tensor
|
| 974 |
+
:param gC_mnl: The global tensor C
|
| 975 |
+
:type gC_mnl: cute.Tensor
|
| 976 |
+
:param epi_tile: The epilogue tiler
|
| 977 |
+
:type epi_tile: cute.Tile
|
| 978 |
+
:param use_2cta_instrs: Whether use_2cta_instrs is enabled
|
| 979 |
+
:type use_2cta_instrs: bool
|
| 980 |
+
|
| 981 |
+
:return: A tuple containing (tiled_copy_t2r, tTR_tAcc, tTR_rAcc) where:
|
| 982 |
+
- tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 983 |
+
- tTR_tAcc: The partitioned accumulator tensor
|
| 984 |
+
- tTR_rAcc: The accumulated tensor in register used to hold t2r results
|
| 985 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 986 |
+
"""
|
| 987 |
+
# Make tiledCopy for tensor memory load
|
| 988 |
+
copy_atom_t2r = sm100_utils.get_tmem_load_op(
|
| 989 |
+
self.cta_tile_shape_mnk,
|
| 990 |
+
self.c_layout,
|
| 991 |
+
self.c_dtype,
|
| 992 |
+
self.acc_dtype,
|
| 993 |
+
epi_tile,
|
| 994 |
+
use_2cta_instrs,
|
| 995 |
+
)
|
| 996 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N)
|
| 997 |
+
tAcc_epi = cute.flat_divide(
|
| 998 |
+
tAcc[((None, None), 0, 0)],
|
| 999 |
+
epi_tile,
|
| 1000 |
+
)
|
| 1001 |
+
# (EPI_TILE_M, EPI_TILE_N)
|
| 1002 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(
|
| 1003 |
+
copy_atom_t2r, tAcc_epi[(None, None, 0, 0)]
|
| 1004 |
+
)
|
| 1005 |
+
|
| 1006 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1007 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M)
|
| 1008 |
+
tTR_tAcc = thr_copy_t2r.partition_S(tAcc_epi)
|
| 1009 |
+
|
| 1010 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1011 |
+
gC_mnl_epi = cute.flat_divide(
|
| 1012 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1013 |
+
)
|
| 1014 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1015 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_mnl_epi)
|
| 1016 |
+
# (T2R, T2R_M, T2R_N)
|
| 1017 |
+
tTR_rAcc = cute.make_fragment(
|
| 1018 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.acc_dtype
|
| 1019 |
+
)
|
| 1020 |
+
return tiled_copy_t2r, tTR_tAcc, tTR_rAcc
|
| 1021 |
+
|
| 1022 |
+
def epilog_smem_copy_and_partition(
|
| 1023 |
+
self,
|
| 1024 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 1025 |
+
tTR_rC: cute.Tensor,
|
| 1026 |
+
tidx: cutlass.Int32,
|
| 1027 |
+
sC: cute.Tensor,
|
| 1028 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1029 |
+
"""
|
| 1030 |
+
Make tiledCopy for shared memory store, then use it to partition register array (source) and shared memory (destination).
|
| 1031 |
+
|
| 1032 |
+
:param tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1033 |
+
:type tiled_copy_t2r: cute.TiledCopy
|
| 1034 |
+
:param tTR_rC: The partitioned accumulator tensor
|
| 1035 |
+
:type tTR_rC: cute.Tensor
|
| 1036 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1037 |
+
:type tidx: cutlass.Int32
|
| 1038 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1039 |
+
:type sC: cute.Tensor
|
| 1040 |
+
|
| 1041 |
+
:return: A tuple containing (tiled_copy_r2s, tRS_rC, tRS_sC) where:
|
| 1042 |
+
- tiled_copy_r2s: The tiled copy operation for register to smem copy(r2s)
|
| 1043 |
+
- tRS_rC: The partitioned tensor C (register source)
|
| 1044 |
+
- tRS_sC: The partitioned tensor C (smem destination)
|
| 1045 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1046 |
+
"""
|
| 1047 |
+
copy_atom_r2s = sm100_utils.get_smem_store_op(
|
| 1048 |
+
self.c_layout, self.c_dtype, self.acc_dtype, tiled_copy_t2r
|
| 1049 |
+
)
|
| 1050 |
+
tiled_copy_r2s = cute.make_tiled_copy_D(copy_atom_r2s, tiled_copy_t2r)
|
| 1051 |
+
# (R2S, R2S_M, R2S_N, PIPE_D)
|
| 1052 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx)
|
| 1053 |
+
tRS_sC = thr_copy_r2s.partition_D(sC)
|
| 1054 |
+
# (R2S, R2S_M, R2S_N)
|
| 1055 |
+
tRS_rC = tiled_copy_r2s.retile(tTR_rC)
|
| 1056 |
+
return tiled_copy_r2s, tRS_rC, tRS_sC
|
| 1057 |
+
|
| 1058 |
+
def epilog_gmem_copy_and_partition(
|
| 1059 |
+
self,
|
| 1060 |
+
tidx: cutlass.Int32,
|
| 1061 |
+
atom: Union[cute.CopyAtom, cute.TiledCopy],
|
| 1062 |
+
gC_mnl: cute.Tensor,
|
| 1063 |
+
epi_tile: cute.Tile,
|
| 1064 |
+
sC: cute.Tensor,
|
| 1065 |
+
) -> Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]:
|
| 1066 |
+
"""Make tiledCopy for global memory store, then use it to:
|
| 1067 |
+
- partition register array (source) and global memory (destination) for none TMA store version;
|
| 1068 |
+
- partition shared memory (source) and global memory (destination) for TMA store version.
|
| 1069 |
+
|
| 1070 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1071 |
+
:type tidx: cutlass.Int32
|
| 1072 |
+
:param atom: The copy_atom_c to be used for TMA store version, or tiled_copy_t2r for none TMA store version
|
| 1073 |
+
:type atom: cute.CopyAtom or cute.TiledCopy
|
| 1074 |
+
:param gC_mnl: The global tensor C
|
| 1075 |
+
:type gC_mnl: cute.Tensor
|
| 1076 |
+
:param epi_tile: The epilogue tiler
|
| 1077 |
+
:type epi_tile: cute.Tile
|
| 1078 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1079 |
+
:type sC: cute.Tensor
|
| 1080 |
+
|
| 1081 |
+
:return: A tuple containing either:
|
| 1082 |
+
- For TMA store: (tma_atom_c, bSG_sC, bSG_gC) where:
|
| 1083 |
+
- tma_atom_c: The TMA copy atom
|
| 1084 |
+
- bSG_sC: The partitioned shared memory tensor C
|
| 1085 |
+
- bSG_gC: The partitioned global tensor C
|
| 1086 |
+
- For non-TMA store: (simt_atom, tTR_rC, tTR_gC) where:
|
| 1087 |
+
- simt_atom: The SIMT copy atom
|
| 1088 |
+
- tTR_rC: The register tensor C
|
| 1089 |
+
- tTR_gC: The partitioned global tensor C
|
| 1090 |
+
:rtype: Tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]
|
| 1091 |
+
"""
|
| 1092 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1093 |
+
gC_epi = cute.flat_divide(
|
| 1094 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1095 |
+
)
|
| 1096 |
+
if cutlass.const_expr(self.use_tma_store):
|
| 1097 |
+
tma_atom_c = atom
|
| 1098 |
+
sC_for_tma_partition = cute.group_modes(sC, 0, 2)
|
| 1099 |
+
gC_for_tma_partition = cute.group_modes(gC_epi, 0, 2)
|
| 1100 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1101 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1102 |
+
bSG_sC, bSG_gC = cpasync.tma_partition(
|
| 1103 |
+
tma_atom_c,
|
| 1104 |
+
0,
|
| 1105 |
+
cute.make_layout(1),
|
| 1106 |
+
sC_for_tma_partition,
|
| 1107 |
+
gC_for_tma_partition,
|
| 1108 |
+
)
|
| 1109 |
+
return tma_atom_c, bSG_sC, bSG_gC
|
| 1110 |
+
else:
|
| 1111 |
+
tiled_copy_t2r = atom
|
| 1112 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1113 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1114 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_epi)
|
| 1115 |
+
# (T2R, T2R_M, T2R_N)
|
| 1116 |
+
tTR_rC = cute.make_fragment(
|
| 1117 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.c_dtype
|
| 1118 |
+
)
|
| 1119 |
+
simt_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), self.c_dtype)
|
| 1120 |
+
return simt_atom, tTR_rC, tTR_gC
|
| 1121 |
+
|
| 1122 |
+
@staticmethod
|
| 1123 |
+
def _compute_stages(
|
| 1124 |
+
tiled_mma: cute.TiledMma,
|
| 1125 |
+
mma_tiler_mnk: Tuple[int, int, int],
|
| 1126 |
+
a_dtype: Type[cutlass.Numeric],
|
| 1127 |
+
b_dtype: Type[cutlass.Numeric],
|
| 1128 |
+
epi_tile: cute.Tile,
|
| 1129 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1130 |
+
c_layout: utils.LayoutEnum,
|
| 1131 |
+
smem_capacity: int,
|
| 1132 |
+
occupancy: int,
|
| 1133 |
+
use_tma_store: bool,
|
| 1134 |
+
) -> Tuple[int, int, int]:
|
| 1135 |
+
"""Computes the number of stages for A/B/C operands based on heuristics.
|
| 1136 |
+
|
| 1137 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1138 |
+
:type tiled_mma: cute.TiledMma
|
| 1139 |
+
:param mma_tiler_mnk: The shape (M, N, K) of the MMA tile.
|
| 1140 |
+
:type mma_tiler_mnk: tuple[int, int, int]
|
| 1141 |
+
:param a_dtype: Data type of operand A.
|
| 1142 |
+
:type a_dtype: type[cutlass.Numeric]
|
| 1143 |
+
:param b_dtype: Data type of operand B.
|
| 1144 |
+
:type b_dtype: type[cutlass.Numeric]
|
| 1145 |
+
:param epi_tile: The epilogue tile shape.
|
| 1146 |
+
:type epi_tile: cute.Tile
|
| 1147 |
+
:param c_dtype: Data type of operand C (output).
|
| 1148 |
+
:type c_dtype: type[cutlass.Numeric]
|
| 1149 |
+
:param c_layout: Layout enum of operand C in global memory.
|
| 1150 |
+
:type c_layout: utils.LayoutEnum
|
| 1151 |
+
:param smem_capacity: Total available shared memory capacity in bytes.
|
| 1152 |
+
:type smem_capacity: int
|
| 1153 |
+
:param occupancy: Target number of CTAs per SM (occupancy).
|
| 1154 |
+
:type occupancy: int
|
| 1155 |
+
:param use_tma_store: Whether TMA store is enabled.
|
| 1156 |
+
:type use_tma_store: bool
|
| 1157 |
+
|
| 1158 |
+
:return: A tuple containing the computed number of stages for:
|
| 1159 |
+
(ACC stages, A/B operand stages, epilogue stages)
|
| 1160 |
+
:rtype: tuple[int, int, int]
|
| 1161 |
+
"""
|
| 1162 |
+
# Default ACC stages
|
| 1163 |
+
num_acc_stage = 1
|
| 1164 |
+
# Default C stages
|
| 1165 |
+
num_c_stage = 2 if use_tma_store else 0
|
| 1166 |
+
|
| 1167 |
+
# Calculate smem layout and size for one stage of A, B, and C
|
| 1168 |
+
a_smem_layout_stage_one = sm100_utils.make_smem_layout_a(
|
| 1169 |
+
tiled_mma,
|
| 1170 |
+
mma_tiler_mnk,
|
| 1171 |
+
a_dtype,
|
| 1172 |
+
1, # a tmp 1 stage is provided
|
| 1173 |
+
)
|
| 1174 |
+
b_smem_layout_staged_one = sm100_utils.make_smem_layout_b(
|
| 1175 |
+
tiled_mma,
|
| 1176 |
+
mma_tiler_mnk,
|
| 1177 |
+
b_dtype,
|
| 1178 |
+
1, # a tmp 1 stage is provided
|
| 1179 |
+
)
|
| 1180 |
+
c_smem_layout_staged_one = (
|
| 1181 |
+
sm100_utils.make_smem_layout_epi(
|
| 1182 |
+
c_dtype,
|
| 1183 |
+
c_layout,
|
| 1184 |
+
epi_tile,
|
| 1185 |
+
1,
|
| 1186 |
+
)
|
| 1187 |
+
if use_tma_store
|
| 1188 |
+
else None
|
| 1189 |
+
)
|
| 1190 |
+
ab_bytes_per_stage = cute.size_in_bytes(
|
| 1191 |
+
a_dtype, a_smem_layout_stage_one
|
| 1192 |
+
) + cute.size_in_bytes(b_dtype, b_smem_layout_staged_one)
|
| 1193 |
+
mbar_helpers_bytes = 1024
|
| 1194 |
+
c_bytes_per_stage = (
|
| 1195 |
+
cute.size_in_bytes(c_dtype, c_smem_layout_staged_one)
|
| 1196 |
+
if use_tma_store
|
| 1197 |
+
else 0
|
| 1198 |
+
)
|
| 1199 |
+
c_bytes = c_bytes_per_stage * num_c_stage
|
| 1200 |
+
|
| 1201 |
+
# Calculate A/B stages:
|
| 1202 |
+
# Start with total smem per CTA (capacity / occupancy)
|
| 1203 |
+
# Subtract reserved bytes and initial C stages bytes
|
| 1204 |
+
# Divide remaining by bytes needed per A/B stage
|
| 1205 |
+
num_ab_stage = (
|
| 1206 |
+
smem_capacity - (occupancy + 1) * (mbar_helpers_bytes + c_bytes)
|
| 1207 |
+
) // ab_bytes_per_stage
|
| 1208 |
+
|
| 1209 |
+
# Refine epilogue stages:
|
| 1210 |
+
# Calculate remaining smem after allocating for A/B stages and reserved bytes
|
| 1211 |
+
# Add remaining unused smem to epilogue
|
| 1212 |
+
if use_tma_store:
|
| 1213 |
+
num_c_stage += (
|
| 1214 |
+
smem_capacity
|
| 1215 |
+
- ab_bytes_per_stage * num_ab_stage
|
| 1216 |
+
- (occupancy + 1) * (mbar_helpers_bytes + c_bytes)
|
| 1217 |
+
) // ((occupancy + 1) * c_bytes_per_stage)
|
| 1218 |
+
return num_acc_stage, num_ab_stage, num_c_stage
|
| 1219 |
+
|
| 1220 |
+
@staticmethod
|
| 1221 |
+
def _compute_grid(
|
| 1222 |
+
c: cute.Tensor,
|
| 1223 |
+
cta_tile_shape_mnk: Tuple[int, int, int],
|
| 1224 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1225 |
+
) -> Tuple[int, int, int]:
|
| 1226 |
+
"""Compute grid shape for the output tensor C.
|
| 1227 |
+
|
| 1228 |
+
:param c: The output tensor C
|
| 1229 |
+
:type c: cute.Tensor
|
| 1230 |
+
:param cta_tile_shape_mnk: The shape (M, N, K) of the CTA tile.
|
| 1231 |
+
:type cta_tile_shape_mnk: tuple[int, int, int]
|
| 1232 |
+
:param cluster_shape_mn: Shape of each cluster in M, N dimensions.
|
| 1233 |
+
:type cluster_shape_mn: tuple[int, int]
|
| 1234 |
+
|
| 1235 |
+
:return: Grid shape for kernel launch.
|
| 1236 |
+
:rtype: tuple[int, int, int]
|
| 1237 |
+
"""
|
| 1238 |
+
|
| 1239 |
+
cluster_shape_mnl = (*cluster_shape_mn, 1)
|
| 1240 |
+
|
| 1241 |
+
grid = cute.round_up(
|
| 1242 |
+
(
|
| 1243 |
+
cute.ceil_div(c.layout.shape[0], cta_tile_shape_mnk[0]),
|
| 1244 |
+
cute.ceil_div(c.layout.shape[1], cta_tile_shape_mnk[1]),
|
| 1245 |
+
c.layout.shape[2],
|
| 1246 |
+
),
|
| 1247 |
+
cluster_shape_mnl,
|
| 1248 |
+
)
|
| 1249 |
+
|
| 1250 |
+
return grid
|
| 1251 |
+
|
| 1252 |
+
@staticmethod
|
| 1253 |
+
def _compute_num_tmem_alloc_cols(
|
| 1254 |
+
tiled_mma: cute.TiledMma, mma_tiler: Tuple[int, int, int]
|
| 1255 |
+
) -> int:
|
| 1256 |
+
"""
|
| 1257 |
+
Compute the number of tensor memory allocation columns.
|
| 1258 |
+
|
| 1259 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1260 |
+
:type tiled_mma: cute.TiledMma
|
| 1261 |
+
:param mma_tiler: The shape (M, N, K) of the MMA tile.
|
| 1262 |
+
:type mma_tiler: tuple[int, int, int]
|
| 1263 |
+
|
| 1264 |
+
:return: The number of tensor memory allocation columns.
|
| 1265 |
+
:rtype: int
|
| 1266 |
+
"""
|
| 1267 |
+
acc_shape = tiled_mma.partition_shape_C(mma_tiler[:2])
|
| 1268 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(acc_shape)
|
| 1269 |
+
return sm100_utils.get_num_tmem_alloc_cols(tCtAcc_fake)
|
| 1270 |
+
|
| 1271 |
+
@staticmethod
|
| 1272 |
+
def is_valid_dtypes(
|
| 1273 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1274 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1275 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1276 |
+
) -> bool:
|
| 1277 |
+
"""
|
| 1278 |
+
Check if the dtypes are valid
|
| 1279 |
+
|
| 1280 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1281 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1282 |
+
:param acc_dtype: The data type of the accumulator
|
| 1283 |
+
:type acc_dtype: Type[cutlass.Numeric]
|
| 1284 |
+
:param c_dtype: The data type of the output tensor
|
| 1285 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1286 |
+
|
| 1287 |
+
:return: True if the dtypes are valid, False otherwise
|
| 1288 |
+
:rtype: bool
|
| 1289 |
+
"""
|
| 1290 |
+
is_valid = True
|
| 1291 |
+
if ab_dtype not in {
|
| 1292 |
+
cutlass.Float16,
|
| 1293 |
+
cutlass.BFloat16,
|
| 1294 |
+
cutlass.TFloat32,
|
| 1295 |
+
cutlass.Uint8,
|
| 1296 |
+
cutlass.Int8,
|
| 1297 |
+
cutlass.Float8E4M3FN,
|
| 1298 |
+
cutlass.Float8E5M2,
|
| 1299 |
+
}:
|
| 1300 |
+
is_valid = False
|
| 1301 |
+
if (
|
| 1302 |
+
acc_dtype not in {cutlass.Float32, cutlass.Float16, cutlass.Int32}
|
| 1303 |
+
or acc_dtype == cutlass.Float16
|
| 1304 |
+
and ab_dtype
|
| 1305 |
+
not in {cutlass.Float16, cutlass.Float8E4M3FN, cutlass.Float8E5M2}
|
| 1306 |
+
or acc_dtype == cutlass.Int32
|
| 1307 |
+
and ab_dtype not in {cutlass.Uint8, cutlass.Int8}
|
| 1308 |
+
):
|
| 1309 |
+
is_valid = False
|
| 1310 |
+
if (
|
| 1311 |
+
acc_dtype == cutlass.Float32
|
| 1312 |
+
and c_dtype
|
| 1313 |
+
not in {
|
| 1314 |
+
cutlass.Float32,
|
| 1315 |
+
cutlass.Float16,
|
| 1316 |
+
cutlass.BFloat16,
|
| 1317 |
+
cutlass.Float8E4M3FN,
|
| 1318 |
+
cutlass.Float8E5M2,
|
| 1319 |
+
cutlass.Int32,
|
| 1320 |
+
cutlass.Int8,
|
| 1321 |
+
cutlass.Uint8,
|
| 1322 |
+
}
|
| 1323 |
+
or acc_dtype == cutlass.Float16
|
| 1324 |
+
and c_dtype
|
| 1325 |
+
not in {
|
| 1326 |
+
cutlass.BFloat16,
|
| 1327 |
+
cutlass.Float16,
|
| 1328 |
+
}
|
| 1329 |
+
or acc_dtype == cutlass.Int32
|
| 1330 |
+
and c_dtype
|
| 1331 |
+
not in {
|
| 1332 |
+
cutlass.BFloat16,
|
| 1333 |
+
cutlass.Float16,
|
| 1334 |
+
cutlass.Float32,
|
| 1335 |
+
cutlass.Int32,
|
| 1336 |
+
cutlass.Int8,
|
| 1337 |
+
cutlass.Uint8,
|
| 1338 |
+
}
|
| 1339 |
+
):
|
| 1340 |
+
is_valid = False
|
| 1341 |
+
return is_valid
|
| 1342 |
+
|
| 1343 |
+
@staticmethod
|
| 1344 |
+
def is_valid_mma_tiler_and_cluster_shape(
|
| 1345 |
+
use_2cta_instrs: bool,
|
| 1346 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1347 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1348 |
+
) -> bool:
|
| 1349 |
+
"""
|
| 1350 |
+
Check if the mma tiler and cluster shape are valid
|
| 1351 |
+
|
| 1352 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1353 |
+
:type use_2cta_instrs: bool
|
| 1354 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1355 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1356 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1357 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1358 |
+
|
| 1359 |
+
:return: True if the mma tiler and cluster shape are valid, False otherwise
|
| 1360 |
+
:rtype: bool
|
| 1361 |
+
"""
|
| 1362 |
+
is_valid = True
|
| 1363 |
+
# Skip invalid mma tile shape
|
| 1364 |
+
if not (
|
| 1365 |
+
(not use_2cta_instrs and mma_tiler_mn[0] in [64, 128])
|
| 1366 |
+
or (use_2cta_instrs and mma_tiler_mn[0] in [128, 256])
|
| 1367 |
+
):
|
| 1368 |
+
is_valid = False
|
| 1369 |
+
if mma_tiler_mn[1] not in range(32, 257, 32):
|
| 1370 |
+
is_valid = False
|
| 1371 |
+
# Skip illegal cluster shape
|
| 1372 |
+
if cluster_shape_mn[0] % (2 if use_2cta_instrs else 1) != 0:
|
| 1373 |
+
is_valid = False
|
| 1374 |
+
# Skip invalid cluster shape
|
| 1375 |
+
is_power_of_2 = lambda x: x > 0 and (x & (x - 1)) == 0
|
| 1376 |
+
if (
|
| 1377 |
+
cluster_shape_mn[0] * cluster_shape_mn[1] > 16
|
| 1378 |
+
or cluster_shape_mn[0] <= 0
|
| 1379 |
+
or cluster_shape_mn[1] <= 0
|
| 1380 |
+
or not is_power_of_2(cluster_shape_mn[0])
|
| 1381 |
+
or not is_power_of_2(cluster_shape_mn[1])
|
| 1382 |
+
):
|
| 1383 |
+
is_valid = False
|
| 1384 |
+
return is_valid
|
| 1385 |
+
|
| 1386 |
+
@staticmethod
|
| 1387 |
+
def is_valid_tensor_alignment(
|
| 1388 |
+
m: int,
|
| 1389 |
+
n: int,
|
| 1390 |
+
k: int,
|
| 1391 |
+
l: int,
|
| 1392 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1393 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1394 |
+
a_major: str,
|
| 1395 |
+
b_major: str,
|
| 1396 |
+
c_major: str,
|
| 1397 |
+
) -> bool:
|
| 1398 |
+
"""
|
| 1399 |
+
Check if the tensor alignment is valid
|
| 1400 |
+
|
| 1401 |
+
:param m: The number of rows in the A tensor
|
| 1402 |
+
:type m: int
|
| 1403 |
+
:param n: The number of columns in the B tensor
|
| 1404 |
+
:type n: int
|
| 1405 |
+
:param k: The number of columns in the A tensor
|
| 1406 |
+
:type k: int
|
| 1407 |
+
:param l: The number of columns in the C tensor
|
| 1408 |
+
:type l: int
|
| 1409 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1410 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1411 |
+
:param c_dtype: The data type of the output tensor
|
| 1412 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1413 |
+
:param a_major: The major axis of the A tensor
|
| 1414 |
+
:type a_major: str
|
| 1415 |
+
:param b_major: The major axis of the B tensor
|
| 1416 |
+
:type b_major: str
|
| 1417 |
+
:param c_major: The major axis of the C tensor
|
| 1418 |
+
:type c_major: str
|
| 1419 |
+
|
| 1420 |
+
:return: True if the problem shape is valid, False otherwise
|
| 1421 |
+
:rtype: bool
|
| 1422 |
+
"""
|
| 1423 |
+
is_valid = True
|
| 1424 |
+
|
| 1425 |
+
def check_contigous_16B_alignment(dtype, is_mode0_major, tensor_shape):
|
| 1426 |
+
major_mode_idx = 0 if is_mode0_major else 1
|
| 1427 |
+
num_major_elements = tensor_shape[major_mode_idx]
|
| 1428 |
+
num_contiguous_elements = 16 * 8 // dtype.width
|
| 1429 |
+
return num_major_elements % num_contiguous_elements == 0
|
| 1430 |
+
|
| 1431 |
+
if (
|
| 1432 |
+
not check_contigous_16B_alignment(ab_dtype, a_major == "m", (m, k, l))
|
| 1433 |
+
or not check_contigous_16B_alignment(ab_dtype, b_major == "n", (n, k, l))
|
| 1434 |
+
or not check_contigous_16B_alignment(c_dtype, c_major == "m", (m, n, l))
|
| 1435 |
+
):
|
| 1436 |
+
is_valid = False
|
| 1437 |
+
return is_valid
|
| 1438 |
+
|
| 1439 |
+
@staticmethod
|
| 1440 |
+
def is_valid_epilog_store_option(
|
| 1441 |
+
use_2cta_instrs: bool,
|
| 1442 |
+
use_tma_store: bool,
|
| 1443 |
+
m: int,
|
| 1444 |
+
n: int,
|
| 1445 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1446 |
+
) -> bool:
|
| 1447 |
+
"""
|
| 1448 |
+
Check if the epilogue store option is valid
|
| 1449 |
+
|
| 1450 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1451 |
+
:type use_2cta_instrs: bool
|
| 1452 |
+
:param use_tma_store: Whether to use TMA store
|
| 1453 |
+
:type use_tma_store: bool
|
| 1454 |
+
:param m: The number of rows in the A tensor
|
| 1455 |
+
:type m: int
|
| 1456 |
+
:param n: The number of columns in the B tensor
|
| 1457 |
+
:type n: int
|
| 1458 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1459 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1460 |
+
|
| 1461 |
+
:return: True if the epilogue store option is valid, False otherwise
|
| 1462 |
+
:rtype: bool
|
| 1463 |
+
"""
|
| 1464 |
+
|
| 1465 |
+
is_valid = True
|
| 1466 |
+
# None TMA store version does not have predication, can not support OOB tiles
|
| 1467 |
+
cta_tile_shape_mn = (
|
| 1468 |
+
mma_tiler_mn[0] // (2 if use_2cta_instrs else 1),
|
| 1469 |
+
mma_tiler_mn[1],
|
| 1470 |
+
)
|
| 1471 |
+
if not use_tma_store:
|
| 1472 |
+
if not (m % cta_tile_shape_mn[0] == 0 and n % cta_tile_shape_mn[1] == 0):
|
| 1473 |
+
is_valid = False
|
| 1474 |
+
return is_valid
|
| 1475 |
+
|
| 1476 |
+
@staticmethod
|
| 1477 |
+
def can_implement(
|
| 1478 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1479 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1480 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1481 |
+
use_2cta_instrs: bool,
|
| 1482 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1483 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1484 |
+
use_tma_store: bool,
|
| 1485 |
+
m: int,
|
| 1486 |
+
n: int,
|
| 1487 |
+
k: int,
|
| 1488 |
+
l: int,
|
| 1489 |
+
a_major: str,
|
| 1490 |
+
b_major: str,
|
| 1491 |
+
c_major: str,
|
| 1492 |
+
) -> bool:
|
| 1493 |
+
"""
|
| 1494 |
+
Check if the gemm can be implemented
|
| 1495 |
+
|
| 1496 |
+
:param ab_dtype: The data type of the A and B operands
|
| 1497 |
+
:type ab_dtype: Type[cutlass.Numeric]
|
| 1498 |
+
:param acc_dtype: The data type of the accumulator
|
| 1499 |
+
:type acc_dtype: Type[cutlass.Numeric]
|
| 1500 |
+
:param c_dtype: The data type of the output tensor
|
| 1501 |
+
:type c_dtype: Type[cutlass.Numeric]
|
| 1502 |
+
:param use_2cta_instrs: Whether to use 2 CTA groups
|
| 1503 |
+
:type use_2cta_instrs: bool
|
| 1504 |
+
:param mma_tiler_mn: The (M, N) shape of the MMA instruction tiler
|
| 1505 |
+
:type mma_tiler_mn: Tuple[int, int]
|
| 1506 |
+
:param cluster_shape_mn: The (ClusterM, ClusterN) shape of the CTA cluster
|
| 1507 |
+
:type cluster_shape_mn: Tuple[int, int]
|
| 1508 |
+
:param use_tma_store: Whether to use TMA store
|
| 1509 |
+
:type use_tma_store: bool
|
| 1510 |
+
:param m: The number of rows in the A tensor
|
| 1511 |
+
:type m: int
|
| 1512 |
+
:param n: The number of columns in the B tensor
|
| 1513 |
+
:type n: int
|
| 1514 |
+
:param k: The number of columns in the A tensor
|
| 1515 |
+
:type k: int
|
| 1516 |
+
:param l: The number of columns in the C tensor
|
| 1517 |
+
:type l: int
|
| 1518 |
+
:param a_major: The major axis of the A tensor
|
| 1519 |
+
:type a_major: str
|
| 1520 |
+
:param b_major: The major axis of the B tensor
|
| 1521 |
+
:type b_major: str
|
| 1522 |
+
:param c_major: The major axis of the C tensor
|
| 1523 |
+
:type c_major: str
|
| 1524 |
+
|
| 1525 |
+
:return: True if the gemm can be implemented, False otherwise
|
| 1526 |
+
:rtype: bool
|
| 1527 |
+
"""
|
| 1528 |
+
can_implement = True
|
| 1529 |
+
# Skip unsupported types
|
| 1530 |
+
if not DenseGemmKernel.is_valid_dtypes(ab_dtype, acc_dtype, c_dtype):
|
| 1531 |
+
can_implement = False
|
| 1532 |
+
# Skip invalid mma tile shape and cluster shape
|
| 1533 |
+
if not DenseGemmKernel.is_valid_mma_tiler_and_cluster_shape(
|
| 1534 |
+
use_2cta_instrs, mma_tiler_mn, cluster_shape_mn
|
| 1535 |
+
):
|
| 1536 |
+
can_implement = False
|
| 1537 |
+
# Skip illegal problem shape for load/store alignment
|
| 1538 |
+
if not DenseGemmKernel.is_valid_tensor_alignment(
|
| 1539 |
+
m, n, k, l, ab_dtype, c_dtype, a_major, b_major, c_major
|
| 1540 |
+
):
|
| 1541 |
+
can_implement = False
|
| 1542 |
+
# Skip invalid epilogue store option
|
| 1543 |
+
if not DenseGemmKernel.is_valid_epilog_store_option(
|
| 1544 |
+
use_2cta_instrs, use_tma_store, m, n, mma_tiler_mn
|
| 1545 |
+
):
|
| 1546 |
+
can_implement = False
|
| 1547 |
+
return can_implement
|
| 1548 |
+
|
| 1549 |
+
|
| 1550 |
+
def run_dense_gemm(
|
| 1551 |
+
mnkl: Tuple[int, int, int, int],
|
| 1552 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1553 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1554 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1555 |
+
a_major: str,
|
| 1556 |
+
b_major: str,
|
| 1557 |
+
c_major: str,
|
| 1558 |
+
mma_tiler_mn: Tuple[int, int],
|
| 1559 |
+
cluster_shape_mn: Tuple[int, int],
|
| 1560 |
+
use_2cta_instrs: bool,
|
| 1561 |
+
use_tma_store: bool,
|
| 1562 |
+
tolerance: float,
|
| 1563 |
+
warmup_iterations: int = 0,
|
| 1564 |
+
iterations: int = 1,
|
| 1565 |
+
skip_ref_check: bool = False,
|
| 1566 |
+
):
|
| 1567 |
+
"""
|
| 1568 |
+
Prepare A/B/C tensors, launch GPU kernel, and reference checking.
|
| 1569 |
+
"""
|
| 1570 |
+
print(f"Running B100 software pipeline Dense GEMM test with:")
|
| 1571 |
+
print(f"mnkl: {mnkl}")
|
| 1572 |
+
print(f"AB dtype: {ab_dtype}, C dtype: {c_dtype}, Acc dtype: {acc_dtype}")
|
| 1573 |
+
print(f"Matrix majors - A: {a_major}, B: {b_major}, C: {c_major}")
|
| 1574 |
+
print(f"Mma Tiler (M, N): {mma_tiler_mn}, Cluster Shape (M, N): {cluster_shape_mn}")
|
| 1575 |
+
print(f"2CTA MMA instructions: {'True' if use_2cta_instrs else 'False'}")
|
| 1576 |
+
print(f"Use TMA Store: {'True' if use_tma_store else 'False'}")
|
| 1577 |
+
print(f"Tolerance: {tolerance}")
|
| 1578 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 1579 |
+
print(f"Iterations: {iterations}")
|
| 1580 |
+
print(f"Skip reference checking: {skip_ref_check}")
|
| 1581 |
+
|
| 1582 |
+
# Unpack parameters
|
| 1583 |
+
m, n, k, l = mnkl
|
| 1584 |
+
|
| 1585 |
+
# Skip unsupported testcase
|
| 1586 |
+
if not DenseGemmKernel.can_implement(
|
| 1587 |
+
ab_dtype,
|
| 1588 |
+
acc_dtype,
|
| 1589 |
+
c_dtype,
|
| 1590 |
+
use_2cta_instrs,
|
| 1591 |
+
mma_tiler_mn,
|
| 1592 |
+
cluster_shape_mn,
|
| 1593 |
+
use_tma_store,
|
| 1594 |
+
m,
|
| 1595 |
+
n,
|
| 1596 |
+
k,
|
| 1597 |
+
l,
|
| 1598 |
+
a_major,
|
| 1599 |
+
b_major,
|
| 1600 |
+
c_major,
|
| 1601 |
+
):
|
| 1602 |
+
raise TypeError(
|
| 1603 |
+
f"Unsupported testcase {ab_dtype}, {acc_dtype}, {c_dtype}, {use_2cta_instrs}, {mma_tiler_mn}, {cluster_shape_mn}, {use_tma_store}, {m}, {n}, {k}, {l}, {a_major}, {b_major}, {c_major}"
|
| 1604 |
+
)
|
| 1605 |
+
|
| 1606 |
+
if not torch.cuda.is_available():
|
| 1607 |
+
raise RuntimeError("GPU is required to run this example!")
|
| 1608 |
+
|
| 1609 |
+
torch.manual_seed(1111)
|
| 1610 |
+
|
| 1611 |
+
# Create and permute tensor A/B/C
|
| 1612 |
+
def create_and_permute_tensor(
|
| 1613 |
+
l, mode0, mode1, is_mode0_major, dtype, is_dynamic_layout=True
|
| 1614 |
+
):
|
| 1615 |
+
# is_mode0_major: (l, mode1, mode0) -> (mode0, mode1, l)
|
| 1616 |
+
# else: (l, mode0, mode1) -> (mode0, mode1, l)
|
| 1617 |
+
shape = (l, mode1, mode0) if is_mode0_major else (l, mode0, mode1)
|
| 1618 |
+
permute_order = (2, 1, 0) if is_mode0_major else (1, 2, 0)
|
| 1619 |
+
is_unsigned = dtype in {cutlass.Uint8}
|
| 1620 |
+
# Temporarily use uint8 as torch does not support fp8 type
|
| 1621 |
+
torch_dtype = (
|
| 1622 |
+
cutlass_torch.dtype(dtype)
|
| 1623 |
+
if dtype not in {cutlass.Float8E5M2, cutlass.Float8E4M3FN}
|
| 1624 |
+
else torch.uint8
|
| 1625 |
+
)
|
| 1626 |
+
|
| 1627 |
+
# Create dtype torch tensor (cpu)
|
| 1628 |
+
torch_tensor_cpu = cutlass_torch.create_and_permute_torch_tensor(
|
| 1629 |
+
shape,
|
| 1630 |
+
torch_dtype,
|
| 1631 |
+
permute_order=permute_order,
|
| 1632 |
+
init_type=cutlass_torch.TensorInitType.RANDOM,
|
| 1633 |
+
init_config=cutlass_torch.RandomInitConfig(
|
| 1634 |
+
min_val=0 if is_unsigned else -2, max_val=4 if is_unsigned else 2
|
| 1635 |
+
),
|
| 1636 |
+
)
|
| 1637 |
+
# Create dtype torch tensor (gpu)
|
| 1638 |
+
torch_tensor = torch_tensor_cpu.cuda()
|
| 1639 |
+
|
| 1640 |
+
# Create f32 torch tensor (cpu)
|
| 1641 |
+
f32_torch_tensor = torch_tensor_cpu.to(dtype=torch.float32)
|
| 1642 |
+
|
| 1643 |
+
# Create dtype cute tensor (gpu)
|
| 1644 |
+
cute_tensor = from_dlpack(torch_tensor, assumed_align=16)
|
| 1645 |
+
cute_tensor.element_type = dtype
|
| 1646 |
+
if is_dynamic_layout:
|
| 1647 |
+
cute_tensor = cute_tensor.mark_layout_dynamic(
|
| 1648 |
+
leading_dim=(0 if is_mode0_major else 1)
|
| 1649 |
+
)
|
| 1650 |
+
cute_tensor = cutlass_torch.convert_cute_tensor(
|
| 1651 |
+
f32_torch_tensor,
|
| 1652 |
+
cute_tensor,
|
| 1653 |
+
dtype,
|
| 1654 |
+
is_dynamic_layout=is_dynamic_layout,
|
| 1655 |
+
)
|
| 1656 |
+
|
| 1657 |
+
return f32_torch_tensor, cute_tensor, torch_tensor
|
| 1658 |
+
|
| 1659 |
+
a_ref, a_tensor, a_torch = create_and_permute_tensor(
|
| 1660 |
+
l, m, k, a_major == "m", ab_dtype, is_dynamic_layout=True
|
| 1661 |
+
)
|
| 1662 |
+
b_ref, b_tensor, b_torch = create_and_permute_tensor(
|
| 1663 |
+
l, n, k, b_major == "n", ab_dtype, is_dynamic_layout=True
|
| 1664 |
+
)
|
| 1665 |
+
c_ref, c_tensor, c_torch = create_and_permute_tensor(
|
| 1666 |
+
l, m, n, c_major == "m", c_dtype, is_dynamic_layout=True
|
| 1667 |
+
)
|
| 1668 |
+
|
| 1669 |
+
# Configure gemm kernel
|
| 1670 |
+
gemm = DenseGemmKernel(
|
| 1671 |
+
acc_dtype,
|
| 1672 |
+
use_2cta_instrs,
|
| 1673 |
+
mma_tiler_mn,
|
| 1674 |
+
cluster_shape_mn,
|
| 1675 |
+
use_tma_store,
|
| 1676 |
+
)
|
| 1677 |
+
|
| 1678 |
+
torch_stream = torch.cuda.Stream()
|
| 1679 |
+
stream = cuda.CUstream(torch_stream.cuda_stream)
|
| 1680 |
+
# Compile gemm kernel
|
| 1681 |
+
compiled_gemm = cute.compile(gemm, a_tensor, b_tensor, c_tensor, stream)
|
| 1682 |
+
|
| 1683 |
+
# Launch GPU kernel
|
| 1684 |
+
# Warm up
|
| 1685 |
+
for i in range(warmup_iterations):
|
| 1686 |
+
compiled_gemm(a_tensor, b_tensor, c_tensor, stream)
|
| 1687 |
+
# Execution
|
| 1688 |
+
for i in range(iterations):
|
| 1689 |
+
compiled_gemm(a_tensor, b_tensor, c_tensor, stream)
|
| 1690 |
+
|
| 1691 |
+
# Compute reference result
|
| 1692 |
+
if not skip_ref_check:
|
| 1693 |
+
if ab_dtype in {
|
| 1694 |
+
cutlass.Int8,
|
| 1695 |
+
cutlass.Uint8,
|
| 1696 |
+
cutlass.Float8E4M3FN,
|
| 1697 |
+
cutlass.Float8E5M2,
|
| 1698 |
+
}:
|
| 1699 |
+
ref = torch.einsum("mkl,nkl->mnl", a_ref.cpu(), b_ref.cpu())
|
| 1700 |
+
else:
|
| 1701 |
+
ref = (torch.einsum("mkl,nkl->mnl", a_ref, b_ref)).cpu()
|
| 1702 |
+
|
| 1703 |
+
# Copy gpu result back
|
| 1704 |
+
gpu_c = c_torch.cpu()
|
| 1705 |
+
|
| 1706 |
+
# Convert ref to c_type
|
| 1707 |
+
if c_dtype == cutlass.Float32:
|
| 1708 |
+
ref_c = ref
|
| 1709 |
+
elif c_dtype in {cutlass.Float8E5M2, cutlass.Float8E4M3FN}:
|
| 1710 |
+
# m major: (l, n, m) -> (m, n, l)
|
| 1711 |
+
# n major: (l, m, n) -> (m, n, l)
|
| 1712 |
+
permute_order = (1, 2, 0) if c_major == "n" else (2, 1, 0)
|
| 1713 |
+
shape = (l, m, n) if c_major == "n" else (l, n, m)
|
| 1714 |
+
f8_torch_tensor = cutlass_torch.create_and_permute_torch_tensor(
|
| 1715 |
+
shape,
|
| 1716 |
+
torch.uint8,
|
| 1717 |
+
permute_order=permute_order,
|
| 1718 |
+
init_type=cutlass_torch.TensorInitType.SKIP,
|
| 1719 |
+
).cuda()
|
| 1720 |
+
# Create dtype cute tensor (gpu)
|
| 1721 |
+
ref_c_tensor = from_dlpack(
|
| 1722 |
+
f8_torch_tensor, assumed_align=16
|
| 1723 |
+
).mark_layout_dynamic(leading_dim=(1 if c_major == "n" else 0))
|
| 1724 |
+
ref_c_tensor.element_type = c_dtype
|
| 1725 |
+
ref_c_tensor = cutlass_torch.convert_cute_tensor(
|
| 1726 |
+
ref,
|
| 1727 |
+
ref_c_tensor,
|
| 1728 |
+
c_dtype,
|
| 1729 |
+
is_dynamic_layout=True,
|
| 1730 |
+
)
|
| 1731 |
+
|
| 1732 |
+
ref_c = f8_torch_tensor.cpu()
|
| 1733 |
+
else:
|
| 1734 |
+
ref_c = ref.to(cutlass_torch.dtype(c_dtype))
|
| 1735 |
+
|
| 1736 |
+
# Reference checking ref_c and gpu_c
|
| 1737 |
+
torch.testing.assert_close(
|
| 1738 |
+
gpu_c,
|
| 1739 |
+
ref_c,
|
| 1740 |
+
atol=tolerance,
|
| 1741 |
+
rtol=1e-05,
|
| 1742 |
+
)
|
| 1743 |
+
|
| 1744 |
+
|
| 1745 |
+
if __name__ == "__main__":
|
| 1746 |
+
|
| 1747 |
+
def parse_comma_separated_ints(s: str) -> Tuple[int, ...]:
|
| 1748 |
+
try:
|
| 1749 |
+
return tuple(int(x.strip()) for x in s.split(","))
|
| 1750 |
+
# or: return tuple([int(x.strip()) for x in s.split(",")])
|
| 1751 |
+
except ValueError:
|
| 1752 |
+
raise argparse.ArgumentTypeError(
|
| 1753 |
+
"Invalid format. Expected comma-separated integers."
|
| 1754 |
+
)
|
| 1755 |
+
|
| 1756 |
+
parser = argparse.ArgumentParser(
|
| 1757 |
+
description="Example of MxNxKxL GEMM on Blackwell."
|
| 1758 |
+
)
|
| 1759 |
+
|
| 1760 |
+
parser.add_argument(
|
| 1761 |
+
"--mnkl",
|
| 1762 |
+
type=parse_comma_separated_ints,
|
| 1763 |
+
default=(256, 256, 512, 1),
|
| 1764 |
+
help="mnkl dimensions (comma-separated)",
|
| 1765 |
+
)
|
| 1766 |
+
parser.add_argument(
|
| 1767 |
+
"--mma_tiler_mn",
|
| 1768 |
+
type=parse_comma_separated_ints,
|
| 1769 |
+
default=(128, 128),
|
| 1770 |
+
help="Mma tiler (comma-separated)",
|
| 1771 |
+
)
|
| 1772 |
+
parser.add_argument(
|
| 1773 |
+
"--cluster_shape_mn",
|
| 1774 |
+
type=parse_comma_separated_ints,
|
| 1775 |
+
default=(1, 1),
|
| 1776 |
+
help="Cluster shape (comma-separated)",
|
| 1777 |
+
)
|
| 1778 |
+
parser.add_argument("--ab_dtype", type=cutlass.dtype, default=cutlass.TFloat32)
|
| 1779 |
+
parser.add_argument("--c_dtype", type=cutlass.dtype, default=cutlass.Float32)
|
| 1780 |
+
parser.add_argument("--acc_dtype", type=cutlass.dtype, default=cutlass.Float32)
|
| 1781 |
+
parser.add_argument(
|
| 1782 |
+
"--use_2cta_instrs",
|
| 1783 |
+
action="store_true",
|
| 1784 |
+
help="Enable 2CTA MMA instructions feature",
|
| 1785 |
+
)
|
| 1786 |
+
parser.add_argument("--a_major", choices=["k", "m"], type=str, default="k")
|
| 1787 |
+
parser.add_argument("--b_major", choices=["k", "n"], type=str, default="k")
|
| 1788 |
+
parser.add_argument("--c_major", choices=["n", "m"], type=str, default="n")
|
| 1789 |
+
parser.add_argument(
|
| 1790 |
+
"--use_tma_store", action="store_true", help="Use tma store or not"
|
| 1791 |
+
)
|
| 1792 |
+
parser.add_argument(
|
| 1793 |
+
"--tolerance", type=float, default=1e-01, help="Tolerance for validation"
|
| 1794 |
+
)
|
| 1795 |
+
parser.add_argument(
|
| 1796 |
+
"--warmup_iterations", type=int, default=0, help="Warmup iterations"
|
| 1797 |
+
)
|
| 1798 |
+
parser.add_argument("--iterations", type=int, default=1, help="Iterations")
|
| 1799 |
+
parser.add_argument(
|
| 1800 |
+
"--skip_ref_check", action="store_true", help="Skip reference checking"
|
| 1801 |
+
)
|
| 1802 |
+
|
| 1803 |
+
args = parser.parse_args()
|
| 1804 |
+
|
| 1805 |
+
if len(args.mnkl) != 4:
|
| 1806 |
+
parser.error("--mnkl must contain exactly 4 values")
|
| 1807 |
+
|
| 1808 |
+
if len(args.mma_tiler_mn) != 2:
|
| 1809 |
+
parser.error("--mma_tiler_mn must contain exactly 2 values")
|
| 1810 |
+
|
| 1811 |
+
if len(args.cluster_shape_mn) != 2:
|
| 1812 |
+
parser.error("--cluster_shape_mn must contain exactly 2 values")
|
| 1813 |
+
|
| 1814 |
+
run_dense_gemm(
|
| 1815 |
+
args.mnkl,
|
| 1816 |
+
args.ab_dtype,
|
| 1817 |
+
args.c_dtype,
|
| 1818 |
+
args.acc_dtype,
|
| 1819 |
+
args.a_major,
|
| 1820 |
+
args.b_major,
|
| 1821 |
+
args.c_major,
|
| 1822 |
+
args.mma_tiler_mn,
|
| 1823 |
+
args.cluster_shape_mn,
|
| 1824 |
+
args.use_2cta_instrs,
|
| 1825 |
+
args.use_tma_store,
|
| 1826 |
+
args.tolerance,
|
| 1827 |
+
args.warmup_iterations,
|
| 1828 |
+
args.iterations,
|
| 1829 |
+
args.skip_ref_check,
|
| 1830 |
+
)
|
| 1831 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/fmha.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/grouped_gemm.py
ADDED
|
@@ -0,0 +1,2445 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
import functools
|
| 31 |
+
from typing import List, Type, Union
|
| 32 |
+
from inspect import isclass
|
| 33 |
+
|
| 34 |
+
import torch
|
| 35 |
+
import cuda.bindings.driver as cuda
|
| 36 |
+
|
| 37 |
+
import cutlass
|
| 38 |
+
import cutlass.cute as cute
|
| 39 |
+
import cutlass.cute.testing as testing
|
| 40 |
+
import cutlass.utils as utils
|
| 41 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 42 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 43 |
+
import cutlass.torch as cutlass_torch
|
| 44 |
+
|
| 45 |
+
"""
|
| 46 |
+
A grouped GEMM example for the NVIDIA Blackwell SM100 architecture using CUTE DSL
|
| 47 |
+
|
| 48 |
+
This example demonstrates an implementation of grouped GEMM using a TMA plus Blackwell SM100 TensorCore
|
| 49 |
+
warp-specialized persistent kernel.
|
| 50 |
+
The grouped GEMM workload computes a batch of GEMM operations with distinct problem sizes. Pointers to matrices
|
| 51 |
+
in global memory are passed to the kernel in an array (also held in global memory). Similarly, problem shapes and
|
| 52 |
+
strides are also stored in arrays in GMEM.
|
| 53 |
+
|
| 54 |
+
This differs from "Batched Array" GEMM since the size of each GEMM problem in the grouped GEMM concept may be distinct.
|
| 55 |
+
|
| 56 |
+
To run this example:
|
| 57 |
+
|
| 58 |
+
.. code-block:: bash
|
| 59 |
+
|
| 60 |
+
python examples/blackwell/grouped_gemm.py \
|
| 61 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 62 |
+
--mma_tiler_mn 128,64 --cluster_shape_mn 1,1 \
|
| 63 |
+
--problem_sizes_mnkl "(8192,1280,32,1),(16,384,1536,1),(640,1280,16,1),(640,160,16,1)" \
|
| 64 |
+
--num_groups 4 --tensormap_update_mode SMEM
|
| 65 |
+
|
| 66 |
+
The above example command makes 4 groups of different m, n, k sizes. The Blackwell tcgen05 MMA tile shape
|
| 67 |
+
is specified as (128, 64) and the cluster shape is (1,1). The input, mma accumulator and output data type
|
| 68 |
+
are set as fp16, fp32 and fp16, respectively.
|
| 69 |
+
|
| 70 |
+
To collect performance with NCU profiler:
|
| 71 |
+
|
| 72 |
+
.. code-block:: bash
|
| 73 |
+
|
| 74 |
+
ncu python examples/blackwell/grouped_gemm.py \
|
| 75 |
+
--ab_dtype Float16 --c_dtype Float16 --acc_dtype Float32 \
|
| 76 |
+
--mma_tiler_mn 128,64 --cluster_shape_mn 1,1 \
|
| 77 |
+
--problem_sizes_mnkl "(8192,1280,32,1),(16,384,1536,1),(640,1280,16,1),(640,160,16,1)" \
|
| 78 |
+
--num_groups 4 --tensormap_update_mode SMEM \
|
| 79 |
+
--warmup_iterations 1 --iterations 10 --skip_ref_check
|
| 80 |
+
|
| 81 |
+
There are some constrains for this example. Besides the constrains from the Balckwell dense GEMM persistent example,
|
| 82 |
+
there are also the following constrains:
|
| 83 |
+
* Only fp16 and bf16 data types are supported as inputs.
|
| 84 |
+
* Output data types could be fp16, bf16 or fp32.
|
| 85 |
+
* The contiguous dimension of each tensor must be at least 16 bytes aligned.
|
| 86 |
+
* The l mode(aka, batch size) for each group must be 1.
|
| 87 |
+
* The majorness for A, B and C must be the same across all groups.
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class GroupedGemmKernel:
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
acc_dtype: type[cutlass.Numeric],
|
| 95 |
+
use_2cta_instrs: bool,
|
| 96 |
+
mma_tiler_mn: tuple[int, int],
|
| 97 |
+
cluster_shape_mn: tuple[int, int],
|
| 98 |
+
tensormap_update_mode: utils.TensorMapUpdateMode = utils.TensorMapUpdateMode.SMEM,
|
| 99 |
+
):
|
| 100 |
+
"""Initializes the configuration for a Blackwell grouped GEMM kernel.
|
| 101 |
+
|
| 102 |
+
Besides configurations for dense persistent GEMM, there is an extra config specific to grouped GEMM:
|
| 103 |
+
|
| 104 |
+
Tensormap Update Mode:
|
| 105 |
+
- tensormap_update_mode: Specifies whether the tensormap is
|
| 106 |
+
updated in global memory(GMEM) or shared memory(SMEM).
|
| 107 |
+
The 2 modes are functionally equivalent and the difference are:
|
| 108 |
+
- We buffer 3 tensormaps in SMEM for A, B, and C tensors (each TMA descriptor takes 128B) when TMA updates performed on SMEM.
|
| 109 |
+
- Performance varies between modes depending on problem size; optimal choice differs across workloads.
|
| 110 |
+
|
| 111 |
+
:param acc_dtype: Data type of the accumulator.
|
| 112 |
+
:type acc_dtype: type[cutlass.Numeric]
|
| 113 |
+
:param use_2cta_instrs: Boolean, True to use cta_group=2 MMA variant.
|
| 114 |
+
:type use_2cta_instrs: bool
|
| 115 |
+
:param mma_tiler_mn: tuple (M, N) shape of the MMA instruction.
|
| 116 |
+
:type mma_tiler_mn: tuple[int, int]
|
| 117 |
+
:param cluster_shape_mn: tuple (ClusterM, ClusterN) shape of the cluster.
|
| 118 |
+
:type cluster_shape_mn: tuple[int, int]
|
| 119 |
+
:param tensormap_update_mode: Mode for updating the tensormap (GMEM or SMEM), defaults to SMEM.
|
| 120 |
+
:type tensormap_update_mode: utils.TensorMapUpdateMode, optional
|
| 121 |
+
"""
|
| 122 |
+
self.acc_dtype: Type[cutlass.Numeric] = acc_dtype
|
| 123 |
+
self.use_2cta_instrs = use_2cta_instrs
|
| 124 |
+
self.cluster_shape_mn = cluster_shape_mn
|
| 125 |
+
# K dimension is deferred in _setup_attributes
|
| 126 |
+
self.mma_tiler = (*mma_tiler_mn, 1)
|
| 127 |
+
self.cta_group = (
|
| 128 |
+
tcgen05.CtaGroup.TWO if use_2cta_instrs else tcgen05.CtaGroup.ONE
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
self.tensormap_update_mode = tensormap_update_mode
|
| 132 |
+
# Delegate tensormap ab initialization to MMA warp when SMEM mode is used for better latency hiding
|
| 133 |
+
self.delegate_tensormap_ab_init = (
|
| 134 |
+
tensormap_update_mode == utils.TensorMapUpdateMode.SMEM
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
self.num_mcast_ctas_a = 1
|
| 138 |
+
self.num_mcast_ctas_b = 1
|
| 139 |
+
self.is_a_mcast = False
|
| 140 |
+
self.is_b_mcast = False
|
| 141 |
+
|
| 142 |
+
self.occupancy = 1
|
| 143 |
+
# Set specialized warp ids
|
| 144 |
+
self.epilog_warp_id = (
|
| 145 |
+
0,
|
| 146 |
+
1,
|
| 147 |
+
2,
|
| 148 |
+
3,
|
| 149 |
+
)
|
| 150 |
+
self.mma_warp_id = 4
|
| 151 |
+
self.tma_warp_id = 5
|
| 152 |
+
self.threads_per_cta = 32 * len(
|
| 153 |
+
(self.mma_warp_id, self.tma_warp_id, *self.epilog_warp_id)
|
| 154 |
+
)
|
| 155 |
+
# Set barrier id for cta sync, epilog sync, tmem ptr sync and tensormap update sync
|
| 156 |
+
self.cta_sync_bar_id = 0
|
| 157 |
+
self.epilog_sync_bar_id = 1
|
| 158 |
+
self.tmem_ptr_sync_bar_id = 2
|
| 159 |
+
# Barrier ID used by MMA/TMA warps to signal A/B tensormap initialization completion
|
| 160 |
+
self.tensormap_ab_init_bar_id = 4
|
| 161 |
+
self.smem_capacity = utils.get_smem_capacity_in_bytes("sm_100")
|
| 162 |
+
self.num_tma_load_bytes = 0
|
| 163 |
+
|
| 164 |
+
def _setup_attributes(self):
|
| 165 |
+
"""Set up configurations that are dependent on GEMM inputs
|
| 166 |
+
|
| 167 |
+
Most of the implementation follows standard dense GEMM patterns,
|
| 168 |
+
with the key difference being additional consideration for SMEM
|
| 169 |
+
buffer needed for tensormap updates.
|
| 170 |
+
"""
|
| 171 |
+
# Configure tiled mma
|
| 172 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 173 |
+
self.a_dtype,
|
| 174 |
+
self.a_major_mode,
|
| 175 |
+
self.b_major_mode,
|
| 176 |
+
self.acc_dtype,
|
| 177 |
+
self.cta_group,
|
| 178 |
+
self.mma_tiler[:2],
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
# Compute mma/cluster/tile shapes
|
| 182 |
+
mma_inst_shape_k = cute.size(tiled_mma.shape_mnk, mode=[2])
|
| 183 |
+
mma_inst_tile_k = 4
|
| 184 |
+
self.mma_tiler = (
|
| 185 |
+
self.mma_tiler[0],
|
| 186 |
+
self.mma_tiler[1],
|
| 187 |
+
mma_inst_shape_k * mma_inst_tile_k,
|
| 188 |
+
)
|
| 189 |
+
self.cta_tile_shape_mnk = (
|
| 190 |
+
self.mma_tiler[0] // cute.size(tiled_mma.thr_id.shape),
|
| 191 |
+
self.mma_tiler[1],
|
| 192 |
+
self.mma_tiler[2],
|
| 193 |
+
)
|
| 194 |
+
self.cluster_tile_shape_mnk = tuple(
|
| 195 |
+
x * y for x, y in zip(self.cta_tile_shape_mnk, (*self.cluster_shape_mn, 1))
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
# Compute cluster layout
|
| 199 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 200 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 201 |
+
(tiled_mma.thr_id.shape,),
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
# Compute number of multicast CTAs for A/B
|
| 205 |
+
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
| 206 |
+
self.num_mcast_ctas_b = cute.size(self.cluster_layout_vmnk.shape[1])
|
| 207 |
+
self.is_a_mcast = self.num_mcast_ctas_a > 1
|
| 208 |
+
self.is_b_mcast = self.num_mcast_ctas_b > 1
|
| 209 |
+
|
| 210 |
+
# Compute epilogue subtile
|
| 211 |
+
self.epi_tile = utils.compute_epilogue_tile_shape(
|
| 212 |
+
self.cta_tile_shape_mnk,
|
| 213 |
+
self.use_2cta_instrs,
|
| 214 |
+
self.c_layout,
|
| 215 |
+
self.c_dtype,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
# Setup A/B/C stage count in shared memory and ACC stage count in tensor memory
|
| 219 |
+
(
|
| 220 |
+
self.num_acc_stage,
|
| 221 |
+
self.num_ab_stage,
|
| 222 |
+
self.num_epi_stage,
|
| 223 |
+
) = self._compute_stages(
|
| 224 |
+
tiled_mma,
|
| 225 |
+
self.mma_tiler,
|
| 226 |
+
self.a_dtype,
|
| 227 |
+
self.b_dtype,
|
| 228 |
+
self.epi_tile,
|
| 229 |
+
self.c_dtype,
|
| 230 |
+
self.c_layout,
|
| 231 |
+
self.smem_capacity,
|
| 232 |
+
self.occupancy,
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
self.a_smem_layout_staged = sm100_utils.make_smem_layout_a(
|
| 236 |
+
tiled_mma,
|
| 237 |
+
self.mma_tiler,
|
| 238 |
+
self.a_dtype,
|
| 239 |
+
self.num_ab_stage,
|
| 240 |
+
)
|
| 241 |
+
self.b_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
| 242 |
+
tiled_mma,
|
| 243 |
+
self.mma_tiler,
|
| 244 |
+
self.b_dtype,
|
| 245 |
+
self.num_ab_stage,
|
| 246 |
+
)
|
| 247 |
+
self.epi_smem_layout_staged = sm100_utils.make_smem_layout_epi(
|
| 248 |
+
self.c_dtype,
|
| 249 |
+
self.c_layout,
|
| 250 |
+
self.epi_tile,
|
| 251 |
+
self.num_epi_stage,
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
tensor_smem_bytes = self._get_tensor_smem_bytes(
|
| 255 |
+
self.a_smem_layout_staged,
|
| 256 |
+
self.a_dtype,
|
| 257 |
+
self.b_smem_layout_staged,
|
| 258 |
+
self.b_dtype,
|
| 259 |
+
self.epi_smem_layout_staged,
|
| 260 |
+
self.c_dtype,
|
| 261 |
+
)
|
| 262 |
+
mbar_smem_bytes = self._get_mbar_smem_bytes(
|
| 263 |
+
num_acc_stage=self.num_acc_stage,
|
| 264 |
+
num_ab_stage=self.num_ab_stage,
|
| 265 |
+
num_epi_stage=self.num_epi_stage,
|
| 266 |
+
)
|
| 267 |
+
tensormap_smem_bytes = self._get_tensormap_smem_bytes(
|
| 268 |
+
self.tensormap_update_mode
|
| 269 |
+
)
|
| 270 |
+
if (
|
| 271 |
+
mbar_smem_bytes
|
| 272 |
+
+ tensormap_smem_bytes
|
| 273 |
+
+ GroupedGemmKernel.tensor_memory_management_bytes
|
| 274 |
+
> self.reserved_smem_bytes
|
| 275 |
+
):
|
| 276 |
+
raise ValueError(
|
| 277 |
+
f"smem consumption for mbar and tensormap {mbar_smem_bytes + tensormap_smem_bytes} exceeds the "
|
| 278 |
+
f"reserved smem bytes {self.reserved_smem_bytes}"
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
# Compute the number of tensor memory allocation columns
|
| 282 |
+
self.num_tmem_alloc_cols = self._compute_num_tmem_alloc_cols(
|
| 283 |
+
tiled_mma, self.mma_tiler, self.num_acc_stage
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
@cute.jit
|
| 287 |
+
def __call__(
|
| 288 |
+
self,
|
| 289 |
+
initial_a: cute.Tensor,
|
| 290 |
+
initial_b: cute.Tensor,
|
| 291 |
+
initial_c: cute.Tensor,
|
| 292 |
+
group_count: cutlass.Constexpr[int],
|
| 293 |
+
problem_shape_mnkl: cute.Tensor,
|
| 294 |
+
strides_abc: cute.Tensor,
|
| 295 |
+
tensor_address_abc: cute.Tensor,
|
| 296 |
+
total_num_clusters: cutlass.Constexpr[int],
|
| 297 |
+
tensormap_cute_tensor: cute.Tensor,
|
| 298 |
+
max_active_clusters: cutlass.Constexpr[int],
|
| 299 |
+
stream: cuda.CUstream,
|
| 300 |
+
):
|
| 301 |
+
"""Execute the GEMM operation in steps:
|
| 302 |
+
- Setup static attributes before smem/grid/tma computation
|
| 303 |
+
- Setup TMA load/store atoms and tensors
|
| 304 |
+
- Compute grid size with regard to hardware constraints
|
| 305 |
+
- Define shared storage for kernel
|
| 306 |
+
- Launch the kernel synchronously
|
| 307 |
+
|
| 308 |
+
For grouped GEMM, tensor shapes, tensor strides, and tensor address are all provided
|
| 309 |
+
by different tensors in global memory. The "initial" tensors only carry data type and
|
| 310 |
+
majorness information.
|
| 311 |
+
|
| 312 |
+
:param initial_a: Initial tensor A, used for data type and majorness information.
|
| 313 |
+
:type initial_a: cute.Tensor
|
| 314 |
+
:param initial_b: Initial tensor B, used for data type and majorness information.
|
| 315 |
+
:type initial_b: cute.Tensor
|
| 316 |
+
:param initial_c: Initial tensor C, used for data type and majorness information.
|
| 317 |
+
:type initial_c: cute.Tensor
|
| 318 |
+
:param group_count: The number of GEMM groups.
|
| 319 |
+
:type group_count: cutlass.Constexpr[int]
|
| 320 |
+
:param problem_shape_mnkl: Tensor containing the (M, N, K, L) shape for each group.
|
| 321 |
+
:type problem_shape_mnkl: cute.Tensor
|
| 322 |
+
:param strides_abc: Tensor containing the strides for A, B, and C for each group.
|
| 323 |
+
:type strides_abc: cute.Tensor
|
| 324 |
+
:param tensor_address_abc: Tensor containing the base addresses for A, B, and C for each group.
|
| 325 |
+
:type tensor_address_abc: cute.Tensor
|
| 326 |
+
:param total_num_clusters: Total number of clusters needed for all groups.
|
| 327 |
+
:type total_num_clusters: cutlass.Constexpr[int]
|
| 328 |
+
:param tensormap_cute_tensor: Tensor for storing tensormaps.
|
| 329 |
+
:type tensormap_cute_tensor: cute.Tensor
|
| 330 |
+
:param max_active_clusters: Maximum number of active clusters.
|
| 331 |
+
:type max_active_clusters: cutlass.Constexpr[int]
|
| 332 |
+
:param stream: CUDA stream for asynchronous execution.
|
| 333 |
+
:type stream: cuda.CUstream
|
| 334 |
+
:raises TypeError: If A and B data types do not match.
|
| 335 |
+
"""
|
| 336 |
+
self.a_dtype = initial_a.element_type
|
| 337 |
+
self.b_dtype = initial_b.element_type
|
| 338 |
+
self.c_dtype = initial_c.element_type
|
| 339 |
+
self.a_major_mode = utils.LayoutEnum.from_tensor(initial_a).mma_major_mode()
|
| 340 |
+
self.b_major_mode = utils.LayoutEnum.from_tensor(initial_b).mma_major_mode()
|
| 341 |
+
self.c_layout = utils.LayoutEnum.from_tensor(initial_c)
|
| 342 |
+
if cutlass.const_expr(self.a_dtype != self.b_dtype):
|
| 343 |
+
raise TypeError(f"Type mismatch: {self.a_dtype} != {self.b_dtype}")
|
| 344 |
+
|
| 345 |
+
# Setup attributes that dependent on gemm inputs
|
| 346 |
+
self._setup_attributes()
|
| 347 |
+
|
| 348 |
+
tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 349 |
+
self.a_dtype,
|
| 350 |
+
self.a_major_mode,
|
| 351 |
+
self.b_major_mode,
|
| 352 |
+
self.acc_dtype,
|
| 353 |
+
self.cta_group,
|
| 354 |
+
self.mma_tiler[:2],
|
| 355 |
+
)
|
| 356 |
+
atom_thr_size = cute.size(tiled_mma.thr_id.shape)
|
| 357 |
+
|
| 358 |
+
# Setup TMA load for A
|
| 359 |
+
a_op = sm100_utils.cluster_shape_to_tma_atom_A(
|
| 360 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 361 |
+
)
|
| 362 |
+
a_smem_layout = cute.slice_(self.a_smem_layout_staged, (None, None, None, 0))
|
| 363 |
+
tma_atom_a, tma_tensor_a = cute.nvgpu.make_tiled_tma_atom_A(
|
| 364 |
+
a_op,
|
| 365 |
+
initial_a,
|
| 366 |
+
a_smem_layout,
|
| 367 |
+
self.mma_tiler,
|
| 368 |
+
tiled_mma,
|
| 369 |
+
self.cluster_layout_vmnk.shape,
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
# Setup TMA load for B
|
| 373 |
+
b_op = sm100_utils.cluster_shape_to_tma_atom_B(
|
| 374 |
+
self.cluster_shape_mn, tiled_mma.thr_id
|
| 375 |
+
)
|
| 376 |
+
b_smem_layout = cute.slice_(self.b_smem_layout_staged, (None, None, None, 0))
|
| 377 |
+
tma_atom_b, tma_tensor_b = cute.nvgpu.make_tiled_tma_atom_B(
|
| 378 |
+
b_op,
|
| 379 |
+
initial_b,
|
| 380 |
+
b_smem_layout,
|
| 381 |
+
self.mma_tiler,
|
| 382 |
+
tiled_mma,
|
| 383 |
+
self.cluster_layout_vmnk.shape,
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
a_copy_size = cute.size_in_bytes(self.a_dtype, a_smem_layout)
|
| 387 |
+
b_copy_size = cute.size_in_bytes(self.b_dtype, b_smem_layout)
|
| 388 |
+
self.num_tma_load_bytes = (a_copy_size + b_copy_size) * atom_thr_size
|
| 389 |
+
|
| 390 |
+
# Setup TMA store for C
|
| 391 |
+
tma_atom_c = None
|
| 392 |
+
tma_tensor_c = None
|
| 393 |
+
c_cta_v_layout = cute.composition(
|
| 394 |
+
cute.make_identity_layout(initial_c.shape), self.epi_tile
|
| 395 |
+
)
|
| 396 |
+
epi_smem_layout = cute.slice_(self.epi_smem_layout_staged, (None, None, 0))
|
| 397 |
+
tma_atom_c, tma_tensor_c = cpasync.make_tiled_tma_atom(
|
| 398 |
+
cpasync.CopyBulkTensorTileS2GOp(),
|
| 399 |
+
initial_c,
|
| 400 |
+
epi_smem_layout,
|
| 401 |
+
c_cta_v_layout,
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
self.tile_sched_params, grid = self._compute_grid(
|
| 405 |
+
total_num_clusters, self.cluster_shape_mn, max_active_clusters
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
self.buffer_align_bytes = 1024
|
| 409 |
+
self.size_tensormap_in_i64 = (
|
| 410 |
+
0
|
| 411 |
+
if self.tensormap_update_mode == utils.TensorMapUpdateMode.GMEM
|
| 412 |
+
else GroupedGemmKernel.num_tensormaps
|
| 413 |
+
* GroupedGemmKernel.bytes_per_tensormap
|
| 414 |
+
// 8
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
# Define shared storage for kernel
|
| 418 |
+
@cute.struct
|
| 419 |
+
class SharedStorage:
|
| 420 |
+
tensormap_buffer: cute.struct.MemRange[
|
| 421 |
+
cutlass.Int64, self.size_tensormap_in_i64
|
| 422 |
+
]
|
| 423 |
+
ab_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 424 |
+
ab_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_ab_stage]
|
| 425 |
+
acc_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 426 |
+
acc_empty_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage]
|
| 427 |
+
tmem_dealloc_mbar_ptr: cutlass.Int64
|
| 428 |
+
tmem_holding_buf: cutlass.Int32
|
| 429 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 430 |
+
sC: cute.struct.Align[
|
| 431 |
+
cute.struct.MemRange[
|
| 432 |
+
self.c_dtype,
|
| 433 |
+
cute.cosize(self.epi_smem_layout_staged.outer),
|
| 434 |
+
],
|
| 435 |
+
self.buffer_align_bytes,
|
| 436 |
+
]
|
| 437 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 438 |
+
sA: cute.struct.Align[
|
| 439 |
+
cute.struct.MemRange[
|
| 440 |
+
self.a_dtype, cute.cosize(self.a_smem_layout_staged.outer)
|
| 441 |
+
],
|
| 442 |
+
self.buffer_align_bytes,
|
| 443 |
+
]
|
| 444 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 445 |
+
sB: cute.struct.Align[
|
| 446 |
+
cute.struct.MemRange[
|
| 447 |
+
self.b_dtype, cute.cosize(self.b_smem_layout_staged.outer)
|
| 448 |
+
],
|
| 449 |
+
self.buffer_align_bytes,
|
| 450 |
+
]
|
| 451 |
+
|
| 452 |
+
self.shared_storage = SharedStorage
|
| 453 |
+
|
| 454 |
+
# Launch the kernel synchronously
|
| 455 |
+
self.kernel(
|
| 456 |
+
tiled_mma,
|
| 457 |
+
tma_atom_a,
|
| 458 |
+
tma_tensor_a,
|
| 459 |
+
tma_atom_b,
|
| 460 |
+
tma_tensor_b,
|
| 461 |
+
tma_atom_c,
|
| 462 |
+
tma_tensor_c,
|
| 463 |
+
self.cluster_layout_vmnk,
|
| 464 |
+
self.a_smem_layout_staged,
|
| 465 |
+
self.b_smem_layout_staged,
|
| 466 |
+
self.epi_smem_layout_staged,
|
| 467 |
+
self.epi_tile,
|
| 468 |
+
self.tile_sched_params,
|
| 469 |
+
group_count,
|
| 470 |
+
problem_shape_mnkl,
|
| 471 |
+
strides_abc,
|
| 472 |
+
tensor_address_abc,
|
| 473 |
+
tensormap_cute_tensor,
|
| 474 |
+
).launch(
|
| 475 |
+
grid=grid,
|
| 476 |
+
block=[self.threads_per_cta, 1, 1],
|
| 477 |
+
cluster=(*self.cluster_shape_mn, 1),
|
| 478 |
+
stream=stream,
|
| 479 |
+
)
|
| 480 |
+
return
|
| 481 |
+
|
| 482 |
+
# GPU device kernel
|
| 483 |
+
@cute.kernel
|
| 484 |
+
def kernel(
|
| 485 |
+
self,
|
| 486 |
+
tiled_mma: cute.TiledMma,
|
| 487 |
+
tma_atom_a: cute.CopyAtom,
|
| 488 |
+
mA_mkl: cute.Tensor,
|
| 489 |
+
tma_atom_b: cute.CopyAtom,
|
| 490 |
+
mB_nkl: cute.Tensor,
|
| 491 |
+
tma_atom_c: cute.CopyAtom,
|
| 492 |
+
mC_mnl: cute.Tensor,
|
| 493 |
+
cluster_layout_vmnk: cute.Layout,
|
| 494 |
+
a_smem_layout_staged: cute.ComposedLayout,
|
| 495 |
+
b_smem_layout_staged: cute.ComposedLayout,
|
| 496 |
+
epi_smem_layout_staged: Union[cute.Layout, cute.ComposedLayout],
|
| 497 |
+
epi_tile: cute.Tile,
|
| 498 |
+
tile_sched_params: utils.PersistentTileSchedulerParams,
|
| 499 |
+
group_count: cutlass.Constexpr[int],
|
| 500 |
+
problem_sizes_mnkl: cute.Tensor,
|
| 501 |
+
strides_abc: cute.Tensor,
|
| 502 |
+
ptrs_abc: cute.Tensor,
|
| 503 |
+
tensormaps: cute.Tensor,
|
| 504 |
+
):
|
| 505 |
+
"""
|
| 506 |
+
GPU device kernel performing the grouped GEMM computation.
|
| 507 |
+
"""
|
| 508 |
+
warp_idx = cute.arch.warp_idx()
|
| 509 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 510 |
+
|
| 511 |
+
#
|
| 512 |
+
# Prefetch tma desc
|
| 513 |
+
#
|
| 514 |
+
if warp_idx == self.tma_warp_id:
|
| 515 |
+
cpasync.prefetch_descriptor(tma_atom_a)
|
| 516 |
+
cpasync.prefetch_descriptor(tma_atom_b)
|
| 517 |
+
cpasync.prefetch_descriptor(tma_atom_c)
|
| 518 |
+
|
| 519 |
+
use_2cta_instrs = cute.size(tiled_mma.thr_id.shape) == 2
|
| 520 |
+
|
| 521 |
+
#
|
| 522 |
+
# Setup cta/thread coordinates
|
| 523 |
+
#
|
| 524 |
+
# Coord inside cluster
|
| 525 |
+
bid = cute.arch.block_idx()
|
| 526 |
+
mma_tile_coord_v = bid[0] % cute.size(tiled_mma.thr_id.shape)
|
| 527 |
+
is_leader_cta = mma_tile_coord_v == 0
|
| 528 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(
|
| 529 |
+
cute.arch.block_idx_in_cluster()
|
| 530 |
+
)
|
| 531 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(
|
| 532 |
+
cta_rank_in_cluster
|
| 533 |
+
)
|
| 534 |
+
# Coord inside cta
|
| 535 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 536 |
+
|
| 537 |
+
#
|
| 538 |
+
# Alloc and init: tensormap buffer, a+b full/empty, accumulator full/empty, tensor memory dealloc barrier
|
| 539 |
+
#
|
| 540 |
+
smem = utils.SmemAllocator()
|
| 541 |
+
storage = smem.allocate(self.shared_storage)
|
| 542 |
+
|
| 543 |
+
tensormap_a_smem_ptr = None
|
| 544 |
+
tensormap_b_smem_ptr = None
|
| 545 |
+
tensormap_c_smem_ptr = None
|
| 546 |
+
if cutlass.const_expr(
|
| 547 |
+
self.tensormap_update_mode == utils.TensorMapUpdateMode.SMEM
|
| 548 |
+
):
|
| 549 |
+
tensormap_smem_ptr = storage.tensormap_buffer.data_ptr()
|
| 550 |
+
tensormap_a_smem_ptr = tensormap_smem_ptr
|
| 551 |
+
tensormap_b_smem_ptr = (
|
| 552 |
+
tensormap_a_smem_ptr + GroupedGemmKernel.bytes_per_tensormap // 8
|
| 553 |
+
)
|
| 554 |
+
tensormap_c_smem_ptr = (
|
| 555 |
+
tensormap_b_smem_ptr + GroupedGemmKernel.bytes_per_tensormap // 8
|
| 556 |
+
)
|
| 557 |
+
ab_full_mbar_ptr = storage.ab_full_mbar_ptr.data_ptr()
|
| 558 |
+
ab_empty_mbar_ptr = storage.ab_empty_mbar_ptr.data_ptr()
|
| 559 |
+
acc_full_mbar_ptr = storage.acc_full_mbar_ptr.data_ptr()
|
| 560 |
+
acc_empty_mbar_ptr = storage.acc_empty_mbar_ptr.data_ptr()
|
| 561 |
+
tmem_dealloc_mbar_ptr = storage.tmem_dealloc_mbar_ptr
|
| 562 |
+
tmem_holding_buf = storage.tmem_holding_buf
|
| 563 |
+
|
| 564 |
+
# init barrier for loading A, B with TMA
|
| 565 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 566 |
+
for k_stage in range(self.num_ab_stage):
|
| 567 |
+
num_tma_producer = self.num_mcast_ctas_a + self.num_mcast_ctas_b - 1
|
| 568 |
+
with cute.arch.elect_one():
|
| 569 |
+
cute.arch.mbarrier_init(ab_full_mbar_ptr + k_stage, 1)
|
| 570 |
+
cute.arch.mbarrier_init(
|
| 571 |
+
ab_empty_mbar_ptr + k_stage, num_tma_producer
|
| 572 |
+
)
|
| 573 |
+
# Accumulator barrier init
|
| 574 |
+
if warp_idx == self.mma_warp_id:
|
| 575 |
+
for acc_stage in range(self.num_acc_stage):
|
| 576 |
+
with cute.arch.elect_one():
|
| 577 |
+
cute.arch.mbarrier_init(acc_full_mbar_ptr + acc_stage, 1)
|
| 578 |
+
cute.arch.mbarrier_init(
|
| 579 |
+
acc_empty_mbar_ptr + acc_stage, 8 if use_2cta_instrs else 4
|
| 580 |
+
)
|
| 581 |
+
# Tensor memory dealloc barrier init
|
| 582 |
+
if use_2cta_instrs:
|
| 583 |
+
if warp_idx == self.tma_warp_id:
|
| 584 |
+
num_tmem_dealloc_threads = 32
|
| 585 |
+
with cute.arch.elect_one():
|
| 586 |
+
cute.arch.mbarrier_init(
|
| 587 |
+
tmem_dealloc_mbar_ptr, num_tmem_dealloc_threads
|
| 588 |
+
)
|
| 589 |
+
cute.arch.mbarrier_init_fence()
|
| 590 |
+
|
| 591 |
+
# Cluster arrive after barrier init
|
| 592 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 593 |
+
cute.arch.cluster_arrive_relaxed()
|
| 594 |
+
|
| 595 |
+
#
|
| 596 |
+
# Setup smem tensor A/B/C
|
| 597 |
+
#
|
| 598 |
+
# (EPI_TILE_M, EPI_TILE_N, STAGE)
|
| 599 |
+
sC = storage.sC.get_tensor(
|
| 600 |
+
epi_smem_layout_staged.outer, swizzle=epi_smem_layout_staged.inner
|
| 601 |
+
)
|
| 602 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 603 |
+
sA = storage.sA.get_tensor(
|
| 604 |
+
a_smem_layout_staged.outer, swizzle=a_smem_layout_staged.inner
|
| 605 |
+
)
|
| 606 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 607 |
+
sB = storage.sB.get_tensor(
|
| 608 |
+
b_smem_layout_staged.outer, swizzle=b_smem_layout_staged.inner
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
#
|
| 612 |
+
# Compute multicast mask for A/B buffer full and empty
|
| 613 |
+
#
|
| 614 |
+
a_full_mcast_mask = None
|
| 615 |
+
b_full_mcast_mask = None
|
| 616 |
+
ab_empty_mcast_mask = None
|
| 617 |
+
if cutlass.const_expr(self.is_a_mcast or self.is_b_mcast or use_2cta_instrs):
|
| 618 |
+
a_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 619 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 620 |
+
)
|
| 621 |
+
b_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 622 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=1
|
| 623 |
+
)
|
| 624 |
+
ab_empty_mcast_mask = a_full_mcast_mask | b_full_mcast_mask
|
| 625 |
+
acc_full_mcast_mask = None
|
| 626 |
+
if cutlass.const_expr(use_2cta_instrs):
|
| 627 |
+
acc_full_mcast_mask = cute.make_layout_image_mask(
|
| 628 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mode=0
|
| 629 |
+
)
|
| 630 |
+
block_in_cluster_coord_vmnk_peer = (
|
| 631 |
+
block_in_cluster_coord_vmnk[0] ^ 1,
|
| 632 |
+
*block_in_cluster_coord_vmnk[1:],
|
| 633 |
+
)
|
| 634 |
+
a_full_mcast_mask_peer = cpasync.create_tma_multicast_mask(
|
| 635 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk_peer, mcast_mode=2
|
| 636 |
+
)
|
| 637 |
+
b_full_mcast_mask_peer = cpasync.create_tma_multicast_mask(
|
| 638 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk_peer, mcast_mode=1
|
| 639 |
+
)
|
| 640 |
+
ab_empty_mcast_mask = (
|
| 641 |
+
a_full_mcast_mask_peer
|
| 642 |
+
| b_full_mcast_mask_peer
|
| 643 |
+
| cutlass.Int16(
|
| 644 |
+
0 if ab_empty_mcast_mask is None else ab_empty_mcast_mask
|
| 645 |
+
)
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
#
|
| 649 |
+
# Local_tile partition global tensors
|
| 650 |
+
#
|
| 651 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 652 |
+
gA_mkl = cute.local_tile(
|
| 653 |
+
mA_mkl, cute.slice_(self.mma_tiler, (None, 0, None)), (None, None, None)
|
| 654 |
+
)
|
| 655 |
+
# (bN, bK, RestN, RestK, RestL)
|
| 656 |
+
gB_nkl = cute.local_tile(
|
| 657 |
+
mB_nkl, cute.slice_(self.mma_tiler, (0, None, None)), (None, None, None)
|
| 658 |
+
)
|
| 659 |
+
# (bM, bN, RestM, RestN, RestL)
|
| 660 |
+
gC_mnl = cute.local_tile(
|
| 661 |
+
mC_mnl, cute.slice_(self.mma_tiler, (None, None, 0)), (None, None, None)
|
| 662 |
+
)
|
| 663 |
+
|
| 664 |
+
#
|
| 665 |
+
# Partition global tensor for TiledMMA_A/B/C
|
| 666 |
+
#
|
| 667 |
+
thr_mma = tiled_mma.get_slice(mma_tile_coord_v)
|
| 668 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 669 |
+
tCgA = thr_mma.partition_A(gA_mkl)
|
| 670 |
+
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
|
| 671 |
+
tCgB = thr_mma.partition_B(gB_nkl)
|
| 672 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 673 |
+
tCgC = thr_mma.partition_C(gC_mnl)
|
| 674 |
+
|
| 675 |
+
#
|
| 676 |
+
# Partition global/shared tensor for load A, B with TMA
|
| 677 |
+
#
|
| 678 |
+
a_cta_layout = cute.make_layout(
|
| 679 |
+
cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape
|
| 680 |
+
)
|
| 681 |
+
# ((atom_v, rest_v), STAGE)
|
| 682 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 683 |
+
tAsA, tAgA = cpasync.tma_partition(
|
| 684 |
+
tma_atom_a,
|
| 685 |
+
block_in_cluster_coord_vmnk[2],
|
| 686 |
+
a_cta_layout,
|
| 687 |
+
cute.group_modes(sA, 0, 3),
|
| 688 |
+
cute.group_modes(tCgA, 0, 3),
|
| 689 |
+
)
|
| 690 |
+
# TMA load B partition_S/D
|
| 691 |
+
b_cta_layout = cute.make_layout(
|
| 692 |
+
cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape
|
| 693 |
+
)
|
| 694 |
+
# ((atom_v, rest_v), STAGE)
|
| 695 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 696 |
+
tBsB, tBgB = cpasync.tma_partition(
|
| 697 |
+
tma_atom_b,
|
| 698 |
+
block_in_cluster_coord_vmnk[1],
|
| 699 |
+
b_cta_layout,
|
| 700 |
+
cute.group_modes(sB, 0, 3),
|
| 701 |
+
cute.group_modes(tCgB, 0, 3),
|
| 702 |
+
)
|
| 703 |
+
|
| 704 |
+
#
|
| 705 |
+
# Partition shared/tensor memory tensor for TiledMMA_A/B/C
|
| 706 |
+
#
|
| 707 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 708 |
+
tCrA = tiled_mma.make_fragment_A(sA)
|
| 709 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 710 |
+
tCrB = tiled_mma.make_fragment_B(sB)
|
| 711 |
+
# (MMA, MMA_M, MMA_N)
|
| 712 |
+
acc_shape = tiled_mma.partition_shape_C(self.mma_tiler[:2])
|
| 713 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 714 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(
|
| 715 |
+
cute.append(acc_shape, self.num_acc_stage)
|
| 716 |
+
)
|
| 717 |
+
|
| 718 |
+
#
|
| 719 |
+
# Cluster wait before tensor memory alloc
|
| 720 |
+
#
|
| 721 |
+
if cute.size(self.cluster_shape_mn) > 1:
|
| 722 |
+
cute.arch.cluster_wait()
|
| 723 |
+
else:
|
| 724 |
+
cute.arch.barrier(
|
| 725 |
+
barrier_id=self.cta_sync_bar_id, number_of_threads=self.threads_per_cta
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
+
#
|
| 729 |
+
# Get tensormap buffer address
|
| 730 |
+
#
|
| 731 |
+
grid_dim = cute.arch.grid_dim()
|
| 732 |
+
tensormap_workspace_idx = (
|
| 733 |
+
bid[2] * grid_dim[1] * grid_dim[0] + bid[1] * grid_dim[0] + bid[0]
|
| 734 |
+
)
|
| 735 |
+
|
| 736 |
+
tensormap_manager = utils.TensorMapManager(
|
| 737 |
+
self.tensormap_update_mode, GroupedGemmKernel.bytes_per_tensormap
|
| 738 |
+
)
|
| 739 |
+
tensormap_a_ptr = tensormap_manager.get_tensormap_ptr(
|
| 740 |
+
tensormaps[(tensormap_workspace_idx, 0, None)].iterator
|
| 741 |
+
)
|
| 742 |
+
tensormap_b_ptr = tensormap_manager.get_tensormap_ptr(
|
| 743 |
+
tensormaps[(tensormap_workspace_idx, 1, None)].iterator
|
| 744 |
+
)
|
| 745 |
+
tensormap_c_ptr = tensormap_manager.get_tensormap_ptr(
|
| 746 |
+
tensormaps[(tensormap_workspace_idx, 2, None)].iterator
|
| 747 |
+
)
|
| 748 |
+
# Setup tensormap initialization pointer based on the mode
|
| 749 |
+
if cutlass.const_expr(
|
| 750 |
+
self.tensormap_update_mode == utils.TensorMapUpdateMode.SMEM
|
| 751 |
+
):
|
| 752 |
+
tensormap_a_init_ptr = tensormap_a_smem_ptr
|
| 753 |
+
tensormap_b_init_ptr = tensormap_b_smem_ptr
|
| 754 |
+
tensormap_c_init_ptr = tensormap_c_smem_ptr
|
| 755 |
+
else:
|
| 756 |
+
tensormap_a_init_ptr = tensormap_a_ptr
|
| 757 |
+
tensormap_b_init_ptr = tensormap_b_ptr
|
| 758 |
+
tensormap_c_init_ptr = tensormap_c_ptr
|
| 759 |
+
|
| 760 |
+
#
|
| 761 |
+
# Specialized TMA load warp
|
| 762 |
+
#
|
| 763 |
+
if warp_idx == self.tma_warp_id:
|
| 764 |
+
# Initialize tensormaps for A, B
|
| 765 |
+
if cutlass.const_expr(self.delegate_tensormap_ab_init == False):
|
| 766 |
+
tensormap_manager.init_tensormap_from_atom(
|
| 767 |
+
tma_atom_a, tensormap_a_init_ptr, self.tma_warp_id
|
| 768 |
+
)
|
| 769 |
+
tensormap_manager.init_tensormap_from_atom(
|
| 770 |
+
tma_atom_b, tensormap_b_init_ptr, self.tma_warp_id
|
| 771 |
+
)
|
| 772 |
+
#
|
| 773 |
+
# Persistent tile scheduling loop
|
| 774 |
+
#
|
| 775 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 776 |
+
tile_sched_params, bid, grid_dim
|
| 777 |
+
)
|
| 778 |
+
# grouped gemm tile scheduler helper will compute the group index for the tile we're working on
|
| 779 |
+
group_gemm_ts_helper = utils.GroupedGemmTileSchedulerHelper(
|
| 780 |
+
group_count,
|
| 781 |
+
tile_sched_params,
|
| 782 |
+
self.cluster_tile_shape_mnk,
|
| 783 |
+
utils.create_initial_search_state(),
|
| 784 |
+
)
|
| 785 |
+
tensormap_init_done = cutlass.Boolean(False)
|
| 786 |
+
# tile count we have searched
|
| 787 |
+
total_k_tile_cnt = cutlass.Int32(0)
|
| 788 |
+
# group index of last tile
|
| 789 |
+
last_group_idx = cutlass.Int32(-1)
|
| 790 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 791 |
+
while work_tile.is_valid_tile:
|
| 792 |
+
cur_tile_coord = work_tile.tile_idx
|
| 793 |
+
grouped_gemm_cta_tile_info = group_gemm_ts_helper.delinearize_z(
|
| 794 |
+
cur_tile_coord,
|
| 795 |
+
problem_sizes_mnkl,
|
| 796 |
+
)
|
| 797 |
+
cur_k_tile_cnt = grouped_gemm_cta_tile_info.cta_tile_count_k
|
| 798 |
+
cur_group_idx = grouped_gemm_cta_tile_info.group_idx
|
| 799 |
+
is_group_changed = cur_group_idx != last_group_idx
|
| 800 |
+
# skip tensormap update if we're working on the same group
|
| 801 |
+
if is_group_changed:
|
| 802 |
+
real_tensor_a = self.make_tensor_for_tensormap_update(
|
| 803 |
+
cur_group_idx,
|
| 804 |
+
self.a_dtype,
|
| 805 |
+
(
|
| 806 |
+
grouped_gemm_cta_tile_info.problem_shape_m,
|
| 807 |
+
grouped_gemm_cta_tile_info.problem_shape_n,
|
| 808 |
+
grouped_gemm_cta_tile_info.problem_shape_k,
|
| 809 |
+
),
|
| 810 |
+
strides_abc,
|
| 811 |
+
ptrs_abc,
|
| 812 |
+
0, # 0 for tensor A
|
| 813 |
+
)
|
| 814 |
+
real_tensor_b = self.make_tensor_for_tensormap_update(
|
| 815 |
+
cur_group_idx,
|
| 816 |
+
self.b_dtype,
|
| 817 |
+
(
|
| 818 |
+
grouped_gemm_cta_tile_info.problem_shape_m,
|
| 819 |
+
grouped_gemm_cta_tile_info.problem_shape_n,
|
| 820 |
+
grouped_gemm_cta_tile_info.problem_shape_k,
|
| 821 |
+
),
|
| 822 |
+
strides_abc,
|
| 823 |
+
ptrs_abc,
|
| 824 |
+
1, # 1 for tensor B
|
| 825 |
+
)
|
| 826 |
+
# wait tensormap initialization complete before update
|
| 827 |
+
if tensormap_init_done == False:
|
| 828 |
+
if cutlass.const_expr(self.delegate_tensormap_ab_init):
|
| 829 |
+
cute.arch.barrier(
|
| 830 |
+
barrier_id=self.tensormap_ab_init_bar_id,
|
| 831 |
+
number_of_threads=64,
|
| 832 |
+
)
|
| 833 |
+
tensormap_manager.fence_tensormap_initialization()
|
| 834 |
+
tensormap_init_done = True
|
| 835 |
+
|
| 836 |
+
tensormap_manager.update_tensormap(
|
| 837 |
+
(real_tensor_a, real_tensor_b),
|
| 838 |
+
(tma_atom_a, tma_atom_b),
|
| 839 |
+
(tensormap_a_ptr, tensormap_b_ptr),
|
| 840 |
+
self.tma_warp_id,
|
| 841 |
+
(tensormap_a_smem_ptr, tensormap_b_smem_ptr),
|
| 842 |
+
)
|
| 843 |
+
|
| 844 |
+
mma_tile_coord_mnl = (
|
| 845 |
+
grouped_gemm_cta_tile_info.cta_tile_idx_m
|
| 846 |
+
// cute.size(tiled_mma.thr_id.shape),
|
| 847 |
+
grouped_gemm_cta_tile_info.cta_tile_idx_n,
|
| 848 |
+
0,
|
| 849 |
+
)
|
| 850 |
+
|
| 851 |
+
#
|
| 852 |
+
# Slice to per mma tile index
|
| 853 |
+
#
|
| 854 |
+
# ((atom_v, rest_v), RestK)
|
| 855 |
+
tAgA_slice = tAgA[
|
| 856 |
+
(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])
|
| 857 |
+
]
|
| 858 |
+
# ((atom_v, rest_v), RestK)
|
| 859 |
+
tBgB_slice = tBgB[
|
| 860 |
+
(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])
|
| 861 |
+
]
|
| 862 |
+
|
| 863 |
+
num_prev_k_blk = total_k_tile_cnt
|
| 864 |
+
total_k_tile_cnt += cur_k_tile_cnt
|
| 865 |
+
|
| 866 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt
|
| 867 |
+
tma_wr_k_tile = cutlass.Int32(0)
|
| 868 |
+
smem_wr_buffer = (num_prev_k_blk + tma_wr_k_tile) % self.num_ab_stage
|
| 869 |
+
tma_wr_ab_empty_phase = (
|
| 870 |
+
num_prev_k_blk + tma_wr_k_tile
|
| 871 |
+
) // self.num_ab_stage % 2 ^ 1
|
| 872 |
+
peek_ab_empty_status = cute.arch.mbarrier_conditional_try_wait(
|
| 873 |
+
tma_wr_k_tile < cur_k_tile_cnt,
|
| 874 |
+
ab_empty_mbar_ptr + smem_wr_buffer,
|
| 875 |
+
tma_wr_ab_empty_phase,
|
| 876 |
+
)
|
| 877 |
+
# ensure the update to tensormap has completed before using it
|
| 878 |
+
if is_group_changed:
|
| 879 |
+
tensormap_manager.fence_tensormap_update(tensormap_a_ptr)
|
| 880 |
+
tensormap_manager.fence_tensormap_update(tensormap_b_ptr)
|
| 881 |
+
#
|
| 882 |
+
# Tma load loop
|
| 883 |
+
#
|
| 884 |
+
for k_tile in cutlass.range(0, cur_k_tile_cnt, 1, unroll=1):
|
| 885 |
+
tma_wr_k_tile_next = tma_wr_k_tile + 1
|
| 886 |
+
smem_wr_buffer_next = (
|
| 887 |
+
num_prev_k_blk + tma_wr_k_tile_next
|
| 888 |
+
) % self.num_ab_stage
|
| 889 |
+
tma_wr_ab_empty_phase_next = (
|
| 890 |
+
tma_wr_ab_empty_phase ^ 1
|
| 891 |
+
if smem_wr_buffer_next == 0
|
| 892 |
+
else tma_wr_ab_empty_phase
|
| 893 |
+
)
|
| 894 |
+
|
| 895 |
+
smem_full_mbar_ptr = ab_full_mbar_ptr + smem_wr_buffer
|
| 896 |
+
|
| 897 |
+
# Wait for AB buffer empty
|
| 898 |
+
if peek_ab_empty_status == 0:
|
| 899 |
+
cute.arch.mbarrier_wait(
|
| 900 |
+
ab_empty_mbar_ptr + smem_wr_buffer, tma_wr_ab_empty_phase
|
| 901 |
+
)
|
| 902 |
+
|
| 903 |
+
# Arrive AB buffer and expect full transaction bytes
|
| 904 |
+
if is_leader_cta:
|
| 905 |
+
with cute.arch.elect_one():
|
| 906 |
+
cute.arch.mbarrier_arrive_and_expect_tx(
|
| 907 |
+
smem_full_mbar_ptr, self.num_tma_load_bytes
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
# Load A/B with TMA
|
| 911 |
+
cute.copy(
|
| 912 |
+
tma_atom_a,
|
| 913 |
+
tAgA_slice[(None, tma_wr_k_tile)],
|
| 914 |
+
tAsA[(None, smem_wr_buffer)],
|
| 915 |
+
tma_bar_ptr=smem_full_mbar_ptr,
|
| 916 |
+
mcast_mask=a_full_mcast_mask,
|
| 917 |
+
tma_desc_ptr=tensormap_manager.get_tensormap_ptr(
|
| 918 |
+
tensormap_a_ptr,
|
| 919 |
+
cute.AddressSpace.generic,
|
| 920 |
+
),
|
| 921 |
+
)
|
| 922 |
+
cute.copy(
|
| 923 |
+
tma_atom_b,
|
| 924 |
+
tBgB_slice[(None, tma_wr_k_tile)],
|
| 925 |
+
tBsB[(None, smem_wr_buffer)],
|
| 926 |
+
tma_bar_ptr=smem_full_mbar_ptr,
|
| 927 |
+
mcast_mask=b_full_mcast_mask,
|
| 928 |
+
tma_desc_ptr=tensormap_manager.get_tensormap_ptr(
|
| 929 |
+
tensormap_b_ptr,
|
| 930 |
+
cute.AddressSpace.generic,
|
| 931 |
+
),
|
| 932 |
+
)
|
| 933 |
+
|
| 934 |
+
# Peek (try_wait) AB buffer empty for k_tile = prefetch_k_tile_cnt + k_tile + 1
|
| 935 |
+
peek_ab_empty_status = cute.arch.mbarrier_conditional_try_wait(
|
| 936 |
+
tma_wr_k_tile_next < cur_k_tile_cnt,
|
| 937 |
+
ab_empty_mbar_ptr + smem_wr_buffer_next,
|
| 938 |
+
tma_wr_ab_empty_phase_next,
|
| 939 |
+
)
|
| 940 |
+
|
| 941 |
+
tma_wr_k_tile = tma_wr_k_tile_next
|
| 942 |
+
smem_wr_buffer = smem_wr_buffer_next
|
| 943 |
+
tma_wr_ab_empty_phase = tma_wr_ab_empty_phase_next
|
| 944 |
+
|
| 945 |
+
# Advance to next tile
|
| 946 |
+
tile_sched.advance_to_next_work()
|
| 947 |
+
work_tile = tile_sched.get_current_work()
|
| 948 |
+
last_group_idx = cur_group_idx
|
| 949 |
+
|
| 950 |
+
#
|
| 951 |
+
# Specialized MMA warp
|
| 952 |
+
#
|
| 953 |
+
if warp_idx == self.mma_warp_id:
|
| 954 |
+
# initialize tensormap A, B for TMA warp
|
| 955 |
+
if cutlass.const_expr(self.delegate_tensormap_ab_init):
|
| 956 |
+
tensormap_manager.init_tensormap_from_atom(
|
| 957 |
+
tma_atom_a, tensormap_a_init_ptr, self.mma_warp_id
|
| 958 |
+
)
|
| 959 |
+
tensormap_manager.init_tensormap_from_atom(
|
| 960 |
+
tma_atom_b, tensormap_b_init_ptr, self.mma_warp_id
|
| 961 |
+
)
|
| 962 |
+
# signal tensormap initialization has finished
|
| 963 |
+
cute.arch.barrier(
|
| 964 |
+
barrier_id=self.tensormap_ab_init_bar_id, number_of_threads=64
|
| 965 |
+
)
|
| 966 |
+
# Bar sync for retrieve tmem ptr from shared mem
|
| 967 |
+
tmem_ptr_read_threads = 32 * len((self.mma_warp_id, *self.epilog_warp_id))
|
| 968 |
+
cute.arch.barrier(
|
| 969 |
+
barrier_id=self.tmem_ptr_sync_bar_id,
|
| 970 |
+
number_of_threads=tmem_ptr_read_threads,
|
| 971 |
+
)
|
| 972 |
+
|
| 973 |
+
#
|
| 974 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 975 |
+
#
|
| 976 |
+
tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 977 |
+
self.acc_dtype,
|
| 978 |
+
alignment=16,
|
| 979 |
+
ptr_to_buffer_holding_addr=tmem_holding_buf,
|
| 980 |
+
)
|
| 981 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 982 |
+
tCtAcc_base = cute.make_tensor(tmem_ptr, tCtAcc_fake.layout)
|
| 983 |
+
|
| 984 |
+
#
|
| 985 |
+
# Persistent tile scheduling loop
|
| 986 |
+
#
|
| 987 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 988 |
+
tile_sched_params, bid, grid_dim
|
| 989 |
+
)
|
| 990 |
+
# grouped gemm tile scheduler helper will compute the group index for the tile we're working on
|
| 991 |
+
group_gemm_ts_helper = utils.GroupedGemmTileSchedulerHelper(
|
| 992 |
+
group_count,
|
| 993 |
+
tile_sched_params,
|
| 994 |
+
self.cluster_tile_shape_mnk,
|
| 995 |
+
utils.create_initial_search_state(),
|
| 996 |
+
)
|
| 997 |
+
|
| 998 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 999 |
+
# tile count we have searched
|
| 1000 |
+
total_k_tile_cnt = cutlass.Int32(0)
|
| 1001 |
+
while work_tile.is_valid_tile:
|
| 1002 |
+
cur_tile_coord = work_tile.tile_idx
|
| 1003 |
+
# MMA warp is only interested in number of tiles along K dimension
|
| 1004 |
+
(
|
| 1005 |
+
cur_k_tile_cnt,
|
| 1006 |
+
cur_group_idx,
|
| 1007 |
+
) = group_gemm_ts_helper.search_cluster_tile_count_k(
|
| 1008 |
+
cur_tile_coord,
|
| 1009 |
+
problem_sizes_mnkl,
|
| 1010 |
+
)
|
| 1011 |
+
# Set tensor memory buffer for current tile
|
| 1012 |
+
acc_buf_idx = tile_sched.num_tiles_executed % self.num_acc_stage
|
| 1013 |
+
# (MMA, MMA_M, MMA_N)
|
| 1014 |
+
tCtAcc = tCtAcc_base[(None, None, None, acc_buf_idx)]
|
| 1015 |
+
|
| 1016 |
+
num_prev_k_blk = total_k_tile_cnt
|
| 1017 |
+
total_k_tile_cnt += cur_k_tile_cnt
|
| 1018 |
+
|
| 1019 |
+
# Peek (try_wait) AB buffer full for k_tile = 0
|
| 1020 |
+
mma_rd_k_tile = cutlass.Int32(0)
|
| 1021 |
+
smem_rd_buffer = (num_prev_k_blk + mma_rd_k_tile) % self.num_ab_stage
|
| 1022 |
+
need_check_rd_buffer_full = (
|
| 1023 |
+
mma_rd_k_tile < cur_k_tile_cnt and is_leader_cta
|
| 1024 |
+
)
|
| 1025 |
+
mma_rd_ab_full_phase = (
|
| 1026 |
+
(num_prev_k_blk + mma_rd_k_tile) // self.num_ab_stage % 2
|
| 1027 |
+
)
|
| 1028 |
+
peek_ab_full_status = cute.arch.mbarrier_conditional_try_wait(
|
| 1029 |
+
need_check_rd_buffer_full,
|
| 1030 |
+
ab_full_mbar_ptr + smem_rd_buffer,
|
| 1031 |
+
mma_rd_ab_full_phase,
|
| 1032 |
+
)
|
| 1033 |
+
|
| 1034 |
+
#
|
| 1035 |
+
# Wait for accumulator buffer empty
|
| 1036 |
+
#
|
| 1037 |
+
if is_leader_cta:
|
| 1038 |
+
acc_empty_phase = (
|
| 1039 |
+
tile_sched.num_tiles_executed // self.num_acc_stage % 2 ^ 1
|
| 1040 |
+
)
|
| 1041 |
+
cute.arch.mbarrier_wait(
|
| 1042 |
+
acc_empty_mbar_ptr + acc_buf_idx, acc_empty_phase
|
| 1043 |
+
)
|
| 1044 |
+
|
| 1045 |
+
#
|
| 1046 |
+
# Reset the ACCUMULATE field for each tile
|
| 1047 |
+
#
|
| 1048 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 1049 |
+
|
| 1050 |
+
#
|
| 1051 |
+
# Mma mainloop
|
| 1052 |
+
#
|
| 1053 |
+
for k_tile in range(cur_k_tile_cnt):
|
| 1054 |
+
mma_rd_k_tile_next = cutlass.Int32(k_tile + 1)
|
| 1055 |
+
smem_rd_buffer_next = (
|
| 1056 |
+
num_prev_k_blk + mma_rd_k_tile_next
|
| 1057 |
+
) % self.num_ab_stage
|
| 1058 |
+
mma_rd_ab_full_phase_next = (
|
| 1059 |
+
mma_rd_ab_full_phase ^ 1
|
| 1060 |
+
if smem_rd_buffer_next == 0
|
| 1061 |
+
else mma_rd_ab_full_phase
|
| 1062 |
+
)
|
| 1063 |
+
if is_leader_cta:
|
| 1064 |
+
# Wait for AB buffer full
|
| 1065 |
+
if peek_ab_full_status == 0:
|
| 1066 |
+
cute.arch.mbarrier_wait(
|
| 1067 |
+
ab_full_mbar_ptr + smem_rd_buffer, mma_rd_ab_full_phase
|
| 1068 |
+
)
|
| 1069 |
+
|
| 1070 |
+
# tCtAcc += tCrA * tCrB
|
| 1071 |
+
num_kblocks = cute.size(tCrA, mode=[2])
|
| 1072 |
+
for kblock_idx in cutlass.range(num_kblocks, unroll_full=True):
|
| 1073 |
+
kblock_coord = (None, None, kblock_idx, smem_rd_buffer)
|
| 1074 |
+
|
| 1075 |
+
cute.gemm(
|
| 1076 |
+
tiled_mma,
|
| 1077 |
+
tCtAcc,
|
| 1078 |
+
tCrA[kblock_coord],
|
| 1079 |
+
tCrB[kblock_coord],
|
| 1080 |
+
tCtAcc,
|
| 1081 |
+
)
|
| 1082 |
+
# Enable accumulate on tCtAcc after first kblock
|
| 1083 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1084 |
+
|
| 1085 |
+
# Async arrive AB buffer empty
|
| 1086 |
+
with cute.arch.elect_one():
|
| 1087 |
+
tcgen05.commit(
|
| 1088 |
+
ab_empty_mbar_ptr + smem_rd_buffer,
|
| 1089 |
+
ab_empty_mcast_mask,
|
| 1090 |
+
self.cta_group,
|
| 1091 |
+
)
|
| 1092 |
+
|
| 1093 |
+
# Peek (try_wait) AB buffer full for k_tile = k_tile + 1
|
| 1094 |
+
need_check_rd_buffer_full = (
|
| 1095 |
+
mma_rd_k_tile_next < cur_k_tile_cnt and is_leader_cta
|
| 1096 |
+
)
|
| 1097 |
+
|
| 1098 |
+
peek_ab_full_status = cute.arch.mbarrier_conditional_try_wait(
|
| 1099 |
+
need_check_rd_buffer_full,
|
| 1100 |
+
ab_full_mbar_ptr + smem_rd_buffer_next,
|
| 1101 |
+
mma_rd_ab_full_phase_next,
|
| 1102 |
+
)
|
| 1103 |
+
|
| 1104 |
+
mma_rd_k_tile = mma_rd_k_tile_next
|
| 1105 |
+
smem_rd_buffer = smem_rd_buffer_next
|
| 1106 |
+
mma_rd_ab_full_phase = mma_rd_ab_full_phase_next
|
| 1107 |
+
|
| 1108 |
+
#
|
| 1109 |
+
# Async arrive accumulator buffer full
|
| 1110 |
+
#
|
| 1111 |
+
if is_leader_cta:
|
| 1112 |
+
with cute.arch.elect_one():
|
| 1113 |
+
tcgen05.commit(
|
| 1114 |
+
acc_full_mbar_ptr + acc_buf_idx,
|
| 1115 |
+
acc_full_mcast_mask,
|
| 1116 |
+
self.cta_group,
|
| 1117 |
+
)
|
| 1118 |
+
|
| 1119 |
+
#
|
| 1120 |
+
# Advance to next tile
|
| 1121 |
+
#
|
| 1122 |
+
tile_sched.advance_to_next_work()
|
| 1123 |
+
work_tile = tile_sched.get_current_work()
|
| 1124 |
+
|
| 1125 |
+
#
|
| 1126 |
+
# Specialized epilogue warps
|
| 1127 |
+
#
|
| 1128 |
+
if warp_idx < self.mma_warp_id:
|
| 1129 |
+
# initialize tensorap for C
|
| 1130 |
+
tensormap_manager.init_tensormap_from_atom(
|
| 1131 |
+
tma_atom_c,
|
| 1132 |
+
tensormap_c_init_ptr,
|
| 1133 |
+
self.epilog_warp_id[0],
|
| 1134 |
+
)
|
| 1135 |
+
# Alloc tensor memory buffer
|
| 1136 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1137 |
+
cute.arch.alloc_tmem(
|
| 1138 |
+
self.num_tmem_alloc_cols,
|
| 1139 |
+
tmem_holding_buf,
|
| 1140 |
+
is_two_cta=use_2cta_instrs,
|
| 1141 |
+
)
|
| 1142 |
+
|
| 1143 |
+
#
|
| 1144 |
+
# Bar sync for retrieve tensor memory ptr from shared memory
|
| 1145 |
+
#
|
| 1146 |
+
tmem_ptr_read_threads = 32 * len((self.mma_warp_id, *self.epilog_warp_id))
|
| 1147 |
+
cute.arch.barrier(
|
| 1148 |
+
barrier_id=self.tmem_ptr_sync_bar_id,
|
| 1149 |
+
number_of_threads=tmem_ptr_read_threads,
|
| 1150 |
+
)
|
| 1151 |
+
|
| 1152 |
+
#
|
| 1153 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 1154 |
+
#
|
| 1155 |
+
tmem_ptr = cute.arch.retrieve_tmem_ptr(
|
| 1156 |
+
self.acc_dtype,
|
| 1157 |
+
alignment=16,
|
| 1158 |
+
ptr_to_buffer_holding_addr=tmem_holding_buf,
|
| 1159 |
+
)
|
| 1160 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 1161 |
+
tCtAcc_base = cute.make_tensor(tmem_ptr, tCtAcc_fake.layout)
|
| 1162 |
+
|
| 1163 |
+
epi_tidx = tidx
|
| 1164 |
+
#
|
| 1165 |
+
# Partition for epilogue
|
| 1166 |
+
#
|
| 1167 |
+
(
|
| 1168 |
+
tiled_copy_t2r,
|
| 1169 |
+
tTR_tAcc_base,
|
| 1170 |
+
tTR_rAcc,
|
| 1171 |
+
) = self.epilog_tmem_copy_and_partition(
|
| 1172 |
+
epi_tidx, tCtAcc_base, tCgC, epi_tile, use_2cta_instrs
|
| 1173 |
+
)
|
| 1174 |
+
|
| 1175 |
+
tTR_rC = cute.make_fragment(tTR_rAcc.shape, self.c_dtype)
|
| 1176 |
+
tiled_copy_r2s, tRS_rC, tRS_sC = self.epilog_smem_copy_and_partition(
|
| 1177 |
+
tiled_copy_t2r, tTR_rC, epi_tidx, sC
|
| 1178 |
+
)
|
| 1179 |
+
(
|
| 1180 |
+
tma_atom_c,
|
| 1181 |
+
bSG_sC,
|
| 1182 |
+
bSG_gC_partitioned,
|
| 1183 |
+
) = self.epilog_gmem_copy_and_partition(tma_atom_c, tCgC, epi_tile, sC)
|
| 1184 |
+
|
| 1185 |
+
#
|
| 1186 |
+
# Persistent tile scheduling loop
|
| 1187 |
+
#
|
| 1188 |
+
tile_sched = utils.StaticPersistentTileScheduler.create(
|
| 1189 |
+
tile_sched_params, bid, grid_dim
|
| 1190 |
+
)
|
| 1191 |
+
# grouped gemm tile scheduler helper will compute the group index for the tile we're working on
|
| 1192 |
+
group_gemm_ts_helper = utils.GroupedGemmTileSchedulerHelper(
|
| 1193 |
+
group_count,
|
| 1194 |
+
tile_sched_params,
|
| 1195 |
+
self.cluster_tile_shape_mnk,
|
| 1196 |
+
utils.create_initial_search_state(),
|
| 1197 |
+
)
|
| 1198 |
+
|
| 1199 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 1200 |
+
# wait tensormap initialization complete before update
|
| 1201 |
+
tensormap_manager.fence_tensormap_initialization()
|
| 1202 |
+
# tile count we have searched
|
| 1203 |
+
total_k_tile_cnt = cutlass.Int32(0)
|
| 1204 |
+
# group index of last tile
|
| 1205 |
+
last_group_idx = cutlass.Int32(-1)
|
| 1206 |
+
while work_tile.is_valid_tile:
|
| 1207 |
+
cur_tile_coord = work_tile.tile_idx
|
| 1208 |
+
grouped_gemm_cta_tile_info = group_gemm_ts_helper.delinearize_z(
|
| 1209 |
+
cur_tile_coord,
|
| 1210 |
+
problem_sizes_mnkl,
|
| 1211 |
+
)
|
| 1212 |
+
cur_group_idx = grouped_gemm_cta_tile_info.group_idx
|
| 1213 |
+
is_group_changed = cur_group_idx != last_group_idx
|
| 1214 |
+
if is_group_changed:
|
| 1215 |
+
# construct tensor C based on real address, shape and stride information
|
| 1216 |
+
real_tensor_c = self.make_tensor_for_tensormap_update(
|
| 1217 |
+
cur_group_idx,
|
| 1218 |
+
self.c_dtype,
|
| 1219 |
+
(
|
| 1220 |
+
grouped_gemm_cta_tile_info.problem_shape_m,
|
| 1221 |
+
grouped_gemm_cta_tile_info.problem_shape_n,
|
| 1222 |
+
grouped_gemm_cta_tile_info.problem_shape_k,
|
| 1223 |
+
),
|
| 1224 |
+
strides_abc,
|
| 1225 |
+
ptrs_abc,
|
| 1226 |
+
2, # 2 for tensor C
|
| 1227 |
+
)
|
| 1228 |
+
tensormap_manager.update_tensormap(
|
| 1229 |
+
((real_tensor_c),),
|
| 1230 |
+
((tma_atom_c),),
|
| 1231 |
+
((tensormap_c_ptr),),
|
| 1232 |
+
self.epilog_warp_id[0],
|
| 1233 |
+
(tensormap_c_smem_ptr,),
|
| 1234 |
+
)
|
| 1235 |
+
|
| 1236 |
+
mma_tile_coord_mnl = (
|
| 1237 |
+
grouped_gemm_cta_tile_info.cta_tile_idx_m
|
| 1238 |
+
// cute.size(tiled_mma.thr_id.shape),
|
| 1239 |
+
grouped_gemm_cta_tile_info.cta_tile_idx_n,
|
| 1240 |
+
0,
|
| 1241 |
+
)
|
| 1242 |
+
cur_k_tile_cnt = grouped_gemm_cta_tile_info.cta_tile_count_k
|
| 1243 |
+
total_k_tile_cnt += cur_k_tile_cnt
|
| 1244 |
+
|
| 1245 |
+
#
|
| 1246 |
+
# Slice to per mma tile index
|
| 1247 |
+
#
|
| 1248 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1249 |
+
bSG_gC = bSG_gC_partitioned[
|
| 1250 |
+
(
|
| 1251 |
+
None,
|
| 1252 |
+
None,
|
| 1253 |
+
None,
|
| 1254 |
+
*mma_tile_coord_mnl,
|
| 1255 |
+
)
|
| 1256 |
+
]
|
| 1257 |
+
|
| 1258 |
+
# Set tensor memory buffer for current tile
|
| 1259 |
+
acc_buf_idx = tile_sched.num_tiles_executed % self.num_acc_stage
|
| 1260 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M)
|
| 1261 |
+
tTR_tAcc = tTR_tAcc_base[(None, None, None, None, None, acc_buf_idx)]
|
| 1262 |
+
|
| 1263 |
+
#
|
| 1264 |
+
# Wait for accumulator buffer full
|
| 1265 |
+
#
|
| 1266 |
+
acc_full_phase = tile_sched.num_tiles_executed // self.num_acc_stage % 2
|
| 1267 |
+
cute.arch.mbarrier_wait(acc_full_mbar_ptr + acc_buf_idx, acc_full_phase)
|
| 1268 |
+
|
| 1269 |
+
tTR_tAcc = cute.group_modes(tTR_tAcc, 3, cute.rank(tTR_tAcc))
|
| 1270 |
+
bSG_gC = cute.group_modes(bSG_gC, 1, cute.rank(bSG_gC))
|
| 1271 |
+
# ensure the update to tensormap has completed before using it
|
| 1272 |
+
if is_group_changed:
|
| 1273 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1274 |
+
tensormap_manager.fence_tensormap_update(tensormap_c_ptr)
|
| 1275 |
+
#
|
| 1276 |
+
# Store accumulator to global memory in subtiles
|
| 1277 |
+
#
|
| 1278 |
+
subtile_cnt = cute.size(tTR_tAcc.shape, mode=[3])
|
| 1279 |
+
num_prev_subtiles = tile_sched.num_tiles_executed * subtile_cnt
|
| 1280 |
+
for subtile_idx in range(subtile_cnt):
|
| 1281 |
+
#
|
| 1282 |
+
# Load accumulator from tensor memory buffer to register
|
| 1283 |
+
#
|
| 1284 |
+
tTR_tAcc_mn = tTR_tAcc[(None, None, None, subtile_idx)]
|
| 1285 |
+
cute.copy(tiled_copy_t2r, tTR_tAcc_mn, tTR_rAcc)
|
| 1286 |
+
|
| 1287 |
+
#
|
| 1288 |
+
# Convert to output type
|
| 1289 |
+
#
|
| 1290 |
+
acc_vec = tiled_copy_r2s.retile(tTR_rAcc).load()
|
| 1291 |
+
tRS_rC.store(acc_vec.to(self.c_dtype))
|
| 1292 |
+
#
|
| 1293 |
+
# Store C to shared memory
|
| 1294 |
+
#
|
| 1295 |
+
epi_buffer = (num_prev_subtiles + subtile_idx) % self.num_epi_stage
|
| 1296 |
+
cute.copy(
|
| 1297 |
+
tiled_copy_r2s,
|
| 1298 |
+
tRS_rC,
|
| 1299 |
+
tRS_sC[(None, None, None, epi_buffer)],
|
| 1300 |
+
)
|
| 1301 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 1302 |
+
cute.arch.fence_proxy(
|
| 1303 |
+
cute.arch.ProxyKind.async_shared,
|
| 1304 |
+
space=cute.arch.SharedSpace.shared_cta,
|
| 1305 |
+
)
|
| 1306 |
+
epilog_threads = 32 * len(self.epilog_warp_id)
|
| 1307 |
+
cute.arch.barrier(
|
| 1308 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 1309 |
+
number_of_threads=epilog_threads,
|
| 1310 |
+
)
|
| 1311 |
+
#
|
| 1312 |
+
# store C to global memory with TMA
|
| 1313 |
+
#
|
| 1314 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1315 |
+
cute.copy(
|
| 1316 |
+
tma_atom_c,
|
| 1317 |
+
bSG_sC[(None, epi_buffer)],
|
| 1318 |
+
bSG_gC[(None, subtile_idx)],
|
| 1319 |
+
tma_desc_ptr=tensormap_manager.get_tensormap_ptr(
|
| 1320 |
+
tensormap_c_ptr,
|
| 1321 |
+
cute.AddressSpace.generic,
|
| 1322 |
+
),
|
| 1323 |
+
)
|
| 1324 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1325 |
+
cute.arch.cp_async_bulk_wait_group(
|
| 1326 |
+
self.num_epi_stage - 1, read=True
|
| 1327 |
+
)
|
| 1328 |
+
cute.arch.barrier(
|
| 1329 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 1330 |
+
number_of_threads=epilog_threads,
|
| 1331 |
+
)
|
| 1332 |
+
#
|
| 1333 |
+
# Async arrive accumulator buffer empty
|
| 1334 |
+
#
|
| 1335 |
+
with cute.arch.elect_one():
|
| 1336 |
+
cute.arch.mbarrier_arrive(
|
| 1337 |
+
acc_empty_mbar_ptr + acc_buf_idx,
|
| 1338 |
+
cta_rank_in_cluster // 2 * 2 if use_2cta_instrs else None,
|
| 1339 |
+
)
|
| 1340 |
+
|
| 1341 |
+
#
|
| 1342 |
+
# Advance to next tile
|
| 1343 |
+
#
|
| 1344 |
+
tile_sched.advance_to_next_work()
|
| 1345 |
+
work_tile = tile_sched.get_current_work()
|
| 1346 |
+
last_group_idx = cur_group_idx
|
| 1347 |
+
|
| 1348 |
+
#
|
| 1349 |
+
# Dealloc the tensor memory buffer
|
| 1350 |
+
#
|
| 1351 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1352 |
+
cute.arch.relinquish_tmem_alloc_permit(is_two_cta=use_2cta_instrs)
|
| 1353 |
+
epilog_threads = 32 * len(self.epilog_warp_id)
|
| 1354 |
+
cute.arch.barrier(
|
| 1355 |
+
barrier_id=self.epilog_sync_bar_id, number_of_threads=epilog_threads
|
| 1356 |
+
)
|
| 1357 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1358 |
+
if use_2cta_instrs:
|
| 1359 |
+
cute.arch.mbarrier_arrive(
|
| 1360 |
+
tmem_dealloc_mbar_ptr, cta_rank_in_cluster ^ 1
|
| 1361 |
+
)
|
| 1362 |
+
cute.arch.mbarrier_wait(tmem_dealloc_mbar_ptr, 0)
|
| 1363 |
+
cute.arch.dealloc_tmem(
|
| 1364 |
+
tmem_ptr, self.num_tmem_alloc_cols, is_two_cta=use_2cta_instrs
|
| 1365 |
+
)
|
| 1366 |
+
|
| 1367 |
+
#
|
| 1368 |
+
# Wait a/b buffer empty
|
| 1369 |
+
#
|
| 1370 |
+
if warp_idx == self.epilog_warp_id[0]:
|
| 1371 |
+
cute.arch.mbarrier_wait(
|
| 1372 |
+
(ab_empty_mbar_ptr + ((total_k_tile_cnt - 1) % self.num_ab_stage)),
|
| 1373 |
+
(((total_k_tile_cnt - 1) // self.num_ab_stage) % 2),
|
| 1374 |
+
)
|
| 1375 |
+
|
| 1376 |
+
@cute.jit
|
| 1377 |
+
def make_tensor_for_tensormap_update(
|
| 1378 |
+
self,
|
| 1379 |
+
group_idx: cutlass.Int32,
|
| 1380 |
+
dtype: Type[cutlass.Numeric],
|
| 1381 |
+
problem_shape_mnk: tuple[cutlass.Int32, cutlass.Int32, cutlass.Int32],
|
| 1382 |
+
strides_abc: cute.Tensor,
|
| 1383 |
+
tensor_address_abc: cute.Tensor,
|
| 1384 |
+
tensor_index: int,
|
| 1385 |
+
):
|
| 1386 |
+
"""Extract stride and tensor address for a given group and construct a global tensor.
|
| 1387 |
+
|
| 1388 |
+
This function is used within the kernel to dynamically create a CUTE tensor
|
| 1389 |
+
representing A, B, or C for the current group being processed, using the
|
| 1390 |
+
group-specific address, shape, and stride information.
|
| 1391 |
+
|
| 1392 |
+
:param group_idx: The index of the current group within the grouped GEMM.
|
| 1393 |
+
:type group_idx: cutlass.Int32
|
| 1394 |
+
:param dtype: The data type of the tensor elements (e.g., cutlass.Float16).
|
| 1395 |
+
:type dtype: Type[cutlass.Numeric]
|
| 1396 |
+
:param problem_shape_mnk: The (M, N, K) problem shape for the current group.
|
| 1397 |
+
:type problem_shape_mnk: tuple[cutlass.Int32, cutlass.Int32, cutlass.Int32]
|
| 1398 |
+
:param strides_abc: Tensor containing strides for A, B, C for all groups. Layout: (group_count, 3, 2).
|
| 1399 |
+
:type strides_abc: cute.Tensor
|
| 1400 |
+
:param tensor_address_abc: Tensor containing global memory addresses for A, B, C for all groups. Layout: (group_count, 3).
|
| 1401 |
+
:type tensor_address_abc: cute.Tensor
|
| 1402 |
+
:param tensor_index: Specifies which tensor to create: 0 for A, 1 for B, 2 for C.
|
| 1403 |
+
:type tensor_index: int
|
| 1404 |
+
:return: A CUTE tensor representing the requested global memory tensor (A, B, or C) for the specified group.
|
| 1405 |
+
:rtype: cute.Tensor
|
| 1406 |
+
:raises TypeError: If the provided dtype is not a subclass of cutlass.Numeric.
|
| 1407 |
+
"""
|
| 1408 |
+
ptr_i64 = tensor_address_abc[(group_idx, tensor_index)]
|
| 1409 |
+
if cutlass.const_expr(
|
| 1410 |
+
not isclass(dtype) or not issubclass(dtype, cutlass.Numeric)
|
| 1411 |
+
):
|
| 1412 |
+
raise TypeError(
|
| 1413 |
+
f"dtype must be a type of cutlass.Numeric, got {type(dtype)}"
|
| 1414 |
+
)
|
| 1415 |
+
tensor_gmem_ptr = cute.make_ptr(
|
| 1416 |
+
dtype, ptr_i64, cute.AddressSpace.gmem, assumed_align=16
|
| 1417 |
+
)
|
| 1418 |
+
|
| 1419 |
+
strides_tensor_gmem = strides_abc[(group_idx, tensor_index, None)]
|
| 1420 |
+
strides_tensor_reg = cute.make_fragment(
|
| 1421 |
+
cute.make_layout(2),
|
| 1422 |
+
strides_abc.element_type,
|
| 1423 |
+
)
|
| 1424 |
+
cute.autovec_copy(strides_tensor_gmem, strides_tensor_reg)
|
| 1425 |
+
stride_mn = strides_tensor_reg[0]
|
| 1426 |
+
stride_k = strides_tensor_reg[1]
|
| 1427 |
+
c1 = cutlass.Int32(1)
|
| 1428 |
+
c0 = cutlass.Int32(0)
|
| 1429 |
+
|
| 1430 |
+
if cutlass.const_expr(tensor_index == 0): # tensor A
|
| 1431 |
+
m = problem_shape_mnk[0]
|
| 1432 |
+
k = problem_shape_mnk[2]
|
| 1433 |
+
return cute.make_tensor(
|
| 1434 |
+
tensor_gmem_ptr,
|
| 1435 |
+
cute.make_layout((m, k, c1), stride=(stride_mn, stride_k, c0)),
|
| 1436 |
+
)
|
| 1437 |
+
elif cutlass.const_expr(tensor_index == 1): # tensor B
|
| 1438 |
+
n = problem_shape_mnk[1]
|
| 1439 |
+
k = problem_shape_mnk[2]
|
| 1440 |
+
return cute.make_tensor(
|
| 1441 |
+
tensor_gmem_ptr,
|
| 1442 |
+
cute.make_layout((n, k, c1), stride=(stride_mn, stride_k, c0)),
|
| 1443 |
+
)
|
| 1444 |
+
else: # tensor C
|
| 1445 |
+
m = problem_shape_mnk[0]
|
| 1446 |
+
n = problem_shape_mnk[1]
|
| 1447 |
+
return cute.make_tensor(
|
| 1448 |
+
tensor_gmem_ptr,
|
| 1449 |
+
cute.make_layout((m, n, c1), stride=(stride_mn, stride_k, c0)),
|
| 1450 |
+
)
|
| 1451 |
+
|
| 1452 |
+
def epilog_tmem_copy_and_partition(
|
| 1453 |
+
self,
|
| 1454 |
+
tidx: cutlass.Int32,
|
| 1455 |
+
tAcc: cute.Tensor,
|
| 1456 |
+
gC_mnl: cute.Tensor,
|
| 1457 |
+
epi_tile: cute.Tile,
|
| 1458 |
+
use_2cta_instrs: Union[cutlass.Boolean, bool],
|
| 1459 |
+
) -> tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1460 |
+
"""
|
| 1461 |
+
Make tiledCopy for tensor memory load, then use it to partition tensor memory (source) and register array (destination).
|
| 1462 |
+
|
| 1463 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1464 |
+
:type tidx: cutlass.Int32
|
| 1465 |
+
:param tAcc: The accumulator tensor to be copied and partitioned
|
| 1466 |
+
:type tAcc: cute.Tensor
|
| 1467 |
+
:param gC_mnl: The global tensor C
|
| 1468 |
+
:type gC_mnl: cute.Tensor
|
| 1469 |
+
:param epi_tile: The epilogue tiler
|
| 1470 |
+
:type epi_tile: cute.Tile
|
| 1471 |
+
:param use_2cta_instrs: Whether use_2cta_instrs is enabled
|
| 1472 |
+
:type use_2cta_instrs: bool
|
| 1473 |
+
|
| 1474 |
+
:return: A tuple containing (tiled_copy_t2r, tTR_tAcc, tTR_rAcc) where:
|
| 1475 |
+
- tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1476 |
+
- tTR_tAcc: The partitioned accumulator tensor
|
| 1477 |
+
- tTR_rAcc: The accumulated tensor in register used to hold t2r results
|
| 1478 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1479 |
+
"""
|
| 1480 |
+
# Make tiledCopy for tensor memory load(t2r)
|
| 1481 |
+
copy_atom_t2r = sm100_utils.get_tmem_load_op(
|
| 1482 |
+
self.cta_tile_shape_mnk,
|
| 1483 |
+
self.c_layout,
|
| 1484 |
+
self.c_dtype,
|
| 1485 |
+
self.acc_dtype,
|
| 1486 |
+
epi_tile,
|
| 1487 |
+
use_2cta_instrs,
|
| 1488 |
+
)
|
| 1489 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, STAGE)
|
| 1490 |
+
tAcc_epi = cute.flat_divide(
|
| 1491 |
+
tAcc[((None, None), 0, 0, None)],
|
| 1492 |
+
epi_tile,
|
| 1493 |
+
)
|
| 1494 |
+
# (EPI_TILE_M, EPI_TILE_N)
|
| 1495 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(
|
| 1496 |
+
copy_atom_t2r, tAcc_epi[(None, None, 0, 0, 0)]
|
| 1497 |
+
)
|
| 1498 |
+
|
| 1499 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1500 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M, STAGE)
|
| 1501 |
+
tTR_tAcc = thr_copy_t2r.partition_S(tAcc_epi)
|
| 1502 |
+
|
| 1503 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1504 |
+
gC_mnl_epi = cute.flat_divide(
|
| 1505 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1506 |
+
)
|
| 1507 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1508 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_mnl_epi)
|
| 1509 |
+
# (T2R, T2R_M, T2R_N)
|
| 1510 |
+
tTR_rAcc = cute.make_fragment(
|
| 1511 |
+
tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape, self.acc_dtype
|
| 1512 |
+
)
|
| 1513 |
+
return tiled_copy_t2r, tTR_tAcc, tTR_rAcc
|
| 1514 |
+
|
| 1515 |
+
def epilog_smem_copy_and_partition(
|
| 1516 |
+
self,
|
| 1517 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 1518 |
+
tTR_rC: cute.Tensor,
|
| 1519 |
+
tidx: cutlass.Int32,
|
| 1520 |
+
sC: cute.Tensor,
|
| 1521 |
+
) -> tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1522 |
+
"""
|
| 1523 |
+
Make tiledCopy for shared memory store, then use it to partition register array (source) and shared memory (destination).
|
| 1524 |
+
|
| 1525 |
+
:param tiled_copy_t2r: The tiled copy operation for tmem to register copy(t2r)
|
| 1526 |
+
:type tiled_copy_t2r: cute.TiledCopy
|
| 1527 |
+
:param tTR_rC: The partitioned accumulator tensor
|
| 1528 |
+
:type tTR_rC: cute.Tensor
|
| 1529 |
+
:param tidx: The thread index in epilogue warp groups
|
| 1530 |
+
:type tidx: cutlass.Int32
|
| 1531 |
+
:param sC: The shared memory tensor to be copied and partitioned
|
| 1532 |
+
:type sC: cute.Tensor
|
| 1533 |
+
|
| 1534 |
+
:return: A tuple containing (tiled_copy_r2s, tRS_rC, tRS_sC) where:
|
| 1535 |
+
- tiled_copy_r2s: The tiled copy operation for register to smem copy(r2s)
|
| 1536 |
+
- tRS_rC: The partitioned tensor C (register source)
|
| 1537 |
+
- tRS_sC: The partitioned tensor C (smem destination)
|
| 1538 |
+
:rtype: Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]
|
| 1539 |
+
"""
|
| 1540 |
+
copy_atom_r2s = sm100_utils.get_smem_store_op(
|
| 1541 |
+
self.c_layout, self.c_dtype, self.acc_dtype, tiled_copy_t2r
|
| 1542 |
+
)
|
| 1543 |
+
tiled_copy_r2s = cute.make_tiled_copy_D(copy_atom_r2s, tiled_copy_t2r)
|
| 1544 |
+
# (R2S, R2S_M, R2S_N, PIPE_D)
|
| 1545 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx)
|
| 1546 |
+
tRS_sC = thr_copy_r2s.partition_D(sC)
|
| 1547 |
+
# (R2S, R2S_M, R2S_N)
|
| 1548 |
+
tRS_rC = tiled_copy_r2s.retile(tTR_rC)
|
| 1549 |
+
return tiled_copy_r2s, tRS_rC, tRS_sC
|
| 1550 |
+
|
| 1551 |
+
def epilog_gmem_copy_and_partition(
|
| 1552 |
+
self,
|
| 1553 |
+
tma_atom_c: cute.CopyAtom,
|
| 1554 |
+
gC_mnl: cute.Tensor,
|
| 1555 |
+
epi_tile: cute.Tile,
|
| 1556 |
+
sC: cute.Tensor,
|
| 1557 |
+
) -> tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]:
|
| 1558 |
+
"""Make tiledCopy for global memory store, then use it to partition
|
| 1559 |
+
shared memory (source) and global memory (destination) for TMA store version.
|
| 1560 |
+
|
| 1561 |
+
:param tma_atom_c: The TMA copy atom configured for storing tensor C.
|
| 1562 |
+
:type tma_atom_c: cute.CopyAtom
|
| 1563 |
+
:param gC_mnl: The global memory tensor C.
|
| 1564 |
+
:type gC_mnl: cute.Tensor
|
| 1565 |
+
:param epi_tile: The epilogue tiler defining the granularity of the operation.
|
| 1566 |
+
:type epi_tile: cute.Tile
|
| 1567 |
+
:param sC: The shared memory epilogue buffer tensor.
|
| 1568 |
+
:type sC: cute.Tensor
|
| 1569 |
+
:return: A tuple containing:
|
| 1570 |
+
- tma_atom_c: The input TMA copy atom (passed through).
|
| 1571 |
+
- bSG_sC: The source shared memory tensor partitioned for the TMA operation.
|
| 1572 |
+
- tCgC: The destination global memory tensor partitioned for the TMA operation.
|
| 1573 |
+
:rtype: tuple[cute.CopyAtom, cute.Tensor, cute.Tensor]
|
| 1574 |
+
"""
|
| 1575 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1576 |
+
gC_epi = cute.flat_divide(
|
| 1577 |
+
gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile
|
| 1578 |
+
)
|
| 1579 |
+
sC_for_tma_partition = cute.group_modes(sC, 0, 2)
|
| 1580 |
+
gC_for_tma_partition = cute.group_modes(gC_epi, 0, 2)
|
| 1581 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N)
|
| 1582 |
+
# ((ATOM_V, REST_V), EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1583 |
+
bSG_sC, bSG_gC = cpasync.tma_partition(
|
| 1584 |
+
tma_atom_c,
|
| 1585 |
+
0,
|
| 1586 |
+
cute.make_layout(1),
|
| 1587 |
+
sC_for_tma_partition,
|
| 1588 |
+
gC_for_tma_partition,
|
| 1589 |
+
)
|
| 1590 |
+
return tma_atom_c, bSG_sC, bSG_gC
|
| 1591 |
+
|
| 1592 |
+
@staticmethod
|
| 1593 |
+
def _compute_stages(
|
| 1594 |
+
tiled_mma: cute.TiledMma,
|
| 1595 |
+
mma_tiler_mnk: tuple[int, int, int],
|
| 1596 |
+
a_dtype: type[cutlass.Numeric],
|
| 1597 |
+
b_dtype: type[cutlass.Numeric],
|
| 1598 |
+
epi_tile: cute.Tile,
|
| 1599 |
+
c_dtype: type[cutlass.Numeric],
|
| 1600 |
+
c_layout: utils.LayoutEnum,
|
| 1601 |
+
smem_capacity: int,
|
| 1602 |
+
occupancy: int,
|
| 1603 |
+
) -> tuple[int, int, int]:
|
| 1604 |
+
"""Computes the number of stages for accumulator, A/B operands, and epilogue based on heuristics.
|
| 1605 |
+
|
| 1606 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1607 |
+
:type tiled_mma: cute.TiledMma
|
| 1608 |
+
:param mma_tiler_mnk: The shape (M, N, K) of the MMA tiler.
|
| 1609 |
+
:type mma_tiler_mnk: tuple[int, int, int]
|
| 1610 |
+
:param a_dtype: Data type of operand A.
|
| 1611 |
+
:type a_dtype: type[cutlass.Numeric]
|
| 1612 |
+
:param b_dtype: Data type of operand B.
|
| 1613 |
+
:type b_dtype: type[cutlass.Numeric]
|
| 1614 |
+
:param epi_tile: The epilogue tile shape.
|
| 1615 |
+
:type epi_tile: cute.Tile
|
| 1616 |
+
:param c_dtype: Data type of operand C (output).
|
| 1617 |
+
:type c_dtype: type[cutlass.Numeric]
|
| 1618 |
+
:param c_layout: Layout enum of operand C in global memory.
|
| 1619 |
+
:type c_layout: utils.LayoutEnum
|
| 1620 |
+
:param smem_capacity: Total available shared memory capacity in bytes.
|
| 1621 |
+
:type smem_capacity: int
|
| 1622 |
+
:param occupancy: Target number of CTAs per SM (occupancy).
|
| 1623 |
+
:type occupancy: int
|
| 1624 |
+
|
| 1625 |
+
:return: A tuple containing the computed number of stages for:
|
| 1626 |
+
(accumulator stages, A/B operand stages, epilogue stages)
|
| 1627 |
+
:rtype: tuple[int, int, int]
|
| 1628 |
+
"""
|
| 1629 |
+
# Default accumulator and epilogue stages
|
| 1630 |
+
num_acc_stage = 2
|
| 1631 |
+
num_epi_stage = 2
|
| 1632 |
+
|
| 1633 |
+
# Calculate smem layout and size for one stage of A, B, and Epilogue
|
| 1634 |
+
a_smem_layout_stage_one = sm100_utils.make_smem_layout_a(
|
| 1635 |
+
tiled_mma,
|
| 1636 |
+
mma_tiler_mnk,
|
| 1637 |
+
a_dtype,
|
| 1638 |
+
1, # stage=1
|
| 1639 |
+
)
|
| 1640 |
+
b_smem_layout_staged_one = sm100_utils.make_smem_layout_b(
|
| 1641 |
+
tiled_mma,
|
| 1642 |
+
mma_tiler_mnk,
|
| 1643 |
+
b_dtype,
|
| 1644 |
+
1, # stage=1
|
| 1645 |
+
)
|
| 1646 |
+
epi_smem_layout_staged_one = sm100_utils.make_smem_layout_epi(
|
| 1647 |
+
c_dtype,
|
| 1648 |
+
c_layout,
|
| 1649 |
+
epi_tile,
|
| 1650 |
+
1, # stage=1
|
| 1651 |
+
)
|
| 1652 |
+
ab_bytes_per_stage = cute.size_in_bytes(
|
| 1653 |
+
a_dtype, a_smem_layout_stage_one
|
| 1654 |
+
) + cute.size_in_bytes(b_dtype, b_smem_layout_staged_one)
|
| 1655 |
+
|
| 1656 |
+
epi_bytes_per_stage = cute.size_in_bytes(c_dtype, epi_smem_layout_staged_one)
|
| 1657 |
+
epi_bytes = epi_bytes_per_stage * num_epi_stage
|
| 1658 |
+
|
| 1659 |
+
# Calculate A/B stages:
|
| 1660 |
+
# Start with total smem per CTA (capacity / occupancy)
|
| 1661 |
+
# Subtract reserved bytes and initial epilogue bytes
|
| 1662 |
+
# Divide remaining by bytes needed per A/B stage
|
| 1663 |
+
num_ab_stage = (
|
| 1664 |
+
smem_capacity // occupancy
|
| 1665 |
+
- GroupedGemmKernel.reserved_smem_bytes
|
| 1666 |
+
- epi_bytes
|
| 1667 |
+
) // ab_bytes_per_stage
|
| 1668 |
+
|
| 1669 |
+
# Refine epilogue stages:
|
| 1670 |
+
# Calculate remaining smem after allocating for A/B stages and reserved bytes
|
| 1671 |
+
# Add remaining unused smem to epilogue
|
| 1672 |
+
remaining_smem = (
|
| 1673 |
+
smem_capacity
|
| 1674 |
+
- occupancy * ab_bytes_per_stage * num_ab_stage
|
| 1675 |
+
- occupancy * (GroupedGemmKernel.reserved_smem_bytes + epi_bytes)
|
| 1676 |
+
)
|
| 1677 |
+
num_epi_stage += remaining_smem // (occupancy * epi_bytes_per_stage)
|
| 1678 |
+
return num_acc_stage, num_ab_stage, num_epi_stage
|
| 1679 |
+
|
| 1680 |
+
@staticmethod
|
| 1681 |
+
def _compute_grid(
|
| 1682 |
+
total_num_clusters: int,
|
| 1683 |
+
cluster_shape_mn: tuple[int, int],
|
| 1684 |
+
max_active_clusters: cutlass.Constexpr[int],
|
| 1685 |
+
) -> tuple[utils.PersistentTileSchedulerParams, tuple[int, int, int]]:
|
| 1686 |
+
"""Compute tile scheduler parameters and grid shape for grouped GEMM operations.
|
| 1687 |
+
|
| 1688 |
+
:param total_num_clusters: Total number of clusters to process across all groups.
|
| 1689 |
+
:type total_num_clusters: int
|
| 1690 |
+
:param cluster_shape_mn: Shape of each cluster in M, N dimensions.
|
| 1691 |
+
:type cluster_shape_mn: tuple[int, int]
|
| 1692 |
+
:param max_active_clusters: Maximum number of active clusters.
|
| 1693 |
+
:type max_active_clusters: cutlass.Constexpr[int]
|
| 1694 |
+
|
| 1695 |
+
:return: A tuple containing:
|
| 1696 |
+
- tile_sched_params: Parameters for the persistent tile scheduler.
|
| 1697 |
+
- grid: Grid shape for kernel launch.
|
| 1698 |
+
:rtype: tuple[utils.PersistentTileSchedulerParams, tuple[int, ...]]
|
| 1699 |
+
"""
|
| 1700 |
+
# Create problem shape with M, N dimensions from cluster shape
|
| 1701 |
+
# and L dimension representing the total number of clusters.
|
| 1702 |
+
problem_shape_ntile_mnl = (
|
| 1703 |
+
cluster_shape_mn[0],
|
| 1704 |
+
cluster_shape_mn[1],
|
| 1705 |
+
cutlass.Int32(total_num_clusters),
|
| 1706 |
+
)
|
| 1707 |
+
|
| 1708 |
+
tile_sched_params = utils.PersistentTileSchedulerParams(
|
| 1709 |
+
problem_shape_ntile_mnl, (*cluster_shape_mn, 1)
|
| 1710 |
+
)
|
| 1711 |
+
|
| 1712 |
+
grid = utils.StaticPersistentTileScheduler.get_grid_shape(
|
| 1713 |
+
tile_sched_params, max_active_clusters
|
| 1714 |
+
)
|
| 1715 |
+
|
| 1716 |
+
return tile_sched_params, grid
|
| 1717 |
+
|
| 1718 |
+
@staticmethod
|
| 1719 |
+
def _get_mbar_smem_bytes(**kwargs_stages: int) -> int:
|
| 1720 |
+
"""Calculate shared memory consumption for memory barriers based on provided stages.
|
| 1721 |
+
|
| 1722 |
+
Each stage requires 2 barriers, and each barrier consumes 8 bytes of shared memory.
|
| 1723 |
+
The total consumption is the sum across all provided stages. This function calculates the total
|
| 1724 |
+
shared memory needed for these barriers.
|
| 1725 |
+
|
| 1726 |
+
:param kwargs_stages: Variable keyword arguments where each key is a stage name
|
| 1727 |
+
(e.g., num_acc_stage, num_ab_stage) and each value is the
|
| 1728 |
+
number of stages of that type.
|
| 1729 |
+
:type kwargs_stages: int
|
| 1730 |
+
:return: Total shared memory bytes required for all memory barriers.
|
| 1731 |
+
:rtype: int
|
| 1732 |
+
"""
|
| 1733 |
+
num_barriers_per_stage = 2
|
| 1734 |
+
num_bytes_per_barrier = 8
|
| 1735 |
+
mbar_smem_consumption = sum(
|
| 1736 |
+
[
|
| 1737 |
+
num_barriers_per_stage * num_bytes_per_barrier * stage
|
| 1738 |
+
for stage in kwargs_stages.values()
|
| 1739 |
+
]
|
| 1740 |
+
)
|
| 1741 |
+
return mbar_smem_consumption
|
| 1742 |
+
|
| 1743 |
+
@staticmethod
|
| 1744 |
+
def _get_tensormap_smem_bytes(
|
| 1745 |
+
tensormap_update_mode: utils.TensorMapUpdateMode,
|
| 1746 |
+
) -> int:
|
| 1747 |
+
"""Get the SMEM consumption for the tensormap buffer based on the update mode.
|
| 1748 |
+
|
| 1749 |
+
:param tensormap_update_mode: Specifies whether tensormaps are updated in GMEM or SMEM.
|
| 1750 |
+
:type tensormap_update_mode: utils.TensorMapUpdateMode
|
| 1751 |
+
:return: The shared memory bytes required for the tensormap buffer. Returns 0 if mode is GMEM.
|
| 1752 |
+
:rtype: int
|
| 1753 |
+
:raises ValueError: If an invalid tensormap update mode is provided.
|
| 1754 |
+
"""
|
| 1755 |
+
if tensormap_update_mode == utils.TensorMapUpdateMode.GMEM:
|
| 1756 |
+
return 0
|
| 1757 |
+
elif tensormap_update_mode == utils.TensorMapUpdateMode.SMEM:
|
| 1758 |
+
return (
|
| 1759 |
+
GroupedGemmKernel.bytes_per_tensormap * GroupedGemmKernel.num_tensormaps
|
| 1760 |
+
)
|
| 1761 |
+
else:
|
| 1762 |
+
raise ValueError(f"Invalid tensormap update mode: {tensormap_update_mode}")
|
| 1763 |
+
|
| 1764 |
+
@staticmethod
|
| 1765 |
+
def _get_tensor_smem_bytes(
|
| 1766 |
+
a_smem_layout_staged: cute.Layout,
|
| 1767 |
+
a_dtype: Type[cutlass.Numeric],
|
| 1768 |
+
b_smem_layout_staged: cute.Layout,
|
| 1769 |
+
b_dtype: Type[cutlass.Numeric],
|
| 1770 |
+
epi_smem_layout_staged: cute.Layout,
|
| 1771 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1772 |
+
) -> int:
|
| 1773 |
+
"""Compute the total SMEM consumption for tensor A, B and C."""
|
| 1774 |
+
ab_bytes = cute.size_in_bytes(
|
| 1775 |
+
a_dtype, a_smem_layout_staged
|
| 1776 |
+
) + cute.size_in_bytes(b_dtype, b_smem_layout_staged)
|
| 1777 |
+
|
| 1778 |
+
epi_bytes = cute.size_in_bytes(c_dtype, epi_smem_layout_staged)
|
| 1779 |
+
return ab_bytes + epi_bytes
|
| 1780 |
+
|
| 1781 |
+
@staticmethod
|
| 1782 |
+
def _compute_num_tmem_alloc_cols(
|
| 1783 |
+
tiled_mma: cute.TiledMma,
|
| 1784 |
+
mma_tiler: tuple[int, int, int],
|
| 1785 |
+
num_acc_stage: int,
|
| 1786 |
+
) -> int:
|
| 1787 |
+
"""
|
| 1788 |
+
Compute the number of tensor memory allocation columns.
|
| 1789 |
+
|
| 1790 |
+
:param tiled_mma: The tiled MMA object defining the core computation.
|
| 1791 |
+
:type tiled_mma: cute.TiledMma
|
| 1792 |
+
:param mma_tiler: The shape (M, N, K) of the MMA tile.
|
| 1793 |
+
:type mma_tiler: tuple[int, int, int]
|
| 1794 |
+
:param acc_stage: The stage of the accumulator tensor.
|
| 1795 |
+
:type acc_stage: int
|
| 1796 |
+
|
| 1797 |
+
:return: The number of tensor memory allocation columns.
|
| 1798 |
+
:rtype: int
|
| 1799 |
+
"""
|
| 1800 |
+
acc_shape = tiled_mma.partition_shape_C(mma_tiler[:2])
|
| 1801 |
+
tCtAcc_fake = tiled_mma.make_fragment_C(cute.append(acc_shape, num_acc_stage))
|
| 1802 |
+
num_tmem_alloc_cols = utils.get_num_tmem_alloc_cols(tCtAcc_fake)
|
| 1803 |
+
|
| 1804 |
+
return num_tmem_alloc_cols
|
| 1805 |
+
|
| 1806 |
+
# Size of smem we reserved for mbarrier, tensor memory management and tensormap update
|
| 1807 |
+
reserved_smem_bytes = 1024
|
| 1808 |
+
bytes_per_tensormap = 128
|
| 1809 |
+
num_tensormaps = 3
|
| 1810 |
+
# size of smem used for tensor memory management
|
| 1811 |
+
tensor_memory_management_bytes = 12
|
| 1812 |
+
|
| 1813 |
+
|
| 1814 |
+
# Create tensor and return the pointer, tensor, and stride
|
| 1815 |
+
def create_tensor_and_stride(
|
| 1816 |
+
l: int,
|
| 1817 |
+
mode0: int,
|
| 1818 |
+
mode1: int,
|
| 1819 |
+
is_mode0_major: bool,
|
| 1820 |
+
dtype: type[cutlass.Numeric],
|
| 1821 |
+
is_dynamic_layout: bool = True,
|
| 1822 |
+
torch_tensor_cpu: torch.Tensor = None,
|
| 1823 |
+
) -> tuple[int, torch.Tensor, cute.Tensor, torch.Tensor, tuple[int, int]]:
|
| 1824 |
+
"""Create a GPU tensor from scratch or based on an existing CPU tensor.
|
| 1825 |
+
|
| 1826 |
+
:param torch_tensor_cpu: Optional existing CPU tensor to reuse. If None, creates a new one.
|
| 1827 |
+
:type torch_tensor_cpu: torch.Tensor, optional
|
| 1828 |
+
"""
|
| 1829 |
+
if torch_tensor_cpu is None:
|
| 1830 |
+
# Create new CPU tensor
|
| 1831 |
+
torch_tensor_cpu = cutlass_torch.matrix(l, mode0, mode1, is_mode0_major, dtype)
|
| 1832 |
+
|
| 1833 |
+
# Create GPU tensor from CPU tensor (new or existing)
|
| 1834 |
+
cute_tensor, torch_tensor = cutlass_torch.cute_tensor_like(
|
| 1835 |
+
torch_tensor_cpu, dtype, is_dynamic_layout, assumed_align=16
|
| 1836 |
+
)
|
| 1837 |
+
return (
|
| 1838 |
+
torch_tensor.data_ptr(),
|
| 1839 |
+
torch_tensor,
|
| 1840 |
+
cute_tensor,
|
| 1841 |
+
torch_tensor_cpu,
|
| 1842 |
+
torch_tensor.stride()[:-1],
|
| 1843 |
+
)
|
| 1844 |
+
|
| 1845 |
+
|
| 1846 |
+
def create_tensors_for_all_groups(
|
| 1847 |
+
problem_sizes_mnkl: List[tuple[int, int, int, int]],
|
| 1848 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1849 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1850 |
+
a_major: str,
|
| 1851 |
+
b_major: str,
|
| 1852 |
+
c_major: str,
|
| 1853 |
+
torch_fp32_tensors_abc: List[List[torch.Tensor]] = None,
|
| 1854 |
+
) -> tuple[
|
| 1855 |
+
List[List[int]],
|
| 1856 |
+
List[List[torch.Tensor]],
|
| 1857 |
+
List[tuple],
|
| 1858 |
+
List[List[tuple]],
|
| 1859 |
+
List[List[torch.Tensor]],
|
| 1860 |
+
]:
|
| 1861 |
+
if torch_fp32_tensors_abc is not None and len(torch_fp32_tensors_abc) != len(
|
| 1862 |
+
problem_sizes_mnkl
|
| 1863 |
+
):
|
| 1864 |
+
raise ValueError("torch_fp32_tensors_abc must have one entry per group")
|
| 1865 |
+
|
| 1866 |
+
# Initialize lists to store tensors for all groups
|
| 1867 |
+
new_torch_fp32_tensors_abc = (
|
| 1868 |
+
[] if torch_fp32_tensors_abc is None else torch_fp32_tensors_abc
|
| 1869 |
+
)
|
| 1870 |
+
torch_tensors_abc = []
|
| 1871 |
+
cute_tensors_abc = []
|
| 1872 |
+
strides_abc = []
|
| 1873 |
+
ptrs_abc = []
|
| 1874 |
+
|
| 1875 |
+
# Iterate through all groups and create tensors for each group
|
| 1876 |
+
for group_idx, (m, n, k, l) in enumerate(problem_sizes_mnkl):
|
| 1877 |
+
# Get existing CPU tensors if available, otherwise None
|
| 1878 |
+
existing_cpu_a = (
|
| 1879 |
+
torch_fp32_tensors_abc[group_idx][0] if torch_fp32_tensors_abc else None
|
| 1880 |
+
)
|
| 1881 |
+
existing_cpu_b = (
|
| 1882 |
+
torch_fp32_tensors_abc[group_idx][1] if torch_fp32_tensors_abc else None
|
| 1883 |
+
)
|
| 1884 |
+
existing_cpu_c = (
|
| 1885 |
+
torch_fp32_tensors_abc[group_idx][2] if torch_fp32_tensors_abc else None
|
| 1886 |
+
)
|
| 1887 |
+
|
| 1888 |
+
# Create tensors (reusing CPU tensors if provided)
|
| 1889 |
+
(
|
| 1890 |
+
ptr_a,
|
| 1891 |
+
torch_tensor_a,
|
| 1892 |
+
cute_tensor_a,
|
| 1893 |
+
tensor_fp32_a,
|
| 1894 |
+
stride_mk_a,
|
| 1895 |
+
) = create_tensor_and_stride(
|
| 1896 |
+
l, m, k, a_major == "m", ab_dtype, torch_tensor_cpu=existing_cpu_a
|
| 1897 |
+
)
|
| 1898 |
+
(
|
| 1899 |
+
ptr_b,
|
| 1900 |
+
torch_tensor_b,
|
| 1901 |
+
cute_tensor_b,
|
| 1902 |
+
tensor_fp32_b,
|
| 1903 |
+
stride_nk_b,
|
| 1904 |
+
) = create_tensor_and_stride(
|
| 1905 |
+
l, n, k, b_major == "n", ab_dtype, torch_tensor_cpu=existing_cpu_b
|
| 1906 |
+
)
|
| 1907 |
+
(
|
| 1908 |
+
ptr_c,
|
| 1909 |
+
torch_tensor_c,
|
| 1910 |
+
cute_tensor_c,
|
| 1911 |
+
tensor_fp32_c,
|
| 1912 |
+
stride_mn_c,
|
| 1913 |
+
) = create_tensor_and_stride(
|
| 1914 |
+
l, m, n, c_major == "m", c_dtype, torch_tensor_cpu=existing_cpu_c
|
| 1915 |
+
)
|
| 1916 |
+
|
| 1917 |
+
# Only append to new_torch_fp32_tensors_abc if we created new CPU tensors
|
| 1918 |
+
if torch_fp32_tensors_abc is None:
|
| 1919 |
+
new_torch_fp32_tensors_abc.append(
|
| 1920 |
+
[tensor_fp32_a, tensor_fp32_b, tensor_fp32_c]
|
| 1921 |
+
)
|
| 1922 |
+
|
| 1923 |
+
ptrs_abc.append([ptr_a, ptr_b, ptr_c])
|
| 1924 |
+
torch_tensors_abc.append([torch_tensor_a, torch_tensor_b, torch_tensor_c])
|
| 1925 |
+
strides_abc.append([stride_mk_a, stride_nk_b, stride_mn_c])
|
| 1926 |
+
cute_tensors_abc.append(
|
| 1927 |
+
(
|
| 1928 |
+
cute_tensor_a,
|
| 1929 |
+
cute_tensor_b,
|
| 1930 |
+
cute_tensor_c,
|
| 1931 |
+
)
|
| 1932 |
+
)
|
| 1933 |
+
|
| 1934 |
+
return (
|
| 1935 |
+
ptrs_abc,
|
| 1936 |
+
torch_tensors_abc,
|
| 1937 |
+
cute_tensors_abc,
|
| 1938 |
+
strides_abc,
|
| 1939 |
+
new_torch_fp32_tensors_abc,
|
| 1940 |
+
)
|
| 1941 |
+
|
| 1942 |
+
|
| 1943 |
+
def run(
|
| 1944 |
+
num_groups: int,
|
| 1945 |
+
problem_sizes_mnkl: tuple[int, int, int, int],
|
| 1946 |
+
ab_dtype: Type[cutlass.Numeric],
|
| 1947 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1948 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 1949 |
+
a_major: str,
|
| 1950 |
+
b_major: str,
|
| 1951 |
+
c_major: str,
|
| 1952 |
+
mma_tiler_mn: tuple[int, int],
|
| 1953 |
+
cluster_shape_mn: tuple[int, int],
|
| 1954 |
+
use_2cta_instrs: bool,
|
| 1955 |
+
tensormap_update_mode: utils.TensorMapUpdateMode,
|
| 1956 |
+
tolerance: float,
|
| 1957 |
+
warmup_iterations: int,
|
| 1958 |
+
iterations: int,
|
| 1959 |
+
skip_ref_check: bool,
|
| 1960 |
+
use_cold_l2: bool = False,
|
| 1961 |
+
**kwargs,
|
| 1962 |
+
):
|
| 1963 |
+
"""Run grouped GEMM example with specified configurations.
|
| 1964 |
+
|
| 1965 |
+
:param use_cold_l2: Whether to use circular buffer strategy to ensure cold L2 cache, defaults to False
|
| 1966 |
+
:type use_cold_l2: bool, optional
|
| 1967 |
+
:return: Execution time of the GEMM kernel in microseconds
|
| 1968 |
+
:rtype: float
|
| 1969 |
+
"""
|
| 1970 |
+
print(f"Running Blackwell Grouped GEMM test with:")
|
| 1971 |
+
print(f"{num_groups} groups")
|
| 1972 |
+
for i, (m, n, k, l) in enumerate(problem_sizes_mnkl):
|
| 1973 |
+
print(f"Group {i}: {m}x{n}x{k}x{l}")
|
| 1974 |
+
print(f"AB dtype: {ab_dtype}, C dtype: {c_dtype}, Acc dtype: {acc_dtype}")
|
| 1975 |
+
print(f"Matrix majors - A: {a_major}, B: {b_major}, C: {c_major}")
|
| 1976 |
+
print(f"Mma Tiler (M, N): {mma_tiler_mn}, Cluster Shape (M, N): {cluster_shape_mn}")
|
| 1977 |
+
print(f"2CTA MMA instructions: {'True' if use_2cta_instrs else 'False'}")
|
| 1978 |
+
print(f"Tensor map update mode: {tensormap_update_mode}")
|
| 1979 |
+
print(f"Tolerance: {tolerance}")
|
| 1980 |
+
print(f"Warmup iterations: {warmup_iterations}")
|
| 1981 |
+
print(f"Iterations: {iterations}")
|
| 1982 |
+
print(f"Skip reference checking: {skip_ref_check}")
|
| 1983 |
+
print(f"Use cold L2: {'True' if use_cold_l2 else 'False'}")
|
| 1984 |
+
|
| 1985 |
+
# Skip unsupported types
|
| 1986 |
+
if ab_dtype not in {
|
| 1987 |
+
cutlass.Float16,
|
| 1988 |
+
cutlass.BFloat16,
|
| 1989 |
+
}:
|
| 1990 |
+
raise ValueError(f"Skip unsupported ab_dtype {ab_dtype}")
|
| 1991 |
+
if c_dtype not in {cutlass.Float16, cutlass.BFloat16, cutlass.Float32}:
|
| 1992 |
+
raise ValueError(f"Skip unsupported c_dtype {c_dtype}")
|
| 1993 |
+
# Skip unsupported acc dtype
|
| 1994 |
+
if acc_dtype not in {cutlass.Float32, cutlass.Float16}:
|
| 1995 |
+
raise ValueError(f"Skip unsupported acc_dtype {acc_dtype}")
|
| 1996 |
+
# Skip invalid ab_dtype and acc_dtype combination
|
| 1997 |
+
if ab_dtype == cutlass.BFloat16 and acc_dtype == cutlass.Float16:
|
| 1998 |
+
raise ValueError("Skip invalid ab_dtype and acc_dtype combination")
|
| 1999 |
+
# Skip invalid mma tile shape
|
| 2000 |
+
if not (
|
| 2001 |
+
(not use_2cta_instrs and mma_tiler_mn[0] in [64, 128])
|
| 2002 |
+
or (use_2cta_instrs and mma_tiler_mn[0] in [128, 256])
|
| 2003 |
+
):
|
| 2004 |
+
raise ValueError(f"Skip invalid mma tiler M {mma_tiler_mn[0]}")
|
| 2005 |
+
if mma_tiler_mn[1] not in range(32, 257, 32):
|
| 2006 |
+
raise ValueError(f"Skip invalid mma tiler N {mma_tiler_mn[1]}")
|
| 2007 |
+
# Skip illegal cluster shape
|
| 2008 |
+
if cluster_shape_mn[0] % (2 if use_2cta_instrs else 1) != 0:
|
| 2009 |
+
raise ValueError(
|
| 2010 |
+
f"cluster_shape_m need align with use_2cta_instrs config {cluster_shape_mn}"
|
| 2011 |
+
)
|
| 2012 |
+
# Skip invalid cluster shape
|
| 2013 |
+
is_power_of_2 = lambda x: x > 0 and (x & (x - 1)) == 0
|
| 2014 |
+
if (
|
| 2015 |
+
cluster_shape_mn[0] * cluster_shape_mn[1] > 16
|
| 2016 |
+
or cluster_shape_mn[0] <= 0
|
| 2017 |
+
or cluster_shape_mn[1] <= 0
|
| 2018 |
+
or not is_power_of_2(cluster_shape_mn[0])
|
| 2019 |
+
or not is_power_of_2(cluster_shape_mn[1])
|
| 2020 |
+
):
|
| 2021 |
+
raise ValueError(f"Skip invalid cluster shape {cluster_shape_mn}")
|
| 2022 |
+
|
| 2023 |
+
# Skip illegal problem shape for load/store alignment
|
| 2024 |
+
def check_contigous_16B_alignment(dtype, is_mode0_major, tensor_shape):
|
| 2025 |
+
major_mode_idx = 0 if is_mode0_major else 1
|
| 2026 |
+
num_major_elements = tensor_shape[major_mode_idx]
|
| 2027 |
+
num_contiguous_elements = 16 * 8 // dtype.width
|
| 2028 |
+
return num_major_elements % num_contiguous_elements == 0
|
| 2029 |
+
|
| 2030 |
+
if (
|
| 2031 |
+
not check_contigous_16B_alignment(ab_dtype, a_major == "m", (m, k, l))
|
| 2032 |
+
or not check_contigous_16B_alignment(ab_dtype, b_major == "n", (n, k, l))
|
| 2033 |
+
or not check_contigous_16B_alignment(c_dtype, c_major == "m", (m, n, l))
|
| 2034 |
+
):
|
| 2035 |
+
raise ValueError("Skip invalid problem alignment")
|
| 2036 |
+
if not torch.cuda.is_available():
|
| 2037 |
+
raise RuntimeError("GPU is required to run this example!")
|
| 2038 |
+
|
| 2039 |
+
# Create tensors for all groups using the new function
|
| 2040 |
+
(
|
| 2041 |
+
ptrs_abc,
|
| 2042 |
+
torch_tensors_abc,
|
| 2043 |
+
cute_tensors_abc,
|
| 2044 |
+
strides_abc,
|
| 2045 |
+
torch_fp32_tensors_abc,
|
| 2046 |
+
) = create_tensors_for_all_groups(
|
| 2047 |
+
problem_sizes_mnkl,
|
| 2048 |
+
ab_dtype,
|
| 2049 |
+
c_dtype,
|
| 2050 |
+
a_major,
|
| 2051 |
+
b_major,
|
| 2052 |
+
c_major,
|
| 2053 |
+
)
|
| 2054 |
+
|
| 2055 |
+
# Choose A, B, C with the smallest size to create initial tensormaps
|
| 2056 |
+
key_size_a = lambda item: item[1][0] * item[1][2]
|
| 2057 |
+
key_size_b = lambda item: item[1][1] * item[1][2]
|
| 2058 |
+
key_size_c = lambda item: item[1][0] * item[1][1]
|
| 2059 |
+
# Find the indices of the groups with the smallest tensor sizes
|
| 2060 |
+
min_a_idx, _ = min(enumerate(problem_sizes_mnkl), key=key_size_a)
|
| 2061 |
+
min_b_idx, _ = min(enumerate(problem_sizes_mnkl), key=key_size_b)
|
| 2062 |
+
min_c_idx, _ = min(enumerate(problem_sizes_mnkl), key=key_size_c)
|
| 2063 |
+
initial_cute_tensors_abc = [
|
| 2064 |
+
cute_tensors_abc[min_a_idx][0], # A with smallest (m, k)
|
| 2065 |
+
cute_tensors_abc[min_b_idx][1], # B with smallest (n, k)
|
| 2066 |
+
cute_tensors_abc[min_c_idx][2], # C with smallest (m, n)
|
| 2067 |
+
]
|
| 2068 |
+
|
| 2069 |
+
hardware_info = utils.HardwareInfo()
|
| 2070 |
+
sm_count = hardware_info.get_max_active_clusters(1)
|
| 2071 |
+
max_active_clusters = hardware_info.get_max_active_clusters(
|
| 2072 |
+
cluster_shape_mn[0] * cluster_shape_mn[1]
|
| 2073 |
+
)
|
| 2074 |
+
# Prepare tensormap buffer for each SM
|
| 2075 |
+
num_tensormap_buffers = sm_count
|
| 2076 |
+
tensormap_shape = (
|
| 2077 |
+
num_tensormap_buffers,
|
| 2078 |
+
GroupedGemmKernel.num_tensormaps,
|
| 2079 |
+
GroupedGemmKernel.bytes_per_tensormap // 8,
|
| 2080 |
+
)
|
| 2081 |
+
tensor_of_tensormap, tensor_of_tensormap_torch = cutlass_torch.cute_tensor_like(
|
| 2082 |
+
torch.empty(tensormap_shape, dtype=torch.int64),
|
| 2083 |
+
cutlass.Int64,
|
| 2084 |
+
is_dynamic_layout=False,
|
| 2085 |
+
)
|
| 2086 |
+
|
| 2087 |
+
grouped_gemm = GroupedGemmKernel(
|
| 2088 |
+
acc_dtype,
|
| 2089 |
+
use_2cta_instrs,
|
| 2090 |
+
mma_tiler_mn,
|
| 2091 |
+
cluster_shape_mn,
|
| 2092 |
+
tensormap_update_mode,
|
| 2093 |
+
)
|
| 2094 |
+
|
| 2095 |
+
# layout (num_groups, 4):(4, 1)
|
| 2096 |
+
(
|
| 2097 |
+
tensor_of_dim_size_mnkl,
|
| 2098 |
+
tensor_of_dim_size_mnkl_torch,
|
| 2099 |
+
) = cutlass_torch.cute_tensor_like(
|
| 2100 |
+
torch.tensor(problem_sizes_mnkl, dtype=torch.int32),
|
| 2101 |
+
cutlass.Int32,
|
| 2102 |
+
is_dynamic_layout=False,
|
| 2103 |
+
assumed_align=16,
|
| 2104 |
+
)
|
| 2105 |
+
# layout (num_groups, 3, 2):(6, 2, 1)
|
| 2106 |
+
tensor_of_strides_abc, tensor_of_strides_abc_torch = cutlass_torch.cute_tensor_like(
|
| 2107 |
+
torch.tensor(strides_abc, dtype=torch.int32),
|
| 2108 |
+
cutlass.Int32,
|
| 2109 |
+
is_dynamic_layout=False,
|
| 2110 |
+
assumed_align=16,
|
| 2111 |
+
)
|
| 2112 |
+
|
| 2113 |
+
# layout (num_groups,3):(3, 1)
|
| 2114 |
+
tensor_of_ptrs_abc, tensor_of_ptrs_abc_torch = cutlass_torch.cute_tensor_like(
|
| 2115 |
+
torch.tensor(ptrs_abc, dtype=torch.int64),
|
| 2116 |
+
cutlass.Int64,
|
| 2117 |
+
is_dynamic_layout=False,
|
| 2118 |
+
assumed_align=16,
|
| 2119 |
+
)
|
| 2120 |
+
|
| 2121 |
+
# Compute total number of cluster tiles we need to compute for given grouped GEMM problem
|
| 2122 |
+
def compute_total_num_clusters(
|
| 2123 |
+
problem_sizes_mnkl: List[tuple[int, int, int, int]],
|
| 2124 |
+
cluster_tile_shape_mn: tuple[int, int],
|
| 2125 |
+
) -> int:
|
| 2126 |
+
total_num_clusters = 0
|
| 2127 |
+
for m, n, _, _ in problem_sizes_mnkl:
|
| 2128 |
+
num_clusters_mn = tuple(
|
| 2129 |
+
(x + y - 1) // y for x, y in zip((m, n), cluster_tile_shape_mn)
|
| 2130 |
+
)
|
| 2131 |
+
total_num_clusters += functools.reduce(lambda x, y: x * y, num_clusters_mn)
|
| 2132 |
+
return total_num_clusters
|
| 2133 |
+
|
| 2134 |
+
# Compute cluster tile shape
|
| 2135 |
+
def compute_cluster_tile_shape(
|
| 2136 |
+
mma_tiler_mn: tuple[int, int],
|
| 2137 |
+
cluster_shape_mn: tuple[int, int],
|
| 2138 |
+
use_2cta_instrs: bool,
|
| 2139 |
+
) -> tuple[int, int]:
|
| 2140 |
+
cta_tile_shape_mn = list(mma_tiler_mn)
|
| 2141 |
+
if use_2cta_instrs:
|
| 2142 |
+
cta_tile_shape_mn[0] = cta_tile_shape_mn[0] // 2
|
| 2143 |
+
return tuple(x * y for x, y in zip(cta_tile_shape_mn, cluster_shape_mn))
|
| 2144 |
+
|
| 2145 |
+
cluster_tile_shape_mn = compute_cluster_tile_shape(
|
| 2146 |
+
mma_tiler_mn, cluster_shape_mn, use_2cta_instrs
|
| 2147 |
+
)
|
| 2148 |
+
total_num_clusters = compute_total_num_clusters(
|
| 2149 |
+
problem_sizes_mnkl, cluster_tile_shape_mn
|
| 2150 |
+
)
|
| 2151 |
+
|
| 2152 |
+
# Initialize Stream
|
| 2153 |
+
current_stream = cutlass_torch.default_stream()
|
| 2154 |
+
|
| 2155 |
+
# Compile grouped GEMM kernel
|
| 2156 |
+
compiled_grouped_gemm = cute.compile(
|
| 2157 |
+
grouped_gemm,
|
| 2158 |
+
initial_cute_tensors_abc[0],
|
| 2159 |
+
initial_cute_tensors_abc[1],
|
| 2160 |
+
initial_cute_tensors_abc[2],
|
| 2161 |
+
num_groups,
|
| 2162 |
+
tensor_of_dim_size_mnkl,
|
| 2163 |
+
tensor_of_strides_abc,
|
| 2164 |
+
tensor_of_ptrs_abc,
|
| 2165 |
+
total_num_clusters,
|
| 2166 |
+
tensor_of_tensormap,
|
| 2167 |
+
max_active_clusters,
|
| 2168 |
+
current_stream,
|
| 2169 |
+
)
|
| 2170 |
+
|
| 2171 |
+
if not skip_ref_check:
|
| 2172 |
+
compiled_grouped_gemm(
|
| 2173 |
+
initial_cute_tensors_abc[0],
|
| 2174 |
+
initial_cute_tensors_abc[1],
|
| 2175 |
+
initial_cute_tensors_abc[2],
|
| 2176 |
+
tensor_of_dim_size_mnkl,
|
| 2177 |
+
tensor_of_strides_abc,
|
| 2178 |
+
tensor_of_ptrs_abc,
|
| 2179 |
+
tensor_of_tensormap,
|
| 2180 |
+
current_stream,
|
| 2181 |
+
)
|
| 2182 |
+
|
| 2183 |
+
# Compute reference result
|
| 2184 |
+
for i, (a, b, c) in enumerate(torch_tensors_abc):
|
| 2185 |
+
ref = torch.einsum(
|
| 2186 |
+
"mkl,nkl->mnl",
|
| 2187 |
+
a.cpu().to(dtype=torch.float32),
|
| 2188 |
+
b.cpu().to(dtype=torch.float32),
|
| 2189 |
+
)
|
| 2190 |
+
print(f"checking group {i}")
|
| 2191 |
+
torch.testing.assert_close(
|
| 2192 |
+
c.cpu(),
|
| 2193 |
+
ref.to(cutlass_torch.dtype(c_dtype)),
|
| 2194 |
+
atol=tolerance,
|
| 2195 |
+
rtol=1e-05,
|
| 2196 |
+
)
|
| 2197 |
+
|
| 2198 |
+
def generate_tensors():
|
| 2199 |
+
# Reuse existing CPU tensors and create new GPU tensors from them
|
| 2200 |
+
(
|
| 2201 |
+
ptrs_abc_workspace,
|
| 2202 |
+
torch_tensors_abc_workspace,
|
| 2203 |
+
cute_tensors_abc_workspace,
|
| 2204 |
+
strides_abc_workspace,
|
| 2205 |
+
_,
|
| 2206 |
+
) = create_tensors_for_all_groups(
|
| 2207 |
+
problem_sizes_mnkl,
|
| 2208 |
+
ab_dtype,
|
| 2209 |
+
c_dtype,
|
| 2210 |
+
a_major,
|
| 2211 |
+
b_major,
|
| 2212 |
+
c_major,
|
| 2213 |
+
torch_fp32_tensors_abc,
|
| 2214 |
+
)
|
| 2215 |
+
|
| 2216 |
+
initial_cute_tensors_abc_workspace = [
|
| 2217 |
+
cute_tensors_abc_workspace[min_a_idx][0], # A with smallest (m, k)
|
| 2218 |
+
cute_tensors_abc_workspace[min_b_idx][1], # B with smallest (n, k)
|
| 2219 |
+
cute_tensors_abc_workspace[min_c_idx][2], # C with smallest (m, n)
|
| 2220 |
+
]
|
| 2221 |
+
|
| 2222 |
+
# Create new tensors for this workspace
|
| 2223 |
+
tensor_of_strides_abc_workspace, _ = cutlass_torch.cute_tensor_like(
|
| 2224 |
+
torch.tensor(strides_abc_workspace, dtype=torch.int32),
|
| 2225 |
+
cutlass.Int32,
|
| 2226 |
+
is_dynamic_layout=False,
|
| 2227 |
+
assumed_align=16,
|
| 2228 |
+
)
|
| 2229 |
+
|
| 2230 |
+
tensor_of_ptrs_abc_workspace, _ = cutlass_torch.cute_tensor_like(
|
| 2231 |
+
torch.tensor(ptrs_abc_workspace, dtype=torch.int64),
|
| 2232 |
+
cutlass.Int64,
|
| 2233 |
+
is_dynamic_layout=False,
|
| 2234 |
+
assumed_align=16,
|
| 2235 |
+
)
|
| 2236 |
+
|
| 2237 |
+
tensormap_workspace, _ = cutlass_torch.cute_tensor_like(
|
| 2238 |
+
torch.empty(tensormap_shape, dtype=torch.int64),
|
| 2239 |
+
cutlass.Int64,
|
| 2240 |
+
is_dynamic_layout=False,
|
| 2241 |
+
)
|
| 2242 |
+
|
| 2243 |
+
return testing.JitArguments(
|
| 2244 |
+
initial_cute_tensors_abc_workspace[0],
|
| 2245 |
+
initial_cute_tensors_abc_workspace[1],
|
| 2246 |
+
initial_cute_tensors_abc_workspace[2],
|
| 2247 |
+
tensor_of_dim_size_mnkl,
|
| 2248 |
+
tensor_of_strides_abc_workspace,
|
| 2249 |
+
tensor_of_ptrs_abc_workspace,
|
| 2250 |
+
tensormap_workspace,
|
| 2251 |
+
current_stream,
|
| 2252 |
+
)
|
| 2253 |
+
|
| 2254 |
+
workspace_count = 1
|
| 2255 |
+
if use_cold_l2:
|
| 2256 |
+
one_workspace_bytes = (
|
| 2257 |
+
sum(
|
| 2258 |
+
[
|
| 2259 |
+
sum(
|
| 2260 |
+
[
|
| 2261 |
+
torch_tensor.numel() * torch_tensor.element_size()
|
| 2262 |
+
for torch_tensor in group_tensors
|
| 2263 |
+
]
|
| 2264 |
+
)
|
| 2265 |
+
for group_tensors in torch_tensors_abc
|
| 2266 |
+
]
|
| 2267 |
+
)
|
| 2268 |
+
+
|
| 2269 |
+
# Add size of strides tensor
|
| 2270 |
+
tensor_of_strides_abc_torch.numel()
|
| 2271 |
+
* tensor_of_strides_abc_torch.element_size()
|
| 2272 |
+
+
|
| 2273 |
+
# Add size of ptrs tensor
|
| 2274 |
+
tensor_of_ptrs_abc_torch.numel() * tensor_of_ptrs_abc_torch.element_size()
|
| 2275 |
+
+
|
| 2276 |
+
# Add size of tensormap tensor
|
| 2277 |
+
tensor_of_tensormap_torch.numel() * tensor_of_tensormap_torch.element_size()
|
| 2278 |
+
)
|
| 2279 |
+
workspace_count = testing.get_workspace_count(
|
| 2280 |
+
one_workspace_bytes, warmup_iterations, iterations
|
| 2281 |
+
)
|
| 2282 |
+
|
| 2283 |
+
exec_time = testing.benchmark(
|
| 2284 |
+
compiled_grouped_gemm,
|
| 2285 |
+
workspace_generator=generate_tensors,
|
| 2286 |
+
workspace_count=workspace_count,
|
| 2287 |
+
stream=current_stream,
|
| 2288 |
+
warmup_iterations=warmup_iterations,
|
| 2289 |
+
iterations=iterations,
|
| 2290 |
+
)
|
| 2291 |
+
|
| 2292 |
+
return exec_time # Return execution time in microseconds
|
| 2293 |
+
|
| 2294 |
+
|
| 2295 |
+
if __name__ == "__main__":
|
| 2296 |
+
|
| 2297 |
+
def parse_comma_separated_ints(s: str) -> tuple[int, ...]:
|
| 2298 |
+
try:
|
| 2299 |
+
return tuple(int(x.strip()) for x in s.split(","))
|
| 2300 |
+
except ValueError:
|
| 2301 |
+
raise argparse.ArgumentTypeError(
|
| 2302 |
+
"Invalid format. Expected comma-separated integers."
|
| 2303 |
+
)
|
| 2304 |
+
|
| 2305 |
+
def parse_comma_separated_tuples(s: str) -> List[tuple[int, ...]]:
|
| 2306 |
+
if s.strip().startswith("("):
|
| 2307 |
+
# Split on ),( to separate tuples
|
| 2308 |
+
tuples = s.strip("()").split("),(")
|
| 2309 |
+
result = []
|
| 2310 |
+
tuple_len = None
|
| 2311 |
+
|
| 2312 |
+
for t in tuples:
|
| 2313 |
+
# Parse individual tuple
|
| 2314 |
+
nums = [int(x.strip()) for x in t.split(",")]
|
| 2315 |
+
|
| 2316 |
+
# Validate tuple length consistency
|
| 2317 |
+
if tuple_len is None:
|
| 2318 |
+
tuple_len = len(nums)
|
| 2319 |
+
elif len(nums) != tuple_len:
|
| 2320 |
+
raise argparse.ArgumentTypeError(
|
| 2321 |
+
"All tuples must have the same length"
|
| 2322 |
+
)
|
| 2323 |
+
|
| 2324 |
+
result.append(tuple(nums))
|
| 2325 |
+
return result
|
| 2326 |
+
|
| 2327 |
+
raise argparse.ArgumentTypeError(
|
| 2328 |
+
"Invalid format. Expected comma-separated integers or list of tuples"
|
| 2329 |
+
)
|
| 2330 |
+
|
| 2331 |
+
parser = argparse.ArgumentParser(
|
| 2332 |
+
description="Example of Grouped GEMM on Blackwell."
|
| 2333 |
+
)
|
| 2334 |
+
parser.add_argument(
|
| 2335 |
+
"--num_groups",
|
| 2336 |
+
type=int,
|
| 2337 |
+
default=2,
|
| 2338 |
+
help="Number of groups",
|
| 2339 |
+
)
|
| 2340 |
+
parser.add_argument(
|
| 2341 |
+
"--problem_sizes_mnkl",
|
| 2342 |
+
type=parse_comma_separated_tuples,
|
| 2343 |
+
default=((128, 128, 128, 1), (128, 128, 128, 1)),
|
| 2344 |
+
help="a tuple of problem sizes for each group (comma-separated tuples)",
|
| 2345 |
+
)
|
| 2346 |
+
parser.add_argument(
|
| 2347 |
+
"--mma_tiler_mn",
|
| 2348 |
+
type=parse_comma_separated_ints,
|
| 2349 |
+
default=(128, 128),
|
| 2350 |
+
help="Mma tile shape (comma-separated)",
|
| 2351 |
+
)
|
| 2352 |
+
parser.add_argument(
|
| 2353 |
+
"--cluster_shape_mn",
|
| 2354 |
+
type=parse_comma_separated_ints,
|
| 2355 |
+
default=(1, 1),
|
| 2356 |
+
help="Cluster shape (comma-separated)",
|
| 2357 |
+
)
|
| 2358 |
+
parser.add_argument(
|
| 2359 |
+
"--tensormap_update_mode",
|
| 2360 |
+
type=str,
|
| 2361 |
+
default="SMEM",
|
| 2362 |
+
help="Tensor map update mode",
|
| 2363 |
+
)
|
| 2364 |
+
parser.add_argument("--ab_dtype", type=cutlass.dtype, default=cutlass.Float16)
|
| 2365 |
+
parser.add_argument("--c_dtype", type=cutlass.dtype, default=cutlass.Float16)
|
| 2366 |
+
parser.add_argument("--acc_dtype", type=cutlass.dtype, default=cutlass.Float32)
|
| 2367 |
+
parser.add_argument(
|
| 2368 |
+
"--use_2cta_instrs",
|
| 2369 |
+
action="store_true",
|
| 2370 |
+
help="Enable 2CTA MMA instructions feature",
|
| 2371 |
+
)
|
| 2372 |
+
parser.add_argument("--a_major", choices=["k", "m"], type=str, default="k")
|
| 2373 |
+
parser.add_argument("--b_major", choices=["k", "n"], type=str, default="k")
|
| 2374 |
+
parser.add_argument("--c_major", choices=["n", "m"], type=str, default="n")
|
| 2375 |
+
parser.add_argument(
|
| 2376 |
+
"--tolerance", type=float, default=1e-01, help="Tolerance for validation"
|
| 2377 |
+
)
|
| 2378 |
+
parser.add_argument(
|
| 2379 |
+
"--warmup_iterations", type=int, default=0, help="Warmup iterations"
|
| 2380 |
+
)
|
| 2381 |
+
parser.add_argument(
|
| 2382 |
+
"--iterations",
|
| 2383 |
+
type=int,
|
| 2384 |
+
default=1,
|
| 2385 |
+
help="Number of iterations to run the kernel",
|
| 2386 |
+
)
|
| 2387 |
+
parser.add_argument(
|
| 2388 |
+
"--skip_ref_check", action="store_true", help="Skip reference checking"
|
| 2389 |
+
)
|
| 2390 |
+
parser.add_argument(
|
| 2391 |
+
"--use_cold_l2",
|
| 2392 |
+
action="store_true",
|
| 2393 |
+
default=False,
|
| 2394 |
+
help="Use circular buffer tensor sets to ensure L2 cold cache",
|
| 2395 |
+
)
|
| 2396 |
+
|
| 2397 |
+
args = parser.parse_args()
|
| 2398 |
+
|
| 2399 |
+
if (
|
| 2400 |
+
len(args.problem_sizes_mnkl) != 0
|
| 2401 |
+
and len(args.problem_sizes_mnkl) != args.num_groups
|
| 2402 |
+
):
|
| 2403 |
+
parser.error("--problem_sizes_mnkl must contain exactly num_groups tuples")
|
| 2404 |
+
|
| 2405 |
+
# l mode must be 1 for all groups
|
| 2406 |
+
for _, _, _, l in args.problem_sizes_mnkl:
|
| 2407 |
+
if l != 1:
|
| 2408 |
+
parser.error("l must be 1 for all groups")
|
| 2409 |
+
|
| 2410 |
+
if len(args.mma_tiler_mn) != 2:
|
| 2411 |
+
parser.error("--mma_tiler_mn must contain exactly 2 values")
|
| 2412 |
+
|
| 2413 |
+
if len(args.cluster_shape_mn) != 2:
|
| 2414 |
+
parser.error("--cluster_shape_mn must contain exactly 2 values")
|
| 2415 |
+
|
| 2416 |
+
if args.tensormap_update_mode not in ["GMEM", "SMEM"]:
|
| 2417 |
+
parser.error("--tensormap_update_mode must be GMEM or SMEM")
|
| 2418 |
+
|
| 2419 |
+
if args.tensormap_update_mode == "GMEM":
|
| 2420 |
+
tensormap_update_mode = utils.TensorMapUpdateMode.GMEM
|
| 2421 |
+
else:
|
| 2422 |
+
tensormap_update_mode = utils.TensorMapUpdateMode.SMEM
|
| 2423 |
+
|
| 2424 |
+
torch.manual_seed(2025)
|
| 2425 |
+
|
| 2426 |
+
run(
|
| 2427 |
+
args.num_groups,
|
| 2428 |
+
args.problem_sizes_mnkl,
|
| 2429 |
+
args.ab_dtype,
|
| 2430 |
+
args.c_dtype,
|
| 2431 |
+
args.acc_dtype,
|
| 2432 |
+
args.a_major,
|
| 2433 |
+
args.b_major,
|
| 2434 |
+
args.c_major,
|
| 2435 |
+
args.mma_tiler_mn,
|
| 2436 |
+
args.cluster_shape_mn,
|
| 2437 |
+
args.use_2cta_instrs,
|
| 2438 |
+
tensormap_update_mode,
|
| 2439 |
+
args.tolerance,
|
| 2440 |
+
args.warmup_iterations,
|
| 2441 |
+
args.iterations,
|
| 2442 |
+
args.skip_ref_check,
|
| 2443 |
+
args.use_cold_l2,
|
| 2444 |
+
)
|
| 2445 |
+
print("PASS")
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/mamba2_ssd/mamba2_ssd.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/torch210-cxx11-cu130-aarch64-linux/include/third-party/cutlass/examples/python/CuTeDSL/blackwell/mamba2_ssd/mamba2_ssd_reference.py
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 3 |
+
|
| 4 |
+
# Redistribution and use in source and binary forms, with or without
|
| 5 |
+
# modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 8 |
+
# list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 12 |
+
# and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
# contributors may be used to endorse or promote products derived from
|
| 16 |
+
# this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 19 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 20 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 21 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 22 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 23 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 24 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 25 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 26 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 27 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 28 |
+
|
| 29 |
+
import torch
|
| 30 |
+
import torch.nn.functional as F
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def ssd_reference_fp32_all(x, a, delta, B, C, Y_out, Fstate_out, D, has_d, d_has_hdim):
|
| 34 |
+
"""
|
| 35 |
+
Rearrange tensor dimensions from cuda layout to reference layout, then directly call TriDao's ssd implementation
|
| 36 |
+
Arguments:
|
| 37 |
+
X/x: (D, L, C, H, B):(C*L, 1, L, D*C*L, H*D*C*L)
|
| 38 |
+
A/delta: (L, C, H, B):(1, L, C*L, H*C*L)
|
| 39 |
+
a: (H):(1)
|
| 40 |
+
B/C: (L, N, C, G, B):(1, C*L, L, N*C*L, G*N*C*L)
|
| 41 |
+
D: (1, H):(0, 1) or (D, H):(1, D)
|
| 42 |
+
has_d: bool
|
| 43 |
+
d_has_hdim: bool
|
| 44 |
+
Return:
|
| 45 |
+
Y_out: (L, D, C, H, B):(1, C*L, L, D*C*L, H*D*C*L)
|
| 46 |
+
Fstate_out: (D, N, H, B):(N, 1, D*N, H*D*N)
|
| 47 |
+
"""
|
| 48 |
+
assert x.dtype == a.dtype == delta.dtype == B.dtype == C.dtype
|
| 49 |
+
|
| 50 |
+
A = delta * a.view(1, 1, -1, 1)
|
| 51 |
+
X = x * delta.unsqueeze(0)
|
| 52 |
+
|
| 53 |
+
# Rearrange to match cutlass layout to tridao's layout
|
| 54 |
+
block_len = A.shape[0]
|
| 55 |
+
initial_states = None
|
| 56 |
+
# A: l c h b-> b c l h
|
| 57 |
+
A = A.permute(3, 1, 0, 2)
|
| 58 |
+
# X: p l c h b -> b c l h p
|
| 59 |
+
X = X.permute(4, 2, 1, 3, 0)
|
| 60 |
+
# B: l n c g b -> b c l g n
|
| 61 |
+
B = B.permute(4, 2, 0, 3, 1)
|
| 62 |
+
# C: l n c g b -> b c l g n
|
| 63 |
+
C = C.permute(4, 2, 0, 3, 1)
|
| 64 |
+
# X/A/B/C: b c l ... -> b (c l) ...
|
| 65 |
+
X, A, B, C = [x.reshape(x.shape[0], -1, *x.shape[3:]) for x in (X, A, B, C)]
|
| 66 |
+
|
| 67 |
+
# Ngroup (g to h) mapping
|
| 68 |
+
B_val, CL_val, G_val, N_val = B.shape
|
| 69 |
+
H_val = X.shape[2]
|
| 70 |
+
ngroup_ratio = H_val // G_val
|
| 71 |
+
# B/C: (B, CL, H, N)
|
| 72 |
+
h_to_g_mapping = torch.arange(H_val, device=B.device) // ngroup_ratio
|
| 73 |
+
B = B.gather(2, h_to_g_mapping.view(1, 1, -1, 1).expand(B_val, CL_val, -1, N_val))
|
| 74 |
+
C = C.gather(2, h_to_g_mapping.view(1, 1, -1, 1).expand(B_val, CL_val, -1, N_val))
|
| 75 |
+
|
| 76 |
+
###################################################################
|
| 77 |
+
# Call reference implementation from Tri Dao ssd_minimal_discrete
|
| 78 |
+
Y, final_state = ssd_minimal_discrete_fp32_all(
|
| 79 |
+
X, A, B, C, block_len, initial_states
|
| 80 |
+
)
|
| 81 |
+
###################################################################
|
| 82 |
+
|
| 83 |
+
if has_d:
|
| 84 |
+
D_val = Y.shape[3]
|
| 85 |
+
if not d_has_hdim:
|
| 86 |
+
D = D.expand(D_val, -1)
|
| 87 |
+
Y = Y + torch.einsum("bchp,ph->bchp", X, D)
|
| 88 |
+
|
| 89 |
+
# Rearrange to match tridao's layout to cutlass layout
|
| 90 |
+
# Y: b (c l) h p -> b c l h p
|
| 91 |
+
Y = Y.reshape(Y.shape[0], -1, block_len, Y.shape[2], Y.shape[3])
|
| 92 |
+
# Y: b c l h p -> l p c h b
|
| 93 |
+
Y = Y.permute(2, 4, 1, 3, 0)
|
| 94 |
+
# Fstate_out: b h p n -> p n h b
|
| 95 |
+
Fstate_out.copy_(final_state.permute(2, 3, 1, 0))
|
| 96 |
+
Y_out.copy_(Y)
|
| 97 |
+
return
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def ssd_reference_lowprecision_intermediates(
|
| 101 |
+
x, a, delta, B, C, Y_out, Fstate_out, intermediate_dtype, D, has_d, d_has_hdim
|
| 102 |
+
):
|
| 103 |
+
"""
|
| 104 |
+
Rearrange tensor dimensions from cuda layout to reference layout, then call a reduced intermediate dtype version of ssd implementation
|
| 105 |
+
Arguments:
|
| 106 |
+
X/x: (D, L, C, H, B):(C*L, 1, L, D*C*L, H*D*C*L)
|
| 107 |
+
A/delta: (L, C, H, B):(1, L, C*L, H*C*L)
|
| 108 |
+
a: (H):(1)
|
| 109 |
+
B/C: (L, N, C, G, B):(1, C*L, L, N*C*L, G*N*C*L)
|
| 110 |
+
intermediate_dtype: input and intermediate data type
|
| 111 |
+
D: (1, H):(0, 1) or (D, H):(1, D)
|
| 112 |
+
has_d: bool
|
| 113 |
+
d_has_hdim: bool
|
| 114 |
+
Return:
|
| 115 |
+
Y_out: (L, D, C, H, B):(1, C*L, L, D*C*L, H*D*C*L)
|
| 116 |
+
Fstate_out: (D, N, H, B):(N, 1, D*N, H*D*N)
|
| 117 |
+
"""
|
| 118 |
+
assert x.dtype == a.dtype == delta.dtype == B.dtype == C.dtype
|
| 119 |
+
|
| 120 |
+
A = delta * a.view(1, 1, -1, 1)
|
| 121 |
+
|
| 122 |
+
# Rearrange to match cutlass layout to tridao's layout
|
| 123 |
+
block_len = A.shape[0]
|
| 124 |
+
initial_states = None
|
| 125 |
+
# A: l c h b-> b c l h
|
| 126 |
+
A = A.permute(3, 1, 0, 2)
|
| 127 |
+
# delta: l c h b-> b c l h
|
| 128 |
+
delta = delta.permute(3, 1, 0, 2)
|
| 129 |
+
# x: p l c h b -> b c l h p
|
| 130 |
+
x = x.permute(4, 2, 1, 3, 0)
|
| 131 |
+
# B: l n c g b -> b c l g n
|
| 132 |
+
B = B.permute(4, 2, 0, 3, 1)
|
| 133 |
+
# C: l n c g b -> b c l g n
|
| 134 |
+
C = C.permute(4, 2, 0, 3, 1)
|
| 135 |
+
# x/A/delta/B/C: b c l ... -> b (c l) ...
|
| 136 |
+
x, A, delta, B, C = [
|
| 137 |
+
tensor.reshape(tensor.shape[0], -1, *tensor.shape[3:])
|
| 138 |
+
for tensor in (x, A, delta, B, C)
|
| 139 |
+
]
|
| 140 |
+
|
| 141 |
+
# Ngroup (g to h) mapping
|
| 142 |
+
B_val, CL_val, G_val, N_val = B.shape
|
| 143 |
+
H_val = x.shape[2]
|
| 144 |
+
ngroup_ratio = H_val // G_val
|
| 145 |
+
# B/C: (B, CL, H, N)
|
| 146 |
+
h_to_g_mapping = torch.arange(H_val, device=B.device) // ngroup_ratio
|
| 147 |
+
B = B.gather(2, h_to_g_mapping.view(1, 1, -1, 1).expand(B_val, CL_val, -1, N_val))
|
| 148 |
+
C = C.gather(2, h_to_g_mapping.view(1, 1, -1, 1).expand(B_val, CL_val, -1, N_val))
|
| 149 |
+
|
| 150 |
+
# Type convert input tensors to input dtype (same as intermediate dtype)
|
| 151 |
+
x = x.to(intermediate_dtype).to(torch.float32)
|
| 152 |
+
A = A.to(intermediate_dtype).to(torch.float32)
|
| 153 |
+
delta = delta.to(intermediate_dtype).to(torch.float32)
|
| 154 |
+
B = B.to(intermediate_dtype).to(torch.float32)
|
| 155 |
+
C = C.to(intermediate_dtype).to(torch.float32)
|
| 156 |
+
|
| 157 |
+
#########################################################################
|
| 158 |
+
# Call reference implementation ssd_minimal_discrete_bf16_intermediates
|
| 159 |
+
Y, final_state = ssd_minimal_discrete_lowprecision_intermediates(
|
| 160 |
+
x, A, delta, B, C, block_len, intermediate_dtype, initial_states
|
| 161 |
+
)
|
| 162 |
+
#########################################################################
|
| 163 |
+
|
| 164 |
+
if has_d:
|
| 165 |
+
D = D.to(intermediate_dtype).to(torch.float32)
|
| 166 |
+
D_val = Y.shape[3]
|
| 167 |
+
if not d_has_hdim:
|
| 168 |
+
D = D.expand(D_val, -1)
|
| 169 |
+
Y = Y + torch.einsum("bchp,ph->bchp", x, D)
|
| 170 |
+
|
| 171 |
+
# Type convert output tensors to output dtype (same as intermediate dtype)
|
| 172 |
+
Y = Y.to(intermediate_dtype).to(torch.float32)
|
| 173 |
+
final_state = final_state.to(intermediate_dtype).to(torch.float32)
|
| 174 |
+
|
| 175 |
+
# Rearrange to match tridao's layout to cutlass layout
|
| 176 |
+
# Y: b (c l) h p -> b c l h p
|
| 177 |
+
Y = Y.reshape(Y.shape[0], -1, block_len, Y.shape[2], Y.shape[3])
|
| 178 |
+
# Y: b c l h p -> l p c h b
|
| 179 |
+
Y = Y.permute(2, 4, 1, 3, 0)
|
| 180 |
+
# Fstate_out: b h p n -> p n h b
|
| 181 |
+
Fstate_out.copy_(final_state.permute(2, 3, 1, 0))
|
| 182 |
+
Y_out.copy_(Y)
|
| 183 |
+
return
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def analyze_relative_diffs(actual, expected):
|
| 187 |
+
"""
|
| 188 |
+
Print statistics of relative differences between actual and expected tensors
|
| 189 |
+
"""
|
| 190 |
+
# Calculate relative differences
|
| 191 |
+
abs_diff = (actual - expected).abs()
|
| 192 |
+
rel_diff = abs_diff / (torch.maximum(expected.abs(), actual.abs()) + 0.00001)
|
| 193 |
+
|
| 194 |
+
total_elements = rel_diff.numel()
|
| 195 |
+
|
| 196 |
+
# Handle special cases first
|
| 197 |
+
nan_mask = torch.isnan(rel_diff)
|
| 198 |
+
inf_mask = torch.isinf(rel_diff)
|
| 199 |
+
nan_count = nan_mask.sum().item()
|
| 200 |
+
inf_count = inf_mask.sum().item()
|
| 201 |
+
|
| 202 |
+
# Find position and value of maximum relative difference
|
| 203 |
+
max_rel_diff = (
|
| 204 |
+
rel_diff[~nan_mask & ~inf_mask].max()
|
| 205 |
+
if (~nan_mask & ~inf_mask).any()
|
| 206 |
+
else float("nan")
|
| 207 |
+
)
|
| 208 |
+
max_rel_diff_pos = (
|
| 209 |
+
rel_diff[~nan_mask & ~inf_mask].argmax()
|
| 210 |
+
if (~nan_mask & ~inf_mask).any()
|
| 211 |
+
else -1
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
# Print max relative difference info
|
| 215 |
+
print(f"Maximum relative difference:")
|
| 216 |
+
print(f"Position: {max_rel_diff_pos}")
|
| 217 |
+
print(f"Value: {max_rel_diff:.6e}")
|
| 218 |
+
print(f"Actual value: {actual.flatten()[max_rel_diff_pos]}")
|
| 219 |
+
print(f"Expected value: {expected.flatten()[max_rel_diff_pos]}")
|
| 220 |
+
print(f"NaN values: {nan_count} ({100.0 * nan_count / total_elements:.2f}%)")
|
| 221 |
+
print(f"Inf values: {inf_count} ({100.0 * inf_count / total_elements:.2f}%)\n")
|
| 222 |
+
|
| 223 |
+
# Check different rtol thresholds
|
| 224 |
+
rtol_levels = [1e-5, 1e-4, 1e-3, 1e-2, 5e-02, 1e-01]
|
| 225 |
+
|
| 226 |
+
for i, rtol in enumerate(rtol_levels):
|
| 227 |
+
if i == 0:
|
| 228 |
+
mask = rel_diff <= rtol
|
| 229 |
+
else:
|
| 230 |
+
mask = (rel_diff <= rtol) & (rel_diff > rtol_levels[i - 1])
|
| 231 |
+
|
| 232 |
+
count = mask.sum().item()
|
| 233 |
+
percentage = (count / total_elements) * 100
|
| 234 |
+
|
| 235 |
+
if i == 0:
|
| 236 |
+
print(f"Elements with rtol <= {rtol:.0e}: {count} ({percentage:.2f}%)")
|
| 237 |
+
else:
|
| 238 |
+
print(
|
| 239 |
+
f"Elements with {rtol_levels[i-1]:.0e} < rtol <= {rtol:.0e}: {count} ({percentage:.2f}%)"
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
# Print elements exceeding the largest rtol
|
| 243 |
+
mask = rel_diff > rtol_levels[-1]
|
| 244 |
+
count = mask.sum().item()
|
| 245 |
+
percentage = (count / total_elements) * 100
|
| 246 |
+
print(f"Elements with rtol > {rtol_levels[-1]:.0e}: {count} ({percentage:.2f}%)\n")
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def segsum(x):
|
| 250 |
+
"""
|
| 251 |
+
More stable segment sum calculation.
|
| 252 |
+
x: b h c l
|
| 253 |
+
"""
|
| 254 |
+
T = x.size(-1)
|
| 255 |
+
# x: b h c l -> b h c l l
|
| 256 |
+
x = x.unsqueeze(-1).expand(*x.shape, T)
|
| 257 |
+
mask = torch.tril(torch.ones(T, T, device=x.device, dtype=bool), diagonal=-1)
|
| 258 |
+
x = x.masked_fill(~mask, 0)
|
| 259 |
+
x_segsum = torch.cumsum(x, dim=-2)
|
| 260 |
+
mask = torch.tril(torch.ones(T, T, device=x.device, dtype=bool), diagonal=0)
|
| 261 |
+
x_segsum = x_segsum.masked_fill(~mask, -torch.inf)
|
| 262 |
+
return x_segsum
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def ssd_minimal_discrete_fp32_all(X, A, B, C, block_len, initial_states=None):
|
| 266 |
+
"""
|
| 267 |
+
This is same with https://github.com/state-spaces/mamba/blob/main/mamba_ssm/modules/ssd_minimal.py
|
| 268 |
+
(all accumulation and intermediate results in fp32)
|
| 269 |
+
|
| 270 |
+
Arguments:
|
| 271 |
+
X: (batch(B), length(C*L), n_heads(H), d_head(D))
|
| 272 |
+
A: (batch(B), length(C*L), n_heads(H))
|
| 273 |
+
B: (batch(B), length(C*L), n_heads(H), d_state(N))
|
| 274 |
+
C: (batch(B), length(C*L), n_heads(H), d_state(N))
|
| 275 |
+
Return:
|
| 276 |
+
Y: (batch(B), length(C*L), n_heads(H), d_head(D))
|
| 277 |
+
final_state: (B, H, D, N)
|
| 278 |
+
"""
|
| 279 |
+
assert X.dtype == A.dtype == B.dtype == C.dtype
|
| 280 |
+
assert X.shape[1] % block_len == 0
|
| 281 |
+
|
| 282 |
+
# Rearrange into blocks/chunks
|
| 283 |
+
# X/A/B/C:b (c l) ... -> b c l ...
|
| 284 |
+
X, A, B, C = [
|
| 285 |
+
x.reshape(x.shape[0], -1, block_len, *x.shape[2:]) for x in (X, A, B, C)
|
| 286 |
+
]
|
| 287 |
+
|
| 288 |
+
# A: b c l h -> b h c l
|
| 289 |
+
A = A.permute(0, 3, 1, 2)
|
| 290 |
+
# A_cumsum: (B, H, C, L)
|
| 291 |
+
A_cumsum = torch.cumsum(A, dim=-1)
|
| 292 |
+
|
| 293 |
+
# 1. Compute the output for each intra-chunk (diagonal blocks)
|
| 294 |
+
segsum_A = segsum(A)
|
| 295 |
+
L = torch.exp(segsum_A)
|
| 296 |
+
Y_diag = torch.einsum("bclhn,bcshn,bhcls,bcshp->bclhp", C, B, L, X)
|
| 297 |
+
|
| 298 |
+
# 2. Compute the state for each intra-chunk
|
| 299 |
+
# (right term of low-rank factorization of off-diagonal blocks; B terms)
|
| 300 |
+
decay_states = torch.exp((A_cumsum[:, :, :, -1:] - A_cumsum))
|
| 301 |
+
states = torch.einsum("bclhn,bhcl,bclhp->bchpn", B, decay_states, X)
|
| 302 |
+
|
| 303 |
+
# 3. Compute the inter-chunk SSM recurrence; produces correct SSM states at chunk boundaries
|
| 304 |
+
# (middle term of factorization of off-diag blocks; A terms)
|
| 305 |
+
if initial_states is None:
|
| 306 |
+
initial_states = torch.zeros_like(states[:, :1])
|
| 307 |
+
states = torch.cat([initial_states, states], dim=1)
|
| 308 |
+
decay_chunk = torch.exp(segsum(F.pad(A_cumsum[:, :, :, -1], (1, 0))))
|
| 309 |
+
new_states = torch.einsum("bhzc,bchpn->bzhpn", decay_chunk, states)
|
| 310 |
+
states, final_state = new_states[:, :-1], new_states[:, -1]
|
| 311 |
+
|
| 312 |
+
# 4. Compute state -> output conversion per chunk
|
| 313 |
+
# (left term of low-rank factorization of off-diagonal blocks; C terms)
|
| 314 |
+
state_decay_out = torch.exp(A_cumsum)
|
| 315 |
+
Y_off = torch.einsum("bclhn,bchpn,bhcl->bclhp", C, states, state_decay_out)
|
| 316 |
+
|
| 317 |
+
# Add output of intra-chunk and inter-chunk terms (diagonal and off-diagonal blocks)
|
| 318 |
+
# Y: b c l h p -> b (c l) h p
|
| 319 |
+
Y = (Y_diag + Y_off).reshape(Y_diag.shape[0], -1, Y_diag.shape[3], Y_diag.shape[4])
|
| 320 |
+
return Y, final_state
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def ssd_minimal_discrete_lowprecision_intermediates(
|
| 324 |
+
X, A, delta, B, C, block_len, intermediate_dtype, initial_states=None
|
| 325 |
+
):
|
| 326 |
+
"""
|
| 327 |
+
This is adjusted from ssd_minimal_discrete_fp32_all, with exceptions:
|
| 328 |
+
1. accumulation in fp32 but intermediates Q/b_tmem/P are in intermediate_dtype
|
| 329 |
+
2. delta is not pre-multiplied with X, delta was applied to generate Q/b_tmem to match GPU implementation
|
| 330 |
+
|
| 331 |
+
Arguments:
|
| 332 |
+
X: (batch(B), length(C*L), n_heads(H), d_head(D))
|
| 333 |
+
A: (batch(B), length(C*L), n_heads(H))
|
| 334 |
+
delta: (batch(B), length(C*L), n_heads(H))
|
| 335 |
+
B: (batch(B), length(C*L), n_heads(H), d_state(N))
|
| 336 |
+
C: (batch(B), length(C*L), n_heads(H), d_state(N))
|
| 337 |
+
Return:
|
| 338 |
+
Y: (batch(B), length(C*L), n_heads(H), d_head(D))
|
| 339 |
+
final_state: (B, H, D, N)
|
| 340 |
+
"""
|
| 341 |
+
assert X.dtype == A.dtype == B.dtype == C.dtype
|
| 342 |
+
assert X.shape[1] % block_len == 0
|
| 343 |
+
|
| 344 |
+
# Rearrange into blocks/chunks
|
| 345 |
+
# X/A/delta/B/C: b (c l) ... -> b c l ...
|
| 346 |
+
X, A, delta, B, C = [
|
| 347 |
+
x.reshape(x.shape[0], -1, block_len, *x.shape[2:]) for x in (X, A, delta, B, C)
|
| 348 |
+
]
|
| 349 |
+
|
| 350 |
+
# A: b c l h -> b h c l
|
| 351 |
+
A = A.permute(0, 3, 1, 2)
|
| 352 |
+
# delta: b c l h -> b h c l
|
| 353 |
+
delta = delta.permute(0, 3, 1, 2)
|
| 354 |
+
# A_cumsum: (B, H, C, L)
|
| 355 |
+
A_cumsum = torch.cumsum(A, dim=-1)
|
| 356 |
+
|
| 357 |
+
# 1. Compute the output for each intra-chunk (diagonal blocks)
|
| 358 |
+
segsum_A = segsum(A)
|
| 359 |
+
L = torch.exp(segsum_A)
|
| 360 |
+
intra_acc_0 = torch.einsum("bclhn,bcshn->bclhs", C, B)
|
| 361 |
+
Q = torch.einsum("bclhs,bhcls,bhcs->bclhs", intra_acc_0, L, delta)
|
| 362 |
+
Y_diag = torch.einsum(
|
| 363 |
+
"bclhs,bcshp->bclhp", Q.to(intermediate_dtype).to(torch.float32), X
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
# 2. Compute the state for each intra-chunk
|
| 367 |
+
# (right term of low-rank factorization of off-diagonal blocks; B terms)
|
| 368 |
+
decay_states = torch.exp((A_cumsum[:, :, :, -1:] - A_cumsum))
|
| 369 |
+
b_tmem = torch.einsum("bclhn,bhcl,bhcl->bclhn", B, decay_states, delta)
|
| 370 |
+
states = torch.einsum(
|
| 371 |
+
"bclhn,bclhp->bchpn", b_tmem.to(intermediate_dtype).to(torch.float32), X
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
# 3. Compute the inter-chunk SSM recurrence; produces correct SSM states at chunk boundaries
|
| 375 |
+
# (middle term of factorization of off-diag blocks; A terms)
|
| 376 |
+
if initial_states is None:
|
| 377 |
+
initial_states = torch.zeros_like(states[:, :1])
|
| 378 |
+
states = torch.cat([initial_states, states], dim=1)
|
| 379 |
+
decay_chunk = torch.exp(segsum(F.pad(A_cumsum[:, :, :, -1], (1, 0))))
|
| 380 |
+
new_states = torch.einsum("bhzc,bchpn->bzhpn", decay_chunk, states)
|
| 381 |
+
states, final_state = new_states[:, :-1], new_states[:, -1]
|
| 382 |
+
final_state = final_state
|
| 383 |
+
|
| 384 |
+
# 4. Compute state -> output conversion per chunk
|
| 385 |
+
# (left term of low-rank factorization of off-diagonal blocks; C terms)
|
| 386 |
+
state_decay_out = torch.exp(A_cumsum)
|
| 387 |
+
Y_off_tmp = torch.einsum(
|
| 388 |
+
"bclhn,bchpn->bclhp", C, states.to(intermediate_dtype).to(torch.float32)
|
| 389 |
+
)
|
| 390 |
+
Y_off = torch.einsum("bclhp,bhcl->bclhp", Y_off_tmp, state_decay_out)
|
| 391 |
+
|
| 392 |
+
# Add output of intra-chunk and inter-chunk terms (diagonal and off-diagonal blocks)
|
| 393 |
+
# Y: b c l h p -> b (c l) h p
|
| 394 |
+
Y = (Y_diag + Y_off).reshape(
|
| 395 |
+
Y_diag.shape[0], -1, Y_diag.shape[3], Y_diag.shape[4]
|
| 396 |
+
) # b (c l) h p
|
| 397 |
+
return Y, final_state
|