"""Test GPU operations that occur between Linear layers in the Lumina2 model.
This tests LayerNorm, RMSNorm, SiLU, tensor reshaping, and manual SDPA."""
import torch
import torch.nn.functional as F
import time, os, sys

os.environ['HSA_OVERRIDE_GFX_VERSION'] = '10.1.0'
os.environ['HSA_ENABLE_SDMA'] = '0'
os.environ['TORCHDYNAMO_DISABLE'] = '1'

print(f"Device: {torch.cuda.get_device_name(0)}", flush=True)
print(f"AMD_SERIALIZE_KERNEL={os.environ.get('AMD_SERIALIZE_KERNEL','unset')}", flush=True)

# Dimensions matching Lumina2 z_image_turbo
B, SEQ, DIM = 1, 1024, 3840
HEADS = 24
HEAD_DIM = DIM // HEADS  # 160
dtype = torch.float16

def test_op(name, fn):
    print(f"\n--- {name} ---", flush=True)
    try:
        t0 = time.time()
        result = fn()
        torch.cuda.synchronize()
        dt = time.time() - t0
        if hasattr(result, 'shape'):
            print(f"  OK: {dt*1000:.1f}ms, shape={list(result.shape)}, dtype={result.dtype}", flush=True)
        else:
            print(f"  OK: {dt*1000:.1f}ms", flush=True)
        return result
    except Exception as e:
        print(f"  ERROR: {type(e).__name__}: {e}", flush=True)
        return None

# Create typical model tensors
x = torch.randn(B, SEQ, DIM, dtype=dtype, device='cuda:0')
torch.cuda.synchronize()
print(f"Input tensor on GPU: {list(x.shape)}", flush=True)

# Test 1: F.layer_norm
weight_ln = torch.randn(DIM, dtype=dtype, device='cuda:0')
bias_ln = torch.randn(DIM, dtype=dtype, device='cuda:0')
test_op("F.layer_norm", lambda: F.layer_norm(x, (DIM,), weight_ln, bias_ln))

# Test 2: RMSNorm manual (common in Lumina2)
def rms_norm(x, w, eps=1e-5):
    rms = torch.sqrt(torch.mean(x * x, dim=-1, keepdim=True) + eps)
    return x / rms * w
test_op("RMSNorm manual", lambda: rms_norm(x, weight_ln))

# Test 3: SiLU activation
test_op("F.silu", lambda: F.silu(x))

# Test 4: GELU activation  
test_op("F.gelu", lambda: F.gelu(x))

# Test 5: Tensor reshape (view + transpose)
def test_reshape():
    q = x.view(B, SEQ, HEADS, HEAD_DIM).transpose(1, 2)  # [B, HEADS, SEQ, HEAD_DIM]
    return q
test_op("view+transpose", test_reshape)

# Test 6: Tensor repeat (GQA expansion)
def test_repeat():
    kv = torch.randn(B, SEQ, 8, HEAD_DIM, dtype=dtype, device='cuda:0')
    kv = kv.unsqueeze(3).repeat(1, 1, 1, 3, 1).flatten(2, 3)
    return kv
test_op("unsqueeze+repeat+flatten", test_repeat)

# Test 7: torch.cat
def test_cat():
    a = torch.randn(B, SEQ, DIM, dtype=dtype, device='cuda:0')
    b = torch.randn(B, SEQ, DIM, dtype=dtype, device='cuda:0')
    return torch.cat([a, b], dim=1)
test_op("torch.cat", test_cat)

# Test 8: Full manual SDPA (like our patch does)
def test_manual_sdpa():
    Q = torch.randn(B, HEADS, SEQ, HEAD_DIM, dtype=dtype, device='cuda:0')
    K = torch.randn(B, HEADS, SEQ, HEAD_DIM, dtype=dtype, device='cuda:0')
    V = torch.randn(B, HEADS, SEQ, HEAD_DIM, dtype=dtype, device='cuda:0')
    torch.cuda.synchronize()
    
    scale = HEAD_DIM ** -0.5
    attn = torch.matmul(Q, K.transpose(-2, -1)) * scale
    torch.cuda.synchronize()
    
    attn_max = attn.max(dim=-1, keepdim=True).values
    exp_attn = torch.exp(attn - attn_max)
    attn_w = exp_attn / exp_attn.sum(dim=-1, keepdim=True)
    torch.cuda.synchronize()
    
    out = torch.matmul(attn_w, V)
    torch.cuda.synchronize()
    return out
test_op("Manual SDPA (24 heads, 1024 seq, 160 dim)", test_manual_sdpa)

# Test 9: Einsum (used in rope)
def test_einsum():
    pos = torch.randn(B, SEQ, dtype=torch.float32, device='cuda:0')
    omega = torch.randn(HEAD_DIM // 2, dtype=torch.float32, device='cuda:0')
    return torch.einsum("...n,d->...nd", pos, omega)
test_op("einsum (rope-like)", test_einsum)

# Test 10: Sequential like model forward (LayerNorm → Linear → SiLU → Linear)
def test_sequential():
    h = F.layer_norm(x, (DIM,), weight_ln, bias_ln)
    torch.cuda.synchronize()
    w1 = torch.randn(DIM*4, DIM, dtype=dtype, device='cuda:0')
    h = F.linear(h, w1)
    torch.cuda.synchronize()
    h = F.silu(h)
    torch.cuda.synchronize()
    w2 = torch.randn(DIM, DIM*4, dtype=dtype, device='cuda:0')
    h = F.linear(h, w2)
    torch.cuda.synchronize()
    return h
test_op("Sequential: LN → Linear → SiLU → Linear", test_sequential)

print("\n=== ALL TESTS PASSED ===", flush=True)
