CUTLASS 3.0 Hopper GEMMs are GETTs in disguise (#897)

This commit is contained in:
Vijay Thakkar
2023-03-29 10:42:40 -04:00
committed by GitHub
parent 1eef5c3cf1
commit 15d9d31f1f
10 changed files with 1231 additions and 71 deletions
@@ -0,0 +1,369 @@
/***************************************************************************************************
* 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.
*
**************************************************************************************************/
/*! \file
\brief GETT command line parser to gather semantic modes, their stride order, and extents.
*/
#pragma once
#include <iostream>
#include <iomanip>
#include <utility>
#include <type_traits>
#include <vector>
#include <map>
#include <algorithm>
#include <numeric>
#include "cutlass/util/command_line.h"
namespace cutlass {
// Output shortcuts
std::ostream& operator<<(std::ostream& os, std::vector<char> data) {
for (auto& a : data) os << a;
return os;
}
template <class T>
std::ostream& operator<<(std::ostream& os, std::vector<T> data) {
for (auto& a : data) os << a << " ";
return os;
}
struct GettCommandLine {
struct GettProblem {
using extent_type = int;
using stride_type = int64_t;
// Row modes: appear in A and C/D
std::vector<extent_type> M;
std::vector<stride_type> ldAm;
std::vector<stride_type> ldCm;
// Column modes: appear in B and C/D
std::vector<extent_type> N;
std::vector<stride_type> ldBn;
std::vector<stride_type> ldCn;
// Reduction modes: appear in A and B
std::vector<extent_type> K;
std::vector<stride_type> ldAk;
std::vector<stride_type> ldBk;
// Batch modes: appear in all in/out tensors
std::vector<extent_type> L;
std::vector<stride_type> ldAl;
std::vector<stride_type> ldBl;
std::vector<stride_type> ldCl;
};
static GettProblem
parse(int argc, char const* argv[], bool parse_verbose = false) {
using extent_type = typename GettProblem::extent_type;
using stride_type = typename GettProblem::stride_type;
cutlass::CommandLine cmd(argc, argv);
// modeA
std::vector<char> a_mode;
cmd.get_cmd_line_arguments("modeA", a_mode);
// modeB
std::vector<char> b_mode;
cmd.get_cmd_line_arguments("modeB", b_mode);
// modeC
std::vector<char> c_mode;
cmd.get_cmd_line_arguments("modeC", c_mode);
// mode_sizes
std::map<char,extent_type> mode_size;
// First, initialize all modes in a, b, c to make sure they're in map
for (char a : a_mode) mode_size[a] = 1;
for (char b : b_mode) mode_size[b] = 1;
for (char c : c_mode) mode_size[c] = 1;
// Then, overwrite the ones in -extent
std::vector<std::pair<std::string, std::string> > extent_tokens;
cmd.get_cmd_line_argument_pairs("extents", extent_tokens);
for (auto e : extent_tokens) {
if (std::get<0>(e).size() > 1) {
std::cerr << "ERROR: Mode name must only be 1 character long.\n";
print_usage();
exit(1);
}
char label = std::get<0>(e)[0];
int size = std::stoi(std::get<1>(e));
mode_size[label] = size;
}
// Print out symbolic modes and their extents
if (parse_verbose) {
std::cout << "C_" << c_mode << " = A_" << a_mode << " * B_" << b_mode << "\n";
for (auto e : mode_size) std::cout << " " << std::get<0>(e) << " : " << std::get<1>(e) << "\n";
}
//
// Collect/Compute strides
//
std::map<char,stride_type> mode_ldA;
std::map<char,stride_type> mode_ldB;
std::map<char,stride_type> mode_ldC;
{
stride_type current;
current = 1;
for (char a : a_mode) { mode_ldA[a] = current; current *= mode_size[a]; }
current = 1;
for (char b : b_mode) { mode_ldB[b] = current; current *= mode_size[b]; }
current = 1;
for (char c : c_mode) { mode_ldC[c] = current; current *= mode_size[c]; }
}
//
// Collect mode categories
//
std::vector<char> row_mode; // rows
std::vector<char> col_mode; // columns
std::vector<char> red_mode; // reductions
std::vector<char> bat_mode; // batches
{
std::vector<char> a_label = a_mode;
std::vector<char> b_label = b_mode;
std::vector<char> c_label = c_mode;
std::sort(std::begin(a_label), std::end(a_label));
std::sort(std::begin(b_label), std::end(b_label));
std::sort(std::begin(c_label), std::end(c_label));
// std::set_intersections to find semantic category of each symbolic mode
std::set_intersection(std::begin(a_label), std::end(a_label),
std::begin(c_label), std::end(c_label),
std::back_inserter(row_mode));
std::set_intersection(std::begin(b_label), std::end(b_label),
std::begin(c_label), std::end(c_label),
std::back_inserter(col_mode));
std::set_intersection(std::begin(a_label), std::end(a_label),
std::begin(b_label), std::end(b_label),
std::back_inserter(red_mode));
std::set_intersection(std::begin(row_mode), std::end(row_mode),
std::begin(col_mode), std::end(col_mode),
std::back_inserter(bat_mode));
// std::set_difference to remove batch modes from other semantic modes
for (char l : bat_mode) {
row_mode.erase(std::remove(std::begin(row_mode), std::end(row_mode), l), std::end(row_mode));
col_mode.erase(std::remove(std::begin(col_mode), std::end(col_mode), l), std::end(col_mode));
red_mode.erase(std::remove(std::begin(red_mode), std::end(red_mode), l), std::end(red_mode));
}
}
// Print out the semantic association of each symbolic mode
if (parse_verbose) {
std::cout << " rows : " << row_mode << '\n';
std::cout << " cols : " << col_mode << '\n';
std::cout << " reds : " << red_mode << '\n';
std::cout << " bats : " << bat_mode << '\n';
}
//
// Permute modes
//
// Permute the batched modes to promote coalescing
// Sort the batched modes by min(ldAl,ldBl) and tie-broken by the size
std::sort(std::begin(bat_mode), std::end(bat_mode), [&](char l1, char l2) {
return std::tie(std::min(mode_ldA[l1],mode_ldB[l1]),mode_size[l1])
< std::tie(std::min(mode_ldA[l2],mode_ldB[l2]),mode_size[l2]);
});
// Compute sizes and strides of ordered reduction modes
std::vector<extent_type> L;
std::vector<stride_type> ldAl;
std::vector<stride_type> ldBl;
std::vector<stride_type> ldCl;
for (char l : bat_mode) {
L.push_back(mode_size[l]);
ldAl.push_back(mode_ldA[l]);
ldBl.push_back(mode_ldB[l]);
ldCl.push_back(mode_ldC[l]);
}
// Permute the reduction modes to promote coalescing
// Sort the reduction modes by min(ldAk,ldBk) and tie-broken by the size
std::sort(std::begin(red_mode), std::end(red_mode), [&](char k1, char k2) {
return std::tie(std::min(mode_ldA[k1],mode_ldB[k1]),mode_size[k1])
< std::tie(std::min(mode_ldA[k2],mode_ldB[k2]),mode_size[k2]);
});
// Compute sizes and strides of ordered reduction modes
std::vector<extent_type> K;
std::vector<stride_type> ldAk;
std::vector<stride_type> ldBk;
for (char k : red_mode) {
K.push_back(mode_size[k]);
ldAk.push_back(mode_ldA[k]);
ldBk.push_back(mode_ldB[k]);
}
// Permute the row modes to promote coalescing
// Sort the row modes by min(ldAm,ldCm) and tie-broken by ldAm
std::sort(std::begin(row_mode), std::end(row_mode), [&](char m1, char m2) {
return std::tie(std::min(mode_ldA[m1],mode_ldC[m1]),mode_ldA[m1])
< std::tie(std::min(mode_ldA[m2],mode_ldC[m2]),mode_ldA[m2]);
});
// Compute sizes and strides of ordered row modes
std::vector<extent_type> M;
std::vector<stride_type> ldAm;
std::vector<stride_type> ldCm;
for (char m : row_mode) {
M.push_back(mode_size[m]);
ldAm.push_back(mode_ldA[m]);
ldCm.push_back(mode_ldC[m]);
}
// Permute the col modes to promote coalescing
// Sort the col modes by min(ldBn,ldCn) and tie-broken by ldBn
std::sort(std::begin(col_mode), std::end(col_mode), [&](char n1, char n2) {
return std::tie(std::min(mode_ldB[n1],mode_ldC[n1]),mode_ldB[n1])
< std::tie(std::min(mode_ldB[n2],mode_ldC[n2]),mode_ldB[n2]);
});
// Compute sizes and strides of ordered col modes
std::vector<extent_type> N;
std::vector<stride_type> ldBn;
std::vector<stride_type> ldCn;
for (char n : col_mode) {
N.push_back(mode_size[n]);
ldBn.push_back(mode_ldB[n]);
ldCn.push_back(mode_ldC[n]);
}
if (parse_verbose) {
std::cout << "C_";
if (! row_mode.empty()) {
std::cout << "(" << row_mode << ")";
}
if (! col_mode.empty()) {
std::cout << "(" << col_mode << ")";
}
if (! bat_mode.empty()) {
std::cout << "(" << bat_mode << ")";
}
std::cout << " = A_";
if (! row_mode.empty()) {
std::cout << "(" << row_mode << ")";
}
if (! red_mode.empty()) {
std::cout << "(" << red_mode << ")";
}
if (! bat_mode.empty()) {
std::cout << "(" << bat_mode << ")";
}
std::cout << " * B_";
if (! col_mode.empty()) {
std::cout << "(" << col_mode << ")";
}
if (! red_mode.empty()) {
std::cout << "(" << red_mode << ")";
}
if (! bat_mode.empty()) {
std::cout << "(" << bat_mode << ")";
}
std::cout << '\n';
int M_size = std::accumulate(std::begin(M), std::end(M), 1, std::multiplies<>{});
int N_size = std::accumulate(std::begin(N), std::end(N), 1, std::multiplies<>{});
int K_size = std::accumulate(std::begin(K), std::end(K), 1, std::multiplies<>{});
int L_size = std::accumulate(std::begin(L), std::end(L), 1, std::multiplies<>{});
std::cout << " M : (" << M_size << ") ";
for (char m : row_mode) std::cout << m << ":" << mode_size[m] << " ";
std::cout << '\n';
std::cout << " N : (" << N_size << ") ";
for (char n : col_mode) std::cout << n << ":" << mode_size[n] << " ";
std::cout << '\n';
std::cout << " K : (" << K_size << ") ";
for (char k : red_mode) std::cout << k << ":" << mode_size[k] << " ";
std::cout << '\n';
std::cout << " L : (" << L_size << ") ";
for (char l : bat_mode) std::cout << l << ":" << mode_size[l] << " ";
std::cout << '\n';
std::cout << " ldAm : " << ldAm << '\n';
std::cout << " ldAk : " << ldAk << '\n';
std::cout << " ldAl : " << ldAl << '\n';
std::cout << " ldBn : " << ldBn << '\n';
std::cout << " ldBk : " << ldBk << '\n';
std::cout << " ldBl : " << ldBl << '\n';
std::cout << " ldCm : " << ldCm << '\n';
std::cout << " ldCn : " << ldCn << '\n';
std::cout << " ldCl : " << ldCl << '\n';
}
return {M, ldAm, ldCm,
N, ldBn, ldCn,
K, ldAk, ldBk,
L, ldAl, ldBl, ldCl};
}
static void
print_usage() {
std::cout <<
"GETT problem command line parser:\n"
" --modeA=<m0,...>\n"
" A comma delimited list of characters that correspond to the row, reduction, and batch modes in A tensor.\n"
" The semantic association of each symbolic mode is determined automatically.\n\n"
" --modeB=<m0,...>\n"
" A comma delimited list of characters that correspond to the column, reduction, and batch modes in B tensor.\n"
" The semantic association of each symbolic mode is determined automatically.\n\n"
" --modeC=<m0,...>\n"
" A comma delimited list of characters that correspond to the row, column, and batch modes in B tensor.\n"
" The semantic association of each symbolic mode is determined automatically.\n\n"
" --extents=<mode:extent,....>\n"
" A command delimited list of symbolic mode and its corresponding extent.\n"
" Extents are defaulted to 1 if any are not provided.\n\n"
"Example usage: gett.exe --modeC=m,n,l --modeA=m,k,l --modeB=k,n,l --extent=m:4096,n:4096,k:4096\n";
}
};
} // namespace cutlass
+106 -52
View File
@@ -60,7 +60,7 @@ struct matrix_inf_norm_result {
// and thus are best passed by reference or const reference.
template <typename EngineType, typename LayoutType>
matrix_inf_norm_result
matrix_inf_norm(const cute::Tensor<EngineType, LayoutType>& host_matrix)
matrix_inf_norm(cute::Tensor<EngineType, LayoutType> const& host_matrix)
{
using std::abs;
using error_type = decltype(std::declval<matrix_inf_norm_result>().inf_norm);
@@ -68,17 +68,14 @@ matrix_inf_norm(const cute::Tensor<EngineType, LayoutType>& host_matrix)
error_type inf_norm = 0.0;
bool found_nan = false;
const auto shape = host_matrix.shape();
using index_type = std::decay_t<decltype(cute::get<0>(shape))>;
// Computing the infinity norm requires that we be able
// to treat the input as a matrix, with rows and columns.
static_assert(std::is_integral_v<index_type>);
const index_type num_rows = cute::get<0>(shape);
const index_type num_cols = cute::get<1>(shape);
const int64_t num_rows = cute::size<0>(host_matrix);
const int64_t num_cols = cute::size<1>(host_matrix);
for(index_type i = 0; i < num_rows; ++i) {
for(int64_t i = 0; i < num_rows; ++i) {
error_type row_abs_sum = 0.0;
for(index_type j = 0; j < num_cols; ++j) {
for(int64_t j = 0; j < num_cols; ++j) {
row_abs_sum += abs(host_matrix(i, j));
}
if(std::isnan(row_abs_sum)) {
@@ -94,39 +91,27 @@ matrix_inf_norm(const cute::Tensor<EngineType, LayoutType>& host_matrix)
// Infinity norm of (X - Y).
template <typename EngineType, typename LayoutType>
matrix_inf_norm_result
matrix_diff_inf_norm(const cute::Tensor<EngineType, LayoutType>& X,
const cute::Tensor<EngineType, LayoutType>& Y)
matrix_diff_inf_norm(cute::Tensor<EngineType, LayoutType> const& X,
cute::Tensor<EngineType, LayoutType> const& Y)
{
using std::abs;
using error_type = decltype(std::declval<matrix_inf_norm_result>().inf_norm);
const auto X_shape = X.shape();
const auto Y_shape = Y.shape();
assert(cute::size<0>(X) == cute::size<0>(Y));
assert(cute::size<1>(X) == cute::size<1>(Y));
using index_type = std::decay_t<decltype(cute::get<0>(X_shape))>;
// Computing the infinity norm requires that we be able
// to treat the input as a matrix, with rows and columns.
static_assert(std::is_integral_v<index_type>);
const index_type num_rows = cute::get<0>(X_shape);
const index_type num_cols = cute::get<1>(X_shape);
assert(num_rows == cute::get<0>(Y_shape));
assert(num_cols == cute::get<1>(Y_shape));
auto matrix_ij = [&](const auto& A, std::size_t i, std::size_t j) {
return A(i, j);
};
auto diff_ij = [&](std::size_t i, std::size_t j) {
return matrix_ij(X, i, j) - matrix_ij(Y, i, j);
};
const int64_t num_rows = cute::size<0>(X);
const int64_t num_cols = cute::size<1>(X);
error_type inf_norm = 0.0;
bool found_nan = false;
for(index_type i = 0; i < num_rows; ++i) {
for(int64_t i = 0; i < num_rows; ++i) {
error_type row_abs_sum = 0.0;
for(index_type j = 0; j < num_cols; ++j) {
row_abs_sum += abs(diff_ij(i, j));
for(int64_t j = 0; j < num_cols; ++j) {
row_abs_sum += abs(X(i,j) - Y(i,j));
}
if(std::isnan(row_abs_sum)) {
found_nan = true;
@@ -140,22 +125,22 @@ matrix_diff_inf_norm(const cute::Tensor<EngineType, LayoutType>& X,
template <typename EngineType_A, typename LayoutType_A,
typename EngineType_B, typename LayoutType_B,
typename EngineType_C_computed, typename LayoutType_C_computed,
typename EngineType_C_expected, typename LayoutType_C_expected>
typename EngineType_C, typename LayoutType_C,
typename EngineType_C_ref, typename LayoutType_C_ref>
void
print_matrix_multiply_mollified_relative_error(
const char A_value_type_name[],
const cute::Tensor<EngineType_A, LayoutType_A>& A,
const char B_value_type_name[],
const cute::Tensor<EngineType_B, LayoutType_B>& B,
const char C_value_type_name[],
const cute::Tensor<EngineType_C_computed, LayoutType_C_computed>& C_computed,
const cute::Tensor<EngineType_C_expected, LayoutType_C_expected>& C_expected)
char const A_value_type_name[],
cute::Tensor<EngineType_A, LayoutType_A> const& A,
char const B_value_type_name[],
cute::Tensor<EngineType_B, LayoutType_B> const& B,
char const C_value_type_name[],
cute::Tensor<EngineType_C, LayoutType_C> const& C,
cute::Tensor<EngineType_C_ref, LayoutType_C_ref> const& C_ref)
{
const auto [A_norm, A_has_nan] = matrix_inf_norm(A);
const auto [B_norm, B_has_nan] = matrix_inf_norm(B);
const auto [C_norm, C_has_nan] = matrix_inf_norm(C_expected);
const auto [diff_norm, diff_has_nan] = matrix_diff_inf_norm(C_computed, C_expected);
const auto [C_norm, C_has_nan] = matrix_inf_norm(C_ref);
const auto [diff_norm, diff_has_nan] = matrix_diff_inf_norm(C, C_ref);
const auto A_norm_times_B_norm = A_norm * B_norm;
const auto relative_error = A_norm_times_B_norm == 0.0 ?
@@ -164,18 +149,19 @@ print_matrix_multiply_mollified_relative_error(
// For expected error bounds, please refer to the LAPACK Users' Guide,
// in particular https://netlib.org/lapack/lug/node108.html .
// Printing the infinity norm of C is a way to check
// that both the function being tested (C_computed)
// and the reference implementation (C_expected)
// that both the function being tested (C)
// and the reference implementation (C_ref)
// don't just do nothing (or fill with zeros).
using std::cout;
cout << "Value type of A: " << A_value_type_name << '\n'
using cute::shape;
cout << "Matrix A: " << shape<0>(A) << "x" << shape<1>(A) << " of " << A_value_type_name << '\n'
<< "Matrix B: " << shape<0>(B) << "x" << shape<1>(B) << " of " << B_value_type_name << '\n'
<< "Matrix C: " << shape<0>(C) << "x" << shape<1>(C) << " of " << C_value_type_name << '\n'
<< std::scientific
<< "Infinity norm of A: " << A_norm << '\n'
<< "Value type of B: " << B_value_type_name << '\n'
<< "Infinity norm of B: " << B_norm << '\n'
<< "Value type of C: " << C_value_type_name << '\n'
<< "Infinity norm of C_expected: " << C_norm << '\n'
<< "Infinity norm of (C_computed - C_expected): " << diff_norm << '\n';
<< "Infinity norm of C: " << C_norm << '\n'
<< "Infinity norm of (C - C_ref): " << diff_norm << '\n';
if(A_norm_times_B_norm == 0.0) {
cout << "Mollified relative error: " << relative_error << '\n';
@@ -183,11 +169,12 @@ print_matrix_multiply_mollified_relative_error(
cout << "Relative error: " << relative_error << '\n';
}
cout << "Did we encounter NaN in A? " << (A_has_nan ? "yes" : "no") << '\n'
<< "Did we encounter NaN in B? " << (B_has_nan ? "yes" : "no") << '\n'
<< "Did we encounter NaN in C_expected? " << (C_has_nan ? "yes" : "no") << '\n'
<< "Did we encounter NaN in (C_computed - C_expected)? "
<< (diff_has_nan ? "yes" : "no") << '\n';
if (A_has_nan || B_has_nan || C_has_nan || diff_has_nan) {
cout << "Did we encounter NaN in A? " << (A_has_nan ? "yes" : "no") << '\n'
<< "Did we encounter NaN in B? " << (B_has_nan ? "yes" : "no") << '\n'
<< "Did we encounter NaN in C? " << (C_has_nan ? "yes" : "no") << '\n'
<< "Did we encounter NaN in (C - C_ref)? " << (diff_has_nan ? "yes" : "no") << '\n';
}
}
template <typename EngineType, typename LayoutType>
@@ -233,3 +220,70 @@ auto host_matrix_to_const_cute_tensor(CutlassHostTensorType& X)
auto X_data_const = const_cast<std::add_const_t< decltype(X_data)> >(X_data);
return cute::make_tensor(X_data_const, layout);
};
template <typename T1, typename T2>
double
print_relative_error(
std::size_t n,
T1 const& data,
T2 const& reference,
bool print_verbose = false,
bool print_error = true) {
using std::abs; using std::sqrt;
// Use either double or complex<double> for error computation
using value_type = cute::remove_cvref_t<decltype(reference[0])>;
using error_type = std::conditional_t<cute::is_complex<value_type>::value,
cute::complex<double>,
double>;
if (print_verbose) {
std::cout << "Idx:\t"<< "Val\t" << "RefVal\t" << "RelError" << std::endl;
}
double eps = 1e-200;
double tot_error_sq = 0;
double tot_norm_sq = 0;
double tot_ind_rel_err = 0;
double max_ind_rel_err = 0;
for (std::size_t i = 0; i < n; ++i)
{
error_type val = data[i];
error_type ref = reference[i];
double aref = abs(ref);
double diff = abs(ref - val);
double rel_error = diff / (aref + eps);
// Individual relative error
tot_ind_rel_err += rel_error;
// Maximum relative error
max_ind_rel_err = std::max(max_ind_rel_err, rel_error);
// Total relative error
tot_error_sq += diff * diff;
tot_norm_sq += aref * aref;
if (print_verbose) {
std::cout << i << ":\t" << val << "\t" << ref << "\t" << rel_error << std::endl;
}
}
printf("Vector reference norm: [%.5e]\n", sqrt(tot_norm_sq));
double tot_rel_err = sqrt(tot_error_sq/(tot_norm_sq+eps));
if (print_error)
printf("Vector relative error: [%.5e]\n", tot_rel_err);
double ave_rel_err = tot_ind_rel_err / double(n);
if (print_error)
printf("Average relative error: [%.5e]\n", ave_rel_err);
if (print_error)
printf("Maximum relative error: [%.5e]\n", max_ind_rel_err);
return tot_rel_err;
}
@@ -0,0 +1,146 @@
/***************************************************************************************************
* 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.
*
**************************************************************************************************/
/*! \file
\brief GETT device reference code
*/
#pragma once
#include <cute/tensor.hpp>
namespace cutlass::reference::device {
template <
class ATensor,
class BTensor,
class CTensor,
class DTensor,
class ElementAccumulator,
class ElementEpilogue>
__global__ static
void
gett_kernel(
DTensor D,
ATensor const A,
BTensor const B,
CTensor const C,
ElementEpilogue alpha, ElementEpilogue beta,
ElementAccumulator acc_init)
{
using namespace cute;
static_assert(DTensor::rank == 3, "(M,N,L)");
static_assert(ATensor::rank == 3, "(M,K,L)");
static_assert(BTensor::rank == 3, "(N,K,L)");
static_assert(CTensor::rank == 3, "(M,N,L)");
assert(size<0>(A) == size<0>(D)); // M
assert(size<0>(C) == size<0>(D)); // M
assert(size<0>(B) == size<1>(D)); // N
assert(size<1>(C) == size<1>(D)); // N
assert(size<1>(A) == size<1>(B)); // K
assert(size<2>(A) == size<2>(D)); // L
assert(size<2>(B) == size<2>(D)); // L
assert(size<2>(C) == size<2>(D)); // L
NumericConverter<ElementAccumulator, typename ATensor::value_type> a_converter;
NumericConverter<ElementAccumulator, typename BTensor::value_type> b_converter;
NumericConverter<ElementEpilogue, ElementAccumulator> acc_converter;
NumericConverter<ElementEpilogue, typename CTensor::value_type> source_converter;
NumericConverter<typename DTensor::value_type, ElementEpilogue> output_converter;
// Thread id to each element of D
for (int tid = threadIdx.x + blockDim.x * blockIdx.x;
tid < size(D);
tid += blockDim.x * gridDim.x) {
// (m,n,l) coordinate
auto mnl_coord = idx2crd(tid, product_each(shape(D)));
auto m = get<0>(mnl_coord);
auto n = get<1>(mnl_coord);
auto l = get<2>(mnl_coord);
auto A_ml = A(m,_,l);
auto B_nl = B(n,_,l);
ElementAccumulator accum = ElementAccumulator(0);
for (int k = 0; k < size<1>(A); ++k) {
ElementAccumulator a = a_converter(A_ml(k));
ElementAccumulator b = b_converter(B_nl(k));
accum += a * b;
}
ElementEpilogue scaled_output = (alpha * acc_converter(accum)) + (beta * source_converter(C(m,n,l)));
D(m,n,l) = output_converter(scaled_output);
}
}
// Most general version
template <
class ProblemShapeMNKL,
class ElementA,
class StrideA,
class ElementB,
class StrideB,
class ElementAccumulator,
class ElementC,
class StrideC,
class ElementD,
class StrideD,
class ElementEpilogue>
void
gett(
ProblemShapeMNKL problem_shape_mnkl,
ElementA const* ptr_A, StrideA stride_a_mkl,
ElementB const* ptr_B, StrideB stride_b_nkl,
ElementAccumulator _,
ElementC const* ptr_C, StrideC stride_c_mnl,
ElementD * ptr_D, StrideD stride_d_mnl,
ElementEpilogue alpha, ElementEpilogue beta,
cudaStream_t stream = 0) {
using namespace cute;
static_assert(rank(ProblemShapeMNKL{}) == 4);
auto M = get<0>(problem_shape_mnkl);
auto N = get<1>(problem_shape_mnkl);
auto K = get<2>(problem_shape_mnkl);
auto L = get<3>(problem_shape_mnkl);
// Represent the full tensors
auto A = make_tensor(make_gmem_ptr(ptr_A), make_shape(M,K,L), stride_a_mkl); // (M,K,L)
auto B = make_tensor(make_gmem_ptr(ptr_B), make_shape(N,K,L), stride_b_nkl); // (N,K,L)
auto C = make_tensor(make_gmem_ptr(ptr_C), make_shape(M,N,L), stride_c_mnl); // (M,N,L)
auto D = make_tensor(make_gmem_ptr(ptr_D), make_shape(M,N,L), stride_d_mnl); // (M,N,L)
dim3 dimBlock(256);
dim3 dimGrid(240);
gett_kernel<<< dimGrid, dimBlock, 0, stream >>>(D, A, B, C, alpha, beta, ElementAccumulator(0));
}
} // namespace cutlass::reference::device