Files
project_6/ex_engine/csrc/factor_moe_topk_softmax.cu
EX Engine 8e6adf20e6 refactor(EX): upstream-aligned kernels + FlashQLA GDN backend
Major changes based on upstream_ref analysis:

1. factor_moe_topk_softmax.cu v2.0: Rewritten using ds_vllm/TRT-LLM
   warp shuffle pattern (from topk_softmax_kernels.cu). Key differences:
   - Zero shared memory (all butterfly __shfl_xor_sync)
   - VPT=2, THREADS_PER_ROW=32 (1 warp per token row)
   - 4 warps per CTA (4 tokens per block)
   - Iterative argmax with winner suppression for top-K
   - NaN/Inf clamping to 0 (prevents duplicate expert IDs)

2. GDN: FlashQLA backend (PROVEN on real BI-V100):
   - Compiles with corex clang/16 --cuda-gpu-arch=ivcore10
   - Real test: NaN=False on gdn_forward(B=1, T=64, H=4, K=128)
   - Replaces custom factor_gdn_chunk_fwd.cu (archived to .ref)
   - patch_model.py now JIT-loads FlashQLA extension at runtime

3. build.sh: Correct corex flags from real compile log:
   --cuda-gpu-arch=ivcore10 (NOT sm_70)
   -D__ILUVATAR__ -D__ILUVATAR_WORKAROUND__ -D__ILUVATAR_DIAG__
   -cl-single-precision-constant -mllvm --bonus-inst-threshold=0

Key insight from xllm/kernels/ilu/ixformer.h:
  ixformer::infer::topk_softmax() EXISTS at C++ level but Python
  ixformer.functions binding is missing. Our .so factor bypasses
  the missing Python binding entirely via dlopen/ctypes.
2026-08-10 02:55:58 +00:00

261 lines
9.3 KiB
Plaintext

