co-authored by
Aniket Shivam
parent
9b8166e3f0
commit
d572cc1aab
@@ -1,6 +1,21 @@
|
||||
# NVIDIA CUTLASS Changelog
|
||||
|
||||
|
||||
## [3.1.0](https://github.com/NVIDIA/cutlass/releases/tag/v3.1.0) (2023-04-14)
|
||||
* New CUTLASS Python interface that aims to provide an ease-of-use interface for instantiating, emitting, compiling, and running CUTLASS kernels via Python. More details [here](/python/README.md) and new [examples](/examples/python).
|
||||
* New [efficient epilogues](test/unit/gemm/device/sm90_gemm_f16_f16_f16_tensor_op_f32_cluster_warpspecialized_cooperative.cu#L783) for FP16 datatype using TMA for Hopper.
|
||||
* Support for [fused epilogues](test/unit/gemm/device/sm90_gemm_f16_f16_f16_tensor_op_f32_cluster_warpspecialized_cooperative_bias_elementwise.cu), such Bias, ReLU and GELU, using the new efficient epilogues.
|
||||
* New [warp-specialized TensorFloat-32 (TF32) GEMM kernels](test/unit/gemm/device/sm90_gemm_tf32_tf32_f32_tensor_op_f32_gmma_rs_cluster_warpspecialized.cu) targeting Hopper TMA.
|
||||
* New [*warp-specialized persistent cooperative*](include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized_cooperative.hpp) kernel design that allows for larger tile sizes and improves performance on Hopper.
|
||||
* An [example](examples/51_hopper_gett) showcasing GEMM-Like Tensor-Tensor Contraction (GETT) capability on Hopper.
|
||||
* Epilogue builders. Similar to mainloop builders (see [example 49](/examples/49_hopper_gemm_with_collective_builder/49_collective_builder.cu)), epilogue builders aim to generate the best-possible epilogue while exposing incremental opt-ins for greater customization.
|
||||
* Profiler support for overriding kernel and epilogue builder auto schedules for 3.x API kernels, allowing specific policies to be run in the CUTLASS profiler.
|
||||
* Performance optimizations for the [*warp-specialized persistent ping-pong*](include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized_pingpong.hpp) kernel.
|
||||
* Changes to the [GEMM API 3.x](media/docs/gemm_api_3x.md), involving the host-facing arguments and the underlying `Params` structs.
|
||||
* The GitHub branch is renamed from `master` to `main` in this release.
|
||||
* Optimal performance using [**CUDA 12.1**](https://developer.nvidia.com/cuda-downloads)
|
||||
* Updates and bugfixes from the community (thanks!)
|
||||
|
||||
## [3.0.0](https://github.com/NVIDIA/cutlass/releases/tag/v3.0.0) (2023-01-23)
|
||||
* [CuTe](/media/docs/cute/00_quickstart.md), a [new core library and backend](/include/cute) for CUTLASS 3.0 that defines a single Layout vocabulary type and an associated algebra of layouts for a much more expressive and composable abstraction for tensors, sets of parallel agents, and operations by said agents on tensors.
|
||||
* [A new conceptual operation hierarchy](media/docs/cutlass_3x_design.md) that replaces the architecture-centric hierarchy of CUTLASS 2.x and [documentation for CUTLASS 3.0's GEMM API changes](/media/docs/gemm_api_3x.md).
|
||||
|
||||
+20
-5
@@ -26,7 +26,8 @@
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
|
||||
cmake_policy(SET CMP0112 NEW)
|
||||
|
||||
if(cutlass_LOADED)
|
||||
# If CUTLASS has been previously fetched and loaded, don't do it again.
|
||||
@@ -39,7 +40,7 @@ endif()
|
||||
message(STATUS "CMake Version: ${CMAKE_VERSION}")
|
||||
set(IMPLICIT_CMAKE_CXX_STANDARD OFF CACHE BOOL "Do not explicitly specify -std=c++11 if set")
|
||||
|
||||
project(CUTLASS VERSION 3.0.0 LANGUAGES CXX)
|
||||
project(CUTLASS VERSION 3.1.0 LANGUAGES CXX)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/CUDA.cmake)
|
||||
|
||||
if (CUDA_VERSION VERSION_LESS 11.3)
|
||||
@@ -124,6 +125,17 @@ endif()
|
||||
set(CUTLASS_NVCC_ARCHS ${CUTLASS_NVCC_ARCHS_SUPPORTED} CACHE STRING "The SM architectures requested.")
|
||||
set(CUTLASS_NVCC_ARCHS_ENABLED ${CUTLASS_NVCC_ARCHS} CACHE STRING "The SM architectures to build code for.")
|
||||
|
||||
# Find unsupported and deprecated compute capabilities
|
||||
if (CUTLASS_NVCC_ARCHS_SUPPORTED)
|
||||
set(CUTLASS_NVCC_ARCHS_UNSUPPORTED ${CUTLASS_NVCC_ARCHS})
|
||||
list(REMOVE_ITEM CUTLASS_NVCC_ARCHS_UNSUPPORTED ${CUTLASS_NVCC_ARCHS_SUPPORTED})
|
||||
if (CUTLASS_NVCC_ARCHS_UNSUPPORTED)
|
||||
message(WARNING "Using unsupported or deprecated compute capabilities ${CUTLASS_NVCC_ARCHS_UNSUPPORTED}. Support may be removed in future versions.")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "No supported compute capabilities for CUDA ${CUDA_VERSION}.")
|
||||
endif()
|
||||
|
||||
# Special policy introduced in CMake 3.13
|
||||
if (POLICY CMP0076)
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
@@ -287,9 +299,10 @@ if (CUTLASS_ENABLE_OPENMP_TESTS)
|
||||
message(WARNING "CUTLASS_ENABLE_OPENMP_TESTS set but OpenMP not found.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
list(APPEND CUTLASS_CUDA_NVCC_FLAGS $<$<BOOL:${UNIX}>:-Xcompiler=-Wconversion>)
|
||||
list(APPEND CUTLASS_CUDA_NVCC_FLAGS $<$<BOOL:${UNIX}>:-Xcompiler=-fno-strict-aliasing>)
|
||||
if(UNIX)
|
||||
list(APPEND CUTLASS_CUDA_NVCC_FLAGS -Xcompiler=-Wconversion)
|
||||
list(APPEND CUTLASS_CUDA_NVCC_FLAGS -Xcompiler=-fno-strict-aliasing)
|
||||
endif()
|
||||
|
||||
# Don't leak lineinfo in release builds
|
||||
if (NOT CMAKE_BUILD_TYPE MATCHES "Release")
|
||||
@@ -838,3 +851,5 @@ install(
|
||||
################################################################################
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/NvidiaCutlassPackageConfig.cmake)
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||

|
||||
|
||||
# CUTLASS 3.0
|
||||
# CUTLASS 3.1
|
||||
|
||||
_CUTLASS 3.0 - January 2023_
|
||||
_CUTLASS 3.1 - April 2023_
|
||||
|
||||
CUTLASS is a collection of CUDA C++ template abstractions for implementing
|
||||
high-performance matrix-matrix multiplication (GEMM) and related computations at all levels
|
||||
@@ -31,33 +31,37 @@ See the [Quick Start Guide](/media/docs/quickstart.md) to get started quickly.
|
||||
See the [functionality listing](/media/docs/functionality.md) for the list of operations
|
||||
supported at each level of the execution model hierarchy.
|
||||
|
||||
CUTLASS 3.0 introduces a new core library, CuTe, to describe and manipulate tensors of threads and data.
|
||||
CUTLASS 3.0 introduced a new core library, CuTe, to describe and manipulate tensors of threads and data.
|
||||
CuTe is a collection of C++ CUDA template abstractions for defining and operating on hierarchically multidimensional layouts of threads and data. CuTe provides `Layout` and `Tensor` objects that compactly package the type, shape, memory space, and layout of data, while performing the complicated indexing for the user. This lets programmers focus on the logical descriptions of their algorithms while CuTe does the mechanical bookkeeping for them. With these tools, we can quickly design, implement, and modify all dense linear algebra operations.
|
||||
|
||||
The core abstractions of CuTe are hierarchically multidimensional layouts which can be composed with data arrays to represent tensors. The representation of layouts is powerful enough to represent nearly everything we need to implement efficient dense linear algebra. Layouts can also be combined and manipulated via functional composition, on which we build a large set of common operations such as tiling and partitioning.
|
||||
|
||||
CUTLASS 3.0 adopts CuTe throughout the GEMM hierarchy in its templates. This greatly simplifies the design
|
||||
CUTLASS 3.0 and beyond adopts CuTe throughout the GEMM hierarchy in its templates. This greatly simplifies the design
|
||||
and improves code composability and readability. More documentation specific to CuTe can be found in its [dedicated documentation directory](/media/docs/cute/00_quickstart.md).
|
||||
|
||||
In addition to GEMMs, CUTLASS implements high-performance convolution via the implicit GEMM algorithm. Implicit GEMM is the formulation of a convolution operation as a GEMM thereby taking advantage of CUTLASS's modular GEMM pipeline. This allows CUTLASS to build convolutions by reusing highly-optimized GEMM components.
|
||||
|
||||
# What's New in CUTLASS 3.0
|
||||
# What's New in CUTLASS 3.1
|
||||
|
||||
CUTLASS 3.0, as the next major version of the CUTLASS API, brings with it CuTe, a new programming model and backend designed for massively parallel heterogenous agents. Using CuTe, CUTLASS 3.0 provides implementations of GEMM kernels for the NVIDIA Hopper architecture.
|
||||
CUTLASS 3.1 is an update to CUTLASS adding:
|
||||
|
||||
- [CuTe-based layouts and layout algebra](/media/docs/cute/00_quickstart.md)
|
||||
- [A new GEMM template API](/media/docs/gemm_api_3x.md) that eschews the architecture-centric hierarchy of 2.x in favour of a new conceptual framing. Read more in the [3.0 design documentation](/media/docs/cutlass_3x_design.md).
|
||||
- Support for 4th generation Hopper Tensor Core instructions (WGMMA) through CuTe.
|
||||
- Support for Hopper asynchronous Tensor Memory Accelerator (TMA) instructions and associated transaction barriers through CuTe.
|
||||
- New warp-specialized GEMM kernels targeting Hopper TMA + WGMMA for speed-of-light GEMMs.
|
||||
- New warp-specialized persistent GEMM kernels targeting Hopper TMA + WGMMA.
|
||||
- Support for CUDA Threadblock Clusters and programmatic TMA multicast for greater execution and data locality.
|
||||
- A new way to instantiate default GEMM kernels using `CollectiveBuilder`s that supersede the 2.x `DefaultXConfiguration` types in favour a metaprogramming based kernel generator functionality. See [example 49](/examples/49_hopper_gemm_schedules_with_collective_builder/49_hopper_gemm_schedules_with_collective_builder.cu).
|
||||
- Extensions to the CUTLASS library and profiler to support CUTLASS 3.0 Hopper kernels, and a new format
|
||||
for kernel procedural names.
|
||||
- *Announcement*: CUTLASS plans to rename the GitHub branch `master` to `main` with a future release.
|
||||
- New CUTLASS Python interface that aims to provide an ease-of-use interface for instantiating, emitting, compiling, and running CUTLASS kernels via Python. More details [here](/python/README.md) and new [examples](/examples/python).
|
||||
- New [efficient epilogues](test/unit/gemm/device/sm90_gemm_f16_f16_f16_tensor_op_f32_cluster_warpspecialized_cooperative.cu#L783) for FP16 datatype using TMA for Hopper.
|
||||
- Support for [fused epilogues](test/unit/gemm/device/sm90_gemm_f16_f16_f16_tensor_op_f32_cluster_warpspecialized_cooperative_bias_elementwise.cu), such Bias, ReLU and GELU, using the new efficient epilogues.
|
||||
- New [warp-specialized TensorFloat-32 (TF32) GEMM kernels](test/unit/gemm/device/sm90_gemm_tf32_tf32_f32_tensor_op_f32_gmma_rs_cluster_warpspecialized.cu) targeting Hopper TMA.
|
||||
- New [*warp-specialized persistent cooperative*](include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized_cooperative.hpp) kernel design that improves performance on Hopper.
|
||||
- An [example](examples/51_hopper_gett) showcasing GEMM-Like Tensor-Tensor Contraction (GETT) capability on Hopper.
|
||||
- New Epilogue builders. Similar to mainloop builders (see [example 49](/examples/49_hopper_gemm_with_collective_builder/49_collective_builder.cu)), epilogue builders aim to generate the best-possible epilogue while exposing incremental opt-ins for greater customization.
|
||||
- Profiler support for overriding kernel and epilogue builder auto schedules for 3.x API kernels, allowing specific policies to be run in the CUTLASS profiler.
|
||||
- Changes to the [GEMM API 3.x](media/docs/gemm_api_3x.md), involving the host-facing arguments and the underlying `Params` structs.
|
||||
- *Announcement*:
|
||||
- The GitHub branch is renamed from `master` to `main` in this release.
|
||||
- A slight modification has been made to the ordering of arguments passed in to epilogues in 3.x kernels.
|
||||
Existing CUTLASS 3.x kernel invocations will need to be modified to reflect this change. 2.x kernels
|
||||
remain unaffected. See [#890](https://github.com/NVIDIA/cutlass/issues/890) for additional information.
|
||||
- The CUTLASS Python interface supersedes PyCUTLASS. PyCUTLASS has been moved to [/python/cutlass/backend](/python/cutlass/backend).
|
||||
Backward compatibility between the Python interface and PyCUTLASS will not be maintained moving forward.
|
||||
|
||||
## New architecture, compiler, and CUDA Toolkit requirements
|
||||
|
||||
Minimum requirements:
|
||||
|
||||
@@ -65,7 +69,7 @@ Minimum requirements:
|
||||
- Compiler: Must support at least C++17
|
||||
- CUDA Toolkit version: 11.4
|
||||
|
||||
CUTLASS 3.0 *removes support* for the following:
|
||||
Starting from CUTLASS 3.0, CUTLASS removed support for the following:
|
||||
|
||||
- Maxwell and Pascal GPU architectures
|
||||
- Ubuntu 16.04
|
||||
@@ -87,20 +91,21 @@ an [NVIDIA A100](https://www.nvidia.com/en-us/data-center/a100/) (NVIDIA Ampere
|
||||
and an [NVIDIA A40](https://www.nvidia.com/en-us/data-center/a40/) (NVIDIA Ampere architecture).
|
||||
CUTLASS 3.0 was compiled with the [CUDA 12.0 Toolkit](https://developer.nvidia.com/cuda-downloads).
|
||||
Tensor Core operations are implemented using CUDA's
|
||||
[mma instruction](https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#warp-level-matrix-instructions-mma).
|
||||
[mma](https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#warp-level-matrix-instructions-mma) and
|
||||
[wgmma](https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#asynchronous-warpgroup-level-matrix-instructions) instructions.
|
||||
|
||||
<p align="center"><img src=media/images/cutlass-2.9-implicit-gemm-performance.png></p>
|
||||
|
||||
When using CUTLASS building blocks to construct device-wide implicit gemm (Fprop, Dgrad, and Wgrad)
|
||||
kernels, CUTLASS performance is also comparable to cuDNN when running Resnet-50 layers on an [NVIDIA A100](https://www.nvidia.com/en-us/data-center/a100/)
|
||||
as shown in the above figure. Tensor Core operations are still implemented using CUDA's
|
||||
as shown in the above figure. Tensor Core operations are implemented using CUDA's
|
||||
[mma instruction](https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#warp-level-matrix-instructions-mma).
|
||||
|
||||
# Compatibility
|
||||
|
||||
CUTLASS requires a C++17 host compiler and
|
||||
performs best when built with the [**CUDA 12.0 Toolkit**](https://developer.nvidia.com/cuda-toolkit).
|
||||
It is also compatible with CUDA 11.4, CUDA 11.5, CUDA 11.6, CUDA 11.7, and CUDA 11.8.
|
||||
performs best when built with the [**CUDA 12.1 Toolkit**](https://developer.nvidia.com/cuda-toolkit).
|
||||
It is also compatible with CUDA 11.4, CUDA 11.5, CUDA 11.6, CUDA 11.7, CUDA 11.8, and CUDA 12.0.
|
||||
|
||||
## Operating Systems
|
||||
We have tested the following environments.
|
||||
@@ -112,6 +117,7 @@ We have tested the following environments.
|
||||
| Ubuntu 22.04 | GCC 11.2.0 |
|
||||
|
||||
Note: We plan to add Windows (MSVC) & Clang compiler support soon.
|
||||
Note: GCC 8.5.0 has known regressions regarding fold expressions and overloaded operators. Using GCC 7.5.0 or (preferred) GCC >= 9 is recommended.
|
||||
|
||||
## Hardware
|
||||
CUTLASS runs successfully on the following NVIDIA GPUs, and it is expected to be efficient on Volta, Turing, Ampere, Ada, and Hopper architecture based NVIDIA GPUs.
|
||||
@@ -131,9 +137,9 @@ CUTLASS runs successfully on the following NVIDIA GPUs, and it is expected to be
|
||||
|
||||
## Target Architecture
|
||||
|
||||
In general, PTX code generated for one target architecture can be run on future architectures (i.e., it is forward compatible). However, CUDA 12.0 introduces the concept of "architecture-accelerated features" whose PTX does not have forward compatibility guarantees. Several Hopper PTX instructions fall under this category of architecture-accelerated features, and thus require a `sm_90a` target architecture (note the "a" appended). For more details on this and other architecture-accelerated instructions, please refer to the [CUDA Documentation](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#feature-availability).
|
||||
In general, PTX code generated for one target architecture can be run on future architectures (i.e., it is forward compatible). However, CUDA 12.0 introduced the concept of "architecture-accelerated features" whose PTX does not have forward compatibility guarantees. Several Hopper PTX instructions fall under this category of architecture-accelerated features, and thus require a `sm_90a` target architecture (note the "a" appended). For more details on this and other architecture-accelerated instructions, please refer to the [CUDA Documentation](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#feature-availability).
|
||||
|
||||
The target architecture information is passed on to CUTLASS via the cmake flag `CUTLASS_NVCC_ARCHS`. In order to maximize performance on Hopper GH100, users are required to build CUTLASS with `90a` as the target architecture. If a user accidentally builds a kernel which uses SM90a features (e.g. Hopper Tensor Core Instructions), using the SM90 target (note the lack of "a"), with either CTK 12.0 or 11.8, the kernel is expected to fail with a runtime error.
|
||||
The target architecture information is passed on to CUTLASS via the cmake flag `CUTLASS_NVCC_ARCHS`. In order to maximize performance on Hopper GH100, users are required to build CUTLASS with `90a` as the target architecture. If a user accidentally builds a kernel which uses SM90a features (e.g. Hopper Tensor Core Instructions), using the SM90 target (note the lack of "a"), with either CTK 12 or 11.8, the kernel is expected to fail with a runtime error.
|
||||
|
||||
```
|
||||
cmake .. -DCUTLASS_NVCC_ARCHS="90a"
|
||||
@@ -558,4 +564,3 @@ SPDX-License-Identifier: BSD-3-Clause
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ endif()
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG 0fe9660
|
||||
GIT_TAG v1.13.0
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(googletest)
|
||||
|
||||
@@ -555,6 +555,7 @@ Result profile_convolution(Options const &options) {
|
||||
LayoutOutput,
|
||||
ElementComputeEpilogue,
|
||||
ElementAccumulator,
|
||||
ElementOutput,
|
||||
cutlass::NumericConverterClamp<ElementOutput, ElementComputeEpilogue>
|
||||
>(
|
||||
problem_size,
|
||||
|
||||
@@ -31,83 +31,181 @@
|
||||
|
||||
/**
|
||||
|
||||
This example shows how to run convolution kernels using functions and data structures
|
||||
provided by CUTLASS using tensor cores; which we run on a NVIDIA Ampere GPU.
|
||||
This example shows how to run CUTLASS's convolution kernels
|
||||
based on the Implicit GEMM algorithm, that use the Tensor Cores
|
||||
on an NVIDIA Ampere GPU.
|
||||
|
||||
Writing a single high performance convolution kernel is hard but do-able. Whereas writing
|
||||
high performance kernels at scale which works for multiple problem sizes with good abstractions is
|
||||
really hard. CUTLASS solves this problem by providing simplified abstractions to compose
|
||||
multiple sections of implicit gemm kernel. When used properly, the kernels can hit peak performance
|
||||
of GPU easily.
|
||||
Writing a single high-performance convolution kernel is hard enough,
|
||||
let alone writing kernels that perform well for multiple problem sizes
|
||||
and use good software abstractions.
|
||||
CUTLASS provides simplified abstractions
|
||||
to compose multiple sections of a convolution kernel.
|
||||
When used properly, the kernels can reach peak GPU performance.
|
||||
|
||||
CUTLASS divides a kernel into hierarchical composable sections. Which means, at each thread, warp
|
||||
and thread-block level, they compute on their own tile-size with higher level of tile sizes being
|
||||
composed from lower level ones. Multiple thread-tiles (tile size each thread computes) can be used
|
||||
to form warp-tiles (tile size each warp computes) and multiple warp tiles can be used to compute
|
||||
threadblock-tile (tile size computed by a threadblock).
|
||||
CUTLASS divides a kernel into hierarchical composable sections
|
||||
for each level of the GPU hardware hierarchy:
|
||||
thread, warp, and threadblock.
|
||||
Each section computes on its own tile shape,
|
||||
with each higher level's tile shape
|
||||
being composed from lower-level tile shapes.
|
||||
Multiple thread tiles (the tile shape each thread computes)
|
||||
can be used to form warp tiles (the tile shape each warp computes),
|
||||
and multiple warp tiles can be used to compute threadblock tiles
|
||||
(the tile shape computed by a threadblock).
|
||||
|
||||
In thie example, we split variable initialization into
|
||||
1. Setting up data properties : describes how tensors are laid out in the memory and how the kernel
|
||||
can view them (logical to physical mapping)
|
||||
2. Setting up computation properties : describes how the above set tensors will be used to compute
|
||||
output of convolution.
|
||||
In thie example, we split variable initialization into two parts.
|
||||
|
||||
First, we setup the data types of the input tensor A, weights' tensor B and output tensor C along
|
||||
with alpha, beta as the equation for convolution is C = alpha * Conv2dFprop(A, B) + beta * C. In CUTLASS,
|
||||
the kernels first compute Conv2dFprop(A, B) and leave the rest of the computation to end of the kernel as
|
||||
alpha * X + beta * C is a simple element-wise operation on X (Conv2dFprop(A, B)) and C. We call this as
|
||||
epilogue of kernel. Hence, we setup data types for alpha and beta to be equal to
|
||||
ElementComputeEpilogue = float. We use the data type for elements in input tensor A and B as
|
||||
cutlass::half_t. We convey this to CUTLASS kernel by initializing template variables ElementAccumulator (float),
|
||||
ElementComputeEpilogue (float), ElementInputA (cutlass::half_t), ElementInputB (cutlass::half_t),
|
||||
ElementOutput (float). Communicating just the data type is not enough. As the data is laid out
|
||||
linearly in memory, we have to convey the layout of tensors. We do that by initializing template
|
||||
variables LayoutInputA, LayoutInputB and LayoutOutput to TensorNHWC cutlass variable. Next, we setup
|
||||
rules to comptue alpha * X + beta * C which is called epilogue of the kernel. We initialize template
|
||||
variable EpilogueOp, which takes the data type of output ElementOutput (float), the number of
|
||||
elements per vector memory access (8), data type of accumulator (float) and data type of
|
||||
computation of linear combination (alpha * X + beta * C).
|
||||
1. Setting up data properties: describes how tensors are laid out in the memory
|
||||
and how the kernel can view them (logical to physical mapping)
|
||||
|
||||
Now that we setup the properties of data, we have to setup properties of computation.
|
||||
2. Setting up computation properties: describes how the above tensors
|
||||
will be used to compute the output of convolution
|
||||
|
||||
Second, we create template variables of tile sizes for thread-block, warp and mma-op to 128x128x64,
|
||||
64x64x64, 16x8x16 (MxNxK) respectively. When passed to instantiate CUTLASS Implicit GEMM kernel, it
|
||||
internally deduces the amount of threads needed per thread-block, amount of shared memory, storing
|
||||
data in bank-conflict free manner, and ton of other variables required to compose, initialize and
|
||||
launch a high performance Implicit GEMM kernel. This is the beauty of CUTLASS, it relieves developer
|
||||
from understanding and coding complicated hardware optimizations which can easily go wrong.
|
||||
We begin by setting up the data types
|
||||
of all the input and output elements of a convolution.
|
||||
A convolution computes
|
||||
C = alpha * Conv2dFprop(A, B) + beta * C,
|
||||
so we set up data types for the input tensor A,
|
||||
weights tensor B, output tensor C,
|
||||
and the scaling factors alpha and beta.
|
||||
CUTLASS divides the convolution into two parts:
|
||||
the "mainloop" that computes X = Conv2dFprop(A, B),
|
||||
and the "epilogue" that computes C = alpha * X + beta * C.
|
||||
The epilogue is an element-wise operation on X and C.
|
||||
In this case, it is a linear combination,
|
||||
but other epilogues are possible.
|
||||
|
||||
CUTLASS also supports multiple MMA pipelines in a threadblock. What are MMA pipelines? MMA pipelines
|
||||
constitute the whole process of loading input data from global memory to shared memory, loading data
|
||||
from shared memory to registers, doing matrix multiplication, store to global memory. The below flow
|
||||
sequence shows a typical mma multistage pipeline.
|
||||
(see include/cutlass/conv/threadblock/implicit_gemm_multistage.h)
|
||||
In this example, we want
|
||||
|
||||
tensor in global memory --cp_async--> tile in shared memory --smem loads--> registers
|
||||
--mma--> registers --global stores--> output to global memory
|
||||
* the scaling factors alpha and beta to be float,
|
||||
|
||||
NVIDIA Ampere uses `cp_async` to build multistage software pipeline to better hide latencies.
|
||||
* the elements of A and B to be cutlass::half_t
|
||||
(a 16-bit floating-point type),
|
||||
|
||||
* the elements of C to be float, and
|
||||
|
||||
There are few more template variables initialized such as, which threadblock tile of output matrix
|
||||
is done which threadblock launched on an SM, CUDA SM architecture of GPU you want to run on.
|
||||
* intermediate sums to be accumulated in float.
|
||||
|
||||
These are all put together to create a template variable which describes CUTLASS Implicit GEMM
|
||||
kernel using cutlass::conv::device::ImplicitGemm template.
|
||||
We convey this to the CUTLASS kernel
|
||||
by setting the following template parameters.
|
||||
|
||||
The next step is to initialize physical data, instantiate and initialize CUTLASS kernel and run it.
|
||||
We use CUTLASS utilities to initialize, fill, compare tensors as they are simple and doesn't come
|
||||
in the way of learning CUTLASS.
|
||||
* alpha and beta: ElementComputeEpilogue = float
|
||||
|
||||
Once all the tensors are initialized and filled with data, create arguments tuple to launch CUTLASS
|
||||
kernel which takes problem size (N = 1, H = 64, W = 64, C = 128), filter size (K = 64,
|
||||
R = 3, S = 3, C = 128 ), padding, strides, dilation, tensors, alpha, beta and the
|
||||
important one, split k-dimension factor. Along with that, we query CUTLASS if any scratch-space
|
||||
memory required by the kernel we instantiated. If yes, we create it and pass it along with other
|
||||
arguments created to initialize CUTLASS kernel then, the kernel is launched.
|
||||
* Elements of input tensor A: ElementInputA = cutlass::half_t
|
||||
|
||||
In this example, we later on launch a reference convolution kernel (from CUTLASS utilities) to
|
||||
compare if the output from CUTLASS kernel is same as the reference implicit GEMM kernel.
|
||||
* Elements of input tensor B: ElementInputB = cutlass::half_t
|
||||
|
||||
* Elements of output tensor C: ElementOutput = float
|
||||
|
||||
* Accumulation type: ElementAccumulator = float
|
||||
|
||||
Next, we describe the layout of the input and output tensors.
|
||||
We convey this to the CUTLASS kernel
|
||||
by setting the following template parameters.
|
||||
|
||||
* Layout of input tensor A: LayoutInputA = TensorNHWC
|
||||
|
||||
* Layout of input tensor B: LayoutInputB = TensorNHWC
|
||||
|
||||
* Layout of output tensor C: LayoutOutput = TensorNHWC
|
||||
|
||||
After that, we set up rules to compute the epilogue.
|
||||
The epilogue in this case is a simple linear combination
|
||||
C = alpha * X + beta * C.
|
||||
Thus, we set the kernel's template parameter EpilogueOp
|
||||
to LinearCombination. LinearCombination itself
|
||||
has template parameters:
|
||||
|
||||
* the element type of the output tensor (ElementOutput),
|
||||
|
||||
* the number of elements per vector memory access (8),
|
||||
|
||||
* the data type of the accumulator (ElementAccumulator),
|
||||
|
||||
* and the data type used to compute the linear combination
|
||||
(ElementComputeEpilogue).
|
||||
|
||||
We then define the tile shapes
|
||||
that each level of the computation uses.
|
||||
We define these as types that encode the tile shapes
|
||||
as compile-time integer values.
|
||||
Each shape expresses the dimensions M x N x K.
|
||||
Here, the letters refer to the dimensions
|
||||
of a matrix-matrix multiply.
|
||||
|
||||
* ThreadblockShape defines the threadblock tile shape
|
||||
as 128 x 128 x 64.
|
||||
|
||||
* WarpShape defines the warp tile shape as 64 x 64 x 64.
|
||||
|
||||
* InstructionShape defines the MMA
|
||||
(matrix multiply-accumulate) operation shape
|
||||
as 16 x 8 x 16.
|
||||
|
||||
These types become template arguments
|
||||
of the kernel properties type
|
||||
cutlass::conv::kernel::DefaultConv2dFprop.
|
||||
The kernel uses these shapes to deduce
|
||||
the number of threads needed per threadblock,
|
||||
the required amount of shared memory,
|
||||
the internal layouts needed to access
|
||||
shared memory without bank conflicts,
|
||||
and many other properties that the kernel needs
|
||||
for good performance.
|
||||
CUTLASS deduces all these properties automatically,
|
||||
so that users don't have to.
|
||||
DefaultConv2dFprop accepts other template parameters
|
||||
that describe things like the target CUDA SM architecture.
|
||||
|
||||
CUTLASS also supports multiple MMA pipelines in a threadblock.
|
||||
An MMA pipeline constitutes the whole process
|
||||
of loading input data from global memory to shared memory,
|
||||
loading data from shared memory to registers,
|
||||
doing matrix multiplication,
|
||||
and storing the result to global memory.
|
||||
The below flow sequence shows a typical MMA multistage pipeline
|
||||
(see include/cutlass/conv/threadblock/implicit_gemm_multistage.h).
|
||||
|
||||
tensor in global memory
|
||||
--cp_async-->
|
||||
tile in shared memory
|
||||
--smem loads-->
|
||||
registers
|
||||
--mma-->
|
||||
registers
|
||||
--global stores-->
|
||||
output to global memory
|
||||
|
||||
On NVIDIA Ampere, the kernel uses `cp_async`
|
||||
to build a multistage software pipeline.
|
||||
This helps it better hide latency.
|
||||
|
||||
At this point, we can define the actual CUTLASS kernel type
|
||||
as the alias ImplicitGemm, a specialization of
|
||||
cutlass::conv::device::ImplicitGemmConvolution.
|
||||
The latter accepts the kernel properties type alias
|
||||
Conv2dFpropKernel as its one template argument.
|
||||
|
||||
This example then sets up a test problem
|
||||
and arguments to the kernel.
|
||||
We use CUTLASS utilities to allocate
|
||||
the input and output tensors
|
||||
and fill them with sample input data.
|
||||
We then create the kernel arguments
|
||||
as an instance of ImplicitGemm::Arguments.
|
||||
The arguments include
|
||||
the problem size (N = 1, H = 64, W = 64, C = 128),
|
||||
filter size (K = 64, R = 3, S = 3, C = 128),
|
||||
padding, strides, dilation, tensors, alpha, beta,
|
||||
and the split k-dimension factor.
|
||||
We also query CUTLASS if the kernel we instantiated
|
||||
requires any memory for scratch space.
|
||||
If yes, we reserve scratch space and pass it along
|
||||
with other arguments to initialize the CUTLASS kernel.
|
||||
|
||||
After lauching the CUTLASS kernel, this example runs
|
||||
a reference convolution kernel (from CUTLASS utilities)
|
||||
to check correctness.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
@@ -131,8 +229,8 @@ compare if the output from CUTLASS kernel is same as the reference implicit GEMM
|
||||
|
||||
#include "helper.h"
|
||||
|
||||
// The code section below describes datatype for input, output tensors and computation between
|
||||
// elements
|
||||
// Data types for input and output tensors
|
||||
// and computation between elements
|
||||
using ElementAccumulator = float; // Data type of accumulator
|
||||
using ElementComputeEpilogue = float; // Data type of epilogue computation (alpha, beta)
|
||||
using ElementInputA = cutlass::half_t; // Data type of elements in input tensor
|
||||
@@ -143,39 +241,40 @@ using LayoutInputA = cutlass::layout::TensorNHWC;
|
||||
using LayoutInputB = cutlass::layout::TensorNHWC;
|
||||
using LayoutOutput = cutlass::layout::TensorNHWC;
|
||||
|
||||
// This code section describes whether you want to use tensor cores or regular SIMT cores on GPU SM
|
||||
// Whether to use tensor cores or regular SIMT cores on GPU SM
|
||||
using MMAOp = cutlass::arch::OpClassTensorOp;
|
||||
|
||||
// This code section describes CUDA SM architecture number
|
||||
// SM architecture number
|
||||
using SmArch = cutlass::arch::Sm80;
|
||||
|
||||
// This code section describes the tile size a thread block will compute
|
||||
using ThreadblockShape = cutlass::gemm::GemmShape<128, 128, 64>; // Threadblock tile shape
|
||||
// Threadblock tile shape
|
||||
using ThreadblockShape = cutlass::gemm::GemmShape<128, 128, 64>;
|
||||
|
||||
// This code section describes tile size a warp will compute
|
||||
using WarpShape = cutlass::gemm::GemmShape<64, 64, 64>; // Warp tile shape
|
||||
// Warp tile shape
|
||||
using WarpShape = cutlass::gemm::GemmShape<64, 64, 64>;
|
||||
|
||||
// This code section describes the size of MMA op
|
||||
using InstructionShape = cutlass::gemm::GemmShape<16, 8, 16>; // TensorCore instruction shape
|
||||
// MMA (Tensor Core instruction, in this case) tile shape
|
||||
using InstructionShape = cutlass::gemm::GemmShape<16, 8, 16>;
|
||||
|
||||
// This code section describes how threadblocks are scheduled on GPU
|
||||
// How the kernel schedules threadblocks
|
||||
using SwizzleThreadBlock = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>;
|
||||
|
||||
// Number of pipelines you want to use
|
||||
// Number of pipeline stages to use
|
||||
constexpr int NumStages = 3;
|
||||
|
||||
// This code section describe iterator algorithm selected is Analytic or Optimized
|
||||
// Which iterator algorithm to use: Analytic or Optimized
|
||||
static cutlass::conv::IteratorAlgorithm const IteratorAlgorithm = cutlass::conv::IteratorAlgorithm::kOptimized;
|
||||
|
||||
// This code section describes the epilogue part of the kernel, we use default value
|
||||
// The epilogue part of the kernel
|
||||
using EpilogueOp = cutlass::epilogue::thread::LinearCombination<
|
||||
ElementOutput, // Data type of output matrix.
|
||||
128 / cutlass::sizeof_bits<ElementOutput>::value, // The number of elements per vectorized.
|
||||
128 / cutlass::sizeof_bits<ElementOutput>::value, // The number of elements per vectorized
|
||||
// memory access. This becomes the vector width of
|
||||
// math instructions in the epilogue too.
|
||||
ElementAccumulator, // Data type of accumulator
|
||||
ElementComputeEpilogue>; // Data type for alpha/beta in linear combination
|
||||
|
||||
// Kernel properties type
|
||||
using Conv2dFpropKernel = typename cutlass::conv::kernel::DefaultConv2dFprop<
|
||||
ElementInputA, LayoutInputA,
|
||||
ElementInputB, LayoutInputB,
|
||||
@@ -193,6 +292,7 @@ using Conv2dFpropKernel = typename cutlass::conv::kernel::DefaultConv2dFprop<
|
||||
IteratorAlgorithm
|
||||
>::Kernel;
|
||||
|
||||
// Type of the actual kernel
|
||||
using ImplicitGemm = cutlass::conv::device::ImplicitGemmConvolution<Conv2dFpropKernel>;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -230,7 +330,7 @@ struct Options {
|
||||
beta(0),
|
||||
benchmark(false) { }
|
||||
|
||||
// Verify the problem size is compatible with the CUTLASS Convolution implementation.
|
||||
// Verify that the problem size is compatible with CUTLASS's convolution implementation
|
||||
bool valid() {
|
||||
|
||||
//
|
||||
@@ -256,7 +356,7 @@ struct Options {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Updates input and filter sizes
|
||||
/// Update input and filter sizes
|
||||
void update(
|
||||
cutlass::Tensor4DCoord input_size,
|
||||
cutlass::Tensor4DCoord filter_size) {
|
||||
@@ -270,7 +370,7 @@ struct Options {
|
||||
padding.c() = filter_size.w() / 2;
|
||||
}
|
||||
|
||||
// Parses the command line
|
||||
// Parse command-line arguments
|
||||
void parse(int argc, char const **args) {
|
||||
cutlass::CommandLine cmd(argc, args);
|
||||
|
||||
@@ -302,11 +402,11 @@ struct Options {
|
||||
cmd.get_cmd_line_argument("k", filter_size.n());
|
||||
cmd.get_cmd_line_argument("r", filter_size.h());
|
||||
cmd.get_cmd_line_argument("s", filter_size.w());
|
||||
filter_size.c() = input_size.c();
|
||||
filter_size.c() = input_size.c();
|
||||
|
||||
cmd.get_cmd_line_argument("alpha", alpha);
|
||||
cmd.get_cmd_line_argument("beta", beta);
|
||||
|
||||
|
||||
cmd.get_cmd_line_argument("iterations", iterations);
|
||||
cmd.get_cmd_line_argument("tag", tag);
|
||||
|
||||
@@ -320,12 +420,12 @@ struct Options {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the usage statement.
|
||||
/// Print an explanation of the command-line arguments
|
||||
std::ostream & print_usage(std::ostream &out) const {
|
||||
|
||||
out << "16_ampere_tensorop_conv2dfprop example\n\n"
|
||||
<< " This example uses Ampere's Tensor Core operators on F16 data types to compute\n"
|
||||
<< " forward convolution on tensors of layout NHWC.\n\n"
|
||||
<< " This example uses Ampere's Tensor Core operators on F16 data types\n"
|
||||
<< " to compute forward convolution on tensors of layout NHWC.\n\n"
|
||||
<< "Options:\n\n"
|
||||
<< " --help If specified, displays this usage statement.\n\n"
|
||||
<< " --n=<int> Input tensor extent N\n"
|
||||
@@ -350,7 +450,7 @@ struct Options {
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/// Computes the output tensor size (NPQK)
|
||||
cutlass::Tensor4DCoord output_size() const {
|
||||
return cutlass::Tensor4DCoord(
|
||||
@@ -360,19 +460,20 @@ struct Options {
|
||||
filter_size.n());
|
||||
}
|
||||
|
||||
/// Compute performance in GFLOP/s
|
||||
/// Compute performance in Gflop/s
|
||||
///
|
||||
/// Gflop/s stands for billions (10^9) of
|
||||
/// floating-point operations per second (Gflop/s).
|
||||
double gflops(double runtime_s) const {
|
||||
|
||||
// Number of multiply-adds = NPQK * CRS
|
||||
int64_t fmas = output_size().product() * int64_t(filter_size.h() * filter_size.w() * filter_size.c());
|
||||
|
||||
|
||||
// Two flops per multiply-add
|
||||
return 2.0 * double(fmas) / double(1.0e9) / runtime_s;
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct Result {
|
||||
double runtime_ms;
|
||||
double gflops;
|
||||
@@ -380,14 +481,14 @@ struct Result {
|
||||
cutlass::Status reference_check;
|
||||
cudaError_t error;
|
||||
|
||||
Result():
|
||||
runtime_ms(0),
|
||||
Result():
|
||||
runtime_ms(0),
|
||||
gflops(0),
|
||||
status(cutlass::Status::kSuccess),
|
||||
reference_check(cutlass::Status::kInvalid),
|
||||
error(cudaSuccess) { }
|
||||
|
||||
static std::ostream & print_header(std::ostream &out, Options const &options) {
|
||||
static std::ostream& print_header(std::ostream &out, Options const &options) {
|
||||
|
||||
if (!options.tag.empty()) {
|
||||
out << "Name,";
|
||||
@@ -404,7 +505,7 @@ struct Result {
|
||||
out << options.tag << ",";
|
||||
}
|
||||
|
||||
out
|
||||
out
|
||||
<< "conv_" << idx << ","
|
||||
<< options.input_size.n() << ","
|
||||
<< options.input_size.h() << ","
|
||||
@@ -420,8 +521,6 @@ struct Result {
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Runs one benchmark
|
||||
Result profile_convolution(Options const &options) {
|
||||
|
||||
@@ -441,7 +540,7 @@ Result profile_convolution(Options const &options) {
|
||||
// Initialize tensors
|
||||
//
|
||||
|
||||
// Fill tensor A on host with uniform-distribution random data
|
||||
// Fill tensor A on host with uniformly distributed random data
|
||||
cutlass::reference::host::TensorFillRandomUniform(
|
||||
tensor_a.host_view(),
|
||||
1,
|
||||
@@ -449,7 +548,7 @@ Result profile_convolution(Options const &options) {
|
||||
ElementInputA(-8),
|
||||
0);
|
||||
|
||||
// Fill tensor B on host with uniform-distribution random data
|
||||
// Fill tensor B on host with uniformly distributed random data
|
||||
cutlass::reference::host::TensorFillRandomUniform(
|
||||
tensor_b.host_view(),
|
||||
1,
|
||||
@@ -457,7 +556,7 @@ Result profile_convolution(Options const &options) {
|
||||
ElementInputB(-8),
|
||||
0);
|
||||
|
||||
// Fill tensor C on host with uniform-distribution random data
|
||||
// Fill tensor C on host with uniformly distributed random data
|
||||
cutlass::reference::host::TensorFillRandomUniform(
|
||||
tensor_c.host_view(),
|
||||
1,
|
||||
@@ -490,7 +589,7 @@ Result profile_convolution(Options const &options) {
|
||||
int split_k_slices = 1;
|
||||
|
||||
// Construct Conv2dProblemSize with user defined output size
|
||||
cutlass::conv::Conv2dProblemSize problem_size(
|
||||
cutlass::conv::Conv2dProblemSize problem_size(
|
||||
options.input_size,
|
||||
options.filter_size,
|
||||
options.padding,
|
||||
@@ -501,7 +600,7 @@ Result profile_convolution(Options const &options) {
|
||||
split_k_slices
|
||||
);
|
||||
|
||||
// Construct ImplicitGemm::Argument structure with conv2d
|
||||
// Construct ImplicitGemm::Argument structure with conv2d
|
||||
// problem size, data pointers, and epilogue values
|
||||
typename ImplicitGemm::Arguments arguments{
|
||||
problem_size,
|
||||
@@ -539,7 +638,7 @@ Result profile_convolution(Options const &options) {
|
||||
//
|
||||
// Optional reference check
|
||||
//
|
||||
|
||||
|
||||
if (options.reference_check) {
|
||||
std::cout << "Verification on host...\n";
|
||||
|
||||
@@ -552,8 +651,7 @@ Result profile_convolution(Options const &options) {
|
||||
ElementOutput,
|
||||
LayoutOutput,
|
||||
ElementComputeEpilogue,
|
||||
ElementAccumulator,
|
||||
cutlass::NumericConverter<ElementOutput, ElementComputeEpilogue>
|
||||
ElementAccumulator
|
||||
>(
|
||||
problem_size,
|
||||
tensor_a.host_ref(),
|
||||
@@ -564,7 +662,7 @@ Result profile_convolution(Options const &options) {
|
||||
options.beta
|
||||
);
|
||||
|
||||
// Check if output from CUTLASS kernel and reference kernel are equal or not
|
||||
// Check if CUTLASS kernel and reference kernel produced the same output
|
||||
tensor_d.sync_host();
|
||||
|
||||
bool passed = cutlass::reference::host::TensorEquals(
|
||||
@@ -589,14 +687,14 @@ Result profile_convolution(Options const &options) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "16_ampere_workspace_conv2dfprop_"
|
||||
<< options.input_size.n() << "x" << options.input_size.h() << "x" << options.input_size.w() << "x" << options.input_size.c()
|
||||
<< options.input_size.n() << "x" << options.input_size.h() << "x" << options.input_size.w() << "x" << options.input_size.c()
|
||||
<< "_"
|
||||
<< options.filter_size.n() << "x" << options.filter_size.h() << "x" << options.filter_size.w() << "x" << options.filter_size.c()
|
||||
<< options.filter_size.n() << "x" << options.filter_size.h() << "x" << options.filter_size.w() << "x" << options.filter_size.c()
|
||||
<< ".dat";
|
||||
|
||||
std::ofstream output_workspace(ss.str());
|
||||
|
||||
output_workspace
|
||||
output_workspace
|
||||
<< "Input = \n" << tensor_a.host_view() << "\n\n"
|
||||
<< "Filters = \n" << tensor_b.host_view() << "\n\n";
|
||||
|
||||
@@ -616,7 +714,7 @@ Result profile_convolution(Options const &options) {
|
||||
if (options.measure_performance) {
|
||||
|
||||
cudaEvent_t events[2];
|
||||
|
||||
|
||||
for (auto & event : events) {
|
||||
result.error = cudaEventCreate(&event);
|
||||
if (result.error != cudaSuccess) {
|
||||
@@ -632,7 +730,7 @@ Result profile_convolution(Options const &options) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Launch a sequence of implicit GEMM operations on the device
|
||||
// Launch a sequence of implicit GEMM operations on the device.
|
||||
for (int iteration = 0; iteration < options.iterations; ++iteration) {
|
||||
result.status = implicit_gemm_op();
|
||||
CUTLASS_CHECK(result.status);
|
||||
@@ -652,7 +750,7 @@ Result profile_convolution(Options const &options) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Measure elapsed runtime
|
||||
// Measure elapsed runtime.
|
||||
float runtime_ms = 0;
|
||||
result.error = cudaEventElapsedTime(&runtime_ms, events[0], events[1]);
|
||||
if (result.error != cudaSuccess) {
|
||||
@@ -660,7 +758,7 @@ Result profile_convolution(Options const &options) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Print average runtime and GFLOPs.
|
||||
// Print average run time and floating-point throughput (Gflop/s).
|
||||
result.runtime_ms = double(runtime_ms) / double(options.iterations);
|
||||
result.gflops = options.gflops(result.runtime_ms / 1000.0);
|
||||
|
||||
@@ -673,8 +771,6 @@ Result profile_convolution(Options const &options) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int main(int argc, char const **args) {
|
||||
|
||||
bool notSupported = false;
|
||||
@@ -701,7 +797,7 @@ int main(int argc, char const **args) {
|
||||
}
|
||||
|
||||
Options options;
|
||||
|
||||
|
||||
options.parse(argc, args);
|
||||
|
||||
if (options.help) {
|
||||
@@ -768,5 +864,3 @@ int main(int argc, char const **args) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -470,8 +470,7 @@ Result profile_convolution(Options const &options) {
|
||||
ElementOutput,
|
||||
LayoutOutput,
|
||||
ElementComputeEpilogue,
|
||||
ElementAccumulator,
|
||||
cutlass::NumericConverter<ElementOutput, ElementComputeEpilogue>
|
||||
ElementAccumulator
|
||||
>(
|
||||
problem_size,
|
||||
tensor_a.host_ref(),
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
leading dimensions and problem sizes are stored in arrays in GMEM.
|
||||
|
||||
This differs from "Batched Array" GEMM because the size of each GEMM problem in the Grouped GEMM
|
||||
concept may be distinct.
|
||||
concept may be distinct.
|
||||
|
||||
This benchmark program initializes a workspace with random problem sizes for a given number of
|
||||
groups. Command line options enable overriding M, N, and/or K dimensions with uniform values to
|
||||
@@ -186,7 +186,7 @@ struct Options {
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
//
|
||||
|
||||
Options():
|
||||
help(false),
|
||||
@@ -216,7 +216,7 @@ struct Options {
|
||||
cmd.get_cmd_line_argument("alignment", alignment, 8);
|
||||
cmd.get_cmd_line_argument("groups", problem_count, 15);
|
||||
cmd.get_cmd_line_argument("alpha", alpha, 1.0f);
|
||||
cmd.get_cmd_line_argument("beta", beta, 0.0f);
|
||||
cmd.get_cmd_line_argument("beta", beta, 0.0f);
|
||||
cmd.get_cmd_line_argument("iterations", iterations, 20);
|
||||
cmd.get_cmd_line_argument("streams", cuda_streams, 0);
|
||||
cmd.get_cmd_line_argument("verbose", verbose, false);
|
||||
@@ -455,13 +455,13 @@ struct Options {
|
||||
/// Compute performance in GFLOP/s
|
||||
double gflops(double runtime_s) const {
|
||||
|
||||
// Number of real-valued multiply-adds
|
||||
// Number of real-valued multiply-adds
|
||||
int64_t fmas = int64_t();
|
||||
|
||||
for (auto const & problem : problem_sizes) {
|
||||
fmas += problem.product();
|
||||
}
|
||||
|
||||
|
||||
// Two flops per multiply-add
|
||||
return 2.0 * double(fmas) / double(1.0e9) / runtime_s;
|
||||
}
|
||||
@@ -546,7 +546,7 @@ public:
|
||||
template <typename Element>
|
||||
void initialize_tensor(
|
||||
Element *ptr,
|
||||
size_t capacity,
|
||||
size_t capacity,
|
||||
cutlass::Distribution::Kind dist_kind,
|
||||
uint32_t seed) {
|
||||
|
||||
@@ -578,7 +578,7 @@ public:
|
||||
|
||||
cutlass::reference::device::BlockFillRandomUniform(
|
||||
ptr, capacity, seed, scope_max, scope_min, 0);
|
||||
}
|
||||
}
|
||||
else if (dist_kind == cutlass::Distribution::Gaussian) {
|
||||
|
||||
cutlass::reference::device::BlockFillRandomGaussian(
|
||||
@@ -589,7 +589,7 @@ public:
|
||||
// Fill with increasing elements
|
||||
cutlass::reference::device::BlockFillSequential(
|
||||
ptr, capacity, Element(1), Element());
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
// Fill with all 1s
|
||||
@@ -674,13 +674,13 @@ public:
|
||||
|
||||
ptr_A.reset(problem_count());
|
||||
ptr_A.copy_from_host(ptr_A_host.data());
|
||||
|
||||
|
||||
ptr_B.reset(problem_count());
|
||||
ptr_B.copy_from_host(ptr_B_host.data());
|
||||
|
||||
|
||||
ptr_C.reset(problem_count());
|
||||
ptr_C.copy_from_host(ptr_C_host.data());
|
||||
|
||||
|
||||
ptr_D.reset(problem_count());
|
||||
ptr_D.copy_from_host(ptr_D_host.data());
|
||||
|
||||
@@ -712,7 +712,7 @@ public:
|
||||
MatrixCoord extent_A{problem.m(), problem.k()};
|
||||
MatrixCoord extent_B{problem.k(), problem.n()};
|
||||
MatrixCoord extent_C{problem.m(), problem.n()};
|
||||
|
||||
|
||||
cutlass::TensorView<ElementA, LayoutA> view_A(block_A.get() + offset_A.at(i), layout_A, extent_A);
|
||||
cutlass::TensorView<ElementB, LayoutB> view_B(block_B.get() + offset_B.at(i), layout_B, extent_B);
|
||||
cutlass::TensorView<ElementC, LayoutC> view_C(block_C.get() + offset_C.at(i), layout_C, extent_C);
|
||||
@@ -724,18 +724,18 @@ public:
|
||||
cutlass::reference::device::GemmComplex<
|
||||
ElementA, LayoutA,
|
||||
ElementB, LayoutB,
|
||||
ElementC, LayoutC,
|
||||
ElementC, LayoutC,
|
||||
ElementCompute, ElementAccumulator
|
||||
>(
|
||||
problem,
|
||||
options.alpha,
|
||||
options.alpha,
|
||||
view_A,
|
||||
Gemm::kTransformA,
|
||||
view_B,
|
||||
Gemm::kTransformB,
|
||||
options.beta,
|
||||
view_C,
|
||||
view_Ref_device,
|
||||
options.beta,
|
||||
view_C,
|
||||
view_Ref_device,
|
||||
ElementAccumulator(0)
|
||||
);
|
||||
|
||||
@@ -781,8 +781,8 @@ public:
|
||||
std::cout << "Conventionally executed as " << this->options.problem_bins.size() << " batched GEMMs:\n";
|
||||
for (auto const & bin : this->options.problem_bins) {
|
||||
|
||||
std::cout << " [" << bin_idx << "]: "
|
||||
<< bin.first.m() << "-by-" << bin.first.n() << "-by-" << bin.first.k()
|
||||
std::cout << " [" << bin_idx << "]: "
|
||||
<< bin.first.m() << "-by-" << bin.first.n() << "-by-" << bin.first.k()
|
||||
<< ", batch count: " << bin.second.size() << "\n";
|
||||
|
||||
++bin_idx;
|
||||
@@ -832,7 +832,7 @@ public:
|
||||
|
||||
for (auto const & bin : this->options.problem_bins) {
|
||||
int first_idx = bin.second.front();
|
||||
|
||||
|
||||
bin_problem_sizes.push_back(this->options.problem_sizes.at(first_idx));
|
||||
bin_count.push_back(int32_t(bin.second.size()));
|
||||
|
||||
@@ -974,7 +974,7 @@ public:
|
||||
std::cerr << "CUTLASS error on line " << __LINE__ << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1027,7 +1027,7 @@ public:
|
||||
int last_stream_idx = 0;
|
||||
|
||||
for (int iter = 0; iter < this->options.iterations; ++iter) {
|
||||
|
||||
|
||||
for (int bin_idx = 0; bin_idx < int32_t(bin_problem_sizes.size()); ++bin_idx) {
|
||||
|
||||
cutlass::gemm::GemmCoord const & problem = bin_problem_sizes[bin_idx];
|
||||
@@ -1098,7 +1098,7 @@ public:
|
||||
std::cerr << "cudaEventRecord() failed: " << cudaGetErrorString(result.error) << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Wait for work to be completed
|
||||
//
|
||||
@@ -1129,10 +1129,10 @@ public:
|
||||
for (auto event : events) {
|
||||
(void)cudaEventDestroy(event);
|
||||
}
|
||||
|
||||
|
||||
for (auto stream : cuda_streams) {
|
||||
if (stream) {
|
||||
(void)cudaStreamDestroy(stream);
|
||||
(void)cudaStreamDestroy(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1203,8 +1203,8 @@ public:
|
||||
int tiles = Gemm::problem_tile_count(problem);
|
||||
total_tiles += tiles;
|
||||
|
||||
std::cout << " [" << idx << "]: "
|
||||
<< problem.m() << "-by-" << problem.n() << "-by-" << problem.k()
|
||||
std::cout << " [" << idx << "]: "
|
||||
<< problem.m() << "-by-" << problem.n() << "-by-" << problem.k()
|
||||
<< " (" << tiles << " threadblock tiles)" << "\n";
|
||||
|
||||
++idx;
|
||||
@@ -1442,12 +1442,12 @@ int main(int argc, char const **args) {
|
||||
}
|
||||
|
||||
if (__CUDACC_VER_MAJOR__ < 11 || props.major < 8) {
|
||||
|
||||
|
||||
//
|
||||
// This example requires an NVIDIA Ampere-architecture GPU.
|
||||
//
|
||||
|
||||
std::cout
|
||||
std::cout
|
||||
<< "CUTLASS's Grouped GEMM example requires a GPU of NVIDIA's Ampere Architecture or "
|
||||
<< "later (compute capability 80 or greater).\n";
|
||||
|
||||
@@ -1497,9 +1497,9 @@ int main(int argc, char const **args) {
|
||||
cutlass::gemm::GemmShape<64, 64, 32>,
|
||||
cutlass::gemm::GemmShape<16, 8, 16>,
|
||||
cutlass::epilogue::thread::LinearCombination<
|
||||
ElementOutput,
|
||||
ElementOutput,
|
||||
128 / cutlass::sizeof_bits<ElementOutput>::value,
|
||||
ElementAccumulator,
|
||||
ElementAccumulator,
|
||||
ElementAccumulator
|
||||
>,
|
||||
cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<8>,
|
||||
@@ -1519,8 +1519,8 @@ int main(int argc, char const **args) {
|
||||
cutlass::ComplexTransform::kNone,
|
||||
8,
|
||||
ElementOutput, LayoutC,
|
||||
ElementAccumulator,
|
||||
cutlass::arch::OpClassTensorOp,
|
||||
ElementAccumulator,
|
||||
cutlass::arch::OpClassTensorOp,
|
||||
cutlass::arch::Sm80,
|
||||
cutlass::gemm::GemmShape<128, 128, 32>,
|
||||
cutlass::gemm::GemmShape<64, 64, 32>,
|
||||
@@ -1531,7 +1531,7 @@ int main(int argc, char const **args) {
|
||||
// NOTE: Threadblock swizzling is currently not supported by CUTLASS's grouped kernels.
|
||||
// This parameter is passed in at present to match the APIs of other kernels. The parameter
|
||||
// is unused within the kernel.
|
||||
cutlass::gemm::threadblock::GemmBatchedIdentityThreadblockSwizzle,
|
||||
cutlass::gemm::threadblock::GemmBatchedIdentityThreadblockSwizzle,
|
||||
4>::GemmKernel;
|
||||
|
||||
using GemmGrouped = cutlass::gemm::device::GemmGrouped<GemmKernel>;
|
||||
|
||||
+1
-1
@@ -181,7 +181,7 @@ struct Options {
|
||||
<< " --benchmark If set (true), performance benchmarking on several layers and batch-size.\n\n";
|
||||
|
||||
out << "\n\nExamples:\n\n"
|
||||
<< "$ ./examples/29_ampere_3xtf32_fast_accurate_tensorop_complex_gemm/29_ampere_3xtf32_fast_accurate_complex_gemm --m=1024 --n=512 \\\n"
|
||||
<< "$ ./examples/29_ampere_3xtf32_fast_accurate_tensorop_complex_gemm/29_3xtf32_complex_gemm --m=1024 --n=512 \\\n"
|
||||
<< " --alpha=2 --beta=0.707 \n\n";
|
||||
|
||||
return out;
|
||||
@@ -27,9 +27,9 @@
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
# Both filenames are shorter to avoid MAX_PATH issues on Windows.
|
||||
cutlass_example_add_executable(
|
||||
29_ampere_3xtf32_fast_accurate_tensorop_complex_gemm
|
||||
29_ampere_3xtf32_fast_accurate_tensorop_complex_gemm.cu
|
||||
29_3xtf32_complex_gemm
|
||||
29_3xtf32_complex_gemm.cu
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,510 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
/*! \file
|
||||
\brief Defines additional layout functions used in Permute GEMM example to simplify
|
||||
computing reference permutations of 4/5D tensors when source data is column-major.
|
||||
*/
|
||||
#pragma once
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/cassert>
|
||||
#else
|
||||
#include "assert.h"
|
||||
#endif
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/layout/pitch_linear.h"
|
||||
#include "cutlass/layout/matrix.h"
|
||||
#include "cutlass/coord.h"
|
||||
#include "cutlass/tensor_coord.h"
|
||||
|
||||
namespace cutlass {
|
||||
namespace layout {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Mapping function for 4-D CWHN tensors.
|
||||
class TensorCWHN {
|
||||
public:
|
||||
/// Logical rank of tensor
|
||||
static int const kRank = 4;
|
||||
|
||||
/// Rank of stride vector
|
||||
static int const kStrideRank = 3;
|
||||
|
||||
/// Index type used for coordinates
|
||||
using Index = int32_t;
|
||||
|
||||
/// Long index type used for offsets
|
||||
using LongIndex = int64_t;
|
||||
|
||||
/// Logical coordinate (n, h, w, c)
|
||||
using TensorCoord = Tensor4DCoord;
|
||||
|
||||
/// Stride vector
|
||||
using Stride = Coord<kStrideRank>;
|
||||
|
||||
private:
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// Stride data member - [n, hn, whn]
|
||||
Stride stride_;
|
||||
|
||||
public:
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorCWHN(Stride const &stride = Stride(0)): stride_(stride) { }
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorCWHN(
|
||||
typename Stride::Index stride_h, ///< number of elements between adjacent N coordinates
|
||||
typename Stride::Index stride_w, ///< number of elements between adjacent C coordinates
|
||||
typename Stride::Index stride_c ///< number of elements between adjacent W coordinates
|
||||
):
|
||||
stride_(make_Coord(stride_h, stride_w, stride_c)) { }
|
||||
|
||||
/// Constructor
|
||||
// Once convolutions implement 64b stride this ctor can be deleted
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorCWHN(Coord<kStrideRank, LongIndex> const &stride):
|
||||
stride_(make_Coord(
|
||||
static_cast<typename Stride::Index>(stride[0]),
|
||||
static_cast<typename Stride::Index>(stride[1]),
|
||||
static_cast<typename Stride::Index>(stride[2]))
|
||||
) { }
|
||||
|
||||
/// Helper returns a layout to a tightly packed WCNH tensor.
|
||||
CUTLASS_HOST_DEVICE
|
||||
static TensorCWHN packed(TensorCoord const &extent) {
|
||||
return TensorCWHN(
|
||||
make_Coord(
|
||||
extent.n(),
|
||||
extent.h() * extent.n(),
|
||||
extent.w() * extent.h() * extent.n()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the offset of a coordinate (n, h, w, c) in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(TensorCoord const &coord) const {
|
||||
return coord.n() +
|
||||
LongIndex(stride_[0] * coord.h()) +
|
||||
LongIndex(stride_[1] * coord.w()) +
|
||||
LongIndex(stride_[2] * coord.c());
|
||||
}
|
||||
|
||||
/// Returns the offset of a pitchlinear coordinate in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(PitchLinearCoord coord) const {
|
||||
return coord.contiguous() + LongIndex(coord.strided() * stride_[2]);
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride stride() const {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride & stride() {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Compute the number of contiguous elements needed to store a tensor with the given size
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex capacity(TensorCoord const &extent) const {
|
||||
// it does not make sense if the extent is larger than stride
|
||||
// and we could not rely on the capacity calculation in such cases
|
||||
// we could move this checkers to debug code only
|
||||
if ((extent.n() > stride_[0])
|
||||
|| (extent.h() * stride_[0] > stride_[1])
|
||||
|| (extent.w() * stride_[1] > stride_[2])) {
|
||||
assert(0);
|
||||
}
|
||||
return extent.c() * stride_[2];
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Mapping function for 4-D NHCW tensors.
|
||||
class TensorNHCW {
|
||||
public:
|
||||
/// Logical rank of tensor
|
||||
static int const kRank = 4;
|
||||
|
||||
/// Rank of stride vector
|
||||
static int const kStrideRank = 3;
|
||||
|
||||
/// Index type used for coordinates
|
||||
using Index = int32_t;
|
||||
|
||||
/// Long index type used for offsets
|
||||
using LongIndex = int64_t;
|
||||
|
||||
/// Logical coordinate (n, h, w, c)
|
||||
using TensorCoord = Tensor4DCoord;
|
||||
|
||||
/// Stride vector
|
||||
using Stride = Coord<kStrideRank>;
|
||||
|
||||
private:
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// Stride data member - [w, cw, hcw]
|
||||
Stride stride_;
|
||||
|
||||
public:
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorNHCW(Stride const &stride = Stride(0)): stride_(stride) { }
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorNHCW(
|
||||
typename Stride::Index stride_c, ///< number of elements between adjacent C coordinates
|
||||
typename Stride::Index stride_h, ///< number of elements between adjacent H coordinates
|
||||
typename Stride::Index stride_n ///< number of elements between adjacent N coordinates
|
||||
):
|
||||
stride_(make_Coord(stride_c, stride_h, stride_n)) { }
|
||||
|
||||
/// Constructor
|
||||
// Once convolutions implement 64b stride this ctor can be deleted
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorNHCW(Coord<kStrideRank, LongIndex> const &stride):
|
||||
stride_(make_Coord(
|
||||
static_cast<typename Stride::Index>(stride[0]),
|
||||
static_cast<typename Stride::Index>(stride[1]),
|
||||
static_cast<typename Stride::Index>(stride[2]))
|
||||
) { }
|
||||
|
||||
/// Helper returns a layout to a tightly packed WCNH tensor.
|
||||
CUTLASS_HOST_DEVICE
|
||||
static TensorNHCW packed(TensorCoord const &extent) {
|
||||
return TensorNHCW(
|
||||
make_Coord(
|
||||
extent.w(),
|
||||
extent.c() * extent.w(),
|
||||
extent.h() * extent.c() * extent.w()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the offset of a coordinate (n, h, w, c) in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(TensorCoord const &coord) const {
|
||||
return coord.w() +
|
||||
LongIndex(stride_[0] * coord.c()) +
|
||||
LongIndex(stride_[1] * coord.h()) +
|
||||
LongIndex(stride_[2] * coord.n());
|
||||
}
|
||||
|
||||
/// Returns the offset of a pitchlinear coordinate in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(PitchLinearCoord coord) const {
|
||||
return coord.contiguous() + LongIndex(coord.strided() * stride_[2]);
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride stride() const {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride & stride() {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Compute the number of contiguous elements needed to store a tensor with the given size
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex capacity(TensorCoord const &extent) const {
|
||||
// it does not make sense if the extent is larger than stride
|
||||
// and we could not rely on the capacity calculation in such cases
|
||||
// we could move this checkers to debug code only
|
||||
if ((extent.w() > stride_[0])
|
||||
|| (extent.c() * stride_[0] > stride_[1])
|
||||
|| (extent.h() * stride_[1] > stride_[2])) {
|
||||
assert(0);
|
||||
}
|
||||
return extent.n() * stride_[2];
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Mapping function for 4-D NHCW tensors.
|
||||
class TensorNCWH {
|
||||
public:
|
||||
/// Logical rank of tensor
|
||||
static int const kRank = 4;
|
||||
|
||||
/// Rank of stride vector
|
||||
static int const kStrideRank = 3;
|
||||
|
||||
/// Index type used for coordinates
|
||||
using Index = int32_t;
|
||||
|
||||
/// Long index type used for offsets
|
||||
using LongIndex = int64_t;
|
||||
|
||||
/// Logical coordinate (n, h, w, c)
|
||||
using TensorCoord = Tensor4DCoord;
|
||||
|
||||
/// Stride vector
|
||||
using Stride = Coord<kStrideRank>;
|
||||
|
||||
private:
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// Stride data member - [h, wh, cwh]
|
||||
Stride stride_;
|
||||
|
||||
public:
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorNCWH(Stride const &stride = Stride(0)): stride_(stride) { }
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorNCWH(
|
||||
typename Stride::Index stride_w, ///< number of elements between adjacent C coordinates
|
||||
typename Stride::Index stride_c, ///< number of elements between adjacent H coordinates
|
||||
typename Stride::Index stride_n ///< number of elements between adjacent N coordinates
|
||||
):
|
||||
stride_(make_Coord(stride_w, stride_c, stride_n)) { }
|
||||
|
||||
/// Constructor
|
||||
// Once convolutions implement 64b stride this ctor can be deleted
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorNCWH(Coord<kStrideRank, LongIndex> const &stride):
|
||||
stride_(make_Coord(
|
||||
static_cast<typename Stride::Index>(stride[0]),
|
||||
static_cast<typename Stride::Index>(stride[1]),
|
||||
static_cast<typename Stride::Index>(stride[2]))
|
||||
) { }
|
||||
|
||||
/// Helper returns a layout to a tightly packed WCNH tensor.
|
||||
CUTLASS_HOST_DEVICE
|
||||
static TensorNCWH packed(TensorCoord const &extent) {
|
||||
return TensorNCWH(
|
||||
make_Coord(
|
||||
extent.h(),
|
||||
extent.w() * extent.h(),
|
||||
extent.c() * extent.w() * extent.h()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the offset of a coordinate (n, h, w, c) in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(TensorCoord const &coord) const {
|
||||
return coord.h() +
|
||||
LongIndex(stride_[0] * coord.w()) +
|
||||
LongIndex(stride_[1] * coord.c()) +
|
||||
LongIndex(stride_[2] * coord.n());
|
||||
}
|
||||
|
||||
/// Returns the offset of a pitchlinear coordinate in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(PitchLinearCoord coord) const {
|
||||
return coord.contiguous() + LongIndex(coord.strided() * stride_[2]);
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride stride() const {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride & stride() {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Compute the number of contiguous elements needed to store a tensor with the given size
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex capacity(TensorCoord const &extent) const {
|
||||
// it does not make sense if the extent is larger than stride
|
||||
// and we could not rely on the capacity calculation in such cases
|
||||
// we could move this checkers to debug code only
|
||||
if ((extent.h() > stride_[0])
|
||||
|| (extent.w() * stride_[0] > stride_[1])
|
||||
|| (extent.c() * stride_[1] > stride_[2])) {
|
||||
assert(0);
|
||||
}
|
||||
return extent.n() * stride_[2];
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Mapping function for 5-D CWHDN tensors.
|
||||
class TensorCWHDN {
|
||||
public:
|
||||
/// Logical rank of tensor
|
||||
static int const kRank = 5;
|
||||
|
||||
/// Rank of stride vector
|
||||
static int const kStrideRank = 4;
|
||||
|
||||
/// Index type used for coordinates
|
||||
using Index = int32_t;
|
||||
|
||||
/// Long index type used for offsets
|
||||
using LongIndex = int64_t;
|
||||
|
||||
/// Logical coordinate (n, d, h, w, c)
|
||||
using TensorCoord = Tensor5DCoord;
|
||||
|
||||
/// Stride vector
|
||||
using Stride = Coord<kStrideRank>;
|
||||
|
||||
private:
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// Stride data member - [n, dn, hdn, whdn]
|
||||
Stride stride_;
|
||||
|
||||
public:
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorCWHDN(Stride const &stride = Stride(0)): stride_(stride) { }
|
||||
|
||||
/// Constructor
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorCWHDN(
|
||||
typename Stride::Index n,
|
||||
typename Stride::Index dn,
|
||||
typename Stride::Index hdn,
|
||||
typename Stride::Index whdn):
|
||||
stride_(make_Coord(n, dn, hdn, whdn)) { }
|
||||
|
||||
/// Constructor
|
||||
// Once convolutions implement 64b stride this ctor can be deleted
|
||||
CUTLASS_HOST_DEVICE
|
||||
TensorCWHDN(Coord<kStrideRank, LongIndex> const &stride):
|
||||
stride_(make_Coord(
|
||||
static_cast<typename Stride::Index>(stride[0]),
|
||||
static_cast<typename Stride::Index>(stride[1]),
|
||||
static_cast<typename Stride::Index>(stride[2]),
|
||||
static_cast<typename Stride::Index>(stride[3]))
|
||||
) { }
|
||||
|
||||
/// Helper returns a layout to a tightly packed CWHDN tensor.
|
||||
CUTLASS_HOST_DEVICE
|
||||
static TensorCWHDN packed(TensorCoord const &extent) {
|
||||
return TensorCWHDN(
|
||||
make_Coord(
|
||||
extent.n(),
|
||||
extent.d() * extent.n(),
|
||||
extent.h() * extent.d() * extent.n(),
|
||||
extent.w() * extent.h() * extent.d() * extent.n()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the offset of a coordinate (n, d, h, w, c) in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(TensorCoord const &coord) const {
|
||||
return coord.n() +
|
||||
LongIndex(stride_[0] * coord.d()) +
|
||||
LongIndex(stride_[1] * coord.h()) +
|
||||
LongIndex(stride_[2] * coord.w()) +
|
||||
LongIndex(stride_[3] * coord.c());
|
||||
}
|
||||
|
||||
/// Returns the offset of a pitchlinear coordinate in linear memory.
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex operator()(PitchLinearCoord coord) const {
|
||||
return coord.contiguous() + LongIndex(coord.strided() * stride_[3]);
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride stride() const {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Returns the stride of the layout
|
||||
CUTLASS_HOST_DEVICE
|
||||
Stride & stride() {
|
||||
return stride_;
|
||||
}
|
||||
|
||||
/// Compute the number of contiguous elements needed to store a tensor with the given size
|
||||
CUTLASS_HOST_DEVICE
|
||||
LongIndex capacity(TensorCoord const &extent) const {
|
||||
// it does not make sense if the extent is larger than stride
|
||||
// and we could not rely on the capacity calculation in such cases
|
||||
// we could move this checkers to debug code only
|
||||
if ((extent.n() > stride_[0])
|
||||
|| (extent.d() * stride_[0] > stride_[1])
|
||||
|| (extent.h() * stride_[1] > stride_[2])
|
||||
|| (extent.w() * stride_[2] > stride_[3])) {
|
||||
assert(0);
|
||||
}
|
||||
return extent.c() * stride_[3];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace layout
|
||||
} // namespace cutlass
|
||||
@@ -0,0 +1,344 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
/*! \file
|
||||
\brief Contains additional metadata about layout permute functions used in the example.
|
||||
*/
|
||||
|
||||
#include "cutlass/tensor_coord.h"
|
||||
#include "cutlass/layout/permute.h"
|
||||
|
||||
/// Additional permutation metadata to facilitate testing/printing
|
||||
template<typename PermuteLayout>
|
||||
struct PermuteInfo;
|
||||
|
||||
/// Specialization for default case (no permute). Other specializations must follow this template.
|
||||
template<>
|
||||
struct PermuteInfo<cutlass::layout::NoPermute> {
|
||||
|
||||
/// Whether this is a BMM or GEMM permutation (NoPermute can actually be either)
|
||||
static bool constexpr kBatched = false;
|
||||
|
||||
/// Minimal divisor for row extent
|
||||
static int constexpr kRowFactor = 1;
|
||||
|
||||
/// Minimum divisor for column extent
|
||||
static int constexpr kColumnFactor = 1;
|
||||
|
||||
/// Minimum divisor for batch size dimension
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
/// Tensor layout used in permutation operation
|
||||
using Layout = cutlass::layout::PackedVectorLayout;
|
||||
|
||||
static std::string name() {
|
||||
return "NoPermute";
|
||||
}
|
||||
|
||||
/// User-friendly description of the permute operation
|
||||
static std::string desc() {
|
||||
return "no permutation";
|
||||
}
|
||||
|
||||
/// Infer original higher-rank tensor shape from GEMM/BMM matrix extents.
|
||||
/// For direct (output) permutations, must be a simple reshape of extent.
|
||||
/// For inverse (input) permutations, must return shape *before* permute operation.
|
||||
/// In case of NoPermute, simply use a linear (rank 1) view of the memory
|
||||
static Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
return Layout::TensorCoord(extent.row() * extent.column() * batch_count);
|
||||
}
|
||||
|
||||
/// Compute the permuted higher-rank tensor shape from the original shape.
|
||||
static Layout::TensorCoord permute(Layout::TensorCoord const &s) {
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template<int D1>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0213RowMajor<D1>> {
|
||||
|
||||
static bool constexpr kBatched = true;
|
||||
static int constexpr kRowFactor = 1;
|
||||
static int constexpr kColumnFactor = 1;
|
||||
static int constexpr kBatchFactor = D1;
|
||||
|
||||
using Layout = cutlass::layout::TensorNHWC;
|
||||
|
||||
static std::string name() {
|
||||
return "Tensor4DPermuteBMM0213<" + std::to_string(D1) + ">";
|
||||
}
|
||||
|
||||
static std::string desc() {
|
||||
return "batched GEMM permutation [0, 2, 1, 3]";
|
||||
}
|
||||
|
||||
static Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int D0 = batch_count / D1;
|
||||
int D2 = extent.row();
|
||||
int D3 = extent.column();
|
||||
return {D0, D1, D2, D3};
|
||||
}
|
||||
|
||||
static Layout::TensorCoord permute(Layout::TensorCoord const &s) {
|
||||
return {s[0], s[2], s[1], s[3]};
|
||||
}
|
||||
};
|
||||
|
||||
template<int D1>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0213RowMajorInverse<D1>>
|
||||
: public PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0213RowMajor<D1>> {
|
||||
|
||||
static bool constexpr kBatched = true;
|
||||
static int constexpr kRowFactor = 1;
|
||||
static int constexpr kColumnFactor = D1;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Base = PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0213RowMajor<D1>>;
|
||||
using Layout = typename Base::Layout;
|
||||
|
||||
static typename Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int D0 = batch_count;
|
||||
int D2 = extent.row();
|
||||
int D3 = extent.column() / D1;
|
||||
return {D0, D1, D2, D3};
|
||||
}
|
||||
};
|
||||
|
||||
template<int D1>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0321ColumnMajor<D1>> {
|
||||
|
||||
static bool constexpr kBatched = true;
|
||||
static int constexpr kRowFactor = 1;
|
||||
static int constexpr kColumnFactor = 1;
|
||||
static int constexpr kBatchFactor = D1;
|
||||
|
||||
using Layout = cutlass::layout::TensorNHCW;
|
||||
|
||||
static std::string name() {
|
||||
return "Tensor4DPermuteBMM0321<" + std::to_string(D1) + ">";
|
||||
}
|
||||
|
||||
static std::string desc() {
|
||||
return "batched GEMM permutation [0, 3, 2, 1]";
|
||||
}
|
||||
|
||||
static Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int D0 = batch_count / D1;
|
||||
int D2 = extent.row();
|
||||
int D3 = extent.column();
|
||||
return {D0, D1, D2, D3};
|
||||
}
|
||||
|
||||
static Layout::TensorCoord permute(Layout::TensorCoord const &s) {
|
||||
return {s[0], s[3], s[2], s[1]};
|
||||
}
|
||||
};
|
||||
|
||||
template<int D1>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0321ColumnMajorInverse<D1>>
|
||||
: public PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0321ColumnMajor<D1>> {
|
||||
|
||||
static bool constexpr kBatched = true;
|
||||
static int constexpr kRowFactor = D1;
|
||||
static int constexpr kColumnFactor = 1;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Base = PermuteInfo<cutlass::layout::Tensor4DPermuteBMM0321ColumnMajor<D1>>;
|
||||
using Layout = typename Base::Layout;
|
||||
|
||||
static typename Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int D0 = batch_count;
|
||||
int D2 = extent.row() / D1;
|
||||
int D3 = extent.column();
|
||||
return {D0, D1, D2, D3};
|
||||
}
|
||||
};
|
||||
|
||||
template<int D1, int D2>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermute0213RowMajor<D1, D2>> {
|
||||
|
||||
static bool constexpr kBatched = false;
|
||||
static int constexpr kRowFactor = D1;
|
||||
static int constexpr kColumnFactor = D2;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Layout = cutlass::layout::TensorNHWC;
|
||||
|
||||
static std::string name() {
|
||||
return "Tensor4DPermute0213<" + std::to_string(D1) + "," + std::to_string(D2) + ">";
|
||||
}
|
||||
|
||||
static std::string desc() {
|
||||
return "normal GEMM permutation [0, 2, 1, 3]";
|
||||
}
|
||||
|
||||
static Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int D0 = extent.row() / D1;
|
||||
int D3 = extent.column() / D2;
|
||||
return {D0, D1, D2, D3};
|
||||
}
|
||||
|
||||
static Layout::TensorCoord permute(Layout::TensorCoord const &s) {
|
||||
return {s[0], s[2], s[1], s[3]};
|
||||
}
|
||||
};
|
||||
|
||||
template<int D1, int D2>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermute0213RowMajorInverse<D1, D2>>
|
||||
: public PermuteInfo<cutlass::layout::Tensor4DPermute0213RowMajor<D1, D2>> {
|
||||
|
||||
static bool constexpr kBatched = false;
|
||||
static int constexpr kRowFactor = D2;
|
||||
static int constexpr kColumnFactor = D1;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Base = PermuteInfo<cutlass::layout::Tensor4DPermute0213RowMajor<D1, D2>>;
|
||||
using Layout = typename Base::Layout;
|
||||
|
||||
static typename Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int D0 = extent.row() / D2;
|
||||
int D3 = extent.column() / D1;
|
||||
return {D0, D1, D2, D3};
|
||||
}
|
||||
};
|
||||
|
||||
template<int D1, int D2>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermute0213ColumnMajor<D1, D2>>
|
||||
: public PermuteInfo<cutlass::layout::Tensor4DPermute0213RowMajor<D1, D2>> {
|
||||
using Layout = cutlass::layout::TensorCWHN;
|
||||
};
|
||||
|
||||
template<int D1, int D2>
|
||||
struct PermuteInfo<cutlass::layout::Tensor4DPermute0213ColumnMajorInverse<D1, D2>>
|
||||
: public PermuteInfo<cutlass::layout::Tensor4DPermute0213RowMajorInverse<D1, D2>> {
|
||||
using Layout = cutlass::layout::TensorCWHN;
|
||||
};
|
||||
|
||||
template<int T1, int T2, int T3>
|
||||
struct PermuteInfo<cutlass::layout::Tensor5DPermute20314RowMajor<T1, T2, T3>> {
|
||||
|
||||
static bool constexpr kBatched = false;
|
||||
static int constexpr kRowFactor = T1;
|
||||
static int constexpr kColumnFactor = T2 * T3;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Layout = cutlass::layout::TensorNDHWC;
|
||||
|
||||
static std::string name() {
|
||||
return "Tensor5DPermute20314<" + std::to_string(T1) + "," + std::to_string(T2) + "," + std::to_string(T3) + ">";
|
||||
}
|
||||
|
||||
static std::string desc() {
|
||||
return "normal GEMM permutation [2, 0, 3, 1, 4]";
|
||||
}
|
||||
|
||||
static Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count)
|
||||
{
|
||||
int const T0 = extent.row() / T1;
|
||||
int const T4 = extent.column() / (T2 * T3);
|
||||
return {T0, T1, T2, T3, T4};
|
||||
}
|
||||
|
||||
static Layout::TensorCoord permute(Layout::TensorCoord const &s)
|
||||
{
|
||||
return {s[2], s[0], s[3], s[1], s[4]};
|
||||
}
|
||||
};
|
||||
|
||||
template<int T1, int T2, int T3>
|
||||
struct PermuteInfo<cutlass::layout::Tensor5DPermute20314RowMajorInverse<T1, T2, T3>>
|
||||
: public PermuteInfo<cutlass::layout::Tensor5DPermute20314RowMajor<T1, T2, T3>> {
|
||||
|
||||
static bool constexpr kBatched = false;
|
||||
static int constexpr kRowFactor = T2;
|
||||
static int constexpr kColumnFactor = T1 * T3;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Base = PermuteInfo<cutlass::layout::Tensor5DPermute20314RowMajor<T1, T2, T3>>;
|
||||
using Layout = typename Base::Layout;
|
||||
|
||||
static typename Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int const T0 = extent.row() / T2;
|
||||
int const T4 = extent.column() / (T1 * T3);
|
||||
return {T0, T1, T2, T3, T4};
|
||||
}
|
||||
};
|
||||
|
||||
template<int T1, int T2, int T3>
|
||||
struct PermuteInfo<cutlass::layout::Tensor5DPermute02413ColumnMajor<T1, T2, T3>> {
|
||||
|
||||
static bool constexpr kBatched = false;
|
||||
static int constexpr kRowFactor = T1;
|
||||
static int constexpr kColumnFactor = T2 * T3;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Layout = cutlass::layout::TensorCWHDN;
|
||||
|
||||
static std::string name() {
|
||||
return "Tensor5DPermute02413<" + std::to_string(T1) + "," + std::to_string(T2) + "," + std::to_string(T3) + ">";
|
||||
}
|
||||
|
||||
static std::string desc() {
|
||||
return "normal GEMM permutation [0, 2, 4, 1, 3]";
|
||||
}
|
||||
|
||||
using Coord = cutlass::Tensor5DCoord;
|
||||
|
||||
static Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count)
|
||||
{
|
||||
int const T0 = extent.row() / T1;
|
||||
int const T4 = extent.column() / (T2 * T3);
|
||||
return {T0, T1, T2, T3, T4};
|
||||
}
|
||||
|
||||
static Layout::TensorCoord permute(Layout::TensorCoord const &s)
|
||||
{
|
||||
return {s[0], s[2], s[4], s[1], s[3]};
|
||||
}
|
||||
};
|
||||
|
||||
template<int T1, int T2, int T3>
|
||||
struct PermuteInfo<cutlass::layout::Tensor5DPermute02413ColumnMajorInverse<T1, T2, T3>>
|
||||
: public PermuteInfo<cutlass::layout::Tensor5DPermute02413ColumnMajor<T1, T2, T3>> {
|
||||
|
||||
static bool constexpr kBatched = false;
|
||||
static int constexpr kRowFactor = T2;
|
||||
static int constexpr kColumnFactor = T1 * T3;
|
||||
static int constexpr kBatchFactor = 1;
|
||||
|
||||
using Base = PermuteInfo<cutlass::layout::Tensor5DPermute02413ColumnMajor<T1, T2, T3>>;
|
||||
using Layout = typename Base::Layout;
|
||||
|
||||
static typename Layout::TensorCoord original_shape(cutlass::MatrixCoord extent, int batch_count) {
|
||||
int const T0 = extent.row() / T2;
|
||||
int const T4 = extent.column() / (T1 * T3);
|
||||
return {T0, T1, T2, T3, T4};
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,15 @@
|
||||
# CUTLASS Python Interface Examples
|
||||
This directory contains examples of using CUTLASS's Python interface. It consists of two types of examples:
|
||||
# PyCUTLASS Examples
|
||||
|
||||
**NOTE:** This directory contains examples for PyCUTLASS, a Python library providing low-level
|
||||
building blocks for emitting CUTLASS C++ kernels. For examples using CUTLASS's Pythonic interface,
|
||||
see the [examples/python](/examples/python) directory.
|
||||
|
||||
Two types of examples are provided:
|
||||
* _Basic examples_: minimal examples that illustrate how to set up GEMMs, convolutions, and grouped GEMM operations
|
||||
* [_Customizable examples_](customizable): examples that allow one to specify a variety of template parameters for the given kernel
|
||||
|
||||
## Setting up the Python interface
|
||||
Please follow the instructions [here](/tools/library/scripts/pycutlass/README.md#installation) to set up the Python API.
|
||||
Please follow the instructions [here](/python/README.md#installation) to set up the PyCUTLASS.
|
||||
|
||||
## Running examples
|
||||
Each of the basic examples can be run as follows:
|
||||
|
||||
@@ -38,10 +38,11 @@ import torch
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
import cutlass
|
||||
import pycutlass
|
||||
from pycutlass import *
|
||||
from pycutlass.utils.device import device_cc
|
||||
import cutlass_bindings
|
||||
import cutlass.backend as pycutlass
|
||||
from cutlass.backend import *
|
||||
from cutlass.backend.utils.reference_model import Conv2dReferenceModule
|
||||
from cutlass.backend.utils.device import device_cc
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -76,11 +77,11 @@ pycutlass.get_memory_pool(init_pool_size=2**30, max_pool_size=2**32)
|
||||
pycutlass.compiler.nvcc()
|
||||
|
||||
# Set up A, B, C and accumulator
|
||||
A = TensorDescription(cutlass.float16, cutlass.TensorNHWC, alignment)
|
||||
B = TensorDescription(cutlass.float16, cutlass.TensorNHWC, alignment)
|
||||
C = TensorDescription(cutlass.float32, cutlass.TensorNHWC, alignment)
|
||||
element_acc = cutlass.float32
|
||||
element_epilogue = cutlass.float32
|
||||
A = TensorDescription(cutlass_bindings.float16, cutlass_bindings.TensorNHWC, alignment)
|
||||
B = TensorDescription(cutlass_bindings.float16, cutlass_bindings.TensorNHWC, alignment)
|
||||
C = TensorDescription(cutlass_bindings.float32, cutlass_bindings.TensorNHWC, alignment)
|
||||
element_acc = cutlass_bindings.float32
|
||||
element_epilogue = cutlass_bindings.float32
|
||||
|
||||
# Select instruction shape based on the Tensor Core instructions supported
|
||||
# by the device on which we are running
|
||||
@@ -89,12 +90,14 @@ if cc == 70:
|
||||
elif cc == 75:
|
||||
instruction_shape = [16, 8, 8]
|
||||
else:
|
||||
# Use CUTLASS kernels for CC 80 by default (e.g., for cases in which SM86 is used)
|
||||
cc = 80
|
||||
instruction_shape = [16, 8, 16]
|
||||
|
||||
math_inst = MathInstruction(
|
||||
instruction_shape,
|
||||
A.element, B.element, element_acc,
|
||||
cutlass.OpClass.TensorOp,
|
||||
cutlass_bindings.OpClass.TensorOp,
|
||||
MathOperation.multiply_add
|
||||
)
|
||||
|
||||
@@ -108,8 +111,8 @@ tile_description = TileDescription(
|
||||
epilogue_functor = pycutlass.LinearCombination(C.element, C.alignment, element_acc, element_epilogue)
|
||||
|
||||
operation = Conv2dOperation(
|
||||
conv_kind=cutlass.conv.Operator.fprop,
|
||||
iterator_algorithm=cutlass.conv.IteratorAlgorithm.optimized,
|
||||
conv_kind=cutlass_bindings.conv.Operator.fprop,
|
||||
iterator_algorithm=cutlass_bindings.conv.IteratorAlgorithm.optimized,
|
||||
arch=cc, tile_description=tile_description,
|
||||
A=A, B=B, C=C, stride_support=StrideSupport.Strided,
|
||||
epilogue_functor=epilogue_functor
|
||||
@@ -125,20 +128,20 @@ pycutlass.compiler.add_module(operations)
|
||||
|
||||
# Randomly initialize tensors
|
||||
|
||||
problem_size = cutlass.conv.Conv2dProblemSize(
|
||||
cutlass.Tensor4DCoord(args.n, args.h, args.c, args.w),
|
||||
cutlass.Tensor4DCoord(args.k, args.r, args.s, args.c),
|
||||
cutlass.Tensor4DCoord(0, 0, 0, 0), # Padding
|
||||
cutlass.MatrixCoord(1, 1), # Strides
|
||||
cutlass.MatrixCoord(1, 1), # Dilation
|
||||
cutlass.conv.Mode.cross_correlation,
|
||||
problem_size = cutlass_bindings.conv.Conv2dProblemSize(
|
||||
cutlass_bindings.Tensor4DCoord(args.n, args.h, args.c, args.w),
|
||||
cutlass_bindings.Tensor4DCoord(args.k, args.r, args.s, args.c),
|
||||
cutlass_bindings.Tensor4DCoord(0, 0, 0, 0), # Padding
|
||||
cutlass_bindings.MatrixCoord(1, 1), # Strides
|
||||
cutlass_bindings.MatrixCoord(1, 1), # Dilation
|
||||
cutlass_bindings.conv.Mode.cross_correlation,
|
||||
1, # Split k slices
|
||||
1 # Groups
|
||||
)
|
||||
|
||||
tensor_A_size = cutlass.conv.implicit_gemm_tensor_a_size(operation.conv_kind, problem_size)
|
||||
tensor_B_size = cutlass.conv.implicit_gemm_tensor_b_size(operation.conv_kind, problem_size)
|
||||
tensor_C_size = cutlass.conv.implicit_gemm_tensor_c_size(operation.conv_kind, problem_size)
|
||||
tensor_A_size = cutlass_bindings.conv.implicit_gemm_tensor_a_size(operation.conv_kind, problem_size)
|
||||
tensor_B_size = cutlass_bindings.conv.implicit_gemm_tensor_b_size(operation.conv_kind, problem_size)
|
||||
tensor_C_size = cutlass_bindings.conv.implicit_gemm_tensor_c_size(operation.conv_kind, problem_size)
|
||||
|
||||
tensor_A = torch.ceil(torch.empty(size=(tensor_A_size,), dtype=torch.float16, device="cuda").uniform_(-8.5, 7.5))
|
||||
tensor_B = torch.ceil(torch.empty(size=(tensor_B_size,), dtype=torch.float16, device="cuda").uniform_(-8.5, 7.5))
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
#
|
||||
################################################################################
|
||||
import numpy as np
|
||||
import pycutlass
|
||||
from pycutlass import *
|
||||
from pycutlass.conv2d_operation import *
|
||||
from pycutlass.utils import reference_model
|
||||
from pycutlass.utils.device import device_cc
|
||||
import cutlass.backend as pycutlass
|
||||
from cutlass.backend import *
|
||||
from cutlass.backend.utils.device import device_cc
|
||||
from cutlass.backend.conv2d_operation import *
|
||||
from cutlass.backend.utils.reference_model import Conv2dReferenceModule
|
||||
import sys
|
||||
import torch.nn.functional as F
|
||||
|
||||
@@ -62,7 +62,7 @@ parser.add_argument("-tacc", "--element_acc", default="float32", type=str,
|
||||
help='Data type of accumulator')
|
||||
parser.add_argument('-m', "--math", default="multiply_add",
|
||||
type=str, choices=["multiply_add", "multiply_add_fast_bf16", "multiply_add_fast_f32"], help="math instruction")
|
||||
parser.add_argument('-op', "--opcode", default="simt", type=str,
|
||||
parser.add_argument('-op', "--opcode", default="Simt", type=str,
|
||||
choices=["Simt", 'TensorOp'],
|
||||
help='This option describes whether you want to use tensor \
|
||||
cores (TensorOp) or regular SIMT cores (Simt) on GPU SM')
|
||||
@@ -156,12 +156,12 @@ pycutlass.get_memory_pool(init_pool_size=2**30, max_pool_size=2**32)
|
||||
|
||||
np.random.seed(0)
|
||||
|
||||
element_a = getattr(cutlass, args.element_a)
|
||||
element_b = getattr(cutlass, args.element_b)
|
||||
element_c = getattr(cutlass, args.element_c)
|
||||
element_acc = getattr(cutlass, args.element_acc)
|
||||
element_a = getattr(cutlass_bindings, args.element_a)
|
||||
element_b = getattr(cutlass_bindings, args.element_b)
|
||||
element_c = getattr(cutlass_bindings, args.element_c)
|
||||
element_acc = getattr(cutlass_bindings, args.element_acc)
|
||||
math_operation = getattr(MathOperation, args.math)
|
||||
opclass = getattr(cutlass.OpClass, args.opcode)
|
||||
opclass = getattr(cutlass_bindings.OpClass, args.opcode)
|
||||
|
||||
math_inst = MathInstruction(
|
||||
args.instruction_shape, element_a, element_b,
|
||||
@@ -173,9 +173,9 @@ tile_description = TileDescription(
|
||||
math_inst
|
||||
)
|
||||
|
||||
layout_a = getattr(cutlass, args.layout_a)
|
||||
layout_b = getattr(cutlass, args.layout_b)
|
||||
layout_c = getattr(cutlass, args.layout_c)
|
||||
layout_a = getattr(cutlass_bindings, args.layout_a)
|
||||
layout_b = getattr(cutlass_bindings, args.layout_b)
|
||||
layout_c = getattr(cutlass_bindings, args.layout_c)
|
||||
|
||||
A = TensorDescription(
|
||||
element_a, layout_a, args.alignment_a
|
||||
@@ -189,7 +189,7 @@ C = TensorDescription(
|
||||
element_c, layout_c, args.alignment_c
|
||||
)
|
||||
|
||||
element_epilogue = getattr(cutlass, args.element_epilogue)
|
||||
element_epilogue = getattr(cutlass_bindings, args.element_epilogue)
|
||||
if (args.activation_function == "identity"
|
||||
or (args.split_k_mode == "Parallel" and args.split_k_slices > 1)):
|
||||
#
|
||||
@@ -200,10 +200,10 @@ else:
|
||||
getattr(pycutlass, args.activation_function)(element_epilogue),
|
||||
C.element, C.alignment, math_inst.element_accumulator, element_epilogue)
|
||||
|
||||
iterator_algorithm = getattr(cutlass.conv.IteratorAlgorithm, args.iterator_algorithm)
|
||||
swizzling_functor = getattr(cutlass, args.swizzling_functor)
|
||||
iterator_algorithm = getattr(cutlass_bindings.conv.IteratorAlgorithm, args.iterator_algorithm)
|
||||
swizzling_functor = getattr(cutlass_bindings, args.swizzling_functor)
|
||||
stride_support = getattr(StrideSupport, args.stride_support)
|
||||
conv_kind = getattr(cutlass.conv.Operator, args.conv_kind)
|
||||
conv_kind = getattr(cutlass_bindings.conv.Operator, args.conv_kind)
|
||||
|
||||
operation = Conv2dOperation(
|
||||
conv_kind=conv_kind, iterator_algorithm=iterator_algorithm,
|
||||
@@ -226,7 +226,7 @@ if args.split_k_mode == "Parallel" and args.split_k_slices > 1:
|
||||
getattr(pycutlass, args.activation_function)(element_epilogue),
|
||||
C.element, C.alignment, math_inst.element_accumulator, element_epilogue)
|
||||
reduction_operation = ReductionOperation(
|
||||
shape=cutlass.MatrixCoord(4, 32 * C.alignment),
|
||||
shape=cutlass_bindings.MatrixCoord(4, 32 * C.alignment),
|
||||
C=C, element_accumulator=element_acc,
|
||||
element_compute=element_epilogue,
|
||||
epilogue_functor=epilogue_functor_reduction,
|
||||
@@ -236,34 +236,34 @@ if args.split_k_mode == "Parallel" and args.split_k_slices > 1:
|
||||
|
||||
pycutlass.compiler.add_module(operations)
|
||||
|
||||
problem_size = cutlass.conv.Conv2dProblemSize(
|
||||
cutlass.Tensor4DCoord(args.nhwc[0], args.nhwc[1], args.nhwc[2], args.nhwc[3]),
|
||||
cutlass.Tensor4DCoord(args.krsc[0], args.krsc[1], args.krsc[2], args.krsc[3]),
|
||||
cutlass.Tensor4DCoord(args.pad[0], args.pad[1], args.pad[2], args.pad[3]),
|
||||
cutlass.MatrixCoord(args.stride[0], args.stride[1]),
|
||||
cutlass.MatrixCoord(args.dilation[0], args.dilation[1]),
|
||||
cutlass.conv.Mode.cross_correlation,
|
||||
problem_size = cutlass_bindings.conv.Conv2dProblemSize(
|
||||
cutlass_bindings.Tensor4DCoord(args.nhwc[0], args.nhwc[1], args.nhwc[2], args.nhwc[3]),
|
||||
cutlass_bindings.Tensor4DCoord(args.krsc[0], args.krsc[1], args.krsc[2], args.krsc[3]),
|
||||
cutlass_bindings.Tensor4DCoord(args.pad[0], args.pad[1], args.pad[2], args.pad[3]),
|
||||
cutlass_bindings.MatrixCoord(args.stride[0], args.stride[1]),
|
||||
cutlass_bindings.MatrixCoord(args.dilation[0], args.dilation[1]),
|
||||
cutlass_bindings.conv.Mode.cross_correlation,
|
||||
args.split_k_slices, 1
|
||||
)
|
||||
|
||||
|
||||
# User-provide inputs
|
||||
tensor_A_size = cutlass.conv.implicit_gemm_tensor_a_size(
|
||||
tensor_A_size = cutlass_bindings.conv.implicit_gemm_tensor_a_size(
|
||||
conv_kind, problem_size
|
||||
)
|
||||
tensor_B_size = cutlass.conv.implicit_gemm_tensor_b_size(
|
||||
tensor_B_size = cutlass_bindings.conv.implicit_gemm_tensor_b_size(
|
||||
conv_kind, problem_size
|
||||
)
|
||||
if args.bias:
|
||||
tensor_C_size = cutlass.conv.implicit_gemm_tensor_c_extent(
|
||||
tensor_C_size = cutlass_bindings.conv.implicit_gemm_tensor_c_extent(
|
||||
conv_kind, problem_size
|
||||
).at(3)
|
||||
else:
|
||||
tensor_C_size = cutlass.conv.implicit_gemm_tensor_c_size(
|
||||
tensor_C_size = cutlass_bindings.conv.implicit_gemm_tensor_c_size(
|
||||
conv_kind, problem_size
|
||||
)
|
||||
|
||||
tensor_D_size = cutlass.conv.implicit_gemm_tensor_c_size(
|
||||
tensor_D_size = cutlass_bindings.conv.implicit_gemm_tensor_c_size(
|
||||
conv_kind, problem_size
|
||||
)
|
||||
|
||||
@@ -288,12 +288,12 @@ arguments = Conv2dArguments(
|
||||
operation=operation, problem_size=problem_size, A=tensor_A,
|
||||
B=tensor_B, C=tensor_C, D=tensor_D,
|
||||
output_op = operation.epilogue_type(*([args.alpha, args.beta] + args.activation_args)),
|
||||
split_k_mode=getattr(cutlass.conv.SplitKMode, args.split_k_mode),
|
||||
split_k_mode=getattr(cutlass_bindings.conv.SplitKMode, args.split_k_mode),
|
||||
split_k_slices=problem_size.split_k_slices
|
||||
)
|
||||
|
||||
if args.split_k_mode == "Parallel" and args.split_k_slices > 1:
|
||||
implicit_gemm_size = cutlass.conv.implicit_gemm_problem_size(conv_kind, arguments.problem_size)
|
||||
implicit_gemm_size = cutlass_bindings.conv.implicit_gemm_problem_size(conv_kind, arguments.problem_size)
|
||||
reduction_arguments = ReductionArguments(
|
||||
reduction_operation,
|
||||
problem_size=[implicit_gemm_size.m(), implicit_gemm_size.n()],
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
#
|
||||
################################################################################
|
||||
import numpy as np
|
||||
import pycutlass
|
||||
from pycutlass import *
|
||||
from pycutlass.utils.device import device_cc
|
||||
import cutlass
|
||||
import cutlass.backend as pycutlass
|
||||
from cutlass.backend import *
|
||||
from cutlass.backend.utils.device import device_cc
|
||||
import cutlass_bindings
|
||||
from bfloat16 import bfloat16
|
||||
import sys
|
||||
|
||||
@@ -62,7 +62,7 @@ parser.add_argument("-tacc", "--element_acc", default="float32", type=str,
|
||||
help='Data type of accumulator')
|
||||
parser.add_argument('-m', "--math", default="multiply_add",
|
||||
type=str, choices=["multiply_add", "multiply_add_fast_bf16", "multiply_add_fast_f32"], help="math instruction")
|
||||
parser.add_argument('-op', "--opcode", default="simt", type=str,
|
||||
parser.add_argument('-op', "--opcode", default="Simt", type=str,
|
||||
choices=["Simt", 'TensorOp'],
|
||||
help="This option describes whether you want to use tensor \
|
||||
cores (TensorOp) or regular SIMT cores (Simt) on GPU SM")
|
||||
@@ -147,12 +147,12 @@ pycutlass.compiler.nvcc()
|
||||
|
||||
np.random.seed(0)
|
||||
|
||||
element_a = getattr(cutlass, args.element_a)
|
||||
element_b = getattr(cutlass, args.element_b)
|
||||
element_c = getattr(cutlass, args.element_c)
|
||||
element_acc = getattr(cutlass, args.element_acc)
|
||||
element_a = getattr(cutlass_bindings, args.element_a)
|
||||
element_b = getattr(cutlass_bindings, args.element_b)
|
||||
element_c = getattr(cutlass_bindings, args.element_c)
|
||||
element_acc = getattr(cutlass_bindings, args.element_acc)
|
||||
math_operation = getattr(MathOperation, args.math)
|
||||
opclass = getattr(cutlass.OpClass, args.opcode)
|
||||
opclass = getattr(cutlass_bindings.OpClass, args.opcode)
|
||||
|
||||
math_inst = MathInstruction(
|
||||
args.instruction_shape, element_a, element_b,
|
||||
@@ -164,9 +164,9 @@ tile_description = TileDescription(
|
||||
math_inst
|
||||
)
|
||||
|
||||
layout_a = getattr(cutlass, args.layout_a)
|
||||
layout_b = getattr(cutlass, args.layout_b)
|
||||
layout_c = getattr(cutlass, args.layout_c)
|
||||
layout_a = getattr(cutlass_bindings, args.layout_a)
|
||||
layout_b = getattr(cutlass_bindings, args.layout_b)
|
||||
layout_c = getattr(cutlass_bindings, args.layout_c)
|
||||
|
||||
A = TensorDescription(
|
||||
element_a, layout_a, args.alignment_a
|
||||
@@ -180,7 +180,7 @@ C = TensorDescription(
|
||||
element_c, layout_c, args.alignment_c
|
||||
)
|
||||
|
||||
element_epilogue = getattr(cutlass, args.element_epilogue)
|
||||
element_epilogue = getattr(cutlass_bindings, args.element_epilogue)
|
||||
if (args.activation_function == "identity"
|
||||
or (args.gemm_mode == "GemmSplitKParallel" and args.split_k_slices > 1)):
|
||||
#
|
||||
@@ -191,7 +191,7 @@ else:
|
||||
getattr(pycutlass, args.activation_function)(element_epilogue),
|
||||
C.element, C.alignment, math_inst.element_accumulator, element_epilogue)
|
||||
|
||||
swizzling_functor = getattr(cutlass, args.swizzling_functor)
|
||||
swizzling_functor = getattr(cutlass_bindings, args.swizzling_functor)
|
||||
|
||||
visitor = args.epilogue_visitor is not None
|
||||
|
||||
@@ -275,7 +275,7 @@ if args.gemm_mode == "GemmSplitKParallel":
|
||||
C.element, C.alignment, math_inst.element_accumulator, element_epilogue)
|
||||
|
||||
reduction_operation = ReductionOperation(
|
||||
shape=cutlass.MatrixCoord(4, 32 * C.alignment),
|
||||
shape=cutlass_bindings.MatrixCoord(4, 32 * C.alignment),
|
||||
C=C, element_accumulator=element_acc,
|
||||
element_compute=element_epilogue,
|
||||
epilogue_functor=epilogue_functor_reduction,
|
||||
@@ -287,7 +287,7 @@ pycutlass.compiler.add_module(operations)
|
||||
|
||||
# User-provide inputs
|
||||
|
||||
problem_size = cutlass.gemm.GemmCoord(
|
||||
problem_size = cutlass_bindings.gemm.GemmCoord(
|
||||
args.problem_size[0], args.problem_size[1], args.problem_size[2])
|
||||
|
||||
tensor_a_size = args.batch * problem_size.m() * problem_size.k()
|
||||
@@ -384,7 +384,7 @@ arguments = GemmArguments(
|
||||
operation=operation, problem_size=problem_size,
|
||||
A=tensor_A, B=tensor_B, C=tensor_C, D=tensor_D,
|
||||
output_op=output_op,
|
||||
gemm_mode=getattr(cutlass.gemm.Mode, args.gemm_mode),
|
||||
gemm_mode=getattr(cutlass_bindings.gemm.Mode, args.gemm_mode),
|
||||
split_k_slices=args.split_k_slices, batch=args.batch
|
||||
)
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
#
|
||||
################################################################################
|
||||
import numpy as np
|
||||
import pycutlass
|
||||
from pycutlass import *
|
||||
from pycutlass.utils.device import device_cc
|
||||
import cutlass.backend as pycutlass
|
||||
from cutlass.backend import *
|
||||
from cutlass.backend.utils.device import device_cc
|
||||
import csv
|
||||
import sys
|
||||
|
||||
@@ -61,7 +61,7 @@ parser.add_argument("-tacc", "--element_acc", default="float32", type=str,
|
||||
help='Data type of accumulator')
|
||||
parser.add_argument('-m', "--math", default="multiply_add",
|
||||
type=str, choices=["multiply_add", "multiply_add_fast_bf16", "multiply_add_fast_f32"], help="math instruction")
|
||||
parser.add_argument('-op', "--opcode", default="simt", type=str,
|
||||
parser.add_argument('-op', "--opcode", default="Simt", type=str,
|
||||
choices=["Simt", 'TensorOp'], help='This option describes whether you want to use tensor \
|
||||
cores (TensorOp) or regular SIMT cores (Simt) on GPU SM')
|
||||
# tile description
|
||||
@@ -111,7 +111,7 @@ parser.add_argument("-pm", "--precompute_mode",
|
||||
default="Device", type=str, choices=["Host", "Device"],
|
||||
help="Grouped Gemm Scheduing on device only (Device) or using host precompute (Host)")
|
||||
# arguments
|
||||
parser.add_argument("-p", "--problem_size_dir", type=str,
|
||||
parser.add_argument("-p", "--problem_size_dir", type=str, default="grouped_gemm_problem_size.csv",
|
||||
help="path to the csv file contains the problem sizes")
|
||||
parser.add_argument("-alpha", "--alpha", default=1.0, type=float, help="alpha")
|
||||
parser.add_argument("-beta", "--beta", default=0.0, type=float, help="beta")
|
||||
@@ -139,12 +139,12 @@ pycutlass.get_memory_pool(init_pool_size=2**30, max_pool_size=2**32)
|
||||
|
||||
np.random.seed(0)
|
||||
|
||||
element_a = getattr(cutlass, args.element_a)
|
||||
element_b = getattr(cutlass, args.element_b)
|
||||
element_c = getattr(cutlass, args.element_c)
|
||||
element_acc = getattr(cutlass, args.element_acc)
|
||||
element_a = getattr(cutlass_bindings, args.element_a)
|
||||
element_b = getattr(cutlass_bindings, args.element_b)
|
||||
element_c = getattr(cutlass_bindings, args.element_c)
|
||||
element_acc = getattr(cutlass_bindings, args.element_acc)
|
||||
math_operation = getattr(MathOperation, args.math)
|
||||
opclass = getattr(cutlass.OpClass, args.opcode)
|
||||
opclass = getattr(cutlass_bindings.OpClass, args.opcode)
|
||||
|
||||
math_inst = MathInstruction(
|
||||
args.instruction_shape, element_a, element_b,
|
||||
@@ -156,9 +156,9 @@ tile_description = TileDescription(
|
||||
math_inst
|
||||
)
|
||||
|
||||
layout_a = getattr(cutlass, args.layout_a)
|
||||
layout_b = getattr(cutlass, args.layout_b)
|
||||
layout_c = getattr(cutlass, args.layout_c)
|
||||
layout_a = getattr(cutlass_bindings, args.layout_a)
|
||||
layout_b = getattr(cutlass_bindings, args.layout_b)
|
||||
layout_c = getattr(cutlass_bindings, args.layout_c)
|
||||
|
||||
A = TensorDescription(
|
||||
element_a, layout_a, args.alignment_a
|
||||
@@ -172,7 +172,7 @@ C = TensorDescription(
|
||||
element_c, layout_c, args.alignment_c
|
||||
)
|
||||
|
||||
element_epilogue = getattr(cutlass, args.element_epilogue)
|
||||
element_epilogue = getattr(cutlass_bindings, args.element_epilogue)
|
||||
if args.activation_function == "identity":
|
||||
epilogue_functor = getattr(pycutlass, args.epilogue_functor)(
|
||||
C.element, C.alignment, math_inst.element_accumulator, element_epilogue)
|
||||
@@ -180,7 +180,7 @@ else:
|
||||
epilogue_functor = getattr(pycutlass, "LinearCombinationGeneric")(
|
||||
getattr(pycutlass, args.activation_function)(element_epilogue),
|
||||
C.element, C.alignment, math_inst.element_accumulator, element_epilogue)
|
||||
swizzling_functor = getattr(cutlass, args.swizzling_functor)
|
||||
swizzling_functor = getattr(cutlass_bindings, args.swizzling_functor)
|
||||
precompute_mode = getattr(SchedulerMode, args.precompute_mode)
|
||||
|
||||
operation = GemmOperationGrouped(
|
||||
@@ -203,7 +203,7 @@ with open(args.problem_size_dir) as csv_file:
|
||||
reader = csv.reader(csv_file)
|
||||
for row in reader:
|
||||
problem_sizes.append(
|
||||
cutlass.gemm.GemmCoord(int(row[0]), int(row[1]), int(row[2]))
|
||||
cutlass_bindings.gemm.GemmCoord(int(row[0]), int(row[1]), int(row[2]))
|
||||
)
|
||||
|
||||
problem_count = len(problem_sizes)
|
||||
|
||||
@@ -37,10 +37,10 @@ import argparse
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
import cutlass
|
||||
import pycutlass
|
||||
from pycutlass import *
|
||||
from pycutlass.utils.device import device_cc
|
||||
import cutlass_bindings
|
||||
import cutlass.backend as pycutlass
|
||||
from cutlass.backend import *
|
||||
from cutlass.backend.utils.device import device_cc
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Launch a GEMM kernel from Python: 'D = alpha * A * B + beta * C'")
|
||||
@@ -72,11 +72,11 @@ pycutlass.get_memory_pool(init_pool_size=2**30, max_pool_size=2**32)
|
||||
pycutlass.compiler.nvcc()
|
||||
|
||||
# Set up A, B, C and accumulator
|
||||
A = TensorDescription(cutlass.float16, cutlass.ColumnMajor, alignment)
|
||||
B = TensorDescription(cutlass.float16, cutlass.RowMajor, alignment)
|
||||
C = TensorDescription(cutlass.float32, cutlass.ColumnMajor, alignment)
|
||||
element_acc = cutlass.float32
|
||||
element_epilogue = cutlass.float32
|
||||
A = TensorDescription(cutlass_bindings.float16, cutlass_bindings.ColumnMajor, alignment)
|
||||
B = TensorDescription(cutlass_bindings.float16, cutlass_bindings.RowMajor, alignment)
|
||||
C = TensorDescription(cutlass_bindings.float32, cutlass_bindings.ColumnMajor, alignment)
|
||||
element_acc = cutlass_bindings.float32
|
||||
element_epilogue = cutlass_bindings.float32
|
||||
|
||||
# Select instruction shape based on the Tensor Core instructions supported
|
||||
# by the device on which we are running
|
||||
@@ -85,12 +85,14 @@ if cc == 70:
|
||||
elif cc == 75:
|
||||
instruction_shape = [16, 8, 8]
|
||||
else:
|
||||
# Use CUTLASS kernels for CC 80 by default (e.g., for cases in which SM86 is used)
|
||||
cc = 80
|
||||
instruction_shape = [16, 8, 16]
|
||||
|
||||
math_inst = MathInstruction(
|
||||
instruction_shape,
|
||||
A.element, B.element, element_acc,
|
||||
cutlass.OpClass.TensorOp,
|
||||
cutlass_bindings.OpClass.TensorOp,
|
||||
MathOperation.multiply_add
|
||||
)
|
||||
|
||||
@@ -122,7 +124,7 @@ tensor_B = np.ceil(np.random.uniform(low=-8.5, high=7.5, size=(args.k * args.n,)
|
||||
tensor_C = np.ceil(np.random.uniform(low=-8.5, high=7.5, size=(args.m * args.n,))).astype(np.float32)
|
||||
tensor_D = np.zeros(shape=(args.m * args.n,)).astype(np.float32)
|
||||
|
||||
problem_size = cutlass.gemm.GemmCoord(args.m, args.n, args.k)
|
||||
problem_size = cutlass_bindings.gemm.GemmCoord(args.m, args.n, args.k)
|
||||
alpha = 1.
|
||||
beta = 0.
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@ import argparse
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
import cutlass
|
||||
import pycutlass
|
||||
from pycutlass import *
|
||||
from pycutlass.utils.device import device_cc
|
||||
import cutlass_bindings
|
||||
import cutlass.backend as pycutlass
|
||||
from cutlass.backend import *
|
||||
from cutlass.backend.utils.device import device_cc
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Launch a grouped GEMM kernel from Python")
|
||||
@@ -65,11 +65,11 @@ pycutlass.compiler.nvcc()
|
||||
|
||||
# Set up A, B, C and accumulator
|
||||
alignment = 1
|
||||
A = TensorDescription(cutlass.float16, cutlass.ColumnMajor, alignment)
|
||||
B = TensorDescription(cutlass.float16, cutlass.RowMajor, alignment)
|
||||
C = TensorDescription(cutlass.float32, cutlass.ColumnMajor, alignment)
|
||||
element_acc = cutlass.float32
|
||||
element_epilogue = cutlass.float32
|
||||
A = TensorDescription(cutlass_bindings.float16, cutlass_bindings.ColumnMajor, alignment)
|
||||
B = TensorDescription(cutlass_bindings.float16, cutlass_bindings.RowMajor, alignment)
|
||||
C = TensorDescription(cutlass_bindings.float32, cutlass_bindings.ColumnMajor, alignment)
|
||||
element_acc = cutlass_bindings.float32
|
||||
element_epilogue = cutlass_bindings.float32
|
||||
|
||||
# Select instruction shape based on the Tensor Core instructions supported
|
||||
# by the device on which we are running
|
||||
@@ -78,12 +78,14 @@ if cc == 70:
|
||||
elif cc == 75:
|
||||
instruction_shape = [16, 8, 8]
|
||||
else:
|
||||
# Use CUTLASS kernels for CC 80 by default (e.g., for cases in which SM86 is used)
|
||||
cc = 80
|
||||
instruction_shape = [16, 8, 16]
|
||||
|
||||
math_inst = MathInstruction(
|
||||
instruction_shape,
|
||||
A.element, B.element, element_acc,
|
||||
cutlass.OpClass.TensorOp,
|
||||
cutlass_bindings.OpClass.TensorOp,
|
||||
MathOperation.multiply_add
|
||||
)
|
||||
|
||||
@@ -112,8 +114,8 @@ pycutlass.compiler.add_module(operations)
|
||||
|
||||
# Initialize tensors for each problem in the group
|
||||
problem_sizes = [
|
||||
cutlass.gemm.GemmCoord(128, 128, 64),
|
||||
cutlass.gemm.GemmCoord(512, 256, 128)
|
||||
cutlass_bindings.gemm.GemmCoord(128, 128, 64),
|
||||
cutlass_bindings.gemm.GemmCoord(512, 256, 128)
|
||||
]
|
||||
problem_count = len(problem_sizes)
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ class DualGemm {
|
||||
using Mma0 = typename cutlass::gemm::threadblock::DefaultMma<
|
||||
ElementA, LayoutA, kAlignmentA, ElementB, LayoutB0, kAlignmentB,
|
||||
ElementAccumulator, layout::RowMajor, arch::OpClassTensorOp, ArchTag,
|
||||
ThreadblockShape, WarpShape,
|
||||
ThreadblockShape, WarpShape,
|
||||
InstructionShape, Stages, Operator>::ThreadblockMma;
|
||||
using Mma1 = typename cutlass::gemm::threadblock::DefaultMma<
|
||||
ElementA, LayoutA, kAlignmentA, ElementB, LayoutB1, kAlignmentB,
|
||||
@@ -348,7 +348,7 @@ public:
|
||||
ThreadblockSwizzle threadblock_swizzle;
|
||||
|
||||
cutlass::gemm::GemmCoord tiled_shape = threadblock_swizzle.get_tiled_shape(
|
||||
args.problem_size,
|
||||
args.problem_size,
|
||||
{ThreadblockShape::kM, ThreadblockShape::kN, ThreadblockShape::kK},
|
||||
args.split_k_slices);
|
||||
|
||||
|
||||
@@ -167,10 +167,10 @@ bool run_nonfused_gemm_f16_sm80() {
|
||||
std::cout << "Running Non-fused GEMMs FP16 TN GEMMs...\n";
|
||||
|
||||
bool pass = nonFusedGemm.run(
|
||||
problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1,
|
||||
true /* is_profiling */
|
||||
);
|
||||
@@ -248,10 +248,10 @@ bool run_fused_gemm_f16_sm80_shmem() {
|
||||
std::cout << "Running Fused FP16 TN GEMMs + Epilogue2...\n";
|
||||
|
||||
bool passed = fusedGemm.run(
|
||||
problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1
|
||||
);
|
||||
|
||||
@@ -301,11 +301,11 @@ bool run_batched_fused_gemm_f16_sm80_shmem() {
|
||||
std::cout << "Running Batched Fused FP16 TN GEMMs + Epilogue2...\n";
|
||||
|
||||
bool passed = fusedGemm.run(
|
||||
batch_problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1,
|
||||
batch_problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1,
|
||||
kBatchCount,
|
||||
false, /* broadcast_b1 */
|
||||
false /* is_profiling */
|
||||
@@ -358,11 +358,11 @@ bool run_broadcast_fused_gemm_f16_sm80_shmem() {
|
||||
std::cout << "Running Broadcast Fused FP16 TN GEMMs + Epilogue2...\n";
|
||||
|
||||
bool passed = fusedGemm.run(
|
||||
problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1,
|
||||
problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1,
|
||||
1, /* batch_count */
|
||||
true, /* broadcast_b1 */
|
||||
true /* is_profiling */
|
||||
@@ -415,11 +415,11 @@ bool run_batched_broadcast_fused_gemm_f16_sm80_shmem() {
|
||||
std::cout << "Running Batch Broadcast Fused FP16 TN GEMMs + Epilogue2...\n";
|
||||
|
||||
bool passed = fusedGemm.run(
|
||||
batch_problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1,
|
||||
batch_problem_size,
|
||||
alpha0,
|
||||
beta0,
|
||||
alpha1,
|
||||
beta1,
|
||||
kBatchCount,
|
||||
true, /* broadcast_b1 */
|
||||
false /* is_profiling */
|
||||
@@ -444,11 +444,11 @@ int main() {
|
||||
};
|
||||
|
||||
std::string test_name = (
|
||||
"dual-gemm f16 bias=" +
|
||||
std::to_string(kUseBias) +
|
||||
" split_k_serial=" +
|
||||
"dual-gemm f16 bias=" +
|
||||
std::to_string(kUseBias) +
|
||||
" split_k_serial=" +
|
||||
std::to_string(kSplitKSerial) +
|
||||
" batch_count=" +
|
||||
" batch_count=" +
|
||||
std::to_string(kBatchCount)
|
||||
);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "cutlass/util/reference/device/gemm.h"
|
||||
#include "cutlass/util/reference/device/tensor_relu.h"
|
||||
|
||||
#include "cutlass/platform/platform.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/device/gemm_universal.h"
|
||||
|
||||
@@ -356,13 +357,13 @@ struct NonFusedDualGemmRun
|
||||
|
||||
for(int i = 0; i < runs; i++) {
|
||||
status = gemm_op_0();
|
||||
|
||||
|
||||
CUTLASS_CHECK(status);
|
||||
}
|
||||
cudaEventRecord(stop1);
|
||||
for(int i = 0; i < runs; i++) {
|
||||
status = gemm_op_1();
|
||||
|
||||
|
||||
CUTLASS_CHECK(status);
|
||||
}
|
||||
|
||||
@@ -564,22 +565,22 @@ struct DualFusedGemmRun
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementA,
|
||||
typename DualGemm::LayoutA> tensor_A0(
|
||||
std::is_same<typename DualGemm::LayoutA, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.k()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutA, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.k()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.k()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementB,
|
||||
typename DualGemm::LayoutB0> tensor_B0(
|
||||
std::is_same<typename DualGemm::LayoutB0, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.k(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutB0, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.k(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.k(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> tensor_C0(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
@@ -589,22 +590,22 @@ struct DualFusedGemmRun
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> tensor_D0(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> reference_D0(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementB,
|
||||
typename DualGemm::LayoutB1> tensor_B1(
|
||||
std::is_same<typename DualGemm::LayoutB1, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.k(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutB1, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.k(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.k(), batch_count * problem_size.n()));
|
||||
if (broadcast_b1) {
|
||||
tensor_B1.resize({problem_size.k(), batch_count});
|
||||
@@ -613,8 +614,8 @@ struct DualFusedGemmRun
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> tensor_C1(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
@@ -624,29 +625,29 @@ struct DualFusedGemmRun
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> tensor_D1(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> tensor_D2(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> reference_D1(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
cutlass::HostTensor<
|
||||
typename DualGemm::ElementC,
|
||||
typename DualGemm::LayoutC> reference_D2(
|
||||
std::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::platform::is_same<typename DualGemm::LayoutC, cutlass::layout::RowMajor>::value ?
|
||||
cutlass::MatrixCoord(batch_count * problem_size.m(), problem_size.n()) :
|
||||
cutlass::MatrixCoord(problem_size.m(), batch_count * problem_size.n()));
|
||||
|
||||
CHECK_TRUE(initialize_tensor(tensor_A0.host_view(), init_A, seed + 2019));
|
||||
@@ -712,16 +713,16 @@ struct DualFusedGemmRun
|
||||
ref_B1 = {tensor_Bias1.device_data(), typename DualGemm::LayoutC::Stride(0)};
|
||||
}
|
||||
typename DualGemm::Arguments arguments{
|
||||
(batch_count > 1 ?
|
||||
cutlass::gemm::DualGemmMode::kBatched :
|
||||
(batch_count > 1 ?
|
||||
cutlass::gemm::DualGemmMode::kBatched :
|
||||
cutlass::gemm::DualGemmMode::kGemm),
|
||||
problem_size,
|
||||
tensor_A0.device_ref(),
|
||||
tensor_B0.device_ref(),
|
||||
ref_B0,
|
||||
DualGemm::kStoreD0 ? tensor_D0.device_ref() : nullptr_ref,
|
||||
(broadcast_b1 ?
|
||||
typename DualGemm::TensorRefB1(tensor_B1.device_data(), 0) :
|
||||
(broadcast_b1 ?
|
||||
typename DualGemm::TensorRefB1(tensor_B1.device_data(), 0) :
|
||||
tensor_B1.device_ref()),
|
||||
ref_B1,
|
||||
DualGemm::kStoreD1 ? tensor_D1.device_ref() : nullptr_ref,
|
||||
@@ -793,15 +794,15 @@ struct DualFusedGemmRun
|
||||
using GemmUniversal0 = cutlass::gemm::device::GemmUniversal<
|
||||
typename DualGemm::ElementA, typename DualGemm::LayoutA,
|
||||
typename DualGemm::ElementB, typename DualGemm::LayoutB0,
|
||||
typename DualGemm::ElementC, typename DualGemm::LayoutC,
|
||||
typename DualGemm::ElementC, typename DualGemm::LayoutC,
|
||||
ElementAccumulator
|
||||
>;
|
||||
|
||||
GemmUniversal0 reference_gemm0;
|
||||
|
||||
typename GemmUniversal0::Arguments args0 {
|
||||
(batch_count > 1 ?
|
||||
cutlass::gemm::GemmUniversalMode::kBatched :
|
||||
(batch_count > 1 ?
|
||||
cutlass::gemm::GemmUniversalMode::kBatched :
|
||||
cutlass::gemm::GemmUniversalMode::kGemm),
|
||||
problem_size,
|
||||
batch_count,
|
||||
@@ -828,15 +829,15 @@ struct DualFusedGemmRun
|
||||
using GemmUniversal1 = cutlass::gemm::device::GemmUniversal<
|
||||
typename DualGemm::ElementA, typename DualGemm::LayoutA,
|
||||
typename DualGemm::ElementB, typename DualGemm::LayoutB1,
|
||||
typename DualGemm::ElementC, typename DualGemm::LayoutC,
|
||||
typename DualGemm::ElementC, typename DualGemm::LayoutC,
|
||||
ElementAccumulator
|
||||
>;
|
||||
|
||||
GemmUniversal1 reference_gemm1;
|
||||
|
||||
typename GemmUniversal1::Arguments args1 {
|
||||
(batch_count > 1 ?
|
||||
cutlass::gemm::GemmUniversalMode::kBatched :
|
||||
(batch_count > 1 ?
|
||||
cutlass::gemm::GemmUniversalMode::kBatched :
|
||||
cutlass::gemm::GemmUniversalMode::kGemm),
|
||||
problem_size,
|
||||
batch_count,
|
||||
@@ -861,7 +862,7 @@ struct DualFusedGemmRun
|
||||
CUTLASS_CHECK(status);
|
||||
|
||||
if(relu) {
|
||||
cutlass::reference::device::TensorReLu(reference_D0.device_view());
|
||||
cutlass::reference::device::TensorReLu(reference_D0.device_view());
|
||||
cutlass::reference::device::TensorReLu(reference_D1.device_view());
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ struct DualGemm {
|
||||
int offset_k = 0;
|
||||
int problem_size_k = params.problem_size.k();
|
||||
|
||||
ElementA *ptr_A0 = static_cast<ElementA *>(params.ref_A0.data());
|
||||
ElementA *ptr_A0 = static_cast<ElementA *>(params.ref_A0.data());
|
||||
ElementB *ptr_B0 = static_cast<ElementB *>(params.ref_B0.data());
|
||||
ElementB *ptr_B1 = static_cast<ElementB *>(params.ref_B1.data());
|
||||
|
||||
@@ -309,7 +309,7 @@ struct DualGemm {
|
||||
//
|
||||
if (params.mode == DualGemmMode::kGemm) {
|
||||
if (threadblock_tile_offset.k() + 1 < params.grid_tiled_shape.k()) {
|
||||
problem_size_k = (threadblock_tile_offset.k() + 1) * params.gemm_k_size;
|
||||
problem_size_k = (threadblock_tile_offset.k() + 1) * params.gemm_k_size;
|
||||
}
|
||||
|
||||
offset_k = threadblock_tile_offset.k() * params.gemm_k_size;
|
||||
@@ -413,11 +413,11 @@ struct DualGemm {
|
||||
|
||||
int block_idx = threadblock_tile_offset.m() + threadblock_tile_offset.n() * params.grid_tiled_shape.m();
|
||||
|
||||
ElementC *ptr_C0 = static_cast<ElementC *>(params.ref_C0.data());
|
||||
ElementC *ptr_C1 = static_cast<ElementC *>(params.ref_C1.data());
|
||||
ElementC *ptr_D0 = static_cast<ElementC *>(params.ref_D0.data());
|
||||
ElementC *ptr_D1 = static_cast<ElementC *>(params.ref_D1.data());
|
||||
ElementC *ptr_D2 = static_cast<ElementC *>(params.ref_D2.data());
|
||||
ElementC *ptr_C0 = static_cast<ElementC *>(params.ref_C0.data());
|
||||
ElementC *ptr_C1 = static_cast<ElementC *>(params.ref_C1.data());
|
||||
ElementC *ptr_D0 = static_cast<ElementC *>(params.ref_D0.data());
|
||||
ElementC *ptr_D1 = static_cast<ElementC *>(params.ref_D1.data());
|
||||
ElementC *ptr_D2 = static_cast<ElementC *>(params.ref_D2.data());
|
||||
|
||||
// Construct the semaphore.
|
||||
Semaphore semaphore(params.semaphore + block_idx, thread_idx);
|
||||
@@ -425,7 +425,7 @@ struct DualGemm {
|
||||
if (params.mode == DualGemmMode::kGemm) {
|
||||
// If performing a reduction via split-K, fetch the initial synchronization
|
||||
if (kSplitKSerial && params.grid_tiled_shape.k() > 1) {
|
||||
|
||||
|
||||
// Fetch the synchronization lock initially but do not block.
|
||||
semaphore.fetch();
|
||||
|
||||
|
||||
@@ -233,6 +233,17 @@ struct Options {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter size passed through command line does not match filter size template parameter
|
||||
if (filter_size.h() != FilterShape::kRow || filter_size.w() != FilterShape::kColumn) {
|
||||
std::cerr << "Filter size passed in (" << filter_size.h() << "x" << filter_size.w() << ") "
|
||||
<< "must match the FilterShape template parameter of the convolution "
|
||||
<< "(" << FilterShape::kRow << "x" << FilterShape::kColumn << "). "
|
||||
<< "To use the filter shape passed in, change the FilterShape template "
|
||||
<< "parameter and recompile this example."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -319,9 +330,9 @@ struct Options {
|
||||
"table\n";
|
||||
|
||||
out << "\n\nExamples:\n\n"
|
||||
<< "$ ./examples/45_depthwise_simt_conv2dfprop/45_depthwise_simt_conv2dfprop --n=32 "
|
||||
<< "$ ./examples/46_depthwise_simt_conv2dfprop/46_depthwise_simt_conv2dfprop --n=32 "
|
||||
"--h=224 --w=224 --c=128 --k=128 --g=128 --r=3 --s=3\n\n"
|
||||
<< "$ ./examples/45_depthwise_simt_conv2dfprop/45_depthwise_simt_conv2dfprop --n=1 "
|
||||
<< "$ ./examples/46_depthwise_simt_conv2dfprop/46_depthwise_simt_conv2dfprop --n=1 "
|
||||
"--h=224 --w=224 --c=32 --k=32 --g=32 --r=3 --s=3 --splitk=10 --ref-check\n\n";
|
||||
|
||||
return out;
|
||||
@@ -515,14 +526,13 @@ Result profile_convolution(Options const &options) {
|
||||
ElementOutput,
|
||||
LayoutOutput,
|
||||
ElementComputeEpilogue,
|
||||
ElementAccumulator,
|
||||
cutlass::NumericConverter<ElementOutput, ElementComputeEpilogue> >(problem_size,
|
||||
tensor_a.host_ref(),
|
||||
tensor_b.host_ref(),
|
||||
tensor_c.host_ref(),
|
||||
tensor_ref_d.host_ref(),
|
||||
options.alpha,
|
||||
options.beta);
|
||||
ElementAccumulator >(problem_size,
|
||||
tensor_a.host_ref(),
|
||||
tensor_b.host_ref(),
|
||||
tensor_c.host_ref(),
|
||||
tensor_ref_d.host_ref(),
|
||||
options.alpha,
|
||||
options.beta);
|
||||
|
||||
// Check if output from CUTLASS kernel and reference kernel are equal or not
|
||||
tensor_d.sync_host();
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include "cutlass/epilogue/thread/linear_combination.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/gemm/collective/collective_builder.hpp"
|
||||
#include "cutlass/epilogue/collective/collective_builder.hpp"
|
||||
#include "cutlass/gemm/device/gemm_universal_adapter.h"
|
||||
#include "cutlass/gemm/kernel/gemm_universal.hpp"
|
||||
|
||||
@@ -95,12 +96,13 @@ constexpr int AlignmentB = 128 / cutlass::sizeof_bits<ElementB>::value; // M
|
||||
// C/D matrix configuration
|
||||
using ElementC = float; // Element type for C and D matrix operands
|
||||
using LayoutC = cutlass::layout::ColumnMajor; // Layout type for C and D matrix operands
|
||||
constexpr int AlignmentC = 128 / cutlass::sizeof_bits<ElementC>::value; // Memory access granularity/alignment of C matrix in units of elements (up to 16 bytes)
|
||||
|
||||
// Core kernel configurations
|
||||
using ElementAccumulator = float; // Element type for internal accumulation
|
||||
using ArchTag = cutlass::arch::Sm90; // Tag indicating the minimum SM that supports the intended feature
|
||||
using OperatorClass = cutlass::arch::OpClassTensorOp; // Operator class tag
|
||||
using TilesShape = Shape<_128,_128,_32>; // Threadblock-level tile size
|
||||
using TileShape = Shape<_128,_128,_32>; // Threadblock-level tile size
|
||||
using ClusterShape = Shape<_1,_2,_1>; // Shape of the threadblocks in a cluster
|
||||
using StageCountType = cutlass::gemm::collective::StageCountAuto; // Stage count maximized based on the tile size
|
||||
using KernelSchedule = cutlass::gemm::collective::KernelScheduleAuto; // Kernel to launch based on the default setting in the Collective Builder
|
||||
@@ -110,15 +112,20 @@ using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder
|
||||
ElementA, LayoutA, AlignmentA,
|
||||
ElementB, LayoutB, AlignmentB,
|
||||
ElementAccumulator,
|
||||
TilesShape, ClusterShape,
|
||||
TileShape, ClusterShape,
|
||||
cutlass::gemm::collective::StageCountAuto,
|
||||
cutlass::gemm::collective::KernelScheduleAuto
|
||||
>::CollectiveOp;
|
||||
|
||||
using CollectiveEpilogue = cutlass::epilogue::collective::DefaultEpilogue<
|
||||
cutlass::gemm::TagToStrideC_t<LayoutC>,
|
||||
cutlass::gemm::TagToStrideC_t<LayoutC>,
|
||||
cutlass::epilogue::thread::LinearCombination<ElementC, 1, ElementAccumulator, ElementAccumulator>>;
|
||||
using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
|
||||
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
||||
TileShape, ClusterShape,
|
||||
cutlass::epilogue::collective::EpilogueTileAuto,
|
||||
ElementAccumulator, ElementAccumulator,
|
||||
ElementC, LayoutC, AlignmentC,
|
||||
ElementC, LayoutC, AlignmentC,
|
||||
cutlass::epilogue::collective::EpilogueScheduleAuto
|
||||
>::CollectiveOp;
|
||||
|
||||
using GemmKernel = cutlass::gemm::kernel::GemmUniversal<
|
||||
Shape<int,int,int>, // Indicates ProblemShape
|
||||
@@ -308,11 +315,8 @@ typename Gemm::Arguments args_from_options(const Options &options)
|
||||
typename Gemm::Arguments arguments{
|
||||
cutlass::gemm::GemmUniversalMode::kGemm,
|
||||
{options.m, options.n, options.k},
|
||||
block_A.get(),
|
||||
stride_A,
|
||||
block_B.get(),
|
||||
stride_B,
|
||||
{block_C.get(), stride_C, block_D.get(), stride_D, {options.alpha, options.beta}}
|
||||
{block_A.get(), stride_A, block_B.get(), stride_B},
|
||||
{{options.alpha, options.beta}, block_C.get(), stride_C, block_D.get(), stride_D}
|
||||
};
|
||||
|
||||
return arguments;
|
||||
|
||||
+88
-47
@@ -77,10 +77,27 @@
|
||||
will fit in shared memory given the types of operands and the thread block shape, rather than simply using
|
||||
a single default value.
|
||||
|
||||
Note that one does not need to use the CollectiveBuilder to declare CUTLASS 3 kernels; one can still provide
|
||||
every template parameter to the gemm::collective::CollectiveMma. Specifying every template parameter in this
|
||||
manner remains the primary API for using CUTLASS 3 kernels. The CollectiveBuilder is simply meant to be
|
||||
a convenience interface.
|
||||
CUTLASS 3.x provides builders for both collective mainloops and epilogues. The particular implementation of
|
||||
the collective is specified via the schedule tags that corresond to the underlying collective's
|
||||
dispatch policy. `gemm::collective::KernelScheduleAuto` and `epilogue::collective::EpilogueScheduleAuto`
|
||||
are special cases of these schedules that allow the builder to also decide the dispatch policy for you,
|
||||
therefore letting the builder pick the collective specialization.
|
||||
|
||||
CUTLASS builders make an attempt to pick the best schedule when `Auto` is provided such that the
|
||||
assembled collctives have the best performance, but this is not a guarantee. A user relying on `Auto`
|
||||
may get a free performance upgrade with newer CUTLASS releases in case we can provide more optimized
|
||||
implementations that the builder can transparently assemble for `Auto`.
|
||||
|
||||
If a user decides to let the builders pick the collective specialization via `Auto` schedules,
|
||||
they must be used for both mainloop and epilogue alike to ensure compatibility between the
|
||||
chosen collectives. Additionally, if a user chooses to opt in to a specific schedule, non-`Auto`
|
||||
schedules must be used for both mainloop and epilogue builder schedules, and these schedules
|
||||
must be compatible.
|
||||
|
||||
One does not need to use the CollectiveBuilder to declare CUTLASS 3 kernels; one can still provide
|
||||
every template parameter to the `gemm::collective::CollectiveMma`. Specifying every template parameter
|
||||
in this manner remains the primary API for using CUTLASS 3 kernels. `CollectiveBuilder`s are
|
||||
simply meant to be a convenience interface.
|
||||
|
||||
Note also that, while the selections made by CollectiveBuilder attempt to maximize performance, this is not
|
||||
a guarantee. Furthermore, the behavior of the CollectiveBuilder when `Auto` parameters are provided is subject
|
||||
@@ -94,7 +111,7 @@
|
||||
extending the problem size with an additional tensor rank.
|
||||
|
||||
Example usage:
|
||||
$ ./examples/49_hopper_gemm_schedules_with_collective_builder/49_hopper_gemm_schedules_with_collective_builder \
|
||||
$ ./examples/49_hopper_with_collective_builder/49_collective_builder \
|
||||
--m=2048 --n=2048 --k=2048 --l=2
|
||||
*/
|
||||
|
||||
@@ -108,6 +125,7 @@
|
||||
#include "cutlass/epilogue/thread/linear_combination.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/gemm/collective/collective_builder.hpp"
|
||||
#include "cutlass/epilogue/collective/collective_builder.hpp"
|
||||
#include "cutlass/gemm/device/gemm_universal_adapter.h"
|
||||
#include "cutlass/gemm/kernel/gemm_universal.hpp"
|
||||
|
||||
@@ -160,7 +178,7 @@ struct Options {
|
||||
/// Prints the usage statement.
|
||||
std::ostream & print_usage(std::ostream &out) const {
|
||||
|
||||
out << "49_hopper_gemm_schedules_with_collective_builder\n\n"
|
||||
out << "49_hopper_with_collective_builder\n\n"
|
||||
<< " This example showcases the use of CUTLASS's collective operation builders to easily construct\n"
|
||||
<< " performant kernels targeting NVIDIA's Hopper architecture.\n\n"
|
||||
<< "Options:\n\n"
|
||||
@@ -212,14 +230,24 @@ bool initialize_block(
|
||||
// operation builders by specializing the GEMM only on the kernel schedule it will use and the
|
||||
// number of pipeline stages.
|
||||
//
|
||||
// For either option, one can use a special `Auto` type that tells the CollectiveBuilder
|
||||
// One can use a special `Auto` type that tells the CollectiveBuilder
|
||||
// to select an appropriate value on its own. The CollectiveBuilder will attempt to select
|
||||
// values that will result in the most-performant kernel, but this is not a guarantee. Furthermore,
|
||||
// the behavior of the CollectiveBuilder with `Auto` types is subject to change in future releases
|
||||
// configurations that will result in the most-performant kernel, but this is not a guarantee.
|
||||
//
|
||||
// If relying on 'Auto' schedules, all builders must use the 'Auto' schedule to ensure compatiblity.
|
||||
// For example, if `KernelScheduleAuto` is used for the mainloop builder, `EpilogueScheduleAuto` must
|
||||
// be used for the epilogue builder.
|
||||
//
|
||||
// Furthermore, if an override schedule is selected, both epilgoue and mainloop schedules must
|
||||
// be specifically opt into a compatible selection.
|
||||
//
|
||||
// Behavior of the CollectiveBuilder with `Auto` types is subject to change in future releases
|
||||
// -- do not rely on `Auto` if you require a specific scheduling policy.
|
||||
template <
|
||||
// Type of kernel schedule to generate
|
||||
class KernelScheduleType = cutlass::gemm::collective::KernelScheduleAuto,
|
||||
class MainloopScheduleType = cutlass::gemm::collective::KernelScheduleAuto,
|
||||
// Type of epilogue schedule to generate
|
||||
class EpilogueScheduleType = cutlass::epilogue::collective::EpilogueScheduleAuto,
|
||||
// Number of pipeline stages to use
|
||||
class StageCountType = cutlass::gemm::collective::StageCountAuto
|
||||
>
|
||||
@@ -230,22 +258,32 @@ struct ExampleRunner {
|
||||
using LayoutC = cutlass::layout::ColumnMajor;
|
||||
using LayoutD = cutlass::layout::ColumnMajor;
|
||||
|
||||
static constexpr int kAlignmentA = 8;
|
||||
static constexpr int kAlignmentB = 8;
|
||||
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
|
||||
static constexpr int AlignmentA = 8;
|
||||
static constexpr int AlignmentB = 8;
|
||||
static constexpr int AlignmentC = 8;
|
||||
static constexpr int AlignmentD = 8;
|
||||
|
||||
using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
|
||||
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
||||
cutlass::half_t, LayoutA, kAlignmentA,
|
||||
cutlass::half_t, LayoutB, kAlignmentB,
|
||||
float,
|
||||
Shape<_128,_128,_64>, Shape<_2,_1,_1>,
|
||||
StageCountType,
|
||||
KernelScheduleType
|
||||
Shape<_128,_128,_64>, Shape<_1,_1,_1>,
|
||||
cutlass::epilogue::collective::EpilogueTileAuto,
|
||||
float, float,
|
||||
cutlass::half_t, LayoutC, AlignmentC,
|
||||
cutlass::half_t, LayoutD, AlignmentD,
|
||||
EpilogueScheduleType
|
||||
>::CollectiveOp;
|
||||
|
||||
using CollectiveEpilogue = cutlass::epilogue::collective::DefaultEpilogue<
|
||||
cutlass::gemm::TagToStrideC_t<LayoutC>,
|
||||
cutlass::gemm::TagToStrideC_t<LayoutD>,
|
||||
cutlass::epilogue::thread::LinearCombination<cutlass::half_t, 1, float, float>>;
|
||||
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
|
||||
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
|
||||
cutlass::half_t, LayoutA, AlignmentA,
|
||||
cutlass::half_t, LayoutB, AlignmentB,
|
||||
float,
|
||||
Shape<_128,_128,_64>, Shape<_2,_1,_1>,
|
||||
std::conditional_t<std::is_same_v<StageCountType, cutlass::gemm::collective::StageCountAuto>,
|
||||
cutlass::gemm::collective::StageCountAutoCarveout<(int)sizeof(typename CollectiveEpilogue::SharedStorage)>,
|
||||
StageCountType>,
|
||||
MainloopScheduleType
|
||||
>::CollectiveOp;
|
||||
|
||||
using GemmKernel = cutlass::gemm::kernel::GemmUniversal<
|
||||
Shape<int,int,int,int>,
|
||||
@@ -262,10 +300,10 @@ struct ExampleRunner {
|
||||
using StrideC = typename Gemm::GemmKernel::StrideC;
|
||||
using StrideD = typename Gemm::GemmKernel::StrideD;
|
||||
|
||||
using LayoutTagA = decltype(cutlass::gemm::detail::stride_to_layout_tag_A<StrideA>());
|
||||
using LayoutTagB = decltype(cutlass::gemm::detail::stride_to_layout_tag_B<StrideB>());
|
||||
using LayoutTagC = decltype(cutlass::gemm::detail::stride_to_layout_tag_A<StrideC>());
|
||||
using LayoutTagD = decltype(cutlass::gemm::detail::stride_to_layout_tag_A<StrideD>());
|
||||
using LayoutTagA = cutlass::gemm::detail::StrideToLayoutTagA_t<StrideA>;
|
||||
using LayoutTagB = cutlass::gemm::detail::StrideToLayoutTagB_t<StrideB>;
|
||||
using LayoutTagC = cutlass::gemm::detail::StrideToLayoutTagC_t<StrideC>;
|
||||
using LayoutTagD = cutlass::gemm::detail::StrideToLayoutTagC_t<StrideD>;
|
||||
|
||||
//
|
||||
// Data members
|
||||
@@ -356,11 +394,8 @@ struct ExampleRunner {
|
||||
typename Gemm::Arguments arguments{
|
||||
cutlass::gemm::GemmUniversalMode::kGemm,
|
||||
problem_size,
|
||||
block_A.get(),
|
||||
stride_A,
|
||||
block_B.get(),
|
||||
stride_B,
|
||||
{block_C.get(), stride_C, block_D.get(), stride_D, {options.alpha, options.beta}},
|
||||
{block_A.get(), stride_A, block_B.get(), stride_B},
|
||||
{{options.alpha, options.beta}, block_C.get(), stride_C, block_D.get(), stride_D},
|
||||
hw_info
|
||||
};
|
||||
|
||||
@@ -477,42 +512,48 @@ int main(int argc, char const **args) {
|
||||
// selected and the maximum number of stages that can fit in shared memory will be selected.
|
||||
//
|
||||
// This example is equivalent to declaring
|
||||
// ExampleRunner<cutlass::gemm::collective::KernelScheduleAuto, cutlass::gemm::collective::StageCountAuto>
|
||||
// ExampleRunner<
|
||||
// cutlass::gemm::collective::KernelScheduleAuto,
|
||||
// cutlass::epilogue::collective::EpilogueScheduleAuto,
|
||||
// cutlass::gemm::collective::StageCountAuto>
|
||||
// Each of the `Auto` types indicate that the CollectiveBuilder should determine the scheduling policy and
|
||||
// stage count. Note that the behavior of the CollectiveBuilder with `Auto` parameters is subject to change
|
||||
// -- do not rely on `Auto` if you require a specific scheduling policy.
|
||||
// If you opt in to a non-'Auto' schedule, make sure all collectives are built using specific, compatible schedules.
|
||||
ExampleRunner<> auto_schedule_auto_stage_runner;
|
||||
passed = auto_schedule_auto_stage_runner.run(options, hw_info);
|
||||
print_result("Automatically-selected schedule and stage count", passed);
|
||||
|
||||
// One can override the stage count used in the GEMM by replacing cutlass::gemm::collective::StageCountAuto
|
||||
// with the number of stages to use (5 in this case).
|
||||
ExampleRunner<cutlass::gemm::collective::KernelScheduleAuto, _5> auto_schedule_5_stage_runner;
|
||||
ExampleRunner<
|
||||
cutlass::gemm::collective::KernelScheduleAuto,
|
||||
cutlass::epilogue::collective::EpilogueScheduleAuto,
|
||||
_5> auto_schedule_5_stage_runner;
|
||||
|
||||
passed = auto_schedule_5_stage_runner.run(options, hw_info);
|
||||
print_result("Automatically-selected schedule with 5 stages", passed);
|
||||
|
||||
// One can also override the scheduling policy to use. In this case, use the KernelTma scheduling
|
||||
// policy, which specifies that the Hopper TMA feature should be used.
|
||||
ExampleRunner<cutlass::gemm::KernelTma> tma_schedule_auto_stage_runner;
|
||||
// policy, which specifies that the Hopper TMA feature should be used, and we also use an epilgoue
|
||||
// that does not use any shared memory.
|
||||
ExampleRunner<cutlass::gemm::KernelTma, cutlass::epilogue::NoSmemWarpSpecialized> tma_schedule_auto_stage_runner;
|
||||
passed = tma_schedule_auto_stage_runner.run(options, hw_info);
|
||||
print_result("TMA schedule with automatically-selected stage count", passed);
|
||||
|
||||
// Here, we override the scheduling policy to use Hopper's TMA feature alongside the warp-specialized
|
||||
// scheduling policy.
|
||||
//
|
||||
// Note that, as of the CUTLASS 3.0 release, this is the default scheduling policy
|
||||
// used by the CollectiveBuilder, so this declaration is equivalent to ExampleRunner<> and
|
||||
// ExampleRunner<cutlass::gemm::collective::KernelScheduleAuto>. However, this default is subject to
|
||||
// change in future releases -- do not rely on `Auto` if you require a specific scheduling policy.
|
||||
ExampleRunner<cutlass::gemm::KernelTmaWarpSpecialized> ws_schedule_auto_stage_runner;
|
||||
// scheduling policy, and an epilgoue that does not use any shared memory.
|
||||
ExampleRunner<cutlass::gemm::KernelTmaWarpSpecialized, cutlass::epilogue::NoSmemWarpSpecialized> ws_schedule_auto_stage_runner;
|
||||
passed = ws_schedule_auto_stage_runner.run(options, hw_info);
|
||||
print_result("Warp-specialized TMA schedule with automatically-selected stage count", passed);
|
||||
|
||||
// Finally, we override the scheduling policy to use Hopper's TMA feature, alongside the warp-specialized
|
||||
// scheduling policy, leveraging persistent thread blocks.
|
||||
ExampleRunner<cutlass::gemm::KernelTmaWarpSpecializedPersistent> ws_persistent_schedule_auto_stage_runner;
|
||||
passed = ws_persistent_schedule_auto_stage_runner.run(options, hw_info);
|
||||
print_result("Persistent warp-specialized TMA schedule with automatically-selected stage count", passed);
|
||||
// scheduling policy, TMA-based epilogue, leveraging persistent thread blocks.
|
||||
ExampleRunner<
|
||||
cutlass::gemm::KernelTmaWarpSpecializedPingpong,
|
||||
cutlass::epilogue::TmaWarpSpecialized> ws_pingpong_schedule_auto_stage_runner;
|
||||
passed = ws_pingpong_schedule_auto_stage_runner.run(options, hw_info);
|
||||
print_result("Ping-pong warp-specialized TMA schedule with automatically-selected stage count", passed);
|
||||
|
||||
#endif
|
||||
|
||||
+3
-4
@@ -27,9 +27,8 @@
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
# Both filenames are shorter to avoid MAX_PATH issues on Windows.
|
||||
cutlass_example_add_executable(
|
||||
49_hopper_gemm_schedules_with_collective_builder
|
||||
49_hopper_gemm_schedules_with_collective_builder.cu
|
||||
49_collective_builder
|
||||
49_collective_builder.cu
|
||||
)
|
||||
+10
-12
@@ -34,7 +34,7 @@
|
||||
|
||||
The following example shows how to assemble a custom GEMM kernel that spells out the Collectives
|
||||
directly instead of using a builder and, in the process, instance a more efficient Epilogue
|
||||
(from `cutlass/epilogue/collective/epilogue.hpp`) instead of using the default epilogue.
|
||||
(from `cutlass/epilogue/collective/sm70_epilogue_vectorized.hpp`) instead of using the default epilogue.
|
||||
|
||||
The GemmUniversal API takes 3 main template arguments:
|
||||
(1) the problem shape / extents
|
||||
@@ -65,7 +65,7 @@
|
||||
#include "cute/tensor.hpp"
|
||||
#include "cutlass/util/command_line.h"
|
||||
#include "cutlass/tensor_ref.h"
|
||||
#include "cutlass/epilogue/collective/epilogue.hpp"
|
||||
#include "cutlass/epilogue/collective/collective_epilogue.hpp"
|
||||
#include "cutlass/epilogue/thread/linear_combination.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/gemm/collective/collective_builder.hpp"
|
||||
@@ -122,7 +122,7 @@ struct Options {
|
||||
/// Prints the usage statement.
|
||||
std::ostream & print_usage(std::ostream &out) const {
|
||||
|
||||
out << "50_hopper_gemm_with_vectorized_epilogue\n\n"
|
||||
out << "50_hopper_gemm_with_epilogue_swizzle\n\n"
|
||||
<< "Hopper GEMM Example with Epilogue Swizzle.\n\n"
|
||||
<< "Options:\n\n"
|
||||
<< " --help If specified, displays this usage statement\n\n"
|
||||
@@ -286,11 +286,8 @@ struct ExampleRunner {
|
||||
typename Gemm::GemmKernel::Arguments arguments{
|
||||
cutlass::gemm::GemmUniversalMode::kGemm,
|
||||
problem_size,
|
||||
block_A.get(),
|
||||
stride_A,
|
||||
block_B.get(),
|
||||
stride_B,
|
||||
{block_C.get(), stride_C, block_D.get(), stride_D, {options.alpha, options.beta}},
|
||||
{block_A.get(), stride_A, block_B.get(), stride_B},
|
||||
{{options.alpha, options.beta}, block_C.get(), stride_C, block_D.get(), stride_D},
|
||||
hw_info
|
||||
};
|
||||
|
||||
@@ -443,11 +440,11 @@ int main(int argc, char const **args) {
|
||||
cute::SM90_TMA_LOAD,
|
||||
cute::SM90_TMA_LOAD_MULTICAST>::type;
|
||||
|
||||
using SmemLayoutAtomA = decltype(cute::GMMA::smem_selector<
|
||||
using SmemLayoutAtomA = decltype(cutlass::gemm::collective::detail::ss_smem_selector<
|
||||
GmmaMajorA, ElementA, decltype(cute::get<0>(TileShape{})), decltype(cute::get<2>(TileShape{}))
|
||||
>());
|
||||
|
||||
using SmemLayoutAtomB = decltype(cute::GMMA::smem_selector<
|
||||
using SmemLayoutAtomB = decltype(cutlass::gemm::collective::detail::ss_smem_selector<
|
||||
GmmaMajorB, ElementB, decltype(cute::get<1>(TileShape{})), decltype(cute::get<2>(TileShape{}))
|
||||
>());
|
||||
|
||||
@@ -494,14 +491,15 @@ int main(int argc, char const **args) {
|
||||
Stride<_16,_1>>,
|
||||
TileShapeS2R>;
|
||||
|
||||
using Epilogue = cutlass::epilogue::collective::Epilogue<
|
||||
using Epilogue = cutlass::epilogue::collective::detail::Sm90TmaWarpSpecializedAdapter<
|
||||
cutlass::epilogue::collective::Epilogue<
|
||||
cutlass::gemm::TagToStrideC_t<LayoutC>,
|
||||
cutlass::gemm::TagToStrideC_t<LayoutD>,
|
||||
cutlass::epilogue::thread::LinearCombination<int32_t, 1, int32_t, int32_t>,
|
||||
SmemLayout,
|
||||
Copy_Atom<DefaultCopy, ElementAcc>,
|
||||
TiledCopyS2R,
|
||||
Copy_Atom<DefaultCopy, ElementOutput>>;
|
||||
Copy_Atom<DefaultCopy, ElementOutput>>>;
|
||||
|
||||
//
|
||||
// Assembling the GemmKernel
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include "cutlass/gemm/kernel/gemm_universal.hpp"
|
||||
#include "cutlass/gemm/collective/collective_builder.hpp"
|
||||
|
||||
#include "cutlass/epilogue/collective/default_epilogue.hpp"
|
||||
#include "cutlass/epilogue/collective/collective_epilogue.hpp"
|
||||
#include "cutlass/epilogue/thread/linear_combination.h"
|
||||
|
||||
namespace example {
|
||||
@@ -88,10 +88,12 @@ gett_kernel(
|
||||
cutlass::FloatRoundStyle::round_to_nearest, ElementC>;
|
||||
|
||||
// No changes are required to the default epilogue
|
||||
using CollectiveEpilogue = cutlass::epilogue::collective::DefaultEpilogue<
|
||||
using CollectiveEpilogue = cutlass::epilogue::collective::detail::Sm90TmaWarpSpecializedAdapter<
|
||||
cutlass::epilogue::collective::DefaultEpilogue<
|
||||
StrideC,
|
||||
StrideD,
|
||||
EpilogueThreadOp>;
|
||||
EpilogueThreadOp,
|
||||
cutlass::gemm::EpilogueDefault>>;
|
||||
|
||||
// CollectiveMma for GETTs can be built using the CollectiveBuilders
|
||||
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
|
||||
@@ -100,7 +102,7 @@ gett_kernel(
|
||||
ElementB, StrideB, 128 / cutlass::sizeof_bits<ElementB>::value,
|
||||
ElementAccumulator,
|
||||
TileShape, Shape<_1,_2,_1>,
|
||||
cutlass::gemm::collective::StageCountAuto,
|
||||
cutlass::gemm::collective::StageCountAutoCarveout<sizeof(typename CollectiveEpilogue::SharedStorage)>,
|
||||
cutlass::gemm::collective::KernelScheduleAuto
|
||||
>::CollectiveOp;
|
||||
|
||||
@@ -115,9 +117,8 @@ gett_kernel(
|
||||
typename GettOperator::Arguments args {
|
||||
cutlass::gemm::GemmUniversalMode::kBatched,
|
||||
problem_shape_mnkl,
|
||||
ptr_A, stride_a_mkl,
|
||||
ptr_B, stride_b_nkl,
|
||||
{ ptr_C, stride_c_mnl, ptr_D, stride_d_mnl, {alpha, beta} }
|
||||
{ ptr_A, stride_a_mkl, ptr_B, stride_b_nkl },
|
||||
{ {alpha, beta}, ptr_C, stride_c_mnl, ptr_D, stride_d_mnl }
|
||||
};
|
||||
|
||||
#if CUTLASS_DEBUG_TRACE_LEVEL > 0
|
||||
|
||||
@@ -129,7 +129,7 @@ foreach(EXAMPLE
|
||||
46_depthwise_simt_conv2dfprop
|
||||
47_ampere_gemm_universal_streamk
|
||||
48_hopper_warp_specialized_gemm
|
||||
49_hopper_gemm_schedules_with_collective_builder
|
||||
49_hopper_gemm_with_collective_builder
|
||||
50_hopper_gemm_with_epilogue_swizzle
|
||||
51_hopper_gett
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "cuda_runtime.h"
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* Panic wrapper for unwinding CUTLASS errors
|
||||
|
||||
@@ -31,4 +31,3 @@ cutlass_example_add_executable(
|
||||
sgemm_nt_1
|
||||
sgemm_nt_1.cu
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1ef96b3f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Basic example of using the CUTLASS Python interface\n",
|
||||
"This notebook walks through a basic example of using the CUTLASS Python interface to declare, compile, and run GEMMs.\n",
|
||||
"\n",
|
||||
"[](https://colab.research.google.com/github/NVIDIA/cutlass/tree/master/examples/00_basic_gemm.ipynb)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "962324fd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We first import various packages needed for the example and construct the input and output tensors that will be used in our example.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0e324219",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import random\n",
|
||||
"\n",
|
||||
"import cutlass\n",
|
||||
"\n",
|
||||
"# This controls whether ther C++ GEMM declaration will be printed at each step. Set to `false` to\n",
|
||||
"# omit this information.\n",
|
||||
"print_module = True\n",
|
||||
"\n",
|
||||
"m = 128\n",
|
||||
"n = m\n",
|
||||
"k = m\n",
|
||||
"\n",
|
||||
"dtype = np.float16\n",
|
||||
"type_A = np.float16\n",
|
||||
"type_B = np.float16\n",
|
||||
"type_C = np.float16\n",
|
||||
"type_D = np.float16\n",
|
||||
"\n",
|
||||
"np.random.seed(1234)\n",
|
||||
"random.seed(1234)\n",
|
||||
"scope_min = -4\n",
|
||||
"scope_max = 4\n",
|
||||
"tensor_A = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(m, k)).astype(type_A))\n",
|
||||
"tensor_B = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(k, n)).astype(type_B))\n",
|
||||
"tensor_C = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(m, n)).astype(type_C))\n",
|
||||
"\n",
|
||||
"alpha = np.float16(1.)\n",
|
||||
"beta = np.float16(0.)\n",
|
||||
"\n",
|
||||
"tensor_D = np.zeros(tensor_C.shape).astype(type_D)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f2c7bf48",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Declaring and running a GEMM\n",
|
||||
"To get started, one only needs to provide the tensors declared above to the `cutlass.op.Gemm` call.\n",
|
||||
"This sets up a default GEMM operation for the given device on which you are running.\n",
|
||||
"\n",
|
||||
"Assuming that we are running on SM80, this default to using a GEMM that leverages FP16 Tensor Core operations.\n",
|
||||
"\n",
|
||||
"Calling `plan.run()` will generate the CUTLASS C++ kernel in question, compile it, and run it on the tensors we previously passed in. By setting `print_module` to `true`, the C++ code that is emitted is printed."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0dfd8975",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# We specify `element_accumulator` here so as to match the kernel run by NumPy below. However,\n",
|
||||
"# specifying `element_accumulator` is not required if it is the same as `element`\n",
|
||||
"plan = cutlass.Gemm(element=dtype, layout=cutlass.LayoutType.RowMajor, element_accumulator=np.float32)\n",
|
||||
"plan.run(tensor_A, tensor_B, tensor_C, tensor_D, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4a5856de",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"There are many other ways to construct a plan from `cutlass.op.Gemm` (e.g., by specifiying they types and layouts of each operand, by providing representative tensors as inputs). For more details on these, see the documentation in the `cutlass.op.Gemm` constructor."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "945478ef",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We then compare the output to running the GEMM using NumPy."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6b669de6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tensor_D_numpy = (alpha * (tensor_A @ tensor_B)) + (beta * tensor_C)\n",
|
||||
"np.testing.assert_array_equal(tensor_D, tensor_D_numpy)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ee5cbbbe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Note that one could use the same kernel just declared for tensors provided by other frameworks beyond NumPy, such as PyTorch or CuPy."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b6c86493",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Changing operation modes\n",
|
||||
"By default, the CUTLASS Python interface will try to use Tensor Core operations whenever possible. If the configuration provided to `cutlass.op.Gemm` is not supported on Tensor Cores, the interface will fall back to using a SIMT kernel.\n",
|
||||
"\n",
|
||||
"The operation mode currently in use can be returned via the `plan.opclass` property. In this case Tensor Core operations."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "529fda93",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(plan.opclass)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6d27c575",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Suppose that we don't want to use Tensor Cores for this GEMM. One can change to using CUTLASS's SIMT GEMMs by setting the plan's `opclass` field.\n",
|
||||
"\n",
|
||||
"As is shown in the printed output, the emitted kernel uses template parameters that fit CUTLASS's SIMT GEMMs.\n",
|
||||
"\n",
|
||||
"Also notice that, this time around, we provided tensor parameters to `plan.run()`. One is free to provide different parameters to `plan.run()` than were passed in at the initial call to `cutlass.op.Gemm`, provided that the passed-in tensors have the same data type and layout as those passed in on intialization."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6a44d35b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tensor_D_simt = np.zeros(tensor_C.shape).astype(type_D)\n",
|
||||
"plan.opclass = cutlass.OpcodeClass.Simt\n",
|
||||
"plan.run(tensor_A, tensor_B, tensor_C, tensor_D_simt, alpha, beta, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "639dcb59",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If we compare the output of the Tensor Core and SIMT GEMMs we just ran we see that they are equal."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9b480853",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"np.testing.assert_array_equal(tensor_D, tensor_D_simt)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0cce1eae",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Running cached kernels\n",
|
||||
"You may have noticed that the `plan.run()` calls for the previous two kernels took some time to execute. This is because the kernel being emitted had not yet been compiled.\n",
|
||||
"\n",
|
||||
"CUTLASS caches compiled binaries so that recompilation isn't necessary every time a kernel is run. For example, if we change modes back to using Tensor Cores and call `plan.run()` again (with a different set of tensor parameters), you'll find the call to return much faster."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f8051e5e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"m = 2400\n",
|
||||
"n = 3232\n",
|
||||
"k = 4096\n",
|
||||
"\n",
|
||||
"tensor_A = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(m, k)).astype(type_A))\n",
|
||||
"tensor_B = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(k, n)).astype(type_B))\n",
|
||||
"tensor_C = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(m, n)).astype(type_C))\n",
|
||||
"tensor_D = np.zeros(tensor_C.shape).astype(type_D)\n",
|
||||
"\n",
|
||||
"alpha = np.float16(1.)\n",
|
||||
"beta = np.float16(2.)\n",
|
||||
"\n",
|
||||
"plan.opclass = cutlass.OpcodeClass.TensorOp\n",
|
||||
"plan.run(tensor_A, tensor_B, tensor_C, tensor_D, alpha, beta, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "52a4e318",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Running non-default GEMMs\n",
|
||||
"The previous examples showed how it is simple to get started running a default GEMM kernel in CUTLASS. But, what do you do if you want a bit more control over the parameters to the GEMM?\n",
|
||||
"\n",
|
||||
"Under the hood, CUTLASS enumerates the different GEMM configuration parameters possible for this kernel from the CUTLASS profiler. The code below shows how one can access the tile descriptions for the kernels (e.g., cluster, threadblock, and warp shape)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1c593be1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tiles = plan.tile_descriptions()\n",
|
||||
"print('{} tile descriptions returned'.format(len(tiles)))\n",
|
||||
"num_print = 10\n",
|
||||
"print('First {} tile descriptions are:'.format(num_print))\n",
|
||||
"for td in tiles[:num_print]:\n",
|
||||
" print(td)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dc3ad875",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we'll pick one of these configurations at random and compile and run it."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a8dc5287",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"idx = random.randint(0, len(tiles)-1)\n",
|
||||
"td = tiles[idx]\n",
|
||||
"print('Tile description {} is: {}'.format(idx, td))\n",
|
||||
"plan.compile(td)\n",
|
||||
"plan.run(tensor_A, tensor_B, tensor_C, tensor_D, alpha, beta, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c5a8b534",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"One can also change the swizzling function used by the kernel. For example, one can modify the kernel to use the stream K feature of CUTLASS via:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e5e88d17",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Stream K is only supported pre-SM90 (at least when this example was written)\n",
|
||||
"if plan.cc != 90:\n",
|
||||
" plan.swizzling_functor = cutlass.swizzle.ThreadblockSwizzleStreamK\n",
|
||||
" plan.run(tensor_A, tensor_B, tensor_C, tensor_D, alpha, beta, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5a8ba2ba",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Handling errors\n",
|
||||
"The CUTLASS Python interface attempts to catch runtime and compilation errors in Python so as to provide more understandable error messages.\n",
|
||||
"\n",
|
||||
"Here's an example in which we try to use too many stages for a given GEMM kernel. Normally, this would result in a runtime error due to the GPU having insufficient shared memory to launch the kernel with 8 stages. The CUTLASS Python interface is able to detect this issue before compiling the kernel, and reports it back to the user."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fe7d0e42",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# td = tiles[0]\n",
|
||||
"# td.stages = 8\n",
|
||||
"# plan.compile(td)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.10"
|
||||
},
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "0466d96796c9cd8f7a1cad264ff326ececc950ba2420e0256d5105fc1a3c6e70"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "5d24a692",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Example of using elementwise activation functions in the CUTLASS Python interface\n",
|
||||
"This notebook walks through a basic example of using the CUTLASS Python interface to declare, compile, and run GEMMs with different epilogues.\n",
|
||||
"\n",
|
||||
"[](https://colab.research.google.com/github/NVIDIA/cutlass/tree/master/examples/00_basic_gemm.ipynb)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3ca993fe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We first import various packages needed for the example and construct the input and output tensors that will be used in our example."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "63a70a3c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"import cutlass\n",
|
||||
"\n",
|
||||
"# This controls whether ther C++ GEMM declaration will be printed at each step. Set to `false` to\n",
|
||||
"# omit this information.\n",
|
||||
"print_module = True\n",
|
||||
"\n",
|
||||
"m = 256\n",
|
||||
"n = m\n",
|
||||
"k = m\n",
|
||||
"\n",
|
||||
"type_A = np.float16\n",
|
||||
"type_B = np.float16\n",
|
||||
"type_C = np.float16\n",
|
||||
"type_D = np.float16\n",
|
||||
"\n",
|
||||
"np.random.seed(1234)\n",
|
||||
"scope_min = -4\n",
|
||||
"scope_max = 4\n",
|
||||
"tensor_A = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(m, k)).astype(type_A))\n",
|
||||
"tensor_B = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(k, n)).astype(type_B))\n",
|
||||
"tensor_C = np.ceil(np.random.uniform(low=scope_min, high=scope_max, size=(m, n)).astype(type_C))\n",
|
||||
"\n",
|
||||
"alpha = np.float16(1.)\n",
|
||||
"beta = np.float16(0.)\n",
|
||||
"\n",
|
||||
"tensor_D = np.zeros(tensor_C.shape).astype(type_D)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1eb0d95b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Run a GEMM with an identity activation function\n",
|
||||
"To begin, we simply run a default GEMM with an identity activation function. This performs the well-known operation `D = alpha * (A @ B) + beta * C`. This is the default activation function used, and does not need to be specified."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8d257833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plan = cutlass.op.Gemm(element=np.float16, layout=cutlass.LayoutType.RowMajor)\n",
|
||||
"plan.run(tensor_A, tensor_B, tensor_C, tensor_D, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "54961694",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Run a GEMM with a ReLU element-wise activation function\n",
|
||||
"CUTLASS makes it easy to support other element-wise activation functions. This results in performing an element-wise after the generic linear combination performed in a GEMM. If we call such an activation function `act`, the resulting formulation is:\n",
|
||||
"```\n",
|
||||
"D = alpha * (A @ B) + beta * C\n",
|
||||
"D = act(D)\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Here, we will add a ReLU activation function. Given an input `x`, ReLU returns `max(x, 0)`.\n",
|
||||
"\n",
|
||||
"This is easy to do in CUTLASS. One only needs to set the plan's `activation` field."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5fe49443",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tensor_D_relu = np.zeros(tensor_C.shape).astype(type_D)\n",
|
||||
"plan.activation = cutlass.epilogue.relu\n",
|
||||
"plan.run(tensor_A, tensor_B, tensor_C, tensor_D_relu, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "455d0a37",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can now verify that the result of the GEMM that used a ReLU activation function:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e32e7798",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"relu_ref = (tensor_D >= 0).astype(type_D) * tensor_D\n",
|
||||
"np.testing.assert_array_equal(relu_ref, tensor_D_relu)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cf959171",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Other element-wise activation functions\n",
|
||||
"CUTLASS supports a variety of widely-used element-wise activation functions. We can obtain a list of these functions via the `get_activations()` method."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9e17d730",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"activations = plan.activations()\n",
|
||||
"for activation in activations:\n",
|
||||
" print(activation)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0e4599fa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can then run each of them:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9c3598c9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for activation in activations:\n",
|
||||
" print('=============================================================================================')\n",
|
||||
" print(f'Compiling and running activation {activation}')\n",
|
||||
" print('=============================================================================================')\n",
|
||||
" plan.activation = activation\n",
|
||||
" plan.run(tensor_A, tensor_B, tensor_C, tensor_D, print_module=print_module)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "751f8d92",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.10"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "6acbea5d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Exporting a CUTLASS grouped GEMM kernel to a PyTorch CUDA extension\n",
|
||||
"This notebook walks through a basic example of using the CUTLASS Python interface to declare\n",
|
||||
"a grouped GEMM kernel and export it as a PyTorch CUDA extension.\n",
|
||||
"\n",
|
||||
"[](https://colab.research.google.com/github/NVIDIA/cutlass/tree/master/examples/00_basic_gemm.ipynb)\n",
|
||||
"\n",
|
||||
"## Background on grouped GEMM\n",
|
||||
"Grouped GEMM enables one to execute a set of GEMMs (each with potentially different sizes and strides)\n",
|
||||
"in a single CUDA kernel. It can be thought of as a generalized version of a pointer-array GEMM,\n",
|
||||
"without the requirement that the sizes and strides of each GEMM be the same.\n",
|
||||
"\n",
|
||||
"For example, if one has `p` GEMMs with sizes:\n",
|
||||
"```text\n",
|
||||
"M_1 x N_1 x K_1\n",
|
||||
"M_2 x N_2 x K_2\n",
|
||||
"...\n",
|
||||
"M_p x N_p x K_p\n",
|
||||
"```\n",
|
||||
"CUTLASS's grouped GEMM will execute these in a single CUDA kernel.\n",
|
||||
"\n",
|
||||
"Grouped GEMM is particularly beneficial for saturating the GPU with many small problems that would\n",
|
||||
"insufficiently utilize the device in isolation.\n",
|
||||
"\n",
|
||||
"## Declaring a grouped GEMM via the CUTLASS Python interface\n",
|
||||
"A grouped GEMM operation is declared similarly to a GEMM operation in the CUTLASS Python interface: one\n",
|
||||
"simply calls `cutlass.op.GroupedGemm`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fdcf21d8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import cutlass\n",
|
||||
"import torch\n",
|
||||
"\n",
|
||||
"dtype = torch.float16\n",
|
||||
"plan = cutlass.op.GroupedGemm(element=dtype, layout=cutlass.LayoutType.RowMajor)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "514f40a4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can then compile and run this operation on a group of GEMMs. We'll first set up some utility functions to initialize GEMMs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c2a7371e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"random.seed(2023)\n",
|
||||
"\n",
|
||||
"# Utility function to initialize A, B, C, and D matrices corresponding to dimensions M, N, and K\n",
|
||||
"def initialize(dtype, M, N, K):\n",
|
||||
" sizes = [(M, K), (K, N), (M, N), (M, N)]\n",
|
||||
" return [torch.randint(-3, 3, size, device='cuda').to(dtype) for size in sizes]\n",
|
||||
"\n",
|
||||
"# Utility function to generate `problems` GEMMs of random sizes\n",
|
||||
"def generate_problems(problems):\n",
|
||||
" valid_sizes = [128, 256, 512, 1024]\n",
|
||||
" As, Bs, Cs, Ds = [], [], [], []\n",
|
||||
" for _ in range(problems):\n",
|
||||
" M, N, K = [random.choice(valid_sizes) for _ in range(3)]\n",
|
||||
" A, B, C, D = initialize(dtype, M, N, K)\n",
|
||||
" As.append(A)\n",
|
||||
" Bs.append(B)\n",
|
||||
" Cs.append(C)\n",
|
||||
" Ds.append(D)\n",
|
||||
" return As, Bs, Cs, Ds"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "590a3bc5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll next run a group of 50 GEMMs via the CUTLASS Python interface and via PyTorch."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "776c9233",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"As, Bs, Cs, Ds, = generate_problems(50)\n",
|
||||
"\n",
|
||||
"plan.run(As, Bs, Cs, Ds, print_module=True)\n",
|
||||
"Ds_torch = [a @ b for a, b in zip(As, Bs)]\n",
|
||||
"\n",
|
||||
"for d, d_torch in zip(Ds, Ds_torch):\n",
|
||||
" assert torch.allclose(d, d_torch)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "766e4f03",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Exporting the CUTLASS kernel to a PyTorch CUDA extension\n",
|
||||
"The procedure above allows one to quickly experiment with using a CUTLASS kernels However, one might prefer to use the CUTLASS kernel via a [PyTorch CUDA extension](https://pytorch.org/tutorials/advanced/cpp_extension.html). This will avoids adding any runtime overheads associated with the Python portions of the CUTLASS Python interface.\n",
|
||||
"\n",
|
||||
"The CUTLASS Python interface provides simple solutions for creating PyTorch CUDA extensions for a CUTLASS kernel. These extensions can either be written out for a later \"ahead-of-time\" compilation, or be just-in-time compiled and returned to the user.\n",
|
||||
"\n",
|
||||
"To create a JIT-compiled module from the CUTLASS kernel we defined above, simply call the following:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3a98dee6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"op = plan.construct()\n",
|
||||
"grouped_gemm = cutlass.emit.pytorch(op, name='grouped_gemm', cc=plan.cc, sourcedir='out', jit=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c8ca3991",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The `cutlass.emit.pytorch` function emits:\n",
|
||||
"* `out/grouped_gemm_kernel.cu`: This file contains the declaration of the CUTLASS kernel and a method to call it from PyTorch tensors\n",
|
||||
"* `out/grouped_gemm.cpp`: This file contains a C++ wrapper around the aforementioned CUTLASS kernel\n",
|
||||
"* `setup.py`: This file contains the `setuptools` script for building and installing the generated extension\n",
|
||||
"\n",
|
||||
"The extension can be build from within the `module_output` directory by running:\n",
|
||||
"```bash\n",
|
||||
"TORCH_CUDA_ARCH_LIST=\"8.0\" python setup.py install\n",
|
||||
"```\n",
|
||||
"Where `TORCH_ARCH_LIST` is set to the compute capability of the device on which the kernel will be run.\n",
|
||||
"\n",
|
||||
"See the PyTorch [\"Custom C++ and CUDA Extensions\"](https://pytorch.org/tutorials/advanced/cpp_extension.html) tutorial for more details on this.\n",
|
||||
"\n",
|
||||
"The PyTorch CUDA extension could be built for this module by running:\n",
|
||||
"```bash\n",
|
||||
"cd out\n",
|
||||
"TORCH_CUDA_ARCH_LIST=\"8.0\" python setup.py\n",
|
||||
"```\n",
|
||||
"(assuming that one is building for SM80)\n",
|
||||
"\n",
|
||||
"One could then use the kernel in a later PyTorch module by running:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"import torch\n",
|
||||
"import grouped_gemm\n",
|
||||
"\n",
|
||||
"grouped_gemm.run(As, Bs)\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"In this case, however, we set `jit=True`, which specifies that we would like to compile and load the PyTorch CUDA extension on the fly.\n",
|
||||
"Under the hood, this leverages the [torch.utils.cpp_extension.load](https://pytorch.org/tutorials/advanced/cpp_extension.html) method\n",
|
||||
"and returns back the loaded extension.\n",
|
||||
"\n",
|
||||
"We can then use the extension and compare its results to running the GEMMs via vanilla PyTorch GEMMs:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cecb26a4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Ds = grouped_gemm.run(As, Bs)\n",
|
||||
"Ds_torch = [a @ b for a, b in zip(As, Bs)]\n",
|
||||
"for d, d_torch in zip(Ds, Ds_torch):\n",
|
||||
" assert torch.allclose(d, d_torch)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "50db80e4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we can profile our grouped GEMM extension:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b76805d3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"num_warmup = 20\n",
|
||||
"num_profile = 100\n",
|
||||
"\n",
|
||||
"# Warmup iterations\n",
|
||||
"for _ in range(num_warmup):\n",
|
||||
" Ds = grouped_gemm.run(As, Bs)\n",
|
||||
" Ds_torch = [a @ b for a, b in zip(As, Bs)]\n",
|
||||
" torch.cuda.synchronize()\n",
|
||||
"\n",
|
||||
"# Timing iterations\n",
|
||||
"import time\n",
|
||||
"grouped = 0\n",
|
||||
"nongrouped = 0\n",
|
||||
"for _ in range(num_profile):\n",
|
||||
" start = time.time()\n",
|
||||
" Ds = grouped_gemm.run(As, Bs)\n",
|
||||
" torch.cuda.synchronize()\n",
|
||||
" grouped += time.time() - start\n",
|
||||
"\n",
|
||||
" start = time.time()\n",
|
||||
" Ds_torch = [a @ b for a, b in zip(As, Bs)]\n",
|
||||
" torch.cuda.synchronize()\n",
|
||||
" nongrouped += time.time() - start\n",
|
||||
"\n",
|
||||
"print('Grouped: {:.3f} us'.format(grouped * 1e6/num_profile))\n",
|
||||
"print('Non-Grouped: {:.3f} us'.format(nongrouped * 1e6/num_profile))\n",
|
||||
"print('Speedup: {:.3f}'.format(nongrouped / grouped))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f22fc696",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.10"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Examples of using the CUTLASS Python interface
|
||||
|
||||
* [00_basic_gemm](/examples/python/00_basic_gemm.ipynb)
|
||||
|
||||
Shows how declare, configure, compile, and run a CUTLASS GEMM using the Python interface
|
||||
|
||||
* [01_epilogue](/examples/python/01_epilogue.ipynb)
|
||||
|
||||
Shows how to fuse elementwise activation functions to GEMMs via the Python interface
|
||||
|
||||
* [02_pytorch_extension_grouped_gemm](/examples/python/02_pytorch_extension_grouped_gemm.ipynb)
|
||||
|
||||
Shows how to declare, compile, and run a grouped GEMM operation via the Python interface,
|
||||
along with how the emitted kernel can be easily exported to a PyTorch CUDA extension.
|
||||
@@ -171,7 +171,7 @@ copy_vec(Tensor<SrcEngine, SrcLayout> const& src,
|
||||
{
|
||||
using SrcType = typename SrcEngine::value_type;
|
||||
using DstType = typename DstEngine::value_type;
|
||||
if constexpr (sizeof(SrcType) == sizeof(DstType) && sizeof(VecType) > sizeof(DstType))
|
||||
if constexpr (sizeof(SrcType) == sizeof(DstType) && sizeof(VecType) > sizeof(DstType))
|
||||
{
|
||||
/* @pre is_aligned<N>(src.data()) &&
|
||||
* is_aligned<N>(dst.data())
|
||||
@@ -259,4 +259,51 @@ copy(Copy_Atom<DefaultCopy, CopyArgs...> const&,
|
||||
return copy(src, dst);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Special Auto-Vectorizing Overloads
|
||||
//////////////////////////////////////////
|
||||
|
||||
#if defined(CUTE_COPY_ATOM_TMA_SM90_ENABLED)
|
||||
template <class... CT_Args, class... CA_Args,
|
||||
class SrcEngine, class SrcLayout,
|
||||
class DstEngine, class DstLayout>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
copy(Copy_Atom<Copy_Traits<SM90_BULK_COPY_AUTO, CT_Args...>, CA_Args...> const& atom,
|
||||
Tensor<SrcEngine, SrcLayout> const& src,
|
||||
Tensor<DstEngine, DstLayout> & dst)
|
||||
{
|
||||
using SrcType = typename SrcEngine::value_type;
|
||||
using DstType = typename DstEngine::value_type;
|
||||
static_assert(sizeof_bits<SrcType>::value == sizeof_bits<DstType>::value);
|
||||
static_assert((is_gmem<SrcEngine>::value && is_smem<DstEngine>::value) ||
|
||||
(is_smem<SrcEngine>::value && is_gmem<DstEngine>::value),
|
||||
"Bulk Copy only supports gmem -> smem or smem -> gmem movement.");
|
||||
// Do BulkCopy dispatch
|
||||
using BULK_COPY_OP = conditional_t<is_gmem<SrcEngine>::value,
|
||||
SM90_BULK_COPY_G2S,
|
||||
SM90_BULK_COPY_S2G>;
|
||||
|
||||
constexpr int N = decltype(max_common_vector(src, dst))::value;
|
||||
|
||||
// Construct a new concrete Atom of the vector size
|
||||
using N_BITS = Int<N*sizeof_bits<SrcType>::value>;
|
||||
using COPY_ATOM = Copy_Atom<Copy_Traits<BULK_COPY_OP, N_BITS, CT_Args...>, SrcType>;
|
||||
auto bulk_atom = apply(atom.opargs_, [&](auto const&... args) { return COPY_ATOM{args...}; });
|
||||
|
||||
// Tile the src and dst to the Atom
|
||||
auto tiler = right_inverse(dst.layout()).compose(Int<N>{});
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print("copy -- found a max_common_vector of %d\n", N);
|
||||
print(" "); print(src.data()); print(" o "); print(layout(src)); print("\n");
|
||||
print(" "); print(dst.data()); print(" o "); print(layout(dst)); print("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
return copy(bulk_atom, logical_divide(src, tiler), logical_divide(dst, tiler));
|
||||
}
|
||||
#endif // #if defined(CUTE_COPY_ATOM_TMA_SM90_ENABLED)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
/** C++14 <functional> extensions */
|
||||
|
||||
namespace cute {
|
||||
|
||||
@@ -32,10 +32,12 @@
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
#include <cute/algorithm/functional.hpp>
|
||||
#include <cute/atom/mma_atom.hpp>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
#include <cute/algorithm/functional.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
#include <cute/atom/mma_atom.hpp>
|
||||
|
||||
/** The gemm algorithm takes four (or three) tensors and computes
|
||||
* D += A * B + C
|
||||
@@ -281,17 +283,15 @@ gemm(MMA_Atom<MMA> const& mma,
|
||||
CUTE_STATIC_ASSERT_V(size<1>(B) == size<2>(C)); // BN == CN
|
||||
CUTE_STATIC_ASSERT_V(size<0>(C) == size<0>(D) && size<1>(C) == size<1>(D) && size<2>(C) == size<2>(D));
|
||||
|
||||
// REGISTER .reuse OPTIMIZATIONS
|
||||
|
||||
auto M = size<1>(A);
|
||||
auto N = size<1>(B);
|
||||
|
||||
// REGISTER .reuse OPTIMIZATIONS
|
||||
// 64-bit traversal specialization -- serpentine path
|
||||
if (size<0>(A) * sizeof(typename Tensor<TA,ALayout>::value_type) == 8 &&
|
||||
size<0>(B) * sizeof(typename Tensor<TB,BLayout>::value_type) == 8)
|
||||
if constexpr (decltype(size<0>(A))::value * sizeof(typename TA::value_type) == 8 &&
|
||||
decltype(size<0>(B))::value * sizeof(typename TB::value_type) == 8)
|
||||
{
|
||||
#if 1 // NOTE: Must depend on the C-matrix order... (which we can test)
|
||||
// Row-major iteration
|
||||
#if 1 // NOTE: Row- vs Col- major could depend on the C-matrix order... (which we can test)
|
||||
// Row-major serpentine iteration
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < M; ++m) {
|
||||
CUTE_UNROLL
|
||||
@@ -301,7 +301,7 @@ gemm(MMA_Atom<MMA> const& mma,
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Col-major iteration
|
||||
// Col-major serpentine iteration
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < N; ++n) {
|
||||
CUTE_UNROLL
|
||||
@@ -312,13 +312,12 @@ gemm(MMA_Atom<MMA> const& mma,
|
||||
}
|
||||
#endif
|
||||
} else
|
||||
|
||||
// 32-bit traversal specialization -- kinked serpentine path
|
||||
if (size<0>(A) * sizeof(typename Tensor<TA,ALayout>::value_type) == 4 &&
|
||||
size<0>(B) * sizeof(typename Tensor<TB,BLayout>::value_type) == 4)
|
||||
if constexpr (decltype(size<0>(A))::value * sizeof(typename TA::value_type) == 4 &&
|
||||
decltype(size<0>(B))::value * sizeof(typename TB::value_type) == 4)
|
||||
{
|
||||
#if 1 // NOTE: Must depend on the C-matrix order... (which we can test)
|
||||
// Row-major iteration
|
||||
#if 1 // NOTE: Row- vs Col- major could depend on the C-matrix order... (which we can test)
|
||||
// Row-major kinked serpentine iteration
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < M; m += 2) {
|
||||
CUTE_UNROLL
|
||||
@@ -332,7 +331,7 @@ gemm(MMA_Atom<MMA> const& mma,
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Col-major iteration
|
||||
// Col-major kinked serpentine iteration
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < N; n += 2) {
|
||||
CUTE_UNROLL
|
||||
@@ -347,9 +346,36 @@ gemm(MMA_Atom<MMA> const& mma,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// Fallback to serpentine loop
|
||||
// Col-major iteration
|
||||
} else
|
||||
// 64-bit + 32-bit traversal order -- keep A (64-bit) in the outer loop and serpentine B
|
||||
if constexpr (decltype(size<0>(A))::value * sizeof(typename TA::value_type) == 8 &&
|
||||
decltype(size<0>(B))::value * sizeof(typename TB::value_type) == 4) {
|
||||
// Row-major serpentine iteration
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < M; ++m) {
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < N; ++n) {
|
||||
int ns = (m & 1) ? N-1-n : n; // Serpentine coordinate
|
||||
gemm(mma, D(_,m,ns), A(_,m), B(_,ns), C(_,m,ns));
|
||||
}
|
||||
}
|
||||
} else
|
||||
// 32-bit + 64-bit traversal order -- keep B (64-bit) in the outer loop and serpentine A
|
||||
if constexpr (decltype(size<0>(A))::value * sizeof(typename TA::value_type) == 4 &&
|
||||
decltype(size<0>(B))::value * sizeof(typename TB::value_type) == 8) {
|
||||
// Col-major serpentine iteration
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < N; ++n) {
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < M; ++m) {
|
||||
int ms = (n & 1) ? M-1-m : m; // Serpentine coordinate
|
||||
gemm(mma, D(_,ms,n), A(_,ms), B(_,n), C(_,ms,n));
|
||||
}
|
||||
}
|
||||
} else
|
||||
// Fallback to serpentine loop
|
||||
{
|
||||
// Col-major serpentine iteration
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < N; ++n) {
|
||||
CUTE_UNROLL
|
||||
@@ -504,9 +530,9 @@ gemm(ThrMMA<Args...> const& thr_mma,
|
||||
using TypeB = typename TB::value_type;
|
||||
using TypeC = typename TC::value_type;
|
||||
|
||||
static_assert(std::is_same_v<std::decay_t<std::invoke_result_t<ALoadTransformOp, TypeA>>, TypeA>,
|
||||
static_assert(is_same_v<decay_t<invoke_result_t<ALoadTransformOp, TypeA>>, TypeA>,
|
||||
"ALoadTransformOp functor must accept and return value of type TA::value_type");
|
||||
static_assert(std::is_same_v<std::decay_t<std::invoke_result_t<BLoadTransformOp, TypeB>>, TypeB>,
|
||||
static_assert(is_same_v<decay_t<invoke_result_t<BLoadTransformOp, TypeB>>, TypeB>,
|
||||
"BLoadTransformOp functor must accept and return value of type TB::value_type");
|
||||
|
||||
// Original, static size of the problem
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace cute
|
||||
{
|
||||
|
||||
// Infinite types that inherit from each other
|
||||
template <std::size_t N>
|
||||
template <size_t N>
|
||||
struct prefer : prefer<N-1> {};
|
||||
|
||||
template <>
|
||||
|
||||
@@ -99,4 +99,25 @@ transform(Tensor<Engine,Layout>&& tensor, UnaryOp&& op)
|
||||
return transform(tensor, std::forward<UnaryOp>(op));
|
||||
}
|
||||
|
||||
// Similar to std::transform transforms one tensors and assigns it to another
|
||||
template <class EngineIn, class LayoutIn, class EngineOut, class LayoutOut, class UnaryOp>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
transform(Tensor<EngineIn,LayoutIn>& tensor_in, Tensor<EngineOut,LayoutOut>& tensor_out, UnaryOp&& op)
|
||||
{
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size(tensor_in); ++i) {
|
||||
tensor_out(i) = static_cast<UnaryOp&&>(op)(tensor_in(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Accept mutable temporaries
|
||||
template <class EngineIn, class LayoutIn, class EngineOut, class LayoutOut, class UnaryOp>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
transform(Tensor<EngineIn,LayoutIn>&& tensor_in, Tensor<EngineOut,LayoutOut>&& tensor_out, UnaryOp&& op)
|
||||
{
|
||||
return transform(tensor_in, tensor_out, std::forward<UnaryOp>(op));
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
#include <cute/container/tuple.hpp>
|
||||
#include <cute/algorithm/functional.hpp>
|
||||
#include <cute/numeric/integer_sequence.hpp>
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
/** Common algorithms on (hierarchical) tuples */
|
||||
/** Style choice:
|
||||
@@ -150,7 +150,7 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
for_each_leaf(T&& t, F&& f)
|
||||
{
|
||||
if constexpr (is_tuple<std::remove_reference_t<T>>::value) {
|
||||
if constexpr (is_tuple<remove_cvref_t<T>>::value) {
|
||||
return detail::apply(static_cast<T&&>(t), [&](auto&&... a){ return (for_each_leaf(static_cast<decltype(a)&&>(a), f), ...); }, tuple_seq<T>{});
|
||||
} else {
|
||||
return f(static_cast<T&&>(t));
|
||||
@@ -205,6 +205,20 @@ transform_leaf(T const& t, F&& f)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <class T0, class T1, class F>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
transform_leaf(T0 const& t0, T1 const& t1, F&& f)
|
||||
{
|
||||
if constexpr (is_tuple<T0>::value) {
|
||||
return transform(t0, t1, [&](auto const& a, auto const& b) { return transform_leaf(a, b, f); });
|
||||
} else {
|
||||
return f(t0, t1);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
//
|
||||
// find and find_if
|
||||
//
|
||||
@@ -258,25 +272,40 @@ find(T const& t, X const& x)
|
||||
}
|
||||
|
||||
template <class T, class F>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
none_of(T const& t, F&& f)
|
||||
{
|
||||
return cute::integral_constant<bool, decltype(find_if(t, f))::value == std::tuple_size<T>::value>{};
|
||||
if constexpr (is_tuple<T>::value) {
|
||||
return cute::integral_constant<bool, decltype(find_if(t, f))::value == tuple_size<T>::value>{};
|
||||
} else {
|
||||
return not f(t);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <class T, class F>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
all_of(T const& t, F&& f)
|
||||
{
|
||||
auto not_f = [&](auto const& a) { return !f(a); };
|
||||
return cute::integral_constant<bool, decltype(find_if(t, not_f))::value == std::tuple_size<T>::value>{};
|
||||
if constexpr (is_tuple<T>::value) {
|
||||
auto not_f = [&](auto const& a) { return not f(a); };
|
||||
return cute::integral_constant<bool, decltype(find_if(t, not_f))::value == tuple_size<T>::value>{};
|
||||
} else {
|
||||
return f(t);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <class T, class F>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
any_of(T const& t, F&& f)
|
||||
{
|
||||
return cute::integral_constant<bool, !decltype(none_of(t, f))::value>{};
|
||||
return not none_of(t, f);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -340,7 +369,7 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
fold(T&& t, V&& v, F&& f)
|
||||
{
|
||||
if constexpr (is_tuple<std::remove_reference_t<T>>::value) {
|
||||
if constexpr (is_tuple<remove_cvref_t<T>>::value) {
|
||||
return detail::fold(static_cast<T&&>(t),
|
||||
static_cast<V&&>(v),
|
||||
f,
|
||||
@@ -357,11 +386,11 @@ CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
fold_first(T&& t, F&& f)
|
||||
{
|
||||
if constexpr (is_tuple<std::remove_reference_t<T>>::value) {
|
||||
if constexpr (is_tuple<remove_cvref_t<T>>::value) {
|
||||
return detail::fold(static_cast<T&&>(t),
|
||||
get<0>(static_cast<T&&>(t)),
|
||||
f,
|
||||
make_range<1,std::tuple_size<std::remove_reference_t<T>>::value>{});
|
||||
make_range<1,tuple_size<remove_cvref_t<T>>::value>{});
|
||||
} else {
|
||||
return static_cast<T&&>(t);
|
||||
}
|
||||
@@ -753,12 +782,12 @@ escan(T const& t, V const& v, F&& f)
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <int J, class T, int... Is>
|
||||
template <int J, class... Ts>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
zip_(T const& t, seq<Is...>)
|
||||
zip_(Ts const&... ts)
|
||||
{
|
||||
return cute::make_tuple(get<J>(get<Is>(t))...);
|
||||
return cute::make_tuple(get<J>(ts)...);
|
||||
}
|
||||
|
||||
template <class T, int... Is, int... Js>
|
||||
@@ -767,7 +796,7 @@ auto
|
||||
zip(T const& t, seq<Is...>, seq<Js...>)
|
||||
{
|
||||
static_assert(conjunction<bool_constant<tuple_size<tuple_element_t<0,T>>::value == tuple_size<tuple_element_t<Is,T>>::value>...>::value, "Mismatched Ranks");
|
||||
return cute::make_tuple(detail::zip_<Js>(t, seq<Is...>{})...);
|
||||
return cute::make_tuple(zip_<Js>(get<Is>(t)...)...);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
@@ -817,8 +846,8 @@ zip2_by(T const& t, TG const& guide, seq<Is...>, seq<Js...>)
|
||||
auto split = cute::make_tuple(zip2_by(get<Is>(t), get<Is>(guide))...);
|
||||
|
||||
// Rearrange and append missing modes from t to make ((A,B,...),(a,b,...,x,y))
|
||||
return cute::make_tuple(cute::make_tuple(get<Is,0>(split)...),
|
||||
cute::make_tuple(get<Is,1>(split)..., get<Js>(t)...));
|
||||
return cute::make_tuple(cute::make_tuple(get<0>(get<Is>(split))...),
|
||||
cute::make_tuple(get<1>(get<Is>(split))..., get<Js>(t)...));
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
@@ -49,7 +49,7 @@ CUTE_DEVICE void cluster_arrive_relaxed()
|
||||
#if defined(CUTE_ARCH_CLUSTER_SM90_ENABLED)
|
||||
asm volatile("barrier.cluster.arrive.relaxed.aligned;\n" : : );
|
||||
#else
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
CUTE_RUNTIME_ASSERT("CUTE_ARCH_CLUSTER_SM90_ENABLED is not defined");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ CUTE_DEVICE void cluster_arrive()
|
||||
#if defined(CUTE_ARCH_CLUSTER_SM90_ENABLED)
|
||||
asm volatile("barrier.cluster.arrive.aligned;\n" : : );
|
||||
#else
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
CUTE_RUNTIME_ASSERT("CUTE_ARCH_CLUSTER_SM90_ENABLED is not defined");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ CUTE_DEVICE void cluster_wait()
|
||||
#if defined(CUTE_ARCH_CLUSTER_SM90_ENABLED)
|
||||
asm volatile("barrier.cluster.wait.aligned;\n" : : );
|
||||
#else
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
CUTE_RUNTIME_ASSERT("CUTE_ARCH_CLUSTER_SM90_ENABLED is not defined");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ CUTE_DEVICE void cluster_sync()
|
||||
cluster_arrive();
|
||||
cluster_wait();
|
||||
#else
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
CUTE_RUNTIME_ASSERT("CUTE_ARCH_CLUSTER_SM90_ENABLED is not defined");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -90,8 +90,13 @@ CUTE_DEVICE dim3 cluster_grid_dims()
|
||||
asm volatile("mov.u32 %0, %nclusterid.y;\n" : "=r"(y) : );
|
||||
asm volatile("mov.u32 %0, %nclusterid.z;\n" : "=r"(z) : );
|
||||
return {x, y, z};
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
// MSVC requires protecting use of gridDim with __CUDA_ARCH__.
|
||||
return gridDim;
|
||||
#elif defined(_MSC_VER)
|
||||
CUTE_RUNTIME_ASSERT("cluster_grid_dims() can only be called on device");
|
||||
#else
|
||||
return {0, 0, 0};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -104,8 +109,13 @@ CUTE_DEVICE dim3 cluster_id_in_grid()
|
||||
asm volatile("mov.u32 %0, %clusterid.y;\n" : "=r"(y) : );
|
||||
asm volatile("mov.u32 %0, %clusterid.z;\n" : "=r"(z) : );
|
||||
return {x, y, z};
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
// MSVC requires protecting use of blockIdx with __CUDA_ARCH__.
|
||||
return blockIdx;
|
||||
#elif defined(_MSC_VER)
|
||||
CUTE_RUNTIME_ASSERT("cluster_id_in_grid() can only be called on device");
|
||||
#else
|
||||
return {0, 0, 0};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -154,8 +164,8 @@ CUTLASS_DEVICE uint32_t set_block_rank(uint32_t smemAddr, uint32_t rank)
|
||||
{
|
||||
#if defined(CUTE_ARCH_CLUSTER_SM90_ENABLED)
|
||||
uint32_t result;
|
||||
asm volatile("mapa.shared::cluster.u32 %0, %1, %2;\n"
|
||||
: "=r"(result)
|
||||
asm volatile("mapa.shared::cluster.u32 %0, %1, %2;\n"
|
||||
: "=r"(result)
|
||||
: "r"(smemAddr), "r"(rank));
|
||||
return result;
|
||||
#else
|
||||
@@ -187,4 +197,34 @@ CUTE_HOST_DEVICE uint32_t elect_one_sync()
|
||||
#endif
|
||||
}
|
||||
|
||||
struct ElectOneLaneIdReturnType {
|
||||
uint32_t is_leader;
|
||||
uint32_t leader_lane_id;
|
||||
};
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
ElectOneLaneIdReturnType
|
||||
elect_one_leader_sync()
|
||||
{
|
||||
#if defined(CUTE_ARCH_ELECT_ONE_SM90_ENABLED)
|
||||
uint32_t pred = 0;
|
||||
uint32_t laneid = 0;
|
||||
asm volatile(
|
||||
"{\n"
|
||||
".reg .b32 %rx;\n"
|
||||
".reg .pred %px;\n"
|
||||
" elect.sync %rx|%px, %2;\n"
|
||||
"@%px mov.s32 %1, 1;\n"
|
||||
" mov.s32 %0, %rx;\n"
|
||||
"}\n"
|
||||
: "+r"(laneid), "+r"(pred)
|
||||
: "r"(0xFFFFFFFF));
|
||||
return {pred, laneid};
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
return {(threadIdx.x % 32) == 0, 0};
|
||||
#else
|
||||
return {true, 0};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
// Config
|
||||
#if defined(__clang__) && defined(__CUDA__)
|
||||
// ldmatrix PTX instructions added in Clang 14: https://reviews.llvm.org/D107046
|
||||
// ... but broken until Clang 15:
|
||||
// ... but will not work until Clang 15:
|
||||
// * https://reviews.llvm.org/D121666
|
||||
// * https://reviews.llvm.org/D126846
|
||||
#define CUTE_ARCH_CLANG_SUPPORTS_LDSM_SM75 (__clang_major__ >= 15)
|
||||
|
||||
@@ -30,7 +30,10 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
#include <cuda.h>
|
||||
#include <cinttypes>
|
||||
#endif
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
@@ -135,18 +138,18 @@ enum class SmemSwizzleBits : uint8_t {
|
||||
|
||||
template <class T>
|
||||
inline CUtensorMapDataType to_CUtensorMapDataType() {
|
||||
if constexpr (std::is_same<T, int8_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT8; } else
|
||||
if constexpr (std::is_same<T, uint8_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT8; } else
|
||||
if constexpr (std::is_same<T, uint16_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT16; } else
|
||||
if constexpr (std::is_same<T, uint32_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT32; } else
|
||||
if constexpr (std::is_same<T, uint64_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT64; } else
|
||||
if constexpr (std::is_same<T, int32_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_INT32; } else
|
||||
if constexpr (std::is_same<T, int64_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_INT64; } else
|
||||
if constexpr (std::is_same<T, half_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_FLOAT16; } else
|
||||
if constexpr (std::is_same<T, float>::value) { return CU_TENSOR_MAP_DATA_TYPE_FLOAT32; } else
|
||||
if constexpr (std::is_same<T, double>::value) { return CU_TENSOR_MAP_DATA_TYPE_FLOAT64; } else
|
||||
if constexpr (std::is_same<T, bfloat16_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_BFLOAT16; } else
|
||||
if constexpr (std::is_same<T, tfloat32_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_TFLOAT32; } else
|
||||
if constexpr (is_same<T, int8_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT8; } else
|
||||
if constexpr (is_same<T, uint8_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT8; } else
|
||||
if constexpr (is_same<T, uint16_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT16; } else
|
||||
if constexpr (is_same<T, uint32_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT32; } else
|
||||
if constexpr (is_same<T, uint64_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_UINT64; } else
|
||||
if constexpr (is_same<T, int32_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_INT32; } else
|
||||
if constexpr (is_same<T, int64_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_INT64; } else
|
||||
if constexpr (is_same<T, half_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_FLOAT16; } else
|
||||
if constexpr (is_same<T, float>::value) { return CU_TENSOR_MAP_DATA_TYPE_FLOAT32; } else
|
||||
if constexpr (is_same<T, double>::value) { return CU_TENSOR_MAP_DATA_TYPE_FLOAT64; } else
|
||||
if constexpr (is_same<T, bfloat16_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_BFLOAT16; } else
|
||||
if constexpr (is_same<T, tfloat32_t>::value) { return CU_TENSOR_MAP_DATA_TYPE_TFLOAT32; } else
|
||||
{ static_assert(sizeof(T) < 0, "Unknown TMA Format!"); }
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ struct SM90_TMA_LOAD
|
||||
void const* const smem_ptr,
|
||||
int32_t const& crd0)
|
||||
{
|
||||
return SM90_TMA_LOAD_1D::copy(desc_ptr, smem_mbar, smem_ptr, crd0);
|
||||
return SM90_TMA_LOAD_1D::copy(desc_ptr, smem_mbar, smem_ptr, crd0);
|
||||
}
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
@@ -201,11 +201,138 @@ struct SM90_TMA_LOAD
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// TMA_LOAD im2col: Initiates a TMA copy, in im2col mode, from global memory to shared memory
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL_3D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_n,
|
||||
uint16_t const& offset_w)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_mbar = cast_smem_ptr_to_uint(&smem_mbar);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
// Copy from global to shared::cluster.
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.3d.shared::cluster.global.im2col.mbarrier::complete_tx::bytes"
|
||||
" [%0], [%1, {%3, %4, %5}], [%2], {%6};"
|
||||
:
|
||||
: "r"(smem_int_ptr), "l"(gmem_int_desc), "r"(smem_int_mbar),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_n),
|
||||
"h"(offset_w)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL_4D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_mbar = cast_smem_ptr_to_uint(&smem_mbar);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
// Copy from global to shared::cluster.
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.4d.shared::cluster.global.im2col.mbarrier::complete_tx::bytes"
|
||||
" [%0], [%1, {%3, %4, %5, %6}], [%2], {%7, %8};"
|
||||
:
|
||||
: "r"(smem_int_ptr), "l"(gmem_int_desc), "r"(smem_int_mbar),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_h), "r"(coord_n),
|
||||
"h"(offset_w), "h"(offset_h)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL_5D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_d, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h,
|
||||
uint16_t const& offset_d)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_mbar = cast_smem_ptr_to_uint(&smem_mbar);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
// Copy from global to shared::cluster.
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.5d.shared::cluster.global.im2col.mbarrier::complete_tx::bytes"
|
||||
" [%0], [%1, {%3, %4, %5, %6, %7}], [%2], {%8, %9, %10};"
|
||||
:
|
||||
: "r"(smem_int_ptr), "l"(gmem_int_desc), "r"(smem_int_mbar),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_h), "r"(coord_d), "r"(coord_n),
|
||||
"h"(offset_w), "h"(offset_h), "h"(offset_d)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_n,
|
||||
uint16_t const& offset_w)
|
||||
{
|
||||
return SM90_TMA_LOAD_IM2COL_3D::copy(desc_ptr, smem_mbar, smem_ptr,
|
||||
coord_c, coord_w, coord_n,
|
||||
offset_w);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h)
|
||||
{
|
||||
return SM90_TMA_LOAD_IM2COL_4D::copy(desc_ptr, smem_mbar, smem_ptr,
|
||||
coord_c, coord_w, coord_h, coord_n,
|
||||
offset_w, offset_h);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_d, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h,
|
||||
uint16_t const& offset_d)
|
||||
{
|
||||
return SM90_TMA_LOAD_IM2COL_5D::copy(desc_ptr, smem_mbar, smem_ptr,
|
||||
coord_c, coord_w, coord_h, coord_d, coord_n,
|
||||
offset_w, offset_h, offset_d);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// TMA_LOAD_MULTICAST: Initiates a TMA copy from global memory to shared memory
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct SM90_TMA_LOAD_1D_MULTICAST
|
||||
struct SM90_TMA_LOAD_MULTICAST_1D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
@@ -230,7 +357,7 @@ struct SM90_TMA_LOAD_1D_MULTICAST
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_2D_MULTICAST
|
||||
struct SM90_TMA_LOAD_MULTICAST_2D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
@@ -255,7 +382,7 @@ struct SM90_TMA_LOAD_2D_MULTICAST
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_3D_MULTICAST
|
||||
struct SM90_TMA_LOAD_MULTICAST_3D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
@@ -280,7 +407,7 @@ struct SM90_TMA_LOAD_3D_MULTICAST
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_4D_MULTICAST
|
||||
struct SM90_TMA_LOAD_MULTICAST_4D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
@@ -305,7 +432,7 @@ struct SM90_TMA_LOAD_4D_MULTICAST
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_5D_MULTICAST
|
||||
struct SM90_TMA_LOAD_MULTICAST_5D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
@@ -337,35 +464,174 @@ struct SM90_TMA_LOAD_MULTICAST
|
||||
void const* const smem_ptr,
|
||||
int32_t const& crd0)
|
||||
{
|
||||
return SM90_TMA_LOAD_1D_MULTICAST::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0);
|
||||
return SM90_TMA_LOAD_MULTICAST_1D::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0);
|
||||
}
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& crd0, int32_t const& crd1)
|
||||
{
|
||||
return SM90_TMA_LOAD_2D_MULTICAST::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1);
|
||||
return SM90_TMA_LOAD_MULTICAST_2D::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1);
|
||||
}
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& crd0, int32_t const& crd1, int32_t const& crd2)
|
||||
{
|
||||
return SM90_TMA_LOAD_3D_MULTICAST::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1, crd2);
|
||||
return SM90_TMA_LOAD_MULTICAST_3D::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1, crd2);
|
||||
}
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& crd0, int32_t const& crd1, int32_t const& crd2, int32_t const& crd3)
|
||||
{
|
||||
return SM90_TMA_LOAD_4D_MULTICAST::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1, crd2, crd3);
|
||||
return SM90_TMA_LOAD_MULTICAST_4D::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1, crd2, crd3);
|
||||
}
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar, uint16_t multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& crd0, int32_t const& crd1, int32_t const& crd2, int32_t const& crd3, int32_t const& crd4)
|
||||
{
|
||||
return SM90_TMA_LOAD_5D_MULTICAST::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1, crd2, crd3, crd4);
|
||||
return SM90_TMA_LOAD_MULTICAST_5D::copy(desc_ptr, smem_mbar, multicast_mask, smem_ptr, crd0, crd1, crd2, crd3, crd4);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// TMA_LOAD_MULTICAST im2col: Initiates a TMA copy, in im2col mode, from global memory to shared memory
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL_MULTICAST_3D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
uint16_t const& multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_n,
|
||||
uint16_t const& offset_w)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_mbar = cast_smem_ptr_to_uint(&smem_mbar);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
// Copy from global to shared::cluster.
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.3d.shared::cluster.global.im2col.mbarrier::complete_tx::bytes.multicast::cluster"
|
||||
" [%0], [%1, {%3, %4, %5}], [%2], {%6}, %7;"
|
||||
:
|
||||
: "r"(smem_int_ptr), "l"(gmem_int_desc), "r"(smem_int_mbar),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_n),
|
||||
"h"(offset_w),
|
||||
"h"(multicast_mask)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL_MULTICAST_4D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
uint16_t const& multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_mbar = cast_smem_ptr_to_uint(&smem_mbar);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
// Copy from global to shared::cluster.
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.4d.shared::cluster.global.im2col.mbarrier::complete_tx::bytes"
|
||||
" [%0], [%1, {%3, %4, %5, %6}], [%2], {%7, %8}, %9;"
|
||||
:
|
||||
: "r"(smem_int_ptr), "l"(gmem_int_desc), "r"(smem_int_mbar),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_h), "r"(coord_n),
|
||||
"h"(offset_w), "h"(offset_h),
|
||||
"h"(multicast_mask)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL_MULTICAST_5D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
uint16_t const& multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_d, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h,
|
||||
uint16_t const& offset_d)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_mbar = cast_smem_ptr_to_uint(&smem_mbar);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
// Copy from global to shared::cluster.
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.5d.shared::cluster.global.im2col.mbarrier::complete_tx::bytes"
|
||||
" [%0], [%1, {%3, %4, %5, %6, %7}], [%2], {%8, %9, %10}, %11;"
|
||||
:
|
||||
: "r"(smem_int_ptr), "l"(gmem_int_desc), "r"(smem_int_mbar),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_h), "r"(coord_d), "r"(coord_n),
|
||||
"h"(offset_w), "h"(offset_h), "h"(offset_d),
|
||||
"h"(multicast_mask)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_LOAD_IM2COL_MULTICAST
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
uint16_t const& multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_n,
|
||||
uint16_t const& offset_w)
|
||||
{
|
||||
return SM90_TMA_LOAD_IM2COL_MULTICAST_3D::copy(desc_ptr, smem_mbar,
|
||||
multicast_mask, smem_ptr,
|
||||
coord_c, coord_w, coord_n,
|
||||
offset_w);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
uint16_t const& multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h)
|
||||
{
|
||||
return SM90_TMA_LOAD_IM2COL_MULTICAST_4D::copy(desc_ptr, smem_mbar,
|
||||
multicast_mask, smem_ptr,
|
||||
coord_c, coord_w, coord_h, coord_n,
|
||||
offset_w, offset_h);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr, uint64_t& smem_mbar,
|
||||
uint16_t const& multicast_mask,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_d, int32_t const& coord_n,
|
||||
uint16_t const& offset_w,
|
||||
uint16_t const& offset_h,
|
||||
uint16_t const& offset_d)
|
||||
{
|
||||
return SM90_TMA_LOAD_IM2COL_MULTICAST_5D::copy(desc_ptr, smem_mbar,
|
||||
multicast_mask, smem_ptr,
|
||||
coord_c, coord_w, coord_h, coord_d, coord_n,
|
||||
offset_w, offset_h, offset_d);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -533,7 +799,7 @@ tma_store_arrive() {
|
||||
}
|
||||
|
||||
// Wait on prior N (Count) TMA_STORE instructions to complete
|
||||
template<int Count>
|
||||
template <int Count>
|
||||
CUTE_HOST_DEVICE static void
|
||||
tma_store_wait() {
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
@@ -547,6 +813,49 @@ tma_store_wait() {
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// BULK_COPY : Copy a bulk of memory between shared memory and global memory
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct SM90_BULK_COPY_G2S
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const gmem_ptr, uint64_t& smem_mbar,
|
||||
void const* const smem_ptr, int32_t load_bytes)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint32_t smem_int_mbar = cast_smem_ptr_to_uint(&smem_mbar);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
asm volatile("cp.async.bulk.shared::cluster.global.mbarrier::complete_tx::bytes [%0], [%1], %2, [%3];\n"
|
||||
:
|
||||
: "r"(smem_int_ptr), "l"(gmem_ptr), "r"(load_bytes), "r"(smem_int_mbar)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use BULK_COPY without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_BULK_COPY_S2G
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const smem_ptr,
|
||||
void const* const gmem_ptr, int32_t store_bytes)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
asm volatile("cp.async.bulk.global.shared::cta.bulk_group [%0], [%1], %2;\n"
|
||||
:
|
||||
: "l"(gmem_ptr), "r"(smem_int_ptr), "r"(store_bytes)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use BULK_COPY without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_BULK_COPY_AUTO {};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include <cute/arch/mma.hpp>
|
||||
|
||||
// Config
|
||||
#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900) && defined(__CUDA_ARCH_FEAT_SM90_ALL))
|
||||
#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
|
||||
# define CUTE_ARCH_MMA_SM90_ENABLED
|
||||
#endif
|
||||
|
||||
@@ -342,7 +342,7 @@ struct SM90_16x8x16_C64C64C64C64_TN
|
||||
namespace cute {
|
||||
namespace GMMA {
|
||||
|
||||
template<
|
||||
template <
|
||||
class ElementA,
|
||||
class ElementB,
|
||||
class ElementC,
|
||||
@@ -362,9 +362,9 @@ ss_op_selector()
|
||||
auto Tile_N = size<1>(TileShape_MNK{});
|
||||
|
||||
// FP16 accumulator
|
||||
if constexpr (std::is_same_v<ElementC, half_t>) {
|
||||
static_assert(std::is_same_v<ElementA, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
static_assert(std::is_same_v<ElementB, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
if constexpr (is_same_v<ElementC, half_t>) {
|
||||
static_assert(is_same_v<ElementA, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
static_assert(is_same_v<ElementB, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 16 == 0, "Tile_K must be a multiple of 16.");
|
||||
|
||||
// Dispatch against the Tile N mode size
|
||||
@@ -398,11 +398,11 @@ ss_op_selector()
|
||||
}
|
||||
|
||||
// FP32 accumulator
|
||||
else if constexpr (std::is_same_v<ElementC, float>) {
|
||||
else if constexpr (is_same_v<ElementC, float>) {
|
||||
|
||||
// FP16 inputs
|
||||
if constexpr (std::is_same_v<ElementA, half_t>) {
|
||||
static_assert(std::is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
if constexpr (is_same_v<ElementA, half_t>) {
|
||||
static_assert(is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 16 == 0, "Tile_K must be a multiple of 16.");
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x16_F32F16F16_SS<MajorA, MajorB, Args...>{};
|
||||
@@ -434,8 +434,8 @@ ss_op_selector()
|
||||
}
|
||||
|
||||
// BF16 inputs
|
||||
else if constexpr (std::is_same_v<ElementA, bfloat16_t>) {
|
||||
static_assert(std::is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
else if constexpr (is_same_v<ElementA, bfloat16_t>) {
|
||||
static_assert(is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 16 == 0, "Tile_K must be a multiple of 16.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
@@ -468,8 +468,8 @@ ss_op_selector()
|
||||
}
|
||||
|
||||
// TF32 inputs
|
||||
else if constexpr (std::is_same_v<ElementA, tfloat32_t>) {
|
||||
static_assert(std::is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
else if constexpr (is_same_v<ElementA, tfloat32_t>) {
|
||||
static_assert(is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
static_assert(MajorA == GMMA::Major::K, "MajorA must be GMMA::Major::K for this config.");
|
||||
static_assert(MajorB == GMMA::Major::K, "MajorB must be GMMA::Major::K for this config.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 8 == 0, "Tile_K must be a multiple of 8.");
|
||||
@@ -508,36 +508,36 @@ ss_op_selector()
|
||||
}
|
||||
|
||||
// S32 accumulator
|
||||
else if constexpr (std::is_same_v<ElementC, int32_t>) {
|
||||
else if constexpr (is_same_v<ElementC, int32_t>) {
|
||||
static_assert(MajorA == GMMA::Major::K, "MajorA must be GMMA::Major::K for this config.");
|
||||
static_assert(MajorB == GMMA::Major::K, "MajorB must be GMMA::Major::K for this config.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
// ElementA == int8_t && ElementB == int8_t
|
||||
if constexpr (std::is_same_v<ElementA, int8_t> && std::is_same_v<ElementB, int8_t>) {
|
||||
if constexpr (is_same_v<ElementA, int8_t> && is_same_v<ElementB, int8_t>) {
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32S8S8_SS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32S8S8_SS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
@@ -545,32 +545,32 @@ ss_op_selector()
|
||||
}
|
||||
|
||||
// ElementA == int8_t && ElementB == uint8_t
|
||||
else if constexpr (std::is_same_v<ElementA, int8_t> && std::is_same_v<ElementB, uint8_t>) {
|
||||
else if constexpr (is_same_v<ElementA, int8_t> && is_same_v<ElementB, uint8_t>) {
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32S8U8_SS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32S8U8_SS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
@@ -578,32 +578,32 @@ ss_op_selector()
|
||||
}
|
||||
|
||||
// ElementA == uint8_t && ElementB == int8_t
|
||||
else if constexpr (std::is_same_v<ElementA, uint8_t> && std::is_same_v<ElementB, int8_t>) {
|
||||
else if constexpr (is_same_v<ElementA, uint8_t> && is_same_v<ElementB, int8_t>) {
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32U8S8_SS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32U8S8_SS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
@@ -611,32 +611,32 @@ ss_op_selector()
|
||||
}
|
||||
|
||||
// ElementA == uint8_t && ElementB == uint8_t
|
||||
else if constexpr (std::is_same_v<ElementA, uint8_t> && std::is_same_v<ElementB, uint8_t>) {
|
||||
else if constexpr (is_same_v<ElementA, uint8_t> && is_same_v<ElementB, uint8_t>) {
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32U8U8_SS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32U8U8_SS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
@@ -650,7 +650,7 @@ ss_op_selector()
|
||||
}
|
||||
}
|
||||
|
||||
template<
|
||||
template <
|
||||
class ElementA,
|
||||
class ElementB,
|
||||
class ElementC,
|
||||
@@ -671,9 +671,9 @@ rs_op_selector()
|
||||
auto Tile_N = size<1>(TileShape_MNK{});
|
||||
|
||||
// FP16 accumulator
|
||||
if constexpr (std::is_same_v<ElementC, half_t>) {
|
||||
static_assert(std::is_same_v<ElementA, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
static_assert(std::is_same_v<ElementB, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
if constexpr (is_same_v<ElementC, half_t>) {
|
||||
static_assert(is_same_v<ElementA, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
static_assert(is_same_v<ElementB, half_t>, "Element types for AB must be half if ElementC is half.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 16 == 0, "Tile_K must be a multiple of 16.");
|
||||
|
||||
// Dispatch against the Tile N mode size
|
||||
@@ -707,12 +707,12 @@ rs_op_selector()
|
||||
}
|
||||
|
||||
// FP32 accumulator
|
||||
else if constexpr (std::is_same_v<ElementC, float>) {
|
||||
static_assert(std::is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
else if constexpr (is_same_v<ElementC, float>) {
|
||||
static_assert(is_same_v<ElementA, ElementB>, "ElementA and ElementB must be the same type for this config.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 16 == 0, "Tile_K must be a multiple of 16.");
|
||||
|
||||
// FP16 inputs
|
||||
if constexpr (std::is_same_v<ElementA, half_t>) {
|
||||
if constexpr (is_same_v<ElementA, half_t>) {
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x16_F32F16F16_RS<MajorA, MajorB, Args...>{};
|
||||
}
|
||||
@@ -743,7 +743,7 @@ rs_op_selector()
|
||||
}
|
||||
|
||||
// BF16 inputs
|
||||
else if constexpr (std::is_same_v<ElementA, bfloat16_t>) {
|
||||
else if constexpr (is_same_v<ElementA, bfloat16_t>) {
|
||||
static_assert(size<2>(TileShape_MNK{}) % 16 == 0, "Tile_K must be a multiple of 16.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
@@ -776,7 +776,7 @@ rs_op_selector()
|
||||
}
|
||||
|
||||
// TF32 inputs
|
||||
else if constexpr (std::is_same_v<ElementA, tfloat32_t>) {
|
||||
else if constexpr (is_same_v<ElementA, tfloat32_t>) {
|
||||
static_assert(MajorB == GMMA::Major::K, "MajorB must be GMMA::Major::K for this config.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 8 == 0, "Tile_K must be a multiple of 8.");
|
||||
|
||||
@@ -815,35 +815,35 @@ rs_op_selector()
|
||||
}
|
||||
|
||||
// S32 accumulator
|
||||
else if constexpr (std::is_same_v<ElementC, int32_t>) {
|
||||
else if constexpr (is_same_v<ElementC, int32_t>) {
|
||||
static_assert(MajorB == GMMA::Major::K, "MajorB must be GMMA::Major::K for this config.");
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
// ElementA == int8_t && ElementB == int8_t
|
||||
if constexpr (std::is_same_v<ElementA, int8_t> && std::is_same_v<ElementB, int8_t>) {
|
||||
if constexpr (is_same_v<ElementA, int8_t> && is_same_v<ElementB, int8_t>) {
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32S8S8_RS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32S8S8_RS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
@@ -851,32 +851,32 @@ rs_op_selector()
|
||||
}
|
||||
|
||||
// ElementA == int8_t && ElementB == uint8_t
|
||||
else if constexpr (std::is_same_v<ElementA, int8_t> && std::is_same_v<ElementB, uint8_t>) {
|
||||
else if constexpr (is_same_v<ElementA, int8_t> && is_same_v<ElementB, uint8_t>) {
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32S8U8_RS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32S8U8_RS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
@@ -884,32 +884,32 @@ rs_op_selector()
|
||||
}
|
||||
|
||||
// ElementA == uint8_t && ElementB == int8_t
|
||||
else if constexpr (std::is_same_v<ElementA, uint8_t> && std::is_same_v<ElementB, int8_t>) {
|
||||
else if constexpr (is_same_v<ElementA, uint8_t> && is_same_v<ElementB, int8_t>) {
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32U8S8_RS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32U8S8_RS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
@@ -917,32 +917,32 @@ rs_op_selector()
|
||||
}
|
||||
|
||||
// ElementA == uint8_t && ElementB == uint8_t
|
||||
else if constexpr (std::is_same_v<ElementA, uint8_t> && std::is_same_v<ElementB, uint8_t>) {
|
||||
else if constexpr (is_same_v<ElementA, uint8_t> && is_same_v<ElementB, uint8_t>) {
|
||||
static_assert(size<2>(TileShape_MNK{}) % 32 == 0, "Tile_K must be a multiple of 32.");
|
||||
|
||||
if constexpr (Tile_N % 256 == 0) {
|
||||
return SM90_64x256x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x256x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 192 == 0) {
|
||||
return SM90_64x192x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x192x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 128 == 0) {
|
||||
return SM90_64x128x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x128x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 96 == 0) {
|
||||
return SM90_64x96x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x96x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 64 == 0) {
|
||||
return SM90_64x64x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x64x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 32 == 0) {
|
||||
return SM90_64x32x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x32x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 16 == 0) {
|
||||
return SM90_64x16x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x16x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else if constexpr (Tile_N % 8 == 0) {
|
||||
return SM90_64x8x32_S32U8U8_RS_TN<Args...>{};
|
||||
return SM90_64x8x32_S32U8U8_RS_TN{};
|
||||
}
|
||||
else {
|
||||
static_assert(Tile_N % 8 == 0, "Tile_N must be a multiple of 8.");
|
||||
|
||||
@@ -31,13 +31,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
#include <cinttypes>
|
||||
#endif
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/arch/mma.hpp>
|
||||
|
||||
// Config
|
||||
#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900) && defined(__CUDA_ARCH_FEAT_SM90_ALL))
|
||||
# define CUTE_ARCH_MMA_SM90_ENABLED
|
||||
# define CUTE_ARCH_MMA_SM90A_ENABLED
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -68,6 +72,7 @@ CUTE_HOST_DEVICE char const* to_string(LayoutType const& t) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
// Output operator for all enums in this namespace
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, LayoutType const& t) {
|
||||
char const* s = to_string(t);
|
||||
@@ -78,6 +83,7 @@ CUTE_HOST std::ostream& operator<<(std::ostream& os, LayoutType const& t) {
|
||||
}
|
||||
return os;
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace GMMA
|
||||
|
||||
@@ -115,12 +121,14 @@ union GmmaDescriptor
|
||||
// Printer
|
||||
CUTE_HOST_DEVICE friend void print(GmmaDescriptor const& t)
|
||||
{
|
||||
printf("GmmaDescriptor: 0x%016lx\n", t.desc_);
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
printf("GmmaDescriptor: 0x%016" PRIx64 "\n", t.desc_);
|
||||
printf(" start_addr : 0x%04x\n", t.start_address_);
|
||||
printf(" leading_off: 0x%04x (%d)\n", t.leading_byte_offset_, t.leading_byte_offset_);
|
||||
printf(" stride_off : 0x%04x (%d)\n", t.stride_byte_offset_, t.stride_byte_offset_);
|
||||
printf(" base_offset: 0x%01x\n", t.base_offset_);
|
||||
printf(" layout_type: 0x%01x (%s)\n", t.layout_type_, to_string(static_cast<GMMA::LayoutType>(t.layout_type_)));
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2126
-1607
File diff suppressed because it is too large
Load Diff
@@ -42,17 +42,21 @@
|
||||
// __nvvm_get_smem_pointer added in Clang 14: https://reviews.llvm.org/D111665
|
||||
#define CUTE_CLANG_SUPPORTS_NVVM_GET_SMEM_POINTER (__clang_major__ >= 14)
|
||||
#else
|
||||
// ... but broken on Windows until Clang 15: https://reviews.llvm.org/D122897
|
||||
// ... but will not work on Windows until Clang 15: https://reviews.llvm.org/D122897
|
||||
#define CUTE_CLANG_SUPPORTS_NVVM_GET_SMEM_POINTER (__clang_major__ >= 15)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__NVCC__) || defined(__CUDACC_RTC__)
|
||||
// __cvta_generic_to_shared added in CUDA 11+
|
||||
#define CUTE_NVCC_SUPPORTS_CVTA_GENERIC_TO_SHARED (defined(__CUDA_ARCH__) && (__CUDACC_VER_MAJOR__ >= 11))
|
||||
#if defined(__CUDA_ARCH__) && (__CUDACC_VER_MAJOR__ >= 11)
|
||||
#define CUTE_NVCC_SUPPORTS_CVTA_GENERIC_TO_SHARED 1
|
||||
#endif
|
||||
|
||||
// __nvvm_get_smem_pointer added in CUDA 10.2
|
||||
#define CUTE_NVCC_SUPPORTS_NVVM_GET_SMEM_POINTER (defined(__CUDA_ARCH__) && (__CUDACC_VER_MAJOR__ == 10 && __CUDACC_VER_MINOR__ >= 2))
|
||||
#if defined(__CUDA_ARCH__) && __CUDACC_VER_MAJOR__ == 10 && __CUDACC_VER_MINOR__ >= 2
|
||||
#define CUTE_NVCC_SUPPORTS_NVVM_GET_SMEM_POINTER 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define CUTE_CVTA_GENERIC_TO_SHARED_SUPPORTED (CUTE_NVCC_SUPPORTS_CVTA_GENERIC_TO_SHARED || CUTE_CLANG_SUPPORTS_CVTA_GENERIC_TO_SHARED)
|
||||
@@ -172,6 +176,40 @@ explode(Fn fn,
|
||||
return fn(d[Id]..., a[Ia]..., b[Ib]..., c[Ic]...);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class PtrA, int... Ia,
|
||||
class PtrB, int... Ib,
|
||||
class PtrC, int... Ic,
|
||||
class ParamType>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode_with_d_scaling(Fn fn,
|
||||
PtrA&& a, int_sequence<Ia...>,
|
||||
PtrB&& b, int_sequence<Ib...>,
|
||||
PtrC&& c, int_sequence<Ic...>,
|
||||
ParamType&& p0)
|
||||
{
|
||||
return fn(a[Ia]..., b[Ib]..., c[Ic]..., p0);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class PtrD, int... Id,
|
||||
class PtrA, int... Ia,
|
||||
class PtrB, int... Ib,
|
||||
class PtrC, int... Ic,
|
||||
class ParamType>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode_with_d_scaling(Fn fn,
|
||||
PtrD&& d, int_sequence<Id...>,
|
||||
PtrA&& a, int_sequence<Ia...>,
|
||||
PtrB&& b, int_sequence<Ib...>,
|
||||
PtrC&& c, int_sequence<Ic...>,
|
||||
ParamType&& p0)
|
||||
{
|
||||
return fn(d[Id]..., a[Ia]..., b[Ib]..., c[Ic]..., p0);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <int SRegCount, int DRegCount,
|
||||
|
||||
+194
-159
@@ -30,51 +30,17 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/arch/copy.hpp>
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
namespace cute {
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
|
||||
// Generic copy_unpack for any Copy_Traits
|
||||
template <class Operation, class... Args,
|
||||
class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits<Operation, Args...> const&,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
// Specializations can generalize on these checks
|
||||
//static_assert(is_smem<TS>::value, "Expected smem for this Copy_Traits<Operation>");
|
||||
//static_assert(is_rmem<TD>::value, "Expected rmem for this Copy_Traits<Operation>");
|
||||
|
||||
using RegistersSrc = typename Operation::SRegisters;
|
||||
using RegistersDst = typename Operation::DRegisters;
|
||||
using RegTypeSrc = typename std::remove_extent<RegistersSrc>::type;
|
||||
using RegTypeDst = typename std::remove_extent<RegistersDst>::type;
|
||||
constexpr int RegNumSrc = std::extent<RegistersSrc>::value;
|
||||
constexpr int RegNumDst = std::extent<RegistersDst>::value;
|
||||
|
||||
Tensor rS = recast<RegTypeSrc>(src);
|
||||
Tensor rD = recast<RegTypeDst>(dst);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rS) == Int<RegNumSrc>{},
|
||||
"In CopyAtom, src layout doesn't vectorize into registers. This src layout is incompatible with this tiled copy.");
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumDst>{},
|
||||
"In CopyAtom, dst layout doesn't vectorize into registers. This dst layout is incompatible with this tiled copy.");
|
||||
|
||||
detail::explode(Operation::copy,
|
||||
rS, make_int_sequence<RegNumSrc>{},
|
||||
rD, make_int_sequence<RegNumDst>{});
|
||||
}
|
||||
|
||||
|
||||
template <class... Args>
|
||||
struct Copy_Atom;
|
||||
@@ -110,33 +76,18 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
|
||||
// Additional Trait parameters/transformations
|
||||
template <class... TraitsArgs>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
with(TraitsArgs&&... args) const {
|
||||
auto traits = Traits::with(std::forward<TraitsArgs>(args)...);
|
||||
return Copy_Atom<decltype(traits), T>{traits};
|
||||
}
|
||||
|
||||
// Print thread and data layouts for debugging
|
||||
CUTE_HOST_DEVICE static
|
||||
void
|
||||
print_all()
|
||||
{
|
||||
print("ThrID: "); print(ThrID{}); print("\n");
|
||||
print("BitLayoutSrc: "); print(BitLayoutSrc{}); print("\n");
|
||||
print("BitLayoutDst: "); print(BitLayoutDst{}); print("\n");
|
||||
print("BitLayoutRef: "); print(BitLayoutRef{}); print("\n");
|
||||
print("ValLayoutSrc: "); print(ValLayoutSrc{}); print("\n");
|
||||
print("ValLayoutDst: "); print(ValLayoutDst{}); print("\n");
|
||||
print("ValLayoutRef: "); print(ValLayoutRef{}); print("\n");
|
||||
print("ValueType: %db", sizeof_bits<ValType>::value); print("\n");
|
||||
}
|
||||
|
||||
//
|
||||
// Tensor call interfaces
|
||||
//
|
||||
|
||||
// Cast, check, and call
|
||||
// Check and call instruction, or recurse
|
||||
template <class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE
|
||||
@@ -147,12 +98,19 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
static_assert(SLayout::rank == 1, "Expected rank-1 src tensor");
|
||||
static_assert(DLayout::rank == 1, "Expected rank-1 dst tensor");
|
||||
|
||||
if constexpr (is_constant<NumValSrc, decltype(size(src))>::value || is_constant<NumValDst, decltype(size(dst))>::value) {
|
||||
if constexpr (is_constant<NumValSrc, decltype(size(src))>::value ||
|
||||
is_constant<NumValDst, decltype(size(dst))>::value) {
|
||||
// Dispatch to unpack for instruction
|
||||
return copy_unpack(*this, src, dst);
|
||||
} else {
|
||||
// Recurse if needed by peeling the tensor mode
|
||||
} else
|
||||
if constexpr (is_tuple<decltype(shape(src))>::value &&
|
||||
is_tuple<decltype(shape(dst))>::value) {
|
||||
// If the size of the src/dst doesn't match the instruction,
|
||||
// recurse this rank-1 layout by peeling off the mode
|
||||
// ((A,B,C,...)) -> (A,B,C,...)
|
||||
return copy(*this, tensor<0>(src), tensor<0>(dst));
|
||||
} else {
|
||||
static_assert(sizeof(TS) < 0, "No instruction match and no recursion possible.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +130,9 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
// A tiling of copy atoms
|
||||
//
|
||||
|
||||
template <class TiledCopy, class ThrIdx>
|
||||
struct ThrCopy;
|
||||
|
||||
template <class Copy_Atom,
|
||||
class LayoutCopy_TV, // (tid,vid) -> coord [Need not be 2D...]
|
||||
class ShapeTile_MN> // coord space
|
||||
@@ -211,7 +172,13 @@ struct TiledCopy : Copy_Atom
|
||||
auto
|
||||
tidfrg_S(STensor&& stensor)
|
||||
{
|
||||
return thrfrg(stensor, right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}));
|
||||
constexpr int R = remove_cvref_t<STensor>::rank;
|
||||
static_assert(R >= rank_v<TiledShape_MN>, "Rank of tensor to be partitioned too small.");
|
||||
// Generalize the dimension checks for arbitrary rank
|
||||
//CUTE_STATIC_ASSERT_V(size<0>(stensor) % size<0>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<1>(stensor) % size<1>(TiledShape_MNK{}) == Int<0>{});
|
||||
|
||||
return tile2thrfrg(zipped_divide(stensor,Tiler_MN{}), right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}));
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
@@ -229,20 +196,24 @@ struct TiledCopy : Copy_Atom
|
||||
auto
|
||||
tidfrg_D(DTensor&& dtensor)
|
||||
{
|
||||
return thrfrg(dtensor, right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}));
|
||||
}
|
||||
|
||||
template <class Tensor, class Ref2TrgLayout>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
thrfrg(Tensor&& tensor, Ref2TrgLayout const& ref2trg)
|
||||
{
|
||||
constexpr int R = remove_cvref_t<Tensor>::rank;
|
||||
constexpr int R = remove_cvref_t<DTensor>::rank;
|
||||
static_assert(R >= rank_v<TiledShape_MN>, "Rank of tensor to be partitioned too small.");
|
||||
// Generalize the dimension checks for arbitrary rank
|
||||
//CUTE_STATIC_ASSERT_V(size<0>(stensor) % size<0>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<1>(stensor) % size<1>(TiledShape_MNK{}) == Int<0>{});
|
||||
|
||||
return tile2thrfrg(zipped_divide(dtensor,Tiler_MN{}), right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}));
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
// (Tile,(RestM,RestN,...))
|
||||
// to shape
|
||||
// ((ThrV,ThrX),FrgV,(RestM,RestN,...))
|
||||
template <class Tensor, class Ref2TrgLayout>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
tile2thrfrg(Tensor&& tensor, Ref2TrgLayout const& ref2trg)
|
||||
{
|
||||
// Take the thrs/vals that the atom is interested in
|
||||
// NOTE: Assumes the AtomNumThr are contiguous and identity within TiledThrID
|
||||
auto atom_layout_TV = zipped_divide(TiledLayout_TV{}, make_shape(AtomNumThr{}, AtomNumVal{}));
|
||||
@@ -259,12 +230,8 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
/// ==================
|
||||
|
||||
// Tile the tensor for TiledLayout
|
||||
auto t_tensor = zipped_divide(tensor, Tiler_MN{});
|
||||
// ((TileM,TileN,...),(RestM,RestN,...))
|
||||
|
||||
// Transform the tile mode
|
||||
auto tv_tensor = t_tensor.compose(thrval2mn, _);
|
||||
auto tv_tensor = tensor.compose(thrval2mn, _);
|
||||
// ((thrid,val),(RM,RN,...))
|
||||
|
||||
// Unfold and return
|
||||
@@ -308,14 +275,22 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutS_MN()
|
||||
get_layoutS_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_S = make_layout(TiledShape_MN{});
|
||||
auto ref_S = make_layout(make_shape(TiledShape_MN{}, Int<1>{}));
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
auto layoutS_TV = tidfrg_S(ref_S);
|
||||
return tile2thrfrg(ref_S, right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}))(_,_,Int<0>{});
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutS_MN()
|
||||
{
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
auto layoutS_TV = get_layoutS_TV();
|
||||
// (M,K) -> (thr_idx,val_idx)
|
||||
auto layoutS_MK = right_inverse(layoutS_TV).with_shape(shape(ref_S));
|
||||
auto layoutS_MK = right_inverse(layoutS_TV).with_shape(TiledShape_MN{});
|
||||
|
||||
// athrid = (v,m,k) -> thr_idx
|
||||
auto thrID_S = make_layout(size<0>(TiledLayout_TV{}));
|
||||
@@ -325,24 +300,22 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutS_TV()
|
||||
get_layoutD_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_S = make_layout(TiledShape_MN{});
|
||||
auto ref_D = make_layout(make_shape(TiledShape_MN{}, Int<1>{}));
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
return tidfrg_S(ref_S)(_,_,Int<0>{});
|
||||
return tile2thrfrg(ref_D, right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}))(_,_,Int<0>{});
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutD_MN()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_D = make_layout(TiledShape_MN{});
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
auto layoutD_TV = tidfrg_D(ref_D);
|
||||
auto layoutD_TV = get_layoutD_TV();
|
||||
// (M,K) -> (thr_idx,val_idx)
|
||||
auto layoutD_MK = right_inverse(layoutD_TV).with_shape(shape(ref_D));
|
||||
auto layoutD_MK = right_inverse(layoutD_TV).with_shape(TiledShape_MN{});
|
||||
|
||||
// athrid = (v,m,k) -> thr_idx
|
||||
auto thrID_D = make_layout(size<0>(TiledLayout_TV{}));
|
||||
@@ -350,70 +323,13 @@ struct TiledCopy : Copy_Atom
|
||||
return cute::make_tuple(layoutD_MK, thrID_D);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutD_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_D = make_layout(TiledShape_MN{});
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
return tidfrg_D(ref_D)(_,_,Int<0>{});
|
||||
}
|
||||
|
||||
template <class ThrIdx>
|
||||
struct ThrCopy : Copy_Atom
|
||||
{
|
||||
ThrIdx thr_idx_;
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
ThrCopy(ThrIdx const& thr_idx) : thr_idx_(thr_idx) {}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_S(STensor&& stensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
// "Expected ValType for tiling SrcTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<STensor>(stensor).data(), tidfrg_S(stensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<STensor>>(_));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_D(DTensor&& dtensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
// "Expected ValType for tiling DstTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<DTensor>(dtensor).data(), tidfrg_D(dtensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<DTensor>>(_));
|
||||
}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_S(STensor&& stensor) {
|
||||
static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
"Expected ValType for tiling SrcTensor.");
|
||||
return make_tensor(std::forward<STensor>(stensor).data(), TiledCopy::retile(stensor.layout()));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_D(DTensor&& dtensor) {
|
||||
static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
"Expected ValType for tiling DstTensor.");
|
||||
return make_tensor(std::forward<DTensor>(dtensor).data(), TiledCopy::retile(dtensor.layout()));
|
||||
}
|
||||
};
|
||||
|
||||
template <class ThrIdx,
|
||||
__CUTE_REQUIRES(is_integral<ThrIdx>::value)>
|
||||
CUTE_HOST_DEVICE static
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
get_slice(ThrIdx const& thr_idx)
|
||||
{
|
||||
return ThrCopy<ThrIdx>(thr_idx);
|
||||
return ThrCopy<TiledCopy, ThrIdx>(thr_idx);
|
||||
}
|
||||
|
||||
template <class ThrIdx,
|
||||
@@ -426,17 +342,64 @@ struct TiledCopy : Copy_Atom
|
||||
}
|
||||
};
|
||||
|
||||
template <class TiledCopy, class ThrIdx>
|
||||
struct ThrCopy
|
||||
{
|
||||
ThrIdx thr_idx_;
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
ThrCopy(ThrIdx const& thr_idx) : thr_idx_(thr_idx) {}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_S(STensor&& stensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling SrcTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<STensor>(stensor).data(), TiledCopy::tidfrg_S(stensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<STensor>>(_));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_D(DTensor&& dtensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling DstTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<DTensor>(dtensor).data(), TiledCopy::tidfrg_D(dtensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<DTensor>>(_));
|
||||
}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_S(STensor&& stensor) {
|
||||
// static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling SrcTensor.");
|
||||
return make_tensor(std::forward<STensor>(stensor).data(), TiledCopy::retile(stensor.layout()));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_D(DTensor&& dtensor) {
|
||||
// static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling DstTensor.");
|
||||
return make_tensor(std::forward<DTensor>(dtensor).data(), TiledCopy::retile(dtensor.layout()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class... Args,
|
||||
class LayoutCopy_TV,
|
||||
class... TLayout>
|
||||
CUTE_HOST_DEVICE
|
||||
class Tiler>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_impl(Copy_Atom<Args...> const& atom,
|
||||
LayoutCopy_TV const&,
|
||||
Tile<TLayout...> const&)
|
||||
Tiler const&)
|
||||
{
|
||||
return TiledCopy<Copy_Atom<Args...>, LayoutCopy_TV, Tile<TLayout...>>{atom};
|
||||
return TiledCopy<Copy_Atom<Args...>, LayoutCopy_TV, Tiler>{atom};
|
||||
}
|
||||
|
||||
//
|
||||
@@ -445,7 +408,7 @@ make_tiled_copy_impl(Copy_Atom<Args...> const& atom,
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_A(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
@@ -456,7 +419,7 @@ make_tiled_copy_A(Copy_Atom<Args...> const& copy_atom,
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_B(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
@@ -467,7 +430,7 @@ make_tiled_copy_B(Copy_Atom<Args...> const& copy_atom,
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_C(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
@@ -476,10 +439,50 @@ make_tiled_copy_C(Copy_Atom<Args...> const& copy_atom,
|
||||
return make_tiled_copy_impl(copy_atom, tiled_mma.get_layoutC_TV(), make_shape(size<0>(MNK{}),size<1>(MNK{})));
|
||||
}
|
||||
|
||||
// returns the smallest tiled copy that can retile LayoutC_TV
|
||||
// for use with pipelined epilogues with subtiled stores
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_C_atom(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
{
|
||||
// Truncate the V-layout to just the Copy_Atom, keep the V-order
|
||||
auto layoutC_TV = tiled_mma.get_layoutC_TV();
|
||||
auto copy_V = Int<Copy_Atom<Args...>::NumValSrc>{};
|
||||
CUTE_STATIC_ASSERT_V(copy_V <= size<1>(layoutC_TV));
|
||||
auto layout_TV = composition(layoutC_TV, make_layout(make_shape(size<0>(layoutC_TV), copy_V)));
|
||||
|
||||
// Recompute tiler and restride the TV layout for the new tiler
|
||||
|
||||
// Tiler -- Find the active elements in the MMA tensor and generate a tiler to extract them
|
||||
// Convert to the awkward by-mode tiler to preserve the modes of the tiled MMA
|
||||
using MNK = typename TiledMMA::TiledShape_MNK;
|
||||
auto mma_tiler = make_shape(size<0>(MNK{}),size<1>(MNK{}));
|
||||
auto mma_zeros = repeat_like(mma_tiler, Int<0>{});
|
||||
|
||||
auto tiler = transform(make_seq<rank(mma_tiler)>{}, [&](auto i) {
|
||||
return filter(composition(make_layout(mma_tiler, replace<i>(mma_zeros, Int<1>{})), layout_TV));
|
||||
});
|
||||
|
||||
// Layout_TV -- Find the (tid,vid) -> tile coord transformation
|
||||
// Apply the tiler to a reference and transform the codomain
|
||||
// tile_coord -> mma_coord
|
||||
auto tile2mma = composition(make_layout(mma_tiler), tiler);
|
||||
|
||||
// (tid,vid) -> tile_coord
|
||||
auto layout_tv = composition(left_inverse(tile2mma), layout_TV);
|
||||
|
||||
|
||||
using MNK = typename TiledMMA::TiledShape_MNK;
|
||||
return make_tiled_copy_impl(copy_atom, layout_tv, tiler);
|
||||
}
|
||||
|
||||
template <class... Args,
|
||||
class ThrLayout,
|
||||
class ValLayout = Layout<_1>>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy(Copy_Atom<Args...> const& copy_atom,
|
||||
ThrLayout const& thr_layout = {}, // (m,n) -> thr_idx
|
||||
@@ -493,11 +496,10 @@ make_tiled_copy(Copy_Atom<Args...> const& copy_atom,
|
||||
// Take the raked_products to compute the Layout_MN
|
||||
auto layout_mn = raked_product(thr_layout_mn, val_layout_mn);
|
||||
auto layout_tv = right_inverse(layout_mn).with_shape(make_shape(size(thr_layout), size(val_layout)));
|
||||
|
||||
//print("thr_layout: "); print(thr_layout_mn); print("\n");
|
||||
//print("val_layout: "); print(val_layout_mn); print("\n");
|
||||
//print("layout_mn : "); print(layout_mn); print("\n");
|
||||
//print("layout_tv : "); print(layout_tv); print("\n");
|
||||
// print("thr_layout: "); print(thr_layout_mn); print("\n");
|
||||
// print("val_layout: "); print(val_layout_mn); print("\n");
|
||||
// print("layout_mn : "); print(layout_mn); print("\n");
|
||||
// print("layout_tv : "); print(layout_tv); print("\n");
|
||||
|
||||
return make_tiled_copy_impl(copy_atom, layout_tv, product_each(shape(layout_mn)));
|
||||
}
|
||||
@@ -505,7 +507,7 @@ make_tiled_copy(Copy_Atom<Args...> const& copy_atom,
|
||||
// Make a TiledCopy out of the copy_atom that matches the Src-Layout of tiled_copy
|
||||
template <class... Args,
|
||||
class TiledCopy>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_S(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledCopy const& tiled_copy)
|
||||
@@ -516,7 +518,7 @@ make_tiled_copy_S(Copy_Atom<Args...> const& copy_atom,
|
||||
// Make a TiledCopy out of the copy_atom that matches the Dst-Layout of tiled_copy
|
||||
template <class... Args,
|
||||
class TiledCopy>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_D(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledCopy const& tiled_copy)
|
||||
@@ -550,6 +552,40 @@ size(TiledCopy<Args...> const&)
|
||||
// Display utilities
|
||||
//
|
||||
|
||||
template <class... Args, class T>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(Copy_Atom<Copy_Traits<Args...>, T> const&)
|
||||
{
|
||||
using Atom = Copy_Atom<Copy_Traits<Args...>, T>;
|
||||
print("Copy_Atom\n");
|
||||
print(" ThrID: "); print(typename Atom::ThrID{}); print("\n");
|
||||
print(" ValLayoutSrc: "); print(typename Atom::ValLayoutSrc{}); print("\n");
|
||||
print(" ValLayoutDst: "); print(typename Atom::ValLayoutDst{}); print("\n");
|
||||
print(" ValLayoutRef: "); print(typename Atom::ValLayoutRef{}); print("\n");
|
||||
print(" ValueType: %db\n", int(sizeof_bits<typename Atom::ValType>::value));
|
||||
}
|
||||
|
||||
template <class Atom, class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(TiledCopy<Atom, Args...> const& copy, char const* pad = "")
|
||||
{
|
||||
using Copy = TiledCopy<Atom, Args...>;
|
||||
print("TiledCopy\n");
|
||||
print(" Tiler_MN: "); print(typename Copy::Tiler_MN{}); print("\n");
|
||||
print(" TiledLayout_TV: "); print(typename Copy::TiledLayout_TV{}); print("\n");
|
||||
print(static_cast<Atom const&>(copy));
|
||||
}
|
||||
|
||||
template <class TiledCopy, class ThrIdx>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(ThrCopy<TiledCopy, ThrIdx> const&)
|
||||
{
|
||||
print(TiledCopy{});
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
@@ -655,7 +691,6 @@ print_latex_copy(LayoutS const& S, ThrIDS const& TS, // (m,n) -> (tid,vid) and
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
#include <cute/atom/copy_traits_sm75.hpp>
|
||||
#include <cute/atom/copy_traits_sm80.hpp>
|
||||
#include <cute/atom/copy_traits_sm90.hpp>
|
||||
|
||||
@@ -32,11 +32,30 @@
|
||||
|
||||
#include <cute/arch/copy.hpp>
|
||||
|
||||
#include <cute/layout.hpp>
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
/**
|
||||
* concept Copy_Traits
|
||||
* {
|
||||
* using ThrID = // Logical thread id (tid) -> tidx
|
||||
*
|
||||
* using SrcLayout = // (Logical src thread id (tid), Logical src value id (vid)) -> bit
|
||||
* using DstLayout = // (Logical dst thread id (tid), Logical dst value id (vid)) -> bit
|
||||
* using RefLayout = // (Logical ref thread id (tid), Logical ref value id (vid)) -> bit
|
||||
* };
|
||||
*
|
||||
* The abstract bit ordering of the Copy_Traits (the codomain of SrcLayout, DstLayout, and RefLayout)
|
||||
* is arbitrary and only used to construct maps
|
||||
* (ref-tid,ref-vid) -> (src-tid,src-vid)
|
||||
* (ref-tid,ref-vid) -> (dst-tid,dst-vid)
|
||||
* in TiledCopy. The Layout_TV in TiledCopy is in accordance with the RefLayout of a Traits, then mapped to
|
||||
* the Src or Dst (tid,vid) representation on demand.
|
||||
*
|
||||
*/
|
||||
|
||||
template <class CopyOperation, class... CopyOpArgs>
|
||||
struct Copy_Traits
|
||||
{
|
||||
@@ -73,4 +92,40 @@ struct Copy_Traits<DefaultCopy>
|
||||
using RefLayout = SrcLayout;
|
||||
};
|
||||
|
||||
//
|
||||
// Generic copy_unpack for any Copy_Traits
|
||||
//
|
||||
template <class Operation, class... Args,
|
||||
class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits<Operation, Args...> const&,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
{
|
||||
// Specializations can generalize on these checks
|
||||
//static_assert(is_smem<TS>::value, "Expected smem for this Copy_Traits<Operation>");
|
||||
//static_assert(is_rmem<TD>::value, "Expected rmem for this Copy_Traits<Operation>");
|
||||
|
||||
using RegistersSrc = typename Operation::SRegisters;
|
||||
using RegistersDst = typename Operation::DRegisters;
|
||||
using RegTypeSrc = typename remove_extent<RegistersSrc>::type;
|
||||
using RegTypeDst = typename remove_extent<RegistersDst>::type;
|
||||
constexpr int RegNumSrc = extent<RegistersSrc>::value;
|
||||
constexpr int RegNumDst = extent<RegistersDst>::value;
|
||||
|
||||
Tensor rS = recast<RegTypeSrc>(src);
|
||||
Tensor rD = recast<RegTypeDst>(dst);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rS) == Int<RegNumSrc>{},
|
||||
"In CopyAtom, src layout doesn't vectorize into registers. This src layout is incompatible with this tiled copy.");
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumDst>{},
|
||||
"In CopyAtom, dst layout doesn't vectorize into registers. This dst layout is incompatible with this tiled copy.");
|
||||
|
||||
detail::explode(Operation::copy,
|
||||
rS, make_int_sequence<RegNumSrc>{},
|
||||
rD, make_int_sequence<RegNumDst>{});
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -30,14 +30,18 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
#include <cuda.h>
|
||||
#endif
|
||||
|
||||
#include <cute/arch/copy_sm90_desc.hpp>
|
||||
#include <cute/arch/copy_sm90_tma.hpp>
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
#include <cute/atom/copy_atom.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
@@ -142,7 +146,7 @@ struct Copy_Traits<SM90_TMA_LOAD, NumBits, GmemStrides>
|
||||
return {tma_desc_, tma_mbar};
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -257,7 +261,7 @@ struct Copy_Traits<SM90_TMA_LOAD_MULTICAST, NumBits, GmemStrides>
|
||||
return {tma_desc_, tma_load_mbar, multicast_mask};
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -300,6 +304,13 @@ struct Copy_Traits<SM90_TMA_STORE, NumBits, GmemStrides>
|
||||
TmaDescriptor tma_desc_;
|
||||
GmemStrides g_stride_;
|
||||
|
||||
// Return TmaDescriptor/TensorMap
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
TmaDescriptor const*
|
||||
get_tma_descriptor() const {
|
||||
return &tma_desc_;
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -352,47 +363,491 @@ struct Copy_Traits<SM90_TMA_STORE, NumBits, GmemStrides>
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////// BULK COPY //////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <class NumBits, class... OpArgs>
|
||||
struct Copy_Traits<SM90_BULK_COPY_G2S, NumBits, OpArgs...>
|
||||
{
|
||||
static_assert(int32_t(NumBits::value / 8) % 16 == 0,
|
||||
"Bulk Copy requires copy vector size align to 16B.");
|
||||
|
||||
using ThrID = Layout<_1>;
|
||||
// Map from (src-thr,src-val) to bit
|
||||
using SrcLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Map from (dst-thr,dst-val) to bit
|
||||
using DstLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Reference map from (thr,val) to bit
|
||||
using RefLayout = SrcLayout;
|
||||
|
||||
// SM90_BULK_COPY_G2S arguments
|
||||
// 0: uint64_t* bulk_load_memory_barrier
|
||||
cute::tuple<OpArgs...> bulk_load_mbar_;
|
||||
|
||||
template <class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE friend constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits const& traits,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
{
|
||||
static_assert(is_same<cute::tuple<OpArgs...>, cute::tuple<uint64_t*>>::value,
|
||||
"Extra arguments not set. Set .with() before use.");
|
||||
static_assert(is_gmem<TS>::value, "Expected gmem src for SM90_BULK_COPY_G2S");
|
||||
static_assert(is_smem<TD>::value, "Expected smem dst for SM90_BULK_COPY_G2S");
|
||||
SM90_BULK_COPY_G2S::copy(src.data().get(), *get<0>(traits.bulk_load_mbar_),
|
||||
dst.data().get(), int32_t(NumBits::value / 8));
|
||||
}
|
||||
|
||||
// Record the memory barrier for the instruction
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Copy_Traits<SM90_BULK_COPY_G2S, NumBits, uint64_t*>
|
||||
with(uint64_t& bulk_mbar) const {
|
||||
return {{&bulk_mbar}};
|
||||
}
|
||||
};
|
||||
|
||||
template <class NumBits>
|
||||
struct Copy_Traits<SM90_BULK_COPY_S2G, NumBits>
|
||||
{
|
||||
static_assert(int32_t(NumBits::value / 8) % 16 == 0,
|
||||
"Bulk Copy requires copy vector size align to 16B.");
|
||||
|
||||
using ThrID = Layout<_1>;
|
||||
// Map from (src-thr,src-val) to bit
|
||||
using SrcLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Map from (dst-thr,dst-val) to bit
|
||||
using DstLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Reference map from (thr,val) to bit
|
||||
using RefLayout = SrcLayout;
|
||||
|
||||
template <class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE friend constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits const& traits,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
{
|
||||
static_assert(is_smem<TS>::value, "Expected smem src for SM90_BULK_COPY_S2G");
|
||||
static_assert(is_gmem<TD>::value, "Expected gmem dst for SM90_BULK_COPY_S2G");
|
||||
SM90_BULK_COPY_S2G::copy(src.data().get(), dst.data().get(), int32_t(NumBits::value / 8));
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Placeholder for the bulk copy algorithm's default, auto-vectorizing behavior
|
||||
//
|
||||
|
||||
template <class... OpArgs>
|
||||
struct Copy_Traits<SM90_BULK_COPY_AUTO, OpArgs...>
|
||||
{
|
||||
// Logical thread id to thread idx (one-thread)
|
||||
using ThrID = Layout<_1>;
|
||||
// Map from (src-thr,src-val) to bit
|
||||
using SrcLayout = Layout<Shape<_1,_1>, Stride<_0,_0>>;
|
||||
// Map from (dst-thr,dst-val) to bit
|
||||
using DstLayout = Layout<Shape<_1,_1>, Stride<_0,_0>>;
|
||||
// Reference map from (thr,val) to bit
|
||||
using RefLayout = SrcLayout;
|
||||
|
||||
// SM90_UBULK_COPY arguments
|
||||
// 0: uint64_t* bulk_load_memory_barrier [if this is a BULK_LOAD_G2S]
|
||||
cute::tuple<OpArgs...> opargs_;
|
||||
|
||||
// Record the memory barrier for the instruction
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Copy_Traits<SM90_BULK_COPY_AUTO, uint64_t*>
|
||||
with(uint64_t& bulk_mbar) const {
|
||||
return {{&bulk_mbar}};
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// MAKE_TMA_COPY and related
|
||||
//
|
||||
|
||||
template <int B, int M, int S, class Offset, class SLayout>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout>)
|
||||
namespace detail
|
||||
{
|
||||
static_assert(M == 4, "Expected 128b=16B=(2^4)B base swizzle.");
|
||||
static_assert(S == 3, "Unsupported layout swizzle");
|
||||
|
||||
switch (B) {
|
||||
default: static_assert(0 <= B && B <= 3, "Expected B = 0,1,2, or 3. Unsupported layout swizzle.");
|
||||
case 3: return TMA::SmemSwizzleBits::B128;
|
||||
case 2: return TMA::SmemSwizzleBits::B64;
|
||||
case 1: return TMA::SmemSwizzleBits::B32;
|
||||
case 0: return TMA::SmemSwizzleBits::DISABLE;
|
||||
}
|
||||
template <int B, int M, int S, class Offset, class SLayout>
|
||||
auto
|
||||
get_swizzle_portion(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout>)
|
||||
{
|
||||
return Swizzle<B,M,S>{};
|
||||
}
|
||||
|
||||
template <class Shape, class Stride>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(Layout<Shape,Stride>)
|
||||
auto
|
||||
get_swizzle_portion(Layout<Shape,Stride>)
|
||||
{
|
||||
return TMA::SmemSwizzleBits::DISABLE;
|
||||
return Swizzle<0,4,3>{};
|
||||
}
|
||||
|
||||
template <int B, int M, int S, class Offset, class SLayout>
|
||||
auto
|
||||
get_nonswizzle_layout(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout> const& slayout)
|
||||
get_nonswizzle_portion(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout> const& slayout)
|
||||
{
|
||||
return slayout.layout_fn();
|
||||
}
|
||||
|
||||
template <class Shape, class Stride>
|
||||
auto
|
||||
get_nonswizzle_layout(Layout<Shape,Stride> const& slayout)
|
||||
get_nonswizzle_portion(Layout<Shape,Stride> const& slayout)
|
||||
{
|
||||
return slayout;
|
||||
}
|
||||
|
||||
template <int B, int M, int S>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(Swizzle<B,M,S>)
|
||||
{
|
||||
if constexpr (M == 4) {
|
||||
switch (B) {
|
||||
default: static_assert(0 <= B && B <= 3, "Expected B = 0,1,2, or 3 when M == 4. Unsupported layout swizzle.");
|
||||
case 3: return TMA::SmemSwizzleBits::B128;
|
||||
case 2: return TMA::SmemSwizzleBits::B64;
|
||||
case 1: return TMA::SmemSwizzleBits::B32;
|
||||
case 0: return TMA::SmemSwizzleBits::DISABLE;
|
||||
}
|
||||
} else
|
||||
{
|
||||
static_assert(M < 0, "Unsupported layout swizzle.");
|
||||
}
|
||||
}
|
||||
|
||||
template <class Layout>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(Layout const& layout)
|
||||
{
|
||||
return get_tma_swizzle_bits(get_swizzle_portion(layout));
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
// Use a smem2gmode map to read through the GMEM tensor
|
||||
// and construct a TMA Descriptor for the resulting instruction
|
||||
template <class GEngine, class GLayout,
|
||||
class SShape, class SStride,
|
||||
int B, int M, int S>
|
||||
CUTE_HOST
|
||||
auto
|
||||
make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GMEM Tensor
|
||||
Layout<SShape,SStride> const& smem_inv, // smem_idx to flat gmode
|
||||
Swizzle<B,M,S> const& swizzle) // Swizzle fn on smem_idx
|
||||
{
|
||||
using T = typename GEngine::value_type;
|
||||
|
||||
auto flat_glayout = flatten(gtensor.layout());
|
||||
CUTE_STATIC_ASSERT_V(rank(flat_glayout) == rank(smem_inv));
|
||||
constexpr int rank_smem_inv = decltype(rank(smem_inv))::value;
|
||||
|
||||
auto tma_multimode = rank(flat_glayout) > Int<5>{};
|
||||
constexpr uint32_t tma_dim = cute::min(rank(flat_glayout), 5);;
|
||||
|
||||
//
|
||||
// TMA gmem desc info
|
||||
//
|
||||
|
||||
void* gmem_address = (void*) gtensor.data();
|
||||
|
||||
cute::array<uint64_t, 5> gmem_prob_shape = {1,1,1,1,1};
|
||||
cute::array<uint64_t, 5> gmem_prob_stride = {0,0,0,0,0};
|
||||
for_each(make_seq<rank_smem_inv>{}, [&](auto i) {
|
||||
auto e = stride<i>(smem_inv); // For g++-7.5, let it deduce e rather than fuse with below
|
||||
constexpr int j = decltype(e.mode())::value;
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
|
||||
// Problem stride
|
||||
uint64_t stride_j = stride<j>(flat_glayout) * sizeof(T);
|
||||
uint64_t old_stride = gmem_prob_stride[tma_i];
|
||||
gmem_prob_stride[tma_i] = gcd(gmem_prob_stride[tma_i], stride_j);
|
||||
|
||||
// Problem shape
|
||||
uint64_t shape_j = shape<j>(flat_glayout);
|
||||
if (gmem_prob_stride[tma_i] != 0) {
|
||||
// We're "resetting" this TMA mode and using it as a "multimode"
|
||||
// Recurrence: g_shape = (s_i - 1) * (d_i / gcd_j d_j) + 1
|
||||
gmem_prob_shape[tma_i] = (gmem_prob_shape[tma_i]-1) * (old_stride / gmem_prob_stride[tma_i])
|
||||
+ (shape_j-1) * (stride_j / gmem_prob_stride[tma_i])
|
||||
+ 1;
|
||||
} else {
|
||||
gmem_prob_shape[tma_i] = shape_j;
|
||||
}
|
||||
});
|
||||
|
||||
assert((reinterpret_cast<uint64_t>(gmem_address) & 0b1111) == 0); // Address must be 16B-aligned
|
||||
|
||||
assert(gmem_prob_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[0] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[1] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[1] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[2] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[2] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[3] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[3] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[4] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[4] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
|
||||
assert((gmem_prob_stride[0]) == sizeof(T)); // First stride is implicitly 1
|
||||
assert((gmem_prob_stride[1]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[1] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[2]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[2] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[3]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[3] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[4]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[4] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
|
||||
//
|
||||
// TMA smem desc info
|
||||
//
|
||||
|
||||
cute::array<uint32_t, 5> smem_box_shape = {1,1,1,1,1};
|
||||
cute::array<uint32_t, 5> smem_box_stride = {1,1,1,1,1};
|
||||
for_each(make_seq<rank_smem_inv>{}, [&](auto i) {
|
||||
uint32_t shape_i = shape<i>(smem_inv);
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
if (tma_multimode && tma_i == 4) {
|
||||
// We're "reusing" this TMA mode and using it as a "multimode"
|
||||
smem_box_shape[tma_i] = 1;
|
||||
} else {
|
||||
smem_box_shape[tma_i] = shape_i;
|
||||
}
|
||||
});
|
||||
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
|
||||
assert(smem_box_stride[0] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[0] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[1] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[1] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[2] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[2] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[3] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[3] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[4] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[4] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
|
||||
//
|
||||
// Construct the descriptor
|
||||
//
|
||||
|
||||
TmaDescriptor tma_desc = {0};
|
||||
|
||||
//
|
||||
// TMA general info
|
||||
//
|
||||
|
||||
#if (__CUDACC_VER_MAJOR__ >= 12)
|
||||
|
||||
CUtensorMapDataType tma_format = TMA::to_CUtensorMapDataType<T>();
|
||||
CUtensorMapInterleave tma_interleave = CU_TENSOR_MAP_INTERLEAVE_NONE;
|
||||
CUtensorMapL2promotion tma_l2Promotion = CU_TENSOR_MAP_L2_PROMOTION_NONE;
|
||||
CUtensorMapFloatOOBfill tma_oobFill = CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE;
|
||||
|
||||
// TMA smem swizzle type
|
||||
CUtensorMapSwizzle smem_swizzle = TMA::to_CUtensorMapSwizzle(get_tma_swizzle_bits(swizzle));
|
||||
CUresult result = cuTensorMapEncodeTiled(
|
||||
&tma_desc,
|
||||
tma_format,
|
||||
tma_dim,
|
||||
gmem_address,
|
||||
gmem_prob_shape.data(),
|
||||
gmem_prob_stride.data() + 1, // gmem_prob_stride[0] implicitly 1
|
||||
smem_box_shape.data(),
|
||||
smem_box_stride.data(),
|
||||
tma_interleave,
|
||||
smem_swizzle,
|
||||
tma_l2Promotion,
|
||||
tma_oobFill);
|
||||
|
||||
if (result != CUDA_SUCCESS) {
|
||||
std::cerr << "TMA Desc Addr: " << &tma_desc
|
||||
<< "\nformat " << tma_format
|
||||
<< "\ndim " << tma_dim
|
||||
<< "\ngmem_address " << gmem_address
|
||||
<< "\nglobalDim " << gmem_prob_shape
|
||||
<< "\nglobalStrides " << gmem_prob_stride
|
||||
<< "\nboxDim " << smem_box_shape
|
||||
<< "\nelementStrides " << smem_box_stride
|
||||
<< "\ninterleave " << tma_interleave
|
||||
<< "\nswizzle " << smem_swizzle
|
||||
<< "\nl2Promotion " << tma_l2Promotion
|
||||
<< "\noobFill " << tma_oobFill << std::endl;
|
||||
std::cerr << "Error: Failed to initialize the TMA descriptor " << result << std::endl;
|
||||
assert(false);
|
||||
}
|
||||
|
||||
#endif // (__CUDACC_VER_MAJOR__ >= 12)
|
||||
// Finally, get the inverse permutation of the E<i> bases for the mocked gmem stride
|
||||
auto gmem_stride_bases_flat = transform(make_seq<rank_smem_inv>{}, [&](auto i) {
|
||||
auto k = find(stride(smem_inv), E<i>{});
|
||||
// For gcc 7.5 -- avoid 'if constexpr'
|
||||
int32_t tma_coord_stride = int32_t(stride<i>(flat_glayout) * sizeof(T) / (gmem_prob_stride[4] != 0 ? gmem_prob_stride[4] : 16));
|
||||
return conditional_return(tma_multimode && (k >= Int<4>{}),
|
||||
E<4>{} * tma_coord_stride, // The 4th TMA mode is the multimode, use int32_t coord stride
|
||||
E<k>{});
|
||||
});
|
||||
|
||||
// Give that the profile of gtensor and fold it
|
||||
// NOTE: This is the only reason we want the original gtensor shape rather than the more intuitive flattened shape
|
||||
auto gmem_stride_bases = stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), gmem_stride_bases_flat),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{}))));
|
||||
|
||||
return make_tuple(tma_desc, gmem_stride_bases);
|
||||
}
|
||||
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
class TShape, class TStride,
|
||||
class VShape, class VStride>
|
||||
CUTE_HOST
|
||||
auto
|
||||
make_tma_copy_tiled(CopyOp,
|
||||
Tensor<GEngine,GLayout> const& gtensor, // Full GMEM Tensor
|
||||
SLayout const& slayout, // CTA Tile of SMEM
|
||||
Layout<TShape,TStride> const& cta_t_map, // T: CTA thr idx -> logical TMA tid
|
||||
Layout<VShape,VStride> const& cta_v_map) // V: CTA val idx -> gmem coord
|
||||
{
|
||||
//
|
||||
// TMA parameter checking
|
||||
//
|
||||
|
||||
CUTE_STATIC_ASSERT_V(product_each(shape(slayout)) == product_each(shape(cta_v_map)),
|
||||
"TMA requires CTA_Tile and SLayout top-level shape equivalence.");
|
||||
CUTE_STATIC_ASSERT_V(size(slayout) % cosize(cta_t_map) == Int<0>{},
|
||||
"Number of active CTAs in TMA must divide domain size of slayout.");
|
||||
|
||||
//
|
||||
// TMA slayout manipulation
|
||||
//
|
||||
|
||||
auto flat_glayout = flatten(gtensor.layout());
|
||||
|
||||
// Invert the smem to get the largest contiguous vector in the smem layout
|
||||
auto inv_smem_layout = right_inverse(get_nonswizzle_portion(slayout));
|
||||
// trunc_smem_idx -> trunc_smem_coord
|
||||
|
||||
// Map from smem idx to a gmem mode
|
||||
auto sidx_to_gmode = coalesce(composition(cta_v_map, inv_smem_layout));
|
||||
|
||||
// Truncate any incompatibilities
|
||||
auto smem_rank = find_if(stride(sidx_to_gmode), [](auto e) {
|
||||
auto v = basis_value(e);
|
||||
return not is_constant<1,decltype(v)>{};
|
||||
});
|
||||
static_assert(smem_rank > 0, "Could not find a common smem-gmem vectorization for TMA. Do they have a common majorness?");
|
||||
// TMA uses a maximum of 5 modes
|
||||
// If the gtensor has more than 5 modes, we need to reserve the last TMA-mode as a "multimode"
|
||||
constexpr int smem_tma_rank = cute::min(int(smem_rank), (rank(flat_glayout) > Int<5>{} ? 4 : 5));
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto sidx_to_gmode_trunc = take<0,smem_tma_rank>(sidx_to_gmode);
|
||||
|
||||
// Split according to the portion each multicast CTA will be responsible for
|
||||
auto sidx_to_gmode_vt = logical_divide(sidx_to_gmode_trunc, shape_div(size(sidx_to_gmode_trunc), cosize(cta_t_map)));
|
||||
|
||||
#if 0
|
||||
print("g_layout : "); print(gtensor.layout()); print("\n");
|
||||
print("s_layout : "); print(slayout); print("\n");
|
||||
print("cta_t_map : "); print(cta_t_map); print("\n");
|
||||
print("cta_v_map : "); print(cta_v_map); print("\n");
|
||||
print("inv_smem : "); print(inv_smem_layout); print("\n");
|
||||
print("sidx_to_gmode : "); print(sidx_to_gmode); print("\n");
|
||||
|
||||
print("sidx_to_gmode_trunc : "); print(sidx_to_gmode_trunc); print("\n");
|
||||
print("sidx_to_gmode_vt : "); print(sidx_to_gmode_vt); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// Generate a TupleBasis for the gtensor
|
||||
auto flat_gbasis = make_basis_like(shape(flat_glayout));
|
||||
|
||||
// Fold the flat_gbasis into the glayout
|
||||
auto glayout_basis = make_layout(shape(gtensor),
|
||||
stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), flat_gbasis),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{})))));
|
||||
|
||||
// Tile the modes of gtensor with the truncated cta_v_map o inv_smem_layout_trunc
|
||||
auto tma_layout_v_trunc = flatten(composition(glayout_basis, layout<0>(sidx_to_gmode_vt)));
|
||||
|
||||
// Append any missing basis on the end as size-1 modes b/c they got truncated
|
||||
// NOTE This is essentially ArithmeticTuple complement...
|
||||
auto missing_basis = fold(stride(tma_layout_v_trunc), flat_gbasis, [](auto init, auto e) {
|
||||
auto k = find(init, e);
|
||||
return remove<k>(init);
|
||||
});
|
||||
|
||||
// The appended map from truncated smem codomain to gmem mode: trunc_smem_idx -> gmem_mode
|
||||
auto tma_layout_v = make_layout(flatten(cute::make_tuple(tma_layout_v_trunc.shape(), repeat<rank(missing_basis)>(Int<1>{}))),
|
||||
flatten(cute::make_tuple(tma_layout_v_trunc.stride(), missing_basis)));
|
||||
|
||||
#if 0
|
||||
print("flat_gbasis : "); print(flat_gbasis); print("\n");
|
||||
print("missing_b : "); print(missing_basis); print("\n");
|
||||
print("tma_layout_v : "); print(tma_layout_v); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// Construct the TMA Desc and GMEM mode ordering
|
||||
//
|
||||
|
||||
auto [tma_desc, gmem_stride_bases] = detail::make_tma_copy_desc(gtensor, tma_layout_v, get_swizzle_portion(slayout));
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
constexpr int num_bits = decltype(size<0>(sidx_to_gmode_vt))::value * sizeof(T) * 8;
|
||||
using Traits = Copy_Traits<CopyOp, Int<num_bits>, decltype(gmem_stride_bases)>;
|
||||
|
||||
#if 0
|
||||
print("num_bits : "); print(num_bits); print("\n");
|
||||
print("g_stride_bases: "); print(gmem_stride_bases); print("\n");
|
||||
#endif
|
||||
|
||||
Traits tma_traits{tma_desc, gmem_stride_bases};
|
||||
|
||||
//
|
||||
// Construct the TiledCopy
|
||||
//
|
||||
|
||||
auto cta_tiler = product_each(shape(cta_v_map));
|
||||
|
||||
// (CTA V, CTA T) -> smem_coord
|
||||
auto layout_vt = composition(inv_smem_layout, make_layout(shape(sidx_to_gmode_vt)));
|
||||
// Scale that up to cover all of the smem_coords
|
||||
auto layout_VT = tile_to_shape(layout_vt, make_shape(size(cta_v_map)/size<1>(layout_vt), size<1>(layout_vt)));
|
||||
// Flip it and change the domain of the T from logical thr to thr_idx
|
||||
auto layout_TV = make_layout(composition(layout<1>(layout_VT), cta_t_map), layout<0>(layout_VT));
|
||||
|
||||
#if 0
|
||||
print("cta_tiler : "); print(cta_tiler); print("\n");
|
||||
print("layout_VT : "); print(layout_VT); print("\n");
|
||||
print("layout_TV : "); print(layout_TV); print("\n");
|
||||
#endif
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
return TiledCopy<Copy_Atom<Traits,T>, decltype(layout_TV), decltype(cta_tiler)>{tma_traits};
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
/** Make a CuTe CTA-collective TiledCopy for a TMA operation.
|
||||
*
|
||||
* @param CopyOp The target copy operation: SM90_TMA_LOAD, SM90_TMA_LOAD_MULTICAST, SM90_TMA_STORE
|
||||
@@ -465,6 +920,7 @@ get_nonswizzle_layout(Layout<Shape,Stride> const& slayout)
|
||||
|
||||
copy(tma.with(barrier, mcast_mask), tAgA, tAsA); // copy with supporting TMA params
|
||||
*/
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
@@ -472,307 +928,18 @@ template <class CopyOp,
|
||||
class Cluster_Size>
|
||||
CUTE_HOST
|
||||
auto
|
||||
make_tma_copy(CopyOp,
|
||||
make_tma_copy(CopyOp const& copy_op,
|
||||
Tensor<GEngine,GLayout> const& gtensor,
|
||||
SLayout const& slayout,
|
||||
CTA_Tile const& cta_tile,
|
||||
Cluster_Size const& cluster_size)
|
||||
Cluster_Size const& cluster_size)
|
||||
{
|
||||
static_assert((std::is_same<CopyOp, SM90_TMA_LOAD>::value && is_constant<1, Cluster_Size>::value) ||
|
||||
(std::is_same<CopyOp, SM90_TMA_LOAD_MULTICAST>::value) ||
|
||||
(std::is_same<CopyOp, SM90_TMA_STORE>::value && is_constant<1, Cluster_Size>::value));
|
||||
|
||||
using T = typename Tensor<GEngine,GLayout>::value_type;
|
||||
|
||||
//
|
||||
// TMA parameter checking
|
||||
//
|
||||
|
||||
auto flat_glayout = flatten(gtensor.layout());
|
||||
|
||||
CUTE_STATIC_ASSERT_V(rank(flatten(cta_tile)) <= Int<5>{},
|
||||
"CTA_Tile cannot have more than five modes, TMA arch restriction.");
|
||||
CUTE_STATIC_ASSERT_V(rank(flat_glayout) <= Int<5>{} || rank(flatten(cta_tile)) <= Int<4>{},
|
||||
"If GTensor has more than five modes, then CTA_Tile cannot have more than four modes. TMA multimode.");
|
||||
CUTE_STATIC_ASSERT_V(compatible(product_each(shape(slayout)), shape(cta_tile)),
|
||||
"CTA_Tile must be compatible with SLayout.");
|
||||
CUTE_STATIC_ASSERT_V(is_integral<Cluster_Size>{} && has_single_bit(cluster_size) && cluster_size <= Int<16>{},
|
||||
"Expecting a pow2 integral Cluster_Size leq 16.");
|
||||
CUTE_STATIC_ASSERT_V(size(slayout) % cluster_size == Int<0>{},
|
||||
"ClusterShape must divide domain size of slayout.");
|
||||
|
||||
//
|
||||
// TMA slayout manipulation
|
||||
//
|
||||
|
||||
auto tma_multimode = rank(flat_glayout) > Int<5>{};
|
||||
|
||||
// Invert the smem to get the largest contiguous vector in the smem layout
|
||||
auto inv_smem_layout = right_inverse(get_nonswizzle_layout(slayout));
|
||||
// trunc_smem_idx -> trunc_smem_coord
|
||||
|
||||
// Map from smem idx to a gmem mode
|
||||
auto sidx_to_gmode = flatten(composition(make_identity_layout(cta_tile), inv_smem_layout));
|
||||
|
||||
// Truncate any incompatibilities
|
||||
auto smem_rank = find_if(stride(sidx_to_gmode), [](auto e){
|
||||
[[maybe_unused]] auto v = basis_value(e);
|
||||
return not is_constant<1,decltype(v)>{};
|
||||
});
|
||||
static_assert(smem_rank > 0, "Could not find a common smem-gmem vectorization for TMA.");
|
||||
constexpr int smem_tma_rank = cute::min(int(smem_rank), (tma_multimode ? 4 : 5));
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto sidx_to_gmode_cluster_trunc = take<0,smem_tma_rank>(sidx_to_gmode);
|
||||
// Keep only the portion each multicast CTA will be responsible for
|
||||
auto sidx_to_gmode_cta_trunc = composition(sidx_to_gmode_cluster_trunc, shape_div(size(sidx_to_gmode_cluster_trunc), cluster_size));
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// Generate a TupleBasis for the gtensor
|
||||
auto flat_gbasis = make_basis_like(shape(flat_glayout));
|
||||
|
||||
// Fold the flat_gbasis into the glayout
|
||||
auto glayout_basis = make_layout(shape(gtensor),
|
||||
stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), flat_gbasis),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{})))));
|
||||
|
||||
// Tile the modes of gtensor with cta_tile
|
||||
auto cta_glayout_basis = composition(glayout_basis, cta_tile);
|
||||
|
||||
// Check that the cta_tile selects modes from gtensor properly
|
||||
for_each(flatten(stride(cta_glayout_basis)), [](auto d) {
|
||||
static_assert(is_constant<1, decltype(d.value())>::value,
|
||||
"CTA_Tile does not faithfully partition the GMEM, it should select the number of elements from each mode of glayout.");
|
||||
});
|
||||
|
||||
// Tile the modes of gtensor again with the truncated cta_tile o inv_smem_layout
|
||||
auto tma_layout_cta_trunc = flatten(composition(glayout_basis, sidx_to_gmode_cta_trunc));
|
||||
|
||||
// Append any missing basis on the end as size-1 modes b/c they got truncated
|
||||
auto missing_basis = fold(stride(tma_layout_cta_trunc), flat_gbasis, [](auto init, auto e){
|
||||
auto k = find(init, e);
|
||||
return remove<k>(init);
|
||||
});
|
||||
|
||||
// The appended map from truncated smem codomain to gmem mode: trunc_smem_idx -> gmem_mode
|
||||
auto tma_layout_cta = flatten(make_layout(tma_layout_cta_trunc,
|
||||
make_layout(repeat<rank(missing_basis)>(Int<1>{}), missing_basis)));
|
||||
|
||||
#if 0
|
||||
print("g_layout : "); print(gtensor.layout()); print("\n");
|
||||
print("s_layout : "); print(slayout); print("\n");
|
||||
print("cta_tile : "); print(cta_tile); print("\n");
|
||||
print("cluster_size : "); print(cluster_size); print("\n");
|
||||
print("flat_gbasis : "); print(flat_gbasis); print("\n");
|
||||
print("cta_glayout : "); print(cta_glayout_basis); print("\n");
|
||||
print("inv_smem : "); print(inv_smem_layout); print("\n");
|
||||
print("sidx_to_gmode : "); print(sidx_to_gmode); print("\n");
|
||||
print("missing_b : "); print(missing_basis); print("\n");
|
||||
print("tma_layout_cta: "); print(tma_layout_cta); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gmem desc info
|
||||
//
|
||||
|
||||
constexpr int TmaRANK = cute::min(rank(flat_glayout), 5);
|
||||
void* gmem_address = (void*) gtensor.data();
|
||||
|
||||
cute::array<cuuint64_t, 5> gmem_prob_shape = {1,1,1,1,1};
|
||||
cute::array<cuuint64_t, 5> gmem_prob_stride = {0,0,0,0,0};
|
||||
for_each(make_seq<rank(tma_layout_cta)>{}, [&](auto i) {
|
||||
// NOTE : WAR g++-7.3.5, let it deduce e rather than fuse with below
|
||||
auto e = stride<i>(tma_layout_cta);
|
||||
constexpr int j = decltype(e.mode())::value;
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
|
||||
// Problem stride
|
||||
uint64_t stride_j = stride<j>(flat_glayout) * sizeof(T);
|
||||
uint64_t old_stride = gmem_prob_stride[tma_i];
|
||||
gmem_prob_stride[tma_i] = gcd(gmem_prob_stride[tma_i], stride_j);
|
||||
|
||||
// Problem shape
|
||||
uint64_t shape_j = shape<j>(flat_glayout);
|
||||
if (gmem_prob_stride[tma_i] != 0) {
|
||||
// We're "resetting" this TMA mode and using it as a "multimode"
|
||||
// Recurrence: g_shape = (s_i - 1) * (d_i / gcd_j d_j) + 1
|
||||
gmem_prob_shape[tma_i] = (gmem_prob_shape[tma_i]-1) * (old_stride / gmem_prob_stride[tma_i])
|
||||
+ (shape_j-1) * (stride_j / gmem_prob_stride[tma_i])
|
||||
+ 1;
|
||||
} else {
|
||||
gmem_prob_shape[tma_i] = shape_j;
|
||||
}
|
||||
});
|
||||
|
||||
assert((reinterpret_cast<uint64_t>(gmem_address) & 0b1111) == 0); // Address must be 16B-aligned
|
||||
|
||||
assert(gmem_prob_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[0] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[1] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[1] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[2] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[2] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[3] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[3] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[4] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[4] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
|
||||
assert((gmem_prob_stride[0]) == sizeof(T)); // First stride is implicitly 1
|
||||
assert((gmem_prob_stride[1]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[1] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[2]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[2] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[3]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[3] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[4]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[4] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
|
||||
//
|
||||
// TMA smem desc info
|
||||
//
|
||||
|
||||
// TMA smem box size
|
||||
cute::array<cuuint32_t, 5> smem_box_shape = {1,1,1,1,1};
|
||||
for_each(make_seq<rank(tma_layout_cta)>{}, [&](auto i) {
|
||||
uint32_t shape_i = shape<i>(tma_layout_cta);
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
if (tma_multimode && tma_i == 4) {
|
||||
// We're "reusing" this TMA mode and using it as a "multimode"
|
||||
smem_box_shape[tma_i] = 1;
|
||||
} else {
|
||||
smem_box_shape[tma_i] = shape_i;
|
||||
}
|
||||
});
|
||||
|
||||
// TMA smem mode strides
|
||||
[[maybe_unused]] cute::array<cuuint32_t, 5> smem_box_stride = {1,1,1,1,1};
|
||||
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
|
||||
assert(smem_box_stride[0] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[0] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[1] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[1] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[2] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[2] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[3] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[3] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[4] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[4] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
|
||||
//
|
||||
// Construct the descriptor
|
||||
//
|
||||
|
||||
TmaDescriptor tma_desc = {0};
|
||||
|
||||
#if (__CUDACC_VER_MAJOR__ >= 12)
|
||||
|
||||
//
|
||||
// TMA general info
|
||||
//
|
||||
|
||||
cuuint32_t tma_dim = TmaRANK;
|
||||
CUtensorMapDataType tma_format = TMA::to_CUtensorMapDataType<T>();
|
||||
CUtensorMapInterleave tma_interleave = CU_TENSOR_MAP_INTERLEAVE_NONE;
|
||||
CUtensorMapL2promotion tma_l2Promotion = CU_TENSOR_MAP_L2_PROMOTION_NONE;
|
||||
CUtensorMapFloatOOBfill tma_oobFill = CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE;
|
||||
|
||||
// TMA smem swizzle type
|
||||
CUtensorMapSwizzle smem_swizzle = TMA::to_CUtensorMapSwizzle(get_tma_swizzle_bits(slayout));
|
||||
|
||||
CUresult result = cuTensorMapEncodeTiled(
|
||||
&tma_desc,
|
||||
tma_format,
|
||||
tma_dim,
|
||||
gmem_address,
|
||||
gmem_prob_shape.data(),
|
||||
gmem_prob_stride.data() + 1, // gmem_prob_stride[0] implicitly 1
|
||||
smem_box_shape.data(),
|
||||
smem_box_stride.data(),
|
||||
tma_interleave,
|
||||
smem_swizzle,
|
||||
tma_l2Promotion,
|
||||
tma_oobFill);
|
||||
|
||||
if (result != CUDA_SUCCESS) {
|
||||
std::cerr << "TMA Desc Addr: " << &tma_desc
|
||||
<< "\nformat " << tma_format
|
||||
<< "\ndim " << tma_dim
|
||||
<< "\ngmem_address " << gmem_address
|
||||
<< "\nglobalDim " << gmem_prob_shape
|
||||
<< "\nglobalStrides " << gmem_prob_stride
|
||||
<< "\nboxDim " << smem_box_shape
|
||||
<< "\nelementStrides " << smem_box_stride
|
||||
<< "\ninterleave " << tma_interleave
|
||||
<< "\nswizzle " << smem_swizzle
|
||||
<< "\nl2Promotion " << tma_l2Promotion
|
||||
<< "\noobFill " << tma_oobFill << std::endl;
|
||||
std::cerr << "Error: Failed to initialize the TMA descriptor " << result << std::endl;
|
||||
assert(false);
|
||||
}
|
||||
#endif // (__CUDACC_VER_MAJOR__ >= 12)
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
// Finally, get the inverse permutation of the E<i> bases for the mocked gmem stride
|
||||
auto gmem_stride_bases_flat = transform(make_seq<rank(tma_layout_cta)>{}, [&](auto i) {
|
||||
auto k = find(stride(tma_layout_cta), E<i>{});
|
||||
// NOTE: gcc 7.3.5 WAR -- avoid if constexpr
|
||||
int32_t tma_coord_stride = int32_t(stride<i>(flat_glayout) * sizeof(T) / (gmem_prob_stride[4] != 0 ? gmem_prob_stride[4] : 16));
|
||||
return conditional_return(tma_multimode && (k >= Int<4>{}),
|
||||
E<4>{} * tma_coord_stride, // The 4th TMA mode is the multimode, use int32_t coord stride
|
||||
E<k>{});
|
||||
});
|
||||
|
||||
// Give that the profile of gtensor and fold it
|
||||
auto gmem_stride_bases = stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), gmem_stride_bases_flat),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{}))));
|
||||
|
||||
constexpr int num_bits = size(sidx_to_gmode_cta_trunc) * sizeof(T) * 8;
|
||||
using Traits = Copy_Traits<CopyOp, Int<num_bits>, decltype(gmem_stride_bases)>;
|
||||
|
||||
#if 0
|
||||
print("num_bits : "); print(num_bits); print("\n");
|
||||
print("g_stride_bases: "); print(gmem_stride_bases); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// Construct the TiledCopy
|
||||
//
|
||||
|
||||
// The ThrVal layout for 1 TMA instruction within cta_tile
|
||||
auto layout_tv_1 = composition(inv_smem_layout, make_layout(make_shape(cluster_size, size(sidx_to_gmode_cta_trunc)), GenRowMajor{}));
|
||||
// The ThrVal layout for N TMA instructions within cta_tile
|
||||
auto layout_tv = tile_to_shape(layout_tv_1, make_shape(cluster_size, size(cta_tile)/cluster_size));
|
||||
|
||||
#if 0
|
||||
print("layout_tv : "); print(layout_tv); print("\n");
|
||||
#endif
|
||||
|
||||
// If CTA_Tile and SLayout are incompatible, product_each makes sure
|
||||
// that the TiledCopy generates consistent accesses.
|
||||
auto cta_tile_tiled = [&]() {
|
||||
if constexpr (compatible(shape(CTA_Tile{}), shape(SLayout{}))) {
|
||||
return cta_tile;
|
||||
} else {
|
||||
return product_each(cta_tile);
|
||||
}
|
||||
}();
|
||||
|
||||
return TiledCopy<Copy_Atom<Traits,T>, decltype(layout_tv), decltype(cta_tile_tiled)>{tma_desc, gmem_stride_bases};
|
||||
return detail::make_tma_copy_tiled(copy_op,
|
||||
gtensor,
|
||||
slayout,
|
||||
make_layout(cluster_size),
|
||||
make_identity_layout(cta_tile));
|
||||
}
|
||||
|
||||
// Explicit defaulting
|
||||
@@ -797,9 +964,10 @@ auto
|
||||
make_tma_copy(CopyOp const& copy_op,
|
||||
Tensor<GEngine,GLayout> const& gtensor,
|
||||
SLayout const& slayout,
|
||||
Cluster_Size const& cluster_size)
|
||||
Cluster_Size const& cluster_size)
|
||||
{
|
||||
return make_tma_copy(copy_op, gtensor, slayout, product_each(shape(slayout)), cluster_size);
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
+113
-129
@@ -30,102 +30,17 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/arch/mma.hpp>
|
||||
#include <cute/atom/mma_traits.hpp>
|
||||
#include <cute/atom/mma_traits_sm90.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
#include <cute/atom/mma_traits.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute {
|
||||
|
||||
// Generic mma_unpack for any MMA_Traits
|
||||
template <class Operation,
|
||||
class TD, class DLayout,
|
||||
class TA, class ALayout,
|
||||
class TB, class BLayout,
|
||||
class TC, class CLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
mma_unpack(MMA_Traits<Operation> const&,
|
||||
Tensor<TD, DLayout> & D,
|
||||
Tensor<TA, ALayout> const& A,
|
||||
Tensor<TB, BLayout> const& B,
|
||||
Tensor<TC, CLayout> const& C)
|
||||
{
|
||||
static_assert(is_rmem<TD>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TA>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TB>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TC>::value, "Expected registers in MMA_Atom::call");
|
||||
|
||||
// Register value types from the MMA_Operation register arrays
|
||||
using RegTypeD = typename std::remove_extent<typename Operation::DRegisters>::type;
|
||||
using RegTypeA = typename std::remove_extent<typename Operation::ARegisters>::type;
|
||||
using RegTypeB = typename std::remove_extent<typename Operation::BRegisters>::type;
|
||||
using RegTypeC = typename std::remove_extent<typename Operation::CRegisters>::type;
|
||||
constexpr int RegNumD = std::extent<typename Operation::DRegisters>::value;
|
||||
constexpr int RegNumA = std::extent<typename Operation::ARegisters>::value;
|
||||
constexpr int RegNumB = std::extent<typename Operation::BRegisters>::value;
|
||||
constexpr int RegNumC = std::extent<typename Operation::CRegisters>::value;
|
||||
|
||||
Tensor rA = recast<RegTypeA>(A);
|
||||
Tensor rB = recast<RegTypeB>(B);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rA) == Int<RegNumA>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rB) == Int<RegNumB>{});
|
||||
|
||||
if constexpr (std::is_same<RegTypeD, void>::value)
|
||||
{
|
||||
static_assert(std::is_same<typename TD::value_type, typename TC::value_type>::value, "GMMA C and D value_type must match.");
|
||||
static_assert(std::is_same<DLayout, CLayout>::value, "GMMA C and D layouts must match.");
|
||||
// assert((void*)&C == (void*)&D);
|
||||
|
||||
Tensor rC = recast<RegTypeC>(D); // NOTE: D and C are same, so use mutable D
|
||||
|
||||
//CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
|
||||
detail::explode(Operation::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
} else
|
||||
{
|
||||
Tensor rD = recast<RegTypeD>(D);
|
||||
Tensor rC = recast<RegTypeC>(C);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumD>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
|
||||
detail::explode(Operation::fma,
|
||||
rD, make_int_sequence<RegNumD>{},
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeA_or_Default { using type = typename X::ElementAVal; };
|
||||
template <class X>
|
||||
struct FrgTypeA_or_Default<X,void_t<typename X::ElementAFrg>> { using type = typename X::ElementAFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeB_or_Default { using type = typename X::ElementBVal; };
|
||||
template <class X>
|
||||
struct FrgTypeB_or_Default<X,void_t<typename X::ElementBFrg>> { using type = typename X::ElementBFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeC_or_Default { using type = typename X::ElementCVal; };
|
||||
template <class X>
|
||||
struct FrgTypeC_or_Default<X,void_t<typename X::ElementCFrg>> { using type = typename X::ElementCFrg; };
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class... Args>
|
||||
struct MMA_Atom;
|
||||
|
||||
@@ -167,17 +82,6 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
return MMA_Atom<decltype(traits)>{traits};
|
||||
}
|
||||
|
||||
// Print thread and data layouts for debugging
|
||||
CUTE_HOST_DEVICE static
|
||||
void
|
||||
print_all()
|
||||
{
|
||||
print("ThrID: "); print(ThrID{}); print("\n");
|
||||
print("LayoutA_TV: "); print(LayoutA_TV{}); print("\n");
|
||||
print("LayoutB_TV: "); print(LayoutB_TV{}); print("\n");
|
||||
print("LayoutC_TV: "); print(LayoutC_TV{}); print("\n");
|
||||
}
|
||||
|
||||
//
|
||||
// Tensor call interfaces
|
||||
//
|
||||
@@ -232,7 +136,6 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
// Check that this tensor is likely already partitioned
|
||||
CUTE_STATIC_ASSERT_V(rank(ctensor) >= Int<3>{}); // VMN
|
||||
CUTE_STATIC_ASSERT_V(size<0>(ctensor) == size<1>(LayoutC_TV{}));
|
||||
|
||||
// C is a bit special because we are after accumulators here
|
||||
// The input/output type doesn't have to match the accumulator type
|
||||
//static_assert(std::is_same<ValTypeC, typename remove_cvref_t<CTensor>::value_type>::value, "Expecting ValTypeC type");
|
||||
@@ -249,12 +152,14 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
// Check that this tensor is likely already partitioned
|
||||
CUTE_STATIC_ASSERT_V(rank(atensor) >= Int<3>{}); // VMK
|
||||
CUTE_STATIC_ASSERT_V(size<0>(atensor) == size<1>(LayoutA_TV{}));
|
||||
static_assert(std::is_same<ValTypeA, typename remove_cvref_t<ATensor>::value_type>::value, "Expecting ValTypeA type");
|
||||
|
||||
if constexpr (has_dereference<FrgTypeA>::value) {
|
||||
return recast<FrgTypeA>(std::forward<ATensor>(atensor));
|
||||
// If the intended FrgTypeA is a view (of the current tensor), forward the whole
|
||||
static_assert(is_same<ValTypeA, typename remove_cvref_t<ATensor>::value_type>::value, "Expecting ValTypeA type");
|
||||
return make_tensor<FrgTypeA>(std::forward<ATensor>(atensor));
|
||||
} else {
|
||||
return make_tensor<FrgTypeA>(make_fragment_like(atensor.layout()));
|
||||
// Else, the intended FrgTypeA is a value type, construct a new tensor with a fragment layout
|
||||
return make_fragment_like<FrgTypeA>(atensor);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -268,12 +173,14 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
// Check that this tensor is likely already partitioned
|
||||
CUTE_STATIC_ASSERT_V(rank(btensor) >= Int<3>{}); // VNK
|
||||
CUTE_STATIC_ASSERT_V(size<0>(btensor) == size<1>(LayoutB_TV{}));
|
||||
static_assert(std::is_same<ValTypeB, typename remove_cvref_t<BTensor>::value_type>::value, "Expecting ValTypeB type");
|
||||
|
||||
if constexpr (has_dereference<FrgTypeB>::value) {
|
||||
return recast<FrgTypeB>(std::forward<BTensor>(btensor));
|
||||
// If the intended FrgTypeB is a view (of the current tensor), forward the whole
|
||||
static_assert(is_same<ValTypeB, typename remove_cvref_t<BTensor>::value_type>::value, "Expecting ValTypeB type");
|
||||
return make_tensor<FrgTypeB>(std::forward<BTensor>(btensor));
|
||||
} else {
|
||||
return make_tensor<FrgTypeB>(make_fragment_like(btensor.layout()));
|
||||
// Else, the intended FrgTypeB is a value type, construct a new tensor with a fragment layout
|
||||
return make_fragment_like<FrgTypeB>(btensor);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -607,7 +514,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_C(CTensor&& ctensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<CTensor>(ctensor).data(), thrfrg_C(ctensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<CTensor>(ctensor).data(), TiledMMA::thrfrg_C(ctensor.layout()));
|
||||
|
||||
auto thr_vmn = make_coord(get<0>(thr_vmnk_), make_coord(get<1>(thr_vmnk_), get<2>(thr_vmnk_)));
|
||||
return thr_tensor(thr_vmn, make_coord(_, repeat<rank<1,1>(thr_tensor)>(_)));
|
||||
@@ -618,7 +525,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_A(ATensor&& atensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<ATensor>(atensor).data(), thrfrg_A(atensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<ATensor>(atensor).data(), TiledMMA::thrfrg_A(atensor.layout()));
|
||||
|
||||
auto thr_vmk = make_coord(get<0>(thr_vmnk_), make_coord(get<1>(thr_vmnk_), get<3>(thr_vmnk_)));
|
||||
return thr_tensor(thr_vmk, make_coord(_, repeat<rank<1,1>(thr_tensor)>(_)));
|
||||
@@ -629,7 +536,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_B(BTensor&& btensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<BTensor>(btensor).data(), thrfrg_B(btensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<BTensor>(btensor).data(), TiledMMA::thrfrg_B(btensor.layout()));
|
||||
|
||||
auto thr_vnk = make_coord(get<0>(thr_vmnk_), make_coord(get<2>(thr_vmnk_), get<3>(thr_vmnk_)));
|
||||
return thr_tensor(thr_vnk, make_coord(_, repeat<rank<1,1>(thr_tensor)>(_)));
|
||||
@@ -675,8 +582,8 @@ make_tiled_mma(MMA_Atom<MMA_Op> const&,
|
||||
MMAValLayout const& val_layout = {},
|
||||
Permutations const& permutations = {})
|
||||
{
|
||||
auto thr_layout_mnk = append<3>(thr_layout, Layout<_1>{});
|
||||
auto val_layout_mnk = append<3>(val_layout, Layout<_1>{});
|
||||
auto thr_layout_mnk = append<3>(thr_layout, Layout<_1,_0>{});
|
||||
auto val_layout_mnk = append<3>(val_layout, Layout<_1,_0>{});
|
||||
auto permutation_mnk = append<3>(permutations, _);
|
||||
|
||||
return TiledMMA<MMA_Atom<MMA_Op>,
|
||||
@@ -707,19 +614,24 @@ make_tiled_mma(MMA_Op const&,
|
||||
template <class... Args, class Shape_MN>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_fragment_C(TiledMMA<Args...>, Shape_MN shapeMN)
|
||||
partition_shape_C(TiledMMA<Args...> const& mma, Shape_MN const& shape_MN)
|
||||
{
|
||||
constexpr int R = rank_v<Shape_MN>;
|
||||
static_assert(R >= 2, "Must have at least rank-2");
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto V = shape<1>(typename TiledMMA<Args...>::AtomLayoutC_TV{});
|
||||
auto M = shape_div(size<0>(shape_MN), size<0>(atomMNK) * size<1>(thrVMNK));
|
||||
auto N = shape_div(size<1>(shape_MN), size<1>(atomMNK) * size<2>(thrVMNK));
|
||||
return tuple_cat(make_shape(V,M,N), take<2,R>(shape_MN));
|
||||
}
|
||||
|
||||
auto V = size<1>(typename TiledMMA<Args...>::AtomLayoutC_TV{});
|
||||
auto M = shape_div(size<0>(shapeMN), size<0>(atomMNK) * size<1>(thrVMNK));
|
||||
auto N = shape_div(size<1>(shapeMN), size<1>(atomMNK) * size<2>(thrVMNK));
|
||||
auto frg_shape = tuple_cat(make_shape(V,M,N), take<2,R>(shapeMN));
|
||||
|
||||
return make_tensor<typename TiledMMA<Args...>::FrgTypeC>(frg_shape);
|
||||
template <class... Args, class Shape_MN>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_fragment_C(TiledMMA<Args...> const& mma, Shape_MN const& shapeMN)
|
||||
{
|
||||
return make_tensor<typename TiledMMA<Args...>::FrgTypeC>(partition_shape_C(mma, shapeMN));
|
||||
}
|
||||
|
||||
// partition_fragment_A and partition_fragment_B often depend on the
|
||||
@@ -727,6 +639,36 @@ partition_fragment_C(TiledMMA<Args...>, Shape_MN shapeMN)
|
||||
// For these reasons, they should not be used in a static context.
|
||||
// See TiledMMA::get_slice(thr_idx).partition_fragment_A(tensorA) instead.
|
||||
|
||||
template <class... Args, class Shape_MK>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_shape_A(TiledMMA<Args...> const& mma, Shape_MK const& shape_MK)
|
||||
{
|
||||
constexpr int R = rank_v<Shape_MK>;
|
||||
static_assert(R >= 2, "Must have at least rank-2");
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto V = shape<1>(typename TiledMMA<Args...>::AtomLayoutA_TV{});
|
||||
auto M = shape_div(size<0>(shape_MK), size<0>(atomMNK) * size<1>(thrVMNK));
|
||||
auto K = shape_div(size<1>(shape_MK), size<2>(atomMNK) * size<3>(thrVMNK));
|
||||
return tuple_cat(make_shape(V,M,K), take<2,R>(shape_MK));
|
||||
}
|
||||
|
||||
template <class... Args, class Shape_NK>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_shape_B(TiledMMA<Args...> const& mma, Shape_NK const& shape_NK)
|
||||
{
|
||||
constexpr int R = rank_v<Shape_NK>;
|
||||
static_assert(R >= 2, "Must have at least rank-2");
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto V = shape<1>(typename TiledMMA<Args...>::AtomLayoutB_TV{});
|
||||
auto N = shape_div(size<0>(shape_NK), size<1>(atomMNK) * size<2>(thrVMNK));
|
||||
auto K = shape_div(size<1>(shape_NK), size<2>(atomMNK) * size<3>(thrVMNK));
|
||||
return tuple_cat(make_shape(V,N,K), take<2,R>(shape_NK));
|
||||
}
|
||||
|
||||
//
|
||||
// Size
|
||||
//
|
||||
@@ -739,18 +681,62 @@ tile_size(TiledMMA<Args...> const& mma)
|
||||
return size<I...>(typename TiledMMA<Args...>::TiledShape_MNK{});
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
template <int... I, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tile_shape(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return shape<I...>(typename TiledMMA<Args...>::TiledShape_MNK{});
|
||||
}
|
||||
|
||||
template <int... I, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
size(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return size(typename TiledMMA<Args...>::ThrLayoutVMNK{});
|
||||
return size<I...>(typename TiledMMA<Args...>::ThrLayoutVMNK{});
|
||||
}
|
||||
|
||||
//
|
||||
// Display utilities
|
||||
//
|
||||
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(MMA_Atom<MMA_Traits<Args...>> const&)
|
||||
{
|
||||
using Atom = MMA_Atom<MMA_Traits<Args...>>;
|
||||
print("MMA_Atom\n");
|
||||
print(" ThrID: "); print(typename Atom::ThrID{}); print("\n");
|
||||
print(" LayoutA_TV: "); print(typename Atom::LayoutA_TV{}); print("\n");
|
||||
print(" LayoutB_TV: "); print(typename Atom::LayoutB_TV{}); print("\n");
|
||||
print(" LayoutC_TV: "); print(typename Atom::LayoutC_TV{}); print("\n");
|
||||
}
|
||||
|
||||
template <class Atom, class TiledThr, class TiledVal, class TiledPerm>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(TiledMMA<Atom, TiledThr, TiledVal, TiledPerm> const& mma)
|
||||
{
|
||||
using MMA = TiledMMA<Atom, TiledThr, TiledVal, TiledPerm>;
|
||||
print("TiledMMA\n");
|
||||
print(" TiledThr: "); print(TiledThr{}); print("\n");
|
||||
print(" TiledVal: "); print(TiledVal{}); print("\n");
|
||||
print(" TiledPerm: "); print(TiledPerm{}); print("\n");
|
||||
print(" TiledShape_MNK: "); print(typename MMA::TiledShape_MNK{}); print("\n");
|
||||
print(" ThrLayoutVMNK: "); print(typename MMA::ThrLayoutVMNK{}); print("\n");
|
||||
print(static_cast<Atom const&>(mma));
|
||||
}
|
||||
|
||||
template <class TiledMMA, class ThrVMNK>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(ThrMMA<TiledMMA, ThrVMNK> const&)
|
||||
{
|
||||
print(TiledMMA{});
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
@@ -992,9 +978,9 @@ print_latex_mma(Shape_MNK const& shape_mnk,
|
||||
|
||||
printf(latex_header);
|
||||
|
||||
int M = size<0>(shape_mnk);
|
||||
int N = size<1>(shape_mnk);
|
||||
int K = size<2>(shape_mnk);
|
||||
constexpr int M = size<0>(shape_mnk);
|
||||
constexpr int N = size<1>(shape_mnk);
|
||||
constexpr int K = size<2>(shape_mnk);
|
||||
|
||||
// C starting at 0,0
|
||||
bool c_filled[M][N] = {};
|
||||
@@ -1070,12 +1056,10 @@ print_latex_mma(Shape_MNK const& shape_mnk,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cute/atom/mma_traits.hpp>
|
||||
#include <cute/atom/mma_traits_sm61.hpp>
|
||||
#include <cute/atom/mma_traits_sm70.hpp>
|
||||
#include <cute/atom/mma_traits_sm75.hpp>
|
||||
#include <cute/atom/mma_traits_sm80.hpp>
|
||||
#include <cute/atom/mma_traits_sm90.hpp>
|
||||
#include <cute/atom/mma_traits_sm90_gmma.hpp>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -32,11 +32,43 @@
|
||||
|
||||
#include <cute/arch/mma.hpp>
|
||||
|
||||
#include <cute/layout.hpp>
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class X, class = void>
|
||||
struct supports_output_scaling { static constexpr bool value = false; };
|
||||
|
||||
template <class X>
|
||||
struct supports_output_scaling<X, void_t<decltype(declval<X>().accumulate_)>> { static constexpr bool value = true; };
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
/**
|
||||
* concept MMA_Traits
|
||||
* {
|
||||
* using ElementDVal = // Logical A-value type
|
||||
* using ElementAVal = // Logical B-value type
|
||||
* using ElementBVal = // Logical C-value type
|
||||
* using ElementCVal = // Logical D-value type (NOTE: Not used? Assumed == ElementDVal)
|
||||
*
|
||||
* using ElementAFrg = // A-type consumed by MMA (if ommitted, same as ElementAVal)
|
||||
* using ElementBFrg = // B_type consumed by MMA (if ommitted, same as ElementBVal)
|
||||
* using ElementCFrg = // C_type consumed by MMA (if ommitted, same as ElementCVal)
|
||||
*
|
||||
* using Shape_MNK = // Logical MxNxK shape of the MMA
|
||||
*
|
||||
* using ThrID = // Logical thread id (tid) -> tidx
|
||||
*
|
||||
* using ALayout = // (Logical thread id (tid), Logical value id (vid)) -> Flat MK-coord
|
||||
* using BLayout = // (Logical thread id (tid), Logical value id (vid)) -> Flat NK-coord
|
||||
* using CLayout = // (Logical thread id (tid), Logical value id (vid)) -> Flat MN-coord
|
||||
* };
|
||||
*/
|
||||
|
||||
template <class MMAOperation, class... MMAOpArgs>
|
||||
struct MMA_Traits
|
||||
{
|
||||
@@ -67,4 +99,110 @@ struct MMA_Traits<UniversalFMA<D,A,B,C>>
|
||||
using CLayout = Layout<Shape<_1,_1>>;
|
||||
};
|
||||
|
||||
//
|
||||
// Generic mma_unpack for any MMA_Traits
|
||||
//
|
||||
template <class MMA_Op, class... MMA_Args,
|
||||
class TD, class DLayout,
|
||||
class TA, class ALayout,
|
||||
class TB, class BLayout,
|
||||
class TC, class CLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
mma_unpack(MMA_Traits<MMA_Op, MMA_Args...> const& traits,
|
||||
Tensor<TD, DLayout> & D,
|
||||
Tensor<TA, ALayout> const& A,
|
||||
Tensor<TB, BLayout> const& B,
|
||||
Tensor<TC, CLayout> const& C)
|
||||
{
|
||||
static_assert(is_rmem<TD>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TA>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TB>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TC>::value, "Expected registers in MMA_Atom::call");
|
||||
|
||||
// Register value types from the MMA_Operation register arrays
|
||||
using RegTypeD = typename remove_extent<typename MMA_Op::DRegisters>::type;
|
||||
using RegTypeA = typename remove_extent<typename MMA_Op::ARegisters>::type;
|
||||
using RegTypeB = typename remove_extent<typename MMA_Op::BRegisters>::type;
|
||||
using RegTypeC = typename remove_extent<typename MMA_Op::CRegisters>::type;
|
||||
using MMATraits = MMA_Traits<MMA_Op, MMA_Args...>;
|
||||
|
||||
constexpr int RegNumD = extent<typename MMA_Op::DRegisters>::value;
|
||||
constexpr int RegNumA = extent<typename MMA_Op::ARegisters>::value;
|
||||
constexpr int RegNumB = extent<typename MMA_Op::BRegisters>::value;
|
||||
constexpr int RegNumC = extent<typename MMA_Op::CRegisters>::value;
|
||||
|
||||
Tensor rA = recast<RegTypeA>(A);
|
||||
Tensor rB = recast<RegTypeB>(B);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rA) == Int<RegNumA>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rB) == Int<RegNumB>{});
|
||||
|
||||
if constexpr (is_same<RegTypeD, void>::value)
|
||||
{
|
||||
static_assert(is_same<typename TD::value_type, typename TC::value_type>::value, "GMMA C and D value_type must match.");
|
||||
static_assert(is_same<DLayout, CLayout>::value, "GMMA C and D layouts must match.");
|
||||
// assert((void*)&C == (void*)&D);
|
||||
|
||||
Tensor rC = recast<RegTypeC>(D); // NOTE: D and C are same, so use mutable D
|
||||
|
||||
//CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
|
||||
if constexpr (detail::supports_output_scaling<MMATraits>::value) {
|
||||
detail::explode_with_d_scaling(MMA_Op::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{},
|
||||
traits.accumulate_);
|
||||
}
|
||||
else {
|
||||
detail::explode(MMA_Op::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
else {
|
||||
Tensor rD = recast<RegTypeD>(D);
|
||||
Tensor rC = recast<RegTypeC>(C);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumD>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
if constexpr (detail::supports_output_scaling<MMATraits>::value) {
|
||||
detail::explode_with_d_scaling(MMA_Op::fma,
|
||||
rD, make_int_sequence<RegNumD>{},
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{},
|
||||
traits.accumulate_);
|
||||
}
|
||||
else {
|
||||
detail::explode(MMA_Op::fma,
|
||||
rD, make_int_sequence<RegNumD>{},
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeA_or_Default { using type = typename X::ElementAVal; };
|
||||
template <class X>
|
||||
struct FrgTypeA_or_Default<X,void_t<typename X::ElementAFrg>> { using type = typename X::ElementAFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeB_or_Default { using type = typename X::ElementBVal; };
|
||||
template <class X>
|
||||
struct FrgTypeB_or_Default<X,void_t<typename X::ElementBFrg>> { using type = typename X::ElementBFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeC_or_Default { using type = typename X::ElementCVal; };
|
||||
template <class X>
|
||||
struct FrgTypeC_or_Default<X,void_t<typename X::ElementCFrg>> { using type = typename X::ElementCFrg; };
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
} // namespace cute
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+41
-2
@@ -40,9 +40,12 @@
|
||||
# define CUTE_HOST inline
|
||||
#endif // CUTE_HOST_DEVICE, CUTE_DEVICE
|
||||
|
||||
#if defined(__CUDA_ARCH__) || defined(_NVHPC_CUDA)
|
||||
#if !defined(__CUDACC_RTC__) && (defined(__CUDA_ARCH__) || defined(_NVHPC_CUDA))
|
||||
# define CUTE_UNROLL #pragma unroll
|
||||
# define CUTE_NO_UNROLL #pragma unroll 1
|
||||
#elif defined(__CUDACC_RTC__)
|
||||
# define CUTE_UNROLL _Pragma("unroll")
|
||||
# define CUTE_NO_UNROLL _Pragma("unroll 1")
|
||||
#else
|
||||
# define CUTE_UNROLL
|
||||
# define CUTE_NO_UNROLL
|
||||
@@ -54,6 +57,24 @@
|
||||
# define CUTE_INLINE_CONSTANT static constexpr
|
||||
#endif
|
||||
|
||||
// __grid_constant__ was introduced in CUDA 11.7.
|
||||
#if ((__CUDACC_VER_MAJOR__ >= 12) || ((__CUDACC_VER_MAJOR__ == 11) && (__CUDACC_VER_MINOR__ >= 7)))
|
||||
# define CUTE_GRID_CONSTANT_SUPPORTED
|
||||
#endif
|
||||
|
||||
// __grid_constant__ can be enabled only on SM70+.
|
||||
#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 700))
|
||||
# define CUTE_GRID_CONSTANT_ENABLED
|
||||
#endif
|
||||
|
||||
#if ! defined(CUTE_GRID_CONSTANT)
|
||||
# if defined(CUTE_GRID_CONSTANT_SUPPORTED) && defined(CUTE_GRID_CONSTANT_ENABLED)
|
||||
# define CUTE_GRID_CONSTANT __grid_constant__
|
||||
# else
|
||||
# define CUTE_GRID_CONSTANT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Some versions of GCC < 11 have trouble deducing that a
|
||||
// function with "auto" return type and all of its returns in an "if
|
||||
// constexpr ... else" statement must actually return. Thus, GCC
|
||||
@@ -72,17 +93,33 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Provides support for alternative operators 'and', 'or', and 'not'
|
||||
#include <iso646.h>
|
||||
#endif // _MSC_VER
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#define CUTE_STL_NAMESPACE cuda::std
|
||||
#define CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
#else
|
||||
#define CUTE_STL_NAMESPACE std
|
||||
#endif
|
||||
|
||||
//
|
||||
// Assertion helpers
|
||||
//
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/cassert>
|
||||
#else
|
||||
#include <cassert>
|
||||
#endif
|
||||
|
||||
#define CUTE_STATIC_ASSERT static_assert
|
||||
#define CUTE_STATIC_ASSERT_V(x,...) static_assert(decltype(x)::value, ##__VA_ARGS__)
|
||||
|
||||
#if defined(__CUDA_ARCH__)
|
||||
# define CUTE_RUNTIME_ASSERT(x) asm volatile ("brkpt;\n" ::: "memory")
|
||||
# define CUTE_RUNTIME_ASSERT(x) __brkpt()
|
||||
#else
|
||||
# define CUTE_RUNTIME_ASSERT(x) assert(0 && x)
|
||||
#endif
|
||||
@@ -91,9 +128,11 @@
|
||||
// IO
|
||||
//
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#endif
|
||||
|
||||
//
|
||||
// Support
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace cute
|
||||
template <int N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
bool
|
||||
is_byte_aligned(void const* const ptr)
|
||||
is_byte_aligned(void const* const ptr)
|
||||
{
|
||||
static_assert(N > 0 && (N & (N - 1)) == 0, "N must be a power of 2 in alignment check");
|
||||
return (reinterpret_cast<uintptr_t>(ptr) & (N-1)) == 0;
|
||||
@@ -54,7 +54,7 @@ is_byte_aligned(void const* const ptr)
|
||||
# define CUTE_ALIGNAS(n) alignas(n)
|
||||
#endif
|
||||
|
||||
template <std::size_t Alignment>
|
||||
template <size_t Alignment>
|
||||
struct aligned_struct {};
|
||||
|
||||
template <> struct CUTE_ALIGNAS( 1) aligned_struct< 1> {};
|
||||
|
||||
@@ -30,20 +30,20 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
struct array
|
||||
{
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using size_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
@@ -184,7 +184,7 @@ struct array
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void swap(array& other)
|
||||
{
|
||||
using std::swap;
|
||||
using CUTE_STL_NAMESPACE::swap;
|
||||
for (size_type i = 0; i < size(); ++i) {
|
||||
swap((*this)[i], other[i]);
|
||||
}
|
||||
@@ -194,11 +194,11 @@ struct array
|
||||
};
|
||||
|
||||
|
||||
template<class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
bool operator==(array<T,N> const& lhs, array<T,N> const& rhs)
|
||||
{
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (lhs[i] != rhs[i]) {
|
||||
return false;
|
||||
}
|
||||
@@ -206,21 +206,21 @@ bool operator==(array<T,N> const& lhs, array<T,N> const& rhs)
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void clear(array<T,N>& a)
|
||||
{
|
||||
a.fill(T(0));
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
template <typename T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void fill(array<T,N>& a, T const& value)
|
||||
{
|
||||
a.fill(value);
|
||||
}
|
||||
|
||||
template<class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void swap(array<T,N>& a, array<T,N>& b)
|
||||
{
|
||||
@@ -234,12 +234,16 @@ void swap(array<T,N>& a, array<T,N>& b)
|
||||
// Specialize tuple-related functionality for cute::array
|
||||
//
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/tuple>
|
||||
#else
|
||||
#include <tuple>
|
||||
#endif
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template<std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T& get(array<T,N>& a)
|
||||
{
|
||||
@@ -247,15 +251,15 @@ T& get(array<T,N>& a)
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template<std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T const& get(array<T,N> const& a)
|
||||
{
|
||||
static_assert(I < N, "Index out of range");
|
||||
static_assert(I < N, "Index out of range");
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template<std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T&& get(array<T,N>&& a)
|
||||
{
|
||||
@@ -265,18 +269,66 @@ T&& get(array<T,N>&& a)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
namespace std
|
||||
namespace CUTE_STL_NAMESPACE
|
||||
{
|
||||
|
||||
template <class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<cute::array<T,N>>
|
||||
: std::integral_constant<std::size_t, N>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, cute::array<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // end std
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<const cute::array<T,N>>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, const cute::array<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // end namespace CUTE_STL_NAMESPACE
|
||||
|
||||
#ifdef CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
namespace std
|
||||
{
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
template <class... _Tp>
|
||||
struct tuple_size;
|
||||
|
||||
template<size_t _Ip, class... _Tp>
|
||||
struct tuple_element;
|
||||
#endif
|
||||
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<cute::array<T,N>>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, cute::array<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<const cute::array<T,N>>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, const cute::array<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // end namepsace std
|
||||
#endif // CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
|
||||
@@ -30,247 +30,13 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/container/array.hpp>
|
||||
#include <cute/container/alignment.hpp>
|
||||
#include <cute/numeric/int.hpp>
|
||||
#include <cute/numeric/math.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <typename T, std::size_t N, std::size_t Alignment = 16>
|
||||
struct array_aligned
|
||||
: public aligned_struct<Alignment>
|
||||
{
|
||||
/// Make sure the Alignment makes sense wrt the size of elements.
|
||||
static_assert(Alignment == 16 || Alignment >= sizeof(T), "Alignment is too small");
|
||||
/// Alignment must be a power of two
|
||||
static_assert(has_single_bit(Alignment), "Alignment must be a power of two");
|
||||
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
reference operator[](size_type pos)
|
||||
{
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_reference operator[](size_type pos) const
|
||||
{
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
reference front()
|
||||
{
|
||||
return *begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_reference front() const
|
||||
{
|
||||
return *begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
reference back()
|
||||
{
|
||||
// return *rbegin();
|
||||
return operator[](N-1);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_reference back() const
|
||||
{
|
||||
// return *rbegin();
|
||||
return operator[](N-1);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T* data()
|
||||
{
|
||||
return reinterpret_cast<T*>(storage);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T const* data() const
|
||||
{
|
||||
return reinterpret_cast<T const*>(storage);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
iterator begin()
|
||||
{
|
||||
return data();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_iterator begin() const
|
||||
{
|
||||
return data();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_iterator cbegin()
|
||||
{
|
||||
return begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_iterator cbegin() const
|
||||
{
|
||||
return begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
iterator end()
|
||||
{
|
||||
return data() + size();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_iterator end() const
|
||||
{
|
||||
return data() + size();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_iterator cend()
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const_iterator cend() const
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
bool empty() const
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
size_type size() const
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
size_type max_size() const
|
||||
{
|
||||
return size();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void fill(T const& value)
|
||||
{
|
||||
for (auto& e : *this) {
|
||||
e = value;
|
||||
}
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void clear()
|
||||
{
|
||||
fill(T(0));
|
||||
}
|
||||
|
||||
// Not private, we want trivial type
|
||||
//private:
|
||||
|
||||
/// Storage type to use for Elements
|
||||
using StorageType = typename uint_byte<static_cast<int>(Alignment)>::type;
|
||||
|
||||
/// Ensure that there's enough storage for all elements
|
||||
static_assert(sizeof(StorageType) <= Alignment, "StorageType is too big for given alignment");
|
||||
|
||||
/// Number of elements in the storage
|
||||
static constexpr std::size_t storageN = (sizeof(T)*N + sizeof(StorageType) - 1) / sizeof(StorageType);
|
||||
|
||||
/// The storage.
|
||||
StorageType storage[storageN > 0 ? storageN : 1];
|
||||
};
|
||||
|
||||
//
|
||||
// Operators
|
||||
//
|
||||
|
||||
template <typename T, std::size_t N, std::size_t Alignment>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void clear(array_aligned<T, N, Alignment>& a)
|
||||
{
|
||||
a.clear();
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N, std::size_t Alignment>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void fill(array_aligned<T, N, Alignment>& a, T const& value)
|
||||
{
|
||||
a.fill(value);
|
||||
}
|
||||
template <class T, size_t N, size_t Alignment = 16>
|
||||
struct CUTE_ALIGNAS(Alignment) array_aligned : cute::array<T,N> {};
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
//
|
||||
// Specialize tuple-related functionality for cute::array
|
||||
//
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T& get(array_aligned<T,N>& a)
|
||||
{
|
||||
static_assert(I < N, "Index out of range");
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T const& get(array_aligned<T,N> const& a)
|
||||
{
|
||||
static_assert(I < N, "Index out of range");
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T&& get(array_aligned<T,N>&& a)
|
||||
{
|
||||
static_assert(I < N, "Index out of range");
|
||||
return std::move(a[I]);
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <class T, std::size_t N>
|
||||
struct tuple_size<cute::array_aligned<T,N>>
|
||||
: std::integral_constant<std::size_t, N>
|
||||
{};
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
struct tuple_element<I, cute::array_aligned<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // end std
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/numeric/int.hpp> // sizeof_bits
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
@@ -45,7 +46,7 @@ namespace cute
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Statically sized array for any data type
|
||||
template <class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
class array_subbyte
|
||||
{
|
||||
public:
|
||||
@@ -54,22 +55,15 @@ class array_subbyte
|
||||
static constexpr int kSizeBits = sizeof_bits<T>::value * N;
|
||||
|
||||
/// Storage type
|
||||
using Storage = typename std::conditional<
|
||||
(kSizeBits % 32) == 0,
|
||||
uint32_t,
|
||||
typename std::conditional<
|
||||
(kSizeBits % 16) == 0,
|
||||
uint16_t,
|
||||
uint8_t
|
||||
>::type
|
||||
>::type;
|
||||
|
||||
using Storage = conditional_t<(kSizeBits % 32) == 0, uint32_t,
|
||||
conditional_t<(kSizeBits % 16) == 0, uint16_t,
|
||||
uint8_t>>;
|
||||
|
||||
/// Number of logical elements per stored object
|
||||
static constexpr int kElementsPerStoredItem = sizeof_bits<Storage>::value / sizeof_bits<T>::value;
|
||||
|
||||
/// Number of storage elements
|
||||
static constexpr std::size_t kStorageElements = (N + kElementsPerStoredItem - 1) / kElementsPerStoredItem;
|
||||
static constexpr size_t kStorageElements = (N + kElementsPerStoredItem - 1) / kElementsPerStoredItem;
|
||||
|
||||
/// Bitmask for covering one item
|
||||
static constexpr Storage bit_mask_ = ((Storage(1) << sizeof_bits<T>::value) - 1);
|
||||
@@ -82,8 +76,8 @@ class array_subbyte
|
||||
using pointer = value_type*;
|
||||
using const_pointer = value_type const*;
|
||||
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using size_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
//
|
||||
// References
|
||||
@@ -110,7 +104,7 @@ class array_subbyte
|
||||
/// Assignment
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
reference& operator=(T x) {
|
||||
Storage item = (reinterpret_cast<Storage const&>(x) & bit_mask_);
|
||||
Storage item = (x & bit_mask_);
|
||||
Storage kUpdateMask = Storage(~(bit_mask_ << (idx_ * sizeof_bits<T>::value)));
|
||||
*ptr_ = Storage((*ptr_ & kUpdateMask) | (item << (idx_ * sizeof_bits<T>::value)));
|
||||
return *this;
|
||||
@@ -118,34 +112,21 @@ class array_subbyte
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T get() const {
|
||||
Storage item = Storage((*ptr_ >> (idx_ * sizeof_bits<T>::value)) & bit_mask_);
|
||||
return reinterpret_cast<T const&>(item);
|
||||
if constexpr (is_same<bool, T>::value) {
|
||||
// Extract to bool -- potentially faster impl
|
||||
return bool((*ptr_) & (bit_mask_ << (idx_ * sizeof_bits<T>::value)));
|
||||
} else {
|
||||
// Extract to T
|
||||
Storage item = Storage((*ptr_ >> (idx_ * sizeof_bits<T>::value)) & bit_mask_);
|
||||
return reinterpret_cast<T const&>(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract to type T -- disable if T == bool
|
||||
template <class U = T, __CUTE_REQUIRES(not std::is_same<U,bool>::value)>
|
||||
/// Extract to type T
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
operator T() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
// Extract to bool -- potentially faster impl
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
operator bool() const {
|
||||
return bool((*ptr_) & (bit_mask_ << (idx_ * sizeof_bits<T>::value)));
|
||||
}
|
||||
|
||||
/// Explicit cast to int
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
explicit operator int() const {
|
||||
return int(get());
|
||||
}
|
||||
|
||||
/// Explicit cast to float
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
explicit operator float() const {
|
||||
return float(get());
|
||||
}
|
||||
};
|
||||
|
||||
/// Reference object extracts sub-byte items
|
||||
@@ -169,34 +150,21 @@ class array_subbyte
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const T get() const {
|
||||
Storage item = Storage((*ptr_ >> (idx_ * sizeof_bits<T>::value)) & bit_mask_);
|
||||
return reinterpret_cast<T const&>(item);
|
||||
if constexpr (is_same<bool, T>::value) {
|
||||
// Extract to bool -- potentially faster impl
|
||||
return bool((*ptr_) & (bit_mask_ << (idx_ * sizeof_bits<T>::value)));
|
||||
} else {
|
||||
// Extract to T
|
||||
Storage item = Storage((*ptr_ >> (idx_ * sizeof_bits<T>::value)) & bit_mask_);
|
||||
return reinterpret_cast<T const&>(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract to type T -- disable if T == bool
|
||||
template <class U = T, __CUTE_REQUIRES(not std::is_same<U,bool>::value)>
|
||||
/// Extract to type T
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
operator T() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
// Extract to bool -- potentially faster impl
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
operator bool() const {
|
||||
return bool((*ptr_) & (bit_mask_ << (idx_ * sizeof_bits<T>::value)));
|
||||
}
|
||||
|
||||
/// Explicit cast to int
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
explicit operator int() const {
|
||||
return int(get());
|
||||
}
|
||||
|
||||
/// Explicit cast to float
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
explicit operator float() const {
|
||||
return float(get());
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
@@ -543,14 +511,14 @@ public:
|
||||
// Operators
|
||||
//
|
||||
|
||||
template <class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void clear(array_subbyte<T,N>& a)
|
||||
{
|
||||
a.clear();
|
||||
}
|
||||
|
||||
template <class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void fill(array_subbyte<T,N>& a, T const& value)
|
||||
{
|
||||
@@ -565,12 +533,16 @@ void fill(array_subbyte<T,N>& a, T const& value)
|
||||
// Specialize tuple-related functionality for cute::array_subbyte
|
||||
//
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/tuple>
|
||||
#else
|
||||
#include <tuple>
|
||||
#endif
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T& get(array_subbyte<T,N>& a)
|
||||
{
|
||||
@@ -578,7 +550,7 @@ T& get(array_subbyte<T,N>& a)
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T const& get(array_subbyte<T,N> const& a)
|
||||
{
|
||||
@@ -586,7 +558,7 @@ T const& get(array_subbyte<T,N> const& a)
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T&& get(array_subbyte<T,N>&& a)
|
||||
{
|
||||
@@ -596,18 +568,66 @@ T&& get(array_subbyte<T,N>&& a)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
namespace std
|
||||
namespace CUTE_STL_NAMESPACE
|
||||
{
|
||||
|
||||
template <class T, std::size_t N>
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<cute::array_subbyte<T,N>>
|
||||
: std::integral_constant<std::size_t, N>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <std::size_t I, class T, std::size_t N>
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, cute::array_subbyte<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<const cute::array_subbyte<T,N>>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, const cute::array_subbyte<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // end namespace CUTE_STL_NAMESPACE
|
||||
|
||||
#ifdef CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
namespace std
|
||||
{
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
template <class... _Tp>
|
||||
struct tuple_size;
|
||||
|
||||
template<size_t _Ip, class... _Tp>
|
||||
struct tuple_element;
|
||||
#endif
|
||||
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<cute::array_subbyte<T,N>>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, cute::array_subbyte<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T, size_t N>
|
||||
struct tuple_size<const cute::array_subbyte<T,N>>
|
||||
: cute::integral_constant<size_t, N>
|
||||
{};
|
||||
|
||||
template <size_t I, class T, size_t N>
|
||||
struct tuple_element<I, const cute::array_subbyte<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // end namespace std
|
||||
#endif // CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <class T, std::size_t N>
|
||||
struct array_view
|
||||
{
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
|
||||
array_view(array<T,N>& a)
|
||||
: __elems_(a.data()) {}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
reference operator[](size_type pos)
|
||||
{
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_reference operator[](size_type pos) const
|
||||
{
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
reference front()
|
||||
{
|
||||
return *begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_reference front() const
|
||||
{
|
||||
return *begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
reference back()
|
||||
{
|
||||
// return *rbegin();
|
||||
return operator[](N-1);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_reference back() const
|
||||
{
|
||||
// return *rbegin();
|
||||
return operator[](N-1);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
T* data()
|
||||
{
|
||||
return __elems_;
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const T* data() const
|
||||
{
|
||||
return __elems_;
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
iterator begin()
|
||||
{
|
||||
return data();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_iterator begin() const
|
||||
{
|
||||
return data();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_iterator cbegin()
|
||||
{
|
||||
return begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_iterator cbegin() const
|
||||
{
|
||||
return begin();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
iterator end()
|
||||
{
|
||||
return data() + size();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_iterator end() const
|
||||
{
|
||||
return data() + size();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_iterator cend()
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
const_iterator cend() const
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
bool empty() const
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
size_type size() const
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
size_type max_size() const
|
||||
{
|
||||
return size();
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
void fill(const T& value)
|
||||
{
|
||||
for(auto& e : *this)
|
||||
{
|
||||
e = value;
|
||||
}
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
void swap(array_view& other)
|
||||
{
|
||||
using std::swap;
|
||||
swap(__elems_, other.__elems_);
|
||||
}
|
||||
|
||||
value_type* __elems_;
|
||||
};
|
||||
|
||||
|
||||
template<class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE
|
||||
bool operator==(const array_view<T,N>& lhs, const array_view<T,N>& rhs)
|
||||
{
|
||||
for(std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
if(lhs[i] != rhs[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
CUTE_HOST_DEVICE
|
||||
void clear(array_view<T, N>& a)
|
||||
{
|
||||
a.fill(T(0));
|
||||
}
|
||||
|
||||
template<class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE
|
||||
void swap(array_view<T,N>& a, array_view<T,N>& b)
|
||||
{
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
} // end cute
|
||||
|
||||
|
||||
//
|
||||
// Specialize tuple-related functionality for cute::array_view
|
||||
//
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template<std::size_t I, class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T&
|
||||
get(array_view<T,N>& a)
|
||||
{
|
||||
static_assert(I < N, "Index out of range");
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template<std::size_t I, class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
const T&
|
||||
get(const array_view<T,N>& a)
|
||||
{
|
||||
static_assert(I < N, "Index out of range");
|
||||
return a[I];
|
||||
}
|
||||
|
||||
template<std::size_t I, class T, std::size_t N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T&&
|
||||
get(array_view<T,N>&& a)
|
||||
{
|
||||
static_assert(I < N, "Index out of range");
|
||||
return std::move(a[I]);
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template<class T, std::size_t N>
|
||||
struct tuple_size<cute::array_view<T,N>>
|
||||
: std::integral_constant<std::size_t, N>
|
||||
{};
|
||||
|
||||
template<std::size_t I, class T, std::size_t N>
|
||||
struct tuple_element<I, cute::array_view<T,N>>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // end std
|
||||
@@ -60,7 +60,7 @@ struct bit_field
|
||||
(BitStart / 32 == (BitStart + NumBits - 1) / 32) ? 32 : 64;
|
||||
using storage_type = cute::uint_bit_t<storage_type_bits>;
|
||||
|
||||
static_assert(sizeof(OtherValueType) == sizeof(value_type) || std::is_same<OtherValueType,dummy_type>::value,
|
||||
static_assert(sizeof(OtherValueType) == sizeof(value_type) || is_same<OtherValueType,dummy_type>::value,
|
||||
"sizeof(OtherValueType) must be same as sizeof(value_type).");
|
||||
|
||||
// Number of storage values needed: ceil_div(BitStart + NumBits, storage_type_bits)
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <vector_types.h>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
//
|
||||
// dim3
|
||||
//
|
||||
|
||||
using dim3 = ::dim3;
|
||||
|
||||
template <size_t I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
uint32_t& get(dim3& a)
|
||||
{
|
||||
static_assert(I < 3, "Index out of range");
|
||||
if constexpr (I == 0) {
|
||||
return a.x;
|
||||
} else if constexpr (I == 1) {
|
||||
return a.y;
|
||||
} else if constexpr (I == 2) {
|
||||
return a.z;
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <size_t I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
uint32_t const& get(dim3 const& a)
|
||||
{
|
||||
static_assert(I < 3, "Index out of range");
|
||||
if constexpr (I == 0) {
|
||||
return a.x;
|
||||
} else if constexpr (I == 1) {
|
||||
return a.y;
|
||||
} else if constexpr (I == 2) {
|
||||
return a.z;
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <size_t I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
uint32_t&& get(dim3&& a)
|
||||
{
|
||||
static_assert(I < 3, "Index out of range");
|
||||
if constexpr (I == 0) {
|
||||
return std::move(a.x);
|
||||
} else if constexpr (I == 1) {
|
||||
return std::move(a.y);
|
||||
} else if constexpr (I == 2) {
|
||||
return std::move(a.z);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
// Specialize cute::tuple-traits for external types
|
||||
template <>
|
||||
struct tuple_size<dim3>
|
||||
: integral_constant<size_t, 3>
|
||||
{};
|
||||
|
||||
template <size_t I>
|
||||
struct tuple_element<I, dim3>
|
||||
{
|
||||
using type = uint32_t;
|
||||
};
|
||||
|
||||
//
|
||||
// uint3
|
||||
//
|
||||
|
||||
using uint3 = ::uint3;
|
||||
|
||||
template <size_t I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
uint32_t& get(uint3& a)
|
||||
{
|
||||
static_assert(I < 3, "Index out of range");
|
||||
if constexpr (I == 0) {
|
||||
return a.x;
|
||||
} else if constexpr (I == 1) {
|
||||
return a.y;
|
||||
} else if constexpr (I == 2) {
|
||||
return a.z;
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <size_t I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
uint32_t const& get(uint3 const& a)
|
||||
{
|
||||
static_assert(I < 3, "Index out of range");
|
||||
if constexpr (I == 0) {
|
||||
return a.x;
|
||||
} else if constexpr (I == 1) {
|
||||
return a.y;
|
||||
} else if constexpr (I == 2) {
|
||||
return a.z;
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <size_t I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
uint32_t&& get(uint3&& a)
|
||||
{
|
||||
static_assert(I < 3, "Index out of range");
|
||||
if constexpr (I == 0) {
|
||||
return std::move(a.x);
|
||||
} else if constexpr (I == 1) {
|
||||
return std::move(a.y);
|
||||
} else if constexpr (I == 2) {
|
||||
return std::move(a.z);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
// Specialize cute::tuple-traits for external types
|
||||
template <>
|
||||
struct tuple_size<uint3>
|
||||
: integral_constant<size_t, 3>
|
||||
{};
|
||||
|
||||
template <size_t I>
|
||||
struct tuple_element<I, uint3>
|
||||
{
|
||||
using type = uint32_t;
|
||||
};
|
||||
|
||||
} // end namespace cute
|
||||
+151
-120
@@ -30,34 +30,16 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
#include <cute/numeric/integral_constant.hpp> // cute::true_type, cute::false_type
|
||||
#include <cute/numeric/integer_sequence.hpp>
|
||||
|
||||
#include <cute/container/cuda_types.hpp>
|
||||
|
||||
//#include <cute/container/array.hpp> // Advanced optimizations
|
||||
|
||||
#if 0
|
||||
//
|
||||
// Use of agency::tuple is functional, but is over-engineered for our purposes...
|
||||
// This tends to result in slow compilation times and unintentionally propagated cvref types
|
||||
//
|
||||
|
||||
#include <agency/tuple.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
using agency::tuple;
|
||||
|
||||
using agency::make_tuple;
|
||||
using agency::tuple_cat;
|
||||
|
||||
} // end namespace cute
|
||||
#endif
|
||||
|
||||
// cute::tuple is like std::tuple, with two differences.
|
||||
//
|
||||
// 1. It works on both host and device.
|
||||
@@ -68,12 +50,12 @@ using agency::tuple_cat;
|
||||
// but do _not_ include references like int& or float&.
|
||||
// (See std::tie for an example of a tuple of references.)
|
||||
//
|
||||
// This is simplified over the implementation in std:: and agency:: by ignoring much of
|
||||
// This is simplified over the implementations in std::, cuda::std::, and thrust:: by ignoring much of
|
||||
// the conversion SFINAE, special overloading, and avoiding cvref template types.
|
||||
// Furthermore, the empty base optimization (EBO) is MORE aggressive by avoiding
|
||||
// construction calls, and ignoring any need for unique element addresses.
|
||||
//
|
||||
// Over the agency::tuple implementation, this appears to accelerate compilation times by over 3x.
|
||||
// Over standard-conforming tuple implementations, this appears to accelerate compilation times by over 3x.
|
||||
|
||||
namespace cute
|
||||
{
|
||||
@@ -91,7 +73,7 @@ namespace detail
|
||||
// EBO always "holds" a single value of type T.
|
||||
// N is like an array index that TupleBase uses
|
||||
// to access the desired tuple element.
|
||||
template <std::size_t N, class T, bool IsEmpty = std::is_empty<T>::value>
|
||||
template <size_t N, class T, bool IsEmpty = is_empty<T>::value>
|
||||
struct EBO;
|
||||
|
||||
// Specialization for types T that have no data;
|
||||
@@ -99,7 +81,7 @@ struct EBO;
|
||||
// integral_constant<U, Value>, Int<Value>,
|
||||
// and any other semiregular type
|
||||
// for which std::is_empty_v<T> is true.
|
||||
template <std::size_t N, class T>
|
||||
template <size_t N, class T>
|
||||
struct EBO<N, T, true>
|
||||
{
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -109,7 +91,7 @@ struct EBO<N, T, true>
|
||||
EBO(T const&) {}
|
||||
};
|
||||
|
||||
template <std::size_t N, class T>
|
||||
template <size_t N, class T>
|
||||
CUTE_HOST_DEVICE constexpr T getv(EBO<N, T, true> const&)
|
||||
{ return {}; }
|
||||
|
||||
@@ -117,7 +99,7 @@ CUTE_HOST_DEVICE constexpr T getv(EBO<N, T, true> const&)
|
||||
// the "dynamic tuple leaf." Valid T here include int,
|
||||
// any other integral or floating-point type,
|
||||
// or any semiregular type for which std::is_empty_v<T> is false.
|
||||
template <std::size_t N, class T>
|
||||
template <size_t N, class T>
|
||||
struct EBO<N, T, false>
|
||||
{
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -130,15 +112,15 @@ struct EBO<N, T, false>
|
||||
T t_;
|
||||
};
|
||||
|
||||
template <std::size_t N, class T>
|
||||
template <size_t N, class T>
|
||||
CUTE_HOST_DEVICE constexpr T const& getv(EBO<N, T, false> const& x)
|
||||
{ return x.t_; }
|
||||
|
||||
template <std::size_t N, class T>
|
||||
template <size_t N, class T>
|
||||
CUTE_HOST_DEVICE constexpr T& getv(EBO<N, T, false>& x)
|
||||
{ return x.t_; }
|
||||
|
||||
template <std::size_t N, class T>
|
||||
template <size_t N, class T>
|
||||
CUTE_HOST_DEVICE constexpr T&& getv(EBO<N, T, false>&& x)
|
||||
{ return static_cast<T&&>(x.t_); }
|
||||
|
||||
@@ -152,8 +134,8 @@ struct TupleBase;
|
||||
// compile-time integer values in a single type.
|
||||
// We only ever use index_sequence<0, 1, ..., sizeof...(T)> in practice,
|
||||
// as the type alias TupleBase below indicates.
|
||||
template <std::size_t... I, class... T>
|
||||
struct TupleBase<std::index_sequence<I...>, T...>
|
||||
template <size_t... I, class... T>
|
||||
struct TupleBase<index_sequence<I...>, T...>
|
||||
: EBO<I,T>...
|
||||
{
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -166,39 +148,50 @@ struct TupleBase<std::index_sequence<I...>, T...>
|
||||
|
||||
template <class... U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
TupleBase(TupleBase<std::index_sequence<I...>, U...> const& u)
|
||||
TupleBase(TupleBase<index_sequence<I...>, U...> const& u)
|
||||
: EBO<I,T>(getv(static_cast<EBO<I,U> const&>(u)))... {}
|
||||
};
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
// make_index_sequence<K> returns index_sequence<0, 1, ..., K-1>.
|
||||
template <class... T>
|
||||
using TupleBase = detail::TupleBase<std::make_index_sequence<sizeof...(T)>, T...>;
|
||||
// Attempting to use the following commented-out alias
|
||||
// in the declaration of `struct tuple` causes MSVC 2022 build errors.
|
||||
//
|
||||
//template <class... T>
|
||||
//using TupleBase = detail::TupleBase<make_index_sequence<sizeof...(T)>, T...>;
|
||||
|
||||
// This is the actual cute::tuple class.
|
||||
// The storage (if any) lives in TupleBase's EBO base classes.
|
||||
//
|
||||
// Inheriting from the above alias TupleBase
|
||||
// causes MSVC 2022 build errors when assigning one tuple to another:
|
||||
//
|
||||
// illegal member initialization:
|
||||
// 'TupleBase< /* template arguments */ >' is not a base or member
|
||||
//
|
||||
// Not using the alias or any kind of alias fixed the errors.
|
||||
// In summary: this is verbose as a work-around for MSVC build errors.
|
||||
template <class... T>
|
||||
struct tuple : TupleBase<T...>
|
||||
struct tuple : detail::TupleBase<make_index_sequence<sizeof...(T)>, T...>
|
||||
{
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
tuple() {}
|
||||
|
||||
template <class... U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
tuple(U const&... u) : TupleBase<T...>(u...) {}
|
||||
tuple(U const&... u) : detail::TupleBase<make_index_sequence<sizeof...(T)>, T...>(u...) {}
|
||||
|
||||
template <class... U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
tuple(tuple<U...> const& u)
|
||||
: TupleBase<T...>(static_cast<TupleBase<U...> const&>(u)) {}
|
||||
: detail::TupleBase<make_index_sequence<sizeof...(T)>, T...>(static_cast<detail::TupleBase<make_index_sequence<sizeof...(U)>, U...> const&>(u)) {}
|
||||
};
|
||||
|
||||
//
|
||||
// get for cute::tuple (just like std::get for std::tuple)
|
||||
//
|
||||
|
||||
template <std::size_t I, class... T>
|
||||
template <size_t I, class... T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
get(tuple<T...> const& t) noexcept
|
||||
@@ -207,7 +200,7 @@ get(tuple<T...> const& t) noexcept
|
||||
return detail::getv<I>(t);
|
||||
}
|
||||
|
||||
template <std::size_t I, class... T>
|
||||
template <size_t I, class... T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
get(tuple<T...>& t) noexcept
|
||||
@@ -216,7 +209,7 @@ get(tuple<T...>& t) noexcept
|
||||
return detail::getv<I>(t);
|
||||
}
|
||||
|
||||
template <std::size_t I, class... T>
|
||||
template <size_t I, class... T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
get(tuple<T...>&& t) noexcept
|
||||
@@ -226,21 +219,19 @@ get(tuple<T...>&& t) noexcept
|
||||
}
|
||||
|
||||
//
|
||||
// Custom is_tuple trait simply checks the existence of std::tuple_size
|
||||
// Custom is_tuple trait simply checks the existence of tuple_size
|
||||
// and assumes std::get<I>(.), std::tuple_element<I,.>
|
||||
//
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
std::integral_constant<bool, std::tuple_size<T>::value >= 0> has_tuple_size(int);
|
||||
|
||||
template <class T>
|
||||
std::false_type has_tuple_size(...);
|
||||
auto has_tuple_size( T*) -> integral_constant<bool, 0 <= tuple_size<T>::value>;
|
||||
auto has_tuple_size(...) -> false_type;
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class T>
|
||||
struct is_tuple : decltype(detail::has_tuple_size<T>(0)) {};
|
||||
struct is_tuple : decltype(detail::has_tuple_size((T*)0)) {};
|
||||
|
||||
//
|
||||
// make_tuple (value-based implementation)
|
||||
@@ -265,11 +256,11 @@ make_tuple(T const&... t)
|
||||
namespace detail {
|
||||
|
||||
template <class T0, class T1,
|
||||
std::size_t... I0, std::size_t... I1>
|
||||
size_t... I0, size_t... I1>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1,
|
||||
std::index_sequence<I0...>, std::index_sequence<I1...>)
|
||||
index_sequence<I0...>, index_sequence<I1...>)
|
||||
{
|
||||
return cute::make_tuple(get<I0>(t0)..., get<I1>(t1)...);
|
||||
}
|
||||
@@ -298,8 +289,8 @@ auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1)
|
||||
{
|
||||
return detail::tuple_cat(t0, t1,
|
||||
std::make_index_sequence<std::tuple_size<T0>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T1>::value>{});
|
||||
make_index_sequence<tuple_size<T0>::value>{},
|
||||
make_index_sequence<tuple_size<T1>::value>{});
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2, class... Ts>
|
||||
@@ -317,41 +308,41 @@ tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2, Ts const&... ts)
|
||||
namespace detail {
|
||||
|
||||
template <class T0, class T1,
|
||||
std::size_t... I0, std::size_t... I1>
|
||||
size_t... I0, size_t... I1>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1,
|
||||
std::index_sequence<I0...>, std::index_sequence<I1...>)
|
||||
index_sequence<I0...>, index_sequence<I1...>)
|
||||
{
|
||||
return cute::make_tuple(get<I0>(t0)..., get<I1>(t1)...);
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2,
|
||||
std::size_t... I0, std::size_t... I1, std::size_t... I2>
|
||||
size_t... I0, size_t... I1, size_t... I2>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2,
|
||||
std::index_sequence<I0...>, std::index_sequence<I1...>, std::index_sequence<I2...>)
|
||||
index_sequence<I0...>, index_sequence<I1...>, index_sequence<I2...>)
|
||||
{
|
||||
return cute::make_tuple(get<I0>(t0)..., get<I1>(t1)..., get<I2>(t2)...);
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2, class T3,
|
||||
std::size_t... I0, std::size_t... I1, std::size_t... I2, std::size_t... I3>
|
||||
size_t... I0, size_t... I1, size_t... I2, size_t... I3>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2, T3 const& t3,
|
||||
std::index_sequence<I0...>, std::index_sequence<I1...>, std::index_sequence<I2...>, std::index_sequence<I3...>)
|
||||
index_sequence<I0...>, index_sequence<I1...>, index_sequence<I2...>, index_sequence<I3...>)
|
||||
{
|
||||
return cute::make_tuple(get<I0>(t0)..., get<I1>(t1)..., get<I2>(t2)..., get<I3>(t3)...);
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2, class T3, class T4,
|
||||
std::size_t... I0, std::size_t... I1, std::size_t... I2, std::size_t... I3, std::size_t... I4>
|
||||
size_t... I0, size_t... I1, size_t... I2, size_t... I3, size_t... I4>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2, T3 const& t3, T4 const& t4,
|
||||
std::index_sequence<I0...>, std::index_sequence<I1...>, std::index_sequence<I2...>, std::index_sequence<I3...>, std::index_sequence<I4...>)
|
||||
index_sequence<I0...>, index_sequence<I1...>, index_sequence<I2...>, index_sequence<I3...>, index_sequence<I4...>)
|
||||
{
|
||||
return cute::make_tuple(get<I0>(t0)..., get<I1>(t1)..., get<I2>(t2)..., get<I3>(t3)..., get<I4>(t4)...);
|
||||
}
|
||||
@@ -380,8 +371,8 @@ auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1)
|
||||
{
|
||||
return detail::tuple_cat(t0, t1,
|
||||
std::make_index_sequence<std::tuple_size<T0>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T1>::value>{});
|
||||
make_index_sequence<tuple_size<T0>::value>{},
|
||||
make_index_sequence<tuple_size<T1>::value>{});
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2>
|
||||
@@ -390,9 +381,9 @@ auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2)
|
||||
{
|
||||
return detail::tuple_cat(t0, t1, t2,
|
||||
std::make_index_sequence<std::tuple_size<T0>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T1>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T2>::value>{});
|
||||
make_index_sequence<tuple_size<T0>::value>{},
|
||||
make_index_sequence<tuple_size<T1>::value>{},
|
||||
make_index_sequence<tuple_size<T2>::value>{});
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2, class T3>
|
||||
@@ -401,10 +392,10 @@ auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2, T3 const& t3)
|
||||
{
|
||||
return detail::tuple_cat(t0, t1, t2, t3,
|
||||
std::make_index_sequence<std::tuple_size<T0>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T1>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T2>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T3>::value>{});
|
||||
make_index_sequence<tuple_size<T0>::value>{},
|
||||
make_index_sequence<tuple_size<T1>::value>{},
|
||||
make_index_sequence<tuple_size<T2>::value>{},
|
||||
make_index_sequence<tuple_size<T3>::value>{});
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2, class T3, class T4>
|
||||
@@ -413,11 +404,11 @@ auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2, T3 const& t3, T4 const& t4)
|
||||
{
|
||||
return detail::tuple_cat(t0, t1, t2, t3, t4,
|
||||
std::make_index_sequence<std::tuple_size<T0>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T1>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T2>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T3>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T4>::value>{});
|
||||
make_index_sequence<tuple_size<T0>::value>{},
|
||||
make_index_sequence<tuple_size<T1>::value>{},
|
||||
make_index_sequence<tuple_size<T2>::value>{},
|
||||
make_index_sequence<tuple_size<T3>::value>{},
|
||||
make_index_sequence<tuple_size<T4>::value>{});
|
||||
}
|
||||
|
||||
template <class T0, class T1, class T2, class T3, class T4, class T5, class... Ts>
|
||||
@@ -434,24 +425,24 @@ tuple_cat(T0 const& t0, T1 const& t1, T2 const& t2, T3 const& t3, T4 const& t4,
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <std::size_t... Ns>
|
||||
template <size_t... Ns>
|
||||
struct tuple_cat_helper
|
||||
{
|
||||
static constexpr cute::array<std::size_t,sizeof...(Ns)> ns = {Ns...};
|
||||
static constexpr cute::array<size_t,sizeof...(Ns)> ns = {Ns...};
|
||||
|
||||
static constexpr std::size_t total_size() {
|
||||
std::size_t sum = 0;
|
||||
for (std::size_t n : ns) sum += n;
|
||||
static constexpr size_t total_size() {
|
||||
size_t sum = 0;
|
||||
for (size_t n : ns) sum += n;
|
||||
return sum;
|
||||
}
|
||||
static constexpr std::size_t total_size_ = total_size();
|
||||
static constexpr size_t total_size_ = total_size();
|
||||
|
||||
static constexpr auto values() {
|
||||
cute::array<std::size_t[2],total_size_> outer_inner = {};
|
||||
cute::array<size_t[2],total_size_> outer_inner = {};
|
||||
|
||||
std::size_t idx = 0;
|
||||
for (std::size_t i = 0; i < ns.size(); ++i) {
|
||||
for (std::size_t j = 0; j < ns[i]; ++j, ++idx) {
|
||||
size_t idx = 0;
|
||||
for (size_t i = 0; i < ns.size(); ++i) {
|
||||
for (size_t j = 0; j < ns[i]; ++j, ++idx) {
|
||||
outer_inner[idx][0] = i;
|
||||
outer_inner[idx][1] = j;
|
||||
}
|
||||
@@ -460,23 +451,23 @@ struct tuple_cat_helper
|
||||
}
|
||||
static constexpr auto outer_inner_ = values();
|
||||
|
||||
using total_sequence = std::make_index_sequence<total_size_>;
|
||||
using total_sequence = make_index_sequence<total_size_>;
|
||||
};
|
||||
|
||||
template <class Helper, class Tuple, std::size_t... I>
|
||||
template <class Helper, class Tuple, size_t... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(Tuple const& t, std::index_sequence<I...>)
|
||||
tuple_cat(Tuple const& t, index_sequence<I...>)
|
||||
{
|
||||
return cute::make_tuple(get<Helper::outer_inner_[I][1]>(get<Helper::outer_inner_[I][0]>(t))...);
|
||||
}
|
||||
|
||||
template <class T0, class T1,
|
||||
std::size_t... I0, std::size_t... I1>
|
||||
size_t... I0, size_t... I1>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1,
|
||||
std::index_sequence<I0...>, std::index_sequence<I1...>)
|
||||
index_sequence<I0...>, index_sequence<I1...>)
|
||||
{
|
||||
return cute::make_tuple(get<I0>(t0)..., get<I1>(t1)...);
|
||||
}
|
||||
@@ -505,8 +496,8 @@ auto
|
||||
tuple_cat(T0 const& t0, T1 const& t1)
|
||||
{
|
||||
return detail::tuple_cat(t0, t1,
|
||||
std::make_index_sequence<std::tuple_size<T0>::value>{},
|
||||
std::make_index_sequence<std::tuple_size<T1>::value>{});
|
||||
make_index_sequence<tuple_size<T0>::value>{},
|
||||
make_index_sequence<tuple_size<T1>::value>{});
|
||||
}
|
||||
|
||||
template <class... Tuples>
|
||||
@@ -514,8 +505,8 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tuple_cat(Tuples const&... ts)
|
||||
{
|
||||
using Helper = detail::tuple_cat_helper<std::tuple_size<Tuples>::value...>;
|
||||
return detail::tuple_cat<Helper>(make_tuple(ts...), typename Helper::total_sequence{});
|
||||
using Helper = detail::tuple_cat_helper<tuple_size<Tuples>::value...>;
|
||||
return detail::tuple_cat<Helper>(cute::make_tuple(ts...), typename Helper::total_sequence{});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -525,14 +516,14 @@ tuple_cat(Tuples const&... ts)
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <std::size_t I, class TupleA, class TupleB>
|
||||
template <size_t I, class TupleA, class TupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
equal_impl(TupleA const& a, TupleB const& b)
|
||||
{
|
||||
if constexpr (I == std::tuple_size<TupleA>::value) {
|
||||
if constexpr (I == tuple_size<TupleA>::value) {
|
||||
return cute::true_type{}; // Terminal: TupleA is exhausted
|
||||
} else if constexpr (I == std::tuple_size<TupleB>::value) {
|
||||
} else if constexpr (I == tuple_size<TupleB>::value) {
|
||||
return cute::false_type{}; // Terminal: TupleA is not exhausted, TupleB is exhausted
|
||||
} else {
|
||||
return (get<I>(a) == get<I>(b)) && equal_impl<I+1>(a,b);
|
||||
@@ -596,24 +587,15 @@ operator!=(TupleT const& t, TupleU const& u)
|
||||
// That said, see int_tuple for more explicitly named common comparison ops.
|
||||
//
|
||||
|
||||
//
|
||||
// Shortcuts
|
||||
//
|
||||
|
||||
//using std::get;
|
||||
using std::tuple_size;
|
||||
using std::tuple_element;
|
||||
using std::tuple_element_t;
|
||||
|
||||
//
|
||||
// Display utilities
|
||||
//
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Tuple, std::size_t... Is>
|
||||
template <class Tuple, size_t... Is>
|
||||
CUTE_HOST_DEVICE void print_tuple(Tuple const& t,
|
||||
std::index_sequence<Is...>, char s = '(', char e = ')')
|
||||
index_sequence<Is...>, char s = '(', char e = ')')
|
||||
{
|
||||
using eat = int[];
|
||||
using cute::print;
|
||||
@@ -622,9 +604,10 @@ CUTE_HOST_DEVICE void print_tuple(Tuple const& t,
|
||||
(print(e), 0)};
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class Tuple, std::size_t... Is>
|
||||
CUTE_HOST std::ostream& print_tuple_os(std::ostream& os, Tuple const& t,
|
||||
std::index_sequence<Is...>, char s = '(', char e = ')')
|
||||
index_sequence<Is...>, char s = '(', char e = ')')
|
||||
{
|
||||
using eat = int[];
|
||||
(void) eat {(void(os << s), 0),
|
||||
@@ -632,6 +615,7 @@ CUTE_HOST std::ostream& print_tuple_os(std::ostream& os, Tuple const& t,
|
||||
(void(os << e), 0)};
|
||||
return os;
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
@@ -639,33 +623,80 @@ template <class Tuple,
|
||||
__CUTE_REQUIRES(is_tuple<Tuple>::value)>
|
||||
CUTE_HOST_DEVICE void print(Tuple const& t)
|
||||
{
|
||||
return detail::print_tuple(t, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
|
||||
return detail::print_tuple(t, make_index_sequence<tuple_size<Tuple>::value>{});
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class Tuple,
|
||||
__CUTE_REQUIRES(is_tuple<Tuple>::value)>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, Tuple const& t)
|
||||
{
|
||||
return detail::print_tuple_os(os, t, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
|
||||
return detail::print_tuple_os(os, t, make_index_sequence<tuple_size<Tuple>::value>{});
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
//
|
||||
// std:: compatability
|
||||
//
|
||||
|
||||
namespace std
|
||||
namespace CUTE_STL_NAMESPACE
|
||||
{
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<cute::tuple<T...>>
|
||||
: std::integral_constant<std::size_t, sizeof...(T)>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <std::size_t I, class... T>
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, cute::tuple<T...>>
|
||||
: std::tuple_element<I, std::tuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
} // end std
|
||||
template <class... T>
|
||||
struct tuple_size<const cute::tuple<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, const cute::tuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, const CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
} // end namespace CUTE_STL_NAMESPACE
|
||||
|
||||
//
|
||||
// std compatibility
|
||||
//
|
||||
|
||||
#ifdef CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
namespace std
|
||||
{
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
template <class... _Tp>
|
||||
struct tuple_size;
|
||||
|
||||
template<size_t _Ip, class... _Tp>
|
||||
struct tuple_element;
|
||||
#endif
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<cute::tuple<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, cute::tuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<const cute::tuple<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, const cute::tuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, const CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
} // end namepsace std
|
||||
#endif // CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
@@ -47,7 +49,12 @@ struct type_list {};
|
||||
// Specialize tuple-related functionality for cute::type_list
|
||||
//
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/tuple>
|
||||
#else
|
||||
#include <tuple>
|
||||
#endif
|
||||
|
||||
#include <cute/container/tuple.hpp>
|
||||
|
||||
namespace cute
|
||||
@@ -55,30 +62,75 @@ namespace cute
|
||||
|
||||
template <int I, class... T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
std::tuple_element_t<I, type_list<T...>>
|
||||
CUTE_STL_NAMESPACE::tuple_element_t<I, type_list<T...>>
|
||||
get(type_list<T...>&) noexcept {
|
||||
return {};
|
||||
}
|
||||
template <int I, class... T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
std::tuple_element_t<I, type_list<T...>>
|
||||
CUTE_STL_NAMESPACE::tuple_element_t<I, type_list<T...>>
|
||||
get(type_list<T...> const& t) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
namespace std
|
||||
namespace CUTE_STL_NAMESPACE
|
||||
{
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<cute::type_list<T...>>
|
||||
: std::integral_constant<std::size_t, sizeof...(T)>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <std::size_t I, class... T>
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, cute::type_list<T...>>
|
||||
: cute::type_c<typename std::tuple_element<I, std::tuple<T...>>::type>
|
||||
: cute::type_c<typename CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>::type>
|
||||
{};
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<const cute::type_list<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, const cute::type_list<T...>>
|
||||
: cute::type_c<typename CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>::type>
|
||||
{};
|
||||
|
||||
} // end namespace std
|
||||
|
||||
#ifdef CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
namespace std
|
||||
{
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
template <class... _Tp>
|
||||
struct tuple_size;
|
||||
|
||||
template<size_t _Ip, class... _Tp>
|
||||
struct tuple_element;
|
||||
#endif
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<cute::type_list<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, cute::type_list<T...>>
|
||||
: cute::type_c<typename CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>::type>
|
||||
{};
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<const cute::type_list<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, const cute::type_list<T...>>
|
||||
: cute::type_c<typename CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>::type>
|
||||
{};
|
||||
|
||||
} // end namespace std
|
||||
#endif // CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
|
||||
+75
-84
@@ -54,7 +54,7 @@ make_int_tuple(Ts const&... t)
|
||||
|
||||
/** if rank(int) == 1, then get<0>(int) should work too
|
||||
*/
|
||||
template <std::size_t I, class T, __CUTE_REQUIRES(is_integral<remove_cvref_t<T>>::value)>
|
||||
template <size_t I, class T, __CUTE_REQUIRES(is_integral<remove_cvref_t<T>>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
get(T&& t) noexcept
|
||||
@@ -65,7 +65,7 @@ get(T&& t) noexcept
|
||||
|
||||
/** Custom recursive get for anything that implements get<I>(.)
|
||||
*/
|
||||
template <std::size_t I0, std::size_t I1, std::size_t... Is, class Tuple>
|
||||
template <size_t I0, size_t I1, size_t... Is, class Tuple>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
get(Tuple&& t) noexcept
|
||||
@@ -96,7 +96,7 @@ rank(IntTuple const& t)
|
||||
}
|
||||
|
||||
template <class IntTuple>
|
||||
using rank_t = decltype(rank(std::declval<IntTuple>()));
|
||||
using rank_t = decltype(rank(declval<IntTuple>()));
|
||||
|
||||
template <class IntTuple>
|
||||
static constexpr int rank_v = rank_t<IntTuple>::value;
|
||||
@@ -196,7 +196,7 @@ depth(IntTuple const& t)
|
||||
}
|
||||
|
||||
template <class Tuple>
|
||||
using depth_t = decltype(depth(std::declval<Tuple>()));
|
||||
using depth_t = decltype(depth(declval<Tuple>()));
|
||||
|
||||
template <class Tuple>
|
||||
static constexpr int depth_v = depth_t<Tuple>::value;
|
||||
@@ -219,72 +219,6 @@ product(IntTuple const& a)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
// Work-around for some compiler versions (e.g., GCC 8.x)
|
||||
// incorrectly not being able to compile certain
|
||||
// legal C++ fold expressions inside generic lambdas.
|
||||
// Issue is known to exist in GCC 8.4 and GCC 8.5.
|
||||
// Work-around should be valid portable CUDA C++.
|
||||
#if ! defined(CUTE_FOLD_GENERIC_LAMBDA_WORKAROUND)
|
||||
# if defined(__GNUC__) && __GNUC__ == 8
|
||||
# define CUTE_FOLD_GENERIC_LAMBDA_WORKAROUND 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(CUTE_FOLD_GENERIC_LAMBDA_WORKAROUND)
|
||||
namespace impl {
|
||||
|
||||
template<int B, int E>
|
||||
struct SubrangeProductImpl {
|
||||
// GCC 8.4 accepts the fold expression here. If that doesn't work,
|
||||
// the other branch (recursive operator()) is known to build
|
||||
// with GCC 8.4 as well. The code does not enable recursion by default,
|
||||
// as fold expressions might be easier for compilers to optimize.
|
||||
#if 1
|
||||
template<class ... Args>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(Args const&... args) const
|
||||
{
|
||||
return (Int<1>{} * ... * product(args));
|
||||
}
|
||||
#else
|
||||
CUTE_HOST_DEVICE constexpr Int<1>
|
||||
operator()() const
|
||||
{
|
||||
return Int<1>{};
|
||||
}
|
||||
|
||||
template<class Head, class ... Tail>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(Head const& head, Tail const&... tail) const
|
||||
{
|
||||
return (*this)(tail...) * product<Head>(head);
|
||||
}
|
||||
#endif // 1
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
#endif // defined(CUTE_FOLD_GENERIC_LAMBDA_WORKAROUND)
|
||||
|
||||
// Product of a subrange
|
||||
template <int B, int E, class Tuple>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
product(Tuple const& a)
|
||||
{
|
||||
// Work around some compiler versions that do not accept
|
||||
// the generic lambda in the else branch, by replacing
|
||||
// the lambda with a function object. The work-around
|
||||
// is legal C++17, but the original code might be easier
|
||||
// for non-broken compilers to optimize, so it remains.
|
||||
#if defined(CUTE_FOLD_GENERIC_LAMBDA_WORKAROUND)
|
||||
impl::SubrangeProductImpl<B, E> function_object;
|
||||
return detail::apply(a, function_object, make_range<B, E>{});
|
||||
#else
|
||||
return detail::apply(a, [](auto const&... v){ return (Int<1>{} * ... * product(v)); }, make_range<B,E>{});
|
||||
#endif // defined(CUTE_FOLD_GENERIC_LAMBDA_WORKAROUND)
|
||||
}
|
||||
|
||||
template <class Tuple>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -309,7 +243,7 @@ size(IntTuple const& a)
|
||||
}
|
||||
|
||||
template <class IntTuple>
|
||||
static constexpr int size_v = decltype(size(std::declval<IntTuple>()))::value;
|
||||
static constexpr int size_v = decltype(size(declval<IntTuple>()))::value;
|
||||
|
||||
//
|
||||
// sum
|
||||
@@ -380,10 +314,10 @@ shape_div(IntTupleA const& a, IntTupleB const& b)
|
||||
if constexpr (is_tuple<IntTupleB>::value) { // tuple tuple
|
||||
static_assert(tuple_size<IntTupleA>::value == tuple_size<IntTupleB>::value, "Mismatched ranks");
|
||||
return transform(a, b, [](auto const& x, auto const& y) { return shape_div(x,y); });
|
||||
} else { // tuple int
|
||||
auto const [result, rest] = fold(a, make_tuple(make_tuple(), b),
|
||||
} else { // tuple int
|
||||
auto const [result, rest] = fold(a, cute::make_tuple(cute::make_tuple(), b),
|
||||
[] (auto const& init, auto const& ai) {
|
||||
return make_tuple(append(get<0>(init), shape_div(ai, get<1>(init))), shape_div(get<1>(init), ai));
|
||||
return cute::make_tuple(append(get<0>(init), shape_div(ai, get<1>(init))), shape_div(get<1>(init), ai));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -436,12 +370,40 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
congruent(IntTupleA const& a, IntTupleB const& b)
|
||||
{
|
||||
return bool_constant<std::is_same<decltype(repeat_like(shape(a),_0{})),
|
||||
return bool_constant<is_same<decltype(repeat_like(shape(a),_0{})),
|
||||
decltype(repeat_like(shape(b),_0{}))>::value>{};
|
||||
}
|
||||
|
||||
template <class A, class B>
|
||||
using is_congruent = decltype(congruent(std::declval<A>(), std::declval<B>()));
|
||||
using is_congruent = decltype(congruent(declval<A>(), declval<B>()));
|
||||
|
||||
/** Test if two IntTuple have the similar profiles up to Shape A (hierarchical rank division)
|
||||
*/
|
||||
template <class IntTupleA, class IntTupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
weakly_congruent(IntTupleA const& a, IntTupleB const& b)
|
||||
{
|
||||
if constexpr (is_tuple<IntTupleA>::value && is_tuple<IntTupleB>::value) {
|
||||
if constexpr (tuple_size<IntTupleA>::value != tuple_size<IntTupleB>::value) {
|
||||
return false_type{};
|
||||
} else {
|
||||
return transform_apply(a, b, [](auto const& x, auto const& y) { return weakly_congruent(x,y); },
|
||||
[](auto const&... z) { return (true_type{} && ... && z); });
|
||||
}
|
||||
} else if constexpr (is_integral<IntTupleA>::value) {
|
||||
return true_type{};
|
||||
} else if constexpr (is_integral<IntTupleB>::value) {
|
||||
return false_type{};
|
||||
} else {
|
||||
return weakly_congruent(shape(a), shape(b));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <class A, class B>
|
||||
using is_weakly_congruent = decltype(weakly_congruent(declval<A>(), declval<B>()));
|
||||
|
||||
/** Test if Shape B is compatible with Shape A:
|
||||
* Any coordinate into A can also be used as a coordinate into B
|
||||
@@ -471,7 +433,36 @@ compatible(IntTupleA const& a, IntTupleB const& b)
|
||||
}
|
||||
|
||||
template <class A, class B>
|
||||
using is_compatible = decltype(compatible(std::declval<A>(), std::declval<B>()));
|
||||
using is_compatible = decltype(compatible(declval<A>(), declval<B>()));
|
||||
|
||||
/** Test if Shape B is weakly compatible with Shape A:
|
||||
* Shape B divides Shape A at some level of refinement
|
||||
*/
|
||||
template <class IntTupleA, class IntTupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
weakly_compatible(IntTupleA const& a, IntTupleB const& b)
|
||||
{
|
||||
if constexpr (is_tuple<IntTupleA>::value && is_tuple<IntTupleB>::value) {
|
||||
if constexpr (tuple_size<IntTupleA>::value != tuple_size<IntTupleB>::value) {
|
||||
return false_type{};
|
||||
} else {
|
||||
return transform_apply(a, b, [](auto const& x, auto const& y) { return weakly_compatible(x,y); },
|
||||
[](auto const&... z) { return (true_type{} && ... && z); });
|
||||
}
|
||||
} else if constexpr (is_integral<IntTupleA>::value) {
|
||||
return a % size(b) == Int<0>{};
|
||||
} else if constexpr (is_integral<IntTupleB>::value) {
|
||||
return false_type{};
|
||||
} else {
|
||||
return weakly_compatible(shape(a), shape(b));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <class A, class B>
|
||||
using is_weakly_compatible = decltype(weakly_compatible(declval<A>(), declval<B>()));
|
||||
|
||||
/** Replace the elements of Tuple B that are paired with an Int<0> with an Int<1>
|
||||
*/
|
||||
@@ -565,13 +556,13 @@ Tuple
|
||||
make_int_tuple_from(Ts const&... ts)
|
||||
{
|
||||
Tuple result = Tuple{};
|
||||
fill_int_tuple_from(result, make_tuple(ts...));
|
||||
fill_int_tuple_from(result, cute::make_tuple(ts...));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Convert a tuple to a flat homogeneous array of type T
|
||||
* \code
|
||||
* auto tup = make_tuple(Int<1>{}, make_tuple(6,3,Int<3>{}),4,Int<2>{});
|
||||
* auto tup = cute::make_tuple(Int<1>{}, cute::make_tuple(6,3,Int<3>{}),4,Int<2>{});
|
||||
* cute::array<uint64_t,6> result = to_array<uint64_t>(tup); // [1,6,3,3,4,2]
|
||||
* \endcode
|
||||
*/
|
||||
@@ -625,7 +616,7 @@ elem_less(IntTupleA const& a, IntTupleB const& b);
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <std::size_t I, class TupleA, class TupleB>
|
||||
template <size_t I, class TupleA, class TupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
lex_less_impl(TupleA const& a, TupleB const& b)
|
||||
@@ -641,7 +632,7 @@ lex_less_impl(TupleA const& a, TupleB const& b)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <std::size_t I, class TupleA, class TupleB>
|
||||
template <size_t I, class TupleA, class TupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
colex_less_impl(TupleA const& a, TupleB const& b)
|
||||
@@ -651,15 +642,15 @@ colex_less_impl(TupleA const& a, TupleB const& b)
|
||||
} else if constexpr (I == tuple_size<TupleA>::value) {
|
||||
return cute::true_type{}; // Terminal: TupleA is exhausted, TupleB is not exhausted
|
||||
} else {
|
||||
constexpr std::size_t A = tuple_size<TupleA>::value - 1 - I;
|
||||
constexpr std::size_t B = tuple_size<TupleB>::value - 1 - I;
|
||||
constexpr size_t A = tuple_size<TupleA>::value - 1 - I;
|
||||
constexpr size_t B = tuple_size<TupleB>::value - 1 - I;
|
||||
return colex_less(get<A>(a), get<B>(b)) || (get<A>(a) == get<B>(b) && colex_less_impl<I+1>(a,b));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <std::size_t I, class TupleA, class TupleB>
|
||||
template <size_t I, class TupleA, class TupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
elem_less_impl(TupleA const& a, TupleB const& b)
|
||||
|
||||
+117
-68
@@ -80,33 +80,24 @@ make_coord(Ts const&... t) {
|
||||
}
|
||||
|
||||
|
||||
template <class LogicalShape,
|
||||
class LogicalStride = ColMajor<LogicalShape> >
|
||||
template <class Shape, class Stride = LayoutLeft::Apply<Shape> >
|
||||
struct Layout
|
||||
: private cute::tuple<LogicalShape, LogicalStride> // EBO for static layouts
|
||||
: private cute::tuple<Shape, Stride> // EBO for static layouts
|
||||
{
|
||||
// Avoid bad CTAD:
|
||||
// Layout smem = GMMA::Layout_MN_SW128_Atom<T>;
|
||||
// Should fail because smem is a ComposedLayout (SwizzleLayout) and not a Layout
|
||||
static_assert(is_integral<LogicalShape>::value || is_tuple<LogicalShape>::value);
|
||||
|
||||
// Expensive in compilation time...
|
||||
//static_assert(is_congruent<LogicalShape, LogicalStride>::value,
|
||||
// "Shape and Stride must have the same hierarchical structure");
|
||||
//static_assert(is_integral<LogicalShape>::value || is_tuple<LogicalShape>::value);
|
||||
//static_assert(is_congruent<Shape, Stride>::value, "Shape and Stride must be congruent");
|
||||
|
||||
// NOTE: This defaults static Shapes/Strides correctly, but not dynamic
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Layout(LogicalShape const& logical_shape = {},
|
||||
LogicalStride const& logical_stride = {})
|
||||
: cute::tuple<LogicalShape, LogicalStride>(logical_shape, logical_stride)
|
||||
Layout(Shape const& shape = {}, Stride const& stride = {})
|
||||
: cute::tuple<Shape, Stride>(shape, stride)
|
||||
{}
|
||||
|
||||
//
|
||||
// Accessors
|
||||
//
|
||||
|
||||
static constexpr int rank = rank_v <LogicalShape>;
|
||||
static constexpr int rank = rank_v<Shape>;
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
@@ -124,28 +115,28 @@ struct Layout
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
shape() {
|
||||
return get<0,I...>(static_cast<cute::tuple<LogicalShape, LogicalStride>&>(*this));
|
||||
return get<0,I...>(static_cast<cute::tuple<Shape, Stride>&>(*this));
|
||||
}
|
||||
|
||||
template <int... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
shape() const {
|
||||
return get<0,I...>(static_cast<cute::tuple<LogicalShape, LogicalStride> const&>(*this));
|
||||
return get<0,I...>(static_cast<cute::tuple<Shape, Stride> const&>(*this));
|
||||
}
|
||||
|
||||
template <int... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
stride() {
|
||||
return get<1,I...>(static_cast<cute::tuple<LogicalShape, LogicalStride>&>(*this));
|
||||
return get<1,I...>(static_cast<cute::tuple<Shape, Stride>&>(*this));
|
||||
}
|
||||
|
||||
template <int... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
stride() const {
|
||||
return get<1,I...>(static_cast<cute::tuple<LogicalShape, LogicalStride> const&>(*this));
|
||||
return get<1,I...>(static_cast<cute::tuple<Shape, Stride> const&>(*this));
|
||||
}
|
||||
|
||||
//
|
||||
@@ -314,7 +305,6 @@ struct Layout
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
template <class Layout>
|
||||
struct is_layout : false_type {};
|
||||
template <class Shape, class Stride>
|
||||
@@ -395,7 +385,15 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_layout_like(Layout<Shape,Stride> const& layout)
|
||||
{
|
||||
auto any_zero = any_of(layout.stride(), [](auto d) { return is_constant<0, decltype(d)>{}; });
|
||||
if constexpr (any_zero) {
|
||||
// If there are static-0 strides, then make a col-major layout that keeps those 0s
|
||||
return make_layout(layout.shape(),
|
||||
compact_col_major(filter_zeros(layout.stride(), layout.shape())));
|
||||
} else
|
||||
if constexpr (is_static<Shape>::value && is_static<Stride>::value) {
|
||||
// If the layout is fully static, then make a layout that follows the same order as the strides
|
||||
// Assumes the strides are unique
|
||||
return make_ordered_layout(layout.shape(), layout.stride());
|
||||
} else {
|
||||
return make_layout(layout.shape());
|
||||
@@ -404,17 +402,18 @@ make_layout_like(Layout<Shape,Stride> const& layout)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
//
|
||||
// Make a layout of the same shape,
|
||||
// with mode-0 being colmajor then following the the mode order in layout
|
||||
// with mode-0 being colmajor then following the mode order in layout
|
||||
//
|
||||
template <class Shape, class Stride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_fragment_like(Layout<Shape,Stride> const& layout)
|
||||
{
|
||||
auto shape = replace<0>(layout.shape(), size<0>(layout));
|
||||
auto order = replace<0>(layout.stride(), Int<0>{});
|
||||
if constexpr (is_static<decltype(shape)>::value && is_static<decltype(order)>::value) {
|
||||
return make_ordered_layout(shape, order);
|
||||
constexpr int R = Layout<Shape,Stride>::rank;
|
||||
if constexpr (R > 1 && is_static<Shape>::value && is_static<Stride>::value) {
|
||||
return tiled_product(make_layout(shape<0>(layout)), make_ordered_layout(take<1,R>(layout)));
|
||||
} else {
|
||||
return make_layout(layout.shape());
|
||||
}
|
||||
@@ -422,6 +421,19 @@ make_fragment_like(Layout<Shape,Stride> const& layout)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <class Shape,
|
||||
__CUTE_REQUIRES(is_tuple<Shape>::value || is_integral<Shape>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_fragment_like(Shape const& shape)
|
||||
{
|
||||
return make_layout(shape);
|
||||
}
|
||||
|
||||
//
|
||||
// Make an identity layout that maps a coordinate to itself
|
||||
//
|
||||
|
||||
template <class Shape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -434,7 +446,7 @@ make_identity_layout(Shape const& shape)
|
||||
// Operations to manipulate Layouts like a tuple of pairs
|
||||
//
|
||||
|
||||
template <std::size_t... Is, class Shape, class Stride>
|
||||
template <size_t... Is, class Shape, class Stride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get(Layout<Shape,Stride> const& layout)
|
||||
@@ -555,7 +567,7 @@ cosize(Layout<Shape,Stride> const& layout)
|
||||
}
|
||||
|
||||
template <class Layout>
|
||||
using cosize_t = decltype(cosize(std::declval<Layout>()));
|
||||
using cosize_t = decltype(cosize(declval<Layout>()));
|
||||
|
||||
template <class Layout>
|
||||
static constexpr int cosize_v = cosize_t<Layout>::value;
|
||||
@@ -840,7 +852,7 @@ composition(Layout<LShape,LStride> const& lhs,
|
||||
|
||||
// NOTE: Should only flatten once for efficiency
|
||||
auto flat_shape = flatten(lhs.shape());
|
||||
auto flat_stride = flatten(lhs.stride());
|
||||
[[maybe_unused]] auto flat_stride = flatten(lhs.stride());
|
||||
[[maybe_unused]] constexpr int R = rank(flat_shape);
|
||||
|
||||
if constexpr (is_constant<0, RStride>::value) {
|
||||
@@ -857,9 +869,9 @@ composition(Layout<LShape,LStride> const& lhs,
|
||||
auto result_shape_0 = take<0,R-1>(flat_shape);
|
||||
|
||||
// Mod out the rhs_shape from the lhs.shape()
|
||||
auto const [result_shape_1, rest_shape] = fold(result_shape_0, make_tuple(make_tuple(), rhs_shape),
|
||||
auto const [result_shape_1, rest_shape] = fold(result_shape_0, cute::make_tuple(cute::make_tuple(), rhs_shape),
|
||||
[] (auto const& init, auto const& si) {
|
||||
return make_tuple(append(get<0>(init), cute::min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
return cute::make_tuple(append(get<0>(init), cute::min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
});
|
||||
|
||||
// Jump into coalesce and append (rest_shape, get<R-1>(lhs.stride())
|
||||
@@ -871,18 +883,18 @@ composition(Layout<LShape,LStride> const& lhs,
|
||||
auto result_stride_0 = take<0,R-1>(flat_stride);
|
||||
|
||||
// Divide out the rhs_stride from the lhs.shape()
|
||||
auto const [result_shape_1, rest_stride] = fold(result_shape_0, make_tuple(make_tuple(), rhs_stride),
|
||||
auto const [result_shape_1, rest_stride] = fold(result_shape_0, cute::make_tuple(cute::make_tuple(), rhs_stride),
|
||||
[] (auto const& init, auto const& di) {
|
||||
return make_tuple(append(get<0>(init), shape_div(di, get<1>(init))), shape_div(get<1>(init), di));
|
||||
return cute::make_tuple(append(get<0>(init), shape_div(di, get<1>(init))), shape_div(get<1>(init), di));
|
||||
});
|
||||
|
||||
// Apply any lhs.shape() changes to the stride
|
||||
auto result_stride_1 = elem_scale(result_stride_0, shape_div(result_shape_0, result_shape_1));
|
||||
|
||||
// Mod out the rhs_shape from the lhs.shape()
|
||||
auto const [result_shape_2, rest_shape] = fold(result_shape_1, make_tuple(make_tuple(), rhs_shape),
|
||||
auto const [result_shape_2, rest_shape] = fold(result_shape_1, cute::make_tuple(cute::make_tuple(), rhs_shape),
|
||||
[] (auto const& init, auto const& si) {
|
||||
return make_tuple(append(get<0>(init), cute::min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
return cute::make_tuple(append(get<0>(init), cute::min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
});
|
||||
|
||||
// Jump into coalesce and append (rest_shape, rest_stride * get<R-1>(lhs.stride())
|
||||
@@ -936,37 +948,37 @@ composition(Layout<LShape,LStride> const& lhs,
|
||||
// @a result(i) != @a layout(j)
|
||||
//
|
||||
|
||||
namespace detail {
|
||||
|
||||
// @pre @a layout has been filtered (flattened and no stride-0 or size-1 modes).
|
||||
template <class Shape, class Stride, class CoSizeHi>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
complement(Layout<Shape,Stride> const& layout, CoSizeHi const& cosize_hi)
|
||||
complement(Shape const& shape, Stride const& stride, CoSizeHi const& cosize_hi)
|
||||
{
|
||||
// Remove the stride-0 modes, the size-1 modes, and flatten the layout
|
||||
auto flat_layout = filter(layout);
|
||||
|
||||
if constexpr (is_constant<0, decltype(flat_layout.stride())>::value) {
|
||||
// Special case for stride-0 layout
|
||||
if constexpr (is_constant<0, Stride>::value) {
|
||||
// Special case for irreducible rank-1 stride-0 layout
|
||||
return make_layout(cosize_hi);
|
||||
} else {
|
||||
// General case
|
||||
constexpr int R = decltype(rank(flat_layout))::value;
|
||||
static_assert(R == 1 || is_static<decltype(flat_layout.stride())>::value,
|
||||
constexpr int R = rank_v<Shape>;
|
||||
static_assert(R == 1 || is_static<Stride>::value,
|
||||
"Dynamic-stride complement only for rank-1 layouts");
|
||||
|
||||
// Should just be a sort and a fold...
|
||||
// Then we could even handle dynamic strides (but they would destroy all static strides)
|
||||
auto result = fold(make_seq<R-1>{},
|
||||
make_tuple(flat_layout.shape(),
|
||||
flat_layout.stride(),
|
||||
make_tuple(),
|
||||
make_tuple(Int<1>{})),
|
||||
cute::make_tuple(shape,
|
||||
stride,
|
||||
cute::make_tuple(),
|
||||
cute::make_tuple(Int<1>{})),
|
||||
[](auto const& init, auto i)
|
||||
{
|
||||
auto curr_stride = cute::min(get<1>(init));
|
||||
auto curr_idx = find(get<1>(init), curr_stride);
|
||||
auto curr_shape = get<curr_idx>(get<0>(init));
|
||||
|
||||
return make_tuple(remove<curr_idx>(get<0>(init)), // Remove the curr shape
|
||||
return cute::make_tuple(remove<curr_idx>(get<0>(init)), // Remove the curr shape
|
||||
remove<curr_idx>(get<1>(init)), // Remove the curr stride
|
||||
append(get<2>(init), curr_stride / get<3,i>(init)), // new shape = curr_stride / last_stride
|
||||
append(get<3>(init), curr_shape * curr_stride)); // new stride = curr_shape * curr_stride
|
||||
@@ -986,12 +998,25 @@ complement(Layout<Shape,Stride> const& layout, CoSizeHi const& cosize_hi)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class Shape, class Stride, class CoSizeHi>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
complement(Layout<Shape,Stride> const& layout, CoSizeHi const& cosize_hi)
|
||||
{
|
||||
static_assert(cute::is_integral<CoSizeHi>::value, "Expected integral codomain size in complement.");
|
||||
auto filter_layout = filter(layout);
|
||||
return detail::complement(filter_layout.shape(), filter_layout.stride(), cosize_hi);
|
||||
}
|
||||
|
||||
template <class Shape, class Stride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
complement(Layout<Shape,Stride> const& layout)
|
||||
{
|
||||
return complement(layout, cosize(layout));
|
||||
auto filter_layout = filter(layout);
|
||||
return detail::complement(filter_layout.shape(), filter_layout.stride(), cosize(filter_layout));
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1067,7 +1092,7 @@ right_inverse(Underscore const& _)
|
||||
//
|
||||
// Build the left-inverse of a layout
|
||||
// @pre is_static<Layout>
|
||||
// @pre not has_int0<Layout> // @a layout has no 0-strides (is injective)
|
||||
// @pre @a layout is an injective function
|
||||
// @result A layout @a result such that
|
||||
// @a result(@a layout(i)) == i for all i < size(@a layout)
|
||||
// @result A layout @a result such that
|
||||
@@ -1090,10 +1115,46 @@ left_inverse(Underscore const& _)
|
||||
}
|
||||
|
||||
//
|
||||
// Max Common Vector
|
||||
// Max Common Layout
|
||||
//
|
||||
|
||||
/* Return Int<N> such that N is the maximum number of continguous elements
|
||||
/* Return a layout that points to the maximum number of contiguous elements
|
||||
* that logically correspond in the layouts of @a a and @a b. This is,
|
||||
* the elements that could reasonably be "vectorized" in the layouts.
|
||||
*
|
||||
* @returns Layout R
|
||||
* @post For all 0 <= i < size(R), a(R(i)) == i && b(R(i)) == i
|
||||
*/
|
||||
template <class ShapeA, class StrideA,
|
||||
class ShapeB, class StrideB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
max_common_layout(Layout<ShapeA,StrideA> const& a,
|
||||
Layout<ShapeB,StrideB> const& b)
|
||||
{
|
||||
if constexpr (is_static<ShapeA>::value && is_static<StrideA>::value &&
|
||||
is_static<ShapeB>::value && is_static<StrideB>::value)
|
||||
{
|
||||
Layout inv_b = right_inverse(b);
|
||||
Layout common = coalesce(composition(a, inv_b));
|
||||
|
||||
if constexpr (is_constant<1, decltype(stride<0>(common))>::value) {
|
||||
// Truncate to the size of the contiguous vector (static stride-1 mode)
|
||||
return composition(inv_b, layout<0>(common));
|
||||
} else {
|
||||
return Layout<_1,_0>{};
|
||||
}
|
||||
} else {
|
||||
// CASE: One of the layouts is dynamic, can't prove alignment+vectorization is valid
|
||||
// NOTE: Could weaken if we assume dynamic shapes/strides obey alignment requirements
|
||||
// (i.e. are large and multiples of the vector)
|
||||
return Layout<_1,_0>{};
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
/* Return Int<N> such that N is the maximum number of contiguous elements
|
||||
* that logically correspond in the layouts of @a a and @a b. This is,
|
||||
* the number of elements that could reasonably be "vectorized" in the layouts.
|
||||
*
|
||||
@@ -1104,24 +1165,10 @@ template <class ShapeA, class StrideA,
|
||||
class ShapeB, class StrideB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
max_common_vector(Layout<ShapeA,StrideA> const& a, Layout<ShapeB,StrideB> const& b)
|
||||
max_common_vector(Layout<ShapeA,StrideA> const& a,
|
||||
Layout<ShapeB,StrideB> const& b)
|
||||
{
|
||||
if constexpr (is_static<Layout<ShapeA,StrideA>>::value &&
|
||||
is_static<Layout<ShapeB,StrideB>>::value)
|
||||
{
|
||||
auto result = coalesce(composition(a, right_inverse(b)));
|
||||
|
||||
if constexpr (is_constant<1, decltype(stride<0>(result))>::value) {
|
||||
return shape<0>(result);
|
||||
} else {
|
||||
return Int<1>{};
|
||||
}
|
||||
} else {
|
||||
// Dynamic case NOTE: could weaken if we assume dynamic strides are large and multiples of the vector
|
||||
return Int<1>{};
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
return size(max_common_layout(a, b));
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1450,11 +1497,13 @@ CUTE_HOST_DEVICE void print(Layout<Shape,Stride> const& layout)
|
||||
print(layout.shape()); print(":"); print(layout.stride());
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class Shape, class Stride>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, Layout<Shape,Stride> const& layout)
|
||||
{
|
||||
return os << shape(layout) << ":" << stride(layout);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Generic 2D Layout to console table
|
||||
template <class Layout>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
#include <cute/algorithm/functional.hpp>
|
||||
#include <cute/algorithm/tuple_algorithms.hpp>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
@@ -361,28 +362,75 @@ operator+(ScaledBasis<T,N> const& t, constant<U,0>) {
|
||||
|
||||
template <class T, int N>
|
||||
CUTE_HOST_DEVICE void print(ScaledBasis<T,N> const& e) {
|
||||
printf("%d:", N); print(e.value());
|
||||
print(e.value()); printf("@%d", N);
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class T, int N>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, ScaledBasis<T,N> const& e) {
|
||||
return os << N << ":" << e.value();
|
||||
return os << e.value() << "@" << N;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
|
||||
namespace std
|
||||
namespace CUTE_STL_NAMESPACE
|
||||
{
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<cute::ArithmeticTuple<T...>>
|
||||
: std::integral_constant<std::size_t, sizeof...(T)>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <std::size_t I, class... T>
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, cute::ArithmeticTuple<T...>>
|
||||
: std::tuple_element<I, std::tuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<const cute::ArithmeticTuple<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, const cute::ArithmeticTuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, const CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
} // end namespace CUTE_STL_NAMESPACE
|
||||
|
||||
#ifdef CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
namespace std
|
||||
{
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
template <class... _Tp>
|
||||
struct tuple_size;
|
||||
|
||||
template<size_t _Ip, class... _Tp>
|
||||
struct tuple_element;
|
||||
#endif
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<cute::ArithmeticTuple<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, cute::ArithmeticTuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
template <class... T>
|
||||
struct tuple_size<const cute::ArithmeticTuple<T...>>
|
||||
: cute::integral_constant<size_t, sizeof...(T)>
|
||||
{};
|
||||
|
||||
template <size_t I, class... T>
|
||||
struct tuple_element<I, const cute::ArithmeticTuple<T...>>
|
||||
: CUTE_STL_NAMESPACE::tuple_element<I, const CUTE_STL_NAMESPACE::tuple<T...>>
|
||||
{};
|
||||
|
||||
} // end namespace std
|
||||
#endif // CUTE_STL_NAMESPACE_IS_CUDA_STD
|
||||
|
||||
@@ -43,9 +43,11 @@ using cutlass::bfloat16_t;
|
||||
// Display utilities
|
||||
//
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, bfloat16_t const& v)
|
||||
{
|
||||
return os << float(v);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
//#if defined(__CUDA_ARCH__)
|
||||
//# include <cuda/std/complex>
|
||||
@@ -38,13 +38,37 @@
|
||||
//# include <complex>
|
||||
//#endif
|
||||
|
||||
// With CUDA 11.4, builds show spurious "-Wconversion" warnings
|
||||
// on line 656 of thrust/detail/type_traits.h.
|
||||
// These pragmas suppress the warnings.
|
||||
// Suppress warnings for code in Thrust headers.
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
// We check for MSVC first, because MSVC also defines __GNUC__.
|
||||
// It's common for non-GCC compilers that emulate GCC's behavior
|
||||
// to define __GNUC__.
|
||||
//
|
||||
// thrust/complex.h triggers MSVC's warning on conversion
|
||||
// from double to float (or const float) ("possible loss of data").
|
||||
// MSVC treats this as an error by default (at least with
|
||||
// CUTLASS's default CMake configuration).
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4244 )
|
||||
#elif defined(__GNUC__)
|
||||
// With GCC + CUDA 11.4, builds show spurious "-Wconversion"
|
||||
// warnings on line 656 of thrust/detail/type_traits.h.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wconversion"
|
||||
#endif
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/complex>
|
||||
#else
|
||||
#include <thrust/complex.h>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning( pop )
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
@@ -62,7 +86,11 @@ namespace cute
|
||||
//template <class T>
|
||||
//using complex = thrust::complex<T>;
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
using cuda::std::complex;
|
||||
#else
|
||||
using thrust::complex;
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
CUTE_HOST_DEVICE
|
||||
@@ -147,6 +175,7 @@ struct is_complex<complex<T>> {
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Display utilities
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class T>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, complex<T> const& z)
|
||||
{
|
||||
@@ -159,5 +188,6 @@ CUTE_HOST std::ostream& operator<<(std::ostream& os, complex<T> const& z)
|
||||
return os << _r;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -46,10 +46,12 @@ namespace cute
|
||||
// Signed integers
|
||||
//
|
||||
|
||||
using int8_t = std::int8_t;
|
||||
using int16_t = std::int16_t;
|
||||
using int32_t = std::int32_t;
|
||||
using int64_t = std::int64_t;
|
||||
using int2_t = cute::int2b_t;
|
||||
using int4_t = cute::int4b_t;
|
||||
using int8_t = CUTE_STL_NAMESPACE::int8_t;
|
||||
using int16_t = CUTE_STL_NAMESPACE::int16_t;
|
||||
using int32_t = CUTE_STL_NAMESPACE::int32_t;
|
||||
using int64_t = CUTE_STL_NAMESPACE::int64_t;
|
||||
|
||||
template <int N> struct int_bit;
|
||||
template <> struct int_bit< 2> { using type = cute::int2b_t; };
|
||||
@@ -72,10 +74,14 @@ using int_byte_t = typename int_byte<N>::type;
|
||||
// Unsigned integers
|
||||
//
|
||||
|
||||
using uint8_t = std::uint8_t;
|
||||
using uint16_t = std::uint16_t;
|
||||
using uint32_t = std::uint32_t;
|
||||
using uint64_t = std::uint64_t;
|
||||
using uint1_t = cute::uint1b_t;
|
||||
using uint2_t = cute::uint2b_t;
|
||||
using uint4_t = cute::uint4b_t;
|
||||
using uint8_t = CUTE_STL_NAMESPACE::uint8_t;
|
||||
using uint16_t = CUTE_STL_NAMESPACE::uint16_t;
|
||||
using uint32_t = CUTE_STL_NAMESPACE::uint32_t;
|
||||
using uint64_t = CUTE_STL_NAMESPACE::uint64_t;
|
||||
using uint128_t = cute::uint128_t;
|
||||
|
||||
template <int N> struct uint_bit;
|
||||
template <> struct uint_bit< 1> { using type = cute::uint1b_t; };
|
||||
@@ -102,7 +108,7 @@ using uint_byte_t = typename uint_byte<N>::type;
|
||||
|
||||
template <class T>
|
||||
struct sizeof_bytes {
|
||||
static constexpr std::size_t value = sizeof(T);
|
||||
static constexpr size_t value = sizeof(T);
|
||||
};
|
||||
template <class T>
|
||||
static constexpr int sizeof_bytes_v = sizeof_bytes<T>::value;
|
||||
@@ -113,15 +119,15 @@ static constexpr int sizeof_bytes_v = sizeof_bytes<T>::value;
|
||||
|
||||
template <class T>
|
||||
struct sizeof_bits {
|
||||
static constexpr std::size_t value = sizeof(T) * 8;
|
||||
static constexpr size_t value = sizeof(T) * 8;
|
||||
};
|
||||
template <>
|
||||
struct sizeof_bits<bool> {
|
||||
static constexpr std::size_t value = 1;
|
||||
static constexpr size_t value = 1;
|
||||
};
|
||||
template <int Bits, bool Signed>
|
||||
struct sizeof_bits<integer_subbyte<Bits,Signed>> {
|
||||
static constexpr std::size_t value = Bits;
|
||||
static constexpr size_t value = Bits;
|
||||
};
|
||||
template <class T>
|
||||
static constexpr int sizeof_bits_v = sizeof_bits<T>::value;
|
||||
|
||||
@@ -30,34 +30,46 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <utility> // std::integer_sequence
|
||||
|
||||
#include <cute/config.hpp>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
using std::integer_sequence;
|
||||
using std::make_integer_sequence;
|
||||
using CUTE_STL_NAMESPACE::integer_sequence;
|
||||
using CUTE_STL_NAMESPACE::make_integer_sequence;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T, class S, T Begin>
|
||||
struct make_integer_range_impl;
|
||||
struct range_impl;
|
||||
|
||||
template <class T, T... N, T Begin>
|
||||
struct make_integer_range_impl<T, integer_sequence<T, N...>, Begin> {
|
||||
struct range_impl<T, integer_sequence<T, N...>, Begin> {
|
||||
using type = integer_sequence<T, N+Begin...>;
|
||||
};
|
||||
|
||||
template <class S>
|
||||
struct reverse_impl;
|
||||
|
||||
template <class T, T... N>
|
||||
struct reverse_impl<integer_sequence<T, N...>> {
|
||||
using type = integer_sequence<T, sizeof...(N)-1-N...>;
|
||||
};
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class T, T Begin, T End>
|
||||
using make_integer_range = typename detail::make_integer_range_impl<
|
||||
using make_integer_range = typename detail::range_impl<
|
||||
T,
|
||||
make_integer_sequence<T, (End-Begin > 0) ? (End-Begin) : 0>,
|
||||
Begin>::type;
|
||||
|
||||
template <class T, T N>
|
||||
using make_integer_sequence_reverse = typename detail::reverse_impl<
|
||||
make_integer_sequence<T, N>>::type;
|
||||
|
||||
//
|
||||
// Common aliases
|
||||
//
|
||||
@@ -70,19 +82,25 @@ using int_sequence = integer_sequence<int, Ints...>;
|
||||
template <int N>
|
||||
using make_int_sequence = make_integer_sequence<int, N>;
|
||||
|
||||
template <int N>
|
||||
using make_int_rsequence = make_integer_sequence_reverse<int, N>;
|
||||
|
||||
template <int Begin, int End>
|
||||
using make_int_range = make_integer_range<int, Begin, End>;
|
||||
|
||||
// index_sequence
|
||||
|
||||
template <std::size_t... Ints>
|
||||
using index_sequence = integer_sequence<std::size_t, Ints...>;
|
||||
template <size_t... Ints>
|
||||
using index_sequence = integer_sequence<size_t, Ints...>;
|
||||
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = make_integer_sequence<std::size_t, N>;
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
|
||||
template <std::size_t Begin, std::size_t End>
|
||||
using make_index_range = make_integer_range<std::size_t, Begin, End>;
|
||||
template <size_t N>
|
||||
using make_index_rsequence = make_integer_sequence_reverse<size_t, N>;
|
||||
|
||||
template <size_t Begin, size_t End>
|
||||
using make_index_range = make_integer_range<size_t, Begin, End>;
|
||||
|
||||
//
|
||||
// Shortcuts
|
||||
@@ -94,46 +112,40 @@ using seq = int_sequence<Ints...>;
|
||||
template <int N>
|
||||
using make_seq = make_int_sequence<N>;
|
||||
|
||||
template <int N>
|
||||
using make_rseq = make_int_rsequence<N>;
|
||||
|
||||
template <int Min, int Max>
|
||||
using make_range = make_int_range<Min, Max>;
|
||||
|
||||
template <class Tuple>
|
||||
using tuple_seq = make_seq<std::tuple_size<std::remove_reference_t<Tuple>>::value>;
|
||||
|
||||
} // end namespace cute
|
||||
using tuple_seq = make_seq<tuple_size<remove_cvref_t<Tuple>>::value>;
|
||||
|
||||
template <class Tuple>
|
||||
using tuple_rseq = make_rseq<tuple_size<remove_cvref_t<Tuple>>::value>;
|
||||
|
||||
//
|
||||
// Specialize tuple-related functionality for cute::integer_sequence
|
||||
// Specialize cute::tuple-traits for std::integer_sequence
|
||||
//
|
||||
|
||||
#include <tuple>
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
template <class T, T... Ints>
|
||||
struct tuple_size<integer_sequence<T, Ints...>>
|
||||
: cute::integral_constant<size_t, sizeof...(Ints)>
|
||||
{};
|
||||
|
||||
namespace cute
|
||||
template <size_t I, class T, T... Is>
|
||||
struct tuple_element<I, integer_sequence<T, Is...>>
|
||||
{
|
||||
constexpr static T idx[sizeof...(Is)] = {Is...};
|
||||
using type = cute::integral_constant<T, idx[I]>;
|
||||
};
|
||||
|
||||
template <std::size_t I, class T, T... Ints>
|
||||
template <size_t I, class T, T... Ints>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
std::tuple_element_t<I, integer_sequence<T, Ints...>>
|
||||
tuple_element_t<I, integer_sequence<T, Ints...>>
|
||||
get(integer_sequence<T, Ints...>) {
|
||||
static_assert(I < sizeof...(Ints), "Index out of range");
|
||||
return {};
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <class T, T... Ints>
|
||||
struct tuple_size<cute::integer_sequence<T, Ints...>>
|
||||
: std::integral_constant<std::size_t, sizeof...(Ints)>
|
||||
{};
|
||||
|
||||
template <std::size_t I, class T, T... Ints>
|
||||
struct tuple_element<I, cute::integer_sequence<T, Ints...>>
|
||||
: std::tuple_element<I, std::tuple<cute::integral_constant<T,Ints>...>>
|
||||
{};
|
||||
|
||||
} // end namespace std
|
||||
|
||||
@@ -53,7 +53,7 @@ struct integer_subbyte
|
||||
static_assert(Bits <= 8*sizeof(Storage), "Require a subbyte of bits in integer_subbyte");
|
||||
|
||||
/// External type
|
||||
using xint_t = typename std::conditional<Signed, int, unsigned>::type;
|
||||
using xint_t = typename conditional<Signed, int, unsigned>::type;
|
||||
|
||||
/// Bitmask for truncation from larger integers
|
||||
static constexpr Storage bits_mask_ = Storage((1 << Bits) - 1);
|
||||
@@ -166,7 +166,7 @@ using bin1_t = bool;
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace std {
|
||||
namespace CUTE_STL_NAMESPACE {
|
||||
|
||||
template <>
|
||||
struct numeric_limits<cute::uint1b_t> {
|
||||
@@ -230,4 +230,4 @@ struct numeric_limits<cute::uint4b_t> {
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace cute
|
||||
{
|
||||
|
||||
template <class T, T v>
|
||||
struct constant : std::integral_constant<T,v> {
|
||||
struct constant : CUTE_STL_NAMESPACE::integral_constant<T,v> {
|
||||
static constexpr T value = v;
|
||||
using value_type = T;
|
||||
using type = constant<T,v>;
|
||||
@@ -56,7 +56,7 @@ using bool_constant = constant<bool,b>;
|
||||
using true_type = bool_constant<true>;
|
||||
using false_type = bool_constant<false>;
|
||||
|
||||
//
|
||||
//
|
||||
// Traits
|
||||
//
|
||||
|
||||
@@ -64,14 +64,14 @@ using false_type = bool_constant<false>;
|
||||
// Use cute::is_integral<T> to match both built-in integral types AND constant<T,t>
|
||||
|
||||
template <class T>
|
||||
struct is_integral : bool_constant<std::is_integral<T>::value> {};
|
||||
struct is_integral : bool_constant<CUTE_STL_NAMESPACE::is_integral<T>::value> {};
|
||||
template <class T, T v>
|
||||
struct is_integral<constant<T,v>> : true_type {};
|
||||
|
||||
// is_static detects if an (abstract) value is defined completely by it's type (no members)
|
||||
|
||||
template <class T>
|
||||
struct is_static : bool_constant<std::is_empty<T>::value> {};
|
||||
struct is_static : bool_constant<is_empty<T>::value> {};
|
||||
|
||||
// is_constant detects if a type is a constant<T,v> and if v is equal to a value
|
||||
|
||||
@@ -95,45 +95,51 @@ struct is_constant<n, constant<T,v> &&> : bool_constant<v == n> {};
|
||||
template <int v>
|
||||
using Int = constant<int,v>;
|
||||
|
||||
using _m32 = Int<-32>;
|
||||
using _m24 = Int<-24>;
|
||||
using _m16 = Int<-16>;
|
||||
using _m12 = Int<-12>;
|
||||
using _m10 = Int<-10>;
|
||||
using _m9 = Int<-9>;
|
||||
using _m8 = Int<-8>;
|
||||
using _m7 = Int<-7>;
|
||||
using _m6 = Int<-6>;
|
||||
using _m5 = Int<-5>;
|
||||
using _m4 = Int<-4>;
|
||||
using _m3 = Int<-3>;
|
||||
using _m2 = Int<-2>;
|
||||
using _m1 = Int<-1>;
|
||||
using _0 = Int<0>;
|
||||
using _1 = Int<1>;
|
||||
using _2 = Int<2>;
|
||||
using _3 = Int<3>;
|
||||
using _4 = Int<4>;
|
||||
using _5 = Int<5>;
|
||||
using _6 = Int<6>;
|
||||
using _7 = Int<7>;
|
||||
using _8 = Int<8>;
|
||||
using _9 = Int<9>;
|
||||
using _10 = Int<10>;
|
||||
using _12 = Int<12>;
|
||||
using _16 = Int<16>;
|
||||
using _24 = Int<24>;
|
||||
using _32 = Int<32>;
|
||||
using _64 = Int<64>;
|
||||
using _96 = Int<96>;
|
||||
using _128 = Int<128>;
|
||||
using _192 = Int<192>;
|
||||
using _256 = Int<256>;
|
||||
using _512 = Int<512>;
|
||||
using _1024 = Int<1024>;
|
||||
using _2048 = Int<2048>;
|
||||
using _4096 = Int<4096>;
|
||||
using _8192 = Int<8192>;
|
||||
using _m32 = Int<-32>;
|
||||
using _m24 = Int<-24>;
|
||||
using _m16 = Int<-16>;
|
||||
using _m12 = Int<-12>;
|
||||
using _m10 = Int<-10>;
|
||||
using _m9 = Int<-9>;
|
||||
using _m8 = Int<-8>;
|
||||
using _m7 = Int<-7>;
|
||||
using _m6 = Int<-6>;
|
||||
using _m5 = Int<-5>;
|
||||
using _m4 = Int<-4>;
|
||||
using _m3 = Int<-3>;
|
||||
using _m2 = Int<-2>;
|
||||
using _m1 = Int<-1>;
|
||||
using _0 = Int<0>;
|
||||
using _1 = Int<1>;
|
||||
using _2 = Int<2>;
|
||||
using _3 = Int<3>;
|
||||
using _4 = Int<4>;
|
||||
using _5 = Int<5>;
|
||||
using _6 = Int<6>;
|
||||
using _7 = Int<7>;
|
||||
using _8 = Int<8>;
|
||||
using _9 = Int<9>;
|
||||
using _10 = Int<10>;
|
||||
using _12 = Int<12>;
|
||||
using _16 = Int<16>;
|
||||
using _24 = Int<24>;
|
||||
using _32 = Int<32>;
|
||||
using _64 = Int<64>;
|
||||
using _96 = Int<96>;
|
||||
using _128 = Int<128>;
|
||||
using _192 = Int<192>;
|
||||
using _256 = Int<256>;
|
||||
using _512 = Int<512>;
|
||||
using _1024 = Int<1024>;
|
||||
using _2048 = Int<2048>;
|
||||
using _4096 = Int<4096>;
|
||||
using _8192 = Int<8192>;
|
||||
using _16384 = Int<16384>;
|
||||
using _32768 = Int<32768>;
|
||||
using _65536 = Int<65536>;
|
||||
using _131072 = Int<131072>;
|
||||
using _262144 = Int<262144>;
|
||||
using _524288 = Int<524288>;
|
||||
|
||||
/***************/
|
||||
/** Operators **/
|
||||
@@ -198,7 +204,7 @@ CUTE_BINARY_OP(<=);
|
||||
//
|
||||
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator*(constant<T, 0>, U) {
|
||||
@@ -206,7 +212,7 @@ operator*(constant<T, 0>, U) {
|
||||
}
|
||||
|
||||
template <class U, class T,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator*(U, constant<T, 0>) {
|
||||
@@ -214,7 +220,7 @@ operator*(U, constant<T, 0>) {
|
||||
}
|
||||
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator/(constant<T, 0>, U) {
|
||||
@@ -222,7 +228,7 @@ operator/(constant<T, 0>, U) {
|
||||
}
|
||||
|
||||
template <class U, class T,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator%(U, constant<T, 1>) {
|
||||
@@ -230,7 +236,7 @@ operator%(U, constant<T, 1>) {
|
||||
}
|
||||
|
||||
template <class U, class T,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator%(U, constant<T,-1>) {
|
||||
@@ -238,7 +244,7 @@ operator%(U, constant<T,-1>) {
|
||||
}
|
||||
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator%(constant<T, 0>, U) {
|
||||
@@ -246,7 +252,7 @@ operator%(constant<T, 0>, U) {
|
||||
}
|
||||
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator&(constant<T, 0>, U) {
|
||||
@@ -254,7 +260,7 @@ operator&(constant<T, 0>, U) {
|
||||
}
|
||||
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<T, 0>
|
||||
operator&(U, constant<T, 0>) {
|
||||
@@ -262,7 +268,7 @@ operator&(U, constant<T, 0>) {
|
||||
}
|
||||
|
||||
template <class T, T t, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value && !bool(t))>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value && !bool(t))>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<bool, false>
|
||||
operator&&(constant<T, t>, U) {
|
||||
@@ -270,7 +276,7 @@ operator&&(constant<T, t>, U) {
|
||||
}
|
||||
|
||||
template <class T, T t, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value && !bool(t))>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value && !bool(t))>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<bool, false>
|
||||
operator&&(U, constant<T, t>) {
|
||||
@@ -278,7 +284,7 @@ operator&&(U, constant<T, t>) {
|
||||
}
|
||||
|
||||
template <class T, class U, T t,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value && bool(t))>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value && bool(t))>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<bool, true>
|
||||
operator||(constant<T, t>, U) {
|
||||
@@ -286,7 +292,7 @@ operator||(constant<T, t>, U) {
|
||||
}
|
||||
|
||||
template <class T, class U, T t,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value && bool(t))>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value && bool(t))>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
constant<bool, true>
|
||||
operator||(U, constant<T, t>) {
|
||||
@@ -314,7 +320,7 @@ operator||(U, constant<T, t>) {
|
||||
} \
|
||||
\
|
||||
template <class T, T t, class U, \
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)> \
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)> \
|
||||
CUTE_HOST_DEVICE constexpr \
|
||||
auto \
|
||||
OP (constant<T,t>, U u) { \
|
||||
@@ -322,7 +328,7 @@ operator||(U, constant<T, t>) {
|
||||
} \
|
||||
\
|
||||
template <class T, class U, U u, \
|
||||
__CUTE_REQUIRES(std::is_integral<T>::value)> \
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<T>::value)> \
|
||||
CUTE_HOST_DEVICE constexpr \
|
||||
auto \
|
||||
OP (T t, constant<U,u>) { \
|
||||
@@ -356,7 +362,7 @@ safe_div(constant<T, t>, constant<U, u>) {
|
||||
}
|
||||
|
||||
template <class T, T t, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
safe_div(constant<T, t>, U u) {
|
||||
@@ -364,7 +370,7 @@ safe_div(constant<T, t>, U u) {
|
||||
}
|
||||
|
||||
template <class T, class U, U u,
|
||||
__CUTE_REQUIRES(std::is_integral<T>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<T>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
safe_div(T t, constant<U, u>) {
|
||||
@@ -376,7 +382,7 @@ safe_div(T t, constant<U, u>) {
|
||||
template <class TrueType, class FalseType>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
conditional_return(std::true_type, TrueType&& t, FalseType&&) {
|
||||
conditional_return(true_type, TrueType&& t, FalseType&&) {
|
||||
return static_cast<TrueType&&>(t);
|
||||
}
|
||||
|
||||
@@ -385,7 +391,7 @@ conditional_return(std::true_type, TrueType&& t, FalseType&&) {
|
||||
template <class TrueType, class FalseType>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
conditional_return(std::false_type, TrueType&&, FalseType&& f) {
|
||||
conditional_return(false_type, TrueType&&, FalseType&& f) {
|
||||
return static_cast<FalseType&&>(f);
|
||||
}
|
||||
|
||||
@@ -397,6 +403,18 @@ conditional_return(bool b, TrueType const& t, FalseType const& f) {
|
||||
return b ? t : f;
|
||||
}
|
||||
|
||||
// TrueType and FalseType don't require a common type
|
||||
template <bool b, class TrueType, class FalseType>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
conditional_return(TrueType const& t, FalseType const& f) {
|
||||
if constexpr (b) {
|
||||
return t;
|
||||
} else {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Display utilities
|
||||
//
|
||||
@@ -406,9 +424,11 @@ CUTE_HOST_DEVICE void print(integral_constant<T,N> const&) {
|
||||
printf("_%d", N);
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class T, T N>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, integral_constant<T,N> const&) {
|
||||
return os << "_" << N;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -30,16 +30,10 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/cstdint>
|
||||
#else
|
||||
#include <cstdint>
|
||||
#endif
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
@@ -48,8 +42,8 @@ namespace cute
|
||||
//
|
||||
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_arithmetic<T>::value &&
|
||||
std::is_arithmetic<U>::value)>
|
||||
__CUTE_REQUIRES(is_arithmetic<T>::value &&
|
||||
is_arithmetic<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
max(T const& t, U const& u) {
|
||||
@@ -57,8 +51,8 @@ max(T const& t, U const& u) {
|
||||
}
|
||||
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_arithmetic<T>::value &&
|
||||
std::is_arithmetic<U>::value)>
|
||||
__CUTE_REQUIRES(is_arithmetic<T>::value &&
|
||||
is_arithmetic<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
min(T const& t, U const& u) {
|
||||
@@ -66,11 +60,11 @@ min(T const& t, U const& u) {
|
||||
}
|
||||
|
||||
template <class T,
|
||||
__CUTE_REQUIRES(std::is_arithmetic<T>::value)>
|
||||
__CUTE_REQUIRES(is_arithmetic<T>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
abs(T const& t) {
|
||||
if constexpr (std::is_signed<T>::value) {
|
||||
if constexpr (is_signed<T>::value) {
|
||||
return t < T(0) ? -t : t;
|
||||
} else {
|
||||
return t;
|
||||
@@ -85,8 +79,8 @@ abs(T const& t) {
|
||||
|
||||
// Greatest common divisor of two integers
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<T>::value &&
|
||||
std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<T>::value &&
|
||||
CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
gcd(T t, U u) {
|
||||
@@ -100,8 +94,8 @@ gcd(T t, U u) {
|
||||
|
||||
// Least common multiple of two integers
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<T>::value &&
|
||||
std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<T>::value &&
|
||||
CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
lcm(T const& t, U const& u) {
|
||||
@@ -133,11 +127,11 @@ template <class T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T
|
||||
bit_width(T x) {
|
||||
static_assert(std::is_unsigned<T>::value, "Only to be used for unsigned types.");
|
||||
constexpr int N = (std::numeric_limits<T>::digits == 64 ? 6 :
|
||||
(std::numeric_limits<T>::digits == 32 ? 5 :
|
||||
(std::numeric_limits<T>::digits == 16 ? 4 :
|
||||
(std::numeric_limits<T>::digits == 8 ? 3 : (assert(false),0)))));
|
||||
static_assert(is_unsigned<T>::value, "Only to be used for unsigned types.");
|
||||
constexpr int N = (numeric_limits<T>::digits == 64 ? 6 :
|
||||
(numeric_limits<T>::digits == 32 ? 5 :
|
||||
(numeric_limits<T>::digits == 16 ? 4 :
|
||||
(numeric_limits<T>::digits == 8 ? 3 : (assert(false),0)))));
|
||||
T r = 0;
|
||||
for (int i = N - 1; i >= 0; --i) {
|
||||
T shift = (x > ((T(1) << (T(1) << i))-1)) << i;
|
||||
@@ -193,7 +187,7 @@ template <class T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T
|
||||
rotl(T x, int s) {
|
||||
constexpr int N = std::numeric_limits<T>::digits;
|
||||
constexpr int N = numeric_limits<T>::digits;
|
||||
return s == 0 ? x : s > 0 ? (x << s) | (x >> (N - s)) : rotr(x, -s);
|
||||
}
|
||||
|
||||
@@ -202,7 +196,7 @@ template <class T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T
|
||||
rotr(T x, int s) {
|
||||
constexpr int N = std::numeric_limits<T>::digits;
|
||||
constexpr int N = numeric_limits<T>::digits;
|
||||
return s == 0 ? x : s > 0 ? (x >> s) | (x << (N - s)) : rotl(x, -s);
|
||||
}
|
||||
|
||||
@@ -214,7 +208,7 @@ template <class T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T
|
||||
countl_zero(T x) {
|
||||
return std::numeric_limits<T>::digits - bit_width(x);
|
||||
return numeric_limits<T>::digits - bit_width(x);
|
||||
}
|
||||
|
||||
// Counts the number of consecutive 1 bits, starting from the most significant bit
|
||||
@@ -236,7 +230,7 @@ template <class T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T
|
||||
countr_zero(T x) {
|
||||
return x == 0 ? std::numeric_limits<T>::digits : bit_width(T(x & T(-x))) - 1; // bit_width of the LSB
|
||||
return x == 0 ? numeric_limits<T>::digits : bit_width(T(x & T(-x))) - 1; // bit_width of the LSB
|
||||
}
|
||||
|
||||
// Counts the number of consecutive 1 bits, starting from the least significant bit
|
||||
@@ -288,7 +282,7 @@ shiftr(T x, int s) {
|
||||
|
||||
// Returns 1 if x > 0, -1 if x < 0, and 0 if x is zero.
|
||||
template <class T,
|
||||
__CUTE_REQUIRES(std::is_unsigned<T>::value)>
|
||||
__CUTE_REQUIRES(is_unsigned<T>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
int
|
||||
signum(T const& x) {
|
||||
@@ -296,7 +290,7 @@ signum(T const& x) {
|
||||
}
|
||||
|
||||
template <class T,
|
||||
__CUTE_REQUIRES(not std::is_unsigned<T>::value)>
|
||||
__CUTE_REQUIRES(not is_unsigned<T>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
int
|
||||
signum(T const& x) {
|
||||
@@ -307,8 +301,8 @@ signum(T const& x) {
|
||||
// @pre t % u == 0
|
||||
// @result t / u
|
||||
template <class T, class U,
|
||||
__CUTE_REQUIRES(std::is_integral<T>::value &&
|
||||
std::is_integral<U>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<T>::value &&
|
||||
CUTE_STL_NAMESPACE::is_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
safe_div(T const& t, U const& u) {
|
||||
|
||||
@@ -43,9 +43,11 @@ using cutlass::tfloat32_t;
|
||||
// Display utilities
|
||||
//
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, tfloat32_t const& v)
|
||||
{
|
||||
return os << float(v);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -44,11 +44,11 @@ namespace cute
|
||||
//
|
||||
|
||||
template <class T, class = void>
|
||||
struct has_dereference : std::false_type {
|
||||
struct has_dereference : false_type {
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct has_dereference<T, void_t<decltype(*std::declval<T>())>> : std::true_type {
|
||||
struct has_dereference<T, void_t<decltype(*declval<T>())>> : true_type {
|
||||
};
|
||||
|
||||
//
|
||||
@@ -91,7 +91,7 @@ struct device_ptr
|
||||
DerivedType operator+(Index const& i) const { return {ptr_ + i}; }
|
||||
|
||||
CUTE_HOST_DEVICE constexpr friend
|
||||
std::ptrdiff_t operator-(device_ptr<T,DerivedType> const& a,
|
||||
ptrdiff_t operator-(device_ptr<T,DerivedType> const& a,
|
||||
device_ptr<T,DerivedType> const& b) {
|
||||
return a.ptr_ - b.ptr_;
|
||||
}
|
||||
@@ -301,6 +301,7 @@ CUTE_HOST_DEVICE void print(rmem_ptr<T> const& ptr)
|
||||
printf("rmem_ptr_%db(%p)", int(8*sizeof(T)), ptr.get());
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class T>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, gmem_ptr<T> const& ptr)
|
||||
{
|
||||
@@ -319,4 +320,6 @@ CUTE_HOST std::ostream& operator<<(std::ostream& os, rmem_ptr<T> const& ptr)
|
||||
return os << "rmem_ptr_" << int(8*sizeof(T)) << "b";
|
||||
}
|
||||
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
+84
-53
@@ -192,7 +192,12 @@ idx2crd(Index const& idx,
|
||||
return transform(shape, compact_col_major(shape, stride), [&](auto const& s, auto const& d){ return idx2crd(idx,s,d); });
|
||||
}
|
||||
} else { // "int" "int" "int"
|
||||
return (idx / stride) % shape;
|
||||
if constexpr (is_constant<1, Shape>::value) {
|
||||
// Skip potential stride-0 division
|
||||
return Int<0>{};
|
||||
} else {
|
||||
return (idx / stride) % shape;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,66 +264,88 @@ crd2crd(Coord const& coord,
|
||||
// Compact Major
|
||||
//
|
||||
|
||||
// General tag for common layouts and dispatching
|
||||
struct GenColMajor {};
|
||||
struct GenRowMajor {};
|
||||
// Tags for common layouts and dispatching
|
||||
struct LayoutLeft; // Col-major layout mapping; leftmost extent has stride 1
|
||||
using GenColMajor = LayoutLeft; // Alias
|
||||
|
||||
template <class Shape, class Current = Int<1>, class Major = GenColMajor>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_major(Shape const& shape,
|
||||
Current const& current = {},
|
||||
Major const& major = {});
|
||||
struct LayoutRight; // Row-major layout mapping; rightmost extent has stride 1
|
||||
using GenRowMajor = LayoutRight; // Alias
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Shape, class Current, int... Is>
|
||||
// GGC8.5 WAR -- Use of lambdas in unevaluated contexts. Instead use function objects.
|
||||
template <class Major>
|
||||
struct CompactLambda;
|
||||
|
||||
// @pre is_integral<Current>
|
||||
// Return (result, current * product(shape)) to enable recurrence
|
||||
template <class Major, class Shape, class Current>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_major_ti(Shape const& shape,
|
||||
Current const& current,
|
||||
GenColMajor const& major, seq<Is...>)
|
||||
compact(Shape const& shape,
|
||||
Current const& current)
|
||||
{
|
||||
return cute::make_tuple(compact_major(get<Is>(shape), current * product<0,Is>(shape), major)...);
|
||||
if constexpr (is_tuple<Shape>::value) { // Shape::tuple Current::int
|
||||
using Lambda = CompactLambda<Major>; // Append or Prepend
|
||||
using Seq = typename Lambda::template seq<Shape>; // Seq or RSeq
|
||||
return cute::detail::fold(shape, cute::make_tuple(cute::make_tuple(), current), Lambda{}, Seq{});
|
||||
} else { // Shape::int Current::int
|
||||
if constexpr (is_constant<1, Shape>::value) {
|
||||
return cute::make_tuple(Int<0>{}, current); // If current is dynamic, this could save a reg
|
||||
} else {
|
||||
return cute::make_tuple(current, current * shape);
|
||||
}
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
template <class Shape, class Current, int... Is>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_major_ti(Shape const& shape,
|
||||
Current const& current,
|
||||
GenRowMajor const& major, seq<Is...>)
|
||||
// GCC8.5 WAR -- Specialization LayoutLeft
|
||||
template <>
|
||||
struct CompactLambda<LayoutLeft>
|
||||
{
|
||||
constexpr int E = tuple_size<Shape>::value;
|
||||
return cute::make_tuple(compact_major(get<Is>(shape), current * product<Is+1,E>(shape), major)...);
|
||||
}
|
||||
template <class Init, class Shape>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(Init const& init, Shape const& si) {
|
||||
auto result = detail::compact<LayoutLeft>(si, get<1>(init));
|
||||
return cute::make_tuple(append(get<0>(init), get<0>(result)), get<1>(result)); // Append
|
||||
}
|
||||
|
||||
template <class Shape>
|
||||
using seq = tuple_seq<Shape>; // Seq
|
||||
};
|
||||
|
||||
// GCC8.5 WAR -- Specialization LayoutRight
|
||||
template <>
|
||||
struct CompactLambda<LayoutRight>
|
||||
{
|
||||
template <class Init, class Shape>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(Init const& init, Shape const& si) {
|
||||
auto result = detail::compact<LayoutRight>(si, get<1>(init));
|
||||
return cute::make_tuple(prepend(get<0>(init), get<0>(result)), get<1>(result)); // Prepend
|
||||
}
|
||||
|
||||
template <class Shape>
|
||||
using seq = tuple_rseq<Shape>; // RSeq
|
||||
};
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class Shape, class Current, class Major>
|
||||
template <class Major, class Shape, class Current = Int<1>,
|
||||
__CUTE_REQUIRES(is_tuple<Shape>::value || is_integral<Shape>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_major(Shape const& shape,
|
||||
Current const& current,
|
||||
Major const& major)
|
||||
Current const& current = {})
|
||||
{
|
||||
if constexpr (is_tuple<Shape>::value) {
|
||||
if constexpr (is_tuple<Current>::value) { // tuple tuple
|
||||
static_assert(tuple_size<Shape>::value == tuple_size<Current>::value, "Mismatched Ranks");
|
||||
return transform(shape, current, [&](auto const& s, auto const& c){ return compact_major(s,c,major); });
|
||||
} else { // tuple int
|
||||
return detail::compact_major_ti(shape, current, major, tuple_seq<Shape>{});
|
||||
}
|
||||
if constexpr (is_tuple<Current>::value) { // Shape::tuple Current::tuple
|
||||
static_assert(is_tuple<Shape>::value, "Invalid parameters");
|
||||
static_assert(tuple_size<Shape>::value == tuple_size<Current>::value, "Mismatched Ranks");
|
||||
// Recurse to apply to the terminals of current
|
||||
return transform(shape, current, [&](auto const& s, auto const& c){ return compact_major<Major>(s,c); });
|
||||
} else {
|
||||
if constexpr (is_tuple<Current>::value) { // int tuple
|
||||
static_assert(sizeof(Shape) == 0, "Invalid parameters");
|
||||
} else { // int int
|
||||
if constexpr (is_constant<1, Shape>::value) {
|
||||
return Int<0>{}; // If current is dynamic, this could save a reg
|
||||
} else {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
return get<0>(detail::compact<Major>(shape, current));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -328,34 +355,38 @@ compact_major(Shape const& shape,
|
||||
// Compact Col Major
|
||||
//
|
||||
|
||||
struct LayoutLeft {
|
||||
template <class Shape>
|
||||
using Apply = decltype(compact_major<LayoutLeft>(declval<Shape>()));
|
||||
};
|
||||
|
||||
template <class Shape, class Current = Int<1>>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_col_major(Shape const& shape,
|
||||
Current const& current = {})
|
||||
{
|
||||
return compact_major(shape, current, GenColMajor{});
|
||||
return compact_major<LayoutLeft>(shape, current);
|
||||
}
|
||||
|
||||
template <class Shape>
|
||||
using ColMajor = decltype(compact_col_major(std::declval<Shape>()));
|
||||
|
||||
//
|
||||
// Compact Row Major
|
||||
//
|
||||
|
||||
struct LayoutRight {
|
||||
template <class Shape>
|
||||
using Apply = decltype(compact_major<LayoutRight>(declval<Shape>()));
|
||||
};
|
||||
|
||||
template <class Shape, class Current = Int<1>>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_row_major(Shape const& shape,
|
||||
Current const& current = {})
|
||||
{
|
||||
return compact_major(shape, current, GenRowMajor{});
|
||||
return compact_major<LayoutRight>(shape, current);
|
||||
}
|
||||
|
||||
template <class Shape>
|
||||
using RowMajor = decltype(compact_row_major(std::declval<Shape>()));
|
||||
|
||||
//
|
||||
// Compact Order -- compute a compact stride based on an ordering of the modes
|
||||
//
|
||||
@@ -397,7 +428,7 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_order(Shape const& shape, GenColMajor const& major)
|
||||
{
|
||||
return compact_major(shape, Int<1>{}, major);
|
||||
return compact_major<LayoutLeft>(shape);
|
||||
}
|
||||
|
||||
template <class Shape>
|
||||
@@ -405,7 +436,7 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
compact_order(Shape const& shape, GenRowMajor const& major)
|
||||
{
|
||||
return compact_major(shape, Int<1>{}, major);
|
||||
return compact_major<LayoutRight>(shape);
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -476,6 +476,7 @@ CUTE_HOST_DEVICE void print(MixedBits<S,D,F> const& m)
|
||||
printf("M_%u|(%u&%u)=%u", S, uint32_t(m.dynamic_int_), F, to_integral(m));
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <uint32_t S, class D, uint32_t F>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, MixedBits<S,D,F> const& m)
|
||||
{
|
||||
@@ -493,5 +494,6 @@ CUTE_HOST std::ostream& operator<<(std::ostream& os, Swizzle<B,M,S> const&)
|
||||
{
|
||||
return os << "S<" << B << "," << M << "," << S << ">";
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -308,7 +308,7 @@ transfer_swizzle(Layout<OldShape,OldStride> const& old_layout,
|
||||
auto active_Y = swizzle_active_bits & shiftr(swizzle_active_bits, -msk_sft) & yyy_msk;
|
||||
|
||||
// Pass the identifiers through the old layout and new layout to make a new swizzle identifier, L*(L[(P o L)(c*)])
|
||||
auto new_active_Z = new_layout(old_layout.get_1d_coord(active_Z));
|
||||
auto new_active_Z = new_layout(old_layout.get_1d_coord(active_Z));
|
||||
auto new_active_Y = new_layout(old_layout.get_1d_coord(active_Y));
|
||||
|
||||
// Use this new swizzle identifier to construct the new swizzle for new_layout
|
||||
@@ -394,7 +394,7 @@ cosize(ComposedLayout<Swizzle,Offset,Layout> const& layout)
|
||||
// Operations to manipulate Layouts like a tuple of pairs
|
||||
//
|
||||
|
||||
template <std::size_t I, class Swizzle, class Offset, class Layout>
|
||||
template <size_t I, class Swizzle, class Offset, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get(ComposedLayout<Swizzle,Offset,Layout> const& a)
|
||||
@@ -450,7 +450,7 @@ make_swizzle_strides(true_type,
|
||||
int_sequence<I...>)
|
||||
{
|
||||
// Below is an optimized/compressed version of:
|
||||
//return make_tuple((swizzle(offset + Z*Int<(1 << I)>{}) - swizzle(offset))...);
|
||||
//return cute::make_tuple((swizzle(offset + Z*Int<(1 << I)>{}) - swizzle(offset))...);
|
||||
// with knowledge of Swizzle, I... ranges for each B bits,
|
||||
// and the layout won't slice along z-bits that are already set
|
||||
|
||||
@@ -471,7 +471,7 @@ make_swizzle_strides(false_type,
|
||||
int_sequence<I...>)
|
||||
{
|
||||
// Below is an optimized/compressed version of:
|
||||
//return make_tuple((swizzle(offset + Y*Int<(1 << I)>{}) - swizzle(offset))...);
|
||||
//return cute::make_tuple((swizzle(offset + Y*Int<(1 << I)>{}) - swizzle(offset))...);
|
||||
// with knowledge of Swizzle, I... ranges for each B bits,
|
||||
// and the layout won't slice along y-bits that are already set
|
||||
|
||||
@@ -1001,10 +1001,12 @@ CUTE_HOST_DEVICE void print(ComposedLayout<Swizzle,Offset,Layout> const& layout)
|
||||
print(layout.swizzle_fn()); print(" o "); print(layout.offset_fn()); print(" o "); print(layout.layout_fn());
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class Swizzle, class Offset, class Layout>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, ComposedLayout<Swizzle,Offset,Layout> const& layout)
|
||||
{
|
||||
return os << layout.swizzle_fn() << " o " << layout.offset_fn() << " o " << layout.layout_fn();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace cute
|
||||
template <class T, class Swizzle>
|
||||
struct smem_ptr_swizzle
|
||||
{
|
||||
static_assert(std::is_empty<Swizzle>::value, "Swizzle can't have state.");
|
||||
static_assert(is_empty<Swizzle>::value, "Swizzle can't have state.");
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T* get() const
|
||||
@@ -86,7 +86,7 @@ struct smem_ptr_swizzle
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
T* apply_swizzle(T* ptr)
|
||||
{
|
||||
return reinterpret_cast<T*>(Swizzle::apply(reinterpret_cast<std::uintptr_t>(ptr)));
|
||||
return reinterpret_cast<T*>(Swizzle::apply(reinterpret_cast<uintptr_t>(ptr)));
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -200,7 +200,7 @@ recast(smem_ptr_swizzle<T const,Swizzle> const& ptr)
|
||||
}
|
||||
|
||||
//
|
||||
// Conversion with swizzle_layout
|
||||
// Conversion with swizzle_layout
|
||||
//
|
||||
|
||||
template <class T, class Swizzle, int B, class Layout>
|
||||
@@ -273,10 +273,12 @@ CUTE_HOST_DEVICE void print(smem_ptr_swizzle<T,Swizzle<B,M,S>> const& ptr)
|
||||
printf("smem_ptr_S<%d,%d,%d>_%db(%p)", B, M, S, int(8*sizeof(T)), ptr.get());
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class T, int B, int M, int S>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, smem_ptr_swizzle<T,Swizzle<B,M,S>> const&)
|
||||
{
|
||||
return os << "smem_ptr_S<" << B << "," << M << "," << S << ">_" << int(8*sizeof(T)) << "b";
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
+106
-50
@@ -57,14 +57,14 @@ namespace cute
|
||||
// };
|
||||
|
||||
template <class T, int N>
|
||||
using ArrayEngine = typename std::conditional<(sizeof_bits<T>::value % 8 == 0),
|
||||
using ArrayEngine = typename conditional<(sizeof_bits<T>::value % 8 == 0),
|
||||
array_aligned<T,N>,
|
||||
array_subbyte<T,N>>::type;
|
||||
|
||||
template <class Iterator>
|
||||
struct ViewEngine
|
||||
{
|
||||
using value_type = typename cute::remove_cvref<decltype(*std::declval<Iterator>())>::type;
|
||||
using value_type = typename cute::remove_cvref<decltype(*declval<Iterator>())>::type;
|
||||
|
||||
using iterator = Iterator;
|
||||
iterator storage_;
|
||||
@@ -91,7 +91,7 @@ struct is_gmem<ViewEngine<Iter>> : is_gmem<Iter> {};
|
||||
template <class Iterator>
|
||||
struct ConstViewEngine
|
||||
{
|
||||
using value_type = typename cute::remove_cvref<decltype(*std::declval<Iterator>())>::type;
|
||||
using value_type = typename cute::remove_cvref<decltype(*declval<Iterator>())>::type;
|
||||
|
||||
using iterator = Iterator;
|
||||
iterator storage_;
|
||||
@@ -335,80 +335,134 @@ template <class Engine, class Layout>
|
||||
struct is_smem<Tensor<Engine,Layout>> : is_smem<Engine> {};
|
||||
template <class Engine, class Layout>
|
||||
struct is_gmem<Tensor<Engine,Layout>> : is_gmem<Engine> {};
|
||||
// Customization point for creation of owning and non-owning Tensors
|
||||
template <class T>
|
||||
struct MakeTensor
|
||||
{
|
||||
template <class Layout,
|
||||
__CUTE_REQUIRES(not has_dereference<T>::value &&
|
||||
is_layout<Layout>::value)>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(Layout const& layout) const
|
||||
{
|
||||
static_assert(is_static<Layout>::value, "Dynamic owning tensors not supported");
|
||||
using Engine = ArrayEngine<T, cosize_v<Layout>>;
|
||||
return Tensor<Engine,Layout>();
|
||||
}
|
||||
|
||||
template <class Layout,
|
||||
__CUTE_REQUIRES(has_dereference<T>::value &&
|
||||
is_layout<Layout>::value)>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(T const& iter, Layout const& layout)
|
||||
{
|
||||
using Engine = ViewEngine<T>;
|
||||
return Tensor<Engine,Layout>(iter, layout);
|
||||
}
|
||||
|
||||
template <class LayoutArg, class... LayoutArgs,
|
||||
__CUTE_REQUIRES(not is_layout<LayoutArg>::value)>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(LayoutArg const& arg, LayoutArgs const&... args) const
|
||||
{
|
||||
return operator()(make_layout(arg, args...));
|
||||
}
|
||||
|
||||
template <class LayoutArg, class... LayoutArgs,
|
||||
__CUTE_REQUIRES(not is_layout<LayoutArg>::value)>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(T const& iter, LayoutArg const& arg, LayoutArgs const&... args)
|
||||
{
|
||||
return operator()(iter, make_layout(arg, args...));
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// make_tensor
|
||||
//
|
||||
|
||||
// Make an owning Tensor that will allocate a static array
|
||||
//
|
||||
|
||||
template <class T, class Layout,
|
||||
__CUTE_REQUIRES(is_layout<Layout>::value)>
|
||||
// e.g. make_tensor<float>(Int<12>{})
|
||||
template <class T, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tensor(Layout const& layout)
|
||||
make_tensor(Args const&... args)
|
||||
{
|
||||
static_assert(is_static<Layout>::value, "Dynamic owning tensors not supported");
|
||||
using Engine = ArrayEngine<T, cosize_v<Layout>>;
|
||||
return Tensor<Engine,Layout>();
|
||||
return MakeTensor<T>{}(args...);
|
||||
}
|
||||
|
||||
// e.g. make_tensor<double>(12)
|
||||
template <class T, class LayoutArg, class... LayoutArgs,
|
||||
__CUTE_REQUIRES(not is_layout<LayoutArg>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tensor(LayoutArg const& arg, LayoutArgs const&... args)
|
||||
{
|
||||
return make_tensor<T>(make_layout(arg, args...));
|
||||
}
|
||||
|
||||
//
|
||||
// Make a non-owning Tensor that will use a pointer (view)
|
||||
//
|
||||
|
||||
template <class Iterator, class Layout,
|
||||
__CUTE_REQUIRES(has_dereference<Iterator>::value &&
|
||||
is_layout<Layout>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tensor(Iterator const& iter, Layout const& layout)
|
||||
{
|
||||
using Engine = ViewEngine<Iterator>;
|
||||
return Tensor<Engine,Layout>(iter, layout);
|
||||
}
|
||||
|
||||
// e.g. make_tensor(vec.data(), 12)
|
||||
template <class Iterator, class LayoutArg, class... LayoutArgs,
|
||||
__CUTE_REQUIRES(not is_layout<LayoutArg>::value)>
|
||||
template <class Iterator, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tensor(Iterator const& iter, LayoutArg const& arg, LayoutArgs const&... args)
|
||||
make_tensor(Iterator const& iter, Args const&... args)
|
||||
{
|
||||
return make_tensor(iter, make_layout(arg, args...));
|
||||
return MakeTensor<Iterator>{}(iter, args...);
|
||||
}
|
||||
|
||||
//
|
||||
// make_tensor_like -- make a register tensor the same type and shape as another
|
||||
// make_tensor_like
|
||||
// Make a register tensor the same type and shape and (if possible) order as another tensor
|
||||
//
|
||||
|
||||
template <class NewT, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tensor_like(Layout const& layout)
|
||||
{
|
||||
if constexpr (is_static<Layout>::value) {
|
||||
return make_tensor<NewT>(make_ordered_layout(layout));
|
||||
} else {
|
||||
return make_tensor<NewT>(make_layout(layout.shape()));
|
||||
}
|
||||
}
|
||||
|
||||
template <class NewT, class Engine, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tensor_like(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
return make_tensor_like<NewT>(tensor.layout());
|
||||
}
|
||||
|
||||
template <class Engine, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tensor_like(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
using value_type = typename Tensor<Engine,Layout>::value_type;
|
||||
return make_tensor<value_type>(tensor.shape());
|
||||
return make_tensor_like<typename Engine::value_type>(tensor.layout());
|
||||
}
|
||||
|
||||
//
|
||||
// make_fragment_like -- make a register tensor the same type, shape, and (if possible) order as another tensor
|
||||
// make_fragment_like --
|
||||
// Make a tensor the same shape and (if possible) order as another tensor, with special
|
||||
// consideration of the 0th mode. The 0th mode is commonly used for MMA_Atoms or Copy_Atoms
|
||||
// so this allocates the 0th mode with LayoutLeft regardless of the reference layout.
|
||||
//
|
||||
|
||||
template <class NewT, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_fragment_like(Layout const& layout)
|
||||
{
|
||||
return make_tensor<NewT>(make_fragment_like(layout));
|
||||
}
|
||||
|
||||
template <class NewT, class Engine, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_fragment_like(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
return make_fragment_like<NewT>(tensor.layout());
|
||||
}
|
||||
|
||||
template <class Engine, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_fragment_like(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
using value_type = typename Tensor<Engine,Layout>::value_type;
|
||||
return make_tensor<value_type>(make_layout_like(tensor.layout()));
|
||||
return make_fragment_like<typename Engine::value_type>(tensor.layout());
|
||||
}
|
||||
|
||||
//
|
||||
@@ -452,7 +506,7 @@ template <int B, int E, class Tensor,
|
||||
__CUTE_REQUIRES(is_tensor<remove_cvref_t<Tensor>>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
decltype(auto)
|
||||
take(Tensor&& tensor)
|
||||
take(Tensor&& tensor)
|
||||
{
|
||||
return make_tensor(std::forward<Tensor>(tensor).data(), take<B,E>(tensor.layout()));
|
||||
}
|
||||
@@ -627,11 +681,11 @@ max_common_vector(Tensor<SrcEngine,SrcLayout> const& a,
|
||||
if constexpr (// Should be the same value_types, else the copy is also performing a cast
|
||||
sizeof(SrcType) == sizeof(DstType) &&
|
||||
// The types should be trivially copyable so that vectorization is valid
|
||||
std::is_trivially_copyable<SrcType>::value &&
|
||||
std::is_trivially_copyable<DstType>::value &&
|
||||
is_trivially_copyable<SrcType>::value &&
|
||||
is_trivially_copyable<DstType>::value &&
|
||||
// Should be load/storing real data, rather than implicit iterators or such
|
||||
std::is_reference<SrcRef>::value &&
|
||||
std::is_reference<DstRef>::value)
|
||||
is_reference<SrcRef>::value &&
|
||||
is_reference<DstRef>::value)
|
||||
{
|
||||
return max_common_vector(a.layout(), b.layout());
|
||||
} else {
|
||||
@@ -833,6 +887,7 @@ CUTE_HOST_DEVICE void print(Tensor<Engine,Layout> const& tensor)
|
||||
print_tensor(tensor);
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class Engine, class Layout>
|
||||
CUTE_HOST std::ostream& print_tensor_os(std::ostream& os, Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
@@ -879,6 +934,7 @@ CUTE_HOST std::ostream& operator<<(std::ostream& os, Tensor<Engine,Layout> const
|
||||
os << tensor.layout() << std::endl;
|
||||
return print_tensor_os(os, tensor);
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
|
||||
@@ -60,11 +60,11 @@ struct has_elem : false_type {};
|
||||
template <class Elem>
|
||||
struct has_elem<Elem, Elem> : true_type {};
|
||||
template <class Tuple, class Elem>
|
||||
struct has_elem<Tuple, Elem, std::enable_if_t<is_tuple<Tuple>::value> >
|
||||
struct has_elem<Tuple, Elem, enable_if_t<is_tuple<Tuple>::value> >
|
||||
: has_elem<Tuple, Elem, tuple_seq<Tuple> > {};
|
||||
template <class Tuple, class Elem, int... Is>
|
||||
struct has_elem<Tuple, Elem, seq<Is...>>
|
||||
: disjunction<has_elem<std::tuple_element_t<Is, Tuple>, Elem>...> {};
|
||||
: disjunction<has_elem<tuple_element_t<Is, Tuple>, Elem>...> {};
|
||||
|
||||
// Tuple trait for detecting static member element
|
||||
template <class Tuple, class Elem, class Enable = void>
|
||||
@@ -72,11 +72,11 @@ struct all_elem : false_type {};
|
||||
template <class Elem>
|
||||
struct all_elem<Elem, Elem> : true_type {};
|
||||
template <class Tuple, class Elem>
|
||||
struct all_elem<Tuple, Elem, std::enable_if_t<is_tuple<Tuple>::value> >
|
||||
struct all_elem<Tuple, Elem, enable_if_t<is_tuple<Tuple>::value> >
|
||||
: all_elem<Tuple, Elem, tuple_seq<Tuple> > {};
|
||||
template <class Tuple, class Elem, int... Is>
|
||||
struct all_elem<Tuple, Elem, seq<Is...>>
|
||||
: conjunction<all_elem<std::tuple_element_t<Is, Tuple>, Elem>...> {};
|
||||
: conjunction<all_elem<tuple_element_t<Is, Tuple>, Elem>...> {};
|
||||
|
||||
// Tuple trait for detecting Underscore member
|
||||
template <class Tuple>
|
||||
@@ -141,8 +141,10 @@ CUTE_HOST_DEVICE void print(Underscore const&) {
|
||||
printf("_");
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, Underscore const&) {
|
||||
return os << "_";
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
+20
-10
@@ -99,8 +99,12 @@ namespace cute
|
||||
|
||||
// A dummy function that uses compilation failure to print a type
|
||||
template <class T>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
CUTE_HOST_DEVICE void
|
||||
print_type() {
|
||||
static_assert(sizeof(T) < 0, "Printing type T.");
|
||||
}
|
||||
template <class T>
|
||||
CUTE_HOST_DEVICE void
|
||||
print_type(T&&) {
|
||||
static_assert(sizeof(T) < 0, "Printing type T.");
|
||||
}
|
||||
@@ -113,13 +117,23 @@ print_type(T&&) {
|
||||
// if (block0()) print(...);
|
||||
// if (thread(42)) print(...);
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
bool
|
||||
block(int bid)
|
||||
{
|
||||
#if defined(__CUDA_ARCH__)
|
||||
return blockIdx.x + blockIdx.y*gridDim.x + blockIdx.z*gridDim.x*gridDim.y == bid;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
bool
|
||||
thread(int tid, int bid)
|
||||
{
|
||||
#if defined(__CUDA_ARCH__)
|
||||
return (threadIdx.x + threadIdx.y*blockDim.x + threadIdx.z*blockDim.x*blockDim.y == tid)
|
||||
&& ( blockIdx.x + blockIdx.y* gridDim.x + blockIdx.z* gridDim.x* gridDim.y == bid);
|
||||
return (threadIdx.x + threadIdx.y*blockDim.x + threadIdx.z*blockDim.x*blockDim.y == tid) && block(bid);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
@@ -129,7 +143,7 @@ CUTE_HOST_DEVICE
|
||||
bool
|
||||
thread(int tid)
|
||||
{
|
||||
return thread(tid, 0);
|
||||
return thread(tid,0);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
@@ -143,11 +157,7 @@ CUTE_HOST_DEVICE
|
||||
bool
|
||||
block0()
|
||||
{
|
||||
#if defined(__CUDA_ARCH__)
|
||||
return !(blockIdx.x | blockIdx.y | blockIdx.z);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
return block(0);
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -30,10 +30,11 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
#include <cute/numeric/integral_constant.hpp>
|
||||
|
||||
//
|
||||
// CUDA compatible print and printf
|
||||
//
|
||||
@@ -123,7 +124,7 @@ print(char const& c) {
|
||||
}
|
||||
|
||||
template <class T,
|
||||
__CUTE_REQUIRES(std::is_integral<T>::value)>
|
||||
__CUTE_REQUIRES(CUTE_STL_NAMESPACE::is_integral<T>::value)>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(T const& a) {
|
||||
|
||||
@@ -30,32 +30,97 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/type_traits>
|
||||
#include <cuda/std/utility>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/cstdint>
|
||||
#include <cuda/std/limits>
|
||||
#else
|
||||
#include <type_traits>
|
||||
#include <utility> // tuple_size, tuple_element
|
||||
#include <cstddef> // ptrdiff_t
|
||||
#include <cstdint> // uintptr_t
|
||||
#include <limits> // numeric_limits
|
||||
#endif
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#define __CUTE_REQUIRES(...) typename std::enable_if<(__VA_ARGS__)>::type* = nullptr
|
||||
#define __CUTE_REQUIRES_V(...) typename std::enable_if<decltype((__VA_ARGS__))::value>::type* = nullptr
|
||||
namespace cute
|
||||
{
|
||||
using CUTE_STL_NAMESPACE::enable_if;
|
||||
using CUTE_STL_NAMESPACE::enable_if_t;
|
||||
}
|
||||
|
||||
#define __CUTE_REQUIRES(...) typename cute::enable_if<(__VA_ARGS__)>::type* = nullptr
|
||||
#define __CUTE_REQUIRES_V(...) typename cute::enable_if<decltype((__VA_ARGS__))::value>::type* = nullptr
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
using std::conjunction;
|
||||
using std::conjunction_v;
|
||||
// <type_traits>
|
||||
using CUTE_STL_NAMESPACE::conjunction;
|
||||
using CUTE_STL_NAMESPACE::conjunction_v;
|
||||
|
||||
using std::disjunction;
|
||||
using std::disjunction_v;
|
||||
using CUTE_STL_NAMESPACE::disjunction;
|
||||
using CUTE_STL_NAMESPACE::disjunction_v;
|
||||
|
||||
using std::negation;
|
||||
using std::negation_v;
|
||||
using CUTE_STL_NAMESPACE::negation;
|
||||
using CUTE_STL_NAMESPACE::negation_v;
|
||||
|
||||
using std::void_t;
|
||||
using CUTE_STL_NAMESPACE::void_t;
|
||||
using CUTE_STL_NAMESPACE::is_void_v;
|
||||
|
||||
using CUTE_STL_NAMESPACE::is_base_of;
|
||||
using CUTE_STL_NAMESPACE::is_base_of_v;
|
||||
|
||||
// using CUTE_STL_NAMESPACE::true_type;
|
||||
// using CUTE_STL_NAMESPACE::false_type;
|
||||
|
||||
using CUTE_STL_NAMESPACE::conditional;
|
||||
using CUTE_STL_NAMESPACE::conditional_t;
|
||||
|
||||
using CUTE_STL_NAMESPACE::remove_cv_t;
|
||||
using CUTE_STL_NAMESPACE::remove_reference_t;
|
||||
|
||||
using CUTE_STL_NAMESPACE::extent;
|
||||
using CUTE_STL_NAMESPACE::remove_extent;
|
||||
|
||||
using CUTE_STL_NAMESPACE::decay;
|
||||
using CUTE_STL_NAMESPACE::decay_t;
|
||||
|
||||
using CUTE_STL_NAMESPACE::is_reference;
|
||||
using CUTE_STL_NAMESPACE::is_trivially_copyable;
|
||||
|
||||
using CUTE_STL_NAMESPACE::is_same;
|
||||
using CUTE_STL_NAMESPACE::is_same_v;
|
||||
|
||||
using CUTE_STL_NAMESPACE::is_arithmetic;
|
||||
using CUTE_STL_NAMESPACE::is_unsigned;
|
||||
using CUTE_STL_NAMESPACE::is_signed;
|
||||
// using CUTE_STL_NAMESPACE::is_integral;
|
||||
|
||||
using CUTE_STL_NAMESPACE::is_empty;
|
||||
|
||||
using CUTE_STL_NAMESPACE::invoke_result_t;
|
||||
|
||||
// <utility>
|
||||
using CUTE_STL_NAMESPACE::declval;
|
||||
|
||||
// <limits>
|
||||
using CUTE_STL_NAMESPACE::numeric_limits;
|
||||
|
||||
// <cstddef>
|
||||
using CUTE_STL_NAMESPACE::ptrdiff_t;
|
||||
|
||||
// <cstdint>
|
||||
using CUTE_STL_NAMESPACE::uintptr_t;
|
||||
|
||||
// C++20
|
||||
// using std::remove_cvref;
|
||||
template <class T>
|
||||
struct remove_cvref {
|
||||
using type = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||
using type = remove_cv_t<remove_reference_t<T>>;
|
||||
};
|
||||
|
||||
// C++20
|
||||
@@ -63,38 +128,79 @@ struct remove_cvref {
|
||||
template <class T>
|
||||
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
//
|
||||
// dependent_false
|
||||
//
|
||||
// @brief An always-false value that depends on one or more template parameters.
|
||||
// See
|
||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1830r1.pdf
|
||||
// https://github.com/cplusplus/papers/issues/572
|
||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2593r0.html
|
||||
template <class... Args>
|
||||
inline constexpr bool dependent_false = false;
|
||||
|
||||
//
|
||||
// tuple_size, tuple_element
|
||||
//
|
||||
// @brief CuTe-local tuple-traits to prevent conflicts with other libraries.
|
||||
// For cute:: types, we specialize std::tuple-traits, which is explicitly allowed.
|
||||
// cute::tuple, cute::array, cute::array_subbyte, etc
|
||||
// But CuTe wants to treat some external types as tuples as well. For those,
|
||||
// we specialize cute::tuple-traits to avoid polluting external traits.
|
||||
// dim3, uint3, etc
|
||||
|
||||
template <class T, class = void>
|
||||
struct tuple_size;
|
||||
|
||||
template <class T>
|
||||
struct tuple_size<T,void_t<typename CUTE_STL_NAMESPACE::tuple_size<T>::type>> : CUTE_STL_NAMESPACE::integral_constant<size_t, CUTE_STL_NAMESPACE::tuple_size<T>::value> {};
|
||||
|
||||
// S = : std::integral_constant<std::size_t, std::tuple_size<T>::value> {};
|
||||
|
||||
template <class T>
|
||||
constexpr size_t tuple_size_v = tuple_size<T>::value;
|
||||
|
||||
template <size_t I, class T, class = void>
|
||||
struct tuple_element;
|
||||
|
||||
template <size_t I, class T>
|
||||
struct tuple_element<I,T,void_t<typename CUTE_STL_NAMESPACE::tuple_element<I,T>::type>> : CUTE_STL_NAMESPACE::tuple_element<I,T> {};
|
||||
|
||||
template <size_t I, class T>
|
||||
using tuple_element_t = typename tuple_element<I,T>::type;
|
||||
|
||||
//
|
||||
// is_valid
|
||||
//
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class F, class... Args, class = decltype(std::declval<F&&>()(std::declval<Args&&>()...))>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid_impl(int) { return std::true_type{}; }
|
||||
template <class F, class... Args, class = decltype(declval<F&&>()(declval<Args&&>()...))>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid_impl(int) { return CUTE_STL_NAMESPACE::true_type{}; }
|
||||
|
||||
template <class F, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid_impl(...) { return std::false_type{}; }
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid_impl(...) { return CUTE_STL_NAMESPACE::false_type{}; }
|
||||
|
||||
template <class F>
|
||||
struct is_valid_fn {
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
operator()(Args&&...) const { return is_valid_impl<F, Args&&...>(int{}); }
|
||||
};
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class F>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid(F&&) {
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid(F&&) {
|
||||
return detail::is_valid_fn<F&&>{};
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid(F&&, Args&&...) {
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
is_valid(F&&, Args&&...) {
|
||||
return detail::is_valid_impl<F&&, Args&&...>(int{});
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,6 @@ struct Sm80 {
|
||||
struct Sm86 {
|
||||
static int const kMinComputeCapability = 86;
|
||||
};
|
||||
|
||||
struct Sm90 {
|
||||
static int const kMinComputeCapability = 90;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2011-2019, NVIDIA CORPORATION. All rights reserved.
|
||||
* Copyright (c) 2023 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are not permit-
|
||||
* ted.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
@@ -22,7 +36,6 @@
|
||||
|
||||
#include <cutlass/arch/memory_sm75.h>
|
||||
#include <cute/arch/cluster_sm90.hpp>
|
||||
|
||||
namespace cutlass {
|
||||
/// @brief
|
||||
namespace arch {
|
||||
@@ -72,7 +85,7 @@ class NamedBarrier {
|
||||
static void arrive_and_wait(uint32_t num_threads, uint32_t barrier_id) {
|
||||
#if CUDA_BARRIER_ENABLED
|
||||
asm volatile("bar.sync %0, %1;" : : "r"(barrier_id), "r"(num_threads));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -81,7 +94,7 @@ class NamedBarrier {
|
||||
static void arrive(uint32_t num_threads, uint32_t barrier_id) {
|
||||
#if CUDA_BARRIER_ENABLED
|
||||
asm volatile("bar.arrive %0, %1;" : : "r"(barrier_id), "r"(num_threads));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -94,15 +107,15 @@ class NamedBarrier {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Hopper introduces a new cluster-wide barrier which handle with Cluster-wide AW behaviour.
|
||||
// This is an extension to the Ampere AW barriers
|
||||
// Note : Ampere AW Barriers have a larger max-arrive count (2^30) than Hopper AW Barriers (2^20).
|
||||
// Hopper introduces a new cluster-wide barrier which handle with Cluster-wide arrive-wait behaviour.
|
||||
// This is an extension to the Ampere arrive-wait barriers
|
||||
// Note : Ampere arrive-wait Barriers have a larger max-arrive count (2^30) than Hopper arrive-wait Barriers (2^20).
|
||||
struct ClusterBarrier {
|
||||
|
||||
using ValueType = uint64_t;
|
||||
|
||||
protected:
|
||||
// Can never be initializated - can only be aliased to smem
|
||||
// Can never be initialized - can only be aliased to smem
|
||||
ValueType barrier_;
|
||||
|
||||
public:
|
||||
@@ -120,6 +133,11 @@ public:
|
||||
return ClusterBarrier::test_wait(&this->barrier_, phase, pred);
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
uint32_t try_wait(uint32_t phase) const {
|
||||
return ClusterBarrier::try_wait(&this->barrier_, phase);
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
void wait(uint32_t phase) const {
|
||||
ClusterBarrier::wait(&this->barrier_, phase);
|
||||
@@ -150,7 +168,7 @@ public:
|
||||
"}"
|
||||
:
|
||||
: "r"(arrive_count), "r"(smem_addr));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -174,7 +192,7 @@ public:
|
||||
:
|
||||
: "r"(smem_addr), "r"(phase), "r"(ticks));
|
||||
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -197,7 +215,29 @@ public:
|
||||
: "r"(smem_addr), "r"(phase), "r"(pred));
|
||||
|
||||
return waitComplete;
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
static uint32_t try_wait(ValueType const* smem_ptr, uint32_t phase) {
|
||||
#if CUDA_BARRIER_ENABLED
|
||||
uint32_t smem_addr = cute::cast_smem_ptr_to_uint(smem_ptr);
|
||||
uint32_t waitComplete;
|
||||
|
||||
asm volatile(
|
||||
"{\n\t"
|
||||
".reg .pred P1; \n\t"
|
||||
"mbarrier.try_wait.parity.shared.b64 P1, [%1], %2; \n\t"
|
||||
"selp.b32 %0, 1, 0, P1; \n\t"
|
||||
"}"
|
||||
: "=r"(waitComplete)
|
||||
: "r"(smem_addr), "r"(phase));
|
||||
|
||||
return waitComplete;
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
return 0;
|
||||
@@ -218,7 +258,7 @@ public:
|
||||
"}"
|
||||
:
|
||||
: "r"(smem_addr), "r"(cta_id), "r"(pred));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -235,7 +275,7 @@ public:
|
||||
"}"
|
||||
:
|
||||
: "r"(smem_addr), "l"(state));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -250,7 +290,7 @@ public:
|
||||
"}"
|
||||
:
|
||||
: "r"(smem_addr));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -303,7 +343,7 @@ struct ClusterTransactionBarrier : public ClusterBarrier {
|
||||
"}"
|
||||
:
|
||||
: "r"(transaction_bytes), "r"(smem_addr));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -324,7 +364,7 @@ struct ClusterTransactionBarrier : public ClusterBarrier {
|
||||
"}"
|
||||
:
|
||||
: "r"(smem_addr), "r"(cta_id), "r"(pred), "r"(transaction_bytes));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -340,7 +380,7 @@ struct ClusterTransactionBarrier : public ClusterBarrier {
|
||||
"}"
|
||||
:
|
||||
: "r"(transaction_bytes), "r"(smem_addr));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -360,7 +400,7 @@ struct ClusterTransactionBarrier : public ClusterBarrier {
|
||||
"}"
|
||||
:
|
||||
: "r"(transaction_bytes), "r"(smem_addr), "r"(pred));
|
||||
#else
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
@@ -378,8 +418,8 @@ void fence_barrier_init() {
|
||||
"fence.mbarrier_init.release.cluster; \n"
|
||||
"}"
|
||||
::);
|
||||
#else
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -392,8 +432,8 @@ void fence_view_async_shared() {
|
||||
"fence.proxy.async.shared::cta; \n"
|
||||
"}"
|
||||
::);
|
||||
#else
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
asm volatile ("brkpt;\n" ::);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -468,7 +468,7 @@ void shared_store<16>(uint32_t ptr, void const *src) {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "memory_sm75.h"
|
||||
#include "memory_sm80.h"
|
||||
#include "cutlass/arch/memory_sm75.h"
|
||||
#include "cutlass/arch/memory_sm80.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -76,6 +76,12 @@ struct OpMultiplyAddFastF32 {};
|
||||
// Perform 3xTF32 or 4xTF32 for every complex<F32> output element
|
||||
struct OpMultiplyAddComplexFastF32 {};
|
||||
|
||||
/// Helper for determining whether staged accumulation should be used for a given operator
|
||||
template <typename Operator>
|
||||
struct UseStagedAccumulation {
|
||||
static bool const value = platform::is_same<Operator, OpMultiplyAddFastF32>::value ||
|
||||
platform::is_same<Operator, OpMultiplyAddComplexFastF32>::value;
|
||||
};
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Tag indicating the complex multiply-add operation
|
||||
|
||||
@@ -81,7 +81,7 @@ protected:
|
||||
CUTLASS_DEVICE
|
||||
static void red_release(int *ptr, int val)
|
||||
{
|
||||
#if defined(__NVCC__) || (defined(__clang__) && defined(__CUDA__)) || defined(__CUDACC_RTC__)
|
||||
#if !defined(CUTLASS_PYTHON_HOST_CC)
|
||||
#if (__CUDA_ARCH__ >= 700)
|
||||
/// SM70 and newer use memory consistency qualifiers
|
||||
|
||||
@@ -104,7 +104,7 @@ public:
|
||||
CUTLASS_DEVICE
|
||||
static void wait_lt(void *lock_ptr, int thread_idx, int flag_idx, int count)
|
||||
{
|
||||
#if defined(__NVCC__) || (defined(__clang__) && defined(__CUDA__)) || defined(__CUDACC_RTC__)
|
||||
#if !defined(CUTLASS_PYTHON_HOST_CC)
|
||||
T *flag_ptr = reinterpret_cast<T*>(lock_ptr) + flag_idx;
|
||||
|
||||
if (thread_idx == 0)
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
CUTLASS_DEVICE
|
||||
static void wait_eq(void *lock_ptr, int thread_idx, int flag_idx, T val = 1)
|
||||
{
|
||||
#if defined(__NVCC__) || (defined(__clang__) && defined(__CUDA__)) || defined(__CUDACC_RTC__)
|
||||
#if !defined(CUTLASS_PYTHON_HOST_CC)
|
||||
T *flag_ptr = reinterpret_cast<T*>(lock_ptr) + flag_idx;
|
||||
|
||||
if (thread_idx == 0)
|
||||
@@ -138,7 +138,7 @@ public:
|
||||
/// Uses thread[0] to wait for the specified count of signals on the given flag counter
|
||||
CUTLASS_DEVICE
|
||||
static void wait_eq_reset(void *lock_ptr, int thread_idx, int flag_idx, T val = 1) {
|
||||
#if defined(__NVCC__) || (defined(__clang__) && defined(__CUDA__)) || defined(__CUDACC_RTC__)
|
||||
#if !defined(CUTLASS_PYTHON_HOST_CC)
|
||||
T *flag_ptr = reinterpret_cast<T*>(lock_ptr) + flag_idx;
|
||||
|
||||
if (thread_idx == 0)
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
CUTLASS_DEVICE
|
||||
static void arrive_inc(void *lock_ptr, int thread_idx, int flag_idx)
|
||||
{
|
||||
#if defined(__NVCC__) || (defined(__clang__) && defined(__CUDA__)) || defined(__CUDACC_RTC__)
|
||||
#if !defined(CUTLASS_PYTHON_HOST_CC)
|
||||
T* flag_ptr = reinterpret_cast<T*>(lock_ptr) + flag_idx;
|
||||
|
||||
__syncthreads();
|
||||
@@ -173,7 +173,7 @@ public:
|
||||
CUTLASS_DEVICE
|
||||
static void arrive_range_inc(void *lock_ptr, int thread_idx, int first_flag_idx, int count = 1)
|
||||
{
|
||||
#if defined(__NVCC__) || (defined(__clang__) && defined(__CUDA__)) || defined(__CUDACC_RTC__)
|
||||
#if !defined(CUTLASS_PYTHON_HOST_CC)
|
||||
int flag_idx = first_flag_idx + thread_idx;
|
||||
T* flag_ptr = reinterpret_cast<T*>(lock_ptr) + flag_idx;
|
||||
|
||||
|
||||
@@ -35,11 +35,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime_api.h>
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/trace.h"
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/type_traits>
|
||||
#else
|
||||
#include <type_traits>
|
||||
#endif
|
||||
|
||||
#if ((__CUDACC_VER_MAJOR__ >= 12) || ((__CUDACC_VER_MAJOR__ == 11) && (__CUDACC_VER_MINOR__ >= 8)))
|
||||
# define CUTLASS_SM90_CLUSTER_LAUNCH_ENABLED
|
||||
#endif
|
||||
@@ -72,7 +77,7 @@ struct ClusterLauncher {
|
||||
|
||||
// Check for hardware compatibility
|
||||
static inline __host__
|
||||
Status check_cluster_dims(dim3 const& grid, dim3 const& cluster) {
|
||||
Status check_cluster_dims(dim3 grid, dim3 cluster) {
|
||||
if (((cluster.x * cluster.y * cluster.z) <= MaxClusterSize) &&
|
||||
(grid.x % cluster.x == 0) && (grid.y % cluster.y == 0) && (grid.z % cluster.z == 0)) {
|
||||
return Status::kSuccess;
|
||||
@@ -105,11 +110,11 @@ struct ClusterLauncher {
|
||||
// This is the method we expect to use going forward
|
||||
static inline __host__
|
||||
Status launch(
|
||||
dim3 const& grid_dims,
|
||||
dim3 const& cluster_dims,
|
||||
dim3 const& block_dims,
|
||||
size_t const& smem_size,
|
||||
cudaStream_t& cuda_stream,
|
||||
dim3 const grid_dims,
|
||||
dim3 const cluster_dims,
|
||||
dim3 const block_dims,
|
||||
size_t const smem_size,
|
||||
cudaStream_t cuda_stream,
|
||||
void const* kernel,
|
||||
void** kernel_params) {
|
||||
#if defined(CUTLASS_SM90_CLUSTER_LAUNCH_ENABLED)
|
||||
@@ -153,4 +158,78 @@ struct ClusterLauncher {
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Arg>
|
||||
void* checked_addressof(Arg&& arg) {
|
||||
static_assert(! std::is_rvalue_reference_v<Arg> || ! std::is_const_v<Arg>, "You cannot take the address of a const rvalue reference (const T&&).");
|
||||
// We use std::addressof to ensure we get the address,
|
||||
// in case the type has an overloaded operator&.
|
||||
// Note that this precludes `const T&&` references.
|
||||
return const_cast<void*>(reinterpret_cast<void const*>(std::addressof(arg)));
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
//! Parameters for launch_on_cluster (see below).
|
||||
struct ClusterLaunchParams {
|
||||
//! Grid dimensions
|
||||
dim3 grid_dims{1, 1, 1};
|
||||
|
||||
//! Block dimensions
|
||||
dim3 block_dims{1, 1, 1};
|
||||
|
||||
//! Cluster dimensions
|
||||
dim3 cluster_dims{1, 1, 1};
|
||||
|
||||
//! Number of bytes required for the kernel's shared memory.
|
||||
int smem_size_in_bytes = 0;
|
||||
|
||||
//! CUDA stream on which to launch the kernel.
|
||||
cudaStream_t cuda_stream = nullptr;
|
||||
};
|
||||
|
||||
/// @brief Launch the kernel on the stream using cluster launch.
|
||||
///
|
||||
/// @param params Cluster launch parameters (see above).
|
||||
/// @param kernel_ptr Pointer to the kernel function (see example).
|
||||
/// @param args Zero or more arguments to pass to the kernel.
|
||||
///
|
||||
/// @tparam Args Types of the arguments passed to the kernel.
|
||||
/// Don't specify this/these template argument(s) explicitly.
|
||||
///
|
||||
/// @return Status::Success on success, else an error code.
|
||||
///
|
||||
/// @code
|
||||
/// template<class SharedMemoryType, class A, class B, class C>
|
||||
/// __global__ void kernel(A a, B b, C c);
|
||||
///
|
||||
/// X x = get_x();
|
||||
/// Y y = get_y();
|
||||
/// Z z = get_z();
|
||||
///
|
||||
/// void const* kernel_ptr =
|
||||
/// const_cast<void const*>(reinterpret_cast<void*>(
|
||||
/// &kernel<SharedMemory, X, Y, Z>));
|
||||
/// auto status = launch_on_cluster(
|
||||
/// {grid_dims, block_dims, cluster_dims, sizeof(SharedMemory)},
|
||||
/// kernel_ptr, x, y, z);
|
||||
/// @endcode
|
||||
template<class ... Args>
|
||||
__host__ cutlass::Status
|
||||
launch_kernel_on_cluster(const ClusterLaunchParams& params,
|
||||
void const* kernel_ptr,
|
||||
Args&& ... args)
|
||||
{
|
||||
// Unfortunately, we find ourselves needing to pass in
|
||||
// the parameters as an array of raw pointers.
|
||||
void* kernel_params[] = {
|
||||
detail::checked_addressof(std::forward<Args>(args))...
|
||||
};
|
||||
return cutlass::ClusterLauncher::launch(
|
||||
params.grid_dims, params.cluster_dims, params.block_dims,
|
||||
params.smem_size_in_bytes, params.cuda_stream,
|
||||
kernel_ptr, kernel_params);
|
||||
}
|
||||
|
||||
} // namespace cutlass
|
||||
|
||||
@@ -179,16 +179,6 @@ class complex
|
||||
complex(cuDoubleComplex const &z) : _real(static_cast<T>(cuCreal(z))), _imag(static_cast<T>(cuCimag(z))) {}
|
||||
#endif
|
||||
|
||||
/// Assignment
|
||||
template<typename A>
|
||||
CUTLASS_HOST_DEVICE
|
||||
complex<T>& operator=(complex<A> const &z)
|
||||
{
|
||||
_real = static_cast<T>(z.real());
|
||||
_imag = static_cast<T>(z.imag());
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Equality operator
|
||||
CUTLASS_HOST_DEVICE bool operator==(complex<T> const &rhs) const {
|
||||
return this->real() == rhs.real() && this->imag() == rhs.imag();
|
||||
|
||||
@@ -47,13 +47,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
#include <cuda/std/cmath>
|
||||
#else
|
||||
#include <cmath>
|
||||
#endif
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/tensor_coord.h"
|
||||
#include "cutlass/fast_math.h"
|
||||
|
||||
@@ -29,18 +29,18 @@
|
||||
*
|
||||
**************************************************************************************************/
|
||||
/*! \file
|
||||
\brief
|
||||
\brief
|
||||
|
||||
This file contains definitions and utility functions for describing convolution problem sizes in terms of
|
||||
activation (NHWC), filter (KRSC), output (NPQK), pading (pad_h, pad_w), stride (stride_h, stride_w),
|
||||
dilation (dilation_h, dilation_w). Furthermore, it defines helper functions to map cutlass' implicit gemm
|
||||
tensor extents, sizes, data types to that of convolutions extents, sizes, and data types.
|
||||
This file contains definitions and utility functions for describing convolution problem sizes in terms of
|
||||
activation (NHWC), filter (KRSC), output (NPQK), padding (pad_h, pad_w), stride (stride_h, stride_w), and
|
||||
dilation (dilation_h, dilation_w). Furthermore, it defines helper functions to map CUTLASS's implicit gemm
|
||||
tensor extents, sizes, and data types to that of the convolution's extents, sizes, and data types.
|
||||
|
||||
* Mapping convolutions to Gemm computation *
|
||||
|
||||
Cutlass employs ImplicitGemm algorithm to implement convolutions. ImplicitGemm algorithm runs gemm operation
|
||||
on convolution tensors Activation, Filter, and Output . The underlying gemm operation follows the standard
|
||||
gemm definition:
|
||||
Cutlass implements convolutions with the Implicit Gemm algorithm. This algorithm performs a gemm
|
||||
(general matrix-matrix multiply) on the convolution tensors Activation, Filter, and Output.
|
||||
The underlying gemm operation follows the standard gemm definition:
|
||||
|
||||
C = A * B + C
|
||||
|
||||
@@ -48,22 +48,23 @@ gemm definition:
|
||||
C is source and output matrix
|
||||
|
||||
|
||||
For the three convolutional operators (Fprop, Dgrad, Wgrad), ImplicitGemm matrices A, B, and C are mapped on
|
||||
to convolution tensors Activation, Filter and Output as per the below table:
|
||||
For the three convolutional operators (Fprop, Dgrad, Wgrad), ImplicitGemm matrices A, B, and C are mapped
|
||||
to convolution tensors Activation, Filter and Output as described in the table below.
|
||||
|
||||
___________________________________________________________________________
|
||||
ConvolutionalOperator | A | B | C
|
||||
ConvolutionalOperator | A | B | C
|
||||
___________________________________________________________________________
|
||||
| | | | |
|
||||
| Fprop | Activation | Filter | Output |
|
||||
| Dgrad | Output | Filter | Activation |
|
||||
| Wgrad | Output | Activation | Filter |
|
||||
| Fprop | Activation | Filter | Output |
|
||||
| Dgrad | Output | Filter | Activation |
|
||||
| Wgrad | Output | Activation | Filter |
|
||||
___________________________________________________________________________
|
||||
|
||||
In convolution codebase, DO NOT mix using (A, B, C) with (Acvitation, Filter, Output).
|
||||
In convolution codebase, DO NOT mix using (A, B, C) with (Activation, Filter, Output).
|
||||
|
||||
For example, a convolution class/function with A, B, Output is confusing and error-prone. Instead use below
|
||||
mapping functions and adhere to using either A, B, C or Acvitation, Filter, Output.
|
||||
For example, it's confusing and error prone to document a convolution class or function
|
||||
as operating on "A, B, Output." Instead, use the mapping functions below,
|
||||
and adhere to using either A, B, C or Activation, Filter, Output.
|
||||
|
||||
Map elements' data types (ImplicitGemm -> Conv): GemmToConvElementMap
|
||||
Map elements' data types (Conv -> ImplicitGemm): ConvToGemmElementMap
|
||||
@@ -83,20 +84,20 @@ namespace conv {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Convolutional operator
|
||||
enum class Operator {
|
||||
kFprop,
|
||||
kDgrad,
|
||||
kWgrad
|
||||
enum class Operator {
|
||||
kFprop,
|
||||
kDgrad,
|
||||
kWgrad
|
||||
};
|
||||
|
||||
/// Distinguishes convolution from cross correlation
|
||||
enum class Mode {
|
||||
kCrossCorrelation,
|
||||
kConvolution
|
||||
/// Distinguishes convolution from cross correlation
|
||||
enum class Mode {
|
||||
kCrossCorrelation,
|
||||
kConvolution
|
||||
};
|
||||
|
||||
/// Selects among several implementation variants trading off performance with simplicity
|
||||
enum class IteratorAlgorithm {
|
||||
enum class IteratorAlgorithm {
|
||||
kAnalytic, ///< functionally correct in all cases but lower performance
|
||||
kOptimized, ///< optimized for R <= 32, S <= 32 and unity-stride dgrad
|
||||
kFixedChannels, ///< Analytic algorithm optimized for fixed channel count (C == AccessSize)
|
||||
@@ -113,9 +114,9 @@ enum class StrideSupport {
|
||||
};
|
||||
|
||||
/// Identifies split-K mode
|
||||
enum class SplitKMode {
|
||||
kNone,
|
||||
kSerial,
|
||||
enum class SplitKMode {
|
||||
kNone,
|
||||
kSerial,
|
||||
kParallel
|
||||
};
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ struct DefaultConv2dFpropWithBroadcast {
|
||||
AlignmentB
|
||||
>::Kernel;
|
||||
|
||||
// Replace epilogue
|
||||
// Define epilogue
|
||||
using Epilogue = typename cutlass::conv::kernel::detail::DefaultConvEpilogueWithBroadcastTensorOp<
|
||||
ArchTag,
|
||||
typename ImplicitGemmBase::Epilogue::Shape,
|
||||
|
||||
@@ -100,7 +100,7 @@ struct DefaultConv2dFpropWithReduction {
|
||||
AlignmentB
|
||||
>::Kernel;
|
||||
|
||||
// Replace epilogue
|
||||
// Define epilogue
|
||||
using Epilogue = typename cutlass::conv::kernel::detail::DefaultConvEpilogueWithReductionTensorOp<
|
||||
ArchTag,
|
||||
typename ImplicitGemmBase::Epilogue::Shape,
|
||||
|
||||
@@ -222,6 +222,138 @@ struct DefaultConv2dGroupFprop <
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Defines a kernel for Conv2dGroupFprop specialization for Analytic IteratorAlgorithm and
|
||||
/// 2 stage pipeline that supports all GroupMode.
|
||||
|
||||
template <
|
||||
typename ElementA,
|
||||
typename LayoutA,
|
||||
typename ElementB,
|
||||
typename LayoutB,
|
||||
typename ElementC,
|
||||
typename LayoutC,
|
||||
typename ElementAccumulator,
|
||||
typename ArchTag,
|
||||
typename ThreadblockShape,
|
||||
typename WarpShape,
|
||||
typename InstructionShape,
|
||||
typename EpilogueOutputOp,
|
||||
typename ThreadblockSwizzle,
|
||||
typename MathOperatorTag,
|
||||
conv::GroupMode GroupMode,
|
||||
conv::StrideSupport StrideSupport,
|
||||
int AlignmentA,
|
||||
int AlignmentB
|
||||
>
|
||||
struct DefaultConv2dGroupFprop <
|
||||
ElementA,
|
||||
LayoutA,
|
||||
ElementB,
|
||||
LayoutB,
|
||||
ElementC,
|
||||
LayoutC,
|
||||
ElementAccumulator,
|
||||
arch::OpClassTensorOp,
|
||||
ArchTag,
|
||||
ThreadblockShape,
|
||||
WarpShape,
|
||||
InstructionShape,
|
||||
EpilogueOutputOp,
|
||||
ThreadblockSwizzle,
|
||||
2,
|
||||
MathOperatorTag,
|
||||
GroupMode,
|
||||
IteratorAlgorithm::kAnalytic,
|
||||
StrideSupport,
|
||||
AlignmentA,
|
||||
AlignmentB
|
||||
> {
|
||||
|
||||
static_assert(std::is_same<LayoutA, cutlass::layout::TensorNHWC>::value,
|
||||
"Current group conv only support NHWC layout");
|
||||
static_assert(std::is_same<LayoutB, cutlass::layout::TensorNHWC>::value,
|
||||
"Current group conv only support NHWC layout");
|
||||
static_assert(std::is_same<LayoutC, cutlass::layout::TensorNHWC>::value,
|
||||
"Current group conv only support NHWC layout");
|
||||
|
||||
// Define the core components from GEMM
|
||||
using MmaCore = typename cutlass::gemm::threadblock::DefaultMmaCore<
|
||||
ThreadblockShape, WarpShape, InstructionShape, ElementA, layout::RowMajor,
|
||||
ElementB, layout::ColumnMajor, ElementAccumulator, layout::RowMajor, arch::OpClassTensorOp,
|
||||
2, MathOperatorTag>;
|
||||
|
||||
// Define iterators over tiles from the A operand
|
||||
using ThreadMapA = typename MmaCore::IteratorThreadMapA;
|
||||
using AccessTypeA = cutlass::AlignedArray<ElementA, AlignmentA>;
|
||||
using IteratorA =
|
||||
cutlass::conv::threadblock::TileIterator<
|
||||
cutlass::conv::threadblock::Conv2dFpropActivationTileAccessIteratorAnalytic<
|
||||
cutlass::MatrixShape<ThreadblockShape::kM, ThreadblockShape::kK>,
|
||||
ElementA, LayoutA,
|
||||
ThreadMapA,
|
||||
AccessTypeA,
|
||||
GroupMode
|
||||
>
|
||||
>;
|
||||
|
||||
using SmemIteratorA = typename MmaCore::SmemIteratorA;
|
||||
|
||||
// Define iterators over tiles from the B operand
|
||||
using ThreadMapB = typename MmaCore::IteratorThreadMapB;
|
||||
using AccessTypeB = cutlass::AlignedArray<ElementB, AlignmentB>;
|
||||
using IteratorB =
|
||||
cutlass::conv::threadblock::TileIterator<
|
||||
cutlass::conv::threadblock::Conv2dFpropFilterTileAccessIteratorAnalytic<
|
||||
cutlass::MatrixShape<ThreadblockShape::kK, ThreadblockShape::kN>,
|
||||
ElementB, LayoutB,
|
||||
ThreadMapB,
|
||||
AccessTypeB,
|
||||
GroupMode
|
||||
>
|
||||
>;
|
||||
|
||||
using SmemIteratorB = typename MmaCore::SmemIteratorB;
|
||||
|
||||
// Warp-level GEMM components
|
||||
using WarpMmaTensorOp = typename MmaCore::MmaTensorOp;
|
||||
using MmaPolicy = typename MmaCore::MmaPolicy;
|
||||
|
||||
// Define the Mma
|
||||
using Mma = threadblock::ImplicitGemmPipelined<
|
||||
ThreadblockShape,
|
||||
IteratorA,
|
||||
SmemIteratorA,
|
||||
IteratorB,
|
||||
SmemIteratorB,
|
||||
ElementC,
|
||||
LayoutC,
|
||||
MmaPolicy
|
||||
>;
|
||||
|
||||
static const int kPartitionsK = ThreadblockShape::kK / WarpShape::kK;
|
||||
|
||||
// Define the epilogue
|
||||
using Epilogue = typename detail::DefaultConvEpilogue<
|
||||
ArchTag,
|
||||
ThreadblockShape,
|
||||
WarpMmaTensorOp,
|
||||
kPartitionsK,
|
||||
EpilogueOutputOp
|
||||
>::Epilogue;
|
||||
|
||||
// Define the kernel
|
||||
using Kernel = cutlass::conv::kernel::ImplicitGemmConvolution<
|
||||
Mma,
|
||||
Epilogue,
|
||||
ThreadblockSwizzle,
|
||||
conv::Operator::kFprop,
|
||||
Conv2dProblemSize,
|
||||
GroupMode
|
||||
>;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Defines a kernel for Conv2dGroupFprop specialization for Optimized IteratorAlgorithm and multistage
|
||||
/// pipeline that supports GroupMode::kSingleGroup.
|
||||
template <
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user