"""Manual correctness check for DeepSeek-V4 fp8 wo_a (deep_gemm.fp8_einsum path).

Mirrors models/deepseek_v4.py MQALayer wo_a: quantize the token-major attention
output [T, G, D] per-token-group(128) to fp8, then run the grouped matmul over the
group/head dim via deep_gemm.fp8_einsum("bhr,hdr->bhd") -> [T, G, R], and compare
against a bf16 einsum reference. sm100 (Blackwell) uses ue8m0 scales + recipe
(1,1,128); sm90 (Hopper) uses fp32 scales + recipe (1,128,128). Covers the Flash
(G=8) and Pro (G=16) shapes for both prefill (T=1024) and decode (small T).

    CUDA_VISIBLE_DEVICES=0 python3 test/manual/dsv4/test_wo_a_fp8_sm90.py
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass

import torch
import torch.nn.functional as F

from sglang.srt.layers import deep_gemm_wrapper


@dataclass(frozen=True)
class WoACase:
    name: str
    groups: int
    tokens: int
    k: int = 4096
    n: int = 1024


def cosine(a: torch.Tensor, b: torch.Tensor) -> float:
    return F.cosine_similarity(a.float().flatten(), b.float().flatten(), dim=0).item()


def quantize_weight_by_group(
    weight: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
    """Per-block (128x128) fp8 cast of the per-group wo_a weight [G, N, K]."""
    from deep_gemm.utils import per_block_cast_to_fp8

    groups, n, k = weight.shape
    weight_fp8 = torch.empty_like(weight, dtype=torch.float8_e4m3fn)
    weight_scale = torch.empty(
        (groups, n // 128, k // 128), device=weight.device, dtype=torch.float32
    )
    for group in range(groups):
        weight_fp8[group], weight_scale[group] = per_block_cast_to_fp8(
            weight[group], use_ue8m0=False, gran_k=128
        )
    return weight_fp8, weight_scale


def run_wo_a_einsum(
    o: torch.Tensor,  # [T, G, D] bf16
    weight_fp8: torch.Tensor,  # [G, N, K] fp8
    weight_scale: torch.Tensor,  # [G, N/128, K/128] fp32
) -> torch.Tensor:
    """Mirror models/deepseek_v4.py MQALayer wo_a fp8 einsum path."""
    import deep_gemm

    from sglang.kernels.ops.quantization.fp8_kernel import (
        sglang_per_token_group_quant_fp8,
    )

    T, G, D = o.shape
    _, R, _ = weight_fp8.shape
    o_fp8, o_s = sglang_per_token_group_quant_fp8(
        o.reshape(T * G, D).contiguous(),
        group_size=128,
    )

    recipe = (1, 128, 128)
    output = torch.empty(T, G, R, device=o.device, dtype=torch.bfloat16)
    deep_gemm.fp8_einsum(
        "bhr,hdr->bhd",
        (o_fp8.view(T, G, D), o_s.view(T, G, -1)),
        (weight_fp8, weight_scale),
        output,
        recipe=recipe,
    )
    return output


def check(case: WoACase, args: argparse.Namespace) -> None:
    device = torch.device(args.device)
    torch.manual_seed(args.seed)
    o = (
        torch.randn(
            case.tokens, case.groups, case.k, device=device, dtype=torch.bfloat16
        )
        * 0.1
    )
    weight = (
        torch.randn(case.groups, case.n, case.k, device=device, dtype=torch.bfloat16)
        * 0.05
    )
    weight_fp8, weight_scale = quantize_weight_by_group(weight)

    out = run_wo_a_einsum(o, weight_fp8, weight_scale)
    bf16_ref = torch.einsum("tgd,grd->tgr", o.float(), weight.float()).to(
        torch.bfloat16
    )
    torch.cuda.synchronize()

    cb = cosine(out, bf16_ref)
    print(f"{case.name}: G={case.groups} T={case.tokens} cos_bf16={cb:.6f}")
    if cb <= args.cos_gate:
        raise AssertionError(f"{case.name} cos_bf16 {cb} <= {args.cos_gate}")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--device", default="cuda")
    parser.add_argument("--seed", type=int, default=0)
    parser.add_argument("--cos-gate", type=float, default=0.999)
    parser.add_argument("--decode-tokens", type=int, default=16)
    args = parser.parse_args()

    if deep_gemm_wrapper.DEEPGEMM_SCALE_UE8M0:
        print(
            "SKIP: this manual test validates the sm90 fp32-scale wo_a einsum path; "
            "the sm100/Blackwell production path uses ue8m0 scales + a weight-scale "
            "transform not reproduced here."
        )
        return

    cases = [
        WoACase("flash prefill", groups=8, tokens=1024),
        WoACase("flash decode", groups=8, tokens=args.decode_tokens),
        WoACase("pro prefill", groups=16, tokens=1024),
        WoACase("pro decode", groups=16, tokens=args.decode_tokens),
    ]
    for case in cases:
        check(case, args)
    print("ALL OK")


if __name__ == "__main__":
    main()
