import torch
import torch.nn.functional as F
from sgl_kernel import gelu_and_mul as gelu_and_mul_aot
from sgl_kernel import gelu_tanh_and_mul as gelu_tanh_and_mul_aot
from sgl_kernel import silu_and_mul as silu_and_mul_aot

from sglang.kernels.jit.benchmark import marker
from sglang.kernels.jit.benchmark.utils import create_random
from sglang.kernels.ops.activation.activation import gelu_and_mul as gelu_and_mul_jit
from sglang.kernels.ops.activation.activation import (
    gelu_tanh_and_mul as gelu_tanh_and_mul_jit,
)
from sglang.kernels.ops.activation.activation import relu2 as relu2_jit
from sglang.kernels.ops.activation.activation import silu_and_mul as silu_and_mul_jit
from sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci

register_cuda_ci(
    est_time=30, stage="base-b-kernel-benchmark", runner_config="1-gpu-large"
)
register_amd_ci(est_time=30, stage="jit-kernel-benchmark", runner_config="amd")


@torch.compile
def silu_and_mul(input: torch.Tensor) -> torch.Tensor:
    lhs, rhs = input.split(input.shape[-1] // 2, dim=-1)
    return F.silu(lhs) * rhs


@torch.compile
def gelu_and_mul(input: torch.Tensor) -> torch.Tensor:
    lhs, rhs = input.split(input.shape[-1] // 2, dim=-1)
    return F.gelu(lhs, approximate="none") * rhs


@torch.compile
def gelu_tanh_and_mul(input: torch.Tensor) -> torch.Tensor:
    lhs, rhs = input.split(input.shape[-1] // 2, dim=-1)
    return F.gelu(lhs, approximate="tanh") * rhs


OPS = {
    "silu": (silu_and_mul_aot, silu_and_mul_jit, silu_and_mul),
    "gelu": (gelu_and_mul_aot, gelu_and_mul_jit, gelu_and_mul),
    "gelu_tanh": (gelu_tanh_and_mul_aot, gelu_tanh_and_mul_jit, gelu_tanh_and_mul),
}


@marker.parametrize("op_name", ["silu", "gelu", "gelu_tanh"])
@marker.parametrize("dim", [1024, 4096, 6144, 8192], [4096])
@marker.parametrize("batch_size", [2**x for x in range(0, 15)], [8, 512])
@marker.benchmark("impl", ["aot", "jit", "torch"])
def benchmark(op_name: str, dim: int, batch_size: int, impl: str):
    x = create_random(batch_size, dim * 2)
    aot_op, jit_op, torch_op = OPS[op_name]
    fn = {"aot": aot_op, "jit": jit_op, "torch": torch_op}[impl]
    return marker.do_bench(fn, input_args=(x,))


def _make_expert_ids(num_tokens: int, skip_ratio: float) -> torch.Tensor:
    expert_ids = torch.randint(low=0, high=8, size=(num_tokens,), dtype=torch.int32)
    if skip_ratio > 0:
        skip = torch.rand(num_tokens) < skip_ratio
        expert_ids[skip] = -1
    return expert_ids


@marker.parametrize("op_name", ["silu", "gelu"])
@marker.parametrize("dim", [1024, 4096, 8192], [4096])
@marker.parametrize("batch_size", [64, 256, 1024, 4096, 16384], [1024])
@marker.parametrize("skip_ratio", [0.0, 0.25, 0.5], [0.25])
@marker.benchmark("impl", ["unfiltered", "filtered"])
def benchmark_filter(
    op_name: str, dim: int, batch_size: int, skip_ratio: float, impl: str
):
    torch.random.manual_seed(42)
    x = create_random(batch_size, dim * 2)
    jit_fn = silu_and_mul_jit if op_name == "silu" else gelu_and_mul_jit
    extra_kwargs = {}
    expert_ids = _make_expert_ids(batch_size, skip_ratio)
    if impl == "filtered":
        extra_kwargs = {"expert_ids": expert_ids.to(x.device), "expert_step": 1}

    # NOTE: get the unmasked part from `experts_ids`
    real_skip_ratio = (expert_ids == -1).sum().item() / batch_size
    effective_bytes = int(x.nbytes * (1 - real_skip_ratio) * 1.5)
    return marker.do_bench(
        jit_fn,
        input_args=(x,),
        input_kwargs=extra_kwargs,
        memory_args=None,  # x is dynamic (counted in extra_memory_footprint)
        memory_output=None,  # same, output is dynamic
        extra_memory_footprint=effective_bytes,
    )


@torch.compile
def relu2_torch(input: torch.Tensor) -> torch.Tensor:
    return F.relu(input).pow(2)


@marker.parametrize("dim", [1024, 4096, 6144, 8192], [4096])
@marker.parametrize("batch_size", [2**x for x in range(0, 15)], [8, 512])
@marker.benchmark("impl", ["jit", "torch"])
def benchmark_unary(dim: int, batch_size: int, impl: str):
    x = create_random(batch_size, dim)
    fn = {"jit": relu2_jit, "torch": relu2_torch}[impl]
    return marker.do_bench(fn, input_args=(x,))


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