"""Fix SDPA patch for BC-250 gfx1010: always use manual SDPA on CUDA"""
import sys

path = '/home/fabian/ComfyUI/bc250_softmax_patch.py'
with open(path, 'r') as f:
    content = f.read()

# Fix 1: patched_sdpa - ALWAYS use manual SDPA on CUDA (no threshold)
old_sdpa = '''def patched_sdpa(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
    S = key.size(-2)
    if query.is_cuda and S > SAFE_SOFTMAX_THRESHOLD:
        if not getattr(patched_sdpa, '_logged', False):
            logger.warning(f"[BC-250] Manual SDPA: Q={list(query.shape)}, S={S}")
            patched_sdpa._logged = True
        return _safe_sdpa(query, key, value, attn_mask=attn_mask,
                         dropout_p=dropout_p, is_causal=is_causal, scale=scale)
    return _original_sdpa(query, key, value, attn_mask=attn_mask,
                         dropout_p=dropout_p, is_causal=is_causal, scale=scale)'''

new_sdpa = '''def patched_sdpa(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
    # gfx1010: ALWAYS use manual SDPA on CUDA — built-in math backend kernel hangs
    if query.is_cuda:
        if not getattr(patched_sdpa, '_logged', False):
            S = key.size(-2)
            logger.warning(f"[BC-250] Manual SDPA (ALWAYS): Q={list(query.shape)}, S={S}")
            patched_sdpa._logged = True
        return _safe_sdpa(query, key, value, attn_mask=attn_mask,
                         dropout_p=dropout_p, is_causal=is_causal, scale=scale)
    return _original_sdpa(query, key, value, attn_mask=attn_mask,
                         dropout_p=dropout_p, is_causal=is_causal, scale=scale)'''

if old_sdpa not in content:
    print("ERROR: patched_sdpa not found!")
    sys.exit(1)
content = content.replace(old_sdpa, new_sdpa)
print("OK: patched_sdpa → always manual on CUDA")

# Fix 2: _safe_sdpa - add sync after GPU matmul+softmax operations
old_safe = '''def _safe_sdpa(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
    L, S = query.size(-2), key.size(-2)
    if scale is None:
        scale = query.size(-1) ** -0.5
    attn_weight = torch.matmul(query, key.transpose(-2, -1)) * scale
    if is_causal:
        causal_mask = torch.triu(torch.ones(L, S, dtype=torch.bool, device=query.device), diagonal=1)
        attn_weight = attn_weight.masked_fill(causal_mask, float('-inf'))
    if attn_mask is not None:
        if attn_mask.dtype == torch.bool:
            attn_weight = attn_weight.masked_fill(~attn_mask, float('-inf'))
        else:
            attn_weight = attn_weight + attn_mask
    attn_weight = _safe_softmax_impl(attn_weight, dim=-1)
    if dropout_p > 0.0:
        attn_weight = torch.nn.functional.dropout(attn_weight, p=dropout_p)
    return torch.matmul(attn_weight, value)'''

new_safe = '''def _safe_sdpa(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
    L, S = query.size(-2), key.size(-2)
    if scale is None:
        scale = query.size(-1) ** -0.5
    # gfx1010: sync between GPU ops to prevent kernel queue buildup + hang
    attn_weight = torch.matmul(query, key.transpose(-2, -1)) * scale
    if query.is_cuda:
        torch.cuda.synchronize()
    if is_causal:
        causal_mask = torch.triu(torch.ones(L, S, dtype=torch.bool, device=query.device), diagonal=1)
        attn_weight = attn_weight.masked_fill(causal_mask, float('-inf'))
    if attn_mask is not None:
        if attn_mask.dtype == torch.bool:
            attn_weight = attn_weight.masked_fill(~attn_mask, float('-inf'))
        else:
            attn_weight = attn_weight + attn_mask
    attn_weight = _safe_softmax_impl(attn_weight, dim=-1)
    if query.is_cuda:
        torch.cuda.synchronize()
    if dropout_p > 0.0:
        attn_weight = torch.nn.functional.dropout(attn_weight, p=dropout_p)
    output = torch.matmul(attn_weight, value)
    if query.is_cuda:
        torch.cuda.synchronize()
    return output'''

if old_safe not in content:
    print("ERROR: _safe_sdpa not found!")
    sys.exit(1)
content = content.replace(old_safe, new_safe)
print("OK: _safe_sdpa → sync after GPU ops")

# Fix 3: Also patch softmax to ALWAYS use manual on CUDA (same reason)
old_softmax = '''def patched_softmax(input, dim=None, _stacklevel=3, dtype=None):
    if dim is None:
        dim = -1
    if dtype is not None:
        input = input.to(dtype)
    if input.is_cuda and input.shape[dim] > SAFE_SOFTMAX_THRESHOLD:
        if not getattr(patched_softmax, '_logged', False):
            logger.warning(f"[BC-250] Manual F.softmax: shape={list(input.shape)}, dim={dim}")
            patched_softmax._logged = True
        return _safe_softmax_impl(input, dim)
    return _original_softmax(input, dim=dim)'''

new_softmax = '''def patched_softmax(input, dim=None, _stacklevel=3, dtype=None):
    if dim is None:
        dim = -1
    if dtype is not None:
        input = input.to(dtype)
    # gfx1010: ALWAYS use manual softmax on CUDA — native kernel unreliable
    if input.is_cuda:
        if not getattr(patched_softmax, '_logged', False):
            logger.warning(f"[BC-250] Manual F.softmax (ALWAYS): shape={list(input.shape)}, dim={dim}")
            patched_softmax._logged = True
        return _safe_softmax_impl(input, dim)
    return _original_softmax(input, dim=dim)'''

if old_softmax not in content:
    print("WARNING: patched_softmax not found (may already be fixed)")
else:
    content = content.replace(old_softmax, new_softmax)
    print("OK: patched_softmax → always manual on CUDA")

# Fix 4: Same for tensor.softmax
old_tsm = '''def patched_tensor_softmax(self, dim=-1, dtype=None):
    if dtype is not None:
        self = self.to(dtype)
    if self.is_cuda and self.shape[dim] > SAFE_SOFTMAX_THRESHOLD:
        if not getattr(patched_tensor_softmax, '_logged', False):
            logger.warning(f"[BC-250] Manual softmax: shape={list(self.shape)}, dim={dim}")
            patched_tensor_softmax._logged = True
        return _safe_softmax_impl(self, dim)
    return _original_tensor_softmax(self, dim=dim)'''

new_tsm = '''def patched_tensor_softmax(self, dim=-1, dtype=None):
    if dtype is not None:
        self = self.to(dtype)
    # gfx1010: ALWAYS use manual softmax on CUDA
    if self.is_cuda:
        if not getattr(patched_tensor_softmax, '_logged', False):
            logger.warning(f"[BC-250] Manual softmax (ALWAYS): shape={list(self.shape)}, dim={dim}")
            patched_tensor_softmax._logged = True
        return _safe_softmax_impl(self, dim)
    return _original_tensor_softmax(self, dim=dim)'''

if old_tsm not in content:
    print("WARNING: patched_tensor_softmax not found (may already be fixed)")
else:
    content = content.replace(old_tsm, new_tsm)
    print("OK: patched_tensor_softmax → always manual on CUDA")

# Fix 5: Update version string
content = content.replace('v17 ready', 'v19 ready — ALL CUDA ops manual (no native kernels)')
content = content.replace('Comprehensive Monkey-Patch v17', 'Comprehensive Monkey-Patch v19')

with open(path, 'w') as f:
    f.write(content)

print("\nALL PATCHES APPLIED — v19")
