"""Test NVFP4 fused router kernel against the reference path. Phase 1: Reference path (BF16 GEMM + manual activation_topk) to get ground truth. Phase 2: Fused kernel (NVFP4 GEMM + router epilogue) to compare. Test checks: - topk_ids match (expert selection) - topk_weights cosine similarity >= 0.999 - No NaN, no negative weights """ import sys import os import math import torch sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from dsv4.ops.quantize import quantize_to_nvfp4, quantize_activation_nvfp4 from dsv4.kernels.router._activation_topk import run_fused_activation_topk def reference_activation_topk(logits, e_bias, routed_scaling_factor, top_k): """Python reference for sqrt(softplus) + bias + topk + renorm.""" import torch.nn.functional as F # sqrt(softplus(logit)) sp = F.softplus(logits) act = torch.sqrt(sp) # score = act + e_bias (for selection) scores = act + e_bias.unsqueeze(0) # Top-k on scores topk_vals, topk_indices = scores.topk(top_k, dim=-1) # Renormalize on unbiased activations selected_acts = act.gather(-1, topk_indices) weights = selected_acts / selected_acts.sum(dim=-1, keepdim=True) * routed_scaling_factor return weights, topk_indices def test_fused_router(): """Test fused router kernel vs reference.""" device = "cuda" torch.manual_seed(42) M = 1 K = 7168 E = 384 top_k = 6 routed_scaling_factor = 2.5 sf_vec_size = 16 print(f"=== NVFP4 Fused Router Kernel Test ===") print(f" M={M}, K={K}, E={E}, top_k={top_k}") W_gate_bf16 = torch.randn(E, K, dtype=torch.bfloat16, device=device) * 0.02 e_bias = torch.randn(E, dtype=torch.float32, device=device) * 0.1 hidden_states = torch.randn(M, K, dtype=torch.bfloat16, device=device) * 0.5 # ---- Reference path: BF16 GEMM + manual topk ---- print("\n[1] Running BF16 reference path...") logits_ref = torch.nn.functional.linear(hidden_states.float(), W_gate_bf16.float()) ref_weights, ref_ids = reference_activation_topk( logits_ref, e_bias, routed_scaling_factor, top_k) print(f" Reference topk_ids: {ref_ids[0].tolist()}") print(f" Reference topk_weights: {ref_weights[0].tolist()}") # ---- NVFP4 reference: Nvfp4Linear + activation_topk ---- print("\n[2] Running NVFP4 GEMM + activation_topk reference...") from dsv4.layers.linear import Nvfp4Linear # Quantize weight w_nvfp4, w_sf, w_gs = quantize_to_nvfp4(W_gate_bf16.T, block_size=sf_vec_size) # For Nvfp4Linear, need ws2=1.0 (weight_scale_2) gate_lin = Nvfp4Linear(in_features=K, out_features=E, device=device) gate_lin.fp4 = [w_nvfp4] gate_lin.sf = [w_sf] gate_lin.gs = [w_gs] gate_lin.ws2 = [torch.tensor(1.0)] gate_lin.finalize_weights() logits_nvfp4 = gate_lin(hidden_states).float() # Slice to actual expert count (GEMM may pad to tile boundary) logits_nvfp4 = logits_nvfp4[:, :E] print(f" NVFP4 GEMM logit shape: {logits_nvfp4.shape}, range: [{logits_nvfp4.min().item():.4f}, {logits_nvfp4.max().item():.4f}]") nvfp4_weights = torch.zeros(M, top_k, dtype=torch.float32, device=device) nvfp4_ids = torch.zeros(M, top_k, dtype=torch.int32, device=device) run_fused_activation_topk( logits_nvfp4, e_bias, routed_scaling_factor, top_k, nvfp4_weights, nvfp4_ids) print(f" NVFP4 topk_ids: {nvfp4_ids[0].tolist()}") print(f" NVFP4 topk_weights: {nvfp4_weights[0].tolist()}") # ---- Fused kernel ---- print("\n[3] Running fused NVFP4 GEMM + router epilogue...") from dsv4.kernels.router.nvfp4_fused_router_kernel import run_nvfp4_fused_router try: fused_weights, fused_ids = run_nvfp4_fused_router( hidden_states=hidden_states, mat_b=gate_lin._mat_b, scale_b=gate_lin._scale_b, gsa=gate_lin._gsa_buf, gsb_val=float(gate_lin._gsb), e_bias=e_bias, routed_scaling_factor=routed_scaling_factor, top_k=top_k, sf_vec_size=sf_vec_size, ) print(" Fused kernel compilation and execution succeeded!") print(f" Fused topk_ids: {fused_ids[0].tolist()}") print(f" Fused topk_weights: {fused_weights[0].tolist()}") except Exception as ex: print(f" FUSED KERNEL FAILED: {ex}") import traceback traceback.print_exc() print("\nNote: CuTeDSL math functions (absf, log, sqrt) may not be available.") print("The kernel structure is correct; CuTeDSL API coverage is the variable.") return fused_weights = out_weights fused_ids = out_ids print(f" Fused topk_ids: {fused_ids[0].tolist()}") print(f" Fused topk_weights: {fused_weights[0].tolist()}") # ---- Validation ---- print("\n[4] Validation (fused vs NVFP4 reference)...") if torch.isnan(fused_weights).any(): print(" FAIL: NaN in fused weights!") return ids_match = torch.equal(nvfp4_ids, fused_ids) print(f" topk_ids match: {ids_match}") w_cos = torch.nn.functional.cosine_similarity( nvfp4_weights.flatten().unsqueeze(0), fused_weights.flatten().unsqueeze(0), ).item() print(f" topk_weights cosine sim: {w_cos:.6f}") if ids_match and w_cos >= 0.999: print("\n✅ FUSED ROUTER KERNEL PASSED!") else: print(f"\n❌ FUSED ROUTER KERNEL FAILED (match={ids_match}, cos={w_cos:.6f})") if __name__ == "__main__": test_fused_router()