Add Echo-Memory codebase used for this run (CC BY 4.0, JD Echo Team) (part 4)
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- code/flash-linear-attention/tests/models/test_modeling_rwkv7.py +57 -0
- code/flash-linear-attention/tests/models/test_modeling_samba.py +56 -0
- code/flash-linear-attention/tests/models/test_modeling_transformer.py +56 -0
- code/flash-linear-attention/tests/models/test_modeling_utils.py +84 -0
- code/flash-linear-attention/tests/modules/test_activation.py +133 -0
- code/flash-linear-attention/tests/modules/test_conv.py +713 -0
- code/flash-linear-attention/tests/modules/test_cross_entropy.py +81 -0
- code/flash-linear-attention/tests/modules/test_grpo.py +56 -0
- code/flash-linear-attention/tests/modules/test_kl_div.py +44 -0
- code/flash-linear-attention/tests/modules/test_l2norm.py +37 -0
- code/flash-linear-attention/tests/modules/test_l2warp.py +70 -0
- code/flash-linear-attention/tests/modules/test_layernorm.py +217 -0
- code/flash-linear-attention/tests/modules/test_layernorm_gated.py +92 -0
- code/flash-linear-attention/tests/modules/test_rotary.py +122 -0
- code/flash-linear-attention/tests/modules/test_token_shift.py +133 -0
- code/flash-linear-attention/tests/ops/test_attn.py +125 -0
- code/flash-linear-attention/tests/ops/test_based.py +63 -0
- code/flash-linear-attention/tests/ops/test_comba.py +368 -0
- code/flash-linear-attention/tests/ops/test_delta.py +152 -0
- code/flash-linear-attention/tests/ops/test_delta_product.py +186 -0
- code/flash-linear-attention/tests/ops/test_deltaformer.py +130 -0
- code/flash-linear-attention/tests/ops/test_dplr_delta.py +432 -0
- code/flash-linear-attention/tests/ops/test_forgetting_attn.py +151 -0
- code/flash-linear-attention/tests/ops/test_gated_delta.py +353 -0
- code/flash-linear-attention/tests/ops/test_gated_delta_product.py +210 -0
- code/flash-linear-attention/tests/ops/test_gla.py +325 -0
- code/flash-linear-attention/tests/ops/test_gsa.py +432 -0
- code/flash-linear-attention/tests/ops/test_hgrn.py +164 -0
- code/flash-linear-attention/tests/ops/test_iplr_delta.py +235 -0
- code/flash-linear-attention/tests/ops/test_kda.py +379 -0
- code/flash-linear-attention/tests/ops/test_linear_attn.py +189 -0
- code/flash-linear-attention/tests/ops/test_log_linear_attn.py +148 -0
- code/flash-linear-attention/tests/ops/test_mesa.py +276 -0
- code/flash-linear-attention/tests/ops/test_nsa.py +148 -0
- code/flash-linear-attention/tests/ops/test_path_attn.py +211 -0
- code/flash-linear-attention/tests/ops/test_retention.py +308 -0
- code/flash-linear-attention/tests/ops/test_rwkv6.py +192 -0
- code/flash-linear-attention/tests/ops/test_rwkv7.py +300 -0
- code/flash-linear-attention/tests/ops/test_simple_gla.py +670 -0
- code/flash-linear-attention/tests/ops/test_solve_tril.py +91 -0
- code/flash-linear-attention/tests/ops/test_titans.py +122 -0
- code/flash-linear-attention/tests/ops/test_ttt.py +268 -0
- code/flash-linear-attention/tests/ops/test_utils.py +410 -0
- code/flash-linear-attention/utils/convert_from_llama.py +189 -0
- code/flash-linear-attention/utils/convert_from_rwkv6.py +213 -0
- code/flash-linear-attention/utils/convert_from_rwkv7.py +150 -0
- code/inference/README.md +73 -0
- code/inference/_shared/common_env_infer.sh +59 -0
- code/inference/context_learning/common_env.sh +5 -0
- 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}"
|