import os import pytest import torch import torch.nn.functional as F from fla.ops.hgrn import chunk_hgrn, fused_recurrent_hgrn from fla.ops.hgrn.naive import naive_recurrent_hgrn from fla.utils import assert_close, device @pytest.mark.parametrize( ('B', 'T', 'D', 'dtype'), [ pytest.param(*test, id="B{}-T{}-D{}-{}".format(*test)) for test in [ (1, 63, 500, torch.float), (2, 1024, 500, torch.float), (2, 1024, 512, torch.float), (2, 1024, 1000, torch.float), (4, 2048, 2048, torch.float), ] ], ) def test_fused_recurrent( B: int, T: int, D: int, dtype: torch.dtype, ): torch.manual_seed(42) os.environ['TRITON_F32_DEFAULT'] = 'ieee' x = torch.randn((B, T, D), dtype=dtype, device=device) g = torch.randn((B, T, D), dtype=dtype, device=device) h0 = torch.randn_like(x[:, 0]) x, g = (1 - g.sigmoid()) * x, F.logsigmoid(g) x, g, h0 = (i.detach().clone().to(dtype).requires_grad_() for i in (x, g, h0)) do = torch.randn_like(x) dht = torch.randn_like(h0) ref, ref_ht = naive_recurrent_hgrn(x, g, h0, output_final_state=True) ((ref * do).sum() + (ref_ht * dht).sum()).backward() ref_dx, x.grad = x.grad.clone(), None ref_dg, g.grad = g.grad.clone(), None ref_dh0, h0.grad = h0.grad.clone(), None tri, tri_ht = fused_recurrent_hgrn(x, g, h0, output_final_state=True) ((tri * do).sum() + (tri_ht * dht).sum()).backward() tri_dx, x.grad = x.grad.clone(), None tri_dg, g.grad = g.grad.clone(), None tri_dh0, h0.grad = h0.grad.clone(), None assert_close('o', ref, tri, 0.005) assert_close('ht', ref_ht, tri_ht, 0.005) assert_close('dx', ref_dx, tri_dx, 0.005) assert_close('dg', ref_dg, tri_dg, 0.005) assert_close('dh0', ref_dh0, tri_dh0, 0.005) @pytest.mark.parametrize( ('D', 'cu_seqlens', 'dtype'), [ pytest.param(*test, id="D{}-cu_seqlens{}-{}".format(*test)) for test in [ (500, [0, 15], torch.float), (512, [0, 256, 500, 1000], torch.float), (1000, [0, 15, 100, 300, 1200, 2000], torch.float), (2048, [0, 200, 512, 1200, 2048], torch.float16), ] ], ) def test_fused_recurrent_varlen( D: int, cu_seqlens: list[int], dtype: torch.dtype, ): torch.manual_seed(42) os.environ['TRITON_F32_DEFAULT'] = 'ieee' N = len(cu_seqlens) - 1 T = cu_seqlens[-1] cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device) x = torch.randn((1, T, D), dtype=dtype, device=device) g = torch.randn((1, T, D), dtype=dtype, device=device) h0 = torch.randn(N, D, dtype=dtype, device=device) x, g = (1 - g.sigmoid()) * x, F.logsigmoid(g) x, g, h0 = (i.detach().clone().to(dtype).requires_grad_() for i in (x, g, h0)) do = torch.randn_like(x) dht = torch.randn_like(h0) refs, ref_hts = [], [] for i in range(N): ref, ref_ht = naive_recurrent_hgrn( x[:, cu_seqlens[i]:cu_seqlens[i+1]], g[:, cu_seqlens[i]:cu_seqlens[i+1]], h0[i:i+1], output_final_state=True, ) refs.append(ref) ref_hts.append(ref_ht) ref = torch.cat(refs, 1) ref_ht = torch.cat(ref_hts, 0) ((ref * do).sum() + (ref_ht * dht).sum()).backward() ref_dx, x.grad = x.grad.clone(), None ref_dg, g.grad = g.grad.clone(), None ref_dh0, h0.grad = h0.grad.clone(), None tri, tri_ht = fused_recurrent_hgrn(x, g, h0, output_final_state=True, cu_seqlens=cu_seqlens) ((tri * do).sum() + (tri_ht * dht).sum()).backward() tri_dx, x.grad = x.grad.clone(), None tri_dg, g.grad = g.grad.clone(), None tri_dh0, h0.grad = h0.grad.clone(), None assert_close('o', ref, tri, 0.005) assert_close('ht', ref_ht, tri_ht, 0.005) assert_close('dx', ref_dx, tri_dx, 0.005) assert_close('dg', ref_dg, tri_dg, 0.005) assert_close('dh0', ref_dh0, tri_dh0, 0.005) @pytest.mark.parametrize( ('B', 'T', 'D', 'dtype'), [ pytest.param(*test, id="B{}-T{}-D{}-{}".format(*test)) for test in [ (1, 63, 500, torch.float16), (2, 500, 1000, torch.float16), (2, 1000, 1024, torch.float16), (4, 2048, 2048, torch.float16), ] ], ) def test_chunk( B: int, T: int, D: int, dtype: torch.dtype, ): torch.manual_seed(42) os.environ['TRITON_F32_DEFAULT'] = 'ieee' x = torch.randn((B, T, D), dtype=dtype, device=device) g = torch.randn((B, T, D), dtype=dtype, device=device) x, g = (1 - g.sigmoid()) * x, F.logsigmoid(g) x, g = (i.detach().clone().to(dtype).requires_grad_() for i in (x, g)) do = torch.randn_like(x) h0 = torch.randn_like(x[:, 0]) ref, _ = fused_recurrent_hgrn(x, g, h0, output_final_state=True) ref.backward(do) ref_dx, x.grad = x.grad.clone(), None ref_dg, g.grad = g.grad.clone(), None tri, _ = chunk_hgrn(x, g, h0, output_final_state=True) tri.backward(do) tri_dx, x.grad = x.grad.clone(), None tri_dg, g.grad = g.grad.clone(), None assert_close('o', ref, tri, 0.005) assert_close('dx', ref_dx, tri_dx, 0.005) assert_close('dg', ref_dg, tri_dg, 0.005)