CUTLASS 3.7 (#2045)

* CUTLASS 3.7

* clean up changelog

---------

Co-authored-by: yuzhai <yuzhai@nvidia.com>
Co-authored-by: Haicheng Wu <haichengw@nvidia.com>
This commit is contained in:
Yujia Zhai
2025-01-18 09:53:07 -05:00
committed by GitHub
co-authored by yuzhai Haicheng Wu
parent 902dff3663
commit b78588d163
2030 changed files with 8947 additions and 3475 deletions
@@ -0,0 +1,163 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 Distributed gemm device layer helpers.
*/
#pragma once
#include "cute/layout.hpp"
#include "cute/tensor.hpp"
#include "cutlass/cutlass.h"
///////////////////////////////////////////////////////////////////////////////
namespace cutlass::distributed::device::detail {
cutlass::Status check_cuda_status(cudaError_t status) {
if (status != cudaSuccess) {
auto result = cudaGetLastError();
CUTLASS_TRACE_HOST(" error message: " << cudaGetErrorString(result));
return cutlass::Status::kErrorInternal;
}
return cutlass::Status::kSuccess;
}
// DistGemmBufferHelper computes required buffer size and offsets for GEMM operands.
template <
typename Tiler_,
typename ElementA_,
typename ElementB_,
typename ElementC_,
typename ElementD_>
struct DistGemmBufferHelper {
using Tiler = Tiler_;
using ElementA = ElementA_;
using ElementB = ElementB_;
using ElementC = ElementC_;
using ElementD = ElementD_;
static constexpr int NumBuffersA = Tiler::NumBuffersA;
static constexpr int NumBuffersB = Tiler::NumBuffersB;
static constexpr int NumBuffersC = Tiler::NumBuffersC;
static constexpr int NumBuffersD = Tiler::NumBuffersD;
template <typename ProblemShape>
static auto
get_buffer_size_a(ProblemShape problem_shape) {
auto a_buffer_layout = cute::make_layout(
cute::make_shape(NumBuffersA, Tiler::get_local_a_shape(problem_shape), sizeof(ElementA))
);
return size(a_buffer_layout);
}
template <typename ProblemShape>
static auto
get_buffer_size_b(ProblemShape problem_shape) {
auto b_buffer_layout = cute::make_layout(
cute::make_shape(NumBuffersB, Tiler::get_local_b_shape(problem_shape), sizeof(ElementB))
);
return size(b_buffer_layout);
}
template <typename ProblemShape>
static auto
get_buffer_size_c(ProblemShape problem_shape) {
auto c_buffer_layout = cute::make_layout(
cute::make_shape(NumBuffersC, Tiler::get_local_c_shape(problem_shape), sizeof(ElementC))
);
return size(c_buffer_layout);
}
template <typename ProblemShape>
static auto
get_buffer_size_d(ProblemShape problem_shape) {
auto d_buffer_layout = cute::make_layout(
cute::make_shape(NumBuffersD, Tiler::get_local_d_shape(problem_shape), sizeof(ElementD))
);
return size(d_buffer_layout);
}
template <typename ProblemShape>
static auto
get_buffer_size(ProblemShape problem_shape) {
size_t buffer_size = 0;
if constexpr (NumBuffersA > 0) {
buffer_size += get_buffer_size_a(problem_shape);
}
if constexpr (NumBuffersB > 0) {
buffer_size += get_buffer_size_b(problem_shape);
}
if constexpr (NumBuffersC > 0) {
buffer_size += get_buffer_size_c(problem_shape);
}
if constexpr (NumBuffersD > 0) {
buffer_size += get_buffer_size_d(problem_shape);
}
return buffer_size;
}
// Buffer space: | buffer_A | buffer_B | buffer_C | buffer_D |
// And buffer_{A,B,C,D}: | iter 1 | iter 2 | ... | iter TP - 1 |
template <typename ProblemShape>
static size_t
get_buffer_offset_A(ProblemShape problem_shape) {
return 0;
}
template <typename ProblemShape>
static size_t
get_buffer_offset_B(ProblemShape problem_shape) {
return get_buffer_size_a(problem_shape);
}
template <typename ProblemShape>
static size_t
get_buffer_offset_C(ProblemShape problem_shape) {
return get_buffer_size_a(problem_shape) + get_buffer_size_b(problem_shape);
}
template <typename ProblemShape>
static size_t
get_buffer_offset_D(ProblemShape problem_shape) {
return get_buffer_size_a(problem_shape) + get_buffer_size_b(problem_shape) + get_buffer_size_c(problem_shape);
}
};
} // namespace cutlass::distributed::device::detail
///////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,717 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 Distributed GEMM Device Adapter
Sets up local GEMM stages, the cuda graph, manages buffer and barrier spaces,
and maps arguments to per-stage arguments.
*/
#pragma once
#include "cutlass/cutlass.h"
#include "cutlass/device_kernel.h"
#include "cutlass/gemm/gemm.h"
#include "cutlass/gemm/device/gemm_universal_adapter.h"
#include "cutlass/experimental/distributed/device/full_barrier.hpp"
#include "cutlass/experimental/distributed/device/detail.hpp"
////////////////////////////////////////////////////////////////////////////////
namespace cutlass::distributed::device {
template <class GemmKernel_>
class DistributedGemmUniversalAdapter {
public:
using DeviceGemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel_>;
using GemmKernel = GemmKernel_;
using TileShape = typename GemmKernel::TileShape;
using ElementA = typename GemmKernel::ElementA;
using ElementB = typename GemmKernel::ElementB;
using ElementC = typename GemmKernel::ElementC;
using ElementD = typename GemmKernel::ElementD;
using ElementAccumulator = typename GemmKernel::ElementAccumulator;
using DispatchPolicy = typename GemmKernel::DispatchPolicy;
using CollectiveMainloop = typename GemmKernel::CollectiveMainloop;
using CollectiveEpilogue = typename GemmKernel::CollectiveEpilogue;
// "Inherit" type decls and static values from device GEMM
using LayoutA = typename DeviceGemm::LayoutA;
using LayoutB = typename DeviceGemm::LayoutB;
using LayoutC = typename DeviceGemm::LayoutC;
using LayoutD = typename DeviceGemm::LayoutD;
using StrideA = typename GemmKernel::StrideA;
using StrideB = typename GemmKernel::StrideB;
using StrideC = typename GemmKernel::StrideC;
using StrideD = typename GemmKernel::StrideD;
static bool const kEnableCudaHostAdapter = DeviceGemm::kEnableCudaHostAdapter;
static ComplexTransform const kTransformA = DeviceGemm::kTransformA;
static ComplexTransform const kTransformB = DeviceGemm::kTransformB;
using MathOperator = typename DeviceGemm::MathOperator;
using OperatorClass = typename DeviceGemm::OperatorClass;
using ArchTag = typename DeviceGemm::ArchTag;
using ThreadblockSwizzle = typename DeviceGemm::ThreadblockSwizzle;
using ThreadblockShape = typename DeviceGemm::ThreadblockShape;
using ClusterShape = typename DeviceGemm::ClusterShape;
using InstructionShape = typename DeviceGemm::InstructionShape;
static int const kThreadCount = DeviceGemm::kThreadCount;
static constexpr int WarpsInMma = DeviceGemm::WarpsInMma;
static constexpr int WarpsInMmaM = DeviceGemm::WarpsInMmaM;
static constexpr int WarpsInMmaN = DeviceGemm::WarpsInMmaN;
using WarpCount = typename DeviceGemm::WarpCount;
using WarpShape = typename DeviceGemm::WarpShape;
static int constexpr kStages = DeviceGemm::kStages;
static int constexpr kAlignmentA = DeviceGemm::kAlignmentA;
static int constexpr kAlignmentB = DeviceGemm::kAlignmentB;
static int constexpr kAlignmentC = DeviceGemm::kAlignmentC;
static int constexpr kAlignmentD = DeviceGemm::kAlignmentD;
using EpilogueOutputOp = typename DeviceGemm::EpilogueOutputOp;
static int constexpr kSplitKAlignment = DeviceGemm::kSplitKAlignment;
// Distributed GEMM types and defs
using DistSchedule = typename GemmKernel::DistSchedule;
static constexpr bool HasMemcpy = DistSchedule::HasMemcpy;
using TP = typename DistSchedule::TP;
static constexpr int TP_ = TP{};
using ElementFlag = typename GemmKernel::ElementFlag;
using ElementBarrier = uint32_t;
using BufferHelper = detail::DistGemmBufferHelper<
DistSchedule,
ElementA,
ElementB,
ElementC,
ElementD>;
/// Argument structure
using Arguments = typename GemmKernel::BaseArguments;
using DistributedArguments = typename GemmKernel::DistributedArguments;
using PackedArguments = typename GemmKernel::PackedArguments;
/// Argument structure: Kernel API
using Params = typename GemmKernel::PackedParams;
struct DistributedGemmState {
int device_idx;
Params params_array[TP_];
cudaGraph_t graph;
cudaGraphExec_t graph_executable;
bool graph_created = false;
bool graph_instantiated = false;
void * memcpy_source_ptr_array[TP_];
void const * memcpy_remote_ptr_array[TP_];
size_t memcpy_bytes[TP_];
cutlass::Array<ElementBarrier*, TP_> device_barrier_ptrs;
bool is_initialized = false;
};
private:
DistributedGemmState state_;
public:
bool is_initialized() {
return state_.is_initialized && state_.graph_created && state_.graph_instantiated;
}
/// Determines whether the GEMM can execute the given problem.
static Status
can_implement(Arguments const& args) {
if (args.epilogue.thread.beta != 0.0 && DistSchedule::RemoteC) {
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Selected TP uses Remote C to communicate " <<
"partial results, which do not support non-zero values for beta yet " <<
"(epilogue must be sourceless.)\n");
return Status::kInvalid;
}
if (not DistSchedule::can_implement_global(args.problem_shape)) {
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Problem shape not divisible by TP.\n");
return Status::kInvalid;
}
Arguments args_copy = args;
args_copy.problem_shape = DistSchedule::get_local_gemm_shape(args.problem_shape);
for (int iteration = 0; iteration < TP_; ++iteration) {
if (not GemmKernel::can_implement(args_copy)) {
return Status::kInvalid;
}
}
return Status::kSuccess;
}
/// Gets buffer space size
static size_t
get_buffer_space_size(Arguments const& args) {
size_t buffer_bytes = 0;
buffer_bytes = BufferHelper::get_buffer_size(args.problem_shape);
buffer_bytes = round_nearest(buffer_bytes, MinWorkspaceAlignment);
return buffer_bytes;
}
static auto
get_tensor_A_for_iter(Arguments const* args_array, void** buffer_space, int device_idx, int iteration) {
auto args = args_array[device_idx];
auto tensor_A = make_tensor(args.mainloop.ptr_A, make_layout(
DistSchedule::get_local_a_shape(args.problem_shape),
args.mainloop.dA));
uint8_t* tensor_buffer = reinterpret_cast<uint8_t*>(buffer_space[device_idx]) +
BufferHelper::get_buffer_offset_A(args.problem_shape);
return DistSchedule::get_tensor_A(tensor_A, tensor_buffer, device_idx, iteration);
}
static auto
get_tensor_B_for_iter(Arguments const* args_array, void** buffer_space, int device_idx, int iteration) {
auto args = args_array[device_idx];
auto tensor_B = make_tensor(args.mainloop.ptr_B, make_layout(
DistSchedule::get_local_b_shape(args.problem_shape),
args.mainloop.dB));
uint8_t* tensor_buffer = reinterpret_cast<uint8_t*>(buffer_space[device_idx]) +
BufferHelper::get_buffer_offset_B(args.problem_shape);
return DistSchedule::get_tensor_B(tensor_B, tensor_buffer, device_idx, iteration);
}
static auto
get_tensor_C_for_iter(Arguments const* args_array, void** buffer_space, int device_idx, int iteration) {
auto args = args_array[device_idx];
auto tensor_C = make_tensor(args.epilogue.ptr_C, make_layout(
DistSchedule::get_local_c_shape(args.problem_shape),
args.epilogue.dC));
auto peer_idx_iter = DistSchedule::get_remote_peer_id(device_idx, iteration);
void* buffer_ptr = DistSchedule::RemoteC ? buffer_space[peer_idx_iter] : buffer_space[device_idx];
uint8_t* tensor_buffer = reinterpret_cast<uint8_t*>(buffer_ptr) +
BufferHelper::get_buffer_offset_C(args.problem_shape);
return DistSchedule::get_tensor_C(tensor_C, tensor_buffer, device_idx, iteration);
}
static auto
get_tensor_D_for_iter(Arguments const* args_array, void** buffer_space, int device_idx, int iteration) {
auto args = args_array[device_idx];
auto tensor_D = make_tensor(args.epilogue.ptr_D, make_layout(
DistSchedule::get_local_d_shape(args.problem_shape),
args.epilogue.dD));
// support remoteD
uint8_t* tensor_buffer = reinterpret_cast<uint8_t*>(buffer_space[device_idx]) +
BufferHelper::get_buffer_offset_D(args.problem_shape);
return DistSchedule::get_tensor_D(tensor_D, tensor_buffer, device_idx, iteration);
}
static size_t
get_workspace_size(Arguments const& args) {
size_t workspace_bytes = 0;
workspace_bytes = get_buffer_space_size(args);
for (int iteration = 0; iteration < TP_; ++iteration) {
// NOTE: assumes underlying kernels align up to alignment requirements on their own,
// and that the alignment requirements of the individual kernels match.
workspace_bytes += GemmKernel::get_workspace_size(args);
}
return workspace_bytes;
}
static size_t
get_barrier_bytes() {
return round_nearest(sizeof(ElementBarrier), 32);
}
static size_t
get_flag_bytes() {
return round_nearest(sizeof(ElementFlag) * TP_, 32);
}
static void *
exclusive_workspace_ptr_to_flag_ptr(void * exclusive_workspace_ptr, int iteration) {
return static_cast<void*>(
static_cast<uint8_t*>(exclusive_workspace_ptr) +
get_barrier_bytes() +
(sizeof(ElementFlag) * iteration));
}
static size_t
get_exclusive_workspace_size() {
return get_barrier_bytes() + get_flag_bytes();
}
/// Initializes GEMM state from arguments.
Status
initialize(
Arguments const* args,
void** workspace_ptrs,
void** exclusive_workspace_ptrs,
int device_idx,
cudaStream_t stream = nullptr,
bool launch_with_pdl = false) {
CUTLASS_TRACE_HOST("DistributedGemm::initialize() - stream: " << (stream ? "non-null" : "null"));
state_.device_idx = device_idx;
for (int device = 0; device < TP_; ++device) {
state_.device_barrier_ptrs[device] = reinterpret_cast<ElementBarrier*>(exclusive_workspace_ptrs[device]);
}
// Zero out exclusive workspace
zero_workspace(exclusive_workspace_ptrs[device_idx], get_exclusive_workspace_size(), stream, nullptr);
for (int iteration = 0; iteration < TP_; ++iteration) {
size_t workspace_iteration_offset = GemmKernel::get_workspace_size(args[device_idx]);
uint8_t* workspace_ptr = reinterpret_cast<uint8_t*>(workspace_ptrs[device_idx]) +
get_buffer_space_size(args[device_idx]) +
(iteration * workspace_iteration_offset);
void * workspace_iter = reinterpret_cast<void*>(workspace_ptr);
void** buffer_space = workspace_ptrs;
// Set up GEMM arguments for the current stage/iteration
auto tensor_a_iter = get_tensor_A_for_iter(args, buffer_space, device_idx, iteration);
auto tensor_b_iter = get_tensor_B_for_iter(args, buffer_space, device_idx, iteration);
auto tensor_c_iter = get_tensor_C_for_iter(args, buffer_space, device_idx, iteration);
auto tensor_d_iter = get_tensor_D_for_iter(args, buffer_space, device_idx, iteration);
Arguments base_args = args[device_idx];
base_args.problem_shape = DistSchedule::get_local_gemm_shape(args[device_idx].problem_shape);
base_args.mainloop = {
reinterpret_cast<const ElementA*>(tensor_a_iter.data()),
tensor_a_iter.stride(),
reinterpret_cast<const ElementB*>(tensor_b_iter.data()),
tensor_b_iter.stride()
};
base_args.epilogue = {
base_args.epilogue.thread,
reinterpret_cast<const ElementC*>(tensor_c_iter.data()),
tensor_c_iter.stride(),
reinterpret_cast<const ElementD*>(tensor_d_iter.data()),
tensor_d_iter.stride()
};
if constexpr (DistSchedule::RemoteC) {
if (iteration > 0) {
base_args.epilogue.thread.beta = 1.0;
}
else if (iteration == 0){
base_args.epilogue.thread.beta = 0.0;
}
}
auto [left_peer_idx, right_peer_idx] = DistSchedule::get_peers_for_device(device_idx);
auto flag_peer_idx = DistSchedule::KernelWritesArrivalFlag ? right_peer_idx : device_idx;
void * self_flag_ptr = exclusive_workspace_ptr_to_flag_ptr(exclusive_workspace_ptrs[device_idx], iteration);
void * peer_flag_ptr = exclusive_workspace_ptr_to_flag_ptr(exclusive_workspace_ptrs[flag_peer_idx], iteration);
DistributedArguments distributed_args = {
device_idx,
iteration,
self_flag_ptr,
peer_flag_ptr
};
PackedArguments args_iter = {base_args, distributed_args};
// Initialize the workspace
Status status = GemmKernel::initialize_workspace(args_iter, workspace_iter, stream);
if (status != Status::kSuccess) {
return status;
}
// Initialize the Params structure
state_.params_array[iteration] = GemmKernel::to_underlying_arguments(args_iter, workspace_iter);
// Set up peer buffer ptrs
if (iteration > 0 && HasMemcpy) {
auto peer_idx_iter = DistSchedule::get_remote_peer_id(device_idx, iteration);
void * local_ptr_itr = nullptr;
void const * remote_ptr_itr = nullptr;
size_t local_size = 0;
size_t remote_size = 0;
static_assert(not DistSchedule::HasMemcpy || (
DistSchedule::MemcpyA || DistSchedule::MemcpyB),
"Expected to either memcpy A or B when scheduler requires memcpy.");
if constexpr (DistSchedule::MemcpyA) {
local_size = cute::cosize(tensor_a_iter.layout()) * sizeof(ElementA);
local_ptr_itr = reinterpret_cast<void*>(tensor_a_iter.data());
// Copy peer's slice in the first iteration (direct access memcpy instead of logical ring)
auto remote_tensor_iter = get_tensor_A_for_iter(args, buffer_space, peer_idx_iter, 0);
remote_ptr_itr = reinterpret_cast<void const*>(remote_tensor_iter.data());
remote_size = cute::cosize(remote_tensor_iter.layout()) * sizeof(ElementA);
}
else if constexpr (DistSchedule::MemcpyB) {
local_size = cute::cosize(tensor_b_iter.layout()) * sizeof(ElementB);
local_ptr_itr = reinterpret_cast<void*>(tensor_b_iter.data());
// Copy peer's slice in the first iteration (direct access memcpy instead of logical ring)
auto remote_tensor_iter = get_tensor_B_for_iter(args, buffer_space, peer_idx_iter, 0);
remote_ptr_itr = reinterpret_cast<void const*>(remote_tensor_iter.data());
remote_size = cute::cosize(remote_tensor_iter.layout()) * sizeof(ElementB);
}
assert(local_size == remote_size && local_size > 0);
state_.memcpy_source_ptr_array[iteration] = local_ptr_itr;
state_.memcpy_remote_ptr_array[iteration] = remote_ptr_itr;
state_.memcpy_bytes[iteration] = local_size;
}
}
//
// Account for dynamic smem capacity if needed
//
int smem_size = GemmKernel::SharedStorageSize;
if (smem_size >= (48 << 10)) {
CUTLASS_TRACE_HOST(" Setting smem size to " << smem_size);
cudaError_t result = cudaFuncSetAttribute(
device_kernel<GemmKernel>,
cudaFuncAttributeMaxDynamicSharedMemorySize,
smem_size);
if (cudaSuccess != result) {
result = cudaGetLastError(); // to clear the error bit
CUTLASS_TRACE_HOST(" cudaFuncSetAttribute() returned error: " << cudaGetErrorString(result));
return Status::kErrorInternal;
}
}
state_.is_initialized = true;
// Instantiate graph
Status status = construct_graph(launch_with_pdl);
if (status != Status::kSuccess) {
return status;
}
return Status::kSuccess;
}
Status
construct_graph(bool launch_with_pdl) {
#if ((__CUDACC_VER_MAJOR__ >= 12) && (__CUDACC_VER_MINOR__ >= 4))
Status status = Status::kSuccess;
// Destroy existing graph, if created
if (state_.graph_created) {
status = detail::check_cuda_status(cudaGraphDestroy(state_.graph));
if (status != Status::kSuccess) {
return status;
}
}
state_.graph_created = true;
cudaGraphNode_t full_barrier_node;
// Create dummy stream
cudaStream_t stream;
status = detail::check_cuda_status(cudaStreamCreate(&stream));
if (status != Status::kSuccess) {
return status;
}
// Create graph
status = detail::check_cuda_status(cudaGraphCreate(&state_.graph, 0));
if (status != Status::kSuccess) {
return status;
}
// 1. Full barrier node
status = detail::check_cuda_status(cudaStreamBeginCaptureToGraph(
stream,
state_.graph,
nullptr, nullptr, 0,
cudaStreamCaptureModeRelaxed));
if (status != Status::kSuccess) {
return status;
}
cutlass::Array<ElementFlag*, TP_> self_flag_ptrs;
for (int iteration = 0; iteration < TP_; ++iteration) {
self_flag_ptrs[iteration] = state_.params_array[iteration].distributed.self_flag_ptr_;
}
launch_full_barrier<TP_, ElementBarrier, TP_, ElementFlag>(
state_.device_barrier_ptrs, self_flag_ptrs, state_.device_idx, stream, launch_with_pdl);
status = detail::check_cuda_status(cudaStreamEndCapture(stream, &state_.graph));
if (status != Status::kSuccess) {
return status;
}
size_t num_nodes;
status = detail::check_cuda_status(cudaGraphGetNodes(state_.graph, nullptr, &num_nodes));
if (status != Status::kSuccess) {
return status;
}
if (num_nodes != 1) {
CUTLASS_TRACE_HOST(" construct_graph() failure: expected a single node in the graph, got " << num_nodes << ".");
return Status::kErrorInternal;
}
if (status != Status::kSuccess) {
return status;
}
status = detail::check_cuda_status(cudaGraphGetNodes(state_.graph, &full_barrier_node, &num_nodes));
if (status != Status::kSuccess) {
return status;
}
// 2. Optional mem copy branch
if constexpr (HasMemcpy) {
status = detail::check_cuda_status(cudaStreamBeginCaptureToGraph(
stream,
state_.graph,
&full_barrier_node,
/* dependencyData = */ nullptr,
1,
cudaStreamCaptureModeRelaxed));
if (status != Status::kSuccess) {
return status;
}
// No copies for first iter; we assume the data is already there.
for (int iteration = 1; iteration < TP_; ++iteration) {
status = detail::check_cuda_status(cudaMemcpyAsync(
state_.memcpy_source_ptr_array[iteration],
state_.memcpy_remote_ptr_array[iteration],
state_.memcpy_bytes[iteration],
cudaMemcpyDeviceToDevice, stream));
if (status != Status::kSuccess) {
return status;
}
// Set flag to non zero
status = detail::check_cuda_status(cudaMemsetAsync(
reinterpret_cast<void *>(state_.params_array[iteration].distributed.peer_flag_ptr_),
0b11111111,
sizeof(ElementFlag),
stream));
if (status != Status::kSuccess) {
return status;
}
}
status = detail::check_cuda_status(cudaStreamEndCapture(stream, &state_.graph));
if (status != Status::kSuccess) {
return status;
}
}
// 3. Run local GEMMs
// 3.1. Create edge between full barrier and the correct gemm stage/iteration
cudaGraphEdgeData barrier_to_gemm_edge = {};
barrier_to_gemm_edge.from_port = HasMemcpy ? cudaGraphKernelNodePortLaunchCompletion: cudaGraphKernelNodePortProgrammatic;
barrier_to_gemm_edge.type = cudaGraphDependencyTypeProgrammatic;
status = detail::check_cuda_status(cudaStreamBeginCaptureToGraph(
stream,
state_.graph,
&full_barrier_node,
/* dependencyData = */ &barrier_to_gemm_edge,
1,
cudaStreamCaptureModeRelaxed));
if (status != Status::kSuccess) {
return status;
}
for (int iteration = 0; iteration < TP_; ++iteration) {
status = DeviceGemm::run(
state_.params_array[iteration],
stream,
/* cuda_adapter = */ nullptr,
/* launch_with_pdl = */ launch_with_pdl);
if (status != Status::kSuccess) {
return status;
}
}
status = detail::check_cuda_status(cudaStreamEndCapture(stream, &state_.graph));
if (status != Status::kSuccess) {
return status;
}
// 4. Cleanup.
//// Destroy dummy stream
status = detail::check_cuda_status(cudaStreamDestroy(stream));
if (status != Status::kSuccess) {
return status;
}
// 5. Instantiate graph
status = detail::check_cuda_status(cudaGraphInstantiate(
&state_.graph_executable,
state_.graph,
/* flags = */ 0));
if (status != Status::kSuccess) {
return status;
}
state_.graph_instantiated = true;
return Status::kSuccess;
#else
CUTLASS_TRACE_HOST(" construct_graph() failure: target was compiled with an incompatible " <<
"version of the CUDA toolkit. Please compile Distributed GEMM with CUDA toolkit 12.4 or later.");
return Status::kErrorInternal;
#endif
}
Status
update(Arguments const& args, void* workspace = nullptr) {
CUTLASS_TRACE_HOST(" DistributedGemm does not support updating arguments yet.");
return Status::kErrorInternal;
}
// NOTE: the interface for run() is different in Distributed Gemm:
// 1. launch_with_pdl is specified in `initialize`, where the cuda graph is being constructed,
// 2. the state of distributed gemm is an array of params for different iterations, and a
// cuda graph.
// 3. Custom cuda adapters aren't supported for simplicity.
static Status
run(DistributedGemmState& state,
cudaStream_t stream = nullptr) {
CUTLASS_TRACE_HOST("DistributedGemm::run()");
if (not state.is_initialized) {
CUTLASS_TRACE_HOST(" Distributed gemm was not initialized. Did you forget to call initialize()?");
return Status::kErrorInternal;
}
if (not state.graph_instantiated) {
CUTLASS_TRACE_HOST(" Distributed gemm graph was not instantiated. Did you forget to call initialize()/construct_graph()?");
return Status::kErrorInternal;
}
cudaError_t result = cudaGraphLaunch(state.graph_executable, stream);
if (cudaSuccess != result) {
result = cudaGetLastError(); // to clear the error bit
CUTLASS_TRACE_HOST(" cudaGraphLaunch() returned error: " << cudaGetErrorString(result));
return Status::kErrorInternal;
}
return Status::kSuccess;
}
//
// Non-static launch overloads that first create and set the internal params struct of this kernel handle.
//
/// Overload that allows a user to re-launch the same kernel without updating internal params struct.
Status
run(
cudaStream_t stream = nullptr) {
return run(state_, stream);
}
/// Overload that allows a user to re-launch the same kernel without updating internal params struct.
Status
operator()(cudaStream_t stream = nullptr) {
return run(state_, stream);
}
/// Launches the kernel after first constructing Params internal state from supplied arguments.
Status
run(
Arguments const* args,
void** workspace_ptrs,
void** exclusive_workspace_ptrs,
int device_idx,
cudaStream_t stream = nullptr) {
Status status = initialize(
args,
workspace_ptrs,
exclusive_workspace_ptrs,
device_idx,
stream);
if (Status::kSuccess == status) {
status = run(stream);
}
return status;
}
/// Launches the kernel after first constructing Params internal state from supplied arguments.
Status
operator()(
Arguments const* args,
void** workspace_ptrs,
void** exclusive_workspace_ptrs,
int device_idx,
cudaStream_t stream = nullptr) {
return run(
args,
workspace_ptrs,
exclusive_workspace_ptrs,
device_idx,
stream);
}
};
////////////////////////////////////////////////////////////////////////////////
} // namespace cutlass::distributed::device
////////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,74 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 Device layer interface for Distributed GEMM barrier kernel.
*/
#pragma once
#include "cutlass/cutlass.h"
#include "cutlass/experimental/distributed/kernel/full_barrier.hpp"
namespace cutlass::distributed::device {
template <int NP, typename IntType, int Iterations, typename FlagType>
void launch_full_barrier(
cutlass::Array<IntType*, NP> device_arrival_ptrs,
cutlass::Array<FlagType*, Iterations> iteration_flag_ptrs,
IntType device_idx,
cudaStream_t stream,
bool launch_with_pdl) {
#if ((__CUDACC_VER_MAJOR__ >= 12) && (__CUDACC_VER_MINOR__ >= 4))
// Legacy (kernel) launch with PDL
cudaLaunchAttribute attributes[1];
attributes[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
attributes[0].val.programmaticStreamSerializationAllowed = 1;
cudaLaunchConfig_t launch_config;
launch_config.gridDim = 1;
launch_config.blockDim = 1;
launch_config.dynamicSmemBytes = 0;
launch_config.stream = stream;
launch_config.attrs = attributes;
launch_config.numAttrs = launch_with_pdl ? 1 : 0;
cudaLaunchKernelEx(
&launch_config,
cutlass::distributed::kernel::full_barrier_kernel<NP, IntType, Iterations, FlagType>,
device_arrival_ptrs,
iteration_flag_ptrs,
device_idx);
#endif
}
} // namespace cutlass::distributed::device
@@ -0,0 +1,72 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 Distributed gemm kernel layer helpers.
*/
#pragma once
#include "cutlass/cutlass.h"
///////////////////////////////////////////////////////////////////////////////
namespace cutlass::distributed::kernel::detail {
// Ld with CV cache hint (dont cache and fetch again)
// Reference:
// https://docs.nvidia.com/cuda/parallel-thread-execution/#cache-operators
// Used for loading arrival counts from peer devices
CUTLASS_DEVICE
void ld_without_cache(uint64_t& val, void const * ptr) {
asm volatile(
"{\n"
" ld.global.cv.u64 %0, [%1];\n"
"}\n"
: "=l"(val)
: "l"(ptr));
}
CUTLASS_DEVICE
void ld_without_cache(uint32_t& val, void const * ptr) {
asm volatile(
"{\n"
" ld.global.cv.u32 %0, [%1];\n"
"}\n"
: "=r"(val)
: "l"(ptr));
}
} // namespace cutlass::distributed::kernel::detail
///////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,235 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 Distributed GEMM Kernel Wrapper
Prepends CUTLASS 3 GEMM kernels with barriers and other necessary instructions to exectue
a Distributed GEMM stage.
*/
#pragma once
#include "cutlass/cutlass.h"
#include "cutlass/arch/grid_dependency_control.h"
#include "cutlass/gemm/gemm.h"
#include "cutlass/experimental/distributed/kernel/detail.hpp"
///////////////////////////////////////////////////////////////////////////////
namespace cutlass::distributed::kernel {
namespace detail {
// Allow all CUTLASS 3.X GEMM kernels
template <typename GemmKernel_>
struct SupportsDistributedGemm: cutlass::gemm::detail::IsCutlass3GemmKernel<GemmKernel_> {};
} // namespace detail
/*!
DistributedGemmKernelWrapper is a wrapper around a GEMM kernel.
Depending on the underlying distribution policy/schedule, it prepends the underlying local GEMM
kernel with a few additional instructions that gate the execution of the GEMM on buffers being
ready for stages/iterations > 0.
*/
template <class GemmKernel_, class DistSchedule_, class Enable = void>
struct DistributedGemmKernelWrapper;
template <class GemmKernel_, class DistSchedule_>
struct DistributedGemmKernelWrapper<
GemmKernel_,
DistSchedule_,
cute::enable_if_t<detail::SupportsDistributedGemm<GemmKernel_>::value>
>: GemmKernel_
{
using DistSchedule = DistSchedule_;
using TP = typename DistSchedule::TP;
static constexpr bool KernelWritesArrivalFlag = DistSchedule::KernelWritesArrivalFlag;
using BaseKernel = GemmKernel_;
using BaseArguments = typename BaseKernel::Arguments;
using BaseParams = typename BaseKernel::Params;
static_assert(BaseKernel::ArchTag::kMinComputeCapability == 90, "DistGEMM only supports Hopper GEMMs for now.");
static_assert(not cute::is_same_v<typename BaseKernel::ElementC, void>, "DistributedGEMM epilogues must have a source.");
using ElementFlag = uint32_t;
// Device side arguments
struct DistributedArguments {
int device_idx = 0;
int iteration = 0;
void* self_flag_ptr{nullptr};
void* peer_flag_ptr{nullptr};
};
struct PackedArguments {
BaseArguments base{};
DistributedArguments distributed{};
};
struct DistributedParams {
int device_idx = 0;
int iteration = 0;
ElementFlag* self_flag_ptr_{nullptr};
ElementFlag* peer_flag_ptr_{nullptr};
};
// Kernel entry point API
struct PackedParams {
BaseParams base{};
DistributedParams distributed{};
};
using Params = PackedParams;
// Convert to underlying arguments. In this case, a simple copy for the aliased type.
static
PackedParams
to_underlying_arguments(PackedArguments const& args, void* workspace) {
CUTLASS_TRACE_HOST("distributed::to_underlying_arguments():");
auto kernel_params = BaseKernel::to_underlying_arguments(args.base, workspace);
DistributedParams dist_params = {
args.distributed.device_idx,
args.distributed.iteration,
reinterpret_cast<ElementFlag*>(args.distributed.self_flag_ptr),
reinterpret_cast<ElementFlag*>(args.distributed.peer_flag_ptr)
};
return {kernel_params, dist_params};
}
static bool
can_implement(BaseArguments const& args) {
return BaseKernel::can_implement(args);
}
static bool
can_implement(PackedArguments const& args) {
return BaseKernel::can_implement(args.base);
}
static size_t
get_workspace_size(BaseArguments const& args) {
return BaseKernel::get_workspace_size(args);
}
static size_t
get_workspace_size(PackedArguments const& args) {
return BaseKernel::get_workspace_size(args.base);
}
static cutlass::Status
initialize_workspace(BaseArguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr,
CudaHostAdapter* cuda_adapter = nullptr) {
return BaseKernel::initialize_workspace(args, workspace, stream, cuda_adapter);
}
static cutlass::Status
initialize_workspace(PackedArguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr,
CudaHostAdapter* cuda_adapter = nullptr) {
return BaseKernel::initialize_workspace(args.base, workspace, stream, cuda_adapter);
}
/// Computes the grid shape
static dim3
get_grid_shape(PackedParams const& params) {
return BaseKernel::get_grid_shape(params.base);
}
static dim3
get_grid_shape(BaseParams const& params) {
return BaseKernel::get_grid_shape(params);
}
CUTLASS_DEVICE
void
barrier_buffer(PackedParams const& params) {
if (params.distributed.iteration > 0) {
ElementFlag comm_iter = 0;
detail::ld_without_cache(comm_iter, params.distributed.self_flag_ptr_);
while (comm_iter == 0) {
detail::ld_without_cache(comm_iter, params.distributed.self_flag_ptr_);
__nanosleep(40);
}
}
}
CUTLASS_DEVICE
void
maybe_signal_arrival(PackedParams const& params) {
if constexpr (KernelWritesArrivalFlag) {
if (blockIdx.x == 0 && blockIdx.y == 0 && blockIdx.z == 0 &&
threadIdx.x == 0 && threadIdx.y == 0 && threadIdx.z == 0 &&
params.distributed.iteration > 0) {
*reinterpret_cast<ElementFlag*>(params.distributed.peer_flag_ptr_) = 1;
}
}
}
CUTLASS_DEVICE
void
operator()(PackedParams const& params, char* smem_buf) {
// Launch next grid as soon as possible
arch::launch_dependent_grids();
// Wait on previous kernels to flush their memory.
arch::wait_on_dependent_grids();
// Optionally write arrivals for the previous stage/iteration.
maybe_signal_arrival(params);
// Spin-wait on an arrival flag, make sure the respective buffers are ready.
// If the buffered operand is memcpied into, it would wait on its local flag.
// If it's a remote buffer that is accessed directly, it would wait on its remote flag.
barrier_buffer(params);
// Perform local gemm
BaseKernel gemm;
gemm(params.base, smem_buf);
}
};
} // namespace cutlass::distributed::kernel
///////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,82 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 Distributed GEMM barrier kernel.
The kernel resets the per-stage arrival flags, performs a full barrier (any-to-any),
and also atomically resets the local barrier arrival count.
*/
#pragma once
#include "cutlass/cutlass.h"
#include "cutlass/arch/grid_dependency_control.h"
#include "cutlass/experimental/distributed/kernel/detail.hpp"
namespace cutlass::distributed::kernel {
template <int NP, typename IntType, int Iterations, typename FlagType>
__global__ void full_barrier_kernel(
cutlass::Array<IntType*, NP> device_arrival_ptrs,
cutlass::Array<FlagType*, Iterations> iteration_flag_ptrs,
IntType device_idx) {
arch::launch_dependent_grids();
arch::wait_on_dependent_grids();
CUTLASS_PRAGMA_UNROLL
for (FlagType i = 0; i < Iterations; ++i) {
iteration_flag_ptrs[i][0] = static_cast<FlagType>(0);
}
IntType val = 1;
IntType max_val = static_cast<IntType>(NP - 1);
CUTLASS_PRAGMA_UNROLL
for (IntType d = 0; d < NP; ++d) {
if (d != device_idx) {
atomicAdd(device_arrival_ptrs[d], val);
}
}
IntType curr_val = 0;
detail::ld_without_cache(curr_val, device_arrival_ptrs[device_idx]);
while (curr_val < max_val) {
__nanosleep(40);
detail::ld_without_cache(curr_val, device_arrival_ptrs[device_idx]);
}
atomicSub(device_arrival_ptrs[device_idx], max_val);
}
} // namespace cutlass::distributed::kernel
@@ -0,0 +1,324 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 1-D Distributed GEMM Schedules
NOTE: This API is __experimental__ and will change heavily over time. Particularly the use of
CuTe layouts as integer functions in defining iteration-to-tile mappings is over-expressive and
leaves plenty of room for incorrect/unexpected behavior.
Please proceed with caution when modifying these schedules or defining new ones.
Device/iteration mappings are defined with CuTe layouts,
since they are functions from integers to integers as well.
Each mapping is defined as a linear function of 2 variables (rank-2 layout):
First variable (mode) is device index, second variable (mode) is iteration.
A constant is also added to the final result as an offset value. This is a temporary workaround
so that identity ownership mappings in the final iteration can be guaranteed for the schedules
currently implemented.
How are these mappings defined?
Each schedule represents a unique parallel matrix multiplication algorithm, which describes how
matrices/tensors are distributed among TP GPUs.
Depending on the algorithm, access patterns (GPU to tile or (GPU, iteration) to tile) mappings)
are not necessarily going to be the identity function.
Pitfalls:
The current representation uses CuTe layouts as arbitrary linear functions that map
(GPU, iteration) to tile indices.
This approach is over-expressive, and therefore makes a lot of assumptions on the part of the
developer in how these mappings are defined. This can easily lead to incorrect implementations
if not handled carefully.
Assumption made in all schedules: TP == number of iterations (stages)
*/
#pragma once
#include "cute/layout.hpp"
#include "cute/tensor.hpp"
#include "cutlass/cutlass.h"
#include "cutlass/experimental/distributed/schedules/dist_gemm_base_schedule.hpp"
///////////////////////////////////////////////////////////////////////////////
namespace cutlass::distributed::schedules {
// GEMM + Reduce Scatter
// A and B are tiled along the K mode, which means each GPU gets an [M, K / TP]-shaped slice of A,
// and an [N, K / TP] slice of B.
// A is further tiled along the M mode, so that each stage/iteration computes a GEMM of shape
// [M / TP, N, K / TP], and the epilogue will perform the reduction by reading its C tensor directly
// from the left peer's previous D buffer.
//
// Below is an illustration of the tiling and iteration mappings for this pattern in the TP=4 case:
//
// Rows correspond to the M mode, columns correspond to the K mode for A and B and N mode for
// C and D. Because sharding is done along K, each column of tiles is owned by one GPU.
// Values in the grid correspond to the iteration/stage accessing the tile.
// * means the same tile is accessed in all iterations/stages.
//
// Tensor A Tensor B
//
// GPU0 GPU1 GPU2 GPU3 GPU0 GPU1 GPU2 GPU3
// |-----|-----|-----|-----| |-----|-----|-----|-----|
// | | | | | | | | | |
// | 3 | 0 | 1 | 2 | | | | | |
// |_____|_____|_____|_____| | | | | |
// | | | | | | | | | |
// | 2 | 3 | 0 | 1 | | | | | |
// |_____|_____|_____|_____| | * | * | * | * |
// | | | | | | | | | |
// | 1 | 2 | 3 | 0 | | | | | |
// |_____|_____|_____|_____| | | | | |
// | | | | | | | | | |
// | 0 | 1 | 2 | 3 | | | | | |
// |_____|_____|_____|_____| |_____|_____|_____|_____|
//
// M x K N x K
//
//
// Tensor C Tensor D
// (Peer's D)
//
//
// |-----------------------| |-----------------------|
// | | | |
// GPU0 | 1,2,3 | GPU0 | * |
// |_______________________| |_______________________|
// | | | |
// GPU1 | 1,2,3 | GPU1 | * |
// |_______________________| |_______________________|
// | | | |
// GPU2 | 1,2,3 | GPU2 | * |
// |_______________________| |_______________________|
// | | | |
// GPU3 | 1,2,3 | GPU3 | * |
// |_______________________| |_______________________|
//
// M x N M x N
//
//
// Tensor A's access pattern can be expressed as follows as a function of GPU index and iteration:
// tile_idx = ((device_idx - 1) - iter + TP) % TP
//
// and can be expressed with the following CuTe layout:
// (TP, TP) : (1, -1)
// with ProcessorOffset = -1
//
//
// Note: Since this schedule does not expose any communication, iteration 0 has no reduction step,
// therefore epilogue is sourceless in iteration 0, and in the rest of the iterations the epilogue
// source is a remote pointer to Tensor D owned by its left peer.
//
// Left peer is simply (device_idx - 1 + TP) % TP, which is expressed with the following CuTe layout:
// (TP, TP) : (1, 0)
//
template <class TP_>
struct ReduceScatter1D_TilingA_RotatingC: BaseSchedule<
TP_,
/* ProcessorTiler_ = */ cute::Shape<_1, _1, TP_, _1>,
/* IterationTiler_ = */ cute::Shape<TP_, _1, _1, _1>,
/* PeerDeviceMapping_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _0>>, // (left neighbor) = (device_idx + ProcessorOffset + TP) % TP, with ProcessorOffset = -1
/* IterationMappingM_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _m1>>, // = (device_idx + ProcessorOffset - iter + TP) % TP, with ProcessorOffset = -1
/* IterationMappingN_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::N == 1) = 0
/* IterationMappingK_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::K == 1) = 0
/* IterationMappingL_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::L == 1) = 0
/* ProcessorOffset_ = */ _m1,
/* MemcpyA_ = */ false,
/* MemcpyB_ = */ false,
/* KernelWritesArrivalFlag_ = */ true,
/* NumBuffersA_ = */ 0,
/* NumBuffersB_ = */ 0,
/* NumBuffersC_ = */ 0,
/* NumBuffersD_ = */ TP_{} - 1> {};
// This schedule is similar to ReduceScatter1D_TilingA_RotatingC, but with the second tiling
// done along N instead of M. All other details remain unchanged.
template <class TP_>
struct ReduceScatter1D_TilingB_RotatingC: BaseSchedule<
TP_,
/* ProcessorTiler_ = */ cute::Shape<_1, _1, TP_, _1>,
/* IterationTiler_ = */ cute::Shape<_1, TP_, _1, _1>,
/* PeerDeviceMapping_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _0>>, // (left neighbor) = (device_idx + ProcessorOffset + TP) % TP, with ProcessorOffset = -1
/* IterationMappingM_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::N == 1) = 0
/* IterationMappingN_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _m1>>, // = (device_idx + ProcessorOffset - iter + TP) % TP, with ProcessorOffset = -1
/* IterationMappingK_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::K == 1) = 0
/* IterationMappingL_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::L == 1) = 0
/* ProcessorOffset_ = */ _m1,
/* MemcpyA_ = */ false,
/* MemcpyB_ = */ false,
/* KernelWritesArrivalFlag_ = */ true,
/* NumBuffersA_ = */ 0,
/* NumBuffersB_ = */ 0,
/* NumBuffersC_ = */ 0,
/* NumBuffersD_ = */ TP_{} - 1> {};
// AllGather + GEMM
// A and B are tiled along the N mode, which means each GPU allgathers A,
// and operates with an [N / TP, K] slice of B.
// For pipelining, A is further tiled along the M mode, so that each stage/iteration computes a
// GEMM of shape [M / TP, N / TP, K], and concurrently we copy a peer's A slice into a local buffer
// for the next stage/iteration.
//
// Below is an illustration of the tiling and iteration mappings for this pattern in the TP=4 case:
//
// Rows correspond to the M mode, columns correspond to the K mode for A and B and N mode for
// C and D.
//
// Since this is a pipelined schedule without exposed communication, the first iteration starts
// off immediately and operates on local slices of A and B. In the rest of the iterations, each
// GPU accesses a slice of A copied from a peer GPU while it was busy with the last stage.
//
// Values in the following grids correspond to the peer buffer accessed by each GPU during
// different iterations:
//
// Tensor A Tensor A
// iter 0 iter 1
//
// |-----------------------| |-----------------------|
// | | | |
// GPU0 | 0 | | 1 |
// |_______________________| |_______________________|
// | | | |
// GPU1 | 1 | | 2 |
// |_______________________| |_______________________|
// | | | |
// GPU2 | 2 | | 3 |
// |_______________________| |_______________________|
// | | | |
// GPU3 | 3 | | 0 |
// |_______________________| |_______________________|
//
// M x K M x K
//
// Tensor A Tensor A
// iter 2 iter 3
//
// |-----------------------| |-----------------------|
// | | | |
// GPU0 | 2 | | 3 |
// |_______________________| |_______________________|
// | | | |
// GPU1 | 3 | | 0 |
// |_______________________| |_______________________|
// | | | |
// GPU2 | 0 | | 1 |
// |_______________________| |_______________________|
// | | | |
// GPU3 | 1 | | 2 |
// |_______________________| |_______________________|
//
// M x K M x K
//
// Values in the following grids correspond to the tile accessed during each iteration.
// * means the same tile is accessed in all iterations/stages.
//
// Tensor B Tensor C/D
//
//
// |-----------------------| |-----|-----|-----|-----|
// | | | | | | |
// GPU0 | * | GPU0 | 0 | 1 | 2 | 3 |
// |_______________________| |_____|_____|_____|_____|
// | | | | | | |
// GPU1 | * | GPU1 | 3 | 0 | 1 | 2 |
// |_______________________| |_____|_____|_____|_____|
// | | | | | | |
// GPU2 | * | GPU2 | 2 | 3 | 0 | 1 |
// |_______________________| |_____|_____|_____|_____|
// | | | | | | |
// GPU3 | * | GPU3 | 1 | 2 | 3 | 0 |
// |_______________________| |_____|_____|_____|_____|
//
// N x K M x N
//
//
// Tensor C/D's access pattern can be expressed as follows as a function of GPU index and iteration:
// tile_idx = (device_idx + iter) % TP
//
// and can be expressed with the following CuTe layout:
// (TP, TP) : (1, 1)
//
// This schedule does not need a ProcessorOffset constant.
//
// Peer devices from which A slices are copied is also expressed with the same function and CuTe
// layout.
//
template <class TP_>
struct AllGather1D_TilingCD_RotatingA: BaseSchedule<
TP_,
/* ProcessorTiler_ = */ cute::Shape<_1, TP_, _1, _1>,
/* IterationTiler_ = */ cute::Shape<TP_, _1, _1, _1>,
/* PeerDeviceMapping_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _1>>, // = device_idx + iter
/* IterationMappingM_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _1>>, // = device_idx + iter
/* IterationMappingN_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::N == 1) = 0
/* IterationMappingK_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::K == 1) = 0
/* IterationMappingL_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::L == 1) = 0
/* ProcessorOffset_ = */ _0,
/* MemcpyA_ = */ true,
/* MemcpyB_ = */ false,
/* KernelWritesArrivalFlag_ = */ false,
/* NumBuffersA_ = */ TP_{} - 1,
/* NumBuffersB_ = */ 0,
/* NumBuffersC_ = */ 0,
/* NumBuffersD_ = */ 0>{};
// This schedule is similar to AllGather1D_TilingCD_RotatingA, but with the order of tiling
// swapped from N then M to M then N. This means slices of B are rotated around GPUs instead of
// slices of A. All other details remain unchanged.
template <class TP_>
struct AllGather1D_TilingCD_RotatingB: BaseSchedule<
TP_,
/* ProcessorTiler_ = */ cute::Shape<TP_, _1, _1, _1>,
/* IterationTiler_ = */ cute::Shape<_1, TP_, _1, _1>,
/* PeerDeviceMapping_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _1>>, // = device_idx + iter
/* IterationMappingM_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::M == 1) = 0
/* IterationMappingN_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_1, _1>>, // = device_idx + iter
/* IterationMappingK_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::K == 1) = 0
/* IterationMappingL_ = */ cute::Layout<cute::Shape<TP_, TP_>, cute::Stride<_0, _0>>, // (IterationTiler::L == 1) = 0
/* ProcessorOffset_ = */ _0,
/* MemcpyA_ = */ false,
/* MemcpyB_ = */ true,
/* KernelWritesArrivalFlag_ = */ false,
/* NumBuffersA_ = */ 0,
/* NumBuffersB_ = */ TP_{} - 1,
/* NumBuffersC_ = */ 0,
/* NumBuffersD_ = */ 0>{};
} // namespace cutlass::distributed::schedules
///////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,538 @@
/***************************************************************************************************
* Copyright (c) 2024 - 2025 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 Base Schedule for Distributed GEMM
Templates Distributed GEMM schedules so that they can be expressed as a set of CuTe primitives and
other static values.
NOTE: This API is __experimental__ and will change heavily over time. Particularly the use of
CuTe layouts as integer functions in defining iteration-to-tile mappings is over-expressive and
leaves plenty of room for incorrect/unexpected behavior.
Please proceed with caution when modifying these schedules or defining new ones.
*/
#pragma once
#include "cute/layout.hpp"
#include "cute/tensor.hpp"
#include "cutlass/cutlass.h"
///////////////////////////////////////////////////////////////////////////////
namespace cutlass::distributed::schedules {
/*
* Distributed GEMM schedules define exactly how operand tensors are tiled and sliced across
* processors (GPUs) and stages/iterations.
*
* BaseSchedule's role is to ease the implementation of arbitrary Distributed GEMM schedules
* and reduce code repetition, simply by reducing the implementation to CuTe primitives and a few
* other static values (buffer sizes, whether tensors are rotated using memcpies or not, and the
* like.)
*/
template <
class TP_, // CuTe constant defining the number of processors / GPUs / TP value
class ProcessorTiler_, // CuTe tiler defining how fully materialized tensors are sharded across devices
class IterationTiler_, // CuTe tiler defining how local tensors are tiled across stages/iterations
class PeerDeviceMapping_, // CuTe layout mapping device index and stage/iteration to the device's peer index for that stage/iteration
class IterationMappingM_, // CuTe layout mapping device index and stage/iteration to M tile index
class IterationMappingN_, // CuTe layout mapping device index and stage/iteration to N tile index
class IterationMappingK_, // CuTe layout mapping device index and stage/iteration to K tile index
class IterationMappingL_, // CuTe layout mapping device index and stage/iteration to L tile index
class ProcessorOffset_, // Constant offset for processor / GPU index in iteration mapping
bool MemcpyA_, // Whether tensor A is memcpied
bool MemcpyB_, // Whether tensor B is memcpied
bool KernelWritesArrivalFlag_, // Whether the kernel writes arrival flags (when tensors are directly accessed from peer and not memcpied)
int NumBuffersA_, // Number of buffers required for tensor A
int NumBuffersB_, // Number of buffers required for tensor B
int NumBuffersC_, // Number of buffers required for tensor C
int NumBuffersD_> // Number of buffers required for tensor D
struct BaseSchedule {
using TP = TP_;
static_assert(
cute::is_static<TP>::value && cute::is_integral<TP>::value && cute::rank(TP{}) == 1 && cute::depth(TP{}) == 0,
"Only integers allowed for TP at this time.");
static_assert(cute::rank(ProcessorTiler_{}) == 4, "Expected rank-4 processor tiler.");
static_assert(cute::rank(IterationTiler_{}) == 4, "Expected rank-4 iteration tiler.");
static_assert(cute::rank(PeerDeviceMapping_{}) == 2,
"PeerDeviceMapping must be rank-2 (device_idx, iter)");
static_assert(cute::rank(IterationMappingM_{}) == 2,
"IterationMappingM must be rank-2 (device_idx, iter).");
static_assert(cute::rank(IterationMappingN_{}) == 2,
"IterationMappingN must be rank-2 (device_idx, iter).");
static_assert(cute::rank(IterationMappingK_{}) == 2,
"IterationMappingK must be rank-2 (device_idx, iter).");
static_assert(cute::rank(IterationMappingL_{}) == 2,
"IterationMappingL must be rank-2 (device_idx, iter).");
using ProcessorTiler = ProcessorTiler_;
using IterationTiler = IterationTiler_;
using PeerDeviceMapping = PeerDeviceMapping_;
using IterationMappingM = IterationMappingM_;
using IterationMappingN = IterationMappingN_;
using IterationMappingK = IterationMappingK_;
using IterationMappingL = IterationMappingL_;
using ProcessorOffset = ProcessorOffset_;
static constexpr bool KernelWritesArrivalFlag = KernelWritesArrivalFlag_;
static constexpr bool MemcpyA = MemcpyA_;
static constexpr bool MemcpyB = MemcpyB_;
static constexpr bool HasMemcpy = MemcpyA || MemcpyB;
static constexpr int NumBuffersA = NumBuffersA_;
static constexpr int NumBuffersB = NumBuffersB_;
static constexpr int NumBuffersC = NumBuffersC_;
static constexpr int NumBuffersD = NumBuffersD_;
static_assert(
NumBuffersA > 0 ^
NumBuffersB > 0 ^
NumBuffersC > 0 ^
NumBuffersD > 0,
"Only one of the ABCD tensors can be buffered!");
static constexpr bool BufferedOutput = NumBuffersC > 0 || NumBuffersD > 0;
static constexpr bool RemoteC = NumBuffersC == 0 && NumBuffersD > 0;
static constexpr bool RemoteD = NumBuffersD == 0 && NumBuffersC > 0;
static_assert(not RemoteD, "Remote D is not supported yet.");
// Host-side API: can_implement based on the GLOBAL problem shape
template <typename ProblemShape>
static bool
can_implement_global(ProblemShape const& global_problem_shape) {
auto [M, N, K, L] = append<4>(global_problem_shape, 1);
auto [ptileM, ptileN, ptileK, ptileL] = ProcessorTiler{};
auto [itileM, itileN, itileK, itileL] = IterationTiler{};
auto tileM = ptileM * itileM;
auto tileN = ptileN * itileN;
auto tileK = ptileK * itileK;
auto tileL = ptileL * itileL;
return M % tileM == 0 && N % tileN == 0 && K % tileK == 0 && L % tileL == 0;
}
template <typename ProblemShape>
CUTLASS_HOST_DEVICE
static auto
get_local_gemm_shape(ProblemShape const& global_problem_shape) {
auto problem_shape_MNKL = append<4>(global_problem_shape, 1);
return shape_div(
shape_div(
problem_shape_MNKL,
ProcessorTiler{}),
IterationTiler{});
}
// Host-side API: determine peers
static auto
get_peers_for_device(int device_idx) {
auto left_peer_id = device_idx > 0 ? device_idx - 1 : TP{} - 1;
auto right_peer_id = device_idx < TP{} - 1 ? device_idx + 1 : 0;
return cute::make_tuple(left_peer_id, right_peer_id);
}
// Determines peer given device index and iteration
static int
get_remote_peer_id(int device_idx, int iteration) {
auto device_iter_to_peer_idx = PeerDeviceMapping{};
auto peer_idx = (
device_iter_to_peer_idx(device_idx + ProcessorOffset{}, iteration) + TP{}
) % TP{};
return peer_idx;
}
// Construct tilers and index mappers for sharding across processors
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_processor_tiler_a(Tensor tensor) {
if constexpr (NumBuffersA > 0) {
return shape_div(tensor.shape(), select<0,2,3>(IterationTiler{}));
} else {
return shape_div(tensor.shape(), select<0,2,3>(ProcessorTiler{}));
}
}
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_processor_tiler_b(Tensor tensor) {
if constexpr (NumBuffersB > 0) {
return shape_div(tensor.shape(), select<1,2,3>(IterationTiler{}));
} else {
return shape_div(tensor.shape(), select<1,2,3>(ProcessorTiler{}));
}
}
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_processor_tiler_c(Tensor tensor) {
if constexpr (BufferedOutput) {
return shape_div(tensor.shape(), select<0,1,3>(IterationTiler{}));
} else {
return shape_div(tensor.shape(), select<0,1,3>(ProcessorTiler{}));
}
}
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_processor_tiler_d(Tensor tensor) {
return get_processor_tiler_c(tensor);
}
// Construct tilers and index mappers for tiling and iterating on device
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_device_tiler_a(Tensor tensor) {
static_assert(NumBuffersA == 0, "Buffered tensors don't have device tilers!");
return shape_div(tensor.shape(), select<0,2,3>(IterationTiler{}));
}
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_device_tiler_b(Tensor tensor) {
static_assert(NumBuffersB == 0, "Buffered tensors don't have device tilers!");
return shape_div(tensor.shape(), select<1,2,3>(IterationTiler{}));
}
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_device_tiler_c(Tensor tensor) {
static_assert(NumBuffersC == 0 && NumBuffersD == 0, "Buffered tensors don't have device tilers!");
return shape_div(tensor.shape(), select<0,1,3>(IterationTiler{}));
}
template <typename Tensor>
CUTLASS_HOST_DEVICE
static auto
get_device_tiler_d(Tensor tensor) {
static_assert(NumBuffersC == 0 && NumBuffersD == 0, "Buffered tensors don't have device tilers!");
return shape_div(tensor.shape(), select<0,1,3>(IterationTiler{}));
}
// Map device index and iteration to tile coordinate
// Must be implemented by children for now.
CUTLASS_HOST_DEVICE
static auto
get_device_tile_idx_a(int device_idx, int iteration) {
auto mapping_m = IterationMappingM{};
auto mapping_k = IterationMappingK{};
auto mapping_l = IterationMappingL{};
auto crd_m = (mapping_m(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_k = (mapping_k(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_l = (mapping_l(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
return make_coord(crd_m, crd_k, crd_l);
}
CUTLASS_HOST_DEVICE
static auto
get_device_tile_idx_b(int device_idx, int iteration) {
auto mapping_n = IterationMappingN{};
auto mapping_k = IterationMappingK{};
auto mapping_l = IterationMappingL{};
auto crd_n = (mapping_n(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_k = (mapping_k(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_l = (mapping_l(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
return make_coord(crd_n, crd_k, crd_l);
}
CUTLASS_HOST_DEVICE
static auto
get_device_tile_idx_c(int device_idx, int iteration) {
auto mapping_m = IterationMappingM{};
auto mapping_n = IterationMappingN{};
auto mapping_l = IterationMappingL{};
auto crd_m = (mapping_m(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_n = (mapping_n(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_l = (mapping_l(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
return make_coord(crd_m, crd_n, crd_l);
}
CUTLASS_HOST_DEVICE
static auto
get_device_tile_idx_d(int device_idx, int iteration) {
auto mapping_m = IterationMappingM{};
auto mapping_n = IterationMappingN{};
auto mapping_l = IterationMappingL{};
auto crd_m = (mapping_m(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_n = (mapping_n(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
auto crd_l = (mapping_l(device_idx + ProcessorOffset{}, iteration) + TP{}) % TP{};
return make_coord(crd_m, crd_n, crd_l);
}
// Device Partitioners: partition non-buffered processor-resident operands.
// Processor-resident operands fall into two categories: buffered, and not buffered.
// Those buffered aren't expected to be further partitioned, and those
template <typename Tensor>
static auto
get_tensor_A(Tensor original_tensor, void * tensor_buffer_ptr, int device_idx, int iteration) {
static_assert(rank(original_tensor) == 3);
using Element = typename Tensor::value_type;
// Recreate tensor without constness. This is to ensure return types match.
Element* ptr = const_cast<Element*>(original_tensor.data());
auto shape = original_tensor.shape();
auto layout = original_tensor.layout();
auto tensor = make_tensor(ptr, layout);
if constexpr (NumBuffersA == 0) {
auto tiler = get_device_tiler_a(tensor);
auto idx = get_device_tile_idx_a(device_idx, iteration);
return inner_partition(tensor, tiler, idx);
} else {
Element* ptr_buffer = reinterpret_cast<Element*>(tensor_buffer_ptr);
if (iteration == 0) {
return tensor;
}
ptr_buffer += size(shape) * (iteration - 1);
return make_tensor(ptr_buffer, layout);
}
}
template <typename Tensor>
static auto
get_tensor_B(Tensor original_tensor, void * tensor_buffer_ptr, int device_idx, int iteration) {
static_assert(rank(original_tensor) == 3);
using Element = typename Tensor::value_type;
// Recreate tensor without constness. This is to ensure return types match.
Element * ptr = const_cast<Element *>(original_tensor.data());
auto shape = original_tensor.shape();
auto layout = original_tensor.layout();
auto tensor = make_tensor(ptr, layout);
if constexpr (NumBuffersB == 0) {
auto tiler = get_device_tiler_b(tensor);
auto idx = get_device_tile_idx_b(device_idx, iteration);
return inner_partition(tensor, tiler, idx);
} else {
Element * ptr_buffer = reinterpret_cast<Element *>(tensor_buffer_ptr);
if (iteration == 0) {
return tensor;
}
ptr_buffer += size(shape) * (iteration - 1);
return make_tensor(ptr_buffer, layout);
}
}
template <typename Tensor>
static auto
get_tensor_C(Tensor original_tensor, void * tensor_buffer_ptr, int device_idx, int iteration) {
static_assert(rank(original_tensor) == 3);
using Element = typename Tensor::value_type;
// Recreate tensor without constness. This is to ensure return types match.
Element * ptr = const_cast<Element *>(original_tensor.data());
auto shape = original_tensor.shape();
auto layout = original_tensor.layout();
auto tensor = make_tensor(ptr, layout);
if constexpr (not BufferedOutput) {
auto tiler = get_device_tiler_c(tensor);
auto idx = get_device_tile_idx_c(device_idx, iteration);
return inner_partition(tensor, tiler, idx);
} else {
// implement Remote D
static_assert(RemoteC, "");
Element * ptr_buffer = reinterpret_cast<Element *>(tensor_buffer_ptr);
if (iteration == 0) {
return tensor;
}
ptr_buffer += size(shape) * (iteration - 1);
return make_tensor(ptr_buffer, layout);
}
}
template <typename Tensor>
static auto
get_tensor_D(Tensor original_tensor, void * tensor_buffer_ptr, int device_idx, int iteration) {
static_assert(rank(original_tensor) == 3);
using Element = typename Tensor::value_type;
// Recreate tensor without constness. This is to ensure return types match.
Element * ptr = const_cast<Element *>(original_tensor.data());
auto shape = original_tensor.shape();
auto layout = original_tensor.layout();
auto tensor = make_tensor(ptr, layout);
if constexpr (not BufferedOutput) {
auto tiler = get_device_tiler_d(tensor);
auto idx = get_device_tile_idx_d(device_idx, iteration);
return inner_partition(tensor, tiler, idx);
} else {
// implement Remote D
static_assert(RemoteC, "");
Element * ptr_buffer = reinterpret_cast<Element *>(tensor_buffer_ptr);
// last iteration is the local tensor, the rest are buffers
if (iteration == TP{} - 1) {
return tensor;
}
ptr_buffer += size(shape) * iteration; // note: iteration, not iteration - 1
return make_tensor(ptr_buffer, layout);
}
}
template <typename ProblemShape>
CUTLASS_HOST_DEVICE
static auto
get_local_a_shape(ProblemShape problem_shape) {
auto problem_shape_MNKL = append<4>(problem_shape, 1);
if constexpr (NumBuffersA == 0) {
return shape_div(
select<0,2,3>(problem_shape_MNKL),
select<0,2,3>(ProcessorTiler{}));
} else {
return shape_div(
shape_div(
select<0,2,3>(problem_shape_MNKL),
select<0,2,3>(ProcessorTiler{})),
select<0,2,3>(IterationTiler{}));
}
}
template <typename ProblemShape>
CUTLASS_HOST_DEVICE
static auto
get_local_b_shape(ProblemShape problem_shape) {
auto problem_shape_MNKL = append<4>(problem_shape, 1);
if constexpr (NumBuffersB == 0) {
return shape_div(
select<1,2,3>(problem_shape_MNKL),
select<1,2,3>(ProcessorTiler{}));
} else {
return shape_div(
shape_div(
select<1,2,3>(problem_shape_MNKL),
select<1,2,3>(ProcessorTiler{})),
select<1,2,3>(IterationTiler{}));
}
}
template <typename ProblemShape>
CUTLASS_HOST_DEVICE
static auto
get_local_c_shape(ProblemShape problem_shape) {
auto problem_shape_MNKL = append<4>(problem_shape, 1);
if constexpr (not BufferedOutput) {
return shape_div(
select<0,1,3>(problem_shape_MNKL),
select<0,1,3>(ProcessorTiler{}));
} else {
return shape_div(
shape_div(
select<0,1,3>(problem_shape_MNKL),
select<0,1,3>(ProcessorTiler{})),
select<0,1,3>(IterationTiler{}));
}
}
template <typename ProblemShape>
CUTLASS_HOST_DEVICE
static auto
get_local_d_shape(ProblemShape problem_shape) {
auto problem_shape_MNKL = append<4>(problem_shape, 1);
if constexpr (not BufferedOutput) {
return shape_div(
select<0,1,3>(problem_shape_MNKL),
select<0,1,3>(ProcessorTiler{}));
} else {
return shape_div(
shape_div(
select<0,1,3>(problem_shape_MNKL),
select<0,1,3>(ProcessorTiler{})),
select<0,1,3>(IterationTiler{}));
}
}
// Host-side APIs: get_device_slice_{A,B,C,D}
// Slice off a view of the GLOBAL tensor that corresponds to the shard that
// is going to be owned by a specific device. This helps with the initial
// distribution of the GLOBAL operands among devices.
template <typename Tensor>
static auto
get_device_slice_A(Tensor tensor, int device_idx) {
auto tiler = get_processor_tiler_a(tensor);
return inner_partition(tensor, tiler, device_idx);
}
template <typename Tensor>
static auto
get_device_slice_B(Tensor tensor, int device_idx) {
auto tiler = get_processor_tiler_b(tensor);
return inner_partition(tensor, tiler, device_idx);
}
template <typename Tensor>
static auto
get_device_slice_C(Tensor tensor, int device_idx) {
auto tiler = get_processor_tiler_c(tensor);
return inner_partition(tensor, tiler, device_idx);
}
template <typename Tensor>
static auto
get_device_slice_D(Tensor tensor, int device_idx) {
auto tiler = get_processor_tiler_d(tensor);
return inner_partition(tensor, tiler, device_idx);
}
};
} // namespace cutlass::gemm::distributed
///////////////////////////////////////////////////////////////////////////////