Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cutlass
Submodule cutlass updated 3061 files
6 changes: 4 additions & 2 deletions tests/pytorch/test_grouped_linear.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,8 +378,10 @@ def test_grouped_linear_accuracy(


@pytest.mark.skipif(
torch.cuda.get_device_capability() != (9, 0),
reason="Only enable CUTLASS grouped gemm on Hopper",
not (
torch.cuda.get_device_capability() == (9, 0) or torch.cuda.get_device_capability()[0] == 10
),
reason="CUTLASS grouped GEMM is supported on Hopper (SM90) and Blackwell (SM100/SM103)",
)
@pytest.mark.parametrize("dtype", param_types, ids=str)
@pytest.mark.parametrize("num_gemms", [3, 6])
Expand Down
2 changes: 2 additions & 0 deletions transformer_engine/common/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -358,6 +358,8 @@ set_property(
target_link_libraries(transformer_engine PUBLIC
CUDA::cublas
CUDA::cudart
CUDA::nvrtc
CUDA::cuda_driver
CUDNN::cudnn_all)

target_include_directories(transformer_engine PRIVATE
Expand Down
49 changes: 47 additions & 2 deletions transformer_engine/common/gemm/cublaslt_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -1062,6 +1062,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor

const int current_device = transformer_engine::cuda::current_device();
const bool is_hopper = (transformer_engine::cuda::sm_arch(current_device) == 90);
const bool is_blackwell = (transformer_engine::cuda::sm_arch(current_device) == 100 ||
transformer_engine::cuda::sm_arch(current_device) == 103);
const bool use_cutlass = transformer_engine::getenv<bool>("NVTE_USE_CUTLASS_GROUPED_GEMM", false);
const bool warn_fallback =
transformer_engine::getenv<bool>("NVTE_CUTLASS_GROUPED_GEMM_WARN_FALLBACK", false);
Expand All@@ -1071,8 +1073,8 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
workspace, accumulate, use_split_accumulator, math_sm_count, stream);
};

// Currently only support cutlass group gemm on Hopper Arch
if (!(is_hopper && use_cutlass)) {
// CUTLASS grouped GEMM: Hopper (SM90) fwd + wgrad; Blackwell (SM100) fwd (tcgen05 Ptr-Array).
if (!((is_hopper || is_blackwell) && use_cutlass)) {
cublas_path();
return;
}
Expand DownExpand Up@@ -1114,6 +1116,42 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
((A_type == CUDA_R_16BF) || (A_type == CUDA_R_16F));
};

auto is_bf16_wgrad_dtype = [&]() -> bool {
auto *inputA = transformer_engine::convertNVTETensorCheck(A[0]);
auto *inputB = transformer_engine::convertNVTETensorCheck(B[0]);
auto *OutputD = transformer_engine::convertNVTETensorCheck(D[0]);
auto A_type = get_cuda_dtype(inputA->data.dtype);
auto B_type = get_cuda_dtype(inputB->data.dtype);
auto D_type = get_cuda_dtype(OutputD->data.dtype);

return (A_type == CUDA_R_16BF) && (B_type == CUDA_R_16BF) &&
(D_type == CUDA_R_32F || D_type == CUDA_R_16BF);
};

// K-grouped BF16 wgrad shape eligibility: every group must be 2D NT with a matching
// (ragged) K and a uniform hidden/expert. Shapes outside this fall back to cuBLAS
// instead of hard-erroring inside the varlen-k kernel.
auto is_bf16_wgrad_shape = [&]() -> bool {
int64_t ref_hidden = -1, ref_expert = -1;
for (size_t i = 0; i < num_gemms; i++) {
const auto *inp = transformer_engine::convertNVTETensorCheck(A[i]);
const auto *grad = transformer_engine::convertNVTETensorCheck(B[i]);
if (inp->data.shape.size() != 2 || grad->data.shape.size() != 2) return false;
const int64_t k = inp->data.shape[0];
const int64_t hidden = inp->data.shape[1];
const int64_t expert = grad->data.shape[1];
if (static_cast<int64_t>(grad->data.shape[0]) != k || hidden <= 0 || expert <= 0)
return false;
if (ref_hidden < 0) {
ref_hidden = hidden;
ref_expert = expert;
} else if (hidden != ref_hidden || expert != ref_expert) {
return false;
}
}
return true;
};

// CUTLASS Grouped GEMM fast path (SM90/TMA)
// Conditions:
// - No fused epilogue: both bias and pre_gelu_out are empty.
Expand All@@ -1127,6 +1165,13 @@ void nvte_multi_tensor_gemm(const NVTETensor *A, const NVTETensor *B, NVTETensor
all_groups_uniform_k128(B, transb)) {
cutlass_grouped_gemm(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
} else if (is_empty_arr(bias) && is_empty_arr(pre_gelu_out) && is_bf16_wgrad_dtype() && !transa &&
transb && grad && is_bf16_wgrad_shape()) {
// Dedicated K-grouped (ragged-K) BF16-in / (FP32 or BF16)-out wgrad path:
// D_i = B_i.T @ A_i, K_i = routed-token dim. Shape eligibility is guarded above, so
// unsupported shapes fall back to cuBLAS rather than hard-erroring in the kernel.
cutlass_grouped_gemm_varlen_k(A, B, D, num_gemms, transa, transb, grad, workspace, accumulate,
current_device, math_sm_count, stream);
Comment thread
alan-hpc marked this conversation as resolved.
} else {
if (warn_fallback) {
NVTE_WARN("Fallback to cuBLAS grouped GEMM.");
Expand Down
25 changes: 24 additions & 1 deletion transformer_engine/common/gemm/cublaslt_grouped_gemm.cu
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
#include "../util/logging.h"
#include "../util/vectorized_pointwise.h"
#include "./config.h"
#include "common/util/system.h"

namespace {

Expand DownExpand Up@@ -443,10 +444,13 @@ struct GroupedGemmConfig {
int64_t avg_m = 0;
int64_t avg_n = 0;
int64_t avg_k = 0;
int64_t out_m = 0;
int64_t out_n = 0;
int64_t contraction_k = 0;
int sm_count = 0;
};

constexpr int kMaxGroups = 64;
constexpr int kMaxGroups = 256;
// Arguments for the grouped GEMM kernel that operates on multiple output tensors.
struct MultiTensorGroupGemmOutputArgs {
void *data_ptrs[kMaxGroups];
Expand DownExpand Up@@ -1637,6 +1641,12 @@ void nvte_grouped_gemm(const NVTEGroupedTensor A, int transa, const NVTEGroupedT
gemm_config.avg_n = config_.avg_n.value_or(compute_avg_last_dim(outputD));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate (never the caller's avg_* override): the REAL per-expert output dims, and the
// true reduction -- the ragged token dim (inputA's first dim) for the NT wgrad, else avg_k.
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1787,6 +1797,12 @@ void nvte_grouped_gemm_with_discrete_inputA(const NVTETensor *A_list, size_t num
gemm_config.avg_n =
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k = config_.avg_k.value_or(transa ? avg_first_dim : avg_last_dim);
// CUTLASS host estimate: output is grouped here, so the real dims come from outputD. (inputA is discrete
// here; this path is not the NT wgrad -- that is nvte_grouped_gemm_with_discrete_out -- so avg_k suffices
// for contraction_k.)
gemm_config.out_m = compute_avg_first_dim(outputD);
gemm_config.out_n = compute_avg_last_dim(outputD);
gemm_config.contraction_k = gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, outputD->dtype(), num_tensors,
gemm_config, workspace.cublas_workspace_ptr, stream);
Expand DownExpand Up@@ -1875,6 +1891,13 @@ void nvte_grouped_gemm_with_discrete_out(const NVTEGroupedTensor A, int transa,
config_.avg_n.value_or(transb ? compute_avg_first_dim(inputB) : compute_avg_last_dim(inputB));
gemm_config.avg_k =
config_.avg_k.value_or(transa ? compute_avg_first_dim(inputA) : compute_avg_last_dim(inputA));
// CUTLASS host estimate: the output is DISCRETE here (avg_m/avg_n above are input-derived = the token
// avg, not the output dims), so read the real per-expert output M,N from D_list[0]'s shape. The NT wgrad
// reduction is the ragged token dim (inputA's first dim).
gemm_config.out_m = static_cast<int64_t>(d0->data.shape[0]);
gemm_config.out_n = static_cast<int64_t>(d0->data.shape[1]);
gemm_config.contraction_k =
(!transa && transb) ? compute_avg_first_dim(inputA) : gemm_config.avg_k;
gemm_config.sm_count = config_.sm_count;
execute_grouped_gemm(workspace.setup_workspace, A_sel, B_sel, d_dtype, num_tensors, gemm_config,
workspace.cublas_workspace_ptr, stream);
Expand Down
Loading