import os import pytest import torch import torch.nn.functional as F from fla.ops.generalized_delta_rule.dplr.fused_recurrent import fused_recurrent_dplr_delta_rule from fla.ops.rwkv7.channel_mixing import channel_mixing_rwkv7, channel_mixing_rwkv7_torch from fla.ops.rwkv7.fused_addcmul import fused_addcmul_rwkv7, torch_addcmul_rwkv7 from fla.ops.rwkv7.fused_k_update import fused_k_rwkv7, k_update_ref from fla.ops.rwkv7.fused_recurrent import fused_mul_recurrent_rwkv7 from fla.ops.rwkv7.gate_output_correction import gate_output_correction, gate_output_correction_ref from fla.utils import assert_close, device, is_nvidia_hopper @pytest.mark.parametrize("B", [2]) @pytest.mark.parametrize("T", [1024]) @pytest.mark.parametrize("n_embd", [1024]) @pytest.mark.parametrize("dim_ffn", [4096]) @pytest.mark.parametrize("dtype", [torch.bfloat16]) @pytest.mark.parametrize("inplace", [True, False]) @pytest.mark.parametrize("xprevdim", [2, 3]) @pytest.mark.skipif( os.getenv("SKIP_TEST_CHUNK_VARLEN") == "0", reason="Skipping test because TEST_CHUNK_VARLEN is enabled", ) def test_channel_mixing_gradients(B, T, n_embd, dim_ffn, dtype, inplace, xprevdim): torch.manual_seed(42) torch._dynamo.config.cache_size_limit = 512 x = torch.randn( B, T, n_embd, device=device, dtype=dtype, requires_grad=True, ) if xprevdim == 3: x_prev = torch.randn( B, 1, n_embd, device=device, dtype=dtype, requires_grad=True, ) else: x_prev = torch.randn( B, n_embd, device=device, dtype=dtype, requires_grad=True, ) x_k = torch.randn(1, 1, n_embd, device=device, dtype=dtype, requires_grad=True) K_ = torch.randn(n_embd, dim_ffn, device=device, dtype=dtype, requires_grad=True) V_ = torch.randn(dim_ffn, n_embd, device=device, dtype=dtype, requires_grad=True) x2 = x.clone().detach().requires_grad_(True) x_prev2 = x_prev.clone().detach().requires_grad_(True) x_k2 = x_k.clone().detach().requires_grad_(True) K_2 = K_.clone().detach().requires_grad_(True) V_2 = V_.clone().detach().requires_grad_(True) o1, last1 = channel_mixing_rwkv7_torch( x.to(torch.float32), x_prev.to(torch.float32), x_k.to(torch.float32), K_.to(torch.float32), V_.to(torch.float32), ) loss1 = o1.mean() + last1.mean() loss1.backward() o2, last2 = channel_mixing_rwkv7(x2, x_prev2, x_k2, K_2, V_2, inplace) loss2 = o2.mean() + last2.mean() loss2.backward() assert_close(" dx", x.grad, x2.grad, ratio=5e-3) assert_close(" dxprev", x_prev.grad, x_prev2.grad, ratio=5e-3) assert_close(" dx_k", x_k.grad, x_k2.grad, ratio=5e-3) assert_close(" dK_", K_.grad, K_2.grad, ratio=5e-3) assert_close(" dV_", V_.grad, V_2.grad, ratio=5e-3) @pytest.mark.parametrize('B', [2]) @pytest.mark.parametrize('T', [1, 1024]) @pytest.mark.parametrize('H', [1]) @pytest.mark.parametrize('D', [64]) @pytest.mark.parametrize('scale', [None, 1]) @pytest.mark.parametrize('dtype', [torch.float32]) @pytest.mark.skipif( os.getenv('SKIP_TEST_CHUNK_VARLEN') == '0', reason='Skipping test because TEST_CHUNK_VARLEN is enabled', ) def test_fused_mul_recurrent_fwd( B: int, T: int, H: int, D: int, scale: float, dtype: torch.dtype, ): torch.manual_seed(42) r = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype) k = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype) v = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype) w = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype) kk = torch.empty(B, T, H, D, device=device).uniform_(-1, 1) kk = F.normalize(kk, dim=-1).to(dtype=dtype) a = -kk.clone() a_scale = torch.empty(B, T, H, D, device=device).uniform_(0, 0.1).to(dtype=dtype) b = (kk * a_scale).requires_grad_(False) # kk*a h0 = torch.randn(B, H, D, D, dtype=torch.float) r, k, v, a, a_scale, b, w, h0 = map(lambda x: x.to(device).requires_grad_(False), (r, k, v, a, a_scale, b, w, h0)) ref, ref_ht = fused_recurrent_dplr_delta_rule( q=r.clone(), k=k.clone(), v=v.clone(), a=a.clone(), b=b.clone(), gk=w.clone(), scale=scale, initial_state=h0.clone(), output_final_state=True, ) tri, tri_ht = fused_mul_recurrent_rwkv7( r=r.clone(), w=w.clone(), k=k.clone(), v=v.clone(), kk=kk.clone(), a=a_scale.clone(), scale=scale, initial_state=h0.clone(), output_final_state=True, ) assert_close('o', ref, tri, 0.002) assert_close('ht', ref_ht, tri_ht, 0.002) @pytest.mark.parametrize("B", [1]) @pytest.mark.parametrize("T", [20, 1024, 4100, 131072]) @pytest.mark.parametrize("H", [2]) @pytest.mark.parametrize("D", [64]) @pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16]) @pytest.mark.parametrize("use_g", [True, False]) @pytest.mark.skipif( os.getenv("SKIP_TEST_CHUNK_VARLEN") == "0", reason="Skipping test because TEST_CHUNK_VARLEN is enabled", ) def test_fused_rwkv7_addcmul( B: int, T: int, H: int, D: int, dtype: torch.dtype, use_g: bool, ): if T == 128 * 1024 and not is_nvidia_hopper: pytest.skip("Skipping test for T=131072 on non-Hopper GPUs") hidden_size = H*D hidden_states = torch.randn(B, T, hidden_size).to(device).to(dtype).requires_grad_() xx = torch.randn(B, T, hidden_size).to(device).to(dtype).requires_grad_() x_r = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_() x_w = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_() x_k = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_() x_v = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_() x_a = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_() if use_g: x_g = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_() else: x_g = None xr0, xw0, xk0, xv0, xa0, xg0 = fused_addcmul_rwkv7(hidden_states, xx, x_r, x_w, x_k, x_v, x_a, x_g) xr1, xw1, xk1, xv1, xa1, xg1 = torch_addcmul_rwkv7(hidden_states.float(), xx.float(), x_r.float(), x_w.float(), x_k.float(), x_v.float(), x_a.float(), x_g.float() if use_g else None) ratio = 1e-5 if dtype == torch.float32 else 0.002 assert_close("xr0", xr0, xr1, ratio=ratio) assert_close("xw0", xw0, xw1, ratio=ratio) assert_close("xk0", xk0, xk1, ratio=ratio) assert_close("xv0", xv0, xv1, ratio=ratio) assert_close("xa0", xa0, xa1, ratio=ratio) if use_g: assert_close("xg0", xg0, xg1, ratio=ratio) (xr0 + xw0 + xk0 + xv0 + xa0 + xg0).sum().backward() else: (xr0 + xw0 + xk0 + xv0 + xa0).sum().backward() d_ixr = x_r.grad.clone() d_ixw = x_w.grad.clone() d_ixk = x_k.grad.clone() d_ixv = x_v.grad.clone() d_ixa = x_a.grad.clone() d_hidden = hidden_states.grad.clone() d_xx = xx.grad.clone() x_r.grad.zero_() x_w.grad.zero_() x_k.grad.zero_() x_v.grad.zero_() x_a.grad.zero_() if use_g: d_ixg = x_g.grad.clone() x_g.grad.zero_() hidden_states.grad.zero_() xx.grad.zero_() if use_g: (xr1 + xw1 + xk1 + xv1 + xa1 + xg1).sum().backward() else: (xr1 + xw1 + xk1 + xv1 + xa1).sum().backward() d_ixr1 = x_r.grad.clone() d_ixw1 = x_w.grad.clone() d_ixk1 = x_k.grad.clone() d_ixv1 = x_v.grad.clone() d_ixa1 = x_a.grad.clone() if use_g: d_ixg1 = x_g.grad.clone() d_hidden1 = hidden_states.grad.clone() d_xx1 = xx.grad.clone() assert_close("d_ixr", d_ixr, d_ixr1, ratio=ratio) assert_close("d_ixw", d_ixw, d_ixw1, ratio=ratio) assert_close("d_ixk", d_ixk, d_ixk1, ratio=ratio) assert_close("d_ixv", d_ixv, d_ixv1, ratio=ratio) assert_close("d_ixa", d_ixa, d_ixa1, ratio=ratio) if use_g: assert_close("d_ixg", d_ixg, d_ixg1, ratio=ratio) assert_close("d_hidden", d_hidden, d_hidden1, ratio=ratio) assert_close("d_xx", d_xx, d_xx1, ratio=ratio) @pytest.mark.parametrize("B", [4]) @pytest.mark.parametrize("T", [13, 4096, 8000]) @pytest.mark.parametrize("H", [64]) @pytest.mark.parametrize("D", [64]) @pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16]) @pytest.mark.parametrize("ka_shape", [1, 3]) def test_fused_k_update( B: int, T: int, H: int, D: int, dtype: torch.dtype, ka_shape: int, ): k = torch.randn(B, T, H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_() a = torch.randn(B, T, H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_() if ka_shape == 1: ka = torch.randn(H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_() else: ka = torch.randn(1, 1, H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_() ref = k_update_ref(k.float(), a.float(), ka.float()) ref.sum().backward() ref_dk, k.grad = k.grad.clone(), None ref_da, a.grad = a.grad.clone(), None ref_dka, ka.grad = ka.grad.clone(), None tri = fused_k_rwkv7(k, a, ka) tri.sum().backward() ratio = 5e-5 if dtype == torch.float32 else 0.002 assert_close(" o", tri, ref, ratio=ratio) assert_close(" dk", ref_dk, k.grad, ratio=ratio) assert_close(" da", ref_da, a.grad, ratio=ratio) assert_close("dka", ref_dka, ka.grad, ratio=ratio) @pytest.mark.parametrize("B", [4]) @pytest.mark.parametrize("T", [4096]) @pytest.mark.parametrize("H", [64]) @pytest.mark.parametrize("D", [64]) @pytest.mark.parametrize("dtype", [torch.bfloat16]) def test_gate_output_correction( B: int, T: int, H: int, D: int, dtype: torch.dtype, ): value_dim = H * D torch.manual_seed(0) o_ref = torch.randn(B, T, value_dim, device=device, dtype=dtype, requires_grad=True) r_ref = torch.randn(B, T, H, D, device=device, dtype=dtype, requires_grad=True) k_ref = torch.randn(B, T, H, D, device=device, dtype=dtype, requires_grad=True) r_k_ref = torch.randn(H, D, device=device, dtype=dtype, requires_grad=True) v_ref = torch.randn(B, T, H, D, device=device, dtype=dtype, requires_grad=True) g_ref = torch.randn(B, T, value_dim, device=device, dtype=dtype, requires_grad=True) tensors_cus = [t.clone().detach().requires_grad_(True) for t in [o_ref, r_ref, k_ref, r_k_ref, v_ref, g_ref]] o_cus, r_cus, k_cus, r_k_cus, v_cus, g_cus = tensors_cus output_ref = gate_output_correction_ref(o_ref.float(), r_ref.float(), k_ref.float(), r_k_ref.float(), v_ref.float(), g_ref.float()) output_ref.sum().backward() output_cus = gate_output_correction(o_cus, r_cus, k_cus, r_k_cus, v_cus, g_cus) output_cus.sum().backward() assert_close(" o", output_ref, output_cus, 0.002) assert_close("do", o_ref.grad, o_cus.grad, 0.002) assert_close("dr", r_ref.grad, r_cus.grad, 0.002) assert_close("dk", k_ref.grad, k_cus.grad, 0.002) assert_close("drk", r_k_ref.grad, r_k_cus.grad, 0.002) assert_close("dv", v_ref.grad, v_cus.grad, 0.002) assert_close("dg", g_ref.grad, g_cus.grad, 0.002)