import torch

from sglang.kernels.jit.benchmark import marker
from sglang.kernels.ops.attention.dsv4.topk import (
    plan_topk_v2,
    topk_transform_512,
    topk_transform_512_v2,
)
from sglang.test.ci.ci_register import register_cuda_ci

register_cuda_ci(
    est_time=120, stage="base-b-kernel-benchmark", runner_config="1-gpu-large"
)

# Compressed page size used by the DSA indexer (real value is 256 // 4 = 64).
PAGE_SIZE = 64


def _make_inputs(batch_size: int, seq_len: int, k: int):
    torch.random.manual_seed(42)
    scores = torch.randn(batch_size, seq_len, dtype=torch.float32, device="cuda")
    seq_lens = torch.full((batch_size,), seq_len, dtype=torch.int32, device="cuda")
    num_pages = (seq_len + PAGE_SIZE - 1) // PAGE_SIZE
    page_table = (
        torch.arange(num_pages, dtype=torch.int32, device="cuda")
        .unsqueeze(0)
        .expand(batch_size, -1)
        .contiguous()
    )
    out = torch.empty(batch_size, k, dtype=torch.int32, device="cuda")
    return scores, seq_lens, page_table, out


def _make_p1_table(batch_size: int, seq_len: int):
    # flashinfer / torch do a per-token (page_size=1) gather, so they need a
    # (batch, seq) table (one entry per position) rather than the page-size-64 one.
    src_page_table = (
        torch.arange(seq_len, dtype=torch.int32, device="cuda")
        .unsqueeze(0)
        .expand(batch_size, -1)
        .contiguous()
    )
    lengths = torch.full((batch_size,), seq_len, dtype=torch.int32, device="cuda")
    return src_page_table, lengths


def _build_fn(provider: str, batch_size: int, seq_len: int, k: int):
    scores, seq_lens, page_table, out = _make_inputs(batch_size, seq_len, k)
    N = PAGE_SIZE

    def fn(scores, seq_lens, page_table):
        if provider == "jit_v1":
            topk_transform_512(scores, seq_lens, page_table, out, N)
            return out
        elif provider == "jit_v2":
            topk_transform_512_v2(scores, seq_lens, page_table, out, N, metadata)
            return out
        elif provider == "flashinfer":
            from flashinfer import top_k_page_table_transform

            return top_k_page_table_transform(scores, page_table, seq_lens, k)
        elif provider == "torch":
            idx = scores.topk(k, dim=-1).indices  # (batch, k) int64
            return torch.gather(page_table, 1, idx)
        else:
            raise ValueError(f"unknown provider {provider}")

    if provider in ("flashinfer", "torch"):
        page_table, seq_lens = _make_p1_table(batch_size, seq_len)
    if provider == "jit_v2":
        metadata = plan_topk_v2(seq_lens)
    return fn, (scores, seq_lens, page_table)


@marker.parametrize("k", [512, 1024, 2048], [512])
@marker.parametrize("seq_len", [2**x for x in range(10, 19)], [4096, 65536])
@marker.parametrize("batch_size", [2**x for x in range(13)], [1, 128, 1024])
@marker.benchmark("provider", ["jit_v1", "jit_v2", "flashinfer", "torch"])
def benchmark(seq_len: int, batch_size: int, k: int, provider: str):
    if k > seq_len:
        marker.skip("k cannot be larger than seq_len")
    if k == 2048 and provider == "jit_v1":
        marker.skip("jit_v1 does not support k=2048")

    fn, input_args = _build_fn(provider, batch_size, seq_len, k)
    return marker.do_bench(fn, input_args=input_args, memory_args=input_args[:2])


if __name__ == "__main__":
    benchmark.run()