// ex_engine/csrc/factor_moe_topk_softmax.cu
//
// Factor 0: MOE_TOPK_SOFTMAX — fused softmax + top-k for MoE routing
//
// Based on: ds_vllm/csrc/moe/topk_softmax_kernels.cu (TensorRT-LLM derived)
// and: xllm/kernels/cuda/moe/moe_topk_softmax_kernels.cuh
//
// Key insight from upstream: 64 experts is a power-of-2, so we use the
// specialized topkGating kernel that packs multiple rows per warp and
// eliminates shared memory entirely.
//
// For NUM_EXPERTS=64, VPT=2, THREADS_PER_ROW=32:
// - Each warp handles 1 row (64 experts / 2 per thread = 32 threads)
// - Softmax via warp shuffle butterfly reduce
// - TopK via iterative warp argmax with winner suppression
// - No shared memory needed, no CTA sync needed
//
// BI-V100 (SM70): 32-wide warps, 16 SMs, 49152 SMEM (not used here)
#include <cuda_runtime.h>
#include <cuda_fp16.h>
#include <float.h>
#include <stdint.h>
extern "C" {
#include "ex_engine.h"
}
// ---------------------------------------------------------------------------
// Compile-time config for Qwen3.5: 64 experts, top_k=8
// ---------------------------------------------------------------------------
static constexpr int NUM_EXPERTS = 64;
static constexpr int VPT = 2; // Values Per Thread (64 experts / 32 threads)
static constexpr int THREADS_PER_ROW = NUM_EXPERTS / VPT; // 32 = 1 warp
static constexpr int WARPS_PER_CTA = 4;
static constexpr int ROWS_PER_CTA = WARPS_PER_CTA; // 1 row per warp
// ---------------------------------------------------------------------------
// topkGatingSoftmax kernel — directly from ds_vllm/TRT-LLM pattern
//
// Each warp processes one token's row of 64 experts.
// Thread i in warp holds experts [2i, 2i+1] (VPT=2).
// All reduces via warp shuffle (__shfl_xor_sync) — zero shared memory.
// ---------------------------------------------------------------------------
__global__ void topk_gating_softmax_kernel(
const float* __restrict__ input, // (num_tokens, num_experts)
float* __restrict__ output, // (num_tokens, k)
int32_t* __restrict__ indices, // (num_tokens, k)
int32_t* __restrict__ source_rows, // (num_tokens, k) — token_expert_indices
int num_tokens,
int k,
bool renormalize
) {
// CTA and warp row assignment
const int cta_base_row = blockIdx.x * ROWS_PER_CTA;
const int warp_id = threadIdx.y;
const int thread_row = cta_base_row + warp_id;
if (thread_row >= num_tokens) return;
const int lane = threadIdx.x;
// ===== Load this thread's VPT=2 experts =====
const float* row_ptr = input + thread_row * NUM_EXPERTS;
float row_chunk[VPT];
#pragma unroll
for (int i = 0; i < VPT; i++) {
row_chunk[i] = row_ptr[lane * VPT + i];
}
// ===== Softmax: max reduction via butterfly =====
float thread_max = row_chunk[0];
#pragma unroll
for (int i = 1; i < VPT; i++) {
thread_max = fmaxf(thread_max, row_chunk[i]);
}
// Butterfly reduce for max across warp (32 threads = 64 experts)
#pragma unroll
for (int mask = THREADS_PER_ROW / 2; mask > 0; mask >>= 1) {
thread_max = fmaxf(thread_max,
__shfl_xor_sync(0xFFFFFFFF, thread_max, mask, THREADS_PER_ROW));
}
// ===== Softmax: exp and sum =====
float row_sum = 0.0f;
#pragma unroll
for (int i = 0; i < VPT; i++) {
row_chunk[i] = expf(row_chunk[i] - thread_max);
row_sum += row_chunk[i];
}
// Butterfly reduce for sum
#pragma unroll
for (int mask = THREADS_PER_ROW / 2; mask > 0; mask >>= 1) {
row_sum += __shfl_xor_sync(0xFFFFFFFF, row_sum, mask, THREADS_PER_ROW);
}
// ===== Normalize =====
float inv_sum = 1.0f / row_sum;
#pragma unroll
for (int i = 0; i < VPT; i++) {
row_chunk[i] *= inv_sum;
// Clamp NaN/Inf to 0 — prevents duplicate expert IDs downstream
if (isnan(row_chunk[i]) || isinf(row_chunk[i])) {
row_chunk[i] = 0.0f;
}
}
// ===== TopK via iterative warp argmax with winner suppression =====
int start_col = lane * VPT;
float selected_sum = 0.0f;
for (int k_idx = 0; k_idx < k; k_idx++) {
// Thread-local argmax
float max_val = row_chunk[0];
int expert = start_col;
#pragma unroll
for (int i = 1; i < VPT; i++) {
if (row_chunk[i] > max_val) {
max_val = row_chunk[i];
expert = start_col + i;
}
}
// Warp butterfly argmax — all threads agree on winner
#pragma unroll
for (int mask = THREADS_PER_ROW / 2; mask > 0; mask >>= 1) {
float other_val = __shfl_xor_sync(0xFFFFFFFF, max_val, mask, THREADS_PER_ROW);
int other_expert = __shfl_xor_sync(0xFFFFFFFF, expert, mask, THREADS_PER_ROW);
// Lower index wins ties (stable selection)
if (other_val > max_val ||
(other_val == max_val && other_expert < expert)) {
max_val = other_val;
expert = other_expert;
}
}
// Lane 0 writes result
if (lane == 0) {
int idx = k * thread_row + k_idx;
output[idx] = max_val;
indices[idx] = expert;
source_rows[idx] = k_idx * num_tokens + thread_row;
selected_sum += max_val;
}
// Suppress winner: the thread that owns the winning expert zeroes it
int winner_ldg = expert / VPT; // which thread owns this expert
int winner_offset = expert % VPT; // which slot in that thread
if (lane == winner_ldg) {
row_chunk[winner_offset] = -1.0f; // suppress for next iteration
}
}
// ===== Renormalize =====
if (renormalize && lane == 0) {
float denom = (selected_sum > 0.0f) ? selected_sum : 1.0f;
for (int k_idx = 0; k_idx < k; k_idx++) {
int idx = k * thread_row + k_idx;
output[idx] /= denom;
}
}
}
// ---------------------------------------------------------------------------
// Dispatch function matching EX Engine interface
// ---------------------------------------------------------------------------
static int moe_topk_softmax_dispatch(
void* output_v,
const void* input_v,
const void* aux_inputs[],
int n_aux,
const int64_t dims[],
int n_dims,
void* stream
) {
// dims[0] = T (tokens), dims[1] = num_experts, dims[2] = top_k
// output = topk_weights (T, K) float32
// aux[0] = topk_ids (T, K) int32
// aux[1] = token_expert_indices (T, K) int32 [needed by vllm]
if (n_dims < 3 || !output_v || !input_v) return -1;
int T = (int)dims[0];
int num_experts = (int)dims[1];
int top_k = (int)dims[2];
// Currently only optimized for 64 experts (Qwen3.5-MoE)
if (num_experts != NUM_EXPERTS) return -1;
float* topk_weights = (float*)output_v;
int32_t* topk_ids = (n_aux >= 1 && aux_inputs) ? (int32_t*)aux_inputs[0] : NULL;
int32_t* token_expert_indices = (n_aux >= 2 && aux_inputs) ? (int32_t*)aux_inputs[1] : NULL;
const float* logits = (const float*)input_v;
if (!topk_ids) return -1;
cudaStream_t cu_stream = (cudaStream_t)stream;
int num_blocks = (T + ROWS_PER_CTA - 1) / ROWS_PER_CTA;
dim3 grid(num_blocks);
dim3 block(THREADS_PER_ROW, WARPS_PER_CTA); // (32, 4) = 128 threads
topk_gating_softmax_kernel<<<grid, block, 0, cu_stream>>>(
logits, topk_weights, topk_ids, token_expert_indices,
T, top_k, true /* renormalize */
);
return 0;
}
// ---------------------------------------------------------------------------
// Also provide a direct C call for the Python ctypes loader
// ---------------------------------------------------------------------------
extern "C" int ex_dispatch_moe_topk_softmax(
float* topk_weights,
int32_t* topk_ids,
const float* logits,
int T, int E, int top_k,
void* stream
) {
if (E != NUM_EXPERTS) return -1;
cudaStream_t cu_stream = (cudaStream_t)stream;
int num_blocks = (T + ROWS_PER_CTA - 1) / ROWS_PER_CTA;
dim3 grid(num_blocks);
dim3 block(THREADS_PER_ROW, WARPS_PER_CTA);
// Allocate token_expert_indices alongside (vllm needs it)
// For EX dispatch, caller is responsible for this buffer
// Here we skip it and only write topk_weights + topk_ids
topk_gating_softmax_kernel<<<grid, block, 0, cu_stream>>>(
logits, topk_weights, topk_ids, NULL,
T, top_k, true
);
return 0;
}
// ---------------------------------------------------------------------------
// .so export
// ---------------------------------------------------------------------------
static ex_factor_t s_factor;
extern "C" ex_factor_t* ex_get_factor(const ex_hardware_t* hw) {
s_factor.factor_id = EX_FACTOR_MOE_TOPK_SOFTMAX;
s_factor.name = "moe_topk_softmax";
s_factor.version = "2.0.0";
s_factor.tuning = (ex_tuning_t){
.threads_per_block = THREADS_PER_ROW * WARPS_PER_CTA, // 128
.items_per_thread = VPT, // 2 experts per thread
.vec_size = 1, // scalar loads (64 < 128B threshold)
.shared_mem_bytes = 0, // zero — all warp shuffle
.num_warps = WARPS_PER_CTA, // 4 rows per CTA
.num_stages = 1
};
s_factor.kernel = moe_topk_softmax_dispatch;
s_factor.kernel_fallback = NULL;
return &s_factor;
}