Files
TheRock/rocm-systems/projects/rccl/contrib/nccl_ep/device/hybridep_adapter.cu
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

625 lines
25 KiB
Plaintext

/*
* Portions of this file are adapted from DeepEP (https://github.com/deepseek-ai/DeepEP).
* Copyright (c) 2025 DeepSeek. Licensed under the MIT License.
* SPDX-License-Identifier: MIT
*/
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
* See LICENSE.txt for more license information.
*/
#include "nccl_device.h"
#include "hybridep_adapter.cuh"
#include "hybridep_configs.cuh"
#include "hybrid_ep.cuh"
#include "include/common.hpp"
namespace nccl_ep {
namespace hybridep {
// ============================================================================
// Kernel: Convert sparse topk_idx to dense routing map
// ============================================================================
__global__ void convert_topk_to_routing_map_kernel(
const int64_t* __restrict__ topk_idx, // [num_tokens, num_topk]
uint8_t* __restrict__ routing_bitmap, // [num_tokens, num_experts_packed]
int num_tokens,
int num_topk,
int num_experts_packed // = ceil(num_experts / 8)
) {
int token = blockIdx.x * blockDim.x + threadIdx.x;
if (token >= num_tokens) return;
// Buffer is pre-zeroed by per-iteration memset; just OR in set bits.
// Each thread exclusively owns its row -- no atomics needed.
uint8_t* row = routing_bitmap + token * num_experts_packed;
for (int k = 0; k < num_topk; k++) {
int expert = static_cast<int>(topk_idx[token * num_topk + k]);
if (expert >= 0) {
row[expert / 8] |= (1u << (expert % 8));
}
}
}
// ============================================================================
// Convert topk to bitmap routing map
// ============================================================================
void convert_topk_to_routing_map(
const int64_t* topk_idx,
uint8_t* routing_bitmap,
int num_tokens,
int num_topk,
int num_experts_packed,
cudaStream_t stream
) {
int block_size = 256;
int grid_size = (num_tokens + block_size - 1) / block_size;
convert_topk_to_routing_map_kernel<<<grid_size, block_size, 0, stream>>>(
topk_idx, routing_bitmap, num_tokens, num_topk, num_experts_packed);
}
// ============================================================================
// Kernel: Convert sparse topk_weights to dense prob
// ============================================================================
__global__ void sparse_to_dense_prob_kernel(
const int64_t* __restrict__ topk_idx, // [num_tokens, topk]
const float* __restrict__ topk_weights, // [num_tokens, topk]
float* __restrict__ dense_prob, // [num_tokens, num_experts]
int num_tokens,
int num_topk,
int num_experts
) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int token = tid / num_topk;
int k = tid % num_topk;
if (token >= num_tokens) return;
int64_t expert = topk_idx[token * num_topk + k];
float weight = topk_weights[token * num_topk + k];
// Scatter weight to the correct expert position
if (expert >= 0 && expert < num_experts) {
dense_prob[token * num_experts + expert] = weight;
}
}
// ============================================================================
// Convert sparse to dense prob
// ============================================================================
void sparse_to_dense_prob(
const int64_t* topk_idx,
const float* topk_weights,
float* dense_prob,
int num_tokens,
int num_topk,
int num_experts,
cudaStream_t stream
) {
int total_elements = num_tokens * num_topk;
int block_size = 256;
int grid_size = (total_elements + block_size - 1) / block_size;
sparse_to_dense_prob_kernel<<<grid_size, block_size, 0, stream>>>(
topk_idx, topk_weights, dense_prob, num_tokens, num_topk, num_experts);
}
// ============================================================================
// Kernel: Convert sparse topk_weights to dense prob for combine input
// ============================================================================
// Used for combine backward pass. Uses local_expert_routing_map to determine
// which experts each token is routed to, matching the order from dispatch output.
// Each thread handles one token.
__global__ void sparse_to_dense_prob_combine_kernel(
const float* __restrict__ topk_weights, // [num_tokens, topk]
const bool* __restrict__ local_expert_routing_map, // [num_tokens, experts_per_rank]
float* __restrict__ dense_prob, // [num_tokens, experts_per_node]
int num_tokens,
int num_topk,
int experts_per_rank,
int experts_per_node,
int local_rank
) {
int token = blockIdx.x * blockDim.x + threadIdx.x;
if (token >= num_tokens) return;
// Scan local experts in order (matches dense_to_sparse_prob output order)
int k_in = 0;
for (int e = 0; e < experts_per_rank && k_in < num_topk; e++) {
if (local_expert_routing_map[token * experts_per_rank + e]) {
// This expert is active for this token - take next weight from sparse input
float weight = topk_weights[token * num_topk + k_in];
// Place at correct position in dense matrix
// Local expert e on local_rank maps to: local_rank * experts_per_rank + e
int dense_idx = token * experts_per_node + local_rank * experts_per_rank + e;
dense_prob[dense_idx] = weight;
k_in++;
}
}
}
// ============================================================================
// Convert sparse to dense prob for combine input
// ============================================================================
void sparse_to_dense_prob_combine(
const float* topk_weights,
const bool* local_expert_routing_map,
float* dense_prob,
int num_tokens,
int num_topk,
int experts_per_rank,
int experts_per_node,
int local_rank,
cudaStream_t stream
) {
int block_size = 256;
int grid_size = (num_tokens + block_size - 1) / block_size;
sparse_to_dense_prob_combine_kernel<<<grid_size, block_size, 0, stream>>>(
topk_weights, local_expert_routing_map, dense_prob, num_tokens, num_topk,
experts_per_rank, experts_per_node, local_rank);
}
// ============================================================================
// Kernel: Convert dense prob output to sparse format
// ============================================================================
// Each thread handles one token, scans for non-zero experts
__global__ void dense_to_sparse_prob_kernel(
const float* __restrict__ dense_prob, // [num_recv_tokens, experts_per_node]
const bool* __restrict__ local_expert_routing_map, // [num_recv_tokens, experts_per_rank]
float* __restrict__ recv_topk_weights, // [num_recv_tokens, topk]
int64_t* __restrict__ recv_topk_idx, // [num_recv_tokens, topk]
int num_recv_tokens,
int topk,
int experts_per_rank,
int experts_per_node,
int local_rank
) {
int token = blockIdx.x * blockDim.x + threadIdx.x;
if (token >= num_recv_tokens) return;
int k_out = 0;
// Scan local experts (the ones this rank is responsible for)
for (int e = 0; e < experts_per_rank && k_out < topk; e++) {
// Check if this token is routed to expert e
if (local_expert_routing_map[token * experts_per_rank + e]) {
// Use local expert id for NCCL API compatibility (expects 0-based local indices)
int64_t local_expert = static_cast<int64_t>(e);
// Get weight from dense output (indexed by local expert within node)
// dense_prob layout: [token, experts_per_node] where experts_per_node = experts_per_rank * ranks_per_node
// Local rank's experts are at offset: local_rank * experts_per_rank
int dense_idx = token * experts_per_node + local_rank * experts_per_rank + e;
float weight = dense_prob[dense_idx];
// Write both outputs
recv_topk_idx[token * topk + k_out] = local_expert;
recv_topk_weights[token * topk + k_out] = weight;
k_out++;
}
}
// Zero-fill remaining topk slots if fewer than topk experts found
for (; k_out < topk; k_out++) {
recv_topk_idx[token * topk + k_out] = -1; // Invalid expert marker
recv_topk_weights[token * topk + k_out] = 0.0f;
}
}
// ============================================================================
// Kernel: Convert dense prob output to sparse format for combine output
// ============================================================================
// Used for combine backward pass. Converts kernel's dense output to sparse format
// with GLOBAL expert indices (matching original dispatch input format).
// Each thread handles one token.
__global__ void dense_to_sparse_prob_combine_kernel(
const float* __restrict__ dense_prob, // [num_tokens, num_experts]
const uint8_t* __restrict__ routing_bitmap, // [num_tokens, ceil(num_experts / 8)]
float* __restrict__ combined_topk_weights, // [num_tokens, topk]
int64_t* __restrict__ combined_topk_idx, // [num_tokens, topk] (optional, can be nullptr)
int num_tokens,
int topk,
int num_experts
) {
int token = blockIdx.x * blockDim.x + threadIdx.x;
if (token >= num_tokens) return;
int packed_cols = (num_experts + 7) / 8;
int k_out = 0;
// Scan all experts in order (matches original dispatch input order)
for (int e = 0; e < num_experts && k_out < topk; e++) {
if ((routing_bitmap[token * packed_cols + e / 8] >> (e % 8)) & 1) {
// This expert is active for this token
float weight = dense_prob[token * num_experts + e];
combined_topk_weights[token * topk + k_out] = weight;
if (combined_topk_idx != nullptr) {
combined_topk_idx[token * topk + k_out] = static_cast<int64_t>(e); // GLOBAL expert ID
}
k_out++;
}
}
// Zero-fill remaining topk slots
for (; k_out < topk; k_out++) {
combined_topk_weights[token * topk + k_out] = 0.0f;
if (combined_topk_idx != nullptr) {
combined_topk_idx[token * topk + k_out] = -1;
}
}
}
// ============================================================================
// Convert dense prob output to sparse format for combine output
// ============================================================================
void dense_to_sparse_prob_combine(
const float* dense_prob,
const uint8_t* routing_bitmap,
float* combined_topk_weights,
int64_t* combined_topk_idx,
int num_tokens,
int topk,
int num_experts,
cudaStream_t stream
) {
int block_size = 256;
int grid_size = (num_tokens + block_size - 1) / block_size;
dense_to_sparse_prob_combine_kernel<<<grid_size, block_size, 0, stream>>>(
dense_prob, routing_bitmap, combined_topk_weights, combined_topk_idx,
num_tokens, topk, num_experts);
}
// ============================================================================
// Dense to sparse prob
// ============================================================================
void dense_to_sparse_prob(
const float* dense_prob,
const bool* local_expert_routing_map,
float* recv_topk_weights,
int64_t* recv_topk_idx,
int num_recv_tokens,
int topk,
int experts_per_rank,
int experts_per_node,
int local_rank,
cudaStream_t stream
) {
int block_size = 256;
int grid_size = (num_recv_tokens + block_size - 1) / block_size;
dense_to_sparse_prob_kernel<<<grid_size, block_size, 0, stream>>>(
dense_prob, local_expert_routing_map, recv_topk_weights, recv_topk_idx,
num_recv_tokens, topk, experts_per_rank, experts_per_node, local_rank);
}
// ============================================================================
// Call metadata preprocessing
// ============================================================================
void call_metadata_preprocessing(
const uint8_t* global_routing_map,
int32_t* sparse_to_dense_map,
bool* rdma_to_attn_map,
bool* attn_to_rdma_map,
uint8_t* token_rank_mask,
int32_t* num_tokens_for_experts,
bool* local_expert_routing_map,
int32_t* per_expert_token_counts,
void* scan_tmp,
int node_rank,
int local_rank,
int num_tokens_per_rank,
int hidden_dim,
int num_nodes,
int num_ranks_per_node,
int experts_per_rank,
cudaStream_t stream
) {
if (per_expert_token_counts != nullptr) {
// Fused scan path accumulates counts with atomicAdd.
CUDA_CHECK(cudaMemsetAsync(
per_expert_token_counts, 0, experts_per_rank * sizeof(int32_t), stream));
}
// MNNVL configurations (> 32 GPUs per LSA domain) are not yet supported: the scan
// kernel uses warp-reduction (LSA_TEAM_SIZE <= 32) and HYBRIDEP_SWITCH_LSA_TEAM_SIZE
// is only instantiated up to 32. Extend both when adding MNNVL support.
EP_HOST_ASSERT(num_ranks_per_node <= 32 && "metadata_preprocessing: LSA team size > 32 not yet supported (MNNVL)");
HYBRIDEP_SWITCH_NUM_LSA_TEAMS(num_nodes, {
HYBRIDEP_SWITCH_LSA_TEAM_SIZE(num_ranks_per_node, {
using HybridEPType = ::hybrid_ep::hybrid_ep<MAX_SUPPORTED_TOKENS_PER_RANK, NUM_LSA_TEAMS, LSA_TEAM_SIZE>;
HybridEPType::template metadata_preprocessing<
HYBRIDEP_NUM_THREADS_PER_BLOCK_PREPROCESSING, HYBRIDEP_NUM_BLOCKS_PREPROCESSING>(
global_routing_map,
reinterpret_cast<::hybrid_ep::tmp_state_t*>(scan_tmp),
sparse_to_dense_map,
rdma_to_attn_map,
attn_to_rdma_map,
token_rank_mask,
num_tokens_for_experts,
local_expert_routing_map,
per_expert_token_counts,
node_rank,
local_rank,
num_tokens_per_rank,
num_ranks_per_node,
experts_per_rank,
stream
);
});
});
}
size_t get_preprocessing_scan_tmp_size(int num_ranks_per_node) {
return HYBRIDEP_NUM_BLOCKS_PREPROCESSING * num_ranks_per_node * sizeof(::hybrid_ep::tmp_state_t);
}
// ============================================================================
// Dispatch wrapper implementation
// ============================================================================
// Helper to populate dispatch_kernel_param_t from DispatchParams
template<typename TOKEN_DATA_TYPE, int LSA_TEAM_SIZE>
::hybrid_ep::dispatch_kernel_param_t<TOKEN_DATA_TYPE, LSA_TEAM_SIZE>
build_dispatch_param(const DispatchParams& params) {
::hybrid_ep::dispatch_kernel_param_t<TOKEN_DATA_TYPE, LSA_TEAM_SIZE> kp{};
// Model configuration
kp.hidden_dim = params.hidden_dim;
kp.experts_per_rank = params.experts_per_rank;
kp.num_of_ranks_per_node = params.num_ranks_per_node;
// User input buffers
kp.attn_input_token = reinterpret_cast<const TOKEN_DATA_TYPE*>(params.attn_input_token);
kp.attn_input_prob = params.attn_input_prob;
kp.attn_input_token_scaling_factor = params.attn_input_scaling_factor;
// Copy IPC buffer pointers from HOST arrays into embedded param struct arrays.
// This allows fast __grid_constant__ access in the kernel (vs slow global memory indirection).
for (int i = 0; i < params.num_ranks_per_node; i++) {
kp.expert_output_token[i] =
reinterpret_cast<TOKEN_DATA_TYPE*>(params.expert_output_token_ptrs[i]);
kp.expert_output_prob[i] = params.expert_output_prob_ptrs ?
params.expert_output_prob_ptrs[i] : nullptr;
kp.expert_output_scaling_factor[i] = params.expert_output_scaling_factor_ptrs ?
params.expert_output_scaling_factor_ptrs[i] : nullptr;
}
// Metadata and sync flags
kp.rdma_to_attn_map = params.rdma_to_attn_map;
kp.attn_to_rdma_map = params.attn_to_rdma_map;
kp.sparse_to_dense_map = params.sparse_to_dense_map;
kp.expected_rdma_flag_value = params.expected_rdma_flag_value;
kp.expected_intra_node_flag_value = params.expected_intra_node_flag_value;
kp.rdma_inter_node_group_flags = params.rdma_inter_node_group_flags;
kp.intra_node_write_completion_flags = params.intra_node_write_completion_flags;
kp.dispatch_grid_barrier_counter = params.dispatch_grid_barrier_counter;
// Runtime config
kp.local_rank = params.local_rank;
kp.node_rank = params.node_rank;
kp.num_of_tokens_per_rank = params.num_tokens_per_rank;
// Pass device communicators and windows
kp.dcomms = params.dcomms;
kp.nccl_window = params.nccl_window;
kp.num_gin_comms = params.num_gin_comms;
kp.num_ctx_per_comm = params.num_ctx_per_comm;
kp.gin_base_ptr = params.gin_base_ptr;
kp.signals_base = params.signals_base;
// Use offsets relative to gin_base_ptr
kp.mr_info = {
.attn_input_token_offset = params.mr_info.attn_input_token_offset,
.attn_input_prob_offset = params.mr_info.attn_input_prob_offset,
.attn_input_scaling_factor_offset = params.mr_info.attn_input_scaling_factor_offset,
// Batched staging parameters (packed layout)
.rdma_send_staging_offset = params.mr_info.rdma_send_staging_offset,
.rdma_inter_node_group_packed_offset = params.mr_info.rdma_inter_node_group_packed_offset,
.bytes_per_entry = params.mr_info.bytes_per_entry,
.max_tokens_per_dest = params.mr_info.max_tokens_per_dest,
// Streaming signal parameters
.signals_tail_base = params.mr_info.signals_tail_base,
.num_max_rdma_chunked_send_tokens = params.mr_info.num_max_rdma_chunked_send_tokens
};
return kp;
}
// Template dispatch launcher for forward/backward and sync modes
template<bool FORWARD_DISPATCH>
void dispatch_impl(
const DispatchParams& params,
int max_tokens_per_rank,
int num_nodes,
bool use_fp8,
cudaStream_t stream
) {
HYBRIDEP_SWITCH_DATATYPE(use_fp8, {
HYBRIDEP_SWITCH_NUM_LSA_TEAMS(num_nodes, {
HYBRIDEP_SWITCH_LSA_TEAM_SIZE(params.num_ranks_per_node, {
// TMA requires prob buffer (experts_per_node * sizeof(float)) to be 16B aligned
// Check alignment at runtime now that experts_per_rank is dynamic
const int experts_per_node = params.experts_per_rank * params.num_ranks_per_node;
assert((experts_per_node * sizeof(float)) % 16 == 0 &&
"experts_per_node must be multiple of 4 for TMA alignment");
using HybridEPType = ::hybrid_ep::hybrid_ep<
MAX_SUPPORTED_TOKENS_PER_RANK,
NUM_LSA_TEAMS,
LSA_TEAM_SIZE>;
auto kp = build_dispatch_param<TOKEN_DATA_TYPE, LSA_TEAM_SIZE>(params);
HybridEPType::template dispatch<
TOKEN_DATA_TYPE,
HYBRIDEP_DISPATCH_NUM_OF_STAGES,
HYBRIDEP_DISPATCH_NUM_OF_IN_FLIGHT_S2G,
HT_OF_NUM_TOKENS_PER_CHUNK,
HYBRIDEP_DISPATCH_NUM_OF_BLOCKS,
FORWARD_DISPATCH>(kp, stream);
});
});
});
}
void call_dispatch(
const DispatchParams& params,
int max_tokens_per_rank,
int num_nodes,
bool use_fp8,
bool forward_dispatch,
cudaStream_t stream
) {
// Dispatch based on forward/backward and sync mode
if (forward_dispatch) {
dispatch_impl<true>(
params, max_tokens_per_rank,
num_nodes, use_fp8, stream);
} else {
dispatch_impl<false>(
params, max_tokens_per_rank,
num_nodes, use_fp8, stream);
}
}
// ============================================================================
// Combine wrapper implementation
// ============================================================================
// Helper to populate combine_kernel_param_t from CombineParams
template<int LSA_TEAM_SIZE>
::hybrid_ep::combine_kernel_param_t<LSA_TEAM_SIZE>
build_combine_param(const CombineParams& params) {
::hybrid_ep::combine_kernel_param_t<LSA_TEAM_SIZE> kp{};
// Copy IPC buffer pointers from HOST arrays into embedded param struct arrays.
// This allows fast __grid_constant__ access in the kernel (vs slow global memory indirection).
for (int i = 0; i < params.num_ranks_per_node; i++) {
kp.expert_input_token[i] = params.expert_input_token_ptrs[i];
kp.expert_input_prob[i] = params.expert_input_prob_ptrs ?
params.expert_input_prob_ptrs[i] : nullptr;
}
// Model configuration
kp.hidden_dim = params.hidden_dim;
kp.experts_per_rank = params.experts_per_rank;
kp.num_of_ranks_per_node = params.num_ranks_per_node;
// User output buffers
kp.attn_output_token = reinterpret_cast<uint16_t*>(params.attn_output_token);
kp.attn_output_prob = params.attn_output_prob;
// RDMA buffers (multi-node only)
kp.rdma_intra_node_red_token = params.rdma_intra_node_red_token;
kp.rdma_intra_node_red_prob = params.rdma_intra_node_red_prob;
kp.rdma_inter_node_group_token = params.combine_rdma_inter_node_group_token;
kp.rdma_inter_node_group_prob = params.combine_rdma_inter_node_group_prob;
// Metadata
kp.sparse_to_dense_map = params.sparse_to_dense_map;
kp.rdma_to_attn_map = params.rdma_to_attn_map;
kp.attn_to_rdma_map = params.attn_to_rdma_map;
// Sync flags
kp.expected_rdma_flag_value = params.combine_expected_rdma_flag_value;
kp.expected_intra_node_flag_value = params.combine_expected_intra_node_flag_value;
kp.rdma_inter_node_group_flags = params.combine_rdma_inter_node_group_flags;
kp.intra_node_write_completion_flags = params.combine_intra_node_write_completion_flags;
// Runtime config
kp.local_rank = params.local_rank;
kp.node_rank = params.node_rank;
kp.num_of_tokens_per_rank = params.num_tokens_per_rank;
// Pass device communicators and windows
kp.dcomms = params.dcomms;
kp.nccl_window = params.nccl_window;
kp.num_gin_comms = params.num_gin_comms;
kp.num_ctx_per_comm = params.num_ctx_per_comm;
kp.gin_base_ptr = params.gin_base_ptr;
kp.signals_base = params.signals_base;
kp.combine_signal_offset = params.combine_signal_offset;
// Use offsets relative to gin_base_ptr
kp.mr_info = {
.rdma_intra_node_red_token_offset = params.mr_info.rdma_intra_node_red_token_offset,
.combine_rdma_inter_node_group_token_offset = params.mr_info.combine_rdma_inter_node_group_token_offset,
.rdma_intra_node_red_prob_offset = params.mr_info.rdma_intra_node_red_prob_offset,
.combine_rdma_inter_node_group_prob_offset = params.mr_info.combine_rdma_inter_node_group_prob_offset
};
return kp;
}
// Template combine launcher for forward/backward
template<bool BACKWARD_COMBINE>
void combine_impl(
const CombineParams& params,
int max_tokens_per_rank,
int num_nodes,
cudaStream_t stream
) {
// HT combine doesn't support FP8, only BF16
using TOKEN_DATA_TYPE = uint16_t;
HYBRIDEP_SWITCH_NUM_LSA_TEAMS(num_nodes, {
HYBRIDEP_SWITCH_LSA_TEAM_SIZE(params.num_ranks_per_node, {
// TMA requires prob buffer (experts_per_node * sizeof(float)) to be 16B aligned
const int experts_per_node = params.experts_per_rank * params.num_ranks_per_node;
assert((experts_per_node * sizeof(float)) % 16 == 0 &&
"experts_per_node must be multiple of 4 for TMA alignment");
using HybridEPType = ::hybrid_ep::hybrid_ep<
MAX_SUPPORTED_TOKENS_PER_RANK,
NUM_LSA_TEAMS,
LSA_TEAM_SIZE>;
auto kp = build_combine_param<LSA_TEAM_SIZE>(params);
// Select config based on NUM_LSA_TEAMS (single-node: 12 stages/2 pipelines, multi-node: 5 stages/1 pipeline)
constexpr int num_stages_g2s = (NUM_LSA_TEAMS == 1)
? HYBRIDEP_COMBINE_SINGLENODE_NUM_OF_STAGES_G2S
: HYBRIDEP_COMBINE_MULTINODE_NUM_OF_STAGES_G2S;
constexpr int num_stages_s2g = (NUM_LSA_TEAMS == 1)
? HYBRIDEP_COMBINE_SINGLENODE_NUM_OF_STAGES_S2G
: HYBRIDEP_COMBINE_MULTINODE_NUM_OF_STAGES_S2G;
HybridEPType::template combine<
num_stages_g2s,
num_stages_s2g,
HT_OF_NUM_TOKENS_PER_CHUNK,
HYBRIDEP_COMBINE_NUM_OF_TOKENS_PER_GROUP,
HYBRIDEP_COMBINE_NUM_OF_BLOCKS,
HYBRIDEP_COMBINE_NUM_OF_ADDITIONAL_IN_FLIGHT_S2G,
BACKWARD_COMBINE>(kp, stream);
});
});
}
void call_combine(
const CombineParams& params,
int max_tokens_per_rank,
int num_nodes,
bool backward_combine,
cudaStream_t stream
) {
if (backward_combine) {
combine_impl<true>(
params, max_tokens_per_rank,
num_nodes, stream);
} else {
combine_impl<false>(
params, max_tokens_per_rank,
num_nodes, stream);
}
}
} // namespace hybridep
} // namespace nccl_ep