/*************************************************************************************************** * 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 Epilogue for threadblock scoped GEMMs using Tensor Ops. The epilogue rearranges the result of a matrix product through shared memory to match canonical tensor layouts in global memory. Epilogues support conversion and reduction operations. */ #pragma once #include "cutlass/cutlass.h" #include "cutlass/numeric_types.h" #include "cutlass/array.h" #include "cutlass/layout/matrix.h" #include "cutlass/layout/tensor.h" #include "cutlass/matrix_shape.h" #include "cutlass/tensor_ref.h" #include "cutlass/transform/pitch_linear_thread_map.h" #include "cutlass/epilogue/threadblock/output_tile_thread_map.h" #include "cutlass/arch/arch.h" #include "cutlass/arch/memory.h" #include "cutlass/epilogue/threadblock/predicated_tile_iterator_params.h" //////////////////////////////////////////////////////////////////////////////// namespace cutlass { //////////////////////////////////////////////////////////////////////////////// namespace epilogue { namespace threadblock { //////////////////////////////////////////////////////////////////////////////// /// Tile iterator used to load and store output tile from global memory in epilogue. /// /// Satisfies: ReadableTileIterator | PredicatedTileIterator | ForwardTileIterator /// template < typename ThreadMap_, ///< Thread map (conept: OutputTileThreadMap) typename Element_, ///< Element data type BlasMode BlasMode_ = BlasMode::kGemm ///< Tile Iterator for a Symmetric or Hermitian Kernel > class PredicatedTileIteratorBlas3 { public: using ThreadMap = ThreadMap_; using Shape = typename ThreadMap::Shape; using Element = Element_; using Layout = layout::RowMajor; using TensorRef = TensorRef; using ConstTensorRef = typename TensorRef::ConstTensorRef; using Index = typename Layout::Index; using LongIndex = typename Layout::LongIndex; using TensorCoord = MatrixCoord; static BlasMode const kBlasMode = BlasMode_; static int const kElementsPerAccess = ThreadMap::kElementsPerAccess; static int const kThreads = ThreadMap::kThreads; static int const kIterations = ThreadMap::Count::kTile; static_assert( ThreadMap::Iterations::kRow > 0,"ThreadMap::Iterations::kRow must be > 0"); static_assert( ThreadMap::Iterations::kGroup > 0,"ThreadMap::Iterations::kGroup must be > 0"); static_assert( ThreadMap::Iterations::kCluster > 0,"ThreadMap::Iterations::kCluster must be > 0"); static_assert( ThreadMap::Iterations::kColumn > 0,"ThreadMap::Iterations::kColumn must be > 0"); /// Fragment object using Fragment = Array< Element, ThreadMap::Iterations::kColumn * ThreadMap::Iterations::kRow * ThreadMap::Iterations::kGroup * ThreadMap::Iterations::kCluster * ThreadMap::kElementsPerAccess>; /// Memory access size using AccessType = AlignedArray; static_assert( AccessType::kElements == 1, "BLAS3 Epilogue must use AccessType::kElements as 1"); // // Parameters struct // /// Uses a non-template class struct Params : PredicatedTileIteratorParams { CUTLASS_HOST_DEVICE Params() { } CUTLASS_HOST_DEVICE Params(Layout const &layout): PredicatedTileIteratorParams( layout.stride(0) * int(sizeof(AccessType)) / kElementsPerAccess, make_OutputTileThreadMapDesc() ) { } }; /// Mask object struct Mask { static int const kCount = ThreadMap::Iterations::kColumn; /// Predicate state bool predicates[kCount]; // // Mask // CUTLASS_HOST_DEVICE Mask() { enable(); } ///< Efficiently disables all accesses guarded by mask CUTLASS_HOST_DEVICE void clear() { CUTLASS_PRAGMA_UNROLL for (int i = 0; i < kCount; ++i) { predicates[i] = false; } } ///< CUTLASS_HOST_DEVICE enables all accesses guarded by mask CUTLASS_DEVICE void enable() { CUTLASS_PRAGMA_UNROLL for (int i = 0; i < kCount; ++i) { predicates[i] = true; } } }; private: // // Data members // /// Parameters structure containing reference and precomputed state. PredicatedTileIteratorParams params_; /// Byte-level pointer uint8_t *byte_pointer_; /// Fill Mode for a tile on diagonal of a symmetric kernel cutlass::FillMode fill_mode; /// Array of boolean values to contain steady-state predicates Mask mask_; /// Extent of the matrix tile in rows Index extent_row_; /// A thread's starting row position (assuming steady-state predicates have been computed) Index thread_start_row_; /// Internal state counter int state_[3]; /// Starting address of the matrix size_t matrix_start_addr; static_assert((kBlasMode == BlasMode::kSymmetric || kBlasMode == BlasMode::kHermitian), "Unsupported blas3 mode."); private: // // Methods // public: // // Methods // /// Constructor CUTLASS_DEVICE PredicatedTileIteratorBlas3( PredicatedTileIteratorParams const & params, Element *pointer, TensorCoord extent, int thread_idx, TensorCoord threadblock_offset , cutlass::FillMode fill_mode ): params_(params), fill_mode(fill_mode) { TensorCoord thread_offset = ThreadMap::initial_offset(thread_idx) + threadblock_offset; extent_row_ = extent.row(); thread_start_row_ = thread_offset.row(); // Initialize predicates CUTLASS_PRAGMA_UNROLL for (int c = 0; c < ThreadMap::Iterations::kColumn; ++c) { mask_.predicates[c] = ((thread_offset.column() + ThreadMap::Delta::kColumn * c) < extent.column()); } // Check Symmetric kernel modes (Lower and Upper - for diagonal CTAs, None for rest CTAs) if ((kBlasMode == BlasMode::kSymmetric || kBlasMode == BlasMode::kHermitian) && fill_mode == cutlass::FillMode::kInvalid) { arch::device_breakpoint(); } // Starting address of the matrix matrix_start_addr = reinterpret_cast(pointer); // Initialize pointer byte_pointer_ = reinterpret_cast(pointer) + LongIndex(thread_offset.row()) * LongIndex(params_.stride) + LongIndex(thread_offset.column()) * sizeof(AccessType) / kElementsPerAccess; // Initialize internal state counter state_[0] = state_[1] = state_[2] = 0; } /// Adds a pointer offset in units of Element CUTLASS_HOST_DEVICE void add_pointer_offset(LongIndex pointer_offset) { byte_pointer_ += pointer_offset * sizeof_bits::value / 8; } /// Loads a fragment from memory CUTLASS_DEVICE void load_with_byte_offset(Fragment &frag, int64_t byte_offset) { uint8_t *byte_pointer = byte_pointer_; AccessType *frag_ptr = reinterpret_cast(&frag); CUTLASS_PRAGMA_UNROLL for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { CUTLASS_PRAGMA_UNROLL for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { CUTLASS_PRAGMA_UNROLL for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { int frag_row_idx = (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + cluster * ThreadMap::Delta::kCluster; bool row_guard = ((row_offset + thread_start_row_) < extent_row_); AccessType *memory_pointer = reinterpret_cast(byte_pointer + byte_offset); CUTLASS_PRAGMA_UNROLL for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { bool guard = row_guard && mask_.predicates[column]; cutlass::arch::global_load< AccessType, sizeof(AccessType) >( frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], (void *)&memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess], guard); } if (row + 1 < ThreadMap::Iterations::kRow) { byte_pointer += params_.increment_row; } } if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } } if (cluster + 1 < ThreadMap::Iterations::kCluster) { byte_pointer += params_.increment_cluster; } } } /// Loads a fragment on the diagonal of a symmetric kernel to memory CUTLASS_DEVICE void load_symmetric_with_byte_offset(Fragment &frag, int64_t byte_offset) { uint8_t *byte_pointer = byte_pointer_; AccessType *frag_ptr = reinterpret_cast(&frag); bool isLowerMode = (fill_mode == cutlass::FillMode::kLower) ? true : false; CUTLASS_PRAGMA_UNROLL for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { CUTLASS_PRAGMA_UNROLL for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { CUTLASS_PRAGMA_UNROLL for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { int frag_row_idx = (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + cluster * ThreadMap::Delta::kCluster; bool row_guard = ((row_offset + thread_start_row_) < extent_row_); AccessType *memory_pointer = reinterpret_cast(byte_pointer + byte_offset); // Offset of row from beginning of the matrix per thread size_t row_start_offset = (size_t)memory_pointer - matrix_start_addr; // Absolute row index int row_index = int(row_start_offset/params_.stride); CUTLASS_PRAGMA_UNROLL for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { bool guard = row_guard && mask_.predicates[column]; // Offset of column from beginning of row per thread size_t col_start_offset = row_start_offset + (column * ThreadMap::Delta::kColumn / kElementsPerAccess) * sizeof(AccessType); // Absolute column index size_t col_index = (col_start_offset%params_.stride)/sizeof(AccessType); guard = guard && ( (isLowerMode && row_index >= col_index) || (!isLowerMode && row_index <= col_index) ); cutlass::arch::global_load< AccessType, sizeof(AccessType) >( frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], (void *)&memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess], guard); // The imaginary parts of the diagonal elements of a complex element are assumed and set to zero if (guard && kBlasMode == BlasMode::kHermitian && cutlass::is_complex::value) { Element *scalar_ptr = reinterpret_cast(frag_ptr); if (row_index == col_index) { scalar_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column] = real(scalar_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column]); } } } if (row + 1 < ThreadMap::Iterations::kRow) { byte_pointer += params_.increment_row; } } if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } } if (cluster + 1 < ThreadMap::Iterations::kCluster) { byte_pointer += params_.increment_cluster; } } } /// Loads a fragment from memory CUTLASS_DEVICE void load(Fragment &frag) { if (fill_mode == cutlass::FillMode::kNone) { load_with_byte_offset(frag, 0); } else { load_symmetric_with_byte_offset(frag, 0); } } /// Stores a fragment to memory CUTLASS_DEVICE void store_with_byte_offset(Fragment const &frag, int64_t byte_offset) { uint8_t *byte_pointer = byte_pointer_; AccessType const *frag_ptr = reinterpret_cast(&frag); CUTLASS_PRAGMA_UNROLL for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { CUTLASS_PRAGMA_UNROLL for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { CUTLASS_PRAGMA_UNROLL for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { int frag_row_idx = (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + cluster * ThreadMap::Delta::kCluster; bool row_guard = ((row_offset + thread_start_row_) < extent_row_); AccessType *memory_pointer = reinterpret_cast(byte_pointer + byte_offset); CUTLASS_PRAGMA_UNROLL for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { bool guard = row_guard && mask_.predicates[column]; cutlass::arch::global_store( frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], (void *)&memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess], guard); } if (row + 1 < ThreadMap::Iterations::kRow) { byte_pointer += params_.increment_row; } } if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } } if (cluster + 1 < ThreadMap::Iterations::kCluster) { byte_pointer += params_.increment_cluster; } } } /// Stores a fragment on the diagonal of a symmetric kernel to memory CUTLASS_DEVICE void store_symmetric_with_byte_offset(Fragment const &frag, int64_t byte_offset) { uint8_t *byte_pointer = byte_pointer_; AccessType const *frag_ptr = reinterpret_cast(&frag); bool isLowerMode = (fill_mode == cutlass::FillMode::kLower) ? true : false; CUTLASS_PRAGMA_UNROLL for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { CUTLASS_PRAGMA_UNROLL for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { CUTLASS_PRAGMA_UNROLL for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { int frag_row_idx = (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + cluster * ThreadMap::Delta::kCluster; bool row_guard = ((row_offset + thread_start_row_) < extent_row_); AccessType *memory_pointer = reinterpret_cast(byte_pointer + byte_offset); // Offset of row from beginning of the matrix per thread size_t row_start_offset = (size_t)memory_pointer - matrix_start_addr; // Absolute row index int row_index = int(row_start_offset/params_.stride); CUTLASS_PRAGMA_UNROLL for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { bool guard = row_guard && mask_.predicates[column]; // Offset of column from beginning of row per thread size_t col_start_offset = row_start_offset + (column * ThreadMap::Delta::kColumn / kElementsPerAccess) * sizeof(AccessType); // Absolute column index size_t col_index = (col_start_offset%params_.stride)/sizeof(AccessType); guard = guard && ( (isLowerMode && row_index >= col_index) || (!isLowerMode && row_index <= col_index) ); // The imaginary parts of the diagonal elements of a complex element are assumed and set to zero if (guard && kBlasMode == BlasMode::kHermitian && cutlass::is_complex::value) { AccessType *frag_ptr_modify = const_cast(frag_ptr); Element *scalar_ptr = reinterpret_cast(frag_ptr_modify); if (row_index == col_index) { scalar_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column] = real(scalar_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column]); } } cutlass::arch::global_store( frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], (void *)&memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess], guard); } if (row + 1 < ThreadMap::Iterations::kRow) { byte_pointer += params_.increment_row; } } if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } } if (cluster + 1 < ThreadMap::Iterations::kCluster) { byte_pointer += params_.increment_cluster; } } } /// Stores a fragment to memory CUTLASS_DEVICE void store(Fragment const &frag) { if (fill_mode == cutlass::FillMode::kNone) { store_with_byte_offset(frag, 0); } else { store_symmetric_with_byte_offset(frag, 0); } } /// Advances to the next position to load or store CUTLASS_HOST_DEVICE PredicatedTileIteratorBlas3 &operator++() { ++state_[0]; byte_pointer_ += params_.advance_row; thread_start_row_ += ThreadMap::Shape::kRow; if (state_[0] == ThreadMap::Count::kRow) { state_[0] = 0; ++state_[1]; byte_pointer_ += params_.advance_group; thread_start_row_ += (ThreadMap::Shape::kGroup - 1) * ThreadMap::Shape::kRow * ThreadMap::Count::kRow; if (state_[1] == ThreadMap::Count::kGroup) { state_[1] = 0; ++state_[2]; byte_pointer_ += params_.advance_cluster; thread_start_row_ += ThreadMap::Count::kGroup * ThreadMap::Shape::kGroup * ThreadMap::Count::kRow * ThreadMap::Shape::kRow; if (state_[2] == ThreadMap::Count::kCluster) { state_[2] = 0; byte_pointer_ += params_.advance_tile; } } } return *this; } ///< Efficiently disables all accesses guarded by mask CUTLASS_DEVICE void clear_mask() { mask_.clear(); } ///< Efficiently enables all accesses guarded by mask CUTLASS_DEVICE void enable_mask() { mask_.enable(); } ///< Sets the mask CUTLASS_DEVICE void get_mask(Mask &mask) { mask = mask_; } ///< Sets the mask CUTLASS_DEVICE void set_mask(Mask const &mask) { mask_ = mask; } }; /////////////////////////////////////////////////////////////////////////////// } // namespace threadblock } // namespace epilogue } // namespace cutlass ////////////////////////////////////////////////////////////////////////////////