|
|
| import pytest |
| import torch |
|
|
| from fla.modules.token_shift import token_shift, token_shift_ref |
| from fla.utils import assert_close, device |
|
|
| test_b_list = [4] |
| test_t_list = [512, 4100, 8192] |
| test_h_list = [2560, 4096] |
| test_cu_seqlens_list = [ |
| None, |
| [0, 4, 7, 40, 128], |
| [0, 10, 20, 64], |
| [0, 32], |
| [0, 1, 3, 4], |
| ] |
| test_dtype_list = [torch.float] |
|
|
|
|
| @pytest.mark.parametrize('B', test_b_list) |
| @pytest.mark.parametrize('T', test_t_list) |
| @pytest.mark.parametrize('H', test_h_list) |
| @pytest.mark.parametrize('cu_seqlens_val', test_cu_seqlens_list) |
| @pytest.mark.parametrize('dtype', test_dtype_list) |
| def test_token_shift(B, T, H, cu_seqlens_val, dtype): |
| if cu_seqlens_val is not None: |
| B = 1 |
| T = cu_seqlens_val[-1] |
| cu_seqlens_tensor = torch.tensor(cu_seqlens_val, dtype=torch.int32, device=device) |
| else: |
| cu_seqlens_tensor = None |
|
|
| torch.manual_seed(42) |
|
|
| x = torch.randn(B, T, H, device=device).to(dtype).requires_grad_(True) |
| dy = torch.randn_like(x) |
|
|
| ref = token_shift_ref(x, cu_seqlens_tensor) |
| tri = token_shift(x, cu_seqlens_tensor) |
|
|
| ref.backward(dy) |
| ref_dx, x.grad = x.grad, None |
|
|
| tri.backward(dy) |
| tri_dx, x.grad = x.grad, None |
|
|
| assert_close(' x', ref, tri, 1e-3) |
| assert_close('dx', ref_dx, tri_dx, 1e-3) |
|
|
|
|
| def _split_for_passing( |
| x: torch.Tensor, |
| cu_seqlens, |
| split_at: int = 1, |
| ): |
| assert x.size(0) == 1 |
| assert 0 < split_at < len(cu_seqlens) - 1 |
|
|
| cu0 = [t - cu_seqlens[0] for t in cu_seqlens[: split_at + 1]] |
| cu1 = [t - cu_seqlens[split_at] for t in cu_seqlens[split_at:]] |
| T0, T1 = cu0[-1], cu1[-1] |
|
|
| x0 = x[:, :T0].contiguous() |
| x1 = x[:, T0: T0 + T1].contiguous() |
| cache1 = x[:, T0 - 1: T0].contiguous() |
| return x0, x1, \ |
| torch.tensor(cu0, dtype=torch.int32, device=x.device), \ |
| torch.tensor(cu1, dtype=torch.int32, device=x.device), \ |
| cache1 |
|
|
|
|
| def _check_passing_vs_whole( |
| B: int, |
| T: int, |
| H: int, |
| cu_seqlens: list[int] | None, |
| dtype: torch.dtype, |
| split_at: int = 1, |
| ): |
| torch.manual_seed(42) |
|
|
| if cu_seqlens is None: |
| x = torch.randn(B, T, H, device=device, dtype=dtype, requires_grad=True) |
| cu_seqlens_tensor = None |
| else: |
| B = 1 |
| T = cu_seqlens[-1] |
| x = torch.randn(1, T, H, device=device, dtype=dtype, requires_grad=True) |
| cu_seqlens_tensor = torch.tensor(cu_seqlens, dtype=torch.int32, device=device) |
|
|
| dy = torch.randn_like(x) |
| ref_out = token_shift(x, cu_seqlens_tensor) |
| ref_out.backward(dy) |
| ref_dx = x.grad.clone() |
| x.grad.zero_() |
|
|
| if cu_seqlens is None: |
| T0 = T // 2 |
| x0 = x[:, :T0].contiguous() |
| x1 = x[:, T0:].contiguous() |
| cu0, cu1 = None, None |
| else: |
| if split_at >= len(cu_seqlens) - 1: |
| pytest.skip("invalid split_at") |
| x0, x1, cu0, cu1, cache1 = _split_for_passing(x, cu_seqlens, split_at) |
|
|
| out0, cache_out0 = token_shift(x0, cu0, output_cache=True) |
| out1, cache_out1 = token_shift(x1, cu1, cache=cache_out0, output_cache=True) |
|
|
| cat_out = torch.cat([out0, out1], dim=1) |
| cat_out.backward(dy) |
|
|
| cat_dx = x.grad.clone() |
|
|
| assert_close("do", ref_out, cat_out, 1e-3) |
| assert_close("dx", ref_dx, cat_dx, 1e-3) |
|
|
|
|
| @pytest.mark.parametrize( |
| ("B", "T", "H", "cu_seqlens", "split_at"), |
| [ |
| pytest.param(*test, id="B{}-T{}-H{}-cu{}-split{}".format(*test)) |
| for test in [ |
| (2, 512, 1024, None, 1), |
| (1, 8192, 1024, None, 2), |
| ] |
| ], |
| ) |
| def test_all_with_and_without_varlen(B, T, H, cu_seqlens, split_at): |
| dtype = torch.float |
| assert cu_seqlens is None, "This test is for cu_seqlens=None case" |
| _check_passing_vs_whole(B, T, H, cu_seqlens, dtype, split_at) |
|
|