amonshano commited on
Commit
c335050
Β·
verified Β·
1 Parent(s): b66f552

Add Echo-Memory codebase used for this run (CC BY 4.0, JD Echo Team) (part 4)

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. code/flash-linear-attention/tests/models/test_modeling_rwkv7.py +57 -0
  2. code/flash-linear-attention/tests/models/test_modeling_samba.py +56 -0
  3. code/flash-linear-attention/tests/models/test_modeling_transformer.py +56 -0
  4. code/flash-linear-attention/tests/models/test_modeling_utils.py +84 -0
  5. code/flash-linear-attention/tests/modules/test_activation.py +133 -0
  6. code/flash-linear-attention/tests/modules/test_conv.py +713 -0
  7. code/flash-linear-attention/tests/modules/test_cross_entropy.py +81 -0
  8. code/flash-linear-attention/tests/modules/test_grpo.py +56 -0
  9. code/flash-linear-attention/tests/modules/test_kl_div.py +44 -0
  10. code/flash-linear-attention/tests/modules/test_l2norm.py +37 -0
  11. code/flash-linear-attention/tests/modules/test_l2warp.py +70 -0
  12. code/flash-linear-attention/tests/modules/test_layernorm.py +217 -0
  13. code/flash-linear-attention/tests/modules/test_layernorm_gated.py +92 -0
  14. code/flash-linear-attention/tests/modules/test_rotary.py +122 -0
  15. code/flash-linear-attention/tests/modules/test_token_shift.py +133 -0
  16. code/flash-linear-attention/tests/ops/test_attn.py +125 -0
  17. code/flash-linear-attention/tests/ops/test_based.py +63 -0
  18. code/flash-linear-attention/tests/ops/test_comba.py +368 -0
  19. code/flash-linear-attention/tests/ops/test_delta.py +152 -0
  20. code/flash-linear-attention/tests/ops/test_delta_product.py +186 -0
  21. code/flash-linear-attention/tests/ops/test_deltaformer.py +130 -0
  22. code/flash-linear-attention/tests/ops/test_dplr_delta.py +432 -0
  23. code/flash-linear-attention/tests/ops/test_forgetting_attn.py +151 -0
  24. code/flash-linear-attention/tests/ops/test_gated_delta.py +353 -0
  25. code/flash-linear-attention/tests/ops/test_gated_delta_product.py +210 -0
  26. code/flash-linear-attention/tests/ops/test_gla.py +325 -0
  27. code/flash-linear-attention/tests/ops/test_gsa.py +432 -0
  28. code/flash-linear-attention/tests/ops/test_hgrn.py +164 -0
  29. code/flash-linear-attention/tests/ops/test_iplr_delta.py +235 -0
  30. code/flash-linear-attention/tests/ops/test_kda.py +379 -0
  31. code/flash-linear-attention/tests/ops/test_linear_attn.py +189 -0
  32. code/flash-linear-attention/tests/ops/test_log_linear_attn.py +148 -0
  33. code/flash-linear-attention/tests/ops/test_mesa.py +276 -0
  34. code/flash-linear-attention/tests/ops/test_nsa.py +148 -0
  35. code/flash-linear-attention/tests/ops/test_path_attn.py +211 -0
  36. code/flash-linear-attention/tests/ops/test_retention.py +308 -0
  37. code/flash-linear-attention/tests/ops/test_rwkv6.py +192 -0
  38. code/flash-linear-attention/tests/ops/test_rwkv7.py +300 -0
  39. code/flash-linear-attention/tests/ops/test_simple_gla.py +670 -0
  40. code/flash-linear-attention/tests/ops/test_solve_tril.py +91 -0
  41. code/flash-linear-attention/tests/ops/test_titans.py +122 -0
  42. code/flash-linear-attention/tests/ops/test_ttt.py +268 -0
  43. code/flash-linear-attention/tests/ops/test_utils.py +410 -0
  44. code/flash-linear-attention/utils/convert_from_llama.py +189 -0
  45. code/flash-linear-attention/utils/convert_from_rwkv6.py +213 -0
  46. code/flash-linear-attention/utils/convert_from_rwkv7.py +150 -0
  47. code/inference/README.md +73 -0
  48. code/inference/_shared/common_env_infer.sh +59 -0
  49. code/inference/context_learning/common_env.sh +5 -0
  50. code/inference/context_learning/run_infer_ctx1.sh +28 -0
code/flash-linear-attention/tests/models/test_modeling_rwkv7.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+
5
+ from fla.models import RWKV7Config
6
+
7
+ from .test_modeling_base import run_test_generation, run_test_model_forward_backward
8
+
9
+
10
+ # ===================================================================================
11
+ # Test for Modeling (Forward/Backward Pass)
12
+ # ===================================================================================
13
+ @pytest.mark.parametrize(
14
+ ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'],
15
+ [
16
+ pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test))
17
+ for test in [
18
+ (4, 4, 1024, 4, 64, True, torch.bfloat16),
19
+ (4, 4, 1024, 4, 64, False, torch.bfloat16),
20
+ (4, 4, 1024, 4, 128, False, torch.bfloat16),
21
+ ]
22
+ ],
23
+ )
24
+ def test_modeling(
25
+ L: int,
26
+ B: int,
27
+ T: int,
28
+ H: int,
29
+ D: int,
30
+ use_l2warp: bool,
31
+ dtype: torch.dtype,
32
+ ):
33
+ run_test_model_forward_backward(L, B, T, H, D, RWKV7Config, use_l2warp=use_l2warp, dtype=dtype)
34
+
35
+ # ===================================================================================
36
+ # Test for Generation
37
+ # ===================================================================================
38
+
39
+
40
+ @pytest.mark.parametrize(
41
+ ['L', 'B', 'T', 'H', 'D', 'dtype'],
42
+ [
43
+ pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test))
44
+ for test in [
45
+ (2, 4, 2000, 8, 64, torch.float16),
46
+ ]
47
+ ],
48
+ )
49
+ def test_generation(
50
+ L: int,
51
+ B: int,
52
+ T: int,
53
+ H: int,
54
+ D: int,
55
+ dtype: torch.dtype,
56
+ ):
57
+ run_test_generation(L, B, T, H, D, RWKV7Config, dtype)
code/flash-linear-attention/tests/models/test_modeling_samba.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+
5
+ from fla.models import SambaConfig
6
+
7
+ from .test_modeling_base import run_test_generation, run_test_model_forward_backward
8
+
9
+
10
+ # ===================================================================================
11
+ # Test for Modeling (Forward/Backward Pass)
12
+ # ===================================================================================
13
+ @pytest.mark.parametrize(
14
+ ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'],
15
+ [
16
+ pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test))
17
+ for test in [
18
+ (4, 4, 1024, 18, 64, True, torch.bfloat16),
19
+ (4, 4, 1024, 18, 64, False, torch.bfloat16),
20
+ (4, 4, 1024, 9, 128, False, torch.bfloat16),
21
+ ]
22
+ ],
23
+ )
24
+ def test_modeling(
25
+ L: int,
26
+ B: int,
27
+ T: int,
28
+ H: int,
29
+ D: int,
30
+ use_l2warp: bool,
31
+ dtype: torch.dtype,
32
+ ):
33
+ run_test_model_forward_backward(L, B, T, H, D, SambaConfig, use_l2warp=use_l2warp, dtype=dtype)
34
+
35
+
36
+ # ===================================================================================
37
+ # Test for Generation
38
+ # ===================================================================================
39
+ @pytest.mark.parametrize(
40
+ ['L', 'B', 'T', 'H', 'D', 'dtype'],
41
+ [
42
+ pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test))
43
+ for test in [
44
+ (2, 4, 2000, 18, 64, torch.float16),
45
+ ]
46
+ ],
47
+ )
48
+ def test_generation(
49
+ L: int,
50
+ B: int,
51
+ T: int,
52
+ H: int,
53
+ D: int,
54
+ dtype: torch.dtype,
55
+ ):
56
+ run_test_generation(L, B, T, H, D, SambaConfig, dtype)
code/flash-linear-attention/tests/models/test_modeling_transformer.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+
5
+ from fla.models import TransformerConfig
6
+
7
+ from .test_modeling_base import run_test_generation, run_test_model_forward_backward
8
+
9
+
10
+ # ===================================================================================
11
+ # Test for Modeling (Forward/Backward Pass)
12
+ # ===================================================================================
13
+ @pytest.mark.parametrize(
14
+ ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'],
15
+ [
16
+ pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test))
17
+ for test in [
18
+ (4, 4, 1024, 4, 64, True, torch.bfloat16),
19
+ (4, 4, 1024, 4, 64, False, torch.bfloat16),
20
+ (4, 4, 1024, 4, 128, False, torch.bfloat16),
21
+ ]
22
+ ],
23
+ )
24
+ def test_modeling(
25
+ L: int,
26
+ B: int,
27
+ T: int,
28
+ H: int,
29
+ D: int,
30
+ use_l2warp: bool,
31
+ dtype: torch.dtype,
32
+ ):
33
+ run_test_model_forward_backward(L, B, T, H, D, TransformerConfig, use_l2warp=use_l2warp, dtype=dtype)
34
+
35
+
36
+ # ===================================================================================
37
+ # Test for Generation
38
+ # ===================================================================================
39
+ @pytest.mark.parametrize(
40
+ ['L', 'B', 'T', 'H', 'D', 'dtype'],
41
+ [
42
+ pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test))
43
+ for test in [
44
+ (2, 4, 2000, 8, 64, torch.float16),
45
+ ]
46
+ ],
47
+ )
48
+ def test_generation(
49
+ L: int,
50
+ B: int,
51
+ T: int,
52
+ H: int,
53
+ D: int,
54
+ dtype: torch.dtype,
55
+ ):
56
+ run_test_generation(L, B, T, H, D, TransformerConfig, dtype)
code/flash-linear-attention/tests/models/test_modeling_utils.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import math
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ from transformers import AutoModelForCausalLM
7
+
8
+ from fla.utils import device
9
+
10
+ # Models that do not yet support variable sequence lengths (for modeling tests)
11
+ MODELING_UNSUPPORTED_VARLEN = [
12
+ "ABCConfig", "ForgettingTransformerConfig", "LinearAttentionConfig", "LightNetConfig",
13
+ "Mamba2Config", "MambaConfig", "MesaNetConfig", "SambaConfig",
14
+ "RodimusConfig",
15
+ ]
16
+
17
+ # Models not yet ready for basic testing
18
+ NOT_READY_FOR_TESTING = ['RodimusConfig']
19
+
20
+ # Models requiring specific hardware (e.g., NVIDIA Hopper)
21
+ HOPPER_EXCLUSIVE = []
22
+
23
+ GENERATION_UNSUPPORTED = [
24
+ "ABCConfig", "LinearAttentionConfig", "LightNetConfig",
25
+ "Mamba2Config", "MambaConfig", "NSAConfig", "SambaConfig", "RWKV6Config", "RWKV7Config",
26
+ "DeltaFormerConfig",
27
+ ]
28
+
29
+
30
+ def create_model_and_config(config_class, L, H, D, dtype, **kwargs):
31
+ """
32
+ A helper function to create a model and its configuration.
33
+ """
34
+ config_params = {
35
+ 'hidden_size': H * D,
36
+ 'num_hidden_layers': L,
37
+ **({'num_heads': H} if config_class.__name__ != 'NSAConfig' else {}),
38
+ **kwargs,
39
+ }
40
+ config = config_class(**config_params)
41
+ model = AutoModelForCausalLM.from_config(config)
42
+ model.apply(init_weights_recursively)
43
+ model.to(dtype).to(device)
44
+ return model, config
45
+
46
+
47
+ def init_weights_with_asymmetric_pattern(module):
48
+ """Initialize weights with asymmetric patterns for debugging.
49
+
50
+ Args:
51
+ module: The module to initialize weights for.
52
+ """
53
+ if isinstance(module, (nn.Linear, nn.Conv1d)):
54
+ nn.init.kaiming_normal_(module.weight, a=math.sqrt(5))
55
+ with torch.no_grad():
56
+ shape = module.weight.shape
57
+ if len(shape) > 1:
58
+ quarter_size = shape[0] // 4
59
+ module.weight[:quarter_size] *= 1.2
60
+ module.weight[-quarter_size:] *= 0.8
61
+ if shape[0] == shape[1]:
62
+ idx = torch.arange(min(shape[0], shape[1]))
63
+ module.weight[idx, idx] += 0.05
64
+ if module.bias is not None:
65
+ fan_in, _ = nn.init._calculate_fan_in_and_fan_out(module.weight)
66
+ bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
67
+ nn.init.uniform_(module.bias, -bound, bound)
68
+ with torch.no_grad():
69
+ module.bias[::3] *= 1.1
70
+ module.bias[1::3] *= 0.9
71
+ elif isinstance(module, nn.Embedding):
72
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
73
+ with torch.no_grad():
74
+ vocab_size, dim = module.weight.shape
75
+ pattern = 0.01 * torch.sin(torch.arange(dim) * (6.28 / dim))
76
+ for i in range(min(100, vocab_size)):
77
+ module.weight[i] += pattern * (1 + i % 5) * 0.2
78
+
79
+
80
+ def init_weights_recursively(module):
81
+ if hasattr(module, 'weight'):
82
+ init_weights_with_asymmetric_pattern(module)
83
+ for submodule in module.children():
84
+ init_weights_recursively(submodule)
code/flash-linear-attention/tests/modules/test_activation.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from fla.modules.activations import logsigmoid, sigmoid, swiglu, swiglu_linear, swish
7
+ from fla.utils import assert_close, device
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ ('B', 'T', 'D', 'compile'),
12
+ [
13
+ (1, 1, 64, False),
14
+ (2, 500, 128, False),
15
+ (2, 512, 128, True),
16
+ (3, 2048, 1200, True),
17
+ ],
18
+ )
19
+ def test_sigmoid(B: int, T: int, D: int, compile: bool):
20
+ torch.manual_seed(42)
21
+ x = torch.randn(B, T, D, device=device, requires_grad=True)
22
+ y_ref = torch.sigmoid(x)
23
+ y_tri = sigmoid(x) if not compile else torch.compile(sigmoid)(x)
24
+
25
+ g = torch.randn_like(y_ref)
26
+ dx_ref = torch.autograd.grad(y_ref, x, g)[0]
27
+ dx_tri = torch.autograd.grad(y_tri, x, g)[0]
28
+
29
+ assert_close('sigmoid fwd', y_ref, y_tri, 1e-3)
30
+ assert_close('sigmoid bwd', dx_ref, dx_tri, 1e-3)
31
+
32
+
33
+ @pytest.mark.parametrize(
34
+ ('B', 'T', 'D', 'temperature', 'compile'),
35
+ [
36
+ (1, 1, 64, 1.0, False),
37
+ (2, 500, 128, 0.5, False),
38
+ (2, 512, 128, 0.5, True),
39
+ (3, 2048, 1200, 2.0, True),
40
+ ],
41
+ )
42
+ def test_logsigmoid(B: int, T: int, D: int, temperature: float, compile: bool):
43
+ torch.manual_seed(42)
44
+ x = torch.randn(B, T, D, device=device, requires_grad=True)
45
+ y_ref = F.logsigmoid(x) / temperature
46
+ y_tri = logsigmoid(x, temperature) if not compile else torch.compile(logsigmoid)(x, temperature)
47
+
48
+ g = torch.randn_like(y_ref)
49
+ dx_ref = torch.autograd.grad(y_ref, x, g)[0]
50
+ dx_tri = torch.autograd.grad(y_tri, x, g)[0]
51
+
52
+ assert_close('logsigmoid fwd', y_ref, y_tri, 1e-3)
53
+ assert_close('logsigmoid bwd', dx_ref, dx_tri, 1e-3)
54
+
55
+
56
+ @pytest.mark.parametrize(
57
+ ('B', 'T', 'D', 'compile'),
58
+ [
59
+ (1, 1, 64, True),
60
+ (2, 500, 128, True),
61
+ (2, 512, 128, False),
62
+ (3, 2048, 1200, False),
63
+ ],
64
+ )
65
+ def test_swish(B: int, T: int, D: int, compile: bool):
66
+ torch.manual_seed(42)
67
+ x = torch.randn(B, T, D, device=device, requires_grad=True)
68
+ y_ref = F.silu(x)
69
+ y_tri = swish(x) if not compile else torch.compile(swish)(x)
70
+
71
+ g = torch.randn_like(y_ref)
72
+ dx_ref = torch.autograd.grad(y_ref, x, g)[0]
73
+ dx_tri = torch.autograd.grad(y_tri, x, g)[0]
74
+
75
+ assert_close('swish fwd', y_ref, y_tri, 1e-3)
76
+ assert_close('swish bwd', dx_ref, dx_tri, 1e-3)
77
+
78
+
79
+ @pytest.mark.parametrize(
80
+ ('B', 'T', 'D', 'compile'),
81
+ [
82
+ (1, 1, 64, True),
83
+ (2, 500, 128, True),
84
+ (2, 512, 128, False),
85
+ (3, 2048, 1200, False),
86
+ ],
87
+ )
88
+ def test_swiglu(B: int, T: int, D: int, compile: bool):
89
+ torch.manual_seed(42)
90
+ x = torch.randn(B, T, D, device=device, requires_grad=True)
91
+ y = torch.randn(B, T, D, device=device, requires_grad=True)
92
+
93
+ y_ref = F.silu(x) * y
94
+ y_tri = swiglu(x, y) if not compile else torch.compile(swiglu)(x, y)
95
+
96
+ g = torch.randn_like(y_ref)
97
+ dx_ref, dy_ref = torch.autograd.grad(y_ref, (x, y), g)
98
+ dx_tri, dy_tri = torch.autograd.grad(y_tri, (x, y), g)
99
+
100
+ assert_close('swiglu fwd', y_ref, y_tri, 1e-3)
101
+ assert_close('swiglu dx', dx_ref, dx_tri, 1e-3)
102
+ assert_close('swiglu dy', dy_ref, dy_tri, 1e-3)
103
+
104
+
105
+ @pytest.mark.parametrize(
106
+ ('B', 'T', 'D', 'O', 'compile'),
107
+ [
108
+ (2, 512, 128, 256, True),
109
+ (1, 1, 64, 32, False),
110
+ (2, 500, 128, 64, True),
111
+ (3, 2048, 1200, 600, False),
112
+ ],
113
+ )
114
+ def test_swiglu_linear(B: int, T: int, D: int, O: int, compile: bool): # noqa: E741
115
+ torch.manual_seed(42)
116
+ x = torch.randn(B, T, D, device=device, requires_grad=True)
117
+ y = torch.randn(B, T, D, device=device, requires_grad=True)
118
+ w = torch.randn(O, D, device=device, requires_grad=True)
119
+ b = torch.randn(O, device=device, requires_grad=True)
120
+
121
+ z_ref = F.silu(x) * y
122
+ out_ref = F.linear(z_ref, w, b)
123
+ out_tri = swiglu_linear(x, y, w, b) if not compile else torch.compile(swiglu_linear)(x, y, w, b)
124
+
125
+ g = torch.randn_like(out_ref)
126
+ dx_ref, dy_ref, dw_ref, db_ref = torch.autograd.grad(out_ref, (x, y, w, b), g)
127
+ dx_tri, dy_tri, dw_tri, db_tri = torch.autograd.grad(out_tri, (x, y, w, b), g)
128
+
129
+ assert_close('swiglu_linear out', out_ref, out_tri, 1e-3)
130
+ assert_close('swiglu_linear dx', dx_ref, dx_tri, 1e-3)
131
+ assert_close('swiglu_linear dy', dy_ref, dy_tri, 1e-3)
132
+ assert_close('swiglu_linear dw', dw_ref, dw_tri, 1e-3)
133
+ assert_close('swiglu_linear db', db_ref, db_tri, 1e-3)
code/flash-linear-attention/tests/modules/test_conv.py ADDED
@@ -0,0 +1,713 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from einops import rearrange
6
+
7
+ from fla.modules.convolution import ShortConvolution, causal_conv1d, causal_conv1d_update
8
+ from fla.utils import assert_close, device
9
+
10
+ try:
11
+ from causal_conv1d import causal_conv1d_fn
12
+ except ImportError:
13
+ causal_conv1d_fn = None
14
+
15
+
16
+ def causal_conv1d_ref_torch(
17
+ x,
18
+ weight,
19
+ bias=None,
20
+ initial_state=None,
21
+ output_final_state=False,
22
+ final_states_out=None,
23
+ activation=None,
24
+ ):
25
+ """
26
+ x: (batch, dim, seqlen)
27
+ weight: (dim, width)
28
+ bias: (dim,)
29
+ initial_state: (batch, dim, width - 1)
30
+ final_states_out: (batch, dim, width - 1)
31
+
32
+ out: (batch, dim, seqlen)
33
+ """
34
+ if activation not in [None, "silu", "swish"]:
35
+ raise NotImplementedError("activation must be None, silu, or swish")
36
+ dtype_in = x.dtype
37
+ x = x.to(weight.dtype)
38
+ seqlen = x.shape[-1]
39
+ dim, width = weight.shape
40
+ if initial_state is None:
41
+ out = F.conv1d(x, weight.unsqueeze(1), bias, padding=width - 1, groups=dim)
42
+ else:
43
+ x = torch.cat([initial_state, x], dim=-1)
44
+ out = F.conv1d(x, weight.unsqueeze(1), bias, padding=0, groups=dim)
45
+ out = out[..., :seqlen]
46
+ if output_final_state:
47
+ final_states = F.pad(x, (width - 1 - x.shape[-1], 0)).to(
48
+ dtype_in,
49
+ ) # (batch, dim, width - 1)
50
+ if final_states_out is not None:
51
+ final_states_out.copy_(final_states)
52
+ else:
53
+ final_states_out = final_states
54
+ out = (out if activation is None else F.silu(out)).to(dtype=dtype_in)
55
+ return out if not output_final_state else (out, final_states_out)
56
+
57
+
58
+ def causal_conv1d_update_ref_torch(x, conv_state, weight, bias=None, activation=None, cache_seqlens=None):
59
+ """
60
+ x: (batch, dim) or (batch, dim, seqlen)
61
+ conv_state: (batch, dim, state_len), where state_len >= width - 1
62
+ weight: (dim, width)
63
+ bias: (dim,)
64
+ cache_seqlens: (batch,), dtype int32.
65
+ If not None, the conv_state is treated as a circular buffer.
66
+ The conv_state will be updated by copying x to the conv_state starting at the index
67
+ @cache_seqlens % state_len before performing the convolution.
68
+
69
+ out: (batch, dim) or (batch, dim, seqlen)
70
+ """
71
+ if activation not in [None, "silu", "swish"]:
72
+ raise NotImplementedError("activation must be None, silu, or swish")
73
+ dtype_in = x.dtype
74
+ unsqueeze = x.dim() == 2
75
+ if unsqueeze:
76
+ x = x.unsqueeze(-1)
77
+ batch, dim, seqlen = x.shape
78
+ width = weight.shape[1]
79
+ state_len = conv_state.shape[-1]
80
+ assert conv_state.shape == (batch, dim, state_len)
81
+ assert weight.shape == (dim, width)
82
+ if cache_seqlens is None:
83
+ x_new = torch.cat([conv_state, x], dim=-1).to(weight.dtype) # (batch, dim, state_len + seqlen)
84
+ conv_state.copy_(x_new[:, :, -state_len:])
85
+ else:
86
+ width_idx = torch.arange(-(width - 1), 0, dtype=torch.long, device=x.device).unsqueeze(0) + cache_seqlens.unsqueeze(1)
87
+ width_idx = torch.remainder(width_idx, state_len).unsqueeze(1).expand(-1, dim, -1)
88
+ x_new = torch.cat([conv_state.gather(2, width_idx), x], dim=-1).to(weight.dtype)
89
+ copy_idx = torch.arange(seqlen, dtype=torch.long, device=x.device).unsqueeze(0) + cache_seqlens.unsqueeze(1)
90
+ copy_idx = torch.remainder(copy_idx, state_len).unsqueeze(1).expand(-1, dim, -1)
91
+ conv_state.scatter_(2, copy_idx, x)
92
+ out = F.conv1d(x_new, weight.unsqueeze(1), bias, padding=0, groups=dim)[:, :, -seqlen:]
93
+ if unsqueeze:
94
+ out = out.squeeze(-1)
95
+ return (out if activation is None else F.silu(out)).to(dtype=dtype_in)
96
+
97
+
98
+ @pytest.mark.parametrize(
99
+ ('B', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype'),
100
+ [
101
+ pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}".format(*test))
102
+ for test in [
103
+ (2, 64, 128, 3, "swish", True, True, torch.float32),
104
+ (2, 128, 128, 4, "swish", False, True, torch.float32),
105
+ (2, 64, 128, 3, "swish", True, False, torch.float32),
106
+ (2, 128, 128, 4, "swish", False, False, torch.float32),
107
+ (2, 500, 1024, 3, None, True, True, torch.float32),
108
+ (2, 1024, 1024, 4, None, False, True, torch.float32),
109
+ (2, 64, 128, 3, None, True, False, torch.float16),
110
+ (2, 128, 128, 4, None, False, False, torch.float16),
111
+ ]
112
+ ],
113
+ )
114
+ def test_conv(
115
+ B: int,
116
+ T: int,
117
+ D: int,
118
+ W: int,
119
+ activation: str,
120
+ has_bias: bool,
121
+ has_residual: bool,
122
+ dtype: torch.dtype,
123
+ ):
124
+ torch.manual_seed(42)
125
+
126
+ x = torch.randn(B, T, D).to(device, dtype).requires_grad_(True)
127
+ weight = torch.randn(D, W).to(device, dtype).requires_grad_(True)
128
+ bias = torch.randn(D).to(device, dtype).requires_grad_(True) if has_bias else None
129
+ residual = x.detach().clone().requires_grad_(True) if has_residual else None
130
+ dy = torch.randn(B, T, D).to(device, dtype)
131
+
132
+ ref = causal_conv1d_ref_torch(
133
+ x=rearrange(x, "b t d -> b d t"),
134
+ weight=weight,
135
+ bias=bias,
136
+ activation=activation,
137
+ )
138
+ ref = rearrange(ref, "b d t -> b t d")
139
+ if has_residual:
140
+ ref += residual
141
+ ref.backward(dy)
142
+ ref_dx, x.grad = x.grad, None
143
+ ref_dw, weight.grad = weight.grad, None
144
+ if has_bias:
145
+ ref_db, bias.grad = bias.grad, None
146
+ if has_residual:
147
+ ref_dr, residual.grad = residual.grad, None
148
+
149
+ tri, _ = causal_conv1d(x, weight, bias, residual=residual, activation=activation)
150
+ tri.backward(dy)
151
+ tri_dx, x.grad = x.grad, None
152
+ tri_dw, weight.grad = weight.grad, None
153
+ if has_bias:
154
+ tri_db, bias.grad = bias.grad, None
155
+ if has_residual:
156
+ tri_dr, residual.grad = residual.grad, None
157
+
158
+ assert_close(" y", ref, tri, 1e-3)
159
+ assert_close("dx", ref_dx, tri_dx, 1e-3)
160
+ assert_close("dw", ref_dw, tri_dw, 1e-3)
161
+ if has_bias:
162
+ assert_close("db", ref_db, tri_db, 1e-3)
163
+ if has_residual:
164
+ assert_close("dr", ref_dr, tri_dr, 1e-3)
165
+
166
+
167
+ @pytest.mark.parametrize(
168
+ ('N', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype'),
169
+ [
170
+ pytest.param(*test, id="N{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}".format(*test))
171
+ for test in [
172
+ (4, 500, 128, 3, "swish", True, True, torch.float32),
173
+ (4, 1024, 200, 4, "swish", False, True, torch.float32),
174
+ (4, 500, 128, 3, None, True, False, torch.float16),
175
+ (4, 1024, 1024, 4, None, False, False, torch.float16),
176
+ ]
177
+ ],
178
+ )
179
+ def test_conv_varlen(
180
+ N: int,
181
+ T: int,
182
+ D: int,
183
+ W: int,
184
+ activation: str,
185
+ has_bias: bool,
186
+ has_residual: bool,
187
+ dtype: torch.dtype,
188
+ ):
189
+ torch.manual_seed(42)
190
+ cu_seqlens = torch.cat([
191
+ torch.tensor([0], dtype=torch.long),
192
+ torch.arange(16, T)[torch.randperm(T - 16)[:N-1]],
193
+ torch.tensor([T], dtype=torch.long),
194
+ ], 0).to(device).sort()[0]
195
+
196
+ x = torch.randn(1, T, D).to(device, dtype).requires_grad_(True)
197
+ weight = torch.randn(D, W).to(device, dtype).requires_grad_(True)
198
+ bias = torch.randn(D).to(device, dtype).requires_grad_(True) if has_bias else None
199
+ residual = x.detach().clone().requires_grad_(True) if has_residual else None
200
+ dy = torch.randn(1, T, D).to(device, dtype)
201
+
202
+ ref = torch.cat([
203
+ rearrange(
204
+ causal_conv1d_ref_torch(
205
+ x=rearrange(x[:, bos:eos].contiguous(), "b t d -> b d t"),
206
+ weight=weight,
207
+ bias=bias,
208
+ activation=activation,
209
+ ),
210
+ "b t d -> b d t",
211
+ ) + (residual[:, bos:eos] if has_residual else torch.zeros_like(x[:, bos:eos]))
212
+ for bos, eos in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)
213
+ ], 1)
214
+ ref.backward(dy)
215
+ ref_dx, x.grad = x.grad, None
216
+ ref_dw, weight.grad = weight.grad, None
217
+ if has_bias:
218
+ ref_db, bias.grad = bias.grad, None
219
+ if has_residual:
220
+ ref_dr, residual.grad = residual.grad, None
221
+
222
+ tri, _ = causal_conv1d(x, weight, bias, residual=residual, activation=activation, cu_seqlens=cu_seqlens)
223
+ tri.backward(dy)
224
+ tri_dx, x.grad = x.grad, None
225
+ tri_dw, weight.grad = weight.grad, None
226
+ if has_bias:
227
+ tri_db, bias.grad = bias.grad, None
228
+ if has_residual:
229
+ tri_dr, residual.grad = residual.grad, None
230
+
231
+ assert_close(" y", ref, tri, 1e-3)
232
+ assert_close("dx", ref_dx, tri_dx, 1e-3)
233
+ assert_close("dw", ref_dw, tri_dw, 1e-3)
234
+ if has_bias:
235
+ assert_close("db", ref_db, tri_db, 1e-3)
236
+ if has_residual:
237
+ assert_close("dr", ref_dr, tri_dr, 1e-3)
238
+
239
+
240
+ @pytest.mark.parametrize(
241
+ ('B', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype'),
242
+ [
243
+ pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}".format(*test))
244
+ for test in [
245
+ (2, 64, 128, 3, "swish", True, True, torch.float32),
246
+ (2, 128, 128, 4, "swish", False, True, torch.float32),
247
+ (2, 64, 128, 3, "swish", True, False, torch.float32),
248
+ (2, 128, 128, 4, "swish", False, False, torch.float32),
249
+ (2, 500, 1024, 3, None, True, True, torch.float32),
250
+ (2, 1024, 1024, 4, None, False, True, torch.float32),
251
+ (2, 64, 128, 3, None, True, False, torch.float16),
252
+ (2, 128, 128, 4, None, False, False, torch.float16),
253
+ ]
254
+ ],
255
+ )
256
+ @torch.no_grad
257
+ def test_conv_decoding(
258
+ B: int,
259
+ T: int,
260
+ D: int,
261
+ W: int,
262
+ activation: str,
263
+ has_bias: bool,
264
+ has_residual: bool,
265
+ dtype: torch.dtype,
266
+ ):
267
+ torch.manual_seed(42)
268
+
269
+ x = torch.randn(B, T, D).to(device, dtype)
270
+ weight = torch.randn(D, W).to(device, dtype) * 0
271
+ bias = torch.randn(D).to(device, dtype) if has_bias else None
272
+ residual = x.clone() if has_residual else None
273
+
274
+ ref = causal_conv1d_ref_torch(
275
+ x=rearrange(x, "b t d -> b d t"),
276
+ weight=weight,
277
+ bias=bias,
278
+ activation=activation,
279
+ )
280
+ ref = rearrange(ref, "b d t -> b t d")
281
+ if has_residual:
282
+ ref += residual
283
+ ref_cache = x.new_zeros(B, D, W)
284
+ ref_cache[:, :, -min(W, T):].copy_(rearrange(x[..., -min(W, T):, :], 'n w d -> n d w'))
285
+
286
+ tri = torch.zeros_like(x)
287
+ tri_cache = x.new_zeros(B, D, W)
288
+ for i in range(T):
289
+ y, tri_cache = causal_conv1d_update(
290
+ x=x[:, i:i+1, :],
291
+ cache=tri_cache,
292
+ residual=residual[:, i:i+1, :] if has_residual else None,
293
+ weight=weight,
294
+ bias=bias,
295
+ activation=activation,
296
+ )
297
+ tri[:, i:i+1, :] = y
298
+
299
+ assert_close(" y", ref, tri, 1e-3)
300
+ assert_close("cache", ref_cache, tri_cache, 1e-3)
301
+
302
+
303
+ @pytest.mark.parametrize(
304
+ ('B', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype', 'backend'),
305
+ [
306
+ pytest.param(
307
+ *test, id="B{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}_{8}".format(*test))
308
+ for test in [
309
+ (2, 64, 128, 3, "swish", True, True, torch.float32, 'triton'),
310
+ (2, 128, 128, 4, "swish", False, True, torch.float32, 'triton'),
311
+ (2, 64, 128, 3, "swish", True, False, torch.float32, 'triton'),
312
+ (2, 128, 128, 4, "swish", False, False, torch.float32, 'triton'),
313
+ (2, 500, 1024, 3, None, True, True, torch.float32, 'triton'),
314
+ (2, 1024, 1024, 4, None, False, True, torch.float32, 'triton'),
315
+ (2, 64, 128, 3, None, True, False, torch.float16, 'triton'),
316
+ (2, 128, 128, 4, None, False, False, torch.float16, 'triton'),
317
+ (2, 64, 128, 3, "swish", True, True, torch.float32, 'cuda'),
318
+ (2, 128, 128, 4, "swish", False, True, torch.float32, 'cuda'),
319
+ (2, 64, 128, 3, "swish", True, False, torch.float32, 'cuda'),
320
+ (2, 128, 128, 4, "swish", False, False, torch.float32, 'cuda'),
321
+ (2, 2, 128, 4, "swish", True, True, torch.float32, 'cuda'), # T_prefill < W
322
+ (2, 2, 128, 4, "swish", True, True, torch.float32, 'triton'),
323
+ (2, 3, 128, 4, "swish", True, True, torch.float32, 'triton'),
324
+ (2, 4, 128, 4, "swish", True, True, torch.float32, 'triton'),
325
+ (2, 2, 128, 3, "swish", True, True, torch.float32, 'triton'),
326
+ ]
327
+ ],
328
+ )
329
+ @torch.no_grad
330
+ def test_conv_with_cache_prefill_fwd(
331
+ B: int,
332
+ T: int,
333
+ D: int,
334
+ W: int,
335
+ activation: str,
336
+ has_bias: bool,
337
+ has_residual: bool,
338
+ dtype: torch.dtype,
339
+ backend: str,
340
+ ):
341
+ if causal_conv1d_fn is None and backend == 'cuda':
342
+ pytest.skip("causal_conv1d is not installed for CUDA backend")
343
+ torch.manual_seed(42)
344
+
345
+ x = torch.randn(B, T, D).to(device, dtype)
346
+ residual = torch.randn(B, T, D).to(device, dtype) if has_residual else None
347
+
348
+ conv = ShortConvolution(
349
+ hidden_size=D,
350
+ kernel_size=W,
351
+ bias=has_bias,
352
+ activation=activation,
353
+ backend=backend,
354
+ device=device,
355
+ dtype=dtype,
356
+ )
357
+
358
+ cache = torch.randn(B, D, W - 1).to(device, dtype)
359
+
360
+ ref = causal_conv1d_ref_torch(
361
+ x=x.transpose(1, 2), # (B, D, T)
362
+ weight=rearrange(conv.weight, "d 1 w -> d w"),
363
+ bias=conv.bias,
364
+ initial_state=cache, # (B, D, W-1)
365
+ activation=activation,
366
+ ).transpose(1, 2) # (B, T, D)
367
+ if has_residual:
368
+ ref += residual
369
+
370
+ zero_padding = torch.zeros(B, D, 1).to(device, dtype)
371
+ tri_cache = torch.cat([zero_padding, cache], dim=-1) # (B, D, W)
372
+ tri, cache_out = conv(x, residual=residual, cache=tri_cache.clone(), output_final_state=True)
373
+
374
+ assert_close("y", ref, tri, 1e-3)
375
+ for p in range(1, W):
376
+ if p <= T:
377
+ expected = x[:, -p, :]
378
+ else:
379
+ expected = tri_cache[:, :, -(p - T)]
380
+ torch.testing.assert_close(
381
+ cache_out[:, :, -p],
382
+ expected,
383
+ atol=1e-3, rtol=1e-3,
384
+ )
385
+
386
+
387
+ @pytest.mark.parametrize(
388
+ ('N', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype', 'backend'),
389
+ [
390
+ pytest.param(
391
+ *test,
392
+ id="N{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}_{8}".format(*test),
393
+ )
394
+ for test in [
395
+ (3, 128, 64, 4, "swish", True, True, torch.float32, 'triton'),
396
+ (4, 256, 128, 3, None, False, True, torch.float32, 'triton'),
397
+ (2, 64, 128, 4, "swish", True, False, torch.float16, 'cuda'),
398
+ (3, 200, 64, 3, None, False, False, torch.float16, 'cuda'),
399
+ (2, 3, 64, 4, "swish", True, True, torch.float32, 'triton'), # T < W
400
+ (2, 3, 64, 3, None, False, True, torch.float32, 'cuda'), # T < W
401
+ ]
402
+ ],
403
+ )
404
+ @torch.no_grad
405
+ def test_conv_varlen_with_cache_prefill_fwd(
406
+ N: int,
407
+ T: int,
408
+ D: int,
409
+ W: int,
410
+ activation: str,
411
+ has_bias: bool,
412
+ has_residual: bool,
413
+ dtype: torch.dtype,
414
+ backend: str,
415
+ ):
416
+ if causal_conv1d_fn is None and backend == 'cuda':
417
+ pytest.skip("causal_conv1d is not installed for CUDA backend")
418
+ torch.manual_seed(42)
419
+
420
+ min_len_each = max(1, T // N)
421
+ lengths = [min_len_each] * N
422
+ lengths[-1] += T % N
423
+ assert all(length >= 1 for length in lengths), "all lengths must >= 1"
424
+ cu_seqlens = torch.tensor([0] + torch.cumsum(torch.tensor(lengths), 0).tolist(),
425
+ device=device, dtype=torch.int32)
426
+
427
+ x = torch.randn(1, T, D).to(device, dtype)
428
+ residual = torch.randn(1, T, D).to(device, dtype) if has_residual else None
429
+
430
+ conv = ShortConvolution(
431
+ hidden_size=D,
432
+ kernel_size=W,
433
+ bias=has_bias,
434
+ activation=activation,
435
+ backend=backend,
436
+ device=device,
437
+ dtype=dtype,
438
+ )
439
+
440
+ cache = torch.randn(N, D, W - 1).to(device, dtype)
441
+ ref_list = []
442
+ for i, (bos, eos) in enumerate(zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)):
443
+ xi = x[:, bos:eos, :].transpose(1, 2) # (1, D, l)
444
+ ci = cache[i:i + 1] # (1, D, W-1)
445
+ refi = causal_conv1d_ref_torch(
446
+ x=xi,
447
+ weight=rearrange(conv.weight, "d 1 w -> d w"),
448
+ bias=conv.bias,
449
+ initial_state=ci,
450
+ activation=activation,
451
+ ).transpose(1, 2) # (1, l, D)
452
+ if has_residual:
453
+ refi += residual[:, bos:eos, :]
454
+ ref_list.append(refi)
455
+ ref = torch.cat(ref_list, dim=1) # (1, T, D)
456
+
457
+ zero_pad = torch.zeros(N, D, 1, device=device, dtype=dtype)
458
+ tri_cache = torch.cat([zero_pad, cache], dim=-1) # (N, D, W)
459
+ tri, cache_out = conv(x,
460
+ residual=residual,
461
+ cache=tri_cache.clone(),
462
+ cu_seqlens=cu_seqlens,
463
+ output_final_state=True)
464
+
465
+ assert_close("varlen y", ref, tri, 1e-3)
466
+
467
+ for i, (bos, eos) in enumerate(zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)):
468
+ length = eos - bos
469
+ for p in range(1, W):
470
+ if p <= length:
471
+ expected = x[0, eos - p, :]
472
+ else:
473
+ expected = tri_cache[i, :, -(p - length)]
474
+ torch.testing.assert_close(
475
+ cache_out[i, :, -p],
476
+ expected,
477
+ atol=1e-3,
478
+ rtol=1e-3,
479
+ )
480
+
481
+
482
+ @pytest.mark.parametrize(
483
+ ('B', 'D', 'W', 'has_bias', 'has_residual', 'activation', 'dtype', 'backend'),
484
+ [
485
+ pytest.param(*test, id="B{0}_D{1}_W{2}_has_bias{3}_has_residual{4}_activation{5}_{6}_{7}".format(*test))
486
+ for test in [
487
+ (2, 128, 3, True, True, "swish", torch.float32, 'triton'),
488
+ (2, 128, 4, False, True, "swish", torch.float32, 'triton'),
489
+ (2, 128, 3, True, False, "swish", torch.float32, 'triton'),
490
+ (2, 128, 4, False, False, "swish", torch.float32, 'triton'),
491
+ (2, 128, 3, True, True, "swish", torch.float32, 'cuda'),
492
+ (2, 128, 4, False, True, "swish", torch.float32, 'cuda'),
493
+ (2, 128, 3, True, False, "swish", torch.float32, 'cuda'),
494
+ (2, 128, 4, False, False, "swish", torch.float32, 'cuda'),
495
+ (2, 128, 4, False, False, None, torch.float32, 'cuda'),
496
+ (2, 128, 4, False, False, None, torch.float32, 'triton'),
497
+ ]
498
+ ],
499
+ )
500
+ @torch.no_grad
501
+ def test_conv_decoding_with_cache(
502
+ B: int,
503
+ D: int,
504
+ W: int,
505
+ activation: str,
506
+ has_bias: bool,
507
+ has_residual: bool,
508
+ dtype: torch.dtype,
509
+ backend: str,
510
+ ):
511
+ if causal_conv1d_fn is None and backend == 'cuda':
512
+ pytest.skip("causal_conv1d is not installed for CUDA backend")
513
+ torch.manual_seed(42)
514
+
515
+ x = torch.randn(B, 1, D).to(device, dtype) # (B, 1, D)
516
+ residual = x.clone() if has_residual else None
517
+
518
+ conv = ShortConvolution(
519
+ hidden_size=D,
520
+ kernel_size=W,
521
+ bias=has_bias,
522
+ activation=activation,
523
+ backend=backend,
524
+ device=device,
525
+ dtype=dtype,
526
+ )
527
+
528
+ state = torch.randn(B, D, W).to(device, dtype)
529
+
530
+ # reference
531
+ ref = causal_conv1d_update_ref_torch(
532
+ x.squeeze(1), # (B, D)
533
+ conv_state=state.clone(),
534
+ weight=rearrange(conv.weight, "d 1 w -> d w"),
535
+ bias=conv.bias,
536
+ activation=activation,
537
+ ).unsqueeze(1) # (B, 1, D)
538
+ if has_residual:
539
+ ref += residual
540
+
541
+ # ShortConvolution step
542
+ with torch.no_grad():
543
+ y, _ = conv.step(x, residual, state.clone())
544
+
545
+ assert_close("y", ref, y, 1e-3)
546
+
547
+
548
+ @pytest.mark.parametrize(
549
+ ('B', 'T', 'D', 'W', 'has_bias', 'has_residual', 'activation', 'dtype'),
550
+ [
551
+ pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_has_bias{4}_has_residual{5}_activation{6}_{7}".format(*test))
552
+ for test in [
553
+ (2, 64, 128, 3, True, True, "swish", torch.float32),
554
+ (2, 128, 128, 4, False, True, "swish", torch.float32),
555
+ (2, 64, 128, 3, True, False, "swish", torch.float32),
556
+ (2, 128, 128, 4, False, False, "swish", torch.float32),
557
+ ]
558
+ ],
559
+ )
560
+ @torch.no_grad
561
+ def test_mixed_backend(
562
+ B: int,
563
+ T: int,
564
+ D: int,
565
+ W: int,
566
+ has_bias: bool,
567
+ has_residual: bool,
568
+ activation: str,
569
+ dtype: torch.dtype,
570
+ ):
571
+ torch.manual_seed(42)
572
+ T_decode = 1
573
+ x = torch.randn(B, T + T_decode, D, device=device, dtype=dtype)
574
+ residual = torch.randn_like(x) if has_residual else None
575
+
576
+ conv = ShortConvolution(
577
+ hidden_size=D,
578
+ kernel_size=W,
579
+ bias=has_bias,
580
+ activation=activation,
581
+ backend="cuda",
582
+ device=device,
583
+ dtype=dtype,
584
+ )
585
+
586
+ cache = torch.randn(B, D, W-1, device=device, dtype=dtype)
587
+ y_cuda_prefill, final_state = conv(
588
+ x[:, :T],
589
+ residual=residual[:, :T] if has_residual else None,
590
+ cache=cache,
591
+ output_final_state=True,
592
+ )
593
+
594
+ conv.backend = "triton"
595
+ y_triton_decode, _ = conv(
596
+ x[:, T:],
597
+ residual=residual[:, T:] if has_residual else None,
598
+ cache=final_state,
599
+ output_final_state=True,
600
+ )
601
+
602
+ conv.backend = "triton"
603
+ cache = torch.cat((torch.zeros_like(cache[..., :1]), cache), -1)
604
+ y_triton_full, _ = conv(x, residual=residual, cache=cache)
605
+
606
+ y_mixed = torch.cat([y_cuda_prefill, y_triton_decode], dim=1)
607
+ assert_close("cuda→triton vs triton", y_mixed, y_triton_full, 1e-3)
608
+
609
+ conv.backend = "triton"
610
+ y_triton_prefill, final_state = conv(
611
+ x[:, :T],
612
+ residual=residual[:, :T] if has_residual else None,
613
+ cache=cache,
614
+ output_final_state=True,
615
+ )
616
+
617
+ conv.backend = "cuda"
618
+ y_cuda_decode, _ = conv(
619
+ x[:, T:],
620
+ residual=residual[:, T:] if has_residual else None,
621
+ cache=final_state,
622
+ output_final_state=True,
623
+ )
624
+
625
+ y_mixed2 = torch.cat([y_triton_prefill, y_cuda_decode], dim=1)
626
+ assert_close("triton→cuda vs triton", y_mixed2, y_triton_full, 1e-3)
627
+
628
+
629
+ @pytest.mark.parametrize(
630
+ ('B', 'T', 'D', 'W', 'has_bias', 'has_residual', 'activation', 'dtype'),
631
+ [
632
+ pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_has_bias{4}_has_residual{5}_activation{6}_{7}".format(*test))
633
+ for test in [
634
+ (2, 64, 100, 3, True, True, "swish", torch.float32),
635
+ (2, 128, 128, 4, True, True, "swish", torch.float32),
636
+ (3, 128, 128, 4, True, True, "swish", torch.float32),
637
+ (3, 128, 256, 4, True, True, "swish", torch.float32),
638
+ (3, 128, 512, 4, True, True, "swish", torch.float32),
639
+ (2, 128, 1024, 4, True, True, "swish", torch.float32),
640
+ (2, 128, 2048, 3, True, True, "swish", torch.float32),
641
+ (2, 128, 4096, 4, True, True, "swish", torch.float32),
642
+ (2, 128, 8192, 4, True, True, "swish", torch.float32),
643
+ ]
644
+ ],
645
+ )
646
+ def test_conv_cache_backward(
647
+ B: int,
648
+ T: int,
649
+ D: int,
650
+ W: int,
651
+ has_bias: bool,
652
+ has_residual: bool,
653
+ activation: str,
654
+ dtype: torch.dtype,
655
+ ):
656
+ torch.manual_seed(42)
657
+
658
+ x = torch.randn(B, T, D, device=device, dtype=dtype, requires_grad=True)
659
+ weight = torch.randn(D, W, device=device, dtype=dtype, requires_grad=True)
660
+ bias = torch.randn(D, device=device, dtype=dtype, requires_grad=True) if has_bias else None
661
+ residual = torch.randn(B, T, D, device=device, dtype=dtype, requires_grad=True) if has_residual else None
662
+ cache = torch.randn(B, D, W - 1, device=device, dtype=dtype, requires_grad=True)
663
+
664
+ def ref_func(x, weight, bias, residual, cache):
665
+ out, cache_out = causal_conv1d_ref_torch(
666
+ x.transpose(1, 2),
667
+ weight,
668
+ bias,
669
+ initial_state=cache,
670
+ output_final_state=True,
671
+ activation=activation,
672
+ )
673
+ out = out.transpose(1, 2)
674
+ if residual is not None:
675
+ out += residual
676
+ return out, cache_out
677
+
678
+ def triton_func(x, weight, bias, residual, cache):
679
+ zero_padding = torch.zeros(B, D, 1, device=device, dtype=dtype)
680
+ triton_cache = torch.cat([zero_padding, cache], dim=-1).contiguous()
681
+ tri, cache_out_triton = causal_conv1d(
682
+ x,
683
+ weight=weight,
684
+ bias=bias,
685
+ residual=residual,
686
+ initial_state=triton_cache,
687
+ output_final_state=True,
688
+ activation=activation,
689
+ )
690
+ cache_out_triton = cache_out_triton[..., 1:].clone() # [B, D, W-1]
691
+ return tri, cache_out_triton
692
+
693
+ d_tri = torch.randn_like(x)
694
+ d_cache_out = torch.randn_like(cache)
695
+
696
+ def get_grads(func, *inputs):
697
+ out, cache_out = func(*inputs)
698
+ loss = (out * d_tri).sum() + (cache_out * d_cache_out).sum()
699
+ grads = torch.autograd.grad(
700
+ loss,
701
+ inputs,
702
+ retain_graph=True,
703
+ create_graph=False,
704
+ )
705
+ return grads
706
+
707
+ inputs = (x, weight, bias, residual, cache)
708
+ grads_ref = get_grads(ref_func, *inputs)
709
+ grads_tri = get_grads(triton_func, *inputs)
710
+
711
+ names = ["x", "weight", "bias", "residual", "cache"]
712
+ for name, g_ref, g_tri in zip(names, grads_ref, grads_tri, strict=False):
713
+ assert_close(name, g_ref, g_tri, ratio=1e-3)
code/flash-linear-attention/tests/modules/test_cross_entropy.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+ from fla.modules import FusedCrossEntropyLoss, FusedLinearCrossEntropyLoss
8
+ from fla.utils import assert_close, device, device_platform
9
+
10
+
11
+ @pytest.mark.parametrize("B", [2])
12
+ @pytest.mark.parametrize("T", [512, 1024])
13
+ @pytest.mark.parametrize("D", [1024, 2048])
14
+ @pytest.mark.parametrize("V", [32000, 100000])
15
+ @pytest.mark.parametrize("reduction", ['mean'])
16
+ @pytest.mark.parametrize("dtype", [torch.bfloat16])
17
+ @pytest.mark.skipif(
18
+ device_platform == 'intel',
19
+ reason="Intel Triton Failure",
20
+ )
21
+ def test_fused_cross_entropy(B: int, T: int, D: int, V: int, reduction: str, dtype: torch.dtype):
22
+ torch.manual_seed(42)
23
+ logits = torch.randn(B * T, V).to(device).to(dtype=dtype).requires_grad_()
24
+ target = torch.randint(0, V, (B, T)).to(device)
25
+ target = torch.cat((target[..., 1:], torch.full_like(target[..., :1], -100)), -1)
26
+ target = target.flatten()
27
+
28
+ ref = nn.CrossEntropyLoss(reduction=reduction)(logits, target).to(dtype=dtype)
29
+ do = torch.randn_like(ref).to(device).to(dtype=dtype)
30
+
31
+ ref.backward(do)
32
+ ref_d, logits.grad = logits.grad.clone(), None
33
+
34
+ tri = FusedCrossEntropyLoss(reduction=reduction)(logits, target).to(dtype=dtype)
35
+ tri.backward(do)
36
+ tri_d, logits.grad = logits.grad.clone(), None
37
+
38
+ assert_close(" o", ref, tri, ratio=1e-2)
39
+ assert_close("dl", ref_d, tri_d, ratio=1e-2)
40
+
41
+
42
+ @pytest.mark.parametrize("B", [2])
43
+ @pytest.mark.parametrize("T", [512, 1024])
44
+ @pytest.mark.parametrize("D", [1024, 2048])
45
+ @pytest.mark.parametrize("V", [32000, 100000])
46
+ @pytest.mark.parametrize("scale", [1., 0.5])
47
+ @pytest.mark.parametrize("reduction", ['mean'])
48
+ @pytest.mark.parametrize("dtype", [torch.bfloat16])
49
+ @pytest.mark.skipif(
50
+ device_platform == 'intel',
51
+ reason="Intel Triton Failure",
52
+ )
53
+ def test_fused_linear_cross_entropy(B: int, T: int, D: int, V: int, scale: float, reduction: str, dtype: torch.dtype):
54
+ torch.manual_seed(42)
55
+
56
+ x = torch.randn(B * T, D).to(device).to(dtype=dtype).requires_grad_()
57
+ target = torch.randint(0, V, (B, T)).to(device)
58
+ target = torch.cat((target[..., 1:], torch.full_like(target[..., :1], -100)), -1)
59
+ target = target.flatten()
60
+ weight = torch.randn(V, D).to(device).to(dtype=dtype).requires_grad_()
61
+ bias = torch.randn(V).to(device).to(dtype=dtype).requires_grad_()
62
+
63
+ logits = F.linear(x, weight, bias)
64
+ ref = FusedCrossEntropyLoss(logit_scale=scale, reduction=reduction)(logits, target)
65
+ do = torch.randn_like(ref).to(device).to(dtype=dtype)
66
+
67
+ ref.backward(do)
68
+ ref_dx, x.grad = x.grad.clone(), None
69
+ ref_dw, weight.grad = weight.grad.clone(), None
70
+ ref_db, bias.grad = bias.grad.clone(), None
71
+
72
+ tri = FusedLinearCrossEntropyLoss(logit_scale=scale, reduction=reduction)(x, target, weight, bias)
73
+ tri.backward(do)
74
+ tri_dx, x.grad = x.grad.clone(), None
75
+ tri_dw, weight.grad = weight.grad.clone(), None
76
+ tri_db, bias.grad = bias.grad.clone(), None
77
+
78
+ assert_close(" o", ref, tri, ratio=1e-2)
79
+ assert_close("dx", ref_dx, tri_dx, ratio=1e-2)
80
+ assert_close("dw", ref_dw, tri_dw, ratio=1e-2)
81
+ assert_close("db", ref_db, tri_db, ratio=1e-2)
code/flash-linear-attention/tests/modules/test_grpo.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+
5
+ from fla.modules.grpo import fused_grpo_loss, grpo_loss_torch
6
+ from fla.utils import assert_close, device, device_torch_lib, is_nvidia_hopper
7
+
8
+
9
+ @pytest.mark.parametrize("B", [2])
10
+ @pytest.mark.parametrize("T", [16, 1024, 4096])
11
+ @pytest.mark.parametrize("V", [32000, 65536, 131072])
12
+ @pytest.mark.parametrize("dtype", [torch.bfloat16])
13
+ @pytest.mark.parametrize("inplace", [True, False])
14
+ @pytest.mark.parametrize("repeat", [100])
15
+ def test_fused_grpos(B: int, T: int, V: int, dtype: torch.dtype, inplace: bool, repeat: int):
16
+ device_torch_lib.manual_seed(42)
17
+ for i in range(repeat):
18
+ if not is_nvidia_hopper and T == 4096:
19
+ pytest.skip("Skip test for T=4096 on Intel Alchemist")
20
+
21
+ def get_random_ref_log_probs(logits, input_ids):
22
+ with torch.inference_mode():
23
+ logits = logits[:, :-1]
24
+ per_token_logps = []
25
+ for logits_row, input_ids_row in zip(logits, input_ids[:, -logits.size(1):], strict=False):
26
+ log_probs = torch.randn_like(logits_row).log_softmax(dim=-1)
27
+ token_log_prob = torch.gather(log_probs, dim=1, index=input_ids_row.unsqueeze(1)).squeeze(1)
28
+ per_token_logps.append(token_log_prob)
29
+ device_torch_lib.empty_cache()
30
+ return torch.stack(per_token_logps)
31
+
32
+ logits = torch.randn(B, T + 1, V, device=device, dtype=dtype)
33
+ logits.requires_grad_(True)
34
+ advantages = torch.randn(B, device=device, dtype=torch.float32)
35
+ input_ids = torch.randint(0, V-1, (B, T + 64), device=device)
36
+ ref_logp = get_random_ref_log_probs(logits, input_ids)
37
+ beta = 0.04
38
+ completion_mask = torch.ones(B, T, dtype=torch.int32, device=device)
39
+ completion_mask[::2, T//3: T//2] = 0
40
+ save_kl = True
41
+
42
+ gold_logits = logits.detach().clone().float()
43
+ gold_logits.requires_grad_(True)
44
+ gold_ref_logp = ref_logp.clone().float()
45
+ device_torch_lib.empty_cache()
46
+ y1 = fused_grpo_loss(logits, ref_logp, input_ids, advantages, beta, completion_mask, save_kl=save_kl, inplace=inplace)
47
+ y2 = grpo_loss_torch(gold_logits, gold_ref_logp, input_ids, advantages, beta, completion_mask, save_kl)
48
+ if save_kl:
49
+ y1, kl2 = y1
50
+ y2, kl3 = y2
51
+ assert (kl2-kl3).abs().max() < 1e-3
52
+ dy = torch.randn_like(y1) * 10
53
+ y1.backward(dy)
54
+ y2.backward(dy.float())
55
+ assert (y1-y2).abs().max() < 1e-3
56
+ assert_close(" dlogits", gold_logits.grad, logits.grad, 3e-3)
code/flash-linear-attention/tests/modules/test_kl_div.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from fla.modules import FusedKLDivLoss
7
+ from fla.utils import assert_close, device, device_platform
8
+
9
+
10
+ @pytest.mark.parametrize("B", [2])
11
+ @pytest.mark.parametrize("T", [16, 32])
12
+ @pytest.mark.parametrize("D", [1024, 2048])
13
+ @pytest.mark.parametrize("V", [32000, 100000])
14
+ @pytest.mark.parametrize("reduction", ["batchmean"])
15
+ @pytest.mark.parametrize("dtype", [torch.float32, torch.float16])
16
+ @pytest.mark.skipif(
17
+ device_platform == 'intel',
18
+ reason="Intel Triton Failure",
19
+ )
20
+ def test_fused(B: int, T: int, D: int, V: int, reduction: str, dtype: torch.dtype):
21
+ torch.manual_seed(42)
22
+ x = torch.randn(B * T, D).to(device).to(dtype=dtype).requires_grad_()
23
+ x_weight = torch.randn(V, D).to(device).to(dtype=dtype).requires_grad_()
24
+ target_x = torch.randn(B * T, D).to(device).to(dtype=dtype)
25
+ target_weight = torch.randn(V, D).to(device).to(dtype=dtype)
26
+
27
+ ref = F.kl_div(
28
+ F.linear(x, x_weight).log_softmax(-1),
29
+ F.linear(target_x, target_weight).softmax(-1),
30
+ reduction=reduction,
31
+ ).to(dtype)
32
+ do = torch.randn_like(ref).to(device)
33
+ ref.backward(do)
34
+ ref_dx, x.grad = x.grad.clone(), None
35
+ ref_dw, x_weight.grad = x_weight.grad.clone(), None
36
+
37
+ tri = FusedKLDivLoss(reduction)(x, target_x, x_weight, target_weight).to(dtype=dtype)
38
+ tri.backward(do)
39
+ tri_dx, x.grad = x.grad.clone(), None
40
+ tri_dw, x_weight.grad = x_weight.grad.clone(), None
41
+
42
+ assert_close(" o", ref, tri, 1e-2)
43
+ assert_close(" dx", ref_dx, tri_dx, 1e-2)
44
+ assert_close(" dw", ref_dw, tri_dw, 1e-2)
code/flash-linear-attention/tests/modules/test_l2norm.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from fla.modules.l2norm import l2_norm
7
+ from fla.utils import assert_close, device
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ ('B', 'T', 'H', 'D', 'dtype'),
12
+ [
13
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
14
+ for test in [
15
+ (1, 63, 1, 60, torch.float),
16
+ (2, 500, 4, 64, torch.float),
17
+ (2, 1000, 2, 100, torch.float),
18
+ (3, 1024, 4, 128, torch.float),
19
+ (4, 1024, 5, 1024, torch.float16),
20
+ (4, 1024, 5, 1024, torch.bfloat16),
21
+ (5, 1024, 6, 2048, torch.float16),
22
+ (5, 1024, 6, 2048, torch.bfloat16),
23
+ ]
24
+ ],
25
+ )
26
+ def test_l2norm(B: int, T: int, H: int, D: int, dtype: torch.dtype):
27
+ torch.manual_seed(42)
28
+ x = torch.randn(B, T, H, D, dtype=dtype).to(device).requires_grad_(True)
29
+ x = x * 0.5 + 0.3
30
+
31
+ ref = F.normalize(x, dim=-1, p=2)
32
+ tri = l2_norm(x)
33
+ ref_dx = torch.autograd.grad(ref.sum(), x)[0]
34
+ tri_dx = torch.autograd.grad(tri.sum(), x)[0]
35
+
36
+ assert_close('y', ref, tri, 0.005)
37
+ assert_close('dx', ref_dx, tri_dx, 0.005)
code/flash-linear-attention/tests/modules/test_l2warp.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+ from fla.modules import FusedLinearCrossEntropyLoss
8
+ from fla.modules.l2warp import l2_warp as standalone_l2_warp
9
+ from fla.utils import assert_close, device, is_intel_alchemist
10
+
11
+
12
+ @pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
13
+ @pytest.mark.parametrize("B", [4, 8])
14
+ @pytest.mark.parametrize("T", [1024])
15
+ @pytest.mark.parametrize("H", [256])
16
+ @pytest.mark.parametrize("V", [2000])
17
+ @pytest.mark.parametrize("l2_penalty_factor", [1e-4, 1])
18
+ @pytest.mark.skipif(
19
+ is_intel_alchemist is True,
20
+ reason="Intel Triton Failure",
21
+ )
22
+ def test_fused_linear_cross_entropy_l2_warp(
23
+ B: int,
24
+ T: int,
25
+ H: int,
26
+ V: int,
27
+ l2_penalty_factor: float,
28
+ dtype: torch.dtype,
29
+ ):
30
+ torch.manual_seed(42)
31
+
32
+ lm_head = nn.Linear(H, V, bias=True, device=device, dtype=dtype)
33
+ x = torch.randn(B, T, H, device=device, dtype=dtype, requires_grad=True)
34
+ labels = torch.randint(0, V, (B, T), device=device)
35
+
36
+ ignore_index = -100
37
+ shift_labels = torch.cat((labels[..., 1:], torch.full_like(labels[:, :1], ignore_index)), 1)
38
+
39
+ ref_criterion = nn.CrossEntropyLoss()
40
+
41
+ ref_logits = F.linear(x.view(-1, H), lm_head.weight, lm_head.bias)
42
+ ref_loss_ce = ref_criterion(ref_logits.view(B * T, V), shift_labels.view(-1))
43
+ ref_loss = standalone_l2_warp(ref_loss_ce, ref_logits.view(B, T, V), l2_penalty_factor)
44
+
45
+ ref_loss.backward()
46
+ ref_x_grad = x.grad.clone()
47
+ ref_w_grad = lm_head.weight.grad.clone()
48
+ ref_b_grad = lm_head.bias.grad.clone()
49
+
50
+ x.grad = None
51
+ lm_head.zero_grad()
52
+
53
+ fused_criterion = FusedLinearCrossEntropyLoss(
54
+ l2_penalty_factor=l2_penalty_factor,
55
+ use_l2warp=True, # Make sure to enable it
56
+ )
57
+
58
+ fused_loss = fused_criterion(x, shift_labels, lm_head.weight, lm_head.bias)
59
+
60
+ fused_loss.backward()
61
+ fused_x_grad = x.grad.clone()
62
+ fused_w_grad = lm_head.weight.grad.clone()
63
+ fused_b_grad = lm_head.bias.grad.clone()
64
+
65
+ ratio = 4e-3 if dtype == torch.bfloat16 else 1e-3
66
+
67
+ assert_close("Loss", ref_loss, fused_loss, ratio)
68
+ assert_close("dx", ref_x_grad, fused_x_grad, ratio)
69
+ assert_close("dw", ref_w_grad, fused_w_grad, ratio)
70
+ assert_close("db", ref_b_grad, fused_b_grad, ratio)
code/flash-linear-attention/tests/modules/test_layernorm.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn as nn
5
+ from einops import rearrange
6
+ from transformers.models.llama.modeling_llama import LlamaRMSNorm
7
+
8
+ from fla.modules import GroupNorm, GroupNormLinear, LayerNorm, LayerNormLinear, RMSNorm, RMSNormLinear
9
+ from fla.modules.layernorm import GroupNormRef
10
+ from fla.utils import assert_close, device
11
+
12
+
13
+ @pytest.mark.parametrize("B", [2])
14
+ @pytest.mark.parametrize("H", [2])
15
+ @pytest.mark.parametrize("T", [512])
16
+ @pytest.mark.parametrize("D", [50, 64, 128])
17
+ @pytest.mark.parametrize("elementwise_affine", [False, True])
18
+ @pytest.mark.parametrize("bias", [False, True])
19
+ def test_layernorm(B: int, H: int, T: int, D: int, elementwise_affine: bool, bias: bool):
20
+ x = torch.randn(B, H, T, D).to(device).requires_grad_(True)
21
+ ref = nn.LayerNorm(D, elementwise_affine=elementwise_affine, bias=bias).to(device)
22
+ tri = LayerNorm(D, elementwise_affine=elementwise_affine, bias=bias).to(device)
23
+ if ref.weight is not None:
24
+ nn.init.normal_(ref.weight)
25
+ tri.weight.data.copy_(ref.weight.data)
26
+ if ref.bias is not None:
27
+ nn.init.normal_(ref.bias)
28
+ tri.bias.data.copy_(ref.bias.data)
29
+
30
+ ref_y = ref(x)
31
+ tri_y = tri(x)
32
+ ref_dx = torch.autograd.grad(ref(x).sum(), x)[0]
33
+ tri_dx = torch.autograd.grad(tri(x).sum(), x)[0]
34
+
35
+ if ref.weight is not None:
36
+ ref_dw = torch.autograd.grad(ref(x).sum(), ref.weight)[0]
37
+ tri_dw = torch.autograd.grad(tri(x).sum(), tri.weight)[0]
38
+ if ref.bias is not None:
39
+ ref_db = torch.autograd.grad(ref(x).sum(), ref.bias)[0]
40
+ tri_db = torch.autograd.grad(tri(x).sum(), tri.bias)[0]
41
+
42
+ assert_close(' y', ref_y, tri_y, 1e-3)
43
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
44
+ if ref.weight is not None:
45
+ assert_close('dw', ref_dw, tri_dw, 1e-3)
46
+ if ref.bias is not None:
47
+ assert_close('db', ref_db, tri_db, 1e-3)
48
+
49
+
50
+ @pytest.mark.parametrize("B", [2])
51
+ @pytest.mark.parametrize("T", [512])
52
+ @pytest.mark.parametrize("D", [64, 128, 512, 1024, 2048])
53
+ @pytest.mark.parametrize("G", [1, 4])
54
+ @pytest.mark.parametrize("is_rms_norm", [True, False])
55
+ def test_groupnorm(B: int, T: int, D: int, G: int, is_rms_norm: bool):
56
+ torch.manual_seed(42)
57
+ x = torch.randn(B, T, D).to(device).requires_grad_(True)
58
+ if is_rms_norm:
59
+ ref = GroupNormRef(num_groups=G, hidden_size=D, bias=True, is_rms_norm=True).to(device)
60
+ else:
61
+ ref = nn.GroupNorm(G, D).to(device)
62
+ tri = GroupNorm(G, D, bias=True, is_rms_norm=is_rms_norm).to(device)
63
+ nn.init.normal_(ref.weight)
64
+ nn.init.normal_(ref.bias)
65
+ tri.weight.data.copy_(ref.weight.data)
66
+ tri.bias.data.copy_(ref.bias.data)
67
+ ref = ref.to(dtype=torch.float32)
68
+
69
+ ref_x = rearrange(x, 'b t d -> (b t) d').to(dtype=torch.float32)
70
+ ref_y = rearrange(ref(ref_x), '(b t) d -> b t d', b=B)
71
+ tri_y = tri(x)
72
+ ref_dx = torch.autograd.grad(ref(ref_x).sum(), x)[0]
73
+ tri_dx = torch.autograd.grad(tri(x).sum(), x)[0]
74
+ ref_dw = torch.autograd.grad(ref(ref_x).sum(), ref.weight)[0]
75
+ tri_dw = torch.autograd.grad(tri(x).sum(), tri.weight)[0]
76
+ ref_db = torch.autograd.grad(ref(ref_x).sum(), ref.bias)[0]
77
+ tri_db = torch.autograd.grad(tri(x).sum(), tri.bias)[0]
78
+
79
+ assert_close(' y', ref_y, tri_y, 1e-3)
80
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
81
+ assert_close('dw', ref_dw, tri_dw, 1e-3)
82
+ assert_close('db', ref_db, tri_db, 1e-3)
83
+
84
+
85
+ @pytest.mark.parametrize("B", [2])
86
+ @pytest.mark.parametrize("H", [2])
87
+ @pytest.mark.parametrize("T", [512])
88
+ @pytest.mark.parametrize("D", [50, 64, 128])
89
+ def test_rmsnorm(B: int, H: int, T: int, D: int):
90
+ x = torch.randn(B, H, T, D).to(device).requires_grad_(True)
91
+ ref = LlamaRMSNorm(D, eps=0).to(device)
92
+ tri = RMSNorm(D, eps=0).to(device)
93
+ nn.init.normal_(ref.weight)
94
+ tri.weight.data.copy_(ref.weight.data)
95
+
96
+ ref_y = ref(x)
97
+ tri_y = tri(x)
98
+ ref_dx = torch.autograd.grad(ref(x).sum(), x)[0]
99
+ tri_dx = torch.autograd.grad(tri(x).sum(), x)[0]
100
+
101
+ ref_dw = torch.autograd.grad(ref(x).sum(), ref.weight)[0]
102
+ tri_dw = torch.autograd.grad(tri(x).sum(), tri.weight)[0]
103
+
104
+ assert_close(' y', ref_y, tri_y, 1e-3)
105
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
106
+ assert_close('dw', ref_dw, tri_dw, 1e-3)
107
+
108
+
109
+ @pytest.mark.parametrize("N", [1, 16, 128])
110
+ @pytest.mark.parametrize("D", [50, 64, 128])
111
+ def test_layernorm_linear(N: int, D: int):
112
+ torch.manual_seed(1)
113
+ x = torch.randn(N, D).to(device).requires_grad_(True)
114
+ ref = nn.Sequential(nn.LayerNorm(D, elementwise_affine=True, bias=True), nn.Linear(D, D)).to(device)
115
+ tri = LayerNormLinear(D, elementwise_affine=True, bias=True).to(device)
116
+ nn.init.normal_(ref[0].weight)
117
+ nn.init.normal_(ref[0].bias)
118
+ nn.init.normal_(ref[1].weight, mean=0.0, std=0.01)
119
+ nn.init.normal_(ref[1].bias, mean=0.0, std=0.01)
120
+ tri.weight.data.copy_(ref[0].weight.data)
121
+ tri.bias.data.copy_(ref[0].bias.data)
122
+ weight, bias = ref[1].weight.clone(), ref[1].bias.clone()
123
+
124
+ ref_y = ref(x)
125
+ tri_y = tri(x, weight, bias)
126
+ ref_dx = torch.autograd.grad(ref(x).sum(), x)[0]
127
+ tri_dx = torch.autograd.grad(tri(x, weight, bias).sum(), x)[0]
128
+ ref_dw = torch.autograd.grad(ref(x).sum(), ref[0].weight)[0]
129
+ tri_dw = torch.autograd.grad(tri(x, weight, bias).sum(), tri.weight)[0]
130
+ ref_db = torch.autograd.grad(ref(x).sum(), ref[0].bias)[0]
131
+ tri_db = torch.autograd.grad(tri(x, weight, bias).sum(), tri.bias)[0]
132
+ ref_dlw = torch.autograd.grad(ref(x).sum(), ref[1].weight)[0]
133
+ tri_dlw = torch.autograd.grad(tri(x, weight, bias).sum(), weight)[0]
134
+ ref_dlb = torch.autograd.grad(ref(x).sum(), ref[1].bias)[0]
135
+ tri_dlb = torch.autograd.grad(tri(x, weight, bias).sum(), bias)[0]
136
+
137
+ assert_close(' y', ref_y, tri_y, 1e-3)
138
+ assert_close(' dx', ref_dx, tri_dx, 1e-3)
139
+ assert_close(' dw', ref_dw, tri_dw, 1e-3)
140
+ assert_close(' db', ref_db, tri_db, 1e-3)
141
+ assert_close('dlw', ref_dlw, tri_dlw, 1e-3)
142
+ assert_close('dlb', ref_dlb, tri_dlb, 1e-3)
143
+
144
+
145
+ @pytest.mark.parametrize("N", [1, 16, 128])
146
+ @pytest.mark.parametrize("D", [64, 128, 512])
147
+ @pytest.mark.parametrize("G", [1, 4])
148
+ @pytest.mark.parametrize("is_rms_norm", [True, False])
149
+ def test_groupnorm_linear(N: int, D: int, G: int, is_rms_norm: bool):
150
+ torch.manual_seed(1)
151
+ x = torch.randn(N, D).to(device).requires_grad_(True)
152
+ if is_rms_norm:
153
+ ref = nn.Sequential(
154
+ GroupNormRef(num_groups=G, hidden_size=D, bias=True, is_rms_norm=True),
155
+ nn.Linear(D, D),
156
+ ).to(device)
157
+ else:
158
+ ref = nn.Sequential(nn.GroupNorm(G, D), nn.Linear(D, D)).to(device)
159
+ tri = GroupNormLinear(G, D, bias=True, is_rms_norm=is_rms_norm).to(device)
160
+ nn.init.normal_(ref[0].weight)
161
+ nn.init.normal_(ref[0].bias)
162
+ nn.init.normal_(ref[1].weight, mean=0.0, std=0.01)
163
+ nn.init.normal_(ref[1].bias, mean=0.0, std=0.01)
164
+ tri.weight.data.copy_(ref[0].weight.data)
165
+ tri.bias.data.copy_(ref[0].bias.data)
166
+ weight, bias = ref[1].weight.clone(), ref[1].bias.clone()
167
+
168
+ ref_y = ref(x)
169
+ tri_y = tri(x, weight, bias)
170
+ ref_dx = torch.autograd.grad(ref(x).sum(), x)[0]
171
+ tri_dx = torch.autograd.grad(tri(x, weight, bias).sum(), x)[0]
172
+ ref_dw = torch.autograd.grad(ref(x).sum(), ref[0].weight)[0]
173
+ tri_dw = torch.autograd.grad(tri(x, weight, bias).sum(), tri.weight)[0]
174
+ ref_db = torch.autograd.grad(ref(x).sum(), ref[0].bias)[0]
175
+ tri_db = torch.autograd.grad(tri(x, weight, bias).sum(), tri.bias)[0]
176
+ ref_dlw = torch.autograd.grad(ref(x).sum(), ref[1].weight)[0]
177
+ tri_dlw = torch.autograd.grad(tri(x, weight, bias).sum(), weight)[0]
178
+ ref_dlb = torch.autograd.grad(ref(x).sum(), ref[1].bias)[0]
179
+ tri_dlb = torch.autograd.grad(tri(x, weight, bias).sum(), bias)[0]
180
+
181
+ assert_close(' y', ref_y, tri_y, 1e-3)
182
+ assert_close(' dx', ref_dx, tri_dx, 1e-3)
183
+ assert_close(' dw', ref_dw, tri_dw, 1e-3)
184
+ assert_close(' db', ref_db, tri_db, 1e-3)
185
+ assert_close('dlw', ref_dlw, tri_dlw, 1e-3)
186
+ assert_close('dlb', ref_dlb, tri_dlb, 1e-3)
187
+
188
+
189
+ @pytest.mark.parametrize("N", [1, 16, 128])
190
+ @pytest.mark.parametrize("D", [50, 64, 128])
191
+ def test_rmsnorm_linear(N: int, D: int):
192
+ torch.manual_seed(1)
193
+ x = torch.randn(N, D).to(device).requires_grad_(True)
194
+ ref = nn.Sequential(LlamaRMSNorm(D, eps=0), nn.Linear(D, D)).to(device)
195
+ tri = RMSNormLinear(D, eps=0).to(device)
196
+ nn.init.normal_(ref[0].weight)
197
+ nn.init.normal_(ref[1].weight, mean=0.0, std=0.01)
198
+ nn.init.normal_(ref[1].bias, mean=0.0, std=0.01)
199
+ tri.weight.data.copy_(ref[0].weight.data)
200
+ weight, bias = ref[1].weight.clone(), ref[1].bias.clone()
201
+
202
+ ref_y = ref(x)
203
+ tri_y = tri(x, weight, bias)
204
+ ref_dx = torch.autograd.grad(ref(x).sum(), x)[0]
205
+ tri_dx = torch.autograd.grad(tri(x, weight, bias).sum(), x)[0]
206
+ ref_dw = torch.autograd.grad(ref(x).sum(), ref[0].weight)[0]
207
+ tri_dw = torch.autograd.grad(tri(x, weight, bias).sum(), tri.weight)[0]
208
+ ref_dlw = torch.autograd.grad(ref(x).sum(), ref[1].weight)[0]
209
+ tri_dlw = torch.autograd.grad(tri(x, weight, bias).sum(), weight)[0]
210
+ ref_dlb = torch.autograd.grad(ref(x).sum(), ref[1].bias)[0]
211
+ tri_dlb = torch.autograd.grad(tri(x, weight, bias).sum(), bias)[0]
212
+
213
+ assert_close(' y', ref_y, tri_y, 1e-3)
214
+ assert_close(' dx', ref_dx, tri_dx, 1e-3)
215
+ assert_close(' dw', ref_dw, tri_dw, 1e-3)
216
+ assert_close('dlw', ref_dlw, tri_dlw, 1e-3)
217
+ assert_close('dlb', ref_dlb, tri_dlb, 1e-3)
code/flash-linear-attention/tests/modules/test_layernorm_gated.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+ from fla.modules import FusedLayerNormGated, FusedRMSNormGated
8
+ from fla.utils import assert_close, device
9
+
10
+
11
+ @pytest.mark.parametrize(
12
+ ('B', 'H', 'T', 'D', 'elementwise_affine', 'activation', 'bias'),
13
+ [
14
+ pytest.param(*test, id=f"B{test[0]}_H{test[1]}_T{test[2]}_D{test[3]}_affine{test[4]}_{test[5]}_bias{test[6]}")
15
+ for test in [
16
+ (2, 2, 1, 64, False, "silu", False),
17
+ (2, 2, 512, 128, True, "silu", True),
18
+ (2, 2, 2048, 1200, True, "sigmoid", False),
19
+ (2, 2, 50, 50, False, "sigmoid", False),
20
+ ]
21
+ ],
22
+ )
23
+ def test_layernorm_gated(B: int, H: int, T: int, D: int, elementwise_affine: bool, activation: str, bias: bool):
24
+ torch.manual_seed(42)
25
+ x = torch.randn(B, H, T, D).to(device).requires_grad_(True)
26
+ g = torch.randn(B, H, T, D).to(device).requires_grad_(True)
27
+
28
+ ref = nn.LayerNorm(D, elementwise_affine=elementwise_affine, bias=bias).to(device)
29
+ tri = FusedLayerNormGated(D, elementwise_affine=elementwise_affine, bias=bias, activation=activation).to(device)
30
+ if ref.weight is not None:
31
+ nn.init.normal_(ref.weight)
32
+ tri.weight.data.copy_(ref.weight.data)
33
+ if ref.bias is not None:
34
+ nn.init.normal_(ref.bias)
35
+ tri.bias.data.copy_(ref.bias.data)
36
+
37
+ act_fn = F.silu if activation == "silu" else F.sigmoid
38
+ ref_y = ref(x) * act_fn(g)
39
+ tri_y = tri(x, g)
40
+ ref_dx, ref_dg = torch.autograd.grad((ref(x) * act_fn(g)).sum(), (x, g))
41
+ tri_dx, tri_dg = torch.autograd.grad(tri_y.sum(), (x, g))
42
+
43
+ if ref.weight is not None:
44
+ ref_dw = torch.autograd.grad((ref(x) * act_fn(g)).sum(), ref.weight)[0]
45
+ tri_dw = torch.autograd.grad(tri(x, g).sum(), tri.weight)[0]
46
+ if ref.bias is not None:
47
+ ref_db = torch.autograd.grad((ref(x) * act_fn(g)).sum(), ref.bias)[0]
48
+ tri_db = torch.autograd.grad(tri(x, g).sum(), tri.bias)[0]
49
+
50
+ assert_close(' y', ref_y, tri_y, 1e-3)
51
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
52
+ assert_close('dg', ref_dg, tri_dg, 1e-3)
53
+ if ref.weight is not None:
54
+ assert_close('dw', ref_dw, tri_dw, 1e-3)
55
+ if ref.bias is not None:
56
+ assert_close('db', ref_db, tri_db, 1e-3)
57
+
58
+
59
+ @pytest.mark.parametrize(
60
+ ('B', 'H', 'T', 'D', 'activation'),
61
+ [
62
+ pytest.param(*test, id=f"B{test[0]}_H{test[1]}_T{test[2]}_D{test[3]}_{test[4]}")
63
+ for test in [
64
+ (2, 2, 1, 64, "silu"),
65
+ (2, 2, 512, 128, "sigmoid"),
66
+ (2, 2, 2048, 1200, "silu"),
67
+ (2, 2, 50, 50, "sigmoid"),
68
+ ]
69
+ ],
70
+ )
71
+ def test_rmsnorm_gated(B: int, H: int, T: int, D: int, activation: str):
72
+ torch.manual_seed(42)
73
+ x = torch.randn(B, H, T, D).to(device).requires_grad_(True)
74
+ g = torch.randn(B, H, T, D).to(device).requires_grad_(True)
75
+ ref = nn.RMSNorm(D, eps=0).to(device)
76
+ tri = FusedRMSNormGated(D, eps=0, activation=activation).to(device)
77
+ nn.init.normal_(ref.weight)
78
+ tri.weight.data.copy_(ref.weight.data)
79
+
80
+ act_fn = F.silu if activation == "silu" else F.sigmoid
81
+ ref_y = ref(x) * act_fn(g)
82
+ tri_y = tri(x, g)
83
+ ref_dx, ref_dg = torch.autograd.grad((ref(x) * act_fn(g)).sum(), (x, g))
84
+ tri_dx, tri_dg = torch.autograd.grad(tri_y.sum(), (x, g))
85
+
86
+ ref_dw = torch.autograd.grad((ref(x) * act_fn(g)).sum(), ref.weight)[0]
87
+ tri_dw = torch.autograd.grad(tri(x, g).sum(), tri.weight)[0]
88
+
89
+ assert_close(' y', ref_y, tri_y, 1e-3)
90
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
91
+ assert_close('dg', ref_dg, tri_dg, 1e-3)
92
+ assert_close('dw', ref_dw, tri_dw, 1e-3)
code/flash-linear-attention/tests/modules/test_rotary.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+
5
+ from fla.modules.rotary import RotaryEmbedding, rotary_embedding_ref
6
+ from fla.utils import assert_close, device
7
+
8
+
9
+ @pytest.mark.parametrize("B", [2])
10
+ @pytest.mark.parametrize("T", [2048, 4096])
11
+ @pytest.mark.parametrize("H", [4])
12
+ @pytest.mark.parametrize("G", [1, 4])
13
+ @pytest.mark.parametrize("D", [128, 256])
14
+ @pytest.mark.parametrize("dtype", [torch.bfloat16])
15
+ def test_rotary(B: int, T: int, H: int, G: int, D: int, dtype: torch.dtype):
16
+ torch.manual_seed(42)
17
+ q = torch.randn(B, T, H, D).to(device).to(dtype=dtype).requires_grad_()
18
+ k = torch.randn(B, T, H//G, D).to(device).to(dtype=dtype).requires_grad_()
19
+ rotary = RotaryEmbedding(D).to(device)
20
+
21
+ tri_q, tri_k = rotary(q, k)
22
+ tri_dq = torch.autograd.grad(tri_q.sum(), q, retain_graph=True)[0]
23
+ tri_dk = torch.autograd.grad(tri_k.sum(), k, retain_graph=True)[0]
24
+
25
+ ref_q = rotary_embedding_ref(q.float(), rotary._cos_cached, rotary._sin_cached).to(dtype=dtype)
26
+ ref_k = rotary_embedding_ref(k.float(), rotary._cos_cached, rotary._sin_cached).to(dtype=dtype)
27
+ ref_dq = torch.autograd.grad(ref_q.sum(), q, retain_graph=True)[0]
28
+ ref_dk = torch.autograd.grad(ref_k.sum(), k, retain_graph=True)[0]
29
+
30
+ assert_close(" q", ref_q, tri_q, ratio=1e-5)
31
+ assert_close(" k", ref_k, tri_k, ratio=1e-5)
32
+ assert_close("dq", ref_dq, tri_dq, ratio=1e-5)
33
+ assert_close("dk", ref_dk, tri_dk, ratio=1e-5)
34
+
35
+
36
+ @pytest.mark.parametrize("B", [2])
37
+ @pytest.mark.parametrize("T", [2048, 4096])
38
+ @pytest.mark.parametrize("H", [4])
39
+ @pytest.mark.parametrize("G", [1, 4])
40
+ @pytest.mark.parametrize("D", [128, 256])
41
+ @pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
42
+ def test_rotary_with_offsets(B: int, T: int, H: int, G: int, D: int, dtype: torch.dtype):
43
+ torch.manual_seed(42)
44
+ q = torch.randn(B, T, H, D).to(device).to(dtype=dtype).requires_grad_()
45
+ k = torch.randn(B, T, H//G, D).to(device).to(dtype=dtype).requires_grad_()
46
+ seqlen_offset = torch.randint(0, T//2, (B,)).to(device)
47
+ max_seqlen = T + seqlen_offset.max().item()
48
+ rotary = RotaryEmbedding(D).to(device)
49
+
50
+ tri_q, tri_k = rotary(q, k, seqlen_offset=seqlen_offset, max_seqlen=max_seqlen)
51
+ tri_dq = torch.autograd.grad(tri_q.sum(), q, retain_graph=True)[0]
52
+ tri_dk = torch.autograd.grad(tri_k.sum(), k, retain_graph=True)[0]
53
+
54
+ ref_q = torch.cat([
55
+ rotary_embedding_ref(
56
+ q[i:i+1].float(),
57
+ rotary._cos_cached[offset:offset+T],
58
+ rotary._sin_cached[offset:offset+T],
59
+ )
60
+ for i, offset in enumerate(seqlen_offset.tolist())
61
+ ]).to(dtype=dtype)
62
+ ref_k = torch.cat([
63
+ rotary_embedding_ref(
64
+ k[i:i+1].float(),
65
+ rotary._cos_cached[offset:offset+T],
66
+ rotary._sin_cached[offset:offset+T],
67
+ )
68
+ for i, offset in enumerate(seqlen_offset.tolist())
69
+ ]).to(dtype=dtype)
70
+ ref_dq = torch.autograd.grad(ref_q.sum(), q, retain_graph=True)[0]
71
+ ref_dk = torch.autograd.grad(ref_k.sum(), k, retain_graph=True)[0]
72
+
73
+ assert_close(" q", ref_q, tri_q, ratio=1e-5)
74
+ assert_close(" k", ref_k, tri_k, ratio=1e-5)
75
+ assert_close("dq", ref_dq, tri_dq, ratio=1e-5)
76
+ assert_close("dk", ref_dk, tri_dk, ratio=1e-5)
77
+
78
+
79
+ @pytest.mark.parametrize("N", [4])
80
+ @pytest.mark.parametrize("T", [2048, 4096])
81
+ @pytest.mark.parametrize("H", [4])
82
+ @pytest.mark.parametrize("G", [1, 4])
83
+ @pytest.mark.parametrize("D", [128, 256])
84
+ @pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
85
+ def test_rotary_varlen(N: int, T: int, H: int, G: int, D: int, dtype: torch.dtype):
86
+ torch.manual_seed(42)
87
+ q = torch.randn(1, T, H, D).to(device).to(dtype=dtype).requires_grad_()
88
+ k = torch.randn(1, T, H//G, D).to(device).to(dtype=dtype).requires_grad_()
89
+ cu_seqlens = torch.cat([
90
+ torch.tensor([0], dtype=torch.long),
91
+ torch.arange(1, T)[torch.randperm(T - 1)[:N-1]],
92
+ torch.tensor([T], dtype=torch.long),
93
+ ], 0).to(device).sort()[0]
94
+ rotary = RotaryEmbedding(D).to(device)
95
+
96
+ tri_q, tri_k = rotary(q, k, cu_seqlens=cu_seqlens)
97
+ tri_dq = torch.autograd.grad(tri_q.sum(), q, retain_graph=True)[0]
98
+ tri_dk = torch.autograd.grad(tri_k.sum(), k, retain_graph=True)[0]
99
+
100
+ ref_q = torch.cat([
101
+ rotary_embedding_ref(
102
+ q[0, start:end].float(),
103
+ rotary._cos_cached[:end-start],
104
+ rotary._sin_cached[:end-start],
105
+ )
106
+ for start, end in zip(cu_seqlens.tolist(), cu_seqlens[1:].tolist(), strict=False)
107
+ ]).to(dtype=dtype).unsqueeze(0)
108
+ ref_k = torch.cat([
109
+ rotary_embedding_ref(
110
+ k[0, start:end].float(),
111
+ rotary._cos_cached[:end-start],
112
+ rotary._sin_cached[:end-start],
113
+ )
114
+ for start, end in zip(cu_seqlens.tolist(), cu_seqlens[1:].tolist(), strict=False)
115
+ ]).to(dtype=dtype).unsqueeze(0)
116
+ ref_dq = torch.autograd.grad(ref_q.sum(), q, retain_graph=True)[0]
117
+ ref_dk = torch.autograd.grad(ref_k.sum(), k, retain_graph=True)[0]
118
+
119
+ assert_close(" q", ref_q, tri_q, ratio=1e-5)
120
+ assert_close(" k", ref_k, tri_k, ratio=1e-5)
121
+ assert_close("dq", ref_dq, tri_dq, ratio=1e-5)
122
+ assert_close("dk", ref_dk, tri_dk, ratio=1e-5)
code/flash-linear-attention/tests/modules/test_token_shift.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+
5
+ from fla.modules.token_shift import token_shift, token_shift_ref
6
+ from fla.utils import assert_close, device
7
+
8
+ test_b_list = [4]
9
+ test_t_list = [512, 4100, 8192]
10
+ test_h_list = [2560, 4096]
11
+ test_cu_seqlens_list = [
12
+ None,
13
+ [0, 4, 7, 40, 128],
14
+ [0, 10, 20, 64],
15
+ [0, 32],
16
+ [0, 1, 3, 4],
17
+ ]
18
+ test_dtype_list = [torch.float]
19
+
20
+
21
+ @pytest.mark.parametrize('B', test_b_list)
22
+ @pytest.mark.parametrize('T', test_t_list)
23
+ @pytest.mark.parametrize('H', test_h_list)
24
+ @pytest.mark.parametrize('cu_seqlens_val', test_cu_seqlens_list)
25
+ @pytest.mark.parametrize('dtype', test_dtype_list)
26
+ def test_token_shift(B, T, H, cu_seqlens_val, dtype):
27
+ if cu_seqlens_val is not None:
28
+ B = 1
29
+ T = cu_seqlens_val[-1]
30
+ cu_seqlens_tensor = torch.tensor(cu_seqlens_val, dtype=torch.int32, device=device)
31
+ else:
32
+ cu_seqlens_tensor = None
33
+
34
+ torch.manual_seed(42)
35
+
36
+ x = torch.randn(B, T, H, device=device).to(dtype).requires_grad_(True)
37
+ dy = torch.randn_like(x)
38
+
39
+ ref = token_shift_ref(x, cu_seqlens_tensor)
40
+ tri = token_shift(x, cu_seqlens_tensor)
41
+
42
+ ref.backward(dy)
43
+ ref_dx, x.grad = x.grad, None
44
+
45
+ tri.backward(dy)
46
+ tri_dx, x.grad = x.grad, None
47
+
48
+ assert_close(' x', ref, tri, 1e-3)
49
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
50
+
51
+
52
+ def _split_for_passing(
53
+ x: torch.Tensor,
54
+ cu_seqlens,
55
+ split_at: int = 1,
56
+ ):
57
+ assert x.size(0) == 1
58
+ assert 0 < split_at < len(cu_seqlens) - 1
59
+
60
+ cu0 = [t - cu_seqlens[0] for t in cu_seqlens[: split_at + 1]]
61
+ cu1 = [t - cu_seqlens[split_at] for t in cu_seqlens[split_at:]]
62
+ T0, T1 = cu0[-1], cu1[-1]
63
+
64
+ x0 = x[:, :T0].contiguous()
65
+ x1 = x[:, T0: T0 + T1].contiguous()
66
+ cache1 = x[:, T0 - 1: T0].contiguous()
67
+ return x0, x1, \
68
+ torch.tensor(cu0, dtype=torch.int32, device=x.device), \
69
+ torch.tensor(cu1, dtype=torch.int32, device=x.device), \
70
+ cache1
71
+
72
+
73
+ def _check_passing_vs_whole(
74
+ B: int,
75
+ T: int,
76
+ H: int,
77
+ cu_seqlens: list[int] | None,
78
+ dtype: torch.dtype,
79
+ split_at: int = 1,
80
+ ):
81
+ torch.manual_seed(42)
82
+
83
+ if cu_seqlens is None:
84
+ x = torch.randn(B, T, H, device=device, dtype=dtype, requires_grad=True)
85
+ cu_seqlens_tensor = None
86
+ else:
87
+ B = 1
88
+ T = cu_seqlens[-1]
89
+ x = torch.randn(1, T, H, device=device, dtype=dtype, requires_grad=True)
90
+ cu_seqlens_tensor = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
91
+
92
+ dy = torch.randn_like(x)
93
+ ref_out = token_shift(x, cu_seqlens_tensor)
94
+ ref_out.backward(dy)
95
+ ref_dx = x.grad.clone()
96
+ x.grad.zero_()
97
+
98
+ if cu_seqlens is None:
99
+ T0 = T // 2
100
+ x0 = x[:, :T0].contiguous()
101
+ x1 = x[:, T0:].contiguous()
102
+ cu0, cu1 = None, None
103
+ else:
104
+ if split_at >= len(cu_seqlens) - 1:
105
+ pytest.skip("invalid split_at")
106
+ x0, x1, cu0, cu1, cache1 = _split_for_passing(x, cu_seqlens, split_at)
107
+
108
+ out0, cache_out0 = token_shift(x0, cu0, output_cache=True)
109
+ out1, cache_out1 = token_shift(x1, cu1, cache=cache_out0, output_cache=True)
110
+
111
+ cat_out = torch.cat([out0, out1], dim=1)
112
+ cat_out.backward(dy)
113
+
114
+ cat_dx = x.grad.clone()
115
+
116
+ assert_close("do", ref_out, cat_out, 1e-3)
117
+ assert_close("dx", ref_dx, cat_dx, 1e-3)
118
+
119
+
120
+ @pytest.mark.parametrize(
121
+ ("B", "T", "H", "cu_seqlens", "split_at"),
122
+ [
123
+ pytest.param(*test, id="B{}-T{}-H{}-cu{}-split{}".format(*test))
124
+ for test in [
125
+ (2, 512, 1024, None, 1),
126
+ (1, 8192, 1024, None, 2),
127
+ ]
128
+ ],
129
+ )
130
+ def test_all_with_and_without_varlen(B, T, H, cu_seqlens, split_at):
131
+ dtype = torch.float
132
+ assert cu_seqlens is None, "This test is for cu_seqlens=None case"
133
+ _check_passing_vs_whole(B, T, H, cu_seqlens, dtype, split_at)
code/flash-linear-attention/tests/ops/test_attn.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+
7
+ from fla.ops.attn.parallel import parallel_attn
8
+ from fla.ops.utils import prepare_lens
9
+ from fla.utils import assert_close, check_shared_mem, device
10
+
11
+ try:
12
+ from flash_attn import flash_attn_func, flash_attn_varlen_func
13
+ HAS_FLASH = True
14
+ except Exception:
15
+ HAS_FLASH = False
16
+
17
+
18
+ @pytest.mark.parametrize(
19
+ ('B', 'T', 'H', 'HQ', 'D', 'scale'),
20
+ [
21
+ pytest.param(*test, id="B{}-T{}-H{}-HQ{}-D{}-scale{}".format(*test))
22
+ for test in [
23
+ (1, 63, 1, 1, 64, 1.0),
24
+ (3, 111, 2, 2, 100, 1.0),
25
+ (3, 1024, 2, 8, 60, 0.1),
26
+ (3, 1024, 2, 8, 128, 0.1),
27
+ (4, 2048, 2, 8, 64, 0.1),
28
+ ]
29
+ ],
30
+ )
31
+ def test_parallel(
32
+ B: int,
33
+ T: int,
34
+ H: int,
35
+ HQ: int,
36
+ D: int,
37
+ scale: float,
38
+ ):
39
+ if not check_shared_mem('hopper') and D > 128:
40
+ pytest.skip(reason="Skip test, do not have enough shard mem")
41
+ if not HAS_FLASH:
42
+ pytest.skip(reason="Skipping test because flash-attn is not installed")
43
+ torch.manual_seed(42)
44
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
45
+ q = torch.randn((B, T, HQ, D), dtype=torch.float16, device=device).requires_grad_(True)
46
+ k = torch.randn((B, T, H, D), dtype=torch.float16, device=device).requires_grad_(True)
47
+ v = torch.randn((B, T, H, D), dtype=torch.float16, device=device).requires_grad_(True)
48
+ do = torch.randn((B, T, HQ, D), dtype=torch.float16, device=device)
49
+
50
+ ref = flash_attn_func(q=q, k=k, v=v, softmax_scale=scale, causal=True)
51
+ ref.backward(do)
52
+ ref_dq, q.grad = q.grad.clone(), None
53
+ ref_dk, k.grad = k.grad.clone(), None
54
+ ref_dv, v.grad = v.grad.clone(), None
55
+
56
+ tri = parallel_attn(q=q, k=k, v=v, scale=scale)
57
+ tri.backward(do)
58
+ tri_dq, q.grad = q.grad.clone(), None
59
+ tri_dk, k.grad = k.grad.clone(), None
60
+ tri_dv, v.grad = v.grad.clone(), None
61
+
62
+ assert_close(" o", ref, tri, 0.005)
63
+ assert_close("dq", ref_dq, tri_dq, 0.005)
64
+ assert_close("dk", ref_dk, tri_dk, 0.005)
65
+ assert_close("dv", ref_dv, tri_dv, 0.005)
66
+
67
+
68
+ @pytest.mark.parametrize(
69
+ ('H', 'HQ', 'D', 'cu_seqlens'),
70
+ [
71
+ pytest.param(*test, id="H{}-HQ{}-D{}-cu_seqlens{}".format(*test))
72
+ for test in [
73
+ (2, 2, 64, [0, 15]),
74
+ (2, 8, 64, [0, 256, 500, 1000]),
75
+ (2, 2, 100, [0, 15, 100, 300, 1200, 2000]),
76
+ ]
77
+ ],
78
+ )
79
+ def test_parallel_varlen(
80
+ H: int,
81
+ HQ: int,
82
+ D: int,
83
+ cu_seqlens: list[int],
84
+ ):
85
+ if not HAS_FLASH:
86
+ pytest.skip(reason="Skipping test because flash-attn is not installed")
87
+ T = cu_seqlens[-1]
88
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
89
+ dtype = torch.float16
90
+
91
+ q = torch.randn((1, T, HQ, D), dtype=dtype, device=device).requires_grad_()
92
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
93
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
94
+ do = torch.randn((1, T, HQ, D), dtype=dtype, device=device)
95
+
96
+ ref = flash_attn_varlen_func(
97
+ q=q.squeeze(0),
98
+ k=k.squeeze(0),
99
+ v=v.squeeze(0),
100
+ cu_seqlens_q=cu_seqlens,
101
+ cu_seqlens_k=cu_seqlens,
102
+ max_seqlen_q=prepare_lens(cu_seqlens).max(),
103
+ max_seqlen_k=prepare_lens(cu_seqlens).max(),
104
+ causal=True,
105
+ )
106
+ ref.backward(do.squeeze(0))
107
+ ref_dq, q.grad = q.grad.clone(), None
108
+ ref_dk, k.grad = k.grad.clone(), None
109
+ ref_dv, v.grad = v.grad.clone(), None
110
+
111
+ tri = parallel_attn(
112
+ q=q,
113
+ k=k,
114
+ v=v,
115
+ cu_seqlens=cu_seqlens,
116
+ )
117
+ tri.backward(do)
118
+ tri_dq, q.grad = q.grad.clone(), None
119
+ tri_dk, k.grad = k.grad.clone(), None
120
+ tri_dv, v.grad = v.grad.clone(), None
121
+
122
+ assert_close(" o", ref, tri, 0.004)
123
+ assert_close("dq", ref_dq.squeeze(), tri_dq.squeeze(), 0.005)
124
+ assert_close("dk", ref_dk.squeeze(), tri_dk.squeeze(), 0.005)
125
+ assert_close("dv", ref_dv.squeeze(), tri_dv.squeeze(), 0.005)
code/flash-linear-attention/tests/ops/test_based.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+
5
+ from fla.ops.based import fused_chunk_based, parallel_based
6
+ from fla.ops.based.naive import naive_parallel_based
7
+ from fla.utils import device
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ ('B', 'T', 'H', 'D', 'dtype'),
12
+ [
13
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
14
+ for test in [
15
+ (1, 63, 1, 60, torch.float16),
16
+ (3, 111, 2, 64, torch.float16),
17
+ (3, 1024, 4, 100, torch.float16),
18
+ (3, 1024, 8, 128, torch.float16),
19
+ (4, 2048, 8, 256, torch.float16),
20
+ ]
21
+ ],
22
+ )
23
+ def test_based(
24
+ B: int,
25
+ T: int,
26
+ H: int,
27
+ D: int,
28
+ dtype: torch.dtype,
29
+ ):
30
+ torch.manual_seed(42)
31
+ q = torch.randn((B, H, T, 16), dtype=dtype, device=device).requires_grad_()
32
+ k = torch.randn((B, H, T, 16), dtype=dtype, device=device).requires_grad_()
33
+ v = torch.randn((B, H, T, D), dtype=dtype, device=device).requires_grad_()
34
+ do = torch.randn_like(v)
35
+ ref = naive_parallel_based(q, k, v, use_norm=True)
36
+ ref.backward(do)
37
+ ref_dq, q.grad = q.grad.clone(), None
38
+ ref_dk, k.grad = k.grad.clone(), None
39
+ ref_dv, v.grad = v.grad.clone(), None
40
+
41
+ tri = parallel_based(q, k, v, use_norm=True)
42
+ tri.backward(do)
43
+ tri_dq, q.grad = q.grad.clone(), None
44
+ tri_dk, k.grad = k.grad.clone(), None
45
+ tri_dv, v.grad = v.grad.clone(), None
46
+
47
+ if dtype == torch.float32:
48
+ assert ref.allclose(tri, 0, 1e-4)
49
+ assert ref_dq.allclose(tri_dq, 0, 1e-4)
50
+ assert ref_dk.allclose(tri_dk, 0, 1e-4)
51
+ assert ref_dv.allclose(tri_dv, 0, 1e-4)
52
+
53
+ tri = fused_chunk_based(q, k, v, use_norm=True)
54
+ tri.backward(do)
55
+ tri_dq, q.grad = q.grad.clone(), None
56
+ tri_dk, k.grad = k.grad.clone(), None
57
+ tri_dv, v.grad = v.grad.clone(), None
58
+
59
+ if dtype == torch.float32:
60
+ assert ref.allclose(tri, 0, 1e-4)
61
+ assert ref_dq.allclose(tri_dq, 0, 1e-4)
62
+ assert ref_dk.allclose(tri_dk, 0, 1e-4)
63
+ assert ref_dv.allclose(tri_dv, 0, 1e-4)
code/flash-linear-attention/tests/ops/test_comba.py ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from einops import rearrange
8
+
9
+ from fla.ops.comba import chunk_comba, fused_recurrent_comba
10
+ from fla.ops.comba.utils import chunk_comba_cumsum_scalar_fwd
11
+ from fla.utils import assert_close, device, is_intel_alchemist
12
+
13
+
14
+ def cumsum_comba_local_fwd_reference(s, reverse=False, chunk_size=128):
15
+ o_0 = torch.zeros_like(s)
16
+ o_1 = torch.zeros_like(s)
17
+ T = s.size(1)
18
+ fn = torch.cumsum
19
+ for i in range(0, T, chunk_size):
20
+ s_chunk = s[:, i:i+chunk_size]
21
+ o_1[:, i:i+chunk_size] = fn(s_chunk.float(), dim=1).to(o_1)
22
+ o_0[:, i:i+chunk_size] = o_1[:, i:i+chunk_size] - s_chunk
23
+
24
+ return o_0, o_1
25
+
26
+
27
+ @pytest.mark.parametrize(
28
+ ('B', 'T', 'H', 'chunk_size', 'dtype'),
29
+ [
30
+ pytest.param(*test, id='B{}-T{}-H{}-chunk_size{}-{}'.format(*test))
31
+ for test in [
32
+ (32, 200, 4, 64, torch.float),
33
+ (32, 1000, 4, 64, torch.float),
34
+ (32, 2048, 8, 128, torch.float),
35
+ ]
36
+ ],
37
+ )
38
+ def test_cumsum_local_scalar_fwd(
39
+ B: int,
40
+ T: int,
41
+ H: int,
42
+ chunk_size: int,
43
+ dtype: torch.dtype,
44
+ ):
45
+ s = torch.randn((B, T, H), dtype=dtype, device=device).requires_grad_()
46
+ ref_0, ref_1 = cumsum_comba_local_fwd_reference(s, chunk_size=chunk_size)
47
+ tri_0, tri_1 = chunk_comba_cumsum_scalar_fwd(s, chunk_size=chunk_size)
48
+ assert_close("local cumsum scalar", ref_0, tri_0, 0.001 if dtype == torch.float else 0.003)
49
+ assert_close("local cumsum scalar", ref_1, tri_1, 0.001 if dtype == torch.float else 0.003)
50
+
51
+
52
+ def chunk_comba_ref(
53
+ q: torch.Tensor,
54
+ k: torch.Tensor,
55
+ v: torch.Tensor,
56
+ p: torch.Tensor,
57
+ g: torch.Tensor,
58
+ beta: torch.Tensor,
59
+ chunk_size: int = 64,
60
+ scale: float = None,
61
+ initial_state: torch.Tensor = None,
62
+ output_final_state: bool = False,
63
+ ):
64
+ BT = chunk_size
65
+ if scale is None:
66
+ scale = 1 / (q.shape[-1] ** 0.5)
67
+ # Calculate padding needed to make T a multiple of BT
68
+ q, k, v, p, beta, g = map(lambda x: x.transpose(1, 2).contiguous().to(torch.float32), [q, k, v, p, beta, g])
69
+
70
+ T = q.shape[-2]
71
+ pad_len = (BT - (T % BT)) % BT
72
+ if pad_len > 0:
73
+ # Pad all tensors
74
+ q = F.pad(q, (0, 0, 0, pad_len))
75
+ k = F.pad(k, (0, 0, 0, pad_len))
76
+ v = F.pad(v, (0, 0, 0, pad_len))
77
+ p = F.pad(p, (0, 0, 0, pad_len))
78
+ beta = F.pad(beta, (0, pad_len))
79
+ g = F.pad(g, (0, pad_len))
80
+ decay = g
81
+ chunk_size = BT
82
+ b, h, l, d_k = q.shape
83
+ d_v = v.shape[-1]
84
+ q = q * scale
85
+ v = v * beta[..., None]
86
+ p_beta = p * beta[..., None]
87
+ assert l % chunk_size == 0
88
+ # note that diagonal is masked.
89
+ mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0)
90
+ q, k, v, p_beta, decay, g = map(
91
+ lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size),
92
+ [q, k, v, p_beta, decay.unsqueeze(-1), g.unsqueeze(-1)],
93
+ )
94
+ decay = decay.squeeze(-1).cumsum(-1) # [B, H, n, c]
95
+ decay_0 = decay - g.squeeze(-1) # [B, H, n, c]
96
+ L_mask = ((decay.unsqueeze(-1) - decay.unsqueeze(-2)).tril().exp().float()).tril()
97
+ L_mask_0 = ((decay_0.unsqueeze(-1) - decay.unsqueeze(-2)).tril().exp().float()).tril()
98
+ # [B, H, n, c, d] @ [B, H, n, d, c] -> [B, H, n, c, c]
99
+ attn = -((p_beta @ k.transpose(-1, -2)) * L_mask_0).masked_fill(mask, 0)
100
+ for i in range(1, chunk_size):
101
+ attn[..., i, :i] = attn[..., i, :i].clone() + (attn[..., i, :i, None].clone() * attn[..., :i, :i].clone()).sum(-2)
102
+ attn = attn + torch.eye(chunk_size, dtype=torch.float, device=q.device)
103
+ # for U
104
+ k_cumsum = attn @ v
105
+ # for W
106
+ k_cumdecay = attn @ (p_beta * decay_0[..., None].exp())
107
+ v = k_cumsum
108
+ S = k.new_zeros(b, h, d_k, d_v)
109
+ if initial_state is not None:
110
+ S += initial_state
111
+ o = torch.zeros_like(v)
112
+ mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1)
113
+ for i in range(0, l // chunk_size):
114
+ q_i, k_i, v_i = q[:, :, i], k[:, :, i], v[:, :, i]
115
+ attn = (q_i @ k_i.transpose(-1, -2) * L_mask[:, :, i]).masked_fill_(mask, 0)
116
+ v_prime = k_cumdecay[:, :, i] @ S
117
+ v_new = v_i - v_prime
118
+ o_inter = (q_i * decay[:, :, i, :, None].exp()) @ S
119
+ o[:, :, i] = o_inter + attn @ v_new
120
+ S = S * decay[:, :, i, -1, None, None].exp() + (k_i * (decay[:, :, i, -1, None] - decay[:, :, i]).exp()
121
+ [..., None]).transpose(-1, -2) @ v_new
122
+ if not output_final_state:
123
+ S = None
124
+ # unpad
125
+ o = rearrange(o, 'b h n c d -> b h (n c) d')
126
+ o = o[:, :, :T]
127
+ o = o.transpose(1, 2)
128
+ return o, S
129
+
130
+
131
+ @pytest.mark.parametrize(
132
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'),
133
+ [
134
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test))
135
+ for test in [
136
+ (1, 63, 1, 64, 1, 1, torch.float),
137
+ (2, 1024, 4, 60, 1, 1, torch.float),
138
+ (2, 1024, 8, 128, 1, 0.1, torch.float),
139
+ (2, 1024, 8, 128, 0.1, 1, torch.float),
140
+ (2, 1024, 8, 128, 1, 10, torch.float),
141
+ (4, 2048, 8, 64, 0.1, 1, torch.float),
142
+ (2, 1024, 8, 128, 1, 0.1, torch.float16),
143
+ (2, 1024, 8, 128, 1, 10, torch.float16),
144
+ ]
145
+ ],
146
+ )
147
+ def test_fused_recurrent(
148
+ B: int,
149
+ T: int,
150
+ H: int,
151
+ D: int,
152
+ scale: float,
153
+ gate_logit_normalizer: float,
154
+ dtype: torch.dtype,
155
+ ):
156
+ torch.manual_seed(42)
157
+ q = F.normalize(torch.randn(B, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
158
+ k = F.normalize(torch.randn(B, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
159
+ v = torch.randn(B, T, H, D, dtype=dtype)
160
+ p = F.normalize(torch.randn(B, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
161
+ beta = torch.rand(B, T, H, dtype=dtype).sigmoid()
162
+ g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.float32))
163
+ g = g / gate_logit_normalizer
164
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
165
+ q, k, v, p, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, p, beta, g, h0))
166
+ ref, ref_ht = chunk_comba_ref(
167
+ q=q.clone(),
168
+ k=k.clone(),
169
+ v=v.clone(),
170
+ p=p.clone(),
171
+ beta=beta.clone(),
172
+ g=g.clone(),
173
+ scale=scale,
174
+ initial_state=h0.clone(),
175
+ output_final_state=True,
176
+ )
177
+ tri, tri_ht = fused_recurrent_comba(
178
+ q=q.clone(),
179
+ k=k.clone(),
180
+ v=v.clone(),
181
+ p=p.clone(),
182
+ beta=beta.clone(),
183
+ g=g.clone(),
184
+ scale=scale,
185
+ initial_state=h0.clone(),
186
+ output_final_state=True,
187
+ )
188
+ assert_close('o', ref, tri, 0.002)
189
+ assert_close('ht', ref_ht, tri_ht, 0.002)
190
+
191
+
192
+ @pytest.mark.parametrize(
193
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'mask_p', 'use_qk_l2norm_in_kernel', 'dtype'),
194
+ [
195
+ pytest.param(
196
+ *test,
197
+ id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-mask_p{}-use_qk_l2norm_in_kernel{}-{}".format(*test),
198
+ )
199
+ for test in [
200
+ (1, 63, 1, 64, 1, 1, 0, False, torch.float16),
201
+ (2, 1000, 3, 60, 1, 1, 0, False, torch.float16),
202
+ (2, 1024, 3, 64, 0.1, 1, 0.5, False, torch.float16),
203
+ (2, 1024, 4, 100, 1, 0.1, 0, False, torch.float16),
204
+ (2, 1024, 4, 128, 0.1, 1, 0, True, torch.float16),
205
+ (2, 1024, 4, 128, 0.1, 1, 0.5, False, torch.float16),
206
+ (2, 1024, 4, 128, 0.1, 10, 0, False, torch.float16),
207
+ (4, 2048, 8, 64, 0.1, 1, 0, True, torch.float16),
208
+ ]
209
+ ],
210
+ )
211
+ def test_chunk(
212
+ B: int,
213
+ T: int,
214
+ H: int,
215
+ D: int,
216
+ scale: float,
217
+ gate_logit_normalizer: float,
218
+ mask_p: float,
219
+ use_qk_l2norm_in_kernel: bool,
220
+ dtype: torch.dtype,
221
+ ):
222
+ if is_intel_alchemist and D > 128:
223
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
224
+
225
+ q = torch.randn(B, T, H, D, dtype=dtype)
226
+ k = torch.randn(B, T, H, D, dtype=dtype)
227
+ p = torch.randn(B, T, H, D, dtype=dtype)
228
+ v = torch.randn(B, T, H, D, dtype=dtype)
229
+ beta = torch.rand(B, T, H, dtype=dtype).sigmoid()
230
+ g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.float32))
231
+ g = g / gate_logit_normalizer
232
+ g = g * (torch.rand_like(g) > mask_p)
233
+ h0 = torch.zeros(B, H, D, D, dtype=torch.float32)
234
+ q, k, v, p, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, p, beta, g, h0))
235
+
236
+ tri, tri_ht = chunk_comba(
237
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
238
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
239
+ p=F.normalize(p.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else p.clone(),
240
+ v=v.clone(),
241
+ g=g.clone(),
242
+ beta=beta.clone(),
243
+ scale=scale,
244
+ initial_state=h0.clone(),
245
+ output_final_state=True,
246
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
247
+ )
248
+ do = torch.randn_like(v)
249
+ dht = torch.randn_like(h0)
250
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
251
+ tri_dq, tri_dk, tri_dv, tri_dp, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, p.grad, beta.grad, g.grad, h0.grad
252
+ q.grad = k.grad = v.grad = p.grad = beta.grad = g.grad = h0.grad = None
253
+
254
+ ref, ref_ht = chunk_comba_ref(
255
+ q=F.normalize(q.clone(), p=2, dim=-1),
256
+ k=F.normalize(k.clone(), p=2, dim=-1),
257
+ p=F.normalize(p.clone(), p=2, dim=-1),
258
+ v=v.clone(),
259
+ g=g.clone(),
260
+ beta=beta.clone(),
261
+ scale=scale,
262
+ initial_state=h0.clone(),
263
+ output_final_state=True,
264
+ )
265
+
266
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
267
+ ref_dq, ref_dk, ref_dv, ref_dp, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, p.grad, beta.grad, g.grad, h0.grad
268
+
269
+ assert_close(" o", ref, tri, 0.005)
270
+ assert_close(" ht", ref_ht, tri_ht, 0.005)
271
+ assert_close(" dq", ref_dq, tri_dq, 0.005)
272
+ assert_close(" dk", ref_dk, tri_dk, 0.008)
273
+ assert_close(" dv", ref_dv, tri_dv, 0.005)
274
+ assert_close(" dp", ref_dp, tri_dp, 0.008)
275
+ assert_close(" dg", ref_dg, tri_dg, 0.02)
276
+ assert_close(" db", ref_dbeta, tri_dbeta, 0.005)
277
+ assert_close("dh0", ref_dh0, tri_dh0, 0.008)
278
+
279
+
280
+ @pytest.mark.parametrize(
281
+ ('H', 'D', 'mask_p', 'cu_seqlens', 'dtype'),
282
+ [
283
+ pytest.param(*test, id="H{}-D{}-mask_p{}-cu_seqlens{}-{}".format(*test))
284
+ for test in [
285
+ (4, 64, 0, [0, 15], torch.float16),
286
+ (4, 64, 0, [0, 256, 500, 1000], torch.float16),
287
+ (4, 64, 0.5, [0, 256, 500, 1000], torch.float16),
288
+ (4, 100, 0, [0, 15, 100, 300, 1200, 2000], torch.float16),
289
+ ]
290
+ ],
291
+ )
292
+ @pytest.mark.skipif(
293
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
294
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
295
+ )
296
+ def test_chunk_varlen(
297
+ H: int,
298
+ D: int,
299
+ mask_p: float,
300
+ cu_seqlens: list[int],
301
+ dtype: torch.dtype,
302
+ ):
303
+ if is_intel_alchemist and D > 128:
304
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
305
+ torch.manual_seed(42)
306
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
307
+
308
+ N = len(cu_seqlens) - 1
309
+ T = cu_seqlens[-1]
310
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
311
+
312
+ q = torch.randn((1, T, H, D), dtype=dtype)
313
+ k = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
314
+ v = torch.randn((1, T, H, D), dtype=dtype)
315
+ p = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
316
+ g = F.logsigmoid(torch.rand(1, T, H, dtype=dtype))
317
+ g = g * (torch.rand_like(g) > mask_p)
318
+ beta = torch.rand(1, T, H, dtype=dtype).sigmoid()
319
+ h0 = torch.randn((N, H, D, D), dtype=dtype)
320
+
321
+ q, k, v, p, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, p, beta, g, h0))
322
+ do = torch.randn_like(v)
323
+ dht = torch.rand_like(h0)
324
+
325
+ tri, tri_ht = chunk_comba(
326
+ q=q.clone(),
327
+ k=k.clone(),
328
+ v=v.clone(),
329
+ p=p.clone(),
330
+ beta=beta.clone(),
331
+ g=g.clone(),
332
+ output_final_state=True,
333
+ initial_state=h0.clone(),
334
+ cu_seqlens=cu_seqlens,
335
+ )
336
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
337
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
338
+ q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None
339
+
340
+ ref = []
341
+ ref_ht = []
342
+ for i in range(N):
343
+ ref_i, ref_ht_i = chunk_comba_ref(
344
+ q=q[:, cu_seqlens[i]:cu_seqlens[i+1]],
345
+ k=k[:, cu_seqlens[i]:cu_seqlens[i+1]],
346
+ v=v[:, cu_seqlens[i]:cu_seqlens[i+1]],
347
+ p=p[:, cu_seqlens[i]:cu_seqlens[i+1]],
348
+ beta=beta[:, cu_seqlens[i]:cu_seqlens[i+1]],
349
+ g=g[:, cu_seqlens[i]:cu_seqlens[i+1]],
350
+ initial_state=h0[i],
351
+ output_final_state=True,
352
+ )
353
+ ref.append(ref_i)
354
+ ref_ht.append(ref_ht_i)
355
+ ref = torch.cat(ref, 1)
356
+ ref_ht = torch.cat(ref_ht, 0)
357
+
358
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
359
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
360
+
361
+ assert_close('o', ref, tri, 0.005)
362
+ assert_close('ht', ref_ht, tri_ht, 0.005)
363
+ assert_close('dq', ref_dq, tri_dq, 0.007)
364
+ assert_close('dk', ref_dk, tri_dk, 0.008)
365
+ assert_close('dv', ref_dv, tri_dv, 0.007)
366
+ assert_close('db', ref_dbeta, tri_dbeta, 0.015)
367
+ assert_close('dg', ref_dg, tri_dg, 0.015)
368
+ assert_close('dh0', ref_dh0, tri_dh0, 0.007)
code/flash-linear-attention/tests/ops/test_delta.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import pytest
4
+ import torch
5
+ import torch.nn.functional as F
6
+
7
+ from fla.ops.delta_rule import chunk_delta_rule, fused_recurrent_delta_rule
8
+ from fla.utils import assert_close, device, device_platform
9
+
10
+
11
+ @pytest.mark.parametrize(
12
+ ('B', 'T', 'H', 'D', 'scale', 'use_qk_l2norm_in_kernel', 'dtype'),
13
+ [
14
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
15
+ for test in [
16
+ (1, 63, 1, 64, 1, False, torch.float16),
17
+ (2, 100, 4, 60, 0.1, False, torch.float16),
18
+ (2, 1000, 3, 128, 0.1, False, torch.float16),
19
+ (2, 1024, 4, 128, 1, True, torch.float16),
20
+ (3, 2000, 4, 128, 0.1, False, torch.float16),
21
+ (4, 2048, 8, 64, 0.1, False, torch.float16),
22
+ ]
23
+ ],
24
+ )
25
+ @pytest.mark.skipif(
26
+ device_platform == 'intel',
27
+ reason='Intel Triton Failure',
28
+ )
29
+ def test_chunk(
30
+ B: int,
31
+ T: int,
32
+ H: int,
33
+ D: int,
34
+ scale: float,
35
+ use_qk_l2norm_in_kernel: bool,
36
+ dtype: torch.dtype,
37
+ ):
38
+ torch.manual_seed(42)
39
+ q = torch.randn(B, T, H, D, dtype=dtype)
40
+ k = torch.randn(B, T, H, D, dtype=dtype)
41
+ v = torch.randn(B, T, H, D, dtype=dtype)
42
+ beta = torch.randn(B, T, H, dtype=dtype).sigmoid()
43
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
44
+ q, k, v, beta, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, beta, h0))
45
+ do = torch.rand_like(v)
46
+ dht = torch.rand_like(h0)
47
+
48
+ tri, tri_ht = chunk_delta_rule(
49
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
50
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
51
+ v=v.clone(),
52
+ beta=beta.clone(),
53
+ scale=scale,
54
+ output_final_state=True,
55
+ initial_state=h0.clone(),
56
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
57
+ )
58
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
59
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
60
+ q.grad = k.grad = v.grad = beta.grad = h0.grad = None
61
+
62
+ ref, ref_ht = fused_recurrent_delta_rule(
63
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
64
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
65
+ v=v.clone(),
66
+ beta=beta.clone(),
67
+ scale=scale,
68
+ output_final_state=True,
69
+ initial_state=h0.clone(),
70
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
71
+ )
72
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
73
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
74
+
75
+ assert_close('o', ref, tri, 0.006)
76
+ assert_close('ht', ref_ht, tri_ht, 0.006)
77
+ assert_close('dq', ref_dq, tri_dq, 0.008)
78
+ assert_close('dk', ref_dk, tri_dk, 0.008)
79
+ assert_close('dv', ref_dv, tri_dv, 0.008)
80
+ assert_close('db', ref_dbeta, tri_dbeta, 0.008)
81
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
82
+
83
+
84
+ @pytest.mark.parametrize(
85
+ ('H', 'D', 'cu_seqlens', 'dtype'),
86
+ [
87
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
88
+ for test in [
89
+ (2, 64, [0, 15], torch.float16),
90
+ (3, 60, [0, 111, 500], torch.float16),
91
+ (3, 64, [0, 256, 500, 900, 1000], torch.float16),
92
+ (4, 100, [0, 15, 100, 300, 1200, 1599, 1800, 2000], torch.float16),
93
+ ]
94
+ ],
95
+ )
96
+ @pytest.mark.skipif(
97
+ device_platform == 'intel',
98
+ reason='Intel Triton Failure',
99
+ )
100
+ def test_chunk_varlen(
101
+ H: int,
102
+ D: int,
103
+ cu_seqlens: list[int],
104
+ dtype: torch.dtype,
105
+ ):
106
+ torch.manual_seed(42)
107
+ T = cu_seqlens[-1]
108
+ N = len(cu_seqlens) - 1
109
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
110
+
111
+ # seq-first required for inputs with variable lengths
112
+ q = torch.randn((1, T, H, D), dtype=dtype)
113
+ k = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
114
+ v = torch.randn((1, T, H, D), dtype=dtype)
115
+ beta = torch.randn(1, T, H, dtype=dtype).sigmoid()
116
+ h0 = torch.randn(N, H, D, D, dtype=dtype)
117
+ q, k, v, beta, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, h0))
118
+ do = torch.randn_like(v)
119
+ dht = torch.rand_like(h0)
120
+
121
+ ref, ref_ht = fused_recurrent_delta_rule(
122
+ q=q.clone(),
123
+ k=k.clone(),
124
+ v=v.clone(),
125
+ beta=beta.clone(),
126
+ output_final_state=True,
127
+ initial_state=h0.clone(),
128
+ cu_seqlens=cu_seqlens,
129
+ )
130
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
131
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
132
+
133
+ tri, tri_ht = chunk_delta_rule(
134
+ q=q.clone(),
135
+ k=k.clone(),
136
+ v=v.clone(),
137
+ beta=beta.clone(),
138
+ output_final_state=True,
139
+ initial_state=h0.clone(),
140
+ cu_seqlens=cu_seqlens,
141
+ )
142
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
143
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
144
+ q.grad = k.grad = v.grad = beta.grad = h0.grad = None
145
+
146
+ assert_close('o', ref, tri, 0.005)
147
+ assert_close('ht', ref_ht, tri_ht, 0.005)
148
+ assert_close('dq', ref_dq, tri_dq, 0.008)
149
+ assert_close('dk', ref_dk, tri_dk, 0.008)
150
+ assert_close('dv', ref_dv, tri_dv, 0.008)
151
+ assert_close('db', ref_dbeta, tri_dbeta, 0.008)
152
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
code/flash-linear-attention/tests/ops/test_delta_product.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import pytest
4
+ import torch
5
+ import torch.nn.functional as F
6
+
7
+ from fla.ops.gated_delta_product import chunk_gated_delta_product
8
+ from fla.ops.gated_delta_product.chunk_ref import chunk_gated_delta_product_ref
9
+ from fla.ops.gated_delta_product.naive import naive_recurrent_gated_delta_product
10
+ from fla.utils import assert_close, device
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ ('B', 'T', 'H', 'D', 'scale', 'num_householder', 'use_qk_l2norm_in_kernel', 'dtype'),
15
+ [
16
+ pytest.param(
17
+ *test,
18
+ id="B{}-T{}-H{}-D{}-scale{}-num_householder{}-l2norm{}-{}".format(*test),
19
+ )
20
+ for test in [
21
+ (1, 63, 1, 64, 0.1, 1, False, torch.float16),
22
+ (2, 200, 3, 60, 0.1, 1, False, torch.float16),
23
+ (2, 1000, 4, 64, 0.1, 2, False, torch.float16),
24
+ (2, 1024, 4, 64, 1, 2, True, torch.float16),
25
+ (2, 1024, 6, 100, 1, 2, False, torch.float16),
26
+ (4, 1500, 8, 128, 0.1, 3, False, torch.float16),
27
+ (2, 2048, 8, 128, 1, 3, False, torch.float16),
28
+ (2, 2048, 8, 128, 1, 3, True, torch.float16),
29
+ ]
30
+ ],
31
+ )
32
+ def test_chunk(
33
+ B: int,
34
+ T: int,
35
+ H: int,
36
+ D: int,
37
+ scale: float,
38
+ num_householder: int,
39
+ use_qk_l2norm_in_kernel: bool,
40
+ dtype: torch.dtype,
41
+ ):
42
+ torch.manual_seed(42)
43
+ q = torch.randn(B, T, H, D, dtype=dtype)
44
+ k = torch.randn(B, T * num_householder, H, D, dtype=dtype)
45
+ v = torch.randn(B, T * num_householder, H, D, dtype=dtype)
46
+ beta = torch.rand(B, T * num_householder, H, dtype=dtype).sigmoid()
47
+ h0 = torch.zeros(B, H, D, D, dtype=torch.float32)
48
+ q, k, v, beta, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, beta, h0))
49
+
50
+ tri, tri_ht = chunk_gated_delta_product(
51
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
52
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
53
+ v=v.clone(),
54
+ g=None,
55
+ beta=beta.clone(),
56
+ num_householder=num_householder,
57
+ scale=scale,
58
+ output_final_state=True,
59
+ initial_state=h0.clone(),
60
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
61
+ )
62
+ do = torch.randn_like(q)
63
+ dht = torch.randn_like(h0)
64
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
65
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
66
+ q.grad = k.grad = v.grad = beta.grad = h0.grad = None
67
+
68
+ ref, ref_ht = chunk_gated_delta_product_ref(
69
+ q=F.normalize(q.clone(), p=2, dim=-1),
70
+ k=F.normalize(k.clone(), p=2, dim=-1),
71
+ v=v.clone(),
72
+ g=None,
73
+ beta=beta.clone(),
74
+ num_householder=num_householder,
75
+ scale=scale,
76
+ initial_state=h0.clone(),
77
+ output_final_state=True,
78
+ )
79
+
80
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
81
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
82
+ assert_close('o', ref, tri, 0.005)
83
+ assert_close('ht', ref_ht, tri_ht, 0.005)
84
+ assert_close('dq', ref_dq, tri_dq, 0.008)
85
+ assert_close('dk', ref_dk, tri_dk, 0.008)
86
+ assert_close('dv', ref_dv, tri_dv, 0.008)
87
+ assert_close('db', ref_dbeta, tri_dbeta, 0.02)
88
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
89
+
90
+
91
+ @pytest.mark.parametrize(
92
+ ('H', 'D', 'num_householder', 'cu_seqlens', 'dtype'),
93
+ [
94
+ (2, 64, 3, [0, 63 ], torch.float16),
95
+ (2, 100, 2, [0, 63, 100, 500, 1000], torch.float16),
96
+ (2, 128, 2, [0, 100, 300, 800, 1500, 2000], torch.float16),
97
+ (2, 256, 3, [0, 100, 123, 300, 500, 800, 1000, 1500, 2048], torch.float16),
98
+ ],
99
+ )
100
+ def test_chunk_varlen(
101
+ H: int,
102
+ D: int,
103
+ num_householder: int,
104
+ cu_seqlens: list[int],
105
+ dtype: torch.dtype,
106
+ ):
107
+ torch.manual_seed(42)
108
+
109
+ T = cu_seqlens[-1]
110
+ N = len(cu_seqlens) - 1
111
+ cu_seqlens = torch.LongTensor(cu_seqlens).to(device)
112
+ scale = 1.0
113
+
114
+ q = torch.nn.functional.normalize(torch.randn((1, T, H, D), dtype=dtype), dim=-1, p=2)
115
+ k = torch.nn.functional.normalize(torch.randn(1, T*num_householder, H, D, dtype=dtype), dim=-1, p=2)
116
+ v = torch.randn((1, T*num_householder, H, D), dtype=dtype)
117
+ beta = torch.rand(1, T*num_householder, H, dtype=dtype).sigmoid()
118
+ h0 = torch.randn((N, H, D, D), dtype=dtype)
119
+
120
+ q, k, v, beta, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, h0))
121
+ do = torch.randn_like(q)
122
+ dht = torch.rand_like(h0)
123
+
124
+ tri, tri_ht = chunk_gated_delta_product(
125
+ q=q.clone(),
126
+ k=k.clone(),
127
+ v=v.clone(),
128
+ beta=beta.clone(),
129
+ g=None,
130
+ scale=scale,
131
+ output_final_state=True,
132
+ num_householder=num_householder,
133
+ initial_state=h0.clone(),
134
+ cu_seqlens=cu_seqlens,
135
+ )
136
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
137
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
138
+ q.grad = k.grad = v.grad = beta.grad = h0.grad = None
139
+
140
+ ref, ref_ht = chunk_gated_delta_product_ref(
141
+ q=q.clone(),
142
+ k=k.clone(),
143
+ v=v.clone(),
144
+ beta=beta.clone(),
145
+ g=None,
146
+ scale=scale,
147
+ output_final_state=True,
148
+ num_householder=num_householder,
149
+ initial_state=h0.clone(),
150
+ cu_seqlens=cu_seqlens,
151
+ )
152
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
153
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, h0.grad
154
+
155
+ assert_close('o', ref, tri, 0.005)
156
+ assert_close('ht', ref_ht, tri_ht, 0.005)
157
+ assert_close('dq', ref_dq, tri_dq, 0.007)
158
+ assert_close('dk', ref_dk, tri_dk, 0.008)
159
+ assert_close('dv', ref_dv, tri_dv, 0.007)
160
+ assert_close('db', ref_dbeta, tri_dbeta, 0.015)
161
+ assert_close('dh0', ref_dh0, tri_dh0, 0.007)
162
+ q.grad = k.grad = v.grad = beta.grad = h0.grad = None
163
+
164
+ torch_ref = torch.zeros_like(ref)
165
+ torch_ref_ht = torch.zeros_like(ref_ht)
166
+ for i in range(len(cu_seqlens) - 1):
167
+ start, end = cu_seqlens[i], cu_seqlens[i+1]
168
+ q_i = q[:, start:end, :, :]
169
+ k_i = k[:, start*num_householder:end*num_householder, :, :]
170
+ v_i = v[:, start*num_householder:end*num_householder, :, :]
171
+ beta_i = beta[:, start*num_householder:end*num_householder, :]
172
+ o3_i, h3_i = naive_recurrent_gated_delta_product(
173
+ q_i, k_i, v_i, None, beta_i, scale=scale, cu_seqlens=None, output_final_state=True, num_householder=num_householder,
174
+ )
175
+ torch_ref[:, start:end, :, :] = o3_i
176
+ torch_ref_ht[i, :, :, :] = h3_i.squeeze(0)
177
+
178
+ ((torch_ref * do).sum() + (torch_ref_ht * dht).sum()).backward(retain_graph=True)
179
+
180
+ assert_close('o', ref, tri, 0.005)
181
+ assert_close('ht', ref_ht, tri_ht, 0.005)
182
+ assert_close('dq', ref_dq, tri_dq, 0.007)
183
+ assert_close('dk', ref_dk, tri_dk, 0.008)
184
+ assert_close('dv', ref_dv, tri_dv, 0.007)
185
+ assert_close('db', ref_dbeta, tri_dbeta, 0.015)
186
+ assert_close('dh0', ref_dh0, tri_dh0, 0.007)
code/flash-linear-attention/tests/ops/test_deltaformer.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import pytest
4
+ import torch
5
+
6
+ from fla.ops.deltaformer import deltaformer_attn
7
+ from fla.ops.deltaformer.naive import naive_deltaformer_attn
8
+ from fla.utils import assert_close, device, is_intel_alchemist
9
+
10
+
11
+ @pytest.mark.parametrize(
12
+ ('B', 'T', 'H', 'D', 'dtype'),
13
+ [
14
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
15
+ for test in [
16
+ (2, 128, 2, 64, torch.float16),
17
+ (1, 256, 4, 64, torch.float16),
18
+ (2, 512, 4, 64, torch.float16),
19
+ (4, 1024, 4, 128, torch.float16),
20
+ ]
21
+ ],
22
+ )
23
+ @pytest.mark.skipif(
24
+ is_intel_alchemist,
25
+ reason="Skipping test on Intel Alchemist due to known issues with SRAM.",
26
+ )
27
+ def test_deltaformer_attn(
28
+ B: int,
29
+ T: int,
30
+ H: int,
31
+ D: int,
32
+ dtype: torch.dtype,
33
+ ):
34
+ """
35
+ Test DeltaFormer pre-attention by comparing fused implementation with naive reference.
36
+ """
37
+ torch.manual_seed(42)
38
+
39
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
40
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
41
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
42
+ beta = torch.randn((B, T, H), dtype=dtype, device=device).sigmoid().requires_grad_(True)
43
+
44
+ do = torch.randn((B, T, H, D), dtype=dtype, device=device)
45
+
46
+ ref = naive_deltaformer_attn(q, k, v, beta)
47
+ ref.backward(do)
48
+ ref_dq, q.grad = q.grad.clone(), None
49
+ ref_dk, k.grad = k.grad.clone(), None
50
+ ref_dv, v.grad = v.grad.clone(), None
51
+ ref_dbeta, beta.grad = beta.grad.clone(), None
52
+
53
+ tri = deltaformer_attn(q, k, v, beta)
54
+ tri.backward(do)
55
+ tri_dq, q.grad = q.grad.clone(), None
56
+ tri_dk, k.grad = k.grad.clone(), None
57
+ tri_dv, v.grad = v.grad.clone(), None
58
+ tri_dbeta, beta.grad = beta.grad.clone(), None
59
+
60
+ assert_close('o', ref, tri, 0.006)
61
+ assert_close('dq', ref_dq, tri_dq, 0.008)
62
+ assert_close('dk', ref_dk, tri_dk, 0.008)
63
+ assert_close('dv', ref_dv, tri_dv, 0.008)
64
+ assert_close('dbeta', ref_dbeta, tri_dbeta, 0.008)
65
+
66
+
67
+ @pytest.mark.parametrize(
68
+ ('H', 'D', 'cu_seqlens', 'dtype'),
69
+ [
70
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
71
+ for test in [
72
+ (2, 64, [0, 63], torch.float16),
73
+ (4, 64, [0, 256, 500, 1000], torch.float16),
74
+ (4, 128, [0, 15, 100, 300, 1200, 2000], torch.float16),
75
+ (2, 128, [0, 100, 123, 300, 500, 800, 1000, 1500, 2048], torch.float16),
76
+ ]
77
+ ],
78
+ )
79
+ @pytest.mark.skipif(
80
+ is_intel_alchemist,
81
+ reason="Skipping test on Intel Alchemist due to known issues with SRAM.",
82
+ )
83
+ def test_deltaformer_attn_varlen(
84
+ H: int,
85
+ D: int,
86
+ cu_seqlens: list[int],
87
+ dtype: torch.dtype,
88
+ ):
89
+ torch.manual_seed(42)
90
+
91
+ T = cu_seqlens[-1]
92
+ N = len(cu_seqlens) - 1
93
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
94
+
95
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
96
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
97
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
98
+ beta = torch.randn((1, T, H), dtype=dtype, device=device).sigmoid().requires_grad_()
99
+
100
+ do = torch.randn_like(q)
101
+
102
+ refs = []
103
+ for i in range(N):
104
+ ref = naive_deltaformer_attn(
105
+ q[:, cu_seqlens[i]:cu_seqlens[i+1]],
106
+ k[:, cu_seqlens[i]:cu_seqlens[i+1]],
107
+ v[:, cu_seqlens[i]:cu_seqlens[i+1]],
108
+ beta[:, cu_seqlens[i]:cu_seqlens[i+1]],
109
+ )
110
+ refs.append(ref)
111
+ ref = torch.cat(refs, dim=1)
112
+
113
+ ref.backward(do)
114
+ ref_dq, q.grad = q.grad.clone(), None
115
+ ref_dk, k.grad = k.grad.clone(), None
116
+ ref_dv, v.grad = v.grad.clone(), None
117
+ ref_dbeta, beta.grad = beta.grad.clone(), None
118
+
119
+ tri = deltaformer_attn(q, k, v, beta, cu_seqlens=cu_seqlens)
120
+ tri.backward(do)
121
+ tri_dq, q.grad = q.grad.clone(), None
122
+ tri_dk, k.grad = k.grad.clone(), None
123
+ tri_dv, v.grad = v.grad.clone(), None
124
+ tri_dbeta, beta.grad = beta.grad.clone(), None
125
+
126
+ assert_close('o', ref, tri, 0.006)
127
+ assert_close('dq', ref_dq, tri_dq, 0.008)
128
+ assert_close('dk', ref_dk, tri_dk, 0.008)
129
+ assert_close('dv', ref_dv, tri_dv, 0.008)
130
+ assert_close('dbeta', ref_dbeta, tri_dbeta, 0.008)
code/flash-linear-attention/tests/ops/test_dplr_delta.py ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from einops import rearrange
8
+
9
+ from fla.ops.generalized_delta_rule.dplr import chunk_dplr_delta_rule, fused_recurrent_dplr_delta_rule
10
+ from fla.utils import assert_close, device, device_platform
11
+
12
+
13
+ def recurrent_dplr_delta_rule_ref(
14
+ q: torch.Tensor,
15
+ k: torch.Tensor,
16
+ v: torch.Tensor,
17
+ a: torch.Tensor,
18
+ b: torch.Tensor,
19
+ gk: torch.Tensor,
20
+ scale: float = None,
21
+ initial_state: torch.Tensor = None,
22
+ output_final_state: bool = False,
23
+ ):
24
+ q, k, v, a, b, gk = map(lambda x: x.transpose(1, 2).to(torch.float), (q, k, v, a, b, gk))
25
+
26
+ B, H, T, K, V = *q.shape, v.shape[-1]
27
+ o = torch.zeros_like(v)
28
+ S = torch.zeros(B, H, K, V).to(v)
29
+ if initial_state is not None:
30
+ S = initial_state
31
+ if scale is None:
32
+ scale = K ** -0.5
33
+ q = q * scale
34
+
35
+ for i in range(T):
36
+ _q = q[:, :, i]
37
+ _k = k[:, :, i]
38
+ _v = v[:, :, i].clone()
39
+ a_i = a[:, :, i]
40
+ b_i = b[:, :, i]
41
+ # first matmul then decay in DPLR.
42
+ _v2 = (S.clone() * a_i[..., None]).sum(-2)
43
+ S = S.clone() * gk[:, :, i].exp()[..., None]
44
+ S = S.clone() + _k.unsqueeze(-1) * _v.unsqueeze(-2) + b_i.unsqueeze(-1) * _v2.unsqueeze(-2)
45
+ o[:, :, i] = torch.einsum('bhd,bhdm->bhm', _q, S)
46
+ if not output_final_state:
47
+ S = None
48
+ o = o.transpose(1, 2)
49
+ return o, S
50
+
51
+
52
+ def chunk_dplr_delta_rule_ref(
53
+ q: torch.Tensor,
54
+ k: torch.Tensor,
55
+ v: torch.Tensor,
56
+ a: torch.Tensor,
57
+ b: torch.Tensor,
58
+ gk: torch.Tensor,
59
+ initial_state: torch.Tensor = None,
60
+ output_final_state: bool = True,
61
+ scale: float = None,
62
+ chunk_size: int = 64,
63
+ ):
64
+ q, k, v, a, b, gk = map(lambda x: x.transpose(1, 2).to(torch.float), (q, k, v, a, b, gk))
65
+ BT = chunk_size
66
+ T = q.shape[-2]
67
+ pad_len = (BT - (T % BT)) % BT
68
+
69
+ q, k, v, a, b, gk = map(lambda x: F.pad(x, (0, 0, 0, pad_len)).to(torch.float), [q, k, v, a, b, gk])
70
+ B, H, _, K, V = *q.shape, v.shape[-1]
71
+ NT = q.shape[-2] // BT
72
+ if scale is None:
73
+ scale = K ** -0.5
74
+ q = q * scale
75
+
76
+ S = k.new_zeros(B, H, K, V)
77
+ if initial_state is not None:
78
+ S += initial_state
79
+
80
+ # note that diagonal is masked.
81
+ mask = torch.triu(torch.ones(BT, BT, dtype=torch.bool, device=q.device), diagonal=0)
82
+ q, k, v, a, b, gk = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=BT), [q, k, v, a, b, gk])
83
+ gk_cumsum = gk.cumsum(-2)
84
+ A_ab = torch.zeros(B, H, NT, BT, BT).to(q.device)
85
+ A_qk = torch.zeros(B, H, NT, BT, BT).to(q.device)
86
+ A_ak = torch.zeros(B, H, NT, BT, BT).to(q.device)
87
+ A_qb = torch.zeros(B, H, NT, BT, BT).to(q.device)
88
+
89
+ for i in range(BT):
90
+ a_i = a[:, :, :, i, None]
91
+ q_i = q[:, :, :, i, None]
92
+ gk_i = gk_cumsum[:, :, :, i, None]
93
+ mask = (torch.arange(BT) <= i).to(q.device)
94
+ attn_i = (gk_i - gk_cumsum).masked_fill(~mask.unsqueeze(-1), float('-inf')).exp()
95
+ A_qk[:, :, :, i, :] = (q_i * k * attn_i).sum(-1).clone()
96
+ A_qb[:, :, :, i, :] = (q_i * b * attn_i).sum(-1).clone()
97
+ mask = (torch.arange(BT) < i).to(q.device)
98
+ # shift by one.
99
+ attn_i = (gk_i - gk[:, :, :, i, None] - gk_cumsum).masked_fill(~mask.unsqueeze(-1), float('-inf')).exp()
100
+ A_ab[:, :, :, i, :] = (a_i * b * attn_i).sum(-1).clone()
101
+ A_ak[:, :, :, i, :] = (a_i * k * attn_i).sum(-1).clone()
102
+
103
+ A_ab = A_ab
104
+ for i in range(1, BT):
105
+ A_ab[..., i, :i] = A_ab[..., i, :i].clone() + (A_ab[..., i, :, None].clone() * A_ab[..., :, :i].clone()).sum(-2)
106
+
107
+ A_ab = A_ab + torch.eye(BT, dtype=torch.float, device=q.device)
108
+ u = A_ab @ (A_ak @ v)
109
+ w = A_ab @ ((gk_cumsum-gk).exp() * a)
110
+
111
+ o = torch.zeros_like(v)
112
+ mask = torch.triu(torch.ones(BT, BT, dtype=torch.bool, device=q.device), diagonal=1)
113
+ for i in range(0, NT):
114
+ q_i, k_i, v_i, u_i, w_i, b_i = q[:, :, i], k[:, :, i], v[:, :, i], u[:, :, i], w[:, :, i], b[:, :, i]
115
+ v2_i = u_i + w_i @ S
116
+ o_1 = A_qk[:, :, i] @ v_i
117
+ o_2 = A_qb[:, :, i] @ v2_i
118
+ o_3 = (q_i * gk_cumsum[:, :, i].exp()) @ S
119
+ o[:, :, i] = o_1 + o_2 + o_3
120
+ decay = (gk_cumsum[:, :, i, -1, None] - gk_cumsum[:, :, i]).exp()
121
+ S = S*gk_cumsum[:, :, i, -1, :, None].exp() + (k_i * decay).transpose(-1, -2) @ v_i + \
122
+ (b_i * decay).transpose(-1, -2) @ v2_i
123
+
124
+ S = None if output_final_state is False else S
125
+ o = rearrange(o, 'b h n c d -> b h (n c) d')
126
+ o = o[:, :, :T].transpose(1, 2)
127
+ return o, S
128
+
129
+
130
+ @pytest.mark.parametrize(
131
+ ('B', 'T', 'H', 'D', 'scale', 'dtype'),
132
+ [
133
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
134
+ for test in [
135
+ (1, 63, 1, 64, 1, torch.float),
136
+ (2, 1024, 4, 60, 1, torch.float),
137
+ (2, 1024, 8, 128, 1, torch.float),
138
+ (2, 1024, 8, 128, 0.1, torch.float),
139
+ (4, 2048, 8, 64, 0.1, torch.float),
140
+ (2, 1024, 8, 128, 1, torch.float16),
141
+ ]
142
+ ],
143
+ )
144
+ def test_recurrent_fwd(
145
+ B: int,
146
+ T: int,
147
+ H: int,
148
+ D: int,
149
+ scale: float,
150
+ dtype: torch.dtype,
151
+ ):
152
+ torch.manual_seed(42)
153
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
154
+ os.environ['TORCH_CUDA_MATMUL_PRECISION'] = 'highest'
155
+ q = torch.randn(B, T, H, D, dtype=dtype)
156
+ k = torch.randn(B, T, H, D, dtype=dtype)
157
+ v = torch.randn(B, T, H, D, dtype=dtype)
158
+ a = torch.rand(B, T, H, D, dtype=dtype)
159
+ gk = torch.randn(B, T, H, D, dtype=torch.float)
160
+
161
+ a = F.normalize(a, p=2, dim=-1)
162
+ b = -a
163
+ gk = F.logsigmoid(gk) / 16
164
+
165
+ h0 = torch.randn(B, H, D, D, dtype=torch.float)
166
+ q, k, v, a, b, gk, h0 = map(lambda x: x.to(device).requires_grad_(False), (q, k, v, a, b, gk, h0))
167
+ ref, ref_ht = chunk_dplr_delta_rule_ref(
168
+ q=q.clone(),
169
+ k=k.clone(),
170
+ v=v.clone(),
171
+ a=a.clone(),
172
+ b=b.clone(),
173
+ gk=gk.clone(),
174
+ scale=scale,
175
+ initial_state=h0.clone(),
176
+ output_final_state=True,
177
+ )
178
+ tri, tri_ht = recurrent_dplr_delta_rule_ref(
179
+ q=q.clone(),
180
+ k=k.clone(),
181
+ v=v.clone(),
182
+ a=a.clone(),
183
+ b=b.clone(),
184
+ gk=gk.clone(),
185
+ scale=scale,
186
+ initial_state=h0.clone(),
187
+ output_final_state=True,
188
+ )
189
+ assert_close('o', ref, tri, 0.001)
190
+ assert_close('ht', ref_ht, tri_ht, 0.001)
191
+
192
+
193
+ @pytest.mark.parametrize(
194
+ ('B', 'T', 'H', 'D', 'scale', 'dtype'),
195
+ [
196
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
197
+ for test in [
198
+ (1, 63, 1, 64, 1, torch.float),
199
+ (2, 1024, 4, 60, 1, torch.float),
200
+ (2, 1024, 8, 100, 1, torch.float),
201
+ (2, 1024, 8, 128, 0.1, torch.float),
202
+ (4, 2048, 8, 64, 0.1, torch.float),
203
+ ]
204
+ ],
205
+ )
206
+ def test_fused_recurrent(
207
+ B: int,
208
+ T: int,
209
+ H: int,
210
+ D: int,
211
+ scale: float,
212
+ dtype: torch.dtype,
213
+ ):
214
+ torch.manual_seed(42)
215
+ q = torch.randn(B, T, H, D, dtype=dtype)
216
+ k = torch.randn(B, T, H, D, dtype=dtype)
217
+ v = torch.randn(B, T, H, D, dtype=dtype)
218
+ a = torch.rand(B, T, H, D, dtype=dtype)
219
+ gk = torch.randn(B, T, H, D, dtype=torch.float)
220
+
221
+ a = F.normalize(a, p=2, dim=-1)
222
+ b = -a
223
+ gk = F.logsigmoid(gk) / 4
224
+
225
+ h0 = torch.randn(B, H, D, D, dtype=torch.float)
226
+ q, k, v, a, b, gk, h0 = map(lambda x: x.to(device).requires_grad_(False), (q, k, v, a, b, gk, h0))
227
+ ref, ref_ht = recurrent_dplr_delta_rule_ref(
228
+ q=q.clone(),
229
+ k=k.clone(),
230
+ v=v.clone(),
231
+ a=a.clone(),
232
+ b=b.clone(),
233
+ gk=gk.clone(),
234
+ scale=scale,
235
+ initial_state=h0.clone(),
236
+ output_final_state=True,
237
+ )
238
+
239
+ tri, tri_ht = fused_recurrent_dplr_delta_rule(
240
+ q=q.clone(),
241
+ k=k.clone(),
242
+ v=v.clone(),
243
+ a=a.clone(),
244
+ b=b.clone(),
245
+ gk=gk.clone(),
246
+ scale=scale,
247
+ initial_state=h0.clone(),
248
+ output_final_state=True,
249
+ )
250
+ assert_close('o', ref, tri, 0.002)
251
+ assert_close('ht', ref_ht, tri_ht, 0.002)
252
+
253
+
254
+ @pytest.mark.parametrize(
255
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'mask_p', 'dtype'),
256
+ [
257
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-mask_p{}-{}".format(*test))
258
+ for test in [
259
+ (1, 63, 1, 64, 1, 1, 0, torch.float16),
260
+ (2, 1000, 3, 60, 1, 1, 0, torch.float16),
261
+ (2, 1024, 3, 64, 0.1, 1, 0.5, torch.float16),
262
+ (2, 1024, 4, 100, 1, 0.1, 0, torch.float16),
263
+ (2, 1024, 4, 128, 0.1, 1, 0, torch.float16),
264
+ (2, 1024, 4, 128, 0.1, 1, 0.5, torch.float16),
265
+ (2, 1024, 4, 128, 0.1, 10, 0, torch.float16),
266
+ (4, 2048, 8, 64, 0.1, 1, 0, torch.float16),
267
+ ]
268
+ ],
269
+ )
270
+ @pytest.mark.skipif(
271
+ device_platform == 'intel',
272
+ reason='Intel Triton Failure',
273
+ )
274
+ def test_chunk(
275
+ B: int,
276
+ T: int,
277
+ H: int,
278
+ D: int,
279
+ scale: float,
280
+ gate_logit_normalizer: float,
281
+ mask_p: float,
282
+ dtype: torch.dtype,
283
+ ):
284
+ torch.manual_seed(42)
285
+ q = torch.randn(B, T, H, D, dtype=dtype)
286
+ k = torch.randn(B, T, H, D, dtype=dtype)
287
+ v = torch.randn(B, T, H, D, dtype=dtype)
288
+ a = torch.rand(B, T, H, D, dtype=dtype)
289
+ gk = torch.randn(B, T, H, D, dtype=torch.float)
290
+
291
+ a = F.normalize(a, p=2, dim=-1)
292
+ b = -a
293
+ gk = F.logsigmoid(gk)
294
+ gk = gk / gate_logit_normalizer
295
+ gk = gk * (torch.rand_like(gk) > mask_p)
296
+
297
+ h0 = torch.randn(B, H, D, D, dtype=torch.float)
298
+ q, k, v, a, b, gk, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, a, b, gk, h0))
299
+ ref, ref_ht = chunk_dplr_delta_rule_ref(
300
+ q=q.clone(),
301
+ k=k.clone(),
302
+ v=v.clone(),
303
+ a=a.clone(),
304
+ b=b.clone(),
305
+ gk=gk.clone(),
306
+ scale=scale,
307
+ initial_state=h0.clone(),
308
+ output_final_state=True,
309
+ )
310
+ do = torch.randn_like(v)
311
+ dht = torch.randn_like(h0)
312
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
313
+ ref_dq, ref_dk, ref_dv, ref_da, ref_db, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, a.grad, b.grad, gk.grad, h0.grad
314
+ q.grad = k.grad = v.grad = a.grad = b.grad = gk.grad = h0.grad = None
315
+
316
+ tri, tri_ht = chunk_dplr_delta_rule(
317
+ q=q.clone(),
318
+ k=k.clone(),
319
+ v=v.clone(),
320
+ a=a.clone(),
321
+ b=b.clone(),
322
+ gk=gk.clone(),
323
+ scale=scale,
324
+ initial_state=h0.clone(),
325
+ output_final_state=True,
326
+ )
327
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
328
+ tri_dq, tri_dk, tri_dv, tri_da, tri_db, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, a.grad, b.grad, gk.grad, h0.grad
329
+ q.grad = k.grad = v.grad = a.grad = b.grad = gk.grad = h0.grad = None
330
+
331
+ assert_close('o', ref, tri, 0.007)
332
+ assert_close('ht', ref_ht, tri_ht, 0.008)
333
+ assert_close('dq', ref_dq, tri_dq, 0.008)
334
+ assert_close('dk', ref_dk, tri_dk, 0.008)
335
+ assert_close('dv', ref_dv, tri_dv, 0.008)
336
+ assert_close('da', ref_da, tri_da, 0.008)
337
+ assert_close('db', ref_db, tri_db, 0.008)
338
+ if gate_logit_normalizer >= 1 and ref_dg.norm() > 0.01: # otherwise it is meaningless
339
+ assert_close('dg', ref_dg, tri_dg, 0.008)
340
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
341
+
342
+
343
+ @pytest.mark.parametrize(
344
+ ('H', 'D', 'mask_p', 'cu_seqlens', 'dtype'),
345
+ [
346
+ pytest.param(*test, id="H{}-D{}-mask_p{}-cu_seqlens{}-{}".format(*test))
347
+ for test in [
348
+ (4, 64, 0, [0, 15], torch.float16),
349
+ (4, 64, 0, [0, 256, 500, 1000], torch.float16),
350
+ (4, 64, 0.5, [0, 256, 500, 1000], torch.float16),
351
+ (4, 100, 0, [0, 15, 100, 300, 1111, 1599, 2000], torch.float16),
352
+ ]
353
+ ],
354
+ )
355
+ @pytest.mark.skipif(
356
+ device_platform == 'intel',
357
+ reason='Intel Triton Failure',
358
+ )
359
+ def test_chunk_varlen(
360
+ H: int,
361
+ D: int,
362
+ mask_p: float,
363
+ cu_seqlens: list[int],
364
+ dtype: torch.dtype,
365
+ ):
366
+ torch.manual_seed(42)
367
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
368
+
369
+ N = len(cu_seqlens) - 1
370
+ T = cu_seqlens[-1]
371
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
372
+
373
+ # seq-first required for inputs with variable lengths
374
+ q = torch.randn(1, T, H, D, dtype=dtype)
375
+ k = torch.randn(1, T, H, D, dtype=dtype)
376
+ v = torch.randn(1, T, H, D, dtype=dtype)
377
+ a = torch.rand(1, T, H, D, dtype=dtype)
378
+ gk = torch.randn(1, T, H, D, dtype=torch.float)
379
+ a = F.normalize(a, p=2, dim=-1)
380
+ b = -a
381
+ gk = F.logsigmoid(gk)
382
+ gk = gk * (torch.rand_like(gk) > mask_p)
383
+ h0 = torch.randn(N, H, D, D, dtype=torch.float)
384
+ q, k, v, a, b, gk, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, a, b, gk, h0))
385
+
386
+ tri, tri_ht = chunk_dplr_delta_rule(
387
+ q=q.clone(),
388
+ k=k.clone(),
389
+ v=v.clone(),
390
+ a=a.clone(),
391
+ b=b.clone(),
392
+ gk=gk.clone(),
393
+ output_final_state=True,
394
+ initial_state=h0.clone(),
395
+ cu_seqlens=cu_seqlens,
396
+ )
397
+ do = torch.randn_like(v)
398
+ dht = torch.randn_like(h0)
399
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
400
+ tri_dq, tri_dk, tri_dv, tri_da, tri_db, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, a.grad, b.grad, gk.grad, h0.grad
401
+ q.grad = k.grad = v.grad = a.grad = b.grad = gk.grad = h0.grad = None
402
+
403
+ ref = []
404
+ ref_ht = []
405
+ for i in range(N):
406
+ ref_i, ref_ht_i = chunk_dplr_delta_rule_ref(
407
+ q=q[:, cu_seqlens[i]:cu_seqlens[i+1]],
408
+ k=k[:, cu_seqlens[i]:cu_seqlens[i+1]],
409
+ v=v[:, cu_seqlens[i]:cu_seqlens[i+1]],
410
+ a=a[:, cu_seqlens[i]:cu_seqlens[i+1]],
411
+ b=b[:, cu_seqlens[i]:cu_seqlens[i+1]],
412
+ gk=gk[:, cu_seqlens[i]:cu_seqlens[i+1]],
413
+ initial_state=h0[i, None],
414
+ output_final_state=True,
415
+ )
416
+ ref.append(ref_i)
417
+ ref_ht.append(ref_ht_i)
418
+
419
+ ref = torch.cat(ref, 1)
420
+ ref_ht = torch.cat(ref_ht, 0)
421
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
422
+ ref_dq, ref_dk, ref_dv, ref_da, ref_db, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, a.grad, b.grad, gk.grad, h0.grad
423
+
424
+ assert_close('o', ref, tri, 0.007)
425
+ assert_close('ht', ref_ht, tri_ht, 0.008)
426
+ assert_close('dq', ref_dq, tri_dq, 0.008)
427
+ assert_close('dk', ref_dk, tri_dk, 0.008)
428
+ assert_close('dv', ref_dv, tri_dv, 0.008)
429
+ assert_close('da', ref_da, tri_da, 0.008)
430
+ assert_close('db', ref_db, tri_db, 0.008)
431
+ assert_close('dg', ref_dg, tri_dg, 0.008)
432
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
code/flash-linear-attention/tests/ops/test_forgetting_attn.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import pytest
4
+ import torch
5
+ import torch.nn.functional as F
6
+ from einops import rearrange, repeat
7
+
8
+ from fla.ops.forgetting_attn.parallel import parallel_forgetting_attn
9
+ from fla.utils import assert_close, check_shared_mem, device, is_intel_alchemist
10
+
11
+
12
+ def naive_forgetting_attn(
13
+ q: torch.Tensor,
14
+ k: torch.Tensor,
15
+ v: torch.Tensor,
16
+ g: torch.Tensor,
17
+ scale: float | None = None,
18
+ ):
19
+ _, T, HQ, D = q.shape
20
+ H = k.shape[2]
21
+ G = HQ // H
22
+ if scale is None:
23
+ scale = D ** -0.5
24
+ gc = g.float().cumsum(1)
25
+ mask = torch.tril(torch.ones((T, T), dtype=torch.bool, device=device))
26
+ ref = torch.einsum("bqhd,bkhd->bhqk", q.float() * scale, repeat(k, "b t h d -> b t (h g) d", g=G).float())
27
+ ref = ref + rearrange(gc, "b t h -> b h t 1") - rearrange(gc, "b t h -> b h 1 t")
28
+ ref = ref.masked_fill(~mask.unsqueeze(0).unsqueeze(0), -float('inf'))
29
+ ref = torch.einsum("bhqk,bkhd->bqhd", F.softmax(ref, dim=-1), repeat(v, "b t h d -> b t (h g) d", g=G).float())
30
+ return ref
31
+
32
+
33
+ @pytest.mark.parametrize(
34
+ ('B', 'T', 'H', 'HQ', 'D', 'scale'),
35
+ [
36
+ pytest.param(*test, id="B{}-T{}-H{}-HQ{}-D{}-scale{}".format(*test))
37
+ for test in [
38
+ (1, 63, 1, 1, 64, 1.0),
39
+ (3, 111, 2, 2, 100, 1.0),
40
+ (3, 1024, 2, 8, 60, 0.1),
41
+ (3, 1024, 2, 8, 128, 0.1),
42
+ (4, 2048, 2, 8, 64, 0.1),
43
+ ]
44
+ ],
45
+ )
46
+ def test_parallel(
47
+ B: int,
48
+ T: int,
49
+ H: int,
50
+ HQ: int,
51
+ D: int,
52
+ scale: float,
53
+ ):
54
+ torch.manual_seed(42)
55
+ dtype = torch.float16
56
+ if not check_shared_mem('hopper') and D > 128:
57
+ # maybe we can enable this test on Triton 3.3.0
58
+ pytest.skip("Skipping test because global shared memory is not available")
59
+
60
+ q = torch.randn((B, T, HQ, D), dtype=dtype, device=device).requires_grad_(True)
61
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
62
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
63
+
64
+ g = torch.randn((B, T, HQ), dtype=dtype, device=device).uniform_(-0.1, -0.01).requires_grad_(True)
65
+
66
+ do = torch.randn((B, T, HQ, D), dtype=dtype, device=device)
67
+ ref = naive_forgetting_attn(q, k, v, g, scale)
68
+ ref.backward(do)
69
+ ref_dq, q.grad = q.grad.clone(), None
70
+ ref_dk, k.grad = k.grad.clone(), None
71
+ ref_dv, v.grad = v.grad.clone(), None
72
+ ref_dg, g.grad = g.grad.clone(), None
73
+
74
+ tri = parallel_forgetting_attn(q=q, k=k, v=v, g=g, scale=scale)
75
+ tri.backward(do)
76
+ tri_dq, q.grad = q.grad.clone(), None
77
+ tri_dk, k.grad = k.grad.clone(), None
78
+ tri_dv, v.grad = v.grad.clone(), None
79
+ tri_dg, g.grad = g.grad.clone(), None
80
+
81
+ assert_close(" o", ref, tri, 0.005)
82
+ assert_close("dq", ref_dq, tri_dq, 0.005)
83
+ assert_close("dk", ref_dk, tri_dk, 0.005)
84
+ assert_close("dv", ref_dv, tri_dv, 0.005)
85
+ assert_close("dg", ref_dg, tri_dg, 0.005)
86
+
87
+
88
+ @pytest.mark.parametrize(
89
+ ('H', 'HQ', 'D', 'cu_seqlens'),
90
+ [
91
+ pytest.param(*test, id="H{}-HQ{}-D{}-cu_seqlens{}".format(*test))
92
+ for test in [
93
+ (2, 2, 64, [0, 15]),
94
+ (2, 8, 64, [0, 256, 500, 1000]),
95
+ (2, 2, 100, [0, 15, 100, 300, 1200, 2000]),
96
+ ]
97
+ ],
98
+ )
99
+ @pytest.mark.skipif(
100
+ is_intel_alchemist,
101
+ reason="Intel Triton Failure",
102
+ )
103
+ def test_parallel_varlen(
104
+ H: int,
105
+ HQ: int,
106
+ D: int,
107
+ cu_seqlens: list[int],
108
+ ):
109
+ torch.manual_seed(42)
110
+ T = cu_seqlens[-1]
111
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
112
+ dtype = torch.float16
113
+ # seq-first required for inputs with variable lengths
114
+ q = torch.randn((1, T, HQ, D), dtype=dtype, device=device).requires_grad_()
115
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
116
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
117
+ g = torch.rand((1, T, HQ), dtype=dtype, device=device).uniform_(-0.1, -0.01).requires_grad_(True)
118
+ do = torch.randn((1, T, HQ, D), dtype=dtype, device=device)
119
+
120
+ ref = q.new_empty(1, T, HQ, D)
121
+ for bos, eos in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False):
122
+ ref[:, bos:eos] = naive_forgetting_attn(
123
+ q=q[:, bos:eos],
124
+ k=k[:, bos:eos],
125
+ v=v[:, bos:eos],
126
+ g=g[:, bos:eos],
127
+ )
128
+ ref.backward(do)
129
+ ref_dq, q.grad = q.grad.clone(), None
130
+ ref_dk, k.grad = k.grad.clone(), None
131
+ ref_dv, v.grad = v.grad.clone(), None
132
+ ref_dg, g.grad = g.grad.clone(), None
133
+
134
+ tri = parallel_forgetting_attn(
135
+ q=q,
136
+ k=k,
137
+ v=v,
138
+ g=g,
139
+ cu_seqlens=cu_seqlens,
140
+ )
141
+ tri.backward(do)
142
+ tri_dq, q.grad = q.grad.clone(), None
143
+ tri_dk, k.grad = k.grad.clone(), None
144
+ tri_dv, v.grad = v.grad.clone(), None
145
+ tri_dg, g.grad = g.grad.clone(), None
146
+
147
+ assert_close(" o", ref, tri, 0.004)
148
+ assert_close(" dq", ref_dq.squeeze(), tri_dq.squeeze(), 0.005)
149
+ assert_close(" dk", ref_dk.squeeze(), tri_dk.squeeze(), 0.005)
150
+ assert_close(" dv", ref_dv.squeeze(), tri_dv.squeeze(), 0.005)
151
+ assert_close(" dg", ref_dg.squeeze(), tri_dg.squeeze(), 0.005)
code/flash-linear-attention/tests/ops/test_gated_delta.py ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from einops import rearrange, repeat
8
+
9
+ from fla.ops.gated_delta_rule import chunk_gated_delta_rule, fused_recurrent_gated_delta_rule
10
+ from fla.utils import assert_close, device, is_intel_alchemist
11
+
12
+
13
+ def recurrent_gated_delta_rule_ref(
14
+ q: torch.Tensor,
15
+ k: torch.Tensor,
16
+ v: torch.Tensor,
17
+ beta: torch.Tensor,
18
+ g: torch.Tensor,
19
+ scale: float = None,
20
+ initial_state: torch.Tensor = None,
21
+ output_final_state: bool = False,
22
+ ):
23
+ q, k, v, beta, g = map(lambda x: x.transpose(1, 2).contiguous().to(torch.float32), [q, k, v, beta, g])
24
+ B, H, T, K, V = *k.shape, v.shape[-1]
25
+ o = torch.zeros(B, H, T, V).to(v)
26
+ h = torch.zeros(B, H, K, V).to(v)
27
+ if initial_state is not None:
28
+ h = initial_state
29
+ if scale is None:
30
+ scale = 1 / (q.shape[-1] ** 0.5)
31
+ q = q * scale
32
+ for i in range(T):
33
+ b_q = q[:, :, i]
34
+ b_k = k[:, :, i]
35
+ b_v = v[:, :, i].clone()
36
+ h = h.clone() * g[:, :, i].exp()[..., None, None]
37
+ b_beta = beta[:, :, i]
38
+ b_v = b_v - (h.clone() * b_k[..., None]).sum(-2)
39
+ b_v = b_v * b_beta[..., None]
40
+ h = h.clone() + b_k.unsqueeze(-1) * b_v.unsqueeze(-2)
41
+ o[:, :, i] = torch.einsum('bhd,bhdm->bhm', b_q, h)
42
+ if not output_final_state:
43
+ h = None
44
+ o = o.transpose(1, 2).contiguous()
45
+ return o, h
46
+
47
+
48
+ def chunk_gated_delta_rule_ref(
49
+ q: torch.Tensor,
50
+ k: torch.Tensor,
51
+ v: torch.Tensor,
52
+ g: torch.Tensor,
53
+ beta: torch.Tensor,
54
+ chunk_size: int = 64,
55
+ scale: float = None,
56
+ initial_state: torch.Tensor = None,
57
+ output_final_state: bool = False,
58
+ ):
59
+ BT = chunk_size
60
+ if scale is None:
61
+ scale = 1 / (q.shape[-1] ** 0.5)
62
+ # Calculate padding needed to make T a multiple of BT
63
+ q, k, v, beta, g = map(lambda x: x.transpose(1, 2).contiguous().to(torch.float32), [q, k, v, beta, g])
64
+
65
+ T = q.shape[-2]
66
+ pad_len = (BT - (T % BT)) % BT
67
+ if pad_len > 0:
68
+ # Pad all tensors
69
+ q = F.pad(q, (0, 0, 0, pad_len))
70
+ k = F.pad(k, (0, 0, 0, pad_len))
71
+ v = F.pad(v, (0, 0, 0, pad_len))
72
+ beta = F.pad(beta, (0, pad_len))
73
+ g = F.pad(g, (0, pad_len))
74
+ q, k, v, beta, g = map(lambda x: x.to(torch.float32), [q, k, v, beta, g])
75
+ decay = g
76
+ chunk_size = BT
77
+ b, h, l, d_k = q.shape
78
+ d_v = v.shape[-1]
79
+ q = q * scale
80
+ v = v * beta[..., None]
81
+ k_beta = k * beta[..., None]
82
+ assert l % chunk_size == 0
83
+ # note that diagonal is masked.
84
+ mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0)
85
+ q, k, v, k_beta, decay = map(
86
+ lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size),
87
+ [q, k, v, k_beta, decay.unsqueeze(-1)],
88
+ )
89
+ decay = decay.squeeze(-1).cumsum(-1)
90
+ decay_exp = decay.exp()[..., None]
91
+ L_mask = ((decay.unsqueeze(-1) - decay.unsqueeze(-2)).tril().exp().float()).tril()
92
+ attn = -((k_beta @ k.transpose(-1, -2)) * L_mask).masked_fill(mask, 0)
93
+ for i in range(1, chunk_size):
94
+ attn[..., i, :i] = attn[..., i, :i].clone() + (attn[..., i, :i, None].clone() * attn[..., :i, :i].clone()).sum(-2)
95
+ attn = attn + torch.eye(chunk_size, dtype=torch.float, device=q.device)
96
+ attn = attn
97
+ k_cumsum = attn @ v
98
+ k_cumdecay = attn @ (k_beta * decay_exp)
99
+ v = k_cumsum
100
+ S = k.new_zeros(b, h, d_k, d_v)
101
+ if initial_state is not None:
102
+ S = initial_state
103
+ o = torch.zeros_like(v)
104
+ mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1)
105
+ for i in range(0, l // chunk_size):
106
+ q_i, k_i, v_i = q[:, :, i], k[:, :, i], v[:, :, i]
107
+ attn = (q_i @ k_i.transpose(-1, -2) * L_mask[:, :, i]).masked_fill_(mask, 0)
108
+ v_prime = (k_cumdecay[:, :, i]) @ S
109
+ v_new = v_i - v_prime
110
+ o_inter = (q_i * decay[:, :, i, :, None].exp()) @ S
111
+ o[:, :, i] = o_inter + attn @ v_new
112
+ S = S * decay[:, :, i, -1, None, None].exp() + (k_i * (decay[:, :, i, -1, None] - decay[:, :, i]).exp()
113
+ [..., None]).transpose(-1, -2) @ v_new
114
+ if not output_final_state:
115
+ S = None
116
+ # unpad
117
+ o = rearrange(o, 'b h n c d -> b h (n c) d')
118
+ o = o[:, :, :T]
119
+ o = o.transpose(1, 2)
120
+ return o, S
121
+
122
+
123
+ @pytest.mark.parametrize(
124
+ ('B', 'T', 'H', 'HV', 'D', 'scale', 'gate_logit_normalizer', 'dtype'),
125
+ [
126
+ pytest.param(*test, id="B{}-T{}-H{}-HV{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test))
127
+ for test in [
128
+ (1, 63, 1, 1, 64, 1, 1, torch.float),
129
+ (2, 500, 4, 4, 60, 1, 1, torch.float),
130
+ (2, 1000, 2, 8, 128, 1, 0.1, torch.float),
131
+ (3, 1024, 2, 2, 128, 0.1, 1, torch.float),
132
+ (4, 1024, 3, 3, 128, 1, 10, torch.float),
133
+ (4, 2048, 4, 4, 64, 0.1, 1, torch.float),
134
+ (2, 1024, 4, 4, 128, 1, 0.1, torch.float16),
135
+ (2, 1024, 4, 8, 128, 1, 10, torch.float16),
136
+ ]
137
+ ],
138
+ )
139
+ def test_fused_recurrent(
140
+ B: int,
141
+ T: int,
142
+ H: int,
143
+ HV: int,
144
+ D: int,
145
+ scale: float,
146
+ gate_logit_normalizer: float,
147
+ dtype: torch.dtype,
148
+ ):
149
+ torch.manual_seed(42)
150
+ q = torch.randn(B, T, H, D, dtype=torch.float32)
151
+ k = torch.randn(B, T, H, D, dtype=torch.float32)
152
+ v = torch.randn(B, T, HV, D, dtype=dtype)
153
+ beta = torch.rand(B, T, HV, dtype=dtype).sigmoid()
154
+ g = F.logsigmoid(torch.rand(B, T, HV, dtype=torch.float32))
155
+ g = g / gate_logit_normalizer
156
+ h0 = torch.randn(B, HV, D, D, dtype=torch.float32)
157
+ q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, g, h0))
158
+ ref, ref_ht = recurrent_gated_delta_rule_ref(
159
+ q=F.normalize(repeat(q.clone(), 'b t h d -> b t (h g) d', g=HV // H), p=2, dim=-1).to(dtype),
160
+ k=F.normalize(repeat(k.clone(), 'b t h d -> b t (h g) d', g=HV // H), p=2, dim=-1).to(dtype),
161
+ v=v.clone(),
162
+ beta=beta.clone(),
163
+ g=g.clone(),
164
+ scale=scale,
165
+ initial_state=h0.clone(),
166
+ output_final_state=True,
167
+ )
168
+ tri, tri_ht = fused_recurrent_gated_delta_rule(
169
+ q=q.clone(),
170
+ k=k.clone(),
171
+ v=v.clone(),
172
+ beta=beta.clone(),
173
+ g=g.clone(),
174
+ scale=scale,
175
+ initial_state=h0.clone(),
176
+ use_qk_l2norm_in_kernel=True,
177
+ output_final_state=True,
178
+ )
179
+ assert_close('o', ref, tri, 0.002)
180
+ assert_close('ht', ref_ht, tri_ht, 0.002)
181
+
182
+
183
+ @pytest.mark.parametrize(
184
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'mask_p', 'use_qk_l2norm_in_kernel', 'dtype'),
185
+ [
186
+ pytest.param(
187
+ *test,
188
+ id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-mask_p{}-use_qk_l2norm_in_kernel{}-{}".format(*test),
189
+ )
190
+ for test in [
191
+ (1, 63, 1, 64, 1, 1, 0, False, torch.float16),
192
+ (2, 500, 3, 60, 1, 1, 0, False, torch.float16),
193
+ (2, 1000, 3, 64, 0.1, 1, 0.5, False, torch.float16),
194
+ (3, 1024, 4, 100, 1, 0.1, 0, False, torch.float16),
195
+ (4, 1024, 4, 128, 0.1, 1, 0, False, torch.float16),
196
+ (4, 1024, 4, 128, 0.1, 1, 0, True, torch.float16),
197
+ (2, 1500, 4, 128, 0.1, 10, 0, False, torch.float16),
198
+ (4, 2048, 8, 64, 0.1, 1, 0, False, torch.float16),
199
+ ]
200
+ ],
201
+ )
202
+ def test_chunk(
203
+ B: int,
204
+ T: int,
205
+ H: int,
206
+ D: int,
207
+ scale: float,
208
+ gate_logit_normalizer: float,
209
+ mask_p: float,
210
+ use_qk_l2norm_in_kernel: bool,
211
+ dtype: torch.dtype,
212
+ ):
213
+ torch.manual_seed(42)
214
+ if is_intel_alchemist and D > 128:
215
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
216
+
217
+ q = torch.rand(B, T, H, D, dtype=dtype)
218
+ k = torch.rand(B, T, H, D, dtype=dtype)
219
+ v = torch.rand(B, T, H, D, dtype=dtype)
220
+ beta = torch.rand(B, T, H, dtype=dtype).sigmoid()
221
+ g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.float32))
222
+ g = g / gate_logit_normalizer
223
+ g = g * (torch.rand_like(g) > mask_p)
224
+ h0 = torch.zeros(B, H, D, D, dtype=torch.float32)
225
+ q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, beta, g, h0))
226
+
227
+ tri, tri_ht = chunk_gated_delta_rule(
228
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
229
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
230
+ v=v.clone(),
231
+ g=g.clone(),
232
+ beta=beta.clone(),
233
+ scale=scale,
234
+ initial_state=h0.clone(),
235
+ output_final_state=True,
236
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
237
+ )
238
+ do = torch.randn_like(v)
239
+ dht = torch.randn_like(h0)
240
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
241
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
242
+ q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None
243
+
244
+ ref, ref_ht = recurrent_gated_delta_rule_ref(
245
+ q=F.normalize(q.clone(), p=2, dim=-1),
246
+ k=F.normalize(k.clone(), p=2, dim=-1),
247
+ v=v.clone(),
248
+ beta=beta.clone(),
249
+ g=g.clone(),
250
+ scale=scale,
251
+ output_final_state=True,
252
+ initial_state=h0.clone(),
253
+ )
254
+
255
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
256
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
257
+ assert_close('o', ref, tri, 0.005)
258
+ assert_close('ht', ref_ht, tri_ht, 0.005)
259
+ assert_close('dq', ref_dq, tri_dq, 0.008)
260
+ assert_close('dk', ref_dk, tri_dk, 0.008)
261
+ assert_close('dv', ref_dv, tri_dv, 0.008)
262
+ assert_close('db', ref_dbeta, tri_dbeta, 0.02)
263
+ assert_close('dg', ref_dg, tri_dg, 0.02)
264
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
265
+
266
+
267
+ @pytest.mark.parametrize(
268
+ ('H', 'D', 'mask_p', 'cu_seqlens', 'dtype'),
269
+ [
270
+ pytest.param(*test, id="H{}-D{}-mask_p{}-cu_seqlens{}-{}".format(*test))
271
+ for test in [
272
+ (4, 60, 0, [0, 15], torch.float16),
273
+ (4, 64, 0, [0, 256, 500, 1000], torch.float16),
274
+ (4, 64, 0.5, [0, 256, 500, 1000], torch.float16),
275
+ (4, 100, 0, [0, 15, 100, 300, 1200, 2000], torch.float16),
276
+ ]
277
+ ],
278
+ )
279
+ @pytest.mark.skipif(
280
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
281
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
282
+ )
283
+ def test_chunk_varlen(
284
+ H: int,
285
+ D: int,
286
+ mask_p: float,
287
+ cu_seqlens: list[int],
288
+ dtype: torch.dtype,
289
+ ):
290
+ if is_intel_alchemist and D > 128:
291
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
292
+ torch.manual_seed(42)
293
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
294
+ # randomly split the sequence into N segments
295
+ cu_seqlens = torch.LongTensor(cu_seqlens).to(device)
296
+ T = cu_seqlens[-1]
297
+ N = len(cu_seqlens) - 1
298
+
299
+ # seq-first required for inputs with variable lengths
300
+ q = torch.randn((1, T, H, D), dtype=dtype)
301
+ k = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
302
+ v = torch.randn((1, T, H, D), dtype=dtype)
303
+ g = F.logsigmoid(torch.rand(1, T, H, dtype=dtype))
304
+ g = g * (torch.rand_like(g) > mask_p)
305
+ beta = torch.rand(1, T, H, dtype=dtype).sigmoid()
306
+ h0 = torch.randn((N, H, D, D), dtype=dtype)
307
+
308
+ q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, g, h0))
309
+ do = torch.randn_like(v)
310
+ dht = torch.rand_like(h0)
311
+
312
+ tri, tri_ht = chunk_gated_delta_rule(
313
+ q=q.clone(),
314
+ k=k.clone(),
315
+ v=v.clone(),
316
+ beta=beta.clone(),
317
+ g=g.clone(),
318
+ initial_state=h0.clone(),
319
+ output_final_state=True,
320
+ cu_seqlens=cu_seqlens,
321
+ )
322
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
323
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
324
+ q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None
325
+
326
+ ref = []
327
+ ref_ht = []
328
+ for i in range(N):
329
+ ref_i, ref_ht_i = recurrent_gated_delta_rule_ref(
330
+ q=q[:, cu_seqlens[i]:cu_seqlens[i+1]],
331
+ k=k[:, cu_seqlens[i]:cu_seqlens[i+1]],
332
+ v=v[:, cu_seqlens[i]:cu_seqlens[i+1]],
333
+ beta=beta[:, cu_seqlens[i]:cu_seqlens[i+1]],
334
+ g=g[:, cu_seqlens[i]:cu_seqlens[i+1]],
335
+ initial_state=h0[i],
336
+ output_final_state=True,
337
+ )
338
+ ref.append(ref_i)
339
+ ref_ht.append(ref_ht_i)
340
+ ref = torch.cat(ref, 1)
341
+ ref_ht = torch.cat(ref_ht, 0)
342
+
343
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
344
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
345
+
346
+ assert_close('o', ref, tri, 0.005)
347
+ assert_close('ht', ref_ht, tri_ht, 0.005)
348
+ assert_close('dq', ref_dq, tri_dq, 0.007)
349
+ assert_close('dk', ref_dk, tri_dk, 0.008)
350
+ assert_close('dv', ref_dv, tri_dv, 0.007)
351
+ assert_close('db', ref_dbeta, tri_dbeta, 0.015)
352
+ assert_close('dg', ref_dg, tri_dg, 0.015)
353
+ assert_close('dh0', ref_dh0, tri_dh0, 0.007)
code/flash-linear-attention/tests/ops/test_gated_delta_product.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.gated_delta_product import chunk_gated_delta_product
9
+ from fla.ops.gated_delta_product.chunk_ref import chunk_gated_delta_product_ref
10
+ from fla.ops.gated_delta_product.naive import naive_recurrent_gated_delta_product
11
+ from fla.utils import assert_close, device, is_intel_alchemist
12
+
13
+
14
+ @pytest.mark.parametrize(
15
+ ('B', 'T', 'H', 'D', 'scale', 'num_householder', 'gate_logit_normalizer', 'mask_p', 'use_qk_l2norm_in_kernel', 'dtype'),
16
+ [
17
+ pytest.param(
18
+ *test,
19
+ id="B{}-T{}-H{}-D{}-scale{}-num_householder{}-gate_logit_normalizer{}-mask_p{}-l2norm{}-{}".format(*test),
20
+ )
21
+ for test in [
22
+ (1, 63, 1, 64, 0.1, 1, 1, 0, False, torch.float16),
23
+ (2, 200, 3, 60, 0.1, 1, 1, 0, False, torch.float16),
24
+ (2, 1000, 4, 64, 0.1, 2, 0.1, 0.5, False, torch.float16),
25
+ (2, 1024, 4, 64, 1, 2, 1, 0, True, torch.float16),
26
+ (2, 1024, 6, 100, 1, 2, 10, 0, False, torch.float16),
27
+ (4, 1500, 8, 128, 0.1, 3, 1, 0.5, False, torch.float16),
28
+ (2, 2048, 8, 128, 1, 3, 1, 0, False, torch.float16),
29
+ (2, 2048, 8, 128, 1, 3, 1, 0, True, torch.float16),
30
+ ]
31
+ ],
32
+ )
33
+ def test_chunk(
34
+ B: int,
35
+ T: int,
36
+ H: int,
37
+ D: int,
38
+ scale: float,
39
+ num_householder: int,
40
+ gate_logit_normalizer: float,
41
+ mask_p: float,
42
+ use_qk_l2norm_in_kernel: bool,
43
+ dtype: torch.dtype,
44
+ ):
45
+ if is_intel_alchemist and D > 128:
46
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
47
+
48
+ q = torch.randn(B, T, H, D, dtype=dtype)
49
+ k = torch.randn(B, T * num_householder, H, D, dtype=dtype)
50
+ v = torch.randn(B, T * num_householder, H, D, dtype=dtype)
51
+ beta = torch.rand(B, T * num_householder, H, dtype=dtype).sigmoid()
52
+ g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.float32))
53
+ h0 = torch.zeros(B, H, D, D, dtype=torch.float32)
54
+ g = g / gate_logit_normalizer
55
+ g = g * (torch.rand_like(g) > mask_p)
56
+ q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, beta, g, h0))
57
+
58
+ tri, tri_ht = chunk_gated_delta_product(
59
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
60
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
61
+ v=v.clone(),
62
+ g=g.clone(),
63
+ beta=beta.clone(),
64
+ num_householder=num_householder,
65
+ scale=scale,
66
+ output_final_state=True,
67
+ initial_state=h0.clone(),
68
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
69
+ )
70
+ do = torch.randn_like(q)
71
+ dht = torch.randn_like(h0)
72
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
73
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
74
+ q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None
75
+
76
+ ref, ref_ht = chunk_gated_delta_product_ref(
77
+ q=F.normalize(q.clone(), p=2, dim=-1),
78
+ k=F.normalize(k.clone(), p=2, dim=-1),
79
+ v=v.clone(),
80
+ g=g.clone(),
81
+ beta=beta.clone(),
82
+ num_householder=num_householder,
83
+ scale=scale,
84
+ initial_state=h0.clone(),
85
+ output_final_state=True,
86
+ )
87
+
88
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
89
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
90
+ assert_close('o', ref, tri, 0.005)
91
+ assert_close('ht', ref_ht, tri_ht, 0.005)
92
+ assert_close('dq', ref_dq, tri_dq, 0.008)
93
+ assert_close('dk', ref_dk, tri_dk, 0.008)
94
+ assert_close('dv', ref_dv, tri_dv, 0.008)
95
+ assert_close('db', ref_dbeta, tri_dbeta, 0.02)
96
+ assert_close('dg', ref_dg, tri_dg, 0.02)
97
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
98
+
99
+
100
+ @pytest.mark.parametrize(
101
+ ('H', 'D', 'num_householder', 'mask_p', 'cu_seqlens', 'dtype'),
102
+ [
103
+ pytest.param(*test, id="H{}-D{}-num_householder{}-mask_p{}-cu_seqlens{}-{}".format(*test))
104
+ for test in [
105
+ (2, 64, 3, 0, [0, 63], torch.float16),
106
+ (2, 100, 2, 0, [0, 63, 100, 500, 1000], torch.float16),
107
+ (2, 100, 2, 0, [0, 100, 256, 512, 1500, 1500], torch.float16),
108
+ (2, 128, 2, 0, [0, 100, 300, 800, 1500, 2000], torch.float16),
109
+ (2, 128, 2, 0.5, [0, 31, 111, 799, 1000, 1500, 1800, 2000], torch.float16),
110
+ (2, 128, 2, 0.5, [0, 63, 300, 800, 1000, 1399, 2048], torch.float16),
111
+ (2, 256, 3, 0, [0, 100, 123, 300, 500, 800, 1000, 1500, 2048], torch.float16),
112
+ ]
113
+ ],
114
+ )
115
+ def test_chunk_varlen(
116
+ H: int,
117
+ D: int,
118
+ num_householder: int,
119
+ mask_p: float,
120
+ cu_seqlens: list[int],
121
+ dtype: torch.dtype,
122
+ ):
123
+ if is_intel_alchemist and D > 128:
124
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
125
+ torch.manual_seed(42)
126
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
127
+ cu_seqlens = torch.LongTensor(cu_seqlens).to(device)
128
+ T = cu_seqlens[-1]
129
+ N = len(cu_seqlens) - 1
130
+
131
+ q = torch.nn.functional.normalize(torch.randn((1, T, H, D), dtype=dtype), dim=-1, p=2)
132
+ k = torch.nn.functional.normalize(torch.randn(1, T*num_householder, H, D, dtype=dtype), dim=-1, p=2)
133
+ v = torch.randn((1, T*num_householder, H, D), dtype=dtype)
134
+ g = F.logsigmoid(torch.rand(1, T, H, dtype=dtype))
135
+ g = g * (torch.rand_like(g) > mask_p)
136
+ beta = torch.rand(1, T*num_householder, H, dtype=dtype).sigmoid()
137
+ h0 = torch.randn((N, H, D, D), dtype=dtype)
138
+
139
+ q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, g, h0))
140
+ do = torch.randn_like(q)
141
+ dht = torch.rand_like(h0)
142
+ scale = D ** -0.5
143
+
144
+ tri, tri_ht = chunk_gated_delta_product(
145
+ q=q.clone(),
146
+ k=k.clone(),
147
+ v=v.clone(),
148
+ beta=beta.clone(),
149
+ g=g.clone(),
150
+ scale=scale,
151
+ output_final_state=True,
152
+ num_householder=num_householder,
153
+ initial_state=h0.clone(),
154
+ cu_seqlens=cu_seqlens,
155
+ )
156
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
157
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
158
+ q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None
159
+
160
+ ref, ref_ht = chunk_gated_delta_product_ref(
161
+ q=q.clone(),
162
+ k=k.clone(),
163
+ v=v.clone(),
164
+ beta=beta.clone(),
165
+ g=g.clone(),
166
+ scale=scale,
167
+ output_final_state=True,
168
+ num_householder=num_householder,
169
+ initial_state=h0.clone(),
170
+ cu_seqlens=cu_seqlens,
171
+ )
172
+
173
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
174
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad
175
+
176
+ assert_close('o', ref, tri, 0.005)
177
+ assert_close('ht', ref_ht, tri_ht, 0.005)
178
+ assert_close('dq', ref_dq, tri_dq, 0.007)
179
+ assert_close('dk', ref_dk, tri_dk, 0.008)
180
+ assert_close('dv', ref_dv, tri_dv, 0.007)
181
+ assert_close('db', ref_dbeta, tri_dbeta, 0.015)
182
+ assert_close('dh0', ref_dh0, tri_dh0, 0.007)
183
+ assert_close('dg', ref_dg, tri_dg, 0.015)
184
+ q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None
185
+
186
+ torch_ref = torch.zeros_like(ref)
187
+ torch_ref_ht = torch.zeros_like(ref_ht)
188
+ for i in range(len(cu_seqlens) - 1):
189
+ start, end = cu_seqlens[i], cu_seqlens[i+1]
190
+ q_i = q[:, start:end, :, :]
191
+ k_i = k[:, start*num_householder:end*num_householder, :, :]
192
+ v_i = v[:, start*num_householder:end*num_householder, :, :]
193
+ g_i = g[:, start:end, :]
194
+ beta_i = beta[:, start*num_householder:end*num_householder, :]
195
+ o3_i, h3_i = naive_recurrent_gated_delta_product(
196
+ q_i, k_i, v_i, g_i, beta_i, scale=scale, cu_seqlens=None, output_final_state=True, num_householder=num_householder,
197
+ )
198
+ torch_ref[:, start:end, :, :] = o3_i
199
+ torch_ref_ht[i, :, :, :] = h3_i.squeeze(0)
200
+
201
+ ((torch_ref * do).sum() + (torch_ref_ht * dht).sum()).backward(retain_graph=True)
202
+
203
+ assert_close('o', ref, tri, 0.005)
204
+ assert_close('ht', ref_ht, tri_ht, 0.005)
205
+ assert_close('dq', ref_dq, tri_dq, 0.007)
206
+ assert_close('dk', ref_dk, tri_dk, 0.008)
207
+ assert_close('dv', ref_dv, tri_dv, 0.007)
208
+ assert_close('db', ref_dbeta, tri_dbeta, 0.015)
209
+ assert_close('dg', ref_dg, tri_dg, 0.015)
210
+ assert_close('dh0', ref_dh0, tri_dh0, 0.007)
code/flash-linear-attention/tests/ops/test_gla.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.gla import chunk_gla, fused_recurrent_gla
9
+ from fla.ops.gla.naive import naive_recurrent_gla
10
+ from fla.utils import assert_close, device, device_platform
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ ('B', 'T', 'H', 'D', 'gate_logit_normalizer', 'dtype'),
15
+ [
16
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-gate_logit_normalizer{}-{}".format(*test))
17
+ for test in [
18
+ (1, 63, 1, 64, 1, torch.float),
19
+ (2, 1024, 4, 60, 1, torch.float),
20
+ (2, 1024, 8, 128, 0.1, torch.float),
21
+ (2, 1024, 8, 128, 1, torch.float),
22
+ (2, 1024, 8, 128, 10, torch.float),
23
+ (4, 2048, 8, 64, 1, torch.float),
24
+ (2, 1024, 8, 128, 0.1, torch.float16),
25
+ (2, 1024, 8, 128, 10, torch.float16),
26
+ ]
27
+ ],
28
+ )
29
+ @pytest.mark.skipif(
30
+ device_platform == 'intel',
31
+ reason='Intel Triton Failure',
32
+ )
33
+ def test_fused_recurrent(
34
+ B: int,
35
+ T: int,
36
+ H: int,
37
+ D: int,
38
+ gate_logit_normalizer: float,
39
+ dtype: torch.dtype,
40
+ ):
41
+ torch.manual_seed(42)
42
+
43
+ q = torch.rand((B, T, H, D), dtype=dtype, device=device).requires_grad_()
44
+ k = torch.rand((B, T, H, D), dtype=dtype, device=device).requires_grad_()
45
+ v = torch.rand((B, T, H, D), dtype=dtype, device=device).requires_grad_()
46
+ g = (F.logsigmoid(torch.rand((B, T, H, D), dtype=dtype, device=device)) / gate_logit_normalizer).requires_grad_()
47
+ h0 = torch.rand(B, H, D, D, device=device).requires_grad_()
48
+ do = torch.randn_like(v)
49
+ dht = torch.randn((B, H, D, D), dtype=dtype, device=device)
50
+
51
+ ref, ref_ht = naive_recurrent_gla(
52
+ q=q,
53
+ k=k,
54
+ v=v,
55
+ gk=g,
56
+ initial_state=h0,
57
+ output_final_state=True,
58
+ )
59
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
60
+ ref_dq, q.grad = q.grad.clone(), None
61
+ ref_dk, k.grad = k.grad.clone(), None
62
+ ref_dv, v.grad = v.grad.clone(), None
63
+ ref_dg, g.grad = g.grad.clone(), None
64
+ ref_dh0, h0.grad = h0.grad.clone(), None
65
+
66
+ tri, tri_ht = fused_recurrent_gla(
67
+ q=q,
68
+ k=k,
69
+ v=v,
70
+ gk=g,
71
+ initial_state=h0,
72
+ output_final_state=True,
73
+ )
74
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
75
+ tri_dq, q.grad = q.grad.clone(), None
76
+ tri_dk, k.grad = k.grad.clone(), None
77
+ tri_dv, v.grad = v.grad.clone(), None
78
+ tri_dg, g.grad = g.grad.clone(), None
79
+ tri_dh0, h0.grad = h0.grad.clone(), None
80
+
81
+ assert_close('o', ref, tri, 0.005)
82
+ assert_close('ht', ref_ht, tri_ht, 0.005)
83
+ assert_close('dq', ref_dq, tri_dq, 0.005)
84
+ assert_close('dk', ref_dk, tri_dk, 0.005)
85
+ assert_close('dv', ref_dv, tri_dv, 0.005)
86
+ assert_close('dg', ref_dg, tri_dg, 0.005)
87
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
88
+
89
+
90
+ @pytest.mark.parametrize(
91
+ ('H', 'D', 'cu_seqlens', 'dtype'),
92
+ [
93
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
94
+ for test in [
95
+ (4, 64, [0, 15], torch.float),
96
+ (4, 64, [0, 256, 500, 1000], torch.float),
97
+ (4, 100, [0, 15, 100, 300, 1200, 2000], torch.float),
98
+ (4, 64, [0, 1, 100, 300, 1200, 2048], torch.float16),
99
+ (4, 128, [0, 200, 512, 1200, 2048], torch.float16),
100
+ ]
101
+ ],
102
+ )
103
+ @pytest.mark.skipif(
104
+ device_platform == 'intel',
105
+ reason='Intel Triton Failure',
106
+ )
107
+ def test_fused_recurrent_varlen(
108
+ H: int,
109
+ D: int,
110
+ cu_seqlens: list[int],
111
+ dtype: torch.dtype,
112
+ ):
113
+ torch.manual_seed(42)
114
+
115
+ N = len(cu_seqlens) - 1
116
+ T = cu_seqlens[-1]
117
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
118
+
119
+ q = torch.rand((1, T, H, D), dtype=dtype, device=device).requires_grad_()
120
+ k = torch.rand((1, T, H, D), dtype=dtype, device=device).requires_grad_()
121
+ v = torch.rand((1, T, H, D), dtype=dtype, device=device).requires_grad_()
122
+ g = F.logsigmoid(torch.rand((1, T, H, D), dtype=dtype, device=device)).requires_grad_()
123
+ h0 = torch.rand(N, H, D, D, device=device).requires_grad_()
124
+ do = torch.randn_like(v)
125
+ dht = torch.randn((N, H, D, D), dtype=dtype, device=device)
126
+
127
+ refs, ref_hts = [], []
128
+ for i in range(N):
129
+ ref, ref_ht = naive_recurrent_gla(
130
+ q=q[:, cu_seqlens[i]:cu_seqlens[i+1]],
131
+ k=k[:, cu_seqlens[i]:cu_seqlens[i+1]],
132
+ v=v[:, cu_seqlens[i]:cu_seqlens[i+1]],
133
+ gk=g[:, cu_seqlens[i]:cu_seqlens[i+1]],
134
+ initial_state=h0[i],
135
+ output_final_state=True,
136
+ )
137
+ refs.append(ref)
138
+ ref_hts.append(ref_ht)
139
+ ref = torch.cat(refs, dim=1)
140
+ ref_ht = torch.cat(ref_hts, dim=0)
141
+
142
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
143
+ ref_dq, q.grad = q.grad.clone(), None
144
+ ref_dk, k.grad = k.grad.clone(), None
145
+ ref_dv, v.grad = v.grad.clone(), None
146
+ ref_dg, g.grad = g.grad.clone(), None
147
+ ref_dh0, h0.grad = h0.grad.clone(), None
148
+
149
+ tri, tri_ht = fused_recurrent_gla(
150
+ q=q,
151
+ k=k,
152
+ v=v,
153
+ gk=g,
154
+ initial_state=h0,
155
+ output_final_state=True,
156
+ cu_seqlens=cu_seqlens,
157
+ )
158
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
159
+ tri_dq, q.grad = q.grad.clone(), None
160
+ tri_dk, k.grad = k.grad.clone(), None
161
+ tri_dv, v.grad = v.grad.clone(), None
162
+ tri_dg, g.grad = g.grad.clone(), None
163
+ tri_dh0, h0.grad = h0.grad.clone(), None
164
+
165
+ assert_close('o', ref, tri, 0.005)
166
+ assert_close('ht', ref_ht, tri_ht, 0.005)
167
+ assert_close('dq', ref_dq, tri_dq, 0.005)
168
+ assert_close('dk', ref_dk, tri_dk, 0.005)
169
+ assert_close('dv', ref_dv, tri_dv, 0.005)
170
+ assert_close('dg', ref_dg, tri_dg, 0.005)
171
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
172
+
173
+
174
+ @pytest.mark.parametrize(
175
+ ('B', 'T', 'H', 'D', 'gate_logit_normalizer', 'dtype'),
176
+ [
177
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-gate_logit_normalizer{}-{}".format(*test))
178
+ for test in [
179
+ (1, 63, 1, 64, 1, torch.float16),
180
+ (2, 1024, 4, 60, 1, torch.float16),
181
+ (2, 1024, 8, 128, 0.1, torch.float16),
182
+ (2, 1024, 8, 128, 1, torch.float16),
183
+ (2, 1024, 8, 128, 10, torch.float16),
184
+ (4, 2048, 8, 64, 1, torch.float16),
185
+ ]
186
+ ],
187
+ )
188
+ @pytest.mark.skipif(
189
+ device_platform == 'intel',
190
+ reason='Intel Triton Failure',
191
+ )
192
+ def test_chunk(
193
+ B: int,
194
+ T: int,
195
+ H: int,
196
+ D: int,
197
+ dtype: torch.dtype,
198
+ gate_logit_normalizer: float,
199
+ ):
200
+ torch.manual_seed(42)
201
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
202
+ # [B, T, H, D]
203
+ q = torch.rand((B, T, H, D), dtype=dtype, device=device).requires_grad_()
204
+ k = torch.rand((B, T, H, D), dtype=dtype, device=device).requires_grad_()
205
+ v = torch.rand((B, T, H, D), dtype=dtype, device=device).requires_grad_()
206
+ g = (F.logsigmoid(torch.rand((B, T, H, D), dtype=dtype, device=device)) / gate_logit_normalizer).requires_grad_()
207
+ h0 = torch.rand((B, H, D, D), dtype=dtype, device=device).requires_grad_()
208
+ do = torch.randn_like(v)
209
+ dht = torch.randn((B, H, D, D), dtype=dtype, device=device)
210
+
211
+ tri, tri_ht = chunk_gla(
212
+ q=q,
213
+ k=k,
214
+ v=v,
215
+ g=g,
216
+ initial_state=h0,
217
+ output_final_state=True,
218
+ )
219
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
220
+ tri_dq, q.grad = q.grad.clone(), None
221
+ tri_dk, k.grad = k.grad.clone(), None
222
+ tri_dv, v.grad = v.grad.clone(), None
223
+ tri_dg, g.grad = g.grad.clone(), None
224
+ tri_dh0, h0.grad = h0.grad.clone(), None
225
+
226
+ ref, ref_ht = fused_recurrent_gla(
227
+ q=q,
228
+ k=k,
229
+ v=v,
230
+ gk=g,
231
+ initial_state=h0,
232
+ output_final_state=True,
233
+ )
234
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
235
+ ref_dq, q.grad = q.grad.clone(), None
236
+ ref_dk, k.grad = k.grad.clone(), None
237
+ ref_dv, v.grad = v.grad.clone(), None
238
+ ref_dg, g.grad = g.grad.clone(), None
239
+ ref_dh0, h0.grad = h0.grad.clone(), None
240
+
241
+ assert_close('o', ref, tri, 0.004)
242
+ assert_close('ht', ref_ht, tri_ht, 0.005)
243
+ assert_close('dq', ref_dq, tri_dq, 0.005)
244
+ assert_close('dk', ref_dk, tri_dk, 0.005)
245
+ assert_close('dv', ref_dv, tri_dv, 0.005)
246
+ assert_close('dg', ref_dg, tri_dg, 0.005)
247
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
248
+
249
+
250
+ @pytest.mark.parametrize(
251
+ ('H', 'D', 'cu_seqlens', 'dtype'),
252
+ [
253
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
254
+ for test in [
255
+ (4, 64, [0, 15], torch.float16),
256
+ (4, 64, [0, 256, 500, 1000], torch.float16),
257
+ (4, 100, [0, 15, 100, 300, 1200, 2000], torch.float16),
258
+ ]
259
+ ],
260
+ )
261
+ @pytest.mark.skipif(
262
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
263
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
264
+ )
265
+ def test_chunk_varlen(
266
+ H: int,
267
+ D: int,
268
+ cu_seqlens: list[int],
269
+ dtype: torch.dtype,
270
+ ):
271
+ torch.manual_seed(42)
272
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
273
+
274
+ N = len(cu_seqlens) - 1
275
+ T = cu_seqlens[-1]
276
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
277
+
278
+ q = torch.rand((1, T, H, D), dtype=dtype, device=device).requires_grad_()
279
+ k = torch.rand((1, T, H, D), dtype=dtype, device=device).requires_grad_()
280
+ v = torch.rand((1, T, H, D), dtype=dtype, device=device).requires_grad_()
281
+ g = F.logsigmoid(torch.rand((1, T, H, D), dtype=dtype, device=device)).requires_grad_()
282
+ h0 = torch.rand((N, H, D, D), dtype=dtype, device=device).requires_grad_()
283
+ do = torch.randn_like(v)
284
+ dht = torch.rand((N, H, D, D), dtype=dtype, device=device)
285
+
286
+ ref, ref_ht = fused_recurrent_gla(
287
+ q=q,
288
+ k=k,
289
+ v=v,
290
+ gk=g,
291
+ initial_state=h0,
292
+ output_final_state=True,
293
+ cu_seqlens=cu_seqlens,
294
+ )
295
+
296
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
297
+ ref_dq, q.grad = q.grad.clone(), None
298
+ ref_dk, k.grad = k.grad.clone(), None
299
+ ref_dv, v.grad = v.grad.clone(), None
300
+ ref_dg, g.grad = g.grad.clone(), None
301
+ ref_dh0, h0.grad = h0.grad.clone(), None
302
+
303
+ tri, tri_ht = chunk_gla(
304
+ q=q,
305
+ k=k,
306
+ v=v,
307
+ g=g,
308
+ initial_state=h0,
309
+ output_final_state=True,
310
+ cu_seqlens=cu_seqlens,
311
+ )
312
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
313
+ tri_dq, q.grad = q.grad.clone(), None
314
+ tri_dk, k.grad = k.grad.clone(), None
315
+ tri_dv, v.grad = v.grad.clone(), None
316
+ tri_dg, g.grad = g.grad.clone(), None
317
+ tri_dh0, h0.grad = h0.grad.clone(), None
318
+
319
+ assert_close('o', ref, tri, 0.004)
320
+ assert_close('ht', ref_ht, tri_ht, 0.005)
321
+ assert_close('dq', ref_dq, tri_dq, 0.005)
322
+ assert_close('dk', ref_dk, tri_dk, 0.005)
323
+ assert_close('dv', ref_dv, tri_dv, 0.005)
324
+ assert_close('dg', ref_dg, tri_dg, 0.005)
325
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
code/flash-linear-attention/tests/ops/test_gsa.py ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.gsa import chunk_gsa, fused_recurrent_gsa
9
+ from fla.ops.gsa.naive import naive_recurrent_gsa
10
+ from fla.utils import assert_close, check_shared_mem, device, device_platform
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ ('B', 'T', 'H', 'D', 'M', 'gate_logit_normalizer', 'dtype'),
15
+ [
16
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-M{}-gate_logit_normalizer{}-{}".format(*test))
17
+ for test in [
18
+ (1, 63, 1, 64, 32, 1, torch.float),
19
+ (2, 1024, 4, 60, 64, 1, torch.float),
20
+ (2, 1024, 8, 128, 64, 0.1, torch.float),
21
+ (2, 1024, 8, 128, 32, 1, torch.float),
22
+ (2, 1024, 8, 128, 64, 1, torch.float),
23
+ (2, 1024, 8, 128, 64, 10, torch.float),
24
+ (4, 2048, 8, 64, 64, 1, torch.float),
25
+ (2, 1024, 8, 128, 64, 0.1, torch.float16),
26
+ (2, 1024, 8, 128, 64, 10, torch.float16),
27
+ ]
28
+ ],
29
+ )
30
+ @pytest.mark.skipif(
31
+ device_platform == 'intel',
32
+ reason='Intel Triton Failure',
33
+ )
34
+ def test_fused_recurrent(
35
+ B: int,
36
+ T: int,
37
+ H: int,
38
+ D: int,
39
+ M: int,
40
+ gate_logit_normalizer: float,
41
+ dtype: torch.dtype,
42
+ ):
43
+ torch.manual_seed(42)
44
+
45
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
46
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
47
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
48
+ s = torch.randn((B, T, H, M), dtype=dtype, device=device).requires_grad_()
49
+ g = (F.logsigmoid(torch.randn((B, T, H, M), dtype=dtype, device=device)) / gate_logit_normalizer).requires_grad_()
50
+ hk0 = torch.randn(B, H, D, M, device=device).requires_grad_()
51
+ hv0 = torch.randn(B, H, M, D, device=device).requires_grad_()
52
+ do = torch.randn_like(v)
53
+ dhkt = torch.randn_like(hk0)
54
+ dhvt = torch.randn_like(hv0)
55
+
56
+ ref, (ref_hkt, ref_hvt) = naive_recurrent_gsa(q, k, v, s, g, initial_state=(hk0, hv0), output_final_state=True)
57
+ ((ref * do).sum() + (ref_hkt * dhkt).sum() + (ref_hvt * dhvt).sum()).backward()
58
+ ref_dq, q.grad = q.grad.clone(), None
59
+ ref_dk, k.grad = k.grad.clone(), None
60
+ ref_dv, v.grad = v.grad.clone(), None
61
+ ref_ds, s.grad = s.grad.clone(), None
62
+ ref_dg, g.grad = g.grad.clone(), None
63
+ ref_dhk0, hk0.grad = hk0.grad.clone(), None
64
+ ref_dhv0, hv0.grad = hv0.grad.clone(), None
65
+
66
+ tri, (tri_hkt, tri_hvt) = fused_recurrent_gsa(
67
+ q=q,
68
+ k=k,
69
+ v=v,
70
+ s=s,
71
+ g=g,
72
+ initial_state=(hk0, hv0),
73
+ output_final_state=True,
74
+ )
75
+ ((tri * do).sum() + (tri_hkt * dhkt).sum() + (tri_hvt * dhvt).sum()).backward()
76
+ tri_dq, q.grad = q.grad.clone(), None
77
+ tri_dk, k.grad = k.grad.clone(), None
78
+ tri_dv, v.grad = v.grad.clone(), None
79
+ tri_ds, s.grad = s.grad.clone(), None
80
+ tri_dg, s.grad = g.grad.clone(), None
81
+ tri_dhk0, hk0.grad = hk0.grad.clone(), None
82
+ tri_dhv0, hv0.grad = hv0.grad.clone(), None
83
+
84
+ assert_close('o', ref, tri, 0.005)
85
+ assert_close('hkt', ref_hkt, tri_hkt, 0.005)
86
+ assert_close('hvt', ref_hvt, tri_hvt, 0.005)
87
+ assert_close('dq', ref_dq, tri_dq, 0.005)
88
+ assert_close('dk', ref_dk, tri_dk, 0.005)
89
+ assert_close('dv', ref_dv, tri_dv, 0.005)
90
+ assert_close('ds', ref_ds, tri_ds, 0.005)
91
+ assert_close('dg', ref_dg, tri_dg, 0.005)
92
+ assert_close('dhk0', ref_dhk0, tri_dhk0, 0.005)
93
+ assert_close('dhv0', ref_dhv0, tri_dhv0, 0.005)
94
+
95
+
96
+ @pytest.mark.parametrize(
97
+ ('H', 'D', 'M', 'cu_seqlens', 'dtype'),
98
+ [
99
+ pytest.param(*test, id="H{}-D{}-M{}-cu_seqlens{}-{}".format(*test))
100
+ for test in [
101
+ (4, 64, 64, [0, 15], torch.float),
102
+ (4, 64, 64, [0, 256, 500, 1000], torch.float),
103
+ (4, 100, 64, [0, 15, 100, 300, 1200, 2000], torch.float),
104
+ (4, 64, 64, [0, 1, 100, 300, 1200, 2048], torch.float16),
105
+ (4, 128, 64, [0, 200, 512, 1200, 2048], torch.float16),
106
+ ]
107
+ ],
108
+ )
109
+ @pytest.mark.skipif(
110
+ device_platform == 'intel',
111
+ reason='Intel Triton Failure',
112
+ )
113
+ def test_fused_recurrent_varlen(
114
+ H: int,
115
+ D: int,
116
+ M: int,
117
+ cu_seqlens: list[int],
118
+ dtype: torch.dtype,
119
+ ):
120
+ torch.manual_seed(42)
121
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
122
+ N = len(cu_seqlens) - 1
123
+ T = cu_seqlens[-1]
124
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
125
+
126
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
127
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
128
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
129
+ s = torch.randn((1, T, H, M), dtype=dtype, device=device).requires_grad_()
130
+ g = F.logsigmoid(torch.randn((1, T, H, M), dtype=dtype, device=device)).requires_grad_()
131
+ hk0 = torch.randn(N, H, D, M, device=device).requires_grad_()
132
+ hv0 = torch.randn(N, H, M, D, device=device).requires_grad_()
133
+ dhkt = torch.randn(N, H, D, M, device=device).requires_grad_()
134
+ dhvt = torch.randn(N, H, M, D, device=device).requires_grad_()
135
+
136
+ do = torch.randn_like(v)
137
+ refs, ref_hkts, ref_hfts = [], [], []
138
+ for i in range(N):
139
+ ref, (ref_hkt, ref_hvt) = naive_recurrent_gsa(
140
+ q[:, cu_seqlens[i]:cu_seqlens[i+1]],
141
+ k[:, cu_seqlens[i]:cu_seqlens[i+1]],
142
+ v[:, cu_seqlens[i]:cu_seqlens[i+1]],
143
+ s[:, cu_seqlens[i]:cu_seqlens[i+1]],
144
+ g[:, cu_seqlens[i]:cu_seqlens[i+1]],
145
+ initial_state=(hk0[i:i+1], hv0[i:i+1]),
146
+ output_final_state=True,
147
+ )
148
+ refs.append(ref)
149
+ ref_hkts.append(ref_hkt)
150
+ ref_hfts.append(ref_hvt)
151
+ ref = torch.cat(refs, 1)
152
+ ref_hkt = torch.cat(ref_hkts, 0)
153
+ ref_hvt = torch.cat(ref_hfts, 0)
154
+ ((ref * do).sum() + (ref_hkt * dhkt).sum() + (ref_hvt * dhvt).sum()).backward()
155
+ ref_dq, q.grad = q.grad.clone(), None
156
+ ref_dk, k.grad = k.grad.clone(), None
157
+ ref_dv, v.grad = v.grad.clone(), None
158
+ ref_ds, s.grad = s.grad.clone(), None
159
+ ref_dg, g.grad = g.grad.clone(), None
160
+ ref_dhk0, hk0.grad = hk0.grad.clone(), None
161
+ ref_dhv0, hv0.grad = hv0.grad.clone(), None
162
+
163
+ tri, (tri_hkt, tri_hvt) = fused_recurrent_gsa(
164
+ q=q,
165
+ k=k,
166
+ v=v,
167
+ s=s,
168
+ g=g,
169
+ initial_state=(hk0, hv0),
170
+ output_final_state=True,
171
+ cu_seqlens=cu_seqlens,
172
+ )
173
+ ((tri * do).sum() + (tri_hkt * dhkt).sum() + (tri_hvt * dhvt).sum()).backward()
174
+ tri_dq, q.grad = q.grad.clone(), None
175
+ tri_dk, k.grad = k.grad.clone(), None
176
+ tri_dv, v.grad = v.grad.clone(), None
177
+ tri_ds, s.grad = s.grad.clone(), None
178
+ tri_dg, s.grad = g.grad.clone(), None
179
+ tri_dhk0, hk0.grad = hk0.grad.clone(), None
180
+ tri_dhv0, hv0.grad = hv0.grad.clone(), None
181
+
182
+ assert_close('o', ref, tri, 0.005)
183
+ assert_close('hkt', ref_hkt, tri_hkt, 0.005)
184
+ assert_close('hvt', ref_hvt, tri_hvt, 0.005)
185
+ assert_close('dq', ref_dq, tri_dq, 0.005)
186
+ assert_close('dk', ref_dk, tri_dk, 0.005)
187
+ assert_close('dv', ref_dv, tri_dv, 0.005)
188
+ assert_close('ds', ref_ds, tri_ds, 0.005)
189
+ assert_close('dg', ref_dg, tri_dg, 0.005)
190
+ assert_close('dhk0', ref_dhk0, tri_dhk0, 0.005)
191
+ assert_close('dhv0', ref_dhv0, tri_dhv0, 0.005)
192
+
193
+
194
+ @pytest.mark.parametrize(
195
+ ('B', 'T', 'H', 'D', 'M', 'gate_logit_normalizer', 'dtype'),
196
+ [
197
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-M{}-gate_logit_normalizer{}-{}".format(*test))
198
+ for test in [
199
+ (1, 63, 1, 64, 32, 1, torch.float16),
200
+ (2, 1024, 4, 60, 64, 1, torch.float16),
201
+ (2, 1024, 4, 256, 64, 1, torch.float16),
202
+ (2, 1024, 4, 128, 64, 0.1, torch.float),
203
+ (2, 1024, 4, 128, 128, 1, torch.float16),
204
+ (2, 1024, 4, 128, 64, 10, torch.float16),
205
+ ]
206
+ ],
207
+ )
208
+ @pytest.mark.skipif(
209
+ device_platform == 'intel',
210
+ reason='Intel Triton Failure',
211
+ )
212
+ def test_chunk(
213
+ B: int,
214
+ T: int,
215
+ H: int,
216
+ D: int,
217
+ M: int,
218
+ gate_logit_normalizer: float,
219
+ dtype: torch.dtype,
220
+ ):
221
+ if (D > 64 or M > 64) and check_shared_mem('hopper') is False:
222
+ pytest.skip(reason='Current CI do not support this config')
223
+ torch.manual_seed(42)
224
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
225
+
226
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
227
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
228
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
229
+ s = torch.randn((B, T, H, M), dtype=dtype, device=device).requires_grad_()
230
+ g = (F.logsigmoid(torch.randn((B, T, H, M), dtype=dtype, device=device)) / gate_logit_normalizer).requires_grad_()
231
+ hk0 = torch.randn(B, H, D, M, device=device).requires_grad_()
232
+ hv0 = torch.randn(B, H, M, D, device=device).requires_grad_()
233
+ dhkt = torch.randn(B, H, D, M, device=device).requires_grad_()
234
+ dhvt = torch.randn(B, H, M, D, device=device).requires_grad_()
235
+
236
+ do = torch.randn_like(v)
237
+ ref, (ref_hkt, ref_hvt) = fused_recurrent_gsa(
238
+ q=q,
239
+ k=k,
240
+ v=v,
241
+ s=s,
242
+ g=g,
243
+ scale=D**-0.5,
244
+ initial_state=(hk0, hv0),
245
+ output_final_state=True)
246
+ ((ref * do).sum() + (ref_hkt * dhkt).sum() + (ref_hvt * dhvt).sum()).backward()
247
+ ref_dq, q.grad = q.grad.clone(), None
248
+ ref_dk, k.grad = k.grad.clone(), None
249
+ ref_dv, v.grad = v.grad.clone(), None
250
+ ref_ds, s.grad = s.grad.clone(), None
251
+ ref_dg, g.grad = g.grad.clone(), None
252
+ ref_dhk0, hk0.grad = hk0.grad.clone(), None
253
+ ref_dhv0, hv0.grad = hv0.grad.clone(), None
254
+
255
+ tri, (tri_hkt, tri_hvt) = chunk_gsa(
256
+ q=q,
257
+ k=k,
258
+ v=v,
259
+ s=s,
260
+ g=g,
261
+ scale=D**-0.5,
262
+ initial_state=(hk0, hv0),
263
+ output_final_state=True,
264
+ )
265
+ ((tri * do).sum() + (tri_hkt * dhkt).sum() + (tri_hvt * dhvt).sum()).backward()
266
+ tri_dq, q.grad = q.grad.clone(), None
267
+ tri_dk, k.grad = k.grad.clone(), None
268
+ tri_dv, v.grad = v.grad.clone(), None
269
+ tri_ds, s.grad = s.grad.clone(), None
270
+ tri_dg, s.grad = g.grad.clone(), None
271
+ tri_dhk0, hk0.grad = hk0.grad.clone(), None
272
+ tri_dhv0, hv0.grad = hv0.grad.clone(), None
273
+
274
+ assert_close('o', ref, tri, 0.005)
275
+ assert_close('hkt', ref_hkt, tri_hkt, 0.005)
276
+ assert_close('hvt', ref_hvt, tri_hvt, 0.005)
277
+ assert_close('dq', ref_dq, tri_dq, 0.005)
278
+ assert_close('dk', ref_dk, tri_dk, 0.005)
279
+ assert_close('dv', ref_dv, tri_dv, 0.005)
280
+ assert_close('ds', ref_ds, tri_ds, 0.008)
281
+ assert_close('dg', ref_dg, tri_dg, 0.008)
282
+ assert_close('dhk0', ref_dhk0, tri_dhk0, 0.005)
283
+ assert_close('dhv0', ref_dhv0, tri_dhv0, 0.005)
284
+
285
+
286
+ @pytest.mark.parametrize(
287
+ ('H', 'D', 'M', 'cu_seqlens', 'dtype'),
288
+ [
289
+ pytest.param(*test, id="H{}-D{}-M{}-cu_seqlens{}-{}".format(*test))
290
+ for test in [
291
+ (4, 64, 64, [0, 15], torch.float16),
292
+ (4, 64, 64, [0, 256, 500, 1000], torch.float16),
293
+ (4, 100, 64, [0, 15, 100, 300, 1200, 2000], torch.float16),
294
+ ]
295
+ ],
296
+ )
297
+ @pytest.mark.skipif(
298
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
299
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
300
+ )
301
+ @pytest.mark.skipif(
302
+ device_platform == 'intel',
303
+ reason='Intel Triton Failure',
304
+ )
305
+ def test_chunk_varlen(
306
+ H: int,
307
+ D: int,
308
+ M: int,
309
+ cu_seqlens: list[int],
310
+ dtype: torch.dtype,
311
+ ):
312
+ if (D > 64 or M > 64) and check_shared_mem('hopper') is False:
313
+ pytest.skip(reason='Current CI do not support this config')
314
+ torch.manual_seed(42)
315
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
316
+ N = len(cu_seqlens) - 1
317
+ T = cu_seqlens[-1]
318
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
319
+
320
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
321
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
322
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
323
+ s = torch.randn((1, T, H, M), dtype=dtype, device=device).requires_grad_()
324
+ g = F.logsigmoid(torch.randn((1, T, H, M), dtype=dtype, device=device)).requires_grad_()
325
+ hk0 = torch.randn(N, H, D, M, device=device).requires_grad_()
326
+ hv0 = torch.randn(N, H, M, D, device=device).requires_grad_()
327
+ dhkt = torch.randn(N, H, D, M, device=device).requires_grad_()
328
+ dhvt = torch.randn(N, H, M, D, device=device).requires_grad_()
329
+
330
+ do = torch.randn_like(v)
331
+
332
+ ref, (ref_hkt, ref_hvt) = fused_recurrent_gsa(
333
+ q=q,
334
+ k=k,
335
+ v=v,
336
+ s=s,
337
+ g=g,
338
+ scale=D**-0.5,
339
+ initial_state=(hk0, hv0),
340
+ output_final_state=True,
341
+ cu_seqlens=cu_seqlens,
342
+ )
343
+ ((ref * do).sum() + (ref_hkt * dhkt).sum() + (ref_hvt * dhvt).sum()).backward()
344
+ ref_dq, q.grad = q.grad.clone(), None
345
+ ref_dk, k.grad = k.grad.clone(), None
346
+ ref_dv, v.grad = v.grad.clone(), None
347
+ ref_ds, s.grad = s.grad.clone(), None
348
+ ref_dg, g.grad = g.grad.clone(), None
349
+ ref_dhk0, hk0.grad = hk0.grad.clone(), None
350
+ ref_dhv0, hv0.grad = hv0.grad.clone(), None
351
+
352
+ tri, (tri_hkt, tri_hvt) = chunk_gsa(
353
+ q=q,
354
+ k=k,
355
+ v=v,
356
+ s=s,
357
+ g=g,
358
+ scale=D**-0.5,
359
+ initial_state=(hk0, hv0),
360
+ output_final_state=True,
361
+ cu_seqlens=cu_seqlens,
362
+ )
363
+ ((tri * do).sum() + (tri_hkt * dhkt).sum() + (tri_hvt * dhvt).sum()).backward()
364
+ tri_dq, q.grad = q.grad.clone(), None
365
+ tri_dk, k.grad = k.grad.clone(), None
366
+ tri_dv, v.grad = v.grad.clone(), None
367
+ tri_ds, s.grad = s.grad.clone(), None
368
+ tri_dg, g.grad = g.grad.clone(), None
369
+ tri_dhk0, hk0.grad = hk0.grad.clone(), None
370
+ tri_dhv0, hv0.grad = hv0.grad.clone(), None
371
+
372
+ assert_close('o', ref, tri, 0.004)
373
+ assert_close('hkt', ref_hkt, tri_hkt, 0.005)
374
+ assert_close('hvt', ref_hvt, tri_hvt, 0.005)
375
+ assert_close('dq', ref_dq, tri_dq, 0.005)
376
+ assert_close('dk', ref_dk, tri_dk, 0.005)
377
+ assert_close('dv', ref_dv, tri_dv, 0.005)
378
+ assert_close('ds', ref_ds, tri_ds, 0.005)
379
+ assert_close('dg', ref_dg, tri_dg, 0.005)
380
+ assert_close('dhk0', ref_dhk0, tri_dhk0, 0.005)
381
+ assert_close('dhv0', ref_dhv0, tri_dhv0, 0.005)
382
+
383
+
384
+ @pytest.mark.parametrize(
385
+ ('B', 'T', 'HQ', 'H', 'D', 'M', 'dtype'),
386
+ [
387
+ pytest.param(*test, id="B{}-T{}-HQ{}-H{}-D{}-M{}-{}".format(*test))
388
+ for test in [
389
+ (2, 63, 2, 1, 64, 32, torch.float),
390
+ (2, 200, 8, 2, 64, 64, torch.float),
391
+ (2, 256, 16, 4, 128, 64, torch.float),
392
+ ]
393
+ ],
394
+ )
395
+ @pytest.mark.skipif(
396
+ device_platform == 'intel',
397
+ reason='Intel Triton Failure',
398
+ )
399
+ def test_inference(
400
+ B: int,
401
+ T: int,
402
+ HQ: int,
403
+ H: int,
404
+ D: int,
405
+ M: int,
406
+ dtype: torch.dtype,
407
+ ):
408
+ torch.manual_seed(42)
409
+
410
+ q = torch.randn((B, T, HQ, D), dtype=dtype, device=device)
411
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device)
412
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device)
413
+ s = torch.randn((B, T, H, M), dtype=dtype, device=device)
414
+ g = F.logsigmoid(torch.randn((B, T, H, M), dtype=dtype, device=device))
415
+ h0 = (torch.randn(B, H, D, M, dtype=dtype, device=device),
416
+ torch.randn(B, H, M, D, dtype=dtype, device=device))
417
+
418
+ ref, _ = naive_recurrent_gsa(q, k, v, s, g, initial_state=h0)
419
+ tri = torch.empty_like(ref)
420
+ for i in range(T):
421
+ o, ht = fused_recurrent_gsa(
422
+ q[:, i:i+1],
423
+ k[:, i:i+1],
424
+ v[:, i:i+1],
425
+ s[:, i:i+1],
426
+ g[:, i:i+1],
427
+ initial_state=h0,
428
+ output_final_state=True,
429
+ )
430
+ tri[:, i] = o.squeeze(1)
431
+ assert_close(f'o{i}', ref[:, i], tri[:, i], 0.005)
432
+ h0 = ht
code/flash-linear-attention/tests/ops/test_hgrn.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.hgrn import chunk_hgrn, fused_recurrent_hgrn
9
+ from fla.ops.hgrn.naive import naive_recurrent_hgrn
10
+ from fla.utils import assert_close, device
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ ('B', 'T', 'D', 'dtype'),
15
+ [
16
+ pytest.param(*test, id="B{}-T{}-D{}-{}".format(*test))
17
+ for test in [
18
+ (1, 63, 500, torch.float),
19
+ (2, 1024, 500, torch.float),
20
+ (2, 1024, 512, torch.float),
21
+ (2, 1024, 1000, torch.float),
22
+ (4, 2048, 2048, torch.float),
23
+ ]
24
+ ],
25
+ )
26
+ def test_fused_recurrent(
27
+ B: int,
28
+ T: int,
29
+ D: int,
30
+ dtype: torch.dtype,
31
+ ):
32
+ torch.manual_seed(42)
33
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
34
+
35
+ x = torch.randn((B, T, D), dtype=dtype, device=device)
36
+ g = torch.randn((B, T, D), dtype=dtype, device=device)
37
+ h0 = torch.randn_like(x[:, 0])
38
+ x, g = (1 - g.sigmoid()) * x, F.logsigmoid(g)
39
+ x, g, h0 = (i.detach().clone().to(dtype).requires_grad_() for i in (x, g, h0))
40
+
41
+ do = torch.randn_like(x)
42
+ dht = torch.randn_like(h0)
43
+ ref, ref_ht = naive_recurrent_hgrn(x, g, h0, output_final_state=True)
44
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
45
+ ref_dx, x.grad = x.grad.clone(), None
46
+ ref_dg, g.grad = g.grad.clone(), None
47
+ ref_dh0, h0.grad = h0.grad.clone(), None
48
+
49
+ tri, tri_ht = fused_recurrent_hgrn(x, g, h0, output_final_state=True)
50
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
51
+ tri_dx, x.grad = x.grad.clone(), None
52
+ tri_dg, g.grad = g.grad.clone(), None
53
+ tri_dh0, h0.grad = h0.grad.clone(), None
54
+
55
+ assert_close('o', ref, tri, 0.005)
56
+ assert_close('ht', ref_ht, tri_ht, 0.005)
57
+ assert_close('dx', ref_dx, tri_dx, 0.005)
58
+ assert_close('dg', ref_dg, tri_dg, 0.005)
59
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
60
+
61
+
62
+ @pytest.mark.parametrize(
63
+ ('D', 'cu_seqlens', 'dtype'),
64
+ [
65
+ pytest.param(*test, id="D{}-cu_seqlens{}-{}".format(*test))
66
+ for test in [
67
+ (500, [0, 15], torch.float),
68
+ (512, [0, 256, 500, 1000], torch.float),
69
+ (1000, [0, 15, 100, 300, 1200, 2000], torch.float),
70
+ (2048, [0, 200, 512, 1200, 2048], torch.float16),
71
+ ]
72
+ ],
73
+ )
74
+ def test_fused_recurrent_varlen(
75
+ D: int,
76
+ cu_seqlens: list[int],
77
+ dtype: torch.dtype,
78
+ ):
79
+ torch.manual_seed(42)
80
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
81
+
82
+ N = len(cu_seqlens) - 1
83
+ T = cu_seqlens[-1]
84
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
85
+
86
+ x = torch.randn((1, T, D), dtype=dtype, device=device)
87
+ g = torch.randn((1, T, D), dtype=dtype, device=device)
88
+ h0 = torch.randn(N, D, dtype=dtype, device=device)
89
+ x, g = (1 - g.sigmoid()) * x, F.logsigmoid(g)
90
+ x, g, h0 = (i.detach().clone().to(dtype).requires_grad_() for i in (x, g, h0))
91
+
92
+ do = torch.randn_like(x)
93
+ dht = torch.randn_like(h0)
94
+ refs, ref_hts = [], []
95
+ for i in range(N):
96
+ ref, ref_ht = naive_recurrent_hgrn(
97
+ x[:, cu_seqlens[i]:cu_seqlens[i+1]],
98
+ g[:, cu_seqlens[i]:cu_seqlens[i+1]],
99
+ h0[i:i+1],
100
+ output_final_state=True,
101
+ )
102
+ refs.append(ref)
103
+ ref_hts.append(ref_ht)
104
+ ref = torch.cat(refs, 1)
105
+ ref_ht = torch.cat(ref_hts, 0)
106
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
107
+ ref_dx, x.grad = x.grad.clone(), None
108
+ ref_dg, g.grad = g.grad.clone(), None
109
+ ref_dh0, h0.grad = h0.grad.clone(), None
110
+
111
+ tri, tri_ht = fused_recurrent_hgrn(x, g, h0, output_final_state=True, cu_seqlens=cu_seqlens)
112
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
113
+ tri_dx, x.grad = x.grad.clone(), None
114
+ tri_dg, g.grad = g.grad.clone(), None
115
+ tri_dh0, h0.grad = h0.grad.clone(), None
116
+
117
+ assert_close('o', ref, tri, 0.005)
118
+ assert_close('ht', ref_ht, tri_ht, 0.005)
119
+ assert_close('dx', ref_dx, tri_dx, 0.005)
120
+ assert_close('dg', ref_dg, tri_dg, 0.005)
121
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
122
+
123
+
124
+ @pytest.mark.parametrize(
125
+ ('B', 'T', 'D', 'dtype'),
126
+ [
127
+ pytest.param(*test, id="B{}-T{}-D{}-{}".format(*test))
128
+ for test in [
129
+ (1, 63, 500, torch.float16),
130
+ (2, 500, 1000, torch.float16),
131
+ (2, 1000, 1024, torch.float16),
132
+ (4, 2048, 2048, torch.float16),
133
+ ]
134
+ ],
135
+ )
136
+ def test_chunk(
137
+ B: int,
138
+ T: int,
139
+ D: int,
140
+ dtype: torch.dtype,
141
+ ):
142
+ torch.manual_seed(42)
143
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
144
+
145
+ x = torch.randn((B, T, D), dtype=dtype, device=device)
146
+ g = torch.randn((B, T, D), dtype=dtype, device=device)
147
+ x, g = (1 - g.sigmoid()) * x, F.logsigmoid(g)
148
+ x, g = (i.detach().clone().to(dtype).requires_grad_() for i in (x, g))
149
+
150
+ do = torch.randn_like(x)
151
+ h0 = torch.randn_like(x[:, 0])
152
+ ref, _ = fused_recurrent_hgrn(x, g, h0, output_final_state=True)
153
+ ref.backward(do)
154
+ ref_dx, x.grad = x.grad.clone(), None
155
+ ref_dg, g.grad = g.grad.clone(), None
156
+
157
+ tri, _ = chunk_hgrn(x, g, h0, output_final_state=True)
158
+ tri.backward(do)
159
+ tri_dx, x.grad = x.grad.clone(), None
160
+ tri_dg, g.grad = g.grad.clone(), None
161
+
162
+ assert_close('o', ref, tri, 0.005)
163
+ assert_close('dx', ref_dx, tri_dx, 0.005)
164
+ assert_close('dg', ref_dg, tri_dg, 0.005)
code/flash-linear-attention/tests/ops/test_iplr_delta.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import pytest
4
+ import torch
5
+ import torch.nn.functional as F
6
+ from einops import rearrange
7
+
8
+ from fla.ops.generalized_delta_rule.iplr.chunk import chunk_iplr_delta_rule
9
+ from fla.ops.generalized_delta_rule.iplr.fused_recurrent import fused_recurrent_iplr_delta_rule
10
+ from fla.utils import assert_close, device
11
+
12
+
13
+ def chunk_iplr_delta_rule_ref(
14
+ q: torch.Tensor,
15
+ k: torch.Tensor,
16
+ v: torch.Tensor,
17
+ a: torch.Tensor,
18
+ b: torch.Tensor,
19
+ initial_state: torch.Tensor = None,
20
+ output_final_state: bool = True,
21
+ scale: float = None,
22
+ chunk_size: int = 64,
23
+ ):
24
+ BT = chunk_size
25
+ if scale is None:
26
+ scale = 1 / (q.shape[-1] ** 0.5)
27
+
28
+ q, k, v, a, b = map(lambda x: x.transpose(1, 2), (q, k, v, a, b))
29
+ T = q.shape[-2]
30
+ pad_len = (BT - (T % BT)) % BT
31
+ if pad_len > 0:
32
+ q = F.pad(q, (0, 0, 0, pad_len))
33
+ k = F.pad(k, (0, 0, 0, pad_len))
34
+ v = F.pad(v, (0, 0, 0, pad_len))
35
+ a = F.pad(a, (0, 0, 0, pad_len))
36
+ b = F.pad(b, (0, 0, 0, pad_len))
37
+ q, k, v, a, b = map(lambda x: x.to(torch.float32), [q, k, v, a, b])
38
+
39
+ B, H, L, DK = q.shape
40
+ DV = v.shape[-1]
41
+ q = q * scale
42
+
43
+ S = k.new_zeros(B, H, DK, DV)
44
+ if initial_state is not None:
45
+ S += initial_state
46
+
47
+ # note that diagonal is masked.
48
+ mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0)
49
+ q, k, v, a, b = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size), [q, k, v, a, b])
50
+
51
+ v2 = (a @ k.transpose(-1, -2)).masked_fill_(mask, 0) @ v
52
+ attn = (a @ b.transpose(-1, -2)).masked_fill(mask, 0)
53
+ for i in range(1, chunk_size):
54
+ attn[..., i, :i] = attn[..., i, :i] + (attn[..., i, :, None].clone() * attn[..., :, :i].clone()).sum(-2)
55
+ attn = attn + torch.eye(chunk_size, dtype=torch.float, device=q.device)
56
+ u = attn @ v2
57
+ w = attn @ a
58
+ o = torch.zeros_like(v)
59
+ mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1)
60
+ for i in range(0, L // chunk_size):
61
+ current_chunk_size = min(chunk_size, L - i * chunk_size) # to handle the last chunk with possibly padding
62
+ q_i = q[:, :, i, :current_chunk_size]
63
+ k_i = k[:, :, i, :current_chunk_size]
64
+ v_i = v[:, :, i, :current_chunk_size]
65
+ u_i = u[:, :, i, :current_chunk_size]
66
+ w_i = w[:, :, i, :current_chunk_size]
67
+ b_i = b[:, :, i, :current_chunk_size]
68
+ o_1 = (q_i @ k_i.transpose(-1, -2)).masked_fill_(mask, 0) @ v_i
69
+ v2_i = u_i + w_i @ S
70
+ o_2 = (q_i @ b_i.transpose(-1, -2)).masked_fill_(mask, 0) @ v2_i
71
+ o_3 = q_i @ S
72
+ o[:, :, i, :current_chunk_size] = o_1 + o_2 + o_3
73
+ S = S + k_i.transpose(-1, -2) @ v_i + b_i.transpose(-1, -2) @ v2_i
74
+ S = None if output_final_state is False else S
75
+ o = rearrange(o, 'b h n c d -> b h (n c) d')
76
+ o = o[:, :, :T]
77
+ o = o.transpose(1, 2)
78
+ return o, S
79
+
80
+
81
+ def recurrence_iplr_delta_rule_ref(
82
+ q,
83
+ k,
84
+ v,
85
+ a,
86
+ b,
87
+ initial_state: torch.Tensor | None = None,
88
+ output_final_state: bool = True,
89
+ scale: float | None = None,
90
+ ):
91
+ orig_dtype = q.dtype
92
+ if scale is None:
93
+ scale = 1 / (q.shape[-1] ** 0.5)
94
+ q, k, v, a, b = map(lambda x: x.transpose(1, 2).to(torch.float32), [q, k, v, a, b])
95
+ q = q * scale
96
+ B, H, L, DK = q.shape
97
+ DV = v.shape[-1]
98
+ o = torch.zeros_like(v)
99
+ S = torch.zeros(B, H, DK, DV).to(v)
100
+ if initial_state is not None:
101
+ S += initial_state
102
+
103
+ for i in range(q.shape[-2]):
104
+ _k = k[:, :, i]
105
+ _q = q[:, :, i]
106
+ _v = v[:, :, i]
107
+ _a = a[:, :, i]
108
+ _b = b[:, :, i]
109
+ _kv = _k[..., None] * _v[..., None, :] + (S.clone() * _a[..., None]).sum(-2, keepdim=True) * _b[..., None]
110
+ S = S + _kv
111
+ o[:, :, i] = torch.einsum('bhd,bhdm->bhm', _q, S)
112
+ S = None if output_final_state is False else S
113
+ o = o.transpose(1, 2)
114
+ return o.to(orig_dtype), S
115
+
116
+
117
+ @pytest.mark.parametrize(
118
+ ('B', 'T', 'H', 'D', 'scale', 'dtype'),
119
+ [
120
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
121
+ for test in [
122
+ (1, 63, 1, 64, 1, torch.float),
123
+ (2, 1024, 4, 60, 1, torch.float),
124
+ (2, 1024, 8, 100, 1, torch.float),
125
+ (2, 1024, 8, 128, 0.1, torch.float),
126
+ (4, 2048, 8, 64, 0.1, torch.float),
127
+ ]
128
+ ],
129
+ )
130
+ def test_fused_recurrent(
131
+ B: int,
132
+ T: int,
133
+ H: int,
134
+ D: int,
135
+ scale: float,
136
+ dtype: torch.dtype,
137
+ ):
138
+ q = torch.randn(B, T, H, D, dtype=dtype)
139
+ k = torch.randn(B, T, H, D, dtype=dtype)
140
+ v = torch.randn(B, T, H, D, dtype=dtype)
141
+ a = torch.rand(B, T, H, D, dtype=dtype)
142
+
143
+ a = F.normalize(a, p=2, dim=-1)
144
+ b = -a
145
+ h0 = torch.zeros(B, H, D, D, dtype=torch.float32)
146
+ q, k, v, a, b, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, a, b, h0))
147
+ ref, ref_ht = recurrence_iplr_delta_rule_ref(
148
+ q=q.clone(),
149
+ k=k.clone(),
150
+ v=v.clone(),
151
+ a=a.clone(),
152
+ b=b.clone(),
153
+ scale=scale,
154
+ initial_state=h0.clone(),
155
+ output_final_state=True,
156
+ )
157
+ dht = torch.rand_like(h0)
158
+ do = torch.rand_like(ref)
159
+ ((dht * ref_ht).sum() + (do * ref).sum()).backward()
160
+ dq, dk, dv, da, db, dh0 = map(lambda x: x.grad, (q, k, v, a, b, h0))
161
+ q.grad, k.grad, v.grad, a.grad, b.grad, h0.grad = None, None, None, None, None, None
162
+ tri, tri_ht = fused_recurrent_iplr_delta_rule(
163
+ q=q.clone(),
164
+ k=k.clone(),
165
+ v=v.clone(),
166
+ a=a.clone(),
167
+ b=b.clone(),
168
+ scale=scale,
169
+ initial_state=h0.clone(),
170
+ output_final_state=True,
171
+ )
172
+ ((dht * tri_ht).sum() + (do * tri).sum()).backward()
173
+ assert_close('o', ref, tri, 0.003)
174
+ assert_close('ht', ref_ht, tri_ht, 0.003)
175
+ assert_close('dq', dq, q.grad, 0.003)
176
+ assert_close('dk', dk, k.grad, 0.003)
177
+ assert_close('dv', dv, v.grad, 0.003)
178
+ assert_close('da', da, a.grad, 0.003)
179
+ assert_close('db', db, b.grad, 0.003)
180
+ assert_close('dh0', dh0, h0.grad, 0.003)
181
+
182
+
183
+ @pytest.mark.parametrize(
184
+ ('B', 'T', 'H', 'D', 'scale', 'dtype'),
185
+ [
186
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
187
+ for test in [
188
+ (1, 63, 1, 64, 1, torch.float16),
189
+ (2, 500, 3, 60, 1, torch.float16),
190
+ (2, 1000, 3, 64, 0.1, torch.float16),
191
+ (2, 1024, 4, 100, 1, torch.float16),
192
+ (3, 1024, 4, 128, 0.1, torch.float16),
193
+ (4, 2048, 8, 64, 0.1, torch.float16),
194
+ ]
195
+ ],
196
+ )
197
+ def test_chunk(
198
+ B: int,
199
+ T: int,
200
+ H: int,
201
+ D: int,
202
+ scale: float,
203
+ dtype: torch.dtype,
204
+ ):
205
+ q = torch.randn(B, T, H, D, dtype=dtype)
206
+ k = torch.randn(B, T, H, D, dtype=dtype)
207
+ v = torch.randn(B, T, H, D, dtype=dtype)
208
+ a = torch.rand(B, T, H, D, dtype=dtype)
209
+
210
+ a = F.normalize(a, p=2, dim=-1)
211
+ b = -a
212
+ h0 = torch.zeros(B, H, D, D, dtype=torch.float32)
213
+ q, k, v, a, b, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, a, b, h0))
214
+ ref, ref_ht = recurrence_iplr_delta_rule_ref(
215
+ q=q.clone(),
216
+ k=k.clone(),
217
+ v=v.clone(),
218
+ a=a.clone(),
219
+ b=b.clone(),
220
+ scale=scale,
221
+ initial_state=h0.clone(),
222
+ output_final_state=True,
223
+ )
224
+ tri, tri_ht = chunk_iplr_delta_rule(
225
+ q=q.clone(),
226
+ k=k.clone(),
227
+ v=v.clone(),
228
+ a=a.clone(),
229
+ b=b.clone(),
230
+ scale=scale,
231
+ initial_state=h0.clone(),
232
+ output_final_state=True,
233
+ )
234
+ assert_close('o', ref, tri, 0.007)
235
+ assert_close('ht', ref_ht, tri_ht, 0.008)
code/flash-linear-attention/tests/ops/test_kda.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.kda import chunk_kda, fused_recurrent_kda
9
+ from fla.ops.kda.gate import fused_kda_gate, kda_gate_ref
10
+ from fla.ops.kda.naive import naive_chunk_kda, naive_recurrent_kda
11
+ from fla.utils import assert_close, device, is_intel_alchemist
12
+
13
+
14
+ @pytest.mark.parametrize(
15
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'),
16
+ [
17
+ pytest.param(
18
+ *test,
19
+ id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test),
20
+ )
21
+ for test in [
22
+ (1, 64, 1, 64, 1, 1, torch.float),
23
+ (2, 512, 3, 60, 1, 1, torch.float),
24
+ (4, 1024, 4, 128, 0.1, 1, torch.float),
25
+ (4, 1024, 4, 128, 1, 10, torch.float),
26
+ ]
27
+ ],
28
+ )
29
+ def test_naive_chunk(
30
+ B: int,
31
+ T: int,
32
+ H: int,
33
+ D: int,
34
+ scale: float,
35
+ gate_logit_normalizer: float,
36
+ dtype: torch.dtype,
37
+ ):
38
+ torch.manual_seed(42)
39
+ if is_intel_alchemist and D > 128:
40
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
41
+
42
+ q = torch.rand(B, T, H, D, dtype=dtype)
43
+ k = torch.rand(B, T, H, D, dtype=dtype)
44
+ v = torch.rand(B, T, H, D, dtype=dtype)
45
+ g = F.logsigmoid(torch.randn(B, T, H, D, dtype=torch.float)) / gate_logit_normalizer
46
+ beta = torch.randn(B, T, H, dtype=dtype).sigmoid()
47
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
48
+ q, k, v, g, beta, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, g, beta, h0))
49
+
50
+ ref, ref_ht = naive_recurrent_kda(
51
+ q=F.normalize(q.clone(), p=2, dim=-1),
52
+ k=F.normalize(k.clone(), p=2, dim=-1),
53
+ v=v.clone(),
54
+ g=g.clone(),
55
+ beta=beta.clone(),
56
+ scale=scale,
57
+ initial_state=h0.clone(),
58
+ output_final_state=True,
59
+ )
60
+
61
+ tri, tri_ht = naive_chunk_kda(
62
+ q=F.normalize(q.clone(), p=2, dim=-1),
63
+ k=F.normalize(k.clone(), p=2, dim=-1),
64
+ v=v.clone(),
65
+ g=g.clone(),
66
+ beta=beta.clone(),
67
+ scale=scale,
68
+ initial_state=h0.clone(),
69
+ output_final_state=True,
70
+ )
71
+ assert_close('o', ref, tri, 0.005)
72
+ assert_close('ht', ref_ht, tri_ht, 0.005)
73
+
74
+
75
+ @pytest.mark.parametrize(
76
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'use_qk_l2norm_in_kernel', 'dtype'),
77
+ [
78
+ pytest.param(
79
+ *test,
80
+ id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-use_qk_l2norm_in_kernel{}-{}".format(*test),
81
+ )
82
+ for test in [
83
+ (1, 64, 1, 64, 1, 1, False, torch.float),
84
+ (2, 512, 3, 60, 1, 1, False, torch.float),
85
+ (3, 1000, 4, 100, 0.1, 1, True, torch.float),
86
+ (4, 1024, 4, 128, 0.1, 1, False, torch.float),
87
+ ]
88
+ ],
89
+ )
90
+ def test_fused_recurrent(
91
+ B: int,
92
+ T: int,
93
+ H: int,
94
+ D: int,
95
+ scale: float,
96
+ gate_logit_normalizer: float,
97
+ use_qk_l2norm_in_kernel: bool,
98
+ dtype: torch.dtype,
99
+ ):
100
+ torch.manual_seed(42)
101
+ if is_intel_alchemist and D > 128:
102
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
103
+
104
+ q = torch.rand(B, T, H, D, dtype=dtype)
105
+ k = torch.rand(B, T, H, D, dtype=dtype)
106
+ v = torch.rand(B, T, H, D, dtype=dtype)
107
+ g = F.logsigmoid(torch.randn(B, T, H, D, dtype=torch.float)) / gate_logit_normalizer
108
+ beta = torch.randn(B, T, H, dtype=dtype).sigmoid()
109
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
110
+ q, k, v, g, beta, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, g, beta, h0))
111
+
112
+ ref, ref_ht = naive_recurrent_kda(
113
+ q=F.normalize(q.clone(), p=2, dim=-1),
114
+ k=F.normalize(k.clone(), p=2, dim=-1),
115
+ v=v.clone(),
116
+ g=g.clone(),
117
+ beta=beta.clone(),
118
+ scale=scale,
119
+ initial_state=h0.clone(),
120
+ output_final_state=True,
121
+ )
122
+
123
+ tri, tri_ht = fused_recurrent_kda(
124
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
125
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
126
+ v=v.clone(),
127
+ g=g.clone(),
128
+ beta=beta.clone(),
129
+ scale=scale,
130
+ initial_state=h0.clone(),
131
+ output_final_state=True,
132
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
133
+ )
134
+ assert_close('o', ref, tri, 0.005)
135
+ assert_close('ht', ref_ht, tri_ht, 0.005)
136
+
137
+
138
+ @pytest.mark.parametrize(
139
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'mask_p', 'use_qk_l2norm_in_kernel', 'dtype', 'tma'),
140
+ [
141
+ pytest.param(
142
+ *test,
143
+ id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-mask_p{}-use_qk_l2norm_in_kernel{}-{}-tma{}".format(*test),
144
+ )
145
+ for test in [
146
+ (1, 63, 1, 64, 1, 1, 0, False, torch.float16, True),
147
+ (2, 500, 3, 60, 1, 1, 0, False, torch.float16, True),
148
+ (2, 1000, 3, 64, 0.1, 1, 0.5, False, torch.float16, False),
149
+ (3, 1024, 4, 100, 1, 0.1, 0, False, torch.float16, False),
150
+ (4, 1024, 4, 128, 0.1, 1, 0, False, torch.float16, True),
151
+ (4, 1024, 4, 128, 0.1, 1, 0, True, torch.float16, True),
152
+ (2, 1500, 4, 128, 0.1, 10, 0, False, torch.float16, False),
153
+ (4, 2048, 8, 64, 0.1, 1, 0, False, torch.float16, True),
154
+ ]
155
+ ],
156
+ )
157
+ def test_chunk(
158
+ B: int,
159
+ T: int,
160
+ H: int,
161
+ D: int,
162
+ scale: float,
163
+ gate_logit_normalizer: float,
164
+ mask_p: float,
165
+ use_qk_l2norm_in_kernel: bool,
166
+ dtype: torch.dtype,
167
+ tma: bool,
168
+ ):
169
+ torch.manual_seed(42)
170
+ if not tma:
171
+ os.environ['FLA_USE_TMA'] = '0'
172
+ else:
173
+ os.environ['FLA_USE_TMA'] = '1'
174
+ q = torch.rand(B, T, H, D, dtype=dtype)
175
+ k = torch.rand(B, T, H, D, dtype=dtype)
176
+ v = torch.rand(B, T, H, D, dtype=dtype)
177
+ g = F.logsigmoid(torch.randn(B, T, H, D, dtype=torch.float)) / gate_logit_normalizer
178
+ g = g * (torch.rand_like(g) > mask_p)
179
+ beta = torch.randn(B, T, H, dtype=dtype).sigmoid()
180
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
181
+ q, k, v, g, beta, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, g, beta, h0))
182
+ do = torch.randn_like(v)
183
+ dht = torch.randn_like(h0)
184
+
185
+ ref, ref_ht = naive_recurrent_kda(
186
+ q=F.normalize(q.clone(), p=2, dim=-1),
187
+ k=F.normalize(k.clone(), p=2, dim=-1),
188
+ v=v.clone(),
189
+ g=g.clone(),
190
+ beta=beta.clone(),
191
+ scale=scale,
192
+ initial_state=h0.clone(),
193
+ output_final_state=True,
194
+ )
195
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
196
+ ref_dq, ref_dk, ref_dv, ref_dg, ref_db, ref_dh0 = q.grad, k.grad, v.grad, g.grad, beta.grad, h0.grad
197
+ q.grad = k.grad = v.grad = g.grad = beta.grad = h0.grad = None
198
+
199
+ tri, tri_ht = chunk_kda(
200
+ q=F.normalize(q.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else q.clone(),
201
+ k=F.normalize(k.clone(), p=2, dim=-1) if not use_qk_l2norm_in_kernel else k.clone(),
202
+ v=v.clone(),
203
+ g=g.clone(),
204
+ beta=beta.clone(),
205
+ scale=scale,
206
+ initial_state=h0.clone(),
207
+ output_final_state=True,
208
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
209
+ )
210
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
211
+ tri_dq, tri_dk, tri_dv, tri_dg, tri_db, tri_dh0 = q.grad, k.grad, v.grad, g.grad, beta.grad, h0.grad
212
+ q.grad = k.grad = v.grad = g.grad = beta.grad = h0.grad = None
213
+
214
+ assert_close('o', ref, tri, 0.005)
215
+ assert_close('ht', ref_ht, tri_ht, 0.005)
216
+ assert_close('dq', ref_dq, tri_dq, 0.008)
217
+ assert_close('dk', ref_dk, tri_dk, 0.008)
218
+ assert_close('dv', ref_dv, tri_dv, 0.008)
219
+ assert_close('dg', ref_dg, tri_dg, 0.02)
220
+ assert_close('db', ref_db, tri_db, 0.02)
221
+ assert_close('dh0', ref_dh0, tri_dh0, 0.008)
222
+
223
+
224
+ @pytest.mark.parametrize(
225
+ ('H', 'D', 'mask_p', 'cu_seqlens', 'dtype'),
226
+ [
227
+ pytest.param(*test, id="H{}-D{}-mask_p{}-cu_seqlens{}-{}".format(*test))
228
+ for test in [
229
+ (4, 60, 0, [0, 15], torch.float16),
230
+ (4, 64, 0, [0, 256, 500, 1000], torch.float16),
231
+ (4, 128, 0.5, [0, 256, 500, 1000], torch.float16),
232
+ (4, 100, 0, [0, 15, 100, 300, 1200, 2000], torch.float16),
233
+ (4, 256, 0, [0, 15, 100, 300, 1200, 4096], torch.float16),
234
+ ]
235
+ ],
236
+ )
237
+ @pytest.mark.skipif(
238
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
239
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
240
+ )
241
+ def test_chunk_varlen(
242
+ H: int,
243
+ D: int,
244
+ mask_p: float,
245
+ cu_seqlens: list[int],
246
+ dtype: torch.dtype,
247
+ ):
248
+ torch.manual_seed(42)
249
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
250
+ # randomly split the sequence into N segments
251
+ cu_seqlens = torch.LongTensor(cu_seqlens).to(device)
252
+ T = cu_seqlens[-1]
253
+ N = len(cu_seqlens) - 1
254
+
255
+ # seq-first required for inputs with variable lengths
256
+ q = torch.randn((1, T, H, D), dtype=dtype)
257
+ k = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
258
+ v = torch.randn((1, T, H, D), dtype=dtype)
259
+ g = F.logsigmoid(torch.randn(1, T, H, D, dtype=torch.float))
260
+ g = g * (torch.rand_like(g) > mask_p)
261
+ beta = torch.rand(1, T, H, dtype=dtype).sigmoid()
262
+ h0 = torch.randn((N, H, D, D), dtype=dtype)
263
+
264
+ q, k, v, g, beta, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, g, beta, h0))
265
+ do = torch.randn_like(v)
266
+ dht = torch.rand_like(h0)
267
+
268
+ tri, tri_ht = chunk_kda(
269
+ q=q.clone(),
270
+ k=k.clone(),
271
+ v=v.clone(),
272
+ g=g.clone(),
273
+ beta=beta.clone(),
274
+ initial_state=h0.clone(),
275
+ output_final_state=True,
276
+ cu_seqlens=cu_seqlens,
277
+ )
278
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True)
279
+ tri_dq, tri_dk, tri_dv, tri_dg, tri_db, tri_dh0 = q.grad, k.grad, v.grad, g.grad, beta.grad, h0.grad
280
+ q.grad = k.grad = v.grad = g.grad = beta.grad = h0.grad = None
281
+
282
+ ref = []
283
+ ref_ht = []
284
+ for i in range(N):
285
+ ref_i, ref_ht_i = naive_recurrent_kda(
286
+ q=q[:, cu_seqlens[i]:cu_seqlens[i+1]],
287
+ k=k[:, cu_seqlens[i]:cu_seqlens[i+1]],
288
+ v=v[:, cu_seqlens[i]:cu_seqlens[i+1]],
289
+ beta=beta[:, cu_seqlens[i]:cu_seqlens[i+1]],
290
+ g=g[:, cu_seqlens[i]:cu_seqlens[i+1]],
291
+ initial_state=h0[i],
292
+ output_final_state=True,
293
+ )
294
+ ref.append(ref_i)
295
+ ref_ht.append(ref_ht_i)
296
+ ref = torch.cat(ref, 1)
297
+ ref_ht = torch.cat(ref_ht, 0)
298
+
299
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True)
300
+ ref_dq, ref_dk, ref_dv, ref_dg, ref_db, ref_dh0 = q.grad, k.grad, v.grad, g.grad, beta.grad, h0.grad
301
+
302
+ assert_close('o', ref, tri, 0.005)
303
+ assert_close('ht', ref_ht, tri_ht, 0.005)
304
+ assert_close('dq', ref_dq, tri_dq, 0.007)
305
+ assert_close('dk', ref_dk, tri_dk, 0.008)
306
+ assert_close('dv', ref_dv, tri_dv, 0.007)
307
+ assert_close('dg', ref_dg, tri_dg, 0.015)
308
+ assert_close('db', ref_db, tri_db, 0.015)
309
+ assert_close('dh0', ref_dh0, tri_dh0, 0.007)
310
+
311
+
312
+ @pytest.mark.parametrize(
313
+ ('B', 'T', 'H', 'D', 'use_bias'),
314
+ [
315
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-bias{}".format(*test))
316
+ for test in [
317
+ (1, 2, 2, 12, False),
318
+ (1, 32, 2, 16, False),
319
+ (2, 64, 4, 32, False),
320
+ (4, 128, 8, 64, False),
321
+ (4, 128, 8, 128, False),
322
+ # Add bias tests
323
+ (1, 2, 2, 12, True),
324
+ (1, 32, 2, 16, True),
325
+ (2, 64, 4, 32, True),
326
+ (4, 128, 8, 64, True),
327
+ (4, 128, 8, 128, True),
328
+ ]
329
+ ],
330
+ )
331
+ def test_kda_gate(
332
+ B: int,
333
+ T: int,
334
+ H: int,
335
+ D: int,
336
+ use_bias: bool,
337
+ ):
338
+ """Test kda gate forward and backward pass - reference vs Triton implementation"""
339
+ torch.manual_seed(42)
340
+
341
+ g = torch.randn(B, T, H * D, dtype=torch.float32)
342
+ # Ensure some values are > 20 to test the threshold logic in softplus
343
+ g = g * 30 # Scale up to get values > 20
344
+ A = torch.log(torch.randn(1, 1, H, 1, dtype=torch.float32).uniform_(1, 16))
345
+ g_bias = torch.randn(H * D, dtype=torch.float32) if use_bias else None
346
+
347
+ # Move to device and set requires_grad
348
+ g, A = map(lambda x: x.to(device).requires_grad_(True), (g, A))
349
+ if g_bias is not None:
350
+ g_bias = g_bias.to(device).requires_grad_(True)
351
+
352
+ # Create gradient output
353
+ do = torch.randn_like(g).view(B, T, H, D)
354
+
355
+ # Reference implementation
356
+ ref = kda_gate_ref(g.clone(), A.clone(), D, g_bias.clone() if g_bias is not None else None)
357
+ # Triton implementation
358
+ tri = fused_kda_gate(g.clone(), A.clone(), D, g_bias.clone() if g_bias is not None else None)
359
+
360
+ # Backward pass
361
+ ((ref * do).sum()).backward(retain_graph=True)
362
+ ref_dg, ref_dA = g.grad, A.grad
363
+ ref_dgbias = g_bias.grad if g_bias is not None else None
364
+ g.grad = A.grad = None
365
+ if g_bias is not None:
366
+ g_bias.grad = None
367
+
368
+ ((tri * do).sum()).backward(retain_graph=True)
369
+ tri_dg, tri_dA = g.grad, A.grad
370
+ tri_dgbias = g_bias.grad if g_bias is not None else None
371
+ g.grad = A.grad = None
372
+ if g_bias is not None:
373
+ g_bias.grad = None
374
+
375
+ assert_close('o', ref, tri, 1e-4)
376
+ assert_close('dg', ref_dg, tri_dg, 1e-4)
377
+ assert_close('dA', ref_dA, tri_dA, 1e-4)
378
+ if use_bias:
379
+ assert_close('dgbias', ref_dgbias, tri_dgbias, 1e-4)
code/flash-linear-attention/tests/ops/test_linear_attn.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import pytest
4
+ import torch
5
+
6
+ from fla.ops.linear_attn import chunk_linear_attn, fused_chunk_linear_attn, fused_recurrent_linear_attn
7
+ from fla.ops.linear_attn.naive import naive_recurrent_linear_attn
8
+ from fla.utils import assert_close, device
9
+
10
+
11
+ @pytest.mark.parametrize(
12
+ ('B', 'T', 'H', 'D', 'scale', 'dtype'),
13
+ [
14
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
15
+ for test in [
16
+ (1, 64, 1, 64, None, torch.float),
17
+ (2, 512, 4, 60, None, torch.float),
18
+ (3, 1024, 8, 128, 1., torch.float),
19
+ (3, 1024, 8, 128, 0.1, torch.float),
20
+ (3, 1024, 8, 128, None, torch.float),
21
+ (2, 2048, 8, 256, None, torch.float16),
22
+ (2, 2048, 4, 256, None, torch.float16),
23
+ ]
24
+ ],
25
+ )
26
+ def test_fused_recurrent(
27
+ B: int,
28
+ T: int,
29
+ H: int,
30
+ D: int,
31
+ scale: float | None,
32
+ dtype: torch.dtype,
33
+ ):
34
+ torch.manual_seed(42)
35
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
36
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
37
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
38
+ h0 = torch.randn((B, H, D, D), dtype=torch.float, device=device).requires_grad_()
39
+ do = torch.randn_like(v)
40
+ dht = torch.randn_like(h0)
41
+
42
+ ref, ref_ht = naive_recurrent_linear_attn(q, k, v, scale=scale, initial_state=h0, output_final_state=True, normalize=False)
43
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
44
+ ref_dq, q.grad = q.grad.clone(), None
45
+ ref_dk, k.grad = k.grad.clone(), None
46
+ ref_dv, v.grad = v.grad.clone(), None
47
+ ref_dh0, h0.grad = h0.grad.clone(), None
48
+
49
+ tri, tri_ht = fused_recurrent_linear_attn(q, k, v, scale=scale, initial_state=h0, output_final_state=True, normalize=False)
50
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
51
+ tri_dq, q.grad = q.grad.clone(), None
52
+ tri_dk, k.grad = k.grad.clone(), None
53
+ tri_dv, v.grad = v.grad.clone(), None
54
+ tri_dh0, h0.grad = h0.grad.clone(), None
55
+
56
+ assert_close('o', ref, tri, 0.001)
57
+ assert_close('ht', ref_ht, tri_ht, 0.001)
58
+ assert_close('dq', ref_dq, tri_dq, 0.001)
59
+ assert_close('dk', ref_dk, tri_dk, 0.001)
60
+ assert_close('dv', ref_dv, tri_dv, 0.001)
61
+ assert_close('dh0', ref_dh0, tri_dh0, 0.001)
62
+
63
+
64
+ @pytest.mark.parametrize(
65
+ ('B', 'T', 'H', 'D', 'dtype'),
66
+ [
67
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
68
+ for test in [
69
+ (1, 63, 1, 64, torch.float16),
70
+ (2, 500, 3, 60, torch.float16),
71
+ (2, 1000, 3, 128, torch.float16),
72
+ (3, 1000, 4, 64, torch.float16),
73
+ (2, 2048, 4, 256, torch.float16),
74
+ ]
75
+ ],
76
+ )
77
+ def test_chunk(
78
+ B: int,
79
+ T: int,
80
+ H: int,
81
+ D: int,
82
+ dtype: torch.dtype,
83
+ ):
84
+ torch.manual_seed(42)
85
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
86
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
87
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
88
+ h0 = torch.randn((B, H, D, D), dtype=torch.float, device=device).requires_grad_()
89
+ do = torch.randn_like(v)
90
+ dht = torch.randn_like(h0)
91
+
92
+ ref, ref_ht = fused_recurrent_linear_attn(
93
+ q.to(torch.float32),
94
+ k.to(torch.float32),
95
+ v.to(torch.float32),
96
+ initial_state=h0,
97
+ output_final_state=True,
98
+ normalize=False,
99
+ )
100
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
101
+ ref_dq, q.grad = q.grad.clone(), None
102
+ ref_dk, k.grad = k.grad.clone(), None
103
+ ref_dv, v.grad = v.grad.clone(), None
104
+ ref_dh0, h0.grad = h0.grad.clone(), None
105
+
106
+ tri, tri_ht = chunk_linear_attn(
107
+ q=q,
108
+ k=k,
109
+ v=v,
110
+ initial_state=h0,
111
+ output_final_state=True,
112
+ normalize=False,
113
+ )
114
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
115
+ tri_dq, q.grad = q.grad.clone(), None
116
+ tri_dk, k.grad = k.grad.clone(), None
117
+ tri_dv, v.grad = v.grad.clone(), None
118
+ tri_dh0, h0.grad = h0.grad.clone(), None
119
+
120
+ assert_close('o', ref, tri, 0.001)
121
+ assert_close('ht', ref_ht, tri_ht, 0.001)
122
+ assert_close('dq', ref_dq, tri_dq, 0.001)
123
+ assert_close('dk', ref_dk, tri_dk, 0.001)
124
+ assert_close('dv', ref_dv, tri_dv, 0.001)
125
+ assert_close('dh0', ref_dh0, tri_dh0, 0.001)
126
+
127
+
128
+ @pytest.mark.parametrize(
129
+ ('B', 'T', 'H', 'D', 'dtype'),
130
+ [
131
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
132
+ for test in [
133
+ (1, 63, 1, 64, torch.float16),
134
+ (2, 500, 3, 60, torch.float16),
135
+ (2, 1000, 3, 128, torch.float16),
136
+ (3, 1000, 4, 64, torch.float16),
137
+ (2, 2048, 4, 256, torch.float16),
138
+ ]
139
+ ],
140
+ )
141
+ def test_fused_chunk(
142
+ B: int,
143
+ T: int,
144
+ H: int,
145
+ D: int,
146
+ dtype: torch.dtype,
147
+ ):
148
+ torch.manual_seed(42)
149
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
150
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
151
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
152
+ h0 = torch.randn((B, H, D, D), dtype=torch.float, device=device).requires_grad_()
153
+ do = torch.randn_like(v)
154
+ dht = torch.randn_like(h0)
155
+
156
+ ref, ref_ht = fused_recurrent_linear_attn(
157
+ q.to(torch.float32),
158
+ k.to(torch.float32),
159
+ v.to(torch.float32),
160
+ initial_state=h0,
161
+ output_final_state=True,
162
+ normalize=False,
163
+ )
164
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
165
+ ref_dq, q.grad = q.grad.clone(), None
166
+ ref_dk, k.grad = k.grad.clone(), None
167
+ ref_dv, v.grad = v.grad.clone(), None
168
+ ref_dh0, h0.grad = h0.grad.clone(), None
169
+
170
+ tri, tri_ht = fused_chunk_linear_attn(
171
+ q=q,
172
+ k=k,
173
+ v=v,
174
+ initial_state=h0,
175
+ output_final_state=True,
176
+ normalize=False,
177
+ )
178
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
179
+ tri_dq, q.grad = q.grad.clone(), None
180
+ tri_dk, k.grad = k.grad.clone(), None
181
+ tri_dv, v.grad = v.grad.clone(), None
182
+ tri_dh0, h0.grad = h0.grad.clone(), None
183
+
184
+ assert_close('o', ref, tri, 0.001)
185
+ assert_close('ht', ref_ht, tri_ht, 0.001)
186
+ assert_close('dq', ref_dq, tri_dq, 0.001)
187
+ assert_close('dk', ref_dk, tri_dk, 0.001)
188
+ assert_close('dv', ref_dv, tri_dv, 0.001)
189
+ assert_close('dh0', ref_dh0, tri_dh0, 0.001)
code/flash-linear-attention/tests/ops/test_log_linear_attn.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import numpy as np
4
+ import pytest
5
+ import torch
6
+
7
+ from fla.ops.log_linear_attn import chunk_log_linear_attn
8
+ from fla.ops.log_linear_attn.naive import naive_log_linear_attn
9
+ from fla.utils import assert_close, device, device_platform
10
+
11
+
12
+ @pytest.mark.parametrize(
13
+ ("B", "T", "H", "D", "dtype"),
14
+ [
15
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
16
+ for test in [(2, 1024, 8, 128, torch.float32), (4, 2048, 8, 64, torch.float32)]
17
+ ],
18
+ )
19
+ @pytest.mark.skipif(device_platform == "intel", reason="Intel Triton Failure")
20
+ def test_chunk(
21
+ B: int,
22
+ T: int,
23
+ H: int,
24
+ D: int,
25
+ dtype: torch.dtype,
26
+ ):
27
+ torch.manual_seed(42)
28
+ os.environ["TRITON_F32_DEFAULT"] = "ieee"
29
+
30
+ L = int(np.log2(T) + 1)
31
+ x = torch.randn(B, T, H, D, dtype=dtype, device=device)
32
+ dt = torch.nn.functional.softplus(
33
+ torch.randn(B, T, H, dtype=torch.float32, device=device) - 4,
34
+ )
35
+ a = -torch.exp(torch.rand(H, dtype=torch.float32, device=device))
36
+ q = torch.randn(B, T, 1, D, dtype=dtype, device=device)
37
+ k = torch.randn(B, T, 1, D, dtype=dtype, device=device)
38
+ level_scales = torch.randn(B, T, H, L, dtype=dtype, device=device)
39
+ v = (x * dt.unsqueeze(-1)).to(dtype=dtype)
40
+ g = a * dt
41
+
42
+ out, _ = chunk_log_linear_attn(q, k, v, g, level_scales)
43
+
44
+ ref = naive_log_linear_attn(q, k, v, g, level_scales)
45
+
46
+ assert_close("o", ref, out, 0.004)
47
+
48
+
49
+ @pytest.mark.parametrize(
50
+ ("B", "T", "H", "D", "dtype"),
51
+ [
52
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
53
+ for test in [(2, 512, 8, 64, torch.float32), (2, 1024, 8, 128, torch.float32)]
54
+ ],
55
+ )
56
+ @pytest.mark.skipif(device_platform == "intel", reason="Intel Triton Failure")
57
+ def test_chunk_bwd(
58
+ B: int,
59
+ T: int,
60
+ H: int,
61
+ D: int,
62
+ dtype: torch.dtype,
63
+ ):
64
+ torch.manual_seed(42)
65
+ os.environ["TRITON_F32_DEFAULT"] = "ieee"
66
+
67
+ L = int(np.log2(T) + 1)
68
+ x = torch.randn(B, T, H, D, dtype=dtype, device=device)
69
+ dt = torch.nn.functional.softplus(
70
+ torch.randn(B, T, H, dtype=torch.float32, device=device) - 4,
71
+ )
72
+ a = -torch.exp(torch.rand(H, dtype=torch.float32, device=device))
73
+ q = torch.randn(B, T, 1, D, dtype=dtype, device=device)
74
+ k = torch.randn(B, T, 1, D, dtype=dtype, device=device)
75
+ level_scales = torch.randn(B, T, H, L, dtype=dtype, device=device)
76
+ v = (x * dt.unsqueeze(-1)).to(dtype=dtype)
77
+ g = a * dt
78
+ do = torch.randn_like(v)
79
+ q, k, v, g, level_scales = map(lambda x: x.to(device).requires_grad_(), (q, k, v, g, level_scales))
80
+
81
+ out, _ = chunk_log_linear_attn(q, k, v, g, level_scales)
82
+ (out * do).sum().backward()
83
+ tri_dq, tri_dk, tri_dv, tri_dg, tri_dl = q.grad, k.grad, v.grad, g.grad, level_scales.grad
84
+ q.grad = k.grad = v.grad = g.grad = level_scales.grad = None
85
+
86
+ ref = naive_log_linear_attn(q, k, v, g, level_scales)
87
+ (ref * do).sum().backward()
88
+ ref_dq, ref_dk, ref_dv, ref_dg, ref_dl = q.grad, k.grad, v.grad, g.grad, level_scales.grad
89
+
90
+ assert_close("o", ref, out, 0.004)
91
+ assert_close("dq", ref_dq, tri_dq, 0.007)
92
+ assert_close("dk", ref_dk, tri_dk, 0.008)
93
+ assert_close("dv", ref_dv, tri_dv, 0.007)
94
+ assert_close("dg", ref_dg, tri_dg, 0.015)
95
+ assert_close("dl", ref_dl, tri_dl, 0.015)
96
+
97
+
98
+ @pytest.mark.parametrize(
99
+ ("H", "D", "cu_seqlens", "dtype"),
100
+ [
101
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
102
+ for test in [
103
+ (4, 64, [0, 15], torch.float32),
104
+ (4, 64, [0, 256, 500, 1000], torch.float32),
105
+ (4, 128, [0, 15, 100, 300, 1200, 2000], torch.float32),
106
+ ]
107
+ ],
108
+ )
109
+ @pytest.mark.skipif(device_platform == "intel", reason="Intel Triton Failure")
110
+ def test_chunk_varlen(
111
+ H: int,
112
+ D: int,
113
+ cu_seqlens: list[int],
114
+ dtype: torch.dtype,
115
+ ):
116
+ torch.manual_seed(42)
117
+ os.environ["TRITON_F32_DEFAULT"] = "ieee"
118
+
119
+ cu_seqlens = torch.LongTensor(cu_seqlens).to(device)
120
+ T = cu_seqlens[-1].item()
121
+
122
+ L = int(np.ceil(np.log2(T)) + 1)
123
+ x = torch.randn(1, T, H, D, dtype=dtype, device=device)
124
+ dt = torch.nn.functional.softplus(
125
+ torch.randn(1, T, H, dtype=torch.float32, device=device) - 4,
126
+ )
127
+ a = -torch.exp(torch.rand(H, dtype=torch.float32, device=device))
128
+ q = torch.randn(1, T, 1, D, dtype=dtype, device=device)
129
+ k = torch.randn(1, T, 1, D, dtype=dtype, device=device)
130
+ level_scales = torch.randn(1, T, H, L, dtype=dtype, device=device)
131
+ v = (x * dt.unsqueeze(-1)).to(dtype=dtype)
132
+ g = a * dt
133
+
134
+ out, _ = chunk_log_linear_attn(q, k, v, g, level_scales, cu_seqlens=cu_seqlens)
135
+
136
+ o = []
137
+ for i in range(cu_seqlens.shape[0] - 1):
138
+ bos, eos = cu_seqlens[i], cu_seqlens[i + 1]
139
+ v_s = v[:, bos:eos]
140
+ g_s = g[:, bos:eos]
141
+ k_s = k[:, bos:eos]
142
+ q_s = q[:, bos:eos]
143
+ level_scales_s = level_scales[:, bos:eos]
144
+
145
+ o.append(naive_log_linear_attn(q_s, k_s, v_s, g_s, level_scales_s))
146
+ ref = torch.cat(o, dim=1)
147
+
148
+ assert_close("o", ref, out, 0.004)
code/flash-linear-attention/tests/ops/test_mesa.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.mesa_net import chunk_mesa_net, mesa_net_decoding_one_step, naive_mesa_net_decoding_one_step, naive_mesa_net_exact
9
+ from fla.utils import assert_close, device, device_platform, is_intel_alchemist
10
+
11
+
12
+ @pytest.mark.parametrize(
13
+ ('B', 'T', 'H', 'D', 'gate_range', 'dtype'),
14
+ [
15
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-gate_range{}-{}".format(*test))
16
+ for test in [
17
+ (1, 63, 1, 64, [0.8, 0.99], torch.float16),
18
+ (2, 500, 4, 60, [0.8, 0.99], torch.float16),
19
+ (2, 1024, 8, 128, [0.8, 0.99], torch.float16),
20
+ (2, 1024, 8, 128, [0.01, 0.1], torch.float16),
21
+ (2, 1024, 8, 128, [1, 1], torch.float16),
22
+ (4, 2048, 8, 64, [0.8, 0.99], torch.float16),
23
+ ]
24
+ ],
25
+ )
26
+ @pytest.mark.skipif(
27
+ device_platform == 'intel',
28
+ reason='Intel Triton Failure',
29
+ )
30
+ def test_chunk(
31
+ B: int,
32
+ T: int,
33
+ H: int,
34
+ D: int,
35
+ gate_range: tuple[float, float],
36
+ dtype: torch.dtype,
37
+ ):
38
+ torch.manual_seed(42)
39
+ q = torch.rand(B, T, H, D, dtype=dtype) / 10
40
+ k = F.normalize(torch.rand(B, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
41
+ v = torch.rand(B, T, H, D, dtype=dtype) / 10
42
+ beta = torch.rand(B, T, H, dtype=dtype).sigmoid()
43
+ lower_gate, upper_gate = gate_range
44
+ g = torch.rand(B, T, H, dtype=dtype).float().uniform_(lower_gate, upper_gate).log()
45
+ lamb = torch.rand(H, D, dtype=dtype).sigmoid() * 0.75 + 0.25
46
+ q, k, v, beta, g, lamb = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, beta, g, lamb))
47
+ do = torch.rand_like(v)
48
+
49
+ k_init_rand = torch.nn.functional.normalize(torch.rand(B, H, D, device=device, dtype=dtype), dim=-1, p=2)
50
+ h_kk_init = (k_init_rand.unsqueeze(-1) * k_init_rand.unsqueeze(-2)).detach().clone().float().requires_grad_(True)
51
+ h_kv_init = torch.rand(B, H, D, D, dtype=torch.float32, device=device).requires_grad_(True)
52
+ d_h_kk_final = torch.rand_like(h_kk_init)
53
+ d_h_kv_final = torch.rand_like(h_kv_init)
54
+
55
+ tri, tri_kk_final, tri_kv_final = chunk_mesa_net(
56
+ q=q.clone(),
57
+ k=k.clone(),
58
+ v=v.clone(),
59
+ beta=beta.clone(),
60
+ g=g.clone(),
61
+ lamb=lamb.clone(),
62
+ max_CG_iteration=D,
63
+ h_kk_init=h_kk_init.clone(),
64
+ h_kv_init=h_kv_init.clone(),
65
+ output_final_state=True,
66
+ )
67
+
68
+ ((tri * do).sum() + (tri_kk_final * d_h_kk_final).sum() + (tri_kv_final * d_h_kv_final).sum()).backward(retain_graph=True)
69
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dlamb = q.grad, k.grad, v.grad, beta.grad, g.grad, lamb.grad
70
+ tri_dh_kk_init, tri_dh_kv_init = h_kk_init.grad, h_kv_init.grad
71
+ q.grad = k.grad = v.grad = beta.grad = g.grad = lamb.grad = h_kk_init.grad = h_kv_init.grad = None
72
+
73
+ ref, ref_hkk_final, ref_hkv_final = naive_mesa_net_exact(
74
+ q=q.clone(),
75
+ k=k.clone(),
76
+ v=v.clone(),
77
+ beta=beta.clone(),
78
+ g=g.clone(),
79
+ lamb=lamb.clone(),
80
+ h_kk_init=h_kk_init.clone(),
81
+ h_kv_init=h_kv_init.clone(),
82
+ )
83
+
84
+ ((ref * do).sum() +
85
+ (ref_hkk_final * d_h_kk_final).sum() + (ref_hkv_final * d_h_kv_final).sum()).backward(retain_graph=True)
86
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dlamb = q.grad, k.grad, v.grad, beta.grad, g.grad, lamb.grad
87
+ ref_dh_kk_init, ref_dh_kv_init = h_kk_init.grad, h_kv_init.grad
88
+ q.grad = k.grad = v.grad = beta.grad = g.grad = lamb.grad = h_kk_init.grad = h_kv_init.grad = None
89
+
90
+ assert_close('o', ref, tri, 0.006)
91
+ assert_close('h_kk_final', ref_hkk_final, tri_kk_final, 0.008)
92
+ assert_close('h_kv_final', ref_hkv_final, tri_kv_final, 0.008)
93
+ assert_close('dq', ref_dq, tri_dq, 0.008)
94
+ assert_close('dk', ref_dk, tri_dk, 0.008)
95
+ assert_close('dv', ref_dv, tri_dv, 0.008)
96
+ assert_close('db', ref_dbeta, tri_dbeta, 0.008)
97
+ assert_close('dg', ref_dg, tri_dg, 0.008)
98
+ assert_close('dlamb', ref_dlamb, tri_dlamb, 0.015)
99
+ assert_close('dh_kk_init', ref_dh_kk_init, tri_dh_kk_init, 0.008)
100
+ assert_close('dh_kv_init', ref_dh_kv_init, tri_dh_kv_init, 0.008)
101
+
102
+
103
+ @pytest.mark.parametrize(
104
+ ('H', 'D', 'gate_range', 'cu_seqlens', 'dtype'),
105
+ [
106
+ pytest.param(*test, id="H{}-D{}-gate_range{}-cu_seqlens{}-{}".format(*test))
107
+ for test in [
108
+ (3, 50, [0.8, 0.99], [0, 15], torch.float16),
109
+ (4, 64, [0.8, 0.99], [0, 14, 121, 421, 500], torch.float16),
110
+ (4, 64, [0.01, 0.1], [0, 256, 500, 1000], torch.float16),
111
+ (4, 100, [1, 1], [0, 15, 100, 300, 1200, 2000], torch.float16),
112
+ ]
113
+ ],
114
+ )
115
+ @pytest.mark.skipif(
116
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
117
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
118
+ )
119
+ def test_chunk_varlen(
120
+ H: int,
121
+ D: int,
122
+ gate_range: tuple[float, float],
123
+ cu_seqlens: list[int],
124
+ dtype: torch.dtype,
125
+ ):
126
+ if is_intel_alchemist and D > 128:
127
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
128
+ torch.manual_seed(42)
129
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
130
+ # randomly split the sequence into N segments
131
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.long, device=device)
132
+ T = cu_seqlens[-1]
133
+ N = len(cu_seqlens) - 1
134
+ # seq-first required for inputs with variable lengths
135
+ q = torch.randn((1, T, H, D), dtype=dtype) / 10
136
+ k = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
137
+ v = torch.randn((1, T, H, D), dtype=dtype) / 10
138
+ lower_gate, upper_gate = gate_range
139
+ g = torch.rand(1, T, H, dtype=dtype).float().uniform_(lower_gate, upper_gate).log()
140
+ beta = torch.rand(1, T, H, dtype=dtype).sigmoid()
141
+ lamb = torch.rand(H, D, dtype=dtype).sigmoid() * 0.75 + 0.25
142
+
143
+ k_init_rand = torch.nn.functional.normalize(torch.rand(N, H, D, device=device, dtype=dtype), dim=-1, p=2)
144
+ h_kk_init = (k_init_rand.unsqueeze(-1) * k_init_rand.unsqueeze(-2)).detach().clone().float().requires_grad_(True)
145
+ h_kv_init = torch.rand(N, H, D, D, dtype=torch.float32, device=device).requires_grad_(True)
146
+
147
+ q, k, v, beta, g, lamb, h_kk_init, h_kv_init = map(lambda x: x.to(
148
+ device).requires_grad_(), (q, k, v, beta, g, lamb, h_kk_init, h_kv_init))
149
+ do = torch.rand_like(v) / 10
150
+ d_h_kk_final = torch.rand_like(h_kk_init)
151
+ d_h_kv_final = torch.rand_like(h_kv_init)
152
+
153
+ tri, tri_h_kk_final, tri_h_kv_final = chunk_mesa_net(
154
+ q=q.clone(),
155
+ k=k.clone(),
156
+ v=v.clone(),
157
+ beta=beta.clone(),
158
+ g=g.clone(),
159
+ lamb=lamb.clone(),
160
+ h_kk_init=h_kk_init.clone(),
161
+ h_kv_init=h_kv_init.clone(),
162
+ output_final_state=True,
163
+ cu_seqlens=cu_seqlens,
164
+ )
165
+
166
+ ((tri * do).sum() +
167
+ (tri_h_kk_final * d_h_kk_final).sum() + (tri_h_kv_final * d_h_kv_final).sum()).backward(retain_graph=True)
168
+ tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dlamb, tri_dh_kk_init, tri_dh_kv_init = \
169
+ q.grad, k.grad, v.grad, beta.grad, g.grad, lamb.grad, h_kk_init.grad, h_kv_init.grad
170
+ q.grad = k.grad = v.grad = beta.grad = g.grad = lamb.grad = h_kk_init.grad = h_kv_init.grad = None
171
+
172
+ ref = []
173
+ ref_h_kk_t = []
174
+ ref_h_kv_t = []
175
+ for i in range(N):
176
+ ref_i, ref_h_kk_i, ref_h_kv_i = naive_mesa_net_exact(
177
+ q=q[:, cu_seqlens[i]:cu_seqlens[i+1]],
178
+ k=k[:, cu_seqlens[i]:cu_seqlens[i+1]],
179
+ v=v[:, cu_seqlens[i]:cu_seqlens[i+1]],
180
+ beta=beta[:, cu_seqlens[i]:cu_seqlens[i+1]],
181
+ g=g[:, cu_seqlens[i]:cu_seqlens[i+1]],
182
+ lamb=lamb,
183
+ h_kk_init=h_kk_init[i],
184
+ h_kv_init=h_kv_init[i],
185
+ )
186
+ ref.append(ref_i)
187
+ ref_h_kk_t.append(ref_h_kk_i)
188
+ ref_h_kv_t.append(ref_h_kv_i)
189
+ ref = torch.cat(ref, 1)
190
+ ref_h_kk_t = torch.cat(ref_h_kk_t, 0)
191
+ ref_h_kv_t = torch.cat(ref_h_kv_t, 0)
192
+
193
+ ((ref * do).sum() + (ref_h_kk_t * d_h_kk_final).sum() + (ref_h_kv_t * d_h_kv_final).sum()).backward(retain_graph=True)
194
+ ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dlamb, ref_dh_kk_init, ref_dh_kv_init = \
195
+ q.grad, k.grad, v.grad, beta.grad, g.grad, lamb.grad, h_kk_init.grad, h_kv_init.grad
196
+ q.grad = k.grad = v.grad = beta.grad = g.grad = lamb.grad = h_kk_init.grad = h_kv_init.grad = None
197
+
198
+ assert_close('o', ref, tri, 0.006)
199
+ assert_close('h_kk_final', ref_h_kk_t, tri_h_kk_final, 0.008)
200
+ assert_close('h_kv_final', ref_h_kv_t, tri_h_kv_final, 0.008)
201
+ assert_close('dq', ref_dq, tri_dq, 0.008)
202
+ assert_close('dk', ref_dk, tri_dk, 0.008)
203
+ assert_close('dv', ref_dv, tri_dv, 0.008)
204
+ assert_close('db', ref_dbeta, tri_dbeta, 0.015)
205
+ assert_close('dlamb', ref_dlamb, tri_dlamb, 0.015)
206
+ assert_close('dg', ref_dg, tri_dg, 0.015)
207
+ assert_close('dh_kk_0', ref_dh_kk_init, tri_dh_kk_init, 0.007)
208
+ assert_close('dh_kv_0', ref_dh_kv_init, tri_dh_kv_init, 0.007)
209
+
210
+
211
+ @pytest.mark.parametrize(
212
+ ('B', 'H', 'D', 'gate_range', 'max_CG_step', 'dtype'),
213
+ [
214
+ pytest.param(*test, id="B{}-H{}-D{}-gate_range{}-max_CG_step{}-{}".format(*test))
215
+ for test in [
216
+ (1, 3, 50, [0.95, 0.99], 1, torch.float16),
217
+ (2, 4, 60, [0.95, 0.99], 5, torch.float16),
218
+ (2, 8, 128, [0.95, 0.99], 1, torch.float16),
219
+ (2, 8, 128, [0.95, 0.99], 5, torch.float16),
220
+ (2, 8, 128, [0.95, 0.99], 30, torch.float16),
221
+ ]
222
+ ],
223
+ )
224
+ def test_decoding_one_step(
225
+ B: int,
226
+ H: int,
227
+ D: int,
228
+ gate_range: tuple[float, float],
229
+ max_CG_step: int,
230
+ dtype: torch.dtype,
231
+ ):
232
+ if is_intel_alchemist and D > 128:
233
+ pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128')
234
+ torch.manual_seed(42)
235
+ torch.set_default_device(device)
236
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
237
+ # randomly split the sequence into N segments
238
+ q = torch.rand((B, H, D), dtype=dtype)
239
+ k = F.normalize(torch.randn(B, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
240
+ v = torch.rand((B, H, D), dtype=dtype)
241
+ lower_gate, upper_gate = gate_range
242
+ g = torch.rand(B, H, dtype=dtype).float().uniform_(lower_gate, upper_gate).log()
243
+ beta = torch.rand(B, H, dtype=dtype).sigmoid()
244
+ lamb = torch.rand(H, D, dtype=dtype).sigmoid() * 0.75 + 0.25
245
+
246
+ k_init_rand = torch.nn.functional.normalize(torch.rand(B, H, D, device=device, dtype=dtype), dim=-1, p=2)
247
+ prev_h_kk = (k_init_rand.unsqueeze(-1) * k_init_rand.unsqueeze(-2)).detach().clone().float().requires_grad_(True)
248
+ prev_h_kv = torch.rand(B, H, D, D, dtype=torch.float32, device=device).requires_grad_(True)
249
+
250
+ o, curr_h_kk, curr_h_kv = mesa_net_decoding_one_step(
251
+ q=q.clone(),
252
+ k=k.clone(),
253
+ v=v.clone(),
254
+ g=g.clone(),
255
+ lamb=lamb.clone(),
256
+ beta=beta.clone(),
257
+ prev_h_kk=prev_h_kk.clone(),
258
+ prev_h_kv=prev_h_kv.clone(),
259
+ max_CG_iteration=max_CG_step,
260
+ )
261
+
262
+ o_ref, curr_h_kk_re, curr_h_kv_re = naive_mesa_net_decoding_one_step(
263
+ q=q.clone(),
264
+ k=k.clone(),
265
+ v=v.clone(),
266
+ g=g.clone(),
267
+ lamb=lamb.clone(),
268
+ beta=beta.clone(),
269
+ prev_h_kk=prev_h_kk.clone(),
270
+ prev_h_kv=prev_h_kv.clone(),
271
+ max_CG_iteration=max_CG_step,
272
+ )
273
+
274
+ assert_close('o', o, o_ref, 0.005)
275
+ assert_close('curr_h_kk', curr_h_kk, curr_h_kk_re, 0.005)
276
+ assert_close('curr_h_kv', curr_h_kv, curr_h_kv_re, 0.005)
code/flash-linear-attention/tests/ops/test_nsa.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import triton
7
+
8
+ from fla.ops.nsa.naive import naive_nsa
9
+ from fla.ops.nsa.parallel import parallel_nsa
10
+ from fla.ops.utils import prepare_token_indices
11
+ from fla.utils import assert_close, device
12
+
13
+
14
+ # FIXME
15
+ @pytest.mark.parametrize(
16
+ ('B', 'T', 'H', 'HQ', 'D', 'S', 'block_size', 'scale', 'dtype'),
17
+ [
18
+ pytest.param(*test, id="B{}-T{}-H{}-HQ{}-D{}-S{}-block_size{}-scale{}-{}".format(*test))
19
+ for test in [
20
+ (1, 63, 1, 16, 64, 16, 32, 1.0, torch.float16),
21
+ (3, 111, 1, 32, 100, 16, 32, 1.0, torch.float16),
22
+ (3, 1024, 2, 32, 60, 16, 32, 0.1, torch.float16),
23
+ (3, 1024, 2, 32, 128, 16, 32, 0.1, torch.float16),
24
+ (4, 2048, 2, 32, 64, 16, 32, 0.1, torch.float16),
25
+ ]
26
+ ],
27
+ )
28
+ def test_parallel(
29
+ B: int,
30
+ T: int,
31
+ H: int,
32
+ HQ: int,
33
+ D: int,
34
+ S: int,
35
+ block_size: int,
36
+ scale: float,
37
+ dtype: torch.dtype,
38
+ ):
39
+ torch.manual_seed(42)
40
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
41
+
42
+ q = torch.randn((B, T, HQ, D), dtype=dtype, device=device).requires_grad_(True)
43
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
44
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
45
+ do = torch.randn((B, T, HQ, D), dtype=dtype, device=device)
46
+
47
+ block_indices = torch.full((B, T, H, S), T, dtype=torch.long, device=device)
48
+ for b in range(B):
49
+ for t in range(T):
50
+ for h in range(H):
51
+ i_i = torch.randperm(max(1, triton.cdiv(t, block_size)))[:S]
52
+ block_indices[b, t, h, :len(i_i)] = i_i
53
+ block_indices = block_indices.sort(-1)[0]
54
+
55
+ ref = naive_nsa(q=q, k=k, v=v, block_indices=block_indices, block_size=block_size, scale=scale)
56
+ ref.backward(do)
57
+ ref_dq, q.grad = q.grad.clone(), None
58
+ ref_dk, k.grad = k.grad.clone(), None
59
+ ref_dv, v.grad = v.grad.clone(), None
60
+
61
+ tri = parallel_nsa(q=q, k=k, v=v, block_indices=block_indices, block_size=block_size, scale=scale)
62
+ tri.backward(do)
63
+ tri_dq, q.grad = q.grad.clone(), None
64
+ tri_dk, k.grad = k.grad.clone(), None
65
+ tri_dv, v.grad = v.grad.clone(), None
66
+
67
+ assert_close(" o", ref, tri, 0.005)
68
+ assert_close("dq", ref_dq, tri_dq, 0.005)
69
+ assert_close("dk", ref_dk, tri_dk, 0.005)
70
+ assert_close("dv", ref_dv, tri_dv, 0.005)
71
+
72
+
73
+ @pytest.mark.parametrize(
74
+ ('H', 'HQ', 'D', 'S', 'block_size', 'cu_seqlens', 'dtype'),
75
+ [
76
+ pytest.param(*test, id="H{}-HQ{}-D{}-S{}-block_size{}-cu_seqlens{}-{}".format(*test))
77
+ for test in [
78
+ (1, 16, 64, 16, 32, [0, 15], torch.float16),
79
+ (2, 32, 64, 16, 32, [0, 256, 500, 1000], torch.float16),
80
+ (2, 32, 100, 16, 32, [0, 15, 100, 300, 1200, 2000], torch.float16),
81
+ ]
82
+ ],
83
+ )
84
+ @pytest.mark.skipif(
85
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
86
+ reason='Skipping test because SKIP_TEST_CHUNK_VARLEN is set',
87
+ )
88
+ def test_parallel_varlen(
89
+ H: int,
90
+ HQ: int,
91
+ D: int,
92
+ S: int,
93
+ block_size: int,
94
+ cu_seqlens: list[int],
95
+ dtype: torch.dtype,
96
+ ):
97
+ torch.manual_seed(42)
98
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
99
+
100
+ T = cu_seqlens[-1]
101
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
102
+
103
+ # seq-first required for inputs with variable lengths
104
+ q = torch.randn((1, T, HQ, D), dtype=dtype, device=device).requires_grad_()
105
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
106
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
107
+ do = torch.randn((1, T, HQ, D), dtype=dtype, device=device)
108
+
109
+ block_indices = torch.full((1, T, H, S), T, dtype=torch.long, device=device)
110
+ seq_indices = prepare_token_indices(cu_seqlens).tolist()
111
+
112
+ for i in range(T):
113
+ _, t = seq_indices[i]
114
+ for h in range(H):
115
+ i_i = torch.randperm(max(1, triton.cdiv(t, block_size)))[:S]
116
+ block_indices[0, i, h, :len(i_i)] = i_i
117
+ block_indices = block_indices.sort(-1)[0]
118
+
119
+ ref = naive_nsa(
120
+ q=q,
121
+ k=k,
122
+ v=v,
123
+ block_indices=block_indices,
124
+ block_size=block_size,
125
+ cu_seqlens=cu_seqlens,
126
+ )
127
+ ref.backward(do)
128
+ ref_dq, q.grad = q.grad.clone(), None
129
+ ref_dk, k.grad = k.grad.clone(), None
130
+ ref_dv, v.grad = v.grad.clone(), None
131
+
132
+ tri = parallel_nsa(
133
+ q=q,
134
+ k=k,
135
+ v=v,
136
+ block_indices=block_indices,
137
+ block_size=block_size,
138
+ cu_seqlens=cu_seqlens,
139
+ )
140
+ tri.backward(do)
141
+ tri_dq, q.grad = q.grad.clone(), None
142
+ tri_dk, k.grad = k.grad.clone(), None
143
+ tri_dv, v.grad = v.grad.clone(), None
144
+
145
+ assert_close('o', ref, tri, 0.004)
146
+ assert_close('dq', ref_dq, tri_dq, 0.005)
147
+ assert_close('dk', ref_dk, tri_dk, 0.005)
148
+ assert_close('dv', ref_dv, tri_dv, 0.005)
code/flash-linear-attention/tests/ops/test_path_attn.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from einops import rearrange
8
+
9
+ from fla.ops.path_attn.parallel import parallel_path_attention
10
+ from fla.utils import assert_close, device, is_intel_alchemist
11
+
12
+
13
+ def naive_path_attn(q, k, v, w, beta, g, scale, BT=64):
14
+ original_dtype = q.dtype
15
+ HQ = q.shape[2]
16
+ H = k.shape[2]
17
+ q, k, v, w, beta, g = map(lambda x: x.to(torch.float).transpose(1, 2), [q, k, v, w, beta, g])
18
+ g_cumsum = g.cumsum(-1)
19
+ q = q.unsqueeze(2).expand(-1, -1, HQ//HQ, -1, -1).flatten(1, 2)
20
+ k = k.unsqueeze(2).expand(-1, -1, HQ//H, -1, -1).flatten(1, 2)
21
+ v = v.unsqueeze(2).expand(-1, -1, HQ//H, -1, -1).flatten(1, 2)
22
+ w = w.unsqueeze(2).expand(-1, -1, HQ//H, -1, -1).flatten(1, 2)
23
+ beta = beta.unsqueeze(2).expand(-1, -1, HQ//H, -1).flatten(1, 2)
24
+
25
+ b, h, l, _ = q.shape
26
+ if l % BT != 0:
27
+ padding_size = BT - l % BT
28
+ q, k, w = map(lambda x: F.pad(x, (0, 0, 0, padding_size)), [q, k, w])
29
+ beta = F.pad(beta, (0, padding_size))
30
+ seq_len = q.shape[2]
31
+ w_beta = w * beta[..., None]
32
+ q, k, w, w_beta = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=BT), [q, k, w, w_beta])
33
+ mask = torch.triu(torch.ones(BT, BT, dtype=torch.bool, device=q.device), diagonal=0)
34
+ T = -(w_beta @ w.transpose(-1, -2)).masked_fill(mask, 0)
35
+ for i in range(1, BT):
36
+ T[..., i, :i] = T[..., i, :i].clone() + (T[..., i, :, None].clone() * T[..., :, :i].clone()).sum(-2)
37
+ T = T + torch.eye(BT, dtype=q.dtype, device=q.device)
38
+ Twbk = T @ (w_beta @ k.transpose(-1, -2)).masked_fill(mask, 0)
39
+ qw = (q @ w.transpose(-1, -2)).tril()
40
+ Twb = T @ w_beta
41
+ A_local = (q @ k.transpose(-1, -2)).tril() - qw @ Twbk
42
+ q = q - qw @ Twb
43
+ k = k - Twbk.transpose(-1, -2) @ w
44
+ H = w.transpose(-1, -2) @ Twb
45
+ A = torch.zeros(b, h, seq_len, seq_len, device=q.device)
46
+ q, k, w, w_beta = map(lambda x: rearrange(x, 'b h n c d -> b h (n c) d'), [q, k, w, w_beta])
47
+ for i in range(0, seq_len, BT):
48
+ q_i = q[:, :, i:i+BT].clone()
49
+ for j in range(i - BT, -BT, -BT):
50
+ k_j = k[:, :, j:j+BT]
51
+ A_ij = q_i @ k_j.transpose(-1, -2)
52
+ A[:, :, i:i+BT, j:j+BT] = A_ij
53
+ q_i = q_i - q_i @ H[:, :, j // BT]
54
+ for i in range(0, seq_len//BT):
55
+ A[:, :, i*BT:i*BT+BT, i*BT:i*BT+BT] = A_local[:, :, i]
56
+ A = A.masked_fill_(~torch.tril(torch.ones(seq_len, seq_len, device=q.device, dtype=torch.bool)), float("-inf"))
57
+ A = A[:, :, :l, :l]
58
+ A = A + g_cumsum[..., None] - g_cumsum[..., None, :]
59
+ ref_o = (A * scale).softmax(-1).to(v) @ v
60
+ return ref_o.to(original_dtype).transpose(1, 2)
61
+
62
+
63
+ @pytest.mark.parametrize(
64
+ ('B', 'T', 'H', 'HQ', 'D', 'use_forget_gate', 'dtype'),
65
+ [
66
+ pytest.param(*test, id="B{}-T{}-H{}-HQ{}-D{}-use_forget_gate{}-{}".format(*test))
67
+ for test in [
68
+ # SY (2025/07/08): It somehow failed on Hopper with error msg: Aborted (core dumped)
69
+ # (10, 62, 2, 8, 128, True, torch.bfloat16),
70
+ (5, 512, 2, 8, 128, True, torch.bfloat16),
71
+ (3, 1024, 2, 8, 64, True, torch.bfloat16),
72
+ (2, 2000, 1, 4, 64, False, torch.bfloat16),
73
+ (1, 4000, 1, 2, 128, False, torch.bfloat16),
74
+ ]
75
+ ],
76
+ )
77
+ @pytest.mark.skipif(
78
+ is_intel_alchemist,
79
+ reason="Intel Triton Failure",
80
+ )
81
+ def test_parallel(
82
+ B: int,
83
+ H: int,
84
+ HQ: int,
85
+ T: int,
86
+ D: int,
87
+ use_forget_gate: bool,
88
+ dtype: torch.dtype,
89
+ ):
90
+ torch.manual_seed(42)
91
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
92
+
93
+ q = torch.randn((B, T, HQ, D), dtype=dtype, device=device).requires_grad_(True)
94
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
95
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
96
+ w = F.normalize(torch.randn((B, T, H, D), dtype=torch.float, device=device), dim=-1, p=2).requires_grad_(True)
97
+ beta = torch.empty((B, T, H), dtype=torch.float, device=device).uniform_(1.5, 2.0).requires_grad_(True)
98
+ if use_forget_gate:
99
+ g = torch.empty((B, T, HQ), dtype=torch.float, device=device).uniform_(
100
+ 0.95, 1).log().requires_grad_(True)
101
+ else:
102
+ g = None
103
+ do = torch.rand((B, T, HQ, D), dtype=dtype, device=device)
104
+ scale = D ** -0.5
105
+ ref = naive_path_attn(q, k, v, w, beta, torch.zeros(B, T, HQ, device=device, dtype=torch.float) if g is None else g, scale)
106
+ ref.backward(do)
107
+ ref_dq, q.grad = q.grad.clone(), None
108
+ ref_dk, k.grad = k.grad.clone(), None
109
+ ref_dv, v.grad = v.grad.clone(), None
110
+ if use_forget_gate:
111
+ ref_dg, g.grad = g.grad.clone(), None
112
+ ref_dw, w.grad = w.grad.clone(), None
113
+ ref_db, beta.grad = beta.grad.clone(), None
114
+
115
+ tri, _ = parallel_path_attention(q=q, k=k, v=v, w=w, beta=beta, g=g, scale=scale)
116
+ tri.backward(do)
117
+ tri_dq, q.grad = q.grad.clone(), None
118
+ tri_dk, k.grad = k.grad.clone(), None
119
+ tri_dv, v.grad = v.grad.clone(), None
120
+ if use_forget_gate:
121
+ tri_dg, g.grad = g.grad.clone(), None
122
+ tri_dw, w.grad = w.grad.clone(), None
123
+ tri_db, beta.grad = beta.grad.clone(), None
124
+
125
+ assert_close(" o", ref, tri, 0.005)
126
+ assert_close("dq", ref_dq, tri_dq, 0.008)
127
+ assert_close("dk", ref_dk, tri_dk, 0.008)
128
+ assert_close("dv", ref_dv, tri_dv, 0.008)
129
+ if use_forget_gate:
130
+ assert_close("dg", ref_dg, tri_dg, 0.02)
131
+ assert_close("dw", ref_dw, tri_dw, 0.015)
132
+ assert_close("db", ref_db, tri_db, 0.02)
133
+
134
+
135
+ @pytest.mark.parametrize(
136
+ ('H', 'HQ', 'D', 'use_forget_gate', 'cu_seqlens', 'dtype'),
137
+ [
138
+ pytest.param(*test, id="H{}-HQ{}-D{}-use_forget_gate{}-cu_seqlens{}-{}".format(*test))
139
+ for test in [
140
+ (2, 4, 128, False, [0, 15, 333, 2048], torch.float16),
141
+ (2, 4, 128, True, [0, 15, 333, 2048], torch.float16),
142
+ (2, 4, 64, True, [0, 841, 889, 4096], torch.float16),
143
+ (2, 4, 64, False, [0, 841, 889, 2000, 3000, 4096], torch.float16),
144
+ (2, 16, 128, True, [0, 500, 1023, 2000, 3000, 4096], torch.float16),
145
+ ]
146
+ ],
147
+ )
148
+ @pytest.mark.skipif(
149
+ os.getenv("SKIP_TEST_CHUNK_VARLEN") == "0",
150
+ reason="Skipping test because TEST_CHUNK_VARLEN is enabled",
151
+ )
152
+ @pytest.mark.skipif(
153
+ is_intel_alchemist,
154
+ reason="Intel Triton Failure",
155
+ )
156
+ def test_parallel_varlen(
157
+ H: int,
158
+ HQ: int,
159
+ D: int,
160
+ use_forget_gate: bool,
161
+ cu_seqlens: list[int],
162
+ dtype: torch.dtype,
163
+ ):
164
+ torch.manual_seed(42)
165
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
166
+ T = cu_seqlens[-1]
167
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
168
+
169
+ q = torch.randn((1, T, HQ, D), dtype=dtype, device=device).requires_grad_(True)
170
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_(True)
171
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_(True)
172
+ w = F.normalize(torch.randn((1, T, H, D), dtype=torch.float, device=device), dim=-1, p=2).requires_grad_(True)
173
+ beta = torch.rand((1, T, H), dtype=torch.float, device=device).sigmoid().requires_grad_(True)
174
+ if use_forget_gate:
175
+ g = torch.empty((1, T, HQ), dtype=torch.float, device=device).uniform_(0.95, 1).log().requires_grad_(True)
176
+ else:
177
+ g = None
178
+ do = torch.randn((1, T, HQ, D), dtype=dtype, device=device)
179
+ scale = D ** -0.5
180
+ ref = torch.zeros(1, T, HQ, D, device=device, dtype=dtype)
181
+ for bos, eos in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False):
182
+ g_segment = torch.zeros(1, eos - bos, HQ, device=device, dtype=torch.float) if g is None else g[:, bos:eos]
183
+ ref[:, bos:eos] = naive_path_attn(
184
+ q[:, bos:eos], k[:, bos:eos], v[:, bos:eos],
185
+ w[:, bos:eos], beta[:, bos:eos], g_segment, scale,
186
+ )
187
+ ref.backward(do)
188
+ ref_dq, q.grad = q.grad.clone(), None
189
+ ref_dk, k.grad = k.grad.clone(), None
190
+ ref_dv, v.grad = v.grad.clone(), None
191
+ if use_forget_gate:
192
+ ref_dg, g.grad = g.grad.clone(), None
193
+ ref_dw, w.grad = w.grad.clone(), None
194
+ ref_db, beta.grad = beta.grad.clone(), None
195
+ tri, _ = parallel_path_attention(q=q, k=k, v=v, w=w, beta=beta, g=g, scale=scale, cu_seqlens=cu_seqlens)
196
+ tri.backward(do)
197
+ tri_dq, q.grad = q.grad.clone(), None
198
+ tri_dk, k.grad = k.grad.clone(), None
199
+ tri_dv, v.grad = v.grad.clone(), None
200
+ if use_forget_gate:
201
+ tri_dg, g.grad = g.grad.clone(), None
202
+ tri_dw, w.grad = w.grad.clone(), None
203
+ tri_db, beta.grad = beta.grad.clone(), None
204
+ assert_close(" o", ref, tri, 0.005)
205
+ assert_close("dq", ref_dq, tri_dq, 0.005)
206
+ assert_close("dk", ref_dk, tri_dk, 0.005)
207
+ assert_close("dv", ref_dv, tri_dv, 0.005)
208
+ if use_forget_gate:
209
+ assert_close("dg", ref_dg, tri_dg, 0.005)
210
+ assert_close("dw", ref_dw, tri_dw, 0.005)
211
+ assert_close("db", ref_db, tri_db, 0.005)
code/flash-linear-attention/tests/ops/test_retention.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+
7
+ from fla.ops.retention import chunk_retention, fused_chunk_retention, fused_recurrent_retention, parallel_retention
8
+ from fla.utils import assert_close, device
9
+
10
+
11
+ @pytest.mark.parametrize(
12
+ ('B', 'T', 'H', 'K', 'expand_ratio', 'dtype'),
13
+ [
14
+ pytest.param(*test, id="B{}-T{}-H{}-K{}-expand_ratio{}-{}".format(*test))
15
+ for test in [
16
+ (1, 63, 1, 64, 1, torch.float16),
17
+ (2, 500, 3, 60, 1, torch.float16),
18
+ (2, 1000, 3, 100, 1, torch.float16),
19
+ (2, 1000, 3, 128, 2, torch.float16),
20
+ (3, 1024, 4, 256, 2, torch.float16),
21
+ (4, 2048, 4, 64, 2, torch.float16),
22
+ ]
23
+ ],
24
+ )
25
+ def test_chunk(
26
+ B: int,
27
+ T: int,
28
+ H: int,
29
+ K: int,
30
+ expand_ratio: int,
31
+ dtype: torch.dtype,
32
+ ):
33
+ torch.manual_seed(42)
34
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
35
+ V = K * expand_ratio
36
+
37
+ q = torch.randn((B, T, H, K), dtype=dtype, device=device).requires_grad_()
38
+ k = torch.randn((B, T, H, K), dtype=dtype, device=device).requires_grad_()
39
+ v = torch.randn((B, T, H, V), dtype=dtype, device=device).requires_grad_()
40
+ h0 = torch.randn((B, H, K, V), dtype=dtype, device=device).requires_grad_()
41
+
42
+ do = torch.randn_like(v)
43
+ dht = torch.randn_like(h0)
44
+ ref, ref_ht = fused_recurrent_retention(q, k, v, initial_state=h0, output_final_state=True)
45
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
46
+ ref_dq, q.grad = q.grad.clone(), None
47
+ ref_dk, k.grad = k.grad.clone(), None
48
+ ref_dv, v.grad = v.grad.clone(), None
49
+
50
+ tri, tri_ht = chunk_retention(q, k, v, initial_state=h0, output_final_state=True)
51
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
52
+ tri_dq, q.grad = q.grad.clone(), None
53
+ tri_dk, k.grad = k.grad.clone(), None
54
+ tri_dv, v.grad = v.grad.clone(), None
55
+
56
+ assert_close('o', ref, tri, 0.005)
57
+ assert_close('ht', ref_ht, tri_ht, 0.005)
58
+ assert_close('dq', ref_dq, tri_dq, 0.005)
59
+ assert_close('dk', ref_dk, tri_dk, 0.005)
60
+ assert_close('dv', ref_dv, tri_dv, 0.005)
61
+
62
+
63
+ @pytest.mark.parametrize(
64
+ ('H', 'K', 'expand_ratio', 'cu_seqlens', 'dtype'),
65
+ [
66
+ pytest.param(*test, id="H{}-K{}-expand_ratio{}-cu_seqlens{}-{}".format(*test))
67
+ for test in [
68
+ (4, 64, 1, [0, 15], torch.float16),
69
+ (4, 64, 2, [0, 256, 500, 1000], torch.float16),
70
+ (4, 100, 2, [0, 15, 100, 300, 1200, 2000], torch.float16),
71
+ ]
72
+ ],
73
+ )
74
+ @pytest.mark.skipif(
75
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
76
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
77
+ )
78
+ def test_chunk_varlen(
79
+ H: int,
80
+ K: int,
81
+ expand_ratio: int,
82
+ cu_seqlens: list[int],
83
+ dtype: torch.dtype,
84
+ ):
85
+ torch.manual_seed(42)
86
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
87
+ V = K * expand_ratio
88
+
89
+ N = len(cu_seqlens) - 1
90
+ T = cu_seqlens[-1]
91
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.long, device=device)
92
+
93
+ # seq-first required for inputs with variable lengths
94
+ q = torch.randn((1, T, H, K), dtype=dtype, device=device).requires_grad_()
95
+ k = torch.randn((1, T, H, K), dtype=dtype, device=device).requires_grad_()
96
+ v = torch.randn((1, T, H, V), dtype=dtype, device=device).requires_grad_()
97
+ h0 = torch.randn((N, H, K, V), dtype=dtype, device=device).requires_grad_()
98
+ do = torch.randn_like(v)
99
+ dht = torch.randn_like(h0)
100
+
101
+ ref, ref_ht = fused_recurrent_retention(
102
+ q=q,
103
+ k=k,
104
+ v=v,
105
+ initial_state=h0,
106
+ output_final_state=True,
107
+ cu_seqlens=cu_seqlens,
108
+ )
109
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
110
+ ref_dq, q.grad = q.grad.clone(), None
111
+ ref_dk, k.grad = k.grad.clone(), None
112
+ ref_dv, v.grad = v.grad.clone(), None
113
+ ref_dh0, h0.grad = h0.grad.clone(), None
114
+
115
+ tri, tri_ht = chunk_retention(
116
+ q=q,
117
+ k=k,
118
+ v=v,
119
+ initial_state=h0,
120
+ output_final_state=True,
121
+ cu_seqlens=cu_seqlens,
122
+ )
123
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
124
+ tri_dq, q.grad = q.grad.clone(), None
125
+ tri_dk, k.grad = k.grad.clone(), None
126
+ tri_dv, v.grad = v.grad.clone(), None
127
+ tri_dh0, h0.grad = h0.grad.clone(), None
128
+
129
+ assert_close('o', ref, tri, 0.004)
130
+ assert_close('ht', ref_ht, tri_ht, 0.005)
131
+ assert_close('dq', ref_dq, tri_dq, 0.005)
132
+ assert_close('dk', ref_dk, tri_dk, 0.005)
133
+ assert_close('dv', ref_dv, tri_dv, 0.005)
134
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
135
+
136
+
137
+ @pytest.mark.parametrize(
138
+ ('B', 'T', 'H', 'K', 'expand_ratio', 'dtype'),
139
+ [
140
+ pytest.param(*test, id="B{}-T{}-H{}-K{}-expand_ratio{}-{}".format(*test))
141
+ for test in [
142
+ (1, 63, 1, 64, 1, torch.float16),
143
+ (2, 500, 3, 60, 1, torch.float16),
144
+ (2, 1000, 3, 100, 1, torch.float16),
145
+ (2, 1000, 3, 128, 2, torch.float16),
146
+ (3, 1024, 4, 256, 2, torch.float16),
147
+ (4, 2048, 4, 64, 2, torch.float16),
148
+ ]
149
+ ],
150
+ )
151
+ def test_fused_chunk(
152
+ B: int,
153
+ T: int,
154
+ H: int,
155
+ K: int,
156
+ expand_ratio: int,
157
+ dtype: torch.dtype,
158
+ ):
159
+ torch.manual_seed(42)
160
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
161
+ V = K * expand_ratio
162
+
163
+ q = torch.randn((B, T, H, K), dtype=dtype, device=device).requires_grad_()
164
+ k = torch.randn((B, T, H, K), dtype=dtype, device=device).requires_grad_()
165
+ v = torch.randn((B, T, H, V), dtype=dtype, device=device).requires_grad_()
166
+ h0 = torch.randn((B, H, K, V), dtype=dtype, device=device).requires_grad_()
167
+
168
+ do = torch.randn_like(v)
169
+ dht = torch.randn_like(h0)
170
+ ref, ref_ht = fused_recurrent_retention(q, k, v, initial_state=h0, output_final_state=True)
171
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
172
+ ref_dq, q.grad = q.grad.clone(), None
173
+ ref_dk, k.grad = k.grad.clone(), None
174
+ ref_dv, v.grad = v.grad.clone(), None
175
+
176
+ tri, tri_ht = fused_chunk_retention(q, k, v, initial_state=h0, output_final_state=True)
177
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
178
+ tri_dq, q.grad = q.grad.clone(), None
179
+ tri_dk, k.grad = k.grad.clone(), None
180
+ tri_dv, v.grad = v.grad.clone(), None
181
+
182
+ assert_close('o', ref, tri, 0.005)
183
+ assert_close('ht', ref_ht, tri_ht, 0.005)
184
+ assert_close('dq', ref_dq, tri_dq, 0.005)
185
+ assert_close('dk', ref_dk, tri_dk, 0.005)
186
+ assert_close('dv', ref_dv, tri_dv, 0.005)
187
+
188
+
189
+ @pytest.mark.parametrize(
190
+ ('H', 'K', 'expand_ratio', 'cu_seqlens', 'dtype'),
191
+ [
192
+ pytest.param(*test, id="H{}-K{}-expand_ratio{}-cu_seqlens{}-{}".format(*test))
193
+ for test in [
194
+ (4, 64, 1, [0, 15], torch.float16),
195
+ (4, 64, 2, [0, 256, 500, 1000], torch.float16),
196
+ (4, 100, 2, [0, 15, 100, 300, 1200, 2000], torch.float16),
197
+ ]
198
+ ],
199
+ )
200
+ @pytest.mark.skipif(
201
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
202
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
203
+ )
204
+ def test_fused_chunk_varlen(
205
+ H: int,
206
+ K: int,
207
+ expand_ratio: int,
208
+ cu_seqlens: list[int],
209
+ dtype: torch.dtype,
210
+ ):
211
+ torch.manual_seed(42)
212
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
213
+ V = K * expand_ratio
214
+
215
+ N = len(cu_seqlens) - 1
216
+ T = cu_seqlens[-1]
217
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.long, device=device)
218
+
219
+ # seq-first required for inputs with variable lengths
220
+ q = torch.randn((1, T, H, K), dtype=dtype, device=device).requires_grad_()
221
+ k = torch.randn((1, T, H, K), dtype=dtype, device=device).requires_grad_()
222
+ v = torch.randn((1, T, H, V), dtype=dtype, device=device).requires_grad_()
223
+ h0 = torch.randn((N, H, K, V), dtype=dtype, device=device).requires_grad_()
224
+ do = torch.randn_like(v)
225
+ dht = torch.randn_like(h0)
226
+
227
+ ref, ref_ht = fused_recurrent_retention(
228
+ q=q,
229
+ k=k,
230
+ v=v,
231
+ initial_state=h0,
232
+ output_final_state=True,
233
+ cu_seqlens=cu_seqlens,
234
+ )
235
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
236
+ ref_dq, q.grad = q.grad.clone(), None
237
+ ref_dk, k.grad = k.grad.clone(), None
238
+ ref_dv, v.grad = v.grad.clone(), None
239
+ ref_dh0, h0.grad = h0.grad.clone(), None
240
+
241
+ tri, tri_ht = fused_chunk_retention(
242
+ q=q,
243
+ k=k,
244
+ v=v,
245
+ initial_state=h0,
246
+ output_final_state=True,
247
+ cu_seqlens=cu_seqlens,
248
+ )
249
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
250
+ tri_dq, q.grad = q.grad.clone(), None
251
+ tri_dk, k.grad = k.grad.clone(), None
252
+ tri_dv, v.grad = v.grad.clone(), None
253
+ tri_dh0, h0.grad = h0.grad.clone(), None
254
+
255
+ assert_close('o', ref, tri, 0.004)
256
+ assert_close('ht', ref_ht, tri_ht, 0.005)
257
+ assert_close('dq', ref_dq, tri_dq, 0.005)
258
+ assert_close('dk', ref_dk, tri_dk, 0.005)
259
+ assert_close('dv', ref_dv, tri_dv, 0.005)
260
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
261
+
262
+
263
+ @pytest.mark.parametrize(
264
+ ('B', 'T', 'H', 'K', 'expand_ratio', 'dtype'),
265
+ [
266
+ pytest.param(*test, id="B{}-T{}-H{}-K{}-expand_ratio{}-{}".format(*test))
267
+ for test in [
268
+ (1, 63, 1, 64, 1, torch.float16),
269
+ (2, 500, 4, 60, 1, torch.float16),
270
+ (2, 1024, 8, 128, 1, torch.float16),
271
+ (3, 1024, 8, 128, 2, torch.float16),
272
+ (3, 1024, 8, 256, 2, torch.float16),
273
+ (4, 2048, 8, 64, 2, torch.float16),
274
+ ]
275
+ ],
276
+ )
277
+ def test_parallel(
278
+ B: int,
279
+ T: int,
280
+ H: int,
281
+ K: int,
282
+ expand_ratio: int,
283
+ dtype: torch.dtype,
284
+ ):
285
+ torch.manual_seed(42)
286
+ V = K * expand_ratio
287
+
288
+ q = torch.randn((B, T, H, K), dtype=dtype, device=device).requires_grad_()
289
+ k = torch.randn((B, T, H, K), dtype=dtype, device=device).requires_grad_()
290
+ v = torch.randn((B, T, H, V), dtype=dtype, device=device).requires_grad_()
291
+ do = torch.randn_like(v)
292
+
293
+ ref, _ = fused_recurrent_retention(q, k, v)
294
+ ref.backward(do)
295
+ ref_dq, q.grad = q.grad.clone(), None
296
+ ref_dk, k.grad = k.grad.clone(), None
297
+ ref_dv, v.grad = v.grad.clone(), None
298
+
299
+ tri, _ = parallel_retention(q, k, v)
300
+ tri.backward(do)
301
+ tri_dq, q.grad = q.grad.clone(), None
302
+ tri_dk, k.grad = k.grad.clone(), None
303
+ tri_dv, v.grad = v.grad.clone(), None
304
+
305
+ assert_close('o', ref, tri, 0.005)
306
+ assert_close('dq', ref_dq, tri_dq, 0.005)
307
+ assert_close('dk', ref_dk, tri_dk, 0.005)
308
+ assert_close('dv', ref_dv, tri_dv, 0.005)
code/flash-linear-attention/tests/ops/test_rwkv6.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.rwkv6 import chunk_rwkv6
9
+ from fla.ops.rwkv6.fused_recurrent import fused_recurrent_rwkv6
10
+ from fla.utils import assert_close, device, device_platform
11
+
12
+
13
+ @pytest.mark.skipif(
14
+ device_platform == 'intel',
15
+ reason="Intel Triton Failure",
16
+ )
17
+ @pytest.mark.parametrize(
18
+ ('B', 'T', 'H', 'D', 'gate_logit_normalizer', 'dtype'),
19
+ [
20
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-gate_logit_normalizer{}-{}".format(*test))
21
+ for test in [
22
+ (1, 15, 2, 60, 1.0, torch.float16),
23
+ (3, 60, 3, 64, 0.1, torch.float16),
24
+ (3, 64, 2, 64, 1, torch.float16),
25
+ (4, 500, 3, 256, 1, torch.float16),
26
+ (4, 1000, 4, 64, 10, torch.float16),
27
+ (4, 2048, 4, 64, 1, torch.float16),
28
+ (4, 2048, 4, 256, 1, torch.float16),
29
+ ]
30
+ ],
31
+ )
32
+ def test_chunk(
33
+ B: int,
34
+ T: int,
35
+ H: int,
36
+ D: int,
37
+ gate_logit_normalizer: float,
38
+ dtype: torch.dtype,
39
+ ):
40
+ torch.manual_seed(42)
41
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
42
+
43
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
44
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
45
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
46
+ w = F.logsigmoid(torch.randn((B, T, H, D), dtype=dtype, device=device)) / gate_logit_normalizer
47
+
48
+ u = torch.randn(H, D, dtype=dtype, device=device).requires_grad_(True)
49
+ h0 = torch.randn(B, H, D, D, dtype=dtype, device=device).requires_grad_()
50
+ w = w.requires_grad_()
51
+ do = torch.randn_like(v)
52
+
53
+ ref, ref_ht = fused_recurrent_rwkv6(
54
+ q.clone(),
55
+ k.clone(),
56
+ v.clone(),
57
+ w.clone(),
58
+ u.clone(),
59
+ initial_state=h0.clone(),
60
+ output_final_state=True,
61
+ )
62
+ ref, _ = fused_recurrent_rwkv6(
63
+ q.clone(),
64
+ k.clone(),
65
+ v.clone(),
66
+ w.clone(),
67
+ u.clone(),
68
+ initial_state=h0.clone(),
69
+ output_final_state=False,
70
+ )
71
+
72
+ ((ref * do).sum()).backward()
73
+ ref_dq, q.grad = q.grad.clone(), None
74
+ ref_dk, k.grad = k.grad.clone(), None
75
+ ref_dv, v.grad = v.grad.clone(), None
76
+ ref_dw, w.grad = w.grad.clone(), None
77
+ ref_du, u.grad = u.grad.clone(), None
78
+ ref_dh0, h0.grad = h0.grad.clone(), None
79
+
80
+ # triton implementation
81
+ tri, tri_ht = chunk_rwkv6(
82
+ q.clone(),
83
+ k.clone(),
84
+ v.clone(),
85
+ w.clone(),
86
+ u.clone(),
87
+ initial_state=h0.clone(),
88
+ output_final_state=True,
89
+ )
90
+ ((tri * do).sum()).backward()
91
+ tri_dq, q.grad = q.grad.clone(), None
92
+ tri_dk, k.grad = k.grad.clone(), None
93
+ tri_dv, v.grad = v.grad.clone(), None
94
+ tri_dw, w.grad = w.grad.clone(), None
95
+ tri_du, u.grad = u.grad.clone(), None
96
+ tri_dh0, h0.grad = h0.grad.clone(), None
97
+
98
+ assert_close('o', ref, tri, 0.004)
99
+ assert_close('ht', ref_ht, tri_ht, 0.005)
100
+ assert_close('dq', ref_dq, tri_dq, 0.005)
101
+ assert_close('dk', ref_dk, tri_dk, 0.005)
102
+ assert_close('dv', ref_dv, tri_dv, 0.005)
103
+ assert_close('dw', ref_dw, tri_dw, 0.005)
104
+ assert_close('du', ref_du, tri_du, 0.005)
105
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
106
+
107
+
108
+ @pytest.mark.parametrize(
109
+ ('H', 'D', 'cu_seqlens', 'dtype'),
110
+ [
111
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
112
+ for test in [
113
+ (4, 64, [0, 15], torch.float16),
114
+ (4, 64, [0, 256, 500, 1000], torch.float16),
115
+ (4, 100, [0, 15, 100, 300, 1200, 2000], torch.float16),
116
+ ]
117
+ ],
118
+ )
119
+ def test_chunk_varlen(
120
+ H: int,
121
+ D: int,
122
+ cu_seqlens: list[int],
123
+ dtype: torch.dtype,
124
+ ):
125
+ torch.manual_seed(42)
126
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
127
+ N = len(cu_seqlens) - 1
128
+ T = cu_seqlens[-1]
129
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
130
+
131
+ # seq-first required for inputs with variable lengths
132
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
133
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
134
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
135
+ w = F.logsigmoid(torch.randn((1, T, H, D), dtype=dtype, device=device)).requires_grad_(True)
136
+ u = torch.randn(H, D, dtype=dtype, device=device).requires_grad_(True)
137
+ h0 = torch.randn((N, H, D, D), dtype=dtype, device=device).requires_grad_()
138
+ do = torch.randn_like(v)
139
+
140
+ ref, ref_ht = fused_recurrent_rwkv6(
141
+ q.clone(),
142
+ k.clone(),
143
+ v.clone(),
144
+ w.clone(),
145
+ u.clone(),
146
+ initial_state=h0.clone(),
147
+ output_final_state=True,
148
+ cu_seqlens=cu_seqlens,
149
+ )
150
+ ref, _ = fused_recurrent_rwkv6(
151
+ q.clone(),
152
+ k.clone(),
153
+ v.clone(),
154
+ w.clone(),
155
+ u.clone(),
156
+ initial_state=h0.clone(),
157
+ output_final_state=False,
158
+ cu_seqlens=cu_seqlens,
159
+ )
160
+ ref.backward(do)
161
+ ref_dq, q.grad = q.grad.clone(), None
162
+ ref_dk, k.grad = k.grad.clone(), None
163
+ ref_dv, v.grad = v.grad.clone(), None
164
+ ref_dw, w.grad = w.grad.clone(), None
165
+ ref_du, u.grad = u.grad.clone(), None
166
+ ref_dh0, h0.grad = h0.grad.clone(), None
167
+
168
+ tri, tri_ht = chunk_rwkv6(
169
+ q.clone(),
170
+ k.clone(),
171
+ v.clone(),
172
+ w.clone(),
173
+ u.clone(),
174
+ initial_state=h0.clone(),
175
+ output_final_state=True,
176
+ cu_seqlens=cu_seqlens,
177
+ )
178
+ tri.backward(do)
179
+ tri_dq, q.grad = q.grad.clone(), None
180
+ tri_dk, k.grad = k.grad.clone(), None
181
+ tri_dv, v.grad = v.grad.clone(), None
182
+ tri_dw, w.grad = w.grad.clone(), None
183
+ tri_du, u.grad = u.grad.clone(), None
184
+ tri_dh0, h0.grad = h0.grad.clone(), None
185
+ assert_close('o', ref, tri, 0.004)
186
+ assert_close('ht', ref_ht, tri_ht, 0.005)
187
+ assert_close('dq', ref_dq, tri_dq, 0.005)
188
+ assert_close('dk', ref_dk, tri_dk, 0.005)
189
+ assert_close('dv', ref_dv, tri_dv, 0.005)
190
+ assert_close('dw', ref_dw, tri_dw, 0.005)
191
+ assert_close('du', ref_du, tri_du, 0.005)
192
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
code/flash-linear-attention/tests/ops/test_rwkv7.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.generalized_delta_rule.dplr.fused_recurrent import fused_recurrent_dplr_delta_rule
9
+ from fla.ops.rwkv7.channel_mixing import channel_mixing_rwkv7, channel_mixing_rwkv7_torch
10
+ from fla.ops.rwkv7.fused_addcmul import fused_addcmul_rwkv7, torch_addcmul_rwkv7
11
+ from fla.ops.rwkv7.fused_k_update import fused_k_rwkv7, k_update_ref
12
+ from fla.ops.rwkv7.fused_recurrent import fused_mul_recurrent_rwkv7
13
+ from fla.ops.rwkv7.gate_output_correction import gate_output_correction, gate_output_correction_ref
14
+ from fla.utils import assert_close, device, is_nvidia_hopper
15
+
16
+
17
+ @pytest.mark.parametrize("B", [2])
18
+ @pytest.mark.parametrize("T", [1024])
19
+ @pytest.mark.parametrize("n_embd", [1024])
20
+ @pytest.mark.parametrize("dim_ffn", [4096])
21
+ @pytest.mark.parametrize("dtype", [torch.bfloat16])
22
+ @pytest.mark.parametrize("inplace", [True, False])
23
+ @pytest.mark.parametrize("xprevdim", [2, 3])
24
+ @pytest.mark.skipif(
25
+ os.getenv("SKIP_TEST_CHUNK_VARLEN") == "0",
26
+ reason="Skipping test because TEST_CHUNK_VARLEN is enabled",
27
+ )
28
+ def test_channel_mixing_gradients(B, T, n_embd, dim_ffn, dtype, inplace, xprevdim):
29
+ torch.manual_seed(42)
30
+ torch._dynamo.config.cache_size_limit = 512
31
+
32
+ x = torch.randn(
33
+ B, T, n_embd, device=device, dtype=dtype, requires_grad=True,
34
+ )
35
+ if xprevdim == 3:
36
+ x_prev = torch.randn(
37
+ B, 1, n_embd, device=device, dtype=dtype, requires_grad=True,
38
+ )
39
+ else:
40
+ x_prev = torch.randn(
41
+ B, n_embd, device=device, dtype=dtype, requires_grad=True,
42
+ )
43
+ x_k = torch.randn(1, 1, n_embd, device=device, dtype=dtype, requires_grad=True)
44
+ K_ = torch.randn(n_embd, dim_ffn, device=device, dtype=dtype, requires_grad=True)
45
+ V_ = torch.randn(dim_ffn, n_embd, device=device, dtype=dtype, requires_grad=True)
46
+
47
+ x2 = x.clone().detach().requires_grad_(True)
48
+ x_prev2 = x_prev.clone().detach().requires_grad_(True)
49
+ x_k2 = x_k.clone().detach().requires_grad_(True)
50
+ K_2 = K_.clone().detach().requires_grad_(True)
51
+ V_2 = V_.clone().detach().requires_grad_(True)
52
+
53
+ o1, last1 = channel_mixing_rwkv7_torch(
54
+ x.to(torch.float32),
55
+ x_prev.to(torch.float32),
56
+ x_k.to(torch.float32),
57
+ K_.to(torch.float32),
58
+ V_.to(torch.float32),
59
+ )
60
+ loss1 = o1.mean() + last1.mean()
61
+ loss1.backward()
62
+
63
+ o2, last2 = channel_mixing_rwkv7(x2, x_prev2, x_k2, K_2, V_2, inplace)
64
+ loss2 = o2.mean() + last2.mean()
65
+ loss2.backward()
66
+
67
+ assert_close(" dx", x.grad, x2.grad, ratio=5e-3)
68
+ assert_close(" dxprev", x_prev.grad, x_prev2.grad, ratio=5e-3)
69
+ assert_close(" dx_k", x_k.grad, x_k2.grad, ratio=5e-3)
70
+ assert_close(" dK_", K_.grad, K_2.grad, ratio=5e-3)
71
+ assert_close(" dV_", V_.grad, V_2.grad, ratio=5e-3)
72
+
73
+
74
+ @pytest.mark.parametrize('B', [2])
75
+ @pytest.mark.parametrize('T', [1, 1024])
76
+ @pytest.mark.parametrize('H', [1])
77
+ @pytest.mark.parametrize('D', [64])
78
+ @pytest.mark.parametrize('scale', [None, 1])
79
+ @pytest.mark.parametrize('dtype', [torch.float32])
80
+ @pytest.mark.skipif(
81
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '0',
82
+ reason='Skipping test because TEST_CHUNK_VARLEN is enabled',
83
+ )
84
+ def test_fused_mul_recurrent_fwd(
85
+ B: int,
86
+ T: int,
87
+ H: int,
88
+ D: int,
89
+ scale: float,
90
+ dtype: torch.dtype,
91
+ ):
92
+ torch.manual_seed(42)
93
+ r = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype)
94
+ k = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype)
95
+ v = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype)
96
+ w = torch.empty(B, T, H, D, device=device).uniform_(-8, -6).to(dtype=dtype)
97
+
98
+ kk = torch.empty(B, T, H, D, device=device).uniform_(-1, 1)
99
+ kk = F.normalize(kk, dim=-1).to(dtype=dtype)
100
+
101
+ a = -kk.clone()
102
+ a_scale = torch.empty(B, T, H, D, device=device).uniform_(0, 0.1).to(dtype=dtype)
103
+ b = (kk * a_scale).requires_grad_(False) # kk*a
104
+ h0 = torch.randn(B, H, D, D, dtype=torch.float)
105
+ r, k, v, a, a_scale, b, w, h0 = map(lambda x: x.to(device).requires_grad_(False),
106
+ (r, k, v, a, a_scale, b, w, h0))
107
+ ref, ref_ht = fused_recurrent_dplr_delta_rule(
108
+ q=r.clone(),
109
+ k=k.clone(),
110
+ v=v.clone(),
111
+ a=a.clone(),
112
+ b=b.clone(),
113
+ gk=w.clone(),
114
+ scale=scale,
115
+ initial_state=h0.clone(),
116
+ output_final_state=True,
117
+ )
118
+
119
+ tri, tri_ht = fused_mul_recurrent_rwkv7(
120
+ r=r.clone(),
121
+ w=w.clone(),
122
+ k=k.clone(),
123
+ v=v.clone(),
124
+ kk=kk.clone(),
125
+ a=a_scale.clone(),
126
+ scale=scale,
127
+ initial_state=h0.clone(),
128
+ output_final_state=True,
129
+ )
130
+ assert_close('o', ref, tri, 0.002)
131
+ assert_close('ht', ref_ht, tri_ht, 0.002)
132
+
133
+
134
+ @pytest.mark.parametrize("B", [1])
135
+ @pytest.mark.parametrize("T", [20, 1024, 4100, 131072])
136
+ @pytest.mark.parametrize("H", [2])
137
+ @pytest.mark.parametrize("D", [64])
138
+ @pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
139
+ @pytest.mark.parametrize("use_g", [True, False])
140
+ @pytest.mark.skipif(
141
+ os.getenv("SKIP_TEST_CHUNK_VARLEN") == "0",
142
+ reason="Skipping test because TEST_CHUNK_VARLEN is enabled",
143
+ )
144
+ def test_fused_rwkv7_addcmul(
145
+ B: int,
146
+ T: int,
147
+ H: int,
148
+ D: int,
149
+ dtype: torch.dtype,
150
+ use_g: bool,
151
+ ):
152
+ if T == 128 * 1024 and not is_nvidia_hopper:
153
+ pytest.skip("Skipping test for T=131072 on non-Hopper GPUs")
154
+ hidden_size = H*D
155
+ hidden_states = torch.randn(B, T, hidden_size).to(device).to(dtype).requires_grad_()
156
+ xx = torch.randn(B, T, hidden_size).to(device).to(dtype).requires_grad_()
157
+ x_r = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_()
158
+ x_w = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_()
159
+ x_k = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_()
160
+ x_v = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_()
161
+ x_a = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_()
162
+ if use_g:
163
+ x_g = torch.randn(1, 1, hidden_size).to(device).to(dtype).requires_grad_()
164
+ else:
165
+ x_g = None
166
+ xr0, xw0, xk0, xv0, xa0, xg0 = fused_addcmul_rwkv7(hidden_states, xx, x_r, x_w, x_k, x_v, x_a, x_g)
167
+ xr1, xw1, xk1, xv1, xa1, xg1 = torch_addcmul_rwkv7(hidden_states.float(),
168
+ xx.float(), x_r.float(),
169
+ x_w.float(), x_k.float(),
170
+ x_v.float(), x_a.float(),
171
+ x_g.float() if use_g else None)
172
+ ratio = 1e-5 if dtype == torch.float32 else 0.002
173
+ assert_close("xr0", xr0, xr1, ratio=ratio)
174
+ assert_close("xw0", xw0, xw1, ratio=ratio)
175
+ assert_close("xk0", xk0, xk1, ratio=ratio)
176
+ assert_close("xv0", xv0, xv1, ratio=ratio)
177
+ assert_close("xa0", xa0, xa1, ratio=ratio)
178
+ if use_g:
179
+ assert_close("xg0", xg0, xg1, ratio=ratio)
180
+ (xr0 + xw0 + xk0 + xv0 + xa0 + xg0).sum().backward()
181
+ else:
182
+ (xr0 + xw0 + xk0 + xv0 + xa0).sum().backward()
183
+ d_ixr = x_r.grad.clone()
184
+ d_ixw = x_w.grad.clone()
185
+ d_ixk = x_k.grad.clone()
186
+ d_ixv = x_v.grad.clone()
187
+ d_ixa = x_a.grad.clone()
188
+ d_hidden = hidden_states.grad.clone()
189
+ d_xx = xx.grad.clone()
190
+
191
+ x_r.grad.zero_()
192
+ x_w.grad.zero_()
193
+ x_k.grad.zero_()
194
+ x_v.grad.zero_()
195
+ x_a.grad.zero_()
196
+ if use_g:
197
+ d_ixg = x_g.grad.clone()
198
+ x_g.grad.zero_()
199
+ hidden_states.grad.zero_()
200
+ xx.grad.zero_()
201
+
202
+ if use_g:
203
+ (xr1 + xw1 + xk1 + xv1 + xa1 + xg1).sum().backward()
204
+ else:
205
+ (xr1 + xw1 + xk1 + xv1 + xa1).sum().backward()
206
+ d_ixr1 = x_r.grad.clone()
207
+ d_ixw1 = x_w.grad.clone()
208
+ d_ixk1 = x_k.grad.clone()
209
+ d_ixv1 = x_v.grad.clone()
210
+ d_ixa1 = x_a.grad.clone()
211
+ if use_g:
212
+ d_ixg1 = x_g.grad.clone()
213
+ d_hidden1 = hidden_states.grad.clone()
214
+ d_xx1 = xx.grad.clone()
215
+
216
+ assert_close("d_ixr", d_ixr, d_ixr1, ratio=ratio)
217
+ assert_close("d_ixw", d_ixw, d_ixw1, ratio=ratio)
218
+ assert_close("d_ixk", d_ixk, d_ixk1, ratio=ratio)
219
+ assert_close("d_ixv", d_ixv, d_ixv1, ratio=ratio)
220
+ assert_close("d_ixa", d_ixa, d_ixa1, ratio=ratio)
221
+ if use_g:
222
+ assert_close("d_ixg", d_ixg, d_ixg1, ratio=ratio)
223
+ assert_close("d_hidden", d_hidden, d_hidden1, ratio=ratio)
224
+ assert_close("d_xx", d_xx, d_xx1, ratio=ratio)
225
+
226
+
227
+ @pytest.mark.parametrize("B", [4])
228
+ @pytest.mark.parametrize("T", [13, 4096, 8000])
229
+ @pytest.mark.parametrize("H", [64])
230
+ @pytest.mark.parametrize("D", [64])
231
+ @pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
232
+ @pytest.mark.parametrize("ka_shape", [1, 3])
233
+ def test_fused_k_update(
234
+ B: int,
235
+ T: int,
236
+ H: int,
237
+ D: int,
238
+ dtype: torch.dtype,
239
+ ka_shape: int,
240
+ ):
241
+ k = torch.randn(B, T, H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_()
242
+ a = torch.randn(B, T, H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_()
243
+ if ka_shape == 1:
244
+ ka = torch.randn(H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_()
245
+ else:
246
+ ka = torch.randn(1, 1, H*D).uniform_(-8, 8).to(device).to(dtype).requires_grad_()
247
+
248
+ ref = k_update_ref(k.float(), a.float(), ka.float())
249
+ ref.sum().backward()
250
+ ref_dk, k.grad = k.grad.clone(), None
251
+ ref_da, a.grad = a.grad.clone(), None
252
+ ref_dka, ka.grad = ka.grad.clone(), None
253
+ tri = fused_k_rwkv7(k, a, ka)
254
+ tri.sum().backward()
255
+ ratio = 5e-5 if dtype == torch.float32 else 0.002
256
+ assert_close(" o", tri, ref, ratio=ratio)
257
+ assert_close(" dk", ref_dk, k.grad, ratio=ratio)
258
+ assert_close(" da", ref_da, a.grad, ratio=ratio)
259
+ assert_close("dka", ref_dka, ka.grad, ratio=ratio)
260
+
261
+
262
+ @pytest.mark.parametrize("B", [4])
263
+ @pytest.mark.parametrize("T", [4096])
264
+ @pytest.mark.parametrize("H", [64])
265
+ @pytest.mark.parametrize("D", [64])
266
+ @pytest.mark.parametrize("dtype", [torch.bfloat16])
267
+ def test_gate_output_correction(
268
+ B: int,
269
+ T: int,
270
+ H: int,
271
+ D: int,
272
+ dtype: torch.dtype,
273
+ ):
274
+ value_dim = H * D
275
+ torch.manual_seed(0)
276
+
277
+ o_ref = torch.randn(B, T, value_dim, device=device, dtype=dtype, requires_grad=True)
278
+ r_ref = torch.randn(B, T, H, D, device=device, dtype=dtype, requires_grad=True)
279
+ k_ref = torch.randn(B, T, H, D, device=device, dtype=dtype, requires_grad=True)
280
+ r_k_ref = torch.randn(H, D, device=device, dtype=dtype, requires_grad=True)
281
+ v_ref = torch.randn(B, T, H, D, device=device, dtype=dtype, requires_grad=True)
282
+ g_ref = torch.randn(B, T, value_dim, device=device, dtype=dtype, requires_grad=True)
283
+
284
+ tensors_cus = [t.clone().detach().requires_grad_(True) for t in [o_ref, r_ref, k_ref, r_k_ref, v_ref, g_ref]]
285
+ o_cus, r_cus, k_cus, r_k_cus, v_cus, g_cus = tensors_cus
286
+
287
+ output_ref = gate_output_correction_ref(o_ref.float(), r_ref.float(), k_ref.float(),
288
+ r_k_ref.float(), v_ref.float(), g_ref.float())
289
+ output_ref.sum().backward()
290
+
291
+ output_cus = gate_output_correction(o_cus, r_cus, k_cus, r_k_cus, v_cus, g_cus)
292
+ output_cus.sum().backward()
293
+
294
+ assert_close(" o", output_ref, output_cus, 0.002)
295
+ assert_close("do", o_ref.grad, o_cus.grad, 0.002)
296
+ assert_close("dr", r_ref.grad, r_cus.grad, 0.002)
297
+ assert_close("dk", k_ref.grad, k_cus.grad, 0.002)
298
+ assert_close("drk", r_k_ref.grad, r_k_cus.grad, 0.002)
299
+ assert_close("dv", v_ref.grad, v_cus.grad, 0.002)
300
+ assert_close("dg", g_ref.grad, g_cus.grad, 0.002)
code/flash-linear-attention/tests/ops/test_simple_gla.py ADDED
@@ -0,0 +1,670 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.simple_gla.chunk import chunk_simple_gla
9
+ from fla.ops.simple_gla.fused_chunk import fused_chunk_simple_gla
10
+ from fla.ops.simple_gla.fused_recurrent import fused_recurrent_simple_gla
11
+ from fla.ops.simple_gla.naive import naive_parallel_simple_gla, naive_recurrent_simple_gla
12
+ from fla.ops.simple_gla.parallel import parallel_simple_gla
13
+ from fla.utils import assert_close, device
14
+
15
+
16
+ @pytest.mark.parametrize(
17
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'),
18
+ [
19
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test))
20
+ for test in [
21
+ (1, 63, 1, 64, 1, 1, torch.float),
22
+ (2, 500, 4, 60, 1, 1, torch.float),
23
+ (2, 1024, 8, 128, 1, 0.1, torch.float),
24
+ (2, 1024, 8, 128, 0.1, 1, torch.float),
25
+ (2, 1024, 8, 128, 1, 10, torch.float),
26
+ (4, 2048, 8, 64, 0.1, 1, torch.float),
27
+ (2, 1024, 8, 128, 1, 0.1, torch.float16),
28
+ (2, 1024, 8, 128, 1, 10, torch.float16),
29
+ ]
30
+ ],
31
+ )
32
+ def test_fused_recurrent(
33
+ B: int,
34
+ T: int,
35
+ H: int,
36
+ D: int,
37
+ scale: float,
38
+ gate_logit_normalizer: float,
39
+ dtype: torch.dtype,
40
+ ):
41
+ torch.manual_seed(42)
42
+
43
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
44
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
45
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_()
46
+ g = torch.randn((B, T, H), dtype=dtype, device=device)
47
+ g = (F.logsigmoid(g) / gate_logit_normalizer).requires_grad_()
48
+ h0 = torch.randn(B, H, D, D, device=device).requires_grad_()
49
+ dht = torch.randn_like(h0)
50
+ do = torch.randn_like(v)
51
+ ref, ref_ht = naive_recurrent_simple_gla(
52
+ q=q,
53
+ k=k,
54
+ v=v,
55
+ g=g,
56
+ scale=scale,
57
+ initial_state=h0,
58
+ output_final_state=True,
59
+ )
60
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
61
+ ref_dq, q.grad = q.grad.clone(), None
62
+ ref_dk, k.grad = k.grad.clone(), None
63
+ ref_dv, v.grad = v.grad.clone(), None
64
+ ref_dg, g.grad = g.grad.clone(), None
65
+ ref_dh0, h0.grad = h0.grad.clone(), None
66
+
67
+ tri, tri_ht = fused_recurrent_simple_gla(
68
+ q=q,
69
+ k=k,
70
+ v=v,
71
+ g=g,
72
+ scale=scale,
73
+ initial_state=h0,
74
+ output_final_state=True,
75
+ )
76
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
77
+ tri_dq, q.grad = q.grad.clone(), None
78
+ tri_dk, k.grad = k.grad.clone(), None
79
+ tri_dv, v.grad = v.grad.clone(), None
80
+ tri_dg, g.grad = g.grad.clone(), None
81
+ tri_dh0, h0.grad = h0.grad.clone(), None
82
+
83
+ assert_close('o', ref, tri, 0.005)
84
+ assert_close('ht', ref_ht, tri_ht, 0.005)
85
+ assert_close('dq', ref_dq, tri_dq, 0.005)
86
+ assert_close('dk', ref_dk, tri_dk, 0.005)
87
+ assert_close('dv', ref_dv, tri_dv, 0.005)
88
+ assert_close('dg', ref_dg, tri_dg, 0.005, err_atol=2e-4)
89
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
90
+
91
+
92
+ @pytest.mark.parametrize(
93
+ ('H', 'D', 'scale', 'gate_logit_normalizer', 'cu_seqlens', 'dtype'),
94
+ [
95
+ pytest.param(*test, id="H{}-D{}-scale{}-gate_logit_normalizer{}-cu_seqlens{}-{}".format(*test))
96
+ for test in [
97
+ (4, 64, 1, 1, [0, 15], torch.float),
98
+ (4, 64, 1, 1, [0, 256, 500, 1000], torch.float),
99
+ (4, 100, 0.1, 1, [0, 15, 100, 300, 1200, 2000], torch.float),
100
+ (4, 100, 1, 1, [0, 15, 100, 300, 1200, 2000], torch.float),
101
+ (4, 100, 1, 10, [0, 15, 100, 300, 1200, 2000], torch.float),
102
+ (4, 64, 1, 1, [0, 1, 100, 300, 1200, 2048], torch.float16),
103
+ (4, 128, 1, 1, [0, 200, 512, 1200, 2048], torch.float16),
104
+ ]
105
+ ],
106
+ )
107
+ def test_fused_recurrent_varlen(
108
+ H: int,
109
+ D: int,
110
+ scale: float,
111
+ gate_logit_normalizer: float,
112
+ cu_seqlens: list[int],
113
+ dtype: torch.dtype,
114
+ ):
115
+ torch.manual_seed(42)
116
+
117
+ N = len(cu_seqlens) - 1
118
+ T = cu_seqlens[-1]
119
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
120
+
121
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
122
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
123
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
124
+ g = torch.randn((1, T, H), dtype=dtype, device=device)
125
+ g = (F.logsigmoid(g) / gate_logit_normalizer).requires_grad_()
126
+ h0 = torch.randn(N, H, D, D, device=device).requires_grad_()
127
+ dht = torch.randn_like(h0)
128
+ do = torch.randn_like(v)
129
+
130
+ refs, ref_hts = [], []
131
+ for i, (bos, eos) in enumerate(zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)):
132
+ ref, ref_ht = naive_recurrent_simple_gla(
133
+ q=q[:, bos:eos],
134
+ k=k[:, bos:eos],
135
+ v=v[:, bos:eos],
136
+ g=g[:, bos:eos],
137
+ scale=scale,
138
+ initial_state=h0[i],
139
+ output_final_state=True,
140
+ )
141
+ refs.append(ref)
142
+ ref_hts.append(ref_ht)
143
+ ref = torch.cat(refs, 1)
144
+ ref_ht = torch.cat(ref_hts, 0)
145
+ ((ref * do).sum() + (ref_ht * dht).sum()).backward()
146
+ ref_dq, q.grad = q.grad.clone(), None
147
+ ref_dk, k.grad = k.grad.clone(), None
148
+ ref_dv, v.grad = v.grad.clone(), None
149
+ ref_dg, g.grad = g.grad.clone(), None
150
+ ref_dh0, h0.grad = h0.grad.clone(), None
151
+
152
+ tri, tri_ht = fused_recurrent_simple_gla(
153
+ q=q,
154
+ k=k,
155
+ v=v,
156
+ g=g,
157
+ scale=scale,
158
+ initial_state=h0,
159
+ output_final_state=True,
160
+ cu_seqlens=cu_seqlens,
161
+ )
162
+ ((tri * do).sum() + (tri_ht * dht).sum()).backward()
163
+ tri_dq, q.grad = q.grad.clone(), None
164
+ tri_dk, k.grad = k.grad.clone(), None
165
+ tri_dv, v.grad = v.grad.clone(), None
166
+ tri_dg, g.grad = g.grad.clone(), None
167
+ tri_dh0, h0.grad = h0.grad.clone(), None
168
+
169
+ assert_close('o', ref, tri, 0.005)
170
+ assert_close('ht', ref_ht, tri_ht, 0.005)
171
+ assert_close('dq', ref_dq, tri_dq, 0.005)
172
+ assert_close('dk', ref_dk, tri_dk, 0.005)
173
+ assert_close('dv', ref_dv, tri_dv, 0.005)
174
+ assert_close('dg', ref_dg, tri_dg, 0.005, err_atol=2e-4)
175
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
176
+
177
+
178
+ @pytest.mark.parametrize(
179
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'),
180
+ [
181
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test))
182
+ for test in [
183
+ (1, 63, 1, 64, 1, 1, torch.float16),
184
+ (2, 500, 3, 60, 1, 1, torch.float16),
185
+ (1, 1000, 4, 128, 1, 0.1, torch.float16),
186
+ (2, 1000, 4, 128, 0.1, 1, torch.float16),
187
+ (3, 1000, 4, 128, 0.1, 10, torch.float16),
188
+ (4, 2048, 8, 64, 0.1, 1, torch.float16),
189
+ ]
190
+ ],
191
+ )
192
+ def test_chunk(
193
+ B: int,
194
+ T: int,
195
+ H: int,
196
+ D: int,
197
+ scale: float,
198
+ gate_logit_normalizer: float,
199
+ dtype: torch.dtype,
200
+ ):
201
+ torch.manual_seed(42)
202
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
203
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
204
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
205
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
206
+ g = torch.randn((B, T, H), dtype=torch.float32, device=device)
207
+ h0 = torch.rand((B, H, D, D), dtype=torch.float32, device=device).requires_grad_(True)
208
+ dht = torch.randn_like(h0)
209
+ g = (F.logsigmoid(g) / gate_logit_normalizer).requires_grad_(True)
210
+ do = torch.randn_like(v)
211
+
212
+ ref, ref_ht = fused_recurrent_simple_gla(
213
+ q=q,
214
+ k=k,
215
+ v=v,
216
+ g=g,
217
+ scale=scale,
218
+ initial_state=h0,
219
+ output_final_state=True,
220
+ )
221
+ ((ref * do).sum() + (dht * ref_ht).sum()).backward()
222
+ ref_dq, q.grad = q.grad.clone(), None
223
+ ref_dk, k.grad = k.grad.clone(), None
224
+ ref_dv, v.grad = v.grad.clone(), None
225
+ ref_dg, g.grad = g.grad.clone(), None
226
+ ref_dh0, h0.grad = h0.grad.clone(), None
227
+
228
+ tri, tri_ht = chunk_simple_gla(
229
+ q=q,
230
+ k=k,
231
+ v=v,
232
+ g=g,
233
+ scale=scale,
234
+ initial_state=h0,
235
+ output_final_state=True,
236
+ )
237
+ ((tri * do).sum() + (dht * tri_ht).sum()).backward()
238
+ tri_dq, q.grad = q.grad.clone(), None
239
+ tri_dk, k.grad = k.grad.clone(), None
240
+ tri_dv, v.grad = v.grad.clone(), None
241
+ tri_dg, g.grad = g.grad.clone(), None
242
+ tri_dh0, h0.grad = h0.grad.clone(), None
243
+
244
+ assert_close('o', ref, tri, 0.004)
245
+ assert_close('ht', ref_ht, tri_ht, 0.005)
246
+ assert_close('dq', ref_dq, tri_dq, 0.005)
247
+ assert_close('dk', ref_dk, tri_dk, 0.005)
248
+ assert_close('dv', ref_dv, tri_dv, 0.005)
249
+ assert_close('dg', ref_dg, tri_dg, 0.005)
250
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
251
+
252
+
253
+ @pytest.mark.parametrize(
254
+ ('H', 'D', 'cu_seqlens', 'dtype'),
255
+ [
256
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
257
+ for test in [
258
+ (4, 64, [0, 15], torch.float16),
259
+ (4, 64, [0, 256, 500, 1000], torch.float16),
260
+ (4, 100, [0, 15, 100, 300, 1200, 2000], torch.float16),
261
+ ]
262
+ ],
263
+ )
264
+ @pytest.mark.skipif(
265
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
266
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
267
+ )
268
+ def test_chunk_varlen(
269
+ H: int,
270
+ D: int,
271
+ cu_seqlens: list[int],
272
+ dtype: torch.dtype,
273
+ ):
274
+ torch.manual_seed(42)
275
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
276
+
277
+ N = len(cu_seqlens) - 1
278
+ T = cu_seqlens[-1]
279
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
280
+
281
+ # seq-first required for inputs with variable lengths
282
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
283
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
284
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
285
+ g = F.logsigmoid(torch.randn((1, T, H), dtype=dtype, device=device)).requires_grad_()
286
+ h0 = torch.randn((N, H, D, D), dtype=torch.float32, device=device).requires_grad_()
287
+ dht = torch.randn_like(h0)
288
+ do = torch.randn_like(v)
289
+
290
+ ref, ref_ht = fused_recurrent_simple_gla(
291
+ q=q,
292
+ k=k,
293
+ v=v,
294
+ g=g,
295
+ initial_state=h0,
296
+ output_final_state=True,
297
+ cu_seqlens=cu_seqlens,
298
+ )
299
+ ((ref * do).sum() + (dht * ref_ht).sum()).backward()
300
+ ref_dq, q.grad = q.grad.clone(), None
301
+ ref_dk, k.grad = k.grad.clone(), None
302
+ ref_dv, v.grad = v.grad.clone(), None
303
+ ref_dg, g.grad = g.grad.clone(), None
304
+ ref_dh0, h0.grad = h0.grad.clone(), None
305
+
306
+ tri, tri_ht = chunk_simple_gla(
307
+ q=q,
308
+ k=k,
309
+ v=v,
310
+ g=g,
311
+ initial_state=h0,
312
+ output_final_state=True,
313
+ cu_seqlens=cu_seqlens,
314
+ )
315
+ ((tri * do).sum() + (dht * tri_ht).sum()).backward()
316
+ tri_dq, q.grad = q.grad.clone(), None
317
+ tri_dk, k.grad = k.grad.clone(), None
318
+ tri_dv, v.grad = v.grad.clone(), None
319
+ tri_dg, g.grad = g.grad.clone(), None
320
+ tri_dh0, h0.grad = h0.grad.clone(), None
321
+
322
+ assert_close('o', ref, tri, 0.004)
323
+ assert_close('ht', ref_ht, tri_ht, 0.005)
324
+ assert_close('dq', ref_dq, tri_dq, 0.005)
325
+ assert_close('dk', ref_dk, tri_dk, 0.005)
326
+ assert_close('dv', ref_dv, tri_dv, 0.005)
327
+ assert_close('dg', ref_dg, tri_dg, 0.005)
328
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
329
+
330
+
331
+ @pytest.mark.parametrize(
332
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'),
333
+ [
334
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test))
335
+ for test in [
336
+ (1, 63, 1, 64, 1, 1, torch.float16),
337
+ (2, 500, 3, 60, 1, 1, torch.float16),
338
+ (1, 1000, 4, 128, 1, 0.1, torch.float16),
339
+ (2, 1000, 4, 128, 0.1, 1, torch.float16),
340
+ (3, 1000, 4, 128, 0.1, 10, torch.float16),
341
+ (4, 2048, 8, 64, 0.1, 1, torch.float16),
342
+ ]
343
+ ],
344
+ )
345
+ def test_fused_chunk(
346
+ B: int,
347
+ T: int,
348
+ H: int,
349
+ D: int,
350
+ dtype: torch.dtype,
351
+ scale: float,
352
+ gate_logit_normalizer: float,
353
+ ):
354
+ torch.manual_seed(42)
355
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
356
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
357
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
358
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
359
+ g = torch.randn((B, T, H), dtype=torch.float32, device=device)
360
+ h0 = torch.rand((B, H, D, D), dtype=torch.float32, device=device).requires_grad_(True)
361
+ dht = torch.randn_like(h0)
362
+ g = (F.logsigmoid(g) / gate_logit_normalizer).requires_grad_(True)
363
+ do = torch.randn_like(v)
364
+
365
+ ref, ref_ht = fused_recurrent_simple_gla(
366
+ q=q,
367
+ k=k,
368
+ v=v,
369
+ g=g,
370
+ scale=scale,
371
+ initial_state=h0,
372
+ output_final_state=True,
373
+ )
374
+ ((ref * do).sum() + (dht * ref_ht).sum()).backward()
375
+ ref_dq, q.grad = q.grad.clone(), None
376
+ ref_dk, k.grad = k.grad.clone(), None
377
+ ref_dv, v.grad = v.grad.clone(), None
378
+ ref_dg, g.grad = g.grad.clone(), None
379
+ ref_dh0, h0.grad = h0.grad.clone(), None
380
+
381
+ tri, tri_ht = fused_chunk_simple_gla(
382
+ q=q,
383
+ k=k,
384
+ v=v,
385
+ g=g,
386
+ scale=scale,
387
+ initial_state=h0,
388
+ output_final_state=True,
389
+ )
390
+ ((tri * do).sum() + (dht * tri_ht).sum()).backward()
391
+ tri_dq, q.grad = q.grad.clone(), None
392
+ tri_dk, k.grad = k.grad.clone(), None
393
+ tri_dv, v.grad = v.grad.clone(), None
394
+ tri_dg, g.grad = g.grad.clone(), None
395
+ tri_dh0, h0.grad = h0.grad.clone(), None
396
+
397
+ assert_close('o', ref, tri, 0.004)
398
+ assert_close('ht', ref_ht, tri_ht, 0.005)
399
+ assert_close('dq', ref_dq, tri_dq, 0.005)
400
+ assert_close('dk', ref_dk, tri_dk, 0.005)
401
+ assert_close('dv', ref_dv, tri_dv, 0.005)
402
+ assert_close('dg', ref_dg, tri_dg, 0.005)
403
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
404
+
405
+
406
+ @pytest.mark.parametrize(
407
+ ('H', 'D', 'cu_seqlens', 'dtype'),
408
+ [
409
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
410
+ for test in [
411
+ (4, 64, [0, 15], torch.float16),
412
+ (4, 64, [0, 256, 500, 1000], torch.float16),
413
+ (4, 100, [0, 15, 100, 300, 1200, 2000], torch.float16),
414
+ ]
415
+ ],
416
+ )
417
+ @pytest.mark.skipif(
418
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
419
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
420
+ )
421
+ def test_fused_chunk_varlen(
422
+ H: int,
423
+ D: int,
424
+ cu_seqlens: list[int],
425
+ dtype: torch.dtype,
426
+ ):
427
+ torch.manual_seed(42)
428
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
429
+
430
+ N = len(cu_seqlens) - 1
431
+ T = cu_seqlens[-1]
432
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
433
+
434
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
435
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
436
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
437
+ g = F.logsigmoid(torch.randn((1, T, H), dtype=dtype, device=device)).requires_grad_()
438
+ h0 = torch.randn((N, H, D, D), dtype=torch.float32, device=device).requires_grad_()
439
+ dht = torch.randn_like(h0)
440
+ do = torch.randn_like(v)
441
+
442
+ ref, ref_ht = fused_recurrent_simple_gla(
443
+ q=q,
444
+ k=k,
445
+ v=v,
446
+ g=g,
447
+ initial_state=h0,
448
+ output_final_state=True,
449
+ cu_seqlens=cu_seqlens,
450
+ )
451
+ ((ref * do).sum() + (dht * ref_ht).sum()).backward()
452
+ ref_dq, q.grad = q.grad.clone(), None
453
+ ref_dk, k.grad = k.grad.clone(), None
454
+ ref_dv, v.grad = v.grad.clone(), None
455
+ ref_dg, g.grad = g.grad.clone(), None
456
+ ref_dh0, h0.grad = h0.grad.clone(), None
457
+
458
+ tri, tri_ht = fused_chunk_simple_gla(
459
+ q=q,
460
+ k=k,
461
+ v=v,
462
+ g=g,
463
+ initial_state=h0,
464
+ output_final_state=True,
465
+ cu_seqlens=cu_seqlens,
466
+ )
467
+ ((tri * do).sum() + (dht * tri_ht).sum()).backward()
468
+ tri_dq, q.grad = q.grad.clone(), None
469
+ tri_dk, k.grad = k.grad.clone(), None
470
+ tri_dv, v.grad = v.grad.clone(), None
471
+ tri_dg, g.grad = g.grad.clone(), None
472
+ tri_dh0, h0.grad = h0.grad.clone(), None
473
+
474
+ assert_close('o', ref, tri, 0.004)
475
+ assert_close('ht', ref_ht, tri_ht, 0.005)
476
+ assert_close('dq', ref_dq, tri_dq, 0.005)
477
+ assert_close('dk', ref_dk, tri_dk, 0.005)
478
+ assert_close('dv', ref_dv, tri_dv, 0.005)
479
+ assert_close('dg', ref_dg, tri_dg, 0.005)
480
+ assert_close('dh0', ref_dh0, tri_dh0, 0.005)
481
+
482
+
483
+ @pytest.mark.parametrize(
484
+ ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'),
485
+ [
486
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test))
487
+ for test in [
488
+ (1, 63, 1, 64, 1, 1, torch.float16),
489
+ (2, 500, 3, 60, 1, 1, torch.float16),
490
+ (2, 1024, 4, 128, 0.1, 1, torch.float16),
491
+ (3, 1024, 4, 128, 0.1, 10, torch.float16),
492
+ (3, 1024, 4, 256, 0.1, 0.1, torch.float16),
493
+ (4, 2048, 4, 64, 0.1, 0.1, torch.float16),
494
+ ]
495
+ ],
496
+ )
497
+ def test_parallel(
498
+ B: int,
499
+ T: int,
500
+ H: int,
501
+ D: int,
502
+ scale: float,
503
+ gate_logit_normalizer: float,
504
+ dtype: torch.dtype,
505
+ ):
506
+ torch.manual_seed(42)
507
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
508
+ USE_G = gate_logit_normalizer > 0
509
+ q = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
510
+ k = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
511
+ v = torch.randn((B, T, H, D), dtype=dtype, device=device).requires_grad_(True)
512
+ g = F.logsigmoid(torch.randn((B, T, H), dtype=dtype, device=device)) if USE_G else None
513
+ g = (g / gate_logit_normalizer).requires_grad_(True) if USE_G else None
514
+ do = torch.randn_like(v)
515
+
516
+ ref, _ = fused_recurrent_simple_gla(q=q, k=k, v=v, g=g, scale=scale, output_final_state=True)
517
+ _, ref_A = naive_parallel_simple_gla(q=q, k=k, v=v, g=g, scale=scale)
518
+ ref.backward(do)
519
+ ref_dq, q.grad = q.grad.clone(), None
520
+ ref_dk, k.grad = k.grad.clone(), None
521
+ ref_dv, v.grad = v.grad.clone(), None
522
+ if USE_G:
523
+ ref_dg, g.grad = g.grad.clone(), None
524
+
525
+ tri, tri_A = parallel_simple_gla(q=q, k=k, v=v, g=g, scale=scale, output_attentions=True)
526
+ tri.backward(do)
527
+ tri_dq, q.grad = q.grad.clone(), None
528
+ tri_dk, k.grad = k.grad.clone(), None
529
+ tri_dv, v.grad = v.grad.clone(), None
530
+ if USE_G:
531
+ tri_dg, g.grad = g.grad.clone(), None
532
+ assert_close('o', ref, tri, 0.005)
533
+ assert_close('A', ref_A, tri_A, 0.005)
534
+ assert_close('dq', ref_dq, tri_dq, 0.005)
535
+ assert_close('dk', ref_dk, tri_dk, 0.005)
536
+ assert_close('dv', ref_dv, tri_dv, 0.005)
537
+ if USE_G:
538
+ assert_close('dg', ref_dg, tri_dg, 0.015)
539
+
540
+
541
+ @pytest.mark.parametrize(
542
+ ('H', 'D', 'cu_seqlens', 'dtype'),
543
+ [
544
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
545
+ for test in [
546
+ (4, 64, [0, 15], torch.float16),
547
+ (4, 64, [0, 256, 500, 1000], torch.float16),
548
+ (4, 100, [0, 15, 100, 300, 1200, 2000], torch.float16),
549
+ ]
550
+ ],
551
+ )
552
+ @pytest.mark.skipif(
553
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
554
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
555
+ )
556
+ def test_parallel_varlen(
557
+ H: int,
558
+ D: int,
559
+ cu_seqlens: list[int],
560
+ dtype: torch.dtype,
561
+ ):
562
+ torch.manual_seed(42)
563
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
564
+
565
+ T = cu_seqlens[-1]
566
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
567
+
568
+ q = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
569
+ k = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
570
+ v = torch.randn((1, T, H, D), dtype=dtype, device=device).requires_grad_()
571
+ g = F.logsigmoid(torch.randn((1, T, H), dtype=dtype, device=device)).requires_grad_()
572
+ do = torch.randn_like(v)
573
+
574
+ ref, _ = fused_recurrent_simple_gla(
575
+ q=q,
576
+ k=k,
577
+ v=v,
578
+ g=g,
579
+ output_final_state=False,
580
+ cu_seqlens=cu_seqlens,
581
+ )
582
+ ((ref * do).sum()).backward()
583
+ ref_dq, q.grad = q.grad.clone(), None
584
+ ref_dk, k.grad = k.grad.clone(), None
585
+ ref_dv, v.grad = v.grad.clone(), None
586
+ ref_dg, g.grad = g.grad.clone(), None
587
+
588
+ tri, _ = parallel_simple_gla(
589
+ q=q,
590
+ k=k,
591
+ v=v,
592
+ g=g,
593
+ cu_seqlens=cu_seqlens,
594
+ )
595
+ ((tri * do).sum()).backward()
596
+ tri_dq, q.grad = q.grad.clone(), None
597
+ tri_dk, k.grad = k.grad.clone(), None
598
+ tri_dv, v.grad = v.grad.clone(), None
599
+ tri_dg, g.grad = g.grad.clone(), None
600
+
601
+ assert_close('o', ref, tri, 0.004)
602
+ assert_close('dq', ref_dq, tri_dq, 0.005)
603
+ assert_close('dk', ref_dk, tri_dk, 0.005)
604
+ assert_close('dv', ref_dv, tri_dv, 0.005)
605
+ assert_close('dg', ref_dg, tri_dg, 0.005)
606
+
607
+
608
+ @pytest.mark.parametrize(
609
+ ('vary_A', 'dtype'),
610
+ [
611
+ pytest.param(True, torch.float, id=f'vary_A{True}-dtype{torch.float}'),
612
+ pytest.param(False, torch.float, id=f'vary_A{False}-dtype{torch.float}'),
613
+ pytest.param(True, torch.float16, id=f'vary_A{True}-dtype{torch.float16}'),
614
+ pytest.param(False, torch.float16, id=f'vary_A{False}-dtype{torch.float16}'),
615
+ ],
616
+ )
617
+ def test_simple_gla_to_mamba2(vary_A, dtype):
618
+ try:
619
+ from mamba_ssm.modules.ssd_minimal import ssd_minimal_discrete
620
+ from mamba_ssm.ops.triton.ssd_combined import mamba_chunk_scan_combined
621
+ except ImportError:
622
+ pytest.skip('mamba_ssm is not installed.')
623
+ torch.manual_seed(42)
624
+
625
+ # Dimensions, Denoted (B, T, Q, D, P) in Mamba2 paper
626
+ batch, seq_len, chunk_size, dim, headdim = 2, 512, 8, 64, 16
627
+ n_heads = dim // headdim # (H) in the paper
628
+ ngroups = n_heads # (G) in the paper; NOTE: do not use group-query here
629
+ dstate = 64 # (N) in the paper
630
+ atol = 5e-4 if dtype == torch.float else 1e-2
631
+
632
+ x = 0.1 * torch.randn(batch, seq_len, n_heads, headdim, dtype=dtype, device=device)
633
+ dt = torch.ones(batch, seq_len, n_heads, dtype=dtype, device=device) # dt=1 can be ignored
634
+
635
+ if vary_A:
636
+ A = -0.1 * torch.rand(1, seq_len, n_heads, dtype=dtype, device=device)
637
+ else: # constant A for all position
638
+ A = -0.1 * torch.rand(n_heads, dtype=dtype, device=device)
639
+
640
+ B = 0.1 * torch.randn(batch, seq_len, ngroups, dstate, dtype=dtype, device=device)
641
+ C = 0.1 * torch.randn(batch, seq_len, ngroups, dstate, dtype=dtype, device=device)
642
+
643
+ y_ssd, final_ssd = ssd_minimal_discrete(x * dt.unsqueeze(-1), A * dt, B, C, chunk_size)
644
+
645
+ if not vary_A:
646
+ # NOTE: fused kernel does not support varying A with time
647
+ y_fuse, final_fuse = mamba_chunk_scan_combined(x, dt, A, B, C, chunk_size, D=None, return_final_states=True)
648
+ assert y_ssd.allclose(y_fuse, 0, atol), f'y diff: {torch.abs(y_ssd - y_fuse).max()}'
649
+ # fused kernel upcasts state to float32
650
+ # https://github.com/state-spaces/mamba/blob/v2.2.2/mamba_ssm/ops/triton/ssd_combined.py#L650
651
+ final_fuse = final_fuse.to(dtype)
652
+ assert final_ssd.allclose(final_fuse, 0, atol), f'final diff: {torch.abs(final_ssd - final_fuse).max()}'
653
+
654
+ # mapping inputs Mamba2 -> FLA
655
+ # FLA Now use head_first = False, therefore there is no need to transpose inputs
656
+ q = C
657
+ k = B
658
+ v = x
659
+ g = (A * dt)
660
+
661
+ # mapping outputs Mamba2 -> FLA
662
+ y_rearrange = y_ssd
663
+ final_rearrange = final_ssd.transpose(2, 3)
664
+
665
+ # comparing output results between FLA kernel and Mamba2 kernel
666
+ # final_gla_fuse :[N, H, K, V]
667
+ outputs_gla_fuse, final_gla_fuse = chunk_simple_gla(q, k, v, g, scale=1.0, output_final_state=True)
668
+ assert y_rearrange.allclose(outputs_gla_fuse, 0, atol), f'y diff: {torch.abs(y_rearrange - outputs_gla_fuse).max()}'
669
+ final_gla_fuse = final_gla_fuse.to(dtype) # states hard-coded to float32 in FLA kernel
670
+ assert final_rearrange.allclose(final_gla_fuse, 0, atol), f'final diff: {torch.abs(final_ssd - final_gla_fuse).max()}'
code/flash-linear-attention/tests/ops/test_solve_tril.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.common.chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd
9
+ from fla.ops.utils.solve_tril import solve_tril
10
+ from fla.utils import assert_close, device, device_platform
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ ('B', 'T', 'H', 'chunk_size'),
15
+ [
16
+ pytest.param(*test, id="B{}-T{}-H{}-chunk_size{}".format(*test))
17
+ for test in [
18
+ (1, 63, 1, 16),
19
+ (2, 500, 4, 32),
20
+ (2, 1000, 5, 64),
21
+ (3, 1024, 6, 64),
22
+ (4, 2048, 8, 64),
23
+ ]
24
+ ],
25
+ )
26
+ @pytest.mark.skipif(
27
+ device_platform == 'intel',
28
+ reason='Intel Pytorch Failure',
29
+ )
30
+ def test_solve_tril(B, T, H, chunk_size):
31
+ # do not randomly intiialize A otherwise the inverse is not stable
32
+ k = F.normalize(torch.randn((B, H, T, 64), dtype=torch.float32, device=device), dim=-1)
33
+ # Pad the second-to-last dimension (T) to be a multiple of chunk_size
34
+ padding_size = (chunk_size - T % chunk_size) % chunk_size
35
+ k_padded = F.pad(k, (0, 0, 0, padding_size, 0, 0, 0, 0))
36
+ k_padded = k_padded.reshape(B, H, -1, chunk_size, 64)
37
+ A = (k_padded @ k_padded.transpose(-1, -2)).tril(-1)
38
+
39
+ ref = torch.inverse(A + torch.eye(A.shape[-1], device=A.device)[None, None, None, ...])
40
+ ref = ref.reshape(B, H, -1, chunk_size)[:, :, :T, :]
41
+
42
+ tri = solve_tril(A.reshape(B, H, -1, chunk_size)[:, :, :T, :].transpose(1, 2)).transpose(1, 2)
43
+
44
+ assert_close('solve_tril', ref, tri, 0.0001)
45
+
46
+
47
+ @pytest.mark.parametrize(
48
+ ('H', 'D', 'chunk_size', 'cu_seqlens'),
49
+ [
50
+ pytest.param(*test, id="H{}-D{}-chunk_size{}-cu_seqlens{}".format(*test))
51
+ for test in [
52
+ (4, 64, 16, [0, 15]),
53
+ (4, 64, 32, [0, 256, 500, 1000]),
54
+ (4, 100, 64, [0, 15, 100, 300, 1200, 2000]),
55
+ (4, 64, 16, [0, 1, 100, 300, 1200, 2048]),
56
+ (4, 128, 32, [0, 200, 512, 1200, 2048]),
57
+ ]
58
+ ],
59
+ )
60
+ @pytest.mark.skipif(
61
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
62
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
63
+ )
64
+ @pytest.mark.skipif(
65
+ device_platform == 'intel',
66
+ reason='Intel Pytorch Failure',
67
+ )
68
+ def test_solve_tril_varlen(
69
+ H: int,
70
+ D: int,
71
+ chunk_size: int,
72
+ cu_seqlens: list[int],
73
+ ):
74
+ T = cu_seqlens[-1]
75
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
76
+ # Construct the input. otherwise inverse's condition number might be too large to measure the error
77
+ k = F.normalize(torch.randn((1, T, H, D), dtype=torch.bfloat16, device=device), dim=-1)
78
+ beta = torch.randn((1, T, H), dtype=torch.bfloat16, device=device).sigmoid()
79
+ A = chunk_scaled_dot_kkt_fwd(k=k, beta=beta, cu_seqlens=cu_seqlens, chunk_size=chunk_size)
80
+
81
+ ref = torch.zeros_like(A)
82
+ for i in range(len(cu_seqlens) - 1):
83
+ for j in range(cu_seqlens[i], cu_seqlens[i+1], chunk_size):
84
+ actual_size = min(chunk_size, cu_seqlens[i+1] - j)
85
+ ref[:, j:j+actual_size, :, :actual_size] = torch.inverse(
86
+ A[:, j:j+actual_size, :, :actual_size].transpose(1, 2) +
87
+ torch.eye(actual_size, device=A.device, dtype=A.dtype)[None, None, ...],
88
+ ).transpose(1, 2)
89
+
90
+ tri = solve_tril(A, cu_seqlens=cu_seqlens)
91
+ assert_close('solve_tril_varlen', ref, tri, 0.0001)
code/flash-linear-attention/tests/ops/test_titans.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import pytest
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from fla.ops.titans.naive import chunk_titans_linear_ref
7
+ from fla.utils import assert_close, device
8
+
9
+
10
+ def initialize_chunked_param(B, H, T, BT, dtype=torch.float32):
11
+ # Calculate number of complete chunks and remaining elements
12
+ num_complete_chunks = T // BT
13
+ remainder = T % BT
14
+
15
+ # Initialize for complete chunks
16
+ if num_complete_chunks > 0:
17
+ theta_chunks = torch.rand(B, H, num_complete_chunks, 1, dtype=dtype)
18
+ theta_main = theta_chunks.repeat_interleave(
19
+ BT, dim=2,
20
+ ) # Shape: (B, H, num_complete_chunks*BT, 1)
21
+ else:
22
+ theta_main = torch.empty(B, H, 0, 1, dtype=dtype)
23
+
24
+ # Handle remaining elements if any
25
+ if remainder > 0:
26
+ theta_remainder = torch.rand(B, H, 1, 1, dtype=dtype)
27
+ theta_remainder = theta_remainder.repeat_interleave(
28
+ remainder, dim=2,
29
+ ) # Shape: (B, H, remainder, 1)
30
+
31
+ # Concatenate main chunks with remainder
32
+ theta = torch.cat([theta_main, theta_remainder], dim=2)
33
+ else:
34
+ theta = theta_main
35
+
36
+ return theta
37
+
38
+
39
+ @pytest.mark.parametrize(
40
+ ('B', 'T', 'H', 'D', 'dtype'),
41
+ [
42
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
43
+ for test in [
44
+ (1, 63, 1, 64, torch.float16),
45
+ (2, 100, 4, 60, torch.float16),
46
+ (2, 1024, 3, 128, torch.float16),
47
+ (3, 2000, 4, 128, torch.float16),
48
+ (4, 2048, 8, 64, torch.float16),
49
+ ]
50
+ ],
51
+ )
52
+ @pytest.mark.skipif(
53
+ True, reason='FIXME',
54
+ )
55
+ def test_naive_chunk(
56
+ B: int,
57
+ T: int,
58
+ H: int,
59
+ D: int,
60
+ dtype: torch.dtype,
61
+ ):
62
+ BT = 64
63
+ # set seed
64
+ torch.manual_seed(1)
65
+ # we don't use such initialization in the original code
66
+ # theta = initialize_chunked_param(B, H, T, BT, dtype)
67
+ # alpha = initialize_chunked_param(B, H, T, BT, dtype)
68
+ # eta = initialize_chunked_param(B, H, T, BT, dtype)
69
+ theta = torch.rand(B, H, T, 1, dtype=dtype)
70
+ alpha = torch.rand(B, H, T, 1, dtype=dtype)
71
+ eta = torch.rand(B, H, T, 1, dtype=dtype)
72
+
73
+ # titans normalize queries and keys using β„“2-normalization
74
+ q = F.normalize(torch.randn(B, H, T, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
75
+ k = F.normalize(torch.randn(B, H, T, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
76
+ v = torch.randn(B, H, T, D, dtype=dtype)
77
+ w = torch.randn(H, D, dtype=dtype)
78
+ b = torch.randn(H, D, dtype=dtype)
79
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
80
+ q = q.permute(0, 2, 1, 3)
81
+ k = k.permute(0, 2, 1, 3)
82
+ v = v.permute(0, 2, 1, 3)
83
+ theta = theta.permute(0, 2, 1, 3)
84
+ alpha = alpha.permute(0, 2, 1, 3)
85
+ eta = eta.permute(0, 2, 1, 3)
86
+ q, k, v, w, b, theta, alpha, eta = map(
87
+ lambda x: x.to(device).requires_grad_(False), (q, k, v, w, b, theta, alpha, eta),
88
+ )
89
+ # in titans paper, h0 is not learnable
90
+ h0 = h0.to(device)
91
+
92
+ ref_naive, ref_ht_naive = chunk_titans_linear_ref(
93
+ q.clone(),
94
+ k.clone(),
95
+ v.clone(),
96
+ w.clone(),
97
+ b.clone(),
98
+ theta.clone(),
99
+ alpha.clone(),
100
+ eta.clone(),
101
+ output_final_state=True,
102
+ chunk_size=BT,
103
+ initial_state=h0.clone(),
104
+ use_chunk=False,
105
+ )
106
+ ref, ref_ht = chunk_titans_linear_ref(
107
+ q.clone(),
108
+ k.clone(),
109
+ v.clone(),
110
+ w.clone(),
111
+ b.clone(),
112
+ theta.clone(),
113
+ alpha.clone(),
114
+ eta.clone(),
115
+ output_final_state=True,
116
+ chunk_size=BT,
117
+ initial_state=h0.clone(),
118
+ use_chunk=True,
119
+ )
120
+
121
+ assert_close(" o", ref, ref_naive, 0.006)
122
+ assert_close("ht", ref_ht, ref_ht_naive, 0.005)
code/flash-linear-attention/tests/ops/test_ttt.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+ import torch.nn.functional as F
7
+
8
+ from fla.ops.ttt import chunk_ttt_linear, fused_chunk_ttt_linear
9
+ from fla.ops.ttt.naive import chunk_ttt_linear_ref
10
+ from fla.utils import assert_close, check_shared_mem, device
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ ('B', 'T', 'H', 'D', 'scale', 'dtype'),
15
+ [
16
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
17
+ for test in [
18
+ (1, 63, 1, 64, 1, torch.float16),
19
+ (2, 100, 4, 60, 0.1, torch.float16),
20
+ (2, 1024, 3, 128, 0.1, torch.float16),
21
+ (2, 1024, 4, 128, 1, torch.float16),
22
+ (3, 2000, 4, 128, 0.1, torch.float16),
23
+ (4, 2048, 8, 64, 0.1, torch.float16),
24
+ ]
25
+ ],
26
+ )
27
+ def test_chunk(
28
+ B: int,
29
+ T: int,
30
+ H: int,
31
+ D: int,
32
+ scale: float,
33
+ dtype: torch.dtype,
34
+ ):
35
+ if D > 64 and check_shared_mem('hopper') is False:
36
+ pytest.skip(reason="Current CI do not support this config")
37
+ if T > 1000:
38
+ pytest.skip(reason="Current CI do not support this config")
39
+ eta_base = 5e-3
40
+ q = torch.randn(B, T, H, D, dtype=dtype)
41
+ k = F.normalize(torch.randn(B, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
42
+ v = torch.randn(B, T, H, D, dtype=dtype)
43
+ w = torch.randn(H, D, dtype=dtype)
44
+ b = torch.randn(H, D, dtype=dtype)
45
+ eta = torch.randn(B, T, H, 1, dtype=dtype) * eta_base
46
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
47
+ hb0 = torch.randn(B, H, 1, D, dtype=torch.float32)
48
+
49
+ q, k, v, w, b, eta, h0, hb0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, w, b, eta, h0, hb0))
50
+ do = torch.rand_like(v)
51
+ dht = torch.rand_like(h0)
52
+ dhbt = torch.rand_like(hb0)
53
+
54
+ tri, tri_ht, tri_hbt = chunk_ttt_linear(
55
+ q.clone(),
56
+ k.clone(),
57
+ v.clone(),
58
+ w.clone(),
59
+ b.clone(),
60
+ eta.clone(),
61
+ scale=scale,
62
+ output_final_state=True,
63
+ initial_state=h0.clone(),
64
+ initial_state_bias=hb0.clone(),
65
+ )
66
+ ((tri * do).sum() + (tri_ht * dht).sum() + (tri_hbt * dhbt).sum()).backward(retain_graph=True)
67
+ tri_dq, tri_dk, tri_dv, tri_dw, tri_db, tri_deta, \
68
+ tri_dh0, tri_dhb0 = q.grad, k.grad, v.grad, w.grad, b.grad, eta.grad, h0.grad, hb0.grad
69
+ q.grad = k.grad = v.grad = w.grad = b.grad = eta.grad = h0.grad = hb0.grad = None
70
+
71
+ ref, ref_ht, ref_hbt = chunk_ttt_linear_ref(
72
+ q.clone(),
73
+ k.clone(),
74
+ v.clone(),
75
+ w.clone(),
76
+ b.clone(),
77
+ eta.clone(),
78
+ scale=scale,
79
+ output_final_state=True,
80
+ initial_state=h0.clone(),
81
+ initial_state_bias=hb0.clone(),
82
+ )
83
+ ((ref * do).sum() + (ref_ht * dht).sum() + (ref_hbt * dhbt).sum()).backward(retain_graph=True)
84
+ ref_dq, ref_dk, ref_dv, ref_dw, ref_db, ref_deta, \
85
+ ref_dh0, ref_dhb0 = q.grad, k.grad, v.grad, w.grad, b.grad, eta.grad, h0.grad, hb0.grad
86
+
87
+ assert_close(" o", ref, tri, 0.005)
88
+ assert_close(" ht", ref_ht, tri_ht, 0.005)
89
+ assert_close(" hbt", ref_hbt, tri_hbt, 0.005)
90
+ assert_close(" dq", ref_dq, tri_dq, 0.005)
91
+ assert_close(" dk", ref_dk, tri_dk, 0.010)
92
+ assert_close(" dv", ref_dv, tri_dv, 0.007)
93
+ assert_close(" dw", ref_dw, tri_dw, 0.006)
94
+ assert_close(" db", ref_db, tri_db, 0.006)
95
+ assert_close(" de", ref_deta, tri_deta, 0.030) # because the last element of the chunk
96
+ assert_close(" de0", ref_deta[:, :14, :, :], tri_deta[:, :14, :, :], 0.010)
97
+ assert_close(" dh0", ref_dh0, tri_dh0, 0.007)
98
+ assert_close("dhb0", ref_dhb0, tri_dhb0, 0.005)
99
+
100
+
101
+ @pytest.mark.parametrize(
102
+ ('B', 'T', 'H', 'D', 'scale', 'dtype'),
103
+ [
104
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-{}".format(*test))
105
+ for test in [
106
+ (1, 63, 1, 64, 1, torch.float16),
107
+ (2, 100, 4, 60, 0.1, torch.float16),
108
+ (2, 1024, 3, 128, 0.1, torch.float16),
109
+ (2, 1024, 4, 128, 1, torch.float16),
110
+ (3, 2000, 4, 128, 0.1, torch.float16),
111
+ (4, 2048, 8, 64, 0.1, torch.float16),
112
+ ]
113
+ ],
114
+ )
115
+ def test_fused_chunk(
116
+ B: int,
117
+ T: int,
118
+ H: int,
119
+ D: int,
120
+ scale: float,
121
+ dtype: torch.dtype,
122
+ ):
123
+ if D > 64 and check_shared_mem('hopper') is False:
124
+ pytest.skip(reason="Current CI do not support this config")
125
+ if T > 1000:
126
+ pytest.skip(reason="Current CI do not support this config")
127
+ eta_base = 5e-3
128
+ q = torch.randn(B, T, H, D, dtype=dtype)
129
+ k = F.normalize(torch.randn(B, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
130
+ v = torch.randn(B, T, H, D, dtype=dtype)
131
+ w = torch.randn(H, D, dtype=dtype)
132
+ b = torch.randn(H, D, dtype=dtype)
133
+ eta = torch.randn(B, T, H, 1, dtype=dtype) * eta_base
134
+ h0 = torch.randn(B, H, D, D, dtype=torch.float32)
135
+ hb0 = torch.randn(B, H, 1, D, dtype=torch.float32)
136
+
137
+ q, k, v, w, b, eta, h0, hb0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, w, b, eta, h0, hb0))
138
+ do = torch.rand_like(v)
139
+ dht = torch.rand_like(h0)
140
+ dhbt = torch.rand_like(hb0)
141
+
142
+ tri, tri_ht, tri_hbt = fused_chunk_ttt_linear(
143
+ q.clone(),
144
+ k.clone(),
145
+ v.clone(),
146
+ w.clone(),
147
+ b.clone(),
148
+ eta.clone(),
149
+ scale=scale,
150
+ output_final_state=True,
151
+ initial_state=h0.clone(),
152
+ initial_state_bias=hb0.clone(),
153
+ )
154
+ ((tri * do).sum() + (tri_ht * dht).sum() + (tri_hbt * dhbt).sum()).backward(retain_graph=True)
155
+ tri_dq, tri_dk, tri_dv, tri_dw, tri_db, tri_deta, \
156
+ tri_dh0, tri_dhb0 = q.grad, k.grad, v.grad, w.grad, b.grad, eta.grad, h0.grad, hb0.grad
157
+ q.grad = k.grad = v.grad = w.grad = b.grad = eta.grad = h0.grad = hb0.grad = None
158
+
159
+ ref, ref_ht, ref_hbt = chunk_ttt_linear_ref(
160
+ q.clone(),
161
+ k.clone(),
162
+ v.clone(),
163
+ w.clone(),
164
+ b.clone(),
165
+ eta.clone(),
166
+ scale=scale,
167
+ output_final_state=True,
168
+ initial_state=h0.clone(),
169
+ initial_state_bias=hb0.clone(),
170
+ )
171
+ ((ref * do).sum() + (ref_ht * dht).sum() + (ref_hbt * dhbt).sum()).backward(retain_graph=True)
172
+ ref_dq, ref_dk, ref_dv, ref_dw, ref_db, ref_deta, \
173
+ ref_dh0, ref_dhb0 = q.grad, k.grad, v.grad, w.grad, b.grad, eta.grad, h0.grad, hb0.grad
174
+
175
+ assert_close(" o", ref, tri, 0.005)
176
+ assert_close(" ht", ref_ht, tri_ht, 0.005)
177
+ assert_close(" hbt", ref_hbt, tri_hbt, 0.005)
178
+ assert_close(" dq", ref_dq, tri_dq, 0.005)
179
+ assert_close(" dk", ref_dk, tri_dk, 0.010)
180
+ assert_close(" dv", ref_dv, tri_dv, 0.007)
181
+ assert_close(" dw", ref_dw, tri_dw, 0.005)
182
+ assert_close(" db", ref_db, tri_db, 0.005)
183
+ assert_close(" de", ref_deta, tri_deta, 0.03) # because the last element of the chunk
184
+ assert_close(" de0", ref_deta[:, :14, :, :], tri_deta[:, :14, :, :], 0.008)
185
+ assert_close(" dh0", ref_dh0, tri_dh0, 0.006)
186
+ assert_close("dhb0", ref_dhb0, tri_dhb0, 0.005)
187
+
188
+
189
+ @pytest.mark.parametrize(
190
+ ('H', 'D', 'cu_seqlens', 'dtype'),
191
+ [
192
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
193
+ for test in [
194
+ (2, 64, [0, 15], torch.float16),
195
+ (3, 60, [0, 111, 500], torch.float16),
196
+ (3, 64, [0, 256, 500, 900, 1000], torch.float16),
197
+ (4, 100, [0, 15, 100, 300, 1200, 1599, 1800, 2000], torch.float16),
198
+ ]
199
+ ],
200
+ )
201
+ @pytest.mark.skipif(
202
+ os.getenv("SKIP_TEST_CHUNK_VARLEN") == "1",
203
+ reason="Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set",
204
+ )
205
+ def test_chunk_varlen(
206
+ H: int,
207
+ D: int,
208
+ cu_seqlens: list[int],
209
+ dtype: torch.dtype,
210
+ ):
211
+ if D > 64 and check_shared_mem('hopper') is False:
212
+ pytest.skip(reason="Current CI do not support this config")
213
+ torch.manual_seed(42)
214
+ os.environ['TRITON_F32_DEFAULT'] = 'ieee'
215
+ T = cu_seqlens[-1]
216
+ N = len(cu_seqlens) - 1
217
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
218
+
219
+ eta_base = 5e-3
220
+ # seq-first required for inputs with variable lengths
221
+ q = torch.randn((1, T, H, D), dtype=dtype)
222
+ k = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype)
223
+ v = torch.randn((1, T, H, D), dtype=dtype)
224
+ eta = torch.randn(1, T, H, 1, dtype=dtype) * eta_base
225
+ w = torch.randn(H, D, dtype=dtype)
226
+ b = torch.randn(H, D, dtype=dtype)
227
+ h0 = torch.randn((N, H, D, D), dtype=torch.float32)
228
+ hb0 = torch.randn((N, H, 1, D), dtype=torch.float32)
229
+ q, k, v, w, b, eta, h0, hb0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, w, b, eta, h0, hb0))
230
+
231
+ tri, tri_ht, tri_hbt = chunk_ttt_linear(
232
+ q.clone(),
233
+ k.clone(),
234
+ v.clone(),
235
+ w.clone(),
236
+ b.clone(),
237
+ eta.clone(),
238
+ output_final_state=True,
239
+ initial_state=h0.clone(),
240
+ initial_state_bias=hb0.clone(),
241
+ cu_seqlens=cu_seqlens,
242
+ )
243
+
244
+ ref = []
245
+ ref_ht = []
246
+ ref_hbt = []
247
+ for i in range(N):
248
+ ref_i, ref_ht_i, ref_hbt_i = chunk_ttt_linear_ref(
249
+ q=q[:, cu_seqlens[i]:cu_seqlens[i+1]],
250
+ k=k[:, cu_seqlens[i]:cu_seqlens[i+1]],
251
+ v=v[:, cu_seqlens[i]:cu_seqlens[i+1]],
252
+ w=w,
253
+ b=b,
254
+ eta=eta[:, cu_seqlens[i]:cu_seqlens[i+1]],
255
+ initial_state=h0[i],
256
+ initial_state_bias=hb0[i],
257
+ output_final_state=True,
258
+ )
259
+ ref.append(ref_i)
260
+ ref_ht.append(ref_ht_i)
261
+ ref_hbt.append(ref_hbt_i)
262
+ ref = torch.cat(ref, 1)
263
+ ref_ht = torch.cat(ref_ht, 0)
264
+ ref_hbt = torch.cat(ref_hbt, 0)
265
+
266
+ assert_close(" o", ref, tri, 0.005)
267
+ assert_close(" ht", ref_ht, tri_ht, 0.005)
268
+ assert_close("hbt", ref_hbt, tri_hbt, 0.005)
code/flash-linear-attention/tests/ops/test_utils.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ import pytest
5
+ import torch
6
+
7
+ from fla.ops.utils import chunk_global_cumsum, chunk_local_cumsum, mean_pooling
8
+ from fla.ops.utils.index import prepare_lens
9
+ from fla.ops.utils.pack import pack_sequence, unpack_sequence
10
+ from fla.utils import assert_close, device
11
+
12
+
13
+ def reversed_cumsum(x, dim=-1):
14
+ dtype = x.dtype
15
+ x = x.float()
16
+ c = x.cumsum(dim)
17
+ y = x + c.index_select(dim, x.new_tensor([c.shape[dim]-1], dtype=torch.long)) - c
18
+ return y.to(dtype)
19
+
20
+
21
+ @pytest.mark.parametrize(
22
+ ('B', 'T', 'H', 'D', 'dtype'),
23
+ [
24
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
25
+ for test in [
26
+ (1, 63, 1, 30, torch.float),
27
+ (2, 500, 4, 60, torch.float),
28
+ (2, 1000, 5, 128, torch.float),
29
+ (3, 1024, 6, 500, torch.float),
30
+ (4, 2048, 8, 1024, torch.float),
31
+ ]
32
+ ],
33
+ )
34
+ def test_global_cumsum(
35
+ B: int,
36
+ T: int,
37
+ H: int,
38
+ D: int,
39
+ dtype: torch.dtype,
40
+ ):
41
+ torch.manual_seed(42)
42
+ s = torch.randn(B, T, H, dtype=dtype).to(device)
43
+ ref = s.float().cumsum(1).to(dtype)
44
+ tri = chunk_global_cumsum(s)
45
+ assert_close('global_cumsum', ref, tri, 1e-3)
46
+
47
+ s = torch.randn(B, T, H, D, dtype=dtype).to(device)
48
+ ref = s.float().cumsum(1).to(dtype)
49
+ tri = chunk_global_cumsum(s)
50
+ assert_close('global_cumsum', ref, tri, 1e-3)
51
+
52
+
53
+ @pytest.mark.parametrize(
54
+ ('H', 'D', 'cu_seqlens', 'dtype'),
55
+ [
56
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
57
+ for test in [
58
+ (2, 60, [0, 15], torch.float),
59
+ (3, 100, [0, 256, 500, 1000], torch.float),
60
+ (4, 256, [0, 15, 100, 300, 1200, 2000], torch.float),
61
+ (4, 500, [0, 1, 100, 300, 1200, 2048], torch.float16),
62
+ (2, 1024, [0, 200, 512, 1200, 2048], torch.float16),
63
+ ]
64
+ ],
65
+ )
66
+ @pytest.mark.skipif(
67
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
68
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
69
+ )
70
+ def test_global_cumsum_varlen(
71
+ H: int,
72
+ D: int,
73
+ cu_seqlens: list[int],
74
+ dtype: torch.dtype,
75
+ ):
76
+ torch.manual_seed(42)
77
+ T = cu_seqlens[-1]
78
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
79
+
80
+ s = torch.randn(1, T, H, dtype=dtype).to(device)
81
+ ref = torch.cat([s[:, start:end].float().cumsum(1) for start, end in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)], 1).to(dtype)
82
+ tri = chunk_global_cumsum(s, cu_seqlens=cu_seqlens)
83
+ assert_close('global_cumsum', ref, tri, 1e-3)
84
+
85
+ s = torch.randn(1, T, H, D, dtype=dtype).to(device)
86
+ ref = torch.cat([s[:, start:end].float().cumsum(1) for start, end in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)], 1).to(dtype)
87
+ tri = chunk_global_cumsum(s, cu_seqlens=cu_seqlens)
88
+ assert_close('global_cumsum', ref, tri, 1e-3)
89
+
90
+
91
+ @pytest.mark.parametrize(
92
+ ('B', 'T', 'H', 'D', 'dtype'),
93
+ [
94
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-{}".format(*test))
95
+ for test in [
96
+ (1, 63, 1, 30, torch.float),
97
+ (2, 500, 4, 60, torch.float),
98
+ (2, 1000, 5, 128, torch.float),
99
+ (3, 1024, 6, 500, torch.float),
100
+ (4, 2048, 8, 1024, torch.float),
101
+ ]
102
+ ],
103
+ )
104
+ def test_global_reversed_cumsum(
105
+ B: int,
106
+ T: int,
107
+ H: int,
108
+ D: int,
109
+ dtype: torch.dtype,
110
+ ):
111
+ torch.manual_seed(42)
112
+ s = torch.randn(B, T, H, dtype=dtype).to(device)
113
+ ref = reversed_cumsum(s, dim=(1)).to(dtype)
114
+ tri = chunk_global_cumsum(s, reverse=True)
115
+ assert_close('global_cumsum', ref, tri, 1e-3)
116
+
117
+ s = torch.randn(B, T, H, D, dtype=dtype).to(device)
118
+ ref = reversed_cumsum(s, dim=(1)).to(dtype)
119
+ tri = chunk_global_cumsum(s, reverse=True)
120
+ assert_close('global_cumsum', ref, tri, 1e-3)
121
+
122
+
123
+ @pytest.mark.parametrize(
124
+ ('H', 'D', 'cu_seqlens', 'dtype'),
125
+ [
126
+ pytest.param(*test, id="H{}-D{}-cu_seqlens{}-{}".format(*test))
127
+ for test in [
128
+ (2, 60, [0, 15], torch.float),
129
+ (3, 100, [0, 256, 500, 1000], torch.float),
130
+ (4, 256, [0, 15, 100, 300, 1200, 2000], torch.float),
131
+ (4, 500, [0, 1, 100, 300, 1200, 2048], torch.float16),
132
+ (2, 1024, [0, 200, 512, 1200, 2048], torch.float16),
133
+ ]
134
+ ],
135
+ )
136
+ @pytest.mark.skipif(
137
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
138
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
139
+ )
140
+ def test_global_reversed_cumsum_varlen(
141
+ H: int,
142
+ D: int,
143
+ cu_seqlens: list[int],
144
+ dtype: torch.dtype,
145
+ ):
146
+ torch.manual_seed(42)
147
+ T = cu_seqlens[-1]
148
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
149
+
150
+ s = torch.randn(1, T, H, dtype=dtype).to(device)
151
+ ref = torch.cat([reversed_cumsum(s[:, start:end], 1) for start, end in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)], 1).to(dtype)
152
+ tri = chunk_global_cumsum(s, reverse=True, cu_seqlens=cu_seqlens)
153
+ assert_close('global_reversed_cumsum', ref, tri, 1e-3)
154
+
155
+ s = torch.randn(1, T, H, D, dtype=dtype).to(device)
156
+ ref = torch.cat([reversed_cumsum(s[:, start:end], 1) for start, end in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)], 1).to(dtype)
157
+ tri = chunk_global_cumsum(s, reverse=True, cu_seqlens=cu_seqlens)
158
+ assert_close('global_reversed_cumsum', ref, tri, 1e-3)
159
+
160
+
161
+ @pytest.mark.parametrize(
162
+ ('B', 'T', 'H', 'C', 'D', 'dtype'),
163
+ [
164
+ pytest.param(*test, id="B{}-T{}-H{}-C{}-D{}-{}".format(*test))
165
+ for test in [
166
+ (1, 63, 1, 16, 30, torch.float),
167
+ (2, 500, 4, 32, 60, torch.float),
168
+ (2, 1000, 5, 64, 128, torch.float),
169
+ (3, 1024, 6, 64, 500, torch.float),
170
+ (4, 2048, 8, 128, 1024, torch.float),
171
+ ]
172
+ ],
173
+ )
174
+ def test_local_cumsum(
175
+ B: int,
176
+ T: int,
177
+ H: int,
178
+ C: int,
179
+ D: int,
180
+ dtype: torch.dtype,
181
+ ):
182
+ torch.manual_seed(42)
183
+ s = torch.randn(B, T, H, dtype=dtype).to(device)
184
+ ref = torch.cat([s[:, i:i+C, :].float().cumsum(1) for i in range(0, T, C)], 1)
185
+ tri = chunk_local_cumsum(s, chunk_size=C)
186
+ assert_close('local_cumsum', ref, tri, 1e-3)
187
+
188
+ s = torch.randn(B, T, H, D, dtype=dtype).to(device)
189
+ ref = torch.cat([s[:, i:i+C, :].float().cumsum(1) for i in range(0, T, C)], 1)
190
+ tri = chunk_local_cumsum(s, chunk_size=C)
191
+ assert_close('local_cumsum', ref, tri, 1e-3)
192
+
193
+
194
+ @pytest.mark.parametrize(
195
+ ('H', 'C', 'D', 'cu_seqlens', 'dtype'),
196
+ [
197
+ pytest.param(*test, id="H{}-C{}-D{}-cu_seqlens{}-{}".format(*test))
198
+ for test in [
199
+ (2, 32, 60, [0, 15], torch.float),
200
+ (3, 64, 100, [0, 256, 500, 1000], torch.float),
201
+ (4, 64, 256, [0, 15, 100, 300, 1200, 2000], torch.float),
202
+ (4, 128, 500, [0, 1, 100, 300, 1200, 2048], torch.float16),
203
+ (2, 128, 1024, [0, 200, 512, 1200, 2048], torch.float16),
204
+ ]
205
+ ],
206
+ )
207
+ @pytest.mark.skipif(
208
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
209
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
210
+ )
211
+ def test_local_cumsum_varlen(
212
+ H: int,
213
+ C: int,
214
+ D: int,
215
+ cu_seqlens: list[int],
216
+ dtype: torch.dtype,
217
+ ):
218
+ torch.manual_seed(42)
219
+ T = cu_seqlens[-1]
220
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
221
+
222
+ s = torch.randn(1, T, H, dtype=dtype).to(device)
223
+ ref = torch.cat([
224
+ torch.cat([s[:, i:min(end, i+C), :].float().cumsum(1) for i in range(start, end, C)], 1)
225
+ for start, end in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)
226
+ ], 1)
227
+ tri = chunk_local_cumsum(s, chunk_size=C, cu_seqlens=cu_seqlens)
228
+ assert_close('local_cumsum', ref, tri, 1e-3)
229
+
230
+ s = torch.randn(1, T, H, D, dtype=dtype).to(device)
231
+ ref = torch.cat([
232
+ torch.cat([s[:, i:min(end, i+C), :].float().cumsum(1) for i in range(start, end, C)], 1)
233
+ for start, end in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)
234
+ ], 1)
235
+ tri = chunk_local_cumsum(s, chunk_size=C, cu_seqlens=cu_seqlens)
236
+ assert_close('local_cumsum', ref, tri, 1e-3)
237
+
238
+
239
+ @pytest.mark.parametrize(
240
+ ('B', 'T', 'H', 'C', 'D', 'dtype'),
241
+ [
242
+ pytest.param(*test, id="B{}-T{}-H{}-C{}-D{}-{}".format(*test))
243
+ for test in [
244
+ (1, 63, 1, 16, 30, torch.float),
245
+ (2, 500, 4, 32, 60, torch.float),
246
+ (2, 1000, 5, 64, 128, torch.float),
247
+ (3, 1024, 6, 64, 500, torch.float),
248
+ (4, 2048, 8, 128, 1024, torch.float),
249
+ ]
250
+ ],
251
+ )
252
+ def test_mean_pooling(
253
+ B: int,
254
+ T: int,
255
+ H: int,
256
+ C: int,
257
+ D: int,
258
+ dtype: torch.dtype,
259
+ ):
260
+ torch.manual_seed(42)
261
+ x = torch.randn(B, T, H, D, dtype=dtype).to(device)
262
+ x.requires_grad = True
263
+ ref = torch.cat([x[:, i:i+C, :].float().mean(1, True) for i in range(0, T, C)], 1).to(dtype)
264
+ do = torch.randn_like(ref)
265
+ ref.backward(do)
266
+ ref_dx, x.grad = x.grad.clone(), None
267
+
268
+ tri = mean_pooling(x, chunk_size=C)
269
+ tri.backward(do)
270
+ tri_dx, x.grad = x.grad.clone(), None
271
+
272
+ assert_close('mean_pooling', ref, tri, 1e-3)
273
+ assert_close('mean_pooling', ref_dx, tri_dx, 1e-3)
274
+
275
+
276
+ @pytest.mark.parametrize(
277
+ ('H', 'C', 'D', 'cu_seqlens', 'dtype'),
278
+ [
279
+ pytest.param(*test, id="H{}-C{}-D{}-cu_seqlens{}-{}".format(*test))
280
+ for test in [
281
+ (2, 32, 60, [0, 15], torch.float),
282
+ (3, 64, 100, [0, 256, 500, 1000], torch.float),
283
+ (4, 64, 256, [0, 15, 100, 300, 1200, 2000], torch.float),
284
+ (4, 128, 500, [0, 1, 100, 300, 1200, 2048], torch.float16),
285
+ (2, 128, 1024, [0, 200, 512, 1200, 2048], torch.float16),
286
+ ]
287
+ ],
288
+ )
289
+ @pytest.mark.skipif(
290
+ os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1',
291
+ reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set',
292
+ )
293
+ def test_mean_pooling_varlen(
294
+ H: int,
295
+ C: int,
296
+ D: int,
297
+ cu_seqlens: list[int],
298
+ dtype: torch.dtype,
299
+ ):
300
+ torch.manual_seed(42)
301
+ T = cu_seqlens[-1]
302
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32, device=device)
303
+
304
+ x = torch.randn(1, T, H, D, dtype=dtype).to(device).requires_grad_(True)
305
+ ref = torch.cat([
306
+ torch.cat([x[:, i:min(end, i+C), :].float().mean(1, True) for i in range(start, end, C)], 1)
307
+ for start, end in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)
308
+ ], 1).to(dtype)
309
+ do = torch.randn_like(ref)
310
+ ref.backward(do)
311
+ ref_dx, x.grad = x.grad.clone(), None
312
+
313
+ tri = mean_pooling(x, chunk_size=C, cu_seqlens=cu_seqlens)
314
+ tri.backward(do)
315
+ tri_dx, x.grad = x.grad.clone(), None
316
+
317
+ torch.testing.assert_close(ref, tri.to(ref.dtype), rtol=1.6e-2, atol=3e-5)
318
+ torch.testing.assert_close(ref_dx, tri_dx.to(ref_dx.dtype), rtol=1.6e-2, atol=3e-5)
319
+
320
+
321
+ @pytest.mark.parametrize(
322
+ ('B', 'T', 'H', 'D', 'padding_side', 'dtype'),
323
+ [
324
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-padding_side{}-{}".format(*test))
325
+ for test in [
326
+ (1, 63, 1, 30, 'left', torch.float),
327
+ (2, 500, 4, 60, 'right', torch.float),
328
+ (2, 1000, 5, 128, 'left', torch.float),
329
+ (3, 1024, 6, 500, 'right', torch.float),
330
+ (4, 2048, 8, 1024, 'left', torch.float),
331
+ ]
332
+ ],
333
+ )
334
+ def test_pack_sequence(
335
+ B: int,
336
+ T: int,
337
+ H: int,
338
+ D: int,
339
+ padding_side: str,
340
+ dtype: torch.dtype,
341
+ ):
342
+ torch.manual_seed(42)
343
+ x = torch.randn(B, T, H, D, dtype=dtype).to(device).requires_grad_(True)
344
+ cu_seqlens = torch.cat(
345
+ [torch.tensor([0])]+[torch.randint(0, T, (1,)).clamp(min=1) for _ in range(B)],
346
+ ).cumsum(-1).to(device)
347
+ lens = prepare_lens(cu_seqlens)
348
+
349
+ if padding_side == 'left':
350
+ ref = torch.cat([x[i, -length:] for i, length in enumerate(lens.tolist())], 0)
351
+ else:
352
+ ref = torch.cat([x[i, :length] for i, length in enumerate(lens.tolist())], 0)
353
+ dy = torch.randn_like(ref)
354
+ ref.backward(dy)
355
+ ref_dx, x.grad = x.grad.clone(), None
356
+
357
+ tri = pack_sequence(x, cu_seqlens, padding_side=padding_side)
358
+ tri.backward(dy)
359
+ tri_dx, x.grad = x.grad.clone(), None
360
+
361
+ assert_close('y', ref, tri, 1e-3)
362
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
363
+
364
+
365
+ @pytest.mark.parametrize(
366
+ ('B', 'T', 'H', 'D', 'padding_side', 'dtype'),
367
+ [
368
+ pytest.param(*test, id="B{}-T{}-H{}-D{}-padding_side{}-{}".format(*test))
369
+ for test in [
370
+ (1, 63, 1, 30, 'left', torch.float),
371
+ (2, 500, 4, 60, 'right', torch.float),
372
+ (2, 1000, 5, 128, 'left', torch.float),
373
+ (3, 1024, 6, 500, 'right', torch.float),
374
+ (4, 2048, 8, 1024, 'left', torch.float),
375
+ ]
376
+ ],
377
+ )
378
+ def test_unpack_sequence(
379
+ B: int,
380
+ T: int,
381
+ H: int,
382
+ D: int,
383
+ padding_side: str,
384
+ dtype: torch.dtype,
385
+ ):
386
+ torch.manual_seed(42)
387
+ cu_seqlens = torch.cat(
388
+ [torch.tensor([0])]+[torch.randint(0, T, (1,)).clamp(min=1) for _ in range(B)],
389
+ ).cumsum(-1).to(device)
390
+ lens = prepare_lens(cu_seqlens)
391
+ desired_shape = (B, lens.max().item() + torch.randint(0, 10, (1,)).item(), H, D)
392
+
393
+ x = torch.randn(cu_seqlens[-1].item(), H, D, dtype=dtype).to(device).requires_grad_(True)
394
+ ref = torch.zeros(desired_shape, device=device, dtype=dtype)
395
+ dy = torch.randn_like(ref)
396
+ for i, (bos, eos) in enumerate(zip(cu_seqlens[:-1].tolist(), cu_seqlens[1:].tolist(), strict=False)):
397
+ length = eos - bos
398
+ if padding_side == 'left':
399
+ ref[i, -length:] = x[bos:eos]
400
+ else:
401
+ ref[i, :length] = x[bos:eos]
402
+ ref.backward(dy)
403
+ ref_dx, x.grad = x.grad.clone(), None
404
+
405
+ tri = unpack_sequence(x, cu_seqlens, padding_side=padding_side, desired_shape=desired_shape)
406
+ tri.backward(dy)
407
+ tri_dx, x.grad = x.grad.clone(), None
408
+
409
+ assert_close('y', ref, tri, 1e-3)
410
+ assert_close('dx', ref_dx, tri_dx, 1e-3)
code/flash-linear-attention/utils/convert_from_llama.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # scripts for converting pretrained hf model weights to fla style
3
+ # calling the code to make conversions for mistralai/Mistral-7B-v0.1 would achieve the following results:
4
+ # | Tasks |Version|Filter|n-shot| Metric |Value | |Stderr|
5
+ # |--------------|------:|------|-----:|----------|-----:|---|-----:|
6
+ # |arc_challenge | 1|none | 0|acc |0.5043|Β± |0.0146|
7
+ # | | |none | 0|acc_norm |0.5392|Β± |0.0146|
8
+ # |arc_easy | 1|none | 0|acc |0.8081|Β± |0.0081|
9
+ # | | |none | 0|acc_norm |0.7946|Β± |0.0083|
10
+ # |boolq | 2|none | 0|acc |0.8373|Β± |0.0065|
11
+ # |copa | 1|none | 0|acc |0.9300|Β± |0.0256|
12
+ # |hellaswag | 1|none | 0|acc |0.6127|Β± |0.0049|
13
+ # | | |none | 0|acc_norm |0.8100|Β± |0.0039|
14
+ # |lambada_openai| 1|none | 0|perplexity|3.1810|Β± |0.0583|
15
+ # | | |none | 0|acc |0.7563|Β± |0.0060|
16
+ # |openbookqa | 1|none | 0|acc |0.3260|Β± |0.0210|
17
+ # | | |none | 0|acc_norm |0.4380|Β± |0.0222|
18
+ # |piqa | 1|none | 0|acc |0.8069|Β± |0.0092|
19
+ # | | |none | 0|acc_norm |0.8215|Β± |0.0089|
20
+ # |sciq | 1|none | 0|acc |0.9580|Β± |0.0063|
21
+ # | | |none | 0|acc_norm |0.9390|Β± |0.0076|
22
+ # |winogrande | 1|none | 0|acc |0.7395|Β± |0.0123|
23
+
24
+
25
+ import argparse
26
+ import warnings
27
+
28
+ import torch
29
+ from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
30
+
31
+ import fla # noqa
32
+
33
+
34
+ def sizeof_fmt(num, suffix='B'):
35
+ for unit in ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'):
36
+ if abs(num) < 1024.0:
37
+ return f'{num:.2f}{unit}{suffix}'
38
+ num /= 1024.0
39
+ return f'{num:.2f}Yi{suffix}'
40
+
41
+
42
+ def convert(
43
+ llama: str,
44
+ config: str,
45
+ output: str,
46
+ precision: str = 'float32',
47
+ ):
48
+ AutoTokenizer.from_pretrained(llama).save_pretrained(output)
49
+ llama = AutoModelForCausalLM.from_pretrained(llama, torch_dtype=precision)
50
+ print(f"Loading Llama ...\n{llama}")
51
+
52
+ config = AutoConfig.from_pretrained(config)
53
+ config.torch_dtype = precision
54
+ model = AutoModelForCausalLM.from_config(config)
55
+ if precision in ['float16', 'fp16']:
56
+ model = model.to(torch.float16)
57
+ elif precision in ['bfloat16', 'bf16']:
58
+ model = model.to(torch.bfloat16)
59
+ num_parameters = model.num_parameters()
60
+ print(f"Initializing the model from the config:\n{config}\n{model}")
61
+ print(f"Number of parameters in total: {num_parameters} ({sizeof_fmt(num_parameters)})")
62
+
63
+ print("Copying the weights from Llama to the model ...")
64
+ vocab_size = llama.model.embed_tokens.weight.shape[0]
65
+ if model.model.embeddings.weight.shape[0] != vocab_size:
66
+ warnings.warn(f"Llama and the model have different embedding sizes "
67
+ f"({vocab_size} vs {model.model.embeddings.weight.shape[0]}), "
68
+ f"the model embeddings will be extended with randomly initialized values or truncated")
69
+ vocab_size = min(model.model.embeddings.weight.shape[0], vocab_size)
70
+ print("llama.model.embed_tokens -> model.model.embeddings")
71
+ model.model.embeddings.weight.data[:vocab_size].copy_(llama.model.embed_tokens.weight[:vocab_size])
72
+ torch.testing.assert_close(model.model.embeddings.weight[:vocab_size], llama.model.embed_tokens.weight[:vocab_size])
73
+ for i in range(config.num_hidden_layers):
74
+ if hasattr(model.model.layers[i], 'attn_norm'):
75
+ if model.model.layers[i].attn_norm.weight is not None:
76
+ print(f"llama.model.layers{i}.input_layernorm.weight -> model.model.layers{i}.attn_norm.weight")
77
+ model.model.layers[i].attn_norm.weight.data.copy_(llama.model.layers[i].input_layernorm.weight)
78
+ torch.testing.assert_close(model.model.layers[i].attn_norm.weight,
79
+ llama.model.layers[i].input_layernorm.weight)
80
+ if model.model.layers[i].attn_norm.bias is not None:
81
+ print(f"llama.model.layers{i}.input_layernorm.bias -> model.model.layers{i}.attn_norm.bias")
82
+ model.model.layers[i].attn_norm.bias.data.copy_(llama.model.layers[i].input_layernorm.bias)
83
+ torch.testing.assert_close(model.model.layers[i].attn_norm.bias,
84
+ llama.model.layers[i].input_layernorm.bias)
85
+ model.model.layers[i].attn_norm.eps = llama.model.layers[i].input_layernorm.variance_epsilon
86
+ if hasattr(model.model.layers[i].attn, 'norm'):
87
+ if model.model.layers[i].attn.norm.weight is not None:
88
+ print(f"llama.model.layers{i}.input_layernorm.weight -> model.model.layers{i}.attn.norm.weight")
89
+ model.model.layers[i].attn.norm.weight.data.copy_(llama.model.layers[i].input_layernorm.weight)
90
+ torch.testing.assert_close(model.model.layers[i].attn.norm.weight,
91
+ llama.model.layers[i].input_layernorm.weight)
92
+ if model.model.layers[i].attn.norm.bias is not None:
93
+ print(f"llama.model.layers{i}.input_layernorm.bias -> model.model.layers{i}.attn.norm.bias")
94
+ model.model.layers[i].attn.norm.bias.data.copy_(llama.model.layers[i].input_layernorm.bias)
95
+ torch.testing.assert_close(model.model.layers[i].attn.norm.bias,
96
+ llama.model.layers[i].input_layernorm.bias)
97
+ model.model.layers[i].attn.norm.eps = llama.model.layers[i].input_layernorm.variance_epsilon
98
+
99
+ print(f"llama.model.layers{i}.attn.q_proj.weight -> model.model.layers{i}.attn.q_proj.weight")
100
+ model.model.layers[i].attn.q_proj.weight.data.copy_(llama.model.layers[i].self_attn.q_proj.weight)
101
+ torch.testing.assert_close(model.model.layers[i].attn.q_proj.weight, llama.model.layers[i].self_attn.q_proj.weight)
102
+ if hasattr(llama.model.layers[i].self_attn.q_proj, 'bias') and hasattr(model.model.layers[i].attn.q_proj, 'bias'):
103
+ print(f"llama.model.layers{i}.attn.q_proj.bias -> model.model.layers{i}.attn.q_proj.bias")
104
+ model.model.layers[i].attn.q_proj.bias.data.copy_(llama.model.layers[i].self_attn.q_proj.bias)
105
+ torch.testing.assert_close(model.model.layers[i].attn.q_proj.bias, llama.model.layers[i].self_attn.q_proj.bias)
106
+ print(f"llama.model.layers.{i}.attn.k_proj.weight -> model.model.layers.{i}.attn.k_proj.weight")
107
+ model.model.layers[i].attn.k_proj.weight.data.copy_(llama.model.layers[i].self_attn.k_proj.weight)
108
+ torch.testing.assert_close(model.model.layers[i].attn.k_proj.weight, llama.model.layers[i].self_attn.k_proj.weight)
109
+ if hasattr(llama.model.layers[i].self_attn.k_proj, 'bias') and hasattr(model.model.layers[i].attn.k_proj, 'bias'):
110
+ print(f"llama.model.layers{i}.attn.k_proj.bias -> model.model.layers{i}.attn.k_proj.bias")
111
+ model.model.layers[i].attn.k_proj.bias.data.copy_(llama.model.layers[i].self_attn.k_proj.bias)
112
+ torch.testing.assert_close(model.model.layers[i].attn.k_proj.bias, llama.model.layers[i].self_attn.k_proj.bias)
113
+ print(f"llama.model.layers.{i}.attn.v_proj.weight -> model.model.layers.{i}.attn.v_proj.weight")
114
+ model.model.layers[i].attn.v_proj.weight.data.copy_(llama.model.layers[i].self_attn.v_proj.weight)
115
+ torch.testing.assert_close(model.model.layers[i].attn.v_proj.weight, llama.model.layers[i].self_attn.v_proj.weight)
116
+ if hasattr(llama.model.layers[i].self_attn.v_proj, 'bias') and hasattr(model.model.layers[i].attn.v_proj, 'bias'):
117
+ print(f"llama.model.layers{i}.attn.v_proj.bias -> model.model.layers{i}.attn.v_proj.bias")
118
+ model.model.layers[i].attn.v_proj.bias.data.copy_(llama.model.layers[i].self_attn.v_proj.bias)
119
+ torch.testing.assert_close(model.model.layers[i].attn.v_proj.bias, llama.model.layers[i].self_attn.v_proj.bias)
120
+
121
+ print(f"llama.model.layers.{i}.attn.o_proj.weight -> model.model.layers.{i}.attn.o_proj.weight")
122
+ model.model.layers[i].attn.o_proj.weight.data.copy_(llama.model.layers[i].self_attn.o_proj.weight)
123
+ torch.testing.assert_close(model.model.layers[i].attn.o_proj.weight, llama.model.layers[i].self_attn.o_proj.weight)
124
+
125
+ if hasattr(model.model.layers[i], 'mlp_norm'):
126
+ if model.model.layers[i].mlp_norm.weight is not None:
127
+ print(f"llama.model.layers{i}.post_attention_layernorm.weight -> model.model.layers{i}.mlp_norm.weight")
128
+ model.model.layers[i].mlp_norm.weight.data.copy_(llama.model.layers[i].post_attention_layernorm.weight)
129
+ torch.testing.assert_close(model.model.layers[i].mlp_norm.weight,
130
+ llama.model.layers[i].post_attention_layernorm.weight)
131
+ if model.model.layers[i].mlp_norm.bias is not None:
132
+ print(f"llama.model.layers{i}.post_attention_layernorm.bias -> model.model.layers{i}.mlp_norm.bias")
133
+ model.model.layers[i].mlp_norm.bias.data.copy_(llama.model.layers[i].post_attention_layernorm.bias)
134
+ torch.testing.assert_close(model.model.layers[i].mlp_norm.bias,
135
+ llama.model.layers[i].post_attention_layernorm.bias)
136
+ model.model.layers[i].mlp_norm.eps = llama.model.layers[i].post_attention_layernorm.variance_epsilon
137
+ if hasattr(model.model.layers[i].mlp, 'norm'):
138
+ if model.model.layers[i].mlp.norm.weight is not None:
139
+ print(f"llama.model.layers{i}.post_attention_layernorm.weight -> model.model.layers{i}.mlp.norm.weight")
140
+ model.model.layers[i].mlp.norm.weight.data.copy_(llama.model.layers[i].post_attention_layernorm.weight)
141
+ torch.testing.assert_close(model.model.layers[i].mlp.norm.weight,
142
+ llama.model.layers[i].post_attention_layernorm.weight)
143
+ if model.model.layers[i].mlp.norm.bias is not None:
144
+ print(f"llama.model.layers{i}.post_attention_layernorm.bias -> model.model.layers{i}.mlp.norm.bias")
145
+ model.model.layers[i].mlp.norm.bias.data.copy_(llama.model.layers[i].post_attention_layernorm.bias)
146
+ torch.testing.assert_close(model.model.layers[i].mlp.norm.bias,
147
+ llama.model.layers[i].post_attention_layernorm.bias)
148
+ model.model.layers[i].mlp.norm.eps = llama.model.layers[i].post_attention_layernorm.variance_epsilon
149
+
150
+ print(f"llama.model.layers.{i}.mlp.gate_proj.weight -> model.model.layers.{i}.mlp.gate_proj.weight")
151
+ model.model.layers[i].mlp.gate_proj.weight.data.copy_(llama.model.layers[i].mlp.gate_proj.weight)
152
+ torch.testing.assert_close(model.model.layers[i].mlp.gate_proj.weight, llama.model.layers[i].mlp.gate_proj.weight)
153
+ print(f"llama.model.layers.{i}.mlp.up_proj.weight -> model.model.layers.{i}.mlp.up_proj.weight")
154
+ model.model.layers[i].mlp.up_proj.weight.data.copy_(llama.model.layers[i].mlp.up_proj.weight)
155
+ torch.testing.assert_close(model.model.layers[i].mlp.up_proj.weight, llama.model.layers[i].mlp.up_proj.weight)
156
+
157
+ print(f"llama.model.layers.{i}.mlp.down_proj.weight -> model.model.layers.{i}.mlp.down_proj.weight")
158
+ model.model.layers[i].mlp.down_proj.weight.data.copy_(llama.model.layers[i].mlp.down_proj.weight)
159
+ torch.testing.assert_close(model.model.layers[i].mlp.down_proj.weight,
160
+ llama.model.layers[i].mlp.down_proj.weight)
161
+
162
+ if model.model.norm.weight is not None:
163
+ print("llama.model.norm.weight -> model.model.norm.weight")
164
+ model.model.norm.weight.data.copy_(llama.model.norm.weight)
165
+ torch.testing.assert_close(model.model.norm.weight, llama.model.norm.weight)
166
+ if model.model.norm.bias is not None:
167
+ print("llama.model.norm.bias -> model.model.norm.bias")
168
+ model.model.norm.bias.data.copy_(llama.model.norm.bias)
169
+ torch.testing.assert_close(model.model.norm.bias, llama.model.norm.bias)
170
+ model.model.norm.eps = llama.model.norm.variance_epsilon
171
+
172
+ if not model.config.tie_word_embeddings:
173
+ print("llama.model.lm_head.weight -> model.lm_head.weight")
174
+ model.lm_head.weight.data[:vocab_size].copy_(llama.lm_head.weight[:vocab_size])
175
+ torch.testing.assert_close(model.lm_head.weight[:vocab_size], llama.lm_head.weight[:vocab_size])
176
+ model.config.rope_theta = llama.config.rope_theta
177
+
178
+ print(f"Saving converted model to {output} ...\n{model}")
179
+ model.save_pretrained(output)
180
+
181
+
182
+ if __name__ == "__main__":
183
+ parser = argparse.ArgumentParser()
184
+ parser.add_argument("--model", default='mistralai/Mistral-7B-v0.1')
185
+ parser.add_argument("--config", default='configs/transformer_7B.json')
186
+ parser.add_argument("--output", default='converted/transformer-7B')
187
+ parser.add_argument('--precision', type=str, default='float32')
188
+ args = parser.parse_args()
189
+ convert(args.model, args.config, args.output, precision=args.precision)
code/flash-linear-attention/utils/convert_from_rwkv6.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # scripts for converting pretrained hf model weights to fla style
3
+ # calling the code to make conversions for RWKV/rwkv-6-world-7b would achieve the following results:
4
+ # | Tasks |Version|Filter|n-shot| Metric | Value | |Stderr|
5
+ # |--------------|------:|------|-----:|---------------|------:|---|------|
6
+ # |arc_challenge | 1|none | 0|acc | 0.4130|Β± |0.0144|
7
+ # | | |none | 0|acc_norm | 0.4403|Β± |0.0145|
8
+ # |arc_easy | 1|none | 0|acc | 0.7382|Β± |0.0090|
9
+ # | | |none | 0|acc_norm | 0.7079|Β± |0.0093|
10
+ # |boolq | 2|none | 0|acc | 0.6823|Β± |0.0081|
11
+ # |copa | 1|none | 0|acc | 0.8700|Β± |0.0338|
12
+ # |hellaswag | 1|none | 0|acc | 0.5508|Β± |0.0050|
13
+ # | | |none | 0|acc_norm | 0.7171|Β± |0.0045|
14
+ # |lambada_openai| 1|none | 0|perplexity | 3.2989|Β± |0.0634|
15
+ # | | |none | 0|acc | 0.7493|Β± |0.0060|
16
+ # |openbookqa | 1|none | 0|acc | 0.3200|Β± |0.0209|
17
+ # | | |none | 0|acc_norm | 0.4440|Β± |0.0222|
18
+ # |piqa | 1|none | 0|acc | 0.7753|Β± |0.0097|
19
+ # | | |none | 0|acc_norm | 0.7894|Β± |0.0095|
20
+ # |sciq | 1|none | 0|acc | 0.9370|Β± |0.0077|
21
+ # | | |none | 0|acc_norm | 0.8860|Β± |0.0101|
22
+ # |winogrande | 1|none | 0|acc | 0.6867|Β± |0.0130|
23
+
24
+ import argparse
25
+
26
+ import torch
27
+ from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
28
+
29
+ import fla # noqa
30
+ from fla.utils import device
31
+
32
+
33
+ def sizeof_fmt(num, suffix='B'):
34
+ for unit in ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'):
35
+ if abs(num) < 1024.0:
36
+ return f'{num:.2f}{unit}{suffix}'
37
+ num /= 1024.0
38
+ return f'{num:.2f}Yi{suffix}'
39
+
40
+
41
+ def convert(
42
+ rwkv6: str,
43
+ config: str,
44
+ output: str,
45
+ ):
46
+ torch.manual_seed(1)
47
+ AutoTokenizer.from_pretrained(rwkv6, trust_remote_code=True).save_pretrained(output)
48
+ rwkv6 = AutoModelForCausalLM.from_pretrained(rwkv6, trust_remote_code=True).to(device)
49
+ print(f"Loading rwkv6 ...\n{rwkv6}")
50
+
51
+ config = AutoConfig.from_pretrained(config)
52
+ model = AutoModelForCausalLM.from_config(config).to(device)
53
+ num_parameters = model.num_parameters()
54
+ print(f"Initializing the model from the config:\n{config}\n{model}")
55
+ print(f"Number of parameters in total: {num_parameters} ({sizeof_fmt(num_parameters)})")
56
+
57
+ print("Copying the weights from rwkv6 to the model ...")
58
+ print("rwkv6.rwkv.embeddings -> model.model.embeddings")
59
+ model.model.embeddings.weight.data.copy_(rwkv6.rwkv.embeddings.weight)
60
+ torch.testing.assert_close(model.model.embeddings.weight, rwkv6.rwkv.embeddings.weight)
61
+ for i in range(config.num_hidden_layers):
62
+ if hasattr(model.model.layers[i], 'pre_norm'):
63
+ if model.model.layers[i].pre_norm.weight is not None:
64
+ print(f"rwkv6.rwkv.blocks{i}.pre_ln.weight -> model.model.layers{i}.pre_norm.weight")
65
+ model.model.layers[i].pre_norm.weight.data.copy_(rwkv6.rwkv.blocks[i].pre_ln.weight)
66
+ torch.testing.assert_close(model.model.layers[i].pre_norm.weight, rwkv6.rwkv.blocks[i].pre_ln.weight)
67
+ if model.model.layers[i].pre_norm.bias is not None:
68
+ print(f"rwkv6.rwkv.blocks{i}.pre_ln.bias -> model.model.layers{i}.pre_norm.bias")
69
+ model.model.layers[i].pre_norm.bias.data.copy_(rwkv6.rwkv.blocks[i].pre_ln.bias)
70
+ torch.testing.assert_close(model.model.layers[i].pre_norm.bias, rwkv6.rwkv.blocks[i].pre_ln.bias)
71
+ model.model.layers[i].pre_norm.eps = rwkv6.rwkv.blocks[i].pre_ln.eps
72
+ if model.model.layers[i].attn_norm.weight is not None:
73
+ print(f"rwkv6.rwkv.blocks{i}.ln1.weight -> model.model.layers{i}.attn_norm.weight")
74
+ model.model.layers[i].attn_norm.weight.data.copy_(rwkv6.rwkv.blocks[i].ln1.weight)
75
+ torch.testing.assert_close(model.model.layers[i].attn_norm.weight, rwkv6.rwkv.blocks[i].ln1.weight)
76
+ if model.model.layers[i].attn_norm.bias is not None:
77
+ print(f"rwkv6.rwkv.blocks{i}.ln1.bias -> model.model.layers{i}.attn_norm.bias")
78
+ model.model.layers[i].attn_norm.bias.data.copy_(rwkv6.rwkv.blocks[i].ln1.bias)
79
+ torch.testing.assert_close(model.model.layers[i].attn_norm.bias, rwkv6.rwkv.blocks[i].ln1.bias)
80
+ model.model.layers[i].attn_norm.eps = rwkv6.rwkv.blocks[i].ln1.eps
81
+
82
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_maa_x -> model.model.layers.{i}.attn.x_proj0.mu")
83
+ model.model.layers[i].attn.x_proj[0].mu.data.copy_(rwkv6.rwkv.blocks[i].attention.time_maa_x.view(-1))
84
+ torch.testing.assert_close(model.model.layers[i].attn.x_proj[0].mu,
85
+ rwkv6.rwkv.blocks[i].attention.time_maa_x.view(-1))
86
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_maa_w1.weight -> model.model.layers{i}.attn.x_proj0.linear.weight")
87
+ ww, wk, wv, wr, wg = rwkv6.rwkv.blocks[i].attention.time_maa_w1.view(config.hidden_size, 5, -1).unbind(-2)
88
+ w = torch.cat((wr, ww, wk, wv, wg), -1).t()
89
+ model.model.layers[i].attn.x_proj[0].linear.weight.data.copy_(w)
90
+ torch.testing.assert_close(model.model.layers[i].attn.x_proj[0].linear.weight, w)
91
+
92
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_maa_w2.weight -> model.model.layers{i}.attn.x_proj2.weight")
93
+ ww, wk, wv, wr, wg = rwkv6.rwkv.blocks[i].attention.time_maa_w2.unbind(0)
94
+ w = torch.cat((wr, ww, wk, wv, wg), 0).t()
95
+ model.model.layers[i].attn.x_proj[2].weight.data.copy_(w)
96
+ torch.testing.assert_close(model.model.layers[i].attn.x_proj[2].weight, w)
97
+
98
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_maa_wkvrg -> model.model.layers{i}.attn.x_bias")
99
+ bias = torch.stack((rwkv6.rwkv.blocks[i].attention.time_maa_r.view(-1),
100
+ rwkv6.rwkv.blocks[i].attention.time_maa_w.view(-1),
101
+ rwkv6.rwkv.blocks[i].attention.time_maa_k.view(-1),
102
+ rwkv6.rwkv.blocks[i].attention.time_maa_v.view(-1),
103
+ rwkv6.rwkv.blocks[i].attention.time_maa_g.view(-1)))
104
+ model.model.layers[i].attn.x_bias.data.copy_(bias)
105
+ torch.testing.assert_close(model.model.layers[i].attn.x_bias, bias)
106
+
107
+ print(f"rwkv6.rwkv.blocks{i}.attention.receptance.weight -> model.model.layers{i}.attn.r_proj.linear.weight")
108
+ model.model.layers[i].attn.r_proj.linear.weight.data.copy_(rwkv6.rwkv.blocks[i].attention.receptance.weight)
109
+ torch.testing.assert_close(model.model.layers[i].attn.r_proj.linear.weight,
110
+ rwkv6.rwkv.blocks[i].attention.receptance.weight)
111
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_decay_w1 -> model.model.layers{i}.attn.w_proj.linear.lora0.weight")
112
+ model.model.layers[i].attn.w_proj.linear.lora[0].weight.data.copy_(rwkv6.rwkv.blocks[i].attention.time_decay_w1.t())
113
+ torch.testing.assert_close(model.model.layers[i].attn.w_proj.linear.lora[0].weight,
114
+ rwkv6.rwkv.blocks[i].attention.time_decay_w1.t())
115
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_decay_w2 -> model.model.layers{i}.attn.w_proj.linear.lora2.weight")
116
+ model.model.layers[i].attn.w_proj.linear.lora[2].weight.data.copy_(rwkv6.rwkv.blocks[i].attention.time_decay_w2.t())
117
+ torch.testing.assert_close(model.model.layers[i].attn.w_proj.linear.lora[2].weight,
118
+ rwkv6.rwkv.blocks[i].attention.time_decay_w2.t())
119
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_decay -> model.model.layers{i}.attn.w_proj.linear.lora2.bias")
120
+ model.model.layers[i].attn.w_proj.linear.lora[2].bias.data.copy_(rwkv6.rwkv.blocks[i].attention.time_decay.view(-1))
121
+ torch.testing.assert_close(model.model.layers[i].attn.w_proj.linear.lora[2].bias,
122
+ rwkv6.rwkv.blocks[i].attention.time_decay.view(-1))
123
+
124
+ print(f"rwkv6.rwkv.blocks{i}.attention.key.weight -> model.model.layers.{i}.attn.k_proj.linear.weight")
125
+ model.model.layers[i].attn.k_proj.linear.weight.data.copy_(rwkv6.rwkv.blocks[i].attention.key.weight)
126
+ torch.testing.assert_close(model.model.layers[i].attn.k_proj.linear.weight,
127
+ rwkv6.rwkv.blocks[i].attention.key.weight)
128
+ print(f"rwkv6.rwkv.blocks{i}.attention.value.weight -> model.model.layers.{i}.attn.v_proj.linear.weight")
129
+ model.model.layers[i].attn.v_proj.linear.weight.data.copy_(rwkv6.rwkv.blocks[i].attention.value.weight)
130
+ torch.testing.assert_close(model.model.layers[i].attn.v_proj.linear.weight,
131
+ rwkv6.rwkv.blocks[i].attention.value.weight)
132
+ print(f"rwkv6.rwkv.blocks{i}.attention.gate.weight -> model.model.layers.{i}.attn.g_proj.linear.weight")
133
+ model.model.layers[i].attn.g_proj.linear.weight.data.copy_(rwkv6.rwkv.blocks[i].attention.gate.weight)
134
+ torch.testing.assert_close(model.model.layers[i].attn.g_proj.linear.weight,
135
+ rwkv6.rwkv.blocks[i].attention.gate.weight)
136
+ print(f"rwkv6.rwkv.blocks{i}.attention.time_faaaa -> model.model.layers.{i}.attn.bonus")
137
+ bonus = rwkv6.rwkv.blocks[i].attention.time_faaaa.view(config.num_heads, -1)
138
+ model.model.layers[i].attn.bonus.data.copy_(bonus)
139
+ torch.testing.assert_close(model.model.layers[i].attn.bonus, bonus)
140
+
141
+ if model.model.layers[i].attn.g_norm.weight is not None:
142
+ print(f"rwkv6.rwkv.blocks{i}.attention.ln_x.weight -> model.model.layers[i].attn.g_norm.weight")
143
+ model.model.layers[i].attn.g_norm.weight.data.copy_(rwkv6.rwkv.blocks[i].attention.ln_x.weight)
144
+ torch.testing.assert_close(model.model.layers[i].attn.g_norm.weight, rwkv6.rwkv.blocks[i].attention.ln_x.weight)
145
+ if model.model.layers[i].attn.g_norm.bias is not None:
146
+ print(f"rwkv6.rwkv.blocks{i}.attention.ln_x.bias -> model.model.layers[i].attn.g_norm.bias")
147
+ model.model.layers[i].attn.g_norm.bias.data.copy_(rwkv6.rwkv.blocks[i].attention.ln_x.bias)
148
+ torch.testing.assert_close(model.model.layers[i].attn.g_norm.bias, rwkv6.rwkv.blocks[i].attention.ln_x.bias)
149
+ model.model.layers[i].attn.g_norm.eps = rwkv6.rwkv.blocks[i].attention.ln_x.eps
150
+
151
+ print(f"rwkv6.rwkv.blocks{i}.attention.output.weight -> model.model.layers.{i}.attn.o_proj.weight")
152
+ model.model.layers[i].attn.o_proj.weight.data.copy_(rwkv6.rwkv.blocks[i].attention.output.weight)
153
+ torch.testing.assert_close(model.model.layers[i].attn.o_proj.weight, rwkv6.rwkv.blocks[i].attention.output.weight)
154
+
155
+ if model.model.layers[i].ffn_norm.weight is not None:
156
+ print(f"rwkv6.rwkv.blocks{i}.ln2.weight -> model.model.layers{i}.ffn_norm.weight")
157
+ model.model.layers[i].ffn_norm.weight.data.copy_(rwkv6.rwkv.blocks[i].ln2.weight)
158
+ torch.testing.assert_close(model.model.layers[i].ffn_norm.weight, rwkv6.rwkv.blocks[i].ln2.weight)
159
+ if model.model.layers[i].ffn_norm.bias is not None:
160
+ print(f"rwkv6.rwkv.blocks{i}.ln2.bias -> model.model.layers{i}.ffn_norm.bias")
161
+ model.model.layers[i].ffn_norm.bias.data.copy_(rwkv6.rwkv.blocks[i].ln2.bias)
162
+ torch.testing.assert_close(model.model.layers[i].ffn_norm.bias, rwkv6.rwkv.blocks[i].ln2.bias)
163
+ model.model.layers[i].ffn_norm.eps = rwkv6.rwkv.blocks[i].ln2.eps
164
+
165
+ print(f"rwkv6.rwkv.blocks{i}.feed_forward.key.weight -> model.model.layers.{i}.ffn.key.linear.weight")
166
+ model.model.layers[i].ffn.key.linear.weight.data.copy_(rwkv6.rwkv.blocks[i].feed_forward.key.weight)
167
+ torch.testing.assert_close(model.model.layers[i].ffn.key.linear.weight,
168
+ rwkv6.rwkv.blocks[i].feed_forward.key.weight)
169
+ print(f"rwkv6.rwkv.blocks{i}.feed_forward.time_maa_k -> model.model.layers.{i}.ffn.key.mu")
170
+ model.model.layers[i].ffn.key.mu.data.copy_(rwkv6.rwkv.blocks[i].feed_forward.time_maa_k.view(-1))
171
+ torch.testing.assert_close(model.model.layers[i].ffn.key.mu,
172
+ rwkv6.rwkv.blocks[i].feed_forward.time_maa_k.view(-1))
173
+
174
+ print(f"rwkv6.rwkv.blocks{i}.feed_forward.value.weight -> model.model.layers.{i}.ffn.value.weight")
175
+ model.model.layers[i].ffn.value.weight.data.copy_(rwkv6.rwkv.blocks[i].feed_forward.value.weight)
176
+ torch.testing.assert_close(model.model.layers[i].ffn.value.weight,
177
+ rwkv6.rwkv.blocks[i].feed_forward.value.weight)
178
+
179
+ print(f"rwkv6.rwkv.blocks{i}.feed_forward.receptance.weight -> model.model.layers.{i}.ffn.receptance.linear.weight")
180
+ model.model.layers[i].ffn.receptance.linear.weight.data.copy_(rwkv6.rwkv.blocks[i].feed_forward.receptance.weight)
181
+ torch.testing.assert_close(model.model.layers[i].ffn.receptance.linear.weight,
182
+ rwkv6.rwkv.blocks[i].feed_forward.receptance.weight)
183
+ print(f"rwkv6.rwkv.blocks{i}.feed_forward.time_maa_r -> model.model.layers.{i}.ffn.receptance.mu")
184
+ model.model.layers[i].ffn.receptance.mu.data.copy_(rwkv6.rwkv.blocks[i].feed_forward.time_maa_r.view(-1))
185
+ torch.testing.assert_close(model.model.layers[i].ffn.receptance.mu,
186
+ rwkv6.rwkv.blocks[i].feed_forward.time_maa_r.view(-1))
187
+
188
+ if model.model.norm.weight is not None:
189
+ print("rwkv6.rwkv.ln_out.weight -> model.model.norm.weight")
190
+ model.model.norm.weight.data.copy_(rwkv6.rwkv.ln_out.weight)
191
+ torch.testing.assert_close(model.model.norm.weight, rwkv6.rwkv.ln_out.weight)
192
+ if model.model.norm.bias is not None:
193
+ print("rwkv6.rwkv.ln_out.bias -> model.model.norm.bias")
194
+ model.model.norm.bias.data.copy_(rwkv6.rwkv.ln_out.bias)
195
+ torch.testing.assert_close(model.model.norm.bias, rwkv6.rwkv.ln_out.bias)
196
+ model.model.norm.eps = rwkv6.rwkv.ln_out.eps
197
+
198
+ if not model.config.tie_word_embeddings:
199
+ print("rwkv6.rwkv.head.weight -> model.lm_head.weight")
200
+ model.lm_head.weight.data.copy_(rwkv6.head.weight)
201
+ torch.testing.assert_close(model.lm_head.weight, rwkv6.head.weight)
202
+
203
+ print(f"Saving converted model \n{model}\n to {output} ...")
204
+ model.save_pretrained(output)
205
+
206
+
207
+ if __name__ == "__main__":
208
+ parser = argparse.ArgumentParser()
209
+ parser.add_argument("--model", default='RWKV/rwkv-6-world-7b')
210
+ parser.add_argument("--config", default='configs/rwkv6_7B.json')
211
+ parser.add_argument("--output", default='converted/rwkv6-7B')
212
+ args = parser.parse_args()
213
+ convert(args.model, args.config, args.output)
code/flash-linear-attention/utils/convert_from_rwkv7.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # scripts for converting pretrained hf model weights to fla style
3
+
4
+ import argparse
5
+ import os
6
+ import re
7
+
8
+ import torch
9
+ from transformers import AutoModelForCausalLM
10
+
11
+ import fla # noqa
12
+ from fla.models.rwkv7 import RWKV7Config
13
+
14
+
15
+ def convert(
16
+ rwkv7: str,
17
+ output: str,
18
+ precision: str = 'float32',
19
+ ):
20
+ weights = torch.load(rwkv7, weights_only=True, map_location='cpu')
21
+ config = RWKV7Config()
22
+ config.vocab_size = weights['emb.weight'].shape[0] # 50304
23
+ config.hidden_size = weights['blocks.0.ffn.key.weight'].shape[1] # 768
24
+ config.hidden_ratio = weights['blocks.0.ffn.key.weight'].shape[0] / weights['blocks.0.ffn.key.weight'].shape[1] # 4.0
25
+ config.intermediate_size = weights['blocks.0.ffn.key.weight'].shape[0]
26
+ config.num_hidden_layers = 0
27
+ while f'blocks.{config.num_hidden_layers}.ffn.key.weight' in weights:
28
+ config.num_hidden_layers += 1
29
+ # 12
30
+ config.value_dim = [config.hidden_size] * config.num_hidden_layers
31
+ config.decay_low_rank_dim = weights['blocks.0.att.w1'].shape[1] # 64
32
+ config.gate_low_rank_dim = weights['blocks.0.att.g1'].shape[1] # 128
33
+ config.a_low_rank_dim = weights['blocks.0.att.a1'].shape[1] # 64
34
+ try:
35
+ config.v_low_rank_dim = weights['blocks.1.att.v1'].shape[1] # 32
36
+ except KeyError:
37
+ config.v_low_rank_dim = 32
38
+
39
+ if precision in ['bf16', 'bfloat16']:
40
+ precision = 'bfloat16'
41
+ dtype = torch.bfloat16
42
+ if precision in ['fp16', 'float16']:
43
+ precision = 'float16'
44
+ dtype = torch.float16
45
+ if precision in ['fp64', 'double', 'float64']:
46
+ precision = 'float64'
47
+ dtype = torch.float64
48
+
49
+ config.torch_dtype = precision
50
+ print(f"Creating model with config:\n{config}")
51
+ model = AutoModelForCausalLM.from_config(config).to(dtype=dtype)
52
+
53
+ print(model)
54
+ model_dict = model.state_dict()
55
+ model_names = [n for n in model_dict]
56
+
57
+ # these parameters may be present in pth file but are never used:
58
+ unused_names = ['blocks.0.att.v0', 'blocks.0.att.v1', 'blocks.0.att.v2']
59
+ # these parameters may or may not be present in pth file:
60
+ possible_absent_weights = [
61
+ 'model.layers.0.pre_norm.weight', 'model.layers.0.pre_norm.bias',
62
+ ]
63
+ # other parameters may raise a KeyError
64
+
65
+ def translate_into_fla(name):
66
+ transposed = False
67
+ emb_head = {
68
+ 'emb.weight': 'model.embeddings.weight',
69
+ 'ln_out.weight': 'model.norm.weight',
70
+ 'ln_out.bias': 'model.norm.bias',
71
+ 'head.weight': 'lm_head.weight',
72
+ }
73
+ proj = {
74
+ 'receptance': 'r_proj',
75
+ 'key': 'k_proj',
76
+ 'value': 'v_proj',
77
+ 'ln_x': 'g_norm',
78
+ 'output': 'o_proj',
79
+ }
80
+ if name in unused_names:
81
+ return '', False
82
+ if name in emb_head:
83
+ return emb_head[name], False
84
+ name_compo = name.split('.')
85
+ assert name_compo[0] == 'blocks'
86
+ name_compo[0] = 'model.layers'
87
+ assert int(name_compo[1]) in range(config.num_hidden_layers)
88
+ name_compo[2] = {
89
+ 'att': 'attn',
90
+ 'ffn': 'ffn',
91
+ 'ln0': 'pre_norm',
92
+ 'ln1': 'attn_norm',
93
+ 'ln2': 'ffn_norm',
94
+ }[name_compo[2]]
95
+ if re.match("[wvag][012]", name_compo[3]):
96
+ typ, num = name_compo[3]
97
+ name_compo[3] = f'{typ}_lora.lora.' + {
98
+ '0': '2.bias',
99
+ '1': '0.weight',
100
+ '2': '2.weight',
101
+ }[num]
102
+ transposed |= (num in ['1', '2'])
103
+ elif name_compo[2] == 'attn' and name_compo[3] in proj:
104
+ name_compo[3] = proj[name_compo[3]]
105
+ return '.'.join(name_compo), transposed
106
+
107
+ for name in weights:
108
+ fla_name, transposed = translate_into_fla(name)
109
+ print(f'{name:32} -> {fla_name:42}, {transposed}')
110
+ if not fla_name:
111
+ print('redundant parameters in source weight: ', name, '\n')
112
+ continue
113
+ weight = weights[name]
114
+ # print shape information
115
+ shape1 = list(weight.shape)
116
+ shape2 = list(model_dict[fla_name].shape)
117
+ print(f'{str(shape1):32} {str(shape2)}\n')
118
+
119
+ if transposed:
120
+ weight.t_()
121
+ if shape1 == [1, 1, config.hidden_size]:
122
+ weight.squeeze_()
123
+
124
+ if "attn.x_" in fla_name:
125
+ assert model_dict[fla_name].shape[2:] == weight.shape, \
126
+ f"Shape mismatch for {fla_name}: model_dict={model_dict[fla_name].shape}, weight={weight.shape}"
127
+ else:
128
+ assert model_dict[fla_name].shape == weight.shape, \
129
+ f"Shape mismatch for {fla_name}: model_dict={model_dict[fla_name].shape}, weight={weight.shape}"
130
+
131
+ model_dict[fla_name].data.copy_(weight)
132
+ model_names.remove(fla_name)
133
+
134
+ print("uninitialized parameters: ", model_names)
135
+ for n in model_names:
136
+ if n not in possible_absent_weights:
137
+ raise KeyError(n)
138
+
139
+ os.makedirs(output, exist_ok=True)
140
+
141
+ model.save_pretrained(output, max_shard_size="1000GB")
142
+
143
+
144
+ if __name__ == '__main__':
145
+ parser = argparse.ArgumentParser(description='Convert RWKV7')
146
+ parser.add_argument('--rwkv7', type=str, help='Path to the input model')
147
+ parser.add_argument('--output', type=str, help='Directory to save model')
148
+ parser.add_argument('--precision', type=str, default='float32')
149
+ args = parser.parse_args()
150
+ convert(args.rwkv7, args.output, precision=args.precision)
code/inference/README.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Inference Recipes
2
+
3
+ Bash-level inference scripts mirroring `train/` β€” one script per memory row, all calling `inference/unified_inference.py`.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ export WAN_BASE_MODEL=/path/to/Wan2.1-T2V-1.3B
9
+
10
+ # Single memory type
11
+ CKPT=./ckpts/context_k1/epoch-0.safetensors \
12
+ bash inference/memory_baselines_basic/run_infer_context_k1.sh
13
+
14
+ # With custom prompt and context image
15
+ CKPT=./ckpts/context_k1/epoch-0.safetensors \
16
+ PROMPT="A toy bear on a table" \
17
+ CONTEXT_IMAGE=assets/opendomain_revisit/1774363417.png \
18
+ bash inference/memory_baselines_basic/run_infer_context_k1.sh
19
+
20
+ # All memory baselines (needs CKPT_DIR with per-row folders)
21
+ CKPT_DIR=./ckpts bash inference/memory_baselines_basic/run_infer_all.sh
22
+
23
+ # Dynamic SpatialVID row
24
+ CKPT=/path/to/retrained_dynamic_spatial_mem/epoch-0.safetensors \
25
+ bash inference/dynamic_spatialvid/run_infer_dyn_spatial_mem.sh
26
+ ```
27
+
28
+ ## Environment Variables
29
+
30
+ | Variable | Default | Description |
31
+ |---|---|---|
32
+ | `CKPT` | (required) | Path to `.safetensors` checkpoint |
33
+ | `WAN_BASE_MODEL` | (required) | Wan 2.1 base model directory |
34
+ | `PROMPT` | Generic game scene prompt | Text prompt |
35
+ | `CONTEXT_IMAGE` | (none) | First-frame context image path |
36
+ | `ACTION_PATH` | `env/action_rotation_left_45.json` | Camera trajectory JSON |
37
+ | `SEED` | `0` | Random seed |
38
+ | `HEIGHT` / `WIDTH` | `352` / `640` | Resolution |
39
+ | `NUM_FRAMES` | `81` | Frames per chunk |
40
+ | `NUM_INFERENCE_STEPS` | `50` | Denoising steps |
41
+ | `SIGMA_SHIFT` | `15.0` (memory baselines) / `5.0` (context learning) | Timestep shift |
42
+ | `INFER_OUTPUT_ROOT` | `inference_outputs/` | Output directory |
43
+
44
+ ## Script Mapping
45
+
46
+ ### Memory Baselines (`inference/memory_baselines_basic/`)
47
+
48
+ | Inference script | `--memory_type` | Training script |
49
+ |---|---|---|
50
+ | `run_infer_no_memory.sh` | `no_memory` | `run_ablation_no_memory_baseline_two_chunk.sh` |
51
+ | `run_infer_framepack_weight.sh` | `framepack_weight` | `run_ablation_framepack_weight_two_chunk.sh` |
52
+ | `run_infer_framepack_len_r2.sh` | `framepack_len_r2` | `run_ablation_framepack_len_r2_two_chunk.sh` |
53
+ | `run_infer_framepack_len_r4.sh` | `framepack_len_r4` | `run_ablation_framepack_len_r4_two_chunk.sh` |
54
+ | `run_infer_framepack_hybrid_r2.sh` | `framepack_hybrid_r2` | `run_ablation_framepack_hybrid_r2_weight_two_chunk.sh` |
55
+ | `run_infer_framepack_hybrid_r4.sh` | `framepack_hybrid_r4` | `run_ablation_framepack_hybrid_r4_weight_two_chunk.sh` |
56
+ | `run_infer_spatial_mem.sh` | `spatial_mem` | `run_spatial_memory_baseline.sh` |
57
+ | `run_infer_spatial_concat_text.sh` | `spatial_concat_text` | `run_ablation_spatial_concat_text_two_chunk.sh` |
58
+ | `run_infer_spatial_inject_none.sh` | `spatial_inject_none` | `run_ablation_spatial_inject_none_two_chunk.sh` |
59
+ | `run_infer_spatial_cross_attn_readout.sh` | `spatial_cross_attn_readout` | `run_ablation_spatial_cross_attn_readout_two_chunk.sh` |
60
+ | `run_infer_videossm_hybrid.sh` | `videossm_hybrid` | `run_videossm_hybrid_baseline.sh` |
61
+ | `run_infer_block_wise_ssm.sh` | `block_wise_ssm` | `run_ablation_block_wise_ssm_two_chunk.sh` |
62
+
63
+ ### Context Learning (`inference/context_learning/`)
64
+
65
+ | Inference script | `--memory_type` | Training script |
66
+ |---|---|---|
67
+ | `run_infer_ctx1.sh` | `context_k1` | `run_pre_qkv_ctx1.sh` |
68
+ | `run_infer_ctx5.sh` | `context_k5` | `run_pre_qkv_ctx5.sh` |
69
+ | `run_infer_ctx20.sh` | `context_k20` | `run_pre_qkv_ctx20.sh` |
70
+
71
+ ### Dynamic SpatialVID (`inference/dynamic_spatialvid/`)
72
+
73
+ Dynamic wrappers mirror the six dynamic training rows in `train/dynamic_spatialvid/`. They are intended for qualitative replay and demo generation; dynamic evaluation scripts are TODO.
code/inference/_shared/common_env_infer.sh ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Shared inference environment β€” mirrors train/_shared/common_env_memory.sh
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")" && pwd)"
6
+ REPO_ROOT="${REPO_ROOT:-$(cd "${SCRIPT_DIR}/../.." && pwd)}"
7
+ export PYTHONPATH="${REPO_ROOT}:${PYTHONPATH:-}"
8
+
9
+ # ── Base model ────────────────────────────────────────────────────────
10
+ WAN_BASE_MODEL="${WAN_BASE_MODEL:-}"
11
+ if [ -z "${WAN_BASE_MODEL}" ]; then
12
+ for _d in \
13
+ "${REPO_ROOT}/checkpoints/Wan2.1-T2V-1.3B" \
14
+ "${REPO_ROOT}/models/Wan2.1-T2V-1.3B" \
15
+ "${REPO_ROOT}/Wan2.1-T2V-1.3B"; do
16
+ [ -d "${_d}" ] && { WAN_BASE_MODEL="${_d}"; break; }
17
+ done
18
+ fi
19
+ if [ -z "${WAN_BASE_MODEL}" ]; then
20
+ echo "[common_env_infer] ERROR: WAN_BASE_MODEL not set." >&2
21
+ echo "[common_env_infer] HINT: export WAN_BASE_MODEL=/path/to/Wan2.1-T2V-1.3B" >&2
22
+ exit 2
23
+ fi
24
+ for _f in diffusion_pytorch_model.safetensors models_t5_umt5-xxl-enc-bf16.pth Wan2.1_VAE.pth; do
25
+ [ -f "${WAN_BASE_MODEL}/${_f}" ] || {
26
+ echo "[common_env_infer] ERROR: missing ${WAN_BASE_MODEL}/${_f}" >&2; exit 2; }
27
+ done
28
+ TOKENIZER_PATH="${TOKENIZER_PATH:-}"
29
+ if [ -z "${TOKENIZER_PATH}" ] && [ -d "${WAN_BASE_MODEL}/google/umt5-xxl" ]; then
30
+ TOKENIZER_PATH="${WAN_BASE_MODEL}/google/umt5-xxl"
31
+ fi
32
+ export WAN_BASE_MODEL
33
+ export TOKENIZER_PATH
34
+
35
+ # ── Checkpoint ────────────────────────────────────────────────────────
36
+ # CKPT must be set by the caller or the wrapper script.
37
+ # Inference scripts validate this themselves.
38
+
39
+ # ── Output ────────────────────────────────────────────────────────────
40
+ INFER_OUTPUT_ROOT="${INFER_OUTPUT_ROOT:-${REPO_ROOT}/inference_outputs}"
41
+ mkdir -p "${INFER_OUTPUT_ROOT}"
42
+ export INFER_OUTPUT_ROOT
43
+
44
+ # ── Defaults ──────────────────────────────────────────────────────────
45
+ PROMPT="${PROMPT:-A game scene, the camera moves through the environment}"
46
+ CONTEXT_IMAGE="${CONTEXT_IMAGE:-}"
47
+ ACTION_PATH="${ACTION_PATH:-${REPO_ROOT}/env/action_rotation_left_45.json}"
48
+ SEED="${SEED:-0}"
49
+ HEIGHT="${HEIGHT:-352}"
50
+ WIDTH="${WIDTH:-640}"
51
+ NUM_FRAMES="${NUM_FRAMES:-81}"
52
+ NUM_INFERENCE_STEPS="${NUM_INFERENCE_STEPS:-50}"
53
+ SIGMA_SHIFT="${SIGMA_SHIFT:-15.0}"
54
+ CFG_SCALE="${CFG_SCALE:-5.0}"
55
+ FPS="${FPS:-15}"
56
+
57
+ echo "[common_env_infer] WAN_BASE_MODEL=${WAN_BASE_MODEL}"
58
+ echo "[common_env_infer] INFER_OUTPUT_ROOT=${INFER_OUTPUT_ROOT}"
59
+ cd "${REPO_ROOT}"
code/inference/context_learning/common_env.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Common inference wrapper for context_learning.
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ SIGMA_SHIFT="${SIGMA_SHIFT:-5.0}"
5
+ source "${SCRIPT_DIR}/../_shared/common_env_infer.sh"
code/inference/context_learning/run_infer_ctx1.sh ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Inference: Context K=1
3
+ # Corresponds to: train/context_learning/run_pre_qkv_ctx1.sh
4
+ set -euo pipefail
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "${SCRIPT_DIR}/common_env.sh"
7
+
8
+ : "${CKPT:?ERROR: set CKPT to your context_k1 checkpoint path}"
9
+ OUTPUT="${INFER_OUTPUT_ROOT}/context_k1_$(date +%Y%m%d_%H%M%S).mp4"
10
+
11
+ EXTRA_ARGS=()
12
+ if [ -n "${CONTEXT_IMAGE}" ]; then
13
+ EXTRA_ARGS+=(--context_image "${CONTEXT_IMAGE}")
14
+ fi
15
+ if [ -n "${ACTION_PATH}" ]; then
16
+ EXTRA_ARGS+=(--action_path "${ACTION_PATH}")
17
+ fi
18
+
19
+ python inference/unified_inference.py \
20
+ --ckpt "${CKPT}" \
21
+ --memory_type context_k1 \
22
+ --base_model "${WAN_BASE_MODEL}" \
23
+ --prompt "${PROMPT}" \
24
+ "${EXTRA_ARGS[@]}" \
25
+ --height "${HEIGHT}" --width "${WIDTH}" --num_frames "${NUM_FRAMES}" \
26
+ --seed "${SEED}" --num_inference_steps "${NUM_INFERENCE_STEPS}" \
27
+ --sigma_shift "${SIGMA_SHIFT}" --cfg_scale "${CFG_SCALE}" --fps "${FPS}" \
28
+ --output_path "${OUTPUT}"