Files
TheRock/rocm-systems/projects/rccl/contrib/nccl_ep/device/hybridep_adapter.cuh
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

431 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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.
*/
#pragma once
#include <cstdint>
#include <cuda_runtime.h>
#include <nccl.h>
#include "nccl_device.h"
namespace nccl_ep {
namespace hybridep {
// ============================================================================
// Runtime constants (from ep_group, not hardcoded):
// lsa_team_size - ncclTeamLsa(comm).nRanks (up to 72 for MNNVL/NVL72)
// rdma_ranks - nRanks / lsa_team_size
// num_local_experts, hidden
//
// Use HYBRIDEP_SWITCH_* macros to instantiate templates with runtime values.
// LSA team size is dispatched in steps of 4 up to 72; each instantiation
// sizes its own register-file arrays via the LSA_TEAM_SIZE template param.
// ============================================================================
// ============================================================================
// Kernel: Convert sparse topk_idx to dense routing map
// ============================================================================
// NCCL API uses sparse format: topk_idx[token][k] = expert_id
// HT uses bitmap format: routing_bitmap[token][expert/8] has bit (expert%8) set
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, // = ceil(num_experts / 8)
cudaStream_t stream);
// ============================================================================
// Kernel: Convert sparse topk_weights to dense prob (for dispatch input)
// ============================================================================
// NCCL API uses sparse format: topk_weights[token][k] = weight, topk_idx[token][k] = expert_id
// HT uses dense format: prob[token][expert] = weight (0 for non-selected)
void sparse_to_dense_prob(
const int64_t* topk_idx, // [num_tokens, topk] - which experts
const float* topk_weights, // [num_tokens, topk] - weights for those experts
float* dense_prob, // [num_tokens, num_experts] - output dense prob (pre-zeroed)
int num_tokens,
int num_topk,
int num_experts,
cudaStream_t stream);
// ============================================================================
// Kernel: Convert sparse topk_weights to dense prob (for combine input)
// ============================================================================
// Used for combine backward pass. Unlike dispatch which has explicit topk_idx,
// combine uses local_expert_routing_map to determine expert positions.
// The order matches dispatch output (dense_to_sparse_prob): experts scanned in order 0,1,2...
// NOTE: No topk_idx needed - expert mapping comes from local_expert_routing_map
void sparse_to_dense_prob_combine(
const float* topk_weights, // [num_tokens, topk] - sparse weights from combine input
const bool* local_expert_routing_map, // [num_tokens, experts_per_rank] - from handle
float* dense_prob, // [num_tokens, experts_per_node] - output (pre-zeroed)
int num_tokens,
int num_topk,
int experts_per_rank,
int experts_per_node, // = experts_per_rank * ranks_per_node
int local_rank, // rank within node
cudaStream_t stream);
// ============================================================================
// Kernel: Convert dense prob output to sparse format (for dispatch output)
// ============================================================================
// HT outputs: dense_prob[recv_token][experts_per_node]
// HT expects: recv_topk_weights[recv_token][topk], recv_topk_idx[recv_token][topk]
// Uses local_expert_routing_map to know which experts each token is routed to.
// NOTE: recv_topk_idx contains LOCAL expert indices (0-based within this rank)
// to match NCCL API expectations.
void dense_to_sparse_prob(
const float* dense_prob, // [num_recv_tokens, experts_per_node] - from IPC buffer
const bool* local_expert_routing_map, // [num_recv_tokens, experts_per_rank] - from preprocessing
float* recv_topk_weights, // [num_recv_tokens, topk] - output sparse weights
int64_t* recv_topk_idx, // [num_recv_tokens, topk] - output LOCAL expert indices (0-based)
int num_recv_tokens,
int topk,
int experts_per_rank,
int experts_per_node, // = experts_per_rank * ranks_per_node
int local_rank, // rank within node
cudaStream_t stream);
// ============================================================================
// Kernel: Convert dense prob output to sparse format (for combine output)
// ============================================================================
// Used for combine backward pass output. Converts kernel's dense output back to
// sparse format with GLOBAL expert indices (matching original dispatch input format).
// Uses local_routing_map (original tokens → global experts) for the conversion.
void dense_to_sparse_prob_combine(
const float* dense_prob, // [num_tokens, num_experts] - from kernel output
const uint8_t* routing_bitmap, // [num_tokens, ceil(num_experts / 8)] - bitmap routing map
float* combined_topk_weights, // [num_tokens, topk] - output sparse weights
int64_t* combined_topk_idx, // [num_tokens, topk] - output GLOBAL expert indices (optional, can be nullptr)
int num_tokens,
int topk,
int num_experts,
cudaStream_t stream);
// ============================================================================
// Switch macros for compile-time template instantiation
// Uses constants from configs.cuh where applicable
// ============================================================================
// Switch on LSA team size (multiples of 4, up to 32).
// Instantiates templates with LSA_TEAM_SIZE, sizing register-file arrays exactly.
// MNNVL configurations (NVL72, LSA_TEAM_SIZE > 32) are not yet supported:
// the scan kernel in metadata_preprocessing uses warp-reduction and requires
// LSA_TEAM_SIZE <= 32. Extend this macro (and the scan kernel) when adding MNNVL support.
//
// Compile-time filtering: set _NCCL_EP_LSA_TEAM_SIZE_MIN / _NCCL_EP_LSA_TEAM_SIZE_MAX
// (via make _NCCL_EP_LSA_TEAM_SIZE_MIN=N or cmake -D_NCCL_EP_LSA_TEAM_SIZE_MIN=N) to
// restrict which instantiations are compiled. Reduces build time during development.
// When unset, the full set (4–32, multiples of 4) is compiled.
// Per-size case helpers — expand to the switch case when N is within the configured
// [MIN, MAX] range, empty otherwise. #if must live at file scope (not inside a macro
// body). MIN/MAX are always defined by the build system (Makefile/?= 4/32, CMake default 4/32).
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 4 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 4
#define _NCCL_EP_LSA_CASE_4(...) case 4: { constexpr int LSA_TEAM_SIZE = 4; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_4(...)
#endif
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 8 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 8
#define _NCCL_EP_LSA_CASE_8(...) case 8: { constexpr int LSA_TEAM_SIZE = 8; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_8(...)
#endif
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 12 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 12
#define _NCCL_EP_LSA_CASE_12(...) case 12: { constexpr int LSA_TEAM_SIZE = 12; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_12(...)
#endif
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 16 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 16
#define _NCCL_EP_LSA_CASE_16(...) case 16: { constexpr int LSA_TEAM_SIZE = 16; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_16(...)
#endif
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 20 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 20
#define _NCCL_EP_LSA_CASE_20(...) case 20: { constexpr int LSA_TEAM_SIZE = 20; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_20(...)
#endif
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 24 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 24
#define _NCCL_EP_LSA_CASE_24(...) case 24: { constexpr int LSA_TEAM_SIZE = 24; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_24(...)
#endif
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 28 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 28
#define _NCCL_EP_LSA_CASE_28(...) case 28: { constexpr int LSA_TEAM_SIZE = 28; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_28(...)
#endif
#if _NCCL_EP_LSA_TEAM_SIZE_MIN <= 32 && _NCCL_EP_LSA_TEAM_SIZE_MAX >= 32
#define _NCCL_EP_LSA_CASE_32(...) case 32: { constexpr int LSA_TEAM_SIZE = 32; __VA_ARGS__; } break;
#else
#define _NCCL_EP_LSA_CASE_32(...)
#endif
#define HYBRIDEP_SWITCH_LSA_TEAM_SIZE(lsa_val, ...) \
do { switch (lsa_val) { \
_NCCL_EP_LSA_CASE_4(__VA_ARGS__) \
_NCCL_EP_LSA_CASE_8(__VA_ARGS__) \
_NCCL_EP_LSA_CASE_12(__VA_ARGS__) \
_NCCL_EP_LSA_CASE_16(__VA_ARGS__) \
_NCCL_EP_LSA_CASE_20(__VA_ARGS__) \
_NCCL_EP_LSA_CASE_24(__VA_ARGS__) \
_NCCL_EP_LSA_CASE_28(__VA_ARGS__) \
_NCCL_EP_LSA_CASE_32(__VA_ARGS__) \
default: assert(false && "Unsupported LSA team size (must be multiple of 4, " \
"in [_NCCL_EP_LSA_TEAM_SIZE_MIN, _NCCL_EP_LSA_TEAM_SIZE_MAX])"); \
} } while(0)
// Switch on number of LSA domains (RDMA peers = nRanks / lsa_team_size).
// Each LSA domain is one NVLink/MNNVL clique; domains communicate via RDMA.
//
// Compile-time filtering: define _NCCL_EP_NUM_LSA_TEAMS_N=1 for each N to include.
// Set via make (e.g. _NCCL_EP_NUM_LSA_TEAMS_LIST="1 2") or cmake.
// Undefined flags default to 0 (excluded). Full set when unset: 1 2 3 4 8.
#if defined(_NCCL_EP_NUM_LSA_TEAMS_1) && _NCCL_EP_NUM_LSA_TEAMS_1
#define _NCCL_EP_NLT_CASE_1(...) case 1: { constexpr int NUM_LSA_TEAMS = 1; __VA_ARGS__; } break;
#else
#define _NCCL_EP_NLT_CASE_1(...)
#endif
#if defined(_NCCL_EP_NUM_LSA_TEAMS_2) && _NCCL_EP_NUM_LSA_TEAMS_2
#define _NCCL_EP_NLT_CASE_2(...) case 2: { constexpr int NUM_LSA_TEAMS = 2; __VA_ARGS__; } break;
#else
#define _NCCL_EP_NLT_CASE_2(...)
#endif
#if defined(_NCCL_EP_NUM_LSA_TEAMS_3) && _NCCL_EP_NUM_LSA_TEAMS_3
#define _NCCL_EP_NLT_CASE_3(...) case 3: { constexpr int NUM_LSA_TEAMS = 3; __VA_ARGS__; } break;
#else
#define _NCCL_EP_NLT_CASE_3(...)
#endif
#if defined(_NCCL_EP_NUM_LSA_TEAMS_4) && _NCCL_EP_NUM_LSA_TEAMS_4
#define _NCCL_EP_NLT_CASE_4(...) case 4: { constexpr int NUM_LSA_TEAMS = 4; __VA_ARGS__; } break;
#else
#define _NCCL_EP_NLT_CASE_4(...)
#endif
#if defined(_NCCL_EP_NUM_LSA_TEAMS_8) && _NCCL_EP_NUM_LSA_TEAMS_8
#define _NCCL_EP_NLT_CASE_8(...) case 8: { constexpr int NUM_LSA_TEAMS = 8; __VA_ARGS__; } break;
#else
#define _NCCL_EP_NLT_CASE_8(...)
#endif
#define HYBRIDEP_SWITCH_NUM_LSA_TEAMS(num_lsa_domains_val, ...) \
do { switch (num_lsa_domains_val) { \
_NCCL_EP_NLT_CASE_1(__VA_ARGS__) \
_NCCL_EP_NLT_CASE_2(__VA_ARGS__) \
_NCCL_EP_NLT_CASE_3(__VA_ARGS__) \
_NCCL_EP_NLT_CASE_4(__VA_ARGS__) \
_NCCL_EP_NLT_CASE_8(__VA_ARGS__) \
default: assert(false && "Unsupported LSA domain count (must be in " \
"_NCCL_EP_NUM_LSA_TEAMS_LIST, default 1 2 3 4 8)"); \
} } while(0)
// Switch on token data type (BF16 = uint16_t, FP8 = uint8_t)
// use_fp8: false = BF16 (uint16_t), true = FP8 (uint8_t)
#define HYBRIDEP_SWITCH_DATATYPE(use_fp8, ...) \
do { \
if (use_fp8) { \
using TOKEN_DATA_TYPE = uint8_t; \
__VA_ARGS__; \
} else { \
using TOKEN_DATA_TYPE = uint16_t; \
__VA_ARGS__; \
} \
} while(0)
// ============================================================================
// Preprocessing wrapper with template parameter resolution
// ============================================================================
// Helper to call metadata_preprocessing with resolved template parameters
// Note: MAX_NUM_OF_TOKENS_PER_RANK is not used by metadata_preprocessing,
// so we use a fixed dummy value for class instantiation.
// Note: Caller must provide a pre-allocated scan temp buffer (see get_preprocessing_scan_tmp_size).
// Also computes per-expert token counts for NCCL API compatibility when requested.
void call_metadata_preprocessing(
const uint8_t* global_routing_map, // Already allgathered bitmap routing map
int32_t* sparse_to_dense_map, // Output: token→rank→position mapping
bool* rdma_to_attn_map, // Output: which tokens come from RDMA
bool* attn_to_rdma_map, // Output: which tokens go to RDMA
uint8_t* token_rank_mask, // Scratch: cached per-token rank mask [tokens * ranks_per_node * nodes]
int32_t* num_tokens_for_experts, // Output: total tokens for local experts
bool* local_expert_routing_map, // Output: per-expert routing for local tokens
int32_t* per_expert_token_counts, // Optional output: per-expert counts (nullptr to skip)
void* scan_tmp, // Pre-allocated temp buffer (from get_preprocessing_scan_tmp_size)
int node_rank, // This node's rank (0 to num_nodes-1)
int local_rank, // Rank within node (0 to num_ranks_per_node-1)
int num_tokens_per_rank, // Actual tokens per rank this iteration (runtime)
int hidden_dim, // Model hidden dimension
int num_nodes, // Number of nodes (RDMA domain size)
int num_ranks_per_node, // Ranks per node (NVLink domain size, 1-8)
int experts_per_rank, // Experts per GPU
cudaStream_t stream);
// Returns required size in bytes for the scan temp buffer used by call_metadata_preprocessing.
// Caller must allocate at least this many bytes and pass the pointer to call_metadata_preprocessing.
size_t get_preprocessing_scan_tmp_size(int num_ranks_per_node);
// ============================================================================
// Memory region info structs for GIN
// All buffers are part of a single large gin_base_ptr buffer
// Offsets are relative to gin_base_ptr (stored as size_t for offset calculation)
// ============================================================================
struct dispatch_memory_region_info_t {
// Offsets relative to gin_base_ptr for RDMA operations
size_t attn_input_token_offset; // Offset of token staging buffer from gin_base_ptr
size_t attn_input_prob_offset; // Offset of prob staging buffer from gin_base_ptr
size_t attn_input_scaling_factor_offset; // Offset of scaling factor staging buffer
// Batched RDMA staging (packed layout: token+prob+sf per entry)
size_t rdma_send_staging_offset; // Offset of per-destination staging buffer
size_t rdma_inter_node_group_packed_offset;// Offset of packed receive buffer (token+prob+sf per entry)
size_t bytes_per_entry; // Size of packed entry (token + prob + sf)
size_t max_tokens_per_dest; // Max tokens that can be staged per destination
// Streaming RDMA signals
unsigned signals_tail_base; // Base signal ID for tail tracking (sender -> receiver)
// Streaming buffer configuration
int num_max_rdma_chunked_send_tokens; // Batch size per RDMA put (default: 6)
};
struct combine_memory_region_info_t {
// Offsets relative to gin_base_ptr for RDMA operations
size_t rdma_intra_node_red_token_offset; // Offset of intra-node reduced token buffer
size_t combine_rdma_inter_node_group_token_offset; // Offset of combine rdma token buffer
size_t rdma_intra_node_red_prob_offset; // Offset of intra-node reduced prob buffer
size_t combine_rdma_inter_node_group_prob_offset; // Offset of combine rdma prob buffer
};
// ============================================================================
// Dispatch wrapper with template parameter resolution
// ============================================================================
// All parameters needed for dispatch kernel, needed redifine because those in hybrid_ep.cuh are with template on data type.
struct DispatchParams {
// User inputs
int hidden_dim; // Model hidden dimension
int experts_per_rank; // Experts per GPU
int num_ranks_per_node; // Ranks per node (NVLink domain size, 1-8)
const void* attn_input_token;
const float* attn_input_prob; // Forward dispatch only
const float* attn_input_scaling_factor; // FP8 only
// IPC-mapped output buffers (from ep_group)
// Pointer tables are expected to be device-resident (d_* arrays).
void* const* expert_output_token_ptrs; // Array[num_ranks_per_node]
float* const* expert_output_prob_ptrs; // Forward only
float* const* expert_output_scaling_factor_ptrs; // FP8 only
// Metadata (from handle->hybridep preprocessing outputs)
const bool* rdma_to_attn_map;
const bool* attn_to_rdma_map;
const int32_t* sparse_to_dense_map;
// Sync state (group-level monotonic counters, computed on host)
uint64_t expected_rdma_flag_value;
uint32_t expected_intra_node_flag_value;
uint64_t* rdma_inter_node_group_flags;
uint32_t* intra_node_write_completion_flags;
// Grid barrier counter for fused device_sync in dispatch tail
uint32_t* dispatch_grid_barrier_counter;
// GIN context (from ep_group, multi-node only)
ncclDevComm_t* dcomms; // Device communicators array
ncclWindow_t nccl_window; // Single registered window handle (by value)
int num_gin_comms; // Number of GIN communicators
int num_ctx_per_comm; // Number of contexts per communicator
void* gin_base_ptr; // Base pointer for offset calculations
unsigned signals_base; // Base signal ID
dispatch_memory_region_info_t mr_info;
// Runtime config
int local_rank;
int node_rank;
int num_tokens_per_rank;
};
// Call dispatch kernel with runtime template parameter resolution
void call_dispatch(
const DispatchParams& params,
int max_tokens_per_rank, // Max tokens for buffer sizing
int num_nodes, // Number of nodes (RDMA domain size)
bool use_fp8, // false = BF16 (uint16_t), true = FP8 (uint8_t)
bool forward_dispatch, // True for forward, false for backward
cudaStream_t stream);
// ============================================================================
// Combine wrapper with template parameter resolution
// ============================================================================
// All parameters needed for combine kernel
struct CombineParams {
// IPC-mapped input buffers (expert outputs from MLP)
// NOTE: Pass HOST arrays containing device pointers (not device arrays).
// These pointers are copied into the kernel param struct for fast __grid_constant__ access.
int hidden_dim; // Model hidden dimension
int experts_per_rank; // Experts per GPU
int num_ranks_per_node; // Ranks per node (NVLink domain size, 1-8)
uint16_t* const* expert_input_token_ptrs; // HOST array[num_ranks_per_node], BF16 only
float* const* expert_input_prob_ptrs; // HOST array, backward only
// User output buffers
void* attn_output_token;
float* attn_output_prob; // Backward only (dense format for kernel)
// RDMA buffers (multi-node only, from ep_group)
uint16_t* rdma_intra_node_red_token;
float* rdma_intra_node_red_prob; // Backward only
const uint16_t* combine_rdma_inter_node_group_token;
const float* combine_rdma_inter_node_group_prob; // Backward only
// Metadata (from handle->hybridep preprocessing outputs)
const int32_t* sparse_to_dense_map;
const bool* rdma_to_attn_map;
const bool* attn_to_rdma_map; // For multi-node RDMA routing
const bool* local_expert_routing_map; // For backward gradient routing
// Sync state (group-level monotonic counters, computed on host)
uint64_t combine_expected_rdma_flag_value;
uint32_t combine_expected_intra_node_flag_value;
uint64_t* combine_rdma_inter_node_group_flags;
uint32_t* combine_intra_node_write_completion_flags;
// GIN context (multi-node only)
ncclDevComm_t* dcomms; // Device communicators array
ncclWindow_t nccl_window; // Single registered window handle (by value)
int num_gin_comms; // Number of GIN communicators
int num_ctx_per_comm; // Number of contexts per communicator
void* gin_base_ptr; // Base pointer for offset calculations
unsigned signals_base; // Base signal ID
unsigned combine_signal_offset; // Signal offset for combine operations
combine_memory_region_info_t mr_info;
// Runtime config
int local_rank;
int node_rank;
int num_tokens_per_rank; // Original token count from dispatch
int num_recv_tokens; // Actual received tokens this rank
};
// Call combine kernel with runtime template parameter resolution
// Note: HT combine doesn't support FP8, only BF16
void call_combine(
const CombineParams& params,
int max_tokens_per_rank, // Max tokens for buffer sizing
int num_nodes, // Number of nodes (RDMA domain size)
bool backward_combine, // True for backward (training), false for forward
cudaStream_t stream);
} // namespace hybridep
} // namespace nccl_ep