#!/usr/bin/env python3
"""Minimal debug test for B1 mixed FP8 FMHA — compare per-step with BF16 reference.

Tests a single head with small N to isolate the precision issue.
"""
import sys
import math
import torch
import torch.nn.functional as F

def main():
    torch.manual_seed(42)
    HD = 512; NOPE = 448; ROPE = 64
    H = 1; B = 1; T = 1
    N = 128  # small
    scale = 1.0 / math.sqrt(HD)

    print(f"=== B1 Minimal Debug: N={N} H={H} HD={HD} ===")

    # Generate synthetic Q and KV
    q_fp32 = torch.randn(B, H, T, HD, dtype=torch.float32) * 0.5
    k_fp32 = torch.randn(N, HD, dtype=torch.float32) * 0.5

    q_bf16 = q_fp32.bfloat16().cuda()

    # Split KV
    k_nope_fp32 = k_fp32[:, :NOPE].contiguous()
    k_rope_fp32 = k_fp32[:, NOPE:].contiguous()

    # Quantize noPE to FP8 (same method as the production path)
    amax = k_nope_fp32.abs().amax(dim=-1, keepdim=True).clamp(min=1e-12)
    k_nope_scale = (amax / 448.0).squeeze(-1)  # (N,) FP32
    k_nope_fp8 = (k_nope_fp32 / k_nope_scale.unsqueeze(-1)).clamp(-448, 448).to(torch.float8_e4m3fn).view(torch.uint8)

    k_nope_fp8 = k_nope_fp8.cuda()
    k_nope_scale = k_nope_scale.cuda()
    k_rope_bf16 = k_rope_fp32.bfloat16().cuda()

    # Reference: BF16 SDPA
    k_nope_dequant = k_nope_fp8.cpu().view(torch.float8_e4m3fn).bfloat16() * k_nope_scale.cpu().unsqueeze(-1).bfloat16()
    k_full_bf16 = torch.cat([k_nope_dequant, k_rope_fp32.bfloat16()], dim=-1).cuda()
    v_full_bf16 = k_full_bf16.clone()

    q_3d = q_bf16.squeeze(0)  # (H, 1, HD)
    k_3d = k_full_bf16.unsqueeze(0)  # (1, N, HD)
    v_3d = v_full_bf16.unsqueeze(0)  # (1, N, HD)

    o_ref = F.scaled_dot_product_attention(
        q_3d.float(), k_3d.unsqueeze(0).float(), v_3d.unsqueeze(0).float(), scale=scale
    ).bfloat16()  # (1, H, 1, HD)
    o_ref = o_ref.squeeze(0)  # (H, 1, HD)

    print(f"Reference: |o|={o_ref.abs().max().item():.6f} mean={o_ref.float().mean().item():.6f}")
    print(f"  o[0,0,:8]={o_ref[0,0,:8].float().tolist()}")
    print(f"  o[0,0,440:448]={o_ref[0,0,440:448].float().tolist()}")

    # Mixed FP8 kernel
    from dsv4.kernels.attention.fmha_mixed_fp8_op import fmha_mixed_fp8_decode_raw
    q_4d = q_bf16  # (B, H, T, HD)
    o_mixed, lse = fmha_mixed_fp8_decode_raw(
        q_4d, k_nope_fp8, k_nope_scale, k_rope_bf16, scale, rope_dim=ROPE)
    # o_mixed: (B, H, T, HD)
    o_mixed_3d = o_mixed.squeeze(0)  # (H, 1, HD)

    print(f"Mixed FP8: |o|={o_mixed.abs().max().item():.6f} mean={o_mixed.float().mean().item():.6f}")
    print(f"  o[0,0,:8]={o_mixed_3d[0,0,:8].float().tolist()}")
    print(f"  o[0,0,440:448]={o_mixed_3d[0,0,440:448].float().tolist()}")

    # Cosine
    cos = F.cosine_similarity(o_ref.flatten().float(), o_mixed.flatten().float(), dim=0).item()
    print(f"\nCosine: {cos:.6f}")

    # LSE comparison
    # Reference LSE: log(sum(exp(scores)))
    q_f = q_3d.float()  # (H, 1, HD)
    k_f = k_3d.unsqueeze(0).float()  # (1, 1, N, HD)
    scores = torch.matmul(q_f, k_f.transpose(-2, -1)) * scale  # (H, 1, 1, N)
    ref_lse = torch.logsumexp(scores, dim=-1)  # (H, 1, 1)
    print(f"Ref LSE: {ref_lse[0,0,0].item():.6f}")
    print(f"Mixed LSE: {lse[0,0,0].item():.6f}")

    # Score distribution
    print(f"\nScores: min={scores.min().item():.4f} max={scores.max().item():.4f} mean={scores.mean().item():.4f}")

    # Check if the noPE vs RoPE contributions are correct
    q_nope_f = q_f[:, :, :NOPE]  # (H, 1, NOPE)
    q_rope_f = q_f[:, :, NOPE:]  # (H, 1, ROPE)
    k_nope_f = k_3d.unsqueeze(0).float()[:, :, :, :NOPE]  # (1, 1, N, NOPE)
    k_rope_f = k_3d.unsqueeze(0).float()[:, :, :, NOPE:]  # (1, 1, N, ROPE)

    scores_nope = torch.matmul(q_nope_f, k_nope_f.transpose(-2, -1)) * scale
    scores_rope = torch.matmul(q_rope_f, k_rope_f.transpose(-2, -1)) * scale
    print(f"noPE scores: [{scores_nope.min().item():.4f}, {scores_nope.max().item():.4f}]")
    print(f"RoPE scores: [{scores_rope.min().item():.4f}, {scores_rope.max().item():.4f}]")

    if cos < 0.999:
        print(f"\n!!! COSINE TOO LOW ({cos:.6f}) — B1 KERNEL IS BROKEN !!!")
        sys.exit(1)
    else:
        print(f"\nPASS: cosine {cos:.6f}")
        sys.exit(0)


if __name__ == "__main__":
    main()
