diff --git a/code/flash-linear-attention/fla/models/sse/modeling_sse.py b/code/flash-linear-attention/fla/models/sse/modeling_sse.py new file mode 100644 index 0000000000000000000000000000000000000000..84c1a20e639eb900f636de27db64b06f058ed0dd --- /dev/null +++ b/code/flash-linear-attention/fla/models/sse/modeling_sse.py @@ -0,0 +1,437 @@ + +from __future__ import annotations + +import math +import warnings +from dataclasses import dataclass +from typing import TYPE_CHECKING, Optional, Tuple + +import torch +import torch.nn as nn +from transformers.modeling_outputs import BaseModelOutputWithPast, MoeCausalLMOutputWithPast +from transformers.modeling_utils import PreTrainedModel +from transformers.utils import logging +from transformers.utils.deprecation import deprecate_kwarg + +from fla.layers.attn import Attention +from fla.layers.sse import SSEGLA, SSEGDN +from fla.models.sse.configuration_sse import SSEConfig +from fla.models.utils import Cache, FLAGenerationMixin +from fla.modules import FusedCrossEntropyLoss, FusedLinearCrossEntropyLoss, RMSNorm +from fla.modules import GatedMLP as SSEMLP +from fla.modules.l2warp import l2_warp + +if TYPE_CHECKING: + from transformers.processing_utils import Unpack + + +try: + from transformers.modeling_layers import GradientCheckpointingLayer +except ImportError: + from fla.models.modeling_layers import GradientCheckpointingLayer + +logger = logging.get_logger(__name__) + + +class SSEBlock(GradientCheckpointingLayer): + + def __init__(self, config: SSEConfig, layer_idx: int): + super().__init__() + + self.config = config + self.layer_idx = layer_idx + + self.attn_norm = (RMSNorm if config.fuse_norm else nn.RMSNorm)(config.hidden_size, eps=config.norm_eps) + if config.attn is not None and layer_idx in config.attn['layers']: + self.attn = Attention( + hidden_size=config.hidden_size, + num_heads=config.attn['num_heads'], + num_kv_heads=config.attn['num_kv_heads'], + qkv_bias=config.attn['qkv_bias'], + window_size=config.attn['window_size'], + rope_theta=config.attn['rope_theta'], + max_position_embeddings=config.max_position_embeddings, + layer_idx=layer_idx, + ) + elif config.linear_attn_type == "gla": + self.attn = SSEGLA( + mode=config.attn_mode, + hidden_size=config.hidden_size, + expand_v=config.expand_v, + head_dim=config.head_dim, + num_heads=config.num_heads, + num_v_heads=config.num_v_heads, + use_output_gate=config.use_output_gate, + use_short_conv=config.use_short_conv, + conv_size=config.conv_size, + num_sparse_partition=config.num_sparse_partition, + num_writer=config.num_writer, + num_reader=config.num_reader, + sse_implementation=config.sse_implementation, + norm_eps=config.norm_eps, + layer_idx=layer_idx, + ) + elif config.linear_attn_type == "gdn": + self.attn = SSEGDN( + mode=config.attn_mode, + hidden_size=config.hidden_size, + expand_v=config.expand_v, + head_dim=config.head_dim, + num_heads=config.num_heads, + num_v_heads=config.num_v_heads, + use_output_gate=config.use_output_gate, + use_short_conv=config.use_short_conv, + allow_neg_eigval=config.allow_neg_eigval, + conv_size=config.conv_size, + num_sparse_partition=config.num_sparse_partition, + num_writer=config.num_writer, + num_reader=config.num_reader, + sse_implementation=config.sse_implementation, + norm_eps=config.norm_eps, + layer_idx=layer_idx, + ) + else: + raise ValueError(f"Unknown linear attention type: {config.linear_attn_type}") + self.mlp_norm = (RMSNorm if config.fuse_norm else nn.RMSNorm)(config.hidden_size, eps=config.norm_eps) + self.mlp = SSEMLP( + hidden_size=config.hidden_size, + hidden_ratio=config.hidden_ratio, + intermediate_size=config.intermediate_size, + hidden_act=config.hidden_act, + fuse_swiglu=config.fuse_swiglu, + ) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor | None = None, + past_key_values: Cache | list[torch.FloatTensor] | None = None, + use_cache: bool | None = False, + output_attentions: bool | None = False, + **kwargs: Unpack[dict], + ) -> tuple[torch.FloatTensor, tuple[torch.FloatTensor, torch.FloatTensor] | None]: + residual = hidden_states + hidden_states = self.attn_norm(hidden_states) + hidden_states, attentions, past_key_values = self.attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + past_key_values=past_key_values, + use_cache=use_cache, + output_attentions=output_attentions, + **kwargs, + ) + if self.config.fuse_norm: + hidden_states, residual = self.mlp_norm(hidden_states, residual, True) + else: + hidden_states = residual + hidden_states + residual = hidden_states + hidden_states = self.mlp_norm(hidden_states) + hidden_states = self.mlp(hidden_states, **kwargs) + hidden_states = residual + hidden_states + + aux_loss = torch.zeros(()).to(hidden_states) + # Compatible with Attention output + if isinstance(attentions, tuple): + attentions, aux_loss = attentions + + outputs = (hidden_states, attentions, past_key_values, aux_loss) + + return outputs + + +class SSEPreTrainedModel(PreTrainedModel): + + config_class = SSEConfig + base_model_prefix = 'model' + supports_gradient_checkpointing = True + _no_split_modules = ['SSEBlock'] + _supports_cache_class = True + + def __init__(self, *inputs, **kwargs): + super().__init__(*inputs, **kwargs) + + def _init_weights( + self, + module: nn.Module, + prenorm_residual_strategy: str | None = None, + num_residuals_per_layer: int = 2, + ): + if isinstance(module, SSEGDN) and next(module.parameters()).device.type != 'meta': + with torch.no_grad(): + module.A_log.copy_(nn.init.uniform_(module.A_log, a=0, b=16).log()) + module.A_log._no_weight_decay = True + dt = torch.exp( + nn.init.uniform_(module.dt_bias) * (math.log(0.1) - math.log(0.001)) + math.log(0.001), + ).clamp(min=1e-4) + # Inverse of softplus: https://github.com/pytorch/pytorch/issues/72759 + inv_dt = dt + torch.log(-torch.expm1(-dt)) + module.dt_bias.copy_(inv_dt) + module.dt_bias._no_weight_decay = True + + elif isinstance(module, (nn.Linear, nn.Conv1d)): + # Slightly different from the TF version which uses truncated_normal for initialization + # cf https://github.com/pytorch/pytorch/pull/5617 + nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range) + if module.bias is not None: + nn.init.zeros_(module.bias) + elif isinstance(module, nn.Embedding): + nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range) + elif hasattr(module, 'reset_parameters'): + module.reset_parameters() + + if prenorm_residual_strategy is not None: + # Reinitialize selected weights subject to the OpenAI GPT-2 Paper Scheme: + # > A modified initialization which accounts for the accumulation on the residual path with model depth. Scale + # > the weights of residual layers at initialization by a factor of 1/√N where N is the # of residual layers. + # > -- GPT-2 :: https://openai.com/blog/better-language-models/ + # + # Reference (Megatron-LM): https://github.com/NVIDIA/Megatron-LM/blob/main/megatron/model/gpt_model.py + p = None + if hasattr(module, 'o_proj'): + p = module.o_proj.weight + elif hasattr(module, 'down_proj'): + p = module.down_proj.weight + if p is not None: + # Special Scaled Initialization --> There are 2 Layer Norms per Transformer Block + # Following Pytorch init, except scale by 1/sqrt(2 * n_layer) + # We need to reinit p since this code could be called multiple times + # Having just p *= scale would repeatedly scale it down + if prenorm_residual_strategy == 'rescale': + nn.init.kaiming_uniform_(p, a=math.sqrt(5)) + with torch.no_grad(): + p /= math.sqrt(num_residuals_per_layer * self.config.num_hidden_layers) + elif prenorm_residual_strategy == 'zero': + nn.init.zeros_(p) + else: + raise ValueError(f"Invalid prenorm_residual_strategy: {prenorm_residual_strategy}") + + +@dataclass +class MoeModelOutputWithPastAndAuxLosses(BaseModelOutputWithPast): + """ + Base class for model's outputs, with potential hidden states and attentions. + + Args: + aux_losses (`Optional[Tuple[torch.FloatTensor]]`, *optional*, returned when `labels` is provided): + aux_losses for the sparse modules. + """ + + aux_losses: Optional[Tuple[torch.FloatTensor]] = None + + +class SSEModel(SSEPreTrainedModel): + + def __init__(self, config: SSEConfig): + super().__init__(config) + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + + self.embeddings = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx) + self.layers = nn.ModuleList([SSEBlock(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]) + self.norm = (RMSNorm if config.fuse_norm else nn.RMSNorm)(config.hidden_size, eps=config.norm_eps) + + self.gradient_checkpointing = False + + self.post_init() + + def get_input_embeddings(self): + return self.embeddings + + def set_input_embeddings(self, value): + self.embeddings = value + + def forward( + self, + input_ids: torch.LongTensor | None = None, + attention_mask: Optional[torch.Tensor] = None, # noqa + inputs_embeds: torch.FloatTensor | None = None, + past_key_values: Cache | list[torch.FloatTensor] | None = None, + use_cache: bool | None = None, + output_attentions: bool | None = None, + output_hidden_states: bool | None = None, + return_dict: bool | None = None, + **kwargs: Unpack[dict], + ) -> tuple | BaseModelOutputWithPast: + if output_attentions: + warnings.warn("`SSEModel` does not `output_attentions` now, setting it to `False`.") + output_attentions = False + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + output_aux_losses = True + use_cache = use_cache if use_cache is not None else (self.config.use_cache if not self.training else False) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # retrieve input_ids and inputs_embeds + if input_ids is not None and inputs_embeds is not None: + raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") + if input_ids is None and inputs_embeds is None: + raise ValueError("You have to specify either input_ids or inputs_embeds") + + if inputs_embeds is None: + inputs_embeds = self.embeddings(input_ids) + hidden_states = inputs_embeds + + if use_cache and not isinstance(past_key_values, Cache): + past_key_values = Cache.from_legacy_cache(past_key_values) + + all_hidden_states = () if output_hidden_states else None + all_attns = () if output_attentions else None + all_aux_losses = () if output_aux_losses else None + for layer in self.layers: + if output_hidden_states: + all_hidden_states += (hidden_states,) + + hidden_states, attentions, past_key_values, aux_loss = layer( + hidden_states, + attention_mask=attention_mask, + past_key_values=past_key_values, + use_cache=use_cache, + output_attentions=output_attentions, + **kwargs, + ) + + if output_attentions: + all_attns += (attentions,) + + if output_aux_losses: + all_aux_losses += (aux_loss,) + + hidden_states = self.norm(hidden_states) + + # add hidden states from the last decoder layer + if output_hidden_states: + all_hidden_states += (hidden_states,) + + if not return_dict: + return tuple(i for i in [hidden_states, past_key_values, all_hidden_states, all_attns, all_aux_losses] if i is not None) + return MoeModelOutputWithPastAndAuxLosses( + last_hidden_state=hidden_states, + past_key_values=past_key_values, + hidden_states=all_hidden_states, + attentions=all_attns, + aux_losses=all_aux_losses, + ) + + +class SSEForCausalLM(SSEPreTrainedModel, FLAGenerationMixin): + + _tied_weights_keys = ["lm_head.weight"] + + def __init__(self, config): + super().__init__(config) + self.model = SSEModel(config) + self.vocab_size = config.vocab_size + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) + self.criterion = None + self.aux_loss_coef = config.aux_loss_coef + + # Initialize weights and apply final processing + self.post_init() + + def get_input_embeddings(self): + return self.model.embeddings + + def set_input_embeddings(self, value): + self.model.embeddings = value + + def get_output_embeddings(self): + return self.lm_head + + def set_output_embeddings(self, new_embeddings): + self.lm_head = new_embeddings + + def set_decoder(self, decoder): + self.model = decoder + + def get_decoder(self): + return self.model + + def generate(self, *args, **kwargs): + try: + return super().generate(*args, **kwargs) + except AttributeError as exception: + if 'past_key_values' in str(exception): + raise AttributeError( + f"You tried to call `generate` with a decoding strategy that manipulates `past_key_values`, " + f"which is not supported for {self.__class__.__name__}. " + f"Try another generation strategy instead. " + f"For the available generation strategies, check this doc: " + f"https://huggingface.co/docs/transformers/en/generation_strategies#decoding-strategies", + ) + else: + raise exception + + @deprecate_kwarg("num_logits_to_keep", version="4.50", new_name="logits_to_keep") + def forward( + self, + input_ids: torch.LongTensor = None, + attention_mask: torch.Tensor | None = None, + inputs_embeds: torch.Tensor | None = None, + past_key_values: Cache | list[torch.FloatTensor] | None = None, + labels: torch.LongTensor | None = None, + use_cache: bool | None = None, + output_attentions: bool | None = None, + output_hidden_states: bool | None = None, + return_dict: bool | None = None, + logits_to_keep: int | None = 0, + **kwargs: Unpack[dict], + ) -> tuple | MoeCausalLMOutputWithPast: + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + outputs = self.model( + input_ids=input_ids, + attention_mask=attention_mask, + inputs_embeds=inputs_embeds, + past_key_values=past_key_values, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + **kwargs, + ) + + hidden_states = outputs[0] + + loss, aux_loss, logits = None, None, None + if not self.config.fuse_linear_cross_entropy or labels is None: + logits = self.lm_head(hidden_states if logits_to_keep is None else hidden_states[:, -logits_to_keep:]) + if labels is not None: + if getattr(self, 'criterion', None) is None: + if self.config.fuse_linear_cross_entropy: + criterion = FusedLinearCrossEntropyLoss(use_l2warp=self.config.use_l2warp) + elif self.config.fuse_cross_entropy: + criterion = FusedCrossEntropyLoss(inplace_backward=True) + else: + criterion = nn.CrossEntropyLoss() + else: + criterion = self.criterion + labels = labels.to(hidden_states.device) + labels = torch.cat((labels[..., 1:], torch.full_like(labels[:, :1], criterion.ignore_index)), 1) + if self.config.fuse_linear_cross_entropy: + loss = criterion(hidden_states, labels, self.lm_head.weight, self.lm_head.bias) + else: + loss = criterion(logits.view(labels.numel(), -1), labels.view(-1)) + loss = l2_warp(loss, logits) if self.config.use_l2warp else loss + + aux_losses = outputs.aux_losses + compute_device = aux_losses[0].device + aux_loss = sum(layer_aux_loss.to(compute_device) for layer_aux_loss in aux_losses) + + loss += self.aux_loss_coef * aux_loss.to(loss.device) + + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return MoeCausalLMOutputWithPast( + loss=loss, + aux_loss=aux_loss, + logits=logits, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) diff --git a/code/flash-linear-attention/fla/models/transformer/__init__.py b/code/flash-linear-attention/fla/models/transformer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..71b99803e4c654b02f1efec6a39b4731b8dc525a --- /dev/null +++ b/code/flash-linear-attention/fla/models/transformer/__init__.py @@ -0,0 +1,12 @@ + +from transformers import AutoConfig, AutoModel, AutoModelForCausalLM + +from fla.models.transformer.configuration_transformer import TransformerConfig +from fla.models.transformer.modeling_transformer import TransformerForCausalLM, TransformerModel + +AutoConfig.register(TransformerConfig.model_type, TransformerConfig, exist_ok=True) +AutoModel.register(TransformerConfig, TransformerModel, exist_ok=True) +AutoModelForCausalLM.register(TransformerConfig, TransformerForCausalLM, exist_ok=True) + + +__all__ = ['TransformerConfig', 'TransformerForCausalLM', 'TransformerModel'] diff --git a/code/flash-linear-attention/fla/models/transformer/configuration_transformer.py b/code/flash-linear-attention/fla/models/transformer/configuration_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..2c58acf195d6a15ed31e8a3e0cdac5c8972cdb74 --- /dev/null +++ b/code/flash-linear-attention/fla/models/transformer/configuration_transformer.py @@ -0,0 +1,85 @@ + +import warnings + +from transformers.configuration_utils import PretrainedConfig + + +class TransformerConfig(PretrainedConfig): + + model_type = 'transformer' + keys_to_ignore_at_inference = ['past_key_values'] + + def __init__( + self, + hidden_size: int = 2048, + num_hidden_layers: int = 24, + num_heads: int = 32, + num_kv_heads: int | None = None, + qkv_bias: bool = False, + qk_norm: bool = False, + window_size: int | None = None, + rope_theta: float | None = 10000., + max_position_embeddings: int = 2048, + hidden_ratio: int | None = 4, + intermediate_size: int | None = None, + hidden_act: str = "swish", + initializer_range: float = 0.02, + elementwise_affine: bool | None = True, + norm_eps: float = 1e-6, + use_cache: bool = True, + pad_token_id: int | None = None, + bos_token_id: int = 1, + eos_token_id: int = 2, + tie_word_embeddings: bool = False, + fuse_norm: bool = True, + fuse_swiglu: bool = True, + fuse_cross_entropy: bool = True, + fuse_linear_cross_entropy: bool = False, + use_l2warp: bool = False, + vocab_size: int = 32000, + **kwargs, + ): + self.hidden_size = hidden_size + self.num_hidden_layers = num_hidden_layers + self.num_heads = num_heads + self.num_kv_heads = num_kv_heads + self.qkv_bias = qkv_bias + self.qk_norm = qk_norm + self.window_size = window_size + self.rope_theta = rope_theta + self.max_position_embeddings = max_position_embeddings + + self.hidden_ratio = hidden_ratio + self.intermediate_size = intermediate_size + self.hidden_act = hidden_act + + self.initializer_range = initializer_range + self.elementwise_affine = elementwise_affine + self.norm_eps = norm_eps + self.use_cache = use_cache + + self.fuse_norm = fuse_norm + self.fuse_swiglu = fuse_swiglu + self.fuse_cross_entropy = fuse_cross_entropy + self.fuse_linear_cross_entropy = fuse_linear_cross_entropy + self.use_l2warp = use_l2warp + self.vocab_size = vocab_size + + if fuse_cross_entropy and fuse_linear_cross_entropy: + raise ValueError( + "`fuse_cross_entropy` and `fuse_linear_cross_entropy` cannot be True at the same time.", + ) + if fuse_linear_cross_entropy: + warnings.warn( + "`fuse_linear_cross_entropy` is enabled, which can improves memory efficiency " + "at the potential cost of reduced precision. " + "If you observe issues like loss divergence, consider disabling this setting.", + ) + + super().__init__( + pad_token_id=pad_token_id, + bos_token_id=bos_token_id, + eos_token_id=eos_token_id, + tie_word_embeddings=tie_word_embeddings, + **kwargs, + ) diff --git a/code/flash-linear-attention/fla/models/transformer/modeling_transformer.py b/code/flash-linear-attention/fla/models/transformer/modeling_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..e501f8dac945b65a54a5765f646e5e09d70c3ffd --- /dev/null +++ b/code/flash-linear-attention/fla/models/transformer/modeling_transformer.py @@ -0,0 +1,356 @@ + +from __future__ import annotations + +import math +import warnings +from typing import TYPE_CHECKING, Any + +import torch +import torch.nn as nn +from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast +from transformers.modeling_utils import PreTrainedModel +from transformers.utils import logging +from transformers.utils.deprecation import deprecate_kwarg + +from fla.layers.attn import Attention +from fla.models.transformer.configuration_transformer import TransformerConfig +from fla.models.utils import Cache, FLAGenerationMixin +from fla.modules import FusedCrossEntropyLoss, FusedLinearCrossEntropyLoss, RMSNorm +from fla.modules import GatedMLP as TransformerMLP +from fla.modules.l2warp import l2_warp + +if TYPE_CHECKING: + from transformers.processing_utils import Unpack + + +try: + from transformers.modeling_layers import GradientCheckpointingLayer +except ImportError: + from fla.models.modeling_layers import GradientCheckpointingLayer + +logger = logging.get_logger(__name__) + + +class TransformerBlock(GradientCheckpointingLayer): + + def __init__(self, config: TransformerConfig, layer_idx: int): + super().__init__() + + self.config = config + self.layer_idx = layer_idx + + self.attn_norm = (RMSNorm if config.fuse_norm else nn.RMSNorm)(config.hidden_size, eps=config.norm_eps) + self.attn = Attention( + hidden_size=config.hidden_size, + num_heads=config.num_heads, + num_kv_heads=config.num_kv_heads, + qkv_bias=config.qkv_bias, + qk_norm=config.qk_norm, + window_size=config.window_size, + rope_theta=config.rope_theta, + max_position_embeddings=config.max_position_embeddings, + layer_idx=layer_idx, + ) + + self.mlp_norm = (RMSNorm if config.fuse_norm else nn.RMSNorm)(config.hidden_size, eps=config.norm_eps) + self.mlp = TransformerMLP( + hidden_size=config.hidden_size, + hidden_ratio=config.hidden_ratio, + intermediate_size=config.intermediate_size, + hidden_act=config.hidden_act, + fuse_swiglu=config.fuse_swiglu, + ) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor | None = None, + past_key_values: tuple[torch.Tensor] | None = None, + output_attentions: bool | None = False, + use_cache: bool | None = False, + **kwargs: Unpack[Any], + ) -> tuple[torch.FloatTensor, tuple[torch.FloatTensor, torch.FloatTensor] | None]: + + residual = hidden_states + hidden_states = self.attn_norm(hidden_states) + hidden_states, attentions, past_key_values = self.attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + past_key_values=past_key_values, + use_cache=use_cache, + output_attentions=output_attentions, + **kwargs, + ) + if self.config.fuse_norm: + hidden_states, residual = self.mlp_norm(hidden_states, residual, True) + else: + hidden_states = residual + hidden_states + residual = hidden_states + hidden_states = self.mlp_norm(hidden_states) + hidden_states = self.mlp(hidden_states, **kwargs) + hidden_states = residual + hidden_states + + outputs = (hidden_states,) + + if output_attentions: + outputs += (attentions,) + + if use_cache: + outputs += (past_key_values,) + + return outputs + + +class TransformerPreTrainedModel(PreTrainedModel): + + config_class = TransformerConfig + base_model_prefix = 'model' + supports_gradient_checkpointing = True + _no_split_modules = ['TransformerBlock'] + _supports_cache_class = True + + def __init__(self, *inputs, **kwargs): + super().__init__(*inputs, **kwargs) + + def _init_weights( + self, + module: nn.Module, + rescale_prenorm_residual: bool = False, + num_residuals_per_layer: int = 2, + ): + if isinstance(module, (nn.Linear, nn.Conv1d)): + # Slightly different from the TF version which uses truncated_normal for initialization + # cf https://github.com/pytorch/pytorch/pull/5617 + nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range) + if module.bias is not None: + nn.init.zeros_(module.bias) + elif isinstance(module, nn.Embedding): + nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range) + elif hasattr(module, 'reset_parameters'): + module.reset_parameters() + + if rescale_prenorm_residual: + # Reinitialize selected weights subject to the OpenAI GPT-2 Paper Scheme: + # > A modified initialization which accounts for the accumulation on the residual path with model depth. Scale + # > the weights of residual layers at initialization by a factor of 1/√N where N is the # of residual layers. + # > -- GPT-2 :: https://openai.com/blog/better-language-models/ + # + # Reference (Megatron-LM): https://github.com/NVIDIA/Megatron-LM/blob/main/megatron/model/gpt_model.py + p = None + if hasattr(module, 'o_proj'): + p = module.o_proj.weight + elif hasattr(module, 'down_proj'): + p = module.down_proj.weight + if p is not None: + # Special Scaled Initialization --> There are 2 Layer Norms per Transformer Block + # Following Pytorch init, except scale by 1/sqrt(2 * n_layer) + # We need to reinit p since this code could be called multiple times + # Having just p *= scale would repeatedly scale it down + nn.init.kaiming_uniform_(p, a=math.sqrt(5)) + with torch.no_grad(): + p /= math.sqrt(num_residuals_per_layer * self.config.num_hidden_layers) + + +class TransformerModel(TransformerPreTrainedModel): + + def __init__( + self, + config: TransformerConfig, + ) -> TransformerModel: + super().__init__(config) + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + + self.embeddings = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx) + self.layers = nn.ModuleList([TransformerBlock(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]) + self.norm = (RMSNorm if config.fuse_norm else nn.RMSNorm)(config.hidden_size, eps=config.norm_eps) + + self.gradient_checkpointing = False + + self.post_init() + + def get_input_embeddings(self): + return self.embeddings + + def set_input_embeddings(self, value): + self.embeddings = value + + def forward( + self, + input_ids: torch.LongTensor | None = None, + attention_mask: torch.Tensor | None = None, + past_key_values: list[torch.FloatTensor] | None = None, + inputs_embeds: torch.FloatTensor | None = None, + use_cache: bool | None = None, + output_attentions: bool | None = None, + output_hidden_states: bool | None = None, + return_dict: bool | None = None, + **kwargs: Unpack[Any], + ) -> tuple | CausalLMOutputWithPast: + if output_attentions: + warnings.warn( + "`TransformerModel` does not support output attention weights now, so `output_attentions` is set to `False`.", + ) + output_attentions = False + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + use_cache = use_cache if use_cache is not None else (self.config.use_cache if not self.training else False) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # retrieve input_ids and inputs_embeds + if input_ids is not None and inputs_embeds is not None: + raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") + elif input_ids is None and inputs_embeds is None: + raise ValueError("You have to specify either input_ids or inputs_embeds") + + if use_cache and not isinstance(past_key_values, Cache): + past_key_values = Cache.from_legacy_cache(past_key_values) + + if inputs_embeds is None: + inputs_embeds = self.embeddings(input_ids) + + # embed positions + hidden_states = inputs_embeds + + all_hidden_states = () if output_hidden_states else None + all_attns = () if output_attentions else None + next_cache = None + + for layer in self.layers: + if output_hidden_states: + all_hidden_states += (hidden_states,) + + layer_outputs = layer( + hidden_states, + attention_mask=attention_mask, + past_key_values=past_key_values, + output_attentions=output_attentions, + use_cache=use_cache, + **kwargs, + ) + + hidden_states = layer_outputs[0] + + if use_cache: + next_cache = layer_outputs[2 if output_attentions else 1] + + if output_attentions: + all_attns += (layer_outputs[1],) + + hidden_states = self.norm(hidden_states) + + # add hidden states from the last decoder layer + if output_hidden_states: + all_hidden_states += (hidden_states,) + + if not return_dict: + return tuple(v for v in [hidden_states, next_cache, all_hidden_states, all_attns] if v is not None) + + return BaseModelOutputWithPast( + last_hidden_state=hidden_states, + past_key_values=next_cache, + hidden_states=all_hidden_states, + attentions=all_attns, + ) + + +class TransformerForCausalLM(TransformerPreTrainedModel, FLAGenerationMixin): + + _tied_weights_keys = ["lm_head.weight"] + + def __init__(self, config): + super().__init__(config) + self.model = TransformerModel(config) + self.vocab_size = config.vocab_size + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) + self.criterion = None + + # Initialize weights and apply final processing + self.post_init() + + def get_input_embeddings(self): + return self.model.embeddings + + def set_input_embeddings(self, value): + self.model.embeddings = value + + def get_output_embeddings(self): + return self.lm_head + + def set_output_embeddings(self, new_embeddings): + self.lm_head = new_embeddings + + def set_decoder(self, decoder): + self.model = decoder + + def get_decoder(self): + return self.model + + @deprecate_kwarg("num_logits_to_keep", version="4.50", new_name="logits_to_keep") + def forward( + self, + input_ids: torch.LongTensor = None, + attention_mask: torch.Tensor | None = None, + past_key_values: Cache | list[torch.FloatTensor] | None = None, + inputs_embeds: torch.FloatTensor | None = None, + labels: torch.LongTensor | None = None, + use_cache: bool | None = None, + output_attentions: bool | None = None, + output_hidden_states: bool | None = None, + return_dict: bool | None = None, + logits_to_keep: int | None = 0, + **kwargs: Unpack[Any], + ) -> tuple | CausalLMOutputWithPast: + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + outputs = self.model( + input_ids=input_ids, + attention_mask=attention_mask, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + **kwargs, + ) + + hidden_states = outputs[0] + + logits = None if self.config.fuse_linear_cross_entropy else self.lm_head(hidden_states[:, -logits_to_keep:]) + + loss = None + if labels is not None: + if getattr(self, 'criterion', None) is None: + if self.config.fuse_linear_cross_entropy: + criterion = FusedLinearCrossEntropyLoss(use_l2warp=self.config.use_l2warp) + elif self.config.fuse_cross_entropy: + criterion = FusedCrossEntropyLoss(inplace_backward=True) + else: + criterion = nn.CrossEntropyLoss() + else: + criterion = self.criterion + # Enable model parallelism + labels = labels.to(hidden_states.device) + labels = torch.cat((labels[..., 1:], torch.full_like(labels[:, :1], criterion.ignore_index)), 1) + if self.config.fuse_linear_cross_entropy: + loss = criterion(hidden_states, labels, self.lm_head.weight, self.lm_head.bias) + else: + loss = criterion(logits.view(labels.numel(), -1), labels.view(-1)) + loss = l2_warp(loss, logits) if self.config.use_l2warp else loss + + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithPast( + loss=loss, + logits=logits, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) diff --git a/code/flash-linear-attention/fla/models/utils.py b/code/flash-linear-attention/fla/models/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..a032beddbd27a9289ddf4764e819393627cc1c67 --- /dev/null +++ b/code/flash-linear-attention/fla/models/utils.py @@ -0,0 +1,471 @@ + +from __future__ import annotations + +import inspect +from typing import Any + +import torch +import transformers +from packaging import version +from transformers.cache_utils import Cache as HFCacheBase +from transformers.generation import GenerationMixin +from transformers.utils.deprecation import deprecate_kwarg + +_TF_VERSION = transformers.__version__ +_NEED_NEW = "4.53.3" +_IS_TRANSFORMERS_4_56_PLUS = version.parse(_TF_VERSION) >= version.parse("4.56.0") + +if version.parse(_TF_VERSION) > version.parse(_NEED_NEW): + from transformers.cache_utils import CacheLayerMixin +else: + CacheLayerMixin = object + + +class FLALayer(CacheLayerMixin): + is_compileable = True + is_sliding = False + + def __init__(self): + super().__init__() + self.state = None + + def lazy_initialization(self, key_states: torch.Tensor): + self.state = None + + def update( + self, + *, + recurrent_state: torch.Tensor | tuple[torch.Tensor, ...] | None = None, + attn_state: tuple[torch.Tensor, ...] | None = None, + conv_state: Any | None = None, + ffn_state: Any | None = None, + cache_kwargs: dict[str, Any] | None = None, + **_: Any, + ) -> dict[str, Any]: + if cache_kwargs is None: + cache_kwargs = {} + window_size = cache_kwargs.get("window_size") + + if attn_state is not None and not isinstance(attn_state, (tuple, list)): + raise ValueError("`attn_state` must be a tuple/list of tensors") + + if self.state is None: + self.state = { + "recurrent_state": None, + "attn_state": None, + "conv_state": None, + "ffn_state": None, + } + + if recurrent_state is not None: + self.state["recurrent_state"] = recurrent_state + + if attn_state is not None: + input_size = attn_state[0].shape[1] + if self.state["attn_state"] is None: + if window_size is not None and input_size > window_size: + attn_state = tuple(x[:, -window_size:].contiguous() for x in attn_state) + self.state["attn_state"] = tuple(attn_state) + else: + old = self.state["attn_state"] + if window_size is not None and old[0].shape[1] >= window_size: + new_tuple = [] + for old_x, new_x in zip(old, attn_state, strict=False): + rolled = old_x.roll(-input_size, dims=1) + tail = new_x[:, -window_size:] + rolled[:, -tail.shape[1]:] = tail + new_tuple.append(rolled) + self.state["attn_state"] = tuple(new_tuple) + else: + self.state["attn_state"] = tuple( + torch.cat([old_x, new_x], dim=1) for old_x, new_x in zip(old, attn_state, strict=False) + ) + + if conv_state is not None: + self.state["conv_state"] = conv_state + if ffn_state is not None: + self.state["ffn_state"] = ffn_state + + if not hasattr(self, 'device'): + self.device = 'cpu' + for state in (recurrent_state, attn_state, conv_state, ffn_state): + if state is not None: + self.device = state.device if isinstance(state, torch.Tensor) else state[0].device + break + + return self.state + + def get_seq_length(self, cache_position=None) -> int: + # we do not store seen_tokens here + return 0 + + def get_max_cache_shape(self) -> int: + return -1 + + def get_mask_sizes(self, cache_position: torch.Tensor) -> tuple[int, int]: + return 0, 0 + + def offload(self): + if self.state is None: + return + + def to_cpu(x): + return x.to("cpu", non_blocking=True) if isinstance(x, torch.Tensor) else x + for k in ("recurrent_state", "attn_state", "conv_state", "ffn_state"): + v = self.state.get(k, None) + if v is None: + continue + if isinstance(v, (tuple, list)): + self.state[k] = tuple(to_cpu(t) for t in v) + else: + self.state[k] = to_cpu(v) + + def prefetch(self): + if self.state is None: + return + + def to_dev(x): + return x.to(self.device, non_blocking=True) if isinstance(x, torch.Tensor) else x + for k in ("recurrent_state", "attn_state", "conv_state", "ffn_state"): + v = self.state.get(k, None) + if v is None: + continue + if isinstance(v, (tuple, list)): + self.state[k] = tuple(to_dev(t) for t in v) + else: + self.state[k] = to_dev(v) + + def reset(self): + pass + + +class LegacyFLACache(HFCacheBase): + """ + A cache used for storing hidden states produced by flash linear attention models. + + It stores the states of each layer as the tensor of shape `[batch_size, key_dim, value_dim]`. + """ + + is_compileable = True + + def __init__( + self, + seen_tokens: int = 0, + ) -> LegacyFLACache: + super().__init__() + + self.states: list[dict[str, Any]] = [] + + self._seen_tokens = seen_tokens # Used in `generate` to keep tally of how many tokens the cache has seen + + def __getitem__(self, layer_idx: int) -> dict[str, Any]: + if layer_idx < len(self): + return self.states[layer_idx] + else: + raise KeyError(f"Cache only has {len(self)} layers, attempted to access layer with index {layer_idx}") + + def __iter__(self): + yield from self.states + + def __len__(self): + return len(self.states) + + def update( + self, + recurrent_state: tuple[torch.Tensor] | None = None, + attn_state: tuple[torch.Tensor] | None = None, + conv_state: tuple[torch.Tensor] | None = None, + ffn_state: tuple[torch.Tensor] | None = None, + layer_idx: int = 0, + offset: int | None = 1, + cache_kwargs: dict[str, Any] | None = None, + ) -> dict[str, Any]: + """ + Args: + recurrent_state (`torch.Tensor`): + The new recurrent state to cache. + attn_state (`tuple[torch.Tensor]`): + The new attention key/value states to cache. + conv_state (`tuple[torch.Tensor]`): + The new convolution state to cache. + ffn_state (`tuple[torch.Tensor]`): + The new feed-forward state to cache. + layer_idx (`int`, defaults to 0): + The index of the layer to cache the states for. + offset (`int`, defaults to 1): + The number of new tokens being processed. + cache_kwargs (`Dict[str, Any]`): + Additional arguments for the cache subclass. + + Return: + Dictionary of the updated state. + """ + + if cache_kwargs is None: + cache_kwargs = {} + if attn_state is not None: + input_size = attn_state[0].shape[1] + window_size = cache_kwargs.get('window_size') + if not isinstance(attn_state, (tuple, list)): + raise ValueError("`attn_state` must be a tuple of tensors for key/value states") + if len(self.states) <= layer_idx: + # update the number of seen tokens + if layer_idx == 0: + self._seen_tokens += offset + if attn_state is not None: + if window_size is not None and input_size > window_size: + attn_state = [state[:, -window_size:].contiguous() for state in attn_state] + state = dict( + recurrent_state=recurrent_state, + attn_state=attn_state, + conv_state=conv_state, + ffn_state=ffn_state, + ) + self.states.append(state) + else: + # update the number of seen tokens + if layer_idx == len(self.states) - 1: + self._seen_tokens += offset + state = self.states[layer_idx] + if recurrent_state is not None: + state['recurrent_state'] = recurrent_state + if attn_state is not None: + if window_size is not None and state['attn_state'][0].shape[1] == window_size: + for i, (old_state, new_state) in enumerate(zip(state['attn_state'], attn_state, strict=False)): + # DO NOT allocate new memory if the cache is full + # roll the key/value states to the left by `input_size` + old_state = old_state.roll(-input_size, 1) + # replace the last `input_size` tokens with the new key/value states + old_state[:, -input_size:] = new_state + state['attn_state'][i] = old_state + else: + attn_state = [ + torch.cat([old_state, new_state], 1) + for old_state, new_state in zip(state['attn_state'], attn_state, strict=False) + ] + state['attn_state'] = attn_state + if conv_state is not None: + state['conv_state'] = conv_state + if ffn_state is not None: + state['ffn_state'] = ffn_state + + return state + + def get_seq_length(self, layer_idx: int | None = 0) -> int: + """Returns the sequence length of the cached states. A layer index can be optionally passed.""" + if len(self.states) <= layer_idx: + return 0 + return self._seen_tokens + + def get_max_cache_shape(self) -> int | None: + """Returns the maximum sequence length of the cached states. Cache does not have a maximum length.""" + return None + + def to_legacy_cache(self) -> tuple: + return tuple(self.states) + + @classmethod + @torch.compiler.disable + def from_legacy_cache( + cls, + past_key_values: tuple | None = None, + seen_tokens: int = 0, + ) -> LegacyFLACache: + """Converts a cache in the legacy cache format into an equivalent `Cache`.""" + + cache = cls(seen_tokens) + if isinstance(past_key_values, list): + for layer_idx in range(len(past_key_values)): + cache.states.append(past_key_values[layer_idx]) + return cache + + +class FLACache(HFCacheBase): + """ + A cache used for storing hidden states produced by flash linear attention models. + + It stores the states of each layer as the tensor of shape `[batch_size, key_dim, value_dim]`. + """ + + is_compileable = True + + def __init__(self, seen_tokens: int = 0, **kwargs): + parent_init = super().__init__ + sig = inspect.signature(parent_init) + param_names = list(sig.parameters.keys()) + + if 'layer_class_to_replicate' in param_names: + self.use_layer_class_to_replicate = True + super().__init__(layer_class_to_replicate=FLALayer, **kwargs) + elif 'layer_classes' in param_names: + self.use_layer_class_to_replicate = False + super().__init__(layer_classes=FLALayer, **kwargs) + else: + raise TypeError( + "FLA cache initialization failed: HFCacheBase.__init__ accepts neither " + "'layer_class_to_replicate' nor 'layer_classes'. This might be caused by an incompatible " + "transformers version. Please check your transformers>=4.36.0", + ) + self._seen_tokens = int(seen_tokens) + + def update( + self, + recurrent_state: tuple[torch.Tensor] | None = None, + attn_state: tuple[torch.Tensor] | None = None, + conv_state: tuple[torch.Tensor] | None = None, + ffn_state: tuple[torch.Tensor] | None = None, + layer_idx: int = 0, + offset: int | None = 1, + cache_kwargs: dict[str, Any] | None = None, + ) -> dict[str, Any]: + if not self.use_layer_class_to_replicate: + self.append_new_layers(layer_idx) + else: + while len(self.layers) <= layer_idx: + self.layers.append(self.layer_class_to_replicate()) + if layer_idx == 0: + self._seen_tokens += int(offset) + + return self.layers[layer_idx].update( + recurrent_state=recurrent_state, + attn_state=attn_state, + conv_state=conv_state, + ffn_state=ffn_state, + cache_kwargs=cache_kwargs, + ) + + def __getitem__(self, layer_idx: int) -> dict[str, Any]: + if layer_idx >= len(self.layers): + raise KeyError(f"Cache only have {len(self.layers)} layers, however accessed {layer_idx} out of bounds") + return self.layers[layer_idx].state + + def __iter__(self): + for i in range(len(self.layers)): + yield self[i] + + def __len__(self): + return super().__len__() + + def get_seq_length(self, layer_idx: int | None = 0, cache_position=None) -> int: + if len(self.layers) <= (layer_idx or 0): + return 0 + return self._seen_tokens + + def get_max_cache_shape(self, layer_idx: int = 0) -> int: + return -1 + + def get_mask_sizes(self, cache_position: torch.Tensor, layer_idx: int) -> tuple[int, int]: + # Respect your global seen_tokens semantics + # kv_length = past_seen + current_query_length + query_len = int(cache_position.shape[0]) if cache_position is not None else 0 + kv_length = int(self._seen_tokens) + query_len + return kv_length, 0 + + def to_legacy_cache(self) -> tuple[dict[str, Any], ...]: + return tuple(self[i] for i in range(len(self.layers))) + + @classmethod + @torch.compiler.disable + def from_legacy_cache( + cls, + past_key_values: tuple[dict[str, Any], ...] | None = None, + seen_tokens: int = 0, + **kwargs, + ) -> FLACache: + cache = cls(seen_tokens=seen_tokens, **kwargs) + if isinstance(past_key_values, (list, tuple)): + for i, st in enumerate(past_key_values): + while len(cache.layers) <= i: + cache.layers.append(cache.layer_class_to_replicate()) + cache.layers[i].state = dict(st) + return cache + + +class FLAGenerationMixin(GenerationMixin): + """ + Flash Linear Attention Generation Mixin that provides version-compatible generation methods. + This mixin handles transformers library version differences, particularly for prepare_inputs_for_generation. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @deprecate_kwarg("num_logits_to_keep", version="4.50", new_name="logits_to_keep") + def prepare_inputs_for_generation( + self, + input_ids: torch.LongTensor = None, + past_key_values: HFCacheBase | None = None, + attention_mask: torch.Tensor | None = None, + inputs_embeds: torch.Tensor | None = None, + use_cache: bool = True, + logits_to_keep: int | None = None, + cache_position: torch.LongTensor | None = None, + **kwargs, + ): + # Use pre-computed version comparison for performance + if _IS_TRANSFORMERS_4_56_PLUS: + # For transformers 4.56.0+, use cache_position-based logic + model_inputs = {} + + # Handle cache-dependent input preparation + if past_key_values is not None: + model_inputs["past_key_values"] = past_key_values + + # Use the new cache-dependent input preparation method if available + if hasattr(self, '_cache_dependant_input_preparation') and cache_position is not None: + inputs_embeds, input_ids = self._cache_dependant_input_preparation( + input_ids, inputs_embeds, cache_position, + ) + elif cache_position is not None: + # Fallback: manually slice using cache_position + if input_ids is not None and input_ids.shape[1] != cache_position.shape[0]: + input_ids = input_ids[:, cache_position] + elif hasattr(past_key_values, '__len__') and len(past_key_values) > 0: + # Ultimate fallback to old behavior + input_ids = input_ids[:, -1:] + + # Handle input format (similar to base class logic) + if inputs_embeds is not None and (cache_position is None or len(cache_position) == inputs_embeds.shape[1]): + model_inputs['inputs_embeds'] = inputs_embeds + model_inputs['input_ids'] = None + else: + model_inputs['input_ids'] = input_ids.contiguous() if input_ids is not None else None + model_inputs['inputs_embeds'] = None + + model_inputs['cache_position'] = cache_position + + else: + # For older transformers versions, use the original logic + model_inputs = {} + # only last token for `inputs_ids` if the `past_key_values` is not empty. + if past_key_values is not None and hasattr(past_key_values, '__len__') and len(past_key_values) > 0: + input_ids = input_ids[:, -1:] + # if `inputs_embeds` are passed, we only want to use them in the 1st generation step + if inputs_embeds is not None and hasattr(past_key_values, '__len__') and len(past_key_values) == 0: + model_inputs = {'inputs_embeds': inputs_embeds} + else: + # The `contiguous()` here is necessary to have a static stride during decoding. torchdynamo otherwise + # recompiles graphs as the stride of the inputs is a guard. + # Ref: https://github.com/huggingface/transformers/pull/29114 + # TODO: use `next_tokens` directly instead. + model_inputs = {'input_ids': input_ids.contiguous()} + + if logits_to_keep is not None: + model_inputs['logits_to_keep'] = logits_to_keep + + model_inputs.update({ + 'past_key_values': past_key_values, + 'use_cache': use_cache, + 'attention_mask': attention_mask, + }) + return model_inputs + + +if version.parse(_TF_VERSION) > version.parse(_NEED_NEW): + class Cache(FLACache): + def __init__(self, seen_tokens: int = 0, **kwargs: Any) -> None: + super().__init__(seen_tokens=seen_tokens, **kwargs) +else: + class Cache(LegacyFLACache): + def __init__(self, seen_tokens: int = 0, **kwargs: Any) -> None: + super().__init__(seen_tokens=seen_tokens) diff --git a/code/flash-linear-attention/fla/modules/__init__.py b/code/flash-linear-attention/fla/modules/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..04a738e01ab15c5e86f0e701677e116267ebc7d9 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/__init__.py @@ -0,0 +1,32 @@ + +from fla.modules.convolution import ImplicitLongConvolution, LongConvolution, ShortConvolution +from fla.modules.fused_bitlinear import BitLinear, FusedBitLinear +from fla.modules.fused_cross_entropy import FusedCrossEntropyLoss +from fla.modules.fused_kl_div import FusedKLDivLoss +from fla.modules.fused_linear_cross_entropy import FusedLinearCrossEntropyLoss +from fla.modules.fused_norm_gate import ( + FusedLayerNormGated, + FusedLayerNormSwishGate, + FusedLayerNormSwishGateLinear, + FusedRMSNormGated, + FusedRMSNormSwishGate, + FusedRMSNormSwishGateLinear, +) +from fla.modules.l2norm import L2Norm +from fla.modules.layernorm import GroupNorm, GroupNormLinear, LayerNorm, LayerNormLinear, RMSNorm, RMSNormLinear +from fla.modules.mlp import GatedMLP +from fla.modules.rotary import RotaryEmbedding +from fla.modules.token_shift import TokenShift + +__all__ = [ + 'ImplicitLongConvolution', 'LongConvolution', 'ShortConvolution', + 'BitLinear', 'FusedBitLinear', + 'FusedCrossEntropyLoss', 'FusedLinearCrossEntropyLoss', 'FusedKLDivLoss', + 'L2Norm', + 'GroupNorm', 'GroupNormLinear', 'LayerNorm', 'LayerNormLinear', 'RMSNorm', 'RMSNormLinear', + 'FusedLayerNormGated', 'FusedLayerNormSwishGate', 'FusedLayerNormSwishGateLinear', + 'FusedRMSNormGated', 'FusedRMSNormSwishGate', 'FusedRMSNormSwishGateLinear', + 'GatedMLP', + 'RotaryEmbedding', + 'TokenShift', +] diff --git a/code/flash-linear-attention/fla/modules/activations.py b/code/flash-linear-attention/fla/modules/activations.py new file mode 100644 index 0000000000000000000000000000000000000000..3553f0b20ee64a3b2c9fd003d2c77281f3910f47 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/activations.py @@ -0,0 +1,555 @@ +# Copyright (c) 2023-2025, Tri Dao, Yu Zhang, Songlin Yang. + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl + +from fla.ops.utils.op import exp, log +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard, is_amd + +try: + from torch.distributed.tensor import DTensor +except (ImportError, AttributeError): + DTensor = None + +NUM_WARPS_AUTOTUNE = [1, 2, 4, 8, 16] if is_amd else [1, 2, 4, 8, 16, 32] + + +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def sigmoid_fwd_kernel( + x, y, + T, + B: tl.constexpr, + D: tl.constexpr, +): + pid = tl.program_id(0) + offs = pid * B + tl.arange(0, B) + mask = offs < T + x_val = tl.load(x + offs, mask=mask, other=0.).to(tl.float32) + y_val = 1.0 / (1.0 + exp(-x_val)) + tl.store(y + offs, y_val.to(y.dtype.element_ty), mask=mask) + + +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def sigmoid_bwd_kernel( + x, dy, dx, + T, + B: tl.constexpr, + D: tl.constexpr, +): + pid = tl.program_id(0) + offs = pid * B + tl.arange(0, B) + mask = offs < T + x_val = tl.load(x + offs, mask=mask, other=0.).to(tl.float32) + g_val = tl.load(dy + offs, mask=mask, other=0.).to(tl.float32) + s = 1.0 / (1.0 + exp(-x_val)) + dx_val = g_val * s * (1.0 - s) + tl.store(dx + offs, dx_val.to(dx.dtype.element_ty), mask=mask) + + +def sigmoid_fwd(x: torch.Tensor) -> torch.Tensor: + T, D = x.numel(), x.shape[-1] + y = torch.empty_like(x) + sigmoid_fwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)](x, y, T=T, D=D) + return y + + +def sigmoid_bwd(x: torch.Tensor, dy: torch.Tensor) -> torch.Tensor: + T, D = x.numel(), x.shape[-1] + dx = torch.empty_like(x) + sigmoid_bwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)](x, dy, dx, T=T, D=D) + return dx + + +class SigmoidFunction(torch.autograd.Function): + + @staticmethod + def forward(ctx, x): + ctx.save_for_backward(x) + return sigmoid_fwd(x) + + @staticmethod + def backward(ctx, dout): + x, = ctx.saved_tensors + return sigmoid_bwd(x, dout) + + +sigmoid = SigmoidFunction.apply + + +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def logsigmoid_fwd_kernel( + x, + y, + temperature, + T, + B: tl.constexpr, + D: tl.constexpr, +): + i = tl.program_id(0) + o_i = i * B + tl.arange(0, B) + m_i = o_i < T + + b_x = tl.load(x + o_i, mask=m_i, other=0.).to(tl.float32) + b_m = tl.minimum(0., b_x) + b_z = 1. + exp(-tl.abs(b_x)) + b_y = (b_m - log(b_z)) / temperature + tl.store(y + o_i, b_y.to(y.dtype.element_ty), mask=m_i) + + +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def logsigmoid_bwd_kernel( + x, + dx, + dy, + temperature, + T, + B: tl.constexpr, + D: tl.constexpr, +): + i = tl.program_id(0) + o_i = i * B + tl.arange(0, B) + m_i = o_i < T + + b_x = tl.load(x + o_i, mask=m_i, other=0.).to(tl.float32) + b_dy = tl.load(dy + o_i, mask=m_i, other=0.).to(tl.float32) + b_dx = b_dy * ((1. - tl.sigmoid(b_x)) / temperature) + tl.store(dx + o_i, b_dx.to(dx.dtype.element_ty), mask=m_i) + + +def logsigmoid_fwd(x: torch.Tensor, temperature: float = 1.) -> torch.Tensor: + T, D = x.numel(), x.shape[-1] + y = torch.empty_like(x) + logsigmoid_fwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)]( + x=x, + y=y, + temperature=temperature, + T=T, + D=D, + ) + return y + + +def logsigmoid_bwd(x: torch.Tensor, dy: torch.Tensor, temperature: float = 1.) -> torch.Tensor: + T, D = x.numel(), x.shape[-1] + dx = torch.empty_like(x) + logsigmoid_bwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)]( + x=x, + dx=dx, + dy=dy, + temperature=temperature, + T=T, + D=D, + ) + return dx + + +class LogSigmoidFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward(ctx, x, temperature): + ctx.save_for_backward(x) + ctx.temperature = temperature + return logsigmoid_fwd(x, temperature) + + @staticmethod + @input_guard + def backward(ctx, dy): + x, = ctx.saved_tensors + return logsigmoid_bwd(x, dy, ctx.temperature), None + + +def logsigmoid(x: torch.Tensor, temperature: float = 1.) -> torch.Tensor: + return LogSigmoidFunction.apply(x, temperature) + + +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def swish_fwd_kernel( + x, y, + T, + B: tl.constexpr, + D: tl.constexpr, +): + pid = tl.program_id(0) + offs = pid * B + tl.arange(0, B) + mask = offs < T + x_val = tl.load(x + offs, mask=mask, other=0.).to(tl.float32) + s = 1.0 / (1.0 + exp(-x_val)) + y_val = x_val * s + tl.store(y + offs, y_val.to(y.dtype.element_ty), mask=mask) + + +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def swish_bwd_kernel( + x, dy, dx, + T, + B: tl.constexpr, + D: tl.constexpr, +): + pid = tl.program_id(0) + offs = pid * B + tl.arange(0, B) + mask = offs < T + x_val = tl.load(x + offs, mask=mask, other=0.).to(tl.float32) + g_val = tl.load(dy + offs, mask=mask, other=0.).to(tl.float32) + s = 1.0 / (1.0 + exp(-x_val)) + dx_val = g_val * s * (1.0 + x_val * (1.0 - s)) + tl.store(dx + offs, dx_val.to(dx.dtype.element_ty), mask=mask) + + +def swish_fwd(x: torch.Tensor) -> torch.Tensor: + T, D = x.numel(), x.shape[-1] + y = torch.empty_like(x) + swish_fwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)](x, y, T=T, D=D) + return y + + +def swish_bwd(x: torch.Tensor, dy: torch.Tensor) -> torch.Tensor: + T, D = x.numel(), x.shape[-1] + dx = torch.empty_like(x) + swish_bwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)](x, dy, dx, T=T, D=D) + return dx + + +class SwishFunction(torch.autograd.Function): + + @staticmethod + def forward(ctx, x): + ctx.save_for_backward(x) + return swish_fwd(x) + + @staticmethod + def backward(ctx, dout): + x, = ctx.saved_tensors + return swish_bwd(x, dout) + + +swish = SwishFunction.apply + +# 1/sqrt(2*pi)-> 0.3989423 +# 1/sqrt(2) -> 0.70710678 +# sqrt(2/pi) -> 0.79788456 + + +# this function is tanh approximation of gelu +# actual gelu is: +# x * 0.5 * (1.0 + torch.erf(x * 0.70710678)) +@torch.compile +def bias_gelu(y, bias): + x = bias + y + return (x * 0.5 * (1.0 + torch.tanh(0.79788456 * x * (1 + 0.044715 * x * x)))).to(dtype=y.dtype) + + +# gradient of tanh approximation of gelu +# gradient of actual gelu is: +# 0.5 * (1. + torch.erf(x * 0.70710678)) + 0.3989423 * x * torch.exp(-0.5 * x * x) +@torch.compile +def bias_gelu_bwd(g, y, bias): + """Assume that y has shape (B, D=D) and bias has shape (D)""" + x = bias + y + tanh_out = torch.tanh(0.79788456 * x * (1 + 0.044715 * x * x)) + # sqrt(2/pi) * 3 * 0.044715 -> 0.1070322243 + ff = 0.5 * x * ((1 - tanh_out * tanh_out) * (0.79788456 + 0.1070322243 * x * x)) + 0.5 * ( + 1 + tanh_out + ) + grad_y = ff * g + return grad_y.to(dtype=y.dtype), grad_y.sum(dim=(0), dtype=bias.dtype) + + +class GeLUFunction(torch.autograd.Function): + + @staticmethod + # bias is an optional argument + def forward(ctx, input, bias): + ctx.save_for_backward(input, bias) + return bias_gelu(input, bias) + + @staticmethod + def backward(ctx, grad_output): + input, bias = ctx.saved_tensors + tmp = bias_gelu_bwd(grad_output, input, bias) + return tmp, tmp + + +bias_gelu_impl = GeLUFunction.apply + + +# this function is tanh approximation of gelu +# actual gelu is: +# x * 0.5 * (1.0 + torch.erf(x * 0.70710678)) +@torch.compile +def gelu_fwd(x): + return (x * 0.5 * (1.0 + torch.tanh(0.79788456 * x * (1 + 0.044715 * x * x)))).to(dtype=x.dtype) + + +# gradient of tanh approximation of gelu +# gradient of actual gelu is: +# 0.5 * (1. + torch.erf(x * 0.70710678)) + 0.3989423 * x * torch.exp(-0.5 * x * x) +@torch.compile +def gelu_bwd(g, x): + tanh_out = torch.tanh(0.79788456 * x * (1 + 0.044715 * x * x)) + # sqrt(2/pi) * 3 * 0.044715 -> 0.1070322243 + ff = 0.5 * x * ((1 - tanh_out * tanh_out) * (0.79788456 + 0.1070322243 * x * x)) + 0.5 * ( + 1 + tanh_out + ) + return (ff * g).to(dtype=x.dtype) + + +class FastGeLUFunction(torch.autograd.Function): + @staticmethod + # bias is an optional argument + def forward(ctx, input): + ctx.save_for_backward(input) + return gelu_fwd(input) + + @staticmethod + def backward(ctx, grad_output): + (input,) = ctx.saved_tensors + tmp = gelu_bwd(grad_output, input) + return tmp + + +fast_gelu_impl = FastGeLUFunction.apply + + +@torch.compile +def relu_bwd(g, x): + return torch.where(x >= 0, g, 0.0).to(dtype=x.dtype) + + +@torch.compile +def sqrelu_fwd(x): + r = F.relu(x.float()) + return (r * r).to(dtype=x.dtype) + + +@torch.compile +def sqrelu_bwd(g, x): + return (2.0 * g * F.relu(x.float())).to(dtype=x.dtype) + + +class SquaredReLUFunction(torch.autograd.Function): + + @staticmethod + def forward(ctx, input): + ctx.save_for_backward(input) + return sqrelu_fwd(input) + + @staticmethod + def backward(ctx, grad_output): + input, = ctx.saved_tensors + return sqrelu_bwd(grad_output, input) + + +sqrelu = SquaredReLUFunction.apply + + +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def swiglu_fwd_kernel( + x, y, z, + T, + B: tl.constexpr, + D: tl.constexpr, +): + pid = tl.program_id(0) + offs = pid * B + tl.arange(0, B) + mask = offs < T + x_val = tl.load(x + offs, mask=mask, other=0.).to(tl.float32) + y_val = tl.load(y + offs, mask=mask, other=0.).to(tl.float32) + s = 1.0 / (1.0 + exp(-x_val)) + z_val = x_val * s * y_val + tl.store(z + offs, z_val.to(z.dtype.element_ty), mask=mask) + + +@triton.heuristics({ + 'HAS_WEIGHT': lambda args: args['z'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'B': bs}, num_warps=num_warps) + for bs in [512, 1024, 2048, 4096, 8192] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def swiglu_fwdbwd_kernel( + x, y, g, dx, dy, z, + T, + B: tl.constexpr, + D: tl.constexpr, + HAS_WEIGHT: tl.constexpr, +): + pid = tl.program_id(0) + offs = pid * B + tl.arange(0, B) + mask = offs < T + x_val = tl.load(x + offs, mask=mask, other=0.).to(tl.float32) + y_val = tl.load(y + offs, mask=mask, other=0.).to(tl.float32) + g_val = tl.load(g + offs, mask=mask, other=0.).to(tl.float32) + + s = 1.0 / (1.0 + exp(-x_val)) + x_s = x_val * s + dx_val = g_val * s * (1.0 + x_val * (1.0 - s)) * y_val + dy_val = g_val * x_s + + tl.store(dx + offs, dx_val.to(dx.dtype.element_ty), mask=mask) + tl.store(dy + offs, dy_val.to(dy.dtype.element_ty), mask=mask) + if HAS_WEIGHT: + z_val = x_s * y_val + tl.store(z + offs, z_val.to(z.dtype.element_ty), mask=mask) + + +def swiglu_fwd(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: + T, D = x.numel(), x.shape[-1] + z = torch.empty_like(x) + swiglu_fwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)](x, y, z, T=T, D=D) + return z + + +def swiglu_fwdbwd(x: torch.Tensor, y: torch.Tensor, g: torch.Tensor, use_weight: bool = False): + T, D = x.numel(), x.shape[-1] + dx = torch.empty_like(x) + dy = torch.empty_like(x) + if use_weight: + # recomputed for weight grad + z = torch.empty_like(x) + else: + z = None + swiglu_fwdbwd_kernel[lambda meta: (triton.cdiv(T, meta['B']),)](x, y, g, dx, dy, z, T=T, D=D) + if use_weight: + return dx, dy, z + return dx, dy + + +class SwiGLUFunction(torch.autograd.Function): + r""" + Swish-Gated Linear Unit (SwiGLU) function. + + .. math:: + \text{SwiGLU}(x, y) = swish(x) * y = \frac{x}{1 + \exp(-x)} * y + """ + + @staticmethod + def forward(ctx, x, y): + ctx.save_for_backward(x, y) + return swiglu_fwd(x, y) + + @staticmethod + def backward(ctx, dout): + x, y = ctx.saved_tensors + return swiglu_fwdbwd(x, y, dout) + + +class SwiGLULinearFunction(torch.autograd.Function): + r""" + Swish-Gated Linear Unit (SwiGLU) function followed by a linear transformation. + + .. math:: + \text{SwiGLULinear}(x, y, W, b) = (swish(x) * y) W + b + + This simple wrap discards the intermediate results of SwiGLU(x, y) to save memory. + """ + + @staticmethod + @autocast_custom_fwd + def forward(ctx, x, y, weight, bias): + z = swiglu_fwd(x, y) + out = F.linear(z, weight, bias) + # We don't store z, will be recomputed in the backward pass to save memory + ctx.save_for_backward(x, y, weight) + ctx.linear_bias_is_none = bias is None + return out + + @staticmethod + @autocast_custom_bwd + def backward(ctx, dout, *args): + x, y, weight = ctx.saved_tensors + dout = dout.reshape(-1, dout.shape[-1]) + dz = F.linear(dout, weight.t()).view_as(x) + dx, dy, z = swiglu_fwdbwd(x, y, dz, use_weight=True) + dlinear_weight = torch.einsum("bo,bi->oi", dout, z.reshape(-1, z.shape[-1])) + dlinear_bias = None if ctx.linear_bias_is_none else dout.sum(0) + return dx, dy, dlinear_weight, dlinear_bias + + +swiglu = SwiGLUFunction.apply + + +swiglu_linear = SwiGLULinearFunction.apply + + +ACT2FN = { + 'relu': F.relu, + 'sigmoid': sigmoid, + 'logsigmoid': logsigmoid, + 'silu': swish, + 'swish': swish, + 'sqrelu': sqrelu, + 'gelu': fast_gelu_impl, + 'bias_gelu': bias_gelu_impl, +} diff --git a/code/flash-linear-attention/fla/modules/convolution.py b/code/flash-linear-attention/fla/modules/convolution.py new file mode 100644 index 0000000000000000000000000000000000000000..329d236ea38b8217f6bf21f2da473138fa968175 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/convolution.py @@ -0,0 +1,1167 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import math +import warnings + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl +from einops import rearrange + +from fla.ops.utils import prepare_chunk_indices, prepare_sequence_ids +from fla.utils import autotune_cache_kwargs, get_multiprocessor_count, input_guard, is_amd + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [4, 8, 16, 32] +STATIC_WARPS = 32 if not is_amd else 16 + + +try: + from causal_conv1d import causal_conv1d_fn + from causal_conv1d import causal_conv1d_update as causal_conv1d_update_cuda +except ImportError: + causal_conv1d_fn = None + causal_conv1d_update_cuda = None + + +@triton.heuristics({ + 'HAS_WEIGHT': lambda args: args['weight'] is not None, + 'HAS_BIAS': lambda args: args['bias'] is not None, + 'HAS_RESIDUAL': lambda args: args['residual'] is not None, + 'USE_INITIAL_STATE': lambda args: args['initial_state'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BD': BD}, num_warps=num_warps) + for BD in [16, 32, 64, 128] + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D', 'W', 'NB'], + **autotune_cache_kwargs, +) +@triton.jit +def causal_conv1d_fwd_kernel( + x, + y, + weight, + bias, + residual, + cu_seqlens, + initial_state, + chunk_indices, + B, + T, + D: tl.constexpr, + W: tl.constexpr, + BT: tl.constexpr, + BW: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + ACTIVATION: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_t, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + i_n = i_b + bos, eos = (i_b * T).to(tl.int64), (i_b * T + T).to(tl.int64) + + o_d = i_d * BD + tl.arange(0, BD) + o_w = tl.arange(0, BW) + W - BW + m_d = o_d < D + m_w = o_w >= 0 + + if HAS_WEIGHT: + # [BD, BW] + b_w = tl.load(weight + o_d[:, None] * W + o_w, mask=m_d[:, None] & m_w, other=0).to(tl.float32) + + b_y = tl.zeros((BT, BD), dtype=tl.float32) + if not USE_INITIAL_STATE: + for i_w in tl.static_range(-W + 1, 1): + p_yi = tl.make_block_ptr(x + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + # [BT, BD] + b_yi = tl.load(p_yi, boundary_check=(0, 1)).to(tl.float32) + if HAS_WEIGHT: + b_yi *= tl.sum(b_w * (o_w == (i_w + W - 1)), 1) + b_y += b_yi + elif i_t * BT >= W: + # to make Triton compiler happy, we need to copy codes + for i_w in tl.static_range(-W + 1, 1): + p_yi = tl.make_block_ptr(x + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + # [BT, BD] + b_yi = tl.load(p_yi, boundary_check=(0, 1)).to(tl.float32) + if HAS_WEIGHT: + b_yi *= tl.sum(b_w * (o_w == (i_w + W - 1)), 1) + b_y += b_yi + else: + o_t = i_t * BT + tl.arange(0, BT) + for i_w in tl.static_range(-W + 1, 1): + o_x = o_t + i_w + m_x = ((o_x >= 0) & (o_x < T))[:, None] & m_d + m_c = ((o_x + W >= 0) & (o_x < 0))[:, None] & m_d + + b_yi = tl.load(x + bos * D + o_x[:, None] * D + o_d, mask=m_x, other=0).to(tl.float32) + + b_yi += tl.load(initial_state + i_n * D*W + o_d * W + (o_x + W)[:, None], mask=m_c, other=0).to(tl.float32) + + if HAS_WEIGHT: + b_yi *= tl.sum(b_w * (o_w == (i_w + W - 1)), 1) + b_y += b_yi + + if HAS_BIAS: + b_y += tl.load(bias + o_d, mask=m_d).to(tl.float32) + + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + b_y = b_y * tl.sigmoid(b_y) + + if HAS_RESIDUAL: + p_residual = tl.make_block_ptr(residual + bos * D, (T, D), (D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + b_residual = tl.load(p_residual, boundary_check=(0, 1)) + b_y += b_residual + + p_y = tl.make_block_ptr(y + bos * D, (T, D), (D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + tl.store(p_y, tl.cast(b_y, dtype=p_y.dtype.element_ty, fp_downcast_rounding='rtne'), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'HAS_WEIGHT': lambda args: args['dw'] is not None, + 'HAS_BIAS': lambda args: args['db'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BD': BD}, num_warps=num_warps) + for BD in [16, 32, 64, 128] + for num_warps in [4, 8, 16, 32] + ], + key=['D', 'W', 'NB'], + **autotune_cache_kwargs, +) +@triton.jit +def causal_conv1d_bwd_kernel( + x, + y, + weight, + initial_state, + dh0, + dht, + dy, + dx, + dw, + db, + cu_seqlens, + chunk_indices, + B, + T, + D: tl.constexpr, + W: tl.constexpr, + BT: tl.constexpr, + BW: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + ACTIVATION: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_t, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + i_tg = i_b * tl.num_programs(1) + i_t + i_n = i_b + bos, eos = (i_b * T).to(tl.int64), (i_b * T + T).to(tl.int64) + + o_d = i_d * BD + tl.arange(0, BD) + o_w = tl.arange(0, BW) + W - BW + m_d = o_d < D + m_w = o_w >= 0 + + if HAS_WEIGHT: + p_x = tl.make_block_ptr(x + bos * D, (T, D), (D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + b_x = tl.load(p_x, boundary_check=(0, 1)) + # [BD, BW] + b_w = tl.load(weight + o_d[:, None] * W + o_w, mask=m_d[:, None] & m_w, other=0) + + b_dx = tl.zeros((BT, BD), dtype=tl.float32) + if HAS_BIAS: + b_db = tl.zeros((BD,), dtype=tl.float32) + + if not USE_FINAL_STATE: + for i_w in tl.static_range(0, W): + p_dy = tl.make_block_ptr(dy + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + # [BT, BD] + b_dy = tl.load(p_dy, boundary_check=(0, 1)).to(tl.float32) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + p_y = tl.make_block_ptr(y + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + b_y = tl.load(p_y, boundary_check=(0, 1)).to(tl.float32) + b_ys = tl.sigmoid(b_y) + b_dy = b_dy * b_ys * (1 + b_y * (1 - b_ys)) + b_wdy = b_dy + if HAS_WEIGHT: + # [BT, BD] + b_wdy = b_wdy * tl.sum(b_w * (o_w == (W - i_w - 1)), 1) + # [BD] + b_dw = tl.sum(b_dy * b_x, 0) + tl.store(dw + i_tg * D*W + o_d * W + W - i_w - 1, b_dw.to(dw.dtype.element_ty), mask=m_d) + if HAS_BIAS and i_w == 0: + b_db += tl.sum(b_dy, 0) + b_dx += b_wdy + elif i_t * BT >= W: + # to make Triton compiler happy, we need to copy codes + for i_w in tl.static_range(0, W): + p_dy = tl.make_block_ptr(dy + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + # [BT, BD] + b_dy = tl.load(p_dy, boundary_check=(0, 1)).to(tl.float32) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + p_y = tl.make_block_ptr(y + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + b_y = tl.load(p_y, boundary_check=(0, 1)).to(tl.float32) + b_ys = tl.sigmoid(b_y) + b_dy = b_dy * b_ys * (1 + b_y * (1 - b_ys)) + b_wdy = b_dy + if HAS_WEIGHT: + # [BT, BD] + b_wdy = b_wdy * tl.sum(b_w * (o_w == (W - i_w - 1)), 1) + # [BD] + b_dw = tl.sum(b_dy * b_x, 0) + tl.store(dw + i_tg * D*W + o_d * W + W - i_w - 1, b_dw.to(dw.dtype.element_ty), mask=m_d) + if HAS_BIAS and i_w == 0: + b_db += tl.sum(b_dy, 0) + b_dx += b_wdy + else: + # which may use initial state + o_t = i_t * BT + tl.arange(0, BT) + for i_w in tl.static_range(0, W): + p_dy = tl.make_block_ptr(dy + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + b_dy_shift = tl.load(p_dy, boundary_check=(0, 1)).to(tl.float32) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + p_y = tl.make_block_ptr(y + bos * D, (T, D), (D, 1), (i_t * BT + i_w, i_d * BD), (BT, BD), (1, 0)) + b_y_shift = tl.load(p_y, boundary_check=(0, 1)).to(tl.float32) + b_ys = tl.sigmoid(b_y_shift) + b_dy_shift = b_dy_shift * b_ys * (1 + b_y_shift * (1 - b_ys)) + if HAS_WEIGHT: + # gradient comes from x:sum_t dy[t+i_w] * x[t] + b_dw = tl.sum(b_dy_shift * b_x, 0) + # index of cache:c = W - i_w + t + if USE_INITIAL_STATE: + mask_head_rows = (o_t < i_w) + # dy_head = dy[t] + b_dy_head = tl.load(dy + bos * D + o_t[:, None] * D + o_d, mask=(mask_head_rows[:, None] & m_d[None, :]), + other=0.0).to(tl.float32) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + # use y[t] (not y[t+i_w]) + b_y_head = tl.load(y + bos * D + o_t[:, None] * D + o_d, + mask=(mask_head_rows[:, None] & m_d[None, :]), other=0.0).to(tl.float32) + b_ys_head = tl.sigmoid(b_y_head) + b_dy_head = b_dy_head * b_ys_head * (1 + b_y_head * (1 - b_ys_head)) + o_c = W - i_w + o_t + # index 0 is padding 0 + mask_c = (mask_head_rows & (o_c >= 1) & (o_c < W)) + b_xc = tl.load(initial_state + i_n * D * W + o_d[None, :] * W + o_c[:, None], + mask=(mask_c[:, None] & m_d[None, :]), other=0.0).to(tl.float32) + # add the gradient comes from initial_state + b_dw += tl.sum(b_dy_head * b_xc, 0) + tl.store(dw + i_tg * D * W + o_d * W + W - i_w - 1, b_dw.to(dw.dtype.element_ty), mask=m_d) + + if HAS_BIAS and i_w == 0: + b_db += tl.sum(b_dy_shift, 0) + b_wdy = b_dy_shift if not HAS_WEIGHT else (b_dy_shift * tl.sum(b_w * (o_w == (W - i_w - 1)), 1)) + b_dx += b_wdy + + if USE_INITIAL_STATE: + p_dy0 = tl.make_block_ptr(dy + bos * D, (T, D), (D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + b_dy0 = tl.load(p_dy0, boundary_check=(0, 1)).to(tl.float32) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + p_y0 = tl.make_block_ptr(y + bos * D, (T, D), (D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + b_y0 = tl.load(p_y0, boundary_check=(0, 1)).to(tl.float32) + b_ys0 = tl.sigmoid(b_y0) + b_dy0 = b_dy0 * b_ys0 * (1 + b_y0 * (1 - b_ys0)) + # index 0 is padding 0, skip calculation + for i_w in tl.static_range(1, W): + m_rows = (o_t < i_w) + if HAS_WEIGHT: + # [BT] + w_idx_rows = i_w - 1 - o_t + # [BT, BW] + w_mask = (o_w[None, :] == w_idx_rows[:, None]) + w_pick = tl.sum(b_w[None, :, :] * w_mask[:, None, :], 2) + else: + w_pick = 1.0 + contrib = (b_dy0 * w_pick).to(tl.float32) + contrib = tl.where(m_rows[:, None] & m_d[None, :], contrib, 0.0) + # [BD] + b_dh0_s = tl.sum(contrib, 0) + # dh0: [NT, B, D, W] + tl.store(dh0 + i_t * B * D * W + i_n * D * W + o_d * W + i_w, + b_dh0_s.to(dh0.dtype.element_ty, fp_downcast_rounding='rtne'), mask=m_d) + + if HAS_BIAS: + b_db = tl.cast(b_db, dtype=db.dtype.element_ty, fp_downcast_rounding='rtne') + tl.store(db + i_tg * D + o_d, b_db, mask=m_d) + + if USE_FINAL_STATE: + if i_t * BT + BT >= T-W: + start_tok = max(0, T - (W - 1)) + offset = i_t * BT + tl.arange(0, BT) + tok_idx = offset - start_tok + mask = (offset >= start_tok) & (offset < T) + w_idx = 1 + tok_idx + dht_off = i_n * D * W + o_d[None, :] * W + w_idx[:, None] + b_dht = tl.load(dht + dht_off, mask=mask[:, None] & m_d[None, :], other=0.).to(tl.float32) + b_dx += b_dht + + p_dx = tl.make_block_ptr(dx + bos * D, (T, D), (D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + tl.store(p_dx, tl.cast(b_dx, dtype=p_dx.dtype.element_ty, fp_downcast_rounding='rtne'), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['cache'] is not None, + 'HAS_WEIGHT': lambda args: args['weight'] is not None, + 'HAS_BIAS': lambda args: args['bias'] is not None, + 'HAS_RESIDUAL': lambda args: args['residual'] is not None, +}) +@triton.jit +def causal_conv1d_update_kernel( + x, + cache, + residual, + y, + weight, + bias, + D: tl.constexpr, + W: tl.constexpr, + BD: tl.constexpr, + BW: tl.constexpr, + ACTIVATION: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, +): + i_d, i_n = tl.program_id(0), tl.program_id(1) + + o_d = i_d * BD + tl.arange(0, BD) + o_w = tl.arange(0, BW) + W - BW + m_d = o_d < D + m_w = o_w >= 0 + m_c = o_w < W - 1 + + # [BD] + b_x = tl.load(x + i_n * D + o_d, mask=m_d, other=0).to(tl.float32) + + if USE_INITIAL_STATE: + # shift the cache by 1 with the last one being discarded + p_cache = tl.make_block_ptr(cache + i_n * D*W, (D, W), (W, 1), (i_d * BD, W - BW + 1), (BD, BW), (1, 0)) + # [BD, BW] + b_cache = tl.load(p_cache, boundary_check=(0, 1)).to(tl.float32) + b_cache = tl.where(m_c[None, :], b_cache, b_x[:, None]) + else: + b_cache = tl.zeros((BD, BW), dtype=tl.float32) + + if HAS_WEIGHT: + b_w = tl.load(weight + o_d[:, None] * W + o_w, mask=m_d[:, None] & m_w, other=0) + b_y = tl.sum(b_cache * b_w, 1) + else: + b_y = tl.sum(b_cache, 1) + if HAS_BIAS: + b_y += tl.load(bias + o_d, mask=m_d) + + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + b_y = b_y * tl.sigmoid(b_y) + + if HAS_RESIDUAL: + b_y += tl.load(residual + i_n * D + o_d, mask=m_d, other=0) + + tl.store(y + i_n * D + o_d, tl.cast(b_y, dtype=y.dtype.element_ty, fp_downcast_rounding='rtne'), mask=m_d) + + if USE_INITIAL_STATE: + b_cache = tl.cast(b_cache, dtype=cache.dtype.element_ty, fp_downcast_rounding='rtne') + # update the cache in-place + p_cache = tl.make_block_ptr(cache + i_n * D*W, (D, W), (W, 1), (i_d * BD, W - BW), (BD, BW), (1, 0)) + tl.store(p_cache, b_cache, boundary_check=(0, 1)) + + +@input_guard +def causal_conv1d_fwd( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + residual: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + activation: str | None = None, + cu_seqlens: torch.Tensor | None = None, +) -> torch.Tensor: + shape = x.shape + if x.shape[-1] != weight.shape[0]: + x = rearrange(x, 'b t ... -> b t (...)') + B, T, D, W = *x.shape, weight.shape[1] + BT = min(64, triton.next_power_of_2(triton.cdiv(max(16, B*T), get_multiprocessor_count(x.device.index)))) + BW = triton.next_power_of_2(W) + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = len(chunk_indices) if cu_seqlens is not None else triton.cdiv(T, BT) + NB = triton.cdiv(B*T, 1024) + + y = torch.empty_like(x) + def grid(meta): return (triton.cdiv(D, meta['BD']), NT, B) + causal_conv1d_fwd_kernel[grid]( + x=x, + y=y, + weight=weight, + bias=bias, + residual=residual, + cu_seqlens=cu_seqlens, + initial_state=initial_state, + chunk_indices=chunk_indices, + B=B, + T=T, + D=D, + W=W, + BT=BT, + BW=BW, + NB=NB, + ACTIVATION=activation, + ) + final_state = None + if output_final_state: + final_state = causal_conv1d_update_states( + x=x, + state_len=W, + initial_state=initial_state, + cu_seqlens=cu_seqlens, + ) + return y.view(shape), final_state + + +def causal_conv1d_bwd( + x: torch.Tensor, + dy: torch.Tensor, + dht: torch.Tensor, + weight: torch.Tensor | None = None, + bias: torch.Tensor | None = None, + residual: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, + activation: str | None = None, + cu_seqlens: torch.Tensor | None = None, +): + shape = x.shape + if x.shape[-1] != weight.shape[0]: + x = rearrange(x, 'b t ... -> b t (...)') + B, T, D = x.shape + W = weight.shape[1] if weight is not None else None + BT = min(64, triton.next_power_of_2(triton.cdiv(max(16, B*T), get_multiprocessor_count(x.device.index)))) + BW = triton.next_power_of_2(W) + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = len(chunk_indices) if cu_seqlens is not None else triton.cdiv(T, BT) + NB = triton.cdiv(B*T, 1024) + + y = None + if activation is not None: + y, _ = causal_conv1d_fwd( + x=x, + weight=weight, + bias=bias, + residual=None, + initial_state=initial_state, + activation=None, + cu_seqlens=cu_seqlens, + output_final_state=False, + ) + dx = torch.empty_like(x) + dw = weight.new_empty(B*NT, *weight.shape, dtype=torch.float) if weight is not None else None + db = bias.new_empty(B*NT, *bias.shape, dtype=torch.float) if bias is not None else None + dr = dy if residual is not None else None + dh0 = initial_state.new_zeros(min(NT, triton.cdiv(W, BT)), *initial_state.shape) if initial_state is not None else None + + def grid(meta): return (triton.cdiv(D, meta['BD']), NT, B) + causal_conv1d_bwd_kernel[grid]( + x=x, + y=y, + weight=weight, + initial_state=initial_state, + dh0=dh0, + dht=dht, + dy=dy, + dx=dx, + dw=dw, + db=db, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + D=D, + W=W, + BT=BT, + BW=BW, + NB=NB, + ACTIVATION=activation, + ) + if weight is not None: + dw = dw.sum(0).to(weight) + if bias is not None: + db = db.sum(0).to(bias) + if initial_state is not None: + dh0 = dh0.sum(0, dtype=torch.float32).to(initial_state) + + return dx.view(shape), dw, db, dr, dh0 + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['initial_state'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit +def causal_conv1d_states_fwd_kernel( + x, + initial_state, + final_state, + cu_seqlens, + T, + D, + W, + BD: tl.constexpr, + BW: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_n = tl.program_id(0), tl.program_id(1) + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = (i_n * T).to(tl.int64), (i_n * T + T).to(tl.int64) + + o_t = eos - BW + tl.arange(0, BW) + o_d = i_d * BD + tl.arange(0, BD) + o_w = W - BW + tl.arange(0, BW) + m_t = (o_t >= tl.maximum(bos, eos - W)) + m_d = o_d < D + m_w = (o_w >= 0) & (o_w < W) + + b_x = tl.load(x + o_t * D + o_d[:, None], mask=(m_t & m_d[:, None]), other=0) + if USE_INITIAL_STATE: + if T < BW: + o_c = W - (BW - T) + tl.arange(0, BW) + m_c = (o_c >= 0) & (o_c < W) + b_cache = tl.load(initial_state + i_n * D*W + o_d[:, None] * W + o_c, mask=m_d[:, None] & m_c, other=0) + b_x += b_cache + + tl.store(final_state + i_n * D*W + o_d[:, None] * W + o_w, b_x, mask=m_d[:, None] & m_w) + + +@input_guard +def causal_conv1d_update_states( + x: torch.Tensor, + state_len: int, + initial_state: torch.Tensor | None = None, + cu_seqlens: torch.Tensor | None = None, +) -> torch.Tensor: + B, T, D, W = *x.shape, state_len + N = len(cu_seqlens) - 1 if cu_seqlens is not None else B + + final_state = torch.empty(N, D, W, dtype=x.dtype, device=x.device) + BD = min(triton.next_power_of_2(D), 256) + BW = triton.next_power_of_2(W) + grid = (triton.cdiv(D, BD), N) + causal_conv1d_states_fwd_kernel[grid]( + x=x, + initial_state=initial_state, + final_state=final_state, + cu_seqlens=cu_seqlens, + T=T, + D=D, + W=W, + BW=BW, + BD=BD, + ) + return final_state + + +@input_guard +def causal_conv1d_update( + x: torch.Tensor, + cache: torch.Tensor, + residual: torch.Tensor | None = None, + weight: torch.Tensor | None = None, + bias: torch.Tensor | None = None, + activation: str | None = None, +) -> torch.Tensor: + shape = x.shape + if weight is not None and x.shape[-1] != weight.shape[0]: + x = rearrange(x, 'b t ... -> b t (...)') + *_, D = x.shape + N = x.numel() // D + W = weight.shape[1] if weight is not None else None + BD = 8 + BW = triton.next_power_of_2(W) + + y = torch.empty_like(x) + # NOTE: autotuning is disabled as cache is updated in-place + def grid(meta): return (triton.cdiv(D, meta['BD']), N) + causal_conv1d_update_kernel[grid]( + x=x, + cache=cache, + residual=residual, + y=y, + weight=weight, + bias=bias, + D=D, + W=W, + BD=BD, + BW=BW, + ACTIVATION=activation, + num_warps=STATIC_WARPS, + ) + return y.view(shape), cache + + +class CausalConv1dFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + weight: torch.Tensor | None = None, + bias: torch.Tensor | None = None, + residual: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool | None = False, + activation: str | None = None, + cu_seqlens: torch.Tensor | None = None, + ): + ctx.activation = activation + ctx.cu_seqlens = cu_seqlens + ctx.save_for_backward(x, weight, bias, residual, initial_state) + y, final_state = causal_conv1d_fwd( + x=x, + weight=weight, + bias=bias, + residual=residual, + initial_state=initial_state, + output_final_state=output_final_state, + activation=activation, + cu_seqlens=cu_seqlens, + ) + return y, final_state + + @staticmethod + @input_guard + def backward(ctx, dy: torch.Tensor, dht: torch.Tensor | None = None): + x, weight, bias, residual, initial_state = ctx.saved_tensors + dx, dw, db, dr, dh0 = causal_conv1d_bwd( + x=x, + dy=dy, + dht=dht, + weight=weight, + bias=bias, + residual=residual, + initial_state=initial_state, + activation=ctx.activation, + cu_seqlens=ctx.cu_seqlens, + ) + return dx, dw, db, dr, dh0, None, None, None + + +@input_guard +def causal_conv1d( + x: torch.Tensor, + weight: torch.Tensor | None = None, + bias: torch.Tensor | None = None, + residual: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool | None = False, + activation: str | None = None, + backend: str | None = 'triton', + cu_seqlens: torch.Tensor | None = None, + **kwargs, +): + """ + A causal 1D convolution implementation that powers Mamba/Mamba2 and DeltaNet architectures. + + When a residual connection is provided, this implements the Canon operation + described in the paper at https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5240330. + + Args: + x (torch.Tensor): + Input tensor of shape [B, T, D]. + weight (Optional[torch.Tensor]): + Weight tensor of shape [D, W]. Default: `None`. + bias (Optional[torch.Tensor]): + Bias tensor of shape [D]. Default: `None`. + residual (Optional[torch.Tensor]): + Residual tensor of shape [B, T, D]. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state tensor of shape [N, D, W], + where `N` is the number of sequences in the batch and `W` is the kernel size. + If provided, the initial state is used to initialize the cache. Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape [N, D, W]. Default: `False`. + activation (Optional[str]): + Activations applied to output, only `swish`/`silu` or `None` (i.e., no activation) are supported. + Default: `None`. + backend (Optional[str]): + Specifies the backend to use for the convolution operation. Supported values are `'cuda'` and `'triton'`. + Default: `'triton'`. + cu_seqlens (Optional[torch.Tensor]): + Cumulative sequence lengths (optional) + + Returns: + Tuple of (output, final_state). + If `output_final_state` is `False`, the final state is `None`. + """ + + if backend == 'triton': + y, final_state = CausalConv1dFunction.apply( + x, + weight, + bias, + residual, + initial_state, + output_final_state, + activation, + cu_seqlens, + ) + return y, final_state + + B, _, D, W = *x.shape, weight.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + x = rearrange(x, 'b t d -> b d t') + + # check if cu_seqlens and cache are both provided + # Sequence index for each token. Used for varlen. + # Suppose a batch consists of two sequences with lengths 3 and 4, + # seq_idx=[0, 0, 0, 1, 1, 1, 1] for this batch. + # NOTE: No need to provide this arg if `cu_seqlens` is passed. + # This arg is just for BC, and will be removed in the future. + # [B, T] + seq_idx = kwargs.get('seq_idx') + if cu_seqlens is not None and seq_idx is None: + seq_idx = prepare_sequence_ids(cu_seqlens).to(torch.int32).unsqueeze(0) + + # equivalent to: + # y = _conv_forward(x, weight, bias)[..., :x.shape[-1]] + # if activation is not None: + # y = ACT2FN[activation](x) + + cache, initial_state = initial_state, None + if cache is not None: + # To make causal-conv1d happy + initial_state = ( + cache[:, :, -(W-1):] # [N, D, W-1] + .transpose(1, 2).contiguous() # [N, W-1, D] and stride(2)==1 + .transpose(1, 2) # [N, D, W-1] and stride(1)==1 + ) + + result = causal_conv1d_fn( + x=x, + weight=weight, + bias=bias, + activation=activation, + seq_idx=seq_idx, + initial_states=initial_state, + return_final_states=output_final_state, + ) + y, final_state = result if output_final_state else (result, None) + y = rearrange(y, 'b d t -> b t d') + if output_final_state: + cache = x.new_zeros(N, D, W) + cache[:, :, -W+1:].copy_(final_state[:, :, -W+1:]) + if residual is not None: + y.add_(residual) + + return y, cache + + +class ShortConvolution(nn.Conv1d): + """Short convolution layer for efficient causal convolution operations. + + This class implements a depthwise separable 1D convolution with causal padding, + designed for efficient sequence processing. It supports multiple backends (Triton/CUDA) + and optional activation functions. + + Args: + hidden_size (int): Number of input/output channels (must be equal for depthwise conv) + kernel_size (int): Size of the convolution kernel + bias (bool, optional): Whether to include learnable bias. Defaults to False. + activation (Optional[str], optional): Activation function ('silu' or 'swish'). Defaults to 'silu'. + backend (Optional[str], optional): Backend implementation ('triton' or 'cuda'). Defaults to 'triton'. + device (Optional[torch.device], optional): Device to place the layer on. Defaults to None. + dtype (Optional[torch.dtype], optional): Data type for layer parameters. Defaults to None. + **kwargs: Additional keyword arguments (deprecated 'use_fast_conv1d' supported for compatibility) + + Attributes: + hidden_size (int): Number of channels + activation (Optional[str]): Selected activation function + backend (str): Actual backend being used (may differ from input due to availability) + + Note: + - Uses depthwise convolution (groups=hidden_size) for efficiency + - Applies causal padding (kernel_size-1) to ensure no future information leakage + - Falls back to Triton backend if CUDA backend is unavailable + """ + + def __init__( + self, + hidden_size: int, + kernel_size: int, + bias: bool = False, + activation: str | None = 'silu', + backend: str | None = 'triton', + device: torch.device | None = None, + dtype: torch.dtype | None = None, + **kwargs, + ): + super().__init__( + in_channels=hidden_size, + out_channels=hidden_size, + kernel_size=kernel_size, + groups=hidden_size, + bias=bias, + padding=kernel_size - 1, + device=device, + dtype=dtype, + ) + + self.hidden_size = hidden_size + self.activation = None + + if activation is not None: + assert activation in ['silu', 'swish'], f"Activation `{activation}` not supported yet." + self.activation = activation + + if 'use_fast_conv1d' in kwargs: + warnings.warn( + "The `use_fast_conv1d` parameter is deprecated and will be ignored. " + "Please use the `backend` parameter instead.", + ) + import os + self.backend = os.environ.get('FLA_CONV_BACKEND', backend) + if backend not in ['cuda', 'triton']: + raise ValueError(f"Invalid backend: {backend}, must be one of ['cuda', 'triton']") + if backend == 'cuda': + if causal_conv1d_fn is None: + warnings.warn( + "The `backend` parameter is set to `cuda`, but `causal_conv1d_fn` is not available. " + "Switching to the Triton implementation instead. " + "Consider installing `causal_conv1d` to enable the CUDA backend.", + ) + self.backend = 'triton' + + def extra_repr(self): + s = ('{in_channels}, {out_channels}, kernel_size={kernel_size}' + ', stride={stride}') + if self.padding != (0,) * len(self.padding): + s += ', padding={padding}' + if self.dilation != (1,) * len(self.dilation): + s += ', dilation={dilation}' + if self.output_padding != (0,) * len(self.output_padding): + s += ', output_padding={output_padding}' + if self.groups != 1: + s += ', groups={groups}' + if self.bias is None: + s += ', bias=False' + if self.padding_mode != 'zeros': + s += ', padding_mode={padding_mode}' + if self.activation is not None: + s += ', activation={activation}' + s += f', backend={self.backend}' + return s.format(**self.__dict__) + + def forward( + self, + x: torch.Tensor, + residual: torch.Tensor | None = None, + mask: torch.Tensor | None = None, + cache: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + **kwargs, + ) -> tuple[torch.Tensor, torch.Tensor]: + """ + Args: + x (`torch.Tensor`): + Tensor of shape `[B, T, D]`. `B` must be 1 if `seq_idx` is provided. + residual (`Optional[torch.Tensor]`): + Residual tensor of shape `[B, T, D]`. Default: `None`. + mask (`Optional[torch.Tensor]`): + Attention mask dealing with padded positions. + cache (`Optional[torch.Tensor]`): + Previous cache tensor of shape `[N, D, W]`, where `W` is the kernel size. + If provided, the cache is updated **inplace**. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, D, W]`. Default: `False`. + cu_seqlens (Optional[torch.LongTensor]): + Cumulative sequence lengths for each batch. Used for varlen. Default: `None`. + Shape: [B+1] + + Returns: + Tensor of shape `[B, T, D]`. + """ + + B, T, *_ = x.shape + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + if mask is not None: + if cu_seqlens is not None: + raise ValueError("`mask` and `cu_seqlens` cannot be provided at the same time") + x = x.mul_(mask.unsqueeze(-1)) + + # in decoding phase, the cache (if provided) is updated inplace + if B * T == N: + y, cache = self.step( + x=x, + residual=residual, + cache=cache, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + return y, cache + + # cuda backend do not support: + # 1. both `cu_seqlens` and `cache` being provided + # 2. both `cu_seqlens` and `output_final_state` being provided + if self.backend == 'cuda' and ( + (cu_seqlens is not None and cache is not None) or + (cu_seqlens is not None and output_final_state) + ): + warnings.warn( + "The CUDA backend does not support both `cu_seqlens` and `cache` being provided, " + "or both `cu_seqlens` and `output_final_state` being provided. " + "Switching to the Triton backend instead. ", + stacklevel=2, + ) + self.backend = 'triton' + + return causal_conv1d( + x=x, + weight=rearrange(self.weight, "d 1 w -> d w"), + bias=self.bias, + residual=residual, + initial_state=cache, + output_final_state=output_final_state, + activation=self.activation, + backend=self.backend, + cu_seqlens=cu_seqlens, + **kwargs, + ) + + def step( + self, + x: torch.Tensor, + residual: torch.Tensor, + cache: torch.Tensor, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + B, _, D, W = *x.shape, self.kernel_size[0] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + if output_final_state and cache is None: + cache = x.new_zeros(N, D, W) + # NOTE: we follow the fast mode that updates the cache in-place + if self.backend == 'triton': + return causal_conv1d_update( + x=x, + cache=cache, + residual=residual, + weight=rearrange(self.weight, "d 1 w -> d w"), + bias=self.bias, + activation=self.activation, + ) + + shape = x.shape + x = x.squeeze(0) if cu_seqlens is not None else x.squeeze(1) + # equivalent to: + # cache.copy_(cache.roll(shifts=-1, dims=-1)) + # cache[:, :, -1] = x + # y = torch.sum(cache * rearrange(self.weight, "d 1 w -> d w"), dim=-1) + y = causal_conv1d_update_cuda( + x=x, + conv_state=cache, + weight=rearrange(self.weight, "d 1 w -> d w"), + bias=self.bias, + activation=self.activation, + ) + y = y.view(shape) + if residual is not None: + y.add_(residual) + return y, cache + + @property + def state_size(self) -> int: + return self.hidden_size * self.kernel_size + + +def fft_conv(u, k, dropout_mask, gelu=True, k_rev=None): + seqlen = u.shape[-1] + fft_size = 2 * seqlen + k_f = torch.fft.rfft(k, n=fft_size) / fft_size + if k_rev is not None: + k_rev_f = torch.fft.rfft(k_rev, n=fft_size) / fft_size + k_f = k_f + k_rev_f.conj() + u_f = torch.fft.rfft(u.to(dtype=k.dtype), n=fft_size) + + if len(u.shape) > 3: + k_f = k_f.unsqueeze(1) + y = torch.fft.irfft(u_f * k_f, n=fft_size, norm="forward")[..., :seqlen] + + out = y + u + if gelu: + out = F.gelu(out) + if dropout_mask is not None: + return (out * rearrange(dropout_mask, "b H -> b H 1")).to(dtype=u.dtype) + else: + return out.to(dtype=u.dtype) + + +class LongConvolution(nn.Module): + """ + LongConvolution applies a convolution operation on the input tensor using a fixed + filter of length max_len. + The filter is learned during training and is applied using FFT convolution. + + Args: + hidden_size (int): The number of expected features in the input and output. + max_len (int): The maximum sequence length. + + Returns: + y: [batch_size, seq_len, hidden_size] tensor + """ + + def __init__( + self, + hidden_size: int, + max_len: int, + **kwargs, + ): + """ + Initializes the LongConvolution module. + Args: + hidden_size (int): The number of expected features in the input and output. + max_len (int): The maximum sequence length. + """ + super().__init__() + self.hidden_size = hidden_size + self.filter = nn.Parameter(torch.randn(self.hidden_size, max_len), requires_grad=True) + + def forward(self, x: torch.Tensor, *args, **kwargs): + """ + Applies the LongConvolution operation on the input tensor. + Args: + x: [batch_size, seq_len, hidden_size] tensor + Returns: + y: [batch_size, seq_len, hidden_size] tensor + """ + x = x.transpose(1, 2) + y = fft_conv(x, self.filter, dropout_mask=None, gelu=False) + y = y.transpose(1, 2) + return y.to(dtype=x.dtype) + + +class PositionalEmbedding(nn.Module): + def __init__(self, emb_dim: int, seq_len: int, **kwargs): + """Complex exponential positional embeddings for implicit long convolution filters.""" + super().__init__() + + self.seq_len = seq_len + # The time embedding fed to the filteres is normalized so that t_f = 1 + t = torch.linspace(0, 1, self.seq_len)[None, :, None] # 1, L, 1 + + if emb_dim > 1: + bands = (emb_dim - 1) // 2 + # To compute the right embeddings we use the "proper" linspace + t_rescaled = torch.linspace(0, seq_len - 1, seq_len)[None, :, None] + w = 2 * math.pi * t_rescaled / seq_len # 1, L, 1 + + f = torch.linspace(1e-4, bands - 1, bands)[None, None] + z = torch.exp(-1j * f * w) + z = torch.cat([t, z.real, z.imag], dim=-1) + self.z = nn.Parameter(z, requires_grad=False) + + def forward(self, L): + return self.z[:, :L] + + +class ImplicitLongConvolution(nn.Module): + """ + Long convolution with implicit filter parameterized by an MLP. + + Args: + hidden_size (int): + The number of expected features in the input and output. + max_len (int): + The maximum sequence length. + d_emb (Optional[int]): + The dimension of the positional embeddings. Must be odd and greater or equal to 3 (time, sine and cosine). + Defaults to 3. + d_hidden (Optional[int]): + The number of features in the hidden layer of the MLP. Defaults to 16. + + Attributes: + pos_emb (`PositionalEmbedding`): The positional embedding layer. + mlp (`nn.Sequential`): The MLP that parameterizes the implicit filter. + + """ + + def __init__( + self, + hidden_size: int, + max_len: int, + d_emb: int = 3, + d_hidden: int = 16, + **kwargs, + ): + """ + Long convolution with implicit filter parameterized by an MLP. + + + """ + super().__init__() + self.hidden_size = hidden_size + self.d_emb = d_emb + + assert ( + d_emb % 2 != 0 and d_emb >= 3 + ), "d_emb must be odd and greater or equal to 3 (time, sine and cosine)" + self.pos_emb = PositionalEmbedding(d_emb, max_len) + + # final linear layer + self.mlp = nn.Sequential( + nn.Linear(d_emb, d_hidden), + torch.nn.ReLU(), + nn.Linear(d_hidden, hidden_size), + ) + + def filter(self, seq_len: int, *args, **kwargs): + return self.mlp(self.pos_emb(seq_len)).transpose(1, 2) + + def forward(self, x: torch.Tensor, *args, **kwargs): + """ + Args: + x: [batch_size, seq_len, hidden_size] tensor + + Returns: + y: [batch_size, seq_len, hidden_size] tensor + """ + x = x.transpose(1, 2) + k = self.filter(x.shape[-1]) + y = fft_conv(x, k, dropout_mask=None, gelu=False) + + y = y.transpose(1, 2) + return y.to(dtype=x.dtype) diff --git a/code/flash-linear-attention/fla/modules/feature_map.py b/code/flash-linear-attention/fla/modules/feature_map.py new file mode 100644 index 0000000000000000000000000000000000000000..15f3b194f0997f7fd5735768b48d3561c2fe6360 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/feature_map.py @@ -0,0 +1,298 @@ + +from __future__ import annotations + +import math + +import torch +import torch.nn.functional as F +from torch import nn + +from fla.modules.activations import fast_gelu_impl, sigmoid, sqrelu, swish +from fla.modules.layernorm import layer_norm +from fla.utils import checkpoint + + +@checkpoint +def flatten_diag_outer_product(x, y): + z = torch.einsum("...i,...j->...ij", x, y) + N = z.size(-1) + indicies = torch.triu_indices(N, N) + return z[..., indicies[0], indicies[1]] + + +@checkpoint +def flatten_diag_outer_product_off1(x, y): + z = torch.einsum("...i,...j->...ij", x, y) + N = z.size(-1) + indicies = torch.triu_indices(N, N, 1) + indices2 = torch.arange(0, N) + return z[..., indicies[0], indicies[1]], z[..., indices2, indices2] + + +def is_power_of_2(n): + return (n & (n - 1) == 0) and n != 0 + + +class HedgehogFeatureMap(nn.Module): + + r""" + Hedgehog feature map as introduced in + `The Hedgehog & the Porcupine: Expressive Linear Attentions with Softmax Mimicry `_ + """ + + def __init__( + self, + head_dim: int, + ) -> HedgehogFeatureMap: + super().__init__() + # Trainable map + self.layer = nn.Linear(head_dim, head_dim) + self.init_weights_() + + def init_weights_(self): + """Initialize trainable map as identity""" + with torch.no_grad(): + identity = torch.eye(*self.layer.weight.shape[-2:], dtype=torch.float) + self.layer.weight.copy_(identity.to(self.layer.weight)) + nn.init.zeros_(self.layer.bias) + + def forward(self, x: torch.Tensor): + x = self.layer(x) # shape b, h, l, d + return torch.cat([2*x, -2*x], dim=-1).softmax(-1) + + +class T2RFeatureMap(nn.Module): + + r""" + Simple linear mapping feature map as in + `Finetuning Pretrained Transformers into RNNs `_ + """ + + def __init__( + self, + head_dim: int, + dot_dim: int = None, + bias: bool | None = False, + ) -> T2RFeatureMap: + super().__init__() + # Trainable map + if dot_dim is None: + dot_dim = head_dim + + self.head_dim = head_dim + self.dot_dim = dot_dim + self.bias = bias + + self.layer = nn.Linear(head_dim, dot_dim, bias=bias) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(head_dim={self.head_dim}, dot_dim={self.dot_dim}, bias={self.bias})" + + def forward(self, x: torch.Tensor): + return self.layer(x).relu() + + +class DPFPFeatureMap(nn.Module): + + r""" + Deterministic Parameter-Free Projection (DPFP) feature map in + `Linear Transformers Are Secretly Fast Weight Programmers `_ + """ + + def __init__( + self, + head_dim: int, + nu: int = 4, + ) -> DPFPFeatureMap: + super().__init__() + self.nu = nu + + def forward(self, x: torch.Tensor): + x = torch.cat([x.relu(), -x.relu()], dim=-1) + x_rolled = torch.cat([x.roll(shifts=j, dims=-1) for j in range(1, self.nu+1)], dim=-1) + x_repeat = torch.cat([x] * self.nu, dim=-1) + return x_repeat * x_rolled + + +class HadamardFeatureMap(nn.Module): + def __init__( + self, + head_dim: int, + ) -> HadamardFeatureMap: + super().__init__() + # Trainable map + self.layer1 = nn.Linear(head_dim, head_dim) + self.layer2 = nn.Linear(head_dim, head_dim) + + def forward(self, x: torch.Tensor): + return self.layer1(x) * self.layer2(x) + + +class LearnableOuterProductFeatureMap(nn.Module): + def __init__( + self, + head_dim: int, + feature_dim: int, + ) -> LearnableOuterProductFeatureMap: + super().__init__() + # Trainable map + self.layer1 = nn.Linear(head_dim, feature_dim, bias=False) + self.layer2 = nn.Linear(head_dim, feature_dim, bias=False) + self.normalizer = feature_dim ** -0.5 + + def forward(self, x: torch.Tensor): + return flatten_diag_outer_product(self.layer1(x), self.layer2(x)) + + +class LearnablePolySketchNonNegativeFeatureMap(nn.Module): + + def __init__( + self, + head_dim: int, + sketch_size: int | None = None, + degree: int | None = 2, + ) -> LearnablePolySketchNonNegativeFeatureMap: + super().__init__() + + assert is_power_of_2(degree) and degree >= 2, f"The degree {degree} must be a power of 2" + + self.head_dim = head_dim + self.sketch_size = sketch_size if sketch_size is not None else head_dim + self.degree = degree + + self.gamma = nn.Parameter(torch.ones(head_dim)) + self.beta = nn.Parameter(torch.zeros(head_dim)) + # NOTE: the sketch layers defined here are quite different from the original paper + # currently we simply use linear layers without any non-linear activations + self.sketches1 = nn.ModuleList([ + nn.Linear(head_dim, sketch_size, bias=False), + *[nn.Linear(sketch_size, sketch_size, bias=False) for _ in range(int(math.log2(self.degree)) - 2)], + ]) + self.sketches2 = nn.ModuleList([ + nn.Linear(head_dim, sketch_size, bias=False), + *[nn.Linear(sketch_size, sketch_size, bias=False) for _ in range(int(math.log2(self.degree)) - 2)], + ]) + + def forward(self, x: torch.Tensor): + # Section 2.1 + x = layer_norm(x, self.gamma, self.beta) + # first map the input to sketch size with learnable parameters + x = self.sketches1[0](x) * self.sketches2[0](x) * self.head_dim ** -0.5 + for i in range(1, int(math.log2(self.degree)) - 1): + x = self.sketches1[i](x) * self.sketches2[i](x) * self.head_dim ** -0.5 + # do sketch mapping for log2(p) - 1 times in total + # do p=2 mapping to ensure non-negativity + return flatten_diag_outer_product(x, x) + + +class TaylorFeatureMap(nn.Module): + def __init__( + self, + head_dim: int, + ) -> TaylorFeatureMap: + super().__init__() + self.head_dim = head_dim + self.r2 = math.sqrt(2) + self.rd = math.sqrt(self.head_dim) + self.rrd = math.sqrt(self.rd) + + def forward(self, x: torch.Tensor): + x2_1, x2_2 = flatten_diag_outer_product_off1(x, x) + return torch.cat([torch.ones_like(x[..., 0:1]), x / self.rrd, x2_2 / (self.rd * self.r2), x2_1 / self.rd], dim=-1) + + +class RebasedFeatureMap(nn.Module): + + def __init__( + self, + head_dim: int, + use_gamma: bool | None = True, + use_beta: bool | None = True, + normalize: bool | None = True, + ) -> RebasedFeatureMap: + super().__init__() + + self.head_dim = head_dim + self.use_gamma = use_gamma + self.use_beta = use_beta + self.normalize = normalize + + self.gamma = None + self.beta = None + if use_gamma: + self.gamma = nn.Parameter(torch.ones(head_dim)) + if use_beta: + self.beta = nn.Parameter(torch.zeros(head_dim)) + + def forward(self, x: torch.Tensor, flatten: bool | None = True): + if self.use_beta and self.use_gamma and self.normalize: + x = layer_norm(x, self.gamma, self.beta) + elif self.normalize: + x = F.layer_norm(x, (self.head_dim,), self.gamma, self.beta) + elif self.use_gamma and self.use_beta: + x = torch.addcmul(self.beta, x, self.gamma) + elif self.use_gamma: + x = x.mul(self.gamma) + else: + raise RuntimeError(f"Not supported combination of `use_gamma`, `use_beta` and `normalize`, " + f"which is currentlt set as (`{self.use_gamma}`, `{self.use_beta}`, `{self.normalize}`)") + if not flatten: + return x + x2_1, x2_2 = flatten_diag_outer_product_off1(x, x) + # rebased use learnable parameters to approximate any quadratic function + return torch.cat([x2_2 * self.head_dim ** -0.5, x2_1 * (2 / self.head_dim) ** 0.5], dim=-1) + + +class ReLUFeatureMap(nn.Module): + + def __init__( + self, + ) -> ReLUFeatureMap: + super().__init__() + + def forward(self, x: torch.Tensor): + return F.relu(x) + + +class SquaredReLUFeatureMap(nn.Module): + + def __init__( + self, + ) -> SquaredReLUFeatureMap: + super().__init__() + + def forward(self, x: torch.Tensor): + return sqrelu(x) + + +class GELUFeatureMap(nn.Module): + + def __init__( + self, + ) -> GELUFeatureMap: + super().__init__() + + def forward(self, x: torch.Tensor): + return fast_gelu_impl(x) + + +class SwishFeatureMap(nn.Module): + + def __init__( + self, + ) -> SwishFeatureMap: + super().__init__() + + def forward(self, x: torch.Tensor): + return swish(x) + + +class SigmoidFeatureMap(nn.Module): + + def __init__( + self, + ) -> SigmoidFeatureMap: + super().__init__() + + def forward(self, x: torch.Tensor): + return sigmoid(x) diff --git a/code/flash-linear-attention/fla/modules/fused_bitlinear.py b/code/flash-linear-attention/fla/modules/fused_bitlinear.py new file mode 100644 index 0000000000000000000000000000000000000000..0451ce4e0f59ff42abbef2ea89d82943ffc49d00 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/fused_bitlinear.py @@ -0,0 +1,633 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +# Implementations of BitLinear layer with fused LayerNorm and quantized Linear layer. +# [The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits](https://arxiv.org/abs/2402.17764) +# [Scalable MatMul-free Language Modeling](https://arxiv.org/abs/2406.02528) + +# Code adapted from https://github.com/ridgerchu/matmulfreellm/ + +from __future__ import annotations + +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl + +from fla.modules.layernorm import RMSNorm +from fla.utils import autotune_cache_kwargs, get_multiprocessor_count, input_guard, is_amd, require_version + +NUM_WARPS_AUTOTUNE = [1, 2, 4, 8, 16] if is_amd else [1, 2, 4, 8, 16, 32] + + +def activation_quant(x): + """ + Per-token quantization to 8 bits. No grouping is needed for quantization. + + Args: + x: An activation tensor with shape [n, d]. + + Returns: + A quantized activation tensor with shape [n, d]. + """ + # Compute the scale factor + scale = 127.0 / x.abs().max(dim=-1, keepdim=True).values.clamp_(min=1e-5) + # Quantize and then de-quantize the tensor + y = (x * scale).round().clamp_(-128, 127) / scale + return y + + +def weight_quant(w): + """ + Per-tensor quantization to 1.58 bits. No grouping is needed for quantization. + + Args: + w: A weight tensor with shape [d, k]. + + Returns: + A quantized weight tensor with shape [d, k]. + """ + # Compute the scale factor + scale = 1.0 / w.abs().mean().clamp_(min=1e-5) + # Quantize and then de-quantize the tensor + u = (w * scale).round().clamp_(-1, 1) / scale + return u + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=["N", "HAS_RESIDUAL", "STORE_RESIDUAL_OUT", "IS_RMS_NORM", "HAS_BIAS"], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_fwd_kernel_quant( + X, # pointer to the input + Y, # pointer to the output + W, # pointer to the weights + B, # pointer to the biases + RESIDUAL, # pointer to the residual + RESIDUAL_OUT, # pointer to the residual + Mean, # pointer to the mean + Rstd, # pointer to the 1/std + stride_x_row, # how much to increase the pointer when moving by 1 row + stride_y_row, + stride_res_row, + stride_res_out_row, + N, # number of columns in X + eps, # epsilon to avoid division by zero + IS_RMS_NORM: tl.constexpr, + BLOCK_N: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, + STORE_RESIDUAL_OUT: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, +): + # Map the program id to the row of X and Y it should compute. + row = tl.program_id(0) + X += row * stride_x_row + Y += row * stride_y_row + if HAS_RESIDUAL: + RESIDUAL += row * stride_res_row + if STORE_RESIDUAL_OUT: + RESIDUAL_OUT += row * stride_res_out_row + # Compute mean and variance + cols = tl.arange(0, BLOCK_N) + x = tl.load(X + cols, mask=cols < N, other=0.0).to(tl.float32) + if HAS_RESIDUAL: + residual = tl.load(RESIDUAL + cols, mask=cols < N, other=0.0).to(tl.float32) + x += residual + if STORE_RESIDUAL_OUT: + tl.store(RESIDUAL_OUT + cols, x, mask=cols < N) + if not IS_RMS_NORM: + mean = tl.sum(x, axis=0) / N + tl.store(Mean + row, mean) + xbar = tl.where(cols < N, x - mean, 0.0) + var = tl.sum(xbar * xbar, axis=0) / N + else: + xbar = tl.where(cols < N, x, 0.0) + var = tl.sum(xbar * xbar, axis=0) / N + rstd = 1 / tl.sqrt(var + eps) + tl.store(Rstd + row, rstd) + # Normalize and apply linear transformation + mask = cols < N + if HAS_WEIGHT: + w = tl.load(W + cols, mask=mask).to(tl.float32) + if HAS_BIAS: + b = tl.load(B + cols, mask=mask).to(tl.float32) + x_hat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd + + y = x_hat * w if HAS_WEIGHT else x_hat + if HAS_BIAS: + y = y + b + + # Aply quantization to the output + scale = 127.0 / tl.maximum(tl.max(tl.abs(y), 0), 1e-5) + # Quantize and then de-quantize the tensor + y = tl.extra.cuda.libdevice.round(y * scale) + y = tl.maximum(tl.minimum(y, 127), -128) / scale + + # Write output + tl.store(Y + cols, y, mask=mask) + + +def layer_norm_fwd_quant( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + eps: float, + residual: torch.Tensor = None, + out_dtype: torch.dtype = None, + residual_dtype: torch.dtype = None, + is_rms_norm: bool = False, +): + if residual is not None: + residual_dtype = residual.dtype + M, N = x.shape + # allocate output + y = torch.empty_like(x, dtype=x.dtype if out_dtype is None else out_dtype) + if residual is not None or (residual_dtype is not None and residual_dtype != x.dtype): + residual_out = torch.empty(M, N, device=x.device, dtype=residual_dtype) + else: + residual_out = None + mean = torch.empty((M,), dtype=torch.float32, device=x.device) if not is_rms_norm else None + rstd = torch.empty((M,), dtype=torch.float32, device=x.device) + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(N)) + if N > BLOCK_N: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + # heuristics for number of warps + layer_norm_fwd_kernel_quant[(M,)]( + x, + y, + weight, + bias, + residual, + residual_out, + mean, + rstd, + x.stride(0), + y.stride(0), + residual.stride(0) if residual is not None else 0, + residual_out.stride(0) if residual_out is not None else 0, + N, + eps, + is_rms_norm, + BLOCK_N, + residual is not None, + residual_out is not None, + weight is not None, + bias is not None, + ) + # residual_out is None if residual is None and residual_dtype == input_dtype + return y, mean, rstd, residual_out if residual_out is not None else x + + +@triton.heuristics({ + "RECOMPUTE_OUTPUT": lambda args: args["Y"] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=["N", "HAS_DRESIDUAL", "STORE_DRESIDUAL", "IS_RMS_NORM", "HAS_BIAS"], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_bwd_kernel( + X, # pointer to the input + W, # pointer to the weights + B, # pointer to the biases + Y, # pointer to the output to be recomputed + DY, # pointer to the output gradient + DX, # pointer to the input gradient + DW, # pointer to the partial sum of weights gradient + DB, # pointer to the partial sum of biases gradient + DRESIDUAL, + DRESIDUAL_IN, + Mean, # pointer to the mean + Rstd, # pointer to the 1/std + stride_x_row, # how much to increase the pointer when moving by 1 row + stride_y_row, + stride_dy_row, + stride_dx_row, + stride_dres_row, + stride_dres_in_row, + M, # number of rows in X + N, # number of columns in X + eps, # epsilon to avoid division by zero + rows_per_program, + IS_RMS_NORM: tl.constexpr, + BLOCK_N: tl.constexpr, + HAS_DRESIDUAL: tl.constexpr, + STORE_DRESIDUAL: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + RECOMPUTE_OUTPUT: tl.constexpr, +): + # Map the program id to the elements of X, DX, and DY it should compute. + row_block_id = tl.program_id(0) + row_start = row_block_id * rows_per_program + cols = tl.arange(0, BLOCK_N) + mask = cols < N + X += row_start * stride_x_row + if HAS_DRESIDUAL: + DRESIDUAL += row_start * stride_dres_row + if STORE_DRESIDUAL: + DRESIDUAL_IN += row_start * stride_dres_in_row + DY += row_start * stride_dy_row + DX += row_start * stride_dx_row + if RECOMPUTE_OUTPUT: + Y += row_start * stride_y_row + if HAS_WEIGHT: + w = tl.load(W + cols, mask=mask).to(tl.float32) + dw = tl.zeros((BLOCK_N,), dtype=tl.float32) + if RECOMPUTE_OUTPUT and HAS_BIAS: + b = tl.load(B + cols, mask=mask, other=0.0).to(tl.float32) + if HAS_BIAS: + db = tl.zeros((BLOCK_N,), dtype=tl.float32) + row_end = min((row_block_id + 1) * rows_per_program, M) + for row in range(row_start, row_end): + # Load data to SRAM + x = tl.load(X + cols, mask=mask, other=0).to(tl.float32) + dy = tl.load(DY + cols, mask=mask, other=0).to(tl.float32) + if not IS_RMS_NORM: + mean = tl.load(Mean + row) + rstd = tl.load(Rstd + row) + # Compute dx + xhat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd + xhat = tl.where(mask, xhat, 0.0) + if RECOMPUTE_OUTPUT: + y = xhat * w if HAS_WEIGHT else xhat + if HAS_BIAS: + y = y + b + + # Aply quantization to the output + scale = 127.0 / tl.maximum(tl.max(tl.abs(y), 0), 1e-5) + # Quantize and then de-quantize the tensor + y = tl.extra.cuda.libdevice.round(y * scale) + y = tl.maximum(tl.minimum(y, 127), -128) / scale + + tl.store(Y + cols, y, mask=mask) + wdy = dy + if HAS_WEIGHT: + wdy = dy * w + dw += dy * xhat + if HAS_BIAS: + db += dy + if not IS_RMS_NORM: + c1 = tl.sum(xhat * wdy, axis=0) / N + c2 = tl.sum(wdy, axis=0) / N + dx = (wdy - (xhat * c1 + c2)) * rstd + else: + c1 = tl.sum(xhat * wdy, axis=0) / N + dx = (wdy - xhat * c1) * rstd + if HAS_DRESIDUAL: + dres = tl.load(DRESIDUAL + cols, mask=mask, other=0).to(tl.float32) + dx += dres + # Write dx + if STORE_DRESIDUAL: + tl.store(DRESIDUAL_IN + cols, dx, mask=mask) + tl.store(DX + cols, dx, mask=mask) + + X += stride_x_row + if HAS_DRESIDUAL: + DRESIDUAL += stride_dres_row + if STORE_DRESIDUAL: + DRESIDUAL_IN += stride_dres_in_row + if RECOMPUTE_OUTPUT: + Y += stride_y_row + DY += stride_dy_row + DX += stride_dx_row + if HAS_WEIGHT: + tl.store(DW + row_block_id * N + cols, dw, mask=mask) + if HAS_BIAS: + tl.store(DB + row_block_id * N + cols, db, mask=mask) + + +def layer_norm_bwd( + dy: torch.Tensor, + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + eps: float, + mean: torch.Tensor, + rstd: torch.Tensor, + dresidual: torch.Tensor = None, + has_residual: bool = False, + is_rms_norm: bool = False, + x_dtype: torch.dtype = None, + recompute_output: bool = False, +): + M, N = x.shape + # allocate output + dx = torch.empty_like(x) if x_dtype is None else torch.empty(M, N, dtype=x_dtype, device=x.device) + dresidual_in = torch.empty_like(x) if has_residual and dx.dtype != x.dtype else None + y = torch.empty(M, N, dtype=dy.dtype, device=dy.device) if recompute_output else None + + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(N)) + if N > BLOCK_N: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + sm_count = get_multiprocessor_count(x.device.index) + _dw = torch.empty((sm_count, N), dtype=torch.float32, device=weight.device) if weight is not None else None + _db = torch.empty((sm_count, N), dtype=torch.float32, device=bias.device) if bias is not None else None + rows_per_program = math.ceil(M / sm_count) + grid = (sm_count,) + layer_norm_bwd_kernel[grid]( + x, + weight, + bias, + y, + dy, + dx, + _dw, + _db, + dresidual, + dresidual_in, + mean, + rstd, + x.stride(0), + 0 if not recompute_output else y.stride(0), + dy.stride(0), + dx.stride(0), + dresidual.stride(0) if dresidual is not None else 0, + dresidual_in.stride(0) if dresidual_in is not None else 0, + M, + N, + eps, + rows_per_program, + is_rms_norm, + BLOCK_N, + dresidual is not None, + dresidual_in is not None, + weight is not None, + bias is not None, + ) + dw = _dw.sum(0).to(weight.dtype) if weight is not None else None + db = _db.sum(0).to(bias.dtype) if bias is not None else None + # Don't need to compute dresidual_in separately in this case + if has_residual and dx.dtype == x.dtype: + dresidual_in = dx + return (dx, dw, db, dresidual_in) if not recompute_output else (dx, dw, db, dresidual_in, y) + + +class LayerNormLinearQuantFn(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual=None, + eps=1e-6, + prenorm=False, + residual_in_fp32=False, + is_rms_norm=False, + ): + x_shape_og = x.shape + # reshape input data into 2D tensor + x = x.reshape(-1, x.shape[-1]) + if residual is not None: + assert residual.shape == x_shape_og + residual = residual.reshape(-1, residual.shape[-1]) + residual_dtype = residual.dtype if residual is not None else (torch.float32 if residual_in_fp32 else None) + y, mean, rstd, residual_out = layer_norm_fwd_quant( + x, + norm_weight, + norm_bias, + eps, + residual, + out_dtype=None if not torch.is_autocast_enabled() else torch.get_autocast_gpu_dtype(), + residual_dtype=residual_dtype, + is_rms_norm=is_rms_norm, + ) + y = y.reshape(x_shape_og) + dtype = torch.get_autocast_gpu_dtype() if torch.is_autocast_enabled() else y.dtype + linear_weight = weight_quant(linear_weight).to(dtype) + linear_bias = linear_bias.to(dtype) if linear_bias is not None else None + out = F.linear(y.to(linear_weight.dtype), linear_weight, linear_bias) + # We don't store y, will be recomputed in the backward pass to save memory + ctx.save_for_backward(residual_out, norm_weight, norm_bias, linear_weight, mean, rstd) + ctx.x_shape_og = x_shape_og + ctx.eps = eps + ctx.is_rms_norm = is_rms_norm + ctx.has_residual = residual is not None + ctx.prenorm = prenorm + ctx.x_dtype = x.dtype + ctx.linear_bias_is_none = linear_bias is None + return out if not prenorm else (out, residual_out.reshape(x_shape_og)) + + @staticmethod + @input_guard + def backward(ctx, dout, *args): + x, norm_weight, norm_bias, linear_weight, mean, rstd = ctx.saved_tensors + dout = dout.reshape(-1, dout.shape[-1]) + dy = F.linear(dout, linear_weight.t()) + dlinear_bias = None if ctx.linear_bias_is_none else dout.sum(0) + assert dy.shape == x.shape + if ctx.prenorm: + dresidual = args[0] + dresidual = dresidual.reshape(-1, dresidual.shape[-1]) + assert dresidual.shape == x.shape + else: + dresidual = None + dx, dnorm_weight, dnorm_bias, dresidual_in, y = layer_norm_bwd( + dy, + x, + norm_weight, + norm_bias, + ctx.eps, + mean, + rstd, + dresidual, + ctx.has_residual, + ctx.is_rms_norm, + x_dtype=ctx.x_dtype, + recompute_output=True, + ) + dlinear_weight = torch.einsum("bo,bi->oi", dout, y) + return ( + dx.reshape(ctx.x_shape_og), + dnorm_weight, + dnorm_bias, + dlinear_weight, + dlinear_bias, + dresidual_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, + None, + None, + None, + None, + ) + + +def layer_norm_linear_quant_fn( + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual=None, + eps=1e-6, + prenorm=False, + residual_in_fp32=False, + is_rms_norm=False, +): + return LayerNormLinearQuantFn.apply( + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual, + eps, + prenorm, + residual_in_fp32, + is_rms_norm, + ) + + +def rms_norm_linear_quant( + x: torch.Tensor, + norm_weight: torch.Tensor, + norm_bias: torch.Tensor, + linear_weight: torch.Tensor, + linear_bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, +): + return layer_norm_linear_quant_fn( + x=x, + norm_weight=norm_weight, + norm_bias=norm_bias, + linear_weight=linear_weight, + linear_bias=linear_bias, + residual=residual, + eps=eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + is_rms_norm=True, + ) + + +@require_version("triton>=3.0", "Triton >= 3.0 is required to do online quantization.") +def bit_linear(x, weight, bias=None, norm_weight=None, norm_bias=None, eps=1e-8): + """ + A functional version of BitLinear that applies quantization to activations and weights. + + Args: + x: Input tensor with shape [n, d]. + weight: Weight tensor with shape [out_features, in_features]. + bias: Bias tensor with shape [out_features] (optional). + norm_weight: Weight tensor for RMS normalization with shape [in_features]. + norm_bias: Bias tensor for RMS normalization with shape [in_features]. + eps: A small constant for numerical stability in normalization. + + Returns: + Output tensor with shape [n, out_features]. + """ + return layer_norm_linear_quant_fn( + x, + norm_weight, + norm_bias, + weight, + bias, + is_rms_norm=True, + ) + + +class BitLinear(nn.Linear): + """ + A custom linear layer that applies quantization on both activations and weights. + This is primarily for training; kernel optimization is needed for efficiency in deployment. + """ + + def __init__( + self, + in_features: int, + out_features: int, + bias: bool = False, + norm_eps: float = 1e-8, + ): + """ + Initializes the BitLinear layer. + + Args: + in_features: Size of each input sample. + out_features: Size of each output sample. + bias: If set to False, the layer will not learn an additive bias. Default: True. + """ + # Initialize the superclass nn.Linear with the given parameters + super().__init__(in_features, out_features, bias=bias) + + self.norm = RMSNorm(in_features, eps=norm_eps) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({super().extra_repr()}, norm_eps={self.norm.eps})" + + def forward(self, x): + """ + Overrides the forward pass to include quantization. + + Args: + x: An input tensor with shape [n, d]. + + Returns: + An output tensor with shape [n, d]. + """ + # Weight tensor + w = self.weight + + # Apply RMS normalization to the input + x_norm = self.norm(x) + + # Apply quantization to both activations and weights + # Uses Straight-Through Estimator (STE) trick with .detach() for gradient flow + x_quant = x_norm + (activation_quant(x_norm) - x_norm).detach() + w_quant = w + (weight_quant(w) - w).detach() + # Perform linear operation with quantized values + y = F.linear(x_quant, w_quant) + + return y + + +class FusedBitLinear(BitLinear): + """ + A custom linear layer that applies quantization on both activations and weights. + This is primarily for training; kernel optimization is needed for efficiency in deployment. + """ + + def __init__(self, in_features, out_features, bias=False): + """ + Initializes the BitLinear layer. + + Args: + in_features: Size of each input sample. + out_features: Size of each output sample. + bias: If set to False, the layer will not learn an additive bias. Default: True. + """ + # Initialize the superclass nn.Linear with the given parameters + super().__init__(in_features, out_features, bias=bias) + + def forward(self, x): + return layer_norm_linear_quant_fn( + x, + self.norm.weight, + self.norm.bias, + self.weight, + self.bias, + is_rms_norm=True, + ) diff --git a/code/flash-linear-attention/fla/modules/fused_cross_entropy.py b/code/flash-linear-attention/fla/modules/fused_cross_entropy.py new file mode 100644 index 0000000000000000000000000000000000000000..e08481b40bb34ad402f6f7545f574a4f935893f7 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/fused_cross_entropy.py @@ -0,0 +1,418 @@ + +# Copyright (c) 2023, Tri Dao. + +from typing import Any + +import torch +import torch.nn as nn +import triton +import triton.language as tl + +from fla.ops.utils.op import exp, log +from fla.utils import input_guard + +# `all_gather_into_tensor` and `reduce_scatter_tensor` are new placeholders for +# `_all_gather_base` and `_reduce_scatter_base`. They require the most recent +# version of PyTorch. The following 2 lines are for backward compatibility with +# older PyTorch. +if "all_gather_into_tensor" not in dir(torch.distributed): + torch.distributed.all_gather_into_tensor = torch.distributed._all_gather_base + + +@triton.heuristics({ + "HAS_SMOOTHING": lambda args: args["label_smoothing"] > 0.0, +}) +@triton.jit +def cross_entropy_fwd_kernel( + loss_ptr, # data ptrs + lse_ptr, + z_loss_ptr, + logits_ptr, + labels_ptr, + label_smoothing, + logit_scale, + lse_square_scale, + ignore_index, + total_classes, + class_start_idx, # Useful for tensor parallel when each rank only has a subset of classes + n_cols, # shapes + n_rows, + logits_row_stride, # strides + BLOCK_SIZE: tl.constexpr, + HAS_SMOOTHING: tl.constexpr, + # if SPLIT (e.g. tensor parallel), don't include the LSE in the loss since it's not the final LSE + SPLIT: tl.constexpr, +): + row_idx = tl.program_id(0) + col_block_idx = tl.program_id(1) + logits_ptr = logits_ptr + row_idx * logits_row_stride.to(tl.int64) + col_offsets = col_block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + label_idx = tl.load(labels_ptr + row_idx) + logits = tl.load(logits_ptr + col_offsets, mask=col_offsets < n_cols, other=-float("inf")) + logits = logits.to(tl.float32) * logit_scale + max_logits = tl.max(logits, 0) + if HAS_SMOOTHING: + sum_logits = tl.sum(tl.where(col_offsets < n_cols, logits, 0.0), 0) + lse = log(tl.sum(exp(logits - max_logits), 0)) + max_logits + tl.store(lse_ptr + col_block_idx * n_rows + row_idx, lse) + if label_idx == ignore_index: + loss = 0.0 + z_loss = 0.0 + else: + label_idx -= class_start_idx + if label_idx >= col_block_idx * BLOCK_SIZE and label_idx < min( + n_cols, (col_block_idx + 1) * BLOCK_SIZE, + ): + logits_label = tl.load(logits_ptr + label_idx) * logit_scale + if HAS_SMOOTHING: + loss = ( + (lse if not SPLIT else 0.0) + - label_smoothing * sum_logits / total_classes + - (1 - label_smoothing) * logits_label + ) + else: + loss = (lse if not SPLIT else 0.0) - logits_label + else: + # If label is out of bounds, we set the CE loss to 0.0. But we still want the label_smoothing loss + if HAS_SMOOTHING: + loss = label_smoothing * ((lse if not SPLIT else 0.0) - sum_logits / total_classes) + else: + loss = 0.0 + if not SPLIT: + z_loss = lse_square_scale * lse * lse + loss += z_loss + else: + z_loss = 0.0 + tl.store(loss_ptr + col_block_idx * n_rows + row_idx, loss) + if not SPLIT: + tl.store(z_loss_ptr + col_block_idx * n_rows + row_idx, z_loss) + + +@triton.heuristics({ + "HAS_SMOOTHING": lambda args: args["label_smoothing"] > 0.0, +}) +@triton.jit +def cross_entropy_bwd_kernel( + dlogits_ptr, # data ptrs + dloss_ptr, + logits_ptr, + lse_ptr, + labels_ptr, + label_smoothing, + logit_scale, + lse_square_scale, + ignore_index, + total_classes, + class_start_idx, # Useful for tensor parallel when each rank only has a subset of classes + n_cols, # shapes + logits_row_stride, # strides + dlogits_row_stride, + dloss_row_stride, + BLOCK_SIZE: tl.constexpr, + HAS_SMOOTHING: tl.constexpr, +): + row_idx = tl.program_id(0) + col_block_idx = tl.program_id(1) + logits_ptr = logits_ptr + row_idx * logits_row_stride.to(tl.int64) + dlogits_ptr = dlogits_ptr + row_idx * dlogits_row_stride.to(tl.int64) + col_offsets = col_block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + label_idx = tl.load(labels_ptr + row_idx) + if label_idx != ignore_index: + dloss = tl.load(dloss_ptr + row_idx * dloss_row_stride) + else: + dloss = 0.0 + logits = tl.load(logits_ptr + col_offsets, mask=col_offsets < n_cols, other=-float("inf")).to( + tl.float32, + ) * logit_scale + lse = tl.load(lse_ptr + row_idx) + probs = exp(logits - lse) + probs += 2.0 * lse_square_scale * lse * probs + label_idx -= class_start_idx + if HAS_SMOOTHING: + smooth_negative = label_smoothing / total_classes + probs = tl.where(col_offsets == label_idx, probs - (1 - label_smoothing), probs) - smooth_negative + else: + probs = tl.where(col_offsets == label_idx, probs - 1.0, probs) + tl.store(dlogits_ptr + col_offsets, (dloss * logit_scale) * probs, mask=col_offsets < n_cols) + + +def fused_cross_entropy_forward( + logits: torch.Tensor, + target: torch.Tensor, + label_smoothing: float = 0.0, + logit_scale: float = 1.0, + lse_square_scale: float = 0.0, + ignore_index: int = -100, + process_group=None, +): + n_rows, n_cols = logits.shape + assert target.shape == (n_rows,) + world_size = 1 if process_group is None else torch.distributed.get_world_size(process_group) + total_classes = world_size * n_cols + rank = 0 if process_group is None else torch.distributed.get_rank(process_group) + class_start_idx = rank * n_cols + + if logits.stride(-1) != 1: + logits = logits.contiguous() + # Set these similar to https://github.com/openai/triton/blob/main/python/tutorials/02-fused-softmax.py + MAX_BLOCK_SIZE = 64 * 1024 + BLOCK_SIZE = min(triton.next_power_of_2(n_cols), MAX_BLOCK_SIZE) + num_warps = ( + 4 + if BLOCK_SIZE < 2048 + else (8 if BLOCK_SIZE < 8192 else (16 if BLOCK_SIZE < 128 * 1024 else 32)) + ) + # We may split the lse computation across multiple blocks, then do a reduction + # lse(local_lse) to get the final LSE. This is faster for large n_cols (e.g., > 64k) + # where having just one thread block processing more than 64k elements is slow. + split = world_size > 1 or n_cols > MAX_BLOCK_SIZE + n_splits = (n_cols + BLOCK_SIZE - 1) // BLOCK_SIZE + loss_shape = (n_splits, n_rows) if n_splits > 1 else (n_rows,) + losses = torch.empty(*loss_shape, dtype=torch.float, device=logits.device) + lse = torch.empty(*loss_shape, dtype=torch.float, device=logits.device) + z_losses = torch.empty(*loss_shape, dtype=torch.float, device=logits.device) + + cross_entropy_fwd_kernel[(n_rows, n_splits)]( + losses, # data ptrs + lse, + z_losses, + logits, + target, + label_smoothing, + logit_scale, + lse_square_scale, + ignore_index, + total_classes, + class_start_idx, + n_cols, # shapes + n_rows, + logits.stride(0), # strides + BLOCK_SIZE=BLOCK_SIZE, # constants + num_warps=num_warps, + SPLIT=split, + ) + + if split: + # If there's no label_smoothing, if target are in the vocab of this partition, losses contains + # - predicted logit, and 0 otherwise. + # If there's label_smoothing=0.1, for target in the vocab of this partition, losses contains + # -0.9 * predicted logit - 0.1 * sum logit / total_classes. + # For target not in the vocab of this partition, losses contains + # -0.1 * sum logit / total_classes. + if n_splits > 1: + lse = torch.logsumexp(lse, dim=0) + losses = losses.sum(dim=0) + if world_size > 1: + lse_allgather = torch.empty(world_size, n_rows, dtype=lse.dtype, device=lse.device) + torch.distributed.all_gather_into_tensor(lse_allgather, lse, group=process_group) + handle_losses = torch.distributed.all_reduce( + losses, op=torch.distributed.ReduceOp.SUM, group=process_group, async_op=True, + ) + lse = torch.logsumexp(lse_allgather, dim=0) + handle_losses.wait() + # After the allreduce, if there's no label_smoothing, the total losses are - predicted_logit, + # we just have to add the (global) lse. + # If there's label_smoothing=0.1, the total losses are + # -0.9 * predicted_logit - 0.1 * sum logit / total_classes. + # Again, we just have to add the (global) lse. + losses += lse + if lse_square_scale != 0.0: + z_losses = lse_square_scale * lse.square() + z_losses.masked_fill_(target == ignore_index, 0.0) + losses += z_losses + else: + z_losses = torch.zeros_like(losses) + losses.masked_fill_(target == ignore_index, 0.0) + + return losses, z_losses, lse, total_classes, class_start_idx + + +class CrossEntropyLossFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + logits, + target, + label_smoothing=0.0, + logit_scale=1.0, + lse_square_scale=0.0, + ignore_index=-100, + inplace_backward=False, + process_group=None, + ): + losses, z_losses, lse, total_classes, class_start_idx = fused_cross_entropy_forward( + logits, + target, + label_smoothing, + logit_scale, + lse_square_scale, + ignore_index, + process_group, + ) + ctx.save_for_backward(logits, lse, target) + ctx.mark_non_differentiable(z_losses) + ctx.label_smoothing = label_smoothing + ctx.logit_scale = logit_scale + ctx.lse_square_scale = lse_square_scale + ctx.ignore_index = ignore_index + ctx.total_classes = total_classes + ctx.class_start_idx = class_start_idx + ctx.inplace_backward = inplace_backward + + return losses, z_losses + + @staticmethod + @input_guard + def backward(ctx, grad_losses, grad_z_losses): + del grad_z_losses # z_losses are only for logging. + + logits, lse, target = ctx.saved_tensors + dlogits = logits if ctx.inplace_backward else torch.empty_like(logits) + n_rows, n_cols = logits.shape + BLOCK_SIZE = min(triton.next_power_of_2(n_cols), 4 * 1024) + num_warps = 4 if BLOCK_SIZE < 2048 else (8 if BLOCK_SIZE < 8192 else 16) + def grid(META): return (n_rows, triton.cdiv(n_cols, META["BLOCK_SIZE"])) # noqa + cross_entropy_bwd_kernel[grid]( + dlogits, # data ptrs + grad_losses, + logits, + lse, + target, + ctx.label_smoothing, + ctx.logit_scale, + ctx.lse_square_scale, + ctx.ignore_index, + ctx.total_classes, + ctx.class_start_idx, + n_cols, # shapes + logits.stride(0), # strides + dlogits.stride(0), + grad_losses.stride(0), + BLOCK_SIZE=BLOCK_SIZE, # constants + num_warps=num_warps, + ) + return dlogits, None, None, None, None, None, None, None, None + + +def cross_entropy_loss( + logits: torch.Tensor, + target: torch.Tensor, + label_smoothing: float = 0.0, + logit_scale: float = 1.0, + lse_square_scale: float = 0.0, + ignore_index=-100, + inplace_backward: bool = False, + process_group=None, +) -> tuple[torch.Tensor, torch.Tensor]: + """ + Arguments: + logits: [batch, vocab_size] + target: [batch,] + label_smoothing: float + logit_scale: float. + Multiply logits by this scale before calculating the loss. + lse_square_scale: float. + If > 0, we add lse_square_scale * lse(logits) ^ 2 to the loss. + This is also referred to as "z-loss". + ignore_index: int. + If target == ignore_index, the loss is set to 0.0. + inplace_backward: bool. + If True, we do the backward pass in-place by modifying the logits. + This saves memory. + process_group: + if not None, we're doing Tensor Parallel: each process is responsible for + one part of the vocab. The loss will be aggregated across processes. + Returns: + losses: [batch,], float + z_losses: [batch,], float + """ + return CrossEntropyLossFunction.apply( + logits, + target, + label_smoothing, + logit_scale, + lse_square_scale, + ignore_index, + inplace_backward, + process_group, + ) + + +class FusedCrossEntropyLoss(nn.Module): + def __init__( + self, + ignore_index: int = -100, + reduction: str = "mean", + label_smoothing: float = 0.0, + logit_scale: float = 1.0, + lse_square_scale: float = 0.0, + inplace_backward: bool = False, + process_group: Any = None, + return_z_loss: bool = False, + ): + """ + Arguments: + ignore_index: int. If target == ignore_index, the loss is set to 0.0. + label_smoothing: float + lse_square_scale: float. If > 0, we add lse_square_scale * lse(logits) ^ 2 to the loss. + This is also referred to as "z-loss". + inplace_backward: bool. If True, we do the backward pass in-place by modifying the logits. + This saves memory. + process_group: if not None, we're doing Tensor Parallel: each process is responsible for + one part of the vocab. The loss will be aggregated across processes. + return_z_loss: bool. If True, we return the component of the loss contributed by + the lse_square_scale value. This value is only for logging and does not support + backprop. + """ + super().__init__() + if reduction not in ["mean", "none", "sum"]: + raise NotImplementedError("Only support reduction = 'mean' or 'none' or 'sum'") + self.ignore_index = ignore_index + self.reduction = reduction + self.label_smoothing = label_smoothing + self.logit_scale = logit_scale + self.lse_square_scale = lse_square_scale + self.inplace_backward = inplace_backward + self.process_group = process_group + self.return_z_loss = return_z_loss + + def forward(self, input, target): + """ + Arguments: + input: (batch, vocab_size) + target: (batch,) + Returns: + losses: (batch,) if reduction is 'none', else (1,), dtype float + z_loss: (batch,) if reduction is 'none', else (1,), dtype float (if self.return_z_loss) + """ + assert input.is_cuda and target.is_cuda, "Only support CUDA tensors" + loss, z_loss = cross_entropy_loss( + input, + target, + label_smoothing=self.label_smoothing, + logit_scale=self.logit_scale, + lse_square_scale=self.lse_square_scale, + ignore_index=self.ignore_index, + inplace_backward=self.inplace_backward, + process_group=self.process_group, + ) + if self.reduction == "mean": + loss = loss.sum() / (target != self.ignore_index).sum() + elif self.reduction == "sum": + loss = loss.sum() + else: + loss = loss + + if not self.return_z_loss: + return loss + + if self.reduction == "mean": + z_loss = z_loss.sum() / (target != self.ignore_index).sum() + elif self.reduction == "sum": + z_loss = z_loss.sum() + else: + z_loss = z_loss + + return loss, z_loss diff --git a/code/flash-linear-attention/fla/modules/fused_kl_div.py b/code/flash-linear-attention/fla/modules/fused_kl_div.py new file mode 100644 index 0000000000000000000000000000000000000000..150ad57aba55f2cf6c76503d551c864aa5f96c53 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/fused_kl_div.py @@ -0,0 +1,322 @@ + + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl + +from fla.ops.utils.op import exp, log +from fla.utils import input_guard, is_amd + +# The hard limit of TRITON_MAX_TENSOR_NUMEL is 1048576 +# https://github.com/triton-lang/triton/blob/ba42a5c68fd0505f8c42f4202d53be0f8d9a5fe0/python/triton/language/core.py#L19 +# However, setting limit as 65536 as in LayerNorm tutorial is faster because of less register spilling +# The optimal maximum block size depends on your hardware, your kernel, and your dtype +MAX_FUSED_SIZE = 65536 // 2 +STATIC_WARPS = 32 if not is_amd else 16 + + +@triton.jit +def kl_div_kernel( + logits, + target_logits, + loss, + s_logits, + s_loss, + reduction: tl.constexpr, + N: tl.constexpr, + V: tl.constexpr, + BV: tl.constexpr, +): + # https://github.com/triton-lang/triton/issues/1058 + # If N*V is too large, i_n * stride will overflow out of int32, so we convert to int64 + i_n = tl.program_id(0).to(tl.int64) + + logits += i_n * s_logits + target_logits += i_n * s_logits + + # m is the max value. use the notation from the paper + sm = float('-inf') + tm = float('-inf') + # d is the sum. use the notation from the paper + sd, td = 0.0, 0.0 + + NV = tl.cdiv(V, BV) + for iv in range(0, NV): + o_x = iv * BV + tl.arange(0, BV) + # for student + b_sl = tl.load(logits + o_x, mask=o_x < V, other=float('-inf')) + b_sm = tl.max(b_sl) + m_new = tl.maximum(sm, b_sm) + sd = sd * exp(sm - m_new) + tl.sum(exp(b_sl - m_new)) + sm = m_new + # for teacher + b_tl = tl.load(target_logits + o_x, mask=o_x < V, other=float('-inf')) + b_tm = tl.max(b_tl) + m_new = tl.maximum(tm, b_tm) + td = td * exp(tm - m_new) + tl.sum(exp(b_tl - m_new)) + tm = m_new + + b_loss = 0. + # KL(y_true || y) = exp(y_true) * (log(y_true) - log(y)) + for iv in range(0, NV): + o_x = iv * BV + tl.arange(0, BV) + b_sl = tl.load(logits + o_x, mask=o_x < V, other=float('-inf')) + b_tl = tl.load(target_logits + o_x, mask=o_x < V, other=float('-inf')) + b_sp_log = b_sl - sm - log(sd) + b_tp_log = b_tl - tm - log(td) + b_sp = exp(b_sp_log) + b_tp = exp(b_tp_log) + b_kl = tl.where(o_x < V, b_tp * (b_tp_log - b_sp_log), 0) + b_dl = -b_tp + b_sp + b_loss += tl.sum(b_kl) + if reduction == 'batchmean': + b_dl = b_dl / N + tl.store(logits + o_x, b_dl, mask=o_x < V) + + # Normalize the loss by the number of elements if reduction is 'batchmean' + if reduction == 'batchmean': + b_loss = b_loss / N + + tl.store(loss + i_n * s_loss, b_loss) + + +@triton.jit +def elementwise_mul_kernel( + x, + g, + N: tl.constexpr, + B: tl.constexpr, +): + """ + This function multiplies each element of the tensor pointed by x with the value pointed by g. + The multiplication is performed in-place on the tensor pointed by x. + + Parameters: + x: + Pointer to the input tensor. + g: + Pointer to the gradient output value. + N (int): + The number of columns in the input tensor. + B (int): + The block size for Triton operations. + """ + + # Get the program ID and convert it to int64 to avoid overflow + i_x = tl.program_id(0).to(tl.int64) + o_x = i_x * B + tl.arange(0, B) + + # Load the gradient output value + b_g = tl.load(g) + b_x = tl.load(x + o_x, mask=o_x < N) + tl.store(x + o_x, b_x * b_g, mask=o_x < N) + + +def fused_kl_div_forward( + x: torch.Tensor, + target_x: torch.Tensor, + weight: torch.Tensor, + target_weight: torch.Tensor, + reduction: str = 'batchmean', +): + device = x.device + + # ideally, we would like to achieve the same memory consumption as [N, H], + # so the expected chunk size should be: + # NC = ceil(V / H) + # C = ceil(N / NC) + # for ex: N = 4096*4, V = 32000, H = 4096 ==> NC = 8, C = ceil(N / NC) = 2048 + N, H, V = *x.shape, weight.shape[0] + BV = min(MAX_FUSED_SIZE, triton.next_power_of_2(V)) + # TODO: in real cases, we may need to limit the number of chunks NC to + # ensure the precisions of accumulated gradients + NC = min(8, triton.cdiv(V, H)) + C = triton.next_power_of_2(triton.cdiv(N, NC)) + NC = triton.cdiv(N, C) + + dx = torch.zeros_like(x, device=device) + dw = torch.zeros_like(weight, device=device) if weight is not None else None + # we use fp32 for loss accumulator + loss = torch.zeros(N, dtype=torch.float32, device=device) + + for ic in range(NC): + start, end = ic * C, min((ic + 1) * C, N) + # [C, N] + c_sx = x[start:end] + c_tx = target_x[start:end] + # when doing matmul, use the original precision + # [C, V] + c_sl = F.linear(c_sx, weight) + c_tl = F.linear(c_tx, target_weight) + + # unreduced loss + c_loss = loss[start:end] + + # Here we calculate the gradient of c_sx in place so we can save memory. + kl_div_kernel[(c_sx.shape[0],)]( + logits=c_sl, + target_logits=c_tl, + loss=c_loss, + s_logits=c_sl.stride(-2), + s_loss=c_loss.stride(-1), + reduction=reduction, + N=N, + V=V, + BV=BV, + num_warps=STATIC_WARPS, + ) + + # gradient of logits is computed in-place by the above triton kernel and is of shape: C x V + # thus dx[start: end] should be of shape: C x H + # additionally, since we are chunking the inputs, observe that the loss and gradients are calculated only + # on `n_non_ignore` tokens. However, the gradient of the input should be calculated for all tokens. + # Thus, we need an additional scaling factor of (n_non_ignore/total) to scale the gradients. + # [C, H] + + dx[start:end] = torch.mm(c_sl, weight) + + if weight is not None: + torch.addmm(input=dw, mat1=c_sl.t(), mat2=c_sx, out=dw) + + loss = loss.sum() + return loss, dx, dw + + +def fused_kl_div_backward( + do: torch.Tensor, + dx: torch.Tensor, + dw: torch.Tensor, +): + # If cross entropy is the last layer, do is 1.0. Skip the mul to save time + if torch.ne(do, torch.tensor(1.0, device=do.device)): + # We use a Triton kernel instead of a PyTorch operation because modifying inputs in-place + # for gradient storage and backward multiple times causes anomalies with PyTorch but not with Triton. + N, H = dx.shape + B = min(MAX_FUSED_SIZE, triton.next_power_of_2(H)) + + elementwise_mul_kernel[(triton.cdiv(N * H, B),)]( + x=dx, + g=do, + N=N*H, + B=B, + num_warps=STATIC_WARPS, + ) + + # handle dw + if dw is not None: + V, H = dw.shape + elementwise_mul_kernel[(triton.cdiv(V * H, B),)]( + x=dw, + g=do, + N=V*H, + B=B, + num_warps=STATIC_WARPS, + ) + + return dx, dw + + +class FusedKLDivLossFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + target_x: torch.Tensor, + weight: torch.Tensor, + target_weight: torch.Tensor, + reduction: str, + ): + loss, dx, dw = fused_kl_div_forward( + x=x, + target_x=target_x, + weight=weight, + target_weight=target_weight, + reduction=reduction, + ) + ctx.save_for_backward(dx, dw) + return loss + + @staticmethod + @input_guard + def backward(ctx, do): + dx, dw = ctx.saved_tensors + dx, dw = fused_kl_div_backward(do, dx, dw) + return dx, None, dw, None, None + + +def fused_kl_div_loss( + x: torch.Tensor, + target_x: torch.Tensor, + weight: torch.Tensor, + target_weight: torch.Tensor, + reduction: str = 'batchmean', +) -> tuple[torch.Tensor, torch.Tensor]: + """ + Args: + x (torch.Tensor): [batch_size * seq_len, hidden_size] + target_x (torch.Tensor): [batch_size * seq_len, hidden_size] + weight (torch.Tensor): [vocab_size, hidden_size] + where `vocab_size` is the number of classes. + target_weight (torch.Tensor): [vocab_size, hidden_size] + where `vocab_size` is the number of classes. + reduction: + Specifies the reduction to apply to the output: 'batchmean'. Default: 'batchmean'. + Returns: + loss + """ + return FusedKLDivLossFunction.apply( + x, + target_x, + weight, + target_weight, + reduction, + ) + + +class FusedKLDivLoss(nn.Module): + + def __init__( + self, + reduction: str = 'batchmean', + ): + """ + Args: + reduction: + Specifies the reduction to apply to the output: 'batchmean'. Default: 'batchmean'. + """ + super().__init__() + + assert reduction in ['batchmean'], f"reduction: {reduction} is not supported" + + self.reduction = reduction + + def forward( + self, + x: torch.Tensor, + target_x: torch.Tensor, + weight: torch.Tensor, + target_weight: torch.Tensor, + ): + """ + Args: + x (torch.Tensor): [batch_size * seq_len, hidden_size] + target_x (torch.Tensor): [batch_size * seq_len, hidden_size] + weight (torch.Tensor): [vocab_size, hidden_size] + where `vocab_size` is the number of classes. + target_weight (torch.Tensor): [vocab_size, hidden_size] + where `vocab_size` is the number of classes. + Returns: + loss + """ + loss = fused_kl_div_loss( + x=x, + target_x=target_x, + weight=weight, + target_weight=target_weight, + reduction=self.reduction, + ) + return loss diff --git a/code/flash-linear-attention/fla/modules/fused_linear_cross_entropy.py b/code/flash-linear-attention/fla/modules/fused_linear_cross_entropy.py new file mode 100644 index 0000000000000000000000000000000000000000..42d2020504cd8fc6ee555cba13caa0e246b25657 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/fused_linear_cross_entropy.py @@ -0,0 +1,630 @@ + +# Code adapted from +# https://github.com/linkedin/Liger-Kernel/blob/main/src/liger_kernel/ops/fused_linear_cross_entropy.py + +from functools import partial + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl +try: + from torch.distributed import DeviceMesh +except ImportError: + DeviceMesh = None +try: + from torch.distributed.tensor import Replicate, Shard, distribute_module +except ImportError: + Replicate = None + Shard = None + distribute_module = None +try: + from torch.distributed.tensor.parallel import ParallelStyle +except ImportError: + class ParallelStyle: + pass + +from fla.ops.utils import logsumexp_fwd +from fla.ops.utils.op import exp +from fla.utils import input_guard, is_amd + +try: + from torch.distributed.tensor import DTensor +except (ImportError, AttributeError): + DTensor = None + +# The hard limit of TRITON_MAX_TENSOR_NUMEL is 1048576 +# https://github.com/triton-lang/triton/blob/ba42a5c68fd0505f8c42f4202d53be0f8d9a5fe0/python/triton/language/core.py#L19 +# However, setting limit as 65536 as in LayerNorm tutorial is faster because of less register spilling +# The optimal maximum block size depends on your hardware, your kernel, and your dtype +MAX_FUSED_SIZE = 65536 // 2 +STATIC_WARPS = 32 if not is_amd else 16 + + +@triton.jit +def cross_entropy_kernel( + logits, + lse, + target, + loss, + total, + ignore_index, + label_smoothing: tl.constexpr, + logit_scale: tl.constexpr, + reduction: tl.constexpr, + V: tl.constexpr, + BV: tl.constexpr, +): + """ + This kernel computes both cross entropy loss and the gradient of the input. + We only consider hard label + mean reduction for now. + Please refer to https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html for the math. + + Args: + logits: + Pointer to logits tensor. + lse: + Pointer to logsumexp tensor. + target: Pointer to target tensor. + loss: + Pointer to tensor to store the loss. + V (int): + The number of columns in the input tensor. + total (int): + The number of non-ignored classes. + ignore_index (int): + The index to ignore in the target. + label_smoothing (float): + The amount of smoothing when computing the loss, where 0.0 means no smoothing. + reduction (str): + The string for the reduction to apply + BV (int): + The block size for vocab. + """ + + # https://github.com/triton-lang/triton/issues/1058 + # If B*T*V is too large, i_n * stride will overflow out of int32, so we convert to int64 + i_n = tl.program_id(0).to(tl.int64) + NV = tl.cdiv(V, BV) + + # 1. Load target first because if the target is ignore_index, we can return right away + b_y = tl.load(target + i_n) + + # 2. locate the start index + logits += i_n * V + + if b_y == ignore_index: + # set all x as 0 + for i in range(0, V, BV): + o_v = i + tl.arange(0, BV) + tl.store(logits + o_v, 0.0, mask=o_v < V) + return + + # Online softmax: 2 loads + 1 store (compared with 3 loads + 1 store for the safe softmax) + # Refer to Algorithm 3 in the paper: https://arxiv.org/pdf/1805.02867 + + # 3. [Online softmax] first pass: compute logsumexp + # we did this in anouter kernel + b_l = tl.load(logits + b_y) * logit_scale + b_lse = tl.load(lse + i_n) + + # 4. Calculate the loss + # loss = lse - logits_l + b_loss = b_lse - b_l + + # Label smoothing is a general case of normal cross entropy + # See the full derivation at https://github.com/linkedin/Liger-Kernel/pull/198#issue-2503665310 + b_z = 0.0 + eps = label_smoothing / V + + # We need tl.debug_barrier() as mentioned in + # https://github.com/triton-lang/triton/blob/ba42a5c68fd0505f8c42f4202d53be0f8d9a5fe0/python/triton/ops/cross_entropy.py#L34 + tl.debug_barrier() + + # 5. [Online Softmax] Second pass: compute gradients + # For 'mean' reduction, gradients are normalized by number of non-ignored elements + # dx_y = (softmax(x_y) - 1) / N + # dx_i = softmax(x_i) / N, i != y + # For label smoothing: + # dx_i = (softmax(x_y) - label_smoothing / V) / N, i != y + # dx_y = (softmax(x_y) - label_smoothing / V - (1 - label_smoothing)) / N + # = dx_i - (1 - label_smoothing) / N + for iv in range(0, NV): + o_v = iv * BV + tl.arange(0, BV) + b_logits = tl.load(logits + o_v, mask=o_v < V, other=float('-inf')) * logit_scale + if label_smoothing > 0: + # scale X beforehand to avoid overflow + b_z += tl.sum(tl.where(o_v < V, -eps * b_logits, 0.0)) + b_p = (exp(b_logits - b_lse) - eps) * logit_scale + if reduction == "mean": + b_p = b_p / total + tl.store(logits + o_v, b_p, mask=o_v < V) + + tl.debug_barrier() + + # Orginal loss = H(q, p), with label smoothing regularization = H(q', p) and (label_smoothing / V) = eps + # H(q', p) = (1 - label_smoothing) * H(q, p) + label_smoothing * H(u, p) + # = (1 - label_smoothing) * H(q, p) + eps * sum(logsoftmax(x_i)) + # By using m (global max of xi) and d (sum of e^(xi-m)), we can simplify as: + # = (1 - label_smoothing) * H(q, p) + (-sum(x_i * eps) + label_smoothing * (m + logd)) + # Refer to H(q', p) in section 7 of the paper: + # https://arxiv.org/pdf/1512.00567 + # pytorch: + # https://github.com/pytorch/pytorch/blob/2981534f54d49fa3a9755c9b0855e7929c2527f0/aten/src/ATen/native/LossNLL.cpp#L516 + # See full derivation at https://github.com/linkedin/Liger-Kernel/pull/198#issuecomment-2333753087 + if label_smoothing > 0: + b_loss = b_loss * (1 - label_smoothing) + (b_z + label_smoothing * b_lse) + + # 6. Specially handle the i==y case where `dx_y = (softmax(x_y) - (1 - label_smoothing) / N` + b_l = tl.load(logits + b_y) + + # Normalize the loss by the number of non-ignored elements if reduction is "mean" + if reduction == 'mean': + b_loss = b_loss / total + b_l += (label_smoothing - 1) / total * logit_scale + else: + b_l += (label_smoothing - 1) * logit_scale + + tl.store(loss + i_n, b_loss) + tl.store(logits + b_y, b_l) + + +@triton.jit +def elementwise_mul_kernel( + x, + g, + N: tl.constexpr, + B: tl.constexpr, +): + """ + This function multiplies each element of the tensor pointed by x with the value pointed by g. + The multiplication is performed in-place on the tensor pointed by x. + + Parameters: + x: + Pointer to the input tensor. + g: + Pointer to the gradient output value. + N (int): + The number of columns in the input tensor. + B (int): + The block size for Triton operations. + """ + + # Get the program ID and convert it to int64 to avoid overflow + i_x = tl.program_id(0).to(tl.int64) + o_x = i_x * B + tl.arange(0, B) + + # Load the gradient output value + b_g = tl.load(g) + b_x = tl.load(x + o_x, mask=o_x < N) + tl.store(x + o_x, b_x * b_g, mask=o_x < N) + + +def fused_linear_cross_entropy_forward( + x: torch.Tensor, + target: torch.LongTensor, + weight: torch.Tensor, + bias: torch.Tensor = None, + ignore_index: int = -100, + label_smoothing: float = 0.0, + logit_scale: float = 1.0, + num_chunks: int = 8, + reduction: str = "mean", + use_l2warp: bool = False, + l2_penalty_factor: float = 1e-4, +): + device = x.device + # inputs have shape: [N, H] + # materialized activations will have shape: [N, V] + # the increase in memory = [N, V] + # reduction can be achieved by partitioning the number of tokens N into smaller chunks. + + # ideally, we would like to achieve the same memory consumption as [N, H], + # so the expected chunk size should be: + # NC = ceil(V / H) + # C = ceil(N / NC) + # for ex: N = 4096*4, V = 32000, H = 4096 ==> NC = 8, C = ceil(N / NC) = 2048 + N, H, V = *x.shape, weight.shape[0] + BV = min(MAX_FUSED_SIZE, triton.next_power_of_2(V)) + # TODO: in real cases, we may need to limit the number of chunks NC to + # ensure the precisions of accumulated gradients + NC = min(num_chunks, triton.cdiv(V, H)) + C = triton.next_power_of_2(triton.cdiv(N, NC)) + NC = triton.cdiv(N, C) + + # [N, H] + dx = torch.zeros_like(x, device=device) + # [V, H] + dw = torch.zeros_like(weight, device=device, dtype=torch.float) if weight is not None else None + # [V] + db = torch.zeros_like(bias, device=device, dtype=torch.float) if bias is not None else None + # [N] + loss = torch.zeros(N, device=device, dtype=torch.float) + + total = target.ne(ignore_index).sum().item() + + for ic in range(NC): + start, end = ic * C, min((ic + 1) * C, N) + # [C, N] + c_x = x[start:end] + # when doing matmul, use the original precision + # [C, V] + c_logits = F.linear(c_x, weight, bias) + c_target = target[start:end] + # [C] + # keep lse in fp32 to maintain precision + c_lse = logsumexp_fwd(c_logits, scale=logit_scale, dtype=torch.float) + + # unreduced loss + c_loss = loss[start:end] + if use_l2warp: + c_maxx, c_ids = torch.max(c_logits, -1, keepdim=True) + + # Here we calculate the gradient of c_logits in place so we can save memory. + cross_entropy_kernel[(c_logits.shape[0],)]( + logits=c_logits, + lse=c_lse, + target=c_target, + loss=c_loss, + total=total, + ignore_index=ignore_index, + label_smoothing=label_smoothing, + logit_scale=logit_scale, + reduction=reduction, + V=V, + BV=BV, + num_warps=STATIC_WARPS, + ) + if use_l2warp: + # a. Calculate the L2 gradient w.r.t logits (g_logits_l2) + g_logits_l2 = torch.zeros_like(c_logits) + + # Normalize factor by B*T, which is the 'total' variable here + l2_factor = l2_penalty_factor / total if reduction == 'mean' else l2_penalty_factor + penalty_grad = c_maxx * l2_factor + g_logits_l2.scatter_(-1, c_ids, penalty_grad) + + # b. Backpropagate g_logits_l2 to get its effect on dx, dw, db + # and add it to the main gradients. + # Total_dx = CE_dx + L2_dx + # Total_dw = CE_dw + L2_dw + # Total_db = CE_db + L2_db + if weight is not None: + dw.add_(g_logits_l2.t() @ c_x) + if bias is not None: + db.add_(g_logits_l2.sum(0)) + # The dx contribution must be added to the final dx calculation + dx_l2_contribution = torch.mm(g_logits_l2, weight) + else: + dx_l2_contribution = 0.0 + + # gradient of logits is computed in-place by the above triton kernel and is of shape: C x V + # thus dx should be of shape: C x H + dx[start:end] = torch.mm(c_logits, weight) + dx_l2_contribution + + # keep dw in fp32 to maintain precision + if weight is not None: + dw += c_logits.t() @ c_x + + if bias is not None: + torch.add(input=db, other=c_logits.sum(0), out=db) + + loss = loss.sum() + if dw is not None: + dw = dw.to(weight) + if db is not None: + db = db.to(bias) + return loss, dx, dw, db + + +def fused_linear_cross_entropy_backward( + do: torch.Tensor, + dx: torch.Tensor, + dw: torch.Tensor, + db: torch.Tensor, +): + # If cross entropy is the last layer, do is 1.0. Skip the mul to save time + if torch.ne(do, torch.tensor(1.0, device=do.device)): + # We use a Triton kernel instead of a PyTorch operation because modifying inputs in-place + # for gradient storage and backward multiple times causes anomalies with PyTorch but not with Triton. + N, H = dx.shape + B = min(MAX_FUSED_SIZE, triton.next_power_of_2(H)) + + elementwise_mul_kernel[(triton.cdiv(N * H, B),)]( + x=dx, + g=do, + N=N*H, + B=B, + num_warps=STATIC_WARPS, + ) + + # handle dw + if dw is not None: + V, H = dw.shape + elementwise_mul_kernel[(triton.cdiv(V * H, B),)]( + x=dw, + g=do, + N=V*H, + B=B, + num_warps=STATIC_WARPS, + ) + + if db is not None: + V = db.shape[0] + elementwise_mul_kernel[(triton.cdiv(V, B),)]( + x=db, + g=do, + N=V, + B=B, + num_warps=STATIC_WARPS, + ) + return dx, dw, db + + +class FusedLinearCrossEntropyFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + target: torch.LongTensor, + weight: torch.Tensor, + bias: torch.Tensor = None, + ignore_index: int = -100, + label_smoothing: float = 0.0, + logit_scale: float = 1.0, + num_chunks: int = 8, + reduction: str = "mean", + use_l2warp: bool = False, + l2_penalty_factor: float = 1e-4, + ): + """ + Fusing the last linear layer with cross-entropy loss + Reference: https://github.com/mgmalek/efficient_cross_entropy + + Handle the forward and backward pass of the final linear layer via cross-entropy loss by avoiding + the materialization of the large logits tensor. Since Cross Entropy Loss is the last layer, we can + compute the gradient at the forward pass. By doing so, we don't have to store the x and target + for the backward pass. + + x (torch.Tensor): [batch_size * seq_len, hidden_size] + target (torch.LongTensor): [batch_size * seq_len] + where each value is in [0, vocab_size). + weight (torch.Tensor): [vocab_size, hidden_size] + where `vocab_size` is the number of classes. + bias (Optional[torch.Tensor]): [vocab_size] + where `vocab_size` is the number of classes. + ignore_index: + the index to ignore in the target. + label_smoothing: + the amount of smoothing when computing the loss, where 0.0 means no smoothing. + logit_scale: float = 1.0, + A scaling factor applied to the logits. Default: 1.0 + num_chunks: int + The number of chunks to split the input tensor into for processing. + This can help optimize memory usage and computation speed. + Default: 8 + reduction: + Specifies the reduction to apply to the output: 'mean' | 'sum'. + 'mean': the weighted mean of the output is taken, + 'sum': the output will be summed. + Default: 'mean'. + use_l2warp: bool = False, + Whether to use L2 regularization on the logits to prevent overconfidence. + Default: False + l2_penalty_factor: float = 1e-4, + """ + loss, dx, dw, db = fused_linear_cross_entropy_forward( + x, + target, + weight, + bias, + ignore_index, + label_smoothing, + logit_scale, + num_chunks, + reduction, + use_l2warp, + l2_penalty_factor, + ) + # downcast to dtype and store for backward + ctx.save_for_backward( + dx.detach(), + dw.detach() if weight is not None else None, + db.detach() if bias is not None else None, + ) + return loss + + @staticmethod + @input_guard + def backward(ctx, do): + dx, dw, db = ctx.saved_tensors + dx, dw, db = fused_linear_cross_entropy_backward(do, dx, dw, db) + return dx, None, dw, db, None, None, None, None, None, None, None + + +def fused_linear_cross_entropy_loss( + x: torch.Tensor, + target: torch.LongTensor, + weight: torch.Tensor, + bias: torch.Tensor = None, + ignore_index: int = -100, + label_smoothing: float = 0.0, + logit_scale: float = 1.0, + num_chunks: int = 8, + reduction: str = "mean", + use_l2warp: bool = False, + l2_penalty_factor: float = 1e-4, +) -> tuple[torch.Tensor, torch.Tensor]: + """ + Args: + x (torch.Tensor): [batch_size * seq_len, hidden_size] + target (torch.LongTensor): [batch_size * seq_len] + where each value is in [0, vocab_size). + weight (torch.Tensor): [vocab_size, hidden_size] + where `vocab_size` is the number of classes. + bias (Optional[torch.Tensor]): [vocab_size] + where `vocab_size` is the number of classes. + ignore_index: int. + If target == ignore_index, the loss is set to 0.0. + label_smoothing: float + logit_scale: float + A scaling factor applied to the logits. Default: 1.0 + num_chunks: int + The number of chunks to split the input tensor into for processing. + This can help optimize memory usage and computation speed. + Default: 8 + reduction: + Specifies the reduction to apply to the output: 'mean' | 'sum'. + 'mean': the weighted mean of the output is taken, + 'sum': the output will be summed. + Default: 'mean'. + Returns: + losses: [batch,], float + """ + return FusedLinearCrossEntropyFunction.apply( + x, + target, + weight, + bias, + ignore_index, + label_smoothing, + logit_scale, + num_chunks, + reduction, + use_l2warp, + l2_penalty_factor, + ) + + +class FusedLinearCrossEntropyLoss(nn.Module): + + def __init__( + self, + ignore_index: int = -100, + label_smoothing: float = 0.0, + logit_scale: float = 1.0, + num_chunks: int = 8, + reduction: str = "mean", + use_l2warp: bool = False, + l2_penalty_factor: float = 1e-4, + ): + """ + Args: + ignore_index: int. + If target == ignore_index, the loss is set to 0.0. + label_smoothing: float + logit_scale: float + A scaling factor applied to the logits. Default: 1.0 + num_chunks: int + The number of chunks to split the input tensor into for processing. + This can help optimize memory usage and computation speed. + Default: 8 + reduction: + Specifies the reduction to apply to the output: 'mean' | 'sum'. + 'mean': the weighted mean of the output is taken, + 'sum': the output will be summed. + Default: 'mean'. + """ + super().__init__() + + assert reduction in ["mean", "sum"], f"reduction: {reduction} is not supported" + + self.ignore_index = ignore_index + self.label_smoothing = label_smoothing + self.logit_scale = logit_scale + self.num_chunks = num_chunks + self.reduction = reduction + self.use_l2warp = use_l2warp + self.l2_penalty_factor = l2_penalty_factor + + @torch.compiler.disable + def forward( + self, + x: torch.Tensor, + target: torch.LongTensor, + weight: torch.Tensor, + bias: torch.Tensor | None = None, + ): + """ + Args: + x (torch.Tensor): [batch_size, seq_len, hidden_size] + target (torch.LongTensor): [batch_size, seq_len] + where each value is in [0, V). + weight (torch.Tensor): [vocab_size, hidden_size] + where `vocab_size` is the number of classes. + bias (Optional[torch.Tensor]): [vocab_size] + where `vocab_size` is the number of classes. + Returns: + loss + """ + loss = fused_linear_cross_entropy_loss( + x.view(-1, x.shape[-1]), + target.view(-1), + weight=weight, + bias=bias, + ignore_index=self.ignore_index, + label_smoothing=self.label_smoothing, + logit_scale=self.logit_scale, + num_chunks=self.num_chunks, + reduction=self.reduction, + use_l2warp=self.use_l2warp, + l2_penalty_factor=self.l2_penalty_factor, + ) + return loss + + +class LinearLossParallel(ParallelStyle): + def __init__( + self, + *, + sequence_dim: int = 1, + use_local_output: bool = False, + ): + super().__init__() + + self.sequence_sharding = (Shard(sequence_dim),) + self.use_local_output = use_local_output + + @staticmethod + def _prepare_input_fn(sequence_sharding, mod, inputs, device_mesh): + x, target, weight, bias = inputs + + if not isinstance(x, DTensor): + # assume the input passed in already sharded on the sequence dim and create the DTensor + x = DTensor.from_local(x, device_mesh, sequence_sharding) + if x.placements != sequence_sharding: + x = x.redistribute(placements=sequence_sharding, async_op=True) + if not isinstance(target, DTensor): + target = DTensor.from_local(target, device_mesh, [Replicate()]) + if target.placements != sequence_sharding: + target = target.redistribute(placements=sequence_sharding, async_op=True) + + if not isinstance(weight, DTensor): + weight = DTensor.from_local(weight, device_mesh, [Replicate()]) + if weight.placements != [Replicate()]: + # we replicate the weight/bias in FLCE + weight = weight.redistribute(placements=[Replicate()], async_op=True) + + if bias is not None and not isinstance(bias, DTensor): + bias = DTensor.from_local(bias, device_mesh, [Replicate()]) + if bias is not None and bias.placements != [Replicate()]: + bias = bias.redistribute(placements=[Replicate()], async_op=True) + + return x.to_local(), target.to_local(), weight.to_local(), bias.to_local() if bias is not None else bias + + @staticmethod + def _prepare_output_fn(use_local_output, mod, outputs, device_mesh): + return outputs.to_local() if use_local_output else outputs + + def _apply(self, module: nn.Module, device_mesh: DeviceMesh) -> nn.Module: + return distribute_module( + module, + device_mesh, + partition_fn=None, + input_fn=partial(self._prepare_input_fn, self.sequence_sharding), + output_fn=partial(self._prepare_output_fn, self.use_local_output), + ) diff --git a/code/flash-linear-attention/fla/modules/fused_norm_gate.py b/code/flash-linear-attention/fla/modules/fused_norm_gate.py new file mode 100644 index 0000000000000000000000000000000000000000..1e12e5675673fe9fdb88c02ad414cf65b3baf2aa --- /dev/null +++ b/code/flash-linear-attention/fla/modules/fused_norm_gate.py @@ -0,0 +1,1245 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +from __future__ import annotations + +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl + +from fla.utils import autotune_cache_kwargs, get_multiprocessor_count, input_guard + + +@triton.heuristics({ + 'STORE_RESIDUAL_OUT': lambda args: args['residual_out'] is not None, + 'HAS_RESIDUAL': lambda args: args['residual'] is not None, + 'HAS_WEIGHT': lambda args: args['w'] is not None, + 'HAS_BIAS': lambda args: args['b'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for BT in [16, 32, 64] + for num_warps in [4, 8, 16] + ], + key=['D', 'NB', 'IS_RMS_NORM', 'STORE_RESIDUAL_OUT', 'HAS_RESIDUAL', 'HAS_WEIGHT'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_gated_fwd_kernel( + x, # pointer to the input + g, # pointer to the gate + y, # pointer to the output + w, # pointer to the weights + b, # pointer to the biases + residual, # pointer to the residual + residual_out, # pointer to the residual + mean, # pointer to the mean + rstd, # pointer to the 1/std + eps, # epsilon to avoid division by zero + T, # number of rows in x + D: tl.constexpr, # number of columns in x + BT: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + ACTIVATION: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + STORE_RESIDUAL_OUT: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, +): + i_t = tl.program_id(0) + + o_d = tl.arange(0, BD) + m_d = o_d < D + + p_x = tl.make_block_ptr(x, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + b_x = tl.load(p_x, boundary_check=(0, 1)).to(tl.float32) + if HAS_RESIDUAL: + p_res = tl.make_block_ptr(residual, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + b_x += tl.load(p_res, boundary_check=(0, 1)).to(tl.float32) + if STORE_RESIDUAL_OUT: + p_res_out = tl.make_block_ptr(residual_out, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + tl.store(p_res_out, b_x.to(p_res_out.dtype.element_ty), boundary_check=(0, 1)) + if not IS_RMS_NORM: + b_mean = tl.sum(b_x, axis=1) / D + p_mean = tl.make_block_ptr(mean, (T,), (1,), (i_t * BT,), (BT,), (0,)) + tl.store(p_mean, b_mean.to(p_mean.dtype.element_ty), boundary_check=(0,)) + b_xbar = tl.where(m_d[None, :], b_x - b_mean[:, None], 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=1) / D + else: + b_xbar = tl.where(m_d[None, :], b_x, 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=1) / D + b_rstd = 1 / tl.sqrt(b_var + eps) + + p_rstd = tl.make_block_ptr(rstd, (T,), (1,), (i_t * BT,), (BT,), (0,)) + tl.store(p_rstd, b_rstd.to(p_rstd.dtype.element_ty), boundary_check=(0,)) + + if HAS_WEIGHT: + b_w = tl.load(w + o_d, mask=m_d).to(tl.float32) + if HAS_BIAS: + b_b = tl.load(b + o_d, mask=m_d).to(tl.float32) + b_x_hat = (b_x - b_mean[:, None]) * b_rstd[:, None] if not IS_RMS_NORM else b_x * b_rstd[:, None] + b_y = b_x_hat * b_w[None, :] if HAS_WEIGHT else b_x_hat + if HAS_BIAS: + b_y = b_y + b_b[None, :] + + # swish/sigmoid output gate + p_g = tl.make_block_ptr(g, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + b_g = tl.load(p_g, boundary_check=(0, 1)).to(tl.float32) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + b_y = b_y * b_g * tl.sigmoid(b_g) + elif ACTIVATION == 'sigmoid': + b_y = b_y * tl.sigmoid(b_g) + + # Write output + p_y = tl.make_block_ptr(y, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + tl.store(p_y, b_y.to(p_y.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_RESIDUAL_OUT': lambda args: args['residual_out'] is not None, + 'HAS_RESIDUAL': lambda args: args['residual'] is not None, + 'HAS_WEIGHT': lambda args: args['w'] is not None, + 'HAS_BIAS': lambda args: args['b'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4, 8, 16] + ], + key=['D', 'IS_RMS_NORM', 'STORE_RESIDUAL_OUT', 'HAS_RESIDUAL', 'HAS_WEIGHT'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_gated_fwd_kernel1( + x, # pointer to the input + g, # pointer to the gate + y, # pointer to the output + w, # pointer to the weights + b, # pointer to the biases + residual, # pointer to the residual + residual_out, # pointer to the residual + mean, # pointer to the mean + rstd, # pointer to the 1/std + eps, # epsilon to avoid division by zero + D: tl.constexpr, # number of columns in x + BD: tl.constexpr, + ACTIVATION: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + STORE_RESIDUAL_OUT: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, +): + i_t = tl.program_id(0) + x += i_t * D + y += i_t * D + g += i_t * D + if HAS_RESIDUAL: + residual += i_t * D + if STORE_RESIDUAL_OUT: + residual_out += i_t * D + + o_d = tl.arange(0, BD) + m_d = o_d < D + b_x = tl.load(x + o_d, mask=m_d, other=0.0).to(tl.float32) + if HAS_RESIDUAL: + b_x += tl.load(residual + o_d, mask=m_d, other=0.0).to(tl.float32) + if STORE_RESIDUAL_OUT: + tl.store(residual_out + o_d, b_x, mask=m_d) + if not IS_RMS_NORM: + b_mean = tl.sum(b_x, axis=0) / D + tl.store(mean + i_t, b_mean) + b_xbar = tl.where(m_d, b_x - b_mean, 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=0) / D + else: + b_xbar = tl.where(m_d, b_x, 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=0) / D + b_rstd = 1 / tl.sqrt(b_var + eps) + tl.store(rstd + i_t, b_rstd) + + if HAS_WEIGHT: + b_w = tl.load(w + o_d, mask=m_d).to(tl.float32) + if HAS_BIAS: + b_b = tl.load(b + o_d, mask=m_d).to(tl.float32) + b_x_hat = (b_x - b_mean) * b_rstd if not IS_RMS_NORM else b_x * b_rstd + b_y = b_x_hat * b_w if HAS_WEIGHT else b_x_hat + if HAS_BIAS: + b_y = b_y + b_b + + # swish/sigmoid output gate + b_g = tl.load(g + o_d, mask=m_d, other=0.0).to(tl.float32) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + b_y = b_y * b_g * tl.sigmoid(b_g) + elif ACTIVATION == 'sigmoid': + b_y = b_y * tl.sigmoid(b_g) + + # Write output + tl.store(y + o_d, b_y, mask=m_d) + + +@triton.heuristics({ + 'HAS_DRESIDUAL': lambda args: args['dresidual'] is not None, + 'HAS_WEIGHT': lambda args: args['w'] is not None, + 'HAS_BIAS': lambda args: args['b'] is not None, + 'RECOMPUTE_OUTPUT': lambda args: args['y'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for BT in [16, 32, 64] + for num_warps in [4, 8, 16] + ], + key=['D', 'NB', 'IS_RMS_NORM', 'HAS_DRESIDUAL', 'HAS_WEIGHT'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_gated_bwd_kernel( + x, # pointer to the input + g, # pointer to the gate + w, # pointer to the weights + b, # pointer to the biases + y, # pointer to the output to be recomputed + dy, # pointer to the output gradient + dx, # pointer to the input gradient + dg, # pointer to the gate gradient + dw, # pointer to the partial sum of weights gradient + db, # pointer to the partial sum of biases gradient + dresidual, + dresidual_in, + mean, + rstd, + T, + BS, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + ACTIVATION: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + STORE_DRESIDUAL: tl.constexpr, + HAS_DRESIDUAL: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + RECOMPUTE_OUTPUT: tl.constexpr, +): + i_s = tl.program_id(0) + o_d = tl.arange(0, BD) + m_d = o_d < D + if HAS_WEIGHT: + b_w = tl.load(w + o_d, mask=m_d).to(tl.float32) + b_dw = tl.zeros((BT, BD), dtype=tl.float32) + if HAS_BIAS: + b_b = tl.load(b + o_d, mask=m_d, other=0.0).to(tl.float32) + b_db = tl.zeros((BT, BD), dtype=tl.float32) + + T = min(i_s * BS + BS, T) + for i_t in range(i_s * BS, T, BT): + p_x = tl.make_block_ptr(x, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + p_g = tl.make_block_ptr(g, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + p_dy = tl.make_block_ptr(dy, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + p_dx = tl.make_block_ptr(dx, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + p_dg = tl.make_block_ptr(dg, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + # [BT, BD] + b_x = tl.load(p_x, boundary_check=(0, 1)).to(tl.float32) + b_g = tl.load(p_g, boundary_check=(0, 1)).to(tl.float32) + b_dy = tl.load(p_dy, boundary_check=(0, 1)).to(tl.float32) + + if not IS_RMS_NORM: + p_mean = tl.make_block_ptr(mean, (T,), (1,), (i_t,), (BT,), (0,)) + b_mean = tl.load(p_mean, boundary_check=(0,)) + p_rstd = tl.make_block_ptr(rstd, (T,), (1,), (i_t,), (BT,), (0,)) + b_rstd = tl.load(p_rstd, boundary_check=(0,)) + # Compute dx + b_xhat = (b_x - b_mean[:, None]) * b_rstd[:, None] if not IS_RMS_NORM else b_x * b_rstd[:, None] + b_xhat = tl.where(m_d[None, :], b_xhat, 0.0) + + b_y = b_xhat * b_w[None, :] if HAS_WEIGHT else b_xhat + if HAS_BIAS: + b_y = b_y + b_b[None, :] + if RECOMPUTE_OUTPUT: + p_y = tl.make_block_ptr(y, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + tl.store(p_y, b_y.to(p_y.dtype.element_ty), boundary_check=(0, 1)) + + b_sigmoid_g = tl.sigmoid(b_g) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + b_dg = b_dy * b_y * (b_sigmoid_g + b_g * b_sigmoid_g * (1 - b_sigmoid_g)) + b_dy = b_dy * b_g * b_sigmoid_g + elif ACTIVATION == 'sigmoid': + b_dg = b_dy * b_y * b_sigmoid_g * (1 - b_sigmoid_g) + b_dy = b_dy * b_sigmoid_g + b_wdy = b_dy + + if HAS_WEIGHT or HAS_BIAS: + m_t = (i_t + tl.arange(0, BT)) < T + if HAS_WEIGHT: + b_wdy = b_dy * b_w + b_dw += tl.where(m_t[:, None], b_dy * b_xhat, 0.0) + if HAS_BIAS: + b_db += tl.where(m_t[:, None], b_dy, 0.0) + if not IS_RMS_NORM: + b_c1 = tl.sum(b_xhat * b_wdy, axis=1) / D + b_c2 = tl.sum(b_wdy, axis=1) / D + b_dx = (b_wdy - (b_xhat * b_c1[:, None] + b_c2[:, None])) * b_rstd[:, None] + else: + b_c1 = tl.sum(b_xhat * b_wdy, axis=1) / D + b_dx = (b_wdy - b_xhat * b_c1[:, None]) * b_rstd[:, None] + if HAS_DRESIDUAL: + p_dres = tl.make_block_ptr(dresidual, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + b_dres = tl.load(p_dres, boundary_check=(0, 1)).to(tl.float32) + b_dx += b_dres + # Write dx + if STORE_DRESIDUAL: + p_dres_in = tl.make_block_ptr(dresidual_in, (T, D), (D, 1), (i_t, 0), (BT, BD), (1, 0)) + tl.store(p_dres_in, b_dx.to(p_dres_in.dtype.element_ty), boundary_check=(0, 1)) + + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + + if HAS_WEIGHT: + tl.store(dw + i_s * D + o_d, tl.sum(b_dw, axis=0), mask=m_d) + if HAS_BIAS: + tl.store(db + i_s * D + o_d, tl.sum(b_db, axis=0), mask=m_d) + + +@triton.heuristics({ + 'HAS_DRESIDUAL': lambda args: args['dresidual'] is not None, + 'HAS_WEIGHT': lambda args: args['w'] is not None, + 'HAS_BIAS': lambda args: args['b'] is not None, + 'RECOMPUTE_OUTPUT': lambda args: args['y'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4, 8, 16] + ], + key=['D', 'IS_RMS_NORM', 'STORE_DRESIDUAL', 'HAS_DRESIDUAL', 'HAS_WEIGHT'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_gated_bwd_kernel1( + x, # pointer to the input + g, # pointer to the gate + w, # pointer to the weights + b, # pointer to the biases + y, # pointer to the output to be recomputed + dy, # pointer to the output gradient + dx, # pointer to the input gradient + dg, # pointer to the gate gradient + dw, # pointer to the partial sum of weights gradient + db, # pointer to the partial sum of biases gradient + dresidual, + dresidual_in, + mean, + rstd, + T, + BS, + D: tl.constexpr, + BD: tl.constexpr, + ACTIVATION: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + STORE_DRESIDUAL: tl.constexpr, + HAS_DRESIDUAL: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + RECOMPUTE_OUTPUT: tl.constexpr, +): + i_s = tl.program_id(0) + o_d = tl.arange(0, BD) + mask = o_d < D + x += i_s * BS * D + g += i_s * BS * D + if HAS_DRESIDUAL: + dresidual += i_s * BS * D + if STORE_DRESIDUAL: + dresidual_in += i_s * BS * D + dy += i_s * BS * D + dx += i_s * BS * D + dg += i_s * BS * D + if RECOMPUTE_OUTPUT: + y += i_s * BS * D + if HAS_WEIGHT: + b_w = tl.load(w + o_d, mask=mask).to(tl.float32) + b_dw = tl.zeros((BD,), dtype=tl.float32) + if HAS_BIAS: + b_b = tl.load(b + o_d, mask=mask, other=0.0).to(tl.float32) + b_db = tl.zeros((BD,), dtype=tl.float32) + + for i_t in range(i_s * BS, min(i_s * BS + BS, T)): + # Load data to SRAM + b_x = tl.load(x + o_d, mask=mask, other=0).to(tl.float32) + b_g = tl.load(g + o_d, mask=mask, other=0).to(tl.float32) + b_dy = tl.load(dy + o_d, mask=mask, other=0).to(tl.float32) + + if not IS_RMS_NORM: + b_mean = tl.load(mean + i_t) + b_rstd = tl.load(rstd + i_t) + # Compute dx + b_xhat = (b_x - b_mean) * b_rstd if not IS_RMS_NORM else b_x * b_rstd + b_xhat = tl.where(mask, b_xhat, 0.0) + + b_y = b_xhat * b_w if HAS_WEIGHT else b_xhat + if HAS_BIAS: + b_y = b_y + b_b + if RECOMPUTE_OUTPUT: + tl.store(y + o_d, b_y, mask=mask) + + b_sigmoid_g = tl.sigmoid(b_g) + if ACTIVATION == 'swish' or ACTIVATION == 'silu': + b_dg = b_dy * b_y * (b_sigmoid_g + b_g * b_sigmoid_g * (1 - b_sigmoid_g)) + b_dy = b_dy * b_g * b_sigmoid_g + elif ACTIVATION == 'sigmoid': + b_dg = b_dy * b_y * b_sigmoid_g * (1 - b_sigmoid_g) + b_dy = b_dy * b_sigmoid_g + b_wdy = b_dy + if HAS_WEIGHT: + b_wdy = b_dy * b_w + b_dw += b_dy * b_xhat + if HAS_BIAS: + b_db += b_dy + if not IS_RMS_NORM: + b_c1 = tl.sum(b_xhat * b_wdy, axis=0) / D + b_c2 = tl.sum(b_wdy, axis=0) / D + b_dx = (b_wdy - (b_xhat * b_c1 + b_c2)) * b_rstd + else: + b_c1 = tl.sum(b_xhat * b_wdy, axis=0) / D + b_dx = (b_wdy - b_xhat * b_c1) * b_rstd + if HAS_DRESIDUAL: + b_dres = tl.load(dresidual + o_d, mask=mask, other=0).to(tl.float32) + b_dx += b_dres + # Write dx + if STORE_DRESIDUAL: + tl.store(dresidual_in + o_d, b_dx, mask=mask) + tl.store(dx + o_d, b_dx, mask=mask) + tl.store(dg + o_d, b_dg, mask=mask) + + x += D + g += D + if HAS_DRESIDUAL: + dresidual += D + if STORE_DRESIDUAL: + dresidual_in += D + if RECOMPUTE_OUTPUT: + y += D + dy += D + dx += D + dg += D + if HAS_WEIGHT: + tl.store(dw + i_s * D + o_d, b_dw, mask=mask) + if HAS_BIAS: + tl.store(db + i_s * D + o_d, b_db, mask=mask) + + +def layer_norm_gated_fwd( + x: torch.Tensor, + g: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + activation: str = 'swish', + eps: float = 1e-5, + residual: torch.Tensor = None, + out_dtype: torch.dtype = None, + residual_dtype: torch.dtype = None, + is_rms_norm: bool = False, +): + if residual is not None: + residual_dtype = residual.dtype + T, D = x.shape + if residual is not None: + assert residual.shape == (T, D) + if weight is not None: + assert weight.shape == (D,) + if bias is not None: + assert bias.shape == (D,) + # allocate output + y = torch.empty_like(x, dtype=x.dtype if out_dtype is None else out_dtype) + if residual is not None or (residual_dtype is not None and residual_dtype != x.dtype): + residual_out = torch.empty(T, D, device=x.device, dtype=residual_dtype) + else: + residual_out = None + mean = torch.empty((T,), dtype=torch.float, device=x.device) if not is_rms_norm else None + rstd = torch.empty((T,), dtype=torch.float, device=x.device) + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + # heuristics for number of warps + + if D <= 512: + NB = triton.cdiv(T, 2048) + def grid(meta): return (triton.cdiv(T, meta['BT']),) + layer_norm_gated_fwd_kernel[grid]( + x=x, + g=g, + y=y, + w=weight, + b=bias, + residual=residual, + residual_out=residual_out, + mean=mean, + rstd=rstd, + eps=eps, + T=T, + D=D, + BD=BD, + NB=NB, + ACTIVATION=activation, + IS_RMS_NORM=is_rms_norm, + ) + else: + layer_norm_gated_fwd_kernel1[(T,)]( + x=x, + g=g, + y=y, + w=weight, + b=bias, + residual=residual, + residual_out=residual_out, + mean=mean, + rstd=rstd, + eps=eps, + D=D, + BD=BD, + ACTIVATION=activation, + IS_RMS_NORM=is_rms_norm, + ) + # residual_out is None if residual is None and residual_dtype == input_dtype + return y, mean, rstd, residual_out if residual_out is not None else x + + +def layer_norm_gated_bwd( + dy: torch.Tensor, + x: torch.Tensor, + g: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + activation: str = 'swish', + eps: float = 1e-5, + mean: torch.Tensor = None, + rstd: torch.Tensor = None, + dresidual: torch.Tensor = None, + has_residual: bool = False, + is_rms_norm: bool = False, + x_dtype: torch.dtype = None, + recompute_output: bool = False, +): + T, D = x.shape + assert dy.shape == (T, D) + if dresidual is not None: + assert dresidual.shape == (T, D) + if weight is not None: + assert weight.shape == (D,) + if bias is not None: + assert bias.shape == (D,) + # allocate output + dx = torch.empty_like(x) if x_dtype is None else torch.empty(T, D, dtype=x_dtype, device=x.device) + dg = torch.empty_like(g) if x_dtype is None else torch.empty(T, D, dtype=x_dtype, device=x.device) + dresidual_in = torch.empty_like(x) if has_residual and dx.dtype != x.dtype else None + y = torch.empty(T, D, dtype=dy.dtype, device=dy.device) if recompute_output else None + + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + NS = get_multiprocessor_count(x.device.index) + BS = math.ceil(T / NS) + + dw = torch.empty((NS, D), dtype=torch.float, device=weight.device) if weight is not None else None + db = torch.empty((NS, D), dtype=torch.float, device=bias.device) if bias is not None else None + grid = (NS,) + + if D <= 512: + NB = triton.cdiv(T, 2048) + layer_norm_gated_bwd_kernel[grid]( + x=x, + g=g, + w=weight, + b=bias, + y=y, + dy=dy, + dx=dx, + dg=dg, + dw=dw, + db=db, + dresidual=dresidual, + dresidual_in=dresidual_in, + mean=mean, + rstd=rstd, + T=T, + D=D, + BS=BS, + BD=BD, + NB=NB, + ACTIVATION=activation, + IS_RMS_NORM=is_rms_norm, + STORE_DRESIDUAL=dresidual_in is not None, + ) + else: + layer_norm_gated_bwd_kernel1[grid]( + x=x, + g=g, + w=weight, + b=bias, + y=y, + dy=dy, + dx=dx, + dg=dg, + dw=dw, + db=db, + dresidual=dresidual, + dresidual_in=dresidual_in, + mean=mean, + rstd=rstd, + T=T, + D=D, + BS=BS, + BD=BD, + ACTIVATION=activation, + IS_RMS_NORM=is_rms_norm, + STORE_DRESIDUAL=dresidual_in is not None, + ) + dw = dw.sum(0).to(weight.dtype) if weight is not None else None + db = db.sum(0).to(bias.dtype) if bias is not None else None + # Don't need to compute dresidual_in separately in this case + if has_residual and dx.dtype == x.dtype: + dresidual_in = dx + return (dx, dg, dw, db, dresidual_in) if not recompute_output else (dx, dg, dw, db, dresidual_in, y) + + +class LayerNormGatedFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + g: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + activation: str, + residual: torch.Tensor | None = None, + eps: float = 1e-6, + prenorm: bool = False, + residual_in_fp32: bool = False, + is_rms_norm: bool = False, + ): + x_shape_og = x.shape + g_shape_og = g.shape + # reshape input data into 2D tensor + x = x.reshape(-1, x.shape[-1]) + g = g.reshape(-1, g.shape[-1]) + if residual is not None: + assert residual.shape == x_shape_og + residual = residual.reshape(-1, residual.shape[-1]) + residual_dtype = ( + residual.dtype + if residual is not None + else (torch.float if residual_in_fp32 else None) + ) + y, mean, rstd, residual_out = layer_norm_gated_fwd( + x=x, + g=g, + weight=weight, + bias=bias, + activation=activation, + eps=eps, + residual=residual, + residual_dtype=residual_dtype, + is_rms_norm=is_rms_norm, + ) + ctx.save_for_backward(residual_out, g, weight, bias, mean, rstd) + ctx.x_shape_og = x_shape_og + ctx.g_shape_og = g_shape_og + ctx.activation = activation + ctx.eps = eps + ctx.is_rms_norm = is_rms_norm + ctx.has_residual = residual is not None + ctx.prenorm = prenorm + ctx.x_dtype = x.dtype + y = y.reshape(x_shape_og) + return y if not prenorm else (y, residual_out.reshape(x_shape_og)) + + @staticmethod + @input_guard + def backward(ctx, dy, *args): + x, g, weight, bias, mean, rstd = ctx.saved_tensors + dy = dy.reshape(-1, dy.shape[-1]) + assert dy.shape == x.shape + if ctx.prenorm: + dresidual = args[0] + dresidual = dresidual.reshape(-1, dresidual.shape[-1]) + assert dresidual.shape == x.shape + else: + dresidual = None + dx, dg, dw, db, dres_in = layer_norm_gated_bwd( + dy=dy, + x=x, + g=g, + weight=weight, + bias=bias, + activation=ctx.activation, + eps=ctx.eps, + mean=mean, + rstd=rstd, + dresidual=dresidual, + has_residual=ctx.has_residual, + is_rms_norm=ctx.is_rms_norm, + x_dtype=ctx.x_dtype, + ) + return ( + dx.reshape(ctx.x_shape_og), + dg.reshape(ctx.g_shape_og), + dw, + db, + None, + dres_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, + None, + None, + None, + None, + ) + + +class LayerNormGatedLinearFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + g: torch.Tensor, + norm_weight: torch.Tensor, + norm_bias: torch.Tensor, + linear_weight: torch.Tensor, + linear_bias: torch.Tensor, + residual: torch.Tensor | None = None, + eps: float = 1e-6, + prenorm: bool = False, + residual_in_fp32: bool = False, + is_rms_norm: bool = False, + ): + x_shape_og = x.shape + g_shape_og = g.shape + # reshape input data into 2D tensor + x = x.reshape(-1, x.shape[-1]) + g = g.reshape(-1, g.shape[-1]) + if residual is not None: + assert residual.shape == x_shape_og + residual = residual.reshape(-1, residual.shape[-1]) + residual_dtype = ( + residual.dtype + if residual is not None + else (torch.float if residual_in_fp32 else None) + ) + y, mean, rstd, residual_out = layer_norm_gated_fwd( + x=x, + g=g, + weight=norm_weight, + bias=norm_bias, + eps=eps, + residual=residual, + residual_dtype=residual_dtype, + is_rms_norm=is_rms_norm, + ) + y = y.reshape(x_shape_og) + dtype = torch.get_autocast_gpu_dtype() if torch.is_autocast_enabled() else y.dtype + linear_weight = linear_weight.to(dtype) + linear_bias = linear_bias.to(dtype) if linear_bias is not None else None + out = F.linear(y.to(linear_weight.dtype), linear_weight, linear_bias) + # We don't store y, will be recomputed in the backward pass to save memory + ctx.save_for_backward(residual_out, g, norm_weight, norm_bias, linear_weight, mean, rstd) + ctx.x_shape_og = x_shape_og + ctx.g_shape_og = g_shape_og + ctx.eps = eps + ctx.is_rms_norm = is_rms_norm + ctx.has_residual = residual is not None + ctx.prenorm = prenorm + ctx.x_dtype = x.dtype + ctx.linear_bias_is_none = linear_bias is None + return out if not prenorm else (out, residual_out.reshape(x_shape_og)) + + @staticmethod + @input_guard + def backward(ctx, dout, *args): + x, g, norm_weight, norm_bias, linear_weight, mean, rstd = ctx.saved_tensors + dout = dout.reshape(-1, dout.shape[-1]) + dy = F.linear(dout, linear_weight.t()) + dlinear_bias = None if ctx.linear_bias_is_none else dout.sum(0) + assert dy.shape == x.shape + if ctx.prenorm: + dresidual = args[0] + dresidual = dresidual.reshape(-1, dresidual.shape[-1]) + assert dresidual.shape == x.shape + else: + dresidual = None + dx, dg, dnorm_weight, dnorm_bias, dres_in, y = layer_norm_gated_bwd( + dy=dy, + x=x, + g=g, + weight=norm_weight, + bias=norm_bias, + eps=ctx.eps, + mean=mean, + rstd=rstd, + dresidual=dresidual, + has_residual=ctx.has_residual, + is_rms_norm=ctx.is_rms_norm, + x_dtype=ctx.x_dtype, + recompute_output=True, + ) + dlinear_weight = torch.einsum("bo,bi->oi", dout, y) + return ( + dx.reshape(ctx.x_shape_og), + dg.reshape(ctx.g_shape_og), + dnorm_weight, + dnorm_bias, + dlinear_weight, + dlinear_bias, + dres_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, + None, + None, + None, + None, + ) + + +def layer_norm_gated( + x: torch.Tensor, + g: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + activation: str = 'swish', + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + eps: float = 1e-6, +): + return LayerNormGatedFunction.apply( + x, + g, + weight, + bias, + activation, + residual, + eps, + prenorm, + residual_in_fp32, + False, + ) + + +def rms_norm_gated( + x: torch.Tensor, + g: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + activation: str = 'swish', + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + eps: float = 1e-6, +): + return LayerNormGatedFunction.apply( + x, + g, + weight, + bias, + activation, + residual, + eps, + prenorm, + residual_in_fp32, + True, + ) + + +def layer_norm_swish_gate_linear( + x: torch.Tensor, + g: torch.Tensor, + norm_weight: torch.Tensor, + norm_bias: torch.Tensor, + linear_weight: torch.Tensor, + linear_bias: torch.Tensor, + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + eps: float = 1e-6, +): + return LayerNormGatedLinearFunction.apply( + x, + g, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual, + eps, + prenorm, + residual_in_fp32, + False, + ) + + +def rms_norm_swish_gate_linear( + x, + g: torch.Tensor, + norm_weight: torch.Tensor, + norm_bias: torch.Tensor, + linear_weight: torch.Tensor, + linear_bias: torch.Tensor, + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + eps: float = 1e-6, +): + return LayerNormGatedLinearFunction.apply( + x, + g, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual, + eps, + prenorm, + residual_in_fp32, + True, + ) + + +class FusedLayerNormGated(nn.Module): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + bias: bool = False, + activation: str = 'swish', + eps: float = 1e-5, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedLayerNormGated: + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + self.activation = activation + + if self.activation not in ['swish', 'silu', 'sigmoid']: + raise ValueError(f"Unsupported activation: {self.activation}") + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += f", activation={self.activation}" + s += ")" + return s + + def forward( + self, + x: torch.Tensor, + g: torch.Tensor, + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + ) -> torch.Tensor: + return layer_norm_gated( + x, + g, + self.weight, + self.bias, + self.activation, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + ) + + +class FusedRMSNormGated(nn.Module): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + eps: float = 1e-5, + activation: str = 'swish', + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedRMSNormGated: + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + self.activation = activation + + if self.activation not in ['swish', 'silu', 'sigmoid']: + raise ValueError(f"Unsupported activation: {self.activation}") + + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + else: + self.register_parameter("weight", None) + self.register_parameter("bias", None) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += f", activation={self.activation}" + s += ")" + return s + + def forward( + self, + x: torch.Tensor, + g: torch.Tensor, + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + ) -> torch.Tensor: + return rms_norm_gated( + x, + g, + self.weight, + self.bias, + self.activation, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + ) + + +class FusedLayerNormSwishGate(FusedLayerNormGated): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedLayerNormSwishGate: + super().__init__( + hidden_size=hidden_size, + elementwise_affine=elementwise_affine, + bias=bias, + eps=eps, + device=device, + dtype=dtype, + ) + + +class FusedRMSNormSwishGate(FusedRMSNormGated): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + eps: float = 1e-5, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedRMSNormSwishGate: + super().__init__( + hidden_size=hidden_size, + elementwise_affine=elementwise_affine, + eps=eps, + device=device, + dtype=dtype, + ) + + +class FusedLayerNormGatedLinear(nn.Module): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + eps: float = 1e-5, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedLayerNormGatedLinear: + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + else: + self.register_parameter("weight", None) + self.register_parameter("bias", None) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward( + self, + x: torch.Tensor, + g: torch.Tensor, + weight: torch.Tensor | None = None, + bias: torch.Tensor | None = None, + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + ) -> torch.Tensor: + return layer_norm_swish_gate_linear( + x, + g, + self.weight, + self.bias, + weight, + bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + ) + + +class FusedLayerNormSwishGateLinear(FusedLayerNormGatedLinear): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + eps: float = 1e-5, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedLayerNormSwishGateLinear: + super().__init__( + hidden_size=hidden_size, + elementwise_affine=elementwise_affine, + eps=eps, + device=device, + dtype=dtype, + ) + + +class FusedRMSNormGatedLinear(nn.Module): + + def __init__( + self, + hidden_size, + elementwise_affine: bool = True, + eps: float = 1e-5, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedRMSNormGatedLinear: + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward( + self, + x: torch.Tensor, + g: torch.Tensor, + weight: torch.Tensor | None = None, + bias: torch.Tensor | None = None, + residual: torch.Tensor | None = None, + prenorm: bool = False, + residual_in_fp32: bool = False, + ) -> torch.Tensor: + return rms_norm_swish_gate_linear( + x, + g, + self.weight, + self.bias, + weight, + bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + ) + + +class FusedRMSNormSwishGateLinear(FusedRMSNormGatedLinear): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + eps: float = 1e-5, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> FusedRMSNormSwishGateLinear: + super().__init__( + hidden_size=hidden_size, + elementwise_affine=elementwise_affine, + eps=eps, + device=device, + dtype=dtype, + ) diff --git a/code/flash-linear-attention/fla/modules/grpo.py b/code/flash-linear-attention/fla/modules/grpo.py new file mode 100644 index 0000000000000000000000000000000000000000..970fb955c1677ff3e34ee0c30425feb2528c680b --- /dev/null +++ b/code/flash-linear-attention/fla/modules/grpo.py @@ -0,0 +1,412 @@ +# modified from https://github.com/mdy666/mdy_triton/blob/e0a856347bd988e05e0152332bba35f1d33c5b1f/others/grpo/grpo_loss.ipynb +# XHS ID: blueeeee + +# https://github.com/huggingface/trl/blob/main/trl/trainer/grpo_trainer.py +""" +# Get the per-token log probabilities for the completions for the model and the reference model + def _get_per_token_logps(self, model, input_ids, attention_mask, logits_to_keep): + # We add 1 to `logits_to_keep` because the last logits of the sequence is later excluded + logits = model(input_ids=input_ids, attention_mask=attention_mask, logits_to_keep=logits_to_keep + 1).logits + logits = logits[:, :-1, :] # (B, L-1, V), exclude the last logit: it corresponds to the next token pred + + input_ids = input_ids[:, -logits_to_keep:] + # For transformers<=4.48, logits_to_keep argument isn't supported, so here we drop logits ourselves. + # See https://github.com/huggingface/trl/issues/2770 + logits = logits[:, -logits_to_keep:] + return selective_log_softmax(logits, input_ids) # compute logprobs for the input tokens + + def compute_loss(self, model, inputs, return_outputs=False, num_items_in_batch=None): + if return_outputs: + raise ValueError("The GRPOTrainer does not support returning outputs") + # Compute the per-token log probabilities for the model + + prompt_ids, prompt_mask = inputs["prompt_ids"], inputs["prompt_mask"] + completion_ids, completion_mask = inputs["completion_ids"], inputs["completion_mask"] + input_ids = torch.cat([prompt_ids, completion_ids], dim=1) + attention_mask = torch.cat([prompt_mask, completion_mask], dim=1) + logits_to_keep = completion_ids.size(1) # we only need to compute the logits for the completion tokens + + per_token_logps = self._get_per_token_logps(model, input_ids, attention_mask, logits_to_keep) + + # Compute the KL divergence between the model and the reference model + ref_per_token_logps = inputs["ref_per_token_logps"] + per_token_kl = torch.exp(ref_per_token_logps - per_token_logps) - (ref_per_token_logps - per_token_logps) - 1 + + # x - x.detach() allows for preserving gradients from x + advantages = inputs["advantages"] + per_token_loss = torch.exp(per_token_logps - per_token_logps.detach()) * advantages.unsqueeze(1) + per_token_loss = -(per_token_loss - self.beta * per_token_kl) + loss = ((per_token_loss * completion_mask).sum(dim=1) / completion_mask.sum(dim=1)).mean() + + # Log the metrics + completion_length = self.accelerator.gather_for_metrics(completion_mask.sum(1)).float().mean().item() + self._metrics["completion_length"].append(completion_length) + + mean_kl = ((per_token_kl * completion_mask).sum(dim=1) / completion_mask.sum(dim=1)).mean() + self._metrics["kl"].append(self.accelerator.gather_for_metrics(mean_kl).mean().item()) + + return loss +""" + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp, log +from fla.utils import autotune_cache_kwargs, input_guard, is_amd + +NUM_WARPS_AUTOTUNE = [4, 8, 16] if is_amd else [4, 8, 16, 32] + + +@triton.autotune( + configs=[ + triton.Config({'BLOCK_SIZE': BLOCK_SIZE}, num_warps=NUM_WARPS, num_stages=NUM_STAGES) + for BLOCK_SIZE in [1024, 2048, 4096, 8192] + for NUM_WARPS in NUM_WARPS_AUTOTUNE + for NUM_STAGES in [1, 2, 4] + ], + key=['B', 'N'], + **autotune_cache_kwargs, +) +@triton.jit +def grpo_fwd_kernel( + logits_ptr, + ref_logp_ptr, + input_ids_ptr, + advantages_ptr, + completion_mask_ptr, + loss_ptr, + lse_ptr, + beta, + save_kl: tl.constexpr, + B, + M, + N, + L, + start_idx, + BLOCK_SIZE: tl.constexpr, +): + row_idx = tl.program_id(0) + + off_b = row_idx // L + N = tl.cast(N, tl.int64) + + loss_ptr += row_idx + + completion_mask_ptr += row_idx + not_skip = tl.load(completion_mask_ptr).to(tl.int1) + if not_skip == 1: + ref_logp_ptr += row_idx + lse_ptr += row_idx + advantages_ptr += off_b + logits_ptr += N * (row_idx + off_b) + input_ids_ptr += row_idx + (off_b+1) * start_idx + base_cols = tl.arange(0, BLOCK_SIZE) + + m_i = -float("inf") + l_i = 0.0 + for start_n in tl.range(0, N, BLOCK_SIZE): + cols = start_n + base_cols + mask = cols < N + logits = tl.load(logits_ptr+cols, mask=mask, other=-float('inf')).to(tl.float32) + m_ij = tl.max(logits) + new_m_i = tl.maximum(m_i, m_ij) + l_i = l_i * exp(m_i - new_m_i) + tl.sum(exp(logits - new_m_i)) + m_i = new_m_i + lse = log(l_i) + m_i + + idx = tl.load(input_ids_ptr) + x = tl.load(logits_ptr+idx).to(tl.float32) + advantage = tl.load(advantages_ptr).to(tl.float32) + ref_logp = tl.load(ref_logp_ptr) + logp = x - lse + diff = ref_logp - logp + kl = exp(diff) - diff - 1 + loss = kl * beta - advantage + + tl.store(loss_ptr, loss.to(loss_ptr.dtype.element_ty)) + tl.store(lse_ptr, lse.to(lse_ptr.dtype.element_ty)) + if save_kl: + tl.store(loss_ptr+M, kl.to(loss_ptr.dtype.element_ty)) + else: + # store 0 + tl.store(loss_ptr, 0.0) + if save_kl: + tl.store(loss_ptr+M, 0.0) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=NUM_WARPS, num_stages=NUM_STAGES) + for NUM_WARPS in [32] + for NUM_STAGES in [4] + ], + key=['B', 'N'], + **autotune_cache_kwargs, +) +@triton.jit +def grpo_bwd_kernel( + dloss_ptr, + dlogits_ptr, + logits_ptr, + ref_logp_ptr, + input_ids_ptr, + advantages_ptr, + completion_mask_ptr, + lse_ptr, + beta, + B, + N, + L, + start_idx, + BLOCK_SIZE: tl.constexpr, +): + + row_idx = tl.program_id(0) # B*L + off_b = row_idx // L + + N = tl.cast(N, tl.int64) + + dlogits_ptr += N * (row_idx + off_b) + base_cols = tl.arange(0, BLOCK_SIZE) + completion_mask_ptr += row_idx + not_skip = tl.load(completion_mask_ptr).to(tl.int1) + + if not_skip == 1: + lse_ptr += row_idx + dloss_ptr += row_idx + advantages_ptr += off_b + ref_logp_ptr += row_idx + logits_ptr += N * (row_idx + off_b) + input_ids_ptr += row_idx + (off_b+1) * start_idx + dloss = tl.load(dloss_ptr).to(tl.float32) + lse = tl.load(lse_ptr).to(tl.float32) + idx = tl.load(input_ids_ptr) + x = tl.load(logits_ptr+idx).to(tl.float32) + advantage = tl.load(advantages_ptr).to(tl.float32) + ref_logp = tl.load(ref_logp_ptr) + # Need for in-place grad. + tl.debug_barrier() + logp = x - lse + + dlogp = (beta * (-1.0 * exp(ref_logp - logp) + 1) + - advantage) * dloss + + for start_n in tl.range(0, N, BLOCK_SIZE): + cols = start_n + base_cols + mask = cols < N + logits = tl.load(logits_ptr+cols, mask=mask, other=-float('inf')).to(tl.float32) + probs = exp(logits - lse) + dlogits = tl.where(cols == idx, 1-probs, -probs) * dlogp + + tl.store(dlogits_ptr+cols, dlogits.to(dlogits_ptr.dtype.element_ty), mask=mask) + else: + dlogits = tl.zeros((BLOCK_SIZE,), dtype=tl.float32) + for start_n in tl.range(0, N, BLOCK_SIZE): + cols = start_n + base_cols + mask = cols < N + + tl.store(dlogits_ptr+cols, dlogits.to(dlogits_ptr.dtype.element_ty), mask=mask) + + +class GrpoLoss(torch.autograd.Function): + + @input_guard + @staticmethod + def forward(ctx, logits, ref_logp, input_ids, advantages, beta, completion_mask, save_kl, inplace=True): + ctx.input_shape = logits.shape + B, L_ADD_1, N = ctx.input_shape + L = L_ADD_1 - 1 + M = B * L + input_ids_start_index = input_ids.size(1) - L + + if not save_kl: + loss = torch.empty(B, L, device=logits.device, dtype=torch.float32) + else: + loss = torch.empty(B*2, L, device=logits.device, dtype=torch.float32) + + lse = torch.empty(B, L, device=logits.device, dtype=torch.float32) + + if completion_mask is None: + completion_mask = torch.ones(B, L, device=logits.device, dtype=torch.int32) + else: + loss[:B].masked_fill_(completion_mask.logical_not(), 0.0) + + grpo_fwd_kernel[(M,)]( + logits_ptr=logits, + ref_logp_ptr=ref_logp, + input_ids_ptr=input_ids, + advantages_ptr=advantages, + completion_mask_ptr=completion_mask, + loss_ptr=loss, + lse_ptr=lse, + beta=beta, + save_kl=save_kl, + B=B, M=M, N=N, L=L, + start_idx=input_ids_start_index, + ) + ctx.beta = beta + ctx.save_for_backward(lse, logits, input_ids, advantages, completion_mask) + ctx.ref_logp = ref_logp + ctx.inplace = inplace + return loss + + @input_guard + @staticmethod + def backward(ctx, dloss): + # The grad of logits comes from two parts, the reward part and the kl part + lse, logits, input_ids, advantages, completion_mask = ctx.saved_tensors + inplace = ctx.inplace + B, L_ADD_1, N = ctx.input_shape + L = L_ADD_1 - 1 + M = B * L + + input_ids_start_index = input_ids.size(1) - L + + # B, L_ADD_1, N + dlogits = logits if inplace else torch.empty_like(logits) + BN = min(65536, triton.next_power_of_2(N)) + + grpo_bwd_kernel[(M,)]( + dloss_ptr=dloss, + dlogits_ptr=dlogits, + logits_ptr=logits, + ref_logp_ptr=ctx.ref_logp, + input_ids_ptr=input_ids, + advantages_ptr=advantages, + completion_mask_ptr=completion_mask, + lse_ptr=lse, + beta=ctx.beta, + B=B, N=N, L=L, + BLOCK_SIZE=BN, + start_idx=input_ids_start_index, + ) + # The last token in the completion is not used in the loss computation + # and therefore its gradient should be set to 0 + dlogits[:, -1, :].fill_(0.0) + return dlogits.view(*ctx.input_shape), None, None, None, None, None, None, None + + +def fused_grpo_loss(logits, ref_logp, input_ids, advantages, + beta=0.1, completion_mask=None, save_kl=False, inplace=False) -> torch.Tensor: + ''' + compute grpo loss, save memory(no addition usage) and fast speed(6X for A800) + + Args: + logtits: Tensor, [B, L+1, vocab_size], the origin output of model, it's not logits[:, :-1] + ref_logp: Tensor, [B, L], the origin output of model, it's not ref_logits[:, :-1] + input_ids: Tensor, [B, K+L], it's prompt_completion_id, it contains the prompt ids and output ids + advantages: Tensor, [B], the advantages of each prompt + beta: float, the weight of kl loss + completion_mask: Tensor, loss mask + save_kl: bool, if true will save kl + + Retutn: + loss: Tensor, [B, L], the loss of grpo, it contains the advantage part and kl part + + NOTE: logits(ref_logits) is computed by these steps + logits_to_keep = completion_ids.size(1) + + def get_per_token_logits(model, input_ids, attention_mask, logits_to_keep): + # We add 1 to `logits_to_keep` because the last logits of the sequence is later excluded + logits = model( + input_ids=input_ids, attention_mask=attention_mask, logits_to_keep=logits_to_keep + 1 + ).logits + return logits + + logits = get_per_token_logits(model, prompt_completion_ids, attention_mask, logits_to_keep) + ''' + out = GrpoLoss.apply(logits, ref_logp, input_ids, advantages, beta, completion_mask, save_kl, inplace) + if not save_kl: + return out + else: + return out.chunk(2, axis=0) + + +def grpo_loss_torch(logits, ref_logp, input_ids, advantages, beta=0.1, completion_mask=None, save_kl=False): + def get_log_probs(logits, input_ids): + per_token_logps = [] + for logits_row, input_ids_row in zip(logits, input_ids[:, -logits.size(1):], strict=False): + log_probs = logits_row.log_softmax(dim=-1) + token_log_prob = torch.gather(log_probs, dim=1, index=input_ids_row.unsqueeze(1)).squeeze(1) + per_token_logps.append(token_log_prob) + return torch.stack(per_token_logps) + + logits = logits[:, :-1] + per_token_logps = get_log_probs(logits, input_ids) + ref_per_token_logps = ref_logp + per_token_kl = torch.exp(ref_per_token_logps - per_token_logps) - (ref_per_token_logps - per_token_logps) - 1 + + per_token_loss = torch.exp(per_token_logps - per_token_logps.detach()) * advantages.unsqueeze(1) + per_token_loss = -(per_token_loss - beta * per_token_kl) + if completion_mask is not None: + per_token_loss *= completion_mask + if save_kl: + per_token_kl *= completion_mask + return per_token_loss if not save_kl else (per_token_loss, per_token_kl) + + +@torch.compile(fullgraph=True) +def grpo_loss_with_old_logps( + logps: torch.Tensor, + ref_logps: torch.Tensor, + old_logps: torch.Tensor, + pad_mask: torch.Tensor, + logits_to_keep: int, + rewards: torch.Tensor, + beta: float = 0.2, + epsilon: float = 0.2, +): + """ + Compute the GRPO (Group Relative Policy Optimization) loss. + + Args: + logps (torch.Tensor): [Batch, Token_length] Log probabilities of the current policy. + ref_logps (torch.Tensor):[Batch, Token_length] Log probabilities of the reference policy. + old_logps (torch.Tensor): [Batch, Token_length] Log probabilities of the old policy. + completion_ids (torch.Tensor): [Batch, Token_length] Completion token IDs (bool). + pad_token_id: Pad token ID. + logits_to_keep (int): Number of logits to keep for masking. + rewards (torch.Tensor): [Batch] Rewards for each generation. + beta (float) = 0.2: A hyperparameter for weighting the KL divergence term. + epsilon (float) = 0.2: An float hyperparameter for clipping the importance weights. + + Returns: + torch.Tensor: The computed GRPO loss. + """ + B = logps.shape[0] + assert B > 1, "Batch * Num generations should be greater than 1" + + rewards_shaped = rewards.view(-1, B) # B,num_generations + advantages = (rewards_shaped - rewards_shaped.mean(dim=1, keepdim=True)) / \ + (rewards_shaped.std(dim=1, keepdim=True) + 1e-8) + advantages = advantages.view(-1) # B*num_generations + # Calculate the per - token KL divergence + per_token_kl = torch.exp(ref_logps - logps) - (ref_logps - logps) - 1 + + # Calculate the ratio of probabilities (importance weights) + # Importance weights are calculated as exp(log_pi_theta - log_pi_theta_old) + importance_weights = torch.exp(logps - old_logps) + + # Clip the importance weights to the range [1 - epsilon, 1 + epsilon] + importance_weights_clipped = torch.clamp(importance_weights, 1 - epsilon, 1 + epsilon) + + # Create a completion mask. It checks which positions are valid based on logits_to_keep + completion_mask = torch.arange(logits_to_keep, device=logps.device)[None, :] >= 0 + + # Combine the completion mask and padding mask + completion_mask = completion_mask & pad_mask # Ensure matching shape + + # Add an extra dimension to advantages to match the shape for element - wise multiplication + advantages = advantages.unsqueeze(1) + + # Calculate the per - token loss. It takes the minimum of the unclipped and clipped importance weights + # and subtracts the KL divergence term weighted by beta, then multiplies by the completion mask + token_loss = -(torch.min(advantages * importance_weights, advantages * + importance_weights_clipped) - beta * per_token_kl) * completion_mask + + # Calculate the final loss by summing the token losses and normalizing by the number of valid tokens + loss = -token_loss.sum() / completion_mask.sum() + + return loss diff --git a/code/flash-linear-attention/fla/modules/l2norm.py b/code/flash-linear-attention/fla/modules/l2norm.py new file mode 100644 index 0000000000000000000000000000000000000000..2ed0e4b7182cc287b3373785c471c94030b46c06 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/l2norm.py @@ -0,0 +1,287 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import torch.nn as nn +import triton +import triton.language as tl + +from fla.utils import autotune_cache_kwargs, input_guard, is_amd + +BT_LIST = [8, 16, 32, 64, 128] +NUM_WARPS_AUTOTUNE = [1, 2, 4, 8, 16] if is_amd else [1, 2, 4, 8, 16, 32] + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit +def l2norm_fwd_kernel1( + x, + y, + rstd, + eps, + D, + BD: tl.constexpr, +): + i_t = tl.program_id(0) + x += i_t * D + y += i_t * D + # Compute mean and variance + cols = tl.arange(0, BD) + mask = cols < D + + b_x = tl.load(x + cols, mask=mask, other=0.0).to(tl.float32) + b_rstd = 1 / tl.sqrt(tl.sum(b_x * b_x) + eps) + b_y = b_x * b_rstd + tl.store(y + cols, b_y, mask=mask) + tl.store(rstd + i_t, b_rstd) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit +def l2norm_bwd_kernel1( + y, + rstd, + dy, + dx, + eps, + D, + BD: tl.constexpr, +): + i_t = tl.program_id(0) + y += i_t * D + dx += i_t * D + dy += i_t * D + + cols = tl.arange(0, BD) + mask = cols < D + b_y = tl.load(y + cols, mask=mask, other=0.0).to(tl.float32) + b_rstd = tl.load(rstd + i_t).to(tl.float32) + b_dy = tl.load(dy + cols, mask=mask, other=0.0).to(tl.float32) + b_dx = b_dy * b_rstd - tl.sum(b_dy * b_y) * b_y * b_rstd + tl.store(dx + cols, b_dx, mask=mask) + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8, 16] + for BT in BT_LIST + ], + key=['D', 'NB'], + **autotune_cache_kwargs, +) +@triton.jit +def l2norm_fwd_kernel( + x, + y, + rstd, + eps, + T: tl.constexpr, + D: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + BT: tl.constexpr, +): + i_t = tl.program_id(0) + p_x = tl.make_block_ptr(x, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + p_y = tl.make_block_ptr(y, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + p_rstd = tl.make_block_ptr(rstd, (T,), (1,), (i_t * BT,), (BT,), (0,)) + + b_x = tl.load(p_x, boundary_check=(0, 1)).to(tl.float32) + b_rstd = 1 / tl.sqrt(tl.sum(b_x * b_x, 1) + eps) + b_y = b_x * b_rstd[:, None] + + tl.store(p_y, b_y.to(p_y.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_rstd, b_rstd.to(p_rstd.dtype.element_ty), boundary_check=(0,)) + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8, 16] + for BT in BT_LIST + ], + key=['D', 'NB'], + **autotune_cache_kwargs, +) +@triton.jit +def l2norm_bwd_kernel( + y, + rstd, + dy, + dx, + eps, + T: tl.constexpr, + D: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + BT: tl.constexpr, +): + i_t = tl.program_id(0) + p_y = tl.make_block_ptr(y, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + p_rstd = tl.make_block_ptr(rstd, (T,), (1,), (i_t * BT,), (BT,), (0,)) + p_dy = tl.make_block_ptr(dy, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + p_dx = tl.make_block_ptr(dx, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + + b_y = tl.load(p_y, boundary_check=(0, 1)).to(tl.float32) + b_rstd = tl.load(p_rstd, boundary_check=(0,)).to(tl.float32) + b_dy = tl.load(p_dy, boundary_check=(0, 1)).to(tl.float32) + b_dx = b_dy * b_rstd[:, None] - tl.sum(b_dy * b_y, 1)[:, None] * b_y * b_rstd[:, None] + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), boundary_check=(0, 1)) + + +def l2norm_fwd( + x: torch.Tensor, + eps: float = 1e-6, + output_dtype: torch.dtype | None = None, +): + x_shape_og = x.shape + x = x.view(-1, x.shape[-1]) + # allocate output + if output_dtype is None: + y = torch.empty_like(x) + else: + y = torch.empty_like(x, dtype=output_dtype) + assert y.stride(-1) == 1 + T, D = x.shape[0], x.shape[-1] + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer doesn't support feature dim >= 64KB.") + + rstd = torch.empty((T,), dtype=torch.float32, device=x.device) + if D <= 512: + NB = triton.cdiv(T, 2048) + def grid(meta): return (triton.cdiv(T, meta['BT']), ) + l2norm_fwd_kernel[grid]( + x=x, + y=y, + rstd=rstd, + eps=eps, + T=T, + D=D, + BD=BD, + NB=NB, + ) + else: + l2norm_fwd_kernel1[(T,)]( + x=x, + y=y, + rstd=rstd, + eps=eps, + D=D, + BD=BD, + ) + return y.view(x_shape_og), rstd.view(x_shape_og[:-1]) + + +def l2norm_bwd( + y: torch.Tensor, + rstd: torch.Tensor, + dy: torch.Tensor, + eps: float = 1e-6, +): + y_shape_og = y.shape + y = y.view(-1, dy.shape[-1]) + dy = dy.view(-1, dy.shape[-1]) + assert dy.shape == y.shape + # allocate output + dx = torch.empty_like(y) + T, D = y.shape[0], y.shape[-1] + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // y.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + + if D <= 512: + NB = triton.cdiv(T, 2048) + def grid(meta): return (triton.cdiv(T, meta['BT']), ) + l2norm_bwd_kernel[grid]( + y=y, + rstd=rstd, + dy=dy, + dx=dx, + eps=eps, + T=T, + D=D, + BD=BD, + NB=NB, + ) + else: + l2norm_bwd_kernel1[(T,)]( + y=y, + rstd=rstd, + dy=dy, + dx=dx, + eps=eps, + D=D, + BD=BD, + ) + + return dx.view(y_shape_og) + + +class L2NormFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x, + eps=1e-6, + output_dtype=None, + ): + y, rstd = l2norm_fwd(x, eps, output_dtype) + ctx.eps = eps + ctx.x_dtype = x.dtype + ctx.save_for_backward(y, rstd) + return y + + @staticmethod + @input_guard + def backward(ctx, dy): + y, rstd = ctx.saved_tensors + dx = l2norm_bwd(y, rstd, dy, ctx.eps) + return dx, None, None + + +def l2norm( + x: torch.Tensor, + eps: float = 1e-6, + output_dtype: torch.dtype | None = None, +) -> torch.Tensor: + return L2NormFunction.apply(x, eps, output_dtype) + + +l2_norm = l2norm + + +class L2Norm(nn.Module): + + def __init__( + self, + eps: float = 1e-6, + output_dtype: torch.dtype | None = None, + ): + super().__init__() + self.eps = eps + self.output_dtype = output_dtype + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return l2norm(x, self.eps, self.output_dtype) diff --git a/code/flash-linear-attention/fla/modules/l2warp.py b/code/flash-linear-attention/fla/modules/l2warp.py new file mode 100644 index 0000000000000000000000000000000000000000..4cfb432745e68b5af9577e180776cb88454551ce --- /dev/null +++ b/code/flash-linear-attention/fla/modules/l2warp.py @@ -0,0 +1,37 @@ + +import torch + + +class L2Wrap(torch.autograd.Function): + r""" + This class of penalty prevents the model from becoming overconfident, + thereby mitigating precision loss in BF16. + + This version is memory-optimized by not storing the full logits tensor. + """ + @staticmethod + def forward(ctx, loss, logits, l2_penalty_factor=1e-4): + """ + Forward pass for L2 penalty. + Args: + loss (torch.Tensor): The loss tensor. + logits (torch.Tensor): Shape[B, T, V] The logits tensor. + l2_penalty_factor (float): The factor for L2 penalty. + """ + maxx, ids = torch.max(logits, dim=-1, keepdim=True) + ctx.logits_shape = logits.shape + factor = l2_penalty_factor / (logits.shape[0] * logits.shape[1]) + maxx = maxx * factor + ctx.save_for_backward(maxx, ids) + return loss + + @staticmethod + def backward(ctx, grad_output): + maxx, ids = ctx.saved_tensors + glogits = torch.zeros(ctx.logits_shape, device=grad_output.device, + dtype=grad_output.dtype) + glogits.scatter_(-1, ids, maxx) + return grad_output, glogits, None + + +l2_warp = L2Wrap.apply diff --git a/code/flash-linear-attention/fla/modules/layernorm.py b/code/flash-linear-attention/fla/modules/layernorm.py new file mode 100644 index 0000000000000000000000000000000000000000..40f98124a2dee5293e2f318ab035650029798234 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/layernorm.py @@ -0,0 +1,1444 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +# Copyright (c) 2023, Tri Dao +# https://github.com/state-spaces/mamba/blob/fb7b5310fa865dbd62aa059b1e26f2b431363e2a/mamba_ssm/ops/triton/layernorm.py +# Implement residual + layer_norm / rms_norm. + +# Based on the Triton LayerNorm tutorial: https://triton-lang.org/main/getting-started/tutorials/05-layer-norm.html +# For the backward pass, we keep weight_grad and bias_grad in registers and accumulate. +# This is faster for dimensions up to 8k, but after that it's much slower due to register spilling. +# The models we train have hidden dim up to 8k anyway (e.g. Llama 70B), so this is fine. + +from __future__ import annotations + +from functools import partial + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl +from einops import rearrange +try: + from torch.distributed import DeviceMesh + from torch.distributed.tensor import Replicate, Shard, distribute_module + from torch.distributed.tensor.parallel import ParallelStyle +except ImportError: + DeviceMesh = None + Replicate = None + Shard = None + distribute_module = None + class ParallelStyle: + pass + +from fla.utils import autotune_cache_kwargs, get_multiprocessor_count, input_guard + +try: + from torch.distributed.tensor import DTensor +except (ImportError, AttributeError): + DTensor = None + + +def layer_norm_ref( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + upcast: bool = False, +): + dtype = x.dtype + if upcast: + weight = weight.float() + bias = bias.float() if bias is not None else None + if upcast: + x = x.float() + residual = residual.float() if residual is not None else residual + if residual is not None: + x = (x + residual).to(x.dtype) + out = F.layer_norm(x.to(weight.dtype), x.shape[-1:], weight=weight, bias=bias, eps=eps).to( + dtype, + ) + return out if not prenorm else (out, x) + + +def rms_norm_ref( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + upcast: bool = False, +): + dtype = x.dtype + if upcast: + weight = weight.float() + bias = bias.float() if bias is not None else None + if upcast: + x = x.float() + residual = residual.float() if residual is not None else residual + if residual is not None: + x = (x + residual).to(x.dtype) + rstd = 1 / torch.sqrt((x.square()).mean(dim=-1, keepdim=True) + eps) + out = (x * rstd * weight) + bias if bias is not None else (x * rstd * weight) + out = out.to(dtype) + return out if not prenorm else (out, x) + + +def group_norm_ref( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + num_groups: int, + residual: torch.Tensor = None, + eps: float = 1e-5, + is_rms_norm: bool = False, + prenorm: bool = False, + upcast: bool = False, +): + dtype = x.dtype + if upcast: + weight = weight.float() + bias = bias.float() if bias is not None else None + if upcast: + x = x.float() + residual = residual.float() if residual is not None else residual + if residual is not None: + x = (x + residual).to(x.dtype) + residual = x + x, weight = [ + rearrange(data, "... (g d) -> ... g d", g=num_groups) for data in (x, weight) + ] + if bias is not None: + bias = rearrange(bias, '... (g d) -> ... g d', g=num_groups) + if not is_rms_norm: + mean = x.mean(dim=-1, keepdim=True) + x = x - mean + rstd = 1 / torch.sqrt((x.square()).mean(dim=-1, keepdim=True) + eps) + out = (x * rstd * weight) + bias if bias is not None else (x * rstd * weight) + out = rearrange(out, "... g d -> ... (g d)") + out = out.to(dtype) + return out if not prenorm else (out, residual) + + +class GroupNormRef(nn.Module): + + def __init__( + self, + num_groups: int, + hidden_size: int, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + is_rms_norm: bool = False, + ) -> GroupNormRef: + super().__init__() + + if hidden_size % num_groups != 0: + raise ValueError('num_channels must be divisible by num_groups') + + self.num_groups = num_groups + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + self.is_rms_norm = is_rms_norm + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.num_groups}, {self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + if self.is_rms_norm: + s += f", is_rms_norm={self.is_rms_norm}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward(self, x, residual=None, prenorm=False): + return group_norm_ref( + x, + self.weight, + self.bias, + num_groups=self.num_groups, + residual=residual, + eps=self.eps, + is_rms_norm=self.is_rms_norm, + prenorm=prenorm, + upcast=True, + ) + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for BT in [32, 64, 128] + for num_warps in [2, 4, 8] + ], + key=['D', 'NB', 'HAS_RESIDUAL', 'STORE_RESIDUAL_OUT', 'IS_RMS_NORM'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_fwd_kernel( + x, # pointer to the input + y, # pointer to the output + w, # pointer to the weights + b, # pointer to the biases + res, # pointer to the res + res_out, # pointer to the res + mean, # pointer to the mean + rstd, # pointer to the 1/std + eps, # epsilon to avoid division by zero + T, + G: tl.constexpr, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, + STORE_RESIDUAL_OUT: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, +): + i_t = tl.program_id(0) + + o_t = i_t * BT + tl.arange(0, BT) + o_g = o_t % G + o_d = tl.arange(0, BD) + m_d = o_d < D + + p_x = tl.make_block_ptr(x, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + b_x = tl.load(p_x, boundary_check=(0, 1)).to(tl.float32) + if HAS_RESIDUAL: + p_res = tl.make_block_ptr(res, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + b_x += tl.load(p_res, boundary_check=(0, 1)).to(tl.float32) + if STORE_RESIDUAL_OUT: + p_res_out = tl.make_block_ptr(res_out, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + tl.store(p_res_out, b_x.to(p_res_out.dtype.element_ty), boundary_check=(0, 1)) + if not IS_RMS_NORM: + b_mean = tl.sum(b_x, axis=1) / D + p_mean = tl.make_block_ptr(mean, (T,), (1,), (i_t * BT,), (BT,), (0,)) + tl.store(p_mean, b_mean.to(p_mean.dtype.element_ty), boundary_check=(0,)) + b_xbar = tl.where(m_d[None, :], b_x - b_mean[:, None], 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=1) / D + else: + b_xbar = tl.where(m_d[None, :], b_x, 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=1) / D + b_rstd = 1 / tl.sqrt(b_var + eps) + + p_rstd = tl.make_block_ptr(rstd, (T,), (1,), (i_t * BT,), (BT,), (0,)) + tl.store(p_rstd, b_rstd.to(p_rstd.dtype.element_ty), boundary_check=(0,)) + + if HAS_WEIGHT: + b_w = tl.load(w + o_g[:, None] * D + o_d[None, :], mask=m_d[None, :]).to(tl.float32) + if HAS_BIAS: + b_b = tl.load(b + o_g[:, None] * D + o_d[None, :], mask=m_d[None, :]).to(tl.float32) + b_x_hat = (b_x - b_mean[:, None]) * b_rstd[:, None] if not IS_RMS_NORM else b_x * b_rstd[:, None] + b_y = b_x_hat * b_w if HAS_WEIGHT else b_x_hat + if HAS_BIAS: + b_y = b_y + b_b + + # Write output + p_y = tl.make_block_ptr(y, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0)) + tl.store(p_y, b_y.to(p_y.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4, 8, 16] + ], + key=['D', 'HAS_RESIDUAL', 'STORE_RESIDUAL_OUT', 'IS_RMS_NORM'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_fwd_kernel1( + x, # pointer to the input + y, # pointer to the output + w, # pointer to the weights + b, # pointer to the biases + res, # pointer to the res + res_out, # pointer to the res + mean, # pointer to the mean + rstd, # pointer to the 1/std + eps, # epsilon to avoid division by zero + G: tl.constexpr, + D: tl.constexpr, + BD: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, + STORE_RESIDUAL_OUT: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, +): + i_t = tl.program_id(0) + i_g = i_t % G + + x += i_t * D + y += i_t * D + if HAS_RESIDUAL: + res += i_t * D + if STORE_RESIDUAL_OUT: + res_out += i_t * D + + o_d = tl.arange(0, BD) + m_d = o_d < D + b_x = tl.load(x + o_d, mask=m_d, other=0.0).to(tl.float32) + if HAS_RESIDUAL: + b_x += tl.load(res + o_d, mask=m_d, other=0.0).to(tl.float32) + if STORE_RESIDUAL_OUT: + tl.store(res_out + o_d, b_x, mask=m_d) + if not IS_RMS_NORM: + b_mean = tl.sum(b_x, axis=0) / D + tl.store(mean + i_t, b_mean) + b_xbar = tl.where(m_d, b_x - b_mean, 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=0) / D + else: + b_xbar = tl.where(m_d, b_x, 0.0) + b_var = tl.sum(b_xbar * b_xbar, axis=0) / D + b_rstd = 1 / tl.sqrt(b_var + eps) + tl.store(rstd + i_t, b_rstd) + + if HAS_WEIGHT: + b_w = tl.load(w + i_g * D + o_d, mask=m_d).to(tl.float32) + if HAS_BIAS: + b_b = tl.load(b + i_g * D + o_d, mask=m_d).to(tl.float32) + b_x_hat = (b_x - b_mean) * b_rstd if not IS_RMS_NORM else b_x * b_rstd + b_y = b_x_hat * b_w if HAS_WEIGHT else b_x_hat + if HAS_BIAS: + b_y = b_y + b_b + + # Write output + tl.store(y + o_d, b_y, mask=m_d) + + +@triton.heuristics({ + 'RECOMPUTE_OUTPUT': lambda args: args['y'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for BT in [32, 64] + for num_warps in [2, 4, 8] + ], + key=['D', 'NB', 'HAS_DRESIDUAL', 'STORE_DRESIDUAL', 'IS_RMS_NORM'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_bwd_kernel( + x, # pointer to the input + w, # pointer to the weights + b, # pointer to the biases + y, # pointer to the output to be recomputed + dy, # pointer to the output gradient + dx, # pointer to the input gradient + dw, # pointer to the partial sum of weights gradient + db, # pointer to the partial sum of biases gradient + dres, + dres_in, + mean, + rstd, + T, + G: tl.constexpr, + D: tl.constexpr, + BS: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + GS: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + HAS_DRESIDUAL: tl.constexpr, + STORE_DRESIDUAL: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + RECOMPUTE_OUTPUT: tl.constexpr, +): + i_s = tl.program_id(0) + i_g, i_sg = i_s // GS, i_s % GS + + o_d = tl.arange(0, BD) + m_d = o_d < D + if HAS_WEIGHT: + b_w = tl.load(w + i_g * D + o_d, mask=m_d).to(tl.float32) + b_dw = tl.zeros((BT, BD), dtype=tl.float32) + if HAS_BIAS: + b_b = tl.load(b + i_g * D + o_d, mask=m_d, other=0.0).to(tl.float32) + b_db = tl.zeros((BT, BD), dtype=tl.float32) + + T = min(i_sg * BS + BS, T // G) + for i_t in range(i_sg * BS, T, BT): + p_x = tl.make_block_ptr(x + i_g * D, (T, D), (G*D, 1), (i_t, 0), (BT, BD), (1, 0)) + p_dy = tl.make_block_ptr(dy + i_g * D, (T, D), (G*D, 1), (i_t, 0), (BT, BD), (1, 0)) + p_dx = tl.make_block_ptr(dx + i_g * D, (T, D), (G*D, 1), (i_t, 0), (BT, BD), (1, 0)) + # [BT, BD] + b_x = tl.load(p_x, boundary_check=(0, 1)).to(tl.float32) + b_dy = tl.load(p_dy, boundary_check=(0, 1)).to(tl.float32) + + if not IS_RMS_NORM: + p_mean = tl.make_block_ptr(mean + i_g, (T,), (G,), (i_t,), (BT,), (0,)) + b_mean = tl.load(p_mean, boundary_check=(0,)) + p_rstd = tl.make_block_ptr(rstd + i_g, (T,), (G,), (i_t,), (BT,), (0,)) + b_rstd = tl.load(p_rstd, boundary_check=(0,)) + # Compute dx + b_xhat = (b_x - b_mean[:, None]) * b_rstd[:, None] if not IS_RMS_NORM else b_x * b_rstd[:, None] + b_xhat = tl.where(m_d[None, :], b_xhat, 0.0) + + b_y = b_xhat * b_w[None, :] if HAS_WEIGHT else b_xhat + if HAS_BIAS: + b_y = b_y + b_b[None, :] + if RECOMPUTE_OUTPUT: + p_y = tl.make_block_ptr(y + i_g * D, (T, D), (G*D, 1), (i_t, 0), (BT, BD), (1, 0)) + tl.store(p_y, b_y.to(p_y.dtype.element_ty), boundary_check=(0, 1)) + + b_wdy = b_dy + + if HAS_WEIGHT or HAS_BIAS: + m_t = (i_t + tl.arange(0, BT)) < T + if HAS_WEIGHT: + b_wdy = b_dy * b_w + b_dw += tl.where(m_t[:, None], b_dy * b_xhat, 0.0) + if HAS_BIAS: + b_db += tl.where(m_t[:, None], b_dy, 0.0) + if not IS_RMS_NORM: + b_c1 = tl.sum(b_xhat * b_wdy, axis=1) / D + b_c2 = tl.sum(b_wdy, axis=1) / D + b_dx = (b_wdy - (b_xhat * b_c1[:, None] + b_c2[:, None])) * b_rstd[:, None] + else: + b_c1 = tl.sum(b_xhat * b_wdy, axis=1) / D + b_dx = (b_wdy - b_xhat * b_c1[:, None]) * b_rstd[:, None] + if HAS_DRESIDUAL: + p_dres = tl.make_block_ptr(dres + i_g * D, (T, D), (G*D, 1), (i_t, 0), (BT, BD), (1, 0)) + b_dres = tl.load(p_dres, boundary_check=(0, 1)).to(tl.float32) + b_dx += b_dres + # Write dx + if STORE_DRESIDUAL: + p_dres_in = tl.make_block_ptr(dres_in + i_g * D, (T, D), (G*D, 1), (i_t, 0), (BT, BD), (1, 0)) + tl.store(p_dres_in, b_dx.to(p_dres_in.dtype.element_ty), boundary_check=(0, 1)) + + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), boundary_check=(0, 1)) + + if HAS_WEIGHT: + tl.store(dw + i_s * D + o_d, tl.sum(b_dw, axis=0), mask=m_d) + if HAS_BIAS: + tl.store(db + i_s * D + o_d, tl.sum(b_db, axis=0), mask=m_d) + + +@triton.heuristics({ + 'RECOMPUTE_OUTPUT': lambda args: args['y'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4, 8] + ], + key=['D', 'HAS_DRESIDUAL', 'STORE_DRESIDUAL', 'IS_RMS_NORM'], + **autotune_cache_kwargs, +) +@triton.jit +def layer_norm_bwd_kernel1( + x, # pointer to the input + w, # pointer to the weights + b, # pointer to the biases + y, # pointer to the output to be recomputed + dy, # pointer to the output gradient + dx, # pointer to the input gradient + dw, # pointer to the partial sum of weights gradient + db, # pointer to the partial sum of biases gradient + dres, + dres_in, + mean, + rstd, + T, + G: tl.constexpr, + D: tl.constexpr, + BS: tl.constexpr, + BD: tl.constexpr, + GS: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + HAS_DRESIDUAL: tl.constexpr, + STORE_DRESIDUAL: tl.constexpr, + HAS_WEIGHT: tl.constexpr, + HAS_BIAS: tl.constexpr, + RECOMPUTE_OUTPUT: tl.constexpr, +): + i_s = tl.program_id(0) + i_g, i_sg = i_s // GS, i_s % GS + + o_d = tl.arange(0, BD) + mask = o_d < D + + if HAS_WEIGHT: + b_w = tl.load(w + i_g * D + o_d, mask=mask).to(tl.float32) + b_dw = tl.zeros((BD,), dtype=tl.float32) + if RECOMPUTE_OUTPUT and HAS_BIAS: + b_b = tl.load(b + i_g * D + o_d, mask=mask, other=0.0).to(tl.float32) + if HAS_BIAS: + b_db = tl.zeros((BD,), dtype=tl.float32) + + for i_t in range(i_sg * BS * G + i_g, min((i_sg * BS + BS) * G + i_g, T), G): + b_x = tl.load(x + i_t * D + o_d, mask=mask, other=0).to(tl.float32) + b_dy = tl.load(dy + i_t * D + o_d, mask=mask, other=0).to(tl.float32) + + if not IS_RMS_NORM: + b_mean = tl.load(mean + i_t) + b_rstd = tl.load(rstd + i_t) + # Compute dx + b_xhat = (b_x - b_mean) * b_rstd if not IS_RMS_NORM else b_x * b_rstd + b_xhat = tl.where(mask, b_xhat, 0.0) + if RECOMPUTE_OUTPUT: + b_y = b_xhat * b_w if HAS_WEIGHT else b_xhat + if HAS_BIAS: + b_y = b_y + b_b + tl.store(y + i_t * D + o_d, b_y, mask=mask) + b_wdy = b_dy + if HAS_WEIGHT: + b_wdy = b_dy * b_w + b_dw += b_dy * b_xhat + if HAS_BIAS: + b_db += b_dy + if not IS_RMS_NORM: + b_c1 = tl.sum(b_xhat * b_wdy, axis=0) / D + b_c2 = tl.sum(b_wdy, axis=0) / D + b_dx = (b_wdy - (b_xhat * b_c1 + b_c2)) * b_rstd + else: + b_c1 = tl.sum(b_xhat * b_wdy, axis=0) / D + b_dx = (b_wdy - b_xhat * b_c1) * b_rstd + if HAS_DRESIDUAL: + b_dres = tl.load(dres + i_t * D + o_d, mask=mask, other=0).to(tl.float32) + b_dx += b_dres + # Write dx + b_dx = tl.cast(b_dx, dtype=dx.dtype.element_ty, fp_downcast_rounding='rtne') + if STORE_DRESIDUAL: + tl.store(dres_in + i_t * D + o_d, b_dx, mask=mask) + tl.store(dx + i_t * D + o_d, b_dx, mask=mask) + + if HAS_WEIGHT: + tl.store(dw + i_s * D + o_d, b_dw, mask=mask) + if HAS_BIAS: + tl.store(db + i_s * D + o_d, b_db, mask=mask) + + +def layer_norm_fwd( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + eps: float = 1e-5, + residual: torch.Tensor = None, + out_dtype: torch.dtype = None, + residual_dtype: torch.dtype = None, + is_rms_norm: bool = False, + num_groups: int = 1, +): + if residual is not None: + residual_dtype = residual.dtype + T, D, G = *x.shape, num_groups + if residual is not None: + assert residual.shape == (T, D) + if weight is not None: + assert weight.shape == (G * D,) + if bias is not None: + assert bias.shape == (G * D,) + # allocate output + y = torch.empty_like(x, dtype=x.dtype if out_dtype is None else out_dtype) + if residual is not None or (residual_dtype is not None and residual_dtype != x.dtype): + res_out = torch.empty(T, D, device=x.device, dtype=residual_dtype) + else: + res_out = None + mean = torch.empty((T,), dtype=torch.float, device=x.device) if not is_rms_norm else None + rstd = torch.empty((T,), dtype=torch.float, device=x.device) + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + # heuristics for number of warps + + if D <= 512: + NB = triton.cdiv(T, 2048) + def grid(meta): return (triton.cdiv(T, meta['BT']), ) + layer_norm_fwd_kernel[grid]( + x, + y, + weight, + bias, + residual, + res_out, + mean, + rstd, + eps, + T=T, + G=G, + D=D, + BD=BD, + NB=NB, + IS_RMS_NORM=is_rms_norm, + HAS_RESIDUAL=residual is not None, + STORE_RESIDUAL_OUT=res_out is not None, + HAS_WEIGHT=weight is not None, + HAS_BIAS=bias is not None, + ) + else: + layer_norm_fwd_kernel1[(T,)]( + x, + y, + weight, + bias, + residual, + res_out, + mean, + rstd, + eps, + G=G, + D=D, + BD=BD, + IS_RMS_NORM=is_rms_norm, + HAS_RESIDUAL=residual is not None, + STORE_RESIDUAL_OUT=res_out is not None, + HAS_WEIGHT=weight is not None, + HAS_BIAS=bias is not None, + ) + # res_out is None if residual is None and residual_dtype == input_dtype + return y, mean, rstd, res_out if res_out is not None else x + + +def layer_norm_bwd( + dy: torch.Tensor, + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + mean: torch.Tensor = None, + rstd: torch.Tensor = None, + dres: torch.Tensor = None, + has_residual: bool = False, + is_rms_norm: bool = False, + x_dtype: torch.dtype = None, + recompute_output: bool = False, + num_groups: int = 1, +): + T, D, G = *x.shape, num_groups + assert dy.shape == (T, D) + if dres is not None: + assert dres.shape == (T, D) + if weight is not None: + assert weight.shape == (G * D,) + if bias is not None: + assert bias.shape == (G * D,) + # allocate output + dx = torch.empty_like(x) if x_dtype is None else torch.empty(T, D, dtype=x_dtype, device=x.device) + dres_in = torch.empty_like(x) if has_residual and dx.dtype != x.dtype else None + y = torch.empty(T, D, dtype=dy.dtype, device=dy.device) if recompute_output else None + + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + # each program handles one group only + NS = triton.cdiv(get_multiprocessor_count(x.device.index), G) * G + BS = triton.cdiv(T, NS) + GS = NS // G + + dw = torch.empty((NS, D), dtype=torch.float, device=weight.device) if weight is not None else None + db = torch.empty((NS, D), dtype=torch.float, device=bias.device) if bias is not None else None + grid = (NS,) + + if D <= 512: + NB = triton.cdiv(T, 2048) + layer_norm_bwd_kernel[grid]( + x, + weight, + bias, + y, + dy, + dx, + dw, + db, + dres, + dres_in, + mean, + rstd, + T=T, + G=G, + D=D, + BS=BS, + BD=BD, + NB=NB, + GS=GS, + IS_RMS_NORM=is_rms_norm, + HAS_DRESIDUAL=dres is not None, + STORE_DRESIDUAL=dres_in is not None, + HAS_WEIGHT=weight is not None, + HAS_BIAS=bias is not None, + ) + else: + layer_norm_bwd_kernel1[grid]( + x, + weight, + bias, + y, + dy, + dx, + dw, + db, + dres, + dres_in, + mean, + rstd, + T=T, + G=G, + D=D, + BS=BS, + BD=BD, + GS=GS, + IS_RMS_NORM=is_rms_norm, + HAS_DRESIDUAL=dres is not None, + STORE_DRESIDUAL=dres_in is not None, + HAS_WEIGHT=weight is not None, + HAS_BIAS=bias is not None, + ) + dw = dw.view(G, -1, D).sum(1).to(weight).view_as(weight) if weight is not None else None + db = db.view(G, -1, D).sum(1).to(bias).view_as(bias) if bias is not None else None + # Don't need to compute dres_in separately in this case + if has_residual and dx.dtype == x.dtype: + dres_in = dx + return (dx, dw, db, dres_in) if not recompute_output else (dx, dw, db, dres_in, y) + + +class LayerNormFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x, + weight, + bias, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, + is_rms_norm: bool = False, + num_groups: int = 1, + ): + x_shape_og = x.shape + + if x.shape[-1] % num_groups != 0: + raise ValueError('num_channels must be divisible by num_groups') + # reshape input data into 2D tensor + x = x.reshape(-1, (x.shape[-1] // num_groups)) + if residual is not None: + assert residual.shape == x_shape_og + residual = residual.reshape_as(x) + residual_dtype = ( + residual.dtype + if residual is not None + else (torch.float32 if residual_in_fp32 else None) + ) + y, mean, rstd, res_out = layer_norm_fwd( + x, + weight, + bias, + eps, + residual, + residual_dtype=residual_dtype, + is_rms_norm=is_rms_norm, + num_groups=num_groups, + ) + ctx.save_for_backward(res_out, weight, bias, mean, rstd) + ctx.x_shape_og = x_shape_og + ctx.eps = eps + ctx.is_rms_norm = is_rms_norm + ctx.num_groups = num_groups + ctx.has_residual = residual is not None + ctx.prenorm = prenorm + ctx.x_dtype = x.dtype + y = y.reshape(x_shape_og) + return y if not prenorm else (y, res_out.reshape(x_shape_og)) + + @staticmethod + @input_guard + def backward(ctx, dy, *args): + x, weight, bias, mean, rstd = ctx.saved_tensors + dy = dy.reshape(-1, (dy.shape[-1] // ctx.num_groups)) + assert dy.shape == x.shape + if ctx.prenorm: + dresidual = args[0] + dresidual = dresidual.reshape(-1, x.shape[-1]) + assert dresidual.shape == x.shape + else: + dresidual = None + dx, dw, db, dresidual_in = layer_norm_bwd( + dy, + x, + weight, + bias, + mean, + rstd, + dresidual, + ctx.has_residual, + ctx.is_rms_norm, + x_dtype=ctx.x_dtype, + num_groups=ctx.num_groups, + ) + return ( + dx.reshape(ctx.x_shape_og), + dw, + db, + dresidual_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, + None, + None, + None, + None, + None, + ) + + +def layer_norm( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, + is_rms_norm: bool = False, +): + return LayerNormFunction.apply( + x, + weight, + bias, + residual, + eps, + prenorm, + residual_in_fp32, + is_rms_norm, + ) + + +def group_norm( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, + is_rms_norm: bool = False, + num_groups: int = 1, +): + return LayerNormFunction.apply( + x, + weight, + bias, + residual, + eps, + prenorm, + residual_in_fp32, + is_rms_norm, + num_groups, + ) + + +def rms_norm( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, +): + return LayerNormFunction.apply( + x, + weight, + bias, + residual, + eps, + prenorm, + residual_in_fp32, + True, + ) + + +def layer_norm_linear( + x: torch.Tensor, + norm_weight: torch.Tensor, + norm_bias: torch.Tensor, + linear_weight: torch.Tensor, + linear_bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, + is_rms_norm: bool = False, + num_groups: int = 1, +): + return LayerNormLinearFunction.apply( + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual, + eps, + prenorm, + residual_in_fp32, + is_rms_norm, + num_groups, + ) + + +def rms_norm_linear( + x: torch.Tensor, + norm_weight: torch.Tensor, + norm_bias: torch.Tensor, + linear_weight: torch.Tensor, + linear_bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, +): + return layer_norm_linear( + x=x, + norm_weight=norm_weight, + norm_bias=norm_bias, + linear_weight=linear_weight, + linear_bias=linear_bias, + residual=residual, + eps=eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + is_rms_norm=True, + ) + + +def group_norm_linear( + x: torch.Tensor, + norm_weight: torch.Tensor, + norm_bias: torch.Tensor, + linear_weight: torch.Tensor, + linear_bias: torch.Tensor, + residual: torch.Tensor = None, + eps: float = 1e-5, + prenorm: bool = False, + residual_in_fp32: bool = False, + is_rms_norm: bool = False, + num_groups: int = 1, +): + return layer_norm_linear( + x=x, + norm_weight=norm_weight, + norm_bias=norm_bias, + linear_weight=linear_weight, + linear_bias=linear_bias, + residual=residual, + eps=eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + is_rms_norm=is_rms_norm, + num_groups=num_groups, + ) + + +class LayerNorm(nn.Module): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + ) -> LayerNorm: + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward(self, x, residual=None, prenorm=False, residual_in_fp32=False): + return layer_norm( + x, + self.weight, + self.bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + ) + + +class GroupNorm(nn.Module): + + def __init__( + self, + num_groups: int, + hidden_size: int, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + is_rms_norm: bool = False, + ) -> GroupNorm: + super().__init__() + + if hidden_size % num_groups != 0: + raise ValueError('num_channels must be divisible by num_groups') + + self.num_groups = num_groups + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + self.is_rms_norm = is_rms_norm + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.num_groups}, {self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + if self.is_rms_norm: + s += f", is_rms_norm={self.is_rms_norm}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward(self, x, residual=None, prenorm=False, residual_in_fp32=False): + return group_norm( + x, + self.weight, + self.bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + is_rms_norm=self.is_rms_norm, + num_groups=self.num_groups, + ) + + +class RMSNorm(nn.Module): + + def __init__( + self, + hidden_size: int, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + ) -> RMSNorm: + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward(self, x, residual=None, prenorm=False, residual_in_fp32=False): + return rms_norm( + x, + self.weight, + self.bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + ) + + +class LayerNormLinearFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual=None, + eps=1e-5, + prenorm=False, + residual_in_fp32=False, + is_rms_norm=False, + num_groups=1, + ): + x_shape_og = x.shape + + if x.shape[-1] % num_groups != 0: + raise ValueError('num_channels must be divisible by num_groups') + # reshape input data into 2D tensor + x = x.reshape(-1, (x.shape[-1] // num_groups)) + if residual is not None: + assert residual.shape == x_shape_og + residual = residual.reshape_as(x) + residual_dtype = ( + residual.dtype + if residual is not None + else (torch.float32 if residual_in_fp32 else None) + ) + y, mean, rstd, res_out = layer_norm_fwd( + x, + norm_weight, + norm_bias, + eps, + residual, + out_dtype=None if not torch.is_autocast_enabled() else torch.get_autocast_gpu_dtype(), + residual_dtype=residual_dtype, + is_rms_norm=is_rms_norm, + num_groups=num_groups, + ) + y = y.reshape(x_shape_og) + dtype = torch.get_autocast_gpu_dtype() if torch.is_autocast_enabled() else y.dtype + linear_weight = linear_weight.to(dtype) + linear_bias = linear_bias.to(dtype) if linear_bias is not None else None + out = F.linear(y.to(linear_weight.dtype), linear_weight, linear_bias) + # We don't store y, will be recomputed in the backward pass to save memory + ctx.save_for_backward(res_out, norm_weight, norm_bias, linear_weight, mean, rstd) + ctx.x_shape_og = x_shape_og + ctx.eps = eps + ctx.is_rms_norm = is_rms_norm + ctx.num_groups = num_groups + ctx.has_residual = residual is not None + ctx.prenorm = prenorm + ctx.x_dtype = x.dtype + ctx.linear_bias_is_none = linear_bias is None + return out if not prenorm else (out, res_out.reshape(x_shape_og)) + + @staticmethod + @input_guard + def backward(ctx, dout, *args): + x, norm_weight, norm_bias, linear_weight, mean, rstd = ctx.saved_tensors + dout = dout.reshape(-1, dout.shape[-1]) + dy = F.linear(dout, linear_weight.t()) + dy = dy.reshape(-1, (dy.shape[-1] // ctx.num_groups)) + dlinear_bias = None if ctx.linear_bias_is_none else dout.sum(0) + assert dy.shape == x.shape + if ctx.prenorm: + dresidual = args[0] + dresidual = dresidual.reshape(-1, x.shape[-1]) + assert dresidual.shape == x.shape + else: + dresidual = None + dx, dnorm_weight, dnorm_bias, dresidual_in, y = layer_norm_bwd( + dy, + x, + norm_weight, + norm_bias, + mean, + rstd, + dresidual, + ctx.has_residual, + ctx.is_rms_norm, + x_dtype=ctx.x_dtype, + recompute_output=True, + num_groups=ctx.num_groups, + ) + dlinear_weight = torch.einsum("bo,bi->oi", dout, y.view(-1, linear_weight.shape[-1])) + return ( + dx.reshape(ctx.x_shape_og), + dnorm_weight, + dnorm_bias, + dlinear_weight, + dlinear_bias, + dresidual_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, + None, + None, + None, + None, + None, + ) + + +class LayerNormLinear(nn.Module): + + def __init__( + self, + hidden_size, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + ) -> LayerNormLinear: + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward(self, x, weight, bias, residual=None, prenorm=False, residual_in_fp32=False): + return layer_norm_linear( + x=x, + norm_weight=self.weight, + norm_bias=self.bias, + linear_weight=weight, + linear_bias=bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + is_rms_norm=False, + ) + + +class GroupNormLinear(nn.Module): + + def __init__( + self, + num_groups: int, + hidden_size: int, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + is_rms_norm: bool = False, + ) -> GroupNormLinear: + super().__init__() + + if hidden_size % num_groups != 0: + raise ValueError('num_channels must be divisible by num_groups') + + self.num_groups = num_groups + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + self.is_rms_norm = is_rms_norm + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.num_groups}, {self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + if self.is_rms_norm: + s += f", is_rms_norm={self.is_rms_norm}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward(self, x, weight, bias, residual=None, prenorm=False, residual_in_fp32=False): + return layer_norm_linear( + x=x, + norm_weight=self.weight, + norm_bias=self.bias, + linear_weight=weight, + linear_bias=bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + is_rms_norm=self.is_rms_norm, + num_groups=self.num_groups, + ) + + +class RMSNormLinear(nn.Module): + + def __init__( + self, + hidden_size, + elementwise_affine: bool = True, + bias: bool = False, + eps: float = 1e-5, + ) -> RMSNormLinear: + super().__init__() + + self.hidden_size = hidden_size + self.elementwise_affine = elementwise_affine + self.eps = eps + + self.register_parameter("weight", None) + self.register_parameter("bias", None) + if elementwise_affine: + self.weight = nn.Parameter(torch.empty(hidden_size)) + if bias: + self.bias = nn.Parameter(torch.empty(hidden_size)) + + self.reset_parameters() + + def reset_parameters(self): + if self.elementwise_affine: + nn.init.ones_(self.weight) + if self.bias is not None: + nn.init.zeros_(self.bias) + + def __repr__(self) -> str: + s = f"{self.__class__.__name__}({self.hidden_size}" + if not self.elementwise_affine: + s += f", elementwise_affine={self.elementwise_affine}" + s += f", eps={self.eps}" + s += ")" + return s + + def forward(self, x, weight, bias, residual=None, prenorm=False, residual_in_fp32=False): + return layer_norm_linear( + x=x, + norm_weight=self.weight, + norm_bias=self.bias, + linear_weight=weight, + linear_bias=bias, + residual=residual, + eps=self.eps, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + is_rms_norm=True, + ) + + +class NormParallel(ParallelStyle): + + def __init__(self, *, sequence_dim: int = 1, use_local_output: bool = False): + super().__init__() + self.sequence_sharding = (Shard(sequence_dim),) + self.use_local_output = use_local_output + + def _replicate_module_fn( + self, name: str, module: nn.Module, device_mesh: DeviceMesh, + ): + for p_name, param in module.named_parameters(): + # simple replication with fixed ones_ init from LayerNorm/RMSNorm, which allow + # us to simply just use from_local + replicated_param = torch.nn.Parameter( + DTensor.from_local(param, device_mesh, [Replicate()], run_check=False), + ) + module.register_parameter(p_name, replicated_param) + + @staticmethod + def _prepare_input_fn(sequence_sharding, mod, inputs, device_mesh): + input_tensor = inputs[0] + if isinstance(input_tensor, DTensor): + # if the passed in input DTensor is not sharded on the sequence dim, we need to redistribute it + if input_tensor.placements != sequence_sharding: + input_tensor = input_tensor.redistribute( + placements=sequence_sharding, async_op=True, + ) + return input_tensor + elif isinstance(input_tensor, torch.Tensor): + # assume the input passed in already sharded on the sequence dim and create the DTensor + return DTensor.from_local( + input_tensor, device_mesh, sequence_sharding, run_check=False, + ) + else: + raise ValueError( + f"expecting input of {mod} to be a torch.Tensor or DTensor, but got {input_tensor}", + ) + + @staticmethod + def _prepare_output_fn(use_local_output, mod, outputs, device_mesh): + return outputs.to_local() if use_local_output else outputs + + def _apply(self, module: nn.Module, device_mesh: DeviceMesh) -> nn.Module: + return distribute_module( + module, + device_mesh, + self._replicate_module_fn, + partial(self._prepare_input_fn, self.sequence_sharding), + partial(self._prepare_output_fn, self.use_local_output), + ) diff --git a/code/flash-linear-attention/fla/modules/layernorm_gated.py b/code/flash-linear-attention/fla/modules/layernorm_gated.py new file mode 100644 index 0000000000000000000000000000000000000000..7702653c0832e49ff10871f3046fc522be7fe998 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/layernorm_gated.py @@ -0,0 +1,527 @@ +# Copyright (c) 2024, Tri Dao. +# Based on the Triton LayerNorm tutorial: https://triton-lang.org/main/getting-started/tutorials/05-layer-norm.html +# For the backward pass, we keep weight_grad and bias_grad in registers and accumulate. +# This backward pass is faster for dimensions up to 8k, but after that it's much slower due to register spilling. +# The models we train have hidden dim up to 8k anyway (e.g. Llama 70B), so this is fine. + +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl +from einops import rearrange + +from fla.utils import get_multiprocessor_count, input_guard + + +def rms_norm_ref(x, weight, bias, z=None, eps=1e-6, group_size=None, norm_before_gate=True, upcast=True): + dtype = x.dtype + weight = weight.float() + bias = bias.float() if bias is not None else None + if upcast: + x = x.float() + z = z.float() if z is not None else z + if z is not None and not norm_before_gate: + x = x * F.silu(z) + if group_size is None: + rstd = 1 / torch.sqrt((x.square()).mean(dim=-1, keepdim=True) + eps) + out = (x * rstd * weight) + bias if bias is not None else (x * rstd * weight) + else: + x_group = rearrange(x, "... (g d) -> ... g d", d=group_size) + rstd = 1 / torch.sqrt((x_group.square()).mean(dim=-1, keepdim=True) + eps) + out = rearrange(x_group * rstd, "... g d -> ... (g d)") * weight + if bias is not None: + out = out + bias + if z is not None and norm_before_gate: + out *= F.silu(z) + return out.to(dtype) + + +@triton.heuristics({ + "HAS_BIAS": lambda args: args["B"] is not None, + "HAS_Z": lambda args: args["Z"] is not None, +}) +@triton.jit +def layer_norm_fwd_kernel( + X, # pointer to the input + Y, # pointer to the output + W, # pointer to the weights + B, # pointer to the biases + Z, # pointer to the other branch + Mean, # pointer to the mean + Rstd, # pointer to the 1/std + stride_x_row, # how much to increase the pointer when moving by 1 row + stride_y_row, + stride_z_row, + M, # number of rows in X + N, # number of columns in X + eps, # epsilon to avoid division by zero + BLOCK_N: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_Z: tl.constexpr, + NORM_BEFORE_GATE: tl.constexpr, + IS_RMS_NORM: tl.constexpr, +): + # Map the program id to the row of X and Y it should compute. + row = tl.program_id(0) + group = tl.program_id(1) + X += row * stride_x_row + group * N + Y += row * stride_y_row + group * N + if HAS_Z: + Z += row * stride_z_row + group * N + if not IS_RMS_NORM: + Mean += group * M + Rstd += group * M + W += group * N + if HAS_BIAS: + B += group * N + # Compute mean and variance + cols = tl.arange(0, BLOCK_N) + x = tl.load(X + cols, mask=cols < N, other=0.).to(tl.float32) + if HAS_Z and not NORM_BEFORE_GATE: + z = tl.load(Z + cols, mask=cols < N).to(tl.float32) + x *= z * tl.sigmoid(z) + if not IS_RMS_NORM: + mean = tl.sum(x, axis=0) / N + tl.store(Mean + row, mean) + xbar = tl.where(cols < N, x - mean, 0.) + var = tl.sum(xbar * xbar, axis=0) / N + else: + xbar = tl.where(cols < N, x, 0.) + var = tl.sum(xbar * xbar, axis=0) / N + rstd = 1 / tl.sqrt(var + eps) + tl.store(Rstd + row, rstd) + # Normalize and apply linear transformation + mask = cols < N + w = tl.load(W + cols, mask=mask).to(tl.float32) + if HAS_BIAS: + b = tl.load(B + cols, mask=mask).to(tl.float32) + x_hat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd + y = x_hat * w + b if HAS_BIAS else x_hat * w + if HAS_Z and NORM_BEFORE_GATE: + z = tl.load(Z + cols, mask=mask).to(tl.float32) + y *= z * tl.sigmoid(z) + # Write output + tl.store(Y + cols, y, mask=mask) + + +def layer_norm_fwd( + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + eps: float, + z: torch.Tensor = None, + out: torch.Tensor = None, + group_size: int = None, + norm_before_gate: bool = True, + is_rms_norm: bool = False, +): + M, N = x.shape + if group_size is None: + group_size = N + assert N % group_size == 0 + ngroups = N // group_size + assert x.stride(-1) == 1 + if z is not None: + assert z.stride(-1) == 1 + assert z.shape == (M, N) + assert weight.shape == (N,) + assert weight.stride(-1) == 1 + if bias is not None: + assert bias.stride(-1) == 1 + assert bias.shape == (N,) + # allocate output + if out is not None: + assert out.shape == x.shape + else: + out = torch.empty_like(x) + assert out.stride(-1) == 1 + mean = torch.empty((ngroups * M, ), dtype=torch.float32, device=x.device) if not is_rms_norm else None + rstd = torch.empty((ngroups * M, ), dtype=torch.float32, device=x.device) + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(group_size)) + if group_size > BLOCK_N: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + # heuristics for number of warps + num_warps = min(max(BLOCK_N // 256, 1), 8) + grid = (M, ngroups) + layer_norm_fwd_kernel[grid]( + x, + out, + weight, + bias, + z, + mean, + rstd, + x.stride(0), + out.stride(0), + z.stride(0) if z is not None else 0, + M, + group_size, + eps, + BLOCK_N=BLOCK_N, + NORM_BEFORE_GATE=norm_before_gate, + IS_RMS_NORM=is_rms_norm, + num_warps=num_warps, + ) + return out, mean, rstd + + +@triton.heuristics({ + "HAS_BIAS": lambda args: args["B"] is not None, + "HAS_Z": lambda args: args["Z"] is not None, + "RECOMPUTE_OUTPUT": lambda args: args["Y"] is not None, +}) +@triton.jit +def layer_norm_bwd_kernel( + X, # pointer to the input + W, # pointer to the weights + B, # pointer to the biases + Z, # pointer to the other branch + Y, # pointer to the output to be recomputed + DY, # pointer to the output gradient + DX, # pointer to the input gradient + DW, # pointer to the partial sum of weights gradient + DB, # pointer to the partial sum of biases gradient + DZ, # pointer to the other branch + Mean, # pointer to the mean + Rstd, # pointer to the 1/std + stride_x_row, # how much to increase the pointer when moving by 1 row + stride_z_row, + stride_y_row, + stride_dy_row, + stride_dx_row, + stride_dz_row, + stride_dw_row, + stride_db_row, + M, # number of rows in X + N, # number of columns in X + eps, # epsilon to avoid division by zero + rows_per_program, + NORM_BEFORE_GATE: tl.constexpr, + IS_RMS_NORM: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_Z: tl.constexpr, + RECOMPUTE_OUTPUT: tl.constexpr, + BLOCK_N: tl.constexpr, +): + # Map the program id to the elements of X, DX, and DY it should compute. + row_block_id = tl.program_id(0) + group = tl.program_id(1) + row_start = row_block_id * rows_per_program + cols = tl.arange(0, BLOCK_N) + mask = cols < N + X += row_start * stride_x_row + group * N + if HAS_Z: + Z += row_start * stride_z_row + group * N + DZ += row_start * stride_dz_row + group * N + DY += row_start * stride_dy_row + group * N + DX += row_start * stride_dx_row + group * N + if RECOMPUTE_OUTPUT: + Y += row_start * stride_y_row + group * N + if not IS_RMS_NORM: + Mean += group * M + Rstd += group * M + W += group * N + w = tl.load(W + cols, mask=mask).to(tl.float32) + if (RECOMPUTE_OUTPUT or HAS_Z) and HAS_BIAS: + B += group * N + b = tl.load(B + cols, mask=mask, other=0.).to(tl.float32) + dw = tl.zeros((BLOCK_N,), dtype=tl.float32) + if HAS_BIAS: + db = tl.zeros((BLOCK_N,), dtype=tl.float32) + row_end = min((row_block_id + 1) * rows_per_program, M) + for row in range(row_start, row_end): + # Load data to SRAM + x = tl.load(X + cols, mask=mask, other=0).to(tl.float32) + dy = tl.load(DY + cols, mask=mask, other=0).to(tl.float32) + if not IS_RMS_NORM: + mean = tl.load(Mean + row) + if HAS_Z and not NORM_BEFORE_GATE: + z = tl.load(Z + cols, mask=mask, other=0.).to(tl.float32) + x_og = x + x = x_og * z * tl.sigmoid(z) + rstd = tl.load(Rstd + row) + # Compute dx + xhat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd + xhat = tl.where(mask, xhat, 0.) + if HAS_Z and NORM_BEFORE_GATE: + z = tl.load(Z + cols, mask=mask, other=0.).to(tl.float32) + z_sigmoid = tl.sigmoid(z) + y = xhat * w + b if HAS_BIAS else xhat * w + if RECOMPUTE_OUTPUT: + tl.store(Y + cols, y * z * z_sigmoid, mask=mask) + dz = dy * y * z_sigmoid * (1 + z * (1 - z_sigmoid)) + tl.store(DZ + cols, dz, mask=mask) + dy *= z * z_sigmoid + else: + if RECOMPUTE_OUTPUT: + y = xhat * w + b if HAS_BIAS else xhat * w + tl.store(Y + cols, y, mask=mask) + wdy = w * dy + c1 = tl.sum(xhat * wdy, axis=0) / N + if not IS_RMS_NORM: + c2 = tl.sum(wdy, axis=0) / N + dx = (wdy - (xhat * c1 + c2)) * rstd + else: + dx = (wdy - xhat * c1) * rstd + dw += dy * xhat + if HAS_BIAS: + db += dy + if HAS_Z and not NORM_BEFORE_GATE: + z_sigmoid = tl.sigmoid(z) + dz = dx * x_og * z_sigmoid * (1 + z * (1 - z_sigmoid)) + tl.store(DZ + cols, dz, mask=mask) + dx *= z * z_sigmoid + # Write dx + tl.store(DX + cols, dx, mask=mask) + + X += stride_x_row + if HAS_Z: + Z += stride_z_row + DZ += stride_dz_row + if RECOMPUTE_OUTPUT: + Y += stride_y_row + DY += stride_dy_row + DX += stride_dx_row + tl.store(DW + row_block_id * stride_dw_row + group * N + cols, dw, mask=mask) + if HAS_BIAS: + tl.store(DB + row_block_id * stride_db_row + group * N + cols, db, mask=mask) + + +def layer_norm_bwd( + dy: torch.Tensor, + x: torch.Tensor, + weight: torch.Tensor, + bias: torch.Tensor, + eps: float, + mean: torch.Tensor, + rstd: torch.Tensor, + z: torch.Tensor = None, + group_size: int = None, + norm_before_gate: bool = True, + is_rms_norm: bool = False, + recompute_output: bool = False, + dz: torch.Tensor = None, + out: torch.Tensor = None, +): + M, N = x.shape + if group_size is None: + group_size = N + assert N % group_size == 0 + ngroups = N // group_size + assert x.stride(-1) == 1 + assert dy.stride(-1) == 1 + assert dy.shape == (M, N) + if z is not None: + assert z.stride(-1) == 1 + assert z.shape == (M, N) + assert weight.shape == (N,) + assert weight.stride(-1) == 1 + if bias is not None: + assert bias.stride(-1) == 1 + assert bias.shape == (N,) + # allocate output + dx = torch.empty_like(x) + if dz is not None: + assert z is not None + assert dz.shape == z.shape + assert dz.stride(-1) == 1 + else: + dz = torch.empty_like(z) if z is not None else None + if recompute_output: + if out is None: + out = torch.empty_like(x) + assert out.shape == x.shape + + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(group_size)) + if group_size > BLOCK_N: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + # heuristics for number of warps + num_warps = min(max(BLOCK_N // 256, 1), 8) + sm_count = get_multiprocessor_count(x.device.index) + # If group size is small (e.g., 64), we're only using 1 warp. So having just 108 programs + # would limit the occupancy. + nrow_groups = math.ceil(sm_count * math.ceil(4 / num_warps) / ngroups) + _dw = torch.empty((nrow_groups, N), dtype=torch.float32, device=weight.device) + _db = torch.empty((nrow_groups, N), dtype=torch.float32, device=bias.device) if bias is not None else None + rows_per_program = math.ceil(M / nrow_groups) + grid = (nrow_groups, ngroups) + layer_norm_bwd_kernel[grid]( + x, + weight, + bias, + z, + out if recompute_output else None, + dy, + dx, + _dw, + _db, + dz, + mean, + rstd, + x.stride(0), + z.stride(0) if z is not None else 0, + 0 if not recompute_output else out.stride(0), + dy.stride(0), + dx.stride(0), + dz.stride(0) if dz is not None else 0, + _dw.stride(0), + _db.stride(0) if _db is not None else 0, + M, group_size, eps, + rows_per_program, + BLOCK_N=BLOCK_N, + NORM_BEFORE_GATE=norm_before_gate, + IS_RMS_NORM=is_rms_norm, + num_warps=num_warps, + ) + dw = _dw.sum(0).to(weight.dtype) + db = _db.sum(0).to(bias.dtype) if bias is not None else None + return (dx, dw, db, dz) if not recompute_output else (dx, dw, db, dz, out) + + +class LayerNormFn(torch.autograd.Function): + + @input_guard + @staticmethod + def forward(ctx, x, weight, bias, z=None, eps=1e-6, group_size=None, norm_before_gate=True, + is_rms_norm=False): + """If z is not None, we do norm(x) * silu(z) if norm_before_gate, else norm(x * silu(z)) + """ + + x_shape_og = x.shape + # reshape input data into 2D tensor + x = x.reshape(-1, x.shape[-1]) + if x.stride(-1) != 1: + x = x.contiguous() + if z is not None: + assert z.shape == x_shape_og + z = z.reshape(-1, z.shape[-1]) + if z.stride(-1) != 1: + z = z.contiguous() + weight = weight.contiguous() + if bias is not None: + bias = bias.contiguous() + y, mean, rstd = layer_norm_fwd( + x, + weight, + bias, + eps, + z=z, + group_size=group_size, + norm_before_gate=norm_before_gate, + is_rms_norm=is_rms_norm, + ) + ctx.save_for_backward(x, weight, bias, mean, rstd, z) + ctx.x_shape_og = x_shape_og + ctx.eps = eps + ctx.group_size = group_size + ctx.norm_before_gate = norm_before_gate + ctx.is_rms_norm = is_rms_norm + return y.reshape(x_shape_og) + + @input_guard + @staticmethod + def backward(ctx, dy): + x, weight, bias, mean, rstd, z = ctx.saved_tensors + dy = dy.reshape(-1, dy.shape[-1]) + if dy.stride(-1) != 1: + dy = dy.contiguous() + assert dy.shape == x.shape + dx, dw, db, dz = layer_norm_bwd( + dy, + x, + weight, + bias, + ctx.eps, + mean, + rstd, + z, + ctx.group_size, + ctx.norm_before_gate, + ctx.is_rms_norm, + ) + dx = dx.reshape(ctx.x_shape_og) + dz = dz.reshape(ctx.x_shape_og) if dz is not None else None + return dx, dw, db, dz, None, None, None, None + + +def layernorm_fn(x, weight, bias, z=None, eps=1e-6, group_size=None, norm_before_gate=True, is_rms_norm=False): + return LayerNormFn.apply(x, weight, bias, z, eps, group_size, norm_before_gate, is_rms_norm) + + +def rmsnorm_fn(x, weight, bias, z=None, eps=1e-6, group_size=None, norm_before_gate=True): + return LayerNormFn.apply(x, weight, bias, z, eps, group_size, norm_before_gate, True) + + +class LayerNormGated(nn.Module): + + def __init__( + self, + hidden_size, + eps: float = 1e-5, + group_size: int | None = None, + norm_before_gate: bool = True, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ): + """If group_size is not None, we do GroupNorm with each group having group_size elements. + group_size=None is equivalent to group_size=hidden_size (i.e. there's only 1 group). + """ + + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + self.bias = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + self.group_size = group_size + self.norm_before_gate = norm_before_gate + self.reset_parameters() + + def reset_parameters(self): + torch.nn.init.ones_(self.weight) + torch.nn.init.zeros_(self.bias) + + def forward(self, x, z=None): + """If z is not None, we do norm(x) * silu(z) if norm_before_gate, else norm(x * silu(z)) + """ + return layernorm_fn(x, self.weight, self.bias, z=z, group_size=self.group_size, eps=self.eps, + norm_before_gate=self.norm_before_gate) + + +class RMSNormGated(nn.Module): + + def __init__( + self, + hidden_size, + eps: float = 1e-5, + group_size: int | None = None, + norm_before_gate: bool = False, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ): + """If group_size is not None, we do GroupNorm with each group having group_size elements. + group_size=None is equivalent to group_size=hidden_size (i.e. there's only 1 group). + """ + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + self.register_parameter("bias", None) + self.group_size = group_size + self.norm_before_gate = norm_before_gate + self.reset_parameters() + + def reset_parameters(self): + torch.nn.init.ones_(self.weight) + + def forward(self, x, z=None): + """If z is not None, we do norm(x) * silu(z) if norm_before_gate, else norm(x * silu(z)) + """ + return rmsnorm_fn(x, self.weight, self.bias, z=z, eps=self.eps, group_size=self.group_size, + norm_before_gate=self.norm_before_gate) diff --git a/code/flash-linear-attention/fla/modules/mlp.py b/code/flash-linear-attention/fla/modules/mlp.py new file mode 100644 index 0000000000000000000000000000000000000000..dc0e8a2d418cc76099666a51c001b13f5ae3ce40 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/mlp.py @@ -0,0 +1,144 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +from __future__ import annotations + +from functools import partial +from typing import TYPE_CHECKING, Any + +import torch +import torch.nn as nn +try: + from torch.distributed import DeviceMesh +except ImportError: + DeviceMesh = None +try: + from torch.distributed.tensor import Placement, Replicate, Shard, distribute_module +except ImportError: + Placement = None + Replicate = None + Shard = None + distribute_module = None +try: + from torch.distributed.tensor.parallel import ParallelStyle +except ImportError: + class ParallelStyle: + pass + +from fla.modules.activations import swiglu, swiglu_linear + +try: + from torch.distributed.tensor import DTensor +except (ImportError, AttributeError): + DTensor = None + +if TYPE_CHECKING: + from transformers.processing_utils import Unpack + + +class GatedMLP(nn.Module): + + def __init__( + self, + hidden_size: int, + hidden_ratio: int | None = None, + intermediate_size: int | None = None, + hidden_act: str = 'swish', + fuse_swiglu: bool = True, + ) -> GatedMLP: + super().__init__() + + self.hidden_size = hidden_size + # the final number of params is `hidden_ratio * hidden_size^2` + # `intermediate_size` is chosen to be a multiple of 256 closest to `2/3 * hidden_size * hidden_ratio` + if hidden_ratio is None: + hidden_ratio = 4 + if intermediate_size is None: + intermediate_size = int(hidden_size * hidden_ratio * 2 / 3) + intermediate_size = 256 * ((intermediate_size + 256 - 1) // 256) + self.hidden_ratio = hidden_ratio + self.intermediate_size = intermediate_size + self.hidden_act = hidden_act + self.fuse_swiglu = fuse_swiglu + + if hidden_act != 'swish': + raise ValueError(f'Unsupported hidden_act: {hidden_act}') + + self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) + if self.fuse_swiglu: + self.swiglu_linear = SwiGLULinear() + + def forward( + self, + x: torch.Tensor, + **kwargs: Unpack[Any], + ) -> torch.Tensor: + gate, y = self.gate_proj(x), self.up_proj(x) + if self.fuse_swiglu: + return self.swiglu_linear(gate, y, self.down_proj.weight, self.down_proj.bias) + else: + return self.down_proj(swiglu(gate, y)) + + +class SwiGLULinear(nn.Module): + + def forward(self, x, y, weight, bias): + return swiglu_linear(x, y, weight, bias) + + +class SwiGLULinearParallel(ParallelStyle): + def __init__( + self, + *, + input_layouts: Placement | None = None, + output_layouts: Placement | None = None, + use_local_output: bool = True, + ): + super().__init__() + self.input_layouts = (input_layouts or Shard(-1),) + self.output_layouts = (output_layouts or Replicate(),) + self.desired_input_layouts = (Shard(-1),) + self.use_local_output = use_local_output + + @staticmethod + def _prepare_input_fn( + input_layouts, desired_input_layouts, mod, inputs, device_mesh, + ): + x, y, weight, bias = inputs + if not isinstance(x, DTensor): + x = DTensor.from_local(x, device_mesh, input_layouts, run_check=False) + if x.placements != desired_input_layouts: + x = x.redistribute(placements=desired_input_layouts, async_op=True) + + if not isinstance(y, DTensor): + y = DTensor.from_local(y, device_mesh, input_layouts, run_check=False) + if y.placements != desired_input_layouts: + y = y.redistribute(placements=desired_input_layouts, async_op=True) + + if not isinstance(weight, DTensor): + weight = DTensor.from_local(weight, device_mesh, (Shard(1),)) + + if bias is not None and not isinstance(bias, DTensor): + bias = DTensor.from_local(bias, device_mesh, (Replicate(),)) + + return x, y, weight, bias + + @staticmethod + def _prepare_output_fn(output_layouts, use_local_output, mod, outputs, device_mesh): + # Rowwise sharding produces partial output, depending on output layouts: + # 1. to replicate -> allreduce + # 2. to shard -> reduce_scatter + if outputs.placements != output_layouts: + outputs = outputs.redistribute(placements=output_layouts, async_op=True) + # back to local tensor if use_local_output is True + return outputs.to_local() if use_local_output else outputs + + def _apply(self, module: nn.Module, device_mesh: DeviceMesh) -> nn.Module: + return distribute_module( + module, + device_mesh, + partition_fn=None, + input_fn=partial(self._prepare_input_fn, self.input_layouts, self.desired_input_layouts), + output_fn=partial(self._prepare_output_fn, self.output_layouts, self.use_local_output), + ) diff --git a/code/flash-linear-attention/fla/modules/parallel.py b/code/flash-linear-attention/fla/modules/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..eaeb514a08afbe93a69b855d0c9479e53f7af8a7 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/parallel.py @@ -0,0 +1,53 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch.nn as nn +try: + from torch.distributed import DeviceMesh +except ImportError: + DeviceMesh = None +try: + from torch.distributed.tensor import distribute_module +except ImportError: + distribute_module = None +try: + from torch.distributed.tensor.parallel import ParallelStyle +except ImportError: + class ParallelStyle: + pass +try: + from torch.distributed.tensor.placement_types import Placement +except ImportError: + Placement = None + +try: + from torch.distributed.tensor import DTensor +except (ImportError, AttributeError): + DTensor = None + + +class PrepareModuleWeight(ParallelStyle): + def __init__(self, *, layouts: Placement | None = None): + super().__init__() + self.layouts = layouts + + def _replicate_module_fn( + self, + name: str, + module: nn.Module, + device_mesh: DeviceMesh, + ): + for p_name, param in module.named_parameters(): + replicated_param = nn.Parameter( + DTensor.from_local(param, device_mesh, [self.layouts], run_check=False), + ) + module.register_parameter(p_name, replicated_param) + + def _apply(self, module: nn.Module, device_mesh: DeviceMesh) -> nn.Module: + return distribute_module( + module, + device_mesh, + partition_fn=self._replicate_module_fn, + input_fn=None, + output_fn=None, + ) diff --git a/code/flash-linear-attention/fla/modules/rotary.py b/code/flash-linear-attention/fla/modules/rotary.py new file mode 100644 index 0000000000000000000000000000000000000000..07aafa402429dfa77fb54df373026e3e71854c73 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/rotary.py @@ -0,0 +1,499 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import torch.nn as nn +import triton +import triton.language as tl +from einops import rearrange, repeat + +from fla.ops.utils import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs, get_multiprocessor_count, input_guard, is_amd + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +def rotate_half(x, interleaved=False): + if not interleaved: + x1, x2 = x.chunk(2, dim=-1) + return torch.cat((-x2, x1), dim=-1) + else: + x1, x2 = x[..., ::2], x[..., 1::2] + return rearrange(torch.stack((-x2, x1), dim=-1), '... d two -> ... (d two)', two=2) + + +def rotary_embedding_ref(x, cos, sin, interleaved=False): + ro_dim = cos.shape[-1] * 2 + assert ro_dim <= x.shape[-1] + cos = repeat(cos, '... d -> ... 1 (2 d)' if not interleaved else '... d -> ... 1 (d 2)') + sin = repeat(sin, '... d -> ... 1 (2 d)' if not interleaved else '... d -> ... 1 (d 2)') + return torch.cat([x[..., :ro_dim] * cos + rotate_half(x[..., :ro_dim], interleaved) * sin, x[..., ro_dim:]], -1) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['B', 'H', 'D', 'INTERLEAVED'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def rotary_embedding_kernel( + x, + cos, + sin, + y, + cu_seqlens, + chunk_indices, + seq_offsets, + T, + B: tl.constexpr, + H: tl.constexpr, + D: tl.constexpr, + R: tl.constexpr, + TR: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + IS_SEQLEN_OFFSETS_TENSOR: tl.constexpr, + IS_VARLEN: tl.constexpr, + INTERLEAVED: tl.constexpr, + CONJUGATE: tl.constexpr, +): + i_t, i_b, i_h = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n), tl.load(cu_seqlens + i_n + 1) + T = eos - bos + x = x + bos * H*D + i_h * D + y = y + bos * H*D + i_h * D + else: + i_n = i_b + x = x + i_n * T*H*D + i_h * D + y = y + i_n * T*H*D + i_h * D + + if i_t * BT >= T: + return + + o_t = i_t * BT + tl.arange(0, BT) + if not IS_SEQLEN_OFFSETS_TENSOR: + o_cs = o_t + seq_offsets + else: + o_cs = o_t + tl.load(seq_offsets + i_n) + m_t = (o_t >= 0) & (o_t < T) & (o_cs >= 0) & (o_cs < TR) + + if not INTERLEAVED: + # Load the 1st and 2nd halves of x, do calculation, then store to 1st and 2nd halves of out + o_r = tl.arange(0, BD // 2) + p_x = x + o_t[:, None] * H*D + o_r[None, :] + p_cos = cos + (o_cs[:, None] * R + o_r[None, :]) + p_sin = sin + (o_cs[:, None] * R + o_r[None, :]) + mask = m_t[:, None] & (o_r < R)[None, :] + + b_cos = tl.load(p_cos, mask=mask, other=1.0).to(tl.float32) + b_sin = tl.load(p_sin, mask=mask, other=0.0).to(tl.float32) + b_x0 = tl.load(p_x, mask=mask, other=0.0).to(tl.float32) + b_x1 = tl.load(p_x + R, mask=mask, other=0.0).to(tl.float32) + if CONJUGATE: + b_sin = -b_sin + b_o0 = b_x0 * b_cos - b_x1 * b_sin + b_o1 = b_x0 * b_sin + b_x1 * b_cos + # write back result + p_y = y + (o_t[:, None] * H*D + o_r[None, :]) + tl.store(p_y, b_o0, mask=mask) + tl.store(p_y + R, b_o1, mask=mask) + else: + # We don't want to load x[0, 2, 4, ...] and x[1, 3, 5, ...] separately since both are slow. + # Instead, we load x0 = x[0, 1, 2, 3, ...] and x1 = x[1, 0, 3, 2, ...]. + # Loading x0 will be fast but x1 will be slow. + # Then we load cos = cos[0, 0, 1, 1, ...] and sin = sin[0, 0, 1, 1, ...]. + # Then we do the calculation and use tl.where to pick put the right outputs for the even + # and for the odd indices. + o_d = tl.arange(0, BD) + o_d_swap = o_d + ((o_d + 1) % 2) * 2 - 1 # 1, 0, 3, 2, 5, 4, ... + o_d_repeat = tl.arange(0, BD) // 2 + p_x0 = x + o_t[:, None] * H*D + o_d[None, :] + p_x1 = x + o_t[:, None] * H*D + o_d_swap[None, :] + p_cos = cos + (o_cs[:, None] * R + o_d_repeat[None, :]) + p_sin = sin + (o_cs[:, None] * R + o_d_repeat[None, :]) + mask = m_t[:, None] & (o_d_repeat < R)[None, :] + + b_cos = tl.load(p_cos, mask=mask, other=1.0).to(tl.float32) + b_sin = tl.load(p_sin, mask=mask, other=0.0).to(tl.float32) + b_x0 = tl.load(p_x0, mask=mask, other=0.0).to(tl.float32) + b_x1 = tl.load(p_x1, mask=mask, other=0.0).to(tl.float32) + if CONJUGATE: + b_sin = -b_sin + b_o0 = b_x0 * b_cos + b_o1 = b_x1 * b_sin + b_y = tl.where(o_d[None, :] % 2 == 0, b_o0 - b_o1, b_o0 + b_o1) + p_y = y + (o_t[:, None] * H*D + o_d[None, :]) + tl.store(p_y, b_y, mask=mask) + + +def rotary_embedding_fwdbwd( + x: torch.Tensor, + cos: torch.Tensor, + sin: torch.Tensor, + seqlen_offsets: int | torch.Tensor = 0, + cu_seqlens: torch.Tensor | None = None, + interleaved: bool = False, + inplace: bool = False, + conjugate: bool = False, +) -> torch.Tensor: + """ + Args: + x: [B, T, H, D]. + cos: [TR, R / 2] + sin: [TR, R / 2] + seqlen_offsets: integer or integer tensor of size [N] + cu_seqlens: [N + 1,] or None + + Returns: + y: [B, T, H, D] + """ + is_varlen = cu_seqlens is not None + + B, T, H, D = x.shape + N = B if not is_varlen else cu_seqlens.shape[0] - 1 + TR, R = cos.shape + R2 = R * 2 + + assert D <= 256, "Only support D <= 256" + assert TR >= T, f"TR must be >= T, got {TR} and {T}" + + assert cos.dtype == sin.dtype, f"cos and sin must have the same dtype, got {cos.dtype} and {sin.dtype}" + assert x.dtype == cos.dtype, f"Input and cos/sin must have the same dtype, got {x.dtype} and {cos.dtype}" + + if isinstance(seqlen_offsets, torch.Tensor): + assert seqlen_offsets.shape == (N,) + assert seqlen_offsets.dtype in [torch.int32, torch.int64] + else: + assert seqlen_offsets + T <= TR + + y = torch.empty_like(x) if not inplace else x + if R2 < D and not inplace: + y[..., R2:].copy_(x[..., R2:]) + + BD = triton.next_power_of_2(R2) + BT = min(128, triton.next_power_of_2(triton.cdiv(T, get_multiprocessor_count(x.device.index)))) + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if is_varlen else None + NT = len(chunk_indices) if is_varlen else triton.cdiv(T, BT) + + grid = (NT, B, H) + rotary_embedding_kernel[grid]( + x, + cos, + sin, + y, + cu_seqlens, + chunk_indices, + seqlen_offsets, + B=B, + T=T, + H=H, + D=D, + R=R, + TR=TR, + BT=BT, + BD=BD, + IS_SEQLEN_OFFSETS_TENSOR=isinstance(seqlen_offsets, torch.Tensor), + IS_VARLEN=is_varlen, + INTERLEAVED=interleaved, + CONJUGATE=conjugate, + ) + return y + + +class RotaryEmbeddingFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x, + cos, + sin, + interleaved=False, + inplace=False, + seqlen_offsets: int | torch.Tensor = 0, + cu_seqlens: torch.Tensor | None = None, + ): + y = rotary_embedding_fwdbwd( + x, + cos, + sin, + seqlen_offsets=seqlen_offsets, + cu_seqlens=cu_seqlens, + interleaved=interleaved, + inplace=inplace, + ) + if isinstance(seqlen_offsets, int): + # Can't save int with save_for_backward + ctx.save_for_backward(cos, sin, cu_seqlens) + ctx.seqlen_offsets = seqlen_offsets + else: + ctx.save_for_backward(cos, sin, cu_seqlens, seqlen_offsets) + ctx.seqlen_offsets = None + ctx.interleaved = interleaved + ctx.inplace = inplace + return y if not inplace else x + + @staticmethod + @input_guard + def backward(ctx, do): + seqlen_offsets = ctx.seqlen_offsets + if seqlen_offsets is None: + cos, sin, cu_seqlens, seqlen_offsets = ctx.saved_tensors + else: + cos, sin, cu_seqlens = ctx.saved_tensors + # TD [2023-09-02]: For some reason Triton (2.0.0.post1) errors with + # "[CUDA]: invalid device context", and cloning makes it work. Idk why. Triton 2.1.0 works. + if not ctx.interleaved and not ctx.inplace: + do = do.clone() + dx = rotary_embedding_fwdbwd( + do, + cos, + sin, + seqlen_offsets=seqlen_offsets, + cu_seqlens=cu_seqlens, + interleaved=ctx.interleaved, + inplace=ctx.inplace, + conjugate=True, + ) + return dx, None, None, None, None, None, None, None + + +def rotary_embedding( + x, + cos, + sin, + interleaved=False, + inplace=False, + seqlen_offsets: int | torch.Tensor = 0, + cu_seqlens: torch.Tensor | None = None, +): + """ + Args: + x: [B, T, H, D] + cos, sin: [TR, R//2] + interleaved: + If True, rotate pairs of even and odd dimensions (GPT-J style) instead of 1st half and 2nd half (GPT-NeoX style). + inplace: + If True, apply rotary embedding in-place. + seqlen_offsets: [N,] or int. + Each sequence in x is shifted by this amount. + Most commonly used in inference when we have KV cache. + cu_seqlens: [N + 1,] or None + + Returns: + out: [B, T, H, D] + """ + return RotaryEmbeddingFunction.apply( + x, + cos, + sin, + interleaved, + inplace, + seqlen_offsets, + cu_seqlens, + ) + + +class RotaryEmbedding(nn.Module): + """ + The rotary position embeddings from RoFormer_ (Su et. al). + A crucial insight from the method is that the query and keys are + transformed by rotation matrices which depend on the relative positions. + + Other implementations are available in the Rotary Transformer repo_ and in + GPT-NeoX_, GPT-NeoX was an inspiration + + .. _RoFormer: https://arxiv.org/abs/2104.09864 + .. _repo: https://github.com/ZhuiyiTechnology/roformer + .. _GPT-NeoX: https://github.com/EleutherAI/gpt-neox + + If scale_base is not None, this implements XPos (Sun et al., https://arxiv.org/abs/2212.10554). + A recommended value for scale_base is 512: https://github.com/HazyResearch/flash-attention/issues/96 + Reference: https://github.com/sunyt32/torchscale/blob/main/torchscale/component/xpos_relative_position.py + """ + + def __init__( + self, + dim: int, + base: float = 10000.0, + scale_base: float | None = None, + interleaved: bool = False, + pos_idx_in_fp32: bool = True, + device: torch.device | None = None, + ): + """ + interleaved: + If True, rotate pairs of even and odd dimensions (GPT-J style) instead of 1st half and 2nd half (GPT-NeoX style). + pos_idx_in_fp32: + If True, the position indices [0.0, ..., seqlen - 1] are in fp32, otherwise they might be in lower precision. + This option was added because previously (before 2023-07-02), when we construct + the position indices, we use the dtype of self.inv_freq. + In most cases this would be fp32, but if the model is trained in pure bf16 (not mixed precision), then + self.inv_freq would be bf16, and the position indices are also in bf16. + Because of the limited precision of bf16 (e.g. 1995.0 is rounded to 2000.0), the + embeddings for some positions will coincide. + To maintain compatibility with models previously trained in pure bf16, we add this option. + """ + super().__init__() + + self.dim = dim + self.base = float(base) + self.scale_base = scale_base + self.interleaved = interleaved + self.pos_idx_in_fp32 = pos_idx_in_fp32 + self.device = device + + # Generate and save the inverse frequency buffer (non trainable) + self.register_buffer("inv_freq", torch.empty(-(dim // -2), dtype=torch.float32, device=device), persistent=False) + + scale = None + if scale_base is not None: + scale = torch.empty(-(dim // -2), dtype=torch.float32, device=device) + self.register_buffer("scale", scale, persistent=False) + + self._seq_len_cached = 0 + self._cos_cached = None + self._sin_cached = None + self._cos_k_cached = None + self._sin_k_cached = None + + self.reset_parameters() + + def reset_parameters(self): + with torch.no_grad(): + self.inv_freq.copy_(self._compute_inv_freq(device=self.inv_freq.device)) + if self.scale_base is not None: + self.scale.copy_(self._compute_scale(device=self.scale.device)) + + def __repr__(self): + s = f"{self.__class__.__name__}(" + s += f"dim={self.dim}, " + s += f"base={self.base}, " + s += f"interleaved={self.interleaved}, " + if self.scale_base is not None: + s += f"scale_base={self.scale_base}, " + s += f"pos_idx_in_fp32={self.pos_idx_in_fp32})" + return s + + def _compute_inv_freq(self, device=None): + return 1.0 / ( + self.base + ** (torch.arange(0, self.dim, 2, device=device, dtype=torch.float32) / self.dim) + ) + + def _compute_scale(self, device=None): + return (torch.arange(0, self.dim, 2, device=device, dtype=torch.float32) + 0.4 * self.dim) / (1.4 * self.dim) + + def _update_cos_sin_cache(self, seqlen, device=None, dtype=None): + # Reset the tables if the sequence length has changed, + # if we're on a new device (possibly due to tracing for instance), + # or if we're switching from inference mode to training + if ( + seqlen > self._seq_len_cached + or self._cos_cached is None + or self._cos_cached.device != device + or self._cos_cached.dtype != dtype + or (self.training and self._cos_cached.is_inference()) + ): + self._seq_len_cached = seqlen + # We want fp32 here, not self.inv_freq.dtype, since the model could be loaded in bf16 + # And the output of arange can be quite large, so bf16 would lose a lot of precision. + # However, for compatibility reason, we add an option to use the dtype of self.inv_freq. + if self.pos_idx_in_fp32: + t = torch.arange(seqlen, device=device, dtype=torch.float32) + # We want fp32 here as well since inv_freq will be multiplied with t, and the output + # will be large. Having it in bf16 will lose a lot of precision and cause the + # cos & sin output to change significantly. + # We want to recompute self.inv_freq if it was not loaded in fp32 + if self.inv_freq.dtype != torch.float32: + inv_freq = self._compute_inv_freq(device=device) + else: + inv_freq = self.inv_freq + else: + t = torch.arange(seqlen, device=device, dtype=self.inv_freq.dtype) + inv_freq = self.inv_freq + # Don't do einsum, it converts fp32 to fp16 under AMP + # freqs = torch.einsum("i,j->ij", t, self.inv_freq) + freqs = torch.outer(t, inv_freq) + if self.scale is None: + self._cos_cached = torch.cos(freqs).to(dtype) + self._sin_cached = torch.sin(freqs).to(dtype) + else: + power = ( + torch.arange(seqlen, dtype=self.scale.dtype, device=self.scale.device) + - seqlen // 2 + ) / self.scale_base + scale = self.scale.to(device=power.device) ** rearrange(power, "s -> s 1") + # We want the multiplication by scale to happen in fp32 + self._cos_cached = (torch.cos(freqs) * scale).to(dtype) + self._sin_cached = (torch.sin(freqs) * scale).to(dtype) + self._cos_k_cached = (torch.cos(freqs) / scale).to(dtype) + self._sin_k_cached = (torch.sin(freqs) / scale).to(dtype) + + def forward( + self, + q: torch.Tensor, + k: torch.Tensor, + seqlen_offset: int | torch.Tensor = 0, + cu_seqlens: torch.Tensor | None = None, + max_seqlen: int | None = None, + ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: + """ + q: [B, T, H, D] + k: [B, T, H, D] + seqlen_offset: + [N] or int. + Each sequence in x is shifted by this amount. + Most commonly used in inference when we have KV cache. + cu_seqlens: [N + 1] or None + max_seqlen: int + """ + if max_seqlen is not None: + self._update_cos_sin_cache(max_seqlen, device=q.device, dtype=q.dtype) + elif isinstance(seqlen_offset, int): + self._update_cos_sin_cache(q.shape[1] + seqlen_offset, device=q.device, dtype=q.dtype) + if self.scale is None: + q = rotary_embedding( + q, + self._cos_cached, + self._sin_cached, + interleaved=self.interleaved, + seqlen_offsets=seqlen_offset, + cu_seqlens=cu_seqlens, + ) + k = rotary_embedding( + k, + self._cos_cached, + self._sin_cached, + interleaved=self.interleaved, + seqlen_offsets=seqlen_offset, + cu_seqlens=cu_seqlens, + ) + + else: + q = rotary_embedding( + q, + self._cos_cached, + self._sin_cached, + interleaved=self.interleaved, + seqlen_offsets=seqlen_offset, + cu_seqlens=cu_seqlens, + ) + k = rotary_embedding( + k, + self._cos_k_cached, + self._sin_k_cached, + interleaved=self.interleaved, + seqlen_offsets=seqlen_offset, + cu_seqlens=cu_seqlens, + ) + + return q, k diff --git a/code/flash-linear-attention/fla/modules/token_shift.py b/code/flash-linear-attention/fla/modules/token_shift.py new file mode 100644 index 0000000000000000000000000000000000000000..1862aff3a9d4e3869520bf607e03d80517436594 --- /dev/null +++ b/code/flash-linear-attention/fla/modules/token_shift.py @@ -0,0 +1,545 @@ + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs, get_multiprocessor_count, input_guard, is_amd, tensor_cache + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +def token_shift_ref( + x: torch.Tensor, + cu_seqlens: torch.Tensor | None = None, +) -> torch.Tensor: + if cu_seqlens is not None: + # Variable length mode with cu_seqlens + assert x.dim() == 3, "Input must be [B, T, D]" + B, T, D = x.shape + assert B == 1, "Batch size must be 1 when using cu_seqlens" + + result = torch.zeros_like(x) + N = cu_seqlens.shape[0] - 1 + + for i in range(N): + start = cu_seqlens[i].item() + end = cu_seqlens[i+1].item() + seq_len = end - start + + if seq_len <= 1: + # For sequences of length 1 or 0, delta is simply -x + result[0, start:end] = -x[0, start:end] + else: + # For longer sequences, handle padding manually + shifted = torch.zeros_like(x[0, start:end]) + shifted[1:] = x[0, start:end-1] + delta = shifted - x[0, start:end] + result[0, start:end] = delta + + return result + else: + time_shift = torch.nn.ZeroPad2d((0, 0, 1, -1)) + shifted = time_shift(x) + delta = shifted - x + return delta + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_INITIAL_STATE': lambda args: args['cache'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [1, 2, 3] + ], + key=['BD'], + **autotune_cache_kwargs, +) +@triton.jit +def token_shift_fwd_kernel_short( + x, + y, + cu_seqlens, + cache, + cache_out, + T, + D: tl.constexpr, + BD: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_DECODE: tl.constexpr, +): + i_b, i_t = tl.program_id(0), tl.program_id(1) + + if IS_VARLEN: + i_n = i_b + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + g_t = i_t + bos + + if g_t >= eos: + return + + is_first_pos = (i_t == 0) + is_last_pos = (g_t == eos - 1) + else: + g_t = i_t + is_first_pos = (g_t == 0) + is_last_pos = (g_t == T - 1) + + o_d = tl.arange(0, BD) + m_d = o_d < D + + if IS_VARLEN: + base_offset = g_t * D + o_d + else: + base_offset = i_b * T*D + g_t * D + o_d + + b_x = tl.load(x + base_offset, mask=m_d) + if IS_VARLEN: + cache_offset = i_n * D + o_d # i_n is seq index + else: + cache_offset = i_b * D + o_d # i_b is batch index + + if IS_DECODE and USE_INITIAL_STATE: + b_cache = tl.load(cache + cache_offset, mask=m_d) + delta = b_cache - b_x + tl.store(y + base_offset, delta, mask=m_d) + if STORE_FINAL_STATE: + tl.store(cache_out + cache_offset, b_x, mask=m_d) + return + + if is_first_pos: + # First position in sequence: delta = -hidden_states + if USE_INITIAL_STATE: + # cache shape: [N, D] + b_cache = tl.load(cache + cache_offset, mask=m_d) + delta = b_cache - b_x + tl.store(y + base_offset, delta, mask=m_d) + else: + tl.store(y + base_offset, -b_x, mask=m_d) + return + + # Other positions: delta = prev - curr + if IS_VARLEN: + prev_offset = (g_t-1) * D + o_d + else: + prev_offset = i_b * T*D + (g_t-1) * D + o_d + + prev_values = tl.load(x + prev_offset, mask=m_d) + delta = prev_values - b_x + tl.store(y + base_offset, delta, mask=m_d) + if STORE_FINAL_STATE: + if is_last_pos: + tl.store(cache_out + cache_offset, b_x, mask=m_d) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_INITIAL_STATE': lambda args: args['cache'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [1, 2, 3] + ], + key=['BD', 'NB'], + **autotune_cache_kwargs, +) +@triton.jit +def token_shift_fwd_kernel_long( + x, + y, + cu_seqlens, + chunk_indices, + cache, + cache_out, + T, + D: tl.constexpr, + BD: tl.constexpr, + BT: tl.constexpr, + NB: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, +): + i_d, i_t, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), \ + tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n), tl.load(cu_seqlens + i_n + 1) + t_start = i_t * BT + t_end = tl.minimum(t_start + BT, eos - bos) + else: + i_n = i_b + bos, eos = i_b * T, (i_b + 1) * T + t_start = i_t * BT + t_end = tl.minimum(t_start + BT, T) + + o_d = i_d * BD + tl.arange(0, BD) + m_d = o_d < D + + for t in range(t_start, t_end): + global_t = bos + t + offset = global_t * D + o_d + b_x = tl.load(x + offset, mask=m_d) + is_first = (global_t == bos) + if is_first: + if USE_INITIAL_STATE: + # cache shape: [N, D] + cache_off = i_n * D + o_d if IS_VARLEN else i_b * D + o_d + b_cache = tl.load(cache + cache_off, mask=m_d) + delta = b_cache - b_x + else: + delta = -b_x + else: + prev_off = offset - D + b_prev = tl.load(x + prev_off, mask=m_d) + delta = b_prev - b_x + + tl.store(y + offset, delta, mask=m_d) + + if STORE_FINAL_STATE: + if global_t == eos - 1: + cache_out_off = i_n * D + o_d if IS_VARLEN else i_b * D + o_d + tl.store(cache_out + cache_out_off, b_x, mask=m_d) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_INITIAL_STATE': lambda args: args['grad_cache_out'] is not None, + 'HAS_DCACHE': lambda args: args['grad_cache_in'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [1, 2, 3] + ], + key=['BD'], + **autotune_cache_kwargs, +) +@triton.jit +def token_shift_bwd_kernel_short( + dx, + dy, + cu_seqlens, + grad_cache_in, + grad_cache_out, + T, + D: tl.constexpr, + BD: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + HAS_DCACHE: tl.constexpr, +): + i_b, i_t = tl.program_id(0), tl.program_id(1) + + if IS_VARLEN: + i_n = i_b + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + g_t = i_t + bos + if g_t >= eos: + return + is_first_pos = (g_t == bos) + is_last_pos = (g_t == eos - 1) + else: + g_t = i_t + is_first_pos = (g_t == 0) + is_last_pos = (g_t == T - 1) + + o_d = tl.arange(0, BD) + m_d = o_d < D + + if IS_VARLEN: + base_offset = g_t * D + o_d + # This should not be used for varlen + cache_off = i_n * D + o_d + else: + base_offset = i_b * T * D + g_t * D + o_d + cache_off = i_b * D + o_d + + b_dy = tl.load(dy + base_offset, mask=m_d) + + if is_last_pos: + # grad = -grad_delta[t] + grad_cache_in(from next rank) + if HAS_DCACHE: + b_dy_cache = tl.load(grad_cache_in + cache_off, mask=m_d) + b_dx = -b_dy + b_dy_cache + else: + b_dx = -b_dy + else: + # grad = -grad_delta[t] + grad_delta[t+1] + if IS_VARLEN: + next_offset = (g_t + 1) * D + o_d + else: + next_offset = i_b * T * D + (g_t + 1) * D + o_d + b_dx = -b_dy + tl.load(dy + next_offset, mask=m_d) + + tl.store(dx + base_offset, b_dx, mask=m_d) + + if USE_INITIAL_STATE: + if is_first_pos: + tl.store(grad_cache_out + cache_off, b_dy, mask=m_d) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_INITIAL_STATE': lambda args: args['grad_cache_out'] is not None, + 'HAS_DCACHE': lambda args: args['grad_cache_in'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [1, 2, 3] + ], + key=['BD', 'NB'], + **autotune_cache_kwargs, +) +@triton.jit +def token_shift_bwd_kernel_long( + dx, + dy, + cu_seqlens, + chunk_indices, + grad_cache_in, + grad_cache_out, + T, + D: tl.constexpr, + BD: tl.constexpr, + BT: tl.constexpr, + NB: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + HAS_DCACHE: tl.constexpr, +): + i_d, i_t_blk, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + if IS_VARLEN: + i_n, i_t_blk = tl.load(chunk_indices + i_t_blk * 2).to(tl.int32), \ + tl.load(chunk_indices + i_t_blk * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n), tl.load(cu_seqlens + i_n + 1) + t_start = i_t_blk * BT + t_end = tl.minimum(t_start + BT, eos - bos) + else: + bos, eos = i_b * T, (i_b + 1) * T + t_start = i_t_blk * BT + t_end = tl.minimum(t_start + BT, T) + + o_d = i_d * BD + tl.arange(0, BD) + m_d = o_d < D + cache_off = i_n * D + o_d if IS_VARLEN else i_b * D + o_d + + for t in range(t_start, t_end): + global_t = bos + t + offset = global_t * D + o_d + b_dy = tl.load(dy + offset, mask=m_d) + + if global_t == eos - 1: + if HAS_DCACHE: + b_dy_cache = tl.load(grad_cache_in + cache_off, mask=m_d) + b_dx = -b_dy + b_dy_cache + else: + b_dx = -b_dy + else: + next_off = offset + D + b_dx = -b_dy + tl.load(dy + next_off, mask=m_d) + + tl.store(dx + offset, b_dx, mask=m_d) + + if USE_INITIAL_STATE: + if global_t == bos: + tl.store(grad_cache_out + cache_off, b_dy, mask=m_d) + + +@tensor_cache +def prepare_maxlens(cu_seqlens: torch.LongTensor) -> int: + return torch.max(cu_seqlens[1:] - cu_seqlens[:-1]).item() + + +def token_shift_fwd( + x: torch.Tensor, + cu_seqlens: torch.Tensor | None = None, + cache: torch.Tensor | None = None, + output_cache: bool = False, +) -> torch.Tensor: + B, T, D = x.shape + y = torch.empty_like(x) + use_short_kernel = T <= 4096 + + if cu_seqlens is not None: + T = prepare_maxlens(cu_seqlens) + N = len(cu_seqlens) - 1 + else: + N = B + + if output_cache: + cache_out = torch.empty((N, D), device=x.device, dtype=x.dtype) + else: + cache_out = None + + if use_short_kernel: + if cu_seqlens is not None: + N = len(cu_seqlens) - 1 + else: + N = B + BD = triton.next_power_of_2(D) + grid = (N, T) + IS_DECODE = T == 1 or (B == 1 and T == N) + token_shift_fwd_kernel_short[grid]( + x=x, + y=y, + cu_seqlens=cu_seqlens, + cache=cache, + cache_out=cache_out, + T=T, + D=D, + BD=BD, + STORE_FINAL_STATE=output_cache, + IS_DECODE=IS_DECODE, + ) + else: + BT = min(64, triton.next_power_of_2(triton.cdiv(max(16, B*T), get_multiprocessor_count(x.device.index)))) + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = len(chunk_indices) if cu_seqlens is not None else triton.cdiv(T, BT) + + BD = triton.next_power_of_2(D) + NB = triton.cdiv(B*T, 1024) + + def grid(meta): return (triton.cdiv(D, meta['BD']), NT, N) + token_shift_fwd_kernel_long[grid]( + x, + y, + cu_seqlens, + chunk_indices, + cache, + cache_out, + T, + D=D, + BD=BD, + BT=BT, + NB=NB, + STORE_FINAL_STATE=output_cache, + ) + + return y, N, T, use_short_kernel, cache_out + + +def token_shift_bwd( + dy: torch.Tensor, + N: int, + T: int, + dcache: torch.Tensor | None = None, + cu_seqlens: torch.Tensor | None = None, + use_short_kernel: bool = True, + has_init_cache: bool = False, +) -> torch.Tensor: + D = dy.shape[2] + BD = triton.next_power_of_2(D) + dx = torch.empty_like(dy) + if has_init_cache: + grad_cache_out = torch.empty((N, D), device=dy.device, dtype=dy.dtype) + else: + grad_cache_out = None + if use_short_kernel: + grid = (N, T) + token_shift_bwd_kernel_short[grid]( + dy=dy, + dx=dx, + cu_seqlens=cu_seqlens, + grad_cache_in=dcache, + grad_cache_out=grad_cache_out, + T=T, + D=D, + BD=BD, + ) + else: + BT = min(64, triton.next_power_of_2(triton.cdiv(max(16, dy.numel() // D), + get_multiprocessor_count(dy.device.index)))) + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = len(chunk_indices) if cu_seqlens is not None else triton.cdiv(T, BT) + NB = triton.cdiv(N * dy.shape[1], 1024) + BD = triton.next_power_of_2(D) + + def grid(meta): return (triton.cdiv(D, meta['BD']), NT, N) + token_shift_bwd_kernel_long[grid]( + dx, + dy, + cu_seqlens, + chunk_indices, + dcache, + grad_cache_out, + T, + D=D, + BD=BD, + BT=BT, + NB=NB, + ) + return dx, grad_cache_out + + +class TokenShift(torch.autograd.Function): + + @staticmethod + @input_guard + def forward(ctx, x: torch.Tensor, cu_seqlens: torch.Tensor | None = None, + cache: torch.Tensor | None = None, output_cache: bool = False): + output, N, T, use_short_kernel, cache_out = token_shift_fwd(x, cu_seqlens, cache, output_cache) + ctx.cu_seqlens = cu_seqlens + ctx.N = N + ctx.T = T + ctx.use_short_kernel = use_short_kernel + ctx.has_cache = cache is not None + return output, cache_out + + @staticmethod + @input_guard + def backward(ctx, dy: torch.Tensor, dcache: torch.Tensor | None = None): + dx, grad_cache = token_shift_bwd(dy, ctx.N, ctx.T, dcache, ctx.cu_seqlens, + ctx.use_short_kernel, ctx.has_cache) + return dx, None, grad_cache, None + + +def token_shift( + x: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + cache: torch.Tensor | None = None, + output_cache: bool = False, +): + """ + Token-shift operation implemented with Triton kernels. + + Args: + x: Input tensor of shape [B, T, D] (or [1, T, D] when `cu_seqlens` is supplied). + cu_seqlens: Optional cumulative sequence lengths of shape [B + 1]. + When supplied, `x.shape[0]` must be 1 and `x.dim()` must be 3. + cache: Optional cache tensor of shape [N, D] that holds the last token + from the previous call. + output_cache: Whether to return the updated cache alongside the output. + In previous versions this parameter did not exist and the + cache was always dropped; to preserve backward compatibility + the default is False. + + Returns: + output: Tensor of shape [B, T, D] after applying the token-shift. + + cache_out: Tensor of shape [B, 1, D] containing the last token that + should be fed as `cache` in the next call. Only returned + when `output_cache=True`. + """ + if cu_seqlens is not None: + assert x.dim() == 3, "Input must be [B, T, D]" + assert x.shape[0] == 1, "Batch size must be 1 when using cu_seqlens" + + output, cache_out = TokenShift.apply(x, cu_seqlens, cache, output_cache) + if output_cache: + return output, cache_out + else: + return output diff --git a/code/flash-linear-attention/fla/ops/__init__.py b/code/flash-linear-attention/fla/ops/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c11ec7832e974b970ba007a81f3e8bc9eaf8b8e0 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/__init__.py @@ -0,0 +1,54 @@ + +from .abc import chunk_abc +from .attn import parallel_attn +from .based import fused_chunk_based, parallel_based +from .comba import chunk_comba, fused_recurrent_comba +from .delta_rule import chunk_delta_rule, fused_chunk_delta_rule, fused_recurrent_delta_rule +from .forgetting_attn import parallel_forgetting_attn +from .gated_delta_rule import chunk_gated_delta_rule, fused_recurrent_gated_delta_rule +from .generalized_delta_rule import ( + chunk_dplr_delta_rule, + chunk_iplr_delta_rule, + fused_recurrent_dplr_delta_rule, + fused_recurrent_iplr_delta_rule, +) +from .gla import chunk_gla, fused_chunk_gla, fused_recurrent_gla +from .gsa import chunk_gsa, fused_recurrent_gsa +from .hgrn import fused_recurrent_hgrn +from .kda import chunk_kda, fused_recurrent_kda +from .lightning_attn import chunk_lightning_attn, fused_recurrent_lightning_attn +from .linear_attn import chunk_linear_attn, fused_chunk_linear_attn, fused_recurrent_linear_attn +from .log_linear_attn import chunk_log_linear_attn +from .mesa_net import chunk_mesa_net +from .nsa import parallel_nsa +from .path_attn import parallel_path_attn +from .retention import chunk_retention, fused_chunk_retention, fused_recurrent_retention, parallel_retention +from .rwkv6 import chunk_rwkv6, fused_recurrent_rwkv6 +from .rwkv7 import chunk_rwkv7, fused_recurrent_rwkv7 +from .simple_gla import chunk_simple_gla, fused_chunk_simple_gla, fused_recurrent_simple_gla, parallel_simple_gla + +__all__ = [ + 'chunk_abc', + 'parallel_attn', + 'fused_chunk_based', 'parallel_based', + 'chunk_delta_rule', 'fused_chunk_delta_rule', 'fused_recurrent_delta_rule', + 'parallel_forgetting_attn', + 'chunk_gated_delta_rule', 'fused_recurrent_gated_delta_rule', + 'chunk_comba', 'fused_recurrent_comba', + 'chunk_dplr_delta_rule', 'chunk_iplr_delta_rule', + 'fused_recurrent_dplr_delta_rule', 'fused_recurrent_iplr_delta_rule', + 'chunk_kda', 'fused_recurrent_kda', + 'chunk_gla', 'fused_chunk_gla', 'fused_recurrent_gla', + 'chunk_gsa', 'fused_recurrent_gsa', + 'fused_recurrent_hgrn', + 'chunk_lightning_attn', 'fused_recurrent_lightning_attn', + 'chunk_linear_attn', 'fused_chunk_linear_attn', 'fused_recurrent_linear_attn', + 'chunk_log_linear_attn', + 'chunk_mesa_net', + 'parallel_nsa', + 'parallel_path_attn', + 'chunk_retention', 'fused_chunk_retention', 'fused_recurrent_retention', 'parallel_retention', + 'chunk_rwkv6', 'fused_recurrent_rwkv6', + 'chunk_rwkv7', 'fused_recurrent_rwkv7', + 'chunk_simple_gla', 'fused_chunk_simple_gla', 'fused_recurrent_simple_gla', 'parallel_simple_gla', +] diff --git a/code/flash-linear-attention/fla/ops/abc/__init__.py b/code/flash-linear-attention/fla/ops/abc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..48d5c6d8e9a1ba557195486ef42aefe5e8fd79df --- /dev/null +++ b/code/flash-linear-attention/fla/ops/abc/__init__.py @@ -0,0 +1,6 @@ + +from .chunk import chunk_abc + +__all__ = [ + 'chunk_abc', +] diff --git a/code/flash-linear-attention/fla/ops/abc/chunk.py b/code/flash-linear-attention/fla/ops/abc/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..a07228ef8da30fb5b982aa4526c091447d055895 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/abc/chunk.py @@ -0,0 +1,1115 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import softmax_bwd, softmax_fwd +from fla.ops.utils.logcumsumexp import logcumsumexp_fwd_kernel +from fla.ops.utils.op import exp +from fla.utils import input_guard + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_fwd_kernel_h( + k, + v, + z, + h, + h0, + ht, + T, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NT: tl.constexpr, + NORMK: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, +): + i_v, i_k, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h = tl.make_block_ptr(h0 + i_bh * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h += tl.load(p_h, boundary_check=(0, 1)).to(tl.float32) + if NORMK: + p_z0 = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), (i_k * BK,), (BK,), (0,)) + else: + p_z0 = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), (i_v * BV,), (BV,), (0,)) + b_zp = tl.load(p_z0).to(tl.float32) + for i_t in range(NT): + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h + i_bh * NT*K*V + i_t * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + if NORMK: + p_zc = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), ((i_t * BT + BT - 1) * K + i_k * BK,), (BK,), (0,)) + # [BK,] + b_zc = tl.load(p_zc, boundary_check=(0,)) + b_r, b_zp = exp(b_zp - b_zc), b_zc + # [BK, BV] + b_h = b_h * b_r[:, None] + b_k = exp(b_k - b_zc[:, None]).to(b_k.dtype) + else: + p_zc = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), ((i_t * BT + BT - 1) * V + i_v * BV,), (BV,), (0,)) + # [BV,] + b_zc = tl.load(p_zc, boundary_check=(0,)) + b_r, b_zp = exp(b_zp - b_zc), b_zc + # [BK, BV] + b_h = b_h * b_r[None, :] + b_v = exp(b_v - b_zc[None, :]).to(b_v.dtype) + # [BK, BV] + b_h += tl.dot(b_k, b_v, allow_tf32=False) + + if STORE_FINAL_STATE: + p_h = tl.make_block_ptr(ht + i_bh * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_fwd_kernel_intra_K( + v, + z, + o, + A, + T, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_t, i_i = i_c // NC, i_c % NC + + p_z = tl.make_block_ptr(z + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_zn = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), ((i_t * BT + i_i * BC) * V + i_v * BV,), (BV,), (0,)) + # [BV,] + b_zn = tl.load(p_zn, boundary_check=(0,)) + # [BC, BV] + b_o = tl.zeros([BC, BV], dtype=tl.float32) + for i_j in range(0, i_i): + p_A = tl.make_block_ptr(A + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + # [BC, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BC, BC] + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_o += tl.dot(b_A, exp(b_v - b_zn[None, :]).to(b_v.dtype), allow_tf32=False) + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_o *= exp(b_zn[None, :] - b_z) + + o_i = tl.arange(0, BC) + o_A = i_bh * T * BT + (i_t * BT + i_i * BC + tl.arange(0, BC)) * BT + i_i * BC + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + for j in range(0, BC): + p_v = tl.make_block_ptr(v + i_bh * T*V, (T * V,), (1,), ((i_t * BT + i_i * BC + j) * V + i_v * BV,), (BV,), (0,)) + # [BC,] + b_A = tl.load(A + o_A + j, mask=m_A, other=0) + # [BV,] + b_v = tl.load(p_v, boundary_check=(0,)).to(tl.float32) + # [BC, BV] + # avoid 0 * inf = inf + m_i = o_i[:, None] >= j + b_o += tl.where(m_i, b_A[:, None] * exp(b_v[None, :] - b_z), 0) + p_o = tl.make_block_ptr(o + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_fwd_kernel_K( + q, + k, + z, + h, + o, + A, + scale, + T, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NT: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_p = tl.maximum(i_t * BT - 1, 0) + + o_i = tl.arange(0, BT) + m_s = o_i[:, None] >= o_i[None, :] + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_h = tl.make_block_ptr(h + i_bh * NT*K*V + i_t * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BV] + b_o += tl.dot(b_q, b_h, allow_tf32=False) + # [BT, BT] + b_A += tl.dot(b_q, b_k, allow_tf32=False) + p_z = tl.make_block_ptr(z + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + # [BT, BV] + b_z = tl.load(p_z, boundary_check=(0, 1)) + # [BT, BV] + p_zp = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), (i_p * V + i_v * BV,), (BV,), (0,)) + b_zp = tl.load(p_zp, boundary_check=(0,)) + b_o = b_o * exp(b_zp[None, :] - b_z) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + p_A = tl.make_block_ptr(A + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + # [BT, BT] + b_A = tl.where(m_s, b_A, 0.) + if i_v == 0: + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_fwd_kernel_intra_V( + q, + k, + z, + A, + scale, + T, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, +): + i_k, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_t, i_i, i_j = i_c // (NC * NC), (i_c % (NC * NC)) // NC, (i_c % (NC * NC)) % NC + n_bh = tl.num_programs(2) + + if i_i > i_j: + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + p_z = tl.make_block_ptr(z + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_A = tl.make_block_ptr(A + (i_k*n_bh+i_bh)*T*BT, (T, BT), (BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + p_zn = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), ((i_t * BT + i_i * BC) * K + i_k * BK,), (BK,), (0,)) + # [BK,] + b_zn = tl.load(p_zn, boundary_check=(0,)) + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_q = (b_q * exp(b_zn[None, :] - b_z) * scale).to(b_q.dtype) + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_k = exp(b_k - b_zn[:, None]).to(b_k.dtype) + # [BC, BC] + b_A = tl.dot(b_q, b_k, allow_tf32=False) + tl.store(p_A, b_A.to(A.dtype.element_ty), boundary_check=(0, 1)) + elif i_i == i_j: + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T * K,), (1,), ((i_t * BT + i_j * BC) * K + i_k * BK,), (BK,), (0,)) + p_z = tl.make_block_ptr(z + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_z = tl.load(p_z, boundary_check=(0, 1)) + + o_i = tl.arange(0, BC) + o_A = (i_bh + i_k * n_bh) * T * BT + (i_t * BT + i_i * BC + tl.arange(0, BC)) * BT + i_j * BC + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + for j in range(0, BC): + # [BK,] + b_k = tl.load(p_k, boundary_check=(0,)).to(tl.float32) + # [BC,] + b_A = tl.sum(b_q * exp(b_k[None, :] - b_z) * scale, 1) + b_A = tl.where(o_i >= j, b_A, 0.) + tl.store(A + o_A + j, b_A.to(b_q.dtype), mask=m_A) + + p_k = tl.advance(p_k, (K,)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_fwd_kernel_V( + q, + v, + z, + h, + o, + A, + scale, + T, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NT: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_p = tl.maximum(i_t * BT - 1, 0) + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_z = tl.make_block_ptr(z + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_h = tl.make_block_ptr(h + i_bh * NT*K*V + i_t * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_zp = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), (i_p * K + i_k * BK,), (BK,), (0,)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BT, BK] + b_z = tl.load(p_z, boundary_check=(0, 1)) + # [BT, BK] + b_zp = tl.load(p_zp, boundary_check=(0,)) + b_q = (b_q * exp(b_zp[None, :] - b_z)).to(b_q.dtype) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # works but dkw, owing to divine benevolence + # [BT, BV] + if i_k >= 0: + b_o += tl.dot(b_q, b_h, allow_tf32=False) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_A = tl.make_block_ptr(A + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BT] + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_o += tl.dot(b_A.to(b_v.dtype), b_v, allow_tf32=False) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_dh( + q, + z, + do, + dh, + scale, + T, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NT: tl.constexpr, + NORMK: tl.constexpr, +): + i_k, i_v, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + b_zp = tl.full([BK if NORMK else BV], float('inf'), dtype=tl.float32) + for i_t in range(NT - 1, -1, -1): + i_p = tl.maximum(i_t * BT - 1, 0) + p_q = tl.make_block_ptr(q + i_bh * T*K, (K, T), (1, K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dh = tl.make_block_ptr(dh + i_bh * NT*K*V + i_t * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + # [BK, BT] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + if NORMK: + p_z = tl.make_block_ptr(z + i_bh * T*K, (K, T), (1, K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_zc = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), (i_p * K + i_k * BK,), (BK,), (0,)) + # [BK,] + b_zc = tl.load(p_zc, boundary_check=(0,)) + b_r, b_zp = exp(b_zc - b_zp), b_zc + # [BK, BT] + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_q = (b_q * exp(b_zc[:, None] - b_z)).to(b_q.dtype) + # [BK, BV] + b_dh = b_dh * b_r[:, None] + else: + p_z = tl.make_block_ptr(z + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_zc = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), (i_p * V + i_v * BV,), (BV,), (0,)) + # [BV,] + b_zc = tl.load(p_zc, boundary_check=(0,)) + b_r, b_zp = exp(b_zc - b_zp), b_zc + # [BT, BV] + b_z = tl.load(p_z, boundary_check=(0,)) + b_do = (b_do * exp(b_zc[None, :] - b_z)).to(b_do.dtype) + # [BK, BV] + b_dh = b_dh * b_r[None, :] + # [BK, BV] + b_dh += tl.dot(b_q, b_do, allow_tf32=False) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_V( + k, + v, + z, + h, + A, + do, + dh, + dq, + dk, + dv, + dA, + scale, + T, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NT: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_p = tl.maximum(i_t * BT - 1, 0) + n_bh = tl.num_programs(2) + + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_zc = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), ((i_t * BT + BT - 1) * K + i_k * BK,), (BK,), (0,)) + p_A = tl.make_block_ptr(A + i_bh * T * BT, (BT, T), (1, BT), (0, i_t * BT), (BT, BT), (0, 1)) + + # [BK,] + b_zc = tl.load(p_zc, boundary_check=(0,)) + # [BT, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_k = exp(b_k - b_zc[None, :]).to(b_k.dtype) + # [BT, BT] + b_A = tl.load(p_A, boundary_check=(0, 1)) + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h + i_bh * NT*K*V + i_t * V * K, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dh = tl.make_block_ptr(dh + i_bh * NT*K*V + i_t * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (i_k*n_bh+i_bh) * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BK, BV] + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + + # [BT, BV] + b_dv = tl.dot(b_k, b_dh, allow_tf32=False) + if i_k == 0: + b_dv += tl.dot(b_A.to(b_do.dtype), b_do, allow_tf32=False) + b_do = (b_do * scale).to(b_do.dtype) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + # [BT, BT] + b_dA += tl.dot(b_do, tl.trans(b_v), allow_tf32=False) + # [BT, BK] + b_dq += tl.dot(b_do, b_h, allow_tf32=False) + # [BT, BK] + b_dk += tl.dot(b_v, tl.trans(b_dh), allow_tf32=False) + p_z = tl.make_block_ptr(z + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_zp = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), (i_p * K + i_k * BK,), (BK,), (0,)) + # [BK,] + b_zp = tl.load(p_zp, boundary_check=(0,)) + # [BT, BK] + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_z = exp(b_zp[None, :] - b_z) + # [BT, BK] + b_dq = b_dq * b_z + b_dk = b_dk * b_k + + p_dq = tl.make_block_ptr(dq + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + o_i = tl.arange(0, BT) + m_s = o_i[:, None] >= o_i[None, :] + # [BT, BT] + b_dA = tl.where(m_s, b_dA, 0.).to(b_k.dtype) + if i_k == 0: + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_intra_V( + q, + k, + z, + dA, + dq, + dk, + T, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, +): + i_k, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_t, i_i = i_c // NC, i_c % NC + + p_z = tl.make_block_ptr(z + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_zn = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), ((i_t * BT + i_i * BC) * K + i_k * BK,), (BK,), (0,)) + # [BK,] + b_zn = tl.load(p_zn, boundary_check=(0,)) + # [BC, BK] + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_zq = exp(b_zn[None, :] - b_z) + b_dq = tl.zeros([BC, BK], dtype=tl.float32) + for i_j in range(0, i_i): + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_kz = exp(b_k - b_zn[None, :]).to(b_k.dtype) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BC, BK] + b_dq += tl.dot(b_dA, b_kz, allow_tf32=False) + b_dq *= b_zq + + o_i = tl.arange(0, BC) + o_dA = i_bh * T * BT + (i_t * BT + i_i * BC + tl.arange(0, BC)) * BT + i_i * BC + m_dA = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + for j in range(0, BC): + p_kj = tl.make_block_ptr(k + i_bh * T*K, (T * K,), (1,), ((i_t * BT + i_i*BC+j) * K + i_k * BK,), (BK,), (0,)) + # [BC,] + b_dA = tl.load(dA + o_dA + j, mask=m_dA, other=0) + # [BK,] + b_kj = tl.load(p_kj, boundary_check=(0,)).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] >= j + # [BC, BK] + b_dq += tl.where(m_i, b_dA[:, None] * exp(b_kj[None, :] - b_z), 0.) + p_dq = tl.make_block_ptr(dq + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + tl.debug_barrier() + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_zn = tl.make_block_ptr(z + i_bh * T*K, (T*K,), (1,), ((i_t * BT + i_i * BC + BC - 1) * K + i_k * BK,), (BK,), (0,)) + # [BK,] + b_zn = tl.load(p_zn, boundary_check=(0,)) + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_kz = exp(b_k - b_zn[None, :]) + b_dk = tl.zeros([BC, BK], dtype=tl.float32) + for i_j in range(i_i + 1, NC): + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_z = tl.make_block_ptr(z + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT + i_j * BC, i_i * BC), (BC, BC), (1, 0)) + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_qz = (b_q * exp(b_zn[None, :] - b_z)).to(b_q.dtype) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BC, BK] + b_dk += tl.dot(tl.trans(b_dA), b_qz, allow_tf32=False) + b_dk *= b_kz + + o_dA = i_bh * T * BT + (i_t * BT + i_i * BC) * BT + i_i * BC + tl.arange(0, BC) + for j in range(0, BC): + p_qj = tl.make_block_ptr(q + i_bh * T*K, (T * K,), (1,), ((i_t * BT + i_i * BC + j) * K + i_k * BK,), (BK,), (0,)) + p_zj = tl.make_block_ptr(z + i_bh * T*K, (T * K,), (1,), ((i_t * BT + i_i * BC + j) * K + i_k * BK,), (BK,), (0,)) + # [BC,] + b_dA = tl.load(dA + o_dA + j * BT, mask=(i_t * BT + i_i * BC + j < T), other=0) + # [BK,] + b_qj = tl.load(p_qj, boundary_check=(0,)).to(tl.float32) + b_zj = tl.load(p_zj, boundary_check=(0,)).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] <= j + b_dk += tl.where(m_i, b_dA[:, None] * b_qj[None, :] * exp(b_k - b_zj[None, :]), 0.) + p_dk = tl.make_block_ptr(dk + i_bh * T*K, (T, K), (K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_intra_K( + v, + z, + do, + dA, + scale, + T, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_t, i_i, i_j = i_c // (NC * NC), (i_c % (NC * NC)) // NC, (i_c % (NC * NC)) % NC + n_bh = tl.num_programs(2) + + if i_i > i_j: + p_v = tl.make_block_ptr(v + i_bh * T*V, (V, T), (1, V), (i_v * BV, i_t * BT + i_j * BC), (BV, BC), (0, 1)) + p_z = tl.make_block_ptr(z + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_zn = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), ((i_t * BT + i_i * BC) * V + i_v * BV,), (BV,), (0,)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_dA = tl.make_block_ptr(dA+(i_bh+i_v*n_bh)*T*BT, (T, BT), (BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + # [BV,] + b_zn = tl.load(p_zn, boundary_check=(0,)) + # [BC, BV] + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_do = (b_do * exp(b_zn[None, :] - b_z) * scale).to(b_do.dtype) + # [BV, BC] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v = exp(b_v - b_zn[:, None]).to(b_v.dtype) + # [BC, BC] + b_dA = tl.dot(b_do, b_v, allow_tf32=False) + tl.store(p_dA, b_dA.to(dA.dtype.element_ty), boundary_check=(0, 1)) + elif i_i == i_j: + p_v = tl.make_block_ptr(v + i_bh * T*V, (T * V,), (1,), ((i_t * BT + i_j * BC) * V + i_v * BV,), (BV,), (0,)) + p_z = tl.make_block_ptr(z + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + # [BC, BV] + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) * scale + + o_i = tl.arange(0, BC) + o_A = (i_bh + i_v * n_bh) * T * BT + (i_t * BT + i_i * BC + tl.arange(0, BC)) * BT + i_j * BC + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + for j in range(0, BC): + # [BV,] + b_v = tl.load(p_v, boundary_check=(0,)).to(tl.float32) + # [BC,] + b_dA = tl.sum(b_do * exp(b_v[None, :] - b_z), 1) + b_dA = tl.where(o_i >= j, b_dA, 0) + tl.store(dA + o_A + j, b_dA.to(b_do.dtype), mask=m_A) + + p_v = tl.advance(p_v, (V,)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_K( + q, + k, + v, + z, + h, + A, + do, + dh, + dq, + dk, + dv, + dA, + scale, + T, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NT: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_p = tl.maximum(i_t * BT - 1, 0) + n_bh = tl.num_programs(2) + + o_i = tl.arange(0, BT) + m_s = o_i[:, None] >= o_i[None, :] + + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_A = tl.make_block_ptr(A + (i_k*n_bh+i_bh) * T * BT, (T, BT ), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BT] + b_A = tl.dot((b_q * scale).to(b_q.dtype), tl.trans(b_k), allow_tf32=False) + b_A = tl.where(m_s, b_A, 0.) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_z = tl.make_block_ptr(z + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_zp = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), (i_p * V + i_v * BV,), (BV,), (0,)) + p_zc = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), ((i_t * BT + BT - 1) * V + i_v * BV,), (BV,), (0,)) + p_h = tl.make_block_ptr(h + i_bh * NT*K*V + i_t * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dh = tl.make_block_ptr(dh + i_bh * NT*K*V + i_t * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (i_k*n_bh+i_bh) * T*V, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + # [BV,] + b_zp = tl.load(p_zp, boundary_check=(0,)) + b_zc = tl.load(p_zc, boundary_check=(0,)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v = exp(b_v - b_zc[None, :]).to(b_v.dtype) + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_z = exp(b_zp[None, :] - b_z) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_do = (b_do * b_z * scale).to(b_do.dtype) + # [BK, BV] + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + + # [BT, BK] + b_dq += tl.dot(b_do, b_h, allow_tf32=False) + b_dk += tl.dot(b_v, tl.trans(b_dh), allow_tf32=False) + # [BT, BV] + b_dv = b_v * tl.dot(b_k, b_dh, allow_tf32=False) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + p_dA = tl.make_block_ptr(dA + i_bh * T * BT, (T, BT ), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + # [BT, BT] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BT, BK] + b_dq += tl.dot(b_dA, b_k, allow_tf32=False) + b_dk += tl.dot(tl.trans(b_dA).to(b_k.dtype), b_q, allow_tf32=False) + + p_dq = tl.make_block_ptr(dq + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_intra_KV( + v, + z, + A, + do, + dv, + T, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_t, i_i = i_c // NC, i_c % NC + + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_zn = tl.make_block_ptr(z + i_bh * T*V, (T*V,), (1,), ((i_t * BT + i_i * BC + BC - 1) * V + i_v * BV,), (BV,), (0,)) + # [BV,] + b_zn = tl.load(p_zn, boundary_check=(0,)) + # [BC, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dv = tl.zeros([BC, BV], dtype=tl.float32) + for i_j in range(i_i + 1, NC): + p_z = tl.make_block_ptr(z + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + p_A = tl.make_block_ptr(A + i_bh * T * BT, (BT, T), (1, BT), (i_i * BC, i_t * BT + i_j * BC), (BC, BC), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + # [BC, BV] + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_do = (b_do * exp(b_zn[None, :] - b_z)).to(b_do.dtype) + # [BC, BC] + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_dv += tl.dot(b_A, b_do, allow_tf32=False) + b_dv *= exp(b_v - b_zn[None, :]) + + o_i = tl.arange(0, BC) + for j in range(0, BC): + p_z = tl.make_block_ptr(z + i_bh * T*V, (T * V,), (1,), ((i_t * BT + i_i * BC + j) * V + i_v * BV,), (BV,), (0,)) + p_A = tl.make_block_ptr(A + i_bh * T * BT, (T * BT,), (1,), ((i_t * BT + i_i * BC + j) * BT + i_i * BC,), (BC,), (0,)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T * V,), (1,), ((i_t * BT + i_i * BC + j) * V + i_v * BV,), (BV,), (0,)) + # [BC,] + b_A = tl.load(p_A, boundary_check=(0,)) + # [BV,] + b_z = tl.load(p_z, boundary_check=(0,)) + b_do = tl.load(p_do, boundary_check=(0,)) + # [BC, BV] + m_i = o_i[:, None] <= j + b_dv += tl.where(m_i, exp(b_v - b_z[None, :]) * b_A[:, None] * b_do[None, :], 0.) + p_dv = tl.make_block_ptr(dv + i_bh * T*V, (T, V), (V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_rcum_inter( + s, + z, + ss, + doo, + T, + S: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + NT: tl.constexpr, +): + i_m, i_bh = tl.program_id(0), tl.program_id(1) + + b_sp = tl.zeros([BS], dtype=tl.float32) + b_zp = tl.full([BS], float('inf'), dtype=tl.float32) + for i_t in range(NT - 1, -1, -1): + p_s = tl.make_block_ptr(s + i_bh * T*S, (T, S), (S, 1), (i_t * BT, i_m * BS), (BT, BS), (1, 0)) + p_z = tl.make_block_ptr(z + i_bh * T*S, (T, S), (S, 1), (i_t * BT, i_m * BS), (BT, BS), (1, 0)) + p_zc = tl.make_block_ptr(z + i_bh * T*S, (T*S,), (1,), ((i_t * BT) * S + i_m * BS,), (BS,), (0,)) + p_ss = tl.make_block_ptr(ss + i_bh * T*S, (T, S), (S, 1), (i_t * BT, i_m * BS), (BT, BS), (1, 0)) + p_doo = tl.make_block_ptr(doo + i_bh * T*S, (T, S), (S, 1), (i_t * BT, i_m * BS), (BT, BS), (1, 0)) + # [BS,] + b_zc = tl.load(p_zc, boundary_check=(0,)) + # [BT, BS] + b_s = tl.load(p_s, boundary_check=(0, 1)) + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_ss = tl.load(p_ss, boundary_check=(0, 1)) + + b_doo = exp(b_s - b_zp[None, :]) * b_sp[None, :] + tl.store(p_doo, b_doo.to(p_doo.dtype.element_ty), boundary_check=(0, 1)) + # [BS,] + b_sp = b_sp * exp(b_zc - b_zp) + tl.sum(b_ss * exp(b_zc[None, :] - b_z), 0) + b_zp = b_zc + + +@triton.jit(do_not_specialize=['T']) +def chunk_abc_bwd_kernel_rcum_intra( + s, + z, + ss, + doo, + T, + S: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BS: tl.constexpr, + NC: tl.constexpr, +): + i_s, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_t, i_i = i_c // NC, i_c % NC + + o_i = tl.arange(0, BC) + m_o = tl.full([BC, BC], 1., dtype=tl.float32) + + p_s = tl.make_block_ptr(s + i_bh * T*S, (T, S), (S, 1), (i_t * BT + i_i * BC, i_s * BS), (BC, BS), (1, 0)) + p_zn = tl.make_block_ptr(z + i_bh * T*S, (T*S,), (1,), ((i_t * BT + i_i * BC + BC - 1) * S + i_s * BS,), (BS,), (0,)) + p_doo = tl.make_block_ptr(doo + i_bh * T*S, (T, S), (S, 1), (i_t * BT + i_i * BC, i_s * BS), (BC, BS), (1, 0)) + # [BC, BS] + b_s = tl.load(p_s, boundary_check=(0, 1)) + # [BS,] + b_zn = tl.load(p_zn, boundary_check=(0,)) + + b_doo = tl.zeros([BC, BS], dtype=tl.float32) + for i_j in range(i_i + 1, NC): + p_z = tl.make_block_ptr(z + i_bh * T*S, (T, S), (S, 1), (i_t * BT + i_j * BC, i_s * BS), (BC, BS), (1, 0)) + p_ss = tl.make_block_ptr(ss + i_bh * T*S, (T, S), (S, 1), (i_t * BT + i_j * BC, i_s * BS), (BC, BS), (1, 0)) + # [BC, BS] + b_z = tl.load(p_z, boundary_check=(0, 1)) + b_ss = tl.load(p_ss, boundary_check=(0, 1)) + # [BC, BS] + b_doo += b_ss * exp(b_zn[None, :] - b_z) + b_doo = exp(b_s - b_zn[None, :]) * tl.dot(m_o.to(b_s.dtype), b_doo.to(b_s.dtype), allow_tf32=False) + + for j in range(0, BC): + p_z = tl.make_block_ptr(z + i_bh * T*S, (T*S,), (1,), ((i_t * BT + i_i * BC + j) * S + i_s * BS,), (BS,), (0,)) + p_ss = tl.make_block_ptr(ss + i_bh * T*S, (T*S,), (1,), ((i_t * BT + i_i * BC + j) * S + i_s * BS,), (BS,), (0,)) + # [BS,] + b_z = tl.load(p_z, boundary_check=(0,)) + b_ss = tl.load(p_ss, boundary_check=(0,)) + # [BC, BS] + m_i = o_i[:, None] <= j + b_doo += tl.where(m_i, exp(b_s - b_z[None, :]) * b_ss[None, :], 0.) + b_doo += tl.load(p_doo, boundary_check=(0, 1)) + tl.store(p_doo, b_doo.to(p_doo.dtype.element_ty), boundary_check=(0, 1)) + + +class ChunkABCFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward(ctx, q, k, v, s, initial_state, output_final_state): + B, H, T, K, V, M = *q.shape, v.shape[-1], s.shape[-1] + BT, BC = 64, 16 + BK = min(64, triton.next_power_of_2(K)) + BV = min(64, triton.next_power_of_2(V)) + BM = min(64, triton.next_power_of_2(M)) + NT, NC = triton.cdiv(T, BT), triton.cdiv(BT, BC) + NV, NM = triton.cdiv(V, BV), triton.cdiv(M, BM) + num_warps = 4 if BK == 64 else 2 + num_stages = 1 + + def fwd_pre(s, B, H, T, S): + # keep cummulative normalizer in fp32 + z = torch.empty_like(s, dtype=torch.float) + grid = (B * H,) + logcumsumexp_fwd_kernel[grid]( + s, z, + T=T, S=S, + ) + return z + + def fwd_inner(q, k, v, z, B, H, T, K, V, BT, BK, BV, NT, normk=False, h0=None, ht=None): + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + h = q.new_empty(B, H, NT * K, V) + grid = (NV, NK, B * H) + chunk_abc_fwd_kernel_h[grid]( + k, v, z, h, h0, ht, + T=T, K=K, V=V, BT=BT, BK=BK, BV=BV, NT=NT, + NORMK=normk, + USE_INITIAL_STATE=h0 is not None, + STORE_FINAL_STATE=ht is not None, + num_warps=num_warps, + num_stages=num_stages, + ) + return h + + final_state = None + if output_final_state: + final_state = (q.new_empty(B, H, K, M, dtype=torch.float), + q.new_empty(B, H, M, V, dtype=torch.float)) + + z = fwd_pre(s, B, H, T, M) + scale = K ** -0.5 + hk = fwd_inner( + q=q, k=k, v=s, z=z, + B=B, H=H, T=T, K=K, V=M, BT=BT, BK=BK, BV=BM, NT=NT, + normk=False, + h0=initial_state[0] if initial_state is not None else None, + ht=final_state[0] if final_state is not None else None, + ) + ok1 = torch.empty_like(s) + Ak = q.new_empty(B, H, T, BT) + grid = (NM, NT, B * H) + chunk_abc_fwd_kernel_K[grid]( + q, k, z, hk, ok1, Ak, + scale=scale, + T=T, K=K, V=M, BT=BT, BK=BK, BV=BM, NT=NT, + num_warps=num_warps, + num_stages=num_stages, + ) + ok0 = torch.empty_like(s) + grid = (NM, NT * NC, B * H) + chunk_abc_fwd_kernel_intra_K[grid]( + s, z, ok0, Ak, + T=T, V=M, BT=BT, BC=BC, BV=BM, NC=NC, + num_warps=2, + num_stages=num_stages, + ) + ok = ok0.add_(ok1) + + scale = 1. + # p is kept in fp32 for safe softmax backward + p = softmax_fwd(ok, dtype=torch.float) + qv = p.to(q.dtype) + + scale = 1. + hv = fwd_inner( + q=qv, k=s, v=v, z=z, + B=B, H=H, T=T, K=M, V=V, BT=BT, BK=BM, BV=BV, NT=NT, + normk=True, + h0=initial_state[1] if initial_state is not None else None, + ht=final_state[1] if final_state is not None else None, + ) + Av = q.new_zeros(NM, B, H, T, BT) + grid = (NM, NT * NC * NC, B * H) + chunk_abc_fwd_kernel_intra_V[grid]( + qv, s, z, Av, + scale=scale, + T=T, K=M, BT=BT, BC=BC, BK=BM, NC=NC, + num_warps=2, + num_stages=num_stages, + ) + Av = Av.sum(0) + ov = torch.empty_like(v) + grid = (NV, NT, B * H) + chunk_abc_fwd_kernel_V[grid]( + qv, v, z, hv, ov, Av, + scale=scale, + T=T, + K=M, + V=V, + BT=BT, + BK=BM, + BV=BV, + NT=NT, + num_warps=num_warps, + num_stages=num_stages, + ) + ctx.save_for_backward(q, k, v, s, z, ok, p, hk, hv, Av) + ctx.BT = BT + return ov, final_state + + @staticmethod + @input_guard + def backward(ctx, dov, dht=None): + q, k, v, s, z, ok, p, hk, hv, Av = ctx.saved_tensors + B, H, T, K, V, M = *q.shape, v.shape[-1], s.shape[-1] + BT, BC = ctx.BT, 16 + BK = min(64, triton.next_power_of_2(K)) + BV = min(64, triton.next_power_of_2(V)) + BM = min(64, triton.next_power_of_2(M)) + NT, NC = triton.cdiv(T, BT), triton.cdiv(BT, BC) + NK, NM = triton.cdiv(K, BK), triton.cdiv(M, BM) + num_warps = 4 if BK == 64 else 2 + num_stages = 1 + + def bwd_inner(q, z, do, B, H, T, K, V, BT, BK, BV, NT, scale, normk=False): + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + dh = q.new_empty(B, H, NT * K, V) + grid = (NK, NV, B * H) + chunk_abc_bwd_kernel_dh[grid]( + q, z, do, dh, + scale=scale, + T=T, K=K, V=V, BT=BT, BK=BK, BV=BV, NT=NT, + NORMK=normk, + num_warps=num_warps, + num_stages=num_stages, + ) + return dh + + def bwd_post(s, z, ss, B, H, T, S, BT, BC, BS, NT, NC, NS): + doo = torch.empty_like(s) + grid = (NS, B * H) + chunk_abc_bwd_kernel_rcum_inter[grid]( + s, z, ss, doo, + T=T, S=S, BT=BT, BS=BS, NT=NT, + num_warps=num_warps, + num_stages=num_stages, + ) + grid = (NS, NT * NC, B * H) + chunk_abc_bwd_kernel_rcum_intra[grid]( + s, z, ss, doo, + T=T, S=S, BT=BT, BC=BC, BS=BS, NC=NC, + num_warps=num_warps, + num_stages=num_stages, + ) + return doo + + scale = 1. + qv = p.to(q.dtype) + dhv = bwd_inner( + qv, z, dov, + B=B, H=H, T=T, K=M, V=V, BT=BT, BK=BM, BV=BV, NT=NT, + scale=scale, + normk=True, + ) + dp1 = torch.empty_like(p) + dsv1 = torch.empty_like(s, dtype=torch.float) + dv = v.new_empty(NM, *v.shape) + dAv = q.new_zeros(B, H, T, BT) + grid = (NM, NT, B * H) + chunk_abc_bwd_kernel_V[grid]( + s, v, z, hv, Av, dov, dhv, dp1, dsv1, dv, dAv, + scale=scale, + T=T, K=M, V=V, BT=BT, BK=BM, BV=BV, NT=NT, + num_warps=num_warps, + num_stages=num_stages, + ) + dv = dv.sum(0) + dp0 = torch.empty_like(p) + dsv0 = s.new_zeros(s.shape, dtype=torch.float) + grid = (NM, NT * NC, B * H) + chunk_abc_bwd_kernel_intra_V[grid]( + qv, s, z, dAv, dp0, dsv0, + T=T, K=M, BT=BT, BC=BC, BK=BM, NC=NC, + num_warps=2, + num_stages=num_stages, + ) + dp = dp1.add_(dp0) + dsv = dsv1.add_(dsv0) + + # softmax gradient, equivalent to: + # dok = p * (dp - (p * dp).sum(-1, True)) + dok = softmax_bwd(p, dp, dtype=ok.dtype) + + scale = K ** -0.5 + dhk = bwd_inner( + q, z, dok, + B=B, H=H, T=T, K=K, V=M, BT=BT, BK=BK, BV=BM, NT=NT, + scale=scale, + normk=False, + ) + dAk = q.new_zeros(NM, B, H, T, BT) + grid = (NM, NT * NC * NC, B * H) + chunk_abc_bwd_kernel_intra_K[grid]( + s, z, dok, dAk, + scale=scale, + T=T, V=M, BT=BT, BC=BC, BV=BM, NC=NC, + num_warps=2, + num_stages=num_stages, + ) + dAk = dAk.sum(0) + + Ak = q.new_zeros(NK, B, H, T, BT) + dq = torch.empty_like(q) + dk = torch.empty_like(k) + dsk1 = s.new_empty(NK, *s.shape, dtype=torch.float) + grid = (NK, NT, B * H) + chunk_abc_bwd_kernel_K[grid]( + q, k, s, z, hk, Ak, dok, dhk, dq, dk, dsk1, dAk, + scale=scale, + T=T, K=K, V=M, BT=BT, BK=BK, BV=BM, NT=NT, + num_warps=num_warps, + num_stages=num_stages, + ) + Ak = Ak.sum(0) + dsk1 = dsk1.sum(0) + dsk0 = torch.empty_like(s, dtype=torch.float) + grid = (NM, NT * NC, B * H) + chunk_abc_bwd_kernel_intra_KV[grid]( + s, z, Ak, dok, dsk0, + T=T, V=M, BT=BT, BC=BC, BV=BM, NC=NC, + num_warps=2, + num_stages=num_stages, + ) + ds = dsv.add_(dsk1.add_(dsk0)) + ds -= bwd_post(s, z, ok * dok + p * dp, B, H, T, M, BT, BC, BM, NT, NC, NM) + ds = ds.to(s.dtype) + return dq, dk, dv, ds, None, None + + +@torch.compiler.disable +def chunk_abc( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + initial_state: tuple[torch.Tensor] | None = None, + output_final_state: bool = False, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + s (torch.Tensor): + slot representations of shape `[B, T, H, M]`. + initial_state (Optional[Tuple[torch.Tensor, torch.Tensor]]): + Initial states of shape `[B, H, K, M]` and `[B, H, M, V]`. Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[B, H, K, M]` and `[B, H, M, V]`. Default: `False`. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[B, H, K, M]` and `[B, H, M, V]` if `output_final_state=True` else `None`. + """ + if not head_first: + q, k, v, s = map(lambda x: x.transpose(1, 2), (q, k, v, s)) + o, final_state = ChunkABCFunction.apply(q, k, v, s, initial_state, output_final_state) + if not head_first: + o = o.transpose(1, 2) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/abc/naive.py b/code/flash-linear-attention/fla/ops/abc/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..66f6193faf5e36026bc54c106a2f7defad613a47 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/abc/naive.py @@ -0,0 +1,94 @@ + + +import torch +from einops import repeat + + +def naive_recurrent_abc( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor | None = None, + scale: int | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool | None = False, +) -> torch.Tensor: + dtype = q.dtype + + NG = q.shape[1]//k.shape[1] + # [batch_size, n_heads, seq_len, n_slots] + if g is None: + z = s.float().logcumsumexp(2) + g = torch.cat((z[:, :, :1], z[:, :, :-1]), 2) - z + s = torch.exp(s - z) + q, k, v, s, g = map(lambda x: x.float(), (q, k, v, s, g)) + k, v, s, g = map(lambda x: repeat(x, 'b h t d -> b (h g) t d', g=NG), (k, v, s, g)) + if initial_state is not None: + initial_state = tuple(map(lambda x: repeat(x, 'b h k v -> b (h g) k v', g=NG), initial_state)) + + B, H, T, K, V, M = *q.shape, v.shape[-1], s.shape[-1] + + hk = torch.zeros(B, H, K, M, dtype=torch.float, device=q.device) + ok = torch.zeros_like(s) + + if scale is None: + scale = q.shape[-1] ** -0.5 + + final_state = None + if initial_state is not None: + hk += initial_state[0] + + for i in range(T): + q_i = q[:, :, i] * scale + k_i = k[:, :, i] + v_i = s[:, :, i] + g_i = g[:, :, i].exp() + hk = hk * g_i[..., None, :] + k_i[..., None] * v_i[..., None, :] + ok[:, :, i] = (q_i[..., None] * hk).sum(-2) + + qv = ok.softmax(-1) + hv = torch.zeros(B, H, M, V, dtype=torch.float, device=q.device) + ov = torch.zeros_like(v) + if initial_state is not None: + hv += initial_state[1] + + for i in range(T): + q_i = qv[:, :, i] + k_i = s[:, :, i] + v_i = v[:, :, i] + g_i = g[:, :, i].exp() + hv = hv * g_i[..., :, None] + k_i[..., None] * v_i[..., None, :] + ov[:, :, i] = (q_i[..., None] * hv).sum(-2) + + if output_final_state: + final_state = (hk.view(B, -1, NG, K, M)[:, :, 0], hv.view(B, -1, NG, M, V)[:, :, 0]) + return ov.to(dtype), final_state + + +def naive_cumsum_abc( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, +) -> torch.Tensor: + """ + A simple implementation of vanilla ABC that is more aligned with the descriptions in the paper. + This is just for demonstration purposes, with no numerical stabilities guaranteed. + """ + + dtype = q.dtype + q, k, v, s = map(lambda x: x.float(), (q, k, v, s)) + + scale = q.shape[-1] ** -0.5 + # [batch_size, n_heads, seq_len, n_slots] + s = (s - s.max(2, True)[0]).exp() + z = s.cumsum(2) + # [batch_size, n_heads, seq_len, n_slots, d_head] + K = (s.unsqueeze(-1) * k.unsqueeze(-2)).cumsum(2) / z.unsqueeze(-1) + V = (s.unsqueeze(-1) * v.unsqueeze(-2)).cumsum(2) / z.unsqueeze(-1) + # [batch_size, n_heads, seq_len, n_slots] + p = torch.einsum('...d,...md->...m', q * scale, K).softmax(-1) + # [batch_size, n_heads, seq_len, d_head] + o = torch.einsum('...m,...md->...d', p, V) + return o.to(dtype), None diff --git a/code/flash-linear-attention/fla/ops/attn/__init__.py b/code/flash-linear-attention/fla/ops/attn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ae1922ed869d4f00202c0d99bf4592728e42de8e --- /dev/null +++ b/code/flash-linear-attention/fla/ops/attn/__init__.py @@ -0,0 +1,6 @@ + +from .parallel import parallel_attn + +__all__ = [ + 'parallel_attn', +] diff --git a/code/flash-linear-attention/fla/ops/attn/decoding.py b/code/flash-linear-attention/fla/ops/attn/decoding.py new file mode 100644 index 0000000000000000000000000000000000000000..e12e097beec327099316dc774bf0749aa86a3c74 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/attn/decoding.py @@ -0,0 +1,181 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.cumsum import chunk_global_cumsum +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem + + +@triton.heuristics({ + 'USE_G': lambda args: args['g_cumsum'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4] + ([] if check_shared_mem('hopper') else [8]) + for num_stages in [2, 3, 4, 5] + ], + key=['H', 'G', 'K', 'V', 'BK', 'BV', 'USE_G'], + **autotune_cache_kwargs, +) +@triton.jit +def naive_attn_decoding_kernel( + q, + k, + v, + o, + g_cumsum, + scale, + gate_scale, + cu_seqlens, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, +): + i_v, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + bos, eos = tl.load(cu_seqlens + i_b).to(tl.int32), tl.load(cu_seqlens + i_b + 1).to(tl.int32) + T = eos - bos + + p_q = tl.make_block_ptr(q + i_bh * K, (K,), (1, ), (0, ), (BK,), (0,)) + p_o = tl.make_block_ptr(o + i_bh * V, (V,), (1, ), (0, ), (BV,), (0,)) + + b_q = tl.load(p_q, boundary_check=(0,)) + b_q = (b_q * scale).to(b_q.dtype) + + b_o = tl.zeros([BV ], dtype=tl.float32) + + b_m = tl.full([1], float('-inf'), dtype=tl.float32) + b_acc = tl.zeros([1], dtype=tl.float32) + + if USE_G: + p_g = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (T-1,), (1,), (0,)) + b_gq = tl.load(p_g, boundary_check=(0,)).to(tl.float32) + else: + b_gq = None + + for i_s in range(0, T, BS): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_s, 0), (BS, BK), (1, 0)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BS] + b_s = tl.sum(b_q[None, :] * b_k, 1) + + mask = i_s + tl.arange(0, BS) < T + b_s = tl.where(mask, b_s, float('-inf')) + + if USE_G: + p_gk = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + b_gk = tl.load(p_gk, boundary_check=(0,)).to(tl.float32) + b_s += (b_gq - b_gk) * gate_scale + # [BT, BS] + b_m, b_mp = tl.maximum(b_m, tl.max(b_s)), b_m + b_r = exp(b_mp - b_m) + # [BT, BS] + b_p = exp(b_s - b_m) + + # [BT] + b_acc = b_acc * b_r + tl.sum(b_p, 0) + # [BT, BV] + b_o = b_o * b_r + tl.sum(b_p[:, None] * b_v, 0) + b_mp = b_m + b_o = b_o / b_acc + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, )) + + +def attn_decoding_one_step( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor = None, + do_gate_scale: bool = False, +): + r""" + Args: + q (torch.Tensor): + query of shape `[1, B, HQ, K]`. + k (torch.Tensor): + keys of shape `[1, T, H, K]`. + GQA will be applied if HQ is divisible by H. T is the cumulative length for all batch. + v (torch.Tensor): + values of shape `[1, T, H, V]`. + g (Optional[torch.Tensor]): + log decay factors of shape `[1, T, H]`. Default: `None`. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + do_gate_scale (bool): + Whether to apply gate scale. Default: `False`. If `True`, the attention scale will also be applied + to the gating bias term in Forgetting Transformer or PaTH-FoX. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, 1, HQ, V]`. + """ + assert cu_seqlens is not None, "The cu_seqlens must be provided for varlen decoding" + B, T, H, K, V = *k.shape, v.shape[-1] + N = len(cu_seqlens) - 1 + HQ = q.shape[2] + G = HQ // H + if scale is None: + scale = K ** -0.5 + + BK = max(triton.next_power_of_2(K), 16) + if check_shared_mem('hopper', q.device.index): + BS = min(64, max(16, triton.next_power_of_2(T))) + BV = min(256, max(16, triton.next_power_of_2(V))) + elif check_shared_mem('ampere', q.device.index): + BS = min(32, max(16, triton.next_power_of_2(T))) + BV = min(128, max(16, triton.next_power_of_2(V))) + else: + BS = min(32, max(16, triton.next_power_of_2(T))) + BV = min(64, max(16, triton.next_power_of_2(V))) + g_cumsum = chunk_global_cumsum(g, cu_seqlens=cu_seqlens, output_dtype=torch.float32) if g is not None else None + NV = triton.cdiv(V, BV) + o = torch.empty(*q.shape[:-1], V, dtype=v.dtype, device=q.device) + gate_scale = 1.0 if not do_gate_scale else scale + + grid = (NV, N * HQ) + naive_attn_decoding_kernel[grid]( + q=q, + k=k, + v=v, + o=o, + g_cumsum=g_cumsum, + scale=scale, + gate_scale=gate_scale, + cu_seqlens=cu_seqlens, + B=B, + T=T, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + BS=BS, + BK=BK, + BV=BV, + ) + return o diff --git a/code/flash-linear-attention/fla/ops/attn/parallel.py b/code/flash-linear-attention/fla/ops/attn/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..8ee8266b64184d6e7e32ac42620b2342dceb4d17 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/attn/parallel.py @@ -0,0 +1,728 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl +from einops import reduce + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.cumsum import chunk_global_cumsum +from fla.ops.utils.op import exp2, log2 +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, check_shared_mem, contiguous + + +@triton.heuristics({ + 'USE_G': lambda args: args['g_cumsum'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit +def parallel_attn_fwd_kernel( + q, + k, + v, + o, + g_cumsum, + lse, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + RCP_LN2: tl.constexpr = 1.4426950216 + + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_lse = tl.make_block_ptr(lse + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + + # the Q block is kept in the shared memory throughout the whole kernel + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BT, BV] + b_o = tl.zeros([BT, BV], dtype=tl.float32) + + b_m = tl.full([BT], float('-inf'), dtype=tl.float32) + b_acc = tl.zeros([BT], dtype=tl.float32) + + if USE_G: + p_g = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + b_gq = tl.load(p_g, boundary_check=(0,)).to(tl.float32) + else: + b_gq = None + + for i_s in range(0, i_t * BT, BS): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, H*K), (0, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BS] + b_s = tl.dot(b_q, b_k) * scale * RCP_LN2 + + if USE_G: + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + b_gk = tl.load(g_cumsum + (bos + o_k) * HQ + i_hq, mask=m_k, other=0).to(tl.float32) + b_s += b_gq[:, None] - b_gk[None, :] + + # [BT, BS] + b_m, b_mp = tl.maximum(b_m, tl.max(b_s, 1)), b_m + b_r = exp2(b_mp - b_m) + # [BT, BS] + b_p = exp2(b_s - b_m[:, None]) + # [BT] + b_acc = b_acc * b_r + tl.sum(b_p, 1) + # [BT, BV] + b_o = b_o * b_r[:, None] + tl.dot(b_p.to(b_q.dtype), b_v) + + b_mp = b_m + + # [BT] + o_q = i_t * BT + tl.arange(0, BT) + for i_s in range(i_t * BT, min((i_t + 1) * BT, T), BS): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, H*K), (0, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + + # [BS] + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BS] + b_s = tl.dot(b_q, b_k) * scale * RCP_LN2 + + if USE_G: + b_gk = tl.load(g_cumsum + (bos + o_k) * HQ + i_hq, mask=m_k, other=0).to(tl.float32) + b_s += b_gq[:, None] - b_gk[None, :] + + b_s = tl.where((o_q[:, None] >= o_k[None, :]) & m_k[None, :], b_s, float('-inf')) + + # [BT] + b_m, b_mp = tl.maximum(b_m, tl.max(b_s, 1)), b_m + b_r = exp2(b_mp - b_m) + # [BT, BS] + b_p = exp2(b_s - b_m[:, None]) + # [BT] + b_acc = b_acc * b_r + tl.sum(b_p, 1) + # [BT, BV] + b_o = b_o * b_r[:, None] + tl.dot(b_p.to(b_q.dtype), b_v) + b_mp = b_m + + b_o = b_o / b_acc[:, None] + b_m += log2(b_acc) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_lse, b_m.to(p_lse.dtype.element_ty), boundary_check=(0,)) + + +@triton.jit +def parallel_attn_bwd_kernel_preprocess( + o, + do, + delta, + B: tl.constexpr, + V: tl.constexpr, +): + i_n = tl.program_id(0) + o_d = tl.arange(0, B) + m_d = o_d < V + + b_o = tl.load(o + i_n * V + o_d, mask=m_d, other=0) + b_do = tl.load(do + i_n * V + o_d, mask=m_d, other=0).to(tl.float32) + b_delta = tl.sum(b_o * b_do) + + tl.store(delta + i_n, b_delta.to(delta.dtype.element_ty)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g_cumsum'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_attn_bwd_kernel_dq( + q, + k, + v, + lse, + delta, + do, + dq, + dg_cumsum, + g_cumsum, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_G: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + # NOTE: we must multiply RCP_LN2 after tl.dot for high precision + RCP_LN2: tl.constexpr = 1.4426950216 + + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_do = tl.make_block_ptr(do + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_lse = tl.make_block_ptr(lse + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + p_delta = tl.make_block_ptr(delta + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BT] + b_lse = tl.load(p_lse, boundary_check=(0,)) + b_delta = tl.load(p_delta, boundary_check=(0,)) + + # [BT, BK] + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + if USE_G: + b_dg = tl.zeros([BT ], dtype=tl.float32) + p_gq = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + b_gq = tl.load(p_gq, boundary_check=(0,)).to(tl.float32) + else: + b_gq = None + b_dg = None + + o_q = i_t * BT + tl.arange(0, BT) + for i_s in range(0, i_t * BT, BS): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, H*K), (0, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (V, T), (1, H*V), (i_v * BV, i_s), (BV, BS), (0, 1)) + + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BS] + b_s = tl.dot(b_q, b_k) * scale * RCP_LN2 + if USE_G: + b_gk = tl.load(g_cumsum + (bos + o_k) * HQ + i_hq, mask=m_k, other=0).to(tl.float32) + b_s += b_gq[:, None] - b_gk[None, :] + + b_s = tl.where((o_q[:, None] >= o_k[None, :]) & m_k[None, :], b_s, float('-inf')) + b_p = exp2(b_s - b_lse[:, None]) + # [BT, BV] @ [BV, BS] -> [BT, BS] + b_dp = tl.dot(b_do, b_v) + b_ds = b_p * (b_dp.to(tl.float32) - b_delta[:, None]) + # [BT, BS] @ [BS, BK] -> [BT, BK] + b_dq += tl.dot(b_ds.to(b_k.dtype), tl.trans(b_k)) + if USE_G: + b_dg += tl.sum(b_ds, 1) + + # [BT] + o_q = i_t * BT + tl.arange(0, BT) + for i_s in range(i_t * BT, min((i_t + 1) * BT, T), BS): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, H*K), (0, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (V, T), (1, H*V), (i_v * BV, i_s), (BV, BS), (0, 1)) + + # [BS] + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BS] + b_s = tl.dot(b_q, b_k) * scale * RCP_LN2 + + if USE_G: + p_gk = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + b_gk = tl.load(p_gk, boundary_check=(0,)).to(tl.float32) + b_s += b_gq[:, None] - b_gk[None, :] + b_p = tl.where((o_q[:, None] >= o_k[None, :]) & m_k[None, :], exp2(b_s - b_lse[:, None]), 0) + + # [BT, BV] @ [BV, BS] -> [BT, BS] + b_dp = tl.dot(b_do, b_v) + b_ds = b_p * (b_dp.to(tl.float32) - b_delta[:, None]) + # [BT, BS] @ [BS, BK] -> [BT, BK] + b_dq += tl.dot(b_ds.to(b_k.dtype), tl.trans(b_k)) + if USE_G: + b_dg += tl.sum(b_ds, 1) + + b_dq *= scale + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + if USE_G: + p_dg = tl.make_block_ptr(dg_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g_cumsum'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_attn_bwd_kernel_dkv( + q, + k, + v, + g_cumsum, + lse, + delta, + do, + dk, + dv, + dg_cumsum, + cu_seqlens, + chunk_indices, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + RCP_LN2: tl.constexpr = 1.4426950216 + + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + # [BT, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dv = tl.zeros([BT, BV], dtype=tl.float32) + + o_k = i_t * BT + tl.arange(0, BT) + + if USE_G: + p_gk = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + b_gk = tl.load(p_gk, boundary_check=(0,)).to(tl.float32) + b_dg = tl.zeros([BT], dtype=tl.float32) + else: + b_gk = None + b_dg = None + + for i_s in range(i_t * BT, min((i_t + 1) * BT, T), BS): + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_s, 0), (BS, BK), (1, 0)) + p_do = tl.make_block_ptr(do + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + p_lse = tl.make_block_ptr(lse + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + p_delta = tl.make_block_ptr(delta + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + + # [BS] + o_q = i_s + tl.arange(0, BS) + m_q = o_q < T + # [BS, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BS, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BS] + b_lse = tl.load(p_lse, boundary_check=(0,)) + b_delta = tl.load(p_delta, boundary_check=(0,)) + # [BT, BS] + b_s = tl.dot(b_k, tl.trans(b_q)) * scale * RCP_LN2 + if USE_G: + p_gq = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + b_gq = tl.load(p_gq, boundary_check=(0,)).to(tl.float32) + b_s += b_gq[None, :] - b_gk[:, None] + b_p = tl.where((o_k[:, None] <= o_q[None, :]) & m_q[None, :], exp2(b_s - b_lse[None, :]), 0) + # [BT, BS] @ [BS, BV] -> [BT, BV] + b_dv += tl.dot(b_p.to(b_do.dtype), b_do) + # [BT, BV] @ [BV, BS] -> [BT, BS] + b_dp = tl.dot(b_v, tl.trans(b_do)) + # [BT, BS] + b_ds = b_p * (b_dp - b_delta[None, :]) + # [BT, BS] @ [BS, BK] -> [BT, BK] + b_dk += tl.dot(b_ds.to(b_q.dtype), b_q) + if USE_G: + b_dg -= tl.sum(b_ds, 1) + + for i_s in range((i_t + 1) * BT, tl.cdiv(T, BS) * BS, BS): + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_s, 0), (BS, BK), (1, 0)) + p_do = tl.make_block_ptr(do + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + p_lse = tl.make_block_ptr(lse + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + p_delta = tl.make_block_ptr(delta + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + + # [BS] + o_q = i_s + tl.arange(0, BS) + m_q = o_q < T + # [BS, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BS, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BS] + b_lse = tl.load(p_lse, boundary_check=(0,)) + b_delta = tl.load(p_delta, boundary_check=(0,)) + # [BT, BS] + b_s = tl.dot(b_k, tl.trans(b_q)) * scale * RCP_LN2 + if USE_G: + p_gq = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_s,), (BS,), (0,)) + b_gq = tl.load(p_gq, boundary_check=(0,)).to(tl.float32) + b_s += b_gq[None, :] - b_gk[:, None] + b_p = tl.where(m_q[None, :], exp2(b_s - b_lse[None, :]), 0) + # [BT, BS] @ [BS, BV] -> [BT, BV] + b_dv += tl.dot(b_p.to(b_do.dtype), b_do) + # [BT, BV] @ [BV, BS] -> [BT, BS] + b_dp = tl.dot(b_v, tl.trans(b_do)) + # [BT, BS] + b_ds = b_p * (b_dp - b_delta[None, :]) + # [BT, BS] @ [BS, BK] -> [BT, BK] + b_dk += tl.dot(b_ds.to(b_q.dtype), b_q) + if USE_G: + b_dg -= tl.sum(b_ds, 1) + + b_dk = b_dk * scale + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + if USE_G: + p_dg = tl.make_block_ptr(dg_cumsum + bos * HQ + i_hq, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + +def parallel_attn_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g_cumsum: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + HQ = q.shape[2] + G = HQ // H + BT = 128 + if check_shared_mem('hopper', q.device.index): + BS = min(64, max(16, triton.next_power_of_2(T))) + BK = min(256, max(16, triton.next_power_of_2(K))) + BV = min(256, max(16, triton.next_power_of_2(V))) + num_warps = 8 + elif check_shared_mem('ampere', q.device.index): + BS = min(32, max(16, triton.next_power_of_2(T))) + BK = min(256, max(16, triton.next_power_of_2(K))) + BV = min(128, max(16, triton.next_power_of_2(V))) + num_warps = 4 + else: + BS = min(32, max(16, triton.next_power_of_2(T))) + BK = min(256, max(16, triton.next_power_of_2(K))) + BV = min(64, max(16, triton.next_power_of_2(V))) + num_warps = 2 + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + assert NK == 1, "The key dimension can not be larger than 256" + + o = torch.empty(B, T, HQ, V, dtype=v.dtype, device=q.device) + lse = torch.empty(B, T, HQ, dtype=torch.float, device=q.device) + grid = (NV, NT, B * HQ) + parallel_attn_fwd_kernel[grid]( + q=q, + k=k, + v=v, + o=o, + g_cumsum=g_cumsum, + lse=lse, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + num_warps=num_warps, + ) + return o, lse + + +def parallel_attn_bwd_preprocess( + o: torch.Tensor, + do: torch.Tensor, +): + V = o.shape[-1] + delta = torch.empty_like(o[..., 0], dtype=torch.float) + parallel_attn_bwd_kernel_preprocess[(delta.numel(),)]( + o=o, + do=do, + delta=delta, + B=triton.next_power_of_2(V), + V=V, + ) + return delta + + +def parallel_attn_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + o: torch.Tensor, + g_cumsum: torch.Tensor, + lse: torch.Tensor, + do: torch.Tensor, + scale: float = None, + chunk_size: int = 128, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + HQ = q.shape[2] + G = HQ // H + if check_shared_mem('hopper'): + BT = 128 + BS = 64 + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + num_warps = 8 + elif check_shared_mem('ampere'): + BS = 32 + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + BT = 128 if K <= 64 else 64 + num_warps = 4 + else: + BT = 64 + BS = 32 + BK = max(triton.next_power_of_2(K), 16) + BV = min(max(triton.next_power_of_2(V), 16), 64) + num_warps = 2 + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NV = triton.cdiv(V, BV) + + delta = parallel_attn_bwd_preprocess(o, do) + + dq = torch.empty(B, T, HQ, K, dtype=k.dtype if H == HQ else torch.float, device=q.device) + dk = torch.empty(B, T, HQ, K, dtype=k.dtype if H == HQ else torch.float, device=q.device) + dv = torch.empty(B, T, HQ, V, dtype=v.dtype if H == HQ else torch.float, device=q.device) + grid = (NV, NT, B * HQ) + + dg_cumsum, dg_cumsum_k = None, None + if g_cumsum is not None: + dg_cumsum = torch.empty(B, T, HQ, dtype=torch.float, device=q.device) + dg_cumsum_k = torch.empty(B, T, HQ, dtype=torch.float, device=q.device) + + parallel_attn_bwd_kernel_dq[grid]( + q=q, + k=k, + v=v, + g_cumsum=g_cumsum, + lse=lse, + delta=delta, + do=do, + dq=dq, + dg_cumsum=dg_cumsum, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + num_warps=num_warps, + ) + parallel_attn_bwd_kernel_dkv[grid]( + q=q, + k=k, + v=v, + g_cumsum=g_cumsum, + lse=lse, + delta=delta, + do=do, + dk=dk, + dv=dv, + dg_cumsum=dg_cumsum_k, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + num_warps=num_warps, + ) + dk = reduce(dk, 'b t (h g) k -> b t h k', g=G, reduction='sum') + dv = reduce(dv, 'b t (h g) v -> b t h v', g=G, reduction='sum') + if g_cumsum is not None: + dg_cumsum.add_(dg_cumsum_k) + return dq, dk, dv, dg_cumsum + + +@torch.compile +class ParallelAttentionFunction(torch.autograd.Function): + + @staticmethod + @contiguous + @autocast_custom_fwd + def forward(ctx, q, k, v, g, scale, cu_seqlens): + ctx.dtype = q.dtype + + RCP_LN2: float = 1.4426950216 + g_cumsum = chunk_global_cumsum(g, cu_seqlens=cu_seqlens, scale=RCP_LN2) if g is not None else None + o, lse = parallel_attn_fwd( + q=q, + k=k, + v=v, + g_cumsum=g_cumsum, + scale=scale, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, k, v, o, g_cumsum, lse) + ctx.cu_seqlens = cu_seqlens + ctx.scale = scale + return o.to(q.dtype) + + @staticmethod + @contiguous + @autocast_custom_bwd + def backward(ctx, do): + q, k, v, o, g_cumsum, lse = ctx.saved_tensors + dq, dk, dv, dg = parallel_attn_bwd( + q=q, + k=k, + v=v, + o=o, + g_cumsum=g_cumsum, + lse=lse, + do=do, + scale=ctx.scale, + cu_seqlens=ctx.cu_seqlens, + ) + if dg is not None: + dg = chunk_global_cumsum(dg, cu_seqlens=ctx.cu_seqlens, reverse=True) + + return dq.to(q), dk.to(k), dv.to(v), dg, None, None + + +def parallel_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> torch.Tensor: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, HQ, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + GQA will be applied if HQ is divisible by H. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (Optional[torch.Tensor]): + log decay factors of shape `[B, T, H]`. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HQ, V]`. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + if cu_seqlens is not None: + assert q.shape[0] == 1, "batch size must be 1 when cu_seqlens are provided" + + o = ParallelAttentionFunction.apply(q, k, v, g, scale, cu_seqlens) + return o diff --git a/code/flash-linear-attention/fla/ops/based/__init__.py b/code/flash-linear-attention/fla/ops/based/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d73959049e530e75fadb63ba7633bb425d045b3c --- /dev/null +++ b/code/flash-linear-attention/fla/ops/based/__init__.py @@ -0,0 +1,8 @@ + +from .fused_chunk import fused_chunk_based +from .parallel import parallel_based + +__all__ = [ + 'fused_chunk_based', + 'parallel_based', +] diff --git a/code/flash-linear-attention/fla/ops/based/fused_chunk.py b/code/flash-linear-attention/fla/ops/based/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..2ad853fa091ab5da19b025de56ecbd0f49ba2e36 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/based/fused_chunk.py @@ -0,0 +1,371 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +@triton.jit(do_not_specialize=['T']) +def fused_chunk_based_fwd_kernel( + q, + k, + v, + o, + z, + scale, # K ** -0.5 + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + i_v, i_k, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + o_i = tl.arange(0, BT) + + # [BT, BT] + m_s = o_i[:, None] >= o_i[None, :] + + # [BV], zero-order taylor expansion + b_h_0o = tl.zeros([BV], dtype=tl.float32) + # [BK, BV], first-order taylor expansion + b_h_1o = tl.zeros([BK, BV], dtype=tl.float32) + # [BK, BK, BV] second-order taylor expansion + b_h_2o = tl.zeros([BK*BK, BV], dtype=tl.float32) + + # make block pointers + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (0, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k * BK, 0), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (0, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + (i_bh + i_k*B*H) * T*V, (T, V), (V, 1), (0, i_v * BV), (BT, BV), (1, 0)) + + p_z = z + (i_bh + i_k * B * H) * T + tl.arange(0, BT) + k_2o = tl.zeros([1, BK * BK], dtype=tl.float32) + k_1o = tl.zeros([1, BK], dtype=tl.float32) + k_0o = 0 + + for i in range(0, tl.cdiv(T, BT)): + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BK*BK, BT] + b_k_2o = b_k[:, None, :] * b_k[None, :, :] + b_k_2o = tl.reshape(b_k_2o, [BK * BK, BT]).to(b_k.dtype) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BK] + b_q = (tl.load(p_q, boundary_check=(0, 1)) * scale).to(b_k.dtype) + b_o = tl.zeros([BT, BV], dtype=tl.float32) + b_z = tl.zeros([BT], dtype=tl.float32) + + # interchunk + # zero-order + b_o += b_h_0o + b_z += k_0o + # first-order + b_o += tl.dot(b_q, b_h_1o.to(b_q.dtype), allow_tf32=False) + b_z += tl.sum(b_q * k_1o, axis=1) + # second-order + b_q_2o = b_q[:, :, None] * b_q[:, None, :] + b_q_2o = tl.reshape(b_q_2o, [BT, BK * BK]).to(b_k.dtype) + b_o += tl.dot(b_q_2o, b_h_2o.to(b_q_2o.dtype), allow_tf32=False) * 0.5 + b_z += tl.sum(b_q_2o * k_2o, axis=1) * 0.5 + + # update running statistics + k_1o += tl.sum(b_k, axis=1)[None, :] + k_2o += tl.sum(b_k_2o, axis=1)[None, :] + k_0o += BT + + # intrachunk + # [BT, BT] + b_s = tl.dot(b_q, b_k, allow_tf32=False) + b_s = 1 + b_s + 0.5 * b_s * b_s + b_s = tl.where(m_s, b_s, 0) + b_z += tl.sum(b_s, axis=1) + b_o += tl.dot(b_s.to(b_q.dtype), b_v, allow_tf32=False) + # [TB, BV] + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_z, b_z.to(p_z.dtype.element_ty), mask=(i * BT + tl.arange(0, BT)) < T) + + # update hidden state + # [BK, BV] + b_h_2o = b_h_2o + tl.dot(b_k_2o.to(b_v.dtype), b_v, allow_tf32=False) + b_h_1o = b_h_1o + tl.dot(b_k, b_v, allow_tf32=False) + b_h_0o = b_h_0o + tl.sum(b_v, axis=0) + + p_q = tl.advance(p_q, (BT, 0)) + p_k = tl.advance(p_k, (0, BT)) + p_v = tl.advance(p_v, (BT, 0)) + p_o = tl.advance(p_o, (BT, 0)) + p_z += BT + + +# Similar to Algorithm1 of https://arxiv.org/abs/2006.16236 +@triton.jit +def fused_chunk_based_bwd_kernel( + # NV: number of split in the V dimension. NK: number of split in the K dimension + q, + k, + v, + do, + dz, + dq, + dk, + dv, + scale, # K ** -0.5 + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + i_v, i_k, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + o_i = tl.arange(0, BT) + m_s = o_i[:, None] >= o_i[None, :] + + # [BV], zero-order taylor expansion + # b_h_0o = tl.zeros([BV], dtype=tl.float32) + # [BK, BV], first-order taylor expansion + b_h_1o = tl.zeros([BV, BK], dtype=tl.float32) + # [BK, BK, BV] second-order taylor expansion + b_h_2o = tl.zeros([BV, BK*BK], dtype=tl.float32) + + k_1o = tl.zeros([1, BK], dtype=tl.float32) + k_2o = tl.zeros([1, BK * BK], dtype=tl.float32) + + for i in range(0, tl.cdiv(T, BT)): + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i * BT, i_k * BK), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (V, T), (1, V), (i_v * BV, i * BT), (BV, BT), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i * BT, i_v * BV), (BT, BV), (1, 0)) + p_dq = tl.make_block_ptr(dq + (i_bh + i_v*B*H) * T*K, (T, K), (K, 1), (i*BT, i_k*BK), (BT, BK), (1, 0)) + p_dz = dz + (i_bh) * T + tl.arange(0, BT) + i * BT + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + + # load tensors + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) + b_dz = tl.load(p_dz, mask=(tl.arange(0, BT) + i * BT) < T) + # [BV, BT] + b_v = tl.load(p_v, boundary_check=(0, 1)) + + # inter-chunk + b_dq += tl.dot(b_do, (b_h_1o).to(b_do.dtype), allow_tf32=False) + if i_v == 0: + b_dq += b_dz[:, None] * k_1o + b_dq_2o = tl.dot(b_do, (b_h_2o).to(b_do.dtype), allow_tf32=False) * 0.5 + if i_v == 0: + b_dq_2o += (b_dz[:, None] * k_2o) * 0.5 + b_dq_2o = tl.reshape(b_dq_2o, [BT, BK, BK]) + b_dq += tl.sum(b_dq_2o * b_q[:, :, None], axis=1) + b_dq += tl.sum(b_dq_2o * b_q[:, None, :], axis=2) + b_dq *= scale + + # intra-chunk + # [BT, BT] + b_ds = tl.dot(b_do, b_v, allow_tf32=False) + if i_v == 0: + b_ds += b_dz[:, None] + b_ds = tl.where(m_s, b_ds, 0) * scale + b_s = tl.dot(b_q, tl.trans(b_k), allow_tf32=False) + b_s = tl.where(m_s, b_s, 0) + b_dq += tl.dot((b_ds * (1 + b_s)).to(b_q.dtype), b_k, allow_tf32=False) + + # store + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + # update hidden state + # [BT, BK*BK] + b_k_2o = b_k[:, :, None] * b_k[:, None, :] + b_k_2o = tl.reshape(b_k_2o, [BT, BK * BK]).to(b_k.dtype) + # [BV, BK*BK] + b_h_2o = b_h_2o + tl.dot(b_v, b_k_2o.to(b_v.dtype), allow_tf32=False) + # [BV, BK] + b_h_1o = b_h_1o + tl.dot(b_v, b_k, allow_tf32=False) + + if i_v == 0: + # update running statistics + k_1o += tl.sum(b_k, axis=0)[None, :] + k_2o += tl.sum(b_k_2o, axis=0)[None, :] + + tl.debug_barrier() + b_h_1o = None + b_h_2o = None + + # [BK, BV], first-order taylor expansion + b_dh_1o = tl.zeros([BK, BV], dtype=tl.float32) + # [BK, BK, BV] second-order taylor expansion + b_dh_2o = tl.zeros([BK*BK, BV], dtype=tl.float32) + b_dh_0o = tl.zeros([BV], dtype=tl.float32) + m_s = tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :] + + dq_1o = tl.zeros([1, BK], dtype=tl.float32) + dq_2o = tl.zeros([BK * BK, 1], dtype=tl.float32) + + for i in range(tl.cdiv(T, BT) * BT - BT, -BT, -BT): + p_q = tl.make_block_ptr(q + i_bh * T*K, (K, T), (1, K), (i_k * BK, i), (BK, BT), (0, 1)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i, i_k * BK), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i, i_v * BV), (BT, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk + (i_bh+i_v*B*H) * T*K, (T, K), (K, 1), (i, i_k*BK), (BT, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv + (i_bh+i_k*B*H) * T*V, (T, V), (V, 1), (i, i_v*BV), (BT, BV), (1, 0)) + p_dz = dz + (i_bh) * T + tl.arange(0, BT) + i + + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dv = tl.zeros([BT, BV], dtype=tl.float32) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) + b_dz = tl.load(p_dz, mask=(tl.arange(0, BT)+i) < T) + b_q = (b_q * scale).to(b_k.dtype) + + # intra chunk + b_ds = tl.dot(b_v, tl.trans(b_do), allow_tf32=False) + if i_v == 0: + b_ds += b_dz[None, :] + b_ds = tl.where(m_s, b_ds, 0) + b_s = tl.dot(b_k, b_q, allow_tf32=False) + b_s2 = 1 + b_s + 0.5 * b_s * b_s + b_s = tl.where(m_s, b_s, 0) + b_s2 = tl.where(m_s, b_s2, 0) + b_ds *= (1+b_s) + + b_dk += tl.dot(b_ds.to(b_k.dtype), tl.trans(b_q), allow_tf32=False) + b_dv += tl.dot(b_s2.to(b_do.dtype), b_do, allow_tf32=False) + + # inter chunk + b_k_2o = b_k[:, :, None] * b_k[:, None, :] + b_k_2o = tl.reshape(b_k_2o, [BT, BK * BK]).to(b_k.dtype) + + b_dv += tl.dot(b_k, b_dh_1o.to(b_k.dtype), allow_tf32=False) + b_dv += tl.dot(b_k_2o, b_dh_2o.to(b_k.dtype), allow_tf32=False) + b_dv += b_dh_0o + + b_dk += tl.dot(b_v, tl.trans(b_dh_1o).to(b_k.dtype), allow_tf32=False) + + if i_v == 0: + b_dk += dq_1o + + b_dk_2o = tl.dot(b_dh_2o.to(b_k.dtype), tl.trans(b_v), allow_tf32=False) + if i_v == 0: + b_dk_2o += dq_2o + b_dk_2o = tl.reshape(b_dk_2o, [BK, BK, BT]) + b_k_fp32 = tl.trans(b_k.to(tl.float32)) + b_dk2 = tl.sum(b_dk_2o * b_k_fp32[:, None, :], axis=0) + b_dk2 += tl.sum(b_dk_2o * b_k_fp32[None, :, :], axis=1) + b_dk += tl.trans(b_dk2) + + # hidden state update + b_dh_0o += tl.sum(b_do, axis=0) + b_dh_1o = b_dh_1o + tl.dot(b_q, b_do, allow_tf32=False) + b_q_2o = b_q[None, :, :] * b_q[:, None, :] + b_q_2o = tl.reshape(b_q_2o, [BK * BK, BT]).to(b_k.dtype) + b_dh_2o = b_dh_2o + tl.dot(b_q_2o, b_do, allow_tf32=False) * 0.5 + + if i_v == 0: + dq_1o += (tl.sum(b_dz[None, :] * b_q, axis=1))[None, :] + dq_2o += (tl.sum(b_dz[None, :] * b_q_2o, axis=1) * 0.5)[:, None] + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +class FusedChunkBasedFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, scale=1): + B, H, T, K, V = *k.shape, v.shape[-1] + + scale = scale + BT = 16 + BK, BV = min(K, 16), min(V, 32) + BK, BV = max(BK, 16), max(BV, 16) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + + num_warps = 4 + + # the norm of o might explode, so we need to use float32 here + o = q.new_empty(NK, B, H, T, V, dtype=torch.float32) + z = q.new_empty(NK, B, H, T, dtype=torch.float32) + + grid = (NV, NK, B * H) + fused_chunk_based_fwd_kernel[grid]( + q, k, v, o, z, + scale, + T=T, B=B, H=H, K=K, V=V, BT=BT, BK=BK, BV=BV, + num_warps=num_warps, + ) + o = o.sum(0) + z = z.sum(0) + ctx.save_for_backward(q, k, v) + ctx.scale = scale + return o.to(q.dtype), z.to(z.dtype) + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dz): + q, k, v = ctx.saved_tensors + B, H, T, K, V = *k.shape, v.shape[-1] + scale = ctx.scale + + BT = 16 + BK, BV = min(K, 16), min(V, 32) + BK, BV = max(BK, 16), max(BV, 16) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + num_stages = 1 + num_warps = 4 + + dq = q.new_empty(NV, B, H, T, K) + dk = q.new_empty(NV, B, H, T, K) + dv = q.new_empty(NK, B, H, T, V) + grid = (NV, NK, B * H) + + fused_chunk_based_bwd_kernel[grid]( + q, k, v, do, dz, dq, dk, dv, + scale, + T=T, B=B, H=H, K=K, V=V, BT=BT, BK=BK, BV=BV, + num_warps=num_warps, + num_stages=num_stages, + ) + dq = dq.sum(0) + dk = dk.sum(0) + dv = dv.sum(0) + return dq.to(q.dtype), dk.to(k.dtype), dv.to(v.dtype), None + + +def fused_chunk_based( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + use_norm: bool = True, + head_first: bool = False, +): + assert q.shape[-1] <= 16, 'only support feature dimension up to 16.' + if scale is None: + scale = q.shape[-1] ** -0.5 + if not head_first: + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + o, z = FusedChunkBasedFunction.apply(q, k, v, scale) + if use_norm: + o = o / (z[..., None] + 1e-6) + if not head_first: + o = o.transpose(1, 2) + return o.to(q.dtype) diff --git a/code/flash-linear-attention/fla/ops/based/naive.py b/code/flash-linear-attention/fla/ops/based/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..cdc8f4ffae572050385adada1ceb11101d4e88e1 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/based/naive.py @@ -0,0 +1,70 @@ + + +import torch +from einops import rearrange + + +def naive_parallel_based( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + use_norm: bool = True, +): + if scale is None: + scale = q.shape[-1] ** -0.5 + q = q * scale + attn = q @ k.transpose(-2, -1) + attn = 1 + attn + 1/2 * (attn ** 2) + attn.masked_fill_(~torch.tril(torch.ones( + q.shape[-2], q.shape[-2], dtype=torch.bool, device=q.device)), 0) + o = attn @ v + if use_norm: + z = attn.sum(-1) + return o / (z[..., None] + 1e-6) + else: + return o + + +def naive_chunk_based(q, k, v, chunk_size=256): + q = q * (q.shape[-1] ** -0.5) + # compute normalizer. + k_cumsum = torch.cumsum(k, dim=-2) + kk_cumsum = torch.cumsum(k.unsqueeze(-1) * k.unsqueeze(-2), dim=-3) + # first + z = (q * k_cumsum).sum(-1) + # second order + z += (q.unsqueeze(-1) * q.unsqueeze(-2) * kk_cumsum).sum((-1, -2)) * 0.5 + # zero-th order + z += (torch.arange(0, q.shape[-2]).to(z.device) * 1.0 + 1.0)[None, None, :] + + # compute o + # constant term + _o = v.cumsum(-2) + + q = rearrange(q, 'b h (n c) d -> b h n c d', c=chunk_size) + + k = rearrange(k, 'b h (n c) d -> b h n c d', c=chunk_size) + v = rearrange(v, 'b h (n c) d -> b h n c d', c=chunk_size) + + intra_chunk_attn = q @ k.transpose(-2, -1) + intra_chunk_attn = intra_chunk_attn + 1/2 * (intra_chunk_attn ** 2) + intra_chunk_attn.masked_fill_(~torch.tril(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device)), 0) + o = intra_chunk_attn @ v + + # quadractic term + kv = torch.einsum('b h n c x, b h n c y, b h n c z -> b h n x y z', k, k, v) + kv = kv.cumsum(2) + kv = torch.cat([torch.zeros_like(kv[:, :, :1]), kv[:, :, :-1]], dim=2) + + o += 0.5 * torch.einsum('b h n x y z, b h n c x, b h n c y -> b h n c z', kv, q, q) + + # linear term + kv = torch.einsum('b h n c x, b h n c y -> b h n x y', k, v) + kv = kv.cumsum(2) + kv = torch.cat([torch.zeros_like(kv[:, :, :1]), kv[:, :, :-1]], dim=2) + o += torch.einsum('b h n x y, b h n c x -> b h n c y', kv, q) + + o = rearrange(o, 'b h n c d -> b h (n c) d') + o = o + _o + return o / (z[..., None] + 1e-6) diff --git a/code/flash-linear-attention/fla/ops/based/parallel.py b/code/flash-linear-attention/fla/ops/based/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..71f10f53f8bd45ce33b5c61b087afe6e81e96d37 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/based/parallel.py @@ -0,0 +1,406 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + +# Based: An Educational and Effective Sequence Mixer +# https://hazyresearch.stanford.edu/blog/2023-12-11-zoology2-based + + +@triton.jit(do_not_specialize=['T']) +def parallel_based_fwd_kernel( + q, + k, + v, + o, + z, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + # i_c: chunk index. used for sequence parallelism + i_kv, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + NV = tl.cdiv(V, BV) + i_k = i_kv // (NV) + i_v = i_kv % (NV) + + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_c * BTL, i_k * BK), (BTL, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k * BK, 0), (BK, BTS), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (0, i_v * BV), (BTS, BV), (1, 0)) + + # [BQ, BD] block Q, in the shared memory throughout the whole kernel + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + b_o = tl.zeros([BTL, BV], dtype=tl.float32) + b_z = tl.zeros([BTL], dtype=tl.float32) + + # Q block and K block have no overlap + # no need for mask, thereby saving flops + for _ in range(0, i_c * BTL, BTS): + # [BK, BTS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + + # [BTS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + b_s = tl.dot(b_q, (b_k), allow_tf32=False) + b_s = 1 + b_s + 0.5 * b_s * b_s + b_z += tl.sum(b_s, axis=1) + + # [BQ, BD] + b_o = b_o + tl.dot(b_s.to(b_v.dtype), b_v, allow_tf32=False) + p_k = tl.advance(p_k, (0, BTS)) + p_v = tl.advance(p_v, (BTS, 0)) + + # # rescale interchunk output + tl.debug_barrier() + o_q = tl.arange(0, BTL) + # # sync threads, easy for compiler to optimize + # tl.debug_barrier() + + o_k = tl.arange(0, BTS) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k * BK, i_c * BTL), (BK, BTS), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_c * BTL, i_v * BV), (BTS, BV), (1, 0)) + # Q block and K block have overlap. masks required + for _ in range(i_c * BTL, (i_c + 1) * BTL, BTS): + # [BK, BTS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BTS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + m_s = o_q[:, None] >= o_k[None, :] + b_s = tl.dot(b_q, b_k, allow_tf32=False) + b_s = 1 + b_s + 0.5 * b_s * b_s + b_s = tl.where(m_s, b_s, 0) + b_z += tl.sum(b_s, axis=1) + # [BTL, BV] + b_o += tl.dot(b_s.to(b_q.dtype), b_v, allow_tf32=False) + + p_k = tl.advance(p_k, (0, BTS)) + p_v = tl.advance(p_v, (BTS, 0)) + o_k += BTS + + p_o = tl.make_block_ptr(o + (i_bh + B * H * i_k) * T*V, (T, V), (V, 1), (i_c*BTL, i_v*BV), (BTL, BV), (1, 0)) + p_z = z + (i_bh + B * H * i_k) * T + i_c * BTL + tl.arange(0, BTL) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_z, b_z.to(p_z.dtype.element_ty), mask=((i_c * BTL + tl.arange(0, BTL)) < T)) + + +@triton.jit +def _parallel_based_bwd_dq( + i_bh, + i_c, + i_k, + i_v, + q, + k, + v, + do, + dz, + dq, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, +): + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_c * BTL, i_v * BV), (BTL, BV), (1, 0)) + p_q = tl.make_block_ptr(q + (i_bh) * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) + b_dq = tl.zeros([BTL, BK], dtype=tl.float32) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (0, i_k * BK), (BTS, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (V, T), (1, V), (i_v * BV, 0), (BV, BTS), (0, 1)) + p_dz = dz + i_bh * T + i_c * BTL + tl.arange(0, BTL) + b_dz = tl.load(p_dz, mask=(i_c * BTL + tl.arange(0, BTL)) < T) + + for _ in range(0, i_c * BTL, BTS): + # [BTS, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BTS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + b_ds = tl.dot(b_do, b_v, allow_tf32=False) + if i_v == 0: + b_ds += b_dz[:, None] + else: + b_ds = b_ds + b_s = tl.dot(b_q, tl.trans(b_k), allow_tf32=False) + # [BQ, BD] + b_dq += tl.dot((b_ds * (1 + b_s)).to(b_v.dtype), b_k, allow_tf32=False) + p_k = tl.advance(p_k, (BTS, 0)) + p_v = tl.advance(p_v, (0, BTS)) + + b_dq *= scale + o_q = tl.arange(0, BTL) + o_k = tl.arange(0, BTS) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_c * BTL, i_k * BK), (BTS, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (V, T), (1, V), (i_v * BV, i_c * BTL), (BV, BTS), (0, 1)) + # Q block and K block have overlap. masks required + for _ in range(i_c * BTL, (i_c + 1) * BTL, BTS): + # [BTS, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BTS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + m_s = o_q[:, None] >= o_k[None, :] + b_ds = tl.dot(b_do, b_v, allow_tf32=False) + if i_v == 0: + b_ds += b_dz[:, None] + else: + b_ds = b_ds + b_ds = tl.where(m_s, b_ds, 0) * scale + b_s = tl.dot(b_q, tl.trans(b_k), allow_tf32=False) + b_s = tl.where(m_s, b_s, 0) + # [BTL, BK] + b_dq += tl.dot((b_ds + b_ds * b_s).to(b_k.dtype), b_k, allow_tf32=False) + p_k = tl.advance(p_k, (BTS, 0)) + p_v = tl.advance(p_v, (0, BTS)) + o_k += BTS + p_dq = tl.make_block_ptr(dq + (i_bh + B * H * i_v) * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + return + + +@triton.jit +def _parallel_based_bwd_dkv( + i_bh, + i_c, + i_k, + i_v, + q, + k, + v, + do, + dz, + dk, + dv, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, +): + # compute dk dv + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_c * BTL, i_k * BK), (BTL, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_c * BTL, i_v * BV), (BTL, BV), (1, 0)) + b_k, b_v = tl.load(p_k, boundary_check=(0, 1)), tl.load(p_v, boundary_check=(0, 1)) + b_dk, b_dv = tl.zeros([BTL, BK], dtype=tl.float32), tl.zeros([BTL, BV], dtype=tl.float32) + + for i in range((tl.cdiv(T, BTS) * BTS)-BTS, (i_c + 1) * BTL - BTS, -BTS): + p_q = tl.make_block_ptr(q + i_bh * T*K, (K, T), (1, K), (i_k * BK, i), (BK, BTS), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (V, T), (1, V), (i_v * BV, i), (BV, BTS), (0, 1)) + p_dz = dz + i_bh * T + i + tl.arange(0, BTS) + b_q = tl.load(p_q, boundary_check=(0, 1)) # [BK, BTS] + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) # [BV, BTS] + b_dz = tl.load(p_dz, mask=(i + tl.arange(0, BTS)) < T) + b_s = tl.dot(b_k.to(b_q.dtype), b_q, allow_tf32=False) * scale # [BTL, BTS] + b_s2 = 1 + b_s + 0.5 * b_s * b_s + b_dv += tl.dot(b_s2.to(b_q.dtype), tl.trans(b_do), allow_tf32=False) + b_ds = tl.dot(b_v, b_do, allow_tf32=False) * scale + if i_v == 0: + b_ds += b_dz[None, :] * scale + else: + b_ds = b_ds + b_dk += tl.dot((b_ds + b_ds * b_s).to(b_q.dtype), tl.trans(b_q), allow_tf32=False) + + tl.debug_barrier() + o_q, o_k = tl.arange(0, BTS), tl.arange(0, BTL) + for i in range(i_c*BTL, (i_c+1)*BTL, BTS): + p_q = tl.make_block_ptr(q + i_bh * T*K, (K, T), (1, K), (i_k * BK, i), (BK, BTS), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (V, T), (1, V), (i_v * BV, i), (BV, BTS), (0, 1)) + p_dz = dz + i_bh * T + i + tl.arange(0, BTS) + b_q = tl.load(p_q, boundary_check=(0, 1)) # [BD, BQ] + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) + b_dz = tl.load(p_dz, mask=(i + tl.arange(0, BTS)) < T) + # [BK, BQ] + m_s = o_k[:, None] <= o_q[None, :] + b_s = tl.dot(b_k, b_q, allow_tf32=False) * scale + b_s2 = 1 + b_s + 0.5 * b_s * b_s + b_s = tl.where(m_s, b_s, 0) + b_s2 = tl.where(m_s, b_s2, 0) + + b_ds = tl.dot(b_v, b_do, allow_tf32=False) + if i_v == 0: + b_ds += b_dz[None, :] + else: + b_ds = b_ds + b_ds = tl.where(m_s, b_ds, 0) * scale + # [BK, BD] + b_dv += tl.dot(b_s2.to(b_q.dtype), tl.trans(b_do), allow_tf32=False) + b_dk += tl.dot((b_ds + b_ds * b_s).to(b_q.dtype), tl.trans(b_q), allow_tf32=False) + o_q += BTS + + p_dk = tl.make_block_ptr(dk + (i_bh + B * H * i_v) * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv + (i_bh + B * H * i_k) * T*V, (T, V), (V, 1), (i_c*BTL, i_v*BV), (BTL, BV), (1, 0)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + return + + +@triton.jit(do_not_specialize=['T']) +def parallel_based_bwd_kernel( + q, + k, + v, + do, + dz, + dq, + dk, + dv, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + i_kv, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + NV = tl.cdiv(V, BV) + i_k = i_kv // (NV) + i_v = i_kv % NV + _parallel_based_bwd_dq( + i_bh, i_c, i_k, i_v, + q, k, v, do, dz, dq, + scale, T, B, H, BTL, BTS, BK, BV, K, V, + ) + tl.debug_barrier() + _parallel_based_bwd_dkv( + i_bh, i_c, i_k, i_v, + q, k, v, do, dz, dk, dv, + scale, T, B, H, BTL, BTS, BK, BV, K, V, + ) + + +class ParallelBasedFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, scale): + BTL, BTS = 128, 32 + assert BTL % BTS == 0 + # assert q.shape[-1] % 16 == 0 + BK = min(128, max(triton.next_power_of_2(k.shape[-1]), 16)) + BV = min(128, max(triton.next_power_of_2(v.shape[-1]), 16)) + B, H, T, K, V = *k.shape, v.shape[-1] + num_stages = 2 + num_warps = 4 + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + grid = (NK * NV, triton.cdiv(T, BTL), B * H) + + assert NK == 1, "will encounter some synchronization issue if not." + + o = torch.empty(NK, B, H, T, V, device=q.device) + z = torch.empty(NK, B, H, T, device=q.device) + parallel_based_fwd_kernel[grid]( + q, k, v, o, z, + scale, + B=B, + H=H, + T=T, + K=K, + V=V, + BTL=BTL, + BTS=BTS, + BK=BK, + BV=BV, + num_warps=num_warps, + num_stages=num_stages, + ) + ctx.save_for_backward(q, k, v) + ctx.scale = scale + return o.sum(0).to(q.dtype), z.sum(0).to(q.dtype) + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dz): + q, k, v = ctx.saved_tensors + scale = ctx.scale + BTL, BTS = 64, 32 + assert BTL % BTS == 0 + BK = min(128, max(triton.next_power_of_2(k.shape[-1]), 16)) + BV = min(128, max(triton.next_power_of_2(v.shape[-1]), 16)) + B, H, T, K, V = *k.shape, v.shape[-1] + num_stages = 2 + num_warps = 4 + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + grid = (NK * NV, triton.cdiv(T, BTL), B * H) + + assert NK == 1, "will encounter some synchronization issue if not" + + dq = torch.empty(NV, B, H, T, K, dtype=q.dtype, device=q.device) + dk = torch.empty(NV, B, H, T, K, dtype=q.dtype, device=q.device) + dv = torch.empty(NK, B, H, T, V, dtype=q.dtype, device=q.device) + + parallel_based_bwd_kernel[grid]( + q, k, v, do, dz, dq, dk, dv, + scale, + B=B, + H=H, + T=T, + K=K, + V=V, + BTL=BTL, + BTS=BTS, + BK=BK, + BV=BV, + num_warps=num_warps, + num_stages=num_stages, + ) + + return dq.sum(0).to(q.dtype), dk.sum(0).to(k.dtype), dv.sum(0).to(v.dtype), None + + +triton_parallel_based = ParallelBasedFunction.apply + + +def parallel_based( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + use_norm: bool = True, + head_first: bool = False, +): + assert q.shape[-1] <= 128, "only support feature dim up to 128" + if scale is None: + scale = q.shape[-1] ** -0.5 + if not head_first: + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + o, z = triton_parallel_based(q, k, v, scale) + if use_norm: + o = o / (z[..., None] + 1e-6) + if not head_first: + o = o.transpose(1, 2) + return o.to(q.dtype) diff --git a/code/flash-linear-attention/fla/ops/comba/__init__.py b/code/flash-linear-attention/fla/ops/comba/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ccb549b0a95201fab583f43424295745f61bc5a1 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/comba/__init__.py @@ -0,0 +1,7 @@ +from .chunk import chunk_comba +from .fused_recurrent import fused_recurrent_comba + +__all__ = [ + "chunk_comba", + "fused_recurrent_comba", +] diff --git a/code/flash-linear-attention/fla/ops/comba/chunk.py b/code/flash-linear-attention/fla/ops/comba/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..f1542a233115f558fad3dee80db790be8868472e --- /dev/null +++ b/code/flash-linear-attention/fla/ops/comba/chunk.py @@ -0,0 +1,340 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.comba.utils import chunk_comba_cumsum_scalar_bwd, chunk_comba_cumsum_scalar_fwd +from fla.ops.comba.wy_fast import chunk_scaled_dot_comba_pkt_fwd, prepare_wy_repr_bwd, recompute_w_u_fwd +from fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_bwd_dhu, chunk_gated_delta_rule_fwd_h +from fla.ops.common.chunk_o import chunk_bwd_dqkwg, chunk_bwd_dv_local, chunk_fwd_o +from fla.ops.utils import chunk_local_cumsum, solve_tril +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_comba_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + p: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, +): + g0, g = chunk_comba_cumsum_scalar_fwd(g, chunk_size=64, cu_seqlens=cu_seqlens) + # obtain WY representation. u is actually the new v. + A = chunk_scaled_dot_comba_pkt_fwd( + k=k, + p=p, + beta=beta, + g0=g0, + g=g, + cu_seqlens=cu_seqlens, + output_dtype=torch.float32, + ) + A = solve_tril( + A=A, + cu_seqlens=cu_seqlens, + output_dtype=k.dtype, + ) + w, u = recompute_w_u_fwd( + k=p, + v=v, + beta=beta, + A=A, + g_cumsum=g0, + cu_seqlens=cu_seqlens, + ) + h, v_new, final_state = chunk_gated_delta_rule_fwd_h( + k=k, + w=w, + u=u, + g=g, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + o = chunk_fwd_o( + q=q, + k=k, + v=v_new, + h=h, + g=g, + scale=scale, + cu_seqlens=cu_seqlens, + ) + return g0, g, o, A, final_state + + +def chunk_comba_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + p: torch.Tensor, + g0: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, +): + w, u = recompute_w_u_fwd( + k=p, + v=v, + beta=beta, + A=A, + g_cumsum=g0, + cu_seqlens=cu_seqlens, + ) + h, v_new, _ = chunk_gated_delta_rule_fwd_h( + k=k, + w=w, + u=u, + g=g, + initial_state=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + ) + dv = chunk_bwd_dv_local( + q=q, + k=k, + g=g, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dh, dh0, dv = chunk_gated_delta_rule_bwd_dhu( + q=q, + k=k, + w=w, + g=g, + h0=initial_state, + dht=dht, + do=do, + dv=dv, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dq, dk, dw, dg = chunk_bwd_dqkwg( + q=q, + k=k, + v=v_new, + w=w, + g=g, + h=h, + dv=dv, + do=do, + dh=dh, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dk2, dv, dp, db, dg0, dg2 = prepare_wy_repr_bwd( + k=k, + v=v, + p=p, + beta=beta, + g0=g0, + g=g, + A=A, + dw=dw, + du=dv, + cu_seqlens=cu_seqlens, + ) + dk.add_(dk2) + dg.add_(dg2) + assert dg.dtype == torch.float32, "dg should be fp32" + dg = chunk_local_cumsum(dg, chunk_size=64, reverse=True, cu_seqlens=cu_seqlens) + # dg0 = d(g_cumsum - g) + dg += chunk_comba_cumsum_scalar_bwd(dg0, chunk_size=64, cu_seqlens=cu_seqlens) + return dq, dk, dv, dp, db, dg, dh0 + + +class ChunkCombaFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + p: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q) + k, k_rstd = l2norm_fwd(k) + p, p_rstd = l2norm_fwd(p) + else: + q_rstd, k_rstd, p_rstd = None, None, None + + g0, g, o, A, final_state = chunk_comba_fwd( + q=q, + k=k, + v=v, + p=p, + g=g, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, q_rstd, k, k_rstd, p, p_rstd, v, g0, g, beta, A, initial_state, cu_seqlens) + ctx.scale = scale + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + q, q_rstd, k, k_rstd, p, p_rstd, v, g0, g, beta, A, initial_state, cu_seqlens = ctx.saved_tensors + dq, dk, dv, dp, db, dg, dh0 = chunk_comba_bwd( + q=q, + k=k, + v=v, + p=p, + g0=g0, + g=g, + beta=beta, + A=A, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + ) + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + dp = l2norm_bwd(p, p_rstd, dp) + return dq.to(q), dk.to(k), dv.to(v), dp.to(p), dg.to(g), db.to(beta), None, dh0, None, None, None + + +@torch.compiler.disable +def chunk_comba( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + p: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + p (torch.Tensor): + auxiliary keys of shape `[B, T, H, K]`. + g (torch.Tensor): + (forget) gating tensor (in log space!) of shape `[B, T, H]`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. + scale (Optional[int]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (bool): + Whether to apply L2norm to the q/k tensor internally. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.comba import chunk_comba + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') + >>> b = torch.rand(H, dtype=torch.bfloat16, device='cuda').sigmoid() + >>> p = k * b[:, None] + >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid() + >>> g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda') + >>> o, ht = chunk_comba( + q, k, v, p, g, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = chunk_comba( + q, k, v, p, g, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if p is None: + p = k + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = ChunkCombaFunction.apply( + q, + k, + v, + p, + g, + beta, + scale, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/comba/fused_recurrent.py b/code/flash-linear-attention/fla/ops/comba/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..b7c4265fa1ef45210bbb4f1926aa5b78ec7bd037 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/comba/fused_recurrent.py @@ -0,0 +1,330 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import input_guard + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_comba_fwd_kernel( + q, + k, + p, + v, + g, + beta, + o, + h0, + ht, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, # whether to use initial state + STORE_FINAL_STATE: tl.constexpr, # whether to store final state + IS_BETA_HEADWISE: tl.constexpr, # whether beta is headwise vector or scalar, + USE_QK_L2NORM_IN_KERNEL: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_hv = i_nh // HV, i_nh % HV + i_h = i_hv // (HV // H) + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + all = B * T + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + + p_q = q + (bos * H + i_h) * K + o_k + p_k = k + (bos * H + i_h) * K + o_k + p_v = v + (bos * HV + i_hv) * V + o_v + p_p = p + (bos * H + i_h) * K + o_k + if IS_BETA_HEADWISE: + p_beta = beta + (bos * HV + i_hv) * V + o_v + else: + p_beta = beta + bos * HV + i_hv + p_g = g + bos * HV + i_hv + p_o = o + ((i_k * all + bos) * HV + i_hv) * V + o_v + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_p = tl.load(p_p, mask=mask_k, other=0).to(tl.float32) + b_g = tl.load(p_g).to(tl.float32) + + if USE_QK_L2NORM_IN_KERNEL: + b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6) + b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6) + b_p = b_p / tl.sqrt(tl.sum(b_p * b_p) + 1e-6) + b_q = b_q * scale + # [BV] + b_v -= tl.sum(b_h * b_p[:, None], 0) + # [BK, BV] + b_h *= exp(b_g) + if IS_BETA_HEADWISE: + b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32) + else: + b_beta = tl.load(p_beta).to(tl.float32) + b_v *= b_beta + # [BK, BV] + b_h += b_k[:, None] * b_v[None, :] + # [BV] + b_o = tl.sum(b_h * b_q[:, None], 0) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + + p_q += H*K + p_k += H*K + p_o += HV*V + p_v += HV*V + p_p += H*K + p_g += HV + p_beta += HV * (V if IS_BETA_HEADWISE else 1) + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +def fused_recurrent_comba_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + p: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HV = v.shape[2] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 8) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + assert NK == 1, "NK > 1 is not supported yet" + num_stages = 3 + num_warps = 1 + + o = q.new_empty(NK, *v.shape) + if output_final_state: + final_state = q.new_empty(N, HV, K, V, dtype=torch.float32) + else: + final_state = None + + grid = (NK, NV, N * HV) + fused_recurrent_comba_fwd_kernel[grid]( + q=q, + k=k, + p=p, + v=v, + g=g, + beta=beta, + o=o, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + scale=scale, + T=T, + B=B, + H=H, + HV=HV, + K=K, + V=V, + BK=BK, + BV=BV, + IS_BETA_HEADWISE=beta.ndim == v.ndim, + USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel, + num_warps=num_warps, + num_stages=num_stages, + ) + o = o.squeeze(0) + return o, final_state + + +class FusedRecurrentCombaFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + p: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + o, final_state = fused_recurrent_comba_fwd( + q=q, + k=k, + p=p, + v=v, + g=g, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + cu_seqlens=cu_seqlens, + ) + + return o, final_state + + @staticmethod + @input_guard + def backward(ctx, do, dht): + raise NotImplementedError( + "Backward pass is not implemented yet and we do not have plans to implement it " + "because we haven't figured out how to compute dg without materializing the full " + "hidden states for all time steps.", + ) + + +def fused_recurrent_comba( + q: torch.Tensor, + k: torch.Tensor, + p: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + p (torch.Tensor): + auxiliary keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, HV, V]`. + GVA is applied if `HV > H`. + g (torch.Tensor): + g (decays) of shape `[B, T, HV]`. + beta (torch.Tensor): + betas of shape `[B, T, HV]`. + scale (Optional[int]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, HV, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, HV, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (Optional[bool]): + Whether to use qk l2norm within the kernel for saving GPU memory. + Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HV, V]`. + final_state (torch.Tensor): + Final state of shape `[N, HV, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.comba import fused_recurrent_comba + # inputs with equal lengths + >>> B, T, H, HV, K, V = 4, 2048, 4, 8, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, HV, V, device='cuda') + >>> b = torch.rand(H, dtype=torch.bfloat16, device='cuda').sigmoid() + >>> p = k * b[:, None] + >>> g = F.logsigmoid(torch.rand(B, T, HV, device='cuda')) + >>> beta = torch.rand(B, T, HV, device='cuda').sigmoid() + >>> h0 = torch.randn(B, HV, K, V, device='cuda') + >>> o, ht = fused_recurrent_comba( + q, k, v, p, g, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, p, g, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, p, g, beta)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = fused_recurrent_comba( + q, k, p, v, g, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + if beta is None: + beta = torch.ones_like(q[..., 0]) + if p is None: + p = k + o, final_state = FusedRecurrentCombaFunction.apply( + q, + k, + p, + v, + g, + beta, + scale, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/comba/utils.py b/code/flash-linear-attention/fla/ops/comba/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..497cc8a648c46b3be992015bba35a85e3348fe68 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/comba/utils.py @@ -0,0 +1,174 @@ + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.index import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['B', 'H', 'BT', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_comba_cumsum_scalar_fwd_kernel( + g, + g0, + g1, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + IS_VARLEN: tl.constexpr, + HEAD_FIRST: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if HEAD_FIRST: + p_g = tl.make_block_ptr(g + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + p_g0 = tl.make_block_ptr(g0 + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + p_g1 = tl.make_block_ptr(g1 + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + else: + p_g = tl.make_block_ptr(g + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_g0 = tl.make_block_ptr(g0 + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_g1 = tl.make_block_ptr(g1 + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + # [BT] + b_g = tl.load(p_g, boundary_check=(0,)).to(tl.float32) + b_g1 = tl.cumsum(b_g, axis=0) + b_g0 = b_g1 - b_g + tl.store(p_g0, b_g0.to(p_g0.dtype.element_ty), boundary_check=(0,)) + tl.store(p_g1, b_g1.to(p_g1.dtype.element_ty), boundary_check=(0,)) + + +def chunk_comba_cumsum_scalar_fwd( + g: torch.Tensor, + chunk_size: int, + cu_seqlens: torch.Tensor | None = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + if head_first: + B, H, T = g.shape + else: + B, T, H = g.shape + assert chunk_size == 2**(chunk_size.bit_length()-1), "chunk_size must be a power of 2" + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + g0, g1 = torch.empty_like(g, dtype=output_dtype or g.dtype), torch.empty_like(g, dtype=output_dtype or g.dtype) + grid = (NT, B * H) + chunk_comba_cumsum_scalar_fwd_kernel[grid]( + g, + g0, + g1, + cu_seqlens, + chunk_indices, + T=T, + B=B, + H=H, + BT=BT, + HEAD_FIRST=head_first, + ) + return g0, g1 + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['B', 'H', 'BT', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_comba_cumsum_scalar_bwd_kernel( + dg0, + dgr, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + IS_VARLEN: tl.constexpr, + HEAD_FIRST: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if HEAD_FIRST: + p_dg0 = tl.make_block_ptr(dg0 + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + p_dgr = tl.make_block_ptr(dgr + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + else: + p_dg0 = tl.make_block_ptr(dg0 + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_dgr = tl.make_block_ptr(dgr + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + # [BT] + """ + b_dg: 1,2,3,4 + b_dg0: 0,1,2,3 + b_temp: 0,1,3,6 + b_dz: 6 + b_dgr: 6,5,3,0 + """ + b_dg0 = tl.load(p_dg0, boundary_check=(0,)).to(tl.float32) + b_temp = tl.cumsum(b_dg0, axis=0) + b_dz = tl.sum(b_dg0, axis=0) + b_dgr = -b_temp + b_dz[None] + tl.store(p_dgr, b_dgr.to(p_dgr.dtype.element_ty), boundary_check=(0,)) + + +def chunk_comba_cumsum_scalar_bwd( + dg0: torch.Tensor, + chunk_size: int, + cu_seqlens: torch.Tensor | None = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + if head_first: + B, H, T = dg0.shape + else: + B, T, H = dg0.shape + assert chunk_size == 2**(chunk_size.bit_length()-1), "chunk_size must be a power of 2" + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + dg = torch.empty_like(dg0, dtype=output_dtype or dg0.dtype) + grid = (NT, B * H) + chunk_comba_cumsum_scalar_bwd_kernel[grid]( + dg0, + dg, + cu_seqlens, + chunk_indices, + T=T, + B=B, + H=H, + BT=BT, + HEAD_FIRST=head_first, + ) + return dg diff --git a/code/flash-linear-attention/fla/ops/comba/wy_fast.py b/code/flash-linear-attention/fla/ops/comba/wy_fast.py new file mode 100644 index 0000000000000000000000000000000000000000..6f339d02c4cf6a4ba78181cb065baf345bae5931 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/comba/wy_fast.py @@ -0,0 +1,424 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_G': lambda args: args['g'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'BT', 'IS_VARLEN', 'USE_G'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_scaled_dot_comba_pkt_fwd_kernel( + k, + p, + beta, + g0, + g, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_G: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + p_beta = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_beta = tl.load(p_beta, boundary_check=(0,)) + + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_p = tl.make_block_ptr(p + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_p = tl.load(p_p, boundary_check=(0, 1)) + b_pb = b_p * b_beta[:, None] + b_A += tl.dot(b_pb.to(b_k.dtype), tl.trans(b_k)) + + if USE_G: + p_g0 = tl.make_block_ptr(g0 + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_g = tl.make_block_ptr(g + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g0 = tl.load(p_g0, boundary_check=(0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_A = b_A * exp(b_g0[:, None] - b_g[None, :]) + + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_A = tl.where(m_A, b_A, 0) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (BT*H, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_scaled_dot_comba_pkt_fwd( + k: torch.Tensor, + p: torch.Tensor, + beta: torch.Tensor, + g0: torch.Tensor | None = None, + g: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + output_dtype: torch.dtype = torch.float32, +) -> torch.Tensor: + r""" + Compute beta \mathcal{A}(i-1/j) * P * K^T. + + Args: + k (torch.Tensor): + The key tensor of shape `[B, T, H, K]`. + p (torch.Tensor): + The auxiliary key tensor of shape `[B, T, H, K]`. + beta (torch.Tensor): + The beta tensor of shape `[B, T, H]`. + g0 (torch.Tensor): + The cumulative sum minus the original one of the gate tensor of shape `[B, T, H]`. + Default: None + g (torch.Tensor): + The cumulative sum of the gate tensor of shape `[B, T, H]`. + Default: None + cu_seqlens (torch.LongTensor): + The cumulative sequence lengths of the input tensor. + Default: None + chunk_size (int): + The chunk size. Default: 64. + output_dtype (torch.dtype): + The dtype of the output tensor. Default: `torch.float32` + + Returns: + beta * K * K^T of shape `[B, T, H, BT]` where `BT` is the chunk size. + """ + B, T, H, K = k.shape + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + A = torch.empty(B, T, H, BT, device=k.device, dtype=output_dtype) + chunk_scaled_dot_comba_pkt_fwd_kernel[(NT, B * H)]( + k=k, + p=p, + beta=beta, + g0=g0, + g=g, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + ) + return A + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_bwd_kernel( + k, + v, + p, + beta, + g0, + g, + A, + dw, + du, + dk, + dv, + dp, + dbeta, + dg0, + dg, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_beta = tl.make_block_ptr(beta + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_g0 = tl.make_block_ptr(g0 + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_g = tl.make_block_ptr(g + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_beta = tl.load(p_beta, boundary_check=(0,)) + b_g0 = tl.load(p_g0, boundary_check=(0,)) + b_g0_exp = tl.exp(b_g0) + b_g = tl.load(p_g, boundary_check=(0,)) + + b_dbeta = tl.zeros([BT], dtype=tl.float32) + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + b_dg0 = tl.zeros([BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_p = tl.make_block_ptr(p + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dp = tl.make_block_ptr(dp + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dw = tl.make_block_ptr(dw + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_p = tl.load(p_p, boundary_check=(0, 1)) + b_p_beta_g0 = (b_p * b_beta[:, None] * b_g0_exp[:, None]).to(b_p.dtype) + b_dw = tl.load(p_dw, boundary_check=(0, 1)) + b_dA += tl.dot(b_dw, tl.trans(b_p_beta_g0)) + b_dp_beta_g0 = tl.dot(b_A, b_dw) + b_dp = b_dp_beta_g0 * b_beta[:, None] * b_g0_exp[:, None] + b_dbeta += tl.sum(b_dp_beta_g0 * b_p * b_g0_exp[:, None], 1) + b_dg0 += tl.sum(b_dp * b_p, 1) + tl.store(p_dp, b_dp.to(p_dp.dtype.element_ty), boundary_check=(0, 1)) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_du = tl.make_block_ptr(du + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v_beta = (b_v * b_beta[:, None]).to(b_v.dtype) + b_du = tl.load(p_du, boundary_check=(0, 1)) + b_dA += tl.dot(b_du, tl.trans(b_v_beta)) + b_dv_beta = tl.dot(b_A, b_du) + b_dv = b_dv_beta * b_beta[:, None] + b_dbeta += tl.sum(b_dv_beta * b_v, 1) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_dA = tl.where(m_A, b_dA, 0) + b_dA = tl.dot(b_dA.to(b_A.dtype), b_A) + b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) + b_dA = tl.where(m_A, -b_dA * exp(b_g0[:, None] - b_g[None, :]), 0).to(k.dtype.element_ty) + b_dA = b_dA.to(k.dtype.element_ty) + b_A = tl.zeros([BT, BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_p = tl.make_block_ptr(p + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dp = tl.make_block_ptr(dp + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_p = tl.load(p_p, boundary_check=(0, 1)) + b_dp = tl.load(p_dp, boundary_check=(0, 1)) + b_p_beta = (b_p * b_beta[:, None]).to(b_p.dtype) + b_A += tl.dot(b_p_beta, tl.trans(b_k)) + b_dp_beta = tl.dot(b_dA, b_k) + b_dbeta += tl.sum(b_dp_beta * b_p, 1) + b_dk = tl.dot(tl.trans(b_dA), b_p_beta) + b_dp += b_dp_beta * b_beta[:, None] + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dp, b_dp.to(p_dp.dtype.element_ty), boundary_check=(0, 1)) + + b_dA_A = b_dA * b_A + b_dg0 += tl.sum(b_dA_A, axis=1) + b_dg = - tl.sum(b_dA_A, axis=0) + p_dg = tl.make_block_ptr(dg + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_dg0 = tl.make_block_ptr(dg0 + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_dbeta = tl.make_block_ptr(dbeta + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + tl.store(p_dg0, b_dg0.to(p_dg0.dtype.element_ty), boundary_check=(0,)) + tl.store(p_dbeta, b_dbeta.to(p_dbeta.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def recompute_w_u_fwd_kernel( + k, + v, + beta, + w, + u, + A, + g, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + p_beta = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_g = tl.make_block_ptr(g + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_beta = tl.load(p_beta, boundary_check=(0,)) + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_g = tl.exp(tl.load(p_g, boundary_check=(0,))) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_u = tl.make_block_ptr(u + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_vb = (b_v * b_beta[:, None]).to(b_v.dtype) + b_u = tl.dot(b_A, b_vb, allow_tf32=False) + tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1)) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_w = tl.make_block_ptr(w + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_kb = (b_k * b_beta[:, None] * b_g[:, None]).to(b_k.dtype) + b_w = tl.dot(b_A, b_kb) + tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1)) + + +def recompute_w_u_fwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + g_cumsum: torch.Tensor, + A: torch.Tensor, + cu_seqlens: torch.LongTensor | None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = A.shape[-1] + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BK = 64 + BV = 64 + + u = torch.empty_like(v) + w = torch.empty_like(k) + recompute_w_u_fwd_kernel[(NT, B*H)]( + k=k, + v=v, + beta=beta, + w=w, + u=u, + A=A, + g=g_cumsum, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return w, u + + +def prepare_wy_repr_bwd( + k: torch.Tensor, + v: torch.Tensor, + p: torch.Tensor, + g0: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + dw: torch.Tensor, + du: torch.Tensor, + cu_seqlens: torch.LongTensor | None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = 64 + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + + dk = torch.empty_like(k) + dv = torch.empty_like(v) + dp = torch.empty_like(p) + dbeta = torch.empty_like(beta) + dg0 = torch.empty_like(g0) + dg = torch.empty_like(g) + prepare_wy_repr_bwd_kernel[(NT, B * H)]( + k=k, + v=v, + p=p, + beta=beta, + g0=g0, + g=g, + A=A, + dw=dw, + du=du, + dk=dk, + dv=dv, + dp=dp, + dbeta=dbeta, + dg0=dg0, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dk, dv, dp, dbeta, dg0, dg diff --git a/code/flash-linear-attention/fla/ops/common/__init__.py b/code/flash-linear-attention/fla/ops/common/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/code/flash-linear-attention/fla/ops/common/chunk_delta_h.py b/code/flash-linear-attention/fla/ops/common/chunk_delta_h.py new file mode 100644 index 0000000000000000000000000000000000000000..d440e4de23b82b816f7061b833cde7071765121f --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/chunk_delta_h.py @@ -0,0 +1,533 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_nvidia_hopper, use_cuda_graph + +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8, 16] + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_GK': lambda args: args['gk'] is not None, + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'SAVE_NEW_VALUE': lambda args: args['v_new'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + for BV in [32, 64] + ], + key=['H', 'K', 'V', 'BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gated_delta_rule_fwd_kernel_h_blockdim64( + k, + v, + w, + v_new, + g, + gk, + h, + h0, + ht, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + SAVE_NEW_VALUE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_h1 = tl.zeros([64, BV], dtype=tl.float32) + if K > 64: + b_h2 = tl.zeros([64, BV], dtype=tl.float32) + if K > 128: + b_h3 = tl.zeros([64, BV], dtype=tl.float32) + if K > 192: + b_h4 = tl.zeros([64, BV], dtype=tl.float32) + + # calculate offset + h += ((boh * H + i_h) * K*V).to(tl.int64) + v += ((bos * H + i_h) * V).to(tl.int64) + k += ((bos * H + i_h) * K).to(tl.int64) + w += ((bos * H + i_h) * K).to(tl.int64) + if SAVE_NEW_VALUE: + v_new += ((bos * H + i_h) * V).to(tl.int64) + stride_v = H*V + stride_h = H*K*V + stride_k = H*K + if USE_INITIAL_STATE: + h0 = h0 + i_nh * K*V + if STORE_FINAL_STATE: + ht = ht + i_nh * K*V + + # load initial state + if USE_INITIAL_STATE: + p_h0_1 = tl.make_block_ptr(h0, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32) + if K > 64: + p_h0_2 = tl.make_block_ptr(h0, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32) + if K > 128: + p_h0_3 = tl.make_block_ptr(h0, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32) + if K > 192: + p_h0_4 = tl.make_block_ptr(h0, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32) + + # main recurrence + for i_t in range(NT): + p_h1 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_h2 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_h3 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h3, b_h3.to(p_h3.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_h4 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h4, b_h4.to(p_h4.dtype.element_ty), boundary_check=(0, 1)) + + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 0), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v = tl.dot(b_w, b_h1.to(b_w.dtype)) + if K > 64: + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v += tl.dot(b_w, b_h2.to(b_w.dtype)) + if K > 128: + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v += tl.dot(b_w, b_h3.to(b_w.dtype)) + if K > 192: + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v += tl.dot(b_w, b_h4.to(b_w.dtype)) + p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) - b_v + + if SAVE_NEW_VALUE: + p_v = tl.make_block_ptr(v_new, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + tl.store(p_v, b_v.to(p_v.dtype.element_ty), boundary_check=(0, 1)) + + last_idx = min((i_t + 1) * BT, T) - 1 + if USE_G: + m_t = (i_t * BT + tl.arange(0, BT)) < T + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_v = b_v * tl.where(m_t, exp(b_g_last - b_g), 0)[:, None] + b_g_last = exp(b_g_last) + b_h1 *= b_g_last + if K > 64: + b_h2 *= b_g_last + if K > 128: + b_h3 *= b_g_last + if K > 192: + b_h4 *= b_g_last + + if USE_GK: + o_k1 = tl.arange(0, 64) + b_gk_last1 = tl.load(gk + (bos + last_idx) * H*K + i_h * K + o_k1, mask=(o_k1 < K), other=0.) + b_h1 *= exp(b_gk_last1)[:, None] + if K > 64: + o_k2 = 64 + o_k1 + b_gk_last2 = tl.load(gk + (bos + last_idx) * H*K + i_h * K + o_k2, mask=(o_k2 < K), other=0.) + b_h2 *= exp(b_gk_last2)[:, None] + if K > 128: + o_k3 = 128 + o_k1 + b_gk_last3 = tl.load(gk + (bos + last_idx) * H*K + i_h * K + o_k3, mask=(o_k3 < K), other=0.) + b_h3 *= exp(b_gk_last3)[:, None] + if K > 192: + o_k4 = 192 + o_k1 + b_gk_last4 = tl.load(gk + (bos + last_idx) * H*K + i_h * K + o_k4, mask=(o_k4 < K), other=0.) + b_h4 *= exp(b_gk_last4)[:, None] + b_v = b_v.to(k.dtype.element_ty) + + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h1 += tl.dot(b_k, b_v) + if K > 64: + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h2 += tl.dot(b_k, b_v) + if K > 128: + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h3 += tl.dot(b_k, b_v) + if K > 192: + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h4 += tl.dot(b_k, b_v) + # epilogue + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h2.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h3.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h4.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_GK': lambda args: args['gk'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in ([4, 3, 2] if check_shared_mem('ampere') else [1]) + for BV in [64, 32] + ], + key=['H', 'K', 'V', 'BT', 'BV', 'USE_G'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gated_delta_rule_bwd_kernel_dhu_blockdim64( + q, + k, + w, + g, + gk, + dht, + dh0, + do, + dh, + dv, + dv2, + cu_seqlens, + chunk_offsets, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh1 = tl.zeros([64, BV], dtype=tl.float32) + if K > 64: + b_dh2 = tl.zeros([64, BV], dtype=tl.float32) + if K > 128: + b_dh3 = tl.zeros([64, BV], dtype=tl.float32) + if K > 192: + b_dh4 = tl.zeros([64, BV], dtype=tl.float32) + + # calculate offset + q += ((bos * H + i_h) * K).to(tl.int64) + k += ((bos * H + i_h) * K).to(tl.int64) + w += ((bos * H + i_h) * K).to(tl.int64) + do += ((bos * H + i_h) * V).to(tl.int64) + dv += ((bos * H + i_h) * V).to(tl.int64) + dv2 += ((bos * H + i_h) * V).to(tl.int64) + dh += ((boh * H + i_h) * K*V).to(tl.int64) + if USE_GK: + gk += ((bos * H + i_h) * K).to(tl.int64) + + stride_v = H*V + stride_h = H*K*V + stride_k = H*K + if USE_INITIAL_STATE: + dh0 += i_nh * K*V + if USE_FINAL_STATE_GRADIENT: + dht += i_nh * K*V + + if USE_FINAL_STATE_GRADIENT: + p_dht1 = tl.make_block_ptr(dht, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + b_dh1 += tl.load(p_dht1, boundary_check=(0, 1)) + if K > 64: + p_dht2 = tl.make_block_ptr(dht, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + b_dh2 += tl.load(p_dht2, boundary_check=(0, 1)) + if K > 128: + p_dht3 = tl.make_block_ptr(dht, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + b_dh3 += tl.load(p_dht3, boundary_check=(0, 1)) + if K > 192: + p_dht4 = tl.make_block_ptr(dht, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + b_dh4 += tl.load(p_dht4, boundary_check=(0, 1)) + + for i_t in range(NT - 1, -1, -1): + p_dh1 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh1, b_dh1.to(p_dh1.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_dh2 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh2, b_dh2.to(p_dh2.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_dh3 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh3, b_dh3.to(p_dh3.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_dh4 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh4, b_dh4.to(p_dh4.dtype.element_ty), boundary_check=(0, 1)) + + last_idx = min((i_t + 1) * BT, T) - 1 + if USE_G: + bg_last = tl.load(g + (bos + last_idx) * H + i_h) + bg_last_exp = exp(bg_last) + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_exp = exp(b_g) + + p_dv = tl.make_block_ptr(dv, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv2 = tl.make_block_ptr(dv2, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + b_do = tl.load(p_do, boundary_check=(0, 1)) + + # Update dv + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 0), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + if USE_GK: + o_k1 = tl.arange(0, 64) + b_gk_last1 = tl.load(gk + last_idx * H*K + o_k1, mask=(o_k1 < K), other=0.) + b_dv = tl.dot(b_k, b_dh1.to(b_k.dtype)) + + if K > 64: + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + if USE_GK: + o_k2 = 64 + o_k1 + b_gk_last2 = tl.load(gk + last_idx * H*K + o_k2, mask=(o_k2 < K), other=0.) + b_dv += tl.dot(b_k, b_dh2.to(b_k.dtype)) + + if K > 128: + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + if USE_GK: + o_k3 = 128 + o_k1 + b_gk_last3 = tl.load(gk + last_idx * H*K + o_k3, mask=(o_k3 < K), other=0.) + b_dv += tl.dot(b_k, b_dh3.to(b_k.dtype)) + + if K > 192: + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + if USE_GK: + o_k4 = 192 + o_k1 + b_gk_last4 = tl.load(gk + last_idx * H*K + o_k4, mask=(o_k4 < K), other=0.) + b_dv += tl.dot(b_k, b_dh4.to(b_k.dtype)) + + if USE_G: + m_t = (i_t * BT + tl.arange(0, BT)) < T + b_dv *= tl.where(m_t, exp(bg_last - b_g), 0)[:, None] + b_dv += tl.load(p_dv, boundary_check=(0, 1)) + + tl.store(p_dv2, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + # Update dh + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)) + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + if USE_G: + b_dh1 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + b_dh1 *= exp(b_gk_last1[:, None]) + b_dh1 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 64: + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + if USE_G: + b_dh2 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + b_dh2 *= exp(b_gk_last2[:, None]) + b_dh2 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 128: + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + if USE_G: + b_dh3 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + b_dh3 *= exp(b_gk_last3[:, None]) + b_dh3 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 192: + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + if USE_G: + b_dh4 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + b_dh4 *= exp(b_gk_last4[:, None]) + b_dh4 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + + if USE_INITIAL_STATE: + p_dh0 = tl.make_block_ptr(dh0, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh0, b_dh1.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_dh1 = tl.make_block_ptr(dh0, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh1, b_dh2.to(p_dh1.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_dh2 = tl.make_block_ptr(dh0, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh2, b_dh3.to(p_dh2.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_dh3 = tl.make_block_ptr(dh0, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh3, b_dh4.to(p_dh3.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_gated_delta_rule_fwd_h( + k: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + chunk_size: int = 64, # SY: remove this argument and force chunk size 64? + save_new_value: bool = True, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, u.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + assert K <= 256, "current kernel does not support head dimension larger than 256." + + h = k.new_empty(B, NT, H, K, V) + final_state = k.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + + v_new = torch.empty_like(u) if save_new_value else None + def grid(meta): return (triton.cdiv(V, meta['BV']), N*H) + chunk_gated_delta_rule_fwd_kernel_h_blockdim64[grid]( + k=k, + v=u, + w=w, + v_new=v_new, + g=g, + gk=gk, + h=h, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return h, v_new, final_state + + +def chunk_gated_delta_rule_bwd_dhu( + q: torch.Tensor, + k: torch.Tensor, + w: torch.Tensor, + do: torch.Tensor, + dv: torch.Tensor, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + h0: torch.Tensor | None = None, + dht: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, # SY: remove this argument and force chunk size 64? +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *q.shape, do.shape[-1] + # N: the actual number of sequences in the batch with either equal or variable lengths + BT = 64 + assert K <= 256, "current kernel does not support head dimension being larger than 256." + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + dh = q.new_empty(B, NT, H, K, V) + dh0 = torch.empty_like(h0, dtype=torch.float32) if h0 is not None else None + dv2 = torch.empty_like(dv) + + def grid(meta): return (triton.cdiv(V, meta['BV']), N*H) + chunk_gated_delta_rule_bwd_kernel_dhu_blockdim64[grid]( + q=q, + k=k, + w=w, + g=g, + gk=gk, + dht=dht, + dh0=dh0, + do=do, + dh=dh, + dv=dv, + dv2=dv2, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return dh, dh0, dv2 diff --git a/code/flash-linear-attention/fla/ops/common/chunk_h.py b/code/flash-linear-attention/fla/ops/common/chunk_h.py new file mode 100644 index 0000000000000000000000000000000000000000..cdc869b0007fd67342dcf6168ab24e01dc10acdf --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/chunk_h.py @@ -0,0 +1,394 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem + +BKV_LIST = [32, 64] if check_shared_mem() else [16, 32] + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'HAS_MIXED_PRECISION': lambda args: args['gk'] is not None and args['k'].dtype != args['gk'].dtype, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BKV_LIST + for BV in BKV_LIST + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_h( + k, + v, + h, + g, + g_gamma, + gk, + gv, + h0, + ht, + cu_seqlens, + split_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, + HAS_MIXED_PRECISION: tl.constexpr = False, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT, NS = tl.cdiv(T, BT), tl.cdiv(T, BS) + boh = tl.load(split_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT, NS = tl.cdiv(T, BT), tl.cdiv(T, BS) + boh = i_n * NS + NTS = BS // BT + + if USE_G_GAMMA: + # decay rate given the head index + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(NT): + i_s = i_t // NTS + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + o_h = ((boh + i_s) * H + i_h).to(tl.int64) * K*V + p_h = tl.make_block_ptr(h + o_h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + if i_t % NTS == 0: + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + last_idx = min((i_t + 1) * BT, T) - 1 + + # scalar decay + if USE_G: + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_g = g + bos*H + (i_t * BT + tl.arange(0, BT)) * H + i_h + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_h *= exp(b_g_last) + b_v = (b_v * exp(b_g_last - b_g)[:, None]).to(b_v.dtype) + + if USE_G_GAMMA: + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_h *= exp(b_g_last) + b_v = (b_v * exp(b_g_last - b_g)[:, None]).to(b_v.dtype) + + # vector decay, h = Diag(gk) @ h + if USE_GK: + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_h *= exp(b_gk_last)[:, None] + + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_k = (b_k * exp(b_gk_last[:, None] - b_gk)).to(b_k.dtype) + + # vector decay, h = h @ Diag(gv) + if USE_GV: + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_h *= exp(b_gv_last)[None, :] + + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_v = (b_v * exp(b_gv_last[None, :] - b_gv)).to(b_v.dtype) + + if HAS_MIXED_PRECISION: + b_h += tl.dot(b_k.to(tl.float32), b_v.to(tl.float32)) + else: + b_h += tl.dot(b_k, b_v) + + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'HAS_MIXED_PRECISION': lambda args: args['gk'] is not None and args['q'].dtype != args['gk'].dtype, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BKV_LIST + for BV in BKV_LIST + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dh( + q, + g, + g_gamma, + gk, + gv, + do, + dh, + dht, + dh0, + cu_seqlens, + split_offsets, + scale, + T, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, + HAS_MIXED_PRECISION: tl.constexpr = False, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // NG + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + NS = tl.cdiv(T, BS) + boh = tl.load(split_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + NS = tl.cdiv(T, BS) + boh = i_n * NS + + if USE_G_GAMMA: + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_dht = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dht, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(NT - 1, -1, -1): + i_s = i_t // (BS // BT) + o_dh = ((boh + i_s) * H + i_h).to(tl.int64) * K*V + p_dh = tl.make_block_ptr(dh + o_dh, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + if i_t % (BS // BT) == 0: + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + last_idx = min(i_t * BT + BT, T) - 1 + # [BK, BT] + p_q = tl.make_block_ptr(q + (bos*HQ + i_hq) * K, (K, T), (1, HQ*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_do = tl.make_block_ptr(do + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + + if USE_G: + p_g = g + (bos + i_t * BT + tl.arange(0, BT)) * H + i_h + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_q = (b_q * exp(b_g)[None, :]).to(b_q.dtype) + b_dh *= exp(b_g_last) + + if USE_G_GAMMA: + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_q = (b_q * exp(b_g)[None, :]).to(b_q.dtype) + b_dh *= exp(b_g_last) + + if USE_GK: + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_q = (b_q * exp(b_gk)).to(b_q.dtype) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_dh *= exp(b_gk_last)[:, None] + + if USE_GV: + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_do = (b_do * exp(b_gv)) + + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_dh *= exp(b_gv_last)[None, :] + + if HAS_MIXED_PRECISION: + b_dh += tl.dot(b_q.to(tl.float32), b_do.to(tl.float32)) + else: + b_dh += tl.dot(b_q, b_do.to(b_q.dtype)) + + if STORE_INITIAL_STATE_GRADIENT: + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_fwd_h( + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + h0: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + split_size: int | None = None, + states_in_fp32: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + BS = BT if split_size is None else split_size + assert BS % BT == 0, f"The `split_size` (got {BS}) must be a multiple of `chunk_size` {BT}" + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NS, split_offsets = B, triton.cdiv(T, BS), None + else: + split_offsets = prepare_chunk_offsets(cu_seqlens, BS) + N, NS = len(cu_seqlens) - 1, split_offsets[-1].item() + + h = k.new_empty(B, NS, H, K, V, dtype=k.dtype if not states_in_fp32 else torch.float) + ht = k.new_empty(N, H, K, V, dtype=torch.float) if output_final_state else None + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * H) + chunk_fwd_kernel_h[grid]( + k=k, + v=v, + h=h, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + h0=h0, + ht=ht, + cu_seqlens=cu_seqlens, + split_offsets=split_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + USE_G=g is not None, + USE_G_GAMMA=g_gamma is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + return h, ht + + +def chunk_bwd_dh( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + do: torch.Tensor, + h0: torch.Tensor, + dht: torch.Tensor, + scale: float, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + split_size: int | None = None, + states_in_fp32: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HQ = q.shape[2] + BT = chunk_size + BS = BT if split_size is None else split_size + assert BS % BT == 0, f"The `split_size` (got {BS}) must be a multiple of `chunk_size` {BT}" + # N: the actual number of sequences in the batch with either equal or variable lengths + # NG: number of groups in GQA + if cu_seqlens is None: + N, NS, split_offsets = B, triton.cdiv(T, BS), None + else: + split_offsets = prepare_chunk_offsets(cu_seqlens, BS) + N, NS = len(cu_seqlens) - 1, split_offsets[-1].item() + NG = HQ // H + + dh = k.new_empty(B, NS, HQ, K, V, dtype=k.dtype if not states_in_fp32 else torch.float) + dh0 = torch.empty_like(h0, dtype=torch.float) if h0 is not None else None + + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * H) + chunk_bwd_kernel_dh[grid]( + q=q, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + do=do, + dh=dh, + dht=dht, + dh0=dh0, + cu_seqlens=cu_seqlens, + split_offsets=split_offsets, + scale=scale, + T=T, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + NG=NG, + USE_G=g is not None, + USE_G_GAMMA=g_gamma is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + return dh, dh0 diff --git a/code/flash-linear-attention/fla/ops/common/chunk_h_parallel.py b/code/flash-linear-attention/fla/ops/common/chunk_h_parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..7923b27652f8b0bc1e7bef016010680e7e1fc689 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/chunk_h_parallel.py @@ -0,0 +1,554 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +""" +Fully parallelized state passing. +""" + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for BV in [32, 64, 128] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_h_parallel( + k, + v, + h, + g, + gk, + gv, + h0, + ht, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_kv, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + NV = tl.cdiv(V, BV) + # i_b: batch index + # i_h: head index + # i_n: sequence index + # i_t: chunk index within current sequence + # i_tg: (global) chunk index across all sequences + i_k, i_v = i_kv // NV, i_kv % NV + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + bos, eos = i_b * T, i_b * T + T + NT = tl.cdiv(T, BT) + i_n, i_tg = i_b, i_b * NT + i_t + i_nh = i_n * H + i_h + + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + if i_t == 0: + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1)).to(tl.float32) + else: + b_h = tl.zeros([BK, BV], dtype=tl.float32) + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + + last_idx = min(i_t * BT + BT, T) - 1 + # scalar decay + if USE_G: + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_g = g + bos*H + (i_t * BT + tl.arange(0, BT)) * H + i_h + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_v = (b_v * exp(b_g_last - b_g)[:, None]).to(b_v.dtype) + + # vector decay, h = Diag(gk) @ h + if USE_GK: + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_k = (b_k * exp(b_gk_last[:, None] - b_gk)).to(b_k.dtype) + + # vector decay, h = h @ Diag(gv) + if USE_GV: + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_v = (b_v * exp(b_gv_last[None, :] - b_gv)).to(b_v.dtype) + + b_h = tl.dot(b_k, b_v) + if i_t < NT - 1: + p_h = tl.make_block_ptr(h + ((i_tg + 1) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + elif STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for BV in [32, 64, 128] + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_h_reduction( + h, + g, + gk, + gv, + kvt, + ht, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + for i_t in range(NT): + p_h = tl.make_block_ptr(h + ((boh + i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h += tl.load(p_h, boundary_check=(0, 1)).to(tl.float32) + if i_t > 0: + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + + last_idx = min(i_t * BT + BT, T) - 1 + # scalar decay + if USE_G: + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + b_h *= exp(b_g_last) + + # vector decay, h = Diag(gk) @ h + if USE_GK: + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_h *= exp(b_gk_last)[:, None] + + # vector decay, h = h @ Diag(gv) + if USE_GV: + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_h *= exp(b_gv_last)[None, :] + + if STORE_FINAL_STATE: + p_kvt = tl.make_block_ptr(kvt + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h += tl.load(p_kvt, boundary_check=(0, 1)).to(tl.float32) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for BV in [32, 64, 128] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dh_parallel( + q, + g, + gk, + gv, + do, + dh, + dht, + dh0, + cu_seqlens, + chunk_indices, + scale, + T, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_kv, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + NV = tl.cdiv(V, BV) + i_k, i_v = i_kv // NV, i_kv % NV + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + bos, eos = i_b * T, i_b * T + T + NT = tl.cdiv(T, BT) + i_n, i_tg = i_b, i_b * NT + i_t + i_nh = i_n * HQ + i_hq + + p_q = tl.make_block_ptr(q + (bos*HQ + i_hq) * K, (K, T), (1, HQ*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_do = tl.make_block_ptr(do + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dh = tl.make_block_ptr(dh + (i_tg * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + if i_t == NT - 1: + if USE_FINAL_STATE_GRADIENT: + p_dht = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh = tl.load(p_dht, boundary_check=(0, 1)).to(tl.float32) + else: + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + + # [BK, BT] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + + if USE_G: + p_g = g + (bos + i_t * BT + tl.arange(0, BT)) * H + i_h + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_q = (b_q * exp(b_g)[None, :]).to(b_q.dtype) + + if USE_GK: + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_q = (b_q * exp(b_gk)).to(b_q.dtype) + + if USE_GV: + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_do = (b_do * exp(b_gv)).to(b_do.dtype) + + b_dh = tl.dot(b_q, b_do) + if i_t > 0: + p_dh = tl.make_block_ptr(dh + ((i_tg - 1) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + elif STORE_INITIAL_STATE_GRADIENT: + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for BV in [32, 64, 128] + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dh_reduction( + g, + gk, + gv, + dh, + doq0, + dh0, + cu_seqlens, + chunk_offsets, + T, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // NG + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + for i_t in range(NT - 1, -1, -1): + p_dh = tl.make_block_ptr(dh + ((boh+i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dh, boundary_check=(0, 1)).to(tl.float32) + if i_t < NT - 1: + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + + last_idx = min(i_t * BT + BT, T) - 1 + if USE_G: + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + b_dh *= exp(b_g_last) + + if USE_GK: + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_dh *= exp(b_gk_last)[:, None] + + if USE_GV: + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_dh *= exp(b_gv_last)[None, :] + + if STORE_INITIAL_STATE_GRADIENT: + p_doq0 = tl.make_block_ptr(doq0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_doq0, boundary_check=(0, 1)).to(tl.float32) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_fwd_h( + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + gk: torch.Tensor, + gv: torch.Tensor, + h0: torch.Tensor, + output_final_state: bool, + states_in_fp32: bool = False, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + h = k.new_empty(B, NT, H, K, V, dtype=torch.float) + ht = k.new_empty(N, H, K, V, dtype=torch.float) if output_final_state else None + def grid(meta): return (triton.cdiv(K, meta['BK']) * triton.cdiv(V, meta['BV']), NT, B * H) + chunk_fwd_kernel_h_parallel[grid]( + k=k, + v=v, + h=h, + g=g, + gk=gk, + gv=gv, + h0=h0, + ht=ht, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + kvt, ht = ht, (torch.empty_like(ht) if output_final_state else None) + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * H) + chunk_fwd_kernel_h_reduction[grid]( + h=h, + g=g, + gk=gk, + gv=gv, + kvt=kvt, + ht=ht, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + h = h.to(k.dtype) if not states_in_fp32 else h + return h, ht + + +def chunk_bwd_dh( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + gk: torch.Tensor, + gv: torch.Tensor, + do: torch.Tensor, + h0: torch.Tensor, + dht: torch.Tensor, + scale: float, + states_in_fp32: bool = False, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HQ = q.shape[2] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + # NG: number of groups in GQA + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + NG = HQ // H + + dh = k.new_empty(B, NT, HQ, K, V, dtype=k.dtype if not states_in_fp32 else torch.float) + dh0 = torch.empty_like(h0, dtype=torch.float) if h0 is not None else None + + def grid(meta): return (triton.cdiv(K, meta['BK']) * triton.cdiv(V, meta['BV']), NT, B * HQ) + chunk_bwd_kernel_dh_parallel[grid]( + q=q, + g=g, + gk=gk, + gv=gv, + do=do, + dh=dh, + dht=dht, + dh0=dh0, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + NG=NG, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + + doq0, dh0 = dh0, (torch.empty_like(dh0) if dh0 is not None else None) + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * HQ) + chunk_bwd_kernel_dh_reduction[grid]( + g=g, + gk=gk, + gv=gv, + dh=dh, + doq0=doq0, + dh0=dh0, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + NG=NG, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + dh = dh.to(q.dtype) if not states_in_fp32 else dh + return dh, dh0 diff --git a/code/flash-linear-attention/fla/ops/common/chunk_h_split.py b/code/flash-linear-attention/fla/ops/common/chunk_h_split.py new file mode 100644 index 0000000000000000000000000000000000000000..8f601eb89c9f794b0d69fa07761aa3ffca31d649 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/chunk_h_split.py @@ -0,0 +1,599 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for BV in [32, 64] + for num_warps in [2, 4, 8] + for num_stages in [2, 3] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_h_split( + k, + v, + g, + gk, + gv, + hs, + hr, + h0, + ht, + cu_seqlens, + split_indices, + T, + S: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + # handle one split at a time + # i_h: head index + # i_n: sequence index + # i_s: local split index inside a sequence + i_k, i_v, i_sh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_ss, i_h = i_sh // H, i_sh % H + if IS_VARLEN: + i_n, i_s = tl.load(split_indices + i_ss * 2).to(tl.int32), tl.load(split_indices + i_ss * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NS = tl.cdiv(T, S) + else: + NS = tl.cdiv(T, S) + i_n, i_s = i_ss // NS, i_ss % NS + bos, eos = i_n * T, i_n * T + T + i_nh = i_n * H + i_h + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + # for the first split, we directly store the state as the final result + if i_s == 0: + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h += tl.load(p_h0, boundary_check=(0, 1)).to(tl.float32) + p_hr = tl.make_block_ptr(hr + i_sh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_hr, b_h.to(p_hr.dtype.element_ty), boundary_check=(0, 1)) + for i_t in range(tl.cdiv(i_s * S, BT), tl.cdiv(min(i_s * S + S, T), BT)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + last_idx = min(i_t * BT + BT, T) - 1 + + # scalar decay + if USE_G: + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_g = g + bos*H + (i_t * BT + tl.arange(0, BT)) * H + i_h + b_h *= exp(b_g_last) + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_v = (b_v * exp(b_g_last - b_g)[:, None]).to(b_v.dtype) + + # vector decay, h = Diag(gk) @ h + if USE_GK: + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_h *= exp(b_gk_last)[:, None] + + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_k = (b_k * exp(b_gk_last[:, None] - b_gk)).to(b_k.dtype) + + # vector decay, h = h @ Diag(gv) + if USE_GV: + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_h *= exp(b_gv_last)[None, :] + + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_v = (b_v * exp(b_gv_last[None, :] - b_gv)).to(b_v.dtype) + + b_h += tl.dot(b_k, b_v) + + # if there are more than one splits, we store the result to (unreduced) hs + # otherwise, we store the result to ht as the final state + if NS > 1: + p_hs = tl.make_block_ptr(hs + i_sh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_hs, b_h.to(p_hs.dtype.element_ty), boundary_check=(0, 1)) + elif STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for BV in [32, 64] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_h_reduction( + g, + gk, + gv, + hs, + hr, + ht, + cu_seqlens, + split_offsets, + T, + S: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NS = tl.cdiv(T, S) + boh = tl.load(split_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NS = tl.cdiv(T, S) + boh = i_n * NS + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + # skip the first split + for i_s in range(1, NS): + p_hs = tl.make_block_ptr(hs + ((boh + i_s-1) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_hr = tl.make_block_ptr(hr + ((boh + i_s) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h += tl.load(p_hs, boundary_check=(0, 1)).to(tl.float32) + tl.store(p_hr, b_h.to(p_hr.dtype.element_ty), boundary_check=(0, 1)) + + for i_t in range(tl.cdiv(i_s * S, BT), tl.cdiv(min(i_s * S + S, T), BT)): + last_idx = min(i_t * BT + BT, T) - 1 + # scalar decay + if USE_G: + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + b_h *= exp(b_g_last) + + # vector decay, h = Diag(gk) @ h + if USE_GK: + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_h *= exp(b_gk_last)[:, None] + + # vector decay, h = h @ Diag(gv) + if USE_GV: + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_h *= exp(b_gv_last)[None, :] + + if NS > 1: + if STORE_FINAL_STATE: + p_hs = tl.make_block_ptr(hs + ((boh + NS-1) * H + i_h)*K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h += tl.load(p_hs, boundary_check=(0, 1)).to(tl.float32) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for BV in [32, 64] + for num_warps in [2, 4, 8] + for num_stages in [2, 3] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dh_split( + q, + g, + gk, + gv, + do, + dht, + dhs, + dhr, + dh0, + cu_seqlens, + split_indices, + scale, + T, + S: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + # handle one split at a time + # i_h: head index + # i_n: sequence index + # i_s: local split index inside a sequence + i_k, i_v, i_sh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_ss, i_hq = i_sh // HQ, i_sh % HQ + if IS_VARLEN: + i_n, i_s = tl.load(split_indices + i_ss * 2).to(tl.int32), tl.load(split_indices + i_ss * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NS = tl.cdiv(T, S) + else: + NS = tl.cdiv(T, S) + i_n, i_s = i_ss // NS, i_ss % NS + bos, eos = i_n * T, i_n * T + T + i_nh = i_n * HQ + i_hq + i_h = i_hq // NG + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if i_s == NS - 1: + if USE_FINAL_STATE_GRADIENT: + p_dht = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dht, boundary_check=(0, 1)).to(tl.float32) + p_dhr = tl.make_block_ptr(dhr + i_sh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dhr, b_dh.to(p_dhr.dtype.element_ty), boundary_check=(0, 1)) + + for i_t in range(tl.cdiv(min(i_s * S + S, T), BT) - 1, tl.cdiv(i_s * S, BT) - 1, -1): + p_q = tl.make_block_ptr(q + (bos*HQ + i_hq) * K, (K, T), (1, HQ*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_do = tl.make_block_ptr(do + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + + last_idx = min(i_t * BT + BT, T) - 1 + if USE_G: + p_g = g + (bos + i_t * BT + tl.arange(0, BT)) * H + i_h + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_q = (b_q * exp(b_g)[None, :]).to(b_q.dtype) + b_dh *= exp(b_g_last) + + if USE_GK: + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_q = (b_q * exp(b_gk)).to(b_q.dtype) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_dh *= exp(b_gk_last)[:, None] + + if USE_GV: + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_do = (b_do * exp(b_gv)).to(b_do.dtype) + + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_dh *= exp(b_gv_last)[None, :] + + b_dh += tl.dot(b_q, b_do) + + if NS > 1: + p_dhs = tl.make_block_ptr(dhs + i_sh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dhs, b_dh.to(p_dhs.dtype.element_ty), boundary_check=(0, 1)) + elif STORE_INITIAL_STATE_GRADIENT: + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for BV in [32, 64] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dh_reduction( + g, + gk, + gv, + dhs, + dhr, + dh0, + cu_seqlens, + split_offsets, + T, + S: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // NG + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NS = tl.cdiv(T, S) + boh = tl.load(split_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NS = tl.cdiv(T, S) + boh = i_n * NS + + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + for i_s in range(NS - 2, -1, -1): + p_dhs = tl.make_block_ptr(dhs + ((boh+i_s+1) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_dhr = tl.make_block_ptr(dhr + ((boh+i_s) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dhs, boundary_check=(0, 1)).to(tl.float32) + tl.store(p_dhr, b_dh.to(p_dhr.dtype.element_ty), boundary_check=(0, 1)) + + for i_t in range(tl.cdiv(min(i_s * S + S, T), BT) - 1, tl.cdiv(i_s * S, BT) - 1, -1): + last_idx = min(i_t * BT + BT, T) - 1 + # scalar decay + if USE_G: + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + b_dh *= exp(b_g_last) + + if USE_GK: + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_dh *= exp(b_gk_last)[:, None] + + if USE_GV: + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_dh *= exp(b_gv_last)[None, :] + + if NS > 1: + if STORE_INITIAL_STATE_GRADIENT: + p_dhs = tl.make_block_ptr(dhs + (boh * H + i_h)*K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dhs, boundary_check=(0, 1)).to(tl.float32) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_fwd_h( + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + gk: torch.Tensor, + gv: torch.Tensor, + h0: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + split_offsets: torch.LongTensor | None = None, + split_indices: torch.LongTensor | None = None, + chunk_size: int = 64, + split_size: int = 256, + states_in_fp32: bool = True, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + # B: batch size + # N: the actual number of sequences in the batch + # H: number of heads + # T: sequence length, can be variable across sequences + # S: split size, a multiple of chunk size + # BT: chunk size + S, BT = split_size, chunk_size + assert S % BT == 0, f"The `split_size` (got {S}) must be a multiple of `chunk_size` {BT}" + if cu_seqlens is None: + N = B + NS = N * triton.cdiv(T, S) + else: + N = len(cu_seqlens) - 1 + NS = split_offsets[-1] + + # unreduced kv states per split + hs = k.new_empty(NS, H, K, V, dtype=torch.float) + # reduced states per split + hr = k.new_empty(NS, H, K, V, dtype=torch.float if states_in_fp32 else k.dtype) + ht = k.new_empty(N, H, K, V, dtype=torch.float) if output_final_state else None + # parallelized over splits + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), NS * H) + chunk_fwd_kernel_h_split[grid]( + k=k, + v=v, + g=g, + gk=gk, + gv=gv, + hs=hs, + hr=hr, + h0=h0, + ht=ht, + cu_seqlens=cu_seqlens, + split_indices=split_indices, + T=T, + S=S, + H=H, + K=K, + V=V, + BT=BT, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * H) + chunk_fwd_kernel_h_reduction[grid]( + g=g, + gk=gk, + gv=gv, + hs=hs, + hr=hr, + ht=ht, + cu_seqlens=cu_seqlens, + split_offsets=split_offsets, + T=T, + S=S, + H=H, + K=K, + V=V, + BT=BT, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + return hr, ht + + +def chunk_bwd_dh( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + gk: torch.Tensor, + gv: torch.Tensor, + do: torch.Tensor, + h0: torch.Tensor, + dht: torch.Tensor, + scale: float, + cu_seqlens: torch.Tensor | None = None, + split_offsets: torch.Tensor | None = None, + split_indices: torch.Tensor | None = None, + chunk_size: int = 64, + split_size: int = 256, + states_in_fp32: bool = True, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HQ = q.shape[2] + # B: batch size + # N: the actual number of sequences in the batch + # H: number of heads + # T: sequence length, can be variable across sequences + # S: split size, a multiple of chunk size + # BT: chunk size + S, BT = max(chunk_size, min(split_size, triton.next_power_of_2(T))), chunk_size + assert S % BT == 0, f"The `split_size` (got {S}) must be a multiple of `chunk_size` {BT}" + if cu_seqlens is None: + N = B + NS = N * triton.cdiv(T, S) + else: + N = len(cu_seqlens) - 1 + NS = split_offsets[-1] + # number of groups in GQA + NG = HQ // H + + dhs = q.new_empty(NS, HQ, K, V, dtype=torch.float) + dhr = q.new_empty(NS, HQ, K, V, dtype=torch.float if states_in_fp32 else k.dtype) + dh0 = torch.empty_like(h0, dtype=torch.float) if h0 is not None else None + + # parallelized over splits + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), NS * HQ) + chunk_bwd_kernel_dh_split[grid]( + q=q, + g=g, + gk=gk, + gv=gv, + do=do, + dht=dht, + dhs=dhs, + dhr=dhr, + dh0=dh0, + cu_seqlens=cu_seqlens, + split_indices=split_indices, + scale=scale, + T=T, + S=S, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + NG=NG, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * HQ) + chunk_bwd_kernel_dh_reduction[grid]( + g=g, + gk=gk, + gv=gv, + dhs=dhs, + dhr=dhr, + dh0=dh0, + cu_seqlens=cu_seqlens, + split_offsets=split_offsets, + T=T, + S=S, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + NG=NG, + USE_G=g is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + ) + return dhr, dh0 diff --git a/code/flash-linear-attention/fla/ops/common/chunk_o.py b/code/flash-linear-attention/fla/ops/common/chunk_o.py new file mode 100644 index 0000000000000000000000000000000000000000..53b5b7f3e3c3314d24ef7ff2769f268a3ed3b890 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/chunk_o.py @@ -0,0 +1,689 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_nvidia_hopper + +BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_G_GAMMA': lambda args: args['g_gamma'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': 128, 'BV': 128}, num_warps=8, num_stages=3), + triton.Config({'BK': 64, 'BV': 64}, num_warps=4, num_stages=3), + triton.Config({'BK': 32, 'BV': 32}, num_warps=2, num_stages=3), + ], + key=['H', 'K', 'V', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_o( + q, + k, + v, + h, + g, + g_gamma, + o, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + o += (bos * H + i_h) * V + h += (i_tg * H + i_h).to(tl.int64) * K*V + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + b_A = tl.zeros([BT, BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_h = tl.make_block_ptr(h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + + # [BT, BK] @ [BK, BV] -> [BT, BV] + b_o += tl.dot(b_q, b_h) + # [BT, BK] @ [BK, BT] -> [BT, BT] + b_A += tl.dot(b_q, b_k) + + if USE_G: + g += bos * H + i_h + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_o = b_o * exp(b_g)[:, None] + b_A = b_A * exp(b_g[:, None] - b_g[None, :]) + + if USE_G_GAMMA: + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + b_o = b_o * exp(b_g)[:, None] + b_A = b_A * exp(b_g[:, None] - b_g[None, :]) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t) + b_A = tl.where(m_A, b_A, 0) + + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + b_v = tl.load(p_v, boundary_check=(0, 1)) + # to fix mma -> mma layout conversion + # already solved by triton v3.2 or higher + b_o = b_o * scale + tl.dot(b_A.to(b_v.dtype), b_v) * scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_G_GAMMA': lambda args: args['g_gamma'] is not None, + 'USE_DW': lambda args: args['dw'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'USE_G', 'USE_G_GAMMA', 'USE_DW'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dqkwg( + q, + k, + v, + g, + g_gamma, + h, + do, + dh, + dq, + dk, + dw, + dv, + dg, + cu_seqlens, + chunk_indices, + scale, + B: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_DW: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + all = B * T + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + # offset calculation + v += (bos * H + i_h) * V + do += (bos * H + i_h) * V + h += (i_tg * H + i_h).to(tl.int64) * K*V + dh += (i_tg * H + i_h).to(tl.int64) * K*V + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + dq += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + + # for delta rule only + if USE_DW: + dw += (bos * H + i_h) * K + dv += (bos * H + i_h) * V + + if USE_G: + dg += i_k * all * H + b_dg_last = tl.zeros([1], dtype=tl.float32) if USE_G else None + if USE_G_GAMMA: + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_ds = tl.zeros([BT, BT], dtype=tl.float32) + b_dw = tl.zeros([BT, BK], dtype=tl.float32) if USE_DW else None + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + if USE_G: + b_dg_last += (tl.sum(b_h * b_dh)) + # [BT, BV] @ [BV, BT] -> [BT, BT] + b_ds += tl.dot(b_do, tl.trans(b_v)) + # [BT, BV] @ [BV, BK] -> [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + # [BT, BV] @ [BV, BK] -> [BT, BK] + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) + if USE_DW: + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_dv = tl.load(p_dv, boundary_check=(0, 1)) + b_dw += tl.dot(b_dv.to(b_v.dtype), b_h.to(b_v.dtype)) + + if USE_DW: + p_dw = tl.make_block_ptr(dw, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dw, -b_dw.to(p_dw.dtype.element_ty), boundary_check=(0, 1)) + + tl.debug_barrier() + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t) + if USE_G: + b_dg = tl.zeros([BT], dtype=tl.float32) + g += bos * H + i_h + dg += bos * H + i_h + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_last = tl.load(g + (min(i_t * BT + BT, T) - 1) * H) + b_dg_last *= exp(b_g_last) + + b_dq = b_dq * exp(b_g)[:, None] * scale + b_dg += tl.sum(b_dq * b_q, axis=1) + + b_dk = b_dk * tl.where(m_t, exp(-b_g + b_g_last), 0)[:, None] + b_dg -= tl.sum(b_k * b_dk, axis=1) + b_dg_last += tl.sum(b_dk * b_k) + + b_ds = tl.where(m_A, b_ds * exp(b_g[:, None] - b_g[None, :]), 0) * scale + b_ds2 = b_ds * tl.dot(b_q, tl.trans(b_k)) + b_dg += tl.sum(b_ds2, axis=1) + b_dg -= tl.sum(b_ds2, axis=0) + + b_ds = b_ds.to(b_k.dtype) + # [BT, BK] + b_dq += tl.dot(b_ds, b_k) + b_dk += tl.dot(tl.trans(b_ds), b_q) + p_dg = tl.make_block_ptr(dg, (T,), (H,), (i_t * BT,), (BT,), (0,)) + # (SY 09/21) revcumsum in a separate kernel due to strange triton compiler issue + # b_dg = tl.dot(tl.where(o_t[:, None] <= o_t[None, :], 1., 0.), b_dg, allow_tf32=False) + b_dg_last) + b_dg = tl.where(o_t < min(i_t * BT + BT, T) - 1, b_dg, b_dg + b_dg_last) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + elif USE_G_GAMMA: + b_dq = b_dq * exp(b_g)[:, None] * scale + b_dk = b_dk * tl.where(m_t, exp(-b_g + b_g_last), 0)[:, None] + b_ds = tl.where(m_A, b_ds * exp(b_g[:, None] - b_g[None, :]), 0) * scale + b_ds = b_ds.to(b_k.dtype) + # [BT, BK] + b_dq += tl.dot(b_ds, b_k) + b_dk += tl.dot(tl.trans(b_ds), b_q) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + else: + b_ds = tl.where(m_A, b_ds, 0) + b_ds = b_ds.to(b_k.dtype) + b_dq += tl.dot(b_ds, b_k) + b_dk += tl.dot(tl.trans(b_ds), b_q) * scale + b_dq *= scale + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_G_GAMMA': lambda args: args['g_gamma'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'USE_G', 'USE_G_GAMMA'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dv( + q, + k, + g, + g_gamma, + do, + dv, + dh, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + b_dv = tl.zeros([BT, BV], dtype=tl.float32) + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + do += (bos * H + i_h) * V + dv += (bos * H + i_h) * V + dh += (i_tg * H + i_h).to(tl.int64) * K*V + + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_q = tl.make_block_ptr(q, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_A += tl.dot(b_k, b_q) + p_dh = tl.make_block_ptr(dh, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_dv += tl.dot(b_k, b_dh.to(b_k.dtype)) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + if USE_G: + g += bos * H + i_h + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_last = tl.load(g + (min(i_t * BT + BT, T) - 1) * H) + if USE_G_GAMMA: + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + b_g_last = b_gamma * min(BT, T - i_t * BT) + + m_A = (o_t[:, None] <= o_t[None, :]) & (m_t[:, None] & m_t) + if USE_G or USE_G_GAMMA: + b_A = tl.where(m_A, b_A * exp(b_g[None, :] - b_g[:, None]) * scale, 0).to(do.dtype.element_ty) + b_dv *= tl.where(m_t, exp(-b_g + b_g_last), 0)[:, None] + else: + b_A = tl.where(m_A, b_A * scale, 0).to(do.dtype.element_ty) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dv += tl.dot(b_A.to(b_do.dtype), b_do) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_G_GAMMA': lambda args: args['g_gamma'] is not None, + 'USE_A': lambda args: args['A'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'USE_G'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dv_local( + q, + k, + g, + g_gamma, + A, + do, + dv, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_A: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + do += (bos * H + i_h) * V + dv += (bos * H + i_h) * V + + if USE_A: + p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + b_A = tl.load(p_A, boundary_check=(0, 1)) + else: + if USE_G: + g += bos * H + i_h + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + if USE_G_GAMMA: + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_q = tl.make_block_ptr(q, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_A += tl.dot(b_k, b_q) * scale + if USE_G or USE_G_GAMMA: + b_A *= exp(b_g[None, :] - b_g[:, None]) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_A = (o_t[:, None] <= o_t[None, :]) & (m_t[:, None] & m_t) + b_A = tl.where(m_A, b_A, 0).to(do.dtype.element_ty) + + for i_v in range(tl.cdiv(V, BV)): + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dv = tl.dot(b_A.to(b_do.dtype), b_do) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_fwd_o( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, K, V = *q.shape, v.shape[-1] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + if scale is None: + scale = k.shape[-1] ** -0.5 + + o = torch.empty_like(v) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * H) + chunk_fwd_kernel_o[grid]( + q=q, + k=k, + v=v, + h=h, + g=g, + g_gamma=g_gamma, + o=o, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return o + + +def chunk_bwd_dv( + q: torch.Tensor, + k: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, K, V = *k.shape, do.shape[-1] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # H100 can have larger block size + if check_shared_mem('hopper', k.device.index): + CONST_TILING = 128 + elif check_shared_mem: + CONST_TILING = 64 + else: + CONST_TILING = 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NV = triton.cdiv(V, BV) + if scale is None: + scale = k.shape[-1] ** -0.5 + + dv = torch.empty_like(do) + grid = (NV, NT, B * H) + chunk_bwd_kernel_dv[grid]( + q=q, + k=k, + g=g, + g_gamma=g_gamma, + do=do, + dv=dv, + dh=dh, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dv + + +def chunk_bwd_dv_local( + q: torch.Tensor, + k: torch.Tensor, + do: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + A: torch.Tensor | None = None, + scale: float = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, K, V = *k.shape, do.shape[-1] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # H100 can have larger block size + if check_shared_mem('hopper', k.device.index): + CONST_TILING = 128 + elif check_shared_mem: + CONST_TILING = 64 + else: + CONST_TILING = 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dv = torch.empty_like(do) + grid = (NT, B * H) + chunk_bwd_kernel_dv_local[grid]( + q=q, + k=k, + g=g, + g_gamma=g_gamma, + A=A, + do=do, + dv=dv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dv + + +def chunk_bwd_dqkwg( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + do: torch.Tensor, + h: torch.Tensor, + dh: torch.Tensor, + w: torch.Tensor | None = None, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + dv: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + NK = triton.cdiv(K, BK) + dq = torch.empty_like(q) + dk = torch.empty_like(k) + dg = torch.empty(NK, *g.shape, dtype=torch.float32, device=g.device) if g is not None else None + dw = torch.empty_like(w) if w is not None else None + + grid = (NK, NT, B * H) + chunk_bwd_kernel_dqkwg[grid]( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + h=h, + do=do, + dh=dh, + dw=dw, + dq=dq, + dk=dk, + dv=dv, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + + if dg is not None: + dg = dg.sum(0) + return dq, dk, dw, dg diff --git a/code/flash-linear-attention/fla/ops/common/chunk_scaled_dot_kkt.py b/code/flash-linear-attention/fla/ops/common/chunk_scaled_dot_kkt.py new file mode 100644 index 0000000000000000000000000000000000000000..c270db1c0258757846b1473d043c8739f0b371a3 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/chunk_scaled_dot_kkt.py @@ -0,0 +1,124 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'BT', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_scaled_dot_kkt_fwd_kernel( + k, + g, + beta, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_G: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + p_b = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_b = tl.load(p_b, boundary_check=(0,)) + + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_A += tl.dot(b_k, tl.trans(b_k)) + + if USE_G: + p_g = tl.make_block_ptr(g + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_diff = b_g[:, None] - b_g[None, :] + b_A *= exp(b_g_diff) + b_A *= b_b[:, None] + + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_A = tl.where(m_A, b_A, 0) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (BT*H, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_scaled_dot_kkt_fwd( + k: torch.Tensor, + g: torch.Tensor | None = None, + beta: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + output_dtype: torch.dtype = torch.float32, +) -> torch.Tensor: + r""" + Compute beta * K * K^T. + + Args: + k (torch.Tensor): + The key tensor of shape `[B, T, H, K]`. + beta (torch.Tensor): + The beta tensor of shape `[B, T, H]`. + g (torch.Tensor): + The cumulative sum of the gate tensor of shape `[B, T, H]`. Default: `None`. + gk (torch.Tensor): + The cumulative sum of the gate tensor of shape `[B, T, H, K]` applied to the key tensor. Default: `None`. + cu_seqlens (torch.LongTensor): + The cumulative sequence lengths of the input tensor. + Default: None + chunk_size (int): + The chunk size. Default: 64. + output_dtype (torch.dtype): + The dtype of the output tensor. Default: `torch.float32` + + Returns: + beta * K * K^T of shape `[B, T, H, BT]` where `BT` is the chunk size. + """ + B, T, H, K = k.shape + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + A = torch.empty(B, T, H, BT, device=k.device, dtype=output_dtype) + chunk_scaled_dot_kkt_fwd_kernel[(NT, B * H)]( + k=k, + g=g, + beta=beta, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + ) + return A diff --git a/code/flash-linear-attention/fla/ops/common/fused_chunk.py b/code/flash-linear-attention/fla/ops/common/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..31b2e914b09ea9c6352fa174611e8c0d6697459d --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/fused_chunk.py @@ -0,0 +1,636 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import chunk_local_cumsum +from fla.ops.utils.op import exp +from fla.utils import ( + autocast_custom_bwd, + autocast_custom_fwd, + autotune_cache_kwargs, + check_shared_mem, + input_guard, + is_nvidia_hopper, +) + +BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_G_GAMMA': lambda args: args['g_gamma'] is not None, + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BV in BKV_LIST + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_chunk_fwd_kernel( + q, + k, + v, + g, + g_gamma, + o, + h0, + ht, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + + all = B * T + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + + o_i = tl.arange(0, BT) + + if USE_G_GAMMA: + # decay rate given the head index + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (o_i + 1) + b_g_last = b_gamma * BT + b_gq = exp(b_g) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + + # [BT, BT] + m_s = o_i[:, None] >= o_i[None, :] + + q = q + (bos*H + i_h) * K + k = k + (bos*H + i_h) * K + v = v + (bos*H + i_h) * V + o = o + (i_k * all + bos).to(tl.int64) * H*V + i_h * V + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h = tl.make_block_ptr(h0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(0, NT): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + last_idx = min(i_t * BT + BT, T) - 1 + + # [BT, BT] + b_s = tl.dot(b_q, b_k) + + # scalar decay + if USE_G: + p_g = g + (bos + o_t) * H + i_h + b_g = tl.load(p_g, mask=(o_t < T), other=0.) + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + + b_gq = exp(b_g) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + if USE_G_GAMMA: + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + if USE_G or USE_G_GAMMA: + b_gs = tl.where(m_s & m_t, exp(b_g[:, None] - b_g[None, :]), 0) + # [BT, BT] + b_s *= b_gs + # [BT, BV] + b_o = tl.dot(b_s.to(b_q.dtype), b_v) + tl.dot(b_q, b_h.to(b_q.dtype)) * b_gq[:, None] + b_v = (b_v * b_gk[:, None]).to(b_v.dtype) + b_h *= b_gn + else: + # [BT, BT] + b_s *= m_s & m_t + # [BT, BV] + b_o = tl.dot(b_s.to(b_q.dtype), b_v) + tl.dot(b_q, b_h.to(b_q.dtype)) + + b_h += tl.dot(b_k, b_v) + + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_G_GAMMA': lambda args: args['g_gamma'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE': lambda args: args['dht'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_chunk_bwd_kernel( + q, + k, + v, + g, + g_gamma, + do, + dq, + dk, + dv, + dg, + h0, + dht, + dh0, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_FINAL_STATE: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + + all = B * T + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + NV = tl.cdiv(V, BV) + + o_i = tl.arange(0, BT) + if USE_G_GAMMA: + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (o_i + 1) + b_g_last = b_gamma * BT + b_gq = exp(b_g) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + + m_s = o_i[:, None] >= o_i[None, :] + + q = q + (bos*H + i_h) * K + k = k + (bos*H + i_h) * K + v = v + (bos*H + i_h) * V + do = do + (bos*H + i_h) * V + dq = dq + (i_v * all + bos).to(tl.int64) * H*K + i_h * K + dk = dk + (i_v * all + bos).to(tl.int64) * H*K + i_h * K + dv = dv + (i_k * all + bos).to(tl.int64) * H*V + i_h * V + + # [BV, BK] + b_h = tl.zeros([BV, BK], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h = tl.make_block_ptr(h0 + i_nh * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + b_h = tl.load(p_h, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(0, NT): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v, (V, T), (1, H*V), (i_v * BV, i_t * BT), (BV, BT), (0, 1)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + # [BT, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BT] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + last_idx = min(i_t * BT + BT, T) - 1 + + # [BT, BT] + b_ds = tl.dot(b_do, b_v) * scale + + # scalar decay + if USE_G: + p_g = g + (bos + o_t) * H + i_h + b_g = tl.load(p_g, mask=(o_t < T), other=0.) + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + + b_gq = exp(b_g) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + + p_dg = dg + ((i_k * NV + i_v) * all + (bos + o_t)).to(tl.int64) * H + i_h + # [BT, BT] + b_gs = tl.where(m_s & m_t, exp(b_g[:, None] - b_g[None, :]), 0) + b_ds = b_ds * b_gs + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_dq = tl.dot(b_ds.to(b_k.dtype), b_k) + tl.dot((b_do * b_gq[:, None] * scale).to(b_k.dtype), b_h.to(b_k.dtype)) + # [BT] + b_dg_t = tl.sum(b_q * b_dq, 1) + tl.store(p_dg, b_dg_t.to(p_dg.dtype.element_ty), mask=m_t) + # [BV, BK] + b_h = b_h * b_gn + tl.dot(b_v, (b_k * b_gk[:, None]).to(b_k.dtype)) + + elif USE_G_GAMMA: + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + + # [BT, BT] + b_gs = tl.where(m_s & m_t, exp(b_g[:, None] - b_g[None, :]), 0) + b_ds = b_ds * b_gs + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_dq = tl.dot(b_ds.to(b_k.dtype), b_k) + tl.dot((b_do * b_gq[:, None] * scale).to(b_k.dtype), b_h.to(b_k.dtype)) + # [BV, BK] + b_h = b_h * b_gn + tl.dot(b_v, (b_k * b_gk[:, None]).to(b_k.dtype)) + + else: + # [BT, BT] + b_ds *= m_s & m_t + # [BT, BK] + b_dq = tl.dot(b_ds.to(b_k.dtype), b_k) + tl.dot((b_do * scale).to(b_k.dtype), b_h.to(b_k.dtype)) + # [BV, BK] + b_h += tl.dot(b_v, b_k) + + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if USE_FINAL_STATE: + p_dh = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dh, boundary_check=(0, 1)).to(tl.float32) + + if USE_G: + b_dg = tl.zeros([BT], dtype=tl.float32) + b_dg_last = tl.sum(tl.trans(b_h) * b_dh) + + # sync threads + b_h = None + tl.debug_barrier() + + for i_t in range(NT - 1, -1, -1): + p_q = tl.make_block_ptr(q, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + # [BK, BT] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BT, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + last_idx = min(i_t * BT + BT, T) - 1 + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + # [BT, BT] + b_s = tl.dot(b_k, b_q) + b_ds = tl.dot(b_v, tl.trans(b_do)) + + if USE_G: + p_g = g + (bos + o_t) * H + i_h + p_dg = dg + ((i_k * NV + i_v) * all + (bos + o_t)).to(tl.int64) * H + i_h + b_g = tl.load(p_g, mask=m_t, other=0.) + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + + b_gq = exp(b_g) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + b_gs = tl.trans(tl.where(m_s & (m_t[:, None] & m_t), exp(b_g[:, None] - b_g[None, :]), 0)) * scale + + b_s = b_s * b_gs + b_ds = b_ds * b_gs + + # [BT, BK] + b_dk = tl.dot(b_ds.to(b_k.dtype), tl.trans(b_q)) + tl.dot(b_v, tl.trans(b_dh).to(b_v.dtype)) * b_gk[:, None] + + # [BT] + b_dg_t = tl.where(m_t, tl.load(p_dg, mask=m_t, other=0.) - tl.sum(b_k * b_dk, 1), 0) + b_dg_last += tl.sum(b_dg_t, 0) + b_dg = b_dg_last + b_dg_t - tl.cumsum(b_dg_t, 0) + + # [BT, BV] + b_dv = tl.dot(b_s.to(b_do.dtype), b_do) + tl.dot(b_k, b_dh.to(b_k.dtype)) * b_gk[:, None] + # [BK, BV] + b_dh = b_dh * b_gn + tl.dot(b_q, (b_do * b_gq[:, None] * scale).to(b_do.dtype)) + + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), mask=m_t) + + elif USE_G_GAMMA: + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_gk = exp(b_g_last - b_g) + b_gn = exp(b_g_last) + b_gs = tl.trans(tl.where(m_s & (m_t[:, None] & m_t), exp(b_g[:, None] - b_g[None, :]), 0)) * scale + + b_s = b_s * b_gs + b_ds = b_ds * b_gs + + b_dk = tl.dot(b_ds.to(b_k.dtype), tl.trans(b_q)) + tl.dot(b_v, tl.trans(b_dh).to(b_v.dtype)) * b_gk[:, None] + # [BT, BV] + b_dv = tl.dot(b_s.to(b_do.dtype), b_do) + tl.dot(b_k, b_dh.to(b_k.dtype)) * b_gk[:, None] + # [BK, BV] + b_dh = b_dh * b_gn + tl.dot(b_q, (b_do * b_gq[:, None] * scale).to(b_do.dtype)) + + else: + mask = tl.trans(m_s & (m_t[:, None] & m_t)) + b_s = tl.where(mask, b_s * scale, 0).to(b_do.dtype) + b_ds = tl.where(mask, b_ds * scale, 0).to(b_q.dtype) + + b_dk = tl.dot(b_ds, tl.trans(b_q)) + tl.dot(b_v, tl.trans(b_dh).to(b_v.dtype)) + # [BT, BV] + b_dv = tl.dot(b_s.to(b_do.dtype), b_do) + tl.dot(b_k, b_dh.to(b_k.dtype)) + # [BK, BV] + b_dh += tl.dot(b_q, (b_do * scale).to(b_do.dtype)) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + if USE_INITIAL_STATE: + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +def fused_chunk_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *q.shape, v.shape[-1] + BT = chunk_size + BK = min(max(triton.next_power_of_2(K), 16), 64) + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + NK = triton.cdiv(K, BK) + + o = v.new_empty(NK, *v.shape, dtype=torch.float) if NK > 1 else torch.empty_like(v) + ht = k.new_empty(N, H, K, V, dtype=torch.float) if output_final_state else None + def grid(meta): return (triton.cdiv(V, meta['BV']), NK, N * H) + fused_chunk_fwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + o=o, + h0=initial_state, + ht=ht, + cu_seqlens=cu_seqlens, + scale=scale, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + ) + if NK > 1: + o = o.sum(0).to(v) + return o, ht + + +def fused_chunk_bwd( + q, + k, + v, + g, + g_gamma, + do, + scale, + initial_state: torch.Tensor, + dht: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *q.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BT = chunk_size + BK = min(max(triton.next_power_of_2(K), 16), 64) + BV = min(max(triton.next_power_of_2(V), 16), 64) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + + dq = q.new_empty(NV, *q.shape, dtype=torch.float) if NV > 1 else torch.empty_like(q) + dk = k.new_empty(NV, *k.shape, dtype=torch.float) if NV > 1 else torch.empty_like(k) + dv = v.new_empty(NK, *v.shape, dtype=torch.float) if NK > 1 else torch.empty_like(v) + dg = g.new_empty(NK*NV, *g.shape, dtype=torch.float) if g is not None else None + dh0 = torch.empty_like(initial_state) if initial_state is not None else None + + grid = (NV, NK, N * H) + fused_chunk_bwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + do=do, + dq=dq, + dk=dk, + dv=dv, + dg=dg, + h0=initial_state, + dht=dht, + dh0=dh0, + cu_seqlens=cu_seqlens, + scale=scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + dq = dq.sum(0) if NV > 1 else dq + dk = dk.sum(0) if NV > 1 else dk + dv = dv.sum(0) if NK > 1 else dv + if dg is not None: + dg = dg.sum(0).to(g) + + return dq, dk, dv, dg, dh0 + + +class FusedChunkFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q, + k, + v, + g, + g_gamma, + scale, + initial_state, + output_final_state, + cu_seqlens, + ): + chunk_size = min(64, max(16, triton.next_power_of_2(q.shape[1]))) + g = chunk_local_cumsum(g, chunk_size=chunk_size, cu_seqlens=cu_seqlens) if g is not None else None + o, ht = fused_chunk_fwd( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + ctx.save_for_backward(q, k, v, g, g_gamma, initial_state) + ctx.chunk_size = chunk_size + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + return o.to(q.dtype), ht + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht=None): + q, k, v, g, g_gamma, initial_state = ctx.saved_tensors + + dq, dk, dv, dg, dh0 = fused_chunk_bwd( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + do=do, + scale=ctx.scale, + initial_state=initial_state, + dht=dht, + cu_seqlens=ctx.cu_seqlens, + chunk_size=ctx.chunk_size, + ) + if g is not None: + dg = dg.to(g) + return dq.to(q), dk.to(k), dv.to(v), dg, None, None, dh0, None, None + + +def fused_chunk( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H]`. + Compared to GLA, the gating is head-wise instead of elementwise. + g_gamma (torch.Tensor): + Log decay of shape `[H]`. + Head-wise data-independent decay is used if `g_gamma` is provided. + Only one of `g` or `g_gamma` should be provided. + scale (Optional[int]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + """ + if g is not None and g_gamma is not None: + raise ValueError("Only one of `g` or `g_gamma` should be provided.") + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = FusedChunkFunction.apply( + q, + k, + v, + g, + g_gamma, + scale, + initial_state, + output_final_state, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/common/fused_recurrent.py b/code/flash-linear-attention/fla/ops/common/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..1990f872f522f2a784207cf643a085450e53f50f --- /dev/null +++ b/code/flash-linear-attention/fla/ops/common/fused_recurrent.py @@ -0,0 +1,567 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [4, 8] + ], + key=['BK', 'BV', 'USE_G', 'USE_G_GAMMA', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['B', 'T']) +def fused_recurrent_fwd_kernel( + q, + k, + v, + g, + g_gamma, + gk, + gv, + o, + h0, + ht, + cu_seqlens, + scale, + B, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + REVERSE: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + + all = B * T + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + p_q = q + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_k = k + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_o = o + ((i_k * all + bos) + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + if USE_G: + p_g = g + (bos + ((T-1) if REVERSE else 0)) * H + i_h + if USE_GK: + p_gk = gk + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + if USE_GV: + p_gv = gv + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + if USE_G_GAMMA: + b_g_gamma = tl.load(g_gamma + i_h) + + m_k = o_k < K + m_v = o_v < V + m_h = m_k[:, None] & m_v[None, :] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=m_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_q = tl.load(p_q, mask=m_k, other=0).to(tl.float32) * scale + b_k = tl.load(p_k, mask=m_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=m_v, other=0).to(tl.float32) + if USE_G: + b_g = tl.load(p_g).to(tl.float32) + b_h = b_h * exp(b_g) + if USE_G_GAMMA: + b_h = b_h * exp(b_g_gamma) + if USE_GK: + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_h = b_h * exp(b_gk[:, None]) + if USE_GV: + b_gv = tl.load(p_gv, mask=m_v, other=0).to(tl.float32) + b_h = b_h * exp(b_gv[None, :]) + b_h += b_k[:, None] * b_v[None, :] + b_o = b_h * b_q[:, None] + b_o = tl.sum(b_o, axis=0) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=m_v) + p_q += (-1 if REVERSE else 1) * H*K + p_k += (-1 if REVERSE else 1) * H*K + p_v += (-1 if REVERSE else 1) * H*V + p_o += (-1 if REVERSE else 1) * H*V + if USE_G: + p_g += (-1 if REVERSE else 1) * H + if USE_GK: + p_gk += (-1 if REVERSE else 1) * H*K + if USE_GV: + p_gv += (-1 if REVERSE else 1) * H*V + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=m_h) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [4] + ], + key=['BK', 'BV', 'USE_G', 'USE_G_GAMMA', 'USE_GK', 'USE_GV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['B', 'T']) +def fused_recurrent_bwd_kernel( + q, + k, + v, + g, + g_gamma, + gk, + gv, + o, + h0, + do, + dq, + dk, + dv, + dg, + dgk, + dgv, + dht, + dh0, + cu_seqlens, + scale, + B, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + REVERSE: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + + all = B * T + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + NV = tl.cdiv(V, BV) + + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + m_k = o_k < K + m_v = o_v < V + m_h = m_k[:, None] & m_v[None, :] + + p_k = k + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_do = do + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_dq = dq + ((i_v * all + bos) + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + if USE_G: + p_g = g + (bos + ((T-1) if REVERSE else 0)) * H + i_h + if USE_GK: + p_gk = gk + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + if USE_GV: + p_gv = gv + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + if USE_G_GAMMA: + b_g_gamma = tl.load(g_gamma + i_h) + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=m_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_k = tl.load(p_k, mask=m_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=m_v, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=m_v, other=0).to(tl.float32) + if USE_G: + b_g = tl.load(p_g).to(tl.float32) + b_h = b_h * exp(b_g) + if USE_G_GAMMA: + b_h = b_h * exp(b_g_gamma) + if USE_GK: + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_h = b_h * exp(b_gk[:, None]) + if USE_GV: + b_gv = tl.load(p_gv, mask=m_v, other=0).to(tl.float32) + b_h = b_h * exp(b_gv[None, :]) + b_h += b_k[:, None] * b_v[None, :] + b_dq = b_h * b_do[None, :] + b_dq = tl.sum(b_dq, axis=1) * scale + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), mask=m_k) + + p_k += (-1 if REVERSE else 1) * H*K + p_v += (-1 if REVERSE else 1) * H*V + p_do += (-1 if REVERSE else 1) * H*V + p_dq += (-1 if REVERSE else 1) * H*K + if USE_G: + p_g += (-1 if REVERSE else 1) * H + if USE_GK: + p_gk += (-1 if REVERSE else 1) * H*K + if USE_GV: + p_gv += (-1 if REVERSE else 1) * H*V + + # sync threads + tl.debug_barrier() + + p_q = q + (bos + ((T - 1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_k = k + (bos + ((T - 1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T - 1) if not REVERSE else 0)) * H*V + i_h * V + o_v + + p_do = do + (bos + ((T - 1) if not REVERSE else 0)) * H*V + i_h * V + o_v + p_dq = dq + ((i_v * all + bos) + ((T - 1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_dk = dk + ((i_v * all + bos) + ((T - 1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_dv = dv + ((i_k * all + bos) + ((T - 1) if not REVERSE else 0)) * H*V + i_h * V + o_v + if USE_G: + p_g = g + (bos + ((T - 1) if not REVERSE else 0)) * H + i_h + p_dg = dg + ((i_k * NV + i_v) * all + bos + ((T - 1) if not REVERSE else 0)) * H + i_h + if USE_GK: + p_gk = gk + (bos + ((T - 1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_dgk = dgk + ((i_v * all + bos) + ((T - 1) if not REVERSE else 0)) * H*K + i_h * K + o_k + if USE_GV: + p_o = o + (bos + ((T - 1) if not REVERSE else 0)) * H*V + i_h * V + o_v + p_gv = gv + (bos + ((T - 1) if not REVERSE else 0)) * H*V + i_h * V + o_v + p_dgv = dgv + ((i_k * all + bos) + ((T - 1) if not REVERSE else 0)) * H*V + i_h * V + o_v + + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_dht = dht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_dh += tl.load(p_dht, mask=m_h, other=0).to(tl.float32) + + if USE_G: + b_dg = tl.sum(b_h * b_dh) + if USE_GK: + b_dgk = tl.sum(b_h * b_dh, 1) + if USE_GV: + b_dgv = tl.sum(b_h * b_dh, 0) + + for _ in range(T): + b_q = tl.load(p_q, mask=m_k, other=0).to(tl.float32) + b_k = tl.load(p_k, mask=m_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=m_v, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=m_v, other=0).to(tl.float32) + b_dh += (b_q * scale)[:, None] * b_do[None, :] + b_dk = tl.sum(b_dh * b_v[None, :], axis=1) + b_dv = tl.sum(b_dh * b_k[:, None], axis=0) + + if USE_G: + b_g = tl.load(p_g).to(tl.float32) + b_dq = tl.load(p_dq, mask=m_k, other=0).to(tl.float32) + b_dg += tl.sum(b_q * b_dq - b_k * b_dk) + b_dh *= exp(b_g) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty)) + if USE_G_GAMMA: + b_dh *= exp(b_g_gamma) + if USE_GK: + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_dq = tl.load(p_dq, mask=m_k, other=0).to(tl.float32) + b_dgk += b_q * b_dq - b_k * b_dk + b_dh *= exp(b_gk)[:, None] + tl.store(p_dgk, b_dgk.to(p_dgk.dtype.element_ty), mask=m_k) + if USE_GV: + b_o = tl.load(p_o, mask=m_v, other=0).to(tl.float32) + b_gv = tl.load(p_gv, mask=m_v, other=0).to(tl.float32) + if i_k == 0: + b_dgv += b_o * b_do + b_dgv -= b_v * b_dv + b_dh *= exp(b_gv)[None, :] + tl.store(p_dgv, b_dgv.to(p_dgv.dtype.element_ty), mask=m_v) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=m_k) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), mask=m_v) + + p_q += (1 if REVERSE else -1) * H*K + p_k += (1 if REVERSE else -1) * H*K + p_v += (1 if REVERSE else -1) * H*V + + p_do += (1 if REVERSE else -1) * H*V + p_dq += (1 if REVERSE else -1) * H*K + p_dk += (1 if REVERSE else -1) * H*K + p_dv += (1 if REVERSE else -1) * H*V + if USE_G: + p_g += (1 if REVERSE else -1) * H + p_dg += (1 if REVERSE else -1) * H + if USE_GK: + p_gk += (1 if REVERSE else -1) * H*K + p_dgk += (1 if REVERSE else -1) * H*K + if USE_GV: + p_o += (1 if REVERSE else -1) * H*V + p_gv += (1 if REVERSE else -1) * H*V + p_dgv += (1 if REVERSE else -1) * H*V + + if STORE_INITIAL_STATE_GRADIENT: + p_dh0 = dh0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), mask=m_h) + + +def fused_recurrent_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK, BV = min(triton.next_power_of_2(K), 64), min(triton.next_power_of_2(V), 64) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + + h0 = initial_state + ht = q.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + o = q.new_empty(NK, *v.shape, dtype=torch.float32) + + grid = (NV, NK, N * H) + fused_recurrent_fwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + o=o, + h0=h0, + ht=ht, + cu_seqlens=cu_seqlens, + scale=scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + USE_G=g is not None, + USE_G_GAMMA=g_gamma is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + REVERSE=reverse, + ) + o = o.sum(0) + return o, ht + + +def fused_recurrent_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + o: torch.Tensor | None = None, + do: torch.Tensor | None = None, + dht: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + + BK, BV = min(triton.next_power_of_2(K), 64), min(triton.next_power_of_2(V), 64) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + + h0 = initial_state + dq = q.new_empty(NV, *q.shape, dtype=torch.float32) + dk = q.new_empty(NV, *k.shape, dtype=torch.float32) + dv = q.new_empty(NK, *v.shape, dtype=torch.float32) + dh0 = torch.empty_like(h0) if h0 is not None else None + + dg, dgk, dgv = None, None, None + if g is not None: + dg = g.new_empty(NK*NV, *g.shape, dtype=torch.float32) + if gk is not None: + dgk = gk.new_empty(NV, *gk.shape, dtype=torch.float32) + if gv is not None: + dgv = gv.new_empty(NK, *gv.shape, dtype=torch.float32) + + grid = (NV, NK, N * H) + fused_recurrent_bwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + o=o, + h0=h0, + do=do, + dq=dq, + dk=dk, + dv=dv, + dg=dg, + dgk=dgk, + dgv=dgv, + dht=dht, + dh0=dh0, + cu_seqlens=cu_seqlens, + scale=scale, + B=B, + T=T, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + USE_G=g is not None, + USE_G_GAMMA=g_gamma is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + REVERSE=reverse, + ) + dq = dq.sum(0) + dk = dk.sum(0) + dv = dv.sum(0) + if g is not None: + dg = dg.sum(0).to(g) + if gk is not None: + dgk = dgk.sum(0).to(gk) + if gv is not None: + dgv = dgv.sum(0).to(gv) + + return dq, dk, dv, dg, dgk, dgv, dh0 + + +class FusedRecurrentFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + o, ht = fused_recurrent_fwd( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, k, v, g, g_gamma, gk, gv, initial_state, o) + ctx.scale = scale + ctx.reverse = reverse + ctx.cu_seqlens = cu_seqlens + return o.to(q.dtype), ht + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht): + q, k, v, g, g_gamma, gk, gv, initial_state, o = ctx.saved_tensors + dq, dk, dv, dg, dgk, dgv, dh0 = fused_recurrent_bwd( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + o=o, + do=do, + dht=dht, + scale=ctx.scale, + initial_state=initial_state, + reverse=ctx.reverse, + cu_seqlens=ctx.cu_seqlens, + ) + return dq.to(q.dtype), dk.to(k.dtype), dv.to(v.dtype), dg, None, dgk, dgv, None, dh0, None, None, None + + +def fused_recurrent( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + if scale is None: + scale = k.shape[-1] ** -0.5 + return FusedRecurrentFunction.apply( + q, + k, + v, + g, + g_gamma, + gk, + gv, + scale, + initial_state, + output_final_state, + reverse, + cu_seqlens, + ) diff --git a/code/flash-linear-attention/fla/ops/delta_rule/README.md b/code/flash-linear-attention/fla/ops/delta_rule/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7dfe2f01bd140b9464c072c4732808bd38d471e6 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/README.md @@ -0,0 +1,90 @@ +# Chunkwise-form Parallelism of DeltaNet + +This section expands on the formulation presented in Appendix B of the DeltaNet paper.[^1] + +To reduce notational clutter, we focus on the first chunk, denoting $\mathbf{S}^r=\mathbf{S}_{[1]}^r$. By partially expanding the recurrence, we have: +```math +\begin{equation} +\begin{aligned} +\mathbf{S}^r &= \underbrace{\left(\prod_{i=1}^r \mathbf{I} - \beta^i \bf{k}^i \bf{k}^{i\top} \right)}_{:= \mathbf{P}^r} \cdot\mathbf{S}^{0} + \overbrace{\sum_{i=1}^{r} \underbrace{\left(\prod_{j=i+1}^r \mathbf{I} - \beta^j \bf{k}^j \bf{k}^{j\top} \right)}_{:= \mathbf{P}_{i+1}^r}\beta^i \bf{k}^i\bf{v}^{i\top}}^{:=\mathbf{H}^r} \\ +&=\mathbf{P}^r \cdot \mathbf{S}^{0} + \mathbf{H}^r +\end{aligned} +\end{equation} +``` + +where $\mathbf{P}_i^r$ involves cumulative products of generalized Householder matrices. +We abbreviate $\mathbf{P}_1^r$ as $\mathbf{P}^r$. +This can be optimized using the classical WY representation: +```math +\begin{equation} +\mathbf{P}^{r} = \mathbf{I} - \sum_{i=1}^{r}\bf{k}^i\bf{w}^{i\top} \in \mathbb{R}^{d_k \times d_k};\qquad +\bf{w}^r = \beta^r \left(\bf{k}^r - \sum_{i=1}^{r-1} \left(\bf{k}^{r\top}\bf{k}^i \right)\bf{w}^i \right) \in \mathbb{R}^{d_k} +\end{equation} +``` + +We prove this by induction: +```math +\begin{align*} +\mathbf{P}^{r} &= \prod_{i=1}^r \mathbf{I} - \beta^i \bf{k}^i \bf{k}^{i\top} \\ +&= \left(\mathbf{I} - \beta^r \bf{k}^r \bf{k}^{r\top}\right)\mathbf{P}^{r-1} \\ +&= \left(\mathbf{I} - \beta^r \bf{k}^r \bf{k}^{r\top}\right)\left(\mathbf{I} - \sum_{i=1}^{r-1}\bf{k}^i\bf{w}^{i\top}\right) \\ +&= \mathbf{I} - \sum_{i=1}^{r-1}\bf{k}^i\bf{w}^{i\top} - \beta^r \bf{k}^r \bf{k}^{r\top} + \beta^r\bf{k}^r \bf{k}^{r\top} \left(\sum_{i=1}^{r-1}\bf{k}^i\bf{w}^{i\top}\right) \\ +&= \mathbf{I} - \sum_{i=1}^{r-1}\bf{k}^i\bf{w}^{i\top} - \beta^r \bf{k}^r \left(\bf{k}^{r} - \left(\sum_{i=1}^{r-1}\left(\bf{k}^{r\top} \bf{k}^i\right)\bf{w}^{i}\right) \right)^\top \\ +&= \mathbf{I} - \sum_{i=1}^{r}\bf{k}^i\bf{w}^{i\top} +\end{align*} +``` + +Similarly, $\mathbf{H}^r$ can be represented as: +```math +\begin{equation} +\mathbf{H}^{r} = \sum_{i=1}^{r} \bf{k}^i \bf{u}^{i\top} \in \mathbb{R}^{d_k \times d_v};\qquad \bf{u}^r = \beta^r \left(\bf{v}^r - \sum_{i=1}^{r-1} \left(\bf{k}^{r\top}\bf{k}^i\right) \bf{u}^i \right)\in \mathbb{R}^{d_v} +\end{equation} +``` + +This can also be proven by induction: +```math +\begin{align*} +\mathbf{H}^{r} &= \sum_{i=1}^{r} \mathbf{P}_{i+1}^r \beta^i \bf{k}^i \bf{v}^{i\top}\\ +&= \left(\mathbf{I} - \beta^r \bf{k}^r \bf{k}^{r\top}\right) \mathbf{H}^{r-1} + \beta^r \bf{k}^r \bf{v}^{r\top}\\ +&= \sum_{i=1}^{r-1}\bf{k}^i \bf{u}^{i\top} - \beta^r \bf{k}^r \bf{k}^{r\top} \sum_{i=1}^{r-1}\bf{k}^i \bf{u}^{i\top} +\beta^r \bf{k}^r \bf{v}^{r\top}\\ +&= \sum_{i=1}^{r-1}\bf{k}^i \bf{u}^{i\top} + \bf{k}^r \left(\beta^r \bf{v}^{r\top}-\beta^r \bf{k}^{r\top} \sum_{i=1}^{r-1}\bf{k}^i \bf{u}^{i\top}\right) \\ +&= \sum_{i=1}^{r-1}\bf{k}^i \bf{u}^{i\top} + \bf{k}^r \beta^r\left(\bf{v}^{r}-\sum_{i=1}^{r-1}\left(\bf{k}^{r\top}\bf{k}^{i}\right)\bf{u}^{i} \right)^\top \\ +&=\sum_{i=1}^{r} \bf{k}^i \bf{u}^{i\top} +\end{align*} +``` + +In matrix form, $\mathbf{P}$ and $\mathbf{H}$ can be written as: +```math +\begin{equation} +\mathbf{P}=\mathbf{I}-\mathbf{K}^\top\mathbf{W} \in \mathbb{R}^{d_k \times d_k}, \qquad\mathbf{H}=\mathbf{K}^\top\mathbf{U} \in \mathbb{R}^{d_k\times d_v} +\end{equation} +``` + +Now we can derive the matrix form of $\mathbf{W}$ and $\mathbf{U}$: +```math +\begin{align*} +\mathbf{W} &= \mathrm{diag}(\beta) \mathbf{K} - \mathrm{tril}(\mathrm{diag}(\beta) \mathbf{K}\mathbf{K}^\top, -1)\mathbf{W}\\ +\left(\mathbf{I} + \mathrm{tril}(\mathrm{diag}(\beta) \mathbf{K}\mathbf{K}^\top, -1)\right) \mathbf{W} &= \mathrm{diag}(\beta) \mathbf{K} +\end{align*} +``` +A similar process holds for $\mathbf{U}$. We can further write $\mathbf{W}$ and $\mathbf{U}$ in matrix form: +```math +\begin{align*} +\mathbf{T} &= \left(\mathbf{I} + \mathrm{tril}\left(\mathrm{diag}(\beta)\mathbf{K} \mathbf{K}^\top,-1\right)\right)^{-1}\mathrm{diag}\left(\beta\right)\in \mathbb{R}^{C \times C}\\ +\mathbf{W} &= \mathbf{T} \mathbf{K}\in \mathbb{R}^{C \times d_k}\\ +\mathbf{U} &= \mathbf{T}\mathbf{V}\in \mathbb{R}^{C \times d_v} +\end{align*} +``` + +Substituting these back into the original equations yields a hardware-efficient chunkwise algorithm for DeltaNet that leverages matrix multiplications, enabling tensor core based GPU optimization: +```math +\begin{equation} +\begin{aligned} +\mathbf{S} &= \mathbf{P}\cdot\mathbf{S}^0 + \mathbf{H} \\ +&= \mathbf{S}^0 + \mathbf{K}^\top (\mathbf{U} -\mathbf{W} \mathbf{S}^0) \in \mathbb{R}^{d_k \times d_v}\\ +\mathbf{O} &= \mathbf{Q} \mathbf{S}^0 + (\mathbf{Q} \mathbf{K}^{\top} \odot \mathbf{M}) \left(\mathbf{U} - \mathbf{W} \mathbf{S}^0\right) \in \mathbb{R}^{C \times d_v} +\end{aligned} +\end{equation} +``` + +[^1]: https://arxiv.org/abs/2406.06484 diff --git a/code/flash-linear-attention/fla/ops/delta_rule/__init__.py b/code/flash-linear-attention/fla/ops/delta_rule/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..664658684f8e0e8d78467219979740a4bc5ef6c9 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/__init__.py @@ -0,0 +1,10 @@ + +from .chunk import chunk_delta_rule +from .fused_chunk import fused_chunk_delta_rule +from .fused_recurrent import fused_recurrent_delta_rule + +__all__ = [ + 'fused_chunk_delta_rule', + 'fused_recurrent_delta_rule', + 'chunk_delta_rule', +] diff --git a/code/flash-linear-attention/fla/ops/delta_rule/chunk.py b/code/flash-linear-attention/fla/ops/delta_rule/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..7607a14a4ad8e30749cef45b8cb6f0eeb2f4eb74 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/chunk.py @@ -0,0 +1,309 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_bwd_dhu, chunk_gated_delta_rule_fwd_h +from fla.ops.common.chunk_o import chunk_bwd_dqkwg, chunk_bwd_dv_local, chunk_fwd_o +from fla.ops.delta_rule.wy_fast import prepare_wy_repr_bwd, prepare_wy_repr_fwd, recompute_w_u_fwd +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_delta_rule_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, +): + # obtain WY representation. u is actually the new v. + w, u, A = prepare_wy_repr_fwd( + k=k, + v=v, + beta=beta, + cu_seqlens=cu_seqlens, + ) + h, v_new, final_state = chunk_gated_delta_rule_fwd_h( + k=k, + w=w, + u=u, + g=None, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + + o = chunk_fwd_o( + q=q, + k=k, + v=v_new, + h=h, + g=None, + scale=scale, + cu_seqlens=cu_seqlens, + ) + return o, A, final_state + + +def chunk_delta_rule_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, +): + w, u = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + cu_seqlens=cu_seqlens, + ) + h, v_new, _ = chunk_gated_delta_rule_fwd_h( + k=k, + w=w, + u=u, + g=None, + initial_state=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + ) + dv = chunk_bwd_dv_local( + q=q, + k=k, + do=do, + g=None, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dh, dh0, dv = chunk_gated_delta_rule_bwd_dhu( + q=q, + k=k, + w=w, + g=None, + h0=initial_state, + dht=dht, + do=do, + dv=dv, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dq, dk, dw, _ = chunk_bwd_dqkwg( + q=q, + k=k, + v=v_new, + h=h, + w=w, + dv=dv, + do=do, + dh=dh, + g=None, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dk2, dv, db = prepare_wy_repr_bwd( + k=k, + v=v, + beta=beta, + A=A, + dw=dw, + du=dv, + cu_seqlens=cu_seqlens, + ) + dk.add_(dk2) + return dq, dk, dv, db, dh0 + + +class ChunkDeltaRuleFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q) + k, k_rstd = l2norm_fwd(k) + else: + q_rstd, k_rstd = None, None + + o, A, final_state = chunk_delta_rule_fwd( + q=q, + k=k, + v=v, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, q_rstd, k, k_rstd, v, beta, A, initial_state) + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + q, q_rstd, k, k_rstd, v, beta, A, initial_state = ctx.saved_tensors + + dq, dk, dv, db, dh0 = chunk_delta_rule_bwd( + q=q, + k=k, + v=v, + beta=beta, + A=A, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=ctx.cu_seqlens, + ) + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + return dq.to(q.dtype), dk.to(k.dtype), dv.to(v.dtype), db.to(beta.dtype), None, dh0, None, None, None, None, None + + +@torch.compiler.disable +def chunk_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (Optional[bool]): + Whether to use qk l2norm within the kernel for saving GPU memory. + Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.delta_rule import chunk_delta_rule + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') + >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid() + >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda') + >>> o, ht = chunk_delta_rule( + q, k, v, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = chunk_delta_rule( + q, k, v, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + assert q.dtype == k.dtype == v.dtype + assert q.dtype != torch.float32, "ChunkDeltaRuleFunction does not support float32. Please use bfloat16." + assert len(beta.shape) == 3, "beta must be of shape (batch size, num of head, seq len)." + + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + scale = k.shape[-1] ** -0.5 if scale is None else scale + o, final_state = ChunkDeltaRuleFunction.apply( + q, + k, + v, + beta, + scale, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/delta_rule/fused_chunk.py b/code/flash-linear-attention/fla/ops/delta_rule/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..36231fa608d474ce3406da7b8fe089a8343187c9 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/fused_chunk.py @@ -0,0 +1,5 @@ + +def fused_chunk_delta_rule( + **kwargs, +): + raise NotImplementedError("fused_chunk_delta_rule is deprecated. Please use chunk_delta_rule instead.") diff --git a/code/flash-linear-attention/fla/ops/delta_rule/fused_recurrent.py b/code/flash-linear-attention/fla/ops/delta_rule/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..f944f02903efced2f44ec444b4885bc5f2c47a03 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/fused_recurrent.py @@ -0,0 +1,533 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.utils import input_guard + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_delta_rule_fwd_kernel( + q, + k, + v, + u, + beta, + o, + h0, + ht, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_BETA_HEADWISE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + all = B * T + + p_q = q + (bos * H + i_h) * K + i_k * BK + tl.arange(0, BK) + p_k = k + (bos * H + i_h) * K + i_k * BK + tl.arange(0, BK) + p_v = v + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + p_u = u + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + if IS_BETA_HEADWISE: + p_beta = beta + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + else: + p_beta = beta + bos * H + i_h + p_o = o + ((i_k * all + bos) * H + i_h) * V + i_v * BV + tl.arange(0, BV) + + mask_k = (i_k * BK + tl.arange(0, BK)) < K + mask_v = (i_v * BV + tl.arange(0, BV)) < V + mask_h = mask_k[None, :] & mask_v[:, None] + + b_h = tl.zeros([BV, BK], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K * V + (i_k * BK + tl.arange(0, BK)[None, :]) * V + (i_v * BV + tl.arange(0, BV)[:, None]) + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) * scale + b_v_minus = tl.sum(b_h * b_k[None, :], axis=1) + b_v -= b_v_minus + if IS_BETA_HEADWISE: + b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32) + else: + b_beta = tl.load(p_beta).to(tl.float32) + tl.store(p_u, b_v.to(p_v.dtype.element_ty), mask=mask_v) + b_v *= b_beta + b_h += b_k[None, :] * b_v[:, None] + b_o = b_h * b_q[None, :] + b_o = tl.sum(b_o, axis=1) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + + p_q += H*K + p_k += H*K + p_o += H*V + p_v += H*V + p_u += H*V + p_beta += H * (V if IS_BETA_HEADWISE else 1) + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K * V + (i_k * BK + tl.arange(0, BK)[None, :]) * V + (i_v * BV + tl.arange(0, BV)[:, None]) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_delta_rule_bwd_kernel( + q, + k, + v, + beta, + h0, + dh0, + dht, + do, + dq, + dk, + dv, + db, + cu_seqlens, + scale, + B: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NK: tl.constexpr, + IS_BETA_HEADWISE: tl.constexpr, # whether beta is headwise vector or scalar + USE_INITIAL_STATE: tl.constexpr, # whether to use dh0 + USE_FINAL_STATE_GRADIENT: tl.constexpr, # whether to use dht + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + all = B * T + + mask_k = i_k * BK + tl.arange(0, BK) < K + mask_v = i_v * BV + tl.arange(0, BV) < V + + p_q = q + (bos * H + i_h) * K + i_k * BK + tl.arange(0, BK) + (T - 1) * H*K + p_k = k + (bos * H + i_h) * K + i_k * BK + tl.arange(0, BK) + (T - 1) * H*K + p_v = v + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + (T - 1) * H*V + p_do = do + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + (T - 1) * H*V + p_dk = dk + ((i_v * all + bos) * H + i_h) * K + i_k * BK + tl.arange(0, BK) + (T - 1) * H*K + p_dv = dv + ((i_k * all + bos) * H + i_h) * V + i_v * BV + tl.arange(0, BV) + (T - 1) * H*V + if IS_BETA_HEADWISE: + p_beta = beta + (bos + T - 1) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + p_dbeta = db + ((i_v * NK + i_k) * all + bos + T - 1) * H*V + i_h * V + tl.arange(0, BV) + else: + p_beta = beta + (bos + T - 1) * H + i_h + p_dbeta = db + (i_v * all + bos + T - 1) * H + i_h + + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_ht = dht + i_nh * K * V + (i_k * BK + tl.arange(0, BK)[:, None]) * V + (i_v * BV + tl.arange(0, BV)[None, :]) + b_dh += tl.load(p_ht, mask=mask_k[:, None] & mask_v[None, :], other=0).to(tl.float32) + + for _ in range(T): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) * scale + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask_v, other=0).to(tl.float32) + if IS_BETA_HEADWISE: + b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32) + else: + b_beta = tl.load(p_beta).to(tl.float32) + b_dh += b_q[:, None] * b_do[None, :] + b_dk = tl.sum(b_dh * (b_v * b_beta)[None, :], axis=1) + b_dv = tl.sum(b_dh * b_k[:, None], axis=0) + + b_db = b_dv * b_v if IS_BETA_HEADWISE else tl.sum(b_dv * b_v) + b_dv = b_dv * b_beta + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=mask_k) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), mask=mask_v) + if IS_BETA_HEADWISE: + tl.store(p_dbeta, b_db.to(p_dbeta.dtype.element_ty), mask=mask_v) + else: + tl.store(p_dbeta, b_db.to(p_dbeta.dtype.element_ty)) + + b_dh -= b_k[:, None] * b_dv[None, :] + + p_q -= H*K + p_k -= H*K + p_v -= H*V + p_do -= H*V + p_dk -= H*K + p_dv -= H*V + p_dbeta -= H * (V if IS_BETA_HEADWISE else 1) + p_beta -= H * (V if IS_BETA_HEADWISE else 1) + + if USE_INITIAL_STATE: + p_dh0 = dh0 + i_nh * K * V + (i_k * BK + tl.arange(0, BK)[:, None]) * V + (i_v * BV + tl.arange(0, BV)[None, :]) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), mask=mask_k[:, None] & mask_v[None, :]) + + tl.debug_barrier() + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + + p_q = q + (bos * H + i_h) * K + i_k * BK + tl.arange(0, BK) + p_k = k + (bos * H + i_h) * K + i_k * BK + tl.arange(0, BK) + p_v = v + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + if IS_BETA_HEADWISE: + p_beta = beta + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + else: + p_beta = beta + bos * H + i_h + p_do = do + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + p_dq = dq + ((i_v * all + bos) * H + i_h) * K + i_k * BK + tl.arange(0, BK) + p_dk = dk + ((i_v * all + bos) * H + i_h) * K + i_k * BK + tl.arange(0, BK) + p_dv = dv + ((i_k * all + bos) * H + i_h) * V + i_v * BV + tl.arange(0, BV) + + if USE_INITIAL_STATE: + mask_h = mask_k[:, None] & mask_v[None, :] + p_h0 = h0 + i_nh * K * V + (i_k * BK + tl.arange(0, BK)[:, None]) * V + (i_v * BV + tl.arange(0, BV)[None, :]) + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_dk = tl.load(p_dk, mask=mask_k, other=0).to(tl.float32) + b_dv = tl.load(p_dv, mask=mask_v, other=0).to(tl.float32) + b_dk -= tl.sum(b_dv[None, :] * b_h, axis=1) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=mask_k) + + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask_v, other=0).to(tl.float32) + if IS_BETA_HEADWISE: + b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32) + else: + b_beta = tl.load(p_beta).to(tl.float32) + b_v *= b_beta + + b_h += b_k[:, None] * b_v[None, :] + b_dq = b_h * b_do[None, :] + d_q = tl.sum(b_dq, axis=1) * scale + tl.store(p_dq, d_q.to(p_dq.dtype.element_ty), mask=mask_k) + + p_k += H*K + p_v += H*V + p_do += H*V + p_dq += H*K + p_dk += H*K + p_dv += H*V + p_beta += H * (V if IS_BETA_HEADWISE else 1) + + +def fused_recurrent_delta_rule_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 8) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + assert NK == 1, "NK > 1 is not supported yet" + num_stages = 1 + num_warps = 1 + + o = q.new_empty(NK, *v.shape) + if output_final_state: + final_state = q.new_empty(N, H, K, V, dtype=torch.float32) + else: + final_state = None + + grid = (NV, NK, N * H) + u = torch.empty_like(v) + fused_recurrent_delta_rule_fwd_kernel[grid]( + q, + k, + v, + u, + beta, + o, + initial_state, + final_state, + cu_seqlens, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + IS_BETA_HEADWISE=beta.ndim == v.ndim, + num_warps=num_warps, + num_stages=num_stages, + ) + o = o.squeeze(0) + return o, u, final_state + + +def fused_recurrent_delta_rule_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + dht: torch.Tensor, + do: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 32) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + assert NK == 1, "NK > 1 is not supported yet" + num_stages = 1 + num_warps = 2 + + beta_vector = beta.ndim == v.ndim + + dq = q.new_empty(NV, *q.shape) + dk = q.new_empty(NV, *k.shape) + dv = q.new_empty(NK, *v.shape) + if beta_vector: + db = q.new_empty(NV, NK, B, T, H, V) + else: + db = q.new_empty(NV, B, T, H) + grid = (NV, NK, N * H) + + if initial_state is not None and initial_state.requires_grad: + dh0 = torch.empty_like(initial_state, dtype=torch.float32) + else: + dh0 = None + + fused_recurrent_delta_rule_bwd_kernel[grid]( + q, + k, + v, + beta, + initial_state, + dh0, + dht, + do, + dq, + dk, + dv, + db, + cu_seqlens, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + NK=NK, + IS_BETA_HEADWISE=beta_vector, + num_warps=num_warps, + num_stages=num_stages, + ) + dq = dq.sum(0) + dk = dk.sum(0) + dv = dv.sum(0) + db = db.sum((0, 1)) if beta_vector else db.sum(0) + + return dq, dk, dv, db, dh0 + + +class FusedRecurrentFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q) + k, k_rstd = l2norm_fwd(k) + else: + q_rstd, k_rstd = None, None + + o, u, final_state = fused_recurrent_delta_rule_fwd( + q=q, + k=k, + v=v, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + + ctx.save_for_backward(q, q_rstd, k, k_rstd, u, beta, initial_state) + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + return o, final_state + + @staticmethod + @input_guard + def backward(ctx, do, dht): + q, q_rstd, k, k_rstd, v, beta, initial_state = ctx.saved_tensors + dq, dk, dv, db, dh0 = fused_recurrent_delta_rule_bwd( + q=q, + k=k, + v=v, + beta=beta, + dht=dht, + do=do, + scale=ctx.scale, + initial_state=initial_state, + cu_seqlens=ctx.cu_seqlens, + ) + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + return dq.to(q), dk.to(k), dv.to(v), db.to(beta), None, dh0, None, None, None + + +@torch.compiler.disable +def fused_recurrent_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (Optional[bool]): + Whether to use L2 normalization in the kernel. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.delta_rule import fused_recurrent_delta_rule + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> beta = torch.rand(B, T, H, device='cuda').sigmoid() + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = fused_recurrent_delta_rule( + q, k, v, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = fused_recurrent_delta_rule( + q, k, v, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + else: + assert scale > 0, "scale must be positive" + if beta is None: + beta = torch.ones_like(q[..., 0]) + o, final_state = FusedRecurrentFunction.apply( + q, + k, + v, + beta, + scale, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/delta_rule/naive.py b/code/flash-linear-attention/fla/ops/delta_rule/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..a0cf44ef1756a42036bc69635c48cc581f5a3e68 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/naive.py @@ -0,0 +1,119 @@ + +import torch +from einops import rearrange + + +def delta_rule_recurrence(q, k, v, beta, initial_state=None, output_final_state=True): + orig_dtype = q.dtype + b, h, l, d_k = q.shape + q, k, v, beta = map(lambda x: x.float(), [q, k, v, beta]) + d_v = v.shape[-1] + o = torch.zeros_like(v) + S = torch.zeros(b, h, d_k, d_v).to(v) + q = q * (d_k ** -0.5) + + if beta.ndim < v.ndim: + beta = beta[..., None] + + if initial_state is not None: + S += initial_state + + for i in range(l): + _k = k[:, :, i] + _q = q[:, :, i] + _v = v[:, :, i].clone() + beta_i = beta[:, :, i] + _v = _v - (S.clone() * _k[..., None]).sum(-2) + _v = _v * beta_i + S = S.clone() + _k.unsqueeze(-1) * _v.unsqueeze(-2) + o[:, :, i] = torch.einsum('bhd,bhdm->bhm', _q, S) + S = None if output_final_state is False else S + return o.to(orig_dtype), S + + +def delta_rule_chunkwise(q, k, v, beta, chunk_size=32): + b, h, l, d_k = q.shape + d_v = v.shape[-1] + q = q * (d_k ** -0.5) + v = v * beta[..., None] + k_beta = k * beta[..., None] + + assert l % chunk_size == 0 + + # compute (I - tri(diag(beta) KK^T))^{-1} + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0) + q, k, v, k_beta = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size), [q, k, v, k_beta]) + attn = -(k_beta @ k.transpose(-1, -2)).masked_fill(mask, 0) + for i in range(1, chunk_size): + attn[..., i, :i] = attn[..., i, :i] + (attn[..., i, :, None].clone() * attn[..., :, :i].clone()).sum(-2) + attn = attn + torch.eye(chunk_size, dtype=torch.float, device=q.device) + + u = attn @ v + w = attn @ k_beta + S = k.new_zeros(b, h, d_k, d_v) + o = torch.zeros_like(v) + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1) + for i in range(0, l // chunk_size): + q_i, k_i = q[:, :, i], k[:, :, i] + attn = (q_i @ k_i.transpose(-1, -2)).masked_fill_(mask, 0) + u_i = u[:, :, i] - w[:, :, i] @ S + o_inter = q_i @ S + o[:, :, i] = o_inter + attn @ u_i + S = S + k_i.transpose(-1, -2) @ u_i + + return rearrange(o, 'b h n c d -> b h (n c) d'), S + + +def delta_rule_parallel(q, k, v, beta, BM=128, BN=32): + b, h, l, d_k = q.shape + # d_v = v.shape[-1] + q = q * (d_k ** -0.5) + v = v * beta[..., None] + k_beta = k * beta[..., None] + # compute (I - tri(diag(beta) KK^T))^{-1} + q, k, v, k_beta = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=BN), [q, k, v, k_beta]) + mask = torch.triu(torch.ones(BN, BN, dtype=torch.bool, device=q.device), diagonal=0) + T = -(k_beta @ k.transpose(-1, -2)).masked_fill(mask, 0) + for i in range(1, BN): + T[..., i, :i] = T[..., i, :i].clone() + (T[..., i, :, None].clone() * T[..., :, :i].clone()).sum(-2) + T = T + torch.eye(BN, dtype=torch.float, device=q.device) + + mask2 = torch.triu(torch.ones(BN, BN, dtype=torch.bool, device=q.device), diagonal=1) + A_local = (q @ k.transpose(-1, -2)).masked_fill(mask2, 0) @ T + o_intra = A_local @ v + + # apply cumprod transition matrices on k to the last position within the chunk + k = k - ((k @ k.transpose(-1, -2)).masked_fill(mask, 0) @ T).transpose(-1, -2) @ k_beta + # apply cumprod transition matrices on q to the first position within the chunk + q = q - A_local @ k_beta + o_intra = A_local @ v + + A = torch.zeros(b, h, l, l, device=q.device) + + q, k, v, k_beta, o_intra = map(lambda x: rearrange(x, 'b h n c d -> b h (n c) d'), [q, k, v, k_beta, o_intra]) + o = torch.empty_like(v) + for i in range(0, l, BM): + q_i = q[:, :, i:i+BM] + o_i = o_intra[:, :, i:i+BM] + # intra block + for j in range(i + BM - 2 * BN, i-BN, -BN): + k_j = k[:, :, j:j+BN] + A_ij = q_i @ k_j.transpose(-1, -2) + mask = torch.arange(i, i+BM) >= (j + BN) + A_ij = A_ij.masked_fill_(~mask[:, None].to(A_ij.device), 0) + A[:, :, i:i+BM, j:j+BN] = A_ij + q_i = q_i - A_ij @ k_beta[:, :, j:j+BN] + o_i += A_ij @ v[:, :, j:j+BN] + # inter block + for j in range(i - BN, -BN, -BN): + k_j = k[:, :, j:j+BN] + A_ij = q_i @ k_j.transpose(-1, -2) + A[:, :, i:i+BM, j:j+BN] = A_ij + q_i = q_i - A_ij @ k_beta[:, :, j:j+BN] + o_i += A_ij @ v[:, :, j:j+BN] + o[:, :, i:i+BM] = o_i + + for i in range(0, l//BN): + A[:, :, i*BN:i*BN+BN, i*BN:i*BN+BN] = A_local[:, :, i] + + return o, A diff --git a/code/flash-linear-attention/fla/ops/delta_rule/parallel.py b/code/flash-linear-attention/fla/ops/delta_rule/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..903c1e631923c09fb85d203dffda02b3f59f602b --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/parallel.py @@ -0,0 +1,403 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl +from einops import rearrange + +from fla.ops.delta_rule.wy_fast import fwd_prepare_T +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BT', 'K', 'V'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_transform_qk_fwd_kernel( + q, + k, + v, + beta, + o, + A, + q_new, + k_new, + A_local, + scale, + T, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + BT: tl.constexpr, + OUTPUT_ATTENTIONS: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + b_q = (tl.load(p_q, boundary_check=(0, 1)) * scale).to(p_q.dtype.element_ty) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + + p_T = tl.make_block_ptr(A + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_T = tl.load(p_T, boundary_check=(0, 1)) + + o_i = tl.arange(0, BT) + m_t = o_i[:, None] >= o_i[None, :] + b_qk = tl.where(m_t, tl.dot(b_q, tl.trans(b_k), allow_tf32=False), 0).to(b_q.dtype) + m_t = o_i[:, None] > o_i[None, :] + b_kk = tl.where(m_t, tl.dot(b_k, tl.trans(b_k), allow_tf32=False), 0).to(b_k.dtype) + + p_beta = tl.make_block_ptr(beta + i_bh * T, (T, ), (1, ), (i_t * BT, ), (BT, ), (0, )) + b_beta = tl.load(p_beta, boundary_check=(0, )) + b_k_beta = (b_k * b_beta[:, None]).to(b_k.dtype) + + b_qkT = tl.dot(b_qk, b_T, allow_tf32=False).to(b_k.dtype) + + if OUTPUT_ATTENTIONS: + p_a = tl.make_block_ptr(A_local + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_a, b_qkT.to(p_a.dtype.element_ty), boundary_check=(0, 1)) + + b_kkT = tl.dot(b_kk, b_T, allow_tf32=False).to(b_k.dtype) + p_o = tl.make_block_ptr(o + i_bh * T*V, (T, V), (V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + tl.store(p_o, tl.dot(b_qkT, b_v).to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + p_q_new = tl.make_block_ptr(q_new + i_bh * T*K, (T, K), (K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_q_new, (b_q - tl.dot(b_qkT, b_k_beta, allow_tf32=False)).to(p_q_new.dtype.element_ty), boundary_check=(0, 1)) + + p_k_new = tl.make_block_ptr(k_new + i_bh * T*K, (T, K), (K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_k_new = b_k - tl.dot(tl.trans(b_kkT), b_k_beta, allow_tf32=False) + tl.store(p_k_new, b_k_new.to(p_k_new.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_transform_qk_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + scale: float, + chunk_size: int, + output_attentions: bool, +): + B, H, T, K = k.shape + BT = chunk_size + q_new = torch.empty_like(q) + k_new = torch.empty_like(k) + o = torch.empty_like(v) + grid = (triton.cdiv(T, BT), B*H) + V = v.shape[-1] + A_local = torch.empty_like(A) if output_attentions else None + chunk_transform_qk_fwd_kernel[grid]( + q, + k, + v, + beta, + o, + A, + q_new, + k_new, + A_local, + scale=scale, + T=T, + K=K, + V=V, + BT=BT, + BK=triton.next_power_of_2(K), + BV=triton.next_power_of_2(V), + OUTPUT_ATTENTIONS=output_attentions, + ) + return q_new, k_new, o, A_local + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def save_intra_chunk_attn( + A, + A_local, + T, + BT: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + p_A = tl.make_block_ptr(A + i_bh * T * T, (T, T), (T, 1), (i_t * BT, i_t * BT), (BT, BT), (1, 0)) + p_A_local = tl.make_block_ptr(A_local + i_bh * T * BT, (T, BT), (BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_A_local = tl.load(p_A_local, boundary_check=(0, 1)) + tl.store(p_A, b_A_local.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'OUTPUT_ATTENTIONS': lambda args: args['attn'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_delta_rule_fwd_kernel( + q, + k, + k2, # original k + v, + beta, + o, + o_new, + attn, + T, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + OUTPUT_ATTENTIONS: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + + # the Q block is kept in the shared memory throughout the whole kernel + # [BT, BK] + b_q = tl.zeros([BT, BK], dtype=tl.float32) + b_q += tl.load(p_q, boundary_check=(0, 1)) + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + p_o = tl.make_block_ptr(o + i_bh * T*V, (T, V), (V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + b_o += tl.load(p_o, boundary_check=(0, 1)) + + # As opposed to Flashattention, this kernel requires scanning the KV blocks from right to left + # Q block and K block have overlap. + # masks required + for offset in range((i_t + 1) * BT - 2 * BS, i_t * BT - BS, -BS): + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (0, offset), (BK, BS), (0, 1)) + p_k2 = tl.make_block_ptr(k2 + i_bh * T*K, (T, K), (K, 1), (offset, 0), (BS, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (offset, 0), (BS, BV), (1, 0)) + p_beta = tl.make_block_ptr(beta + i_bh * T, (T, ), (1, ), (offset, ), (BS, ), (0,)) + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BS] + b_beta = tl.load(p_beta, boundary_check=(0,)) + # [BT, BS] + m_s = tl.arange(0, BT) >= (offset - i_t*BT + BS) + b_s = tl.dot(b_q.to(b_k.dtype), b_k, allow_tf32=False) + b_s = tl.where(m_s[:, None], b_s, 0) + + b_o += tl.dot(b_s.to(b_v.dtype), b_v, allow_tf32=False) + b_k2 = (tl.load(p_k2, boundary_check=(0, 1)) * b_beta[:, None]).to(b_v.dtype) + b_q -= tl.dot(b_s.to(b_v.dtype), b_k2, allow_tf32=False) + + if OUTPUT_ATTENTIONS: + p_a = tl.make_block_ptr(attn + i_bh * T * T, (T, T), (T, 1), (i_t * BT, offset), (BT, BS), (1, 0)) + tl.store(p_a, b_s.to(p_a.dtype.element_ty), boundary_check=(0, 1)) + + # Q block and K block have no overlap + # no need for mask, thereby saving flops + for offset in range(i_t * BT - BS, -BS, -BS): + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (0, offset), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (offset, 0), (BS, BV), (1, 0)) + p_beta = tl.make_block_ptr(beta + i_bh * T, (T, ), (1, ), (offset, ), (BS, ), (0,)) + p_k2 = tl.make_block_ptr(k2 + i_bh * T*K, (T, K), (K, 1), (offset, 0), (BS, BK), (1, 0)) + + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BS] + b_beta = tl.load(p_beta, boundary_check=(0,)) + # [BT, BS] + b_s = (tl.dot(b_q.to(b_k.dtype), b_k, allow_tf32=False)) + # [BT, BV] + b_o += tl.dot(b_s.to(b_v.dtype), b_v, allow_tf32=False) + b_k2 = (tl.load(p_k2, boundary_check=(0, 1)) * b_beta[:, None]).to(b_v.dtype) + b_q -= tl.dot(b_s.to(b_v.dtype), b_k2, allow_tf32=False).to(b_q.dtype) + + if OUTPUT_ATTENTIONS: + p_a = tl.make_block_ptr(attn + i_bh * T * T, (T, T), (T, 1), (i_t * BT, offset), (BT, BS), (1, 0)) + tl.store(p_a, b_s.to(p_a.dtype.element_ty), boundary_check=(0, 1)) + + p_o_new = tl.make_block_ptr(o_new + i_bh * T*V, (T, V), (V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + tl.store(p_o_new, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +class ParallelDeltaRuleFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, beta, scale, output_attentions): + B, H, T, K, V = *k.shape, v.shape[-1] + assert q.shape[-1] <= 128, 'The maximum supported sequence length is 128.' + BT, BS = 128, 32 + BK = triton.next_power_of_2(k.shape[-1]) + BV = triton.next_power_of_2(v.shape[-1]) + assert BT % BS == 0 + + A = fwd_prepare_T(k, beta, BS) + attn = q.new_zeros(B, H, T, T) if output_attentions else None + q_new, k_new, o, A_local = chunk_transform_qk_fwd( + q, + k, + v, + beta, + A, + scale, + BS, + output_attentions, + ) + + num_stages = 3 if K <= 64 else 2 + num_warps = 4 + grid = (triton.cdiv(T, BT), B * H) + o_new = torch.empty_like(o) + + parallel_delta_rule_fwd_kernel[grid]( + q=q_new, + k=k_new, + k2=k, + v=v, + beta=beta, + o=o, + o_new=o_new, + attn=attn, + T=T, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + num_stages=num_stages, + num_warps=num_warps, + ) + + if output_attentions: + grid = (triton.cdiv(T, BS), B * H) + save_intra_chunk_attn[grid]( + A=attn, + A_local=A_local, + T=T, + BT=BS, + ) + return o_new.to(q.dtype), attn + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, d_attn=None): + raise NotImplementedError('Backward pass is not implemented. Stay tuned!') + + +def parallel_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + scale: float = None, + output_attentions: bool = False, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + output_attentions (bool): + Whether to output the materialized attention scores of shape [B, H, T, T]. Default: `False`. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + attn (torch.Tensor): + Attention scores of shape `[B, H, T, T]` if `output_attentions=True` else `None`. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + o, attn = ParallelDeltaRuleFunction.apply(q, k, v, beta, scale, output_attentions) + return o, attn + + +def naive_delta_rule_parallel(q, k, v, beta, BM=128, BN=32): + b, h, l, d_k = q.shape + q = q * (d_k ** -0.5) + v = v * beta[..., None] + k_beta = k * beta[..., None] + # compute (I - tri(diag(beta) KK^T))^{-1} + q, k, v, k_beta = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=BN), [q, k, v, k_beta]) + mask = torch.triu(torch.ones(BN, BN, dtype=torch.bool, device=q.device), diagonal=0) + T = -(k_beta @ k.transpose(-1, -2)).masked_fill(mask, 0) + for i in range(1, BN): + T[..., i, :i] = T[..., i, :i].clone() + (T[..., i, :, None].clone() * T[..., :, :i].clone()).sum(-2) + T = T + torch.eye(BN, dtype=q.dtype, device=q.device) + + mask2 = torch.triu(torch.ones(BN, BN, dtype=torch.bool, device=q.device), diagonal=1) + A_local = (q @ k.transpose(-1, -2)).masked_fill(mask2, 0) @ T + o_intra = A_local @ v + + # apply cumprod transition matrices on k to the last position within the chunk + k = k - ((k @ k.transpose(-1, -2)).masked_fill(mask, 0) @ T).transpose(-1, -2) @ k_beta + # apply cumprod transition matrices on q to the first position within the chunk + q = q - A_local @ k_beta + o_intra = A_local @ v + + A = torch.zeros(b, h, l, l, device=q.device) + + q, k, v, k_beta, o_intra = map(lambda x: rearrange(x, 'b h n c d -> b h (n c) d'), [q, k, v, k_beta, o_intra]) + o = torch.empty_like(v) + for i in range(0, l, BM): + q_i = q[:, :, i:i+BM] + o_i = o_intra[:, :, i:i+BM] + # intra block + for j in range(i + BM - 2 * BN, i-BN, -BN): + k_j = k[:, :, j:j+BN] + A_ij = q_i @ k_j.transpose(-1, -2) + mask = torch.arange(i, i+BM) >= (j + BN) + A_ij = A_ij.masked_fill_(~mask[:, None].to(A_ij.device), 0) + A[:, :, i:i+BM, j:j+BN] = A_ij + q_i = q_i - A_ij @ k_beta[:, :, j:j+BN] + o_i += A_ij @ v[:, :, j:j+BN] + # inter block + for j in range(i - BN, -BN, -BN): + k_j = k[:, :, j:j+BN] + A_ij = q_i @ k_j.transpose(-1, -2) + A[:, :, i:i+BM, j:j+BN] = A_ij + q_i = q_i - A_ij @ k_beta[:, :, j:j+BN] + o_i += A_ij @ v[:, :, j:j+BN] + o[:, :, i:i+BM] = o_i + + for i in range(0, l//BN): + A[:, :, i*BN:i*BN+BN, i*BN:i*BN+BN] = A_local[:, :, i] + + return o, A diff --git a/code/flash-linear-attention/fla/ops/delta_rule/wy_fast.py b/code/flash-linear-attention/fla/ops/delta_rule/wy_fast.py new file mode 100644 index 0000000000000000000000000000000000000000..6f442d288f3c557a2dab8fc73dc262618a42dc8b --- /dev/null +++ b/code/flash-linear-attention/fla/ops/delta_rule/wy_fast.py @@ -0,0 +1,294 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.common.chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.solve_tril import solve_tril +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_nvidia_hopper + +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def recompute_w_u_fwd_kernel( + k, + v, + beta, + w, + u, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_beta = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_beta = tl.load(p_beta, boundary_check=(0,)) + b_A = tl.load(p_A, boundary_check=(0, 1)) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_u = tl.make_block_ptr(u + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_vb = (b_v * b_beta[:, None]).to(b_v.dtype) + b_u = tl.dot(b_A.to(b_vb.dtype), b_vb, allow_tf32=False) + tl.store(p_u, (b_u).to(p_u.dtype.element_ty), boundary_check=(0, 1)) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_w = tl.make_block_ptr(w + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_kb = (b_k * b_beta[:, None]).to(b_k.dtype) + b_w = tl.dot(b_A.to(b_kb.dtype), b_kb, allow_tf32=False) + tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_bwd_kernel( + k, + v, + beta, + A, + dw, + du, + dk, + dv, + dbeta, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_beta = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + + b_beta = tl.load(p_beta, boundary_check=(0,)) + b_A = tl.load(p_A, boundary_check=(0, 1)) + + b_dbeta = tl.zeros([BT], dtype=tl.float32) + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_du = tl.make_block_ptr(du + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v_beta = (b_v * b_beta[:, None]).to(b_v.dtype) + b_du = tl.load(p_du, boundary_check=(0, 1)) + b_dA += tl.dot(b_du, tl.trans(b_v_beta), allow_tf32=False) + b_dv_beta = tl.dot(b_A, b_du, allow_tf32=False) + b_dv = b_dv_beta * b_beta[:, None] + b_dbeta += tl.sum(b_dv_beta * b_v, 1) + + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dw = tl.make_block_ptr(dw + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_k_beta = (b_k * b_beta[:, None]).to(b_k.dtype) + b_dw = tl.load(p_dw, boundary_check=(0, 1)) + b_dA += tl.dot(b_dw, tl.trans(b_k_beta), allow_tf32=False) + b_dk_beta = tl.dot(b_A, b_dw, allow_tf32=False) + b_dk = b_dk_beta * b_beta[:, None] + b_dbeta += tl.sum(b_dk_beta * b_k, 1) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + b_dA = tl.where(tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :], b_dA, 0) + b_dA = tl.dot(b_dA.to(b_A.dtype), b_A) + b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) + b_dA = tl.where(tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :], -b_dA, 0).to(k.dtype.element_ty) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dk = tl.load(p_dk, boundary_check=(0, 1)) + b_k_beta = (b_k * b_beta[:, None]).to(b_k.dtype) + + b_dk_beta = tl.dot(b_dA, b_k, allow_tf32=False) + b_dbeta += tl.sum(b_dk_beta * b_k, 1) + b_dk += tl.dot(tl.trans(b_dA), b_k_beta, allow_tf32=False) + b_dk += b_dk_beta * b_beta[:, None] + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + p_dbeta = tl.make_block_ptr(dbeta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dbeta, b_dbeta.to(p_dbeta.dtype.element_ty), boundary_check=(0,)) + + +def prepare_wy_repr_fwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + cu_seqlens: torch.LongTensor | None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + A = chunk_scaled_dot_kkt_fwd( + k=k, + beta=beta, + cu_seqlens=cu_seqlens, + chunk_size=64, + output_dtype=torch.float32, + ) + A = solve_tril( + A=A, + cu_seqlens=cu_seqlens, + output_dtype=k.dtype, + ) + w, u = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + cu_seqlens=cu_seqlens, + ) + return w, u, A + + +def recompute_w_u_fwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + cu_seqlens: torch.LongTensor | None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = 64 + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + u = torch.empty_like(v) + w = torch.empty_like(k) + recompute_w_u_fwd_kernel[(NT, B*H)]( + k, + v, + beta, + w, + u, + A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return w, u + + +def prepare_wy_repr_bwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + dw: torch.Tensor, + du: torch.Tensor, + cu_seqlens: torch.LongTensor | None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = A.shape[-1] + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dk = torch.empty_like(k) + dv = torch.empty_like(v) + dbeta = torch.empty_like(beta) + prepare_wy_repr_bwd_kernel[(NT, B * H)]( + k, + v, + beta, + A, + dw, + du, + dk, + dv, + dbeta, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dk, dv, dbeta + + +fwd_prepare_wy_repr = prepare_wy_repr_fwd + +bwd_prepare_wy_repr = prepare_wy_repr_bwd + +fwd_recompute_w_u = recompute_w_u_fwd diff --git a/code/flash-linear-attention/fla/ops/deltaformer/__init__.py b/code/flash-linear-attention/fla/ops/deltaformer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6d811ba8b19c41251677fc2a30b721f7f02a7dcc --- /dev/null +++ b/code/flash-linear-attention/fla/ops/deltaformer/__init__.py @@ -0,0 +1,8 @@ + +from .naive import naive_deltaformer_attn +from .parallel import deltaformer_attn + +__all__ = [ + 'deltaformer_attn', + 'naive_deltaformer_attn', +] diff --git a/code/flash-linear-attention/fla/ops/deltaformer/invcum.py b/code/flash-linear-attention/fla/ops/deltaformer/invcum.py new file mode 100644 index 0000000000000000000000000000000000000000..2be2b295a06ca5e96fd05f2ff69a4dec65a64243 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/deltaformer/invcum.py @@ -0,0 +1,37 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import torch + + +def forward(u, w): + return torch.linalg.solve_triangular( + w.float(), + u.float(), + upper=False, + unitriangular=True, + ).to(u.dtype) + + +def forward_inplace(u, w): + u.copy_(forward(u, w)) + + +def backward_x(do, w): + return torch.linalg.solve_triangular( + w.tril(-1).mH.float(), + do.float(), + upper=True, + unitriangular=True, + ).to(do.dtype) + + +def backward(do, w, x): + du = torch.linalg.solve_triangular( + w.tril(-1).mH.float(), + do.float(), + upper=True, + unitriangular=True, + ).to(do.dtype) + dw = torch.bmm(-du, x.mH) + dw = dw.tril(-1) + return du, dw diff --git a/code/flash-linear-attention/fla/ops/deltaformer/naive.py b/code/flash-linear-attention/fla/ops/deltaformer/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..20bb89c6b4d18d828e8a413fbd908118b15e1dbd --- /dev/null +++ b/code/flash-linear-attention/fla/ops/deltaformer/naive.py @@ -0,0 +1,150 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import math + +import torch + + +def tril_softmax(scores: torch.Tensor, strict: bool = True) -> torch.Tensor: + """ + Row-wise causal softmax over strictly lower-triangular (j < i) positions. + + Args: + scores: [B, H, T, T] raw attention scores (q @ k^T). + strict: if True, mask out diagonal as well (strictly causal). Otherwise include diagonal. + + Returns: + probs: [B, H, T, T] with probabilities on j < i (or j <= i if strict=False), zeros elsewhere. + """ + T = scores.size(-1) + device = scores.device + i = torch.arange(T, device=device).view(1, 1, T, 1) + j = torch.arange(T, device=device).view(1, 1, 1, T) + if strict: + mask = (j < i) + else: + mask = (j <= i) + + masked = scores.masked_fill(~mask, float('-inf')) + max_per_row = masked.max(dim=-1, keepdim=True).values + exp = (masked - max_per_row).exp() + exp = exp.masked_fill(~mask, 0.0) + denom = exp.sum(dim=-1, keepdim=True).clamp_min_(1e-20) + probs = exp / denom + return probs + + +def naive_causal_attention_bhtd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, +) -> torch.Tensor: + B, H, T, D = q.shape + qk_scale = 1.0 / math.sqrt(D) + scores = torch.matmul(q, k.transpose(-1, -2)) * qk_scale # [B, H, T, T] + causal_mask = torch.triu(torch.ones(T, T, device=q.device), diagonal=1).bool() + scores = scores.masked_fill(causal_mask, float('-inf')) + attn_weights = torch.softmax(scores, dim=-1) # [B, H, T, T] + o = torch.matmul(attn_weights, v) # [B, H, T, D] + + return o + + +def naive_deltaformer_attn_head_first( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor | None = None, +) -> torch.Tensor: + """ + Naive reference implementation of DeltaFormer attention for head-first format. + + Two-stage process: + 1. Computes u[i] = v[i] - beta[i] * sum_{j torch.Tensor: + """ + Naive reference implementation of DeltaFormer attention for sequence-first format. + + Args: + q: [B, T, H, D] + k: [B, T, H, D] + v: [B, T, H, D] + beta: [B, T, H] or None (defaults to ones) + + Returns: + o: [B, T, H, D] + """ + assert q.dim() == 4 and k.dim() == 4 and v.dim() == 4, "q,k,v must be [B,T,H,D]" + B, T, H, D = q.shape + assert k.shape == (B, T, H, D) and v.shape == (B, T, H, D) + + q_bhtd = q.transpose(1, 2) # [B, T, H, D] -> [B, H, T, D] + k_bhtd = k.transpose(1, 2) # [B, T, H, D] -> [B, H, T, D] + v_bhtd = v.transpose(1, 2) # [B, T, H, D] -> [B, H, T, D] + + if beta is not None: + assert beta.shape == (B, T, H) + beta_bhtd = beta.transpose(1, 2) # [B, T, H] -> [B, H, T] + else: + beta_bhtd = None + + o_bhtd = naive_deltaformer_attn_head_first(q_bhtd, k_bhtd, v_bhtd, beta_bhtd) + + o_bthd = o_bhtd.transpose(1, 2) # [B, H, T, D] -> [B, T, H, D] + + return o_bthd + + +__all__ = [ + 'naive_deltaformer_attn', + 'tril_softmax', +] diff --git a/code/flash-linear-attention/fla/ops/deltaformer/parallel.py b/code/flash-linear-attention/fla/ops/deltaformer/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..fd8f276edc1c5bbfd20a420bd1f43a178f2bb350 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/deltaformer/parallel.py @@ -0,0 +1,991 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import math +import warnings + +import torch +import triton +import triton.language as tl + +from . import invcum + +try: + from flash_attn import flash_attn_func, flash_attn_varlen_func +except ImportError: + warnings.warn( + "Flash Attention is not installed. Please install it via `pip install flash-attn --no-build-isolation`", + category=ImportWarning, + ) + flash_attn_func = None + +from fla.layers.utils import pad_input, unpad_input + +BLOCK_SIZE_C = 512 + + +def parallel_deltaformer_chunk_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + u: torch.Tensor, + qk_scale: float, + beta: torch.Tensor, +): + C, H, D = q.size() + T, _H, _D = k.size() + __C, __H = beta.size() + assert H == _H and D == _D and H == __H and __C == C + w = torch.empty(C, H, C, device=q.device, dtype=q.dtype) + lse = torch.empty(C, H, device=q.device, dtype=torch.float) + parallel_deltaformer_kernel(q, k, v, u, w, lse, qk_scale, beta) + return w, lse + + +def parallel_deltaformer_bwd_u_chunk( + q: torch.Tensor, + k: torch.Tensor, + lse: torch.Tensor, + grad_v: torch.Tensor, + fa_scale: float, + beta: torch.Tensor, +): + C, H, D = q.size() + T, _H, _D = k.size() + grad_u = torch.empty_like(q) + + def grid(META): + return (triton.cdiv(C, META['BLOCK_C']), H) + + parallel_deltaformer_bwd_kernel_u[grid]( + grad_u, q, k, grad_v, lse, beta, + H, T, C, D, fa_scale, + ) + return grad_u + + +def parallel_deltaformer_bwd_qk( + q: torch.Tensor, + k: torch.Tensor, + u: torch.Tensor, + lse: torch.Tensor, + grad_v: torch.Tensor, + qk_scale: float, + fa_scale: float, + beta: torch.Tensor, +): + T, H, D = k.size() + row_dot_sum = torch.empty_like(lse) + + def grid_bp(META): + return (triton.cdiv(T, META['BLOCK_C']), H) + + parallel_deltaformer_bwd_kernel_row_sum[grid_bp]( + row_dot_sum, q, k, grad_v, u, lse, + H, T, D, + fa_scale, + ) + grad_k = torch.empty_like(k) + grad_q = torch.empty_like(q) + + parallel_deltaformer_bwd_kernel_qk[grid_bp]( + grad_q, grad_k, q, k, grad_v, u, lse, beta, row_dot_sum, + H, T, D, + fa_scale, qk_scale, + ) + return grad_q, grad_k, row_dot_sum + + +def parallel_deltaformer_kernel( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + u: torch.Tensor, + w: torch.Tensor, + lse: torch.Tensor, + qk_scale: float, + beta: torch.Tensor, +) -> None: + C, H, D = q.size() + T, _H, _D = k.size() + + def grid(META): + return (triton.cdiv(C, META['BLOCK_C']), H) + + parallel_deltaformer_fwd_kernel[grid]( + q, k, v, u, w, lse, beta, + H, T, C, D, qk_scale, + ) + + +def _config_deltaformer(): + return [ + triton.Config({'BLOCK_C': BC, 'BLOCK_T': BT}, num_stages=ns, num_warps=nw) + for BC in [128, 64] + for BT in [64, 32] + for ns in [3, 2] + for nw in [8, 4] + ] + + +@triton.autotune(configs=_config_deltaformer(), key=['C', 'D']) +@triton.jit +def parallel_deltaformer_fwd_kernel( + q_ptr, + k_ptr, + v_ptr, + u_ptr, + w_ptr, + lse_ptr, + beta_ptr, + H, + T, + C, + D: tl.constexpr, + qk_scale: float, + BLOCK_C: tl.constexpr, + BLOCK_T: tl.constexpr, +): + pid_c = tl.program_id(axis=0) + pid_h = tl.program_id(axis=1) + + rowid_block = tl.arange(0, BLOCK_C) + pid_c * BLOCK_C + colid_block = tl.arange(0, BLOCK_T) + + rowmax = tl.zeros([BLOCK_C], dtype=tl.float32) - float('inf') + rowsum = tl.zeros([BLOCK_C], dtype=tl.float32) + 1 + acc = tl.zeros([BLOCK_C, D], dtype=tl.float32) + + q_blk_ptr = tl.make_block_ptr( + base=q_ptr + pid_h * D, + shape=(C, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + q = tl.load(q_blk_ptr, boundary_check=(0,)) + + for kv_i in range(0, T, BLOCK_T): + k_blk_ptr = tl.make_block_ptr( + base=k_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_T), + order=(0, 1), + ) + k = tl.load(k_blk_ptr, boundary_check=(1,)) + qk = tl.dot(q, k) * qk_scale + + if kv_i >= T - C: + mask = (T - C - kv_i + rowid_block[:, None] - colid_block[None, :] < 1) + qk = tl.where(mask, -1e6, qk) + + rowmax_i = tl.maximum(rowmax, tl.max(qk, axis=1)) + qk -= rowmax_i[:, None] + p = tl.math.exp2(qk) + + rowsum_i = tl.sum(p, axis=1) + alpha = tl.math.exp2(rowmax - rowmax_i) + rowsum = rowsum * alpha + rowsum_i + acc = acc * alpha[:, None] + rowmax = rowmax_i + + if kv_i < T - C: + u_blk_ptr = tl.make_block_ptr( + base=u_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(kv_i, 0), + block_shape=(BLOCK_T, D), + order=(1, 0), + ) + u = tl.load(u_blk_ptr, boundary_check=(0,)) + acc = tl.dot(p.to(u_ptr.dtype.element_ty), u, acc) + + lse = rowmax + tl.math.log2(rowsum) + lse_block_ptr = lse_ptr + pid_h + rowid_block * H + lse_mask = rowid_block < C + tl.store(lse_block_ptr, lse, mask=lse_mask) + + v_ptr = tl.make_block_ptr( + base=v_ptr + pid_h * D, + shape=(C, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + acc = acc / rowsum[:, None] + + beta_ptr = tl.make_block_ptr( + base=beta_ptr + pid_h, + shape=(C,), + strides=(H,), + offsets=(pid_c * BLOCK_C,), + block_shape=(BLOCK_C,), + order=(0,), + ) + beta = tl.load(beta_ptr, boundary_check=(0,)) + acc = acc * beta[:, None] + + v = tl.load(v_ptr, boundary_check=(0,)) + u = v - acc.to(v_ptr.dtype.element_ty) + u_block_ptr = tl.make_block_ptr( + base=u_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(T - C + pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + tl.store(u_block_ptr, u, boundary_check=(0, 1)) + + for kv_i in range(T - C, T, BLOCK_T): + k_blk_ptr = tl.make_block_ptr( + base=k_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_T), + order=(0, 1), + ) + k = tl.load(k_blk_ptr, boundary_check=(1,)) + qk = tl.dot(q, k) * qk_scale + + mask = (T - C - kv_i + rowid_block[:, None] - colid_block[None, :] < 1) + qk -= rowmax[:, None] + p = tl.math.exp2(qk) / rowsum[:, None] + p = tl.where(mask, 0, p) + w_blk_ptr = tl.make_block_ptr( + base=w_ptr + pid_h * C, + shape=(C, C), + strides=(H * C, 1), + offsets=(pid_c * BLOCK_C, kv_i - (T - C)), + block_shape=(BLOCK_C, BLOCK_T), + order=(1, 0), + ) + tl.store(w_blk_ptr, p.to(w_ptr.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.autotune(configs=_config_deltaformer(), key=['C', 'D']) +@triton.jit +def parallel_deltaformer_bwd_kernel_u( + o_ptr, + q_ptr, + k_ptr, + v_ptr, + lse_ptr, + beta_ptr, + H, + T, + C, + D: tl.constexpr, + fa_scale, + BLOCK_C: tl.constexpr, + BLOCK_T: tl.constexpr, +): + pid_c = tl.program_id(axis=0) + pid_h = tl.program_id(axis=1) + + acc = tl.zeros([BLOCK_C, D], dtype=tl.float32) + + q_blk_ptr = tl.make_block_ptr( + base=q_ptr + pid_h * D, + shape=(C, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + q = tl.load(q_blk_ptr, boundary_check=(0,)) + + for kv_i in range(0, T, BLOCK_T): + k_blk_ptr = tl.make_block_ptr( + base=k_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_T), + order=(0, 1), + ) + k = tl.load(k_blk_ptr, boundary_check=(1,)) + qk = tl.dot(q, k) * fa_scale + + lse_blk_ptr = tl.make_block_ptr( + base=lse_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(kv_i,), + block_shape=(BLOCK_T,), + order=(0,), + ) + lse = tl.load(lse_blk_ptr, boundary_check=(0,)) + beta_blk_ptr = tl.make_block_ptr( + base=beta_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(kv_i,), + block_shape=(BLOCK_T,), + order=(0,), + ) + beta = tl.load(beta_blk_ptr, boundary_check=(0,)) + + p = tl.math.exp2(qk - lse[None, :]) * beta[None, :] + + v_blk_ptr = tl.make_block_ptr( + base=v_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(kv_i, 0), + block_shape=(BLOCK_T, D), + order=(1, 0), + ) + v = tl.load(v_blk_ptr, boundary_check=(0,)) + acc = tl.dot(p.to(v_ptr.dtype.element_ty), v, acc) + + o_blk_ptr = tl.make_block_ptr( + base=o_ptr + pid_h * D, + shape=(C, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + tl.store(o_blk_ptr, acc.to(o_ptr.dtype.element_ty), boundary_check=(0,)) + + +@triton.autotune(configs=_config_deltaformer(), key=['T', 'D']) +@triton.jit +def parallel_deltaformer_bwd_kernel_row_sum( + row_dot_ptr, + q_ptr, + k_ptr, + grad_v_ptr, + u_ptr, + lse_ptr, + H, + T, + D: tl.constexpr, + fa_scale, + BLOCK_C: tl.constexpr, + BLOCK_T: tl.constexpr, +): + pid_c = tl.program_id(axis=0) + pid_h = tl.program_id(axis=1) + + rowid_block = tl.arange(0, BLOCK_C) + pid_c * BLOCK_C + colid_block = tl.arange(0, BLOCK_T) + + acc = tl.zeros([BLOCK_C], dtype=tl.float32) + + k_row_blk_ptr = tl.make_block_ptr( + base=q_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + k_row = tl.load(k_row_blk_ptr, boundary_check=(0,)) + lse_blk_ptr = tl.make_block_ptr( + base=lse_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(pid_c * BLOCK_C,), + block_shape=(BLOCK_C,), + order=(0,), + ) + lse = tl.load(lse_blk_ptr, boundary_check=(0,)) + grad_v_blk_ptr = tl.make_block_ptr( + base=grad_v_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + grad_v_row = -tl.load(grad_v_blk_ptr, boundary_check=(0,)) + + for kv_i in range(0, (pid_c + 1) * BLOCK_C, BLOCK_T): + k_blk_ptr = tl.make_block_ptr( + base=k_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_T), + order=(0, 1), + ) + k = tl.load(k_blk_ptr, boundary_check=(1,)) + qk = tl.dot(k_row, k) * fa_scale + p = tl.math.exp2(qk - lse[:, None]) + + u_blk_ptr = tl.make_block_ptr( + base=u_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_T), + order=(0, 1), + ) + ut = tl.load(u_blk_ptr, boundary_check=(1,)) + dp = tl.dot(grad_v_row, ut) + if kv_i + BLOCK_T >= pid_c * BLOCK_C: + mask = (rowid_block[:, None] <= colid_block[None, :] + kv_i) + p = tl.where(mask, 0., p) + dp = tl.where(mask, 0., dp) + acc += tl.sum(p * dp, axis=1) + row_dot_block_ptr = tl.make_block_ptr( + base=row_dot_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(pid_c * BLOCK_C,), + block_shape=(BLOCK_C,), + order=(0,), + ) + tl.store(row_dot_block_ptr, acc, boundary_check=(0,)) + + +@triton.autotune(configs=[triton.Config({'BLOCK_C': BC}, num_stages=ns, num_warps=nw) + for BC in [64, 32] + for ns in [4, 3] + for nw in [4]], key=['T', 'D']) +@triton.jit +def parallel_deltaformer_bwd_kernel_qk( + grad_q_ptr, + grad_k_ptr, + q_ptr, + k_ptr, + grad_v_ptr, + u_ptr, + lse_ptr, + beta_ptr, + row_dot_ptr, + H, + T, + D: tl.constexpr, + fa_scale: tl.constexpr, + qk_scale: tl.constexpr, + BLOCK_C: tl.constexpr, +): + pid_c = tl.program_id(axis=0) + pid_h = tl.program_id(axis=1) + block_i = tl.arange(0, BLOCK_C) + + acc = tl.zeros([BLOCK_C, D], dtype=tl.float32) + + k_row_blk_ptr = tl.make_block_ptr( + base=q_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + k_row = tl.load(k_row_blk_ptr, boundary_check=(0,)) + lse_blk_ptr = tl.make_block_ptr( + base=lse_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(pid_c * BLOCK_C,), + block_shape=(BLOCK_C,), + order=(0,), + ) + lse = tl.load(lse_blk_ptr, boundary_check=(0,)) + beta_blk_ptr = tl.make_block_ptr( + base=beta_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(pid_c * BLOCK_C,), + block_shape=(BLOCK_C,), + order=(0,), + ) + beta = tl.load(beta_blk_ptr, boundary_check=(0,)) + grad_v_blk_ptr = tl.make_block_ptr( + base=grad_v_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + grad_v_row = -tl.load(grad_v_blk_ptr, boundary_check=(0,)) + row_dot_blk_ptr = tl.make_block_ptr( + base=row_dot_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(pid_c * BLOCK_C,), + block_shape=(BLOCK_C,), + order=(0,), + ) + row_dot_row = tl.load(row_dot_blk_ptr, boundary_check=(0,)).to(k_ptr.dtype.element_ty) + + for kv_i in range(0, pid_c * BLOCK_C, BLOCK_C): + k_blk_ptr = tl.make_block_ptr( + base=k_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_C), + order=(0, 1), + ) + kt = tl.load(k_blk_ptr, boundary_check=(1,)) + qk = tl.dot(k_row, kt) * fa_scale + p = tl.math.exp2(qk - lse[:, None]) * beta[:, None] + + u_blk_ptr = tl.make_block_ptr( + base=u_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_C), + order=(0, 1), + ) + ut = tl.load(u_blk_ptr) + dp = tl.dot(grad_v_row, ut) + da = p * (dp - row_dot_row[:, None]) + k = tl.trans(kt, 1, 0) + acc = tl.dot(da.to(k.dtype), k, acc) + + k_row_blk_ptr = tl.make_block_ptr( + base=k_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(pid_c * BLOCK_C, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + k_row_true = tl.load(k_row_blk_ptr, boundary_check=(0,)) + qk = tl.dot(k_row, tl.trans(k_row_true, 1, 0)) * fa_scale + p = tl.math.exp2(qk - lse[:, None]) * beta[:, None] + u_blk_ptr = tl.make_block_ptr( + base=u_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, pid_c * BLOCK_C), + block_shape=(D, BLOCK_C), + order=(0, 1), + ) + ut = tl.load(u_blk_ptr) + dp = tl.dot(grad_v_row, ut) + dpm = dp - row_dot_row[:, None] + mask = block_i[None, :] < block_i[:, None] + p = tl.where(mask, p, 0.) + dpm = tl.where(mask, dpm, 0.) + da = p * dpm + daat = da + acc = tl.dot(daat.to(k_row.dtype), k_row_true, acc) + + grad_q_blk_ptr = tl.make_block_ptr( + base=grad_q_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(BLOCK_C * pid_c, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + acc = acc * qk_scale + tl.store(grad_q_blk_ptr, acc.to(grad_q_ptr.dtype.element_ty), boundary_check=(0,)) + + daat = tl.trans(da, 1, 0) + acc = tl.dot(daat.to(k_row.dtype), k_row) + k_row = k_row_true + nu = -tl.trans(ut, 1, 0) + for kv_i in range((pid_c + 1) * BLOCK_C, T, BLOCK_C): + k_blk_ptr = tl.make_block_ptr( + base=q_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_C), + order=(0, 1), + ) + kt = tl.load(k_blk_ptr, boundary_check=(1,)) + lse_blk_ptr = tl.make_block_ptr( + base=lse_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(kv_i,), + block_shape=(BLOCK_C,), + order=(0,), + ) + lse = tl.load(lse_blk_ptr, boundary_check=(0,)) + beta_blk_ptr = tl.make_block_ptr( + base=beta_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(kv_i,), + block_shape=(BLOCK_C,), + order=(0,), + ) + beta = tl.load(beta_blk_ptr, boundary_check=(0,)) + qk = tl.dot(k_row, kt) * fa_scale + p = tl.math.exp2(qk - lse[None, :]) * beta[None, :] + + grad_vt_blk_ptr = tl.make_block_ptr( + base=grad_v_ptr + pid_h * D, + shape=(D, T), + strides=(1, H * D), + offsets=(0, kv_i), + block_shape=(D, BLOCK_C), + order=(0, 1), + ) + grad_vt = tl.load(grad_vt_blk_ptr, boundary_check=(1,)) + row_dot_blk_ptr = tl.make_block_ptr( + base=row_dot_ptr + pid_h, + shape=(T,), + strides=(H,), + offsets=(kv_i,), + block_shape=(BLOCK_C,), + order=(0,), + ) + row_dot = tl.load(row_dot_blk_ptr, boundary_check=(0,)).to(k_ptr.dtype.element_ty) + dp = tl.dot(nu, grad_vt) + da = p * (dp - row_dot[None, :]) + k = tl.trans(kt, 1, 0) + acc = tl.dot(da.to(k.dtype), k, acc) + + grad_k_blk_ptr = tl.make_block_ptr( + base=grad_k_ptr + pid_h * D, + shape=(T, D), + strides=(H * D, 1), + offsets=(BLOCK_C * pid_c, 0), + block_shape=(BLOCK_C, D), + order=(1, 0), + ) + acc = acc * qk_scale + tl.store(grad_k_blk_ptr, acc.to(grad_k_ptr.dtype.element_ty), boundary_check=(0,)) + + +class ParallelDeltaformerFunction(torch.autograd.Function): + @staticmethod + def forward( + ctx, + qo: torch.Tensor, + ko: torch.Tensor, + vo: torch.Tensor, + betao: torch.Tensor | None = None, + C: int = BLOCK_SIZE_C, + cu_seqlens: torch.LongTensor | None = None, + ): + B, T, H, D = ko.size() + C = min(C, T) + ctx.C = C + ctx.cu_seqlens = cu_seqlens + + if cu_seqlens is not None: + need_aux = qo.requires_grad or ko.requires_grad or vo.requires_grad or (betao is not None and betao.requires_grad) + u, ws, lses = ParallelDeltaformerFunction._forward_impl( + qo, ko, vo, betao, C, need_aux=need_aux, cu_seqlens=cu_seqlens) + saved_beta = betao if betao is not None else torch.ones(B, T, H, device=ko.device, dtype=ko.dtype) + ctx.beta_is_none = betao is None + if need_aux: + ctx.save_for_backward(qo, ko, vo, u, ws, lses, saved_beta) + else: + ctx.save_for_backward() + return u + + u, ws, lses = ParallelDeltaformerFunction._forward_impl(qo, ko, vo, betao, C, need_aux=True) + saved_beta = betao if betao is not None else torch.ones(B, T, H, device=ko.device, dtype=ko.dtype) + ctx.save_for_backward(qo, ko, vo, u, ws, lses, saved_beta) + ctx.beta_is_none = betao is None + return u + + @staticmethod + def backward( + ctx, + grad_u: torch.Tensor, + ): + if getattr(ctx, 'cu_seqlens', None) is not None: + cu = ctx.cu_seqlens + qo, ko, vo, u_full, ws, lses, betao = ctx.saved_tensors + B, T_max, H, D = ko.size() + qk_scale = 1.0 / math.sqrt(D) + fa_scale = qk_scale / math.log(2) + + dq = torch.zeros_like(qo) + dk = torch.zeros_like(ko) + dv = torch.zeros_like(vo) + dbeta = None if ctx.beta_is_none else torch.zeros_like(betao) + + C = ctx.C + N = len(cu) - 1 + chunk_bases = [] + total = 0 + lengths = [] + for b in range(N): + L = int(cu[b + 1].item() - cu[b].item()) + lengths.append(L) + chunk_bases.append(total) + if L > 0: + total += (L + C - 1) // C + + for b in range(N): + L = lengths[b] + if L == 0: + continue + base = chunk_bases[b] + seq_start = int(cu[b].item()) + + seq_end = seq_start + L + q_seq = qo[0, seq_start:seq_end, :, :] + k_seq = ko[0, seq_start:seq_end, :, :] + u_seq = u_full[0, seq_start:seq_end, :, :] + beta_seq = betao[0, seq_start:seq_end, :] + lse_seq = lses[0, seq_start:seq_end, :] + go_seq = grad_u[0, seq_start:seq_end, :, :] + + gv_seq = torch.zeros_like(u_seq) + start = ((L - 1) // C) * C + for i_local in range(start, -1, -C): + Ci = min(C, L - i_local) + i0 = i_local + i1 = i_local + Ci + do = go_seq[i0:i1, :, :] + if i_local < L - C: + qi = k_seq[i0:i1, :, :] + ki = q_seq[i1:L, :, :] + lse_tail = lse_seq[i1:L, :] + beta_tail = beta_seq[i1:L, :] + du_tail = parallel_deltaformer_bwd_u_chunk(qi, ki, lse_tail, gv_seq[i1:L, :, :], fa_scale, beta_tail) + do = do - du_tail + Wpad = ws[base + (i_local // C)] + W = Wpad[:Ci, :, :Ci] + W_t = W.transpose(0, 1).contiguous() + du_chunk = invcum.backward_x(do.transpose(0, 1).contiguous(), W_t).transpose(0, 1).contiguous() + gv_seq[i0:i1, :, :].copy_(du_chunk) + + gq, gk, gbeta = parallel_deltaformer_bwd_qk(q_seq, k_seq, u_seq, lse_seq, gv_seq, qk_scale, fa_scale, beta_seq) + dq[0, seq_start:seq_end, :, :].copy_(gq) + dk[0, seq_start:seq_end, :, :].copy_(gk) + dv[0, seq_start:seq_end, :, :].copy_(gv_seq) + if dbeta is not None: + dbeta[0, seq_start:seq_end, :].copy_(gbeta) + + return dq, dk, dv, dbeta, None, None + qo, ko, vo, u, ws, lses, betao = ctx.saved_tensors + C = ctx.C + B, T, H, D = ko.size() + + grad_q = torch.zeros_like(qo) + grad_k = torch.zeros_like(ko) + grad_v = torch.zeros_like(vo) + grad_beta_out = None if ctx.beta_is_none else torch.zeros_like(betao) + + qk_scale = 1.0 / math.sqrt(D) + fa_scale = qk_scale / math.log(2) + + chunk_base = 0 + for b in range(B): + grad_v_seq = torch.empty(T, H, D, device=ko.device, dtype=ko.dtype) + for i in range(T - C, -1, -C): + Ci = min(C, T - i) + do = grad_u[b, i:i + Ci, :, :] + + if i < T - C: + qi = ko[b, i:i + Ci, :, :] + ki = qo[b, i + Ci:, :, :] + lse = lses[b, i + Ci:, :] + if not ctx.beta_is_none: + beta_single = betao[b, i + Ci:, :] + else: + beta_single = torch.ones(T - i - Ci, H, device=ko.device, dtype=ko.dtype) + du = parallel_deltaformer_bwd_u_chunk(qi, ki, lse, grad_v_seq[i + Ci:, :, :], fa_scale, beta_single) + do = grad_u[b, i:i + Ci, :, :] - du + + W = ws[chunk_base + (i // C)][:Ci, :, :Ci] + W_t = W.transpose(0, 1).contiguous() + du = invcum.backward_x(do.transpose(0, 1).contiguous(), W_t).transpose(0, 1).contiguous() + grad_v_seq[i:i + Ci, :, :].copy_(du) + + q_seq = qo[b] + k_seq = ko[b] + u_seq = u[b] + lse_seq = lses[b] + beta_seq = betao[b] if not ctx.beta_is_none else torch.ones(T, H, device=ko.device, dtype=ko.dtype) + + gq, gk, gbeta = parallel_deltaformer_bwd_qk(q_seq, k_seq, u_seq, lse_seq, grad_v_seq, qk_scale, fa_scale, beta_seq) + + grad_q[b].copy_(gq) + grad_k[b].copy_(gk) + grad_v[b].copy_(grad_v_seq) + if not ctx.beta_is_none: + grad_beta_out[b].copy_(gbeta) + + chunk_base += (T + C - 1) // C + + return grad_q, grad_k, grad_v, grad_beta_out, None, None + + @staticmethod + def _forward_impl( + qo: torch.Tensor, + ko: torch.Tensor, + vo: torch.Tensor, + betao: torch.Tensor | None, + C: int, + need_aux: bool, + cu_seqlens: torch.LongTensor | None = None, + ) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]: + B, T_max, H, D = ko.size() + C = min(C, T_max) + qk_scale = 1.0 / math.sqrt(D) + fa_scale = qk_scale / math.log(2) + + if cu_seqlens is None: + if betao is None: + beta_full = torch.ones(B, T_max, H, device=ko.device, dtype=ko.dtype) + else: + beta_full = betao + + u_full = torch.empty_like(vo) + if need_aux: + total_chunks = B * ((T_max + C - 1) // C) + ws = torch.empty(total_chunks, C, H, C, device=ko.device, dtype=ko.dtype) + lses = torch.empty(B, T_max, H, device=ko.device, dtype=torch.float) + chunk_base = 0 + else: + ws = None + lses = None + chunk_base = 0 + + for b in range(B): + for i in range(0, T_max, C): + Ci = min(C, T_max - i) + + qi = qo[b, i:i + Ci, :, :] + ki = ko[b, :i + Ci, :, :] + vi = vo[b, i:i + Ci, :, :] + ui_prev = u_full[b, :i + Ci, :, :] + betai = beta_full[b, i:i + Ci, :] + + w, lse_chunk = parallel_deltaformer_chunk_fwd(qi, ki, vi, ui_prev, fa_scale, betai) + w = w * betai.unsqueeze(-1) + if need_aux: + wpad = torch.zeros(C, H, C, device=ko.device, dtype=ko.dtype) + wpad[:Ci, :, :Ci].copy_(w) + ws[chunk_base + (i // C)].copy_(wpad) + lses[b, i:i + Ci, :].copy_(lse_chunk) + + u_chunk_view = u_full[b, i:i + Ci, :, :] + w_t = w.transpose(0, 1).contiguous() + u_chunk_view_t = u_chunk_view.transpose(0, 1).contiguous() + invcum.forward_inplace(u_chunk_view_t, w_t) + u_chunk_view.copy_(u_chunk_view_t.transpose(0, 1)) + + chunk_base += (T_max + C - 1) // C + + return u_full, ws, lses + + N = len(cu_seqlens) - 1 + assert cu_seqlens.dim() == 1 and cu_seqlens.size(0) == N + 1, "cu_seqlens must be [N+1]" + device = ko.device + dtype_k = ko.dtype + if betao is None: + beta_full = torch.ones(B, T_max, H, device=device, dtype=dtype_k) + else: + beta_full = betao + + u_full = torch.empty_like(vo) + if need_aux: + total_chunks = sum((max(0, int(cu_seqlens[b + 1].item() - cu_seqlens[b].item())) + C - 1) // C + for b in range(N)) + ws = torch.empty(total_chunks, C, H, C, device=device, dtype=dtype_k) + lses = torch.empty(B, T_max, H, device=device, dtype=torch.float) + chunk_base = 0 + else: + ws = None + lses = None + chunk_base = 0 + + for b in range(N): + seq_start = int(cu_seqlens[b].item()) + seq_end = int(cu_seqlens[b + 1].item()) + L = max(0, seq_end - seq_start) + if L == 0: + continue + + for i_local in range(0, L, C): + Ci = min(C, L - i_local) + li0 = i_local + li1 = i_local + Ci + + abs_start = seq_start + li0 + abs_end = seq_start + li1 + abs_context_end = seq_start + li1 + + qi = qo[0, abs_start:abs_end, :, :] + ki = ko[0, seq_start:abs_context_end, :, :] + vi = vo[0, abs_start:abs_end, :, :] + ui_prev = u_full[0, seq_start:abs_context_end, :, :] + betai = beta_full[0, abs_start:abs_end, :] + + w, lse_chunk = parallel_deltaformer_chunk_fwd(qi, ki, vi, ui_prev, fa_scale, betai) + w = w * betai.unsqueeze(-1) + if need_aux: + wpad = torch.zeros(C, H, C, device=device, dtype=dtype_k) + wpad[:Ci, :, :Ci].copy_(w) + ws[chunk_base + (i_local // C)].copy_(wpad) + lses[0, abs_start:abs_end, :].copy_(lse_chunk) + + u_chunk_view = u_full[0, abs_start:abs_end, :, :] + w_t = w.transpose(0, 1).contiguous() + u_chunk_view_t = u_chunk_view.transpose(0, 1).contiguous() + invcum.forward_inplace(u_chunk_view_t, w_t) + u_chunk_view.copy_(u_chunk_view_t.transpose(0, 1)) + + chunk_base += (L + C - 1) // C + + return u_full, ws, lses + + +def deltaformer_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor | None = None, + attention_mask: torch.LongTensor | None = None, + cu_seqlens: torch.LongTensor | None = None, + C: int = BLOCK_SIZE_C, +) -> torch.Tensor: + if flash_attn_func is None: + raise ImportError("Please install Flash Attention via `pip install flash-attn --no-build-isolation` first") + + B, T, H, D = k.shape + C = min(C, T) + + u = ParallelDeltaformerFunction.apply(q, k, v, beta, C, cu_seqlens) + + if attention_mask is not None: + q_padded, (k_padded, u_padded), indices_q, cu_seqlens_lens, max_seq_lens = unpad_input(q, (k, u), attention_mask, T) + cu_seqlens_q, cu_seqlens_k = cu_seqlens_lens + max_seqlen_q, max_seqlen_k = max_seq_lens + o = flash_attn_varlen_func( + q_padded, k_padded, u_padded, + cu_seqlens_q=cu_seqlens_q, + cu_seqlens_k=cu_seqlens_k, + max_seqlen_q=max_seqlen_q, + max_seqlen_k=max_seqlen_k, + causal=True, + window_size=(-1, -1), + ) + o = pad_input(o, indices_q, B, T) + elif cu_seqlens is not None: + max_seqlen = int((cu_seqlens[1:] - cu_seqlens[:-1]).max().item()) + o = flash_attn_varlen_func( + q.squeeze(0), k.squeeze(0), u.squeeze(0), + cu_seqlens_q=cu_seqlens, + cu_seqlens_k=cu_seqlens, + max_seqlen_q=max_seqlen, + max_seqlen_k=max_seqlen, + causal=True, + window_size=(-1, -1), + ).unsqueeze(0) + else: + o = flash_attn_func(q, k, u, causal=True, window_size=(-1, -1)) + + return o + + +__all__ = [ + 'deltaformer_attn', +] diff --git a/code/flash-linear-attention/fla/ops/forgetting_attn/__init__.py b/code/flash-linear-attention/fla/ops/forgetting_attn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6914f51d04de572b6f7226618ab508d294361b8c --- /dev/null +++ b/code/flash-linear-attention/fla/ops/forgetting_attn/__init__.py @@ -0,0 +1,6 @@ + +from .parallel import parallel_forgetting_attn + +__all__ = [ + 'parallel_forgetting_attn', +] diff --git a/code/flash-linear-attention/fla/ops/forgetting_attn/parallel.py b/code/flash-linear-attention/fla/ops/forgetting_attn/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..46ed9bae25a69c6c825581d38178e6348f23a7c7 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/forgetting_attn/parallel.py @@ -0,0 +1,62 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.attn.parallel import parallel_attn + + +def parallel_forgetting_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> torch.Tensor: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, HQ, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + GQA will be applied if HQ is divisible by H. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Log decay at rach time step (in **log space**) of shape `[B, T, HQ]` if `head_first=False` else `[B, HQ, T]`. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HQ, V]`. + """ + + if scale is None: + scale = k.shape[-1] ** -0.5 + if cu_seqlens is not None: + assert q.shape[0] == 1, "batch size must be 1 when cu_seqlens are provided" + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + o = parallel_attn(q, k, v, g, scale, cu_seqlens) + return o diff --git a/code/flash-linear-attention/fla/ops/gated_delta_product/__init__.py b/code/flash-linear-attention/fla/ops/gated_delta_product/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6ab6b529cb664b3092d820e08f7601ff0cc9df05 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_product/__init__.py @@ -0,0 +1,5 @@ +from .chunk import chunk_gated_delta_product + +__all__ = [ + "chunk_gated_delta_product", +] diff --git a/code/flash-linear-attention/fla/ops/gated_delta_product/chunk.py b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..68cb76e2c9c2c19ab0a14aa82e6eab927612bae2 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk.py @@ -0,0 +1,303 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +from einops import rearrange + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.common.chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd +from fla.ops.delta_rule.chunk import chunk_delta_rule_bwd +from fla.ops.delta_rule.wy_fast import recompute_w_u_fwd as dn_recompute_w_u_fwd +from fla.ops.gated_delta_product.chunk_deltaproduct_h import chunk_gated_delta_product_fwd_h +from fla.ops.gated_delta_product.chunk_deltaproduct_o import chunk_gated_delta_product_fwd_o +from fla.ops.gated_delta_rule.chunk import chunk_gated_delta_rule_bwd +from fla.ops.gated_delta_rule.wy_fast import recompute_w_u_fwd as gdn_recompute_w_u_fwd +from fla.ops.utils import chunk_local_cumsum, solve_tril +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_gated_delta_product_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + num_householder: int = 1, +): + cu_seqlens_dp = cu_seqlens * num_householder if cu_seqlens is not None else None + if g is not None: + g_interleaved = g.new_zeros(g.shape[0], g.shape[1], num_householder, g.shape[2], dtype=torch.float32) + g_interleaved[:, :, 0] = g + g_interleaved = rearrange(g_interleaved, 'b l n h -> b (l n) h').contiguous() + g = chunk_local_cumsum(g, chunk_size=64, cu_seqlens=cu_seqlens, output_dtype=torch.float32) + g_interleaved = chunk_local_cumsum(g_interleaved, chunk_size=64, cu_seqlens=cu_seqlens_dp, output_dtype=torch.float32) + else: + g_interleaved = None + g = None + # obtain WY representation. u is actually the new v. + A = chunk_scaled_dot_kkt_fwd( + k=k, + g=g_interleaved, + beta=beta, + cu_seqlens=cu_seqlens_dp, + output_dtype=torch.float32, + ) + A = solve_tril( + A=A, + cu_seqlens=cu_seqlens_dp, + output_dtype=k.dtype, + ) + if g is not None: + w, u = gdn_recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + g=g_interleaved, + cu_seqlens=cu_seqlens_dp, + ) + else: + w, u = dn_recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + cu_seqlens=cu_seqlens_dp, + ) + h, v_new, final_state = chunk_gated_delta_product_fwd_h( + k=k, + w=w, + u=u, + g=g_interleaved, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens_dp, + num_householder=num_householder, + ) + o = chunk_gated_delta_product_fwd_o( + q=q, + k=k, + v=v_new, + h=h, + g=g, + scale=scale, + cu_seqlens=cu_seqlens, + num_householder=num_householder, + ) + return g, g_interleaved, o, A, final_state + + +class ChunkGatedDeltaProductFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + num_householder: int, + initial_state: torch.Tensor, + output_final_state: bool, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q) + k, k_rstd = l2norm_fwd(k) + else: + q_rstd, k_rstd = None, None + + g, g_interleaved, o, A, final_state = chunk_gated_delta_product_fwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + num_householder=num_householder, + ) + ctx.save_for_backward(q, q_rstd, k, k_rstd, v, g_interleaved, beta, A, initial_state, cu_seqlens) + ctx.scale = scale + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + ctx.num_householder = num_householder + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + q, q_rstd, k, k_rstd, v, g, beta, A, initial_state, cu_seqlens = ctx.saved_tensors + q_new = q.new_zeros(q.shape[0], q.shape[1], ctx.num_householder, q.shape[2], q.shape[3]) + q_new[:, :, -1] = q + do_new = do.new_zeros(do.shape[0], do.shape[1], ctx.num_householder, do.shape[2], do.shape[3]) + do_new[:, :, -1] = do + q_org, q = q, rearrange(q_new, 'b t n h d -> b (t n) h d') + do = rearrange(do_new, 'b t n h d -> b (t n) h d') + # call the gated deltanet kernel for now. + # TODO: optimize the backward pass like the forward pass. + if g is not None: + dq, dk, dv, db, dg, dh0 = chunk_gated_delta_rule_bwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + A=A, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens * ctx.num_householder if cu_seqlens is not None else None, + ) + dg = rearrange(dg, 'b (l n) h -> b l n h ', n=ctx.num_householder)[:, :, 0].contiguous().to(g) + else: + dq, dk, dv, db, dh0 = chunk_delta_rule_bwd( + q=q, + k=k, + v=v, + beta=beta, + A=A, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens * ctx.num_householder if cu_seqlens is not None else None, + ) + dg = None + dq = rearrange(dq, 'b (l n) h d -> b l n h d', n=ctx.num_householder)[:, :, -1].contiguous() + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q_org, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + return dq.to(q), dk.to(k), dv.to(v), dg, db.to(beta), None, None, dh0, None, None, None + + +@torch.compiler.disable +def chunk_gated_delta_product( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + num_householder: int, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + (forget) gating tensor (in log space!) of shape `[B, T, H]`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. + num_householder (int): + Number of householder transformations to apply. Default: `1`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (Optional[bool]): + Whether to use qk l2norm within the kernel for saving GPU memory. + Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gated_delta_rule import chunk_gated_delta_product + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') + >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid() + >>> g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda') + >>> o, ht = chunk_gated_delta_product( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = chunk_gated_delta_product( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + assert q.dtype != torch.float32, "ChunkGatedDeltaProductFunction does not support float32. Please use bfloat16." + B, T, H, K, V = *q.shape, v.shape[-1] + assert k.shape == (B, T*num_householder, H, K) + assert v.shape == (B, T*num_householder, H, V) + assert beta.shape == (B, T*num_householder, H) + if g is not None: + assert g.shape == (B, T, H) + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = ChunkGatedDeltaProductFunction.apply( + q, + k, + v, + g, + beta, + scale, + num_householder, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_deltaproduct_h.py b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_deltaproduct_h.py new file mode 100644 index 0000000000000000000000000000000000000000..e620963cab0662b45c8ea4660c6c2983f477c43e --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_deltaproduct_h.py @@ -0,0 +1,502 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, is_nvidia_hopper, use_cuda_graph + +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8, 16] + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'SAVE_NEW_VALUE': lambda args: args['v_new'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + for BV in [32, 64] + ], + key=['H', 'K', 'V', 'BT', 'USE_G'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gated_delta_product_fwd_kernel_h_blockdim64( + k, + v, + w, + v_new, + g, + h, + h0, + ht, + cu_seqlens, + chunk_offsets, + T, + num_householder: tl.constexpr, # number of delta products + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + SAVE_NEW_VALUE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * tl.cdiv(T // num_householder, BT) + + # [BK, BV] + b_h1 = tl.zeros([64, BV], dtype=tl.float32) + if K > 64: + b_h2 = tl.zeros([64, BV], dtype=tl.float32) + if K > 128: + b_h3 = tl.zeros([64, BV], dtype=tl.float32) + if K > 192: + b_h4 = tl.zeros([64, BV], dtype=tl.float32) + + # calculate offset + h += (boh * H + i_h) * K*V + v += (bos * H + i_h) * V + k += (bos * H + i_h) * K + w += (bos * H + i_h) * K + if SAVE_NEW_VALUE: + v_new += (bos * H + i_h) * V + stride_v = H*V + stride_h = H*K*V + stride_k = H*K + if USE_INITIAL_STATE: + h0 = h0 + i_nh * K*V + if STORE_FINAL_STATE: + ht = ht + i_nh * K*V + + # load initial state + if USE_INITIAL_STATE: + p_h0_1 = tl.make_block_ptr(h0, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32) + if K > 64: + p_h0_2 = tl.make_block_ptr(h0, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32) + if K > 128: + p_h0_3 = tl.make_block_ptr(h0, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32) + if K > 192: + p_h0_4 = tl.make_block_ptr(h0, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32) + + # main recurrence + for i_t in range(NT): + if i_t % num_householder == 0: + i_t_true = i_t // num_householder + p_h1 = tl.make_block_ptr(h + i_t_true * stride_h, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_h2 = tl.make_block_ptr(h + i_t_true * stride_h, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_h3 = tl.make_block_ptr(h + i_t_true * stride_h, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h3, b_h3.to(p_h3.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_h4 = tl.make_block_ptr(h + i_t_true * stride_h, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_h4, b_h4.to(p_h4.dtype.element_ty), boundary_check=(0, 1)) + + p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), + (BT, BV), (1, 0)) if SAVE_NEW_VALUE else None + b_v_new = tl.zeros([BT, BV], dtype=tl.float32) + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 0), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v_new += tl.dot(b_w, b_h1.to(b_w.dtype)) + if K > 64: + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v_new += tl.dot(b_w, b_h2.to(b_w.dtype)) + if K > 128: + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v_new += tl.dot(b_w, b_h3.to(b_w.dtype)) + if K > 192: + p_w = tl.make_block_ptr(w, (T, K), (stride_k, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_v_new += tl.dot(b_w, b_h4.to(b_w.dtype)) + b_v_new = -b_v_new + tl.load(p_v, boundary_check=(0, 1)) + + if SAVE_NEW_VALUE: + p_v_new = tl.make_block_ptr(v_new, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + tl.store(p_v_new, b_v_new.to(p_v_new.dtype.element_ty), boundary_check=(0, 1)) + + if USE_G: + m_t = (i_t * BT + tl.arange(0, BT)) < T + last_idx = min((i_t + 1) * BT, T) - 1 + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_v_new = b_v_new * tl.where(m_t, exp(b_g_last - b_g), 0)[:, None] + b_g_last = exp(b_g_last) + b_h1 = b_h1 * b_g_last + if K > 64: + b_h2 = b_h2 * b_g_last + if K > 128: + b_h3 = b_h3 * b_g_last + if K > 192: + b_h4 = b_h4 * b_g_last + b_v_new = b_v_new.to(k.dtype.element_ty) + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h1 += tl.dot(b_k, b_v_new) + if K > 64: + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h2 += tl.dot(b_k, b_v_new) + if K > 128: + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h3 += tl.dot(b_k, b_v_new) + if K > 192: + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_h4 += tl.dot(b_k, b_v_new) + # epilogue + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h2.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h3.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_ht, b_h4.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [4, 3, 2] + for BV in [64, 32] + ], + key=['H', 'K', 'V', 'BT', 'BV', 'USE_G'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gated_delta_product_bwd_kernel_dhu_blockdim64( + q, + k, + w, + g, + dht, + dh0, + do, + dh, + dv, + dv2, + cu_seqlens, + chunk_offsets, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh1 = tl.zeros([64, BV], dtype=tl.float32) + if K > 64: + b_dh2 = tl.zeros([64, BV], dtype=tl.float32) + if K > 128: + b_dh3 = tl.zeros([64, BV], dtype=tl.float32) + if K > 192: + b_dh4 = tl.zeros([64, BV], dtype=tl.float32) + + # calculate offset + dh += (boh * H + i_h) * K*V + dv += (bos * H + i_h) * V + dv2 += (bos * H + i_h) * V + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + w += (bos * H + i_h) * K + do += (bos * H + i_h) * V + stride_v = H*V + stride_h = H*K*V + stride_k = H*K + if USE_INITIAL_STATE: + dh0 += i_nh * K*V + if USE_FINAL_STATE_GRADIENT: + dht += i_nh * K*V + + if USE_FINAL_STATE_GRADIENT: + p_dht1 = tl.make_block_ptr(dht, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + b_dh1 += tl.load(p_dht1, boundary_check=(0, 1)) + if K > 64: + p_dht2 = tl.make_block_ptr(dht, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + b_dh2 += tl.load(p_dht2, boundary_check=(0, 1)) + if K > 128: + p_dht3 = tl.make_block_ptr(dht, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + b_dh3 += tl.load(p_dht3, boundary_check=(0, 1)) + if K > 192: + p_dht4 = tl.make_block_ptr(dht, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + b_dh4 += tl.load(p_dht4, boundary_check=(0, 1)) + + for i_t in range(NT - 1, -1, -1): + p_dh1 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh1, b_dh1.to(p_dh1.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_dh2 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh2, b_dh2.to(p_dh2.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_dh3 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh3, b_dh3.to(p_dh3.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_dh4 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh4, b_dh4.to(p_dh4.dtype.element_ty), boundary_check=(0, 1)) + + if USE_G: + last_idx = min((i_t + 1) * BT, T) - 1 + bg_last = tl.load(g + (bos + last_idx) * H + i_h) + bg_last_exp = exp(bg_last) + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_exp = exp(b_g) + else: + bg_last = None + last_idx = None + b_g = None + b_g_exp = None + + p_dv = tl.make_block_ptr(dv, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_wo = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv2 = tl.make_block_ptr(dv2, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + b_wo = tl.load(p_wo, boundary_check=(0, 1)) + b_dv = tl.zeros([BT, BV], dtype=tl.float32) + + # Update dv + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 0), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dv += tl.dot(b_k, b_dh1.to(b_k.dtype)) + + if K > 64: + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dv += tl.dot(b_k, b_dh2.to(b_k.dtype)) + + if K > 128: + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dv += tl.dot(b_k, b_dh3.to(b_k.dtype)) + + if K > 192: + p_k = tl.make_block_ptr(k, (T, K), (stride_k, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dv += tl.dot(b_k, b_dh4.to(b_k.dtype)) + + if USE_G: + m_t = (i_t * BT + tl.arange(0, BT)) < T + b_dv *= tl.where(m_t, exp(bg_last - b_g), 0)[:, None] + b_dv += tl.load(p_dv, boundary_check=(0, 1)) + + tl.store(p_dv2, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + # Update dh + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)) + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + if USE_G: + b_dh1 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + b_q = (b_q * scale).to(b_q.dtype) + b_dh1 += tl.dot(b_q, b_wo.to(b_q.dtype))-tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 64: + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + if USE_G: + b_dh2 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + b_q = (b_q * scale).to(b_q.dtype) + b_dh2 += tl.dot(b_q, b_wo.to(b_q.dtype))-tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 128: + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + if USE_G: + b_dh3 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + b_q = (b_q * scale).to(b_q.dtype) + b_dh3 += tl.dot(b_q, b_wo.to(b_q.dtype))-tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 192: + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + if USE_G: + b_dh4 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + b_q = (b_q * scale).to(b_q.dtype) + b_dh4 += tl.dot(b_q, b_wo.to(b_q.dtype))-tl.dot(b_w, b_dv.to(b_w.dtype)) + + if USE_INITIAL_STATE: + p_dh0 = tl.make_block_ptr(dh0, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh0, b_dh1.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + if K > 64: + p_dh1 = tl.make_block_ptr(dh0, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh1, b_dh2.to(p_dh1.dtype.element_ty), boundary_check=(0, 1)) + if K > 128: + p_dh2 = tl.make_block_ptr(dh0, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh2, b_dh3.to(p_dh2.dtype.element_ty), boundary_check=(0, 1)) + if K > 192: + p_dh3 = tl.make_block_ptr(dh0, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)) + tl.store(p_dh3, b_dh4.to(p_dh3.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_gated_delta_product_fwd_h( + k: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + g: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + chunk_size: int = 64, # SY: remove this argument and force chunk size 64? + save_new_value: bool = True, + cu_seqlens: torch.LongTensor | None = None, + num_householder: int = 1, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, u.shape[-1] + assert T % num_householder == 0, "T must be divisible by num_householder" + T_true = T // num_householder + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens // num_householder, chunk_size) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T_true, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - \ + 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens // num_householder, BT) + assert K <= 256, "current kernel does not support head dimension larger than 256." + h = k.new_empty(B, NT, H, K, V) + final_state = k.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + v_new = torch.empty_like(u) if save_new_value else None + + def grid(meta): return (triton.cdiv(V, meta['BV']), N*H) + chunk_gated_delta_product_fwd_kernel_h_blockdim64[grid]( + k=k, + v=u, + w=w, + v_new=v_new, + g=g, + h=h, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + num_householder=num_householder, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return h, v_new, final_state + + +def chunk_gated_delta_product_bwd_dhu( + q: torch.Tensor, + k: torch.Tensor, + w: torch.Tensor, + g: torch.Tensor, + h0: torch.Tensor, + dht: torch.Tensor | None, + do: torch.Tensor, + dv: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, # SY: remove this argument and force chunk size 64? +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *q.shape, do.shape[-1] + + # N: the actual number of sequences in the batch with either equal or variable lengths + BT = 64 + assert K <= 256, "current kernel does not support head dimension being larger than 256." + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + dh = q.new_empty(B, NT, H, K, V) + dh0 = torch.empty_like(h0, dtype=torch.float32) if h0 is not None else None + dv2 = torch.empty_like(dv) + + def grid(meta): return (triton.cdiv(V, meta['BV']), N*H) + chunk_gated_delta_product_bwd_kernel_dhu_blockdim64[grid]( + q=q, + k=k, + w=w, + g=g, + dht=dht, + dh0=dh0, + do=do, + dh=dh, + dv=dv, + dv2=dv2, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return dh, dh0, dv2 diff --git a/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_deltaproduct_o.py b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_deltaproduct_o.py new file mode 100644 index 0000000000000000000000000000000000000000..5e1cfaa410ffeeb6af4e12183a7a27f28f4a6adc --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_deltaproduct_o.py @@ -0,0 +1,153 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_nvidia_hopper + +BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BKV_LIST + for BV in BKV_LIST + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_o( + q, + k, + v, + h, + g, + o, + cu_seqlens, + chunk_indices, + scale, + T, + num_householder: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * num_householder * H + i_h) * K + v += (bos * num_householder * H + i_h) * V + o += (bos * H + i_h) * V + h += (i_tg * H + i_h).to(tl.int64) * K*V + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_h = tl.make_block_ptr(h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BK] @ [BK, BV] -> [BT, BV] + b_o += tl.dot(b_q, b_h) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + if USE_G: + g += bos * H + i_h + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t) + b_m = tl.where(m_A, exp(b_g[:, None] - b_g[None, :]), 0) + b_o = b_o * exp(b_g)[:, None] + else: + b_m = ((o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t)).to(tl.float32) + + for i_dp in range(num_householder): + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k+i_dp*H*K, (K, T), (1, num_householder*H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BK] @ [BK, BT] -> [BT, BT] + b_A += tl.dot(b_q, b_k) + b_A = b_A * b_m + p_v = tl.make_block_ptr(v+i_dp*H*V, (T, V), (H*V*num_householder, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_o += tl.dot(b_A.to(b_v.dtype), b_v) + b_o = b_o * scale + p_o = tl.make_block_ptr(o, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_gated_delta_product_fwd_o( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + g: torch.Tensor | None = None, # cumsum of log decay + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + num_householder: int = 1, +) -> torch.Tensor: + assert q.shape[1] * num_householder == k.shape[1], "q.shape[1] * num_householder must be equal to k.shape[1]" + B, T, H, K, V = *q.shape, v.shape[-1] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + o = v.new_empty(B, T, H, V).fill_(-float('inf')) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * H) + chunk_fwd_kernel_o[grid]( + q, + k, + v, + h, + g, + o, + cu_seqlens, + chunk_indices, + scale, + T=T, + num_householder=num_householder, + H=H, + K=K, + V=V, + BT=BT, + ) + return o diff --git a/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_ref.py b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_ref.py new file mode 100644 index 0000000000000000000000000000000000000000..d162035ef7df75f74394b395399bf0bc7958c3ee --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_product/chunk_ref.py @@ -0,0 +1,66 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +from einops import rearrange + +from fla.ops.delta_rule import chunk_delta_rule +from fla.ops.gated_delta_rule import chunk_gated_delta_rule + + +@torch.compiler.disable +def chunk_gated_delta_product_ref( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + num_householder: int, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + use_qk_l2norm_in_kernel: bool = False, +): + assert q.dtype != torch.float32, "ChunkGatedDeltaProductFunction does not support float32. Please use bfloat16." + B, T, H, K = q.shape + V = v.shape[-1] + assert k.shape == (B, T*num_householder, H, K) + assert v.shape == (B, T*num_householder, H, V) + assert beta.shape == (B, T*num_householder, H) + if g is not None: + assert g.shape == (B, T, H) + q_new = q.new_zeros(B, T, num_householder, H, K) + q_new[:, :, -1] = q + q = rearrange(q_new, 'b t n h d -> b (t n) h d') + + if g is not None: + g_new = g.new_zeros(B, T, num_householder, H, dtype=torch.float32) + g_new[:, :, 0] = g + g = rearrange(g_new, 'b t n h -> b (t n) h') + o, final_state = chunk_gated_delta_rule( + q=q, + k=k, + v=v, + g=g, + beta=beta, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens * num_householder if cu_seqlens is not None else None, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + scale=scale, + ) + else: + o, final_state = chunk_delta_rule( + q=q, + k=k, + v=v, + beta=beta, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens * num_householder if cu_seqlens is not None else None, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + scale=scale, + ) + o = rearrange(o, 'b (t n) h d -> b t n h d', n=num_householder) + return o[:, :, -1].contiguous(), final_state diff --git a/code/flash-linear-attention/fla/ops/gated_delta_product/naive.py b/code/flash-linear-attention/fla/ops/gated_delta_product/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..04b5b87f57b33c7842979a473f5ab4395b9e33d9 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_product/naive.py @@ -0,0 +1,36 @@ +import torch + + +def naive_recurrent_gated_delta_product(q, k, v, g, beta, scale, cu_seqlens, + initial_state=None, output_final_state=False, + num_householder=1): + q_original_dtype = q.dtype + B, T, H, K = q.shape + V = v.shape[-1] + assert k.shape == (B, T*num_householder, H, K) + assert v.shape == (B, T*num_householder, H, V) + assert beta.shape == (B, T*num_householder, H) + if g is not None: + assert g.shape == (B, T, H) + q, k, v, beta = map(lambda x: x.float(), (q, k, v, beta)) + + h = torch.zeros(B, H, K, V, dtype=torch.float32, device=q.device) + if initial_state is not None: + h = initial_state + + o = torch.zeros(B, T, H, V, dtype=torch.float32, device=q.device) + + for i in range(T): + if g is not None: + h = h * g[:, i, :].exp()[..., None, None] + # multiple state transition + for j in range(num_householder): + k_ij = k[:, i*num_householder+j, :, :] + v_ij = v[:, i*num_householder+j, :, :] + beta_ij = beta[:, i*num_householder+j, :] + h = h + (v_ij - (h * k_ij[..., None]).sum(-2)).unsqueeze(-2) * k_ij[..., None] * beta_ij[..., None, None] + # memory readout + q_i = q[:, i, :, :] + o_i = (h * q_i[..., None]).sum(-2) + o[:, i] = o_i + return o.to(q_original_dtype), h diff --git a/code/flash-linear-attention/fla/ops/gated_delta_rule/__init__.py b/code/flash-linear-attention/fla/ops/gated_delta_rule/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b0183a19a9a805f2b1640e42919ba7c1b6d0e2a3 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_rule/__init__.py @@ -0,0 +1,7 @@ +from .chunk import chunk_gated_delta_rule +from .fused_recurrent import fused_recurrent_gated_delta_rule + +__all__ = [ + "chunk_gated_delta_rule", + "fused_recurrent_gated_delta_rule", +] diff --git a/code/flash-linear-attention/fla/ops/gated_delta_rule/chunk.py b/code/flash-linear-attention/fla/ops/gated_delta_rule/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..35bffaa7d5e2f1e32a2942d287bcdb26be13e3f3 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_rule/chunk.py @@ -0,0 +1,323 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_bwd_dhu, chunk_gated_delta_rule_fwd_h +from fla.ops.common.chunk_o import chunk_bwd_dqkwg, chunk_bwd_dv_local, chunk_fwd_o +from fla.ops.common.chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd +from fla.ops.gated_delta_rule.wy_fast import prepare_wy_repr_bwd, recompute_w_u_fwd +from fla.ops.utils import chunk_local_cumsum, solve_tril +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_gated_delta_rule_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, +): + g = chunk_local_cumsum(g, chunk_size=64, cu_seqlens=cu_seqlens) + # obtain WY representation. u is actually the new v. + A = chunk_scaled_dot_kkt_fwd( + k=k, + g=g, + beta=beta, + cu_seqlens=cu_seqlens, + output_dtype=torch.float32, + ) + A = solve_tril( + A=A, + cu_seqlens=cu_seqlens, + output_dtype=k.dtype, + ) + w, u = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + g=g, + cu_seqlens=cu_seqlens, + ) + h, v_new, final_state = chunk_gated_delta_rule_fwd_h( + k=k, + w=w, + u=u, + g=g, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + o = chunk_fwd_o( + q=q, + k=k, + v=v_new, + h=h, + g=g, + scale=scale, + cu_seqlens=cu_seqlens, + ) + return g, o, A, final_state + + +def chunk_gated_delta_rule_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, +): + w, u = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + g=g, + cu_seqlens=cu_seqlens, + ) + h, v_new, _ = chunk_gated_delta_rule_fwd_h( + k=k, + w=w, + u=u, + g=g, + initial_state=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + ) + dv = chunk_bwd_dv_local( + q=q, + k=k, + g=g, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dh, dh0, dv = chunk_gated_delta_rule_bwd_dhu( + q=q, + k=k, + w=w, + g=g, + h0=initial_state, + dht=dht, + do=do, + dv=dv, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dq, dk, dw, dg = chunk_bwd_dqkwg( + q=q, + k=k, + v=v_new, + w=w, + g=g, + h=h, + dv=dv, + do=do, + dh=dh, + scale=scale, + cu_seqlens=cu_seqlens, + ) + dk2, dv, db, dg2 = prepare_wy_repr_bwd( + k=k, + v=v, + beta=beta, + g=g, + A=A, + dw=dw, + du=dv, + cu_seqlens=cu_seqlens, + ) + dk.add_(dk2) + dg.add_(dg2) + dg = chunk_local_cumsum(dg, chunk_size=64, reverse=True, cu_seqlens=cu_seqlens) + return dq, dk, dv, db, dg, dh0 + + +class ChunkGatedDeltaRuleFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + use_qk_l2norm_in_kernel: bool = False, + ): + q_rstd, k_rstd = None, None + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q) + k, k_rstd = l2norm_fwd(k) + + g, o, A, final_state = chunk_gated_delta_rule_fwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, q_rstd, k, k_rstd, v, g, beta, A, initial_state, cu_seqlens) + ctx.scale = scale + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + q, q_rstd, k, k_rstd, v, g, beta, A, initial_state, cu_seqlens = ctx.saved_tensors + dq, dk, dv, db, dg, dh0 = chunk_gated_delta_rule_bwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + A=A, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + ) + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + return dq.to(q), dk.to(k), dv.to(v), dg.to(g), db.to(beta), None, dh0, None, None, None + + +@torch.compiler.disable +def chunk_gated_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + **kwargs, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + (forget) gating tensor (in log space!) of shape `[B, T, H]`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (bool): + Whether to apply L2norm to the q/k tensor internally. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gated_delta_rule import chunk_gated_delta_rule + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') + >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid() + >>> g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda') + >>> o, ht = chunk_gated_delta_rule( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = chunk_gated_delta_rule( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if 'head_first' in kwargs: + warnings.warn( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = ChunkGatedDeltaRuleFunction.apply( + q, + k, + v, + g, + beta, + scale, + initial_state, + output_final_state, + cu_seqlens, + use_qk_l2norm_in_kernel, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gated_delta_rule/fused_recurrent.py b/code/flash-linear-attention/fla/ops/gated_delta_rule/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..aeb8b73d4e7427c6055bac3370fc9cb23f143078 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_rule/fused_recurrent.py @@ -0,0 +1,349 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import input_guard + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_GK': lambda args: args['gk'] is not None, + 'USE_GV': lambda args: args['gv'] is not None, + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_gated_delta_rule_fwd_kernel( + q, + k, + v, + g, + gk, + gv, + beta, + o, + h0, + ht, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_QK_L2NORM_IN_KERNEL: tl.constexpr, + IS_BETA_HEADWISE: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_hv = i_nh // HV, i_nh % HV + i_h = i_hv // (HV // H) + + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + o_k = tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + + p_q = q + (bos * H + i_h) * K + o_k + p_k = k + (bos * H + i_h) * K + o_k + p_v = v + (bos * HV + i_hv) * V + o_v + if USE_G: + p_g = g + bos * HV + i_hv + if USE_GK: + p_gk = gk + (bos * HV + i_hv) * K + o_k + if USE_GV: + p_gv = gv + (bos * HV + i_hv) * V + o_v + if IS_BETA_HEADWISE: + p_beta = beta + bos * HV + i_hv + else: + p_beta = beta + (bos * HV + i_hv) * V + o_v + + p_o = o + (bos * HV + i_hv) * V + o_v + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + if USE_QK_L2NORM_IN_KERNEL: + b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6) + b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6) + b_q = b_q * scale + if IS_BETA_HEADWISE: + b_beta = tl.load(p_beta).to(tl.float32) + else: + b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32) + + # [BK, BV] + if USE_G: + b_g = tl.load(p_g).to(tl.float32) + b_h *= exp(b_g) + + if USE_GK: + b_gk = tl.load(p_gk).to(tl.float32) + b_h *= exp(b_gk[:, None]) + + if USE_GV: + b_gv = tl.load(p_gv).to(tl.float32) + b_h *= exp(b_gv[None, :]) + + b_v = b_beta * (b_v - tl.sum(b_h * b_k[:, None], 0)) + b_h += b_k[:, None] * b_v + + # [BV] + b_o = tl.sum(b_h * b_q[:, None], 0) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + + p_q += H*K + p_k += H*K + p_v += HV*V + if USE_G: + p_g += HV + if USE_GK: + p_gk += HV*K + if USE_GV: + p_gv += HV*V + p_beta += HV * (1 if IS_BETA_HEADWISE else V) + p_o += HV*V + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +def fused_recurrent_gated_delta_rule_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + beta: torch.Tensor | None = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HV = v.shape[2] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK = triton.next_power_of_2(K) + BV = min(8, triton.next_power_of_2(V)) if gv is None else triton.next_power_of_2(V) + NV = triton.cdiv(V, BV) + + o = torch.empty_like(v) + final_state = q.new_empty(N, HV, K, V, dtype=torch.float32) if output_final_state else None + + grid = (NV, N * HV) + fused_recurrent_gated_delta_rule_fwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + gk=gk, + gv=gv, + beta=beta, + o=o, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + scale=scale, + T=T, + B=B, + H=H, + HV=HV, + K=K, + V=V, + BK=BK, + BV=BV, + IS_BETA_HEADWISE=beta.ndim != v.ndim, + USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel, + num_warps=1, + num_stages=3, + ) + return o, final_state + + +class FusedRecurrentFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + beta: torch.Tensor | None = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + o, final_state = fused_recurrent_gated_delta_rule_fwd( + q=q, + k=k, + v=v, + g=g, + gk=gk, + gv=gv, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + cu_seqlens=cu_seqlens, + ) + + return o, final_state + + @staticmethod + @input_guard + def backward(ctx, do, dht): + raise NotImplementedError( + "Backward pass is not implemented yet and we do not have plans to implement it " + "because we haven't figured out how to compute dg without materializing the full " + "hidden states for all time steps.", + ) + + +def fused_recurrent_gated_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + beta: torch.Tensor | None = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, HV, V]`. + GVA is applied if `HV > H`. + g (torch.Tensor): + g (decays) of shape `[B, T, HV]`. Default: `None`. + gk (torch.Tensor): + gk (decays) of shape `[B, T, HV, K]`. Default: `None`. + gv (torch.Tensor): + gv (decays) of shape `[B, T, HV, V]`. Default: `None`. + beta (torch.Tensor): + betas of shape `[B, T, HV]`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, HV, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, HV, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (Optional[bool]): + Whether to use L2 normalization in the kernel. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HV, V]`. + final_state (torch.Tensor): + Final state of shape `[N, HV, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gated_delta_rule import fused_recurrent_gated_delta_rule + # inputs with equal lengths + >>> B, T, H, HV, K, V = 4, 2048, 4, 8, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, HV, V, device='cuda') + >>> g = F.logsigmoid(torch.rand(B, T, HV, device='cuda')) + >>> beta = torch.rand(B, T, HV, device='cuda').sigmoid() + >>> h0 = torch.randn(B, HV, K, V, device='cuda') + >>> o, ht = fused_gated_recurrent_delta_rule( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, g, beta)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = fused_gated_recurrent_delta_rule( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + if beta is None: + beta = torch.ones_like(q[..., 0]) + + o, final_state = FusedRecurrentFunction.apply( + q, + k, + v, + g, + gk, + gv, + beta, + scale, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gated_delta_rule/wy_fast.py b/code/flash-linear-attention/fla/ops/gated_delta_rule/wy_fast.py new file mode 100644 index 0000000000000000000000000000000000000000..c5119dcf7c8f40516c9b60289f2f1297f359866d --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gated_delta_rule/wy_fast.py @@ -0,0 +1,301 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def recompute_w_u_fwd_kernel( + k, + v, + beta, + w, + u, + A, + g, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + p_b = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_b = tl.load(p_b, boundary_check=(0,)) + + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_A = tl.load(p_A, boundary_check=(0, 1)) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_u = tl.make_block_ptr(u + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_vb = (b_v * b_b[:, None]).to(b_v.dtype) + b_u = tl.dot(b_A, b_vb, allow_tf32=False) + tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1)) + + if USE_G: + p_g = tl.make_block_ptr(g + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = exp(tl.load(p_g, boundary_check=(0,))) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_w = tl.make_block_ptr(w + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_kb = b_k * b_b[:, None] + if USE_G: + b_kb *= b_g[:, None] + b_w = tl.dot(b_A, b_kb.to(b_k.dtype)) + tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_bwd_kernel( + k, + v, + beta, + g, + A, + dw, + du, + dk, + dv, + db, + dg, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_b = tl.make_block_ptr(beta + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_db = tl.make_block_ptr(db + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + + b_b = tl.load(p_b, boundary_check=(0,)) + b_db = tl.zeros([BT], dtype=tl.float32) + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + + if USE_G: + p_g = tl.make_block_ptr(g + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_exp = tl.exp(b_g) + b_dg = tl.zeros([BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dw = tl.make_block_ptr(dw + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + # [BT, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + if USE_G: + b_kbg = b_k * (b_b * b_g_exp)[:, None] + else: + b_kbg = b_k * b_b[:, None] + b_dw = tl.load(p_dw, boundary_check=(0, 1)) + + b_dA += tl.dot(b_dw, tl.trans(b_kbg).to(b_dw.dtype)) + b_dkbg = tl.dot(b_A, b_dw) + if USE_G: + b_dk = b_dkbg * (b_g_exp * b_b)[:, None] + b_db += tl.sum(b_dkbg * b_k * b_g_exp[:, None], 1) + b_dg += tl.sum(b_dkbg * b_kbg, 1) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_du = tl.make_block_ptr(du + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_vb = (b_v * b_b[:, None]).to(b_v.dtype) + b_du = tl.load(p_du, boundary_check=(0, 1)) + b_dA += tl.dot(b_du, tl.trans(b_vb)) + b_dvb = tl.dot(b_A, b_du) + b_dv = b_dvb * b_b[:, None] + b_db += tl.sum(b_dvb * b_v, 1) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_dA = tl.where(m_A, b_dA, 0) + b_dA = tl.dot(b_dA.to(b_A.dtype), b_A) + b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) + + if USE_G: + b_dA *= exp(b_g[:, None] - b_g[None, :]) + b_dA = tl.where(m_A, -b_dA, 0) + + b_dA = b_dA.to(k.dtype.element_ty) + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dk = tl.load(p_dk, boundary_check=(0, 1)) + b_kb = (b_k * b_b[:, None]).to(b_k.dtype) + b_A += tl.dot(b_kb, tl.trans(b_k)) + b_dkb = tl.dot(b_dA, b_k) + b_db += tl.sum(b_dkb * b_k, 1) + b_dk += tl.dot(tl.trans(b_dA), b_kb) + b_dk += b_dkb * b_b[:, None] + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0,)) + + if USE_G: + b_AdA = b_dA * b_A + p_dg = tl.make_block_ptr(dg + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_dg += tl.sum(b_AdA, axis=1) - tl.sum(b_AdA, axis=0) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + +def recompute_w_u_fwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + g: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = A.shape[-1] + BK = 64 + BV = 64 + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + w = torch.empty_like(k) + u = torch.empty_like(v) + recompute_w_u_fwd_kernel[(NT, B*H)]( + k=k, + v=v, + beta=beta, + w=w, + u=u, + A=A, + g=g, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return w, u + + +def prepare_wy_repr_bwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + dw: torch.Tensor, + du: torch.Tensor, + g: torch.Tensor = None, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = 64 + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + + dk = torch.empty_like(k) + dv = torch.empty_like(v) + dg = torch.empty_like(g) if g is not None else None + db = torch.empty_like(beta) + prepare_wy_repr_bwd_kernel[(NT, B * H)]( + k=k, + v=v, + beta=beta, + g=g, + A=A, + dw=dw, + du=du, + dk=dk, + dv=dv, + db=db, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dk, dv, db, dg + + +bwd_prepare_wy_repr = prepare_wy_repr_bwd + +fwd_recompute_w_u = recompute_w_u_fwd diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/README.md b/code/flash-linear-attention/fla/ops/generalized_delta_rule/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f96c22f44a51ad3e6fdeb824eb2aded660223600 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/README.md @@ -0,0 +1,37 @@ +# Generalized Delta Rule + +In delta rule we have the recurrence: + +```math +\mathbf{S}_t = \mathbf{S}_{t-1}(\mathbf{I}-\beta_t \mathbf{k}_t\mathbf{k}_t^T) + \beta_t \mathbf{v}_t\mathbf{k}_t^T +``` + +This repository implements a delta rule variant where $\mathbf{I}$ is not necessarily an identity matrix; $\mathbf{k}_t$ in $\mathbf{I} - \beta_t \mathbf{k}_t\mathbf{k}_t^T$ might be different from input $\mathbf{k}_t$ in $\mathbf{v}_t\mathbf{k}_t^T$. + +## IPLR (Identity Plus Low Rank) + +The first variant is IPLR, where we have: + +```math +\mathbf{S}_t = \mathbf{S}_{t-1}(\mathbf{I}+\mathbf{a}_t\mathbf{b}_t^T) + \mathbf{v}_t\mathbf{k}_t^T +``` + +When $\mathbf{a}_t = -\beta_t \mathbf{k}_t$, $\mathbf{b}_t = \mathbf{k}_t$, $\mathbf{v}_t= \beta_t \mathbf{v}_t$, we recover the original delta rule. Since here the transition matrix is identity-plus-low-rank, we refer to this variant as IPLR. + +### Numerical Stability + +$\mathbf{a}_t$ and $\mathbf{b}_t$ must be in opposite directions, that is, $\mathbf{b}_t = \lambda_t \mathbf{a}_t$ where $\lambda_t < 0$. For an understanding of why this is necessary, you can derive the eigenvalues of the transition matrix. + +## DPLR (Diagonal Plus Low Rank) + +The second variant is DPLR, where we have: + +```math +\mathbf{S}_t = \mathbf{S}_{t-1}(\mathbf{D}_t+\mathbf{a}_t\mathbf{b}_t^T) + \mathbf{v}_t\mathbf{k}_t^T +``` + +Here, $\mathbf{I}$ is replaced by a diagonal matrix $\mathbf{D}_t$. This transition matrix structure has been utilized in RWKV7. + +## Efficient Chunkwise Implementation + +For detailed information about efficient chunkwise implementation, please refer to our [technical note](https://drive.google.com/file/d/1rJbO3dU4fe7OKG3w7Yg058z_BNIuavNF/view?usp=sharing). diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/__init__.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..33875bc69eae5bea4ec131e11b773c69b6e1c093 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/__init__.py @@ -0,0 +1,9 @@ +from .dplr import chunk_dplr_delta_rule, fused_recurrent_dplr_delta_rule +from .iplr import chunk_iplr_delta_rule, fused_recurrent_iplr_delta_rule + +__all__ = [ + 'chunk_dplr_delta_rule', + 'fused_recurrent_dplr_delta_rule', + 'chunk_iplr_delta_rule', + 'fused_recurrent_iplr_delta_rule', +] diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/__init__.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..95b9d07a31be8868d25c7e86d564b399eb5b1532 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/__init__.py @@ -0,0 +1,7 @@ +from .chunk import chunk_dplr_delta_rule +from .fused_recurrent import fused_recurrent_dplr_delta_rule + +__all__ = [ + 'chunk_dplr_delta_rule', + 'fused_recurrent_dplr_delta_rule', +] diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..9b1e302e824a5efb1d6ab2f655c2831406cca889 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk.py @@ -0,0 +1,357 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.generalized_delta_rule.dplr.chunk_A_bwd import chunk_dplr_bwd_dqk_intra +from fla.ops.generalized_delta_rule.dplr.chunk_A_fwd import chunk_dplr_fwd_intra +from fla.ops.generalized_delta_rule.dplr.chunk_h_bwd import chunk_dplr_bwd_dhu +from fla.ops.generalized_delta_rule.dplr.chunk_h_fwd import chunk_dplr_fwd_h +from fla.ops.generalized_delta_rule.dplr.chunk_o_bwd import chunk_dplr_bwd_dAu, chunk_dplr_bwd_dv, chunk_dplr_bwd_o +from fla.ops.generalized_delta_rule.dplr.chunk_o_fwd import chunk_dplr_fwd_o +from fla.ops.generalized_delta_rule.dplr.wy_fast_bwd import chunk_dplr_bwd_wy +from fla.ops.generalized_delta_rule.dplr.wy_fast_fwd import prepare_wy_repr_fwd +from fla.ops.rwkv6.chunk import chunk_rwkv6_fwd_cumsum +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_dplr_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gk: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + gi, ge = chunk_rwkv6_fwd_cumsum(gk, chunk_size, cu_seqlens=cu_seqlens) + + A_ab, A_qk, A_ak, A_qb, qg, kg, ag, bg = chunk_dplr_fwd_intra( + q=q, + k=k, + a=a, + b=b, + gi=gi, + ge=ge, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del ge + + # A_ab, A_ak, gi, ge torch.float32 + # A_qk, A_qb, qg, kg, ag, bg, dtype=q.dtype, eg: bf16 + w, u, _ = prepare_wy_repr_fwd( + ag=ag, + A_ab=A_ab, + A_ak=A_ak, + v=v, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del A_ab, A_ak + h, v_new, final_state = chunk_dplr_fwd_h( + kg=kg, + bg=bg, + v=v, + w=w, + u=u, + gk=gi, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del u, kg, bg, gi + + o = chunk_dplr_fwd_o( + qg=qg, + v=v, + v_new=v_new, + A_qk=A_qk, + A_qb=A_qb, + h=h, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del v_new, h, A_qk, A_qb + + return o, final_state + + +class ChunkDPLRDeltaRuleFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gk: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + ): + chunk_size = 16 + o, final_state = chunk_dplr_fwd( + q=q, + k=k, + v=v, + a=a, + b=b, + gk=gk, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + ctx.save_for_backward(q, k, v, a, b, gk, initial_state) + ctx.cu_seqlens = cu_seqlens + ctx.scale = scale + ctx.chunk_size = chunk_size + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + q, k, v, a, b, gk, initial_state = ctx.saved_tensors + chunk_size = ctx.chunk_size + cu_seqlens = ctx.cu_seqlens + scale = ctx.scale + + # ******* start recomputing everything, otherwise i believe the gpu memory will be exhausted ******* + gi, ge = chunk_rwkv6_fwd_cumsum(gk, chunk_size, cu_seqlens=cu_seqlens) + + A_ab, A_qk, A_ak, A_qb, qg, kg, ag, bg = chunk_dplr_fwd_intra( + q=q, + k=k, + a=a, + b=b, + gi=gi, + ge=ge, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + w, u, A_ab_inv = prepare_wy_repr_fwd( + ag=ag, + A_ab=A_ab, + A_ak=A_ak, + v=v, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del A_ab + h, v_new, _ = chunk_dplr_fwd_h( + kg=kg, + bg=bg, + v=v, + w=w, + u=u, + gk=gi, + initial_state=initial_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del u + # ******* end of recomputation ******* + # A_ak, A_ab_inv, gi, ge torch.float32 + # A_qk, A_qb, qg, kg, ag, bg, v_new dtype=q.dtype, eg: bf16 + + dv_new_intra, dA_qk, dA_qb = chunk_dplr_bwd_dAu( + v=v, + v_new=v_new, + do=do, + A_qb=A_qb, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + dh, dh0, dv_new = chunk_dplr_bwd_dhu( + qg=qg, + bg=bg, + w=w, + gk=gi, + h0=initial_state, + dht=dht, + do=do, + dv=dv_new_intra, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + dv = chunk_dplr_bwd_dv( + A_qk=A_qk, + kg=kg, + do=do, + dh=dh, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del A_qk + + dqg, dkg, dw, dbg, dgk_last = chunk_dplr_bwd_o( + k=kg, + b=bg, + v=v, + v_new=v_new, + do=do, + h=h, + dh=dh, + dv=dv_new, + w=w, + gk=gi, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + scale=scale, + ) + del v_new + + dA_ab, dA_ak, dv, dag = chunk_dplr_bwd_wy( + A_ab_inv=A_ab_inv, + A_ak=A_ak, + v=v, + ag=ag, + dw=dw, + du=dv_new, + dv0=dv, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + del A_ak + + dq, dk, da, db, dgk = chunk_dplr_bwd_dqk_intra( + q=q, + k=k, + a=a, + b=b, + gi=gi, + ge=ge, + dAqk=dA_qk, + dAqb=dA_qb, + dAak=dA_ak, + dAab=dA_ab, + dgk_last=dgk_last, + dqg=dqg, + dkg=dkg, + dag=dag, + dbg=dbg, + chunk_size=chunk_size, + scale=scale, + cu_seqlens=cu_seqlens, + ) + + return dq.to(q), dk.to(k), dv.to(v), da.to(a), db.to(b), dgk.to(gk), None, dh0, None, None + + +@torch.compiler.disable +def chunk_dplr_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gk: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + a (torch.Tensor): + activations of shape `[B, T, H, K]`. + b (torch.Tensor): + betas of shape `[B, T, H, K]`. + gk (torch.Tensor): + gk of shape `[B, T, H, K]`. decay term in log space! + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if q.dtype == torch.float32: + warnings.warn( + """ChunkDeltaRuleFunction does not support float32 on some platforms. Please use bfloat16/float16. + If you want to use float32, please solve the issue by yourself.""", + category=RuntimeWarning, + stacklevel=2, + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + scale = k.shape[-1] ** -0.5 if scale is None else scale + o, final_state = ChunkDPLRDeltaRuleFunction.apply( + q, + k, + v, + a, + b, + gk, + scale, + initial_state, + output_final_state, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_A_bwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_A_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..abd7b8b2dfba1215b5135b3c5a5b56c9d24b6d53 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_A_bwd.py @@ -0,0 +1,367 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp, gather +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_amd, is_gather_supported, use_cuda_graph + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['BK', 'BT', 'K'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_dplr_bwd_kernel_intra( + q, + k, + a, + b, + gi, + ge, + dAqk, + dAqb, + dAak, + dAab, + dq, + dk, + da, + db, + dqg, + dkg, + dag, + dbg, + dgk, + dgk_offset, + cu_seqlens, + chunk_indices, + scale: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, + GATHER_SUPPORTED: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = (i_b * T).to(tl.int32), (i_b * T + T).to(tl.int32) + + if i_t * BT >= T: + return + + # offset calculation + ge += (bos*H + i_h) * K + gi += (bos*H + i_h) * K + q += (bos*H + i_h) * K + a += (bos*H + i_h) * K + b += (bos*H + i_h) * K + k += (bos*H + i_h) * K + dq += (bos*H + i_h) * K + dk += (bos*H + i_h) * K + da += (bos*H + i_h) * K + db += (bos*H + i_h) * K + dqg += (bos*H + i_h) * K + dag += (bos*H + i_h) * K + dkg += (bos*H + i_h) * K + dbg += (bos*H + i_h) * K + dgk += (bos*H + i_h) * K + dgk_offset += (bos*H + i_h) * K + dAqk += (bos*H + i_h) * BT + dAqb += (bos*H + i_h) * BT + dAak += (bos*H + i_h) * BT + dAab += (bos*H + i_h) * BT + + stride_qk = H*K + stride_A = H*BT + + p_ge = tl.make_block_ptr(ge, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_gi = tl.make_block_ptr(gi, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + # [BC, BK] + b_ge = tl.load(p_ge, boundary_check=(0, 1)) + b_gi = tl.load(p_gi, boundary_check=(0, 1)) + b_dq = tl.zeros([BC, BK], dtype=tl.float32) + b_da = tl.zeros([BC, BK], dtype=tl.float32) + b_dk = tl.zeros([BC, BK], dtype=tl.float32) + b_db = tl.zeros([BC, BK], dtype=tl.float32) + # intra chunk gradient calculation + p_dAqk = tl.make_block_ptr(dAqk, (T, BT), (stride_A, 1), (i_t*BT, 0), (BC, BC), (1, 0)) + p_dAab = tl.make_block_ptr(dAab, (T, BT), (stride_A, 1), (i_t*BT, 0), (BC, BC), (1, 0)) + p_dAqb = tl.make_block_ptr(dAqb, (T, BT), (stride_A, 1), (i_t*BT, 0), (BC, BC), (1, 0)) + p_dAak = tl.make_block_ptr(dAak, (T, BT), (stride_A, 1), (i_t*BT, 0), (BC, BC), (1, 0)) + o_i = tl.arange(0, BC) + p_k = tl.make_block_ptr(k, (T, K), (stride_qk, 1), (i_t*BT, i_k*BK), (BC, BK), (1, 0)) + p_b = tl.make_block_ptr(b, (T, K), (stride_qk, 1), (i_t*BT, i_k*BK), (BC, BK), (1, 0)) + p_a = tl.make_block_ptr(a, (T, K), (stride_qk, 1), (i_t*BT, i_k*BK), (BC, BK), (1, 0)) + p_q = tl.make_block_ptr(q, (T, K), (stride_qk, 1), (i_t*BT, i_k*BK), (BC, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_b = tl.load(p_b, boundary_check=(0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_a = tl.load(p_a, boundary_check=(0, 1)) + b_dAqk = tl.load(p_dAqk, boundary_check=(0, 1)) + b_dAab = tl.load(p_dAab, boundary_check=(0, 1)) + b_dAqb = tl.load(p_dAqb, boundary_check=(0, 1)) + b_dAak = tl.load(p_dAak, boundary_check=(0, 1)) + + # inter chunk gradient calculation + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + # intra chunk gradient calculation + for j in range(0, min(BC, T - i_t * BT)): + # trick to index the block + if GATHER_SUPPORTED: + row_idx = tl.full([1, BK], j, dtype=tl.int16) + col_idx = tl.full([BC, 1], j, dtype=tl.int16) + row_idx_bc = tl.full([1, BC], j, dtype=tl.int16) + # [1, BK] + b_kj = gather(b_k, row_idx, axis=0) + b_bj = gather(b_b, row_idx, axis=0) + b_gij = gather(b_gi, row_idx, axis=0) + b_gej = gather(b_ge, row_idx, axis=0) + b_qj = gather(b_q, row_idx, axis=0) + b_aj = gather(b_a, row_idx, axis=0) + # [BC, 1] + b_dAqk_j = gather(b_dAqk, col_idx, axis=1) + b_dAab_j = gather(b_dAab, col_idx, axis=1) + b_dAqb_j = gather(b_dAqb, col_idx, axis=1) + b_dAak_j = gather(b_dAak, col_idx, axis=1) + # [1, BC] -> [BC, 1] + b_dA_qk_j = tl.sum(gather(b_dAqk, row_idx_bc, axis=0), 0)[:, None] + b_dA_qk_j = tl.sum(gather(b_dAqk, row_idx_bc, axis=0), 0)[:, None] + b_dA_ab_j = tl.sum(gather(b_dAab, row_idx_bc, axis=0), 0)[:, None] + b_dA_qb_j = tl.sum(gather(b_dAqb, row_idx_bc, axis=0), 0)[:, None] + b_dA_ak_j = tl.sum(gather(b_dAak, row_idx_bc, axis=0), 0)[:, None] + else: + mask_idx = tl.arange(0, BC) == j + b_kj = tl.sum(tl.where(mask_idx[:, None], b_k, 0), 0)[None, :] + b_bj = tl.sum(tl.where(mask_idx[:, None], b_b, 0), 0)[None, :] + b_gij = tl.sum(tl.where(mask_idx[:, None], b_gi, 0), 0)[None, :] + b_gej = tl.sum(tl.where(mask_idx[:, None], b_ge, 0), 0)[None, :] + b_dAqk_j = tl.sum(tl.where(mask_idx[None, :], b_dAqk, 0), 1)[:, None] + b_dAab_j = tl.sum(tl.where(mask_idx[None, :], b_dAab, 0), 1)[:, None] + b_dAqb_j = tl.sum(tl.where(mask_idx[None, :], b_dAqb, 0), 1)[:, None] + b_dAak_j = tl.sum(tl.where(mask_idx[None, :], b_dAak, 0), 1)[:, None] + b_dA_qk_j = tl.sum(tl.where(mask_idx[:, None], b_dAqk, 0), 0)[:, None] + b_dA_ab_j = tl.sum(tl.where(mask_idx[:, None], b_dAab, 0), 0)[:, None] + b_dA_qb_j = tl.sum(tl.where(mask_idx[:, None], b_dAqb, 0), 0)[:, None] + b_dA_ak_j = tl.sum(tl.where(mask_idx[:, None], b_dAak, 0), 0)[:, None] + # [1, BK] b_qj, b_aj + b_qj = tl.sum(tl.where(mask_idx[:, None], b_q, 0), 0)[None, :] + b_aj = tl.sum(tl.where(mask_idx[:, None], b_a, 0), 0)[None, :] + + m_e = o_i[:, None] > j + m_i = o_i[:, None] >= j + tmp1 = exp(b_gi - b_gij) + tmp2 = exp(b_ge - b_gij) + b_dq += tl.where(m_i, b_dAqk_j * b_kj * tmp1, 0.) + b_dq += tl.where(m_i, b_dAqb_j * b_bj * tmp1, 0.) + b_da += tl.where(m_e, b_dAab_j * b_bj * tmp2, 0.) + b_da += tl.where(m_e, b_dAak_j * b_kj * tmp2, 0.) + + m_i = o_i[:, None] <= j + m_e = o_i[:, None] < j + tmp1 = exp(b_gij - b_gi) + tmp2 = exp(b_gej - b_gi) + b_dk += tl.where(m_i, b_dA_qk_j * b_qj * tmp1, 0.) + b_dk += tl.where(m_e, b_dA_ak_j * b_aj * tmp2, 0.) + b_db += tl.where(m_i, b_dA_qb_j * b_qj * tmp1, 0.) + b_db += tl.where(m_e, b_dA_ab_j * b_aj * tmp2, 0.) + + # post processing + p_dq = tl.make_block_ptr(dq, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_da = tl.make_block_ptr(da, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_db = tl.make_block_ptr(db, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_dgk = tl.make_block_ptr(dgk, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_dgk_offset = tl.make_block_ptr(dgk_offset, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_dqg = tl.make_block_ptr(dqg, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_dkg = tl.make_block_ptr(dkg, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_dag = tl.make_block_ptr(dag, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_dbg = tl.make_block_ptr(dbg, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_gn = gi + (min(i_t * BT + BT, T) - 1)*stride_qk + o_k + p_gn = tl.max_contiguous(tl.multiple_of(p_gn, BK), BK) + b_gn = tl.load(p_gn, mask=m_k, other=0) + b_da += tl.load(p_dag, boundary_check=(0, 1)) * exp(b_ge) + b_dq += tl.load(p_dqg, boundary_check=(0, 1)) * exp(b_gi) * scale + tmp = exp(b_gn[None, :] - b_gi) + b_dk += tl.load(p_dkg, boundary_check=(0, 1)).to(tl.float32) * tmp + b_db += tl.load(p_dbg, boundary_check=(0, 1)).to(tl.float32) * tmp + tl.store(p_dq, (b_dq).to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_da, b_da.to(p_da.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0, 1)) + b_dgk = (b_dq * b_q + b_da * b_a - b_dk * b_k - b_db * b_b).to(tl.float32) + b_dgk_offset = b_da * b_a + tl.store(p_dgk, b_dgk.to(p_dgk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dgk_offset, b_dgk_offset.to(p_dgk_offset.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + for BK in [32, 64] + ], + key=['BK', 'BT', 'K'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_dplr_bwd_dgk_kernel( + dgk, + dgk_offset, + dgk_last, + dgk_output, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_k, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = (i_b * NT + i_t).to(tl.int32) + bos, eos = (i_b * T).to(tl.int32), (i_b * T + T).to(tl.int32) + + stride_qk = H * K + dgk += (bos * H + i_h) * K + dgk_offset += (bos * H + i_h) * K + dgk_last += (i_tg * H + i_h) * K + dgk_output += (bos * H + i_h) * K + p_dgk_last = dgk_last + tl.arange(0, BK) + i_k * BK + m_k = tl.arange(0, BK) + i_k * BK < K + b_dgk_last = tl.load(p_dgk_last, mask=m_k, other=0) + p_dgk_offset = tl.make_block_ptr(dgk_offset, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dgk = tl.make_block_ptr(dgk, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_dgk = tl.load(p_dgk, boundary_check=(0, 1)) + b_dgk_offset = tl.load(p_dgk_offset, boundary_check=(0, 1)) + # m_inv_cumsum = (tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :]).to(tl.float32) + # b_dgk_cumsum = tl.dot(m_inv_cumsum, b_dgk, allow_tf32=False) + b_dgk_cumsum = tl.cumsum(b_dgk, 0, reverse=True) + b_dgk_cumsum += b_dgk_last[None, :] + b_dgk_cumsum -= b_dgk_offset + p_dgk_output = tl.make_block_ptr(dgk_output, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dgk_output, b_dgk_cumsum.to(p_dgk_output.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_dplr_bwd_dqk_intra( + q: torch.Tensor, + k: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gi: torch.Tensor, + ge: torch.Tensor, + dAqk: torch.Tensor, + dAqb: torch.Tensor, + dAak: torch.Tensor, + dAab: torch.Tensor, + dqg: torch.Tensor, + dkg: torch.Tensor, + dag: torch.Tensor, + dbg: torch.Tensor, + dgk_last: torch.Tensor, + scale: float = 1.0, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K = q.shape + BT = chunk_size + BK = min(64, triton.next_power_of_2(K)) if check_shared_mem() else min(32, triton.next_power_of_2(K)) + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NK = triton.cdiv(K, BK) + + dq = torch.empty_like(q) + dk = torch.empty_like(k) + da = torch.empty_like(a) + db = torch.empty_like(b) + dgk = torch.empty_like(gi, dtype=torch.float) + dgk_offset = torch.empty_like(gi, dtype=torch.float) + + grid = (NK, NT, B * H) + chunk_dplr_bwd_kernel_intra[grid]( + q=q, + k=k, + a=a, + b=b, + gi=gi, + ge=ge, + dAqk=dAqk, + dAqb=dAqb, + dAak=dAak, + dAab=dAab, + dq=dq, + dk=dk, + dgk=dgk, + dgk_offset=dgk_offset, + dqg=dqg, + dkg=dkg, + dag=dag, + dbg=dbg, + da=da, + db=db, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BT, + BK=BK, + GATHER_SUPPORTED=is_gather_supported, + ) + + dgk_output = torch.empty_like(dgk) + + def grid(meta): return (NT, triton.cdiv(K, meta['BK']), B * H) + chunk_dplr_bwd_dgk_kernel[grid]( + dgk=dgk, + dgk_offset=dgk_offset, + dgk_last=dgk_last, + dgk_output=dgk_output, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + ) + return dq, dk, da, db, dgk_output diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_A_fwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_A_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..7b458e5b2185ef093f00d6d391ce5cba297255b3 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_A_fwd.py @@ -0,0 +1,197 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp, gather +from fla.utils import autotune_cache_kwargs, is_amd, is_gather_supported, use_cuda_graph + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['BK', 'BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_dplr_fwd_A_kernel_intra_sub_intra( + q, + k, + a, + b, + gi, + ge, + qg, + kg, + ag, + bg, + Aqk, + Aqb, + Aab, + Aak, + cu_seqlens, + chunk_indices, + scale: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, + GATHER_SUPPORTED: tl.constexpr, +): + i_t, i_b, i_h = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT >= T: + return + + o_i = tl.arange(0, BC) + o_k = tl.arange(0, BK) + m_k = o_k < K + m_A = (i_t * BT + tl.arange(0, BC)) < T + last_idx = min((i_t+1) * BT, T) - 1 + o_A = (bos + i_t * BT + tl.arange(0, BC)) * H*BT + i_h * BT + p_q = tl.make_block_ptr(q + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_a = tl.make_block_ptr(a + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_b = tl.make_block_ptr(b + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_gi = tl.make_block_ptr(gi + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_ge = tl.make_block_ptr(ge + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_g_last = gi + (bos * H + i_h) * K + last_idx * H * K + tl.arange(0, BK) + b_g_last = tl.load(p_g_last, mask=m_k, other=0) + p_qg = tl.make_block_ptr(qg + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_kg = tl.make_block_ptr(kg + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_ag = tl.make_block_ptr(ag + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + p_bg = tl.make_block_ptr(bg + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, 0), (BC, BK), (1, 0)) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = b_q * scale + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_a = tl.load(p_a, boundary_check=(0, 1)) + b_b = tl.load(p_b, boundary_check=(0, 1)) + b_gi = tl.load(p_gi, boundary_check=(0, 1)).to(tl.float32) + b_ge = tl.load(p_ge, boundary_check=(0, 1)).to(tl.float32) + + # deal with decay term. + g_exp = exp(b_gi) + g_exp_inv = exp(-b_gi + b_g_last[None, :]) + b_qg = b_q * g_exp + b_kg = b_k * g_exp_inv + b_bg = b_b * g_exp_inv + b_ag = b_a * exp(b_ge) + tl.store(p_qg, b_qg.to(p_qg.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_bg, b_bg.to(p_bg.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_ag, b_ag.to(p_ag.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_kg, b_kg.to(p_kg.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + # tl.debug_barrier() + + b_q = b_q.to(b_k.dtype) + # inner attn + for j in range(0, min(BC, T - i_t * BT)): + # a trick to index the j-th row of b_k, b_g, b_b + if GATHER_SUPPORTED: + row_idx = tl.full([1, BK], j, dtype=tl.int16) + # [1, BK] + b_k_j = gather(b_k, row_idx, axis=0) + b_gk_j = gather(b_gi, row_idx, axis=0) + b_b_j = gather(b_b, row_idx, axis=0) + else: + mask = tl.arange(0, BC) == j + b_k_j = tl.sum(tl.where(mask[:, None], b_k, 0), 0)[None, :] + b_gk_j = tl.sum(tl.where(mask[:, None], b_gi, 0), 0)[None, :] + b_b_j = tl.sum(tl.where(mask[:, None], b_b, 0), 0)[None, :] + tmp = exp(b_gi - b_gk_j) + b_A_qk = tl.sum(b_q * b_k_j * tmp, 1) + m_i = (o_i >= j).to(tl.float32) + b_A_qk = b_A_qk * m_i + b_A_qb = tl.sum(b_q * b_b_j * tmp, 1) + b_A_qb = b_A_qb * m_i + tmp2 = exp(b_ge - b_gk_j) + b_A_ak = tl.sum(b_a * b_k_j * tmp2, 1) + m_i2 = (o_i > j).to(tl.float32) + b_A_ak = b_A_ak * m_i2 + b_A_ab = tl.sum(b_a * b_b_j * tmp2, 1) + b_A_ab = b_A_ab * m_i2 + + tl.store(Aqk + o_A + j, b_A_qk.to(dtype=Aqk.dtype.element_ty, fp_downcast_rounding="rtne"), mask=m_A) + tl.store(Aqb + o_A + j, b_A_qb.to(dtype=Aqb.dtype.element_ty, fp_downcast_rounding="rtne"), mask=m_A) + tl.store(Aab + o_A + j, b_A_ab.to(dtype=Aqb.dtype.element_ty, fp_downcast_rounding="rtne"), mask=m_A) + tl.store(Aak + o_A + j, b_A_ak.to(dtype=Aqk.dtype.element_ty, fp_downcast_rounding="rtne"), mask=m_A) + + +def chunk_dplr_fwd_intra( + q: torch.Tensor, + k: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gi: torch.Tensor, + ge: torch.Tensor, + scale: float, + chunk_size: int, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K = k.shape + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + Aqk = q.new_empty(B, T, H, BT, dtype=q.dtype) + Aqb = q.new_empty(B, T, H, BT, dtype=q.dtype) + # involving matrix inverse and it'd be better to use float here. + Aab = q.new_empty(B, T, H, BT, dtype=torch.float) + Aak = q.new_empty(B, T, H, BT, dtype=torch.float) + + grid = (NT, B, H) + BK = max(triton.next_power_of_2(K), 16) + qg = torch.empty_like(q) + kg = torch.empty_like(k, dtype=q.dtype) + ag = torch.empty_like(a, dtype=q.dtype) + bg = torch.empty_like(b, dtype=q.dtype) + chunk_dplr_fwd_A_kernel_intra_sub_intra[grid]( + q=q, + k=k, + a=a, + b=b, + gi=gi, + ge=ge, + Aqk=Aqk, + Aqb=Aqb, + Aab=Aab, + Aak=Aak, + qg=qg, + kg=kg, + ag=ag, + bg=bg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BT, + BK=BK, + GATHER_SUPPORTED=is_gather_supported, + ) + return Aab, Aqk, Aak, Aqb, qg, kg, ag, bg diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_h_bwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_h_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..20722982287702acc869bdbdaa56cd9edf535095 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_h_bwd.py @@ -0,0 +1,174 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_amd, use_cuda_graph + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +@triton.heuristics({ + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['BT', 'BK', 'BV', "V"], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_dplr_bwd_kernel_dhu( + qg, + bg, + w, + gk, + dht, + dh0, + do, + dh, + dv, + dv2, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_dht = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dht, boundary_check=(0, 1)) + + mask_k = tl.arange(0, BK) < K + for i_t in range(NT - 1, -1, -1): + p_dh = tl.make_block_ptr(dh + ((boh+i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + b_dh_tmp = tl.zeros([BK, BV], dtype=tl.float32) + for i_c in range(tl.cdiv(BT, BC) - 1, -1, -1): + p_qg = tl.make_block_ptr(qg+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_c * BC), (BK, BC), (0, 1)) + p_bg = tl.make_block_ptr(bg+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_c * BC, i_k * BK), (BC, BK), (1, 0)) + p_w = tl.make_block_ptr(w+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_c * BC), (BK, BC), (0, 1)) + p_dv = tl.make_block_ptr(dv+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_c * BC, i_v * BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_c * BC, i_v * BV), (BC, BV), (1, 0)) + p_dv2 = tl.make_block_ptr(dv2+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_c * BC, i_v * BV), (BC, BV), (1, 0)) + # [BK, BT] + b_qg = tl.load(p_qg, boundary_check=(0, 1)) + # [BT, BK] + b_bg = tl.load(p_bg, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + # [BT, V] + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dv = tl.load(p_dv, boundary_check=(0, 1)) + b_dv2 = b_dv + tl.dot(b_bg, b_dh.to(b_bg.dtype)) + tl.store(p_dv2, b_dv2.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + # [BK, BV] + b_dh_tmp += tl.dot(b_qg, b_do.to(b_qg.dtype)) + b_dh_tmp += tl.dot(b_w, b_dv2.to(b_qg.dtype)) + last_idx = min((i_t + 1) * BT, T) - 1 + bg_last = tl.load(gk + ((bos + last_idx) * H + i_h) * K + tl.arange(0, BK), mask=mask_k) + b_dh *= exp(bg_last)[:, None] + b_dh += b_dh_tmp + + if USE_INITIAL_STATE: + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_dplr_bwd_dhu( + qg: torch.Tensor, + bg: torch.Tensor, + w: torch.Tensor, + gk: torch.Tensor, + h0: torch.Tensor, + dht: torch.Tensor | None, + do: torch.Tensor, + dv: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *qg.shape, do.shape[-1] + BT = chunk_size + BK = max(triton.next_power_of_2(K), 16) + assert BK <= 256, "current kernel does not support head dimension being larger than 256." + # H100 + if check_shared_mem('hopper', qg.device.index): + BV = 64 + BC = 64 if K <= 128 else 32 + elif check_shared_mem('ampere', qg.device.index): # A100 + BV = 32 + BC = 32 + else: # Etc: 4090 + BV = 16 + BC = 16 + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + BC = min(BT, BC) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + assert NK == 1, 'NK > 1 is not supported because it involves time-consuming synchronization' + + dh = qg.new_empty(B, NT, H, K, V) + dh0 = torch.empty_like(h0, dtype=torch.float32) if h0 is not None else None + dv2 = torch.zeros_like(dv) + + grid = (NK, NV, N * H) + chunk_dplr_bwd_kernel_dhu[grid]( + qg=qg, + bg=bg, + w=w, + gk=gk, + dht=dht, + dh0=dh0, + do=do, + dh=dh, + dv=dv, + dv2=dv2, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BC=BC, + BK=BK, + BV=BV, + ) + return dh, dh0, dv2 diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_h_fwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_h_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..743b4bb52aff293323aef6397c5ec0da322ef3a4 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_h_fwd.py @@ -0,0 +1,174 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_amd, use_cuda_graph + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['BT', 'BK', 'BV'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_dplr_fwd_kernel_h( + kg, + v, + w, + bg, + u, + v_new, + gk, + h, + h0, + ht, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + o_k = i_k * BK + tl.arange(0, BK) + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(NT): + p_h = tl.make_block_ptr(h + ((boh + i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + + b_hc = tl.zeros([BK, BV], dtype=tl.float32) + # since we need to make all DK in the SRAM. we face serve SRAM memory burden. By subchunking we allievate such burden + for i_c in range(tl.cdiv(min(BT, T - i_t * BT), BC)): + p_kg = tl.make_block_ptr(kg+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_c * BC), (BK, BC), (0, 1)) + p_bg = tl.make_block_ptr(bg+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_c * BC), (BK, BC), (0, 1)) + p_w = tl.make_block_ptr(w+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_c * BC, i_k * BK), (BC, BK), (1, 0)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT + i_c * BC, i_v * BV), (BC, BV), (1, 0)) + p_u = tl.make_block_ptr(u+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT + i_c * BC, i_v * BV), (BC, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT+i_c*BC, i_v * BV), (BC, BV), (1, 0)) + # [BK, BC] + b_kg = tl.load(p_kg, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_bg = tl.load(p_bg, boundary_check=(0, 1)) + b_v2 = tl.dot(b_w, b_h.to(b_w.dtype)) + tl.load(p_u, boundary_check=(0, 1)) + b_hc += tl.dot(b_kg, b_v) + b_hc += tl.dot(b_bg.to(b_hc.dtype), b_v2) + tl.store(p_v_new, b_v2.to(p_v_new.dtype.element_ty), boundary_check=(0, 1)) + + last_idx = min((i_t + 1) * BT, T) - 1 + b_g_last = tl.load(gk + (bos + last_idx) * H*K + i_h * K + o_k, mask=o_k < K).to(tl.float32) + b_h *= exp(b_g_last[:, None]) + b_h += b_hc + + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + + +def chunk_dplr_fwd_h( + kg: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + bg: torch.Tensor, + gk: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *kg.shape, u.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + BK = max(triton.next_power_of_2(K), 16) + assert BK <= 256, "current kernel does not support head dimension larger than 256." + # H100 can have larger block size + + if check_shared_mem('hopper', kg.device.index): + BV = 64 + BC = 64 if K <= 128 else 32 + elif check_shared_mem('ampere', kg.device.index): # A100 + BV = 32 + BC = 32 + else: + BV = 16 + BC = 16 + + BC = min(BT, BC) + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert NK == 1, 'NK > 1 is not supported because it involves time-consuming synchronization' + + h = kg.new_empty(B, NT, H, K, V) + final_state = kg.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + v_new = torch.empty_like(u) + grid = (NK, NV, N * H) + chunk_dplr_fwd_kernel_h[grid]( + kg=kg, + v=v, + w=w, + bg=bg, + u=u, + v_new=v_new, + h=h, + gk=gk, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BC=BC, + BK=BK, + BV=BV, + ) + return h, v_new, final_state diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_o_bwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_o_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..130d4c3902527039e6a5004a45ab46675d2999a6 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_o_bwd.py @@ -0,0 +1,431 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_amd, use_cuda_graph + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + +BK_LIST = [32, 64, 128] if check_shared_mem() else [16, 32] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['BV', 'BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_dplr_bwd_kernel_dAu( + v, + do, + v_new, + A_qb, + dA_qk, + dA_qb, + dv_new, + cu_seqlens, + chunk_indices, + scale: tl.constexpr, + T, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + + b_dA_qk = tl.zeros([BT, BT], dtype=tl.float32) + b_dA_qb = tl.zeros([BT, BT], dtype=tl.float32) + + p_A_qb = tl.make_block_ptr(A_qb + (bos * H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + b_A_qb = tl.load(p_A_qb, boundary_check=(0, 1)) + # causal mask + b_A_qb = tl.where(tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :], b_A_qb, 0.).to(b_A_qb.dtype) + + for i_v in range(tl.cdiv(V, BV)): + p_do = tl.make_block_ptr(do + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (V, T), (1, H*V), (i_v * BV, i_t * BT), (BV, BT), (0, 1)) + p_v_new = tl.make_block_ptr(v_new + (bos*H + i_h) * V, (V, T), (1, H*V), (i_v * BV, i_t * BT), (BV, BT), (0, 1)) + p_dv_new = tl.make_block_ptr(dv_new + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_v_new = tl.load(p_v_new, boundary_check=(0, 1)) + b_dA_qk += tl.dot(b_do, b_v) + b_dA_qb += tl.dot(b_do, b_v_new) + b_dv_new = tl.dot(tl.trans(b_A_qb), b_do) + # for recurrent + tl.store(p_dv_new, b_dv_new.to(p_dv_new.dtype.element_ty), boundary_check=(0, 1)) + + p_dA_qk = tl.make_block_ptr(dA_qk + (bos * H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_dA_qb = tl.make_block_ptr(dA_qb + (bos * H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + m_s = tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :] + b_dA_qk = tl.where(m_s, b_dA_qk * scale, 0.) + tl.store(p_dA_qk, b_dA_qk.to(p_dA_qk.dtype.element_ty), boundary_check=(0, 1)) + b_dA_qb = tl.where(m_s, b_dA_qb * scale, 0.) + tl.store(p_dA_qb, b_dA_qb.to(p_dA_qb.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['BT', 'BK', 'BV'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit +def chunk_dplr_bwd_o_kernel( + v, + v_new, + h, + do, + dh, + dk, + db, + w, + dq, + dv, + dw, + gk, + dgk_last, + k, + b, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + # offset calculation + v += (bos * H + i_h) * V + v_new += (bos * H + i_h) * V + do += (bos * H + i_h) * V + h += (i_tg * H + i_h) * K * V + dh += (i_tg * H + i_h) * K * V + dk += (bos * H + i_h) * K + k += (bos * H + i_h) * K + db += (bos * H + i_h) * K + b += (bos * H + i_h) * K + dw += (bos * H + i_h) * K + dv += (bos * H + i_h) * V + dq += (bos * H + i_h) * K + w += (bos * H + i_h) * K + + dgk_last += (i_tg * H + i_h) * K + gk += (bos * H + i_h) * K + + stride_qk = H*K + stride_vo = H*V + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dw = tl.zeros([BT, BK], dtype=tl.float32) + b_db = tl.zeros([BT, BK], dtype=tl.float32) + b_dgk_last = tl.zeros([BK], dtype=tl.float32) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v_new = tl.load(p_v_new, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_dgk_last += tl.sum((b_h * b_dh).to(tl.float32), axis=0) + + # [BT, BV] @ [BV, BK] -> [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + # [BT, BV] @ [BV, BK] -> [BT, BK] + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) + b_db += tl.dot(b_v_new, b_dh.to(b_v_new.dtype)) + p_dv = tl.make_block_ptr(dv, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_dv = tl.load(p_dv, boundary_check=(0, 1)) + b_dw += tl.dot(b_dv.to(b_v.dtype), b_h.to(b_v.dtype)) + + m_k = (i_k*BK+tl.arange(0, BK)) < K + last_idx = min(i_t * BT + BT, T) - 1 + b_gk_last = tl.load(gk + last_idx * stride_qk + i_k*BK + tl.arange(0, BK), mask=m_k, other=float('-inf')) + b_dgk_last *= exp(b_gk_last) + p_k = tl.make_block_ptr(k, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_b = tl.make_block_ptr(b, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_b = tl.load(p_b, boundary_check=(0, 1)) + b_dgk_last += tl.sum(b_k * b_dk, axis=0) + b_dgk_last += tl.sum(b_b * b_db, axis=0) + tl.store(dgk_last + tl.arange(0, BK) + i_k * BK, b_dgk_last, mask=m_k) + + p_dw = tl.make_block_ptr(dw, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_db = tl.make_block_ptr(db, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dw, b_dw.to(p_dw.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + for BK in BK_LIST + for BV in BK_LIST + ], + key=['BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit +def chunk_dplr_bwd_kernel_dv( + A_qk, + kg, + do, + dv, + dh, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + b_dv = tl.zeros([BT, BV], dtype=tl.float32) + + # offset calculation + A_qk += (bos * H + i_h) * BT + do += (bos * H + i_h) * V + dv += (bos * H + i_h) * V + kg += (bos * H + i_h) * K + dh += (i_tg * H + i_h) * K*V + + stride_qk = H*K + stride_vo = H*V + stride_A = H*BT + + for i_k in range(tl.cdiv(K, BK)): + p_dh = tl.make_block_ptr(dh, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_kg = tl.make_block_ptr(kg, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_kg = tl.load(p_kg, boundary_check=(0, 1)) + b_dv += tl.dot(b_kg, b_dh.to(b_kg.dtype)) + + p_Aqk = tl.make_block_ptr(A_qk, (BT, T), (1, stride_A), (0, i_t * BT), (BT, BT), (0, 1)) + b_A = tl.where(tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :], tl.load(p_Aqk, boundary_check=(0, 1)), 0) + p_do = tl.make_block_ptr(do, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dv += tl.dot(b_A.to(b_do.dtype), b_do) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_dplr_bwd_dv( + A_qk: torch.Tensor, + kg: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, K, V = *kg.shape, do.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dv = torch.empty_like(do) + + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * H) + chunk_dplr_bwd_kernel_dv[grid]( + A_qk=A_qk, + kg=kg, + do=do, + dv=dv, + dh=dh, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return dv + + +def chunk_dplr_bwd_o( + k: torch.Tensor, + b: torch.Tensor, + v: torch.Tensor, + v_new: torch.Tensor, + gk: torch.Tensor, + do: torch.Tensor, + h: torch.Tensor, + dh: torch.Tensor, + dv: torch.Tensor, + w: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + scale: float = 1.0, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + + B, T, H, K, V = *w.shape, v.shape[-1] + + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + BK = min(max(triton.next_power_of_2(K), 16), 64) if check_shared_mem() else min(triton.next_power_of_2(K), 32) + BV = min(max(triton.next_power_of_2(V), 16), 64) if check_shared_mem() else min(triton.next_power_of_2(K), 32) + NK = triton.cdiv(K, BK) + dq = torch.empty_like(k) + dk = torch.empty_like(k) + dw = torch.empty_like(w) + db = torch.empty_like(b) + grid = (NK, NT, B * H) + + dgk_last = torch.empty(B, NT, H, K, dtype=torch.float, device=w.device) + + chunk_dplr_bwd_o_kernel[grid]( + k=k, + b=b, + v=v, + v_new=v_new, + h=h, + do=do, + dh=dh, + dq=dq, + dk=dk, + db=db, + dgk_last=dgk_last, + w=w, + dv=dv, + dw=dw, + gk=gk, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dq, dk, dw, db, dgk_last + + +def chunk_dplr_bwd_dAu( + v: torch.Tensor, + v_new: torch.Tensor, + do: torch.Tensor, + A_qb: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, V = v.shape + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + if check_shared_mem('ampere'): # A100 + BV = min(triton.next_power_of_2(V), 128) + elif check_shared_mem('ada'): # 4090 + BV = min(max(triton.next_power_of_2(V), 16), 64) + else: + BV = min(triton.next_power_of_2(V), 32) + + grid = (NT, B * H) + dA_qk = torch.empty(B, T, H, BT, dtype=torch.float, device=v.device) + dA_qb = torch.empty(B, T, H, BT, dtype=torch.float, device=v.device) + dv_new = torch.empty_like(v_new) + chunk_dplr_bwd_kernel_dAu[grid]( + v=v, + do=do, + v_new=v_new, + A_qb=A_qb, + dA_qk=dA_qk, + dA_qb=dA_qb, + dv_new=dv_new, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + V=V, + BT=BT, + BV=BV, + ) + return dv_new, dA_qk, dA_qb diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_o_fwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_o_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..b40dad2bf1f2f9c57ef95432a8efce346cb1c19a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/chunk_o_fwd.py @@ -0,0 +1,124 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_amd, use_cuda_graph + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + +BK_LIST = [32, 64, 128] if check_shared_mem() else [16, 32] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BK_LIST + for BV in BK_LIST + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3, 4] + ], + key=['BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_dplr_fwd_kernel_o( + qg, + v, + v_new, + A_qk, + A_qb, + h, + o, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_qg = tl.make_block_ptr(qg + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_qg = tl.load(p_qg, boundary_check=(0, 1)) + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_o += tl.dot(b_qg, b_h) + + p_Aqk = tl.make_block_ptr(A_qk + (bos * H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_Aqb = tl.make_block_ptr(A_qb + (bos * H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + + m_s = tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :] + b_Aqk = tl.load(p_Aqk, boundary_check=(0, 1)) + b_Aqb = tl.load(p_Aqb, boundary_check=(0, 1)) + b_Aqk = tl.where(m_s, b_Aqk, 0) + b_Aqb = tl.where(m_s, b_Aqb, 0) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v_new = tl.load(p_v_new, boundary_check=(0, 1)) + b_o = b_o + tl.dot(b_Aqk.to(b_v.dtype), b_v) + tl.dot(b_Aqb.to(b_v_new.dtype), b_v_new) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_dplr_fwd_o( + qg: torch.Tensor, + v: torch.Tensor, + v_new: torch.Tensor, + A_qk: torch.Tensor, + A_qb: torch.Tensor, + h: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, K, V = *qg.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + o = torch.empty_like(v) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * H) + chunk_dplr_fwd_kernel_o[grid]( + qg=qg, + v=v, + v_new=v_new, + A_qk=A_qk, + A_qb=A_qb, + h=h, + o=o, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return o diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/fused_recurrent.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..e1edb85b19191f8b055c1c1816e2f71c3b108ad9 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/fused_recurrent.py @@ -0,0 +1,267 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard, use_cuda_graph + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BV in [16, 32, 64] + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3, 4] + ], + key=['BK'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_dplr_delta_rule_fwd_kernel( + q, + k, + v, + a, + b, + gk, + o, + h0, + ht, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + REVERSE: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_k = tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + p_q = q + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_k = k + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_a = a + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_b = b + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_gk = gk + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T - 1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_o = o + (bos + ((T - 1) if REVERSE else 0)) * H*V + i_h * V + o_v + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) * scale + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_a = tl.load(p_a, mask=mask_k, other=0).to(tl.float32) + b_b = tl.load(p_b, mask=mask_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + + b_h = exp(b_gk)[:, None] * b_h + b_b[:, None] * tl.sum(b_a[:, None] * b_h, 0)[None, :] + b_h += b_k[:, None] * b_v[None, :] + b_o = tl.sum(b_h * b_q[:, None], 0) + + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + p_q += (-1 if REVERSE else 1) * H*K + p_k += (-1 if REVERSE else 1) * H*K + p_a += (-1 if REVERSE else 1) * H*K + p_b += (-1 if REVERSE else 1) * H*K + p_gk += (-1 if REVERSE else 1) * H*K + p_v += (-1 if REVERSE else 1) * H*V + p_o += (-1 if REVERSE else 1) * H*V + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +def fused_recurrent_dplr_delta_rule_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gk: torch.Tensor, + scale: float | None = 1.0, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK = triton.next_power_of_2(K) + + h0 = initial_state + ht = q.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + o = torch.empty_like(v) + + def grid(meta): return (triton.cdiv(V, meta['BV']), N * H) + fused_recurrent_dplr_delta_rule_fwd_kernel[grid]( + q=q, + k=k, + v=v, + a=a, + b=b, + gk=gk, + o=o, + h0=h0, + ht=ht, + cu_seqlens=cu_seqlens, + scale=scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + REVERSE=reverse, + ) + return o, ht + + +class FusedRecurrentDPLRDeltaRuleFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gk: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + o, ht = fused_recurrent_dplr_delta_rule_fwd( + q=q, + k=k, + v=v, + a=a, + b=b, + gk=gk, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + return o, ht + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht): + raise NotImplementedError( + "Backward pass for fused_recurrent_dplr_delta_rule is not implemented and will not be supported. " + "This kernel is only for inference. " + "For training, please use `chunk_dplr_delta_rule`.", + ) + + +def fused_recurrent_dplr_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + gk: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + This function computes the recurrence S_t = S_t @ (Diag(g_t) + a_t b_t^T) + v_t k_t^T in a recurrent manner. + + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + a (torch.Tensor): + a of shape `[B, T, H, K]`. + b (torch.Tensor): + b of shape `[B, T, H, K]`. + gk (torch.Tensor): + gk of shape `[B, T, H, K]`. decay term in log space! + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + reverse (Optional[bool]): + If `True`, process the state passing in reverse order. Default: `False`. + cu_seqlens (Optional[torch.Tensor]): + Cumulative sequence lengths of shape `[N + 1]` used for variable-length training, + consistent with the FlashAttention API. + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = q.shape[-1] ** -0.5 + o, final_state = FusedRecurrentDPLRDeltaRuleFunction.apply( + q, + k, + v, + a, + b, + gk, + scale, + initial_state, + output_final_state, + reverse, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/naive.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..1abfdf8ea1267f71319b9b6370e841f5f081ff39 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/naive.py @@ -0,0 +1,95 @@ + +import torch +from einops import rearrange + +# S_t = S_t @ (I + alpha_t beta_t^T) + v_t k_t^T +# q, k, alpha, beta [B, H, L, D_K] +# v [B, H, L, D_V] + + +def dplr_recurrence(q, k, v, alpha, beta, gk, initial_state=None, output_final_state=True): + orig_dtype = q.dtype + b, h, l, d_k = q.shape + q, k, v, beta, gk = map(lambda x: x.float(), [q, k, v, beta, gk]) + d_v = v.shape[-1] + o = torch.zeros_like(v) + S = torch.zeros(b, h, d_k, d_v).to(v) + q = q * (d_k ** -0.5) + + if initial_state is not None: + S += initial_state + + for i in range(l): + _k = k[:, :, i] + _q = q[:, :, i] + _v = v[:, :, i] + _alpha = alpha[:, :, i].clone() + _beta = beta[:, :, i].clone() + _kv = _k[..., None] * _v[..., None, :] + (S.clone() * _alpha[..., None]).sum(-2, keepdim=True) * _beta[..., None] + S = S.clone() * gk[:, :, i].exp()[..., None] + _kv + o[:, :, i] = torch.einsum('bhd,bhdm->bhm', _q, S) + S = None if output_final_state is False else S + return o.to(orig_dtype), S + + +def dplr_chunkwise(q, k, v, alpha, beta, gk, initial_state=None, output_final_state=True, chunk_size=32): + b, h, l, d_k = q.shape + d_v = v.shape[-1] + q = q * (d_k ** -0.5) + v = v + assert l % chunk_size == 0 + + S = k.new_zeros(b, h, d_k, d_v).to(q) + if initial_state is not None: + S += initial_state + + # note that diagonal is masked. + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0) + q, k, v, alpha, beta, gk = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', + c=chunk_size).float(), [q, k, v, alpha, beta, gk]) + + gk_cumsum = gk.cumsum(-2) + + # v2 = (alpha @ k.transpose(-1, -2)).masked_fill_(mask, 0) @ v + A_ab = torch.zeros(b, h, l // chunk_size, chunk_size, chunk_size).to(q.device) + A_qk = torch.zeros(b, h, l // chunk_size, chunk_size, chunk_size).to(q.device) + A_ak = torch.zeros(b, h, l // chunk_size, chunk_size, chunk_size).to(q.device) + A_qb = torch.zeros(b, h, l // chunk_size, chunk_size, chunk_size).to(q.device) + + for i in range(chunk_size): + alpha_i = alpha[:, :, :, i, None] + q_i = q[:, :, :, i, None] + gk_i = gk_cumsum[:, :, :, i, None] + mask = (torch.arange(chunk_size) <= i).to(q.device) + attn_i = (gk_i - gk_cumsum).masked_fill(~mask.unsqueeze(-1), float('-inf')).exp() + A_qk[:, :, :, i, :] = (q_i * k * attn_i).sum(-1).clone() + A_qb[:, :, :, i, :] = (q_i * beta * attn_i).sum(-1).clone() + mask = (torch.arange(chunk_size) < i).to(q.device) + # shift by one. + attn_i = (gk_i - gk[:, :, :, i, None] - gk_cumsum).masked_fill(~mask.unsqueeze(-1), float('-inf')).exp() + A_ab[:, :, :, i, :] = (alpha_i * beta * attn_i).sum(-1).clone() + A_ak[:, :, :, i, :] = (alpha_i * k * attn_i).sum(-1).clone() + + A_ab = A_ab + for i in range(1, chunk_size): + A_ab[..., i, :i] = A_ab[..., i, :i].clone() + (A_ab[..., i, :, None].clone() * A_ab[..., :, :i].clone()).sum(-2) + + A_ab = A_ab + torch.eye(chunk_size, dtype=torch.float, device=q.device) + u = A_ab @ (A_ak @ v) + w = A_ab @ ((gk_cumsum-gk).exp() * alpha) + + o = torch.zeros_like(v) + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1) + for i in range(0, l // chunk_size): + q_i, k_i, v_i, u_i, w_i, beta_i = q[:, :, i], k[:, :, i], v[:, :, i], u[:, :, i], w[:, :, i], beta[:, :, i] + v2_i = u_i + w_i @ S + + o_1 = A_qk[:, :, i] @ v_i + o_2 = A_qb[:, :, i] @ v2_i + o_3 = (q_i * gk_cumsum[:, :, i].exp()) @ S + o[:, :, i] = o_1 + o_2 + o_3 + decay = (gk_cumsum[:, :, i, -1, None] - gk_cumsum[:, :, i]).exp() + S = S*gk_cumsum[:, :, i, -1, :, None].exp() + (k_i * decay).transpose(-1, -2) @ v_i + \ + (beta_i * decay).transpose(-1, -2) @ v2_i + S = None if output_final_state is False else S + return rearrange(o, 'b h n c d -> b h (n c) d'), S diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/wy_fast_bwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/wy_fast_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..7b8ab53a7b987d6101af155bedffb3933707a5bc --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/wy_fast_bwd.py @@ -0,0 +1,163 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_intel_alchemist, use_cuda_graph + +# https://github.com/intel/intel-xpu-backend-for-triton/issues/3449 +triton_config = {'grf_mode': 'large'} if is_intel_alchemist else {} + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config(triton_config, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3, 4] + ], + key=['BT', 'BK', 'BV'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_bwd_kernel( + A_ab_inv, + A_ak, + ag, + v, + dw, + du, + dv, + dv0, + dag, + dAak, + dAab, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_Aak_t = tl.make_block_ptr(A_ak + (bos*H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + p_Aab_inv_t = tl.make_block_ptr(A_ab_inv + (bos*H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + p_dAak = tl.make_block_ptr(dAak + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_dAab = tl.make_block_ptr(dAab + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + b_A_ab_inv_t = tl.load(p_Aab_inv_t, boundary_check=(0, 1)) + b_A_ak_t = tl.load(p_Aak_t, boundary_check=(0, 1)) + b_A_ak_t = tl.where(tl.arange(0, BT)[:, None] < tl.arange(0, BT)[None, :], b_A_ak_t, 0) + b_A_ab_inv_t = tl.where(tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :], b_A_ab_inv_t, 0) + b_A_tmp_t = tl.dot(b_A_ak_t, b_A_ab_inv_t).to(v.dtype.element_ty) + b_dA_tmp = tl.zeros([BT, BT], dtype=tl.float32) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv0 = tl.make_block_ptr(dv0 + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_du = tl.make_block_ptr(du + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_du = tl.load(p_du, boundary_check=(0, 1)) + b_dA_tmp += tl.dot(b_du.to(b_v.dtype), tl.trans(b_v)) + b_dv0 = tl.load(p_dv0, boundary_check=(0, 1)) + b_dv = b_dv0 + tl.dot(b_A_tmp_t, b_du) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + m_i = tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :] + b_dA_tmp = tl.where(m_i, b_dA_tmp, 0) + b_dA_ak = tl.dot(b_A_ab_inv_t, b_dA_tmp) + b_dA_ak = tl.where(m_i, b_dA_ak, 0) + tl.store(p_dAak, b_dA_ak, boundary_check=(0, 1)) + b_dA_ab_inv = tl.dot(b_dA_tmp, b_A_ak_t) + + for i_k in range(tl.cdiv(K, BK)): + p_ag = tl.make_block_ptr(ag + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dag = tl.make_block_ptr(dag + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dw = tl.make_block_ptr(dw + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_ag = tl.load(p_ag, boundary_check=(0, 1)) + b_dw = tl.load(p_dw, boundary_check=(0, 1)) + b_dA_ab_inv += tl.dot(b_dw, tl.trans(b_ag)) + b_dag = tl.dot(b_A_ab_inv_t.to(b_dw.dtype), b_dw) + tl.store(p_dag, b_dag.to(p_dag.dtype.element_ty), boundary_check=(0, 1)) + + # if we know dL/dA^(-1), for dL/dA, we can use the following formula: + # dL/dA = -(A^(-1))^T @ (dL/dA^(-1)) @ (A^(-1))^T + # in the fwd pass we use fwd substitution to calculate (I-lower(A_ab))^-1. + # denote A = I - lower(A_ab), B = A^-1 + # in the backward pass. + # dL/dA = -(B)^T @ (dL/dB) @ B^T + # dL/dA_ab = lower(B^T @ dL/dB @ B^T) + b_dA_ab_inv = tl.where(tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :], b_dA_ab_inv, 0) + b_dA_ab_inv = tl.dot(b_A_ab_inv_t, b_dA_ab_inv) + b_dA_ab_inv = tl.dot(b_dA_ab_inv, b_A_ab_inv_t) + b_dA_ab_inv = tl.where(m_i, b_dA_ab_inv, 0) + tl.store(p_dAab, b_dA_ab_inv, boundary_check=(0, 1)) + + +def chunk_dplr_bwd_wy( + A_ab_inv: torch.Tensor, + A_ak: torch.Tensor, + v: torch.Tensor, + ag: torch.Tensor, + dw: torch.Tensor, + du: torch.Tensor, + dv0: torch.Tensor, + cu_seqlens: torch.LongTensor | None, + chunk_size: int, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + A_ab_inv, A_ak, v, ag, dw, du = map(lambda x: x.contiguous(), [A_ab_inv, A_ak, v, ag, dw, du]) + B, T, H, K, V = *dw.shape, du.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BK = min(max(triton.next_power_of_2(K), 16), 64) + BV = min(max(triton.next_power_of_2(V), 16), 64) if check_shared_mem() else min(max(triton.next_power_of_2(V), 16), 32) + + dA_ab = torch.empty_like(A_ab_inv, dtype=torch.float) + dA_ak = torch.empty_like(A_ak, dtype=torch.float) + dv = torch.empty_like(v) + dag = torch.empty_like(ag) + + prepare_wy_repr_bwd_kernel[(NT, B * H)]( + A_ab_inv=A_ab_inv, + A_ak=A_ak, + ag=ag, + v=v, + dw=dw, + du=du, + dv=dv, + dv0=dv0, + dag=dag, + dAak=dA_ak, + dAab=dA_ab, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dA_ab, dA_ak, dv, dag diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/wy_fast_fwd.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/wy_fast_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..5f3bd302fd1f34e44a84b92bcf4fc231fdedb6d8 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/dplr/wy_fast_fwd.py @@ -0,0 +1,285 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import gather +from fla.utils import autotune_cache_kwargs, is_gather_supported, use_cuda_graph + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8, 16] + ], + key=['BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_fwd_kernel_chunk32( + A_ab, + A_ab_inv, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, # placeholder, do not delete + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + p_Aab = tl.make_block_ptr(A_ab + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_Aab_inv = tl.make_block_ptr(A_ab_inv + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_A_ab = tl.load(p_Aab, boundary_check=(0, 1)) + b_A_ab = tl.where(tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :], b_A_ab, 0) + for i in range(1, BT): + mask = tl.arange(0, BT) == i + b_a = tl.sum(tl.where(mask[:, None], b_A_ab, 0), 0) + b_a = b_a + tl.sum(b_a[:, None] * b_A_ab, 0) * (tl.arange(0, BT) < i) + b_A_ab = tl.where(mask[:, None], b_a, b_A_ab) + b_A_ab += tl.arange(0, BT)[:, None] == tl.arange(0, BT)[None, :] + tl.store(p_Aab_inv, b_A_ab.to(p_Aab_inv.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BC'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_fwd_kernel_chunk64( + A_ab, + A_ab_inv, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + IS_VARLEN: tl.constexpr, + GATHER_SUPPORTED: tl.constexpr = is_gather_supported, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_A1 = tl.make_block_ptr(A_ab + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BC, BC), (1, 0)) + p_A2 = tl.make_block_ptr(A_ab + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT + BC, BC), (BC, BC), (1, 0)) + p_A3 = tl.make_block_ptr(A_ab + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT + BC, 0), (BC, BC), (1, 0)) + p_A_inv1 = tl.make_block_ptr(A_ab_inv + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BC, BC), (1, 0)) + p_A_inv2 = tl.make_block_ptr(A_ab_inv + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT + BC, BC), (BC, BC), (1, 0)) + p_A_inv3 = tl.make_block_ptr(A_ab_inv + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT + BC, 0), (BC, BC), (1, 0)) + p_A_inv4 = tl.make_block_ptr(A_ab_inv + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, BC), (BC, BC), (1, 0)) + + b_A = tl.load(p_A1, boundary_check=(0, 1)) + b_A2 = tl.load(p_A2, boundary_check=(0, 1)) + b_A3 = tl.load(p_A3, boundary_check=(0, 1)) + b_A = tl.where(tl.arange(0, BC)[:, None] > tl.arange(0, BC)[None, :], b_A, 0) + b_A2 = tl.where(tl.arange(0, BC)[:, None] > tl.arange(0, BC)[None, :], b_A2, 0) + + for i in range(1, BC): + if GATHER_SUPPORTED: + row_idx = tl.full([1, BC], i, dtype=tl.int16) + # [1, BK] -> [BK] + b_a = tl.sum(gather(b_A, row_idx, axis=0), 0) + b_a2 = tl.sum(gather(b_A2, row_idx, axis=0), 0) + else: + mask = tl.arange(0, BC) == i + b_a = tl.sum(tl.where(mask[:, None], b_A, 0), 0) + b_a2 = tl.sum(tl.where(mask[:, None], b_A2, 0), 0) + mask = tl.arange(0, BC) == i + # b_a = tl.sum(tl.where(mask[:, None], b_A, 0), 0) + # b_a2 = tl.sum(tl.where(mask[:, None], b_A2, 0), 0) + b_a = b_a + tl.sum(b_a[:, None] * b_A, 0) * (tl.arange(0, BC) < i) + b_a2 = b_a2 + tl.sum(b_a2[:, None] * b_A2, 0) * (tl.arange(0, BC) < i) + b_A = tl.where(mask[:, None], b_a, b_A) + b_A2 = tl.where(mask[:, None], b_a2, b_A2) + + # blockwise computation of lower triangular matrix's inverse + # i.e., [A11, 0; A21, A22]^-1 = [A11^-1, 0; -A22^-1 A21 A11^-1, A22^-1] + b_A += tl.arange(0, BC)[:, None] == tl.arange(0, BC)[None, :] + b_A2 += tl.arange(0, BC)[:, None] == tl.arange(0, BC)[None, :] + b_A3 = tl.dot(tl.dot(b_A2, b_A3), b_A) + # tl.debug_barrier() + tl.store(p_A_inv1, b_A.to(p_A_inv1.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_A_inv2, b_A2.to(p_A_inv2.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_A_inv3, b_A3.to(p_A_inv3.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + # causal mask + tl.store(p_A_inv4, tl.zeros([BC, BC], dtype=tl.float32).to(p_A_inv4.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def wu_fwd_kernel( + w, + u, + ag, + v, + A_ab_inv, + A_ak, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + o_s = tl.arange(0, BT) + + p_A_ab_inv = tl.make_block_ptr(A_ab_inv + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_A_ak = tl.make_block_ptr(A_ak + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + b_Aab_inv = tl.load(p_A_ab_inv, boundary_check=(0, 1)) + b_Aak = tl.load(p_A_ak, boundary_check=(0, 1)) + b_Aab_inv = tl.where(o_s[:, None] >= o_s[None, :], b_Aab_inv, 0) + b_Aak = tl.where(o_s[:, None] > o_s[None, :], b_Aak, 0) + # let's use tf32 here + b_Aak = tl.dot(b_Aab_inv, b_Aak) + # (SY 01/04) should be bf16 or tf32? To verify. + b_Aak = b_Aak.to(v.dtype.element_ty, fp_downcast_rounding="rtne") + b_Aab_inv = b_Aab_inv.to(ag.dtype.element_ty, fp_downcast_rounding="rtne") + + for i_k in range(tl.cdiv(K, BK)): + p_ag = tl.make_block_ptr(ag + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_w = tl.make_block_ptr(w + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_ag = tl.load(p_ag, boundary_check=(0, 1)) + b_w = tl.dot(b_Aab_inv, b_ag) # both bf16 or fp16 + tl.store(p_w, b_w.to(p_w.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_u = tl.make_block_ptr(u + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_u = tl.dot(b_Aak, b_v) # both bf16 or fp16 + tl.store(p_u, b_u.to(p_u.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + + +def wu_fwd( + ag: torch.Tensor, + v: torch.Tensor, + A_ak: torch.Tensor, + A_ab_inv: torch.Tensor, + cu_seqlens: torch.LongTensor | None, + chunk_size: int, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *ag.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BK = min(max(triton.next_power_of_2(K), 16), 64) + BV = min(max(triton.next_power_of_2(V), 16), 64) + + w = torch.empty_like(ag) + u = torch.empty_like(v) + wu_fwd_kernel[(NT, B * H)]( + ag=ag, + v=v, + A_ak=A_ak, + A_ab_inv=A_ab_inv, + w=w, + u=u, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return w, u + + +def prepare_wy_repr_fwd( + ag: torch.Tensor, + v: torch.Tensor, + A_ak: torch.Tensor, + A_ab: torch.Tensor, + cu_seqlens: torch.LongTensor | None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, _ = ag.shape + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BC = min(BT, 32) + fwd_fn = prepare_wy_repr_fwd_kernel_chunk64 if BT == 64 else prepare_wy_repr_fwd_kernel_chunk32 + A_ab_inv = torch.empty_like(A_ab) + fwd_fn[(NT, B * H)]( + A_ab=A_ab, + A_ab_inv=A_ab_inv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + BT=BT, + BC=BC, + ) + w, u = wu_fwd( + ag=ag, + v=v, + A_ak=A_ak, + A_ab_inv=A_ab_inv, + cu_seqlens=cu_seqlens, + chunk_size=BT, + ) + return w, u, A_ab_inv + + +fwd_prepare_wy_repr = prepare_wy_repr_fwd + +fwd_wu = wu_fwd diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/__init__.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e1b0916fc99cb5783fe959ae5d40b89bc9326425 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/__init__.py @@ -0,0 +1,7 @@ +from .chunk import chunk_iplr_delta_rule +from .fused_recurrent import fused_recurrent_iplr_delta_rule + +__all__ = [ + 'chunk_iplr_delta_rule', + 'fused_recurrent_iplr_delta_rule', +] diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/chunk.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..e2388dfa86e5ebcc887851724a100e24e4851173 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/chunk.py @@ -0,0 +1,496 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl + +from fla.ops.generalized_delta_rule.iplr.wy_fast import prepare_wy_repr_fwd +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.utils import ( + autocast_custom_bwd, + autocast_custom_fwd, + autotune_cache_kwargs, + check_shared_mem, + input_guard, + use_cuda_graph, +) + +BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4] + ([] if check_shared_mem('hopper') else [8]) + ], + key=['BT', 'BK', 'BV'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_generalized_iplr_delta_rule_fwd_kernel_h( + k, + v, + d, + b, + u, + v_new, + h, + h0, + ht, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(NT): + p_h = tl.make_block_ptr(h + ((boh + i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + b_hc = tl.zeros([BK, BV], dtype=tl.float32) + # since we need to make all DK in the SRAM. we face serve SRAM memory burden. By subchunking we allievate such burden + for i_c in range(tl.cdiv(min(BT, T - i_t * BT), BC)): + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_c * BC), (BK, BC), (0, 1)) + p_b = tl.make_block_ptr(b+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_c * BC), (BK, BC), (0, 1)) + p_d = tl.make_block_ptr(d+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_c * BC, i_k * BK), (BC, BK), (1, 0)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT + i_c * BC, i_v * BV), (BC, BV), (1, 0)) + p_u = tl.make_block_ptr(u+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT + i_c * BC, i_v * BV), (BC, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT+i_c*BC, i_v * BV), (BC, BV), (1, 0)) + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_d = tl.load(p_d, boundary_check=(0, 1)) + b_b = tl.load(p_b, boundary_check=(0, 1)) + b_v2 = tl.dot(b_d, b_h.to(b_d.dtype)) + tl.load(p_u, boundary_check=(0, 1)) + b_hc += tl.dot(b_k, b_v) + b_hc += tl.dot(b_b, b_v2.to(b_k.dtype)) + tl.store(p_v_new, b_v2.to(p_v_new.dtype.element_ty), boundary_check=(0, 1)) + b_h += b_hc + + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps) + for BK in BKV_LIST + for BV in BKV_LIST + for num_warps in [2, 4, 8] + ], + key=['BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_generalized_iplr_delta_rule_fwd_kernel_o( + q, + k, + v, + u, + b, + h, + o, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + b += (bos * H + i_h) * K + v += (bos * H + i_h) * V + u += (bos * H + i_h) * V + o += (bos * H + i_h) * V + h += (i_tg * H + i_h) * K * V + stride_qk = H*K + stride_vo = H*V + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + b_Aqk = tl.zeros([BT, BT], dtype=tl.float32) + b_Aqb = tl.zeros([BT, BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (K, T), (1, stride_qk), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_h = tl.make_block_ptr(h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_b = tl.make_block_ptr(b, (K, T), (1, stride_qk), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_b = tl.load(p_b, boundary_check=(0, 1)) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BK] @ [BK, BV] -> [BT, BV] + b_o += tl.dot(b_q, b_h) + # [BT, BK] @ [BK, BT] -> [BT, BT] + b_Aqk += tl.dot(b_q, b_k) + # [BT, BK] @ [BK, BT] -> [BT, BT] + b_Aqb += tl.dot(b_q, b_b) + + o_i = tl.arange(0, BT) + m_A = o_i[:, None] >= o_i[None, :] + b_Aqk = tl.where(m_A, b_Aqk, 0) + b_Aqb = tl.where(m_A, b_Aqb, 0) + + p_v = tl.make_block_ptr(v, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_u = tl.make_block_ptr(u, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_u = tl.load(p_u, boundary_check=(0, 1)) + b_o = (b_o + tl.dot(b_Aqk.to(b_v.dtype), b_v) + tl.dot(b_Aqb.to(b_u.dtype), b_u)) * scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_generalized_iplr_delta_rule_fwd_o( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + v_new: torch.Tensor, + b: torch.Tensor, + h: torch.Tensor, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, K, V = *q.shape, v.shape[-1] + if scale is None: + scale = k.shape[-1] ** -0.5 + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + o = torch.empty_like(v) + + def grid(meta): return ( + triton.cdiv(V, meta['BV']), + NT, + B * H, + ) + chunk_generalized_iplr_delta_rule_fwd_kernel_o[grid]( + q=q, + k=k, + v=v, + u=v_new, + b=b, + h=h, + o=o, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return o + + +def chunk_generalized_iplr_delta_rule_fwd_h( + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + b: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, u.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + BK = max(triton.next_power_of_2(K), 16) + assert BK <= 256, "current kernel does not support head dimension larger than 256." + # H100 can have larger block size + + if check_shared_mem('hopper', k.device.index): + BV = 64 + BC = 64 if K <= 128 else 32 + elif check_shared_mem('ampere', k.device.index): # A100 + BV = 32 + BC = 32 + else: + BV = 16 + BC = 16 + + BC = min(BT, BC) + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + + assert NK == 1, 'NK > 1 is not supported because it involves time-consuming synchronization' + + h = k.new_empty(B, NT, H, K, V) + final_state = k.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + + v_new = torch.empty_like(u) + grid = (NK, NV, N * H) + + chunk_generalized_iplr_delta_rule_fwd_kernel_h[grid]( + k=k, + v=v, + d=w, + b=b, + u=u, + v_new=v_new, + h=h, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BC=BC, + BK=BK, + BV=BV, + ) + return h, v_new, final_state + + +def chunk_generalized_iplr_delta_rule_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + w, u, _ = prepare_wy_repr_fwd( + a=a, + b=b, + k=k, + v=v, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + h, v_new, final_state = chunk_generalized_iplr_delta_rule_fwd_h( + k=k, + v=v, + b=b, + w=w, + u=u, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + o = chunk_generalized_iplr_delta_rule_fwd_o( + q=q, + k=k, + v=v, + v_new=v_new, + b=b, + h=h, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return o, final_state + + +class ChunkGeneralizedIPLRDeltaRuleFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + ): + chunk_size = min(64, max(triton.next_power_of_2(q.shape[1]), 16)) + o, final_state = chunk_generalized_iplr_delta_rule_fwd( + q=q, + k=k, + v=v, + a=a, + b=b, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + raise NotImplementedError( + "Backward pass for ChunkGeneralizedIPLRDeltaRuleFunction is not implemented yet. " + "Stay tuned!", + ) + + +@torch.compiler.disable +def chunk_iplr_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + a (torch.Tensor): + activations of shape `[B, T, H, K]`. + b (torch.Tensor): + betas of shape `[B, T, H, K]`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + scale = k.shape[-1] ** -0.5 if scale is None else scale + o, final_state = ChunkGeneralizedIPLRDeltaRuleFunction.apply( + q, + k, + v, + a, + b, + scale, + initial_state, + output_final_state, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/fused_recurrent.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..3a8596840f8ee7f06222ffa4857523e20220d152 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/fused_recurrent.py @@ -0,0 +1,452 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.utils import autotune_cache_kwargs, input_guard + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BV in [32, 64] + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3, 4] + ], + key=["BK"], + **autotune_cache_kwargs, +) +@triton.jit +def fused_recurrent_fwd_kernel( + q, # query [B, H, L, K] + k, # key [B, H, L, V] + v, # value [B, H, L, V]. + a, # a [B, H, L, K] + b, # b [B, H, L, K] + o, # output [B, H, L, V] + ha, # tmp variable [B, H, L, V] for storing intermediate results of (h * a[None, :]).sum(0) + h0, # initial hidden state [B, H, K, V] + ht, # final hidden state [B, H, K, V] + cu_seqlens, # varlen cu_seqlens + scale, # K ** -0.5 + H, # n_heads + T, # seq_len + K: tl.constexpr, # K + V: tl.constexpr, # V + BK: tl.constexpr, # BLOCK SIZE along the K dimension + BV: tl.constexpr, # BLOCK SIZE along the V dimension + USE_INITIAL_STATE: tl.constexpr, # whether to use initial state + STORE_FINAL_STATE: tl.constexpr, # whether to store final state + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + p_q = q + (bos * H + i_h) * K + tl.arange(0, BK) + p_k = k + (bos * H + i_h) * K + tl.arange(0, BK) + p_a = a + (bos * H + i_h) * K + tl.arange(0, BK) + p_b = b + (bos * H + i_h) * K + tl.arange(0, BK) + p_ha = ha + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + p_v = v + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + p_o = o + (bos * H + i_h) * V + i_v * BV + tl.arange(0, BV) + + mask_k = tl.arange(0, BK) < K + mask_v = (i_v * BV + tl.arange(0, BV)) < V + mask_h = mask_k[None, :] & mask_v[:, None] + + b_h = tl.zeros([BV, BK], dtype=tl.float32) + + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K * V + (tl.arange(0, BK)[None, :]) * V + ((i_v * BV + tl.arange(0, BV))[:, None]) + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) * scale + b_a = tl.load(p_a, mask=mask_k, other=0).to(tl.float32) + b_b = tl.load(p_b, mask=mask_k, other=0).to(tl.float32) + # to store + tmp = tl.sum(b_h * b_a[None, :], axis=1) + b_h += (tmp[:, None] * b_b[None, :] + b_k[None, :] * b_v[:, None]) + b_o = b_h * b_q[None, :] + b_o = tl.sum(b_o, axis=1) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + tl.store(p_ha, tmp.to(p_ha.dtype.element_ty), mask=mask_v) + p_q += K*H + p_k += K*H + p_o += V*H + p_v += V*H + p_ha += V*H + p_a += K*H + p_b += K*H + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K * V + (tl.arange(0, BK)[None, :]) * V + ((i_v * BV + tl.arange(0, BV))[:, None]) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'USE_DHT': lambda args: args['dht'] is not None, + 'USE_DH0': lambda args: args['dh0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3] + ], + key=["BK", "BV"], + **autotune_cache_kwargs, +) +@triton.jit +def fused_recurrent_bwd_kernel( + # B: batch_size, H: n_heads, T: seq_len, D: b_dhead + # NV: number of split in the V dimension. NK: number of split in the K dimension + q, # query [B, H, L, K] + k, # key [B, H, L, V] + v, # value [B, H, L, V] + a, # a [B, H, L, K] + b, # b [B, H, L, K] + ha, # ha [B, H, L, V] + dht, # gradient of final state [B, H, K, V] + dh0, # gradient of initial state [B, H, K, V] + do, # gradient of output [B, H, L, V] + dq, # gradient of query [NV, B, H, L, K] + dk, # gradient of key [NV, B, H, L, K] + dv, # gradient of value [NK, B, H, L, V] + da, # gradient of a [NV, B, H, L, K] + db, # gradient of b [NV, B, H, L, K] + dha, # gradient of ha [NK, B, H, L, V] + h0, # initial state [B, H, K, V] + scale, # K ** -0.5 + cu_seqlens, # cu_seqlens + B, # batch_size + H, # n_heads + T, # seq_len + K: tl.constexpr, # K + V: tl.constexpr, # V + BK: tl.constexpr, # BLOCK SIZE along the K dimension + BV: tl.constexpr, # BLOCK SIZE along the V dimension + USE_INITIAL_STATE: tl.constexpr, # whether to use initial state h0 + USE_DH0: tl.constexpr, # whether to use dh0 + USE_DHT: tl.constexpr, # whether to use dht + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + dk += i_v * B * H * K * T + db += i_v * B * H * K * T + dq += i_v * B * H * K * T + da += i_v * B * H * K * T + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + mask_k = tl.arange(0, BK) < K + mask_v = (tl.arange(0, BV) + i_v * BV) < V + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + i_v * BV + ha += (bos * H + i_h) * V + i_v * BV + a += (bos * H + i_h) * K + b += (bos * H + i_h) * K + do += (bos * H + i_h) * V + i_v * BV + dq += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + dv += (bos * H + i_h) * V + i_v * BV + da += (bos * H + i_h) * K + db += (bos * H + i_h) * K + dha += (bos * H + i_h) * V + i_v * BV + + p_q = q + tl.arange(0, BK) + (T - 1) * H*K + p_k = k + tl.arange(0, BK) + (T - 1) * H*K + p_v = v + tl.arange(0, BV) + (T - 1) * H*V + p_ha = ha + tl.arange(0, BV) + (T - 1) * H*V + p_a = a + tl.arange(0, BK) + (T - 1) * H*K + p_b = b + tl.arange(0, BK) + (T - 1) * H*K + p_do = do + tl.arange(0, BV) + (T - 1) * H*V + p_dk = dk + tl.arange(0, BK) + (T - 1) * H*K + p_dv = dv + tl.arange(0, BV) + (T - 1) * H*V + p_dha = dha + tl.arange(0, BV) + (T - 1) * H*V + p_db = db + tl.arange(0, BK) + (T - 1) * H*K + p_da = da + tl.arange(0, BK) + (T - 1) * H*K + p_dq = dq + tl.arange(0, BK) + (T - 1) * H*K + + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if USE_DHT: + p_ht = dht + i_nh * K * V + (tl.arange(0, BK)[:, None]) * V + ((i_v * BV + tl.arange(0, BV))[None, :]) + b_dh += tl.load(p_ht, mask=mask_k[:, None] & mask_v[None, :], other=0).to(tl.float32) + + for _ in range(T): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) * scale + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask_v, other=0).to(tl.float32) + b_b = tl.load(p_b, mask=mask_k, other=0).to(tl.float32) + b_a = tl.load(p_a, mask=mask_k, other=0).to(tl.float32) + b_ha = tl.load(p_ha, mask=mask_v, other=0).to(tl.float32) + + b_dh += b_q[:, None] * b_do[None, :] + d_k = tl.sum(b_dh * b_v[None, :], axis=1) + d_v = tl.sum(b_dh * b_k[:, None], axis=0) + tl.store(p_dk, d_k.to(p_dk.dtype.element_ty), mask=mask_k) + tl.store(p_dv, d_v.to(p_dv.dtype.element_ty), mask=mask_v) + + b_dha = tl.sum(b_dh * b_b[:, None], axis=0) + tl.store(p_dha, b_dha.to(p_dha.dtype.element_ty), mask=mask_v) + b_db = tl.sum(b_dh * b_ha[None, :], axis=1) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), mask=mask_k) + + b_dh += b_dha[None, :] * b_a[:, None] + p_do -= H*V + p_q -= H*K + p_k -= H*K + p_v -= H*V + p_dk -= H*K + p_dv -= H*V + p_b -= H*K + p_db -= H*K + p_a -= H*K + p_dha -= H*V + p_ha -= H*V + + if USE_DH0: + p_dh0 = dh0 + i_nh * K * V + (tl.arange(0, BK)[:, None]) * V + (i_v * BV + tl.arange(0, BV)[None, :]) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), mask=mask_k[:, None] & mask_v[None, :]) + + tl.debug_barrier() + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + + if USE_INITIAL_STATE: + mask_kv = mask_k[:, None] & mask_v[None, :] + p_h0 = h0 + i_nh * K * V + (tl.arange(0, BK)[:, None]) * V + ((i_v * BV + tl.arange(0, BV))[None, :]) + b_h += tl.load(p_h0, mask=mask_kv, other=0).to(tl.float32) + + p_k = k + tl.arange(0, BK) + p_v = v + tl.arange(0, BV) + p_ha = ha + tl.arange(0, BV) + p_do = do + tl.arange(0, BV) + p_dha = dha + tl.arange(0, BV) + p_da = da + tl.arange(0, BK) + p_dq = dq + tl.arange(0, BK) + p_b = b + tl.arange(0, BK) + + for i in range(0, T): + b_dha = tl.load(p_dha, mask=mask_v, other=0).to(tl.float32) + d_a = tl.sum(b_dha[None, :] * b_h, axis=1) + tl.store(p_da, d_a.to(p_da.dtype.element_ty), mask=mask_k) + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask_v, other=0).to(tl.float32) + b_b = tl.load(p_b, mask=mask_k, other=0).to(tl.float32) + b_ha = tl.load(p_ha, mask=mask_v, other=0).to(tl.float32) + b_h += b_k[:, None] * b_v[None, :] + b_b[:, None] * b_ha[None, :] + _d_q = b_h * b_do[None, :] + d_q = tl.sum(_d_q, axis=1) * scale + tl.store(p_dq, d_q.to(p_dq.dtype.element_ty), mask=mask_k) + + p_k += H*K + p_do += H*V + p_v += H*V + p_da += H*K + p_dha += H*V + p_ha += H*V + p_dq += H*K + p_b += H*K + + +class FusedRecurrentIPLRDeltaRuleFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + + BK = triton.next_power_of_2(K) + if output_final_state: + final_state = q.new_empty(B, H, K, V, dtype=torch.float32) + else: + final_state = None + + ha = torch.empty_like(v, dtype=torch.float32) + + def grid(meta): return ( + triton.cdiv(V, meta['BV']), + N * H, + ) + o = torch.empty_like(v) + fused_recurrent_fwd_kernel[grid]( + q=q, + k=k, + v=v, + a=a, + b=b, + o=o, + ha=ha, + h0=initial_state, + ht=final_state, + scale=scale, + cu_seqlens=cu_seqlens, + H=H, + T=T, + K=K, + V=V, + BK=BK, + ) + ctx.save_for_backward(q, k, v, a, b, ha, initial_state) + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + return o, final_state + + @staticmethod + @input_guard + def backward(ctx, do, dht): + q, k, v, a, b, ha, initial_state = ctx.saved_tensors + B, T, H, K, V = *q.shape, v.shape[-1] + N = B if ctx.cu_seqlens is None else len(ctx.cu_seqlens) - 1 + BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 64) + NV = triton.cdiv(V, BV) + scale = ctx.scale + + dq = q.new_empty(NV, *q.shape) + dk = k.new_empty(NV, *k.shape) + da = a.new_empty(NV, *a.shape) + db = b.new_empty(NV, *b.shape) + dv = torch.empty_like(v) + dha = torch.empty_like(ha) + grid = (NV, N * H) + + if initial_state is not None and initial_state.requires_grad: + dh0 = torch.empty_like(initial_state, dtype=torch.float32) + else: + dh0 = None + + fused_recurrent_bwd_kernel[grid]( + q=q, + k=k, + v=v, + a=a, + b=b, + ha=ha, + dht=dht, + dh0=dh0, + do=do, + dq=dq, + dk=dk, + dv=dv, + da=da, + db=db, + dha=dha, + h0=initial_state, + scale=scale, + cu_seqlens=ctx.cu_seqlens, + B=B, + H=H, + T=T, + K=K, + V=V, + BK=BK, + BV=BV, + ) + dq = dq.sum(0) + dk = dk.sum(0) + da = da.sum(0) + db = db.sum(0) + return dq.to(q), dk.to(k), dv.to(v), da.to(a), db.to(b), None, dh0, None, None + + +def fused_recurrent_iplr_delta_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.Tensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + This function computes the recurrence S_t = S_t @ (I + a_t b_t^T) + v_t k_t^T in a recurrent manner. + + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]` + k (torch.Tensor): + keys of shape `[B, T, H, K]` + v (torch.Tensor): + values of shape `[B, T, H, V]` + a (torch.Tensor): + as of shape `[B, T, H, K]` + b (torch.Tensor): + bs of shape `[B, T, H, K]` + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[B, H, K, V]`. Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[B, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = q.shape[-1] ** -0.5 + else: + assert scale > 0, "scale must be positive" + o, final_state = FusedRecurrentIPLRDeltaRuleFunction.apply( + q, + k, + v, + a, + b, + scale, + initial_state, + output_final_state, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/naive.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..2368b2baa0a2357bedc146f84c97b785cbaf6506 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/naive.py @@ -0,0 +1,68 @@ + +import torch +from einops import rearrange + + +# S_t = S_t @ (I + alpha_t beta_t^T) + v_t k_t^T +# q, k, alpha, beta [B, H, L, D_K] +# v [B, H, L, D_V] +def iplr_recurrence(q, k, v, alpha, beta, initial_state=None, output_final_state=True): + orig_dtype = q.dtype + b, h, l, d_k = q.shape + q, k, v, beta = map(lambda x: x.float(), [q, k, v, beta]) + d_v = v.shape[-1] + o = torch.zeros_like(v) + S = torch.zeros(b, h, d_k, d_v).to(v) + q = q * (d_k ** -0.5) + + if initial_state is not None: + S += initial_state + + for i in range(l): + _k = k[:, :, i] + _q = q[:, :, i] + _v = v[:, :, i] + _alpha = alpha[:, :, i] + _beta = beta[:, :, i] + _kv = _k[..., None] * _v[..., None, :] + (S.clone() * _alpha[..., None]).sum(-2, keepdim=True) * _beta[..., None] + S = S + _kv + o[:, :, i] = torch.einsum('bhd,bhdm->bhm', _q, S) + S = None if output_final_state is False else S + return o.to(orig_dtype), S + + +def iplr_chunkwise(q, k, v, alpha, beta, initial_state=None, output_final_state=True, chunk_size=32): + b, h, l, d_k = q.shape + d_v = v.shape[-1] + q = q * (d_k ** -0.5) + v = v + assert l % chunk_size == 0 + + S = k.new_zeros(b, h, d_k, d_v) + if initial_state is not None: + S += initial_state + + # note that diagonal is masked. + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0) + q, k, v, alpha, beta = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size), [q, k, v, alpha, beta]) + + v2 = (alpha @ k.transpose(-1, -2)).masked_fill_(mask, 0) @ v + attn = (alpha @ beta.transpose(-1, -2)).masked_fill(mask, 0) + for i in range(1, chunk_size): + attn[..., i, :i] = attn[..., i, :i] + (attn[..., i, :, None].clone() * attn[..., :, :i].clone()).sum(-2) + + attn = attn + torch.eye(chunk_size, dtype=torch.float, device=q.device) + u = attn @ v2 + w = attn @ alpha + o = torch.zeros_like(v) + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1) + for i in range(0, l // chunk_size): + q_i, k_i, v_i, u_i, w_i, beta_i = q[:, :, i], k[:, :, i], v[:, :, i], u[:, :, i], w[:, :, i], beta[:, :, i] + o_1 = (q_i @ k_i.transpose(-1, -2)).masked_fill_(mask, 0) @ v_i + v2_i = u_i + w_i @ S + o_2 = (q_i @ beta_i.transpose(-1, -2)).masked_fill_(mask, 0) @ (v2_i) + o_3 = q_i @ S + o[:, :, i] = o_1 + o_2 + o_3 + S = S + k_i.transpose(-1, -2) @ v_i + beta_i.transpose(-1, -2) @ v2_i + S = None if output_final_state is False else S + return rearrange(o, 'b h n c d -> b h (n c) d'), S diff --git a/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/wy_fast.py b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/wy_fast.py new file mode 100644 index 0000000000000000000000000000000000000000..4c6db246b24ff504172a3490e694ddeffcdd97a6 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/generalized_delta_rule/iplr/wy_fast.py @@ -0,0 +1,301 @@ + +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_nvidia_hopper + +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8, 16] + ], + key=['BK'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_fwd_kernel_chunk32( + a, + b, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BC: tl.constexpr, # dummy placeholder + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_a = tl.make_block_ptr(a + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_b = tl.make_block_ptr(b + (bos * H + i_h) * K, (K, T), (1, K*H), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + b_a = tl.load(p_a, boundary_check=(0, 1)) + b_b = tl.load(p_b, boundary_check=(0, 1)) + b_A += tl.dot(b_a, b_b) + + b_A = tl.where(tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :], b_A, 0) + for i in range(1, BT): + mask = tl.arange(0, BT) == i + b_a = tl.sum(tl.where(mask[:, None], b_A, 0), 0) + b_a = b_a + tl.sum(b_a[:, None] * b_A, 0) * (tl.arange(0, BT) < i) + b_A = tl.where(mask[:, None], b_a, b_A) + b_A += tl.arange(0, BT)[:, None] == tl.arange(0, BT)[None, :] + + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8, 16] + ], + key=['BK'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_fwd_kernel_chunk64( + a, + b, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + b_A2 = tl.zeros([BC, BC], dtype=tl.float32) + b_A3 = tl.zeros([BC, BC], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_a1 = tl.make_block_ptr(a + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BC, BK), (1, 0)) + p_a2 = tl.make_block_ptr(a + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + BC, i_k * BK), (BC, BK), (1, 0)) + p_b1 = tl.make_block_ptr(b + (bos * H + i_h) * K, (K, T), (1, K*H), (i_k * BK, i_t * BT), (BK, BC), (0, 1)) + p_b2 = tl.make_block_ptr(b + (bos * H + i_h) * K, (K, T), (1, K*H), (i_k * BK, i_t * BT + BC), (BK, BC), (0, 1)) + b_a1 = tl.load(p_a1, boundary_check=(0, 1)) + b_a2 = tl.load(p_a2, boundary_check=(0, 1)) + b_b1 = tl.load(p_b1, boundary_check=(0, 1)) + b_b2 = tl.load(p_b2, boundary_check=(0, 1)) + b_A += tl.dot(b_a1, b_b1, allow_tf32=False) + b_A2 += tl.dot(b_a2, b_b2, allow_tf32=False) + b_A3 += tl.dot(b_a2, b_b1, allow_tf32=False) + + b_A = tl.where(tl.arange(0, BC)[:, None] > tl.arange(0, BC)[None, :], b_A, 0) + b_A2 = tl.where(tl.arange(0, BC)[:, None] > tl.arange(0, BC)[None, :], b_A2, 0) + + for i in range(1, BC): + mask = tl.arange(0, BC) == i + b_a = tl.sum(tl.where(mask[:, None], b_A, 0), 0) + b_a2 = tl.sum(tl.where(mask[:, None], b_A2, 0), 0) + b_a = b_a + tl.sum(b_a[:, None] * b_A, 0) * (tl.arange(0, BC) < i) + b_a2 = b_a2 + tl.sum(b_a2[:, None] * b_A2, 0) * (tl.arange(0, BC) < i) + b_A = tl.where(mask[:, None], b_a, b_A) + b_A2 = tl.where(mask[:, None], b_a2, b_A2) + + # blockwise computation of lower triangular matrix's inverse + # i.e., [A11, 0; A21, A22]^-1 = [A11^-1, 0; -A22^-1 A21 A11^-1, A22^-1] + b_A += tl.arange(0, BC)[:, None] == tl.arange(0, BC)[None, :] + b_A2 += tl.arange(0, BC)[:, None] == tl.arange(0, BC)[None, :] + b_A3 = tl.dot(tl.dot(b_A2, b_A3, allow_tf32=False), b_A, allow_tf32=False) + + p_A1 = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BC, BC), (1, 0)) + p_A2 = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT + BC, BC), (BC, BC), (1, 0)) + p_A3 = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT + BC, 0), (BC, BC), (1, 0)) + p_A4 = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, BC), (BC, BC), (1, 0)) + tl.store(p_A1, b_A.to(p_A1.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_A2, b_A2.to(p_A2.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_A3, b_A3.to(p_A3.dtype.element_ty), boundary_check=(0, 1)) + # causal mask + tl.store(p_A4, tl.zeros([BC, BC], dtype=tl.float32).to(p_A4.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def wu_fwd_kernel( + w, + u, + a, + k, + v, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_Aak = tl.zeros([BT, BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_a = tl.make_block_ptr(a + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_w = tl.make_block_ptr(w + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_a = tl.load(p_a, boundary_check=(0, 1)) + b_w = tl.dot(b_A, b_a) + b_Aak += tl.dot(b_a, tl.trans(b_k)) + tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1)) + + b_Aak = tl.where(tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :], b_Aak, 0) + b_Aak = b_Aak.to(k.dtype.element_ty) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_u = tl.make_block_ptr(u + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v = tl.dot(b_Aak, b_v).to(v.dtype.element_ty) + b_u = tl.dot(b_A, b_v) + tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1)) + + +def prepare_wy_repr_fwd( + a: torch.Tensor, + b: torch.Tensor, + v: torch.Tensor, + k: torch.Tensor, + cu_seqlens: torch.LongTensor | None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K = a.shape + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BC = min(BT, 32) + BK = min(max(triton.next_power_of_2(K), 16), 64) + + A = torch.empty(B, T, H, BT, device=a.device, dtype=a.dtype) + fwd_fn = prepare_wy_repr_fwd_kernel_chunk64 if BT == 64 else prepare_wy_repr_fwd_kernel_chunk32 + + fwd_fn[(NT, B * H)]( + a=a, + b=b, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BK=BK, + BC=BC, + ) + w, u = wu_fwd( + a=a, + v=v, + k=k, + A=A, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return w, u, A + + +def wu_fwd( + a: torch.Tensor, + v: torch.Tensor, + k: torch.Tensor, + A: torch.Tensor, + cu_seqlens: torch.LongTensor | None, + chunk_size: int, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *a.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + + u = torch.empty_like(v) + w = torch.empty_like(a) + wu_fwd_kernel[(NT, B*H)]( + a=a, + v=v, + w=w, + u=u, + A=A, + k=k, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return w, u + + +fwd_prepare_wy_repr = prepare_wy_repr_fwd + +fwd_wu = wu_fwd diff --git a/code/flash-linear-attention/fla/ops/gla/__init__.py b/code/flash-linear-attention/fla/ops/gla/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..aa786e039f493817fb353072917587534c863a8d --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gla/__init__.py @@ -0,0 +1,10 @@ + +from .chunk import chunk_gla +from .fused_chunk import fused_chunk_gla +from .fused_recurrent import fused_recurrent_gla + +__all__ = [ + 'chunk_gla', + 'fused_chunk_gla', + 'fused_recurrent_gla', +] diff --git a/code/flash-linear-attention/fla/ops/gla/chunk.py b/code/flash-linear-attention/fla/ops/gla/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..b2f90a2915427a7ed27db1918811f3f68b0422d5 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gla/chunk.py @@ -0,0 +1,1320 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.common.chunk_h import chunk_bwd_dh, chunk_fwd_h +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.cumsum import chunk_local_cumsum +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, input_guard + +BK_LIST = [64] +BV_LIST = [64] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [64] + for num_warps in [4] + for num_stages in [3] + ], + key=["BC"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_inter( + q, + k, + g, + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_i, i_j = i_c // NC, i_c % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + if i_i <= i_j: + return + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + p_q = tl.make_block_ptr(q + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(g + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + p_gk = tl.make_block_ptr(g + (bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + p_gn = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_qg = b_q * exp(b_g - b_gn[None, :]) * scale + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kg = b_k * exp(b_gn[:, None] - b_gk) + + # [BC, BC] using tf32 to improve precision here. + b_A += tl.dot(b_qg, b_kg) + + p_A = tl.make_block_ptr(A + (bos*H + i_h)*BT, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + tl.store(p_A, b_A.to(A.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [2, 3] + ], + key=["BK", "BT"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_intra( + q, + k, + g, + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_i, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_j = i_i + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = tl.arange(0, BK) + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + o_A = (bos + i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BT + i_h * BT + i_j * BC + + p_q = tl.make_block_ptr(q + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(g + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + + p_k = k + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + p_gk = g + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_k = tl.load(p_k, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_A = tl.sum(b_q * b_k[None, :] * exp(b_g - b_gk[None, :]), 1) + b_A = tl.where(o_i >= j, b_A * scale, 0.) + + tl.store(A + o_A + j, b_A, mask=m_A) + p_k += H*K + p_gk += H*K + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [4] + ], + key=['BC', 'BK'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_intra_split( + q, + k, + g, + A, + cu_seqlens, + chunk_indices, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_tc, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_t, i_i = i_tc // NC, i_tc % NC + i_j = i_i + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + + o_A = (i_k * all + bos + i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BC + i_h * BC + + p_q = tl.make_block_ptr(q + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(g + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + + p_k = k + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + p_gk = g + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_A = tl.zeros([BC], dtype=tl.float32) + b_k = tl.load(p_k, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_A += tl.sum(b_q * b_k[None, :] * exp(b_g - b_gk[None, :]), 1) + b_A = tl.where(o_i >= j, b_A * scale, 0.) + tl.store(A + o_A + j, b_A, mask=m_A) + p_k += H*K + p_gk += H*K + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + triton.Config({}, num_warps=8), + ], + key=['BC'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_intra_merge( + A, + A2, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + NK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + if i_t * BT + i_c * BC >= T: + return + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + for i_k in range(0, NK): + p_A = tl.make_block_ptr(A + (i_k*all+bos)*H*BC+i_h*BC, (T, BC), (H*BC, 1), (i_t*BT + i_c*BC, 0), (BC, BC), (1, 0)) + b_A += tl.load(p_A, boundary_check=(0, 1)) + p_A2 = tl.make_block_ptr(A2 + (bos*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t * BT + i_c * BC, i_c * BC), (BC, BC), (1, 0)) + tl.store(p_A2, b_A.to(A2.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [64] + for BV in [64, 128] + for num_warps in [4] + for num_stages in [3] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_kernel_o( + q, + v, + g, + h, + o, + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + m_s = tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :] + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_g = tl.make_block_ptr(g + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BT, BK] + b_g = tl.load(p_g, boundary_check=(0, 1)) + # [BT, BK] + b_qg = (b_q * exp(b_g)).to(b_q.dtype) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # works but dkw, owing to divine benevolence + # [BT, BV] + if i_k >= 0: + b_o += tl.dot(b_qg, b_h.to(b_qg.dtype)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BT] + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_A = tl.where(m_s, b_A, 0.).to(b_v.dtype) + b_o += tl.dot(b_A, b_v, allow_tf32=False) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [3] + ], + key=['BK', 'NC', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_intra( + q, + k, + g, + dA, + dq, + dk, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_kc, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_k, i_i = i_kc // NC, i_kc % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + if i_t * BT + i_i * BC >= T: + return + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + p_g = tl.make_block_ptr(g + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + # [BC, BK] + b_g = tl.load(p_g, boundary_check=(0, 1)) + + b_dq = tl.zeros([BC, BK], dtype=tl.float32) + if i_i > 0: + p_gn = g + (bos + i_t * BT + i_i * BC) * H*K + i_h*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(0, i_i): + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT+i_j*BC, i_k * BK), (BC, BK), (1, 0)) + p_gk = tl.make_block_ptr(g+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT+i_j*BC, i_k * BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA+(bos*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t*BT+i_i*BC, i_j * BC), (BC, BC), (1, 0)) + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kg = b_k * exp(b_gn[None, :] - b_gk) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + + b_dq += tl.dot(b_dA, b_kg) + b_dq *= exp(b_g - b_gn[None, :]) + + o_i = tl.arange(0, BC) + m_dA = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + o_dA = bos*H*BT + (i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BT + i_h * BT + i_i * BC + p_kj = k + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gkj = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_dq = tl.make_block_ptr(dq + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dA = tl.load(dA + o_dA + j, mask=m_dA, other=0) + # [BK,] + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] >= j + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dq += tl.where(m_i, b_dA[:, None] * b_kj[None, :] * exp(b_g - b_gkj[None, :]), 0.) + p_kj += H*K + p_gkj += H*K + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + tl.debug_barrier() + # [BC, BK] + b_dk = tl.zeros([BC, BK], dtype=tl.float32) + + NC = min(NC, tl.cdiv(T - i_t * BT, BC)) + if i_i < NC - 1: + p_gn = g + (bos + min(i_t * BT + i_i * BC + BC, T) - 1) * H*K + i_h * K + o_k + + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(i_i + 1, NC): + p_q = tl.make_block_ptr(q + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT+i_j*BC, i_k*BK), (BC, BK), (1, 0)) + p_gq = tl.make_block_ptr(g + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT+i_j*BC, i_k*BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA + (bos*H+i_h)*BT, (BT, T), (1, H*BT), (i_i*BC, i_t*BT + i_j*BC), (BC, BC), (0, 1)) + + o_j = i_t * BT + i_j * BC + o_i + m_j = o_j < T + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_gq = tl.load(p_gq, boundary_check=(0, 1)) + b_qg = b_q * tl.where(m_j[:, None], exp(b_gq - b_gn[None, :]), 0) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dk += tl.dot(b_dA, b_qg) + b_dk *= exp(b_gn[None, :] - b_g) + o_dA = bos*H*BT + (i_t * BT + i_i * BC) * H*BT + i_h * BT + i_i * BC + tl.arange(0, BC) + p_qj = q + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gqj = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_dk = tl.make_block_ptr(dk + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dA = tl.load(dA + o_dA + j * H*BT) + # [BK,] + b_qj = tl.load(p_qj, mask=m_k, other=0).to(tl.float32) + b_gqj = tl.load(p_gqj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] <= j + b_dk += tl.where(m_i, b_dA[:, None] * b_qj[None, :] * exp(b_gqj[None, :] - b_g), 0.) + p_qj += H*K + p_gqj += H*K + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [3] + ], + key=['BV', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_dA( + v, + do, + dA, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + for i_v in range(tl.cdiv(V, BV)): + p_do = tl.make_block_ptr(do + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (V, T), (1, H*V), (i_v * BV, i_t * BT), (BV, BT), (0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + + b_dA += tl.dot(b_do, b_v) + + p_dA = tl.make_block_ptr(dA + (bos * H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + m_s = tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :] + b_dA = tl.where(m_s, b_dA * scale, 0.) + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in [4] + for num_stages in [3] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_dv( + k, + g, + A, + do, + dh, + dv, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + p_do = tl.make_block_ptr(do + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + + b_A = tl.where(tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :], b_A, 0.) + # (SY 09/17) important to disallow tf32 here to maintain a good precision. + b_dv = tl.dot(b_A, b_do.to(b_A.dtype), allow_tf32=False) + + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_gk = tl.make_block_ptr(g + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_gn = g + (bos + min(i_t * BT + BT, T) - 1)*H*K + i_h * K + o_k + p_dh = tl.make_block_ptr(dh + (i_tg * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + + b_gn = exp(tl.load(p_gn, mask=m_k, other=0)[None, :] - b_gk) + b_k = (b_k * b_gn).to(b_k.dtype) + # [BT, BV] + # (SY 09/17) it is ok to have bf16 interchunk gradient contribution here + b_dv += tl.dot(b_k, b_dh.to(b_k.dtype)) + + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in [4] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_inter( + q, + k, + v, + g, + h, + do, + dh, + dq, + dk, + dq2, + dk2, + dg, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + g += (bos * H + i_h) * K + h += (i_tg * H + i_h) * K*V + do += (bos * H + i_h) * V + dh += (i_tg * H + i_h) * K*V + dq += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + dq2 += (bos * H + i_h) * K + dk2 += (bos * H + i_h) * K + dg += (bos * H + i_h) * K + + p_gk = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + p_gn = g + (min(T, i_t * BT + BT) - 1) * H*K + o_k + b_gn = tl.load(p_gn, mask=m_k, other=0) + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dgk = tl.zeros([BK], dtype=tl.float32) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + + # [BK] + b_dgk += tl.sum(b_h * b_dh, axis=0) + # [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) + + b_dgk *= exp(b_gn) + b_dq *= scale + b_dq = b_dq * exp(b_gk) + b_dk = b_dk * exp(b_gn[None, :] - b_gk) + + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dgk += tl.sum(b_dk * b_k, axis=0) + b_dq += tl.load(p_dq, boundary_check=(0, 1)) + b_dk += tl.load(p_dk, boundary_check=(0, 1)) + b_dg = b_q * b_dq - b_k * b_dk + # tl.debug_barrier() + b_dg = b_dg - tl.cumsum(b_dg, axis=0) + tl.sum(b_dg, axis=0)[None, :] + b_dgk[None, :] + # Buggy due to strange triton compiler issue. + # m_s = tl.where(tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :], 1., 0.) + # b_dg = tl.dot(m_s, b_dg, allow_tf32=False) + b_dgk[None, :] + p_dq = tl.make_block_ptr(dq2, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk2, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dg = tl.make_block_ptr(dg, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_gla_fwd_intra_gk( + q: torch.Tensor, + k: torch.Tensor, + g: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K = k.shape + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BC = min(16, BT) + NC = triton.cdiv(BT, BC) + + A = q.new_empty(B, T, H, BT, dtype=torch.float) + grid = (NT, NC * NC, B * H) + chunk_gla_fwd_A_kernel_intra_sub_inter[grid]( + q=q, + k=k, + g=g, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + NC=NC, + ) + + grid = (NT, NC, B * H) + # load the entire [BC, K] blocks into SRAM at once + if K <= 256: + BK = max(triton.next_power_of_2(K), 16) + chunk_gla_fwd_A_kernel_intra_sub_intra[grid]( + q=q, + k=k, + g=g, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + ) + # split then merge + else: + BK = min(128, triton.next_power_of_2(K)) + NK = triton.cdiv(K, BK) + A_intra = q.new_empty(NK, B, T, H, BC, dtype=torch.float) + + grid = (NK, NT * NC, B * H) + chunk_gla_fwd_A_kernel_intra_sub_intra_split[grid]( + q=q, + k=k, + g=g, + A=A_intra, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + + grid = (NT, NC, B * H) + chunk_gla_fwd_A_kernel_intra_sub_intra_merge[grid]( + A=A_intra, + A2=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + B=B, + H=H, + BT=BT, + BC=BC, + NK=NK, + ) + return A + + +def chunk_gla_fwd_o_gk( + q: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + A: torch.Tensor, + h: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *q.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + o = torch.empty_like(v) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * H) + chunk_gla_fwd_kernel_o[grid]( + q=q, + v=v, + g=g, + h=h, + o=o, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return o + + +def chunk_gla_bwd_dA( + v: torch.Tensor, + do: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, V = v.shape + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BV = min(64, triton.next_power_of_2(V)) + + dA = v.new_empty(B, T, H, BT, dtype=torch.float) + grid = (NT, B * H) + chunk_gla_bwd_kernel_dA[grid]( + v=v, + do=do, + dA=dA, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + V=V, + BT=BT, + BV=BV, + ) + return dA + + +def chunk_gla_bwd_dv( + k: torch.Tensor, + g: torch.Tensor, + A: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *k.shape, do.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dv = torch.empty_like(do) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * H) + chunk_gla_bwd_kernel_dv[grid]( + k=k, + g=g, + A=A, + do=do, + dh=dh, + dv=dv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return dv + + +def chunk_gla_bwd_dqk_intra( + q: torch.Tensor, + k: torch.Tensor, + g: torch.Tensor, + dA: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K = q.shape + BT = chunk_size + BC = min(16, BT) + BK = min(64, triton.next_power_of_2(K)) + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + NK = triton.cdiv(K, BK) + + dq = torch.empty_like(q, dtype=torch.float) + dk = torch.empty_like(k, dtype=torch.float) + grid = (NK * NC, NT, B * H) + chunk_gla_bwd_kernel_intra[grid]( + q=q, + k=k, + g=g, + dA=dA, + dq=dq, + dk=dk, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + return dq, dk + + +def chunk_gla_bwd_dqkg( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + g: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + dq: torch.Tensor, + dk: torch.Tensor, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dg = torch.empty_like(g) + dq2 = torch.empty_like(dq) + dk2 = torch.empty_like(dk) + def grid(meta): return (triton.cdiv(K, meta['BK']), NT, B * H) + chunk_gla_bwd_kernel_inter[grid]( + q=q, + k=k, + v=v, + g=g, + h=h, + do=do, + dh=dh, + dq=dq, + dk=dk, + dq2=dq2, + dk2=dk2, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return dq2, dk2, dg + + +def chunk_gla_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + g_cumsum: torch.Tensor | None, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + if g_cumsum is None: + g_cumsum = chunk_local_cumsum(g, chunk_size, cu_seqlens=cu_seqlens) + + h, ht = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=g_cumsum, + gv=None, + h0=initial_state, + output_final_state=output_final_state, + states_in_fp32=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + # the intra A is kept in fp32 + # the computation has very marginal effect on the entire throughput + A = chunk_gla_fwd_intra_gk( + q=q, + k=k, + g=g_cumsum, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + o = chunk_gla_fwd_o_gk( + q=q, + v=v, + g=g_cumsum, + A=A, + h=h, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return g_cumsum, A, h, ht, o + + +def chunk_gla_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + g_cumsum: torch.Tensor | None, + scale: float, + initial_state: torch.Tensor, + h: torch.Tensor, + A: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + if g_cumsum is None: + g_cumsum = chunk_local_cumsum(g, chunk_size, cu_seqlens=cu_seqlens) + + if h is None: + h, _ = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=g_cumsum, + gv=None, + h0=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=True, + ) + dh, dh0 = chunk_bwd_dh( + q=q, + k=k, + v=v, + g=None, + gk=g_cumsum, + gv=None, + do=do, + h0=initial_state, + dht=dht, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=True, + ) + + dv = chunk_gla_bwd_dv( + k=k, + g=g_cumsum, + A=A, + do=do, + dh=dh, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + # dq dk in fp32 + dA = chunk_gla_bwd_dA( + v=v, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dq, dk = chunk_gla_bwd_dqk_intra( + q=q, + k=k, + g=g_cumsum, + dA=dA, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dq, dk, dg = chunk_gla_bwd_dqkg( + q=q, + k=k, + v=v, + h=h, + g=g_cumsum, + do=do, + dh=dh, + dq=dq, + dk=dk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return dq, dk, dv, dg, dh0 + + +class ChunkGLAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q, + k, + v, + g, + scale, + initial_state, + output_final_state, + cu_seqlens, + ): + chunk_size = min(64, max(16, triton.next_power_of_2(q.shape[1]))) + + g_cumsum, A, _, ht, o = chunk_gla_fwd( + q=q, + k=k, + v=v, + g=g, + g_cumsum=None, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + # recompute g_cumsum in bwd pass + if g.dtype != torch.float: + g_cumsum = None + else: + g = None + ctx.save_for_backward(q, k, v, g, g_cumsum, initial_state, A) + ctx.chunk_size = chunk_size + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + return o, ht + + @staticmethod + @input_guard + def backward(ctx, do, dht): + q, k, v, g, g_cumsum, initial_state, A = ctx.saved_tensors + chunk_size, scale, cu_seqlens = ctx.chunk_size, ctx.scale, ctx.cu_seqlens + dq, dk, dv, dg, dh0 = chunk_gla_bwd( + q=q, + k=k, + v=v, + g=g, + g_cumsum=g_cumsum, + scale=scale, + h=None, + A=A, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return dq.to(q), dk.to(k), dv.to(v), dg, None, dh0, None, None + + +@torch.compiler.disable +def chunk_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: int | None = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H, K]`. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gla import chunk_gla + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, K, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = chunk_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = chunk_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = q.shape[-1] ** -0.5 + o, final_state = ChunkGLAFunction.apply(q, k, v, g, scale, initial_state, output_final_state, cu_seqlens) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gla/fused_chunk.py b/code/flash-linear-attention/fla/ops/gla/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..b45f56e96d644fd7d0d6e7ad8ca2ce1919c8adb7 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gla/fused_chunk.py @@ -0,0 +1,640 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl +from einops import rearrange +from packaging import version + +from fla.ops.utils import chunk_local_cumsum +from fla.ops.utils.op import exp, safe_exp +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +@triton.jit(do_not_specialize=['T']) +def prepare_qg_kg( + q, + k, + g, + qg, + kg, + scale, + T, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, +): + i_k, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + p_q = q + i_bh * T*K + i_c * BT * K + i_k * BK + tl.arange(0, BK) + p_g = g + i_bh * T*K + i_c * BT * K + i_k * BK + tl.arange(0, BK) + p_k = k + i_bh * T*K + i_c * BT * K + i_k * BK + tl.arange(0, BK) + p_qg = qg + i_bh * T*K + i_c * BT * K + i_k * BK + tl.arange(0, BK) + p_kg = kg + i_bh * T*K + i_c * BT * K + i_k * BK + tl.arange(0, BK) + + mask = (i_k * BK + tl.arange(0, BK)) < K + + last_decay = tl.load(g + i_bh * T*K + (i_c * BT + BT - 1) * K + i_k * BK + tl.arange(0, BK)) + + for _ in range(BT): + b_q = tl.load(p_q, mask=mask, other=0) + b_k = tl.load(p_k, mask=mask, other=0) + b_g = tl.load(p_g, mask=mask, other=0).to(tl.float32) + b_q *= exp(b_g) * scale + b_k *= exp(last_decay - b_g) + tl.store(p_kg, b_k.to(p_kg.dtype.element_ty), mask=mask) + tl.store(p_qg, b_q.to(p_qg.dtype.element_ty), mask=mask) + p_q += K + p_g += K + p_k += K + p_kg += K + p_qg += K + + +@triton.jit(do_not_specialize=['T']) +def bwd_decay_global_cumsum( + dq_inner, + dq_inter, + dk_inner, + dk_inter, + q, + k, + g, + dg, + T, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, +): + i_k, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + p_q = q + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + p_k = k + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + p_g = g + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + p_dg = dg + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + p_dq_inner = dq_inner + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + p_dk_inner = dk_inner + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + p_dq_inter = dq_inter + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + p_dk_inter = dk_inter + i_bh * T*K + i_k * BK + tl.arange(0, BK) + (i_c * BT + BT - 1) * K + cum_grad_dg = tl.zeros([BK], dtype=tl.float32) + mask = (i_k * BK + tl.arange(0, BK)) < K + last_g = tl.zeros([BK], dtype=tl.float32) + for j in range(BT-1, -1, -1): + b_g = tl.load(p_g, mask=mask, other=0).to(tl.float32) + if j == (BT-1): + last_g = b_g + b_dq1 = tl.load(p_dq_inner, mask=mask, other=0) + b_dq2 = tl.load(p_dq_inter, mask=mask, other=0) + b_dq2 *= exp(b_g) + b_dq = b_dq1 + b_dq2 + tl.store(p_dq_inter, b_dq, mask=mask) + b_dk1 = tl.load(p_dk_inner, mask=mask, other=0) + b_dk2 = tl.load(p_dk_inter, mask=mask, other=0) + b_dk2 *= safe_exp(last_g - b_g) + b_dk = b_dk1 + b_dk2 + tl.store(p_dk_inter, b_dk, mask=mask) + b_q = tl.load(p_q, mask=mask, other=0) + b_k = tl.load(p_k, mask=mask, other=0) + b_dg = b_dq * b_q - b_dk * b_k + cum_grad_dg += b_dg + tl.store(p_dg, cum_grad_dg.to(p_dg.dtype.element_ty), mask=mask) + p_g -= K + p_k -= K + p_q -= K + p_dq_inner -= K + p_dk_inner -= K + p_dq_inter -= K + p_dk_inter -= K + p_dg -= K + + +@triton.jit(do_not_specialize=['T']) +def fused_chunk_gla_fwd_kernel( + q, + k, + v, + g, + o, + h0, + ht, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + CHECK: tl.constexpr, +): + i_v, i_k, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + + # make block pointers + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (0, i_k * BK), (BT, BK), (1, 0)) + p_gn = g + i_bh * T*K + (BT - 1) * K + i_k * BK + tl.arange(0, BK) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k * BK, 0), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (0, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + (i_bh + i_k * B * H) * T*V, (T, V), (V, 1), (0, i_v * BV), (BT, BV), (1, 0)) + + if USE_INITIAL_STATE: + p_h = tl.make_block_ptr(h0 + i_bh * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h += tl.load(p_h, boundary_check=(0, 1)).to(tl.float32) + + mask = (i_k * BK + tl.arange(0, BK)) < K + + for i in range(0, tl.cdiv(T, BT)): + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_gn = tl.load(p_gn, mask=mask, other=0).to(tl.float32) + if CHECK and i == 0: + b_o = tl.dot(b_q.to(b_v.dtype), b_h.to(b_v.dtype), allow_tf32=False) + b_h = b_h * exp(b_gn)[:, None] + tl.dot(b_k.to(b_v.dtype), b_v, allow_tf32=False) + else: + b_o = tl.dot(b_q.to(b_v.dtype), b_h.to(b_v.dtype), allow_tf32=False) + b_h = b_h * exp(b_gn)[:, None] + tl.dot(b_k.to(b_v.dtype), b_v, allow_tf32=False) + + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + p_q = tl.advance(p_q, (BT, 0)) + p_k = tl.advance(p_k, (0, BT)) + p_v = tl.advance(p_v, (BT, 0)) + p_o = tl.advance(p_o, (BT, 0)) + p_gn += BT * K + + if STORE_FINAL_STATE: + p_final = tl.make_block_ptr(ht + i_bh * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_final, b_h.to(p_final.dtype.element_ty), boundary_check=(0, 1)) + + +# Similar to Algorithm1 of https://arxiv.org/abs/2006.16236 +@triton.jit(do_not_specialize=['T']) +def fused_chunk_gla_bwd_kernel( + q, k, v, g, + do, + dq, + dk, + dv, + h0, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + # clamp_min, # minimum log value of the gate for numerical stability. default: -5 + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + CHECK: tl.constexpr, +): + i_v, i_k, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + # [BV, BK] + b_h = tl.zeros([BV, BK], dtype=tl.float32) + + if USE_INITIAL_STATE: + p_h = tl.make_block_ptr(h0 + i_bh * K * V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + b_h += tl.load(p_h, boundary_check=(0, 1)).to(tl.float32) + + mask = (i_k * BK + tl.arange(0, BK)) < K + for i in range(0, tl.cdiv(T, BT)): + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i * BT, i_k * BK), (BT, BK), (1, 0)) + p_gn = g + i_bh * T*K + ((i+1) * BT - 1) * K + i_k * BK + tl.arange(0, BK) + p_v = tl.make_block_ptr(v + i_bh * T*V, (V, T), (1, V), (i_v * BV, i * BT), (BV, BT), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i * BT, i_v * BV), (BT, BV), (1, 0)) + p_dq = tl.make_block_ptr(dq + (i_bh+i_v*B*H)*T*K, (T, K), (K, 1), (i * BT, i_k * BK), (BT, BK), (1, 0)) + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + # [BT, K] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gn = tl.load(p_gn, mask=mask, other=0).to(tl.float32) + + # [V, BT] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, V] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [V, K] + if CHECK and i == 0: + b_dq += tl.dot(b_do, b_h.to(b_do.dtype), allow_tf32=False) + b_h = b_h * exp(b_gn)[None, :] + tl.dot(b_v, b_k.to(b_v.dtype), allow_tf32=False) + else: + b_dq += tl.dot(b_do, b_h.to(b_do.dtype), allow_tf32=False) + b_h = b_h * exp(b_gn)[None, :] + tl.dot(b_v, b_k.to(b_v.dtype), allow_tf32=False) + b_dq *= scale + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + # sync threads + b_h = None + tl.debug_barrier() + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + + # cum = tl.zeros([BK], dtype=tl.float32) + for i in range(1, tl.cdiv(T, BT) + 1): + p_q = tl.make_block_ptr(q + i_bh * T*K, (K, T), (1, K), (i_k * BK, T - i * BT), (BK, BT), (0, 1)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (T - i * BT, i_k * BK), (BT, BK), (1, 0)) + p_gn = g + i_bh * T*K + (T - (i-1) * BT - 1) * K + i_k * BK + tl.arange(0, BK) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (T - i * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (T - i * BT, i_v * BV), (BT, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk + (i_bh + i_v * B * H) * T*K, (T, K), + (K, 1), (T - i * BT, i_k * BK), (BT, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv + (i_bh + i_k * B * H) * T*V, (T, V), + (V, 1), (T - i * BT, i_v * BV), (BT, BV), (1, 0)) + # [K, BT] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BT, K] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, V] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_db = tl.load(p_gn, mask=mask, other=0).to(tl.float32) + + # inter-chunk + # [K, V] + if CHECK and i == 1: + b_dk = tl.trans(tl.dot(b_dh.to(b_v.dtype), tl.trans(b_v), allow_tf32=False)) + b_dv = tl.dot((b_k).to(b_v.dtype), b_dh.to(b_v.dtype), allow_tf32=False) + b_dh = b_dh * exp(b_db)[:, None] + tl.dot(b_q.to(b_do.dtype), b_do, allow_tf32=False) + else: + b_dk = tl.trans(tl.dot(b_dh.to(b_v.dtype), tl.trans(b_v), allow_tf32=False)) + b_dv = tl.dot((b_k).to(b_v.dtype), b_dh.to(b_v.dtype), allow_tf32=False) + b_dh = b_dh * exp(b_db)[:, None] + tl.dot(b_q.to(b_do.dtype), b_do, allow_tf32=False) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit +def fwd_inner_chunk( + q, k, g, A, + scale, # K ** -0.5 + B: tl.constexpr, # B + H: tl.constexpr, # H + T, # T + K: tl.constexpr, # K + BT: tl.constexpr, # BLOCK SIZE along the sequence dimension, a.k.a. chunk size + BK: tl.constexpr, # BLOCK SIZE along the K dimension +): + + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_g = tl.make_block_ptr(g + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)).to(tl.float32) + + mask = (i_k * BK + tl.arange(0, BK)) < K + o_i = tl.arange(0, BT) + + p_q = q + i_bh * T*K + i_k * BK + i_t * BT * K + tl.arange(0, BK) + p_gq = g + i_bh * T*K + i_k * BK + i_t * BT * K + tl.arange(0, BK) + p_A = A + (i_bh + (i_k * B * H)) * (tl.cdiv(T, BT) * BT * BT) + i_t * BT * BT + tl.arange(0, BT) + + for i in range(BT): + b_q = tl.load(p_q, mask=mask, other=0) * scale + b_gq = tl.load(p_gq, mask=mask, other=0).to(tl.float32) + s = b_q[None, :] * b_k * safe_exp(b_gq[None, :] - b_g) + score = tl.sum(s, axis=1) + score = tl.where(o_i <= i, score, 0) + tl.store(p_A, score.to(p_A.dtype.element_ty)) + p_q += K + p_gq += K + p_A += BT + + +@triton.jit +def bwd_inner_chunk( + q, + k, + g, + dA, + dq, + dk, + T, # T + K: tl.constexpr, # K + # clamp_min, # minimum log value of the gate for numerical stability. default: -5 + BT: tl.constexpr, # BLOCK SIZE along the sequence dimension, a.k.a. chunk size + BK: tl.constexpr, # BLOCK SIZE along the K dimension +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + p_g = tl.make_block_ptr(g + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_g = tl.load(p_g, boundary_check=(0, 1)).to(tl.float32) + + mask = (i_k * BK + tl.arange(0, BK)) < K + o_i = tl.arange(0, BT) + + p_q = q + i_bh * T*K + i_k * BK + i_t * BT * K + tl.arange(0, BK) + p_dq = dq + (i_bh) * T*K + i_k * BK + i_t * BT * K + tl.arange(0, BK) + p_gq = g + i_bh * T*K + i_k * BK + i_t * BT * K + tl.arange(0, BK) + p_dA = dA + i_bh * (tl.cdiv(T, BT) * BT * BT) + i_t * BT * BT + tl.arange(0, BT) + + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + + for i in range(BT): + b_q = tl.load(p_q, mask=mask, other=0) + b_gq = tl.load(p_gq, mask=mask, other=0).to(tl.float32) + score = safe_exp(b_gq[None, :] - b_g) + score = tl.where(o_i[:, None] <= i, score, 0) + b_dA = tl.load(p_dA) + b_dA = tl.where(o_i <= i, b_dA, 0) + b_dk += (b_dA[:, None] * score * b_q[None, :]) + b_dq = tl.sum(b_dA[:, None] * score * b_k, axis=0) + tl.store(p_dq, b_dq, mask=mask) + p_q += K + p_dq += K + p_gq += K + p_dA += BT + + p_dk = tl.make_block_ptr(dk + i_bh * T*K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dk, b_dk.to(dk.dtype.element_ty), boundary_check=(0, 1)) + + +class FusedChunkGLAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, g, scale, initial_state, output_final_state): + ctx.g_dtype = g.dtype + ctx.scale = scale + B, H, T, K, V = *k.shape, v.shape[-1] + BT = 16 # chunk_size + BK, BV = min(K, 64), min(V, 64) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + num_stages = 1 + num_warps = 2 + + g_org = g + # cumulative decay should be in float32, otherwise the err will be accumulated and amplified. + g = chunk_local_cumsum(g_org, chunk_size=BT) + o = q.new_empty(NK, B, H, T, V) + q_g = torch.empty_like(q) + k_g = torch.empty_like(k) + + grid = (NK, triton.cdiv(T, BT), B * H) + prepare_qg_kg[grid]( + q, + k, + g, + q_g, + k_g, + scale, + T=T, + K=K, + BT=BT, + BK=BK, + num_warps=1, + ) + + if output_final_state: + final_state = q.new_empty(B, H, K, V, dtype=torch.float, requires_grad=False) + else: + final_state = None + # the bug still exists even for Triton 2.2 on H100 GPUs + # so we always enable initial checks + CHECK = True + if version.parse(triton.__version__) < version.parse('2.2.0'): + import warnings + warnings.warn( + "Triton<2.2.0 detected for running this kernel, " + "which is known to have some weird compiler issues (refer to https://github.com/openai/triton/issues/2852) " + "that lead to significant precision loss. " + "We've add some initial condition checks to resolve this, sadly at the sacrifice of the speed. " + "For optimal performance, it is recommended to install Triton>=2.2.0 (if possible).", + ) + CHECK = True + + grid = (NV, NK, B * H) + fused_chunk_gla_fwd_kernel[grid]( + q_g, k_g, v, g, o, initial_state, final_state, + T=T, + B=B, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + USE_INITIAL_STATE=initial_state is not None, + STORE_FINAL_STATE=output_final_state, + CHECK=CHECK, + num_warps=num_warps, + num_stages=num_stages, + ) + + o = o.sum(0) + + # intra-chunk + chunk_size = 16 + num_chunk = T // chunk_size + v2 = rearrange(v, 'b h (n c) d -> b h n c d', n=num_chunk) + BK = min(K, 64) + NK = triton.cdiv(K, BK) + A = q.new_empty(NK, B, H, triton.cdiv(T, BT), BT, BT) + grid = (NK, triton.cdiv(T, BT), B * H) + fwd_inner_chunk[grid]( + q, k, g, A, + scale, + B=B, + H=H, + T=T, + K=K, + BT=BT, + BK=BK, + num_stages=3, + num_warps=4, + ) + A = A.sum(0) + o2 = A @ v2 + o2 = rearrange(o2, 'b h n c d -> b h (n c) d') + # combine inner and inter + o.add_(o2) + ctx.save_for_backward(q, k, v, g_org, A, initial_state) + ctx.CHECK = CHECK + return o.to(v), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht=None): + q, k, v, g_org, A, initial_state = ctx.saved_tensors + B, H, T, K, V = *k.shape, v.shape[-1] + scale = ctx.scale + + # recomputation + # inter-chunk + BT = 16 # chunk_size + g = chunk_local_cumsum(g_org, chunk_size=BT) + BK, BV = min(K, 64), min(V, 64) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + q_g = torch.empty_like(q) + k_g = torch.empty_like(k) + grid = (NK, triton.cdiv(T, BT), B * H) + prepare_qg_kg[grid]( + q, + k, + g, + q_g, + k_g, + scale, + T=T, + K=K, + BT=BT, + BK=BK, + num_warps=1, + ) + + BK, BV = min(max(triton.next_power_of_2(K), 16), 64), min(max(triton.next_power_of_2(V), 16), 64) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + num_stages = 1 + num_warps = 2 + dq = q.new_empty(NV, B, H, T, K) + dk = q.new_empty(NV, B, H, T, K) + dv = q.new_empty(NK, B, H, T, V) + + grid = (NV, NK, B * H) + + fused_chunk_gla_bwd_kernel[grid]( + q_g, + k_g, + v, + g, + do, + dq, + dk, + dv, + initial_state, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + USE_INITIAL_STATE=initial_state is not None, + CHECK=ctx.CHECK, + num_warps=num_warps, + num_stages=num_stages, + ) + dq = dq.sum(0) + dk = dk.sum(0) + dv = dv.sum(0) + + # intra chunk + NT = T // BT + v2 = rearrange(v, 'b h (n c) d -> b h n c d', n=NT) + do2 = rearrange(do, 'b h (n c) d -> b h n c d', n=NT) + dA2 = (do2 @ v2.transpose(-2, -1)) * scale + dv2 = A.transpose(-1, -2) @ do2 + dv2 = rearrange(dv2, 'b h n c d -> b h (n c) d', n=NT) + + BK = 16 + NK = triton.cdiv(K, BK) + dk2 = torch.empty_like(k) + dq2 = torch.empty_like(q) + + grid = (NK, NT, B * H) + bwd_inner_chunk[grid]( + q, k, g, + dA2, + dq2, + dk2, + T=T, + K=K, + BT=BT, + BK=BK, + num_warps=1, + num_stages=3, + ) + + BK = min(max(triton.next_power_of_2(K), 16), 32) + NK = triton.cdiv(K, BK) + dg = torch.empty_like(g, dtype=torch.float32) + grid = (NK, triton.cdiv(T, BT), B * H) + bwd_decay_global_cumsum[grid]( + dq2, + dq, + dk2, + dk, + q, + k, + g, + dg, + T=T, + K=K, + BT=BT, + BK=BK, + num_warps=1, + num_stages=1, + ) + dg = rearrange(dg, 'b h (n c) d -> b h n c d', c=BT) + + def rev_cumsum_exclusive(x): + cumsum_x = x.cumsum(-2) + rev_cumsum_x = cumsum_x[..., -1, None, :] - cumsum_x + return rev_cumsum_x + + rev_cumsum_dg = rev_cumsum_exclusive(dg[..., 0, :]) + dg.add_(rev_cumsum_dg.unsqueeze(-2)) + dv.add_(dv2) + dg = rearrange(dg, 'b h n c d -> b h (n c) d') + + return dq.to(q), dk.to(k), dv.to(v), dg.to(ctx.g_dtype), None, None, None + + +def ceildiv(a, b): + return -(a // -b) + + +def pad(x, chunk_size=16): + T = x.shape[-2] + padded_seq_len = ceildiv(T, chunk_size) * chunk_size + if x.shape[-2] % chunk_size != 0: + x = F.pad(x, (0, 0, 0, padded_seq_len - T)) + return x + + +def fused_chunk_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: int = -1, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + if head_first: + warnings.warn( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + q, k, v, g = map(lambda x: rearrange(x, 'b h t ... -> b t h ...'), (q, k, v, g)) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if scale == -1: + scale = q.shape[-1] ** -0.5 + seq_len = q.shape[-2] + q, k, v, g = map(lambda x: pad(x), [q, k, v, g]) + o, final_state = FusedChunkGLAFunction.apply(q, k, v, g, scale, initial_state, output_final_state) + o = o[..., :seq_len, :].contiguous() + if head_first: + o = rearrange(o, 'b t h ... -> b h t ...') + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gla/fused_recurrent.py b/code/flash-linear-attention/fla/ops/gla/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..5f7c99a7633d0f7725b9fab6d6255ab83bac0f66 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gla/fused_recurrent.py @@ -0,0 +1,109 @@ +# Copyright (c) 2024, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.common.fused_recurrent import fused_recurrent + + +def fused_recurrent_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + scale: int | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + gk (torch.Tensor): + Forget gates of shape `[B, T, H, K]`. + gv (torch.Tensor): + Forget gates of shape `[B, T, H, V]` applied to values. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + reverse (Optional[bool]): + If `True`, process the state passing in reverse order. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gla import fused_recurrent_gla + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, K, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = fused_recurrent_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = fused_recurrent_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + >>> assert o.allclose(o_var.view(o.shape)) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = fused_recurrent( + q=q, + k=k, + v=v, + g=None, + gk=gk, + gv=gv, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gla/naive.py b/code/flash-linear-attention/fla/ops/gla/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..2fcbd5089d5178ad2cb3e26e2c75cf9b7d8165d7 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gla/naive.py @@ -0,0 +1,39 @@ + + +import torch + + +def ceildiv(a, b): + return -(a // -b) + + +def naive_recurrent_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gk: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, +): + dtype = q.dtype + q, k, v, gk = map(lambda x: x.transpose(1, 2).float(), (q, k, v, gk)) + B, H, T, K, V = *q.shape, v.shape[-1] + o = torch.zeros_like(v) + scale = K ** -0.5 + + h = q.new_zeros(B, H, K, V, dtype=torch.float32) + if initial_state is not None: + h += initial_state.float() + + for i in range(T): + q_i = q[:, :, i] * scale + k_i = k[:, :, i] + v_i = v[:, :, i] + gk_i = gk[:, :, i].exp() + kv_i = k_i[..., None] * v_i[..., None, :] + h = h * gk_i[..., None] + kv_i + o[:, :, i] = (q_i[..., None] * h).sum(-2) + + if not output_final_state: + h = None + return o.transpose(1, 2).to(dtype), h diff --git a/code/flash-linear-attention/fla/ops/gsa/__init__.py b/code/flash-linear-attention/fla/ops/gsa/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1d827b221e7a0ac104e57a506c7576e7f871a19a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gsa/__init__.py @@ -0,0 +1,8 @@ + +from .chunk import chunk_gsa +from .fused_recurrent import fused_recurrent_gsa + +__all__ = [ + 'chunk_gsa', + 'fused_recurrent_gsa', +] diff --git a/code/flash-linear-attention/fla/ops/gsa/chunk.py b/code/flash-linear-attention/fla/ops/gsa/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..d2704303a07e66ad94e550c6bed7f91644e01b65 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gsa/chunk.py @@ -0,0 +1,1133 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl +from einops import reduce + +from fla.ops.common.chunk_h import chunk_bwd_dh, chunk_fwd_h +from fla.ops.gla.chunk import chunk_gla_bwd, chunk_gla_fwd +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.cumsum import chunk_local_cumsum +from fla.ops.utils.op import exp +from fla.ops.utils.softmax import softmax_bwd, softmax_fwd +from fla.utils import autotune_cache_kwargs, input_guard + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for BV in [32, 64] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gsa_fwd_k_kernel_inter( + q, + k, + h, + g, + o, + A, + cu_seqlens, + chunk_indices, + scale, + T, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_i = tl.arange(0, BT) + m_s = o_i[:, None] >= o_i[None, :] + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BV] + b_o += tl.dot(b_q, b_h) + # [BT, BT] + b_A += tl.dot(b_q, b_k) + p_g = tl.make_block_ptr(g + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_A = tl.make_block_ptr(A + (bos * HQ + i_hq) * BT, (T, BT), (HQ*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + # [BT, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_o = b_o * exp(b_g) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + # [BT, BT] + b_A = tl.where(m_s, b_A, 0.) + if i_v == 0: + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def chunk_gsa_fwd_k_kernel_intra( + v, + g, + o, + A, + cu_seqlens, + chunk_indices, + T, + HQ: tl.constexpr, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, + NG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + i_t, i_i = i_c // NC, i_c % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + + if i_t * BT + i_i * BC >= T: + return + + p_g = tl.make_block_ptr(g + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_gn = g + (bos + min(i_t * BT + i_i * BC, T)) * H*V + i_h * V + o_v + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0) + # [BC, BV] + b_o = tl.zeros([BC, BV], dtype=tl.float32) + for i_j in range(0, i_i): + p_A = tl.make_block_ptr(A + (bos*HQ+i_hq) * BT, (T, BT), (HQ*BT, 1), (i_t*BT+i_i*BC, i_j * BC), (BC, BC), (1, 0)) + p_v = tl.make_block_ptr(v + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + p_gv = tl.make_block_ptr(g + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + # [BC, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_vg = (b_v * exp(b_gn[None, :] - b_gv)).to(b_v.dtype) + # [BC, BC] + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_o += tl.dot(b_A, b_vg) + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_o *= exp(b_g - b_gn[None, :]) + + o_i = tl.arange(0, BC) + o_A = (bos + i_t * BT + i_i * BC + tl.arange(0, BC)) * HQ*BT + i_hq * BT + i_i * BC + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + p_v = v + (bos + i_t * BT + i_i * BC + j) * H*V + i_h * V + o_v + p_gv = g + (bos + i_t * BT + i_i * BC + j) * H*V + i_h * V + o_v + # [BC,] + b_A = tl.load(A + o_A + j, mask=m_A, other=0) + # [BV,] + b_v = tl.load(p_v, mask=m_v, other=0).to(tl.float32) + b_gv = tl.load(p_gv, mask=m_v, other=0).to(tl.float32) + # [BC, BV] + b_vg = b_v[None, :] * exp(b_g - b_gv[None, :]) + # avoid 0 * inf = inf + b_o += tl.where(o_i[:, None] >= j, b_A[:, None] * b_vg, 0.) + p_o = tl.make_block_ptr(o + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + b_o += tl.load(p_o, boundary_check=(0, 1)) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4, 8] + ], + key=["BT"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gsa_bwd_k_kernel_dA( + v, + g, + do, + dA, + chunk_indices, + cu_seqlens, + scale, + T, + B: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, + NG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + i_t, i_i, i_j = i_c // (NC * NC), (i_c % (NC * NC)) // NC, (i_c % (NC * NC)) % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + + if i_t * BT + i_i * BC >= T: + return + + p_dA = tl.make_block_ptr(dA+((i_v*all+bos)*HQ+i_hq)*BT, (T, BT), (HQ*BT, 1), (i_t*BT+i_i*BC, i_j*BC), (BC, BC), (1, 0)) + + # [BC, BC] + b_dA = tl.zeros([BC, BC], dtype=tl.float32) + if i_i > i_j: + p_v = tl.make_block_ptr(v + (bos*H+i_h) * V, (V, T), (1, H*V), (i_v * BV, i_t*BT + i_j*BC), (BV, BC), (0, 1)) + p_gv = tl.make_block_ptr(g + (bos*H+i_h) * V, (V, T), (1, H*V), (i_v * BV, i_t*BT + i_j*BC), (BV, BC), (0, 1)) + p_gn = g + (bos + i_t*BT + i_i*BC) * H*V + i_h * V + o_v + p_g = tl.make_block_ptr(g + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do + (bos*HQ+i_hq) * V, (T, V), (HQ*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0.) + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_do = (b_do * exp(b_g - b_gn[None, :]) * scale).to(b_do.dtype) + # [BV, BC] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_vg = (b_v * exp(b_gn[:, None] - b_gv)).to(b_v.dtype) + # [BC, BC] + b_dA = tl.dot(b_do, b_vg) + elif i_i == i_j: + p_g = tl.make_block_ptr(g + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_v = v + (bos + i_t*BT + i_j*BC) * H*V + i_h * V + o_v + p_gv = g + (bos + i_t*BT + i_j*BC) * H*V + i_h * V + o_v + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) * scale + m_v = o_v < V + + o_i = tl.arange(0, BC) + # [BC, BC] + m_dA = o_i[:, None] >= o_i[None, :] + for j in range(0, min(BC, T - i_t * BT - i_j * BC)): + # [BV,] + b_v = tl.load(p_v, mask=m_v, other=0).to(tl.float32) + b_gv = tl.load(p_gv, mask=m_v, other=0).to(tl.float32) + # [BC,] + b_dAj = tl.sum(b_do * b_v[None, :] * exp(b_g - b_gv[None, :]), 1) + b_dA = tl.where((o_i == j)[None, :], b_dAj[:, None], b_dA) + + p_v += H*V + p_gv += H*V + b_dA = tl.where(m_dA, b_dA, 0.) + tl.store(p_dA, b_dA.to(dA.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gsa_bwd_k_kernel_dqkvg( + q, + k, + v, + h, + g, + A, + do, + dh, + dq, + dk, + dv, + dg, + dgv, + dA, + cu_seqlens, + chunk_indices, + scale, + T, + B: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + all = T + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + all = B * T + + o_i = tl.arange(0, BT) + o_t = min(i_t * BT + BT, T) + m_s = o_i[:, None] >= o_i[None, :] + + p_q = tl.make_block_ptr(q + (bos*HQ+i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos*H+i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_A = tl.make_block_ptr(A + ((i_k*all+bos)*HQ+i_hq)*BT, (T, BT), (HQ*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BT] + b_A = tl.dot((b_q * scale).to(b_q.dtype), tl.trans(b_k)) + b_A = tl.where(m_s, b_A, 0.) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + p_v = tl.make_block_ptr(v + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_g = tl.make_block_ptr(g + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gn = g + (bos + o_t - 1) * H*V + i_h * V + o_v + p_do = tl.make_block_ptr(do + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + ((i_k*all+bos)*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dg = tl.make_block_ptr(dg + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dgv = tl.make_block_ptr(dgv+((i_k*all+bos)*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh + (i_tg * HQ + i_hq) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + m_v = o_v < V + + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_gv = exp(b_gn[None, :] - b_g) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_do = (b_do * exp(b_g) * scale).to(b_do.dtype) + # [BK, BV] + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + # [BV] + b_dg = tl.sum(tl.trans(b_h) * b_dh, 0) * exp(b_gn) + + b_dh = b_dh.to(b_k.dtype) + # [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_k.dtype)) + b_dk += tl.dot((b_v * b_gv).to(b_v.dtype), tl.trans(b_dh)) + # [BT, BV] + b_dv = tl.dot(b_k, b_dh) * b_gv + # [BV] + b_dg += tl.sum(b_dv * b_v, 0) + + if i_k == 0: + b_dgv = tl.load(p_dg, boundary_check=(0, 1)) + b_dg[None, :] + else: + b_dgv = tl.zeros([BT, BV], dtype=tl.float32) + b_dg[None, :] + + tl.store(p_dgv, b_dgv.to(p_dgv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + p_dA = tl.make_block_ptr(dA + (bos*HQ + i_hq) * BT, (T, BT), (HQ*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_dq = tl.make_block_ptr(dq + (bos*HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + # [BT, BT] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BT, BK] + b_dq += tl.dot(b_dA, b_k) + b_dk += tl.dot(tl.trans(b_dA).to(b_k.dtype), b_q) + + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def chunk_gsa_bwd_k_kernel_intra_dvg( + v, + g, + o, + A, + do, + dv, + dg, + cu_seqlens, + chunk_indices, + T, + HQ: tl.constexpr, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, + NG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + i_t, i_i = i_c // NC, i_c % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + + if i_t * BT + i_i * BC >= T: + return + + p_gv = tl.make_block_ptr(g + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_gn = g + (bos + min(i_t * BT + i_i * BC + BC, T)-1)*H*V + i_h*V + o_v + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0) + # [BC, BV] + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_dv = tl.zeros([BC, BV], dtype=tl.float32) + for i_j in range(i_i + 1, NC): + p_g = tl.make_block_ptr(g + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + p_A = tl.make_block_ptr(A + (bos*HQ+i_hq) * BT, (BT, T), (1, HQ*BT), (i_i*BC, i_t*BT + i_j*BC), (BC, BC), (0, 1)) + p_do = tl.make_block_ptr(do + (bos*HQ+i_hq) * V, (T, V), (HQ*V, 1), (i_t*BT + i_j*BC, i_v*BV), (BC, BV), (1, 0)) + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) * exp(b_g - b_gn[None, :]) + # [BC, BC] + b_A = tl.load(p_A, boundary_check=(0, 1)) + # [BC, BV] + b_dv += tl.dot(b_A, b_do.to(b_A.dtype)) + b_dv *= exp(b_gn[None, :] - b_gv) + + o_i = tl.arange(0, BC) + o_c = i_i * BC + tl.arange(0, BC) + + p_g = g + (bos + i_t * BT + i_i * BC) * H*V + i_h * V + o_v + p_A = A + (bos + i_t*BT + i_i*BC) * HQ*BT + i_hq * BT + o_c + p_do = do + (bos + i_t*BT + i_i*BC) * HQ*V + i_hq * V + o_v + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_A = tl.load(p_A) + # [BV,] + b_g = tl.load(p_g, mask=m_v, other=0) + b_do = tl.load(p_do, mask=m_v, other=0) + # [BC, BV] + m_i = o_i[:, None] <= j + b_dv += tl.where(m_i, exp(b_g[None, :] - b_gv) * b_A[:, None] * b_do[None, :], 0.) + + p_g += H * V + p_A += HQ * BT + p_do += HQ * V + p_o = tl.make_block_ptr(o + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_v = tl.make_block_ptr(v + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_dg = tl.make_block_ptr(dg + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + + b_o = tl.load(p_o, boundary_check=(0, 1)).to(tl.float32) + b_v = tl.load(p_v, boundary_check=(0, 1)).to(tl.float32) + b_do = tl.load(p_do, boundary_check=(0, 1)).to(tl.float32) + b_dv = b_dv + tl.load(p_dv, boundary_check=(0, 1)).to(tl.float32) + b_dg = b_o * b_do - b_v * b_dv + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_gsa_fwd_v( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: float = 1., + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + _, A, h, ht, o = chunk_gla_fwd( + q=q, + k=k, + v=v, + g=None, + g_cumsum=g, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return A, h, ht, o + + +def chunk_gsa_fwd_k( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + h0: torch.Tensor | None = None, + output_final_state: bool = False, + scale: float = 1., + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + BC = min(16, BT) + BV = min(64, triton.next_power_of_2(V)) + HQ = q.shape[2] + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + NG = HQ // H + + h, ht = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=None, + gv=g, + h0=h0, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=BT, + states_in_fp32=False, + ) + o = v.new_empty(B, T, HQ, V) + A = q.new_empty(B, T, HQ, BT) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * HQ) + chunk_gsa_fwd_k_kernel_inter[grid]( + q, + k, + h, + g, + o, + A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + NG=NG, + ) + + def grid(meta): return (triton.cdiv(V, meta['BV']), NT * NC, B * HQ) + chunk_gsa_fwd_k_kernel_intra[grid]( + v, + g, + o, + A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + HQ=HQ, + H=H, + V=V, + BT=BT, + BC=BC, + BV=BV, + NC=NC, + NG=NG, + num_warps=4, + num_stages=2, + ) + return A, h, ht, o + + +def chunk_gsa_bwd_v( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + h0: torch.Tensor, + h: torch.Tensor, + A: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + dg: torch.Tensor, + scale: float = 1., + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + dq, dk, dv, dg, dh0 = chunk_gla_bwd( + q=q, + k=k, + v=v, + g=None, + g_cumsum=g, + scale=scale, + initial_state=h0, + h=h, + A=A, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return dq, dk, dv, dg, dh0 + + +def chunk_gsa_bwd_k( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + h: torch.Tensor, + h0: torch.Tensor, + o: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + dg: torch.Tensor, + scale: float = 1., + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + BC = min(16, BT) + BK = min(64, triton.next_power_of_2(K)) + BV = min(64, triton.next_power_of_2(V)) + HQ = q.shape[2] + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + NG = HQ // H + + if h is None: + h, _ = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=None, + gv=g, + h0=h0, + output_final_state=False, + cu_seqlens=cu_seqlens, + chunk_size=BT, + states_in_fp32=False, + ) + dh, dh0 = chunk_bwd_dh( + q=q, + k=k, + v=v, + g=None, + gk=None, + gv=g, + do=do, + h0=h0, + dht=dht, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=BT, + states_in_fp32=True, + ) + dA = q.new_empty(NV, B, T, HQ, BT) + grid = (NV, NT * NC * NC, B * HQ) + chunk_gsa_bwd_k_kernel_dA[grid]( + v, + g, + do, + dA, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + HQ=HQ, + H=H, + V=V, + BT=BT, + BC=BC, + BV=BV, + NC=NC, + NG=NG, + ) + dA = dA.sum(0, dtype=dA.dtype) + + A = do.new_empty(NK, B, T, HQ, BT) + dq = torch.empty_like(q) + dk = k.new_empty(B, T, HQ, K) + dv = v.new_empty(NK, B, T, HQ, V) + dgv = g.new_empty(NK, B, T, HQ, V, dtype=torch.float) + grid = (NK, NT, B * HQ) + chunk_gsa_bwd_k_kernel_dqkvg[grid]( + q, + k, + v, + h, + g, + A, + do, + dh, + dq, + dk, + dv, + dg, + dgv, + dA, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + NG=NG, + ) + A = A.sum(0, dtype=A.dtype) + dv = dv.sum(0, dtype=dv.dtype) + dgv = dgv.sum(0, dtype=dgv.dtype) + + def grid(meta): return (triton.cdiv(V, meta['BV']), NT * NC, B * HQ) + chunk_gsa_bwd_k_kernel_intra_dvg[grid]( + v, + g, + o, + A, + do, + dv, + dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + HQ=HQ, + H=H, + V=V, + BT=BT, + BC=BC, + BV=BV, + NC=NC, + NG=NG, + num_warps=4, + num_stages=2, + ) + dg = dgv.add_(chunk_local_cumsum(dg, chunk_size=BT, reverse=True, cu_seqlens=cu_seqlens)) + + return dq, dk, dv, dg, dh0 + + +def chunk_gsa_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor, + initial_state: tuple[torch.Tensor, torch.Tensor] | None = None, + output_final_state: bool = False, + scale: float = 1., + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + hk0, hv0 = None, None + if initial_state is not None: + hk0, hv0 = initial_state + Ak, hk, hkt, ok = chunk_gsa_fwd_k( + q=q, + k=k, + v=s, + g=g, + h0=hk0, + output_final_state=output_final_state, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + # p is kept in fp32 for safe softmax backward + p = softmax_fwd(ok, dtype=torch.float) + + qv = p.to(q.dtype) + Av, hv, hvt, ov = chunk_gsa_fwd_v( + q=qv, + k=s, + v=v, + g=g, + scale=1., + initial_state=hv0, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return Ak, hk, hkt, ok, p, Av, hv, hvt, ov + + +def chunk_gsa_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor, + ok: torch.Tensor, + p: torch.Tensor, + A: tuple[torch.Tensor, torch.Tensor], + h: tuple[torch.Tensor, torch.Tensor], + initial_state: tuple[torch.Tensor, torch.Tensor] | None, + scale: float, + do: torch.Tensor, + dht: tuple[torch.Tensor, torch.Tensor], + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + hk0, hv0 = None, None + if initial_state is not None: + hk0, hv0 = initial_state + + _, Av = A + hk, hv = h + dhkt, dhvt = dht + + qv = p.to(q.dtype) + dqv, dsv, dv, dg, dhv0 = chunk_gsa_bwd_v( + q=qv, + k=s, + v=v, + g=g, + h0=hv0, + h=hv, + A=Av, + do=do, + dht=dhvt, + dg=None, + scale=1., + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + # softmax gradient, equivalent to: + # dok = qv * (dqv - (qv * dqv).sum(-1, True)) + dok = softmax_bwd(p, dqv, dtype=ok.dtype) + + dq, dk, dsk, dg, dhk0 = chunk_gsa_bwd_k( + q=q, + k=k, + v=s, + g=g, + h0=hk0, + h=hk, + o=ok, + do=dok, + dht=dhkt, + dg=dg, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + ds = dsv.add_(dsk) + if q.shape[1] != k.shape[1]: + dk, dv, ds, dg = map(lambda x: reduce(x, 'b (h g) ... -> b h ...', 'sum', h=k.shape[1]), (dk, dv, ds, dg)) + dg = dg.to(s.dtype) + return dq, dk, dv, ds, dg, dhk0, dhv0 + + +class ChunkGSAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor, + scale: float, + hk0: torch.Tensor | None, + hv0: torch.Tensor | None, + output_final_state: bool, + checkpoint_level: int, + cu_seqlens: torch.LongTensor | None, + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + chunk_size = min(64, max(16, triton.next_power_of_2(q.shape[1]))) + + g_org, g = g, chunk_local_cumsum(g, chunk_size, cu_seqlens=cu_seqlens) + Ak, hk, hkt, ok, p, Av, hv, hvt, ov = chunk_gsa_fwd( + q=q, + k=k, + v=v, + s=s, + g=g, + initial_state=(hk0, hv0), + output_final_state=output_final_state, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + if checkpoint_level >= 1: + del g + g = g_org + if checkpoint_level > 1: + del hk + del hv + hk, hv = None, None + else: + hk0, hv0 = None, None + + ctx.save_for_backward(q, k, v, s, g, ok, p, Av, hk0, hv0, hk, hv) + ctx.checkpoint_level = checkpoint_level + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + ctx.chunk_size = chunk_size + return ov, hkt, hvt + + @staticmethod + @input_guard + def backward(ctx, dov, dhkt=None, dhvt=None): + q, k, v, s, g, ok, p, Av, hk0, hv0, hk, hv = ctx.saved_tensors + scale = ctx.scale + cu_seqlens = ctx.cu_seqlens + chunk_size = ctx.chunk_size + + if ctx.checkpoint_level >= 1: + g = chunk_local_cumsum(g, chunk_size, cu_seqlens=cu_seqlens) + dq, dk, dv, ds, dg, dhk0, dhv0 = chunk_gsa_bwd( + q=q, + k=k, + v=v, + s=s, + g=g, + ok=ok, + p=p, + A=(None, Av), + h=(hk, hv), + initial_state=(hk0, hv0), + scale=scale, + do=dov, + dht=(dhkt, dhvt), + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return dq, dk, dv, ds, dg, None, dhk0, dhv0, None, None, None, None + + +@torch.compiler.disable +def chunk_gsa( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor | None = None, + scale: int | None = None, + initial_state: tuple[torch.Tensor] | None = None, + output_final_state: bool | None = False, + checkpoint_level: int | None = 2, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool | None = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, HQ, K]`.. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + GQA is performed if `H` is not equal to `HQ`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + s (torch.Tensor): + slot representations of shape `[B, T, H, M]`.. + g (torch.Tensor): + Forget gates of shape `[B, T, H, M]` applied to keys. + If not provided, this function is equivalent to vanilla ABC. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[Tuple[torch.Tensor]]): + Initial state tuple having tensors of shape `[N, H, K, M]` and `[N, H, M, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state tuple, having tensors of shape `[N, H, K, M]` and `[N, H, M, V]`. + Default: `False`. + checkpoint_level (Optional[int]): + Checkpointing level; higher values will save more memories and do more recomputations during backward. + Default: `2`: + - Level `0`: no memory saved, no recomputation. + - Level `1`: recompute the fp32 cumulative values during backward. + - Level `2`: recompute the fp32 cumulative values and forward hidden states during backward. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (Tuple[torch.Tensor]): + Final state tuple having tensors of shape `[N, H, K, M]` and `[N, H, M, V]` if `output_final_state=True`. + `None` otherwise. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gsa import fused_recurrent_gsa + # inputs with equal lengths + >>> B, T, H, K, V, M = 4, 2048, 4, 512, 512, 64 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> s = torch.randn(B, T, H, M, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, M, device='cuda')) + >>> h0 = (torch.randn(B, H, K, M, device='cuda'), torch.randn(B, H, M, V, device='cuda')) + >>> o, (hk, hv) = chunk_gsa( + q, k, v, s, g, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, s, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, s, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, (hk_var, hv_var) = chunk_gsa( + q, k, v, s, g, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + >>> assert o.allclose(o_var.view(o.shape)) + >>> assert hk.allclose(hk_var) + >>> assert hv.allclose(hv_var) + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state[0].shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state[0].shape[0]}.", + ) + assert checkpoint_level in [0, 1, 2] + if g is None: + # TODO: this 3 steps took huge amount of time, ought to be optimized + z = s.float().logcumsumexp(2) + g = torch.cat((z[:, :, :1], z[:, :, :-1]), 1) - z + s = torch.exp(s - z).to(k.dtype) + if scale is None: + scale = q.shape[-1] ** -0.5 + + hk0, hv0 = None, None + if initial_state is not None: + hk0, hv0 = initial_state + o, *final_state = ChunkGSAFunction.apply( + q, + k, + v, + s, + g, + scale, + hk0, + hv0, + output_final_state, + checkpoint_level, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gsa/fused_recurrent.py b/code/flash-linear-attention/fla/ops/gsa/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..82f49a421b66a6db0ea74bd4b2699b6014fe124e --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gsa/fused_recurrent.py @@ -0,0 +1,534 @@ +# Copyright (c) 2024, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.common.fused_recurrent import fused_recurrent_bwd_kernel, fused_recurrent_fwd_kernel +from fla.ops.utils.op import exp +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +@triton.jit +def fused_recurrent_gsa_inference_kernel( + q, + k, + v, + s, + g, + o, + hk0, + hv0, + hkt, + hvt, + scale, + K: tl.constexpr, + V: tl.constexpr, + M: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, +): + i_bh = tl.program_id(0) + i_bg = i_bh // NG + + b_s = tl.load(s + i_bg * M + tl.arange(0, M)).to(tl.float32) + b_g = tl.load(g + i_bg * M + tl.arange(0, M)).to(tl.float32) + b_g = exp(b_g) + + b_ok = tl.zeros([M], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + + p_hk0 = hk0 + i_bg * K * M + (o_k[None, :]) * M + tl.arange(0, M)[:, None] + # [BK,] + mask_k = o_k < K + # [M, BK] + mask_hk = (tl.arange(0, M) < M)[:, None] & mask_k[None, :] + # [M, BK] + b_hk = tl.load(p_hk0, mask=mask_hk, other=0.).to(tl.float32) + # [BK,] + b_q = tl.load(q + i_bh * K + o_k, mask=mask_k, other=0.).to(tl.float32) * scale + b_k = tl.load(k + i_bg * K + o_k, mask=mask_k, other=0.).to(tl.float32) + b_hk = b_hk * b_g[:, None] + b_k[None, :] * b_s[:, None] + b_ok += tl.sum(b_hk * b_q[None, :], axis=1) + + if i_bh % NG == 0: + p_hkt = hkt + i_bg * K * M + o_k[None, :] * M + tl.arange(0, M)[:, None] + tl.store(p_hkt, b_hk.to(p_hkt.dtype.element_ty), mask=mask_hk) + + b_qv = tl.softmax(b_ok) + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + + p_hv0 = hv0 + i_bg * M * V + tl.arange(0, M)[None, :] * V + o_v[:, None] + # [BV,] + mask_v = o_v < V + # [BV, M] + mask_hv = mask_v[:, None] & (tl.arange(0, M) < M)[None, :] + # [BV, M] + b_hv = tl.load(p_hv0, mask=mask_hv, other=0).to(tl.float32) + # [BV,] + b_v = tl.load(v + i_bg * V + o_v, mask=mask_v, other=0).to(tl.float32) + b_hv = b_hv * b_g[None, :] + b_s[None, :] * b_v[:, None] + b_ov = tl.sum(b_hv * b_qv[None, :], axis=1) + + tl.store(o + i_bh * V + o_v, b_ov.to(o.dtype.element_ty), mask=mask_v) + + if i_bh % NG == 0: + p_hvt = hvt + i_bg * M * V + tl.arange(0, M)[None, :] * V + o_v[:, None] + tl.store(p_hvt, b_hv.to(p_hvt.dtype.element_ty), mask=mask_hv) + + +def fused_recurrent_gsa_inference( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor, + initial_state: tuple[torch.Tensor, torch.Tensor] | None = None, + output_final_state: bool = False, + scale: float = 1., +) -> torch.Tensor: + B, T, H, K, V, M = *k.shape, v.shape[-1], s.shape[-1] + HQ = q.shape[2] + BK, BV = min(triton.next_power_of_2(K), 64), min(triton.next_power_of_2(V), 64) + NG = HQ // H + + if initial_state != (None, None) and initial_state is not None: + hk0, hv0 = initial_state + else: + hk0, hv0 = q.new_zeros(B, H, K, M, dtype=torch.float), q.new_zeros(B, H, M, V, dtype=torch.float) + + hkt, hvt = None, None + if output_final_state: + if NG == 1: + hkt, hvt = hk0, hv0 + else: + hkt, hvt = q.new_empty(B, H, K, M, dtype=torch.float), q.new_empty(B, H, M, V, dtype=torch.float) + + o = v.new_empty(B, T, HQ, V) + grid = (B * HQ,) + fused_recurrent_gsa_inference_kernel[grid]( + q, + k, + v, + s, + g, + o, + hk0, + hv0, + hkt, + hvt, + scale=scale, + K=K, + V=V, + M=M, + BK=BK, + BV=BV, + NG=NG, + ) + return o, (hkt, hvt) + + +def fused_recurrent_gsa_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor, + initial_state: tuple[torch.Tensor, torch.Tensor] | None = None, + output_final_state: bool = False, + scale: float = 1., + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, tuple[torch.Tensor]]: + B, T, H, K, V, M = *k.shape, v.shape[-1], s.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + HQ = q.shape[2] + if HQ != H: + raise ValueError("GQA not supported yet.") + + BK, BV, BM = min(triton.next_power_of_2(K), 64), min(triton.next_power_of_2(V), 64), min(triton.next_power_of_2(M), 64) + NK, NV, NM = triton.cdiv(K, BK), triton.cdiv(V, BV), triton.cdiv(M, BM) + + hk0, hv0 = None, None + if initial_state != (None, None) and initial_state is not None: + hk0, hv0 = initial_state + hkt, hvt = None, None + if output_final_state: + hkt, hvt = q.new_empty(N, H, K, M, dtype=torch.float), q.new_empty(N, H, M, V, dtype=torch.float) + + ok = q.new_empty(NK, *s.shape, dtype=torch.float) + gk, gv = None, g + grid = (NM, NK, N * H) + fused_recurrent_fwd_kernel[grid]( + q=q, + k=k, + v=s, + g=None, + g_gamma=None, + gk=gk, + gv=gv, + o=ok, + h0=hk0, + ht=hkt, + cu_seqlens=cu_seqlens, + scale=scale, + B=B, + T=T, + H=H, + K=K, + V=M, + BK=BK, + BV=BM, + USE_G=False, + USE_G_GAMMA=False, + USE_GK=False, + USE_GV=True, + REVERSE=reverse, + ) + ok = ok.sum(0) + + qv = ok.softmax(-1, dtype=torch.float) + ov = q.new_empty(NM, *v.shape, dtype=torch.float) + gk, gv = g, None + grid = (NV, NM, N * H) + fused_recurrent_fwd_kernel[grid]( + q=qv, + k=s, + v=v, + g=None, + g_gamma=None, + gk=gk, + gv=gv, + o=ov, + h0=hv0, + ht=hvt, + cu_seqlens=cu_seqlens, + scale=1., + B=B, + T=T, + H=H, + K=M, + V=V, + BK=BM, + BV=BV, + USE_G=False, + USE_G_GAMMA=False, + USE_GK=True, + USE_GV=False, + REVERSE=reverse, + ) + ov = ov.sum(0) + return ok, hkt, qv, ov, hvt + + +def fused_recurrent_gsa_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor, + qv: torch.Tensor, + hk0: torch.Tensor | None = None, + hv0: torch.Tensor | None = None, + ok: torch.Tensor | None = None, + do: torch.Tensor | None = None, + dhkt: torch.Tensor | None = None, + dhvt: torch.Tensor | None = None, + scale: float = 1., + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor]: + B, T, H, K, V, M = *q.shape, v.shape[-1], s.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + + BK, BV, BM = min(triton.next_power_of_2(K), 64), min(triton.next_power_of_2(V), 64), min(triton.next_power_of_2(M), 64) + NK, NV, NM = triton.cdiv(K, BK), triton.cdiv(V, BV), triton.cdiv(M, BM) + + dqv = q.new_empty(NV, B, T, H, M, dtype=torch.float) + dsv = q.new_empty(NV, B, T, H, M, dtype=torch.float) + dv = q.new_empty(NM, B, T, H, V, dtype=torch.float) + dgv = q.new_empty(NV, B, T, H, M, dtype=torch.float) + dhv0 = torch.empty_like(hv0)if hv0 is not None else None + + grid = (NV, NM, N * H) + fused_recurrent_bwd_kernel[grid]( + q=qv, + k=s, + v=v, + g=None, + g_gamma=None, + gk=g, + gv=None, + o=None, + h0=hv0, + do=do, + dq=dqv, + dk=dsv, + dv=dv, + dg=None, + dgk=dgv, + dgv=None, + dht=dhvt, + dh0=dhv0, + cu_seqlens=cu_seqlens, + scale=1., + B=B, + T=T, + H=H, + K=M, + V=V, + BK=BM, + BV=BV, + USE_G=False, + USE_G_GAMMA=False, + USE_GK=True, + USE_GV=False, + REVERSE=reverse, + ) + dqv = dqv.sum(0) + dsv = dsv.sum(0) + dv = dv.sum(0) + dgv = dgv.sum(0) + + dok = qv * (dqv - (qv * dqv).sum(-1, True)) + dq = q.new_empty(NM, B, T, H, K, dtype=torch.float) + dk = q.new_empty(NM, B, T, H, K, dtype=torch.float) + dsk = q.new_empty(NK, B, T, H, M, dtype=torch.float) + dgk = q.new_empty(NK, B, T, H, M, dtype=torch.float) + dhk0 = torch.empty_like(hk0)if hk0 is not None else None + + grid = (NM, NK, N * H) + fused_recurrent_bwd_kernel[grid]( + q=q, + k=k, + v=s, + g=None, + g_gamma=None, + gk=None, + gv=g, + o=ok, + h0=hk0, + do=dok, + dq=dq, + dk=dk, + dv=dsk, + dg=None, + dgk=None, + dgv=dgk, + dht=dhkt, + dh0=dhk0, + cu_seqlens=cu_seqlens, + scale=scale, + B=B, + T=T, + H=H, + K=K, + V=M, + BK=BK, + BV=BM, + USE_G=False, + USE_G_GAMMA=False, + USE_GK=False, + USE_GV=True, + REVERSE=reverse, + ) + dq = dq.sum(0) + dk = dk.sum(0) + dsk = dsk.sum(0) + dgk = dgk.sum(0) + + ds = dsk.add_(dsv) + dg = dgk.add_(dgv) + + return dq, dk, dv, ds, dg, dhk0, dhv0 + + +class FusedRecurrentGSAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor, + scale: float | None = None, + hk0: torch.Tensor | None = None, + hv0: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ) -> tuple[torch.Tensor, tuple[torch.Tensor]]: + T = q.shape[1] + if T == 1 and not q.requires_grad: + o, (hkt, hvt) = fused_recurrent_gsa_inference( + q=q, + k=k, + v=v, + s=s, + g=g, + initial_state=(hk0, hv0), + output_final_state=output_final_state, + scale=scale, + ) + return o, hkt, hvt + ok, hkt, qv, ov, hvt = fused_recurrent_gsa_fwd( + q=q, + k=k, + v=v, + s=s, + g=g, + initial_state=(hk0, hv0), + output_final_state=output_final_state, + scale=scale, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, k, v, s, g, qv, hk0, hv0, ok) + ctx.scale = scale + ctx.reverse = reverse + ctx.cu_seqlens = cu_seqlens + return ov.to(q.dtype), hkt, hvt + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dhkt=None, dhvt=None): + q, k, v, s, g, qv, hk0, hv0, ok = ctx.saved_tensors + scale = ctx.scale + reverse = ctx.reverse + cu_seqlens = ctx.cu_seqlens + + dq, dk, dv, ds, dg, dhk0, dhv0 = fused_recurrent_gsa_bwd( + q=q, + k=k, + v=v, + s=s, + g=g, + qv=qv, + hk0=hk0, + hv0=hv0, + ok=ok, + do=do, + dhkt=dhkt, + dhvt=dhvt, + scale=scale, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + return dq.to(q), dk.to(k), dv.to(v), ds.to(s), dg.to(g), None, dhk0, dhv0, None, None, None + + +def fused_recurrent_gsa( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor | None = None, + scale: int | None = None, + initial_state: tuple[torch.Tensor] | None = None, + output_final_state: bool | None = False, + reverse: bool | None = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + s (torch.Tensor): + slot representations of shape `[B, T, H, M]`. + g (torch.Tensor): + Forget gates of shape `[B, H, T, M]` applied to keys. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[Tuple[torch.Tensor]]): + Initial state tuple having tensors of shape `[N, H, K, M]` and `[N, H, M, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]` and `[N, H, M, V]`. + Default: `False`. + reverse (Optional[bool]): + If `True`, process the state passing in reverse order. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (Tuple[torch.Tensor]): + Final state tuple having tensors of shape `[N, H, K, M]` and `[N, H, M, V]`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gsa import fused_recurrent_gsa + # inputs with equal lengths + >>> B, T, H, K, V, M = 4, 2048, 4, 512, 512, 64 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> s = torch.randn(B, T, H, M, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, M, device='cuda')) + >>> h0 = (torch.randn(B, H, K, M, device='cuda'), torch.randn(B, H, M, V, device='cuda')) + >>> o, (hk, hv) = fused_recurrent_gsa( + q, k, v, s, g, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, s, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, s, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, (hk_var, hv_var) = fused_recurrent_gsa( + q, k, v, s, g, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + >>> assert o.allclose(o_var.view(o.shape)) + >>> assert hk.allclose(hk_var) + >>> assert hv.allclose(hv_var) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state[0].shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state[0].shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + if initial_state is None: + initial_state = (None, None) + o, *final_state = FusedRecurrentGSAFunction.apply( + q, + k, + v, + s, + g, + scale, + *initial_state, + output_final_state, + reverse, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/gsa/naive.py b/code/flash-linear-attention/fla/ops/gsa/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..f306aada68d15a416a04abf1cbe2785dd482a47a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/gsa/naive.py @@ -0,0 +1,67 @@ + + +import torch +from einops import repeat + + +def naive_recurrent_gsa( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + s: torch.Tensor, + g: torch.Tensor | None = None, + scale: int | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool | None = False, +) -> torch.Tensor: + dtype = q.dtype + q, k, v, s, g = map(lambda x: x.transpose(1, 2).contiguous().float(), (q, k, v, s, g)) + + NG = q.shape[1]//k.shape[1] + # [batch_size, n_heads, seq_len, n_slots] + if g is None: + z = s.float().logcumsumexp(2) + g = torch.cat((z[:, :, :1], z[:, :, :-1]), 2) - z + s = torch.exp(s - z) + k, v, s, g = map(lambda x: repeat(x, 'b h t d -> b (h g) t d', g=NG), (k, v, s, g)) + if initial_state is not None: + initial_state = tuple(map(lambda x: repeat(x, 'b h k v -> b (h g) k v', g=NG), initial_state)) + + B, H, T, K, V, M = *q.shape, v.shape[-1], s.shape[-1] + + hk = torch.zeros(B, H, K, M, dtype=torch.float, device=q.device) + ok = torch.zeros_like(s) + + if scale is None: + scale = q.shape[-1] ** -0.5 + + final_state = None + if initial_state is not None: + hk += initial_state[0] + + for i in range(T): + q_i = q[:, :, i] * scale + k_i = k[:, :, i] + v_i = s[:, :, i] + g_i = g[:, :, i].exp() + hk = hk * g_i[..., None, :] + k_i[..., None] * v_i[..., None, :] + ok[:, :, i] = (q_i[..., None] * hk).sum(-2) + + qv = ok.softmax(-1) + hv = torch.zeros(B, H, M, V, dtype=torch.float, device=q.device) + ov = torch.zeros_like(v) + if initial_state is not None: + hv += initial_state[1] + + for i in range(T): + q_i = qv[:, :, i] + k_i = s[:, :, i] + v_i = v[:, :, i] + g_i = g[:, :, i].exp() + hv = hv * g_i[..., :, None] + k_i[..., None] * v_i[..., None, :] + ov[:, :, i] = (q_i[..., None] * hv).sum(-2) + + if output_final_state: + final_state = (hk.view(B, -1, NG, K, M)[:, :, 0], hv.view(B, -1, NG, M, V)[:, :, 0]) + ov = ov.transpose(1, 2).contiguous() + return ov.to(dtype), final_state diff --git a/code/flash-linear-attention/fla/ops/hgrn/__init__.py b/code/flash-linear-attention/fla/ops/hgrn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..575f5902029609449f105d400352bec9487e1428 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/hgrn/__init__.py @@ -0,0 +1,8 @@ + +from .chunk import chunk_hgrn +from .fused_recurrent import fused_recurrent_hgrn + +__all__ = [ + 'chunk_hgrn', + 'fused_recurrent_hgrn', +] diff --git a/code/flash-linear-attention/fla/ops/hgrn/chunk.py b/code/flash-linear-attention/fla/ops/hgrn/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..356275d6702eaa72f81e56e50a36a17ab4506cea --- /dev/null +++ b/code/flash-linear-attention/fla/ops/hgrn/chunk.py @@ -0,0 +1,282 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +# this function implements the chunkwise form of HGRN, inspired by +# [Volodymyr Kyrylov in his blog post](https://proger.github.io/posts/scan/chunk.html) +# also refer to the `accelerated-scan` lib: https://github.com/proger/accelerated-scan + +# from tests on H800, with B, D = 16, 128, we see that the chunk can be greatly faster than the recurrent: +# +# Performance: +# seq_len chunk recurrent chunk_bwd recurrent_bwd +# 0 128.0 0.039360 0.061056 0.312160 0.205008 +# 1 256.0 0.045824 0.123712 0.308784 0.297696 +# 2 512.0 0.058688 0.241952 0.310720 0.626528 +# 3 1024.0 0.088288 0.476992 0.313184 1.333152 +# 4 2048.0 0.169472 0.943264 0.452464 2.724864 +# 5 4096.0 0.329920 1.886144 0.881600 5.551520 +# 6 8192.0 0.647872 3.755040 1.740496 11.117184 +# 7 16384.0 1.272064 7.520576 3.446608 22.362528 + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, input_guard + + +@triton.autotune( + configs=[ + triton.Config({'BD': 32}, num_warps=1), + triton.Config({'BD': 32}, num_warps=2), + triton.Config({'BD': 32}, num_warps=4), + triton.Config({'BD': 32}, num_warps=8), + triton.Config({'BD': 64}, num_warps=1), + triton.Config({'BD': 64}, num_warps=2), + triton.Config({'BD': 64}, num_warps=4), + triton.Config({'BD': 64}, num_warps=8), + triton.Config({'BD': 128}, num_warps=1), + triton.Config({'BD': 128}, num_warps=2), + triton.Config({'BD': 128}, num_warps=4), + triton.Config({'BD': 128}, num_warps=8), + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_hgrn_fwd_kernel_h( + x, + g, + gc, + o, + h0, + T, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, +): + i_d, i_t, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + o_d = i_d * BD + tl.arange(0, BD) + mask = o_d < D + + p_x = x + i_b * T * D + i_t * BT * D + o_d + p_g = g + i_b * T * D + i_t * BT * D + o_d + p_gc = gc + i_b * T * D + i_t * BT * D + o_d + p_o = o + i_b * T * D + i_t * BT * D + o_d + + b_h = tl.zeros([BD], dtype=tl.float32) + b_gc = tl.zeros([BD], dtype=tl.float32) + if USE_INITIAL_STATE: + if i_t == 0: + b_h += tl.load(h0 + i_b * D + o_d, mask=mask, other=0).to(tl.float32) + for i in range(0, BT): + mask_t = mask & ((i_t * BT + i) < T) + b_x = tl.load(p_x, mask=mask_t, other=0).to(tl.float32) + b_g = tl.load(p_g, mask=mask_t, other=0).to(tl.float32) + b_h = exp(b_g) * b_h + b_x + b_gc = b_gc + b_g + tl.store(p_gc, b_gc.to(p_o.dtype.element_ty), mask=mask_t) + tl.store(p_o, b_h.to(p_o.dtype.element_ty), mask=mask_t) + + p_x += D + p_g += D + p_gc += D + p_o += D + + +@triton.jit(do_not_specialize=['T']) +def chunk_hgrn_fwd_kernel_o( + gc, + o, + s_b, + s_t, + s_d, + T, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, +): + i_d, i_b = tl.program_id(0), tl.program_id(1) + o_d = i_d * BD + tl.arange(0, BD) + mask = o_d < D + + for i_t in range(1, tl.cdiv(T, BT)): + p_gc = tl.make_block_ptr(gc + i_b * s_b, (T, D), (s_t, s_d), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + p_o = tl.make_block_ptr(o + i_b * s_b, (T, D), (s_t, s_d), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + + # [BD,] + b_h0 = tl.load(o + i_b * T * D + i_t * BT * D - D + o_d, mask=mask, other=0).to(tl.float32) + # [BT, BD] + b_gc = tl.load(p_gc, boundary_check=(0, 1)).to(tl.float32) + b_o = tl.load(p_o, boundary_check=(0, 1)).to(tl.float32) + b_o = b_o + exp(b_gc) * b_h0[None, :] + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.autotune( + configs=[ + triton.Config({'BD': BD}, num_warps=num_warps) + for BD in [32, 64, 128] + for num_warps in [1, 2, 4, 8] + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_hgrn_bwd_kernel_h( + g, + gc, + dx, + do, + T, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, +): + i_d, i_t, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + o_d = i_d * BD + tl.arange(0, BD) + mask = o_d < D + BC = min(BT, T - i_t * BT) + NT = tl.num_programs(1) + + p_g = g + (i_b * T + i_t * BT + BC - 1) * D + o_d + p_gc = gc + (i_b * T + i_t * BT + BC - 1) * D + o_d + p_dx = dx + (i_b * T + i_t * BT + BC - 1) * D + o_d + p_do = do + (i_b * T + i_t * BT + BC - 1) * D + o_d + + if i_t == NT - 1: + b_gc = tl.zeros([BD], dtype=tl.float32) + else: + b_gc = tl.load(g + (i_b * T + i_t * BT + BT) * D + o_d, mask=mask, other=0).to(tl.float32) + b_dh = tl.zeros([BD], dtype=tl.float32) + for _ in range(BC - 1, -1, -1): + tl.store(p_gc, b_gc.to(p_gc.dtype.element_ty), mask=mask) + + b_g = tl.load(p_g, mask=mask, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask, other=0).to(tl.float32) + + b_gc = b_gc + b_g + b_dh = b_dh + b_do + b_dx = b_dh + b_dh = b_dh * exp(b_g) + + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), mask=mask) + + p_g -= D + p_gc -= D + p_dx -= D + p_do -= D + + +@triton.jit(do_not_specialize=['T']) +def chunk_hgrn_bwd_kernel_o( + g, + gc, + o, + dx, + dg, + s_b, + s_t, + s_d, + T, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, +): + i_d, i_b = tl.program_id(0), tl.program_id(1) + o_d = i_d * BD + tl.arange(0, BD) + mask = o_d < D + + for i_t in range(tl.cdiv(T, BT) - 1, -1, -1): + p_g = tl.make_block_ptr(g + i_b * s_b, (T, D), (s_t, s_d), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + p_gc = tl.make_block_ptr(gc + i_b * s_b, (T, D), (s_t, s_d), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + p_o = tl.make_block_ptr(o + i_b * s_b, (T, D), (s_t, s_d), (i_t * BT - 1, i_d * BD), (BT, BD), (1, 0)) + p_dx = tl.make_block_ptr(dx + i_b * s_b, (T, D), (s_t, s_d), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + p_dg = tl.make_block_ptr(dg + i_b * s_b, (T, D), (s_t, s_d), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + + # [BD,] + mask_t = mask & ((i_t + 1) * BT < T) + b_ht = tl.load(dx + i_b * T * D + (i_t + 1) * BT * D + o_d, mask=mask_t, other=0).to(tl.float32) + # [BT, BD] + b_g = tl.load(p_g, boundary_check=(0, 1)).to(tl.float32) + b_gc = tl.load(p_gc, boundary_check=(0, 1)).to(tl.float32) + b_o = tl.load(p_o, boundary_check=(0, 1)).to(tl.float32) + b_dx = tl.load(p_dx, boundary_check=(0, 1)).to(tl.float32) + + b_dx = b_dx + exp(b_gc) * b_ht[None, :] + b_dg = b_o * b_dx * exp(b_g) + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + + +class ChunkHGRNFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward(ctx, x, g, initial_state=None, output_final_state=False): + B, T, D = x.shape + BT, BD = 128, min(64, triton.next_power_of_2(D)) + num_warps = 8 if BD == 64 else 4 + + gc = torch.empty_like(g, dtype=torch.float) + o = torch.empty_like(x, dtype=torch.float) + def grid(meta): return (triton.cdiv(D, meta['BD']), triton.cdiv(T, meta['BT']), B) + chunk_hgrn_fwd_kernel_h[grid]( + x, g, gc, o, initial_state, + T=T, D=D, BT=BT, + USE_INITIAL_STATE=initial_state is not None, + ) + def grid(meta): return (triton.cdiv(D, meta['BD']), B) + chunk_hgrn_fwd_kernel_o[grid]( + gc, o, + o.stride(-3), o.stride(-2), o.stride(-1), + T=T, D=D, BT=BT, BD=BD, + num_warps=num_warps, + ) + final_state = None + if output_final_state: + final_state = o[:, -1].clone() + o = o.to(x.dtype) + ctx.save_for_backward(g, o, initial_state) + return o, final_state + + @staticmethod + @input_guard + def backward(ctx, do, dht=None): + g, o, initial_state = ctx.saved_tensors + B, T, D = do.shape + BT, BD = 128, min(64, triton.next_power_of_2(D)) + num_warps = 8 if BD == 64 else 4 + + gc = torch.empty_like(g, dtype=torch.float) + dx = torch.empty_like(o, dtype=torch.float) + def grid(meta): return (triton.cdiv(D, meta['BD']), triton.cdiv(T, meta['BT']), B) + chunk_hgrn_bwd_kernel_h[grid]( + g, gc, dx, do, + T=T, D=D, BT=BT, + ) + + dg = torch.empty_like(g, dtype=torch.float) + def grid(meta): return (triton.cdiv(D, meta['BD']), B) + chunk_hgrn_bwd_kernel_o[grid]( + g, gc, o, dx, dg, + o.stride(-3), o.stride(-2), o.stride(-1), + T=T, D=D, BT=BT, BD=BD, + num_warps=num_warps, + ) + if initial_state is not None: + dg[:, 0] = (initial_state * dx[:, 0] * g[:, 0].float().exp()).to(dg.dtype) + + return dx.to(o.dtype), dg, None, None + + +@torch.compiler.disable +def chunk_hgrn( + x: torch.Tensor, + g: torch.Tensor, + initial_state: torch.Tensor = None, + output_final_state: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + return ChunkHGRNFunction.apply(x, g, initial_state, output_final_state) diff --git a/code/flash-linear-attention/fla/ops/hgrn/fused_recurrent.py b/code/flash-linear-attention/fla/ops/hgrn/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..21680c73237b2218379095c07492bd59b034f5ae --- /dev/null +++ b/code/flash-linear-attention/fla/ops/hgrn/fused_recurrent.py @@ -0,0 +1,308 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, input_guard + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BD': BD}, num_warps=num_warps) + for BD in [32, 64, 128] + for num_warps in [1, 2, 4, 8] + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_hgrn_fwd_kernel( + x, + g, + o, + h0, + ht, + cu_seqlens, + T, + D: tl.constexpr, + BD: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_n = tl.program_id(0), tl.program_id(1) + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_d = i_d * BD + tl.arange(0, BD) + mask = o_d < D + + p_x = x + bos * D + o_d + p_g = g + bos * D + o_d + p_o = o + bos * D + o_d + + b_h = tl.zeros([BD], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_n * D + o_d + b_h += tl.load(p_h0, mask=mask, other=0).to(tl.float32) + for _ in range(0, T): + b_x = tl.load(p_x, mask=mask, other=0).to(tl.float32) + b_g = tl.load(p_g, mask=mask, other=0).to(tl.float32) + b_h = exp(b_g) * b_h + b_x + tl.store(p_o, b_h.to(p_o.dtype.element_ty), mask=mask) + + p_x += D + p_g += D + p_o += D + + if STORE_FINAL_STATE: + p_ht = ht + i_n * D + o_d + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BD': BD}, num_warps=num_warps) + for BD in [32, 64, 128] + for num_warps in [1, 2, 4, 8] + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_hgrn_bwd_kernel( + g, + o, + h0, + dx, + dg, + do, + dht, + dh0, + cu_seqlens, + T, + D: tl.constexpr, + BD: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_n = tl.program_id(0), tl.program_id(1) + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_d = i_d * BD + tl.arange(0, BD) + mask = o_d < D + + p_g = g + (bos + T - 1) * D + o_d + p_o = o + (bos + T - 2) * D + o_d + p_dx = dx + (bos + T - 1) * D + o_d + p_dg = dg + (bos + T - 1) * D + o_d + p_do = do + (bos + T - 1) * D + o_d + + b_dh = tl.zeros([BD], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_dht = dht + i_n * D + o_d + b_dh += tl.load(p_dht, mask=mask, other=0).to(tl.float32) + + for i in range(T - 1, -1, -1): + b_g = tl.load(p_g, mask=mask, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask, other=0).to(tl.float32) + if i > 0: + b_o = tl.load(p_o, mask=mask, other=0).to(tl.float32) + elif USE_INITIAL_STATE: + b_o = tl.load(h0 + i_n * D + o_d, mask=mask, other=0).to(tl.float32) + else: + b_o = tl.zeros([BD], dtype=tl.float32) + + b_dh = b_dh + b_do + b_dx = b_dh + b_dh = b_dh * exp(b_g) + b_dg = b_dh * b_o + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), mask=mask) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), mask=mask) + + p_g -= D + p_o -= D + p_dx -= D + p_dg -= D + p_do -= D + + if USE_INITIAL_STATE: + p_dh0 = dh0 + i_n * D + o_d + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), mask=mask) + + +def fused_recurrent_hgrn_fwd( + x: torch.Tensor, + g: torch.Tensor, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, D = x.shape + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + + o = torch.empty_like(x) + final_state = x.new_empty(N, D) if output_final_state else None + + def grid(meta): return (triton.cdiv(D, meta['BD']), N) + fused_recurrent_hgrn_fwd_kernel[grid]( + x=x, + g=g, + o=o, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + T=T, + D=D, + ) + return o, final_state + + +def fused_recurrent_hgrn_bwd( + g: torch.Tensor, + o: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor = None, + initial_state: torch.Tensor = None, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, D = do.shape + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + + dx = torch.empty_like(o, dtype=torch.float) + dg = torch.empty_like(g, dtype=torch.float) + dh0 = torch.empty_like(initial_state, dtype=torch.float) if initial_state is not None else None + def grid(meta): return (triton.cdiv(D, meta['BD']), N) + fused_recurrent_hgrn_bwd_kernel[grid]( + g=g, + o=o, + h0=initial_state, + dx=dx, + dg=dg, + do=do, + dht=dht, + dh0=dh0, + cu_seqlens=cu_seqlens, + T=T, + D=D, + ) + return dx, dg, dh0 + + +class FusedRecurrentHGRNFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + g: torch.Tensor, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + o, ht = fused_recurrent_hgrn_fwd( + x=x, + g=g, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(g, o, initial_state) + ctx.cu_seqlens = cu_seqlens + return o, ht + + @staticmethod + @input_guard + def backward(ctx, do, dht=None): + g, o, initial_state = ctx.saved_tensors + cu_seqlens = ctx.cu_seqlens + + dx, dg, dh0 = fused_recurrent_hgrn_bwd( + g=g, + o=o, + do=do, + dht=dht, + initial_state=initial_state, + cu_seqlens=cu_seqlens, + ) + return dx, dg, dh0, None, None + + +@torch.compiler.disable +def fused_recurrent_hgrn( + x: torch.Tensor, + g: torch.Tensor, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + x (torch.Tensor): + inputs of shape `[B, T, D]. + g (torch.Tensor): + Forget gates of shape `[B, T, D]`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, D]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, D]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, D]`. + final_state (torch.Tensor): + Final state of shape `[N, D]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.hgrn import fused_recurrent_hgrn + # inputs with equal lengths + >>> B, T, D = 4, 2048, 512 + >>> x = torch.randn(B, T, D, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, D, device='cuda')) + >>> h0 = torch.randn(B, D, device='cuda') + >>> o, ht = fused_recurrent_hgrn(x, g, initial_state=h0, output_final_state=True) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> x, g = map(lambda x: rearrange(x, 'b t d -> 1 (b t) d'), (x, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = x.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = fused_recurrent_hgrn(x, g, initial_state=h0, output_final_state=True, cu_seqlens=cu_seqlens) + >>> assert o.allclose(o_var.view(o.shape)) + >>> assert ht.allclose(ht_var) + """ + return FusedRecurrentHGRNFunction.apply( + x, + g, + initial_state, + output_final_state, + cu_seqlens, + ) diff --git a/code/flash-linear-attention/fla/ops/hgrn/naive.py b/code/flash-linear-attention/fla/ops/hgrn/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..ce2cf030edb9b9c11ed686f33f4e5e7ccbf8d409 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/hgrn/naive.py @@ -0,0 +1,61 @@ + + +import torch + + +def naive_recurrent_hgrn( + x: torch.Tensor, + g: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool | None = False, +) -> torch.Tensor: + dtype = x.dtype + x, g = map(lambda i: i.float(), (x, g)) + B, T, D = x.shape + + h = torch.zeros(B, D, dtype=torch.float, device=x.device) + o = torch.zeros_like(x) + + final_state = None + if initial_state is not None: + h += initial_state + + for i in range(T): + h = g[:, i].exp() * h + x[:, i] + o[:, i] = h + + if output_final_state: + final_state = h + return o.to(dtype), final_state + + +def naive_chunk_hgrn( + x: torch.Tensor, + g: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool | None = False, + chunk_size: int = 64, +) -> torch.Tensor: + dtype = x.dtype + x, g = map(lambda i: i.float(), (x, g)) + B, T, D = x.shape + + gc = g.view(B, chunk_size, D).cumsum(-2).view_as(g) + h = torch.zeros(B, D, dtype=torch.float, device=x.device) + o = torch.zeros_like(x) + + final_state = None + if initial_state is not None: + h += initial_state + + for i in range(0, T, chunk_size): + hp = h + h = torch.zeros(B, D, dtype=torch.float, device=x.device) + for j in range(i, i + chunk_size): + h = g[:, j].exp() * h + x[:, j] + o[:, j] = hp * gc[:, j].exp() + h + h = o[:, j].clone() + + if output_final_state: + final_state = h + return o.to(dtype), final_state diff --git a/code/flash-linear-attention/fla/ops/kda/__init__.py b/code/flash-linear-attention/fla/ops/kda/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..483321f63b4702705ac4f5981faffb06f5c86033 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/__init__.py @@ -0,0 +1,7 @@ +from .chunk import chunk_kda +from .fused_recurrent import fused_recurrent_kda + +__all__ = [ + "chunk_kda", + "fused_recurrent_kda", +] diff --git a/code/flash-linear-attention/fla/ops/kda/chunk.py b/code/flash-linear-attention/fla/ops/kda/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..a1fe6b4926bb1de575edac785c009be6519467bc --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/chunk.py @@ -0,0 +1,351 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_bwd_dhu, chunk_gated_delta_rule_fwd_h +from fla.ops.common.chunk_o import chunk_bwd_dv_local +from fla.ops.gla.chunk import chunk_gla_bwd_dA, chunk_gla_fwd_o_gk +from fla.ops.kda.chunk_inter import chunk_kda_bwd_dqkwg +from fla.ops.kda.chunk_intra import chunk_kda_bwd_intra, chunk_kda_fwd_intra +from fla.ops.kda.wy_fast import prepare_wy_repr_bwd, recompute_w_u_fwd +from fla.ops.utils import chunk_local_cumsum +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_kda_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, +): + chunk_size = 64 + g = chunk_local_cumsum(g, chunk_size=chunk_size, cu_seqlens=cu_seqlens) + # the intra Aqk is kept in fp32 + # the computation has very marginal effect on the entire throughput + Aqk, Akk = chunk_kda_fwd_intra( + q=q, + k=k, + gk=g, + beta=beta, + scale=scale, + cu_seqlens=cu_seqlens, + output_dtype=torch.float32, + ) + w, u, _, kg = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=Akk, + gk=g, + cu_seqlens=cu_seqlens, + ) + h, v_new, final_state = chunk_gated_delta_rule_fwd_h( + k=kg, + w=w, + u=u, + gk=g, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + + o = chunk_gla_fwd_o_gk( + q=q, + v=v_new, + g=g, + A=Aqk, + h=h, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return g, o, Aqk, Akk, final_state + + +def chunk_kda_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + Aqk: torch.Tensor, + Akk: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, +): + chunk_size = 64 + w, u, qg, kg = recompute_w_u_fwd( + q=q, + k=k, + v=v, + beta=beta, + A=Akk, + gk=g, + cu_seqlens=cu_seqlens, + ) + h, v_new, _ = chunk_gated_delta_rule_fwd_h( + k=kg, + w=w, + u=u, + gk=g, + initial_state=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + ) + dv = chunk_bwd_dv_local( + q=q, + k=k, + do=do, + A=Aqk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + dh, dh0, dv = chunk_gated_delta_rule_bwd_dhu( + q=qg, + k=kg, + w=w, + gk=g, + h0=initial_state, + dht=dht, + do=do, + dv=dv, + scale=scale, + cu_seqlens=cu_seqlens, + ) + + # dq dk in fp32 + dAqk = chunk_gla_bwd_dA( + v=v_new, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dq, dk, dw, dg = chunk_kda_bwd_dqkwg( + q=q, + k=k, + v=v_new, + w=w, + g=g, + h=h, + dv=dv, + do=do, + dh=dh, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dk2, dv, db, dg2, dAkk = prepare_wy_repr_bwd( + k=k, + v=v, + beta=beta, + gk=g, + A=Akk, + dw=dw, + du=dv, + cu_seqlens=cu_seqlens, + ) + dq, dk2, db, dg2 = chunk_kda_bwd_intra( + q=q, + k=k, + g=g, + beta=beta, + dAqk=dAqk, + dAkk=dAkk, + dq=dq, + dk=dk2, + db=db, + dg=dg2, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dk.add_(dk2) + dg.add_(dg2) + return dq, dk, dv, db, dg, dh0 + + +class ChunkKDAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + q_rstd, k_rstd = None, None + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q) + k, k_rstd = l2norm_fwd(k) + + g, o, Aqk, Akk, final_state = chunk_kda_fwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, q_rstd, k, k_rstd, v, g, beta, Aqk, Akk, initial_state, cu_seqlens) + ctx.scale = scale + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + q, q_rstd, k, k_rstd, v, g, beta, Aqk, Akk, initial_state, cu_seqlens = ctx.saved_tensors + dq, dk, dv, db, dg, dh0 = chunk_kda_bwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + Aqk=Aqk, + Akk=Akk, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + ) + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + return dq.to(q), dk.to(k), dv.to(v), dg.to(g), db.to(beta), None, dh0, None, None, None + + +@torch.compiler.disable +def chunk_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + **kwargs, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + (forget) gating tensor (in log space!) of shape `[B, T, H, K]`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (bool): + Whether to apply L2norm to the q,k tensor internally. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.kda import chunk_kda + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> k = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') + >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid() + >>> g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda') + >>> o, ht = chunk_kda( + q, k, v, g, beta, + use_qk_l2norm_in_kernel=True, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = chunk_kda( + q, k, v, g, beta, + use_qk_l2norm_in_kernel=True, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = ChunkKDAFunction.apply( + q, + k, + v, + g, + beta, + scale, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/kda/chunk_inter.py b/code/flash-linear-attention/fla/ops/kda/chunk_inter.py new file mode 100644 index 0000000000000000000000000000000000000000..87b07544dfaba9d2541269626657514e7a053bf8 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/chunk_inter.py @@ -0,0 +1,186 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem + +BK_LIST = [32, 64] if check_shared_mem() else [16, 32] +BV_LIST = [64, 128] if check_shared_mem('ampere') else [16, 32] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_kda_bwd_kernel_inter( + q, + k, + v, + g, + h, + do, + dh, + dq, + dk, + dv, + dw, + dg, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + g += (bos * H + i_h) * K + h += (i_tg * H + i_h) * K*V + do += (bos * H + i_h) * V + dh += (i_tg * H + i_h) * K*V + dq += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + dw += (bos * H + i_h) * K + dv += (bos * H + i_h) * V + dg += (bos * H + i_h) * K + + p_g = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + p_gn = g + (min(T, i_t * BT + BT) - 1) * H*K + o_k + b_gn = tl.load(p_gn, mask=m_k, other=0) + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dw = tl.zeros([BT, BK], dtype=tl.float32) + b_dgk = tl.zeros([BK], dtype=tl.float32) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + + # [BK] + b_dgk += tl.sum(b_h * b_dh, axis=0) + # [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) + + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_dv = tl.load(p_dv, boundary_check=(0, 1)) + b_dw += tl.dot(b_dv.to(b_v.dtype), b_h.to(b_v.dtype)) + + p_dw = tl.make_block_ptr(dw, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dw, -b_dw.to(p_dw.dtype.element_ty), boundary_check=(0, 1)) + + b_dgk *= exp(b_gn) + b_dq *= scale + b_dq = b_dq * exp(b_g) + b_dk = b_dk * exp(b_gn[None, :] - b_g) + + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dg = tl.make_block_ptr(dg, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dgk += tl.sum(b_dk * b_k, axis=0) + b_dg = b_q * b_dq - b_k * b_dk + b_dg = b_dg - tl.cumsum(b_dg, axis=0) + tl.sum(b_dg, axis=0)[None, :] + b_dgk[None, :] + + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_kda_bwd_dqkwg( + q: torch.Tensor, + k: torch.Tensor, + w: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + g: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + dv: torch.Tensor, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT = min(chunk_size, max(16, triton.next_power_of_2(T))) + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dq = torch.empty_like(q, dtype=torch.float) + dk = torch.empty_like(k, dtype=torch.float) + dw = torch.empty_like(w) + dg = torch.empty_like(g) + def grid(meta): return (triton.cdiv(K, meta['BK']), NT, B * H) + chunk_kda_bwd_kernel_inter[grid]( + q=q, + k=k, + v=v, + g=g, + h=h, + do=do, + dh=dh, + dq=dq, + dk=dk, + dv=dv, + dw=dw, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return dq, dk, dw, dg diff --git a/code/flash-linear-attention/fla/ops/kda/chunk_intra.py b/code/flash-linear-attention/fla/ops/kda/chunk_intra.py new file mode 100644 index 0000000000000000000000000000000000000000..b4e2891b731568ce0a6f754fb04d9dd1cb979660 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/chunk_intra.py @@ -0,0 +1,538 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import chunk_local_cumsum, prepare_chunk_indices, solve_tril +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=["BC"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_kda_fwd_kernel_intra_sub_inter( + q, + k, + g, + beta, + Aqk, + Akk, + scale, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_i, i_j = i_c // NC, i_c % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + if i_i <= i_j: + return + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + g += (bos * H + i_h) * K + Aqk += (bos * H + i_h) * BT + Akk += (bos * H + i_h) * BT + + p_b = tl.make_block_ptr(beta + bos * H + i_h, (T,), (H,), (i_t * BT + i_i * BC,), (BC,), (0,)) + b_b = tl.load(p_b, boundary_check=(0,)) + + b_Aqk = tl.zeros([BC, BC], dtype=tl.float32) + b_Akk = tl.zeros([BC, BC], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + b_kt = tl.make_block_ptr(k, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + p_gk = tl.make_block_ptr(g, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + # [BK,] + b_gn = tl.load(g + (i_t * BT + i_i * BC) * H*K + o_k, mask=m_k, other=0) + # [BC, BK] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) * exp(b_g - b_gn[None, :]) + # [BK, BC] + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kt = tl.load(b_kt, boundary_check=(0, 1)) + # [BC, BC] + b_ktg = b_kt * exp(b_gn[:, None] - b_gk) + b_Akk += tl.dot(b_k, b_ktg) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_qg = b_q * exp(b_g - b_gn[None, :]) * scale + b_Aqk += tl.dot(b_qg, b_ktg) + + b_Akk *= b_b[:, None] + + p_Akk = tl.make_block_ptr(Akk, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + tl.store(p_Akk, b_Akk.to(Akk.dtype.element_ty), boundary_check=(0, 1)) + p_Aqk = tl.make_block_ptr(Aqk, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + tl.store(p_Aqk, b_Aqk.to(Aqk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=["BK", "BT"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_kda_fwd_kernel_intra_sub_intra( + q, + k, + g, + beta, + Aqk, + Akk, + scale, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_i, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = tl.arange(0, BK) + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + o_i) < T + o_A = (bos + i_t * BT + i_i * BC + o_i) * H*BT + i_h * BT + i_i * BC + + p_q = tl.make_block_ptr(q + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(g + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + + p_b = beta + (bos + i_t * BT + i_i * BC + o_i) * H + i_h + b_k = b_k * tl.load(p_b, mask=m_A, other=0)[:, None] + + p_kt = k + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gk = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_kt = tl.load(p_kt, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_ktg = b_kt[None, :] * exp(b_g - b_gk[None, :]) + b_Aqk = tl.sum(b_q * b_ktg, 1) + b_Aqk = tl.where(o_i >= j, b_Aqk * scale, 0.) + b_Akk = tl.sum(b_k * b_ktg, 1) + b_Akk = tl.where(o_i > j, b_Akk, 0.) + tl.store(Aqk + o_A + j, b_Aqk, mask=m_A) + tl.store(Akk + o_A + j, b_Akk, mask=m_A) + p_kt += H*K + p_gk += H*K + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BK', 'NC', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['B', 'T']) +def chunk_kda_bwd_kernel_intra( + q, + k, + g, + beta, + dAqk, + dAkk, + dq, + dq2, + dk, + dk2, + dg, + db, + cu_seqlens, + chunk_indices, + B, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_kc, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_k, i_i = i_kc // NC, i_kc % NC + + all = B * T + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + if i_t * BT + i_i * BC >= T: + return + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + g += (bos * H + i_h) * K + beta += bos * H + i_h + + dAqk += (bos * H + i_h) * BT + dAkk += (bos * H + i_h) * BT + dq += (bos * H + i_h) * K + dq2 += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + dk2 += (bos * H + i_h) * K + dg += (bos * H + i_h) * K + db += (i_k * all + bos) * H + i_h + + p_g = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + + p_b = tl.make_block_ptr(beta, (T,), (H,), (i_t * BT + i_i * BC,), (BC,), (0,)) + b_b = tl.load(p_b, boundary_check=(0,)) + + b_dq2 = tl.zeros([BC, BK], dtype=tl.float32) + b_dk2 = tl.zeros([BC, BK], dtype=tl.float32) + if i_i > 0: + p_gn = g + (i_t * BT + i_i * BC) * H*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(0, i_i): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_gk = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_dAqk = tl.make_block_ptr(dAqk, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + p_dAkk = tl.make_block_ptr(dAkk, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kg = b_k * exp(b_gn[None, :] - b_gk) + # [BC, BC] + b_dAqk = tl.load(p_dAqk, boundary_check=(0, 1)) + b_dAkk = tl.load(p_dAkk, boundary_check=(0, 1)) + # [BC, BK] + b_dq2 += tl.dot(b_dAqk, b_kg) + b_dk2 += tl.dot(b_dAkk, b_kg) + b_dq2 *= exp(b_g - b_gn[None, :]) + b_dk2 *= exp(b_g - b_gn[None, :]) + + o_i = tl.arange(0, BC) + m_dA = (i_t * BT + i_i * BC + o_i) < T + o_dA = (i_t * BT + i_i * BC + o_i) * H*BT + i_i * BC + p_kj = k + (i_t * BT + i_i * BC) * H*K + o_k + p_gkj = g + (i_t * BT + i_i * BC) * H*K + o_k + + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC] + b_dAqk = tl.load(dAqk + o_dA + j, mask=m_dA, other=0) + b_dAkk = tl.load(dAkk + o_dA + j, mask=m_dA, other=0) + # [BK] + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] >= j + # [BC, BK] + b_dq2 += tl.where(m_i, b_dAqk[:, None] * b_kj[None, :] * exp(b_g - b_gkj[None, :]), 0.) + b_dk2 += tl.where(m_i, b_dAkk[:, None] * b_kj[None, :] * exp(b_g - b_gkj[None, :]), 0.) + + p_kj += H*K + p_gkj += H*K + b_db = tl.sum(b_dk2 * b_k, 1) + b_dk2 *= b_b[:, None] + + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_dq2 = tl.make_block_ptr(dq2, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_db = tl.make_block_ptr(db, (T,), (H,), (i_t * BT + i_i * BC,), (BC,), (0,)) + + b_dg = b_q * b_dq2 + b_dq2 = b_dq2 + tl.load(p_dq, boundary_check=(0, 1)) + tl.store(p_dq2, b_dq2.to(p_dq2.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0,)) + + tl.debug_barrier() + b_dkt = tl.zeros([BC, BK], dtype=tl.float32) + + NC = min(NC, tl.cdiv(T - i_t * BT, BC)) + if i_i < NC - 1: + p_gn = g + (min(i_t * BT + i_i * BC + BC, T) - 1) * H*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(i_i + 1, NC): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t*BT+i_j*BC, i_k*BK), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_gk = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k*BK), (BC, BK), (1, 0)) + p_b = tl.make_block_ptr(beta, (T,), (H,), (i_t * BT + i_j * BC,), (BC,), (0,)) + p_dAqk = tl.make_block_ptr(dAqk, (BT, T), (1, H*BT), (i_i * BC, i_t * BT + i_j * BC), (BC, BC), (0, 1)) + p_dAkk = tl.make_block_ptr(dAkk, (BT, T), (1, H*BT), (i_i * BC, i_t * BT + i_j * BC), (BC, BC), (0, 1)) + # [BC] + b_b = tl.load(p_b, boundary_check=(0,)) + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_kb = tl.load(p_k, boundary_check=(0, 1)) * b_b[:, None] + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + # [BC, BC] + b_dAqk = tl.load(p_dAqk, boundary_check=(0, 1)) + b_dAkk = tl.load(p_dAkk, boundary_check=(0, 1)) + + o_j = i_t * BT + i_j * BC + o_i + m_j = o_j < T + # [BC, BK] + b_qg = b_q * tl.where(m_j[:, None], exp(b_gk - b_gn[None, :]), 0) + b_kbg = b_kb * tl.where(m_j[:, None], exp(b_gk - b_gn[None, :]), 0) + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dkt += tl.dot(b_dAqk, b_qg) + b_dkt += tl.dot(b_dAkk, b_kbg) + b_dkt *= exp(b_gn[None, :] - b_g) + o_dA = (i_t * BT + i_i * BC) * H*BT + i_i * BC + o_i + p_qj = q + (i_t * BT + i_i * BC) * H*K + o_k + p_kj = k + (i_t * BT + i_i * BC) * H*K + o_k + p_gkj = g + (i_t * BT + i_i * BC) * H*K + o_k + p_bj = beta + (i_t * BT + i_i * BC) * H + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dAqk = tl.load(dAqk + o_dA + j * H*BT) + b_dAkk = tl.load(dAkk + o_dA + j * H*BT) + # [BK,] + b_qj = tl.load(p_qj, mask=m_k, other=0).to(tl.float32) + b_kbj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) * tl.load(p_bj) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] <= j + b_dkt += tl.where(m_i, b_dAqk[:, None] * b_qj[None, :] * exp(b_gkj[None, :] - b_g), 0.) + b_dkt += tl.where(m_i, b_dAkk[:, None] * b_kbj[None, :] * exp(b_gkj[None, :] - b_g), 0.) + + p_qj += H*K + p_kj += H*K + p_gkj += H*K + p_bj += H + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_dk2 = tl.make_block_ptr(dk2, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_dg = tl.make_block_ptr(dg, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + + b_dg += (b_dk2 - b_dkt) * b_k + b_dk2 += tl.load(p_dk, boundary_check=(0, 1)) + b_dk2 += b_dkt + + tl.store(p_dk2, b_dk2.to(p_dk2.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_kda_fwd_intra( + q: torch.Tensor, + k: torch.Tensor, + gk: torch.Tensor | None = None, + beta: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + output_dtype: torch.dtype = torch.float32, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + The query tensor of shape `[B, T, H, K]`. + k (torch.Tensor): + The key tensor of shape `[B, T, H, K]`. + gk (torch.Tensor): + The cumulative sum of the gate tensor of shape `[B, T, H, K]` applied to the key tensor. Default: `None`. + beta (torch.Tensor): + The beta tensor of shape `[B, T, H]`. Default: `None`. + scale (Optional[float]): + The scale factor. Default: `None`. + cu_seqlens (torch.LongTensor): + The cumulative sequence lengths of the input tensor. + Default: None + chunk_size (int): + The chunk size. Default: 64. + output_dtype (torch.dtype): + The dtype of the output tensor. Default: `torch.float32` + + Returns: + Aqk (torch.Tensor): + The intra Aqk tensor of shape `[B, T, H, BT]` where `BT` is the chunk size. + Akk (torch.Tensor): + The intra Akk tensor of shape `[B, T, H, BT]` where `BT` is the chunk size. + """ + B, T, H, K = k.shape + assert K <= 256 + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + BC = min(16, BT) + NC = triton.cdiv(BT, BC) + BK = max(triton.next_power_of_2(K), 16) + + Aqk = torch.zeros(B, T, H, BT, device=k.device, dtype=output_dtype) + Akk = torch.zeros(B, T, H, BT, device=k.device, dtype=output_dtype) + grid = (NT, NC * NC, B * H) + chunk_kda_fwd_kernel_intra_sub_inter[grid]( + q=q, + k=k, + g=gk, + beta=beta, + Aqk=Aqk, + Akk=Akk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + NC=NC, + ) + + grid = (NT, NC, B * H) + chunk_kda_fwd_kernel_intra_sub_intra[grid]( + q=q, + k=k, + g=gk, + beta=beta, + Aqk=Aqk, + Akk=Akk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + ) + Akk = solve_tril( + A=Akk, + cu_seqlens=cu_seqlens, + output_dtype=k.dtype, + ) + return Aqk, Akk + + +def chunk_kda_bwd_intra( + q: torch.Tensor, + k: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + dAqk: torch.Tensor, + dAkk: torch.Tensor, + dq: torch.Tensor, + dk: torch.Tensor, + db: torch.Tensor, + dg: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K = k.shape + BT = chunk_size + BC = min(16, BT) + BK = min(64, triton.next_power_of_2(K)) + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + NK = triton.cdiv(K, BK) + + dq2 = torch.empty_like(q) + dk2 = torch.empty_like(k) + db2 = beta.new_empty(NK, *beta.shape, dtype=torch.float) + dg2 = torch.empty_like(dg, dtype=torch.float) + grid = (NK * NC, NT, B * H) + chunk_kda_bwd_kernel_intra[grid]( + q=q, + k=k, + g=g, + beta=beta, + dAqk=dAqk, + dAkk=dAkk, + dq=dq, + dq2=dq2, + dk=dk, + dk2=dk2, + dg=dg2, + db=db2, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + dq = dq2 + dk = dk2 + db = db2.sum(0).add_(db) + dg = chunk_local_cumsum(dg2.add_(dg), chunk_size=chunk_size, reverse=True, cu_seqlens=cu_seqlens) + + return dq, dk2, db, dg diff --git a/code/flash-linear-attention/fla/ops/kda/fused_recurrent.py b/code/flash-linear-attention/fla/ops/kda/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..9ce78415ef19ab77e6633445e49a02691e3bceec --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/fused_recurrent.py @@ -0,0 +1,112 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.gated_delta_rule.fused_recurrent import fused_recurrent_gated_delta_rule + + +def fused_recurrent_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, + **kwargs, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, HV, V]`. + GVA is applied if `HV > H`. + g (torch.Tensor): + g (decays) of shape `[B, T, HV]`. + beta (torch.Tensor): + betas of shape `[B, T, HV]`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, HV, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, HV, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (Optional[bool]): + Whether to use L2 normalization in the kernel. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HV, V]`. + final_state (torch.Tensor): + Final state of shape `[N, HV, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.kda import fused_recurrent_kda + # inputs with equal lengths + >>> B, T, H, HV, K, V = 4, 2048, 4, 8, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, HV, V, device='cuda') + >>> g = F.logsigmoid(torch.rand(B, T, HV, K, device='cuda')) + >>> beta = torch.rand(B, T, HV, device='cuda').sigmoid() + >>> h0 = torch.randn(B, HV, K, V, device='cuda') + >>> o, ht = fused_recurrent_kda( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, g, beta)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = fused_recurrent_kda( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + + o, final_state = fused_recurrent_gated_delta_rule( + q=q, + k=k, + v=v, + gk=g, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + cu_seqlens=cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/kda/gate.py b/code/flash-linear-attention/fla/ops/kda/gate.py new file mode 100644 index 0000000000000000000000000000000000000000..ef9ddf29f6ecebd71e6da4123f0ce68ad5a2cf6e --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/gate.py @@ -0,0 +1,348 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl +from einops import rearrange + +from fla.ops.utils.op import log +from fla.utils import autotune_cache_kwargs, input_guard, is_amd + +BT_LIST_AUTOTUNE = [32, 64, 128] +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [4, 8, 16, 32] + + +def kda_gate_ref( + g: torch.Tensor, + A: torch.Tensor, + head_k_dim: int, + g_bias: torch.Tensor | None = None, + beta=1.0, threshold=20.0, +) -> torch.Tensor: + """ + Torch reference implementation for KDA gate computation. + + Computes: g = -A.exp().unsqueeze(-1) * softplus(rearrange(g, '... (h d) -> ... h d', d=head_k_dim)) + + Supports both formats: + - Standard: [batch_size, seq_len, num_heads * head_k_dim] + - vLLM: [num_tokens, num_heads * head_k_dim] + + Args: + g: Input tensor of shape [..., num_heads * head_k_dim] + A: Parameter tensor of shape [num_heads] or [1, 1, num_heads, 1] + g_bias : Optional bias tensor added to g before activation, shape [num_heads * head_k_dim] + head_k_dim: Dimension of each head + + Returns: + Output tensor of shape [..., num_heads, head_k_dim] + """ + # Rearrange g to separate heads: [..., H*D] -> [..., H, D] + A = A.view(-1) # Flatten A to [num_heads] to handle any input shape + if g_bias is not None: + g = g + g_bias + g = rearrange(g, '... (h d) -> ... h d', d=head_k_dim) + + # Apply the gate computation: -A.exp().unsqueeze(-1) * softplus(g) + # A: [H] -> [H, 1] for broadcasting + A_exp = -A.float().exp().unsqueeze(-1) # [H, 1] + g_softplus = F.softplus(g.float(), beta, threshold) # [..., H, D] + + return A_exp * g_softplus + + +@triton.autotune( + configs=[ + triton.Config({'BT': bt}, num_warps=nw, num_stages=ns) + for bt in BT_LIST_AUTOTUNE + for nw in NUM_WARPS_AUTOTUNE + for ns in [2, 3] + ], + key=['H', 'D'], + **autotune_cache_kwargs, +) +@triton.jit +def kda_gate_fwd_kernel( + g, A, y, + g_bias, + beta: tl.constexpr, + threshold: tl.constexpr, + T, + H, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + HAS_BIAS: tl.constexpr, +): + i_t, i_h = tl.program_id(0), tl.program_id(1) + n_t = i_t * BT + + b_a = tl.load(A + i_h).to(tl.float32) + b_a = -tl.exp(b_a) + + stride_row = H * D + stride_col = 1 + + g_ptr = tl.make_block_ptr( + base=g + i_h * D, + shape=(T, D), + strides=(stride_row, stride_col), + offsets=(n_t, 0), + block_shape=(BT, BD), + order=(1, 0), + ) + + y_ptr = tl.make_block_ptr( + base=y + i_h * D, + shape=(T, D), + strides=(stride_row, stride_col), + offsets=(n_t, 0), + block_shape=(BT, BD), + order=(1, 0), + ) + + b_g = tl.load(g_ptr, boundary_check=(0, 1)).to(tl.float32) + + if HAS_BIAS: + n_d = tl.arange(0, BD) + bias_mask = n_d < D + b_bias = tl.load(g_bias + i_h * D + n_d, mask=bias_mask, other=0.0).to(tl.float32) + b_g = b_g + b_bias[None, :] + + # softplus(x, beta) = (1/beta) * log(1 + exp(beta * x)) + # When beta * x > threshold, use linear approximation x + # Use threshold to switch to linear when beta*x > threshold + g_scaled = b_g * beta + use_linear = g_scaled > threshold + sp = tl.where(use_linear, b_g, (1.0 / beta) * log(1.0 + tl.exp(g_scaled))) + b_y = b_a * sp + + tl.store(y_ptr, b_y.to(y.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=nw, num_stages=ns) + for nw in NUM_WARPS_AUTOTUNE + for ns in [2, 3] + ], + key=['H', 'D'], + **autotune_cache_kwargs, +) +@triton.jit +def kda_gate_bwd_kernel( + g, + A, + dy, + dg, + dA, + g_bias, + beta: tl.constexpr, + threshold: tl.constexpr, + T, + H: tl.constexpr, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + HAS_BIAS: tl.constexpr, +): + i_t, i_h = tl.program_id(0), tl.program_id(1) + n_t = i_t * BT + + a_h = tl.load(A + i_h).to(tl.float32) + neg_exp_a = -tl.exp(a_h) + + stride_row = H * D + stride_col = 1 + + g_ptr = tl.make_block_ptr( + base=g + i_h * D, + shape=(T, D), + strides=(stride_row, stride_col), + offsets=(n_t, 0), + block_shape=(BT, BD), + order=(1, 0), + ) + dy_ptr = tl.make_block_ptr( + base=dy + i_h * D, + shape=(T, D), + strides=(stride_row, stride_col), + offsets=(n_t, 0), + block_shape=(BT, BD), + order=(1, 0), + ) + dg_ptr = tl.make_block_ptr( + base=dg + i_h * D, + shape=(T, D), + strides=(stride_row, stride_col), + offsets=(n_t, 0), + block_shape=(BT, BD), + order=(1, 0), + ) + + b_g = tl.load(g_ptr, boundary_check=(0, 1)).to(tl.float32) # [BT, BD] + b_dy = tl.load(dy_ptr, boundary_check=(0, 1)).to(tl.float32) # [BT, BD] + + if HAS_BIAS: + n_d = tl.arange(0, BD) + bias_mask = n_d < D + b_bias = tl.load(g_bias + i_h * D + n_d, mask=bias_mask, other=0.0).to(tl.float32) + b_g = b_g + b_bias[None, :] + + # softplus(g + bias) + g_scaled = b_g * beta + use_linear = g_scaled > threshold + sp = tl.where(use_linear, b_g, (1.0 / beta) * log(1.0 + tl.exp(g_scaled))) + + sig = tl.sigmoid(g_scaled) + + # grad_g = dy * (-exp(A)) * sigmoid(beta*g) + b_dg = b_dy * (neg_exp_a * sig) + tl.store(dg_ptr, b_dg.to(dg_ptr.dtype.element_ty), boundary_check=(0, 1)) + + contrib = b_dy * (neg_exp_a * sp) + tile_sum = tl.sum(tl.sum(contrib, axis=1), axis=0) + + out_off = i_t * H + i_h + tl.store(dA + out_off, tile_sum) + + +def kda_gate_fwd( + g: torch.Tensor, + A: torch.Tensor, + head_k_dim: int, + g_bias: torch.Tensor | None = None, + beta: float = 1.0, + threshold: float = 20.0, +) -> torch.Tensor: + """ + Forward pass for KDA gate: + input g: [..., H*D] + param A: [H] or [1, 1, H, 1] + beta: softplus beta parameter + threshold: softplus threshold parameter + return : [..., H, D] + """ + orig_shape = g.shape[:-1] + + g = g.view(-1, g.shape[-1]) + T = g.shape[0] + HD = g.shape[1] + H = A.numel() + assert H * head_k_dim == HD + + y = torch.empty_like(g, dtype=torch.float32) + + def grid(meta): return (triton.cdiv(T, meta['BT']), H) + + kda_gate_fwd_kernel[grid]( + g, A, y, g_bias, + beta, threshold, + T, H, head_k_dim, + BD=triton.next_power_of_2(head_k_dim), + HAS_BIAS=g_bias is not None, + ) + + y = y.view(*orig_shape, H, head_k_dim) + return y + + +def kda_gate_bwd( + grad_output: torch.Tensor, # [..., H, D] + g: torch.Tensor, # [..., H*D] + A: torch.Tensor, # [H] + head_k_dim: int, + g_bias: torch.Tensor | None = None, + beta: float = 1.0, + threshold: float = 20.0, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]: + + g_flat = g.view(-1, g.shape[-1]) + T = g_flat.shape[0] + A_ori_shape = A.shape + + H = A.numel() + D = head_k_dim + + dy = grad_output.view(T, H * D) + dg = torch.empty_like(g_flat, dtype=torch.float32) + + BT = 32 + NT = triton.cdiv(T, BT) + dA = torch.empty((NT, H), dtype=torch.float32, device=g.device) + + grid = (triton.cdiv(T, BT), H) + kda_gate_bwd_kernel[grid]( + g_flat, A, dy, dg, dA, g_bias, + beta, threshold, + T, H, D, + BT=BT, + BD=triton.next_power_of_2(D), + HAS_BIAS=g_bias is not None, + ) + + dA = dA.sum(0).view(A_ori_shape).type_as(A) + dgbias = dg.sum(0).type_as(g_bias) if g_bias is not None else None + dg = dg.view(g.shape).type_as(g) + return dg, dA, dgbias + + +class KDAGateFunction(torch.autograd.Function): + """ + Autograd function for KDA gate computation. + + Supports both formats: + - Standard: [batch_size, seq_len, num_heads * head_k_dim] + - vLLM: [num_tokens, num_heads * head_k_dim] + """ + + @input_guard + @staticmethod + def forward(ctx, g: torch.Tensor, A: torch.Tensor, head_k_dim: int, + g_bias: torch.Tensor | None = None, + beta: float = 1.0, + threshold: float = 20.0) -> torch.Tensor: + ctx.save_for_backward(g, A) + ctx.g_bias = g_bias + ctx.head_k_dim = head_k_dim + ctx.beta = beta + ctx.threshold = threshold + + return kda_gate_fwd(g, A, head_k_dim, g_bias, beta, threshold) + + @input_guard + @staticmethod + def backward(ctx, grad_output: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, None, None, None]: + g, A = ctx.saved_tensors + head_k_dim = ctx.head_k_dim + beta = ctx.beta + threshold = ctx.threshold + g_bias = ctx.g_bias + + grad_g, grad_A, grad_gbias = kda_gate_bwd(grad_output, g, A, head_k_dim, g_bias, beta, threshold) + return grad_g, grad_A, None, grad_gbias, None, None + + +def fused_kda_gate(g: torch.Tensor, A: torch.Tensor, head_k_dim: int, + g_bias: torch.Tensor | None = None, + beta: float = 1.0, threshold: float = 20.0) -> torch.Tensor: + """ + Fused KDA gate computation with autograd support. + + Supports both formats: + - Standard: [batch_size, seq_len, num_heads * head_k_dim] + - vLLM: [num_tokens, num_heads * head_k_dim] + + Args: + g: Input tensor of shape [..., num_heads * head_k_dim] + A: Parameter tensor of shape [num_heads] or [1, 1, num_heads, 1] + head_k_dim: Dimension of each head + beta: softplus beta parameter + threshold: softplus threshold parameter + + Returns: + Output tensor of shape [..., num_heads, head_k_dim] + """ + return KDAGateFunction.apply(g, A, head_k_dim, g_bias, beta, threshold) diff --git a/code/flash-linear-attention/fla/ops/kda/naive.py b/code/flash-linear-attention/fla/ops/kda/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..d7bcf45b47bffe13500c80e9a7ea7744e947135a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/naive.py @@ -0,0 +1,100 @@ + + +import torch +from einops import rearrange + + +def naive_recurrent_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, +): + dtype = v.dtype + B, T, H, K, V = *q.shape, v.shape[-1] + if scale is None: + scale = K ** -0.5 + + q, k, v, g, beta = map(lambda x: x.to(torch.float), [q, k, v, g, beta]) + q = q * scale + + S = k.new_zeros(B, H, K, V).to(q) + if initial_state is not None: + S += initial_state + o = torch.zeros_like(v) + for i in range(0, T): + q_i, k_i, v_i, g_i, b_i = q[:, i], k[:, i], v[:, i], g[:, i], beta[:, i] + S = S * g_i[..., None].exp() + S = S + torch.einsum('b h k, b h v -> b h k v', b_i[..., None] * k_i, v_i - (k_i[..., None] * S).sum(-2)) + o[:, i] = torch.einsum('b h k, b h k v -> b h v', q_i, S) + if not output_final_state: + S = None + return o.to(dtype), S + + +def naive_chunk_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + chunk_size: int = 64, +): + dtype = v.dtype + B, T, H, K, V = *q.shape, v.shape[-1] + BT = chunk_size + NT = T // BT + if scale is None: + scale = K ** -0.5 + assert T % BT == 0 + + q, k, v, g, beta = map(lambda x: rearrange(x, 'b (n c) h ... -> b h n c ...', c=BT).to(torch.float), [q, k, v, g, beta]) + q = q * scale + g = g.cumsum(-2) + + # note that diagonal is masked. + mask = torch.triu(torch.ones(BT, BT, dtype=torch.bool, device=q.device), diagonal=0) + + A = torch.zeros(*q.shape[:-1], BT, dtype=torch.float, device=q.device) + for i in range(BT): + k_i = k[..., i, :] + g_i = g[..., i:i+1, :] + A[..., i] = torch.einsum('... c d, ... d -> ... c', k * (g - g_i).exp(), k_i) + A = A * beta[..., None] + + A = -A.masked_fill(mask, 0) + for i in range(1, BT): + A[..., i, :i] = A[..., i, :i].clone() + (A[..., i, :, None].clone() * A[..., :, :i].clone()).sum(-2) + A = (A + torch.eye(BT, dtype=torch.float, device=q.device)) * beta[..., None, :] + + w = A @ (g.exp() * k) + u = A @ v + + S = k.new_zeros(B, H, K, V).to(q) + if initial_state is not None: + S += initial_state + o = torch.zeros_like(v) + mask = torch.triu(torch.ones(BT, BT, dtype=torch.bool, device=q.device), diagonal=1) + for i in range(0, NT): + # [B, H, BT, ...] + q_i, k_i, u_i, g_i, w_i = q[:, :, i], k[:, :, i], u[:, :, i], g[:, :, i], w[:, :, i] + A = torch.zeros(B, H, BT, BT, dtype=torch.float, device=q.device) + for j in range(BT): + k_j = k[:, :, i, j] + g_j = g[:, :, i, j:j+1, :] + A[..., j] = torch.einsum('... c d, ... d -> ... c', q_i * (g_i - g_j).exp(), k_j) + A = A.masked_fill(mask, 0) + v_i = u_i - w_i @ S + o[:, :, i] = (q_i * g_i.exp()) @ S + A @ v_i + S = S * rearrange(g_i[:, :, -1].exp(), 'b h k -> b h k 1') + S += rearrange((g_i[:, :, -1:] - g_i).exp() * k_i, 'b h c k -> b h k c') @ v_i + if not output_final_state: + S = None + return rearrange(o, 'b h n c d -> b (n c) h d').to(dtype), S diff --git a/code/flash-linear-attention/fla/ops/kda/wy_fast.py b/code/flash-linear-attention/fla/ops/kda/wy_fast.py new file mode 100644 index 0000000000000000000000000000000000000000..c3c28c16f76de8719bd4d26b01cb4e03f82a817d --- /dev/null +++ b/code/flash-linear-attention/fla/ops/kda/wy_fast.py @@ -0,0 +1,302 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_tf32_supported + + +@triton.heuristics({ + 'STORE_QG': lambda args: args['qg'] is not None, + 'STORE_KG': lambda args: args['kg'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'DOT_PRECISION': DOT_PRECISION}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + for DOT_PRECISION in (["tf32x3", "ieee"] if is_tf32_supported else ["ieee"]) + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def recompute_w_u_fwd_kernel( + q, + k, + qg, + kg, + v, + beta, + w, + u, + A, + gk, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + STORE_QG: tl.constexpr, + STORE_KG: tl.constexpr, + IS_VARLEN: tl.constexpr, + DOT_PRECISION: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + p_b = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_b = tl.load(p_b, boundary_check=(0,)) + + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_A = tl.load(p_A, boundary_check=(0, 1)) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_u = tl.make_block_ptr(u + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_vb = (b_v * b_b[:, None]).to(b_v.dtype) + b_u = tl.dot(b_A, b_vb, input_precision=DOT_PRECISION) + tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1)) + + for i_k in range(tl.cdiv(K, BK)): + p_w = tl.make_block_ptr(w + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_kb = b_k * b_b[:, None] + + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kb *= exp(b_gk) + if STORE_QG: + p_q = tl.make_block_ptr(q + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_qg = tl.make_block_ptr(qg + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_qg = b_q * exp(b_gk) + tl.store(p_qg, b_qg.to(p_qg.dtype.element_ty), boundary_check=(0, 1)) + if STORE_KG: + last_idx = min(i_t * BT + BT, T) - 1 + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + b_gn = tl.load(gk + ((bos + last_idx) * H + i_h) * K + o_k, mask=m_k, other=0.) + b_kg = b_k * exp(b_gn - b_gk) + + p_kg = tl.make_block_ptr(kg + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_kg, b_kg.to(p_kg.dtype.element_ty), boundary_check=(0, 1)) + + b_w = tl.dot(b_A, b_kb.to(b_k.dtype)) + tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_bwd_kernel( + k, + v, + beta, + gk, + A, + dA, + dw, + du, + dk, + dv, + db, + dg, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_b = tl.make_block_ptr(beta + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_db = tl.make_block_ptr(db + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) + + b_b = tl.load(p_b, boundary_check=(0,)) + b_db = tl.zeros([BT], dtype=tl.float32) + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dw = tl.make_block_ptr(dw + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + # [BT, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + + p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_gk_exp = exp(tl.load(p_gk, boundary_check=(0, 1))) + b_kbg = b_k * b_b[:, None] * b_gk_exp + b_dw = tl.load(p_dw, boundary_check=(0, 1)) + + b_dA += tl.dot(b_dw, tl.trans(b_kbg).to(b_dw.dtype)) + b_dkbg = tl.dot(b_A, b_dw) + b_dk = b_dkbg * b_gk_exp * b_b[:, None] + b_db += tl.sum(b_dkbg * b_k * b_gk_exp, 1) + b_dg = b_kbg * b_dkbg + + p_dg = tl.make_block_ptr(dg + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_du = tl.make_block_ptr(du + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_vb = (b_v * b_b[:, None]).to(b_v.dtype) + b_du = tl.load(p_du, boundary_check=(0, 1)) + b_dA += tl.dot(b_du, tl.trans(b_vb)) + b_dvb = tl.dot(b_A, b_du) + b_dv = b_dvb * b_b[:, None] + b_db += tl.sum(b_dvb * b_v, 1) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_dA = tl.where(m_A, b_dA, 0) + b_dA = tl.dot(b_dA.to(b_A.dtype), b_A) + b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) + + b_dA = tl.where(m_A, -b_dA, 0) + + # if using gk, save dA first and handle dk in another kernel + p_dA = tl.make_block_ptr(dA + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0,)) + + +def recompute_w_u_fwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + q: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = A.shape[-1] + BK = 64 + BV = 64 + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + w = torch.empty_like(k) + u = torch.empty_like(v) + qg = torch.empty_like(q) if q is not None else None + kg = torch.empty_like(k) if gk is not None else None + recompute_w_u_fwd_kernel[(NT, B*H)]( + q=q, + k=k, + qg=qg, + kg=kg, + v=v, + beta=beta, + w=w, + u=u, + A=A, + gk=gk, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return w, u, qg, kg + + +def prepare_wy_repr_bwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + gk: torch.Tensor, + A: torch.Tensor, + dw: torch.Tensor, + du: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = 64 + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + + dk = torch.empty_like(k, dtype=torch.float) + dv = torch.empty_like(v) + dg = torch.empty_like(gk, dtype=torch.float) + dA = torch.empty_like(A, dtype=torch.float) + db = torch.empty_like(beta, dtype=torch.float) + prepare_wy_repr_bwd_kernel[(NT, B * H)]( + k=k, + v=v, + beta=beta, + gk=gk, + A=A, + dA=dA, + dw=dw, + du=du, + dk=dk, + dv=dv, + db=db, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dk, dv, db, dg, dA diff --git a/code/flash-linear-attention/fla/ops/lightning_attn/__init__.py b/code/flash-linear-attention/fla/ops/lightning_attn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f2684e74721530e6cd4a72714e657d2b3e5188a2 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/lightning_attn/__init__.py @@ -0,0 +1,8 @@ + +from .chunk import chunk_lightning_attn +from .fused_recurrent import fused_recurrent_lightning_attn + +__all__ = [ + 'chunk_lightning_attn', + 'fused_recurrent_lightning_attn', +] diff --git a/code/flash-linear-attention/fla/ops/lightning_attn/chunk.py b/code/flash-linear-attention/fla/ops/lightning_attn/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..674d53b6dab600e0f8e5e634dcea904179e5ccf5 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/lightning_attn/chunk.py @@ -0,0 +1,82 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.simple_gla.chunk import chunk_simple_gla + + +@torch.compiler.disable +def chunk_lightning_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + layer_idx: int, + num_layers: int, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + layer_idx (int): + The index of the current layer. + num_layers (int): + The total number of layers. Both `layer_idx` and `num_layers` are used to compute the decay factor. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + + H = q.shape[2] + g_gamma = -(8 / H * (1 - layer_idx / num_layers)) * q.new_tensor(range(H), dtype=torch.float) + return chunk_simple_gla( + q=q, + k=k, + v=v, + scale=scale, + g_gamma=g_gamma, + initial_state=initial_state, + output_final_state=output_final_state, + head_first=head_first, + cu_seqlens=cu_seqlens, + ) diff --git a/code/flash-linear-attention/fla/ops/lightning_attn/fused_recurrent.py b/code/flash-linear-attention/fla/ops/lightning_attn/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..05182e9073de3a1f5a63d2bb15e39872fe9b2cf1 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/lightning_attn/fused_recurrent.py @@ -0,0 +1,82 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.simple_gla.fused_recurrent import fused_recurrent_simple_gla + + +def fused_recurrent_lightning_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + layer_idx: int, + num_layers: int, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + layer_idx (int): + The index of the current layer. + num_layers (int): + The total number of layers. Both `layer_idx` and `num_layers` are used to compute the decay factor. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + H = q.shape[2] + g_gamma = -(8 / H * (1 - layer_idx / num_layers)) * q.new_tensor(range(H), dtype=torch.float) + return fused_recurrent_simple_gla( + q=q, + k=k, + v=v, + g_gamma=g_gamma, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + head_first=head_first, + ) diff --git a/code/flash-linear-attention/fla/ops/linear_attn/__init__.py b/code/flash-linear-attention/fla/ops/linear_attn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..56d329a7c987a17913cf83bd16560265d4f34dc2 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/linear_attn/__init__.py @@ -0,0 +1,10 @@ + +from .chunk import chunk_linear_attn +from .fused_chunk import fused_chunk_linear_attn +from .fused_recurrent import fused_recurrent_linear_attn + +__all__ = [ + 'chunk_linear_attn', + 'fused_chunk_linear_attn', + 'fused_recurrent_linear_attn', +] diff --git a/code/flash-linear-attention/fla/ops/linear_attn/chunk.py b/code/flash-linear-attention/fla/ops/linear_attn/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..735e2241d3496feb4f0839064838c82b4d8890de --- /dev/null +++ b/code/flash-linear-attention/fla/ops/linear_attn/chunk.py @@ -0,0 +1,74 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.linear_attn.utils import normalize_output +from fla.ops.simple_gla import chunk_simple_gla + + +@torch.compiler.disable +def chunk_linear_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + normalize: bool = True, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + scale (Optional[float]): + Scale factor for the linear attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[B, H, K, V]`. Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[B, H, K, V]`. Default: `False`. + normalize (bool): + Whether to normalize the output. Default: `True`. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[B, H, K, V]` if `output_final_state=True` else `None`. + """ + + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first: + if q.shape[1] < q.shape[2]: + raise DeprecationWarning( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = chunk_simple_gla( + q=q, + k=k, + v=v, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + ) + if normalize: + o = normalize_output(q * scale, k, o) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/linear_attn/fused_chunk.py b/code/flash-linear-attention/fla/ops/linear_attn/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..6806f2003b5080c377194a9c258e1d54e1326b72 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/linear_attn/fused_chunk.py @@ -0,0 +1,59 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.linear_attn.utils import normalize_output +from fla.ops.simple_gla import fused_chunk_simple_gla + + +@torch.compiler.disable +def fused_chunk_linear_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + normalize: bool = True, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + scale (Optional[float]): + Scale factor for linear attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[B, H, K, V]`. Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[B, H, K, V]`. Default: `False`. + normalize (bool): + Whether to normalize the output. Default: `True`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[B, H, K, V]` if `output_final_state=True` else `None` + """ + o, final_state = fused_chunk_simple_gla( + q=q, + k=k, + v=v, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + if normalize: + o = normalize_output(q * scale, k, o) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/linear_attn/fused_recurrent.py b/code/flash-linear-attention/fla/ops/linear_attn/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..f745affa594fbe65fb4419012ab71df3450fab33 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/linear_attn/fused_recurrent.py @@ -0,0 +1,33 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.linear_attn.utils import normalize_output +from fla.ops.simple_gla.fused_recurrent import fused_recurrent_simple_gla + + +def fused_recurrent_linear_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + normalize: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + o, final_state = fused_recurrent_simple_gla( + q=q, + k=k, + v=v, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + if normalize: + o = normalize_output(q * scale, k, o) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/linear_attn/naive.py b/code/flash-linear-attention/fla/ops/linear_attn/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..2cf6ed46d6f20059ce9f8bf9e76804065b3059ab --- /dev/null +++ b/code/flash-linear-attention/fla/ops/linear_attn/naive.py @@ -0,0 +1,62 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +from einops import rearrange + +from fla.ops.linear_attn.utils import normalize_output + + +def naive_recurrent_linear_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + scale: float | None = None, + normalize: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + dtype = q.dtype + if scale is None: + scale = q.shape[-1] ** -0.5 + B, T, H, K, V = *q.shape, v.shape[-1] + q, k, v = map(lambda x: x.to(torch.float32), (q, k, v)) + o = torch.empty_like(v) + + S = torch.zeros((B, H, K, V), device=q.device, dtype=torch.float32) + if initial_state is not None: + S = S + initial_state + for t in range(T): + S = S + torch.einsum('b h k, b h v -> b h k v', k[:, t], v[:, t]) + o[:, t] = torch.einsum('b h k v, b h k -> b h v', S, q[:, t] * scale) + if normalize: + o = normalize_output(q * scale, k, o) + return o.to(dtype), S if output_final_state else None + + +def naive_chunk_linear_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + normalize: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + if scale is None: + scale = q.shape[-1] ** -0.5 + chunk_size = 64 + q = rearrange(q, 'b (n c) h d -> b h n c d', c=chunk_size) * scale + k = rearrange(k, 'b (n c) h d -> b h n c d', c=chunk_size) + v = rearrange(v, 'b (n c) h d -> b h n c d', c=chunk_size) + kv = k.transpose(-1, -2) @ v + kv = kv.cumsum(2) + kv = torch.cat([torch.zeros_like(kv[:, :, :1]), kv[:, :, :-1]], dim=2) + inter = q @ kv + intra = (( + q @ k.transpose(-1, -2)).masked_fill_( + torch.triu(torch.ones(chunk_size, chunk_size, dtype=bool, device=q.device), diagonal=1), + 0, + )) @ v + o = inter + intra + if normalize: + o = normalize_output(q * scale, k, o) + return rearrange(o, 'b h n c d -> b (n c) h d') diff --git a/code/flash-linear-attention/fla/ops/linear_attn/utils.py b/code/flash-linear-attention/fla/ops/linear_attn/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..cd8ca9c46e4362c3976f0de744d2ebbfa14fa4e1 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/linear_attn/utils.py @@ -0,0 +1,9 @@ + +import torch + + +@torch.jit.script +def normalize_output(q: torch.Tensor, k: torch.Tensor, o: torch.Tensor) -> torch.Tensor: + k = k.cumsum(1) + z = (q * k).sum(-1, keepdim=True) + return o / (z + 1e-10) diff --git a/code/flash-linear-attention/fla/ops/log_linear_attn/__init__.py b/code/flash-linear-attention/fla/ops/log_linear_attn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a4fb56280686ea42fee81767cd450fd21947a25b --- /dev/null +++ b/code/flash-linear-attention/fla/ops/log_linear_attn/__init__.py @@ -0,0 +1,6 @@ + +from .chunk import chunk_log_linear_attn + +__all__ = [ + 'chunk_log_linear_attn', +] diff --git a/code/flash-linear-attention/fla/ops/log_linear_attn/chunk.py b/code/flash-linear-attention/fla/ops/log_linear_attn/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..4e19a85d9e15c6eef56888e038b76051781da3ab --- /dev/null +++ b/code/flash-linear-attention/fla/ops/log_linear_attn/chunk.py @@ -0,0 +1,1909 @@ +import math +import warnings +from dataclasses import dataclass + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl +from einops import reduce + +from fla.ops.utils import chunk_local_cumsum +from fla.ops.utils.op import safe_exp +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard + +BLOCK_K = 64 + + +@triton.heuristics( + { + "IS_VARLEN": lambda args: args["cu_seqlens"] is not None, + "USE_INITIAL_STATE": lambda args: args["h0"] is not None, + "STORE_FINAL_STATE": lambda args: args["ht"] is not None, + }, +) +@triton.autotune( + configs=[ + triton.Config({"BK": BLOCK_K}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [2, 3, 4] + ], + key=["H", "K", "V"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunkwise_fwd_kernel( + q, + k, + v, + g, + level_scales, + llut, + o, + h0, + ht, + offsets, + new_offsets, + cu_seqlens, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + L_IN: tl.constexpr, + L_OUT: tl.constexpr, + MIN_LEVEL: tl.constexpr, + MAX_LEVEL: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, +): + p_llut = tl.make_block_ptr(llut, (BT, BT), (BT, 1), (0, 0), (BT, BT), (1, 0)) + b_llut = tl.load(p_llut, boundary_check=(0, 1)) + # parallel over sequences and heads + i_k = tl.program_id(0) + i_nh = tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_i = tl.arange(0, BT) + + # For hierarchical masking + num_intra_levels = (tl.log2(float(BT))).to(tl.int32) + 1 + i_idx = o_i[:, None] # BT x 1 + j_idx = o_i[None, :] # 1 x BT + + # This is not great. + # See issue: https://github.com/triton-lang/triton/discussions/1313 + KV_0_CREATED = MIN_LEVEL <= 1 and MAX_LEVEL >= 0 + KV_1_CREATED = MIN_LEVEL <= 2 and MAX_LEVEL >= 0 + KV_2_CREATED = MIN_LEVEL <= 3 and MAX_LEVEL >= 1 + KV_3_CREATED = MIN_LEVEL <= 4 and MAX_LEVEL >= 2 + KV_4_CREATED = MIN_LEVEL <= 5 and MAX_LEVEL >= 3 + KV_5_CREATED = MIN_LEVEL <= 6 and MAX_LEVEL >= 4 + KV_6_CREATED = MIN_LEVEL <= 7 and MAX_LEVEL >= 5 + KV_7_CREATED = MIN_LEVEL <= 8 and MAX_LEVEL >= 6 + KV_8_CREATED = MIN_LEVEL <= 9 and MAX_LEVEL >= 7 + KV_9_CREATED = MIN_LEVEL <= 10 and MAX_LEVEL >= 8 + KV_10_CREATED = MIN_LEVEL <= 11 and MAX_LEVEL >= 9 + KV_11_CREATED = MIN_LEVEL <= 12 and MAX_LEVEL >= 10 + + kv_0 = tl.zeros([BK, V], dtype=tl.float32) + kv_1 = tl.zeros([BK, V], dtype=tl.float32) + kv_2 = tl.zeros([BK, V], dtype=tl.float32) + kv_3 = tl.zeros([BK, V], dtype=tl.float32) + kv_4 = tl.zeros([BK, V], dtype=tl.float32) + kv_5 = tl.zeros([BK, V], dtype=tl.float32) + kv_6 = tl.zeros([BK, V], dtype=tl.float32) + kv_7 = tl.zeros([BK, V], dtype=tl.float32) + kv_8 = tl.zeros([BK, V], dtype=tl.float32) + kv_9 = tl.zeros([BK, V], dtype=tl.float32) + kv_10 = tl.zeros([BK, V], dtype=tl.float32) + kv_11 = tl.zeros([BK, V], dtype=tl.float32) + + offset = 0 # total number to cached tokens + first_chunk_index = 0 # next chunk index to compute + if USE_INITIAL_STATE: + offset = tl.load(offsets + i_n) + + first_chunk_index = offset // BT + + if KV_0_CREATED and (first_chunk_index & 1 > 0): + p_kv_0 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 0) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_0 = tl.load(p_kv_0, boundary_check=(0, 1)) + if KV_1_CREATED and (first_chunk_index & 2 > 0): + p_kv_1 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 1) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_1 = tl.load(p_kv_1, boundary_check=(0, 1)) + if KV_2_CREATED and (first_chunk_index & 4 > 0): + p_kv_2 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 2) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_2 = tl.load(p_kv_2, boundary_check=(0, 1)) + if KV_3_CREATED and (first_chunk_index & 8 > 0): + p_kv_3 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 3) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_3 = tl.load(p_kv_3, boundary_check=(0, 1)) + if KV_4_CREATED and (first_chunk_index & 16 > 0): + p_kv_4 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 4) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_4 = tl.load(p_kv_4, boundary_check=(0, 1)) + if KV_5_CREATED and (first_chunk_index & 32 > 0): + p_kv_5 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 5) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_5 = tl.load(p_kv_5, boundary_check=(0, 1)) + if KV_6_CREATED and (first_chunk_index & 64 > 0): + p_kv_6 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 6) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_6 = tl.load(p_kv_6, boundary_check=(0, 1)) + if KV_7_CREATED and (first_chunk_index & 128 > 0): + p_kv_7 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 7) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_7 = tl.load(p_kv_7, boundary_check=(0, 1)) + if KV_8_CREATED and (first_chunk_index & 256 > 0): + p_kv_8 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 8) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_8 = tl.load(p_kv_8, boundary_check=(0, 1)) + if KV_9_CREATED and (first_chunk_index & 512 > 0): + p_kv_9 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 9) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_9 = tl.load(p_kv_9, boundary_check=(0, 1)) + if KV_10_CREATED and (first_chunk_index & 1024 > 0): + p_kv_10 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 10) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_10 = tl.load(p_kv_10, boundary_check=(0, 1)) + if KV_11_CREATED and (first_chunk_index & 2048 > 0): + p_kv_11 = tl.make_block_ptr( + h0 + ((i_n * L_IN + 11) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + kv_11 = tl.load(p_kv_11, boundary_check=(0, 1)) + + NT = tl.cdiv(T, BT) + output_offset = -1 * (offset % BT) + for i_t in range(NT): + b_h_ptrs = level_scales + ((bos + i_t * BT + i_idx) * H + i_h) * L + b_llut + b_h = tl.load(b_h_ptrs, mask=i_idx >= j_idx) + + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_q = tl.make_block_ptr( + q + bos * K, (T, K), (K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0), + ) + p_k = tl.make_block_ptr( + k + bos * K, (K, T), (1, K), (i_k * BK, i_t * BT), (BK, BT), (0, 1), + ) + p_v = tl.make_block_ptr( + v + (bos * H + i_h) * V, + (T, V), + (H * V, 1), + (i_t * BT, 0), + (BT, V), + (1, 0), + ) + p_o = tl.make_block_ptr( + o + ((bos * H + i_h) * (K // BK) + i_k) * V, + (T, V), + (H * (K // BK) * V, 1), + (i_t * BT + output_offset, 0), + (BT, V), + (1, 0), + ) + + b_g = tl.load(p_g, boundary_check=(0,)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + + b_s = (tl.dot(b_q, b_k) * safe_exp(b_g[:, None] - b_g[None, :])).to( + b_q.dtype, + ) * b_h + + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_o = tl.zeros((BT, V), dtype=tl.float32) + if MIN_LEVEL == 0: + b_o += tl.dot(b_s, b_v) + + chunk_index = ( + first_chunk_index + i_t + ) # index of the chunk over the entire sequence, including the offset + + if MIN_LEVEL <= 0 and MAX_LEVEL >= 0: + if chunk_index & 1: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_0.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 1 and MAX_LEVEL >= 1: + if chunk_index & 2: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 1), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_1.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 2 and MAX_LEVEL >= 2: + if chunk_index & 4: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 2), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_2.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 3 and MAX_LEVEL >= 3: + if chunk_index & 8: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 3), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_3.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 4 and MAX_LEVEL >= 4: + if chunk_index & 16: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 4), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_4.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 5 and MAX_LEVEL >= 5: + if chunk_index & 32: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 5), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_5.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 6 and MAX_LEVEL >= 6: + if chunk_index & 64: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 6), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_6.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 7 and MAX_LEVEL >= 7: + if chunk_index & 128: # 8192 - 16384 + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 7), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_7.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 8 and MAX_LEVEL >= 8: + if chunk_index & 256: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 8), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_8.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 9 and MAX_LEVEL >= 9: + if chunk_index & 512: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 9), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_9.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 10 and MAX_LEVEL >= 10: + if chunk_index & 1024: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 10), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_10.to(b_q.dtype)) * tl.exp(b_g)[:, None] + if MIN_LEVEL <= 11 and MAX_LEVEL >= 11: + if chunk_index & 2048: + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, num_intra_levels + 11), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0, 1)) + b_o += tl.dot((b_l * b_q), kv_11.to(b_q.dtype)) * tl.exp(b_g)[:, None] + + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + if i_t < NT - 1 or T % BT == 0: + # Only apply the state update if the last chunk is a full chunk. + # Otherwise, it needs to be included in the next kernel call. + + # update the recurrent states + last_idx = min((i_t + 1) * BT, T) - 1 + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + if KV_0_CREATED: + kv_0 *= tl.exp(b_g_last) + if KV_1_CREATED: + kv_1 *= tl.exp(b_g_last) + if KV_2_CREATED: + kv_2 *= tl.exp(b_g_last) + if KV_3_CREATED: + kv_3 *= tl.exp(b_g_last) + if KV_4_CREATED: + kv_4 *= tl.exp(b_g_last) + if KV_5_CREATED: + kv_5 *= tl.exp(b_g_last) + if KV_6_CREATED: + kv_6 *= tl.exp(b_g_last) + if KV_7_CREATED: + kv_7 *= tl.exp(b_g_last) + if KV_8_CREATED: + kv_8 *= tl.exp(b_g_last) + if KV_9_CREATED: + kv_9 *= tl.exp(b_g_last) + if KV_10_CREATED: + kv_10 *= tl.exp(b_g_last) + if KV_11_CREATED: + kv_11 *= tl.exp(b_g_last) + + b_v = (b_v * tl.exp(b_g_last - b_g)[:, None]).to(b_v.dtype) + if MIN_LEVEL <= 1: + kv_0 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 2: + kv_1 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 3: + kv_2 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 4: + kv_3 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 5: + kv_4 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 6: + kv_5 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 7: + kv_6 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 8: + kv_7 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 9: + kv_8 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 10: + kv_9 += tl.dot(b_k, b_v) + elif MIN_LEVEL == 11: + kv_10 += tl.dot(b_k, b_v) + + check_value = (~chunk_index & (chunk_index + 1)) - 1 + + if MIN_LEVEL <= 1 and MAX_LEVEL >= 0: + if check_value & 1: + kv_1 += kv_0 + kv_0 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 2 and MAX_LEVEL >= 1: + if check_value & 2: + kv_2 += kv_1 + kv_1 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 3 and MAX_LEVEL >= 2: + if check_value & 4: + kv_3 += kv_2 + kv_2 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 4 and MAX_LEVEL >= 3: + if check_value & 8: + kv_4 += kv_3 + kv_3 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 5 and MAX_LEVEL >= 4: + if check_value & 16: + kv_5 += kv_4 + kv_4 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 6 and MAX_LEVEL >= 5: + if check_value & 32: + kv_6 += kv_5 + kv_5 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 7 and MAX_LEVEL >= 6: + if check_value & 64: + kv_7 += kv_6 + kv_6 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 8 and MAX_LEVEL >= 7: + if check_value & 128: + kv_8 += kv_7 + kv_7 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 9 and MAX_LEVEL >= 8: + if check_value & 256: + kv_9 += kv_8 + kv_8 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 10 and MAX_LEVEL >= 9: + if check_value & 512: + kv_10 += kv_9 + kv_9 = tl.zeros([BK, V], dtype=tl.float32) + if MIN_LEVEL <= 11 and MAX_LEVEL >= 10: + if check_value & 1024: + kv_11 += kv_10 + kv_10 = tl.zeros([BK, V], dtype=tl.float32) + + chunk_index = offset // BT + T // BT + + if STORE_FINAL_STATE: + if (MIN_LEVEL <= 0 and MAX_LEVEL >= 0) and (chunk_index & 1 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 0) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_0, boundary_check=(0, 1)) + if (MIN_LEVEL <= 1 and MAX_LEVEL >= 1) and (chunk_index & 2 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 1) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_1, boundary_check=(0, 1)) + if (MIN_LEVEL <= 2 and MAX_LEVEL >= 2) and (chunk_index & 4 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 2) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_2, boundary_check=(0, 1)) + if (MIN_LEVEL <= 3 and MAX_LEVEL >= 3) and (chunk_index & 8 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 3) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_3, boundary_check=(0, 1)) + if (MIN_LEVEL <= 4 and MAX_LEVEL >= 4) and (chunk_index & 16 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 4) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_4, boundary_check=(0, 1)) + if (MIN_LEVEL <= 5 and MAX_LEVEL >= 5) and (chunk_index & 32 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 5) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_5, boundary_check=(0, 1)) + if (MIN_LEVEL <= 6 and MAX_LEVEL >= 6) and (chunk_index & 64 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 6) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_6, boundary_check=(0, 1)) + if (MIN_LEVEL <= 7 and MAX_LEVEL >= 7) and (chunk_index & 128 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 7) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_7, boundary_check=(0, 1)) + if (MIN_LEVEL <= 8 and MAX_LEVEL >= 8) and (chunk_index & 256 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 8) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_8, boundary_check=(0, 1)) + if (MIN_LEVEL <= 9 and MAX_LEVEL >= 9) and (chunk_index & 512 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 9) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_9, boundary_check=(0, 1)) + if (MIN_LEVEL <= 10 and MAX_LEVEL >= 10) and (chunk_index & 1024 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 10) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_10, boundary_check=(0, 1)) + if (MIN_LEVEL <= 11 and MAX_LEVEL >= 11) and (chunk_index & 2048 > 0): + p_kv = tl.make_block_ptr( + ht + ((i_n * L_OUT + 11) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + tl.store(p_kv, kv_11, boundary_check=(0, 1)) + + tl.store(new_offsets + i_n, (offset // BT) * BT + T) + + +@triton.heuristics({ + "IS_VARLEN": lambda args: args["cu_seqlens"] is not None, +}) +@triton.jit(do_not_specialize=["T"]) +def copy_input_kernel( + q, + k, + v, + g, + level_scales, + cu_seqlens, + q_prev, + k_prev, + v_prev, + g_prev, + level_scales_prev, + offsets, + q_new, + k_new, + v_new, + g_new, + level_scales_new, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + # parallel over sequences and heads + i_nh = tl.program_id(0) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + offset = tl.load(offsets + i_n) + input_offset = -1 * (offset % BT) + + NT = tl.cdiv(T, BT) + + for i_t in range(NT): + p_g = tl.make_block_ptr( + g + bos * H + i_h, (T,), (H,), (i_t * BT + input_offset,), (BT,), (0,), + ) + p_q = tl.make_block_ptr( + q + bos * K, (T, K), (K, 1), (i_t * BT + input_offset, 0), (BT, K), (1, 0), + ) + p_k = tl.make_block_ptr( + k + bos * K, (T, K), (K, 1), (i_t * BT + input_offset, 0), (BT, K), (1, 0), + ) + p_v = tl.make_block_ptr( + v + (bos * H + i_h) * V, + (T, V), + (H * V, 1), + (i_t * BT + input_offset, 0), + (BT, V), + (1, 0), + ) + p_g_new = tl.make_block_ptr( + g_new + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,), + ) + p_q_new = tl.make_block_ptr( + q_new + bos * K, (T, K), (K, 1), (i_t * BT, 0), (BT, K), (1, 0), + ) + p_k_new = tl.make_block_ptr( + k_new + bos * K, (T, K), (K, 1), (i_t * BT, 0), (BT, K), (1, 0), + ) + p_v_new = tl.make_block_ptr( + v_new + (bos * H + i_h) * V, + (T, V), + (H * V, 1), + (i_t * BT, 0), + (BT, V), + (1, 0), + ) + + b_g = tl.load(p_g, boundary_check=(0,)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + + if i_t == 0: + p_g_prev = tl.make_block_ptr( + g_prev + i_n * BT * H + i_h, (BT,), (H,), (0,), (BT,), (0,), + ) + p_q_prev = tl.make_block_ptr( + q_prev + i_n * BT * K, (BT, K), (K, 1), (0, 0), (BT, K), (1, 0), + ) + p_k_prev = tl.make_block_ptr( + k_prev + i_n * BT * K, (BT, K), (K, 1), (0, 0), (BT, K), (1, 0), + ) + p_v_prev = tl.make_block_ptr( + v_prev + (i_n * BT * H + i_h) * V, + (BT, V), + (H * V, 1), + (0, 0), + (BT, V), + (1, 0), + ) + + b_g += tl.load(p_g_prev, boundary_check=(0,)) + b_q += tl.load(p_q_prev, boundary_check=(0, 1)) + b_k += tl.load(p_k_prev, boundary_check=(0, 1)) + b_v += tl.load(p_v_prev, boundary_check=(0, 1)) + + tl.store(p_g_new, b_g, boundary_check=(0,)) + tl.store(p_q_new, b_q, boundary_check=(0, 1)) + tl.store(p_k_new, b_k, boundary_check=(0, 1)) + tl.store(p_v_new, b_v, boundary_check=(0, 1)) + + for i in range(L): + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT + input_offset, i), + (BT, 1), + (1, 0), + ) + p_l_new = tl.make_block_ptr( + level_scales_new + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (i_t * BT, i), + (BT, 1), + (1, 0), + ) + b_l = tl.load(p_l, boundary_check=(0,)) + if i_t == 0: + p_l_prev = tl.make_block_ptr( + level_scales_prev + (i_n * BT * H + i_h) * L, + (BT, L), + (H * L, 1), + (0, i), + (BT, 1), + (1, 0), + ) + b_l += tl.load(p_l_prev, boundary_check=(0,)) + tl.store(p_l_new, b_l, boundary_check=(0,)) + + +@triton.heuristics( + { + "IS_VARLEN": lambda args: args["cu_seqlens"] is not None, + }, +) +@triton.jit(do_not_specialize=["T"]) +def copy_last_chunk_kernel( + q, + k, + v, + g, + level_scales, + cu_seqlens, + q_prev, + k_prev, + v_prev, + g_prev, + level_scales_prev, + offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + # parallel over sequences and heads + i_nh = tl.program_id(0) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + seq_offset = (T // BT) * BT + + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (seq_offset,), (BT,), (0,)) + p_q = tl.make_block_ptr( + q + bos * K, (T, K), (K, 1), (seq_offset, 0), (BT, K), (1, 0), + ) + p_k = tl.make_block_ptr( + k + bos * K, (T, K), (K, 1), (seq_offset, 0), (BT, K), (1, 0), + ) + p_v = tl.make_block_ptr( + v + (bos * H + i_h) * V, + (T, V), + (H * V, 1), + (seq_offset, 0), + (BT, V), + (1, 0), + ) + p_g_prev = tl.make_block_ptr( + g_prev + i_n * BT * H + i_h, (BT,), (H,), (0,), (BT,), (0,), + ) + p_q_prev = tl.make_block_ptr( + q_prev + i_n * BT * K, (BT, K), (K, 1), (0, 0), (BT, K), (1, 0), + ) + p_k_prev = tl.make_block_ptr( + k_prev + i_n * BT * K, (BT, K), (K, 1), (0, 0), (BT, K), (1, 0), + ) + p_v_prev = tl.make_block_ptr( + v_prev + (i_n * BT * H + i_h) * V, (BT, V), (H * V, 1), (0, 0), (BT, V), (1, 0), + ) + + tl.store(p_g_prev, tl.load(p_g, boundary_check=(0,)), boundary_check=(0,)) + tl.store(p_q_prev, tl.load(p_q, boundary_check=(0, 1)), boundary_check=(0, 1)) + tl.store(p_k_prev, tl.load(p_k, boundary_check=(0, 1)), boundary_check=(0, 1)) + tl.store(p_v_prev, tl.load(p_v, boundary_check=(0, 1)), boundary_check=(0, 1)) + + for i in range(L): + p_l = tl.make_block_ptr( + level_scales + (bos * H + i_h) * L, + (T, L), + (H * L, 1), + (seq_offset, i), + (BT, 1), + (1, 0), + ) + p_l_prev = tl.make_block_ptr( + level_scales_prev + (i_n * BT * H + i_h) * L, + (BT, L), + (H * L, 1), + (0, i), + (BT, 1), + (1, 0), + ) + tl.store(p_l_prev, tl.load(p_l, boundary_check=(0,)), boundary_check=(0,)) + + +@triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None}) +@triton.autotune( + configs=[ + triton.Config({"BK": BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for num_warps in [4] + for num_stages in [2, 3, 4] + ], + key=["H", "K", "V"], + restore_value=["dh", "dg_last"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunkwise_bwd_kernel_dhg( + do, + q, + g, + l, + h_l, + dh, + dg_last, + ell, + T, + cu_seqlens, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + NT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + # parallel over batches and heads + i_k = tl.program_id(0) + i_nh = tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + b_dh = tl.zeros([BK, V], dtype=tl.float32) + + num_intra_levels = (tl.log2(float(BT))).to(tl.int32) + 1 + + for i_t in range(tl.cdiv(T, BT) - 1, -1, -1): + p_dh = tl.make_block_ptr( + dh + ((i_n * NT + i_t) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + b_dh_old = tl.load(p_dh, boundary_check=(0, 1)) + + if (i_t & (1 << ell)) == 0: # store the chunk + tl.store( + p_dh, b_dh.to(p_dh.dtype.element_ty) + b_dh_old, boundary_check=(0, 1), + ) + # if you are about the transition to compute, reset to zeros + if i_t > 0 and ((i_t - 1) & (1 << ell)) > 0: + b_dh = tl.zeros([BK, V], dtype=tl.float32) + if i_t & (1 << ell): + p_h = tl.make_block_ptr( + h_l + ((i_n * NT + i_t) * H + i_h) * K * V, + (K, V), + (V, 1), + (i_k * BK, 0), + (BK, V), + (1, 0), + ) + + b_h = tl.load(p_h, boundary_check=(0, 1)) + p_dg_last = dg_last + i_n * NT * H + i_t * H + i_h + tl.atomic_add(p_dg_last, tl.sum(b_h * (b_dh + b_dh_old))) + last_idx = min((i_t + 1) * BT, T) - 1 + b_g_last = tl.exp(tl.load(g + bos * H + last_idx * H + i_h)) + b_dh *= b_g_last + if i_t & (1 << ell): # compute this chunk + p_g = tl.make_block_ptr( + g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,), + ) + p_q = tl.make_block_ptr( + q + bos * K, (K, T), (1, K), (i_k * BK, i_t * BT), (BK, BT), (0, 1), + ) + p_do = tl.make_block_ptr( + do + (bos * H + i_h) * V, + (T, V), + (H * V, 1), + (i_t * BT, 0), + (BT, V), + (1, 0), + ) + p_l = tl.make_block_ptr( + l + (bos * H + i_h) * L + num_intra_levels + ell, + (T,), + (H * L,), + (i_t * BT,), + (BT,), + (0,), + ) + b_l = tl.load(p_l, boundary_check=(0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * (tl.exp(b_g) * b_l)[None, :]).to(b_q.dtype) + b_do = tl.load(p_do, boundary_check=(0, 1)) + + b_s = tl.dot(b_q, b_do).to(b_q.dtype) + b_dh += b_s + + +@triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [2, 3, 4] + ], + key=["H", "K", "V"], + restore_value=["dq", "dg"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunkwise_bwd_kernel_hdqgl( + do, + q, + k, + v, + g, + l, + h_l, + dq, + dg, + dl, + ell, + T, + cu_seqlens, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + NT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + # parallel over batches and heads + i_nh = tl.program_id(0) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + b_h = tl.zeros([V, K], dtype=tl.float32) + + num_intra_levels = (tl.log2(float(BT))).to(tl.int32) + 1 + + for i_t in range(tl.cdiv(T, BT)): + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + if i_t & (1 << ell): # compute and store derivatives + p_do = tl.make_block_ptr( + do + (bos * H + i_h) * V, + (T, V), + (H * V, 1), + (i_t * BT, 0), + (BT, V), + (1, 0), + ) + p_q = tl.make_block_ptr( + q + bos * K, (T, K), (K, 1), (i_t * BT, 0), (BT, K), (1, 0), + ) + p_l = tl.make_block_ptr( + l + (bos * H + i_h) * L + num_intra_levels + ell, + (T,), + (H * L,), + (i_t * BT,), + (BT,), + (0,), + ) + p_dq = tl.make_block_ptr( + dq + (bos * H + i_h) * K, + (T, K), + (H * K, 1), + (i_t * BT, 0), + (BT, K), + (1, 0), + ) + p_dg = tl.make_block_ptr( + dg + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,), + ) + p_dl = tl.make_block_ptr( + dl + (bos * H + i_h) * L + num_intra_levels + ell, + (T,), + (H * L,), + (i_t * BT,), + (BT,), + (0,), + ) + p_h = tl.make_block_ptr( + h_l + ((i_n * NT + i_t) * H + i_h) * K * V, + (V, K), + (1, V), + (0, 0), + (V, K), + (0, 1), + ) + + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_l = tl.load(p_l, boundary_check=(0,)) + + b_dlq = tl.exp(b_g)[:, None] * tl.dot(b_do, b_h.to(b_do.dtype)) + + b_dl = tl.sum(b_dlq * b_q, axis=1) + b_dg = b_l * b_dl + + tl.store(p_h, b_h, boundary_check=(0, 1)) + b_dq_old = tl.load(p_dq, boundary_check=(0, 1)) + tl.store( + p_dq, + (b_l[:, None] * b_dlq).to(p_dq.dtype.element_ty) + b_dq_old, + boundary_check=(0, 1), + ) + tl.store(p_dl, b_dl.to(p_dl.dtype.element_ty), boundary_check=(0,)) + b_dg_old = tl.load(p_dg, boundary_check=(0,)) + tl.store( + p_dg, b_dg.to(p_dg.dtype.element_ty) + b_dg_old, boundary_check=(0,), + ) + if ((i_t + 1) & (1 << ell)) == 0: + b_h = tl.zeros([V, K], dtype=tl.float32) + + last_idx = min((i_t + 1) * BT, T) - 1 + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + b_h *= tl.exp(b_g_last) + if (i_t & (1 << ell)) == 0: # update the state + p_k = tl.make_block_ptr( + k + bos * K, (T, K), (K, 1), (i_t * BT, 0), (BT, K), (1, 0), + ) + p_v = tl.make_block_ptr( + v + (bos * H + i_h) * V, + (V, T), + (1, H * V), + (0, i_t * BT), + (V, BT), + (0, 1), + ) + b_g = tl.load(p_g, boundary_check=(0,)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_k = (b_k * tl.exp(b_g_last - b_g)[:, None]).to(b_k.dtype) + b_h += tl.dot(b_v, b_k) + + +@triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [2, 3, 4] + ], + key=["H", "K", "V"], + restore_value=["dk", "dg", "dg_last"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunkwise_bwd_kernel_dkg( + dh, + k, + v, + g, + dg_last, + dk, + dg, + cu_seqlens, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + NT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_i = tl.arange(0, BT) + + p_dh = tl.make_block_ptr( + dh + ((i_n * NT + i_t) * H + i_h) * K * V, + (V, K), + (1, V), + (0, 0), + (V, K), + (0, 1), + ) + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_k = tl.make_block_ptr(k + bos * K, (T, K), (K, 1), (i_t * BT, 0), (BT, K), (1, 0)) + p_v = tl.make_block_ptr( + v + (bos * H + i_h) * V, + (T, V), + (H * V, 1), + (i_t * BT, 0), + (BT, V), + (1, 0), + ) + p_dk = tl.make_block_ptr( + dk + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, 0), (BT, K), (1, 0), + ) + p_dg = tl.make_block_ptr(dg + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + last_idx = min((i_t + 1) * BT, T) - 1 + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_dg_last = dg_last + i_n * NT * H + i_t * H + i_h + b_dg_last = tl.load(p_dg_last) + + b_dg_last *= tl.exp(b_g_last) + b_dk = safe_exp(b_g_last - b_g)[:, None] * tl.dot(b_v, b_dh).to(b_v.dtype) + b_dg = tl.load(p_dg, boundary_check=(0,)) + b_dg -= tl.sum(b_k * b_dk, axis=1) + b_dg_last += tl.sum(b_dk * b_k) + + b_dg = tl.where(o_i < BT - 1, b_dg, b_dg + b_dg_last) + + tl.store(p_dg, b_dg, boundary_check=(0,)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [2, 3, 4] + ], + key=["H", "K", "V"], + restore_value=["dv"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunkwise_bwd_kernel_dv( + dh, + k, + g, + dv, + T, + cu_seqlens, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + NT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + p_dh = tl.make_block_ptr( + dh + ((i_n * NT + i_t) * H + i_h) * K * V, + (K, V), + (V, 1), + (0, 0), + (K, V), + (1, 0), + ) + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_k = tl.make_block_ptr(k + bos * K, (T, K), (K, 1), (i_t * BT, 0), (BT, K), (1, 0)) + p_dv = tl.make_block_ptr( + dv + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, 0), (BT, V), (1, 0), + ) + + last_idx = min((i_t + 1) * BT, T) - 1 + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dv = safe_exp(-b_g + b_g_last)[:, None] * tl.dot(b_k, b_dh).to(b_k.dtype) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [4] + for num_stages in [2, 3, 4] + ], + key=["H", "K", "V"], + restore_value=["dl", "dq", "dk", "dv", "dg"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunkwise_bwd_kernel_diag( + do, + q, + k, + v, + g, + l, + llut, + mask, + dq, + dk, + dv, + dg, + dl, + cu_seqlens, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + L: tl.constexpr, + BT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + p_llut = tl.make_block_ptr(llut, (BT, BT), (BT, 1), (0, 0), (BT, BT), (1, 0)) + b_llut = tl.load(p_llut, boundary_check=(0, 1)) + i_t, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int32), + tl.load(cu_seqlens + i_n + 1).to(tl.int32), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_i = tl.arange(0, BT) + i_idx = o_i[:, None] # BT x 1 + j_idx = o_i[None, :] # 1 x BT + + b_h_ptrs = l + ((bos + i_t * BT + i_idx) * H + i_h) * L + b_llut + b_h = tl.load(b_h_ptrs, mask=i_idx >= j_idx) + + p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_q = tl.make_block_ptr(q + bos * K, (K, T), (1, K), (0, i_t * BT), (K, BT), (0, 1)) + p_k = tl.make_block_ptr(k + bos * K, (T, K), (K, 1), (i_t * BT, 0), (BT, K), (1, 0)) + p_v = tl.make_block_ptr( + v + (bos * H + i_h) * V, (V, T), (1, H * V), (0, i_t * BT), (V, BT), (0, 1), + ) + p_do = tl.make_block_ptr( + do + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, 0), (BT, V), (1, 0), + ) + p_dg = tl.make_block_ptr(dg + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_dq = tl.make_block_ptr( + dq + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, 0), (BT, K), (1, 0), + ) + p_dk = tl.make_block_ptr( + dk + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, 0), (BT, K), (1, 0), + ) + p_dv = tl.make_block_ptr( + dv + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, 0), (BT, V), (1, 0), + ) + + b_g = tl.load(p_g, boundary_check=(0,)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dq = tl.load(p_dq, boundary_check=(0, 1)) + b_dk = tl.load(p_dk, boundary_check=(0, 1)) + b_dv = tl.load(p_dv, boundary_check=(0, 1)) + b_dg = tl.load(p_dg, boundary_check=(0,)) + + b_s = (tl.dot(b_k, b_q)).to(b_q.dtype) + b_a = safe_exp(b_g[:, None] - b_g[None, :]) + b_dv += tl.dot((b_s * tl.trans(b_a * b_h)).to(b_do.dtype), b_do) + b_ds = tl.dot(b_do, b_v) * b_a + b_dl = b_ds * tl.trans(b_s) + b_dg += tl.sum(b_dl * b_h, axis=1) + b_dg -= tl.sum(b_dl * b_h, axis=0) + b_ds = (b_ds * b_h).to(b_k.dtype) + b_dq += tl.dot(b_ds, b_k) + b_dk += tl.trans(tl.dot(b_q, b_ds)) + + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + num_intra_levels = (tl.log2(float(BT))).to(tl.int32) + 1 + + for i in range(num_intra_levels): + p_mask = tl.make_block_ptr(mask + i * (BT * BT), (BT, BT), (BT, 1), (0, 0), (BT, BT), (1, 0)) + b_mask = tl.load(p_mask, boundary_check=(0, 1)) + dl_i = tl.sum(tl.where(b_mask == 1, b_dl, 0), axis=1) + p_dl_i = tl.make_block_ptr(dl + (bos * H + i_h) * L + i, (T,), (H * L,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dl_i, dl_i, boundary_check=(0,)) + + +def construct_binary_level_mask(level, T): + if level == 0: + return torch.diag(torch.ones(T, dtype=torch.bool)) + + indices = torch.cartesian_prod(torch.arange(T), torch.arange(T)) + + mask = torch.where( + torch.logical_and( + torch.logical_and( + indices[:, 0] % (1 << level) >= (1 << (level - 1)), + indices[:, 1] + (1 << (level - 1)) + >= indices[:, 0] - (indices[:, 0] % (1 << (level - 1))), + ), + indices[:, 1] < indices[:, 0] - (indices[:, 0] % (1 << (level - 1))), + ).view(T, T), + 1, + 0, + ) + + return mask + + +def level_lut(BT, device): + lut = torch.zeros((BT, BT), dtype=torch.int32, device=device) + for level in range(1, ceil_log(BT, 2) + 1): + mask = construct_binary_level_mask(level, BT).to(device) + lut = torch.where(mask.to(torch.bool), level, lut) + return lut + + +def masks(BT, device): + masks = [] + for level in range(0, ceil_log(BT, 2) + 1): + mask = construct_binary_level_mask(level, BT).to(device).to(torch.int32) + masks.append(mask) + return torch.stack(masks) + + +def ceil_div(x: int, y: int) -> int: + return math.ceil(x / y) + + +def ceil_log(x: int, b: int) -> int: + return math.ceil(math.log(x, b)) + + +@dataclass +class LogLinearAttentionState: + ht: torch.Tensor + offsets: torch.Tensor + q_prev: torch.Tensor + k_prev: torch.Tensor + v_prev: torch.Tensor + g_prev: torch.Tensor + level_scales_prev: torch.Tensor + + +class ChunkLogLinearAttentionFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q, + k, + v, + g, + level_scales, + initial_state, + output_final_state, + cu_seqlens, + ): + B, T, G, K = k.shape + _, _, H, V = v.shape + _, _, _, L = level_scales.shape + + if G != 1: + raise ValueError("Group dimension must be 1.") + + if not math.log2(V).is_integer(): + raise ValueError( + "Head dimension must be a power of two. Please pad the head dimension to the next power of two.", + ) + + if K % BLOCK_K != 0: + raise ValueError(f"State dimension must be divisible by {BLOCK_K}.") + + if triton.__version__ > "3.2.0": + warnings.warn("Triton>3.2.0 detected, which is known to have worse performance. " + "For optimal performance, it is recommended to install Triton==3.2.0 (if possible).") + + BT = 64 # chunk size + + h0 = initial_state.ht if initial_state is not None else None + offsets = initial_state.offsets if initial_state is not None else None + + if cu_seqlens is None: + NT = ceil_div(T + (torch.max(offsets) if offsets is not None else 0), BT) + MAX_LEVEL = ceil_log(NT, 2) - 1 + else: + NT = max( + [ + ceil_div( + cu_seqlens[i + 1] + - cu_seqlens[i] + + (offsets[i] if offsets is not None else 0), + BT, + ) + for i in range(len(cu_seqlens) - 1) + ], + ) + MAX_LEVEL = ceil_log(NT, 2) - 1 + B = len(cu_seqlens) - 1 + + if MAX_LEVEL > 10: + raise ValueError("Sequence length must be less than 2**17") + + S0 = B if cu_seqlens is None else 1 + o = torch.zeros( + (S0, T, H, (K // BLOCK_K), V), + dtype=v.dtype, + device=v.device, + ) + + if initial_state is not None: + if cu_seqlens is not None: + cu_seqlens = cu_seqlens + F.pad(torch.cumsum(offsets % BT), (1, 0)) + else: + assert (offsets == offsets[0]).all() + T += offsets[0].item() % BT + S1 = cu_seqlens[-1] if cu_seqlens is not None else T + q_new = torch.zeros((S0, S1, G, K), dtype=q.dtype, device=q.device) + k_new = torch.zeros((S0, S1, G, K), dtype=k.dtype, device=k.device) + v_new = torch.zeros((S0, S1, H, V), dtype=v.dtype, device=v.device) + g_new = torch.zeros((S0, S1, H), dtype=g.dtype, device=g.device) + level_scales_new = torch.zeros((S0, S1, H, L), dtype=level_scales.dtype, device=level_scales.device) + + copy_input_kernel[(B * H,)]( + q=q, + k=k, + v=v, + g=g, + level_scales=level_scales, + cu_seqlens=cu_seqlens, + q_prev=initial_state.q_prev, + k_prev=initial_state.k_prev, + v_prev=initial_state.v_prev, + g_prev=initial_state.g_prev, + level_scales_prev=initial_state.level_scales_prev, + q_new=q_new, + k_new=k_new, + v_new=v_new, + g_new=g_new, + level_scales_new=level_scales_new, + offsets=offsets, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + ) + q = q_new + k = k_new + v = v_new + g = g_new + level_scales = level_scales_new + + # Store one extra level (MAX_LEVEL + 2) in case the length is multiple of 2 + ht = ( + torch.zeros((B, MAX_LEVEL + 2, H, K, V), dtype=torch.float, device=v.device) + if output_final_state + else None + ) + + new_offsets = torch.zeros((B,), dtype=torch.int32, device=v.device) + g = chunk_local_cumsum(g, chunk_size=BT, cu_seqlens=cu_seqlens) + + def grid(meta): + return (triton.cdiv(K, meta["BK"]), B * H) + + l_in = h0.shape[1] if initial_state is not None else None + l_out = ht.shape[1] if output_final_state else None + + ctx.llut = level_lut(BT, v.device) + + chunkwise_fwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + level_scales=level_scales, + llut=ctx.llut, + o=o, + h0=h0, + ht=ht, + offsets=offsets, + new_offsets=new_offsets, + cu_seqlens=cu_seqlens, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + L_IN=l_in, + L_OUT=l_out, + MIN_LEVEL=0, + MAX_LEVEL=MAX_LEVEL, + ) + + ctx.save_for_backward(q, k, v, g, level_scales, initial_state, cu_seqlens) + ctx.chunk_size = BT + + if output_final_state: + q_prev = torch.zeros((B, BT, G, K), dtype=q.dtype, device=q.device) + k_prev = torch.zeros((B, BT, G, K), dtype=k.dtype, device=k.device) + v_prev = torch.zeros((B, BT, H, V), dtype=v.dtype, device=v.device) + g_prev = torch.zeros((B, BT, H), dtype=g.dtype, device=g.device) + level_scales_prev = torch.zeros((B, BT, H, L), dtype=level_scales.dtype, device=level_scales.device) + + copy_last_chunk_kernel[(B * H,)]( + q=q, + k=k, + v=v, + g=g, + level_scales=level_scales, + cu_seqlens=cu_seqlens, + q_prev=q_prev, + k_prev=k_prev, + v_prev=v_prev, + g_prev=g_prev, + level_scales_prev=level_scales_prev, + offsets=new_offsets, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + ) + + final_state = LogLinearAttentionState( + ht=ht, + offsets=new_offsets, + q_prev=q_prev, + k_prev=k_prev, + v_prev=v_prev, + g_prev=g_prev, + level_scales_prev=level_scales_prev, + ) + return o.sum(dim=-2), final_state + + return o.sum(dim=-2), None + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht): + if triton.__version__ < "3.1.0": + raise ValueError("Triton>=3.1.0 is required") + + q, k, v, g, level_scales, initial_state, cu_seqlens = ctx.saved_tensors + chunk_size = ctx.chunk_size + llut = ctx.llut + mask = masks(chunk_size, v.device) + + if initial_state is not None: + raise NotImplementedError( + "Backward pass is not implemented for log-linear attention with a prefilled kernel.", + ) + + B, T, G, K = k.shape + assert G == 1, "Multi-head attention is not supported" + _, _, H, V = v.shape + _, _, _, L = level_scales.shape + BT = chunk_size + if cu_seqlens is not None: + NT = max( + [ + ceil_div(cu_seqlens[i + 1] - cu_seqlens[i], BT) + for i in range(len(cu_seqlens) - 1) + ], + ) + else: + NT = ceil_div(T, BT) + + if cu_seqlens is not None: + B = len(cu_seqlens) - 1 + + dh = torch.zeros((B, NT, H, K, V), dtype=v.dtype, device=v.device) + dq = torch.zeros((B if cu_seqlens is None else 1, T, H, K), dtype=v.dtype, device=v.device) + dk = torch.zeros((B if cu_seqlens is None else 1, T, H, K), dtype=v.dtype, device=v.device) + dv = torch.zeros_like(v) + dg = torch.zeros(g.shape, dtype=torch.float, device=v.device) + dl = torch.zeros(level_scales.shape, dtype=torch.float, device=v.device) + h_l = torch.zeros((B, NT, H, K, V), dtype=torch.float, device=v.device) + dg_last = torch.zeros((B, NT, H), dtype=torch.float, device=v.device) + do = do.to(v.dtype) + + grid = (B * H,) + + def grid_f(meta): + return (triton.cdiv(K, meta["BK"]), B * H) + + grid_t = (NT, B * H) + + num_inter_chunk_levels = ceil_log(NT, 2) + for ell in range(num_inter_chunk_levels - 1, -1, -1): + chunkwise_bwd_kernel_hdqgl[grid]( + do=do, + q=q, + k=k, + v=v, + g=g, + l=level_scales, + h_l=h_l, + dq=dq, + dg=dg, + dl=dl, + cu_seqlens=cu_seqlens, + ell=ell, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + NT=NT, + ) + chunkwise_bwd_kernel_dhg[grid_f]( + do=do, + q=q, + g=g, + l=level_scales, + h_l=h_l, + dh=dh, + dg_last=dg_last, + cu_seqlens=cu_seqlens, + ell=ell, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + NT=NT, + ) + + chunkwise_bwd_kernel_dkg[grid_t]( + dh=dh, + k=k, + v=v, + g=g, + dg_last=dg_last, + dk=dk, + dg=dg, + cu_seqlens=cu_seqlens, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + NT=NT, + ) + + chunkwise_bwd_kernel_dv[grid_t]( + dh=dh, + k=k, + g=g, + dv=dv, + cu_seqlens=cu_seqlens, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + NT=NT, + ) + + chunkwise_bwd_kernel_diag[grid_t]( + do=do, + q=q, + k=k, + v=v, + g=g, + l=level_scales, + llut=llut, + mask=mask, + dq=dq, + dk=dk, + dv=dv, + dg=dg, + dl=dl, + cu_seqlens=cu_seqlens, + T=T, + H=H, + K=K, + V=V, + L=L, + BT=BT, + ) + + dg = chunk_local_cumsum(dg, chunk_size=chunk_size, reverse=True, cu_seqlens=cu_seqlens).to(g.dtype) + + dq = reduce(dq, "b t (g h) k -> b t g k", "sum", g=G, h=H // G) + dk = reduce(dk, "b t (g h) k -> b t g k", "sum", g=G, h=H // G) + return dq, dk, dv, dg, dl, None, None, None + + +@torch.compiler.disable +def chunk_log_linear_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + level_scales: torch.Tensor, + initial_state: LogLinearAttentionState | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H]`. + level_scales (torch.Tensor): + Scales for each level of shape `[B, T, H, L]`. + initial_state (Optional[LogLinearAttentionState]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of type `LogLinearAttentionState` if `output_final_state=True` else `None`. + + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + + o, final_state = ChunkLogLinearAttentionFunction.apply( + q, + k, + v, + g, + level_scales, + initial_state, + output_final_state, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/log_linear_attn/naive.py b/code/flash-linear-attention/fla/ops/log_linear_attn/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..aa16b814e0fb5f096583121481a81be6df5eba4c --- /dev/null +++ b/code/flash-linear-attention/fla/ops/log_linear_attn/naive.py @@ -0,0 +1,50 @@ +import numpy as np +import torch + + +def segsum(x): + T = x.size(-1) + x_cumsum = torch.cumsum(x, dim=-1) + x_segsum = x_cumsum[..., :, None] - x_cumsum[..., None, :] + mask = torch.tril(torch.ones(T, T, device=x.device, dtype=bool)) + x_segsum = x_segsum.masked_fill(~mask, -torch.inf) + return x_segsum + + +def construct_level_mask(level, L): + T = L.size(-1) + if level == 0: + return torch.diag_embed(L[..., level, :]) + + indices = torch.cartesian_prod(torch.arange(T), torch.arange(T)).to(L.device) + + mask = torch.where( + torch.logical_and( + torch.logical_and( + indices[:, 0] % (1 << level) >= (1 << (level - 1)), + indices[:, 1] + (1 << (level - 1)) + >= indices[:, 0] - (indices[:, 0] % (1 << (level - 1))), + ), + indices[:, 1] < indices[:, 0] - (indices[:, 0] % (1 << (level - 1))), + ).view(T, T), + L[..., level, :].unsqueeze(-1).expand(*([-1] * (len(L.shape) - 2)), T, T), + 0, + ) + + return mask + + +def construct_H_matrix(a, L): + T = a.size(-1) + A = torch.exp(segsum(a)) + H = torch.zeros_like(A) + for level in range(int(np.ceil(np.log2(T))) + 1): + mask = construct_level_mask(level, L) + H += A * mask + return H + + +def naive_log_linear_attn(q, k, v, g, level_scales): + H = construct_H_matrix(g.permute(0, 2, 1), level_scales.permute(0, 2, 3, 1)) + M = torch.einsum("bhlc,blhn,bchn->bhlc", H, q, k) + return torch.einsum("bhlc,bchp->blhp", M, v) diff --git a/code/flash-linear-attention/fla/ops/mesa_net/__init__.py b/code/flash-linear-attention/fla/ops/mesa_net/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..24036866a902a98c9f5db16b6758694ea133be1f --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/__init__.py @@ -0,0 +1,5 @@ +from .chunk import chunk_mesa_net +from .decoding_one_step import mesa_net_decoding_one_step +from .naive import naive_mesa_net_decoding_one_step, naive_mesa_net_exact + +__all__ = ['chunk_mesa_net', 'naive_mesa_net_exact', 'mesa_net_decoding_one_step', 'naive_mesa_net_decoding_one_step'] diff --git a/code/flash-linear-attention/fla/ops/mesa_net/chunk.py b/code/flash-linear-attention/fla/ops/mesa_net/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..9adb2375ae1fbe0204d9b64e9b7877ed387c0d16 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/chunk.py @@ -0,0 +1,369 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.common.chunk_h import chunk_bwd_dh +from fla.ops.mesa_net.chunk_cg_solver_bwd import chunk_mesa_cg_bwd +from fla.ops.mesa_net.chunk_cg_solver_fwd import chunk_mesa_cg_fwd +from fla.ops.mesa_net.chunk_h_fwd import chunk_mesa_fwd_h +from fla.ops.mesa_net.chunk_h_kk_intra_bwd import chunk_mesa_net_h_kk_bwd_intra_fn +from fla.ops.mesa_net.chunk_h_kv_intra_bwd import chunk_mesa_net_h_kv_bwd_intra_fn +from fla.ops.utils import chunk_local_cumsum +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_fwd_mesa_net_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + lamb: torch.Tensor, + cu_seqlens: torch.Tensor, + max_CG_iteration: int = 30, + chunk_size: int = 64, + h_kk_init: torch.Tensor | None = None, + h_kv_init: torch.Tensor | None = None, + output_final_state: bool = False, +) -> torch.Tensor: + + g = chunk_local_cumsum(g, chunk_size=chunk_size, cu_seqlens=cu_seqlens) if g is not None else None + h_kk, h_kv, h_kk_final, h_kv_final = chunk_mesa_fwd_h( + k=k, + v=v, + g=g, + beta=beta, + h_init=h_kk_init, + h_kv_init=h_kv_init, + output_final_state=output_final_state, + states_in_fp32=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + q_star, o = chunk_mesa_cg_fwd( + q=q, + k=k, + h=h_kk, + h_kv=h_kv, + v=v, + g_local_cumsum=g, + beta=beta, + lamb=lamb, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + max_CG_iteration=max_CG_iteration, + ) + return g, q_star, o, (h_kk_final, h_kv_final) + + +def chunk_fwd_mesa_net_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + lamb: torch.Tensor, + q_star: torch.Tensor, # should be cached in the forward pass + do: torch.Tensor, + cu_seqlens: torch.Tensor, + max_CG_iteration: int = 30, + chunk_size: int = 64, + h_kk_init: torch.Tensor | None = None, + h_kv_init: torch.Tensor | None = None, + dh_kv_final: torch.Tensor | None = None, + dh_kk_final: torch.Tensor | None = None, +) -> torch.Tensor: + # recompute the hidden states, which is quite cheap + h_kk, h_kv, _, _ = chunk_mesa_fwd_h( + k=k, + v=v, + g=g, + beta=beta, + h_init=h_kk_init, + h_kv_init=h_kv_init, + output_final_state=False, + states_in_fp32=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dh_kv, dh0_kv = chunk_bwd_dh( + q=q_star, + k=k, + v=v, + g=g, + gk=None, + gv=None, + do=do, + h0=h_kv_init, + dht=dh_kv_final, + states_in_fp32=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + scale=1, + ) + dq, dk_beta, dv, dg = chunk_mesa_net_h_kv_bwd_intra_fn( + q_star=q_star, + k=k, + v=v, + beta=beta, + h_kv=h_kv, + dh_kv=dh_kv, + g=g, + do=do, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dq = chunk_mesa_cg_bwd( + dq=dq, + k=k, + h=h_kk, + g_local_cumsum=g, + beta=beta, + lamb=lamb, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + max_CG_iteration=max_CG_iteration, + output_dtype=torch.float16, + ) + dh_kk, dh0_kk = chunk_bwd_dh( + q=dq, + k=k, + v=k, + g=g, + gk=None, + gv=None, + do=q_star, + h0=h_kk_init, + dht=-dh_kk_final if dh_kk_final is not None else None, + states_in_fp32=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + scale=1, + ) + dk, dg2, dlamb, dbeta = chunk_mesa_net_h_kk_bwd_intra_fn( + k=k, + g=g, + beta=beta, + h=h_kk, + dh=dh_kk, + dk_beta=dk_beta, + q_star=q_star, + dq=dq, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dg.add_(dg2) + dg = chunk_local_cumsum(dg, chunk_size=chunk_size, reverse=True, cu_seqlens=cu_seqlens).to(g) + return dq, dk, dv, dg, dbeta, dlamb, -dh0_kk if dh0_kk is not None else None, dh0_kv if dh0_kv is not None else None + + +class ChunkMesaNetFunction(torch.autograd.Function): + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q, + k, + v, + g, + beta, + lamb, + cu_seqlens, + max_CG_iteration, + h_kk_init, + h_kv_init, + output_final_state, + use_qk_l2norm_in_kernel, + ): + chunk_size = 64 + + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q, output_dtype=torch.float16) + k, k_rstd = l2norm_fwd(k, output_dtype=torch.float16) + else: + q_rstd, k_rstd = None, None + q = q.to(torch.float16) + k = k.to(torch.float16) + + g_cumsum, q_star, o, (h_kk_final, h_kv_final) = chunk_fwd_mesa_net_fwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + lamb=lamb, + cu_seqlens=cu_seqlens, + max_CG_iteration=max_CG_iteration, + chunk_size=chunk_size, + h_kk_init=h_kk_init, + h_kv_init=h_kv_init, + output_final_state=output_final_state, + ) + ctx.max_CG_iteration = max_CG_iteration + ctx.chunk_size = chunk_size + ctx.cu_seqlens = cu_seqlens + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + ctx.save_for_backward(q, q_rstd, k, k_rstd, v, g_cumsum, beta, lamb, h_kk_init, h_kv_init, q_star, o) + return o, h_kk_final, h_kv_final + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dh_kk_final=None, dh_kv_final=None): + q, q_rstd, k, k_rstd, v, g, beta, lamb, h_kk_init, h_kv_init, q_star, o = ctx.saved_tensors + + max_CG_iteration = ctx.max_CG_iteration + chunk_size = ctx.chunk_size + cu_seqlens = ctx.cu_seqlens + dq, dk, dv, dg, dbeta, dlamb, dh0_kk, dh0_kv = chunk_fwd_mesa_net_bwd( + q=q, k=k, v=v, g=g, beta=beta, lamb=lamb, q_star=q_star, do=do, + cu_seqlens=cu_seqlens, max_CG_iteration=max_CG_iteration, chunk_size=chunk_size, + h_kk_init=h_kk_init, h_kv_init=h_kv_init, dh_kv_final=dh_kv_final, dh_kk_final=dh_kk_final, + ) + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + return dq, dk, dv.to(v), dg.to(g), dbeta.to(beta), dlamb.to(lamb), None, None, dh0_kk, dh0_kv, None, None + + +@torch.compiler.disable +def chunk_mesa_net( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + lamb: torch.Tensor, + h_kk_init: torch.Tensor | None = None, + h_kv_init: torch.Tensor | None = None, + output_final_state: bool = False, + max_CG_iteration: int = 30, + use_qk_l2norm_in_kernel: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]` + k (torch.Tensor): + keys of shape `[B, T, H, K]`. Should be l2-normalized before passing in. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + decay factors of shape `[B, T, H]`. Note that `g` should be in log space, that is, `g = log(decay_factor) < 0`. + Recommended input dtype: `torch.float32`. + beta (torch.Tensor): + betas of shape `[B, T, H]`. Recommended input dtype: `torch.float32`. + lamb (torch.Tensor): + lambdas of shape `[B, T, H]`. Recommended input dtype: `torch.float32`. + h_kk_init (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + h_kv_init (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + max_CG_iteration (int): + Maximum number of conjugate gradient iterations for solving the linear system. Default: `30`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (bool): + Do l2 normalization on Q and K in the kernel for saving GPU memory. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + (final_states_kk, final_states_kv) (Tuple[torch.Tensor, torch.Tensor]): + Final states of shape `[N, H, K, K]` and `[N, H, K, V]` if `output_final_state=True` else `(None, None)`. + Recall that MesaNet has two states, `h_kk` and `h_kv`! + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.mesa_net import chunk_mesa_net + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 16, 128, 128 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> k = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, dtype=torch.float32, device='cuda')) + >>> beta = torch.rand(B, T, H, dtype=torch.float32, device='cuda').sigmoid() + # lower bound is 0.25 for numerical stability + >>> lamb = F.softplus(torch.rand(H, K, dtype=torch.float32, device='cuda')) + 0.25 + >>> init_state_kk = torch.randn(B, H, K, V, dtype=torch.float32, device='cuda') + >>> init_state_kv = torch.randn(B, H, K, V, dtype=torch.float32, device='cuda') + >>> o, (final_state_kk, final_state_kv) = chunk_mesa_net( + q, k, v, beta, lamb, + h_kk_init=init_state_kk, + h_kv_init=init_state_kv, + max_CG_iteration=30, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, (final_state_kk_var, final_state_kv_var) = chunk_mesa_net( + q, k, v, beta, lamb, + h_kk_init=init_state_kk, + h_kv_init=init_state_kv, + max_CG_iteration=30, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + B, T, H, K = q.shape + assert k.shape == (B, T, H, K), "k must be of shape (batch size, seq len, num head, head dim)." + assert v.shape == (B, T, H, K), "v must be of shape (batch size, seq len, num head, head dim)." + assert g.shape == (B, T, H), "g must be of shape (batch size, seq len, num head)." + assert beta.shape == (B, T, H), "beta must be of shape (batch size, seq len, num head)." + assert lamb.shape == (H, K), "lamb must be of shape (num head, key dim)." + + if h_kv_init is not None: + assert h_kv_init.dtype == torch.float32, "h_kv_init must be in float32." + if cu_seqlens is None: + assert h_kv_init.shape == (B, H, K, K), "h_kv_init must be of shape (batch size, num head, head dim, head dim)." + if h_kk_init is not None: + assert h_kk_init.dtype == torch.float32, "h_kk_init must be in float32." + if cu_seqlens is None: + assert h_kk_init.shape == (B, H, K, K), "h_kk_init must be of shape (batch size, num head, head dim, head dim)." + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if h_kk_init is not None and h_kk_init.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {h_kk_init.shape[0]}.", + ) + if h_kv_init is not None and h_kv_init.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {h_kv_init.shape[0]}.", + ) + o, final_state_kk, final_state_kv = ChunkMesaNetFunction.apply( + q, + k, + v, + g, + beta, + lamb, + cu_seqlens, + max_CG_iteration, + h_kk_init, + h_kv_init, + output_final_state, + use_qk_l2norm_in_kernel, + ) + return o, final_state_kk, final_state_kv diff --git a/code/flash-linear-attention/fla/ops/mesa_net/chunk_cg_solver_bwd.py b/code/flash-linear-attention/fla/ops/mesa_net/chunk_cg_solver_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..2722c71699cbf43077453a0adde20d7b8f4fc3fc --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/chunk_cg_solver_bwd.py @@ -0,0 +1,160 @@ + +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp + + +@triton.jit() +def chunk_update_once( + b_p, + b_k, + b_v, + b_m, + b_g_exp_q, + b_h, + b_lamb, +): + b_o = tl.dot((tl.dot(b_p.to(b_k.dtype), tl.trans(b_k)) * b_m).to(b_v.dtype), b_v) + b_o += tl.dot((b_p * b_g_exp_q).to(b_h.dtype), b_h) + if b_lamb is not None: + b_o += b_lamb[None, :] * b_p + return b_o + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_mesa_cg_dim64_kernel( + dq, + dq_final, + k, + h, + g, + beta, + lamb, + cu_seqlens, + chunk_indices, + T, + max_CG_iteration: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + # offset calculation + dq += (bos * H + i_h) * K + dq_final += (bos * H + i_h) * K + k += (bos * H + i_h) * K + h += (i_tg * H + i_h).to(tl.int64) * K * K + + g += bos * H + i_h + beta += bos * H + i_h + lamb += i_h * K + + p_q = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_h = tl.make_block_ptr(h, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)).to(tl.float32) + + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)).to(tl.float32) + p_beta = tl.make_block_ptr(beta, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_beta = tl.load(p_beta, boundary_check=(0,)).to(tl.float32) + p_lamb = tl.make_block_ptr(lamb, (K,), (1,), (0,), (BK,), (0,)) + b_lamb = tl.load(p_lamb, boundary_check=(0,)).to(tl.float32) + + b_m = exp(b_g[:, None] - b_g[None, :]) * b_beta[None, :] + b_m = tl.where((o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t[None, :]), b_m, 0) + b_g_exp_q = tl.exp(b_g)[:, None] + + b_x = tl.zeros([BT, BK], dtype=tl.float32) + b_p = tl.zeros([BT, BK], dtype=tl.float32) + b_r = tl.zeros([BT, BK], dtype=tl.float32) + + b_x += b_q * 0. + b_r += b_q + b_p += b_q + b_delta_old = tl.sum(b_r*b_r, axis=1) + for _ in range(max_CG_iteration): + b_o = chunk_update_once(b_p, b_k, b_k, b_m, b_g_exp_q, b_h, b_lamb) + alpha = b_delta_old / (tl.sum(b_p*b_o, axis=1) + 1e-5) + b_x += alpha[:, None] * b_p + b_r = b_r - alpha[:, None] * b_o + b_delta_new = tl.sum(b_r*b_r, axis=1) + b_p = b_r + (b_delta_new / (b_delta_old + 1e-5))[:, None] * b_p + b_delta_old = b_delta_new + + p_q_final = tl.make_block_ptr(dq_final, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_q_final, b_x.to(p_q_final.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_mesa_cg_bwd( + dq: torch.Tensor, + k: torch.Tensor, + h: torch.Tensor, + g_local_cumsum: torch.Tensor, + beta: torch.Tensor, + lamb: torch.Tensor, # lambda + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + max_CG_iteration: int = 30, + output_dtype: torch.dtype | None = None, +) -> torch.Tensor: + B, T, H, K = dq.shape + assert K <= 128, "head dimension must be less than 128" + assert chunk_size <= 64 or K <= 64, "either chunk size or head dimension must be no greater than 64" + dq_final = torch.empty_like(dq, dtype=dq.dtype if output_dtype is None else output_dtype) + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, chunk_size) if cu_seqlens is None else len(chunk_indices) + BK = max(triton.next_power_of_2(K), 16) + grid = (NT, H*B) + + chunk_fwd_mesa_cg_dim64_kernel[grid]( + dq=dq, + dq_final=dq_final, + k=k, + h=h, + g=g_local_cumsum, + beta=beta, + lamb=lamb, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + max_CG_iteration=max_CG_iteration, + T=T, + H=H, + K=K, + BT=chunk_size, + BK=BK, + num_warps=4, + num_stages=1, + ) + return dq_final diff --git a/code/flash-linear-attention/fla/ops/mesa_net/chunk_cg_solver_fwd.py b/code/flash-linear-attention/fla/ops/mesa_net/chunk_cg_solver_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..38fa700f7022de30eb60ec9934d4105c5a98b4d6 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/chunk_cg_solver_fwd.py @@ -0,0 +1,184 @@ + +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp + + +@triton.jit() +def chunk_update_once( + b_p, + b_k, + b_v, + b_m, + b_g_exp_q, + b_h, + b_lamb, +): + b_o = tl.dot((tl.dot(b_p.to(b_k.dtype), tl.trans(b_k)) * b_m).to(b_v.dtype), b_v) + b_o += tl.dot((b_p * b_g_exp_q).to(b_h.dtype), b_h) + if b_lamb is not None: + b_o += b_lamb[None, :] * b_p + return b_o + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_mesa_cg_dim64_kernel( + q, + q_final, + k, + h, + o, + v, + h_kv, + g, + beta, + lamb, + cu_seqlens, + chunk_indices, + T, + max_CG_iteration: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + # offset calculation + q += (bos * H + i_h) * K + q_final += (bos * H + i_h) * K + k += (bos * H + i_h) * K + h += (i_tg * H + i_h).to(tl.int64) * K * K + g += bos * H + i_h + beta += bos * H + i_h + lamb += i_h * K + + o += (bos * H + i_h) * K + v += (bos * H + i_h) * K + h_kv += (i_tg * H + i_h).to(tl.int64) * K * K + + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_h = tl.make_block_ptr(h, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)).to(tl.float32) + + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)).to(tl.float32) + p_beta = tl.make_block_ptr(beta, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_beta = tl.load(p_beta, boundary_check=(0,)).to(tl.float32) + p_lamb = tl.make_block_ptr(lamb, (K,), (1,), (0,), (BK,), (0,)) + + b_lamb = tl.load(p_lamb, boundary_check=(0,)).to(tl.float32) + + b_m = exp(b_g[:, None] - b_g[None, :]) * b_beta[None, :] + b_m = tl.where((o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t[None, :]), b_m, 0) + b_g_exp_q = tl.exp(b_g)[:, None] + + b_x = tl.zeros([BT, BK], dtype=tl.float32) + b_p = tl.zeros([BT, BK], dtype=tl.float32) + b_r = tl.zeros([BT, BK], dtype=tl.float32) + + b_x += b_q * 0. + b_r += b_q + b_p += b_r + b_delta_old = tl.sum(b_r*b_r, axis=1) + for i in range(max_CG_iteration): + b_o = chunk_update_once(b_p, b_k, b_k, b_m, b_g_exp_q, b_h, b_lamb) + alpha = b_delta_old / (tl.sum(b_p*b_o, axis=1) + 1e-5) + b_x += alpha[:, None] * b_p + b_r = b_r - alpha[:, None] * b_o + b_delta_new = tl.sum(b_r*b_r, axis=1) + b_p = b_r + (b_delta_new / (b_delta_old + 1e-5))[:, None] * b_p + b_delta_old = b_delta_new + + p_q_final = tl.make_block_ptr(q_final, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_q_final, b_x.to(p_q_final.dtype.element_ty), boundary_check=(0, 1)) + + p_h_kv = tl.make_block_ptr(h_kv, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + b_h_kv = tl.load(p_h_kv, boundary_check=(0, 1)) + p_v = tl.make_block_ptr(v, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_o = chunk_update_once(b_x, b_k, b_v, b_m, b_g_exp_q, b_h_kv, None) + p_o = tl.make_block_ptr(o, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_mesa_cg_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + h_kv: torch.Tensor, + g_local_cumsum: torch.Tensor, + beta: torch.Tensor, + lamb: torch.Tensor, # lambda + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + max_CG_iteration: int = 30, + output_dtype: torch.dtype | None = None, +) -> torch.Tensor: + B, T, H, K = q.shape + assert K <= 128, "head dimension must be less than 128" + assert chunk_size <= 64 or K <= 64, "either chunk size or head dimension must be no greater than 64" + q_final = torch.empty_like(q, dtype=q.dtype if output_dtype is None else output_dtype) + + assert v is not None, "v must be provided if calculate_output is True" + assert h_kv is not None, "h_kv must be provided if calculate_output is True" + o = torch.empty_like(v) + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, chunk_size) if cu_seqlens is None else len(chunk_indices) + BK = max(triton.next_power_of_2(K), 16) + grid = (NT, H*B) + + chunk_fwd_mesa_cg_dim64_kernel[grid]( + q=q, + q_final=q_final, + o=o, + v=v, + h_kv=h_kv, + k=k, + h=h, + g=g_local_cumsum, + beta=beta, + lamb=lamb, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + max_CG_iteration=max_CG_iteration, + T=T, + H=H, + K=K, + BT=chunk_size, + BK=BK, + num_warps=4, + num_stages=1, + ) + return q_final, o diff --git a/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_fwd.py b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..119f86c00db4e393f97a08baafca011c39234bbb --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_fwd.py @@ -0,0 +1,170 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h_init'] is not None, + 'STORE_FINAL_STATE': lambda args: args['h_final'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_mesa_net_fwd_kernel_h( + k, + v, + beta, + g, + h, + h_kv, + h_init, + h_kv_init, + h_final, + h_kv_final, + cu_seqlens, + split_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + NS = tl.cdiv(T, BS) + boh = tl.load(split_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + NS = tl.cdiv(T, BS) + boh = i_n * NS + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + b_h_kv = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h_init + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1)).to(tl.float32) + p_h_kv0 = tl.make_block_ptr(h_kv_init + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h_kv = tl.load(p_h_kv0, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(NT): + i_s = i_t // (BS // BT) + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k2 = tl.make_block_ptr(k + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_beta = tl.make_block_ptr(beta + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT, ), (0,)) + b_beta = tl.load(p_beta, boundary_check=(0,)) + + o_h = ((boh + i_s) * H + i_h).to(tl.int64) * K*V + p_h = tl.make_block_ptr(h + o_h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_h_kv = tl.make_block_ptr(h_kv + o_h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + if i_t % (BS // BT) == 0: + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_h_kv, b_h_kv.to(p_h_kv.dtype.element_ty), boundary_check=(0, 1)) + + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_k2 = tl.load(p_k2, boundary_check=(0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + last_idx = min((i_t + 1) * BT, T) - 1 + + # scalar decay + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_g = g + bos*H + (i_t * BT + tl.arange(0, BT)) * H + i_h + b_h *= exp(b_g_last) + b_h_kv *= exp(b_g_last) + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_k_decay = ((b_k * exp(b_g_last - b_g)[:, None]) * b_beta[:, None]).to(b_k2.dtype) + b_h += tl.dot(tl.trans(b_k_decay), b_k2) + b_h_kv += tl.dot(tl.trans(b_k_decay), b_v.to(b_k2.dtype)) + + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(h_final + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + p_h_kv_final = tl.make_block_ptr(h_kv_final + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_h_kv_final, b_h_kv.to(p_h_kv_final.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_mesa_fwd_h( + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + h_init: torch.Tensor, + h_kv_init: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + split_size: int | None = None, + states_in_fp32: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + assert K == V, "K must be equal to V for now" + BT = chunk_size + BS = BT if split_size is None else split_size + assert BS % BT == 0, f"The `split_size` (got {BS}) must be a multiple of `chunk_size` {BT}" + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NS, split_offsets = B, triton.cdiv(T, BS), None + else: + split_offsets = prepare_chunk_offsets(cu_seqlens, BS) + N, NS = len(cu_seqlens) - 1, split_offsets[-1].item() + + h = k.new_empty(B, NS, H, K, V, dtype=k.dtype if not states_in_fp32 else torch.float) + h_kv = k.new_empty(B, NS, H, K, V, dtype=k.dtype if not states_in_fp32 else torch.float) + h_final = k.new_empty(N, H, K, V, dtype=torch.float) if output_final_state else None + h_kv_final = k.new_empty(N, H, K, V, dtype=torch.float) + + def grid(meta): return (triton.cdiv(K, 64), triton.cdiv(V, 64), N * H) + + chunk_mesa_net_fwd_kernel_h[grid]( + k=k, + v=v, + beta=beta, + g=g, + h=h, + h_kv=h_kv, + h_init=h_init, + h_kv_init=h_kv_init, + h_final=h_final, + h_kv_final=h_kv_final, + cu_seqlens=cu_seqlens, + split_offsets=split_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + BK=64, + BV=64, + ) + return h, h_kv, h_final, h_kv_final diff --git a/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kk_intra_bwd.py b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kk_intra_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..44590e80d08b295a5631fe9cb4abe1be55ab8796 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kk_intra_bwd.py @@ -0,0 +1,189 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def chunk_mesa_net_h_kk_bwd_intra_kernel( + k, + beta, + h, + dh, + g, + q_star, + dq, + dk, + dg, + dbeta, + dk_beta, + dlamb, + cu_seqlens, + chunk_indices, + B: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + # offset calculation + q_star += (bos * H + i_h) * V + dq += (bos * H + i_h) * V + h += (i_tg * H + i_h).to(tl.int64) * K*V + dh += (i_tg * H + i_h).to(tl.int64) * K*V + k += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + dk_beta += (bos * H + i_h) * K + dlamb += (i_tg * H + i_h).to(tl.int64) * K + beta += (bos * H + i_h) + dbeta += (bos * H + i_h) + g += bos * H + i_h + dg += bos * H + i_h + + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dv = tl.zeros([BT, BK], dtype=tl.float32) + b_dbeta = tl.zeros([BT ], dtype=tl.float32) + b_dg_last = tl.zeros([1], dtype=tl.float32) + b_dg = tl.zeros([BT], dtype=tl.float32) + + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_last = tl.load(g + (min(i_t * BT + BT, T) - 1) * H) + b_gk = tl.where(m_t, exp(b_g_last - b_g), 0) + + p_q_star = tl.make_block_ptr(q_star, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + b_q_star = tl.load(p_q_star, boundary_check=(0, 1)) + p_dq = tl.make_block_ptr(dq, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + b_dq = tl.load(p_dq, boundary_check=(0, 1)) + b_dlamb = -tl.sum(b_q_star * b_dq, axis=0) + p_dlamb = tl.make_block_ptr(dlamb, (K,), (1,), (0,), (BK,), (0,)) + tl.store(p_dlamb, b_dlamb.to(p_dlamb.dtype.element_ty), boundary_check=(0,)) + + p_h = tl.make_block_ptr(h, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + p_beta = tl.make_block_ptr(beta, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_beta = tl.load(p_beta, boundary_check=(0,)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_v = tl.load(p_k, boundary_check=(0, 1)) + b_k = (b_v * b_beta[:, None]).to(b_v.dtype) + + b_m = tl.where((o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t[None, :]), exp(b_g[:, None] - b_g[None, :]), 0) + b_s = tl.dot(b_q_star, tl.trans(b_k)) * b_m + b_ds = tl.dot(b_dq, tl.trans(b_v)) + b_dv += tl.dot(tl.trans(b_s.to(b_dq.dtype)), b_dq) + b_dm = b_s * b_ds + b_dm = tl.where(tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :], b_dm, 0) + b_dg += tl.sum(b_dm, axis=1) + b_dg -= tl.sum(b_dm, axis=0) + b_ds = b_ds * b_m + b_dk += tl.dot(tl.trans(b_ds.to(b_q_star.dtype)), b_q_star) + + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dg += tl.sum(tl.dot(b_dq, tl.trans(b_h)) * tl.exp(b_g)[:, None] * b_q_star, axis=1) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_dk2 = tl.dot(b_v, b_dh.to(b_v.dtype)) * b_gk[:, None] + b_dg -= tl.sum(b_dk2 * b_k, axis=1) + b_dg_last += tl.sum(b_dk2 * b_k) + b_dk += b_dk2 + b_dv += tl.dot(b_k, tl.trans(b_dh).to(b_k.dtype)) * b_gk[:, None] + b_dh = b_dh * b_h + b_dg_last += tl.sum(b_dh) * exp(b_g_last) + + p_dk_beta = tl.make_block_ptr(dk_beta, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_dk -= tl.load(p_dk_beta, boundary_check=(0, 1)) + b_dbeta = tl.sum(b_dk * b_v, axis=1) + b_dk = b_dk * b_beta[:, None] + b_dv + b_dk = -b_dk + + b_dg = tl.where(o_t < min(i_t * BT + BT, T) - 1, b_dg, b_dg + b_dg_last) + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + p_dg = tl.make_block_ptr(dg, (T,), (H,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dg, -b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + p_dbeta = tl.make_block_ptr(dbeta, (T,), (H,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dbeta, -b_dbeta.to(p_dbeta.dtype.element_ty), boundary_check=(0,)) + + +def chunk_mesa_net_h_kk_bwd_intra_fn( + k: torch.Tensor, + beta: torch.Tensor, + g: torch.Tensor, + h: torch.Tensor, + dh: torch.Tensor, + q_star: torch.Tensor, + dq: torch.Tensor, + dk_beta: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + + B, T, H, K = k.shape + V = K + BT = min(chunk_size, max(16, triton.next_power_of_2(T))) + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + # CONST_TILING = 64 + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + dk = torch.empty_like(k) + dg = torch.empty_like(g) + dbeta = torch.empty_like(beta) + dlamb = torch.empty(B, NT, H, K, dtype=torch.float32, device=k.device) + grid = (NT, B * H) + + chunk_mesa_net_h_kk_bwd_intra_kernel[grid]( + k=k, + h=h, + dh=dh, + g=g, + q_star=q_star, + beta=beta, + dbeta=dbeta, + dq=dq, + dk=dk, + dk_beta=dk_beta, + dg=dg, + dlamb=dlamb, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + dlamb = dlamb.sum([0, 1]) + return dk, dg, dlamb, dbeta diff --git a/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kv_intra_bwd.py b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kv_intra_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..807d014bcc74964f841c10a22e922abccb9233dc --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kv_intra_bwd.py @@ -0,0 +1,209 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import torch +import triton +import triton.language as tl + +from fla.ops.mesa_net.chunk_h_kv_intra_bwd_separate import chunk_mesa_net_h_kv_bwd_intra_separate_fn +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, check_shared_mem, is_nvidia_hopper + +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_mesa_net_h_kv_bwd_intra_kernel( + q_star, + k, + v, + beta, + h_kv, + g, + do, + dh_kv, + dq, + dk_beta, + dg, + dv, + cu_seqlens, + chunk_indices, + B: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + # offset calculation + v += (bos * H + i_h) * V + do += (bos * H + i_h) * V + h_kv += (i_tg * H + i_h).to(tl.int64) * K*V + dh_kv += (i_tg * H + i_h).to(tl.int64) * K*V + q_star += (bos * H + i_h) * K + k += (bos * H + i_h) * K + beta += (bos * H + i_h) + g += bos * H + i_h + dg += bos * H + i_h + dq += (bos * H + i_h) * K + dk_beta += (bos * H + i_h) * K + dv += (bos * H + i_h) * V + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_ds = tl.zeros([BT, BT], dtype=tl.float32) + b_dv = tl.zeros([BT, BK], dtype=tl.float32) + b_dg_last = tl.zeros([1], dtype=tl.float32) + b_dg = tl.zeros([BT], dtype=tl.float32) + + p_q = tl.make_block_ptr(q_star, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_beta = tl.make_block_ptr(beta, (T, ), (H, ), (i_t * BT,), (BT,), (0,)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h_kv, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh_kv, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_beta = tl.load(p_beta, boundary_check=(0, )) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_g_last = tl.load(g + (min(i_t * BT + BT, T) - 1) * H) + + # calculation + b_dg_last += tl.sum(b_h * b_dh) + b_dg_last *= exp(b_g_last) + + b_m = tl.where((o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t[None, :]), exp(b_g[:, None] - b_g[None, :]), 0) + b_k = (b_k * b_beta[:, None]).to(b_k.dtype) + b_s = tl.dot(b_q, tl.trans(b_k)) * b_m + + b_ds = tl.dot(b_do, tl.trans(b_v)) + b_dm = b_s * b_ds + b_dm = tl.where(tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :], b_dm, 0) + + b_dg += tl.sum(b_dm, axis=1) + b_dg -= tl.sum(b_dm, axis=0) + + b_g_exp_q = exp(b_g) + b_g_exp_k = tl.where(m_t, exp(-b_g + b_g_last), 0) + b_ds = b_ds * b_m + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) * b_g_exp_q[:, None] + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) * b_g_exp_k[:, None] + b_dg_last += tl.sum(b_dk * b_k) + b_dg -= tl.sum(b_dk * b_k, axis=1) + b_dg += tl.sum(b_dq * b_q, axis=1) + b_dq += tl.dot(b_ds.to(b_k.dtype), b_k) + b_dv += tl.dot(b_k, tl.trans(b_dh).to(b_k.dtype)) * b_g_exp_k[:, None] + tl.dot(tl.trans(b_s.to(b_do.dtype)), b_do) + b_dk += tl.dot(tl.trans(b_ds.to(b_q.dtype)), b_q) + + b_dg = tl.where(o_t < min(i_t * BT + BT, T) - 1, b_dg, b_dg + b_dg_last) + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk_beta, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_dg = tl.make_block_ptr(dg, (T,), (H,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + +def chunk_mesa_net_h_kv_bwd_intra_fn( + q_star, + k, + v, + beta, + h_kv, + dh_kv, + g, + do, + cu_seqlens, + chunk_size=64, +): + # share memory is not large enough for a single fused kernel + if not check_shared_mem('ampere'): + return chunk_mesa_net_h_kv_bwd_intra_separate_fn( + q_star=q_star, + k=k, + v=v, + beta=beta, + h_kv=h_kv, + dh_kv=dh_kv, + g=g, + do=do, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + dq = torch.empty_like(q_star, dtype=torch.float32) + dk = torch.empty_like(k) + dv = torch.empty_like(v) + dg = torch.empty_like(g) + grid = (NT, B * H) + chunk_mesa_net_h_kv_bwd_intra_kernel[grid]( + q_star=q_star, + k=k, + v=v, + beta=beta, + h_kv=h_kv, + g=g, + do=do, + dh_kv=dh_kv, + dq=dq, + dk_beta=dk, + dg=dg, + dv=dv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dq, dk, dv, dg diff --git a/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kv_intra_bwd_separate.py b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kv_intra_bwd_separate.py new file mode 100644 index 0000000000000000000000000000000000000000..f3ba755449518f088761898bc2e2e4abc402d5e2 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/chunk_h_kv_intra_bwd_separate.py @@ -0,0 +1,301 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, is_nvidia_hopper + +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_mesa_net_h_kv_bwd_intra_kernel_dkv( + q_star, + k, + v, + beta, + h_kv, + g, + do, + dh_kv, + dk_beta, + dg, + dv, + cu_seqlens, + chunk_indices, + B: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + # offset calculation + v += (bos * H + i_h) * V + do += (bos * H + i_h) * V + h_kv += (i_tg * H + i_h).to(tl.int64) * K*V + dh_kv += (i_tg * H + i_h).to(tl.int64) * K*V + q_star += (bos * H + i_h) * K + k += (bos * H + i_h) * K + beta += (bos * H + i_h) + g += bos * H + i_h + dg += bos * H + i_h + dk_beta += (bos * H + i_h) * K + dv += (bos * H + i_h) * V + + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_ds = tl.zeros([BT, BT], dtype=tl.float32) + b_dv = tl.zeros([BT, BK], dtype=tl.float32) + b_dg_last = tl.zeros([1], dtype=tl.float32) + b_dg = tl.zeros([BT], dtype=tl.float32) + + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_beta = tl.make_block_ptr(beta, (T, ), (H, ), (i_t * BT,), (BT,), (0,)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h_kv, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh_kv, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + p_q = tl.make_block_ptr(q_star, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_beta = tl.load(p_beta, boundary_check=(0, )) + b_g = tl.load(p_g, boundary_check=(0,)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + b_g_last = tl.load(g + (min(i_t * BT + BT, T) - 1) * H) + + # calculation + b_dg_last += tl.sum(b_h * b_dh) + b_dg_last *= exp(b_g_last) + + b_m = tl.where((o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t[None, :]), exp(b_g[:, None] - b_g[None, :]), 0) + b_k = (b_k * b_beta[:, None]).to(b_k.dtype) + b_s = tl.dot(b_q, tl.trans(b_k)) * b_m + b_ds = tl.dot(b_do, tl.trans(b_v)) + b_dm = b_s * b_ds + b_dm = tl.where(tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :], b_dm, 0) + b_dg += tl.sum(b_dm, axis=1) + b_dg -= tl.sum(b_dm, axis=0) + b_g_exp_k = tl.where(m_t, exp(-b_g + b_g_last), 0) + b_ds = b_ds * b_m + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) * b_g_exp_k[:, None] + b_dg_last += tl.sum(b_dk * b_k) + b_dg -= tl.sum(b_dk * b_k, axis=1) + b_dv += tl.dot(b_k, tl.trans(b_dh).to(b_k.dtype)) * b_g_exp_k[:, None] + tl.dot(tl.trans(b_s.to(b_do.dtype)), b_do) + b_dk += tl.dot(tl.trans(b_ds.to(b_q.dtype)), b_q) + b_dg = tl.where(o_t < min(i_t * BT + BT, T) - 1, b_dg, b_dg + b_dg_last) + p_dk = tl.make_block_ptr(dk_beta, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_dg = tl.make_block_ptr(dg, (T,), (H,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_mesa_net_h_kv_bwd_intra_kernel_dq( + q_star, + k, + v, + beta, + h_kv, + g, + do, + dq, + dg_prev, + dg, + cu_seqlens, + chunk_indices, + B: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + # offset calculation + v += (bos * H + i_h) * V + do += (bos * H + i_h) * V + h_kv += (i_tg * H + i_h).to(tl.int64) * K*V + q_star += (bos * H + i_h) * K + k += (bos * H + i_h) * K + beta += (bos * H + i_h) + g += bos * H + i_h + dg_prev += bos * H + i_h + dg += bos * H + i_h + dq += (bos * H + i_h) * K + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_beta = tl.make_block_ptr(beta, (T, ), (H, ), (i_t * BT,), (BT,), (0,)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h_kv, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + p_q = tl.make_block_ptr(q_star, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_dg_prev = tl.make_block_ptr(dg_prev, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_dg = tl.make_block_ptr(dg, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_beta = tl.load(p_beta, boundary_check=(0, )) + b_g = tl.load(p_g, boundary_check=(0,)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_h = tl.load(p_h, boundary_check=(0, 1)) + + b_m = tl.where((o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t[None, :]), exp(b_g[:, None] - b_g[None, :]), 0) + b_k = (b_k * b_beta[:, None]).to(b_k.dtype) + + b_ds = tl.dot(b_do, tl.trans(b_v)) * b_m + b_g_exp_q = exp(b_g) + b_dq = tl.dot(b_do, b_h.to(b_do.dtype)) * b_g_exp_q[:, None] + b_dg = tl.sum(b_dq * b_q, axis=1) + tl.load(p_dg_prev, boundary_check=(0,)) + b_dq += tl.dot(b_ds.to(b_k.dtype), b_k) + + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + +def chunk_mesa_net_h_kv_bwd_intra_separate_fn( + q_star, + k, + v, + beta, + h_kv, + dh_kv, + g, + do, + cu_seqlens, + chunk_size=64, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + dq = torch.empty_like(q_star, dtype=torch.float32) + dk = torch.empty_like(k) + dv = torch.empty_like(v) + dg = torch.empty_like(g) + grid = (NT, B * H) + chunk_mesa_net_h_kv_bwd_intra_kernel_dkv[grid]( + q_star=q_star, + k=k, + v=v, + beta=beta, + h_kv=h_kv, + g=g, + do=do, + dh_kv=dh_kv, + dk_beta=dk, + dg=dg, + dv=dv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + dg_final = torch.empty_like(dg) + chunk_mesa_net_h_kv_bwd_intra_kernel_dq[grid]( + q_star=q_star, + k=k, + v=v, + beta=beta, + h_kv=h_kv, + g=g, + do=do, + dg=dg_final, + dg_prev=dg, + dq=dq, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dq, dk, dv, dg_final diff --git a/code/flash-linear-attention/fla/ops/mesa_net/decoding_one_step.py b/code/flash-linear-attention/fla/ops/mesa_net/decoding_one_step.py new file mode 100644 index 0000000000000000000000000000000000000000..b05700df47ccb16ce1559684d7c74d69552d8236 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/decoding_one_step.py @@ -0,0 +1,174 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import input_guard + + +@triton.jit +def mesa_net_decoding_one_step_kernel( + q, + k, + v, + g, + o, + lamb, + beta, + prev_h_kk, + prev_h_kv, + curr_h_kk, + curr_h_kv, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + MAX_CG_STEP: tl.constexpr, +): + i_nh = tl.program_id(0) + i_h = i_nh % H + + o_k = tl.arange(0, BK) + o_v = tl.arange(0, BV) + + p_q = q + i_nh * K + o_k + p_k = k + i_nh * K + o_k + p_v = v + i_nh * V + o_v + p_beta = beta + i_nh + p_g = g + i_nh + p_lamb = lamb + i_h * K + o_k + + b_g = exp(tl.load(p_g).to(tl.float32)) + b_beta = tl.load(p_beta).to(tl.float32) + + mask_k = o_k < K + mask_v = o_v < V + mask_kk = mask_k[:, None] & mask_k[None, :] + mask_kv = mask_k[:, None] & mask_v[None, :] + + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) + b_lamb = tl.load(p_lamb, mask=mask_k, other=0).to(tl.float32) + + p_hkk_prev = prev_h_kk + i_nh * K * K + o_k[:, None] * K + o_k[None, :] + b_h_kk = tl.load(p_hkk_prev, mask=mask_kk, other=0).to(tl.float32) + + b_h_kk = b_h_kk * b_g + (b_k * b_beta)[:, None] * b_k[None, :] + + p_hkk_curr = curr_h_kk + i_nh * K * K + o_k[:, None] * K + o_k[None, :] + tl.store(p_hkk_curr, b_h_kk.to(p_hkk_curr.dtype.element_ty), mask=mask_kk) + + p_hkv_prev = prev_h_kv + i_nh * K * V + o_k[:, None] * V + o_v[None, :] + b_h_kv = tl.load(p_hkv_prev, mask=mask_kv, other=0).to(tl.float32) + b_h_kv = b_h_kv * b_g + (b_k * b_beta)[:, None] * b_v[None, :] + p_hkv_curr = curr_h_kv + i_nh * K * V + o_k[:, None] * V + o_v[None, :] + tl.store(p_hkv_curr, b_h_kv.to(p_hkv_curr.dtype.element_ty), mask=mask_kv) + + diag_mask = tl.arange(0, BK)[:, None] == tl.arange(0, BK)[None, :] + diag_mask = diag_mask & mask_kk + b_h_kk_diag = tl.sum(tl.where(diag_mask, b_h_kk, 0.0), axis=1) + + b_x = b_q / (b_h_kk_diag + b_lamb + 1e-5) + b_Hx = tl.sum(b_h_kk * b_x[:, None], axis=0) + b_r = b_q - b_Hx - b_lamb * b_x + b_p = tl.zeros([BK], dtype=tl.float32) + b_p += b_r + delta_old = tl.sum(b_r * b_r) + + for i_iter in range(MAX_CG_STEP): + b_Ap = tl.sum(b_h_kk * b_p[:, None], axis=0) + b_lamb * b_p + pAp = tl.sum(b_p * b_Ap) + alpha = delta_old / (pAp + 1e-5) + b_x = b_x + alpha * b_p + b_r = b_r - alpha * b_Ap + delta_new = tl.sum(b_r * b_r) + beta_cg = delta_new / (delta_old + 1e-5) + b_p = b_r + beta_cg * b_p + delta_old = delta_new + b_o = tl.sum(b_h_kv * b_x[:, None], axis=0) + p_o = o + i_nh * V + o_v + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + + +@input_guard +def mesa_net_decoding_one_step( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + lamb: torch.Tensor, + beta: torch.Tensor, + prev_h_kk: torch.Tensor, + prev_h_kv: torch.Tensor, + max_CG_iteration: int = 30, +): + """ + Triton implementation of Mesa Net CG one step + + Args: + q (torch.Tensor): + query tensor [B, H, K] + k (torch.Tensor): + key tensor [B, H, K] + v (torch.Tensor): + value tensor [B, H, V] + g (torch.Tensor): + gate tensor [B, H] + lamb (torch.Tensor): + lambda tensor [H, K] + beta (torch.Tensor): + beta tensor [B, H] + prev_h_kk (torch.Tensor): + previous hidden state KK [B, H, K, K] + prev_h_kv (torch.Tensor): + previous hidden state KV [B, H, K, V] + max_CG_iteration (int): + maximum CG iterations + + Returns: + o (torch.Tensor): + output tensor [B, H, V] + h_kk_new (torch.Tensor): + updated hidden state KK [B, H, K, K] + h_kv_new (torch.Tensor): + updated hidden state KV [B, H, K, V] + """ + B, H, K, V = *q.shape, v.shape[-1] + + o = torch.empty((B, H, V), dtype=q.dtype, device=q.device) + curr_h_kk = torch.empty_like(prev_h_kk) + curr_h_kv = torch.empty_like(prev_h_kv) + + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + + assert BK <= 128 and BV <= 128, "BK and BV must be less than or equal to 128" + + grid = (B * H,) + mesa_net_decoding_one_step_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + o=o, + lamb=lamb, + beta=beta, + prev_h_kk=prev_h_kk, + prev_h_kv=prev_h_kv, + curr_h_kk=curr_h_kk, + curr_h_kv=curr_h_kv, + B=B, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + MAX_CG_STEP=max_CG_iteration, + num_warps=4 if BK <= 64 else 8, + ) + return o, curr_h_kk, curr_h_kv diff --git a/code/flash-linear-attention/fla/ops/mesa_net/naive.py b/code/flash-linear-attention/fla/ops/mesa_net/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..9e7b0843342a682da2b9de189ab7251a4d27d7a1 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/mesa_net/naive.py @@ -0,0 +1,130 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import torch +from einops import rearrange + + +def naive_mesa_net_decoding_one_step(q, k, v, g, lamb, beta, prev_h_kk, prev_h_kv, max_CG_iteration=30): + q = q.float().clone() + k = k.float().clone() + v = v.float().clone() + g = g.float().clone() + lamb = lamb.float().clone() + beta = beta.float().clone() + B, h, d = q.shape + k_beta = k * beta.unsqueeze(-1) + + h_kk = prev_h_kk * g.exp()[..., None, None] + k_beta.unsqueeze(-1) * k.unsqueeze(-2) + h_kv = prev_h_kv * g.exp()[..., None, None] + k_beta.unsqueeze(-1) * v.unsqueeze(-2) + diag_H = torch.diagonal(h_kk, dim1=-2, dim2=-1) + lamb = lamb.unsqueeze(0) + x = q / (diag_H + lamb) + r = q - (x.unsqueeze(-1) * h_kk).sum(-2) - (lamb * x) + p = r.clone() + delta_old = (r * r).sum(-1) + # CG iteration + for i in range(max_CG_iteration): + q = (p.unsqueeze(-1) * h_kk).sum(-2) + (lamb * p) + alpha = (delta_old / ((p * q).sum(-1) + 1e-5)) + x = x + (alpha[..., None] * p) + r = r - (alpha[..., None] * q) + delta_new = (r * r).sum(-1) + beta = delta_new / (delta_old + 1e-5) + p = r + (beta[..., None] * p) + delta_old = delta_new + o = (x.unsqueeze(-1) * h_kv).sum(-2) + return o, h_kk, h_kv + + +def naive_mesa_net_exact(q, k, v, g, lamb, beta, h_kk_init=None, h_kv_init=None): + B, L, h, d = q.shape + q = q.float() + k = k.float() + v = v.float() + g = g.float() + lamb = lamb.float() + beta = beta.float() + + h_kk = h_kk_init.clone() if h_kk_init is not None else torch.zeros(B, h, d, d, device=q.device) + h_kv = h_kv_init.clone() if h_kv_init is not None else torch.zeros(B, h, d, d, device=q.device) + + h_kk_all = torch.zeros(B, L, h, d, d, device=q.device) + h_kv_all = torch.zeros(B, L, h, d, d, device=q.device) + for i in range(L): + h_kk = h_kk * g[:, i, :, None, None].exp() + (k[:, i, :, :] * beta[:, i, :, None] + )[..., None] * k[:, i, :, None, :] + h_kv = h_kv * g[:, i, :, None, None].exp() + (k[:, i, :, :] * beta[:, i, :, None] + )[..., None] * v[:, i, :, None, :] + h_kk_all[:, i] = h_kk + h_kv_all[:, i] = h_kv + + q_star_gold = torch.linalg.solve(h_kk_all + torch.diag_embed(lamb)[None, None, ...], q) + o_gold = (q_star_gold[..., :, None] * h_kv_all).sum(-2) + return o_gold, h_kk, h_kv + + +def naive_mesa_net_CG(q, k, v, g, lamb, beta, chunk_size, max_CG_iteration=30, h_kk_init=None, h_kv_init=None): + B, L, h, d = q.shape + C = chunk_size + + def chunk_fn(x): return rearrange(x, 'b (n c) h ... -> b h n c ...', c=C).float() + + q_chunk, k_chunk, v_chunk, g_chunk, beta_chunk = map(chunk_fn, [q, k, v, g, beta]) + + g_chunk = g_chunk.cumsum(dim=-1) + + pairwise_decay = (g_chunk[..., None] - g_chunk[..., None, :]).exp().tril() * beta_chunk[..., None, :] + + num_chunks = q_chunk.shape[2] + + h_kv_all = torch.zeros(B, h, num_chunks, d, d, device=q.device) + h_kk_all = torch.zeros(B, h, num_chunks, d, d, device=q.device) + + h_kv = torch.zeros(B, h, d, d, device=q.device) + h_kk = torch.zeros(B, h, d, d, device=q.device) + + if h_kk_init is not None: + h_kk += h_kk_init + if h_kv_init is not None: + h_kv += h_kv_init + + chunk_decay_k = (g_chunk[..., -1, None] - g_chunk).exp() + chunk_decay_q = g_chunk.exp() + + k_chunk_processed = k_chunk * chunk_decay_k[..., None] * beta_chunk[..., None] + + for i in range(num_chunks): + h_kv_all[:, :, i, :, :] = h_kv + h_kk_all[:, :, i, :, :] = h_kk + + k_chunk_i = k_chunk[:, :, i, :, :] + v_chunk_i = v_chunk[:, :, i, :, :] + k_chunk_i_processed = k_chunk_processed[:, :, i, :, :] + + h_kk = h_kk * g_chunk[:, :, i, -1, None, None].exp() + (k_chunk_i_processed).transpose(-2, -1) @ k_chunk_i + h_kv = h_kv * g_chunk[:, :, i, -1, None, None].exp() + (k_chunk_i_processed).transpose(-2, -1) @ v_chunk_i + + # CG solver to approximate the matrix inverse solution. + # diag_H = torch.diagonal(h_kk_all, dim1=-2, dim2=-1) + lamb = lamb[None, :, None, None, :] + x = torch.zeros_like(q_chunk) + r = q_chunk - (x * chunk_decay_q[..., None]) @ h_kk_all - ((x @ k_chunk.transpose(-2, -1)) + * pairwise_decay) @ k_chunk - (lamb * x) + p = r.clone() + delta_old = (r * r).sum(-1) + + # CG iteration + for i in range(max_CG_iteration): + q = (p * chunk_decay_q[..., None]) @ h_kk_all + ((p @ k_chunk.transpose(-1, -2)) + * pairwise_decay) @ k_chunk + (lamb * p) + alpha = (delta_old / ((p * q).sum(-1) + 1e-5)) + x = x + (alpha[..., None] * p) + r = r - (alpha[..., None] * q) + delta_new = (r * r).sum(-1) + beta = delta_new / (delta_old + 1e-5) + p = r + (beta[..., None] * p) + delta_old = delta_new + + o = (x * chunk_decay_q[..., None]) @ h_kv_all + ((x @ k_chunk.transpose(-1, -2)) + * pairwise_decay) @ v_chunk + return rearrange(o, 'b h n c d -> b (n c) h d'), h_kk, h_kv diff --git a/code/flash-linear-attention/fla/ops/nsa/__init__.py b/code/flash-linear-attention/fla/ops/nsa/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1a61b433234397578b12773138afb6d55e200e0e --- /dev/null +++ b/code/flash-linear-attention/fla/ops/nsa/__init__.py @@ -0,0 +1,8 @@ + +from .naive import naive_nsa +from .parallel import parallel_nsa + +__all__ = [ + 'naive_nsa', + 'parallel_nsa', +] diff --git a/code/flash-linear-attention/fla/ops/nsa/compression.py b/code/flash-linear-attention/fla/ops/nsa/compression.py new file mode 100644 index 0000000000000000000000000000000000000000..791d30af24ca9db08746b19841d5620a2a3d7f20 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/nsa/compression.py @@ -0,0 +1,537 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.attn.parallel import parallel_attn_bwd_preprocess +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets, prepare_token_indices +from fla.ops.utils.op import exp, log +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, check_shared_mem, contiguous + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BS', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit +def parallel_nsa_compression_fwd_kernel( + q, + k, + v, + o, + lse, + scale, + cu_seqlens, + token_indices, + chunk_offsets, + T, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BC: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_v, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_n, i_t = tl.load(token_indices + i_t * 2).to(tl.int32), tl.load(token_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + boc = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + boc = i_b * tl.cdiv(T, BS) + + p_q = tl.make_block_ptr(q + (bos + i_t) * HQ*K, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + + # the Q block is kept in the shared memory throughout the whole kernel + # [G, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + # the number of compression representations in total + TC = tl.cdiv(T, BS) + # the number of compression representations required to iterate over + # incomplete compression blocks are not included + NC = (i_t + 1) // BS + + p_o = tl.make_block_ptr(o + (bos + i_t) * HQ*V, (HQ, V), (V, 1), (i_h * G, i_v * BV), (G, BV), (1, 0)) + # [G, BV] + b_o = tl.zeros([G, BV], dtype=tl.float32) + # max scores for the current block + b_m = tl.full([G], float('-inf'), dtype=tl.float32) + # lse = log(acc) + m + b_acc = tl.zeros([G], dtype=tl.float32) + + for i_c in range(0, NC, BC): + o_c = i_c + tl.arange(0, BC) + + p_k = tl.make_block_ptr(k + (boc * H + i_h) * K, (K, TC), (1, H*K), (0, i_c), (BK, BC), (0, 1)) + p_v = tl.make_block_ptr(v + (boc * H + i_h) * V, (TC, V), (H*V, 1), (i_c, i_v * BV), (BC, BV), (1, 0)) + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BC, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [G, BC] + b_s = tl.dot(b_q, b_k) + b_s = tl.where((o_c < NC)[None, :], b_s, float('-inf')) + + # [G] + b_m, b_mp = tl.maximum(b_m, tl.max(b_s, 1)), b_m + b_r = exp(b_mp - b_m) + # [G, BC] + b_p = exp(b_s - b_m[:, None]) + # [G] + b_acc = b_acc * b_r + tl.sum(b_p, 1) + + # [G, BV] + b_o = b_o * b_r[:, None] + tl.dot(b_p.to(b_q.dtype), b_v) + + b_mp = b_m + if NC == 0: + b_lse = tl.zeros([G], dtype=tl.float32) + else: + b_o = b_o / b_acc[:, None] + b_lse = b_m + log(b_acc) + + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + if i_v == 0: + tl.store(lse + (bos + i_t) * HQ + i_h * G + tl.arange(0, G), b_lse.to(lse.dtype.element_ty)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BS', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def parallel_nsa_compression_bwd_kernel_dq( + q, + k, + v, + lse, + delta, + do, + dq, + scale, + cu_seqlens, + token_indices, + chunk_offsets, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BC: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_v, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + all = B * T + if IS_VARLEN: + i_n, i_t = tl.load(token_indices + i_t * 2).to(tl.int32), tl.load(token_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + boc = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + boc = i_b * tl.cdiv(T, BS) + + q += (bos + i_t) * HQ*K + do += (bos + i_t) * HQ*V + lse += (bos + i_t) * HQ + delta += (bos + i_t) * HQ + dq += (i_v * all + bos + i_t) * HQ*K + + p_q = tl.make_block_ptr(q, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + + # [G, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + p_do = tl.make_block_ptr(do, (HQ, V), (V, 1), (i_h * G, i_v * BV), (G, BV), (1, 0)) + p_lse = lse + i_h * G + tl.arange(0, G) + p_delta = delta + i_h * G + tl.arange(0, G) + + # the number of compression representations in total + TC = tl.cdiv(T, BS) + # the number of compression representations required to iterate over + # incomplete compression blocks are not included + NC = (i_t + 1) // BS + + # [G, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [G] + b_lse = tl.load(p_lse) + b_delta = tl.load(p_delta) + + # [G, BK] + b_dq = tl.zeros([G, BK], dtype=tl.float32) + for i_c in range(0, NC, BC): + o_c = i_c + tl.arange(0, BC) + p_k = tl.make_block_ptr(k + (boc * H + i_h) * K, (K, TC), (1, H*K), (0, i_c), (BK, BC), (0, 1)) + p_v = tl.make_block_ptr(v + (boc * H + i_h) * V, (V, TC), (1, H*V), (i_v * BV, i_c), (BV, BC), (0, 1)) + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BC] + b_v = tl.load(p_v, boundary_check=(0, 1)) + + # [G, BC] + b_s = tl.dot(b_q, b_k) + b_p = exp(b_s - b_lse[:, None]) + b_p = tl.where((o_c < NC)[None, :], b_p, 0) + + # [G, BV] @ [BV, BC] -> [G, BC] + b_dp = tl.dot(b_do, b_v) + b_ds = b_p * (b_dp.to(tl.float32) - b_delta[:, None]) + # [G, BC] @ [BC, BK] -> [G, BK] + b_dq += tl.dot(b_ds.to(b_k.dtype), tl.trans(b_k)) + b_dq *= scale + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BS', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def parallel_nsa_compression_bwd_kernel_dkv( + q, + k, + v, + lse, + delta, + do, + dk, + dv, + cu_seqlens, + chunk_indices, + chunk_offsets, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BC: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + all = B * T + if IS_VARLEN: + i_n, i_c = tl.load(chunk_indices + i_c * 2).to(tl.int32), tl.load(chunk_indices + i_c * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + boc = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + boc = i_b * tl.cdiv(T, BS) + + # the number of compression representations in total + TC = tl.cdiv(T, BS) + + p_k = tl.make_block_ptr(k + (boc * H + i_h) * K, (TC, K), (H*K, 1), (i_c * BC, 0), (BC, BK), (1, 0)) + p_v = tl.make_block_ptr(v + (boc * H + i_h) * V, (TC, V), (H*V, 1), (i_c * BC, i_v * BV), (BC, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk + (i_v * all*H + boc * H + i_h) * K, (TC, K), (H*K, 1), (i_c * BC, 0), (BC, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv + (i_v * all*H + boc * H + i_h) * V, (TC, V), (H*V, 1), (i_c * BC, i_v * BV), (BC, BV), (1, 0)) + + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dk = tl.zeros([BC, BK], dtype=tl.float32) + # [BC, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dv = tl.zeros([BC, BV], dtype=tl.float32) + + for i in range(i_c * BC * BS, T): + o_c = i_c * BC + tl.arange(0, BC) + + p_q = tl.make_block_ptr(q + (bos + i) * HQ*K, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + # [G, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + p_do = tl.make_block_ptr(do + (bos + i) * HQ*V, (HQ, V), (V, 1), (i_h * G, i_v * BV), (G, BV), (1, 0)) + p_lse = lse + (bos + i) * HQ + i_h * G + tl.arange(0, G) + p_delta = delta + (bos + i) * HQ + i_h * G + tl.arange(0, G) + # [G, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [G] + b_lse = tl.load(p_lse) + b_delta = tl.load(p_delta) + # [BC, G] + b_s = tl.dot(b_k, tl.trans(b_q)) + b_p = exp(b_s - b_lse[None, :]) + b_p = tl.where((i >= max(0, (o_c + 1) * BS - 1))[:, None], b_p, 0) + # [BC, G] @ [G, BV] -> [BC, BV] + b_dv += tl.dot(b_p.to(b_do.dtype), b_do) + # [BC, BV] @ [BV, G] -> [BC, G] + b_dp = tl.dot(b_v, tl.trans(b_do)) + # [BC, G] + b_ds = b_p * (b_dp - b_delta[None, :]) + # [BC, G] @ [G, BK] -> [BC, BK] + b_dk += tl.dot(b_ds.to(b_q.dtype), b_q) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +def parallel_nsa_compression_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + block_size: int, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + token_indices: torch.LongTensor | None = None, +): + B, T, HQ, K, V = *q.shape, v.shape[-1] + H = k.shape[2] + G = HQ // H + BC = BS = block_size + if check_shared_mem('hopper', q.device.index): + BK = min(256, triton.next_power_of_2(K)) + BV = min(256, triton.next_power_of_2(V)) + else: + BK = min(128, triton.next_power_of_2(K)) + BV = min(128, triton.next_power_of_2(V)) + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert NK == 1, "The key dimension can not be larger than 256" + + chunk_offsets = prepare_chunk_offsets(cu_seqlens, BS) if cu_seqlens is not None else None + + grid = (T, NV, B * H) + o = torch.empty(B, T, HQ, V, dtype=v.dtype, device=q.device) + lse = torch.empty(B, T, HQ, dtype=torch.float, device=q.device) + + parallel_nsa_compression_fwd_kernel[grid]( + q=q, + k=k, + v=v, + o=o, + lse=lse, + scale=scale, + cu_seqlens=cu_seqlens, + token_indices=token_indices, + chunk_offsets=chunk_offsets, + T=T, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + BC=BC, + BS=BS, + BK=BK, + BV=BV, + ) + return o, lse + + +def parallel_nsa_compression_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + o: torch.Tensor, + lse: torch.Tensor, + do: torch.Tensor, + block_size: int = 64, + scale: float = None, + cu_seqlens: torch.LongTensor | None = None, + token_indices: torch.LongTensor | None = None, +): + B, T, HQ, K, V = *q.shape, v.shape[-1] + H = k.shape[2] + G = HQ // H + BC = BS = block_size + BK = max(triton.next_power_of_2(K), 16) + BV = min(128, max(triton.next_power_of_2(v.shape[-1]), 16)) + NV = triton.cdiv(V, BV) + if cu_seqlens is not None: + chunk_indices, chunk_offsets = prepare_chunk_indices(cu_seqlens, BS), prepare_chunk_offsets(cu_seqlens, BS) + NC = len(chunk_indices) + else: + chunk_indices, chunk_offsets = None, None + NC = triton.cdiv(triton.cdiv(T, BS), BC) + + delta = parallel_attn_bwd_preprocess(o, do) + + dq = torch.empty(NV, *q.shape, dtype=q.dtype if NV == 1 else torch.float, device=q.device) + grid = (T, NV, B * H) + parallel_nsa_compression_bwd_kernel_dq[grid]( + q=q, + k=k, + v=v, + lse=lse, + delta=delta, + do=do, + dq=dq, + scale=scale, + cu_seqlens=cu_seqlens, + token_indices=token_indices, + chunk_offsets=chunk_offsets, + T=T, + B=B, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + BC=BC, + BS=BS, + BK=BK, + BV=BV, + ) + dq = dq.sum(0) + + dk = torch.empty(NV, *k.shape, dtype=k.dtype if NV == 1 else torch.float, device=q.device) + dv = torch.empty(v.shape, dtype=v.dtype, device=q.device) + + grid = (NV, NC, B * H) + parallel_nsa_compression_bwd_kernel_dkv[grid]( + q=q, + k=k, + v=v, + lse=lse, + delta=delta, + do=do, + dk=dk, + dv=dv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + chunk_offsets=chunk_offsets, + scale=scale, + T=T, + B=B, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + BC=BC, + BS=BS, + BK=BK, + BV=BV, + ) + dk = dk.sum(0) + return dq, dk, dv + + +class ParallelNSACompressionFunction(torch.autograd.Function): + + @staticmethod + @contiguous + @autocast_custom_fwd + def forward( + ctx, + q, + k, + v, + block_size, + scale, + cu_seqlens, + ): + ctx.dtype = q.dtype + + # 2-d sequence indices denoting the cu_seqlens of tokens in each sequence + # for example, if the passed `cu_seqlens` is [0, 2, 6], + # then there are 2 and 4 tokens in the 1st and 2nd sequences respectively, and `token_indices` will be + # [[0, 0], [0, 1], [1, 0], [1, 1], [1, 2], [1, 3]] + token_indices = prepare_token_indices(cu_seqlens) if cu_seqlens is not None else None + + o, lse = parallel_nsa_compression_fwd( + q=q, + k=k, + v=v, + block_size=block_size, + scale=scale, + cu_seqlens=cu_seqlens, + token_indices=token_indices, + ) + ctx.save_for_backward(q, k, v, o, lse) + ctx.cu_seqlens = cu_seqlens + ctx.token_indices = token_indices + ctx.block_size = block_size + ctx.scale = scale + return o.to(q.dtype), lse + + @staticmethod + @contiguous + @autocast_custom_bwd + def backward(ctx, do, *args): + q, k, v, o, lse = ctx.saved_tensors + dq, dk, dv = parallel_nsa_compression_bwd( + q=q, + k=k, + v=v, + o=o, + lse=lse, + do=do, + block_size=ctx.block_size, + scale=ctx.scale, + cu_seqlens=ctx.cu_seqlens, + token_indices=ctx.token_indices, + ) + return dq.to(q), dk.to(k), dv.to(v), None, None, None + + +def parallel_nsa_compression( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + block_size: int = 64, + scale: float = None, + cu_seqlens: torch.LongTensor | None = None, +): + if scale is None: + scale = k.shape[-1] ** -0.5 + return ParallelNSACompressionFunction.apply( + q, + k, + v, + block_size, + scale, + cu_seqlens, + ) diff --git a/code/flash-linear-attention/fla/ops/nsa/naive.py b/code/flash-linear-attention/fla/ops/nsa/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..e879ab8d03988169ec2952ca030f270e7857995f --- /dev/null +++ b/code/flash-linear-attention/fla/ops/nsa/naive.py @@ -0,0 +1,101 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +from einops import repeat + + +def naive_nsa( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + block_indices: torch.LongTensor, + block_size: int = 64, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> torch.Tensor: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, HQ, K]`.. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + GQA is enforced here. The ratio of query heads (HQ) to key/value heads (H) must be a power of 2 and >=16. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + block_indices (torch.LongTensor): + Block indices of shape `[B, T, H, S]` if `head_first=False` else `[B, H, T, S]`. + `S` is the number of selected blocks for each query token, which is set to 16 in the paper. + block_size (int): + Selected block size. Default: 64. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HQ, V]`. + """ + if scale is None: + scale = k.shape[-1] ** -0.5 + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + + dtype = q.dtype + G = q.shape[2] // k.shape[2] + BS = block_size + k, v, block_indices = (repeat(x, 'b t h d -> b t (h g) d', g=G) for x in (k, v, block_indices)) + q, k, v = map(lambda x: x.float(), (q, k, v)) + + o = torch.zeros_like(v) + varlen = True + if cu_seqlens is None: + varlen = False + B, T = q.shape[:2] + cu_seqlens = torch.cat([ + block_indices.new_tensor(range(0, B*T, T)), block_indices.new_tensor([B*T]), + ]) + + for i in range(len(cu_seqlens) - 1): + if not varlen: + q_b, k_b, v_b, i_b = q[i], k[i], v[i], block_indices[i] + else: + T = cu_seqlens[i+1] - cu_seqlens[i] + q_b, k_b, v_b, i_b = map(lambda x: x[0][cu_seqlens[i]:cu_seqlens[i+1]], (q, k, v, block_indices)) + + i_b = i_b.unsqueeze(-1) * BS + i_b.new_tensor(range(BS)) + # [T, S*BS, HQ] + i_b = i_b.view(T, block_indices.shape[2], -1).transpose(1, 2) + for i_q in range(T): + # [HQ, D] + q_i = q_b[i_q] * scale + # [S*BS, HQ] + i_i = i_b[i_q] + # [S*BS, HQ, -1] + k_i, v_i = map(lambda x: x.gather(0, i_i.clamp(0, T-1).unsqueeze(-1).expand(*i_i.shape, x.shape[-1])), (k_b, v_b)) + # [S*BS, HQ] + attn = torch.einsum('h d, n h d -> n h', q_i, k_i).masked_fill(i_i > i_q, float('-inf')).softmax(0) + if not varlen: + o[i, i_q] = torch.einsum('n h, n h v -> h v', attn, v_i) + else: + o[0][cu_seqlens[i]+i_q] = torch.einsum('n h, n h v -> h v', attn, v_i) + + return o.to(dtype) diff --git a/code/flash-linear-attention/fla/ops/nsa/parallel.py b/code/flash-linear-attention/fla/ops/nsa/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..d5340068dc9c5a3e85cc1d5d7407b489132a427a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/nsa/parallel.py @@ -0,0 +1,881 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl + +from fla.ops.attn.parallel import parallel_attn_bwd_preprocess +from fla.ops.nsa.compression import parallel_nsa_compression +from fla.ops.nsa.utils import _bitonic_merge +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets, prepare_lens, prepare_token_indices +from fla.ops.utils.op import exp, log +from fla.ops.utils.pooling import mean_pooling +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, check_shared_mem, contiguous + +try: + from flash_attn import flash_attn_func, flash_attn_varlen_func +except ImportError: + warnings.warn( + "Flash Attention is not installed. Please install it via `pip install flash-attn --no-build-isolation`", + category=ImportWarning, + ) + flash_attn_func = None + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BS', 'BK'], + **autotune_cache_kwargs, +) +@triton.jit +def parallel_nsa_kernel_topk( + q, + k, + lse, + scale, + block_indices, + cu_seqlens, + token_indices, + chunk_offsets, + T, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + S: tl.constexpr, + BC: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_n, i_t = tl.load(token_indices + i_t * 2).to(tl.int32), tl.load(token_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + boc = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + boc = i_b * tl.cdiv(T, BS) + + p_q = tl.make_block_ptr(q + (bos + i_t) * HQ*K, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + + # the Q block is kept in the shared memory throughout the whole kernel + # [G, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + # the number of compression representations in total + TC = tl.cdiv(T, BS) + # the number of compression representations required to iterate over + # incomplete compression blocks are not included + NC = (i_t + 1) // BS + ################################ + # 1. lse computation + ################################ + if lse is not None: + b_lse = tl.load(lse + (bos + i_t) * HQ + i_h * G + tl.arange(0, G)) + else: + # max scores for the current block + b_m = tl.full([G], float('-inf'), dtype=tl.float32) + # lse = log(acc) + m + b_acc = tl.zeros([G], dtype=tl.float32) + for i_c in range(0, NC, BC): + o_c = i_c + tl.arange(0, BC) + + p_k = tl.make_block_ptr(k + (boc * H + i_h) * K, (K, TC), (1, H*K), (0, i_c), (BK, BC), (0, 1)) + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + + # [G, BC] + b_s = tl.dot(b_q, b_k) + b_s = tl.where((o_c < NC)[None, :], b_s, float('-inf')) + + # [G] + b_m, b_mp = tl.maximum(b_m, tl.max(b_s, 1)), b_m + b_r = exp(b_mp - b_m) + # [G, BC] + b_p = exp(b_s - b_m[:, None]) + # [G] + b_acc = b_acc * b_r + tl.sum(b_p, 1) + + b_mp = b_m + if NC == 0: + b_lse = tl.zeros([G], dtype=tl.float32) + else: + b_lse = b_m + log(b_acc) + + ################################ + # 2. topk selection + ################################ + # [BC] + b_i = tl.full([BC], -1, dtype=tl.float32) + o_i = tl.zeros([BC], dtype=tl.int32) + m_i = tl.arange(0, BC) < BC//2 + + IC = i_t // BS + for i_c in range(0, tl.cdiv(i_t + 1, BS), BC): + o_c = i_c + tl.arange(0, BC) + + p_k = tl.make_block_ptr(k + (boc * H + i_h) * K, (K, TC), (1, H*K), (0, i_c), (BK, BC), (0, 1)) + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [G, BC] + b_s = tl.dot(b_q, b_k) + b_s = tl.where(o_c < IC, b_s, float('-inf')) + # [G, BC] + # the 1st and the last 2 blocks are always selected + b_p = tl.where((o_c == 0) | ((o_c == IC - 1) | (o_c == IC)), 1., exp(b_s - b_lse[:, None])) + # the importance scores of the current block + # [BC] + b_i, b_ip = tl.sum(b_p, 0), b_i + # blocks with index < 0 will be skipped + o_i, o_ip = tl.where(o_c <= IC, o_c, -1), o_i + + n_dims: tl.constexpr = tl.standard._log2(b_i.shape[0]) + for i in tl.static_range(1, n_dims): + b_i, o_i = _bitonic_merge(b_i, o_i.to(tl.int32), i, 2, n_dims) + + if i_c != 0: + b_i, o_i = _bitonic_merge(b_i, o_i.to(tl.int32), n_dims, False, n_dims) + b_i_new = b_ip * m_i + b_i * (1 - m_i) + o_i_new = o_ip * m_i + o_i * (1 - m_i) + b_i, o_i = _bitonic_merge(b_i_new, o_i_new.to(tl.int32), n_dims, True, n_dims) + else: + b_i, o_i = _bitonic_merge(b_i, o_i.to(tl.int32), n_dims, True, n_dims) + + m_top = tl.arange(0, BC//S) == 0 + b_top = tl.sum(m_top[:, None] * tl.reshape(o_i, [BC//S, S]), 0) + + p_b = tl.make_block_ptr(block_indices + (bos + i_t) * H*S, (H*S,), (1,), (i_h * S,), (S,), (0,)) + tl.store(p_b, b_top.to(p_b.dtype.element_ty)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_BLOCK_COUNTS': lambda args: isinstance(args['block_counts'], torch.Tensor), +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BS', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit +def parallel_nsa_fwd_kernel( + q, + k, + v, + o, + lse, + scale, + block_indices, + block_counts, + cu_seqlens, + token_indices, + T, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + S: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_BLOCK_COUNTS: tl.constexpr, +): + i_t, i_v, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_n, i_t = tl.load(token_indices + i_t * 2).to(tl.int32), tl.load(token_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + block_indices += (bos + i_t) * H*S + i_h * S + + if USE_BLOCK_COUNTS: + NS = tl.load(block_counts + (bos + i_t) * H + i_h) + else: + NS = S + + p_q = tl.make_block_ptr(q + (bos + i_t) * HQ*K, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + # the Q block is kept in the shared memory throughout the whole kernel + # [G, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + p_o = tl.make_block_ptr(o + (bos + i_t) * HQ*V, (HQ, V), (V, 1), (i_h * G, i_v * BV), (G, BV), (1, 0)) + p_lse = lse + (bos + i_t) * HQ + i_h * G + tl.arange(0, G) + # [G, BV] + b_o = tl.zeros([G, BV], dtype=tl.float32) + + b_m = tl.full([G], float('-inf'), dtype=tl.float32) + b_acc = tl.zeros([G], dtype=tl.float32) + for i in range(NS): + i_s = tl.load(block_indices + i).to(tl.int32) * BS + if i_s <= i_t and i_s >= 0: + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (0, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [G, BS] + b_s = tl.dot(b_q, b_k) + b_s = tl.where((i_t >= (i_s + tl.arange(0, BS)))[None, :], b_s, float('-inf')) + + # [G] + b_m, b_mp = tl.maximum(b_m, tl.max(b_s, 1)), b_m + b_r = exp(b_mp - b_m) + # [G, BS] + b_p = exp(b_s - b_m[:, None]) + # [G] + b_acc = b_acc * b_r + tl.sum(b_p, 1) + # [G, BV] + b_o = b_o * b_r[:, None] + tl.dot(b_p.to(b_q.dtype), b_v) + + b_mp = b_m + b_o = b_o / b_acc[:, None] + b_m += log(b_acc) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_lse, b_m.to(p_lse.dtype.element_ty)) + + +@triton.heuristics({ + 'USE_BLOCK_COUNTS': lambda args: isinstance(args['block_counts'], torch.Tensor), +}) +@triton.jit(do_not_specialize=['T']) +def parallel_nsa_kernel_mask( + block_indices, + block_counts, + block_mask, + T, + H: tl.constexpr, + S: tl.constexpr, + BS: tl.constexpr, + NS: tl.constexpr, + USE_BLOCK_COUNTS: tl.constexpr, +): + i_t, i_b, i_hs = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_h, i_s = i_hs // S, i_hs % S + + b_i = tl.load(block_indices + i_b * T * H * S + i_t * H * S + i_h * S + i_s) + if USE_BLOCK_COUNTS: + b_m = b_i * BS <= i_t and i_s < tl.load(block_counts + i_b * T * H + i_t * H + i_h) + else: + b_m = b_i * BS <= i_t + + if b_i < NS and b_i >= 0: + tl.store(block_mask + i_b * T * H * NS + i_t * H * NS + i_h * NS + b_i, b_m.to(block_mask.dtype.element_ty)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_BLOCK_COUNTS': lambda args: isinstance(args['block_counts'], torch.Tensor), +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BS', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def parallel_nsa_bwd_kernel_dq( + q, + k, + v, + lse, + delta, + do, + dq, + scale, + block_indices, + block_counts, + cu_seqlens, + token_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + S: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_BLOCK_COUNTS: tl.constexpr, +): + i_t, i_v, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + all = B * T + if IS_VARLEN: + i_n, i_t = tl.load(token_indices + i_t * 2).to(tl.int32), tl.load(token_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + q += (bos + i_t) * HQ*K + do += (bos + i_t) * HQ*V + lse += (bos + i_t) * HQ + delta += (bos + i_t) * HQ + dq += (i_v * all + bos + i_t) * HQ*K + block_indices += (bos + i_t) * H*S + i_h * S + + if USE_BLOCK_COUNTS: + NS = tl.load(block_counts + (bos + i_t) * H + i_h) + else: + NS = S + + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + + p_q = tl.make_block_ptr(q, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + + # [G, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + p_do = tl.make_block_ptr(do, (HQ, V), (V, 1), (i_h * G, i_v * BV), (G, BV), (1, 0)) + p_lse = lse + i_h * G + tl.arange(0, G) + p_delta = delta + i_h * G + tl.arange(0, G) + + # [G, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [G] + b_lse = tl.load(p_lse) + b_delta = tl.load(p_delta) + + # [G, BK] + b_dq = tl.zeros([G, BK], dtype=tl.float32) + for i in range(NS): + i_s = tl.load(block_indices + i).to(tl.int32) * BS + if i_s <= i_t and i_s >= 0: + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (0, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v, (V, T), (1, H*V), (i_v * BV, i_s), (BV, BS), (0, 1)) + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + + # [G, BS] + b_s = tl.dot(b_q, b_k) + b_p = exp(b_s - b_lse[:, None]) + b_p = tl.where((i_t >= (i_s + tl.arange(0, BS)))[None, :], b_p, 0) + + # [G, BV] @ [BV, BS] -> [G, BS] + b_dp = tl.dot(b_do, b_v) + b_ds = b_p * (b_dp.to(tl.float32) - b_delta[:, None]) + # [G, BS] @ [BS, BK] -> [G, BK] + b_dq += tl.dot(b_ds.to(b_k.dtype), tl.trans(b_k)) + b_dq *= scale + + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4] + ], + key=['BS', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def parallel_nsa_bwd_kernel_dkv( + q, + k, + v, + lse, + delta, + do, + dk, + dv, + block_mask, + cu_seqlens, + chunk_indices, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + G: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + M: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_s, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + all = B * T + if IS_VARLEN: + i_n, i_s = tl.load(chunk_indices + i_s * 2).to(tl.int32), tl.load(chunk_indices + i_s * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_s * BS, 0), (BS, BK), (1, 0)) + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_s * BS, i_v * BV), (BS, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk + (i_v * all * H + bos * H + i_h) * K, (T, K), (H*K, 1), (i_s * BS, 0), (BS, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_s * BS, i_v * BV), (BS, BV), (1, 0)) + + # [BS, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dk = tl.zeros([BS, BK], dtype=tl.float32) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dv = tl.zeros([BS, BV], dtype=tl.float32) + + for i in range(i_s * BS, T): + b_m = tl.load(block_mask + (bos + i) * H*M + i_h * M + i_s) + if b_m: + p_q = tl.make_block_ptr(q + (bos + i) * HQ*K, (HQ, K), (K, 1), (i_h * G, 0), (G, BK), (1, 0)) + # [G, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + + p_do = tl.make_block_ptr(do + (bos + i) * HQ*V, (HQ, V), (V, 1), (i_h * G, i_v * BV), (G, BV), (1, 0)) + p_lse = lse + (bos + i) * HQ + i_h * G + tl.arange(0, G) + p_delta = delta + (bos + i) * HQ + i_h * G + tl.arange(0, G) + # [G, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [G] + b_lse = tl.load(p_lse) + b_delta = tl.load(p_delta) + # [BS, G] + b_s = tl.dot(b_k, tl.trans(b_q)) + b_p = exp(b_s - b_lse[None, :]) + b_p = tl.where((i >= (i_s * BS + tl.arange(0, BS)))[:, None], b_p, 0) + # [BS, G] @ [G, BV] -> [BS, BV] + b_dv += tl.dot(b_p.to(b_do.dtype), b_do) + # [BS, BV] @ [BV, G] -> [BS, G] + b_dp = tl.dot(b_v, tl.trans(b_do)) + # [BS, G] + b_ds = b_p * (b_dp - b_delta[None, :]) + # [BS, G] @ [G, BK] -> [BS, BK] + b_dk += tl.dot(b_ds.to(b_q.dtype), b_q) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +def parallel_nsa_topk( + q: torch.Tensor, + k: torch.Tensor, + lse: torch.Tensor, + block_counts: torch.LongTensor | int, + block_size: int = 64, + scale: float = None, + cu_seqlens: torch.LongTensor | None = None, +) -> torch.LongTensor: + B, T, HQ, K = q.shape + H = k.shape[2] + G = HQ // H + # the number of selected blocks for each token + S = block_counts if isinstance(block_counts, int) else block_counts.max().item() + S = triton.next_power_of_2(S) + # here we set BC = BS, but beware that they can be chosen separately if required + BC = BS = block_size + BK = max(triton.next_power_of_2(K), 16) + assert BC >= 2 * S, f"BC ({BC}) must be greater than or equal to 2 * S ({S})" + + block_indices = torch.zeros(B, T, H, S, dtype=torch.int32, device=q.device) + token_indices = prepare_token_indices(cu_seqlens) if cu_seqlens is not None else None + chunk_offsets = prepare_chunk_offsets(cu_seqlens, BS) if cu_seqlens is not None else None + grid = (T, B * H) + # the 1st and the last 2 blocks are always selected + parallel_nsa_kernel_topk[grid]( + q=q, + k=k, + lse=lse, + scale=scale, + block_indices=block_indices, + cu_seqlens=cu_seqlens, + token_indices=token_indices, + chunk_offsets=chunk_offsets, + T=T, + H=H, + HQ=HQ, + G=G, + K=K, + S=S, + BC=BC, + BS=BS, + BK=BK, + ) + return block_indices + + +def parallel_nsa_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + block_indices: torch.LongTensor, + block_counts: torch.LongTensor | int, + block_size: int, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + token_indices: torch.LongTensor | None = None, +): + B, T, H, K, V, S = *k.shape, v.shape[-1], block_indices.shape[-1] + HQ = q.shape[2] + G = HQ // H + BS = block_size + if check_shared_mem('hopper', q.device.index): + BK = min(256, triton.next_power_of_2(K)) + BV = min(256, triton.next_power_of_2(V)) + else: + BK = min(128, triton.next_power_of_2(K)) + BV = min(128, triton.next_power_of_2(V)) + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert NK == 1, "The key dimension can not be larger than 256" + + grid = (T, NV, B * H) + o = torch.empty(B, T, HQ, V, dtype=v.dtype, device=q.device) + lse = torch.empty(B, T, HQ, dtype=torch.float, device=q.device) + + parallel_nsa_fwd_kernel[grid]( + q=q, + k=k, + v=v, + o=o, + lse=lse, + scale=scale, + block_indices=block_indices, + block_counts=block_counts, + cu_seqlens=cu_seqlens, + token_indices=token_indices, + T=T, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + S=S, + BS=BS, + BK=BK, + BV=BV, + ) + return o, lse + + +def parallel_nsa_block_mask( + block_indices: torch.LongTensor, + block_counts: torch.LongTensor | int, + cu_seqlens: torch.LongTensor, + block_size: int, +): + B, T, H, S = block_indices.shape + BS = block_size + if cu_seqlens is not None: + NS = triton.cdiv(prepare_lens(cu_seqlens).max().item(), BS) + else: + NS = triton.cdiv(T, BS) + block_mask = torch.zeros(B, T, H, NS, dtype=torch.bool, device=block_indices.device) + + parallel_nsa_kernel_mask[(T, B, H*S)]( + block_indices=block_indices, + block_counts=block_counts, + block_mask=block_mask, + T=T, + H=H, + S=S, + BS=BS, + NS=NS, + ) + return block_mask + + +def parallel_nsa_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + o: torch.Tensor, + lse: torch.Tensor, + do: torch.Tensor, + block_indices: torch.Tensor, + block_counts: torch.LongTensor | int, + block_size: int = 64, + scale: float = None, + cu_seqlens: torch.LongTensor | None = None, + token_indices: torch.LongTensor | None = None, +): + B, T, H, K, V, S = *k.shape, v.shape[-1], block_indices.shape[-1] + HQ = q.shape[2] + G = HQ // H + BS = block_size + BK = max(triton.next_power_of_2(K), 16) + BV = min(128, max(triton.next_power_of_2(v.shape[-1]), 16)) + NV = triton.cdiv(V, BV) + + delta = parallel_attn_bwd_preprocess(o, do) + + dq = torch.empty(NV, *q.shape, dtype=q.dtype if NV == 1 else torch.float, device=q.device) + grid = (T, NV, B * H) + parallel_nsa_bwd_kernel_dq[grid]( + q=q, + k=k, + v=v, + lse=lse, + delta=delta, + do=do, + dq=dq, + block_indices=block_indices, + block_counts=block_counts, + cu_seqlens=cu_seqlens, + token_indices=token_indices, + scale=scale, + T=T, + B=B, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + S=S, + BS=BS, + BK=BK, + BV=BV, + ) + dq = dq.sum(0) + + if cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BS) + NS = len(chunk_indices) + else: + chunk_indices = None + NS = triton.cdiv(T, BS) + + # [B, T, H, M] + block_mask = parallel_nsa_block_mask(block_indices, block_counts, cu_seqlens, block_size) + dk = torch.empty(NV, *k.shape, dtype=k.dtype if NV == 1 else torch.float, device=q.device) + dv = torch.empty(v.shape, dtype=v.dtype, device=q.device) + + grid = (NV, NS, B * H) + parallel_nsa_bwd_kernel_dkv[grid]( + q=q, + k=k, + v=v, + lse=lse, + delta=delta, + do=do, + dk=dk, + dv=dv, + block_mask=block_mask, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + H=H, + HQ=HQ, + G=G, + K=K, + V=V, + M=block_mask.shape[-1], + BS=BS, + BK=BK, + BV=BV, + ) + dk = dk.sum(0) + return dq, dk, dv + + +@torch.compile +class ParallelNSAFunction(torch.autograd.Function): + + @staticmethod + @contiguous + @autocast_custom_fwd + def forward(ctx, q, k, v, block_indices, block_counts, block_size, scale, cu_seqlens): + ctx.dtype = q.dtype + + # 2-d sequence indices denoting the cu_seqlens of tokens in each sequence + # for example, if the passed `cu_seqlens` is [0, 2, 6], + # then there are 2 and 4 tokens in the 1st and 2nd sequences respectively, and `token_indices` will be + # [[0, 0], [0, 1], [1, 0], [1, 1], [1, 2], [1, 3]] + token_indices = prepare_token_indices(cu_seqlens) if cu_seqlens is not None else None + + o, lse = parallel_nsa_fwd( + q=q, + k=k, + v=v, + block_indices=block_indices, + block_counts=block_counts, + block_size=block_size, + scale=scale, + cu_seqlens=cu_seqlens, + token_indices=token_indices, + ) + ctx.save_for_backward(q, k, v, o, lse) + ctx.block_indices = block_indices + ctx.block_counts = block_counts + ctx.cu_seqlens = cu_seqlens + ctx.token_indices = token_indices + ctx.block_size = block_size + ctx.scale = scale + return o.to(q.dtype) + + @staticmethod + @contiguous + @autocast_custom_bwd + def backward(ctx, do): + q, k, v, o, lse = ctx.saved_tensors + dq, dk, dv = parallel_nsa_bwd( + q=q, + k=k, + v=v, + o=o, + lse=lse, + do=do, + block_indices=ctx.block_indices, + block_counts=ctx.block_counts, + block_size=ctx.block_size, + scale=ctx.scale, + cu_seqlens=ctx.cu_seqlens, + token_indices=ctx.token_indices, + ) + return dq.to(q), dk.to(k), dv.to(v), None, None, None, None, None, None, None, None + + +def parallel_nsa( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g_cmp: torch.Tensor | None = None, + g_slc: torch.Tensor | None = None, + g_swa: torch.Tensor | None = None, + block_indices: torch.LongTensor | None = None, + block_counts: torch.LongTensor | int = 16, + block_size: int = 64, + window_size: int = 0, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, +) -> torch.Tensor: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, HQ, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + GQA is enforced here. The ratio of query heads (HQ) to key/value heads (H) must be a power of 2 and >=16. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g_cmp (torch.Tensor): + Gate score for compressed attention of shape `[B, T, HQ]`. + g_slc (torch.Tensor): + Gate score for selected attention of shape `[B, T, HQ]`. + g_swa (torch.Tensor): + Gate score for sliding attentionof shape `[B, T, HQ]`. + block_indices (torch.LongTensor): + Block indices of shape `[B, T, H, S]`. + `S` is the number of selected blocks for each query token, which is set to 16 in the paper. + If `g_cmp` is provided, the passed `block_indices` will be ignored. + block_counts (Optional[Union[torch.LongTensor, int]]): + Number of selected blocks for each query. + If a tensor is provided, with shape `[B, T, H]`, + each query can select the same number of blocks. + If not provided, it will default to 16. + block_size (int): + Selected block size. Default: 64. + window_size (int): + Sliding window size. Default: 0. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HQ, V]`. + """ + assert block_counts is not None, "block counts must be provided for selection" + if scale is None: + scale = k.shape[-1] ** -0.5 + if cu_seqlens is not None: + assert q.shape[0] == 1, "batch size must be 1 when cu_seqlens are provided" + assert q.shape[2] % (k.shape[2] * 16) == 0, "Group size must be a multiple of 16 in NSA" + + k_cmp, v_cmp = mean_pooling(k, block_size, cu_seqlens), mean_pooling(v, block_size, cu_seqlens) + o_cmp, lse_cmp = None, None + if g_cmp is not None: + o_cmp, lse_cmp = parallel_nsa_compression( + q=q, + k=k_cmp, + v=v_cmp, + block_size=block_size, + scale=scale, + cu_seqlens=cu_seqlens, + ) + if block_indices is not None: + warnings.warn("`block_indices` will be ignored when `g_cmp` is provided") + block_indices = parallel_nsa_topk( + q=q, + k=k_cmp, + lse=lse_cmp, + block_counts=block_counts, + block_size=block_size, + scale=scale, + cu_seqlens=cu_seqlens, + ) + o = o_slc = ParallelNSAFunction.apply(q, k, v, block_indices, block_counts, block_size, scale, cu_seqlens) + if g_slc is not None: + o = o_slc * g_slc.unsqueeze(-1) + if o_cmp is not None: + o = torch.addcmul(o, o_cmp, g_cmp.unsqueeze(-1)) + if window_size > 0: + if cu_seqlens is not None: + max_seqlen = q.shape[1] + o_swa = flash_attn_varlen_func( + q.squeeze(0), k.squeeze(0), v.squeeze(0), + cu_seqlens_q=cu_seqlens, + cu_seqlens_k=cu_seqlens, + max_seqlen_q=max_seqlen, + max_seqlen_k=max_seqlen, + causal=True, + window_size=(window_size-1, 0), + ).unsqueeze(0) + else: + o_swa = flash_attn_func( + q, k, v, + causal=True, + window_size=(window_size-1, 0), + ) + o = torch.addcmul(o, o_swa, g_swa.unsqueeze(-1)) + return o diff --git a/code/flash-linear-attention/fla/ops/nsa/utils.py b/code/flash-linear-attention/fla/ops/nsa/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..0eadb7798292cd92a2220366a2e63c2a2f7e59f7 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/nsa/utils.py @@ -0,0 +1,91 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +# Implements argsort based on bitonic sort. +# [What is bitonic sort?](https://en.wikipedia.org/wiki/Bitonic_sorter) + +# Code adapted from https://github.com/triton-lang/triton/issues/3698#issuecomment-2067681396 + + +import triton +import triton.language as tl + +from fla.ops.utils.op import log2 + + +@triton.jit +def _compare_and_swap( + x, + ids, + flip, + i: tl.constexpr, + n_dims: tl.constexpr, +): + n_outer: tl.constexpr = x.numel >> n_dims + shape: tl.constexpr = [n_outer * 2**i, 2, 2**(n_dims - i - 1)] + y = tl.reshape(x, shape) + # slice left/right with 'stride' 2**(n_dims - i - 1) + mask = tl.arange(0, 2)[None, :, None] + left = tl.broadcast_to(tl.sum(y * (1 - mask), 1)[:, None, :], shape).to(y.dtype) + right = tl.broadcast_to(tl.sum(y * mask, 1)[:, None, :], shape).to(y.dtype) + left = tl.reshape(left, x.shape) + right = tl.reshape(right, x.shape) + # idx + y_idx = tl.reshape(ids, shape) + left_idx = tl.broadcast_to(tl.sum(y_idx * (1 - mask), 1)[:, None, :], shape) + right_idx = tl.broadcast_to(tl.sum(y_idx * mask, 1)[:, None, :], shape) + left_idx = tl.reshape(left_idx, x.shape).to(y_idx.dtype) + right_idx = tl.reshape(right_idx, x.shape).to(y_idx.dtype) + # actual compare-and-swap + idtype = tl.core.get_int_dtype(bitwidth=x.dtype.primitive_bitwidth, signed=True) + ileft = left.to(idtype, bitcast=True) + iright = right.to(idtype, bitcast=True) + ix = x.to(idtype, bitcast=True) + + cond = (left > right) != flip + ret = ix ^ tl.where(cond, ileft ^ iright, tl.zeros_like(ix)) + new_ids = ids ^ tl.where(cond, left_idx ^ right_idx, tl.zeros_like(ids)) + return ret.to(x.dtype, bitcast=True), new_ids + + +@triton.jit +def _bitonic_merge( + x, + ids, + stage: tl.constexpr, + order: tl.constexpr, + n_dims: tl.constexpr, +): + n_outer: tl.constexpr = x.numel >> n_dims + tl.static_assert(stage <= n_dims) + # flip denotes whether to re-arrange sub-sequences of elements in ascending or + # descending order. + # if flip = 00000000... then all elements will be re-arranged ascendingly at this stage + # if flip = 00110011... then all the elements will be re-arranged alternatingly (with + # a stride of 2) at this stage + if order == 2: + shape: tl.constexpr = [n_outer * 2**(n_dims - 1 - stage), 2, 2**stage] + flip = tl.reshape(tl.broadcast_to(tl.arange(0, 2)[None, :, None], shape), x.shape) + else: + flip = order + # perform `stage` rounds of `compare-and-swap` + for i in tl.static_range(stage): + x, ids = _compare_and_swap(x, ids, flip, i + (n_dims - stage), n_dims) + return x, ids + + +@triton.jit +def argsort( + x, + ids, + dim: tl.constexpr = None, + descending: tl.constexpr = tl.core.CONSTEXPR_0, +): + # handle default dimension or check that it is the most minor dim + _dim: tl.constexpr = len(x.shape) - 1 if dim is None else dim + tl.static_assert(_dim == len(x.shape) - 1, "only minor dimension is currently supported") + # iteratively run bitonic merge-sort steps + n_dims: tl.constexpr = log2(x.shape[_dim]) + + for i in tl.static_range(1, n_dims + 1): + x, ids = _bitonic_merge(x, ids, i, 2 if i < n_dims else descending, n_dims) + return x, ids diff --git a/code/flash-linear-attention/fla/ops/path_attn/__init__.py b/code/flash-linear-attention/fla/ops/path_attn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5b5b572ede2b7d2392ced5ea48b6ece74f49e686 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/__init__.py @@ -0,0 +1,6 @@ + +from .parallel import parallel_path_attn + +__all__ = [ + 'parallel_path_attn', +] diff --git a/code/flash-linear-attention/fla/ops/path_attn/cumprod_householder_bwd.py b/code/flash-linear-attention/fla/ops/path_attn/cumprod_householder_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..4c9c814a63d1133baf69e9899e021c38f861fc04 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/cumprod_householder_bwd.py @@ -0,0 +1,139 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.utils import check_shared_mem + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def chunk_cumprod_householder_bwd_kernel( + hc_suffix, dhc_whole, + k, dk, w1, w2, dw1, dw2, dk_new, + cu_seqlens, split_indices, chunk_offsets, split_offsets, + BT: tl.constexpr, # previous small chunk size + K: tl.constexpr, + BK: tl.constexpr, + T: tl.constexpr, + S: tl.constexpr, + G: tl.constexpr, + H: tl.constexpr, + HQ: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_ss, i_hq = tl.program_id(0), tl.program_id(1) + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_s = tl.load(split_indices + i_ss * 2).to(tl.int32), tl.load(split_indices + i_ss * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NS = tl.cdiv(T, S) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + boh_large = tl.load(split_offsets + i_n).to(tl.int32) + else: + NS = tl.cdiv(T, S) + i_n, i_s = i_ss // NS, i_ss % NS + bos, eos = i_n * T, i_n * T + T + boh = i_n * tl.cdiv(T, BT) + boh_large = i_n * tl.cdiv(T, S) + + # offset calculations + dhc_whole += ((boh_large + i_s) * HQ + i_hq) * K * K + hc_suffix += ((boh + tl.cdiv(i_s * S, BT)) * H + i_h) * K * K + k += (bos * H + i_h) * K + w1 += (bos * H + i_h) * K + w2 += (bos * H + i_h) * K + dw1 += (bos * HQ + i_hq) * K + dw2 += (bos * HQ + i_hq) * K + + # dh += ((boh + tl.cdiv(i_s * S, BT)) * HQ + i_hq) * K * K + dk += (bos * HQ + i_hq) * K + dk_new += (bos * HQ + i_hq) * K + + stride_h = H * K * K + NT_small = tl.cdiv(min(S, T-i_s*S), BT) + p_dhc_whole = tl.make_block_ptr(dhc_whole, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + b_dhc = tl.zeros([BK, BK], dtype=tl.float32) + b_dhc += tl.load(p_dhc_whole, boundary_check=(0, 1)) + + # calculate dh + for i_t_small in range(0, NT_small): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_s*S + i_t_small*BT, 0), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (HQ*K, 1), (i_s*S + i_t_small*BT, 0), (BT, BK), (1, 0)) + p_dk_new = tl.make_block_ptr(dk_new, (T, K), (HQ*K, 1), (i_s*S + i_t_small*BT, 0), (BT, BK), (1, 0)) + p_hc = tl.make_block_ptr(hc_suffix + i_t_small * stride_h, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dk = tl.load(p_dk, boundary_check=(0, 1)) + + p_w1 = tl.make_block_ptr(w1, (T, K), (H*K, 1), (i_s*S + i_t_small*BT, 0), (BT, BK), (1, 0)) + p_w2 = tl.make_block_ptr(w2, (T, K), (H*K, 1), (i_s*S + i_t_small*BT, 0), (BT, BK), (1, 0)) + + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + b_hc = tl.load(p_hc, boundary_check=(0, 1)) + + b_dk_new = b_dk - tl.dot(b_dk.to(b_hc.dtype), b_hc) + tl.store(p_dk_new, b_dk_new.to(dk_new.dtype.element_ty), boundary_check=(0, 1)) + + b_dh = b_dhc - tl.dot(tl.trans(b_hc), b_dhc.to(b_hc.dtype)) + b_dw2 = tl.dot(b_w1, b_dh.to(b_w1.dtype)) + b_dw1 = tl.dot(b_w2, tl.trans(b_dh.to(b_w2.dtype))) + + p_dw1 = tl.make_block_ptr(dw1, (T, K), (HQ*K, 1), (i_s*S + i_t_small*BT, 0), (BT, BK), (1, 0)) + p_dw2 = tl.make_block_ptr(dw2, (T, K), (HQ*K, 1), (i_s*S + i_t_small*BT, 0), (BT, BK), (1, 0)) + + tl.store(p_dw1, b_dw1.to(dw1.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dw2, b_dw2.to(dw2.dtype.element_ty), boundary_check=(0, 1)) + + b_dhc = b_dhc - tl.dot(tl.dot(b_dhc.to(b_w2.dtype), tl.trans(b_w2)).to(b_w1.dtype), b_w1) + b_dhc -= tl.dot(tl.trans(b_dk).to(b_k.dtype), b_k) + + +def chunk_cumprod_householder_bwd_fn( + w1: torch.Tensor, + w2: torch.Tensor, + hc_suffix: torch.Tensor, + dhc_whole: torch.Tensor, + k: torch.Tensor, + dk: torch.Tensor, + S: int, # split size, aka large chunk size + BT: int, # small chunk size + cu_seqlens: torch.Tensor = None, +): + B, T, HQ, K = dk.shape + H = k.shape[2] + G = HQ // H + + split_indices = prepare_chunk_indices(cu_seqlens, S) if cu_seqlens is not None else None + chunk_offsets = prepare_chunk_offsets(cu_seqlens, BT) if cu_seqlens is not None else None + split_offsets = prepare_chunk_offsets(cu_seqlens, S) if cu_seqlens is not None else None + + if cu_seqlens is None: + N = B + NS = N * triton.cdiv(T, S) + else: + N = len(cu_seqlens) - 1 + NS = split_offsets[-1].item() + + grid = (NS, HQ) + dw1 = torch.empty_like(dk, dtype=torch.float32) + dw2 = torch.empty_like(dk, dtype=torch.float32) + dk_new = torch.empty_like(dk, dtype=torch.float32) + + chunk_cumprod_householder_bwd_kernel[grid]( + hc_suffix=hc_suffix, dhc_whole=dhc_whole, + k=k, dk=dk, w1=w1, w2=w2, dw1=dw1, dw2=dw2, dk_new=dk_new, + cu_seqlens=cu_seqlens, + split_indices=split_indices, chunk_offsets=chunk_offsets, split_offsets=split_offsets, + BT=BT, K=K, G=G, H=H, HQ=HQ, BK=K, + T=T, S=S, + # SY (2025/07/08): I don't know why when K == 128 if I set num_warps=4 the result would be completely wrong + num_warps=8 if K == 128 else 4, + num_stages=2 if check_shared_mem('ampere') else 1, + ) + return dw1, dw2, dk_new diff --git a/code/flash-linear-attention/fla/ops/path_attn/cumprod_householder_fwd.py b/code/flash-linear-attention/fla/ops/path_attn/cumprod_householder_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..2a6c462d1760f305d5eb822ce981257b2d61c2f0 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/cumprod_householder_fwd.py @@ -0,0 +1,119 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.utils import check_shared_mem + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit +def chunk_cumprod_householder_fwd_kernel( + k, + k_new, + w1, + w2, + hc_suffix, + hc_whole, + cu_seqlens, + split_indices, + chunk_offsets, + split_offsets, + BT: tl.constexpr, # small chunk size + K: tl.constexpr, + H: tl.constexpr, + BK: tl.constexpr, + T: tl.constexpr, + S: tl.constexpr, # split size, aka large chunk size + IS_VARLEN: tl.constexpr, +): + i_ss, i_h = tl.program_id(0), tl.program_id(1) + + if IS_VARLEN: + i_n, i_s = tl.load(split_indices + i_ss * 2).to(tl.int32), tl.load(split_indices + i_ss * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NS = tl.cdiv(T, S) + + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + boh_large = tl.load(split_offsets + i_n).to(tl.int32) + else: + NS = tl.cdiv(T, S) + i_n, i_s = i_ss // NS, i_ss % NS + bos, eos = i_n * T, i_n * T + T + + boh = i_n * tl.cdiv(T, BT) + boh_large = i_n * tl.cdiv(T, S) + + NT_small = tl.cdiv(min(S, T-i_s*S), BT) + stride_h = H*K*K + + # offset calculations + hc_whole += ((boh_large + i_s) * H + i_h) * K * K + hc_suffix += ((boh + tl.cdiv(i_s * S, BT)) * H + i_h) * K * K + + k += (bos * H + i_h) * K + k_new += (bos * H + i_h) * K + w1 += (bos * H + i_h) * K + w2 += (bos * H + i_h) * K + + b_h = tl.zeros([BK, BK], dtype=tl.float32) + for i_t_small in range(NT_small-1, -1, -1): + p_hc_suffix = tl.make_block_ptr(hc_suffix + i_t_small * stride_h, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + tl.store(p_hc_suffix, b_h.to(hc_suffix.dtype.element_ty), boundary_check=(0, 1)) + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_s * S + i_t_small * BT, 0), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_k = (b_k - tl.dot(b_k, tl.trans(b_h.to(b_k.dtype)))) + p_w1 = tl.make_block_ptr(w1, (K, T), (1, H*K), (0, i_s * S + i_t_small * BT), (BK, BT), (0, 1)) + p_w2 = tl.make_block_ptr(w2, (T, K), (H*K, 1), (i_s * S + i_t_small * BT, 0), (BT, BK), (1, 0)) + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + b_v_new = (b_w1 - tl.dot(b_h.to(b_w1.dtype), b_w1)).to(b_w2.dtype) + b_h += tl.dot(b_v_new, b_w2) + p_k_new = tl.make_block_ptr(k_new, (T, K), (H*K, 1), (i_s * S + i_t_small * BT, 0), (BT, BK), (1, 0)) + tl.store(p_k_new, b_k.to(k_new.dtype.element_ty), boundary_check=(0, 1)) + + p_hc_whole = tl.make_block_ptr(hc_whole, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + tl.store(p_hc_whole, b_h.to(hc_whole.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_cumprod_householder_fwd_fn( + k: torch.Tensor, + w1: torch.Tensor, + w2: torch.Tensor, + S: int, # split size, aka large chunk size + BT: int, # small chunk size + cu_seqlens: torch.Tensor = None, +): + B, T, H, K = k.shape + + split_indices = prepare_chunk_indices(cu_seqlens, S) if cu_seqlens is not None else None + chunk_offsets = prepare_chunk_offsets(cu_seqlens, BT) if cu_seqlens is not None else None + split_offsets = prepare_chunk_offsets(cu_seqlens, S) if cu_seqlens is not None else None + + if cu_seqlens is None: + N = B + NS = N * triton.cdiv(T, S) + NT = N * triton.cdiv(T, BT) + else: + N = len(cu_seqlens) - 1 + NS = split_offsets[-1] + NT = chunk_offsets[-1] + + grid = (NS, H) + hc_whole = torch.empty((NS, H, K, K), device=k.device, dtype=w1.dtype) + k_new = torch.empty_like(k, dtype=k.dtype) + hc_suffix = torch.empty((NT, H, K, K), device=k.device, dtype=w1.dtype) + chunk_cumprod_householder_fwd_kernel[grid]( + k=k, k_new=k_new, w1=w1, w2=w2, hc_whole=hc_whole, hc_suffix=hc_suffix, + cu_seqlens=cu_seqlens, + split_indices=split_indices, chunk_offsets=chunk_offsets, split_offsets=split_offsets, + BT=BT, K=K, H=H, BK=K, + T=T, S=S, + # SY (2025/07/08): I don't know why when K == 128 if I set num_warps=4 the result would be completely wrong + num_warps=8 if K == 128 else 4, + num_stages=3 if check_shared_mem('ampere') else 1, + ) + return k_new, hc_suffix, hc_whole diff --git a/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_bwd.py b/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_bwd.py new file mode 100644 index 0000000000000000000000000000000000000000..10e5fc218dd1b7ed2e92fcdf9b36e886bf8d4e34 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_bwd.py @@ -0,0 +1,141 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.utils import check_shared_mem + + +# episold +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['offsets'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def intra_chunk_preprocess_bwd_kernel( + q, k, w, w2, beta, + AT, + dA_local, dq, dq_new, dk, dk_new, dw, dbeta, dw1, dw2, T, + offsets, indices, + HQ: tl.constexpr, G: tl.constexpr, H: tl.constexpr, + K: tl.constexpr, BT: tl.constexpr, BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(offsets + i_n).to(tl.int32), tl.load(offsets + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dw_beta = tl.zeros([BT, BK], dtype=tl.float32) + b_dw = tl.zeros([BT, BK], dtype=tl.float32) + b_dT = tl.zeros([BT, BT], dtype=tl.float32) + + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (K*H, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_w = tl.make_block_ptr(w + (bos * H + i_h) * K, (T, K), (K*H, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_w2 = tl.make_block_ptr(w2 + (bos * H + i_h) * K, (T, K), (K*H, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_beta = tl.make_block_ptr(beta + (bos * H + i_h), (T, ), (H, ), (i_t * BT, ), (BT, ), (0, )) + p_T = tl.make_block_ptr(AT + (bos * H + i_h) * BT, (T, BT), (BT*H, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_Twb = tl.load(p_w2, boundary_check=(0, 1)) + b_beta = tl.load(p_beta, boundary_check=(0, )) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_T = tl.load(p_T, boundary_check=(0, 1)) + b_w_beta = (b_w * b_beta[:, None]).to(b_w.dtype) + + o_i = tl.arange(0, BT) + b_qw = tl.where(o_i[:, None] >= o_i[None, :], tl.dot(b_q, tl.trans(b_w)), 0).to(b_q.dtype) + b_wbk = tl.where(o_i[:, None] > o_i[None, :], tl.dot(b_w_beta, tl.trans(b_k)), 0).to(b_k.dtype) + b_Twbk = tl.dot(b_T, b_wbk).to(b_w.dtype) + + p_dA_local = tl.make_block_ptr(dA_local + (bos * HQ + i_hq) * BT, (T, BT), (BT*HQ, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_dA_local = tl.load(p_dA_local, boundary_check=(0, 1)) + + # # Twb part qw part. + p_dq = tl.make_block_ptr(dq + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_dq = tl.load(p_dq, boundary_check=(0, 1)) + + p_dw1 = tl.make_block_ptr(dw1 + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_dw += tl.load(p_dw1, boundary_check=(0, 1)) + + b_dqw = -tl.dot(b_dA_local, tl.trans(b_Twbk)) - tl.dot(b_dq.to(b_Twb.dtype), tl.trans(b_Twb)) + p_dw2 = tl.make_block_ptr(dw2 + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_dTwb = -tl.dot(tl.trans(b_qw), b_dq) + tl.load(p_dw2, boundary_check=(0, 1)) + b_dT += tl.dot(b_dTwb.to(b_w_beta.dtype), tl.trans(b_w_beta)) + b_dw_beta += tl.dot(tl.trans(b_T), b_dTwb.to(b_T.dtype)) + + b_dqw = tl.where(tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :], b_dqw, 0) + b_dq += tl.dot(b_dA_local.to(b_k.dtype), b_k) + b_dq += tl.dot(b_dqw.to(b_w.dtype), b_w) + b_dw += tl.dot(tl.trans(b_dqw.to(b_q.dtype)), b_q) + p_q_new = tl.make_block_ptr(dq_new + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_q_new, b_dq.to(dq_new.dtype.element_ty), boundary_check=(0, 1)) + + # Twbk part + p_dk = tl.make_block_ptr(dk + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_dk = tl.load(p_dk, boundary_check=(0, 1)) + b_dTwbk = -tl.dot(tl.trans(b_qw), b_dA_local.to(b_qw.dtype)) - tl.dot(b_w, tl.trans(b_dk.to(b_w.dtype))) + b_dw -= tl.dot(b_Twbk, b_dk.to(b_w.dtype)) + b_dT += tl.dot(b_dTwbk.to(b_wbk.dtype), tl.trans(b_wbk)) + b_dwbk = tl.where(o_i[:, None] > o_i[None, :], tl.dot(tl.trans(b_T), b_dTwbk.to(b_T.dtype)), 0).to(b_w.dtype) + b_dw_beta += tl.dot(b_dwbk, b_k) + + b_dk += tl.dot(tl.trans(b_dwbk), b_w_beta) + b_dk += tl.dot(tl.trans(b_dA_local), b_q) + p_dk_new = tl.make_block_ptr(dk_new + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_dk_new, b_dk.to(dk_new.dtype.element_ty), boundary_check=(0, 1)) + + # matrix inverse's gradient + p_T = tl.make_block_ptr(AT + (bos * H + i_h) * BT, (BT, T), (1, BT*H), (0, i_t * BT), (BT, BT), (0, 1)) + b_Tt = tl.load(p_T, boundary_check=(0, 1)) + b_dT = tl.where(tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :], b_dT, 0).to(b_w.dtype) + b_dT = tl.dot(b_Tt, b_dT).to(b_w.dtype) + b_dT = tl.dot(b_dT, b_Tt) + b_dT = tl.where(tl.arange(0, BT)[:, None] > tl.arange(0, BT)[None, :], -b_dT, 0).to(b_k.dtype) + + b_dw_beta += tl.dot(b_dT, b_w) + b_dw += tl.dot(tl.trans(b_dT), b_w_beta) + b_dw += b_dw_beta * b_beta[:, None] + b_dbeta = tl.sum(b_dw_beta * b_w, axis=1) + + p_dw = tl.make_block_ptr(dw + (bos * HQ + i_hq) * K, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_dw, b_dw.to(dw.dtype.element_ty), boundary_check=(0, 1)) + p_dbeta = tl.make_block_ptr(dbeta + (bos * HQ + i_hq), (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + tl.store(p_dbeta, b_dbeta.to(dbeta.dtype.element_ty), boundary_check=(0, )) + + +def intra_chunk_preprocess_bwd_fn(q, k, w, w2, beta, + dq, dk, dA_local, + dw1, dw2, + A, L, D, do, scale, cu_seqlens=None): + BT = A.shape[-1] + HQ = q.shape[-2] + B, T, H, K = k.shape + G = HQ//H + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + grid = (NT, B*HQ) + # better precision because h would be of norm smaller than 1 anyways + + dbeta = torch.empty(B, T, HQ, device=q.device, dtype=k.dtype if G == 1 else torch.float32) + dw = torch.empty(B, T, HQ, K, device=q.device, dtype=k.dtype if G == 1 else torch.float32) + dk_new = torch.empty_like(dk, dtype=k.dtype if G == 1 else torch.float32) # float32 reduction + dq_new = torch.empty_like(dq, dtype=q.dtype) + + intra_chunk_preprocess_bwd_kernel[grid]( + q=q, k=k, w=w, w2=w2, beta=beta, + AT=A, + dA_local=dA_local, dq=dq, dq_new=dq_new, dk=dk, dk_new=dk_new, dw=dw, dbeta=dbeta, dw1=dw1, dw2=dw2, T=T, + offsets=cu_seqlens, indices=indices, + HQ=HQ, G=G, H=H, + K=K, BT=BT, BK=triton.next_power_of_2(K), + num_stages=3 if check_shared_mem('hopper') else 1, + ) + return dq_new, dk_new, dbeta, dw diff --git a/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_bwd_prepare.py b/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_bwd_prepare.py new file mode 100644 index 0000000000000000000000000000000000000000..3cf1ebc9584f6039fbd8ba6ff4d680697c50a2ac --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_bwd_prepare.py @@ -0,0 +1,196 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets + + +@triton.heuristics({ + "USE_GATE": lambda args: args['g_cumsum'] is not None, + "IS_VARLEN": lambda args: args['offsets'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def chunk_transform_qk_bwd_kernel_prepare( + q, + k, + v, + w, + beta, + g_cumsum, + L, + D, + h, + q_new, + k_new, + AT, + dA_local, + dv, + do, + dg_cumsum, + scale, + indices, # varlen helper + offsets, # varlen helper + chunk_offsets, # varlen helper + T, + G: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + BT: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_GATE: tl.constexpr, + RETURN_H: tl.constexpr, +): + i_t, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(offsets + i_n).to(tl.int32), tl.load(offsets + i_n + 1).to(tl.int32) + T = eos - bos + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + sm_scale = scale * 1.44269504 + # offset calculations + dA_local += (bos*HQ + i_hq) * BT + AT += (bos*H + i_h) * BT + q += (bos*HQ + i_hq) * K + q_new += (bos*HQ + i_hq) * K + k += (bos*H + i_h) * K + k_new += (bos*H + i_h) * K + w += (bos*H + i_h) * K + v += (bos*H + i_h) * V + do += (bos*HQ + i_hq) * V + dv += (bos*HQ + i_hq) * V + beta += (bos*H + i_h) + if RETURN_H: + h += ((boh + i_t) * H + i_h) * K * K + else: + h += (bos*H + i_h) * K + if USE_GATE: + g_cumsum += (bos*HQ + i_hq) + dg_cumsum += (bos*HQ + i_hq) + L += (bos*HQ + i_hq) + D += (bos*HQ + i_hq) + + p_q = tl.make_block_ptr(q, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (0, i_t * BT), (BK, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_beta = tl.make_block_ptr(beta, (T, ), (H, ), (i_t * BT, ), (BT, ), (0, )) + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_kt = tl.load(p_k, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_beta = tl.load(p_beta, boundary_check=(0, )) + p_T = tl.make_block_ptr(AT, (T, BT), (BT*H, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_T = tl.load(p_T, boundary_check=(0, 1)) * b_beta[None, :] + + o_i = tl.arange(0, BT) + m_t = o_i[:, None] >= o_i[None, :] + b_qw = tl.where(m_t, tl.dot(b_q, tl.trans(b_w.to(b_q.dtype))), 0).to(b_q.dtype) + b_qwT = tl.dot(b_qw, b_T.to(b_q.dtype)).to(b_q.dtype) + b_wbk = tl.where(o_i[:, None] > o_i[None, :], tl.dot(b_w.to(b_kt.dtype), b_kt), 0).to(b_q.dtype) + b_A = tl.where(m_t, tl.dot(b_q, b_kt) - tl.dot(b_qwT, b_wbk), 0) + + b_q = b_q.to(tl.float32) - tl.dot(b_qwT, b_w.to(b_qwT.dtype)) + p_q_new = tl.make_block_ptr(q_new, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, K), (1, 0)) + tl.store(p_q_new, b_q.to(p_q_new.dtype.element_ty), boundary_check=(0, 1)) + + if i_hq % G == 0: + b_Twb = tl.dot(b_T, b_w) # tf32 + p_h = tl.make_block_ptr(h, (T, K), (K * H, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_h, b_Twb.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + b_T_wbk = tl.dot(b_T.to(b_wbk.dtype), b_wbk).to(b_kt.dtype) + p_k_new = tl.make_block_ptr(k_new, (K, T), (1, K*H), (0, i_t * BT), (BK, BT), (0, 1)) + tl.store(p_k_new, (b_kt - tl.dot(tl.trans(b_w.to(b_kt.dtype)), b_T_wbk)).to(p_k_new.dtype.element_ty), boundary_check=(0, 1)) + + if USE_GATE: + p_g_cumsum = tl.make_block_ptr(g_cumsum, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + b_g_cumsum = tl.load(p_g_cumsum, boundary_check=(0, )) + b_A = b_A + (b_g_cumsum[:, None] - b_g_cumsum[None, :]) + b_A = tl.where((i_t * BT + tl.arange(0, BT) < T)[:, None], b_A, float("-inf")) # avoid nan + + p_l = tl.make_block_ptr(L, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + b_l = tl.load(p_l, boundary_check=(0, )) + p_delta = tl.make_block_ptr(D, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + delta = tl.load(p_delta, boundary_check=(0, )) + + b_A_softmax = tl.exp2(tl.where(o_i[:, None] >= o_i[None, :], b_A * sm_scale - b_l[:, None], float("-inf"))) + p_do = tl.make_block_ptr(do, (T, V), (HQ*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dv = tl.dot(tl.trans(b_A_softmax.to(b_do.dtype)), b_do) + p_dv = tl.make_block_ptr(dv, (T, V), (HQ*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + p_v = tl.make_block_ptr(v, (V, T), (1, H*V), (0, i_t * BT), (BV, BT), (0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dp = tl.dot(b_do, b_v) + b_dA = ((b_dp - delta[:, None]) * b_A_softmax * scale) + if USE_GATE: + b_dgq = tl.sum(b_dA, axis=1) - tl.sum(b_dA, axis=0) + p_dg = tl.make_block_ptr(dg_cumsum, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + tl.store(p_dg, b_dgq.to(p_dg.dtype.element_ty), boundary_check=(0,)) + p_dA = tl.make_block_ptr(dA_local, (T, BT), (BT*HQ, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), boundary_check=(0, 1)) + + +def intra_chunk_preprocess_bwd_prepare_fn(q, k, v, w, beta, g_cumsum, A, L, D, do, scale, return_h=True, cu_seqlens=None): + BT = A.shape[-1] + HQ = q.shape[-2] + B, T, H, K = k.shape + G = HQ//H + + V = v.shape[-1] + q_new = torch.empty_like(q) + k_new = torch.empty_like(k) + + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + chunk_offsets = prepare_chunk_offsets(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + grid = (NT, B*HQ) + h = torch.empty_like(w) + dA_local = torch.empty(B, T, HQ, BT, dtype=q.dtype, device=q.device) + dv = torch.empty(B, T, HQ, V, device=q.device, dtype=torch.float32) + dg_cumsum = torch.empty_like(g_cumsum) if g_cumsum is not None else None + + chunk_transform_qk_bwd_kernel_prepare[grid]( + q=q, + k=k, + v=v, + w=w, + beta=beta, + g_cumsum=g_cumsum, + AT=A, + dA_local=dA_local, + dv=dv, + dg_cumsum=dg_cumsum, + do=do, + L=L, + D=D, + h=h, + q_new=q_new, + k_new=k_new, + scale=scale, + offsets=cu_seqlens, + indices=indices, + chunk_offsets=chunk_offsets, + T=T, + H=H, + G=G, + HQ=HQ, + K=K, + V=V, + BK=triton.next_power_of_2(K), + BV=triton.next_power_of_2(V), + BT=BT, + RETURN_H=return_h, + ) + return q_new, k_new, h, dA_local, dv, dg_cumsum diff --git a/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_fwd.py b/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..13848506f6c97c01f6300d6d0b8baba819295f64 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/intra_chunk_preprocess_fwd.py @@ -0,0 +1,170 @@ + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices + + +@triton.heuristics({ + "USE_G": lambda args: args['g_cumsum'] is not None, + "IS_VARLEN": lambda args: args['offsets'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def intra_chunk_preprocess_fwd_kernel( + q, + k, + v, + w, + beta, + g_cumsum, + o, + A, + L, + M, + w2, + q_new, + k_new, + scale, + indices, # varlen helper + offsets, # varlen helper + T, + H: tl.constexpr, + G: tl.constexpr, + HQ: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + BT: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_G: tl.constexpr, +): + i_t, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(offsets + i_n).to(tl.int32), tl.load(offsets + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + sm_scale = scale * 1.44269504 + # offset calculations + A += (bos*H + i_h) * BT + q += (bos*HQ + i_hq) * K + q_new += (bos*HQ + i_hq) * K + k += (bos*H + i_h) * K + k_new += (bos*H + i_h) * K + w2 += (bos*H + i_h) * K + w += (bos*H + i_h) * K + v += (bos*H + i_h) * V + o += (bos*HQ + i_hq) * V + beta += (bos*H + i_h) + if USE_G: + g_cumsum += (bos*HQ + i_hq) + L += (bos*HQ + i_hq) + M += (bos*HQ + i_hq) + + p_q = tl.make_block_ptr(q, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (0, i_t * BT), (BK, BT), (0, 1)) + p_w = tl.make_block_ptr(w, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + p_beta = tl.make_block_ptr(beta, (T, ), (H, ), (i_t * BT, ), (BT, ), (0, )) + p_T = tl.make_block_ptr(A, (T, BT), (BT*H, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + b_beta = tl.load(p_beta, boundary_check=(0, )) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_kt = tl.load(p_k, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_T = tl.load(p_T, boundary_check=(0, 1)) + b_T = b_T * b_beta[None, :] + + o_i = tl.arange(0, BT) + m_t = o_i[:, None] >= o_i[None, :] + + b_qw = tl.where(m_t, tl.dot(b_q, tl.trans(b_w.to(b_q.dtype))), 0).to(b_q.dtype) + b_qwT = tl.dot(b_qw, b_T.to(b_q.dtype)).to(b_q.dtype) + b_wbk = tl.where(o_i[:, None] > o_i[None, :], tl.dot(b_w.to(b_q.dtype), b_kt), 0).to(b_q.dtype) + b_A = tl.where(m_t, tl.dot(b_q, b_kt) - tl.dot(b_qwT.to(b_q.dtype), b_wbk), 0) + + b_q = b_q.to(tl.float32) - tl.dot(b_qwT, b_w.to(b_q.dtype)) + p_q_new = tl.make_block_ptr(q_new, (T, K), (K*HQ, 1), (i_t * BT, 0), (BT, K), (1, 0)) + tl.store(p_q_new, b_q.to(p_q_new.dtype.element_ty), boundary_check=(0, 1)) + + if i_hq % G == 0: + b_Twb = tl.dot(b_T, b_w) + p_w2 = tl.make_block_ptr(w2, (T, K), (K*H, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_w2, b_Twb.to(p_w2.dtype.element_ty), boundary_check=(0, 1)) + b_T_wbk = tl.dot(b_T.to(b_kt.dtype), b_wbk).to(b_kt.dtype) + p_k_new = tl.make_block_ptr(k_new, (K, T), (1, K*H), (0, i_t * BT), (BK, BT), (0, 1)) + tl.store(p_k_new, (b_kt - tl.dot(tl.trans(b_w.to(b_kt.dtype)), b_T_wbk)).to(p_k_new.dtype.element_ty), boundary_check=(0, 1)) + + if USE_G: + p_g_cumsum = tl.make_block_ptr(g_cumsum, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + b_g_cumsum = tl.load(p_g_cumsum, boundary_check=(0, )) + b_A = b_A + (b_g_cumsum[:, None] - b_g_cumsum[None, :]) + b_A = tl.where((i_t * BT + tl.arange(0, BT) < T)[:, None], b_A, float("-inf")) # avoid nan + + b_qkT_softmax = tl.where(o_i[:, None] >= o_i[None, :], b_A * sm_scale, float("-inf")) + m_i = tl.max(b_qkT_softmax, 1) + b_qkT_softmax = tl.math.exp2(b_qkT_softmax - m_i[:, None]) + l_i = tl.sum(b_qkT_softmax, 1) + b_o = tl.dot(b_qkT_softmax.to(b_v.dtype), b_v) + p_o = tl.make_block_ptr(o, (T, V), (V*HQ, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + p_l = tl.make_block_ptr(L, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + p_m = tl.make_block_ptr(M, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + tl.store(p_m, m_i.to(p_m.dtype.element_ty), boundary_check=(0,)) + tl.store(p_l, l_i.to(p_l.dtype.element_ty), boundary_check=(0,)) + + + +def intra_chunk_preprocess_fwd_fn(q, k, v, w, beta, g_cumsum, A, scale, BT, cu_seqlens): + HQ = q.shape[-2] + B, T, H, K = k.shape + V = v.shape[-1] + q_new = torch.empty_like(q, dtype=torch.float32) # for stability + k_new = torch.empty_like(k) + o = torch.empty(B, T, HQ, V, device=q.device, dtype=torch.float32) + + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + grid = (NT, B*HQ) + L = torch.empty(B, T, HQ, dtype=torch.float32, device=q.device) + M = torch.empty(B, T, HQ, dtype=torch.float32, device=q.device) + w2 = torch.empty_like(w) + G = HQ//H + + intra_chunk_preprocess_fwd_kernel[grid]( + q=q, + k=k, + v=v, + w=w, + beta=beta, + g_cumsum=g_cumsum, + o=o, + A=A, + L=L, + M=M, + w2=w2, + q_new=q_new, + k_new=k_new, + scale=scale, + offsets=cu_seqlens, + indices=indices, + T=T, + H=H, + G=G, + HQ=HQ, + K=K, + V=V, + BK=triton.next_power_of_2(K), + BV=triton.next_power_of_2(V), + BT=BT, + num_warps=4 if BT == 64 else 2, + ) + return q_new, k_new, w2, o, L, M diff --git a/code/flash-linear-attention/fla/ops/path_attn/parallel.py b/code/flash-linear-attention/fla/ops/path_attn/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..6121eceb2512c554b650574d2c44234b296883a7 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/parallel.py @@ -0,0 +1,277 @@ +# Copyright (c) 2024, Songlin Yang, Yu Zhang + + +import torch +from einops import reduce + +from fla.ops.attn.parallel import parallel_attn_bwd_preprocess +from fla.ops.common.chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd +from fla.ops.path_attn.cumprod_householder_bwd import chunk_cumprod_householder_bwd_fn +from fla.ops.path_attn.cumprod_householder_fwd import chunk_cumprod_householder_fwd_fn +from fla.ops.path_attn.intra_chunk_preprocess_bwd import intra_chunk_preprocess_bwd_fn +from fla.ops.path_attn.intra_chunk_preprocess_bwd_prepare import intra_chunk_preprocess_bwd_prepare_fn +from fla.ops.path_attn.intra_chunk_preprocess_fwd import intra_chunk_preprocess_fwd_fn +from fla.ops.path_attn.parallel_path_bwd_inter_dkv import parallel_path_bwd_dkv_fn +from fla.ops.path_attn.parallel_path_bwd_inter_dqh import parallel_path_bwd_dq_fn +from fla.ops.path_attn.parallel_path_bwd_intra import parallel_path_bwd_intra_chunk_fn +from fla.ops.path_attn.parallel_path_fwd import parallel_path_fwd_fn +from fla.ops.path_attn.prepare_k_cache import prepare_k_cache_fn +from fla.ops.path_attn.transform_q import transform_q_fwd_fn +from fla.ops.utils.cumsum import chunk_global_cumsum +from fla.ops.utils.solve_tril import solve_tril +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, check_shared_mem, input_guard + + +class ParallelPATHAttentionFunction(torch.autograd.Function): + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, w, beta, g, scale, cu_seqlens, use_cache=False): + + g_cumsum = chunk_global_cumsum(g, cu_seqlens=cu_seqlens, output_dtype=torch.float32) if g is not None else None + BS = 64 if check_shared_mem('hopper') else 32 + BT = 128 if check_shared_mem('ampere') else 64 + + A = chunk_scaled_dot_kkt_fwd( + k=w, + beta=beta, + cu_seqlens=cu_seqlens, + chunk_size=BS, + output_dtype=torch.float32, + ) + A = solve_tril( + A=A, + cu_seqlens=cu_seqlens, + output_dtype=w.dtype, # force fp32? + ) + q_new, k_new, w2, o, L, M = intra_chunk_preprocess_fwd_fn( + q=q, + k=k, + v=v, + w=w, + beta=beta, + g_cumsum=g_cumsum, + A=A, + scale=scale, + BT=BS, + cu_seqlens=cu_seqlens, + ) + w_fp16 = w.to(torch.float16) + w2_fp16 = w2.to(torch.float16) + o, L = parallel_path_fwd_fn( + q=q_new, + k=k_new, + v=v, + L=L, + w1=w_fp16, + w2=w2_fp16, + M=M, + o=o, + g_cumsum=g_cumsum, + scale=scale, + cu_seqlens=cu_seqlens, + BT=BT, + BS=BS, + ) + k_cache = prepare_k_cache_fn(k=k_new, w1=w, w2=w2, cu_seqlens=cu_seqlens, BS=BS, use_cache=use_cache) + ctx.save_for_backward(q, k, v, w, g_cumsum, o, beta, L, A) + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + return o, k_cache + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dk_new): + q, k, v, w, g_cumsum, o, beta, L, A = ctx.saved_tensors + BT = 128 if check_shared_mem('ampere') else 64 + BS = 64 if check_shared_mem('hopper') else 32 + S = 512 + cu_seqlens = ctx.cu_seqlens + delta = parallel_attn_bwd_preprocess(o, do) + + q_new, k_new, h, dA_local, dv, dg_cumsum = intra_chunk_preprocess_bwd_prepare_fn( + q=q, + k=k, + v=v, + w=w, + beta=beta, + g_cumsum=g_cumsum, + A=A, + L=L, + D=delta, + do=do, + scale=ctx.scale, + cu_seqlens=cu_seqlens, + return_h=False, + ) + w_fp16 = w.to(torch.float16) + h_fp16 = h.to(torch.float16) + k_new_large, hc_suffix, hc_whole = chunk_cumprod_householder_fwd_fn( + k=k_new, + w1=w_fp16, + w2=h_fp16, + S=S, + BT=BS, + cu_seqlens=cu_seqlens, + ) + q_new_large = transform_q_fwd_fn(q=q_new, w1=w_fp16, w2=h_fp16, cu_seqlens=cu_seqlens, BT=BT, BS=BS, S=S) + w = w.to(q.dtype) + h = h.to(q.dtype) + A = A.to(q.dtype) + dk, dv, _ = parallel_path_bwd_dkv_fn( + q=q_new_large, + k=k_new_large, + v=v, + g_cumsum=g_cumsum, + do=do, + dv=dv, + dg_cumsum=dg_cumsum, + hc_whole=hc_whole, + scale=ctx.scale, + cu_seqlens=cu_seqlens, + L=L, + D=delta, + S=S, + BT=BT, + BS=BS, + ) + dq, dhc_whole, dg_cumsum = parallel_path_bwd_dq_fn( + q=q_new_large, + k=k_new_large, + v=v, + g_cumsum=g_cumsum, + do=do, + dg_cumsum=dg_cumsum, + hc_whole=hc_whole, + scale=ctx.scale, + cu_seqlens=cu_seqlens, + L=L, + D=delta, + S=S, + BT=BT, + BS=BS, + ) + dw1, dw2, dk = chunk_cumprod_householder_bwd_fn( + w1=w, + w2=h, + k=k_new, + dk=dk, + hc_suffix=hc_suffix, + dhc_whole=dhc_whole, + cu_seqlens=cu_seqlens, + S=S, + BT=BS, + ) + dq, dk, dv, dw1, dw2, dg_cumsum = parallel_path_bwd_intra_chunk_fn( + q=q_new, + k=k_new, + v=v, + g_cumsum=g_cumsum, + w1=w, + w2=h, + L=L, + D=delta, + scale=ctx.scale, + dw1=dw1, + dw2=dw2, + dq=dq, + dk=dk, + dv=dv, + do=do, + dg_cumsum=dg_cumsum, + cu_seqlens=cu_seqlens, + S=S, + BT=BS, + ) + dq, dk, dbeta, dw = intra_chunk_preprocess_bwd_fn( + q=q, + k=k, + w=w, + w2=h, + beta=beta, + dq=dq, + dk=dk, + dw1=dw1, + dw2=dw2, + dA_local=dA_local, + A=A, + L=L, + D=delta, + do=do, + scale=ctx.scale, + cu_seqlens=cu_seqlens, + ) + G = q.shape[-2] // k.shape[-2] + if G > 1: + assert dk.dtype == dv.dtype == dw.dtype == dbeta.dtype == torch.float32, 'reduction requires float32' + dk = reduce(dk, 'b t (h g) k -> b t h k', g=G, reduction='sum') + dv = reduce(dv, 'b t (h g) k -> b t h k', g=G, reduction='sum') + dw = reduce(dw, 'b t (h g) k -> b t h k', g=G, reduction='sum') + dbeta = reduce(dbeta, 'b t (h g) -> b t h', g=G, reduction='sum') + if dg_cumsum is not None: + dg_cumsum = chunk_global_cumsum(dg_cumsum, cu_seqlens=cu_seqlens, reverse=True) + return (dq.to(q.dtype), dk.to(k.dtype), dv.to(v.dtype), dw.to(w.dtype), + dbeta.to(beta.dtype), + dg_cumsum.to(g_cumsum.dtype) if g_cumsum is not None else None, + None, None, None, None) + + +@torch.compiler.disable +def parallel_path_attn( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + beta: torch.Tensor, + g: torch.Tensor | None = None, + scale: float = None, + cu_seqlens: torch.Tensor | None = None, + use_cache: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, HQ, K]` + k (torch.Tensor): + keys of shape `[B, T, H, K]` + v (torch.Tensor): + values of shape `[B, T, H, V]` + w (torch.Tensor): + weights of shape `[B, T, H, K]` + beta (torch.Tensor): + beta of shape `[B, T, H]` + g (torch.Tensor): + g of shape `[B, T, HQ]` + scale (float): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + use_cache (bool): + Whether to transform and cache the key values for decoding. Default: `False`. + + Returns: + o (torch.Tensor): + output of shape `[B, T, HQ, V]` + k_cache (torch.Tensor): + k_cache of shape `[B, T, H, K]` + """ + if scale is None: + scale = k.shape[-1]**-0.5 + assert w.dtype == beta.dtype == torch.float32, 'w, beta should be float32 to preserve precision.' + if g is not None: + assert g.dtype == torch.float32, 'g should be float32 to preserve precision.' + assert q.shape[-1] in [16, 32, 64, 128], "only support head_dim in [16, 32, 64, 128] for now. Stay tuned!" + assert v.shape[-1] in [16, 32, 64, 128], "only support head_dim in [16, 32, 64, 128] for now. Stay tuned!" + assert q.shape[-1] == k.shape[-1], 'q, k should have the same head_dim.' + assert k.shape == w.shape, 'k, w should have the same shape.' + assert beta.shape[:3] == k.shape[:3], 'beta should have the same number of heads as k' + if g is not None: + assert g.shape[:3] == q.shape[:3], 'g should have the same number of heads as q' + assert q.shape[-2] % k.shape[-2] == 0, 'the number of query heads should be divisible by the number of key heads' + o, k_cache = ParallelPATHAttentionFunction.apply(q, k, v, w, beta, g, scale, cu_seqlens, use_cache) + return o, k_cache + +parallel_path_attention = parallel_path_attn diff --git a/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_inter_dkv.py b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_inter_dkv.py new file mode 100644 index 0000000000000000000000000000000000000000..56882980daa0484a63fbd482552544d2ee584798 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_inter_dkv.py @@ -0,0 +1,189 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets + + +@triton.heuristics({ + 'USE_GATE': lambda args: args['g_cumsum'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_path_bwd_dkv_kernel( + q, + k, + v, + g_cumsum, + hc_whole, + scale, + L, + D, + dk, + dv, + do, + dg_cumsum, + cu_seqlens, + indices, + split_offsets, + T, + G: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + S: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_GATE: tl.constexpr, + NUM_BLOCKS: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + boh_large = tl.load(split_offsets + i_n).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + boh_large = i_n * tl.cdiv(T, S) + + # offset calculations + + do += (bos * HQ + i_hq) * V + dk += (bos * HQ + i_hq) * K + dv += (bos * HQ + i_hq) * K + L += (bos * HQ + i_hq) + D += (bos * HQ + i_hq) + + k += (bos * H + i_h) * K # GQA when H!=HQ + v += (bos * H + i_h) * V # GQA when H!=HQ + hc_whole += (boh_large * H + i_h) * K * K + + if USE_GATE: + g_cumsum += (bos * HQ + i_hq) + dg_cumsum += (bos * HQ + i_hq) + + # constants + sm_scale = scale * 1.44269504 + + # load query + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + p_v = tl.make_block_ptr(v, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + + if USE_GATE: + b_g_cumsum_k = tl.zeros([BT], dtype=tl.float32) + p_g_cumsum_k = tl.make_block_ptr(g_cumsum, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + b_g_cumsum_k += tl.load(p_g_cumsum_k, boundary_check=(0, )) + b_dg_cumsum_k = tl.zeros([BT], dtype=tl.float32) + else: + b_g_cumsum_k = None + b_dg_cumsum_k = None + + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dv = tl.zeros([BT, BK], dtype=tl.float32) + + last_chunk_start = tl.floor(i_t*BT / S).to(tl.int32) * S + idx_j = (tl.floor(i_t * BT / S).to(tl.int32) + 1).to(tl.int32) + + last_chunk_end = tl.ceil(T / BS).to(tl.int32) * BS - BS + + for offset in range(last_chunk_end, last_chunk_start+S-BS, -BS): + p_delta = tl.make_block_ptr(D, (T, ), (HQ, ), (offset, ), (BS, ), (0, )) + p_l = tl.make_block_ptr(L, (T, ), (HQ, ), (offset, ), (BS, ), (0, )) + b_delta = tl.load(p_delta, boundary_check=(0, )) + b_l = tl.load(p_l, boundary_check=(0, )) + + p_q = tl.make_block_ptr(q + ((bos * NUM_BLOCKS + idx_j) * HQ + i_hq) * K, (T, K), + (HQ*K*NUM_BLOCKS, 1), (offset, 0), (BS, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_A = tl.dot(b_k, tl.trans(b_q).to(b_k.dtype)) + if USE_GATE: + p_g_cumsum_q = tl.make_block_ptr(g_cumsum, (T, ), (HQ, ), (offset, ), (BS, ), (0, )) + b_g_cumsum_q = tl.load(p_g_cumsum_q, boundary_check=(0, )) + b_A = b_A + b_g_cumsum_q[None, :] - b_g_cumsum_k[:, None] + b_A = tl.where((offset + tl.arange(0, BS) < T)[None, :], b_A, float("-inf")) # avoid nan + b_A_softmax = tl.math.exp2(b_A * sm_scale - b_l[None, :]) + p_do = tl.make_block_ptr(do, (T, V), (HQ*V, 1), (offset, 0), (BS, BV), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dv += tl.dot(b_A_softmax.to(b_do.dtype), b_do) + b_dp = tl.dot(b_v, tl.trans(b_do)) + + b_dA = ((b_dp - b_delta[None, :]) * b_A_softmax * scale) + if USE_GATE: + b_dg_cumsum_k -= tl.sum(b_dA, axis=1) + b_dk += tl.dot(b_dA.to(b_q.dtype), b_q) + + p_dk = tl.make_block_ptr(dk, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_dk, b_dk.to(dk.dtype.element_ty), boundary_check=(0, 1)) + mask = i_t * BT + tl.arange(0, BT) < T + tl.atomic_add( + dv + (i_t * BT + tl.arange(0, BT))[:, None] * HQ * K + tl.arange(0, BK)[None, :], + b_dv, + mask=mask[:, None], + sem='relaxed', + ) + if USE_GATE: + tl.atomic_add(dg_cumsum + (i_t * BT + tl.arange(0, BT)) * HQ, b_dg_cumsum_k, mask=mask, sem='relaxed') + + +def parallel_path_bwd_dkv_fn( + q, k, v, g_cumsum, do, dv, dg_cumsum, + hc_whole, scale, L, D, + cu_seqlens, + S, BT, BS, +): + B, T, num_blocks, HQ, K = q.shape + V = v.shape[-1] + H = k.shape[-2] + G = HQ // H + + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + split_offsets = prepare_chunk_offsets(cu_seqlens, S) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + + if cu_seqlens is not None: + assert split_offsets[-1] == hc_whole.shape[0] + + dk = torch.empty(B, T, HQ, K, dtype=torch.float32, device=q.device) + + parallel_path_bwd_dkv_kernel[(NT, B*HQ)]( + q=q, + k=k, + v=v, + g_cumsum=g_cumsum, + hc_whole=hc_whole, + scale=scale, + L=L, + D=D, + dk=dk, + dv=dv, + do=do, + dg_cumsum=dg_cumsum, + cu_seqlens=cu_seqlens, + indices=indices, + split_offsets=split_offsets, + T=T, + S=S, + BT=BT, + BS=BS, + G=G, + HQ=HQ, + H=H, + K=K, + V=V, + BK=triton.next_power_of_2(K), + BV=triton.next_power_of_2(V), + num_warps=8 if (BT == 128 and K == 128) else 4, + NUM_BLOCKS=num_blocks, + ) + return dk, dv, dg_cumsum diff --git a/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_inter_dqh.py b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_inter_dqh.py new file mode 100644 index 0000000000000000000000000000000000000000..f1e2edf46cf0dc8d1c9a974f46597f723b81b522 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_inter_dqh.py @@ -0,0 +1,200 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp2 +from fla.utils import check_shared_mem + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_GATE': lambda args: args['g_cumsum'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_path_bwd_dq_kernel( + q, + k, + v, + g_cumsum, + hc_whole, + scale, + L, + D, + dq, + do, + dhc_whole, + dg_cumsum, + cu_seqlens, + indices, + split_offsets, # varlen specific + T, + G: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + S: tl.constexpr, # aka larger chunk size + NUM_BLOCKS: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_GATE: tl.constexpr, +): + i_t, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + boh_large = tl.load(split_offsets + i_n).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + boh_large = i_n * tl.cdiv(T, S) + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + + k += (bos * H + i_h) * K # GQA when H!=HQ + v += (bos * H + i_h) * V # GQA when H!=HQ + do += (bos * HQ + i_hq) * V + dq += (bos * HQ + i_hq) * K + hc_whole += (boh_large * H + i_h) * K * K + dhc_whole += (boh_large * HQ + i_hq) * K * K + L += (bos * HQ + i_hq) + D += (bos * HQ + i_hq) + if USE_GATE: + g_cumsum += (bos * HQ + i_hq) + dg_cumsum += (bos * HQ + i_hq) + + # constants + stride_h = H * K * K + stride_hq = HQ * K * K + sm_scale = scale * 1.44269504 + + # load query + p_do = tl.make_block_ptr(do, (T, V), (HQ*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + + p_l = tl.make_block_ptr(L, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + p_d = tl.make_block_ptr(D, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + b_l = tl.load(p_l, boundary_check=(0,)) + b_delta = tl.load(p_d, boundary_check=(0,)) + + if USE_GATE: + p_g_cumsum_q = tl.make_block_ptr(g_cumsum, (T,), (HQ,), (i_t * BT,), (BT,), (0,)) + b_g_cumsum_q = tl.load(p_g_cumsum_q, boundary_check=(0,)).to(tl.float32) + b_dg_cumsum_q = tl.zeros([BT], dtype=tl.float32) + else: + b_g_cumsum_q = None + b_dg_cumsum_q = None + + curr_end = ((i_t * BT // S) * S).to(tl.int32) + b_dq = tl.zeros([BT, K], dtype=tl.float32) + + for offset_outer in range(0, curr_end, S): + idx_j = offset_outer // S + p_q = tl.make_block_ptr(q + ((bos * NUM_BLOCKS + idx_j + 1) * HQ + i_hq) * K, (T, K), + (HQ*K*NUM_BLOCKS, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + + b_dh = -tl.dot(tl.trans(b_q), b_dq.to(b_q.dtype)) + tl.atomic_add(dhc_whole + idx_j * stride_hq + tl.arange(0, K) + [:, None] * K + tl.arange(0, K)[None, :], b_dh, sem='relaxed') + p_h = tl.make_block_ptr(hc_whole + idx_j * stride_h, (K, K), (K, 1), (0, 0), (BK, BK), (1, 0)) + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dq = b_dq - tl.dot(b_dq.to(b_h.dtype), tl.trans(b_h)) + + for offset in range(offset_outer, min(offset_outer+S, i_t*BT), BS): + p_k = tl.make_block_ptr(k, (T, K), (H * K, 1), (offset, 0), (BS, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_A = tl.dot(b_q, tl.trans(b_k).to(b_q.dtype)) + if USE_GATE: + p_g_cumsum_k = tl.make_block_ptr(g_cumsum, (T,), (HQ,), (offset,), (BS,), (0,)) + b_g_cumsum_k = tl.load(p_g_cumsum_k, boundary_check=(0,)).to(tl.float32) + b_A = b_A + b_g_cumsum_q[:, None] - b_g_cumsum_k[None, :] + b_A = exp2(b_A * sm_scale - b_l[:, None]) + b_A = tl.where(m_t[:, None], b_A, 0) + p_v = tl.make_block_ptr(v, (V, T), (1, V*H), (0, offset), (BK, BS), (0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dp = tl.dot(b_do, b_v.to(b_do.dtype)) + b_dA = (b_dp - b_delta[:, None]) * b_A * scale + b_dq += tl.dot(b_dA.to(b_k.dtype), b_k) + if USE_GATE: + b_dg_cumsum_q += tl.sum(b_dA, axis=1) + + p_dq = tl.make_block_ptr(dq, (T, K), (K * HQ, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_dq, b_dq.to(dq.dtype.element_ty), boundary_check=(0, 1)) + if USE_GATE: + tl.atomic_add(dg_cumsum + o_t * HQ, b_dg_cumsum_q, mask=m_t, sem='relaxed') + + +def parallel_path_bwd_dq_fn( + q, + k, + v, + g_cumsum, + do, + dg_cumsum, + hc_whole, + scale, + L, + D, + cu_seqlens, + S, + BT, + BS, +): + B, T, num_blocks, HQ, K = q.shape + H, V = v.shape[-2:] + G = HQ // H + BK, BV = triton.next_power_of_2(K), triton.next_power_of_2(V) + + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + split_offsets = prepare_chunk_offsets(cu_seqlens, S) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + + # should be NS + if cu_seqlens is not None: + assert split_offsets[-1] == hc_whole.shape[0] + dq = torch.empty(B, T, HQ, K, dtype=torch.float32, device=q.device) + + # [NS, HQ, K, K] instead of [NS, H, K, K] + # atomic add must be initialized to 0 + dhc_whole = torch.zeros(hc_whole.shape[0], HQ, K, K, dtype=torch.float32, device=q.device) + + parallel_path_bwd_dq_kernel[(NT, B*HQ)]( + q=q, + k=k, + v=v, + g_cumsum=g_cumsum, + hc_whole=hc_whole, + scale=scale, + L=L, + D=D, + dq=dq, + do=do, + dhc_whole=dhc_whole, + dg_cumsum=dg_cumsum, + cu_seqlens=cu_seqlens, + indices=indices, + split_offsets=split_offsets, + T=T, + S=S, + BT=BT, + BS=BS, + G=G, + HQ=HQ, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + NUM_BLOCKS=num_blocks, + num_warps=8 if (BT == 128 and K == 128) else 4, + num_stages=3 if check_shared_mem('ampere') else 2, + ) + return dq, dhc_whole, dg_cumsum diff --git a/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_intra.py b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_intra.py new file mode 100644 index 0000000000000000000000000000000000000000..6ce2740c58f3941bbd561b4fb126ac630472125c --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_bwd_intra.py @@ -0,0 +1,172 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['offsets'] is not None, + 'USE_GATE': lambda args: args['g_cumsum'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_path_bwd_intra_chunk_kernel( + q, k, v, g_cumsum, w1, w2, + L, D, + dq, dq_new, dk, dv, dw1, dw2, do, dg_cumsum, + offsets, indices, + T, scale, + G: tl.constexpr, HQ: tl.constexpr, H: tl.constexpr, + K: tl.constexpr, V: tl.constexpr, BK: tl.constexpr, BV: tl.constexpr, + BT: tl.constexpr, S: tl.constexpr, + IS_VARLEN: tl.constexpr, USE_GATE: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(offsets + i_n).to(tl.int32), tl.load(offsets + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + + # offset calculations + k += (bos * H + i_h) * K # GQA when H!=HQ + v += (bos * H + i_h) * V # GQA when H!=HQ + w1 += (bos * H + i_h) * K + w2 += (bos * H + i_h) * K + + q += (bos * HQ + i_hq) * K + dq += (bos * HQ + i_hq) * K + dq_new += (bos * HQ + i_hq) * K + dk += (bos * HQ + i_hq) * K + dv += (bos * HQ + i_hq) * V + do += (bos * HQ + i_hq) * V + dw1 += (bos * HQ + i_hq) * K + dw2 += (bos * HQ + i_hq) * K + L += (bos * HQ + i_hq) + D += (bos * HQ + i_hq) + if USE_GATE: + g_cumsum += (bos * HQ + i_hq) + dg_cumsum += (bos * HQ + i_hq) + + # constants + sm_scale = scale * 1.44269504 + + p_do = tl.make_block_ptr(do, (T, V), (HQ*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + + p_delta = tl.make_block_ptr(D, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + b_delta = tl.load(p_delta, boundary_check=(0, )) + p_l = tl.make_block_ptr(L, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + b_l = tl.load(p_l, boundary_check=(0, )) + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + p_dq = tl.make_block_ptr(dq, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_dq += tl.load(p_dq, boundary_check=(0, 1)) + p_q = tl.make_block_ptr(q, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + + if USE_GATE: + p_gq_cumsum = tl.make_block_ptr(g_cumsum, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0, )) + b_gq_cumsum = tl.load(p_gq_cumsum, boundary_check=(0, )) + b_dgq = tl.zeros([BT ], dtype=tl.float32) + else: + b_dgq = None + + curr_start = (tl.floor(i_t * BT / S).to(tl.int32) * S).to(tl.int32) + + for offset in range(curr_start, i_t * BT, BT): + mask = offset + tl.arange(0, BT) < T + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (offset, 0), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_q_tmp = tl.zeros([BT, BK], dtype=tl.float32) + b_q_tmp += b_q + for i_t_small in range(i_t * BT - BT, offset, -BT): + p_w1 = tl.make_block_ptr(w1, (T, K), (H*K, 1), (i_t_small, 0), (BT, BK), (1, 0)) + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + p_w2 = tl.make_block_ptr(w2, (T, K), (H*K, 1), (i_t_small, 0), (BT, BK), (1, 0)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + b_A_tmp = tl.dot(b_q_tmp.to(b_w1.dtype), tl.trans(b_w1)) + b_q_tmp -= tl.dot(b_A_tmp.to(b_w1.dtype), b_w2) + b_q2 = b_q_tmp.to(b_k.dtype) + b_A = tl.dot(b_q2, tl.trans(b_k)) + if USE_GATE: + p_gk_cumsum = tl.make_block_ptr(g_cumsum, (T, ), (HQ, ), (offset, ), (BT, ), (0, )) + b_gk_cumsum = tl.load(p_gk_cumsum, boundary_check=(0, )) + b_A = b_A + b_gq_cumsum[:, None] - b_gk_cumsum[None, :] + b_A = tl.where((i_t * BT + tl.arange(0, BT) < T)[:, None], b_A, float("-inf")) # avoid nan + b_A_softmax = tl.math.exp2(b_A * sm_scale - b_l[:, None]) + b_dv = tl.dot(tl.trans(b_A_softmax.to(b_do.dtype)), b_do) + tl.atomic_add( + dv + ((offset + tl.arange(0, BT)) * HQ * V)[:, None] + tl.arange(0, BV)[None, :], + b_dv.to(dv.dtype.element_ty), + mask=mask[:, None], + sem='relaxed', + ) + p_v = tl.make_block_ptr(v, (T, V), (V*H, 1), (offset, 0), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dp = tl.dot(b_do, tl.trans(b_v)) + b_dA = ((b_dp - b_delta[:, None]) * b_A_softmax * scale) + if USE_GATE: + b_dgk = -tl.sum(b_dA, axis=0) + tl.atomic_add(dg_cumsum + (offset + tl.arange(0, BT)) * HQ, b_dgk, mask=mask, sem='relaxed') + b_dgq += tl.sum(b_dA, axis=1) + b_dA = b_dA.to(b_v.dtype) + b_dk = tl.dot(tl.trans(b_dA), b_q2) + tl.atomic_add(dk + (offset + tl.arange(0, BT))[:, None] * HQ*K + tl.arange(0, + BK)[None, :], b_dk, mask=mask[:, None], sem='relaxed') + p_w1 = tl.make_block_ptr(w1, (T, K), (H*K, 1), (offset, 0), (BT, BK), (1, 0)) + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + p_w2 = tl.make_block_ptr(w2, (T, K), (H*K, 1), (offset, 0), (BT, BK), (1, 0)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + b_dA2 = tl.dot(b_dq.to(b_w2.dtype), tl.trans(b_w2)).to(b_v.dtype) + b_A2 = tl.dot(b_q2.to(b_w1.dtype), tl.trans(b_w1)).to(b_v.dtype) + b_dw2 = -tl.dot(tl.trans(b_A2), b_dq.to(b_v.dtype)) + tl.atomic_add(dw2 + (offset + tl.arange(0, BT))[:, None] * HQ*K + tl.arange(0, + BK)[None, :], b_dw2, mask=mask[:, None], sem='relaxed') + b_dw1 = -tl.dot(tl.trans(b_dA2), b_q2.to(b_v.dtype)) + tl.atomic_add(dw1 + (offset + tl.arange(0, BT))[:, None] * HQ*K + tl.arange(0, + BK)[None, :], b_dw1, mask=mask[:, None], sem='relaxed') + b_dq -= tl.dot(b_dA2, b_w1.to(b_v.dtype)) + b_dq += tl.dot(b_dA.to(b_k.dtype), b_k) + + p_dq_new = tl.make_block_ptr(dq_new, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_dq_new, b_dq.to(dq_new.dtype.element_ty), boundary_check=(0, 1)) + mask = i_t * BT + tl.arange(0, BT) < T + if USE_GATE: + tl.atomic_add(dg_cumsum + (i_t * BT + tl.arange(0, BT)) * HQ, b_dgq, mask=mask, sem='relaxed') + + +def parallel_path_bwd_intra_chunk_fn( + q, k, v, g_cumsum, w1, w2, + dq, dk, dv, dg_cumsum, dw1, dw2, do, + scale, L, D, + cu_seqlens, + S, BT, +): + assert dk.dtype == dv.dtype == dw1.dtype == dw2.dtype == torch.float32, 'atomic_add requires float32' + B, T, HQ, K = q.shape + assert dk.shape == dq.shape + + V = v.shape[-1] + H = k.shape[-2] + G = HQ // H + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + dq_new = torch.empty_like(dq, dtype=q.dtype) + parallel_path_bwd_intra_chunk_kernel[(NT, B*HQ)]( + q=q, k=k, v=v, g_cumsum=g_cumsum, + w1=w1, w2=w2, L=L, D=D, + dq=dq, dq_new=dq_new, dk=dk, dv=dv, dw1=dw1, dw2=dw2, + do=do, dg_cumsum=dg_cumsum, + offsets=cu_seqlens, indices=indices, + T=T, S=S, BT=BT, scale=scale, + G=G, HQ=HQ, H=H, K=K, V=V, + BK=triton.next_power_of_2(K), BV=triton.next_power_of_2(V), + ) + return dq_new, dk, dv, dw1, dw2, dg_cumsum diff --git a/code/flash-linear-attention/fla/ops/path_attn/parallel_path_fwd.py b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_fwd.py new file mode 100644 index 0000000000000000000000000000000000000000..319661fd89333e7dde907b670ad5971ddc0c2638 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/parallel_path_fwd.py @@ -0,0 +1,195 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices + + +@triton.heuristics({ + 'USE_GATE': lambda args: args['g_cumsum'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_path_fwd_kernel( + q, + k, + v, + o, + o_new, + g_cumsum, + w1, + w2, + scale, + L, + L_new, + M, + cu_seqlens, + indices, + T, + G: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_GATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_q = tl.zeros([BT, BK], dtype=tl.float32) + b_q += tl.load(p_q, boundary_check=(0, 1)) + sm_scale = scale * 1.44269504 + b_o = tl.zeros([BT, BV], dtype=tl.float32) + p_o = tl.make_block_ptr(o + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, 0), (BT, BV), (1, 0)) + b_o += tl.load(p_o, boundary_check=(0, 1)) + + p_L = tl.make_block_ptr(L + bos * HQ + i_hq, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0,)) + p_M = tl.make_block_ptr(M + bos * HQ + i_hq, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0,)) + b_l = tl.load(p_L, boundary_check=(0,)) + b_m = tl.load(p_M, boundary_check=(0,)) + + if USE_GATE: + p_g_cumsum_q = tl.make_block_ptr(g_cumsum + bos * HQ + i_hq, (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0,)) + b_g_cumsum_q = tl.load(p_g_cumsum_q, boundary_check=(0,)) + else: + b_g_cumsum_q = None + + for offset in range((i_t + 1) * BT - 2 * BS, i_t*BT-BS, -BS): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, K*H), (0, offset), (BK, BS), (0, 1)) # GQA when H!=HQ + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (V*H, 1), (offset, 0), (BS, BV), (1, 0)) # GQA when H!=HQ + p_w1 = tl.make_block_ptr(w1 + (bos * H + i_h) * K, (K, T), (1, K*H), (0, offset), (BK, BS), (0, 1)) + p_w2 = tl.make_block_ptr(w2 + (bos * H + i_h) * K, (T, K), (K*H, 1), (offset, 0), (BS, BK), (1, 0)) + # [BK, BS] + + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BK, BK] + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + # [BT, BS] + m_s = i_t * BT + tl.arange(0, BT) >= (offset + BS) + b_s = tl.dot(b_q.to(b_k.dtype), b_k) + + if USE_GATE: + p_g_cumsum_k = tl.make_block_ptr(g_cumsum + (bos * HQ + i_hq), (T, ), (HQ, ), (offset, ), (BS, ), (0,)) + b_g_cumsum_k = tl.load(p_g_cumsum_k, boundary_check=(0,)) + b_s = b_s + b_g_cumsum_q[:, None] - b_g_cumsum_k[None, :] + b_s = tl.where(m_s[:, None], b_s * sm_scale, float("-inf")) + b_m_new = tl.maximum(b_m, tl.max(b_s, 1)) + alpha = tl.math.exp2(b_m - b_m_new) + b_s = tl.math.exp2(b_s - b_m_new[:, None]) + b_o *= alpha[:, None] + b_l = b_l * alpha + tl.sum(b_s, 1) + b_m = b_m_new + b_o += tl.dot(b_s.to(b_v.dtype), b_v) + b_s2 = tl.dot(b_q.to(b_w1.dtype), b_w1) + b_s2 = tl.where(m_s[:, None], b_s2, 0) + b_q -= tl.dot(b_s2.to(b_w2.dtype), b_w2) + + tl.debug_barrier() + + for offset in range(i_t * BT - BS, -BS, -BS): + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, K*H), (0, offset), (BK, BS), (0, 1)) # GQA when H!=HQ + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (V*H, 1), (offset, 0), (BS, BV), (1, 0)) # GQA when H!=HQ + p_w1 = tl.make_block_ptr(w1 + (bos * H + i_h) * K, (K, T), (1, K*H), (0, offset), (BK, BS), (0, 1)) + p_w2 = tl.make_block_ptr(w2 + (bos * H + i_h) * K, (T, K), (K*H, 1), (offset, 0), (BS, BK), (1, 0)) + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + # [BT, BS] + b_s = tl.dot(b_q.to(b_k.dtype), b_k) + if USE_GATE: + p_g_cumsum_k = tl.make_block_ptr(g_cumsum + (bos * HQ + i_hq), (T, ), (HQ, ), (offset, ), (BS, ), (0,)) + b_g_cumsum_k = tl.load(p_g_cumsum_k, boundary_check=(0,)) + b_s = b_s + b_g_cumsum_q[:, None] - b_g_cumsum_k[None, :] + b_s = b_s * sm_scale + b_m_new = tl.maximum(b_m, tl.max(b_s, 1)) + alpha = tl.math.exp2(b_m - b_m_new) + b_s = tl.math.exp2(b_s - b_m_new[:, None]) + b_o *= alpha[:, None] + b_l = b_l * alpha + tl.sum(b_s, 1) + b_m = b_m_new + b_o += tl.dot(b_s.to(b_v.dtype), b_v) + b_s2 = tl.dot(b_q.to(b_w1.dtype), b_w1) + b_q -= tl.dot(b_s2.to(b_w2.dtype), b_w2) + + b_o = b_o / b_l[:, None] + p_o_new = tl.make_block_ptr(o_new + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + tl.store(p_o_new, b_o.to(p_o_new.dtype.element_ty), boundary_check=(0, 1)) + b_l = tl.math.log2(b_l) + b_m + p_L_new = tl.make_block_ptr(L_new + (bos * HQ + i_hq), (T, ), (HQ, ), (i_t * BT, ), (BT, ), (0,)) + tl.store(p_L_new, b_l.to(p_L_new.dtype.element_ty), boundary_check=(0,)) + + +def parallel_path_fwd_fn( + q, + k, + v, + o, + g_cumsum, + w1, + w2, + scale, + L, + M, + cu_seqlens, + BT, + BS, +): + B, T, HQ, K = q.shape + V = v.shape[-1] + H = k.shape[-2] + G = HQ // H + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + grid = (NT, B * HQ) + o_new = torch.empty_like(o, dtype=v.dtype) + L_new = torch.empty_like(L) + + parallel_path_fwd_kernel[grid]( + q=q, + k=k, + v=v, + o=o, + o_new=o_new, + w1=w1, + w2=w2, + g_cumsum=g_cumsum, + scale=scale, + cu_seqlens=cu_seqlens, + indices=indices, + L=L, + L_new=L_new, + M=M, + T=T, + K=K, + V=V, + BK=triton.next_power_of_2(K), + BV=triton.next_power_of_2(V), + G=G, + HQ=HQ, + H=H, + BS=BS, + BT=BT, + num_warps=8 if (BT == 128 and K == 128) else 4, + ) + return o_new, L_new diff --git a/code/flash-linear-attention/fla/ops/path_attn/prepare_k_cache.py b/code/flash-linear-attention/fla/ops/path_attn/prepare_k_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..28462856e3aee6ff160d3c1c4048a830ebc8d2de --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/prepare_k_cache.py @@ -0,0 +1,74 @@ +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['offsets'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def parallel_path_fwd_kernel_prepare_k_cache( + k, k_new, w1, w2, + offsets, indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(offsets + i_n).to(tl.int32), tl.load(offsets + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + + k += (bos * H + i_h) * K + k_new += (bos * H + i_h) * K + w1 += (bos * H + i_h) * K + w2 += (bos * H + i_h) * K + # constants + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_k = tl.zeros([BT, BK], dtype=tl.float32) + b_k += tl.load(p_k, boundary_check=(0, 1)) + for k_block_idx in range(i_t + 1, tl.cdiv(T, BT)): + p_w1 = tl.make_block_ptr(w1, (T, K), (H*K, 1), (k_block_idx * BT, 0), (BT, BK), (1, 0)) + p_w2 = tl.make_block_ptr(w2, (T, K), (H*K, 1), (k_block_idx * BT, 0), (BT, BK), (1, 0)) + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + b_A = tl.dot(b_k.to(b_w2.dtype), tl.trans(b_w2)) + b_k = b_k - tl.dot(b_A.to(b_w1.dtype), b_w1) + + p_k_new = tl.make_block_ptr(k_new, (T, K), (H*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_k_new, b_k.to(p_k_new.dtype.element_ty), boundary_check=(0, 1)) + + +def prepare_k_cache_fn(k, w1, w2, cu_seqlens, BS, use_cache=False): + if not use_cache: + return None + else: + B, T, H, K = k.shape + k_new = torch.empty_like(k) + indices = prepare_chunk_indices(cu_seqlens, BS) if cu_seqlens is not None else None + NT = triton.cdiv(T, BS) if cu_seqlens is None else len(indices) + grid = (NT, B * H) + parallel_path_fwd_kernel_prepare_k_cache[grid]( + k=k, + k_new=k_new, + w1=w1, + w2=w2, + offsets=cu_seqlens, + indices=indices, + H=H, + T=T, + K=K, + BT=BS, + BK=triton.next_power_of_2(K), + ) + return k_new diff --git a/code/flash-linear-attention/fla/ops/path_attn/transform_q.py b/code/flash-linear-attention/fla/ops/path_attn/transform_q.py new file mode 100644 index 0000000000000000000000000000000000000000..d065a884e055c6d7112a17178702633ff176d231 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/path_attn/transform_q.py @@ -0,0 +1,107 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import get_max_num_splits, prepare_chunk_indices + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.jit(do_not_specialize=['T']) +def transform_q_fwd_kernel( + q, + q_new, + w1, + w2, + cu_seqlens, + indices, + T, + S: tl.constexpr, + G: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + NUM_BLOCKS: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // G + + if IS_VARLEN: + i_n, i_t = tl.load(indices + i_t * 2).to(tl.int32), tl.load(indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + i_n = i_b + bos, eos = i_n * T, i_n * T + T + # boh = i_n * tl.cdiv(T, BS) + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + b_q = tl.zeros([BT, BK], dtype=tl.float32) + b_q += tl.load(p_q, boundary_check=(0, 1)) + + if BS == BT: + if (i_t * BT) % S == 0: + p_q_new = tl.make_block_ptr(q_new + ((bos * NUM_BLOCKS + (i_t * BT // S)) * HQ + i_hq) * K, + (T, K), (HQ*K*NUM_BLOCKS, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_q_new, b_q.to(q_new.dtype.element_ty), boundary_check=(0, 1)) + + for offset in range((i_t + 1) * BT - 2 * BS, S-BS, -BS): + p_w1 = tl.make_block_ptr(w1 + (bos * H + i_h) * K, (K, T), (1, K*H), (0, offset), (BK, BS), (0, 1)) + p_w2 = tl.make_block_ptr(w2 + (bos * H + i_h) * K, (T, K), (K*H, 1), (offset, 0), (BS, BK), (1, 0)) + b_w1 = tl.load(p_w1, boundary_check=(0, 1)) + b_w2 = tl.load(p_w2, boundary_check=(0, 1)) + m_s = i_t * BT + tl.arange(0, BT) >= (offset + BS) + b_s2 = tl.dot(b_q.to(b_w1.dtype), b_w1) + b_s2 = tl.where(m_s[:, None], b_s2, 0) + b_q -= tl.dot(b_s2.to(b_w2.dtype), b_w2) + + if offset % S == 0: + p_q_new = tl.make_block_ptr(q_new + ((bos * NUM_BLOCKS + (offset // S)) * HQ + i_hq) * K, + (T, K), (HQ*K*NUM_BLOCKS, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + tl.store(p_q_new, b_q.to(q_new.dtype.element_ty), boundary_check=(0, 1)) + + +def transform_q_fwd_fn( + q, + w1, + w2, + cu_seqlens, + BT, + BS, + S, +): + B, T, HQ, K = q.shape + H = w1.shape[-2] + G = HQ // H + indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(indices) + + num_blocks = triton.cdiv(T, S) if cu_seqlens is None else get_max_num_splits(cu_seqlens, S) + q_new = torch.zeros(B, T, num_blocks, HQ, K, dtype=q.dtype, device=q.device) + transform_q_fwd_kernel[(NT, B * HQ)]( + q=q, + q_new=q_new, + w1=w1, + w2=w2, + cu_seqlens=cu_seqlens, + indices=indices, + T=T, + K=K, + BK=triton.next_power_of_2(K), + G=G, + HQ=HQ, + H=H, + BS=BS, + BT=BT, + S=S, + NUM_BLOCKS=num_blocks, + num_warps=8 if (BT == 128 and K == 128) else 4, + ) + return q_new diff --git a/code/flash-linear-attention/fla/ops/rebased/__init__.py b/code/flash-linear-attention/fla/ops/rebased/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..292c918e3e9ef8319a2738bcb5ca3c314c22e1df --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rebased/__init__.py @@ -0,0 +1,6 @@ + +from .parallel import parallel_rebased + +__all__ = [ + 'parallel_rebased', +] diff --git a/code/flash-linear-attention/fla/ops/rebased/naive.py b/code/flash-linear-attention/fla/ops/rebased/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..5811e6634c6c2e7a9e0f699da6f302457aeb4980 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rebased/naive.py @@ -0,0 +1,25 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + + +def naive_parallel_rebased( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + use_norm: bool = True, +) -> torch.Tensor: + if scale is None: + scale = q.shape[-1] ** -0.5 + q = q * scale + attn = q @ k.transpose(-2, -1) + attn = attn ** 2 + attn.masked_fill_(~torch.tril(torch.ones(q.shape[-2], q.shape[-2], dtype=torch.bool, device=q.device)), 0) + o = attn @ v + if use_norm: + z = attn.sum(-1) + return o / (z[..., None] + 1e-6) + else: + return o diff --git a/code/flash-linear-attention/fla/ops/rebased/parallel.py b/code/flash-linear-attention/fla/ops/rebased/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..83351ee76f9438403f8b6b68e2ee6c298b9c023a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rebased/parallel.py @@ -0,0 +1,463 @@ + +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import torch +import triton +import triton.language as tl + +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + +# Rebased: Linear Transformers with Learnable Kernel Functions are Better In-Context Models +# https://github.com/corl-team/rebased/blob/main/flash_linear_attention/fla/ops/triton/rebased_fast/parallel.py + + +@triton.jit(do_not_specialize=['T']) +def parallel_rebased_fwd_kernel( + q, + k, + v, + o, + z, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + # i_c: chunk index. used for sequence parallelism + i_kv, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + NV = tl.cdiv(V, BV) + i_k = i_kv // (NV) + i_v = i_kv % (NV) + + p_q = tl.make_block_ptr(q + i_bh * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k*BK, 0), (BK, BTS), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (0, i_v*BV), (BTS, BV), (1, 0)) + + # [BQ, BD] block Q, in the shared memory throughout the whole kernel + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + b_o = tl.zeros([BTL, BV], dtype=tl.float32) + b_z = tl.zeros([BTL], dtype=tl.float32) + + # Q block and K block have no overlap + # no need for mask, thereby saving flops + for _ in range(0, i_c*BTL, BTS): + # [BK, BTS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + + # [BTS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + b_s = tl.dot(b_q, (b_k), allow_tf32=False) + b_s = b_s * b_s + b_z += tl.sum(b_s, axis=1) + + # [BQ, BD] + b_o = b_o + tl.dot(b_s.to(b_v.dtype), b_v, allow_tf32=False) + p_k = tl.advance(p_k, (0, BTS)) + p_v = tl.advance(p_v, (BTS, 0)) + + # # rescale interchunk output + tl.debug_barrier() + o_q = tl.arange(0, BTL) + # # sync threads, easy for compiler to optimize + # tl.debug_barrier() + + o_k = tl.arange(0, BTS) + p_k = tl.make_block_ptr(k + i_bh * T*K, (K, T), (1, K), (i_k*BK, i_c*BTL), (BK, BTS), (0, 1)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_c*BTL, i_v*BV), (BTS, BV), (1, 0)) + # Q block and K block have overlap. masks required + for _ in range(i_c*BTL, (i_c + 1) * BTL, BTS): + # [BK, BTS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BTS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + m_s = o_q[:, None] >= o_k[None, :] + b_s = tl.dot(b_q, b_k, allow_tf32=False) + b_s = b_s * b_s + b_s = tl.where(m_s, b_s, 0) + b_z += tl.sum(b_s, axis=1) + # [BTL, BV] + b_o += tl.dot(b_s.to(b_q.dtype), b_v, allow_tf32=False) + p_k = tl.advance(p_k, (0, BTS)) + p_v = tl.advance(p_v, (BTS, 0)) + o_k += BTS + + p_o = tl.make_block_ptr(o + (i_bh + B * H * i_k) * T*V, (T, V), (V, 1), (i_c*BTL, i_v*BV), (BTL, BV), (1, 0)) + p_z = z + (i_bh + B * H * i_k) * T + i_c*BTL + tl.arange(0, BTL) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_z, b_z.to(p_z.dtype.element_ty), mask=((i_c*BTL + tl.arange(0, BTL)) < T)) + + +@triton.jit(do_not_specialize=['T']) +def _parallel_rebased_bwd_dq( + i_bh, + i_c, + i_k, + i_v, + i_h, + q, + k, + v, + do, + dz, + dq, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + p_do = tl.make_block_ptr(do + i_bh * T*V, (T, V), (V, 1), (i_c*BTL, i_v*BV), (BTL, BV), (1, 0)) + p_q = tl.make_block_ptr(q + (i_bh) * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) + b_q = (b_q * scale).to(b_q.dtype) + b_dq = tl.zeros([BTL, BK], dtype=tl.float32) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (0, i_k*BK), (BTS, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (V, T), (1, V), (i_v*BV, 0), (BV, BTS), (0, 1)) + p_dz = dz + i_bh * T + i_c*BTL + tl.arange(0, BTL) + b_dz = tl.load(p_dz, mask=(i_c*BTL + tl.arange(0, BTL)) < T) + + for _ in range(0, i_c*BTL, BTS): + # [BTS, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BTS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + b_ds = tl.dot(b_do, b_v, allow_tf32=False) + if i_v == 0: + b_ds += b_dz[:, None] + else: + b_ds = b_ds + b_s = tl.dot(b_q, tl.trans(b_k), allow_tf32=False) + # [BQ, BD] + b_dq += tl.dot((2 * b_ds * b_s).to(b_v.dtype), b_k, allow_tf32=False) + p_k = tl.advance(p_k, (BTS, 0)) + p_v = tl.advance(p_v, (0, BTS)) + + b_dq *= scale + o_q = tl.arange(0, BTL) + o_k = tl.arange(0, BTS) + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTS, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (V, T), (1, V), (i_v*BV, i_c*BTL), (BV, BTS), (0, 1)) + # Q block and K block have overlap. masks required + for _ in range(i_c*BTL, (i_c + 1) * BTL, BTS): + # [BTS, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BTS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BTL, BTS] + m_s = o_q[:, None] >= o_k[None, :] + b_ds = tl.dot(b_do, b_v, allow_tf32=False) + if i_v == 0: + b_ds += b_dz[:, None] + else: + b_ds = b_ds + b_ds = tl.where(m_s, b_ds, 0) * scale + b_s = tl.dot(b_q, tl.trans(b_k), allow_tf32=False) + b_s = tl.where(m_s, b_s, 0) + # [BTL, BK] + b_dq += tl.dot((2 * b_ds * b_s).to(b_k.dtype), + b_k, allow_tf32=False) + p_k = tl.advance(p_k, (BTS, 0)) + p_v = tl.advance(p_v, (0, BTS)) + o_k += BTS + p_dq = tl.make_block_ptr(dq + (i_bh + B * H * i_v) * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + return + + +@triton.jit(do_not_specialize=['T']) +def _parallel_rebased_bwd_dkv( + i_bh, + i_c, + i_k, + i_v, + i_h, + q, + k, + v, + do, + dz, + dk, + dv, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + # compute dk dv + p_k = tl.make_block_ptr(k + i_bh * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + p_v = tl.make_block_ptr(v + i_bh * T*V, (T, V), (V, 1), (i_c*BTL, i_v*BV), (BTL, BV), (1, 0)) + b_k, b_v = tl.load(p_k, boundary_check=(0, 1)), tl.load(p_v, boundary_check=(0, 1)) + b_dk, b_dv = tl.zeros([BTL, BK], dtype=tl.float32), tl.zeros( + [BTL, BV], dtype=tl.float32) + + for i in range((tl.cdiv(T, BTS) * BTS)-BTS, (i_c + 1) * BTL - BTS, -BTS): + p_q = tl.make_block_ptr(q + i_bh * T*K, (K, T), (1, K), (i_k*BK, i), (BK, BTS), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (V, T), (1, V), (i_v*BV, i), (BV, BTS), (0, 1)) + p_dz = dz + i_bh * T + i + tl.arange(0, BTS) + # [BK, BTS] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BV, BTS] + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) + b_dz = tl.load(p_dz, mask=(i + tl.arange(0, BTS)) < T) + # [BTL, BTS] + b_s = tl.dot(b_k.to(b_q.dtype), b_q, allow_tf32=False) * scale + b_s2 = b_s * b_s + b_dv += tl.dot(b_s2.to(b_q.dtype), tl.trans(b_do), allow_tf32=False) + b_ds = tl.dot(b_v, b_do, allow_tf32=False) * scale + if i_v == 0: + b_ds += b_dz[None, :] * scale + else: + b_ds = b_ds + b_dk += tl.dot((2 * b_ds * b_s).to(b_q.dtype), tl.trans(b_q), allow_tf32=False) + + tl.debug_barrier() + o_q, o_k = tl.arange(0, BTS), tl.arange(0, BTL) + for i in range(i_c*BTL, (i_c+1)*BTL, BTS): + p_q = tl.make_block_ptr(q + i_bh * T*K, (K, T), (1, K), (i_k*BK, i), (BK, BTS), (0, 1)) + p_do = tl.make_block_ptr(do + i_bh * T*V, (V, T), (1, V), (i_v*BV, i), (BV, BTS), (0, 1)) + p_dz = dz + i_bh * T + i + tl.arange(0, BTS) + b_q = tl.load(p_q, boundary_check=(0, 1)) # [BD, BQ] + b_do = tl.load(p_do, boundary_check=(0, 1)).to(b_q.dtype) + b_dz = tl.load(p_dz, mask=(i + tl.arange(0, BTS)) < T) + # [BK, BQ] + m_s = o_k[:, None] <= o_q[None, :] + b_s = tl.dot(b_k, b_q, allow_tf32=False) * scale + b_s2 = b_s * b_s + b_s = tl.where(m_s, b_s, 0) + b_s2 = tl.where(m_s, b_s2, 0) + + b_ds = tl.dot(b_v, b_do, allow_tf32=False) + if i_v == 0: + b_ds += b_dz[None, :] + else: + b_ds = b_ds + b_ds = tl.where(m_s, b_ds, 0) * scale + # [BK, BD] + b_dv += tl.dot(b_s2.to(b_q.dtype), tl.trans(b_do), allow_tf32=False) + b_dk += tl.dot((2 * b_ds * b_s).to(b_q.dtype), tl.trans(b_q), allow_tf32=False) + o_q += BTS + + p_dk = tl.make_block_ptr(dk + (i_bh + B * H * i_v) * T*K, (T, K), (K, 1), (i_c*BTL, i_k*BK), (BTL, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv + (i_bh + B * H * i_k) * T*V, (T, V), (V, 1), (i_c*BTL, i_v*BV), (BTL, BV), (1, 0)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + return + + +@triton.jit(do_not_specialize=['T']) +def parallel_rebased_bwd_kernel( + q, + k, + v, + do, + dz, + dq, + dk, + dv, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BTL: tl.constexpr, + BTS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, +): + i_kv, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + NV = tl.cdiv(V, BV) + i_k = i_kv // (NV) + i_v = i_kv % (NV) + i_h = i_bh % H + _parallel_rebased_bwd_dq( + i_bh, + i_c, + i_k, + i_v, + i_h, + q, + k, + v, + do, + dz, + dq, + scale, + B=B, + H=H, + T=T, + K=K, + V=V, + BTL=BTL, + BTS=BTS, + BK=BK, + BV=BV, + ) + tl.debug_barrier() + _parallel_rebased_bwd_dkv( + i_bh, + i_c, + i_k, + i_v, + i_h, + q, + k, + v, + do, + dz, + dk, + dv, + scale, + B=B, + H=H, + T=T, + K=K, + V=V, + BTL=BTL, + BTS=BTS, + BK=BK, + BV=BV, + ) + + +class ParallelBasedFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, scale): + BTL, BTS = 128, 32 + assert BTL % BTS == 0 + # assert q.shape[-1] % 16 == 0 + BK = min(128, max(triton.next_power_of_2(k.shape[-1]), 16)) + BV = min(128, max(triton.next_power_of_2(v.shape[-1]), 16)) + B, H, T, K, V = *k.shape, v.shape[-1] + num_stages = 2 + num_warps = 4 + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + grid = (NK * NV, triton.cdiv(T, BTL), B * H) + + assert NK == 1, "will encounter some synchronization issue if not." + + o = torch.empty(NK, B, H, T, V, device=q.device) + z = torch.empty(NK, B, H, T, device=q.device) + parallel_rebased_fwd_kernel[grid]( + q, + k, + v, + o, + z, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BTL=BTL, + BTS=BTS, + BK=BK, + BV=BV, + num_warps=num_warps, + num_stages=num_stages, + ) + ctx.save_for_backward(q, k, v) + ctx.scale = scale + return o.sum(0).to(q.dtype), z.sum(0).to(q.dtype) + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dz): + q, k, v = ctx.saved_tensors + scale = ctx.scale + BTL, BTS = 64, 32 + assert BTL % BTS == 0 + BK = min(128, max(triton.next_power_of_2(k.shape[-1]), 16)) + BV = min(128, max(triton.next_power_of_2(v.shape[-1]), 16)) + B, H, T, K, V = *k.shape, v.shape[-1] + num_stages = 2 + num_warps = 4 + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + grid = (NK * NV, triton.cdiv(T, BTL), B * H) + + assert NK == 1, "will encounter some synchronization issue if not" + + dq = torch.empty(NV, B, H, T, K, dtype=q.dtype, device=q.device) + dk = torch.empty(NV, B, H, T, K, dtype=q.dtype, device=q.device) + dv = torch.empty(NK, B, H, T, V, dtype=q.dtype, device=q.device) + + parallel_rebased_bwd_kernel[grid]( + q, + k, + v, + do, + dz, + dq, + dk, + dv, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BTL=BTL, + BTS=BTS, + BK=BK, + BV=BV, + num_warps=num_warps, + num_stages=num_stages, + ) + + return dq.sum(0).to(q.dtype), dk.sum(0).to(k.dtype), dv.sum(0).to(v.dtype), None + + +def parallel_rebased( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + eps: float = 1e-5, + use_scale: bool = True, + use_normalize: bool = True, + return_both: bool = False, + head_first: bool = False, +): + assert q.shape[-1] <= 128, "only support feature dim up to 128" + if use_scale: + scale = q.shape[-1] ** -0.5 + else: + scale = 1 + if not head_first: + q, k, v = map(lambda x: x.transpose(1, 2), (q, k, v)) + o, z = ParallelBasedFunction.apply(q, k, v, scale) + if return_both: + return o, z + if use_normalize: + o = o / (z[..., None] + eps) + if not head_first: + o = o.transpose(1, 2) + return o.to(q.dtype) diff --git a/code/flash-linear-attention/fla/ops/retention/__init__.py b/code/flash-linear-attention/fla/ops/retention/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0aea0d1461d071c58838d517185423b00c4a7420 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/retention/__init__.py @@ -0,0 +1,12 @@ + +from .chunk import chunk_retention +from .fused_chunk import fused_chunk_retention +from .fused_recurrent import fused_recurrent_retention +from .parallel import parallel_retention + +__all__ = [ + 'chunk_retention', + 'fused_chunk_retention', + 'parallel_retention', + 'fused_recurrent_retention', +] diff --git a/code/flash-linear-attention/fla/ops/retention/chunk.py b/code/flash-linear-attention/fla/ops/retention/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..7b97e931cbacb5d86860b6cf12b4ace861d0cd82 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/retention/chunk.py @@ -0,0 +1,75 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.simple_gla.chunk import chunk_simple_gla + + +@torch.compiler.disable +def chunk_retention( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + g_gamma = (1 - q.new_tensor(2., dtype=torch.float).pow(-5. - q.new_tensor(range(q.shape[2]), dtype=torch.float))).log() + o, final_state = chunk_simple_gla( + q=q, + k=k, + v=v, + scale=scale, + g_gamma=g_gamma, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/retention/fused_chunk.py b/code/flash-linear-attention/fla/ops/retention/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..3b431c86219f113e20a06e66fb3b4b098db09c30 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/retention/fused_chunk.py @@ -0,0 +1,76 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.simple_gla import fused_chunk_simple_gla + + +@torch.compiler.disable +def fused_chunk_retention( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. + Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + g_gamma = (1 - q.new_tensor(2., dtype=torch.float).pow(-5. - q.new_tensor(range(q.shape[2]), dtype=torch.float))).log() + o, final_state = fused_chunk_simple_gla( + q=q, + k=k, + v=v, + g_gamma=g_gamma, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/retention/fused_recurrent.py b/code/flash-linear-attention/fla/ops/retention/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..a242c61e675501cee8c82d4347e0f5f935f74ce1 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/retention/fused_recurrent.py @@ -0,0 +1,31 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.simple_gla.fused_recurrent import fused_recurrent_simple_gla + + +def fused_recurrent_retention( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + g_gamma = (1 - q.new_tensor(2., dtype=torch.float).pow(-5. - q.new_tensor(range(q.shape[2]), dtype=torch.float))).log() + o, final_state = fused_recurrent_simple_gla( + q=q, + k=k, + v=v, + g_gamma=g_gamma, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/retention/naive.py b/code/flash-linear-attention/fla/ops/retention/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..ff62f148cb0ae38282bf31465c1ea805d86ad74d --- /dev/null +++ b/code/flash-linear-attention/fla/ops/retention/naive.py @@ -0,0 +1,14 @@ + +import torch + + +def naive_retention(q, k, v): + orig_type = q.dtype + q, k, v = q.float(), k.float(), v.float() + _, n_heads, seq_len, d_head = q.shape + s = (1 - q.new_tensor(2., dtype=torch.float).pow(-5. - q.new_tensor(range(n_heads), dtype=torch.float))).log2() + n = q.new_tensor(range(seq_len), dtype=torch.float) + n = torch.exp2((n.unsqueeze(-1) - n) * s.view(-1, 1, 1)) * n.unsqueeze(-1).ge(n) + s = torch.einsum('bhqd,bhkd,hqk->bhqk', q * d_head ** -0.5, k, n.to(q.dtype)) + o = torch.einsum('bhqk,bhkd->bhqd', s, v) + return o.to(orig_type) diff --git a/code/flash-linear-attention/fla/ops/retention/parallel.py b/code/flash-linear-attention/fla/ops/retention/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..5143a1c30770e11bf980c03479b1c9e32e45ac90 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/retention/parallel.py @@ -0,0 +1,69 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.simple_gla.parallel import parallel_simple_gla + + +def parallel_retention( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + scale: float | None = None, + output_attentions: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + output_attentions (bool): + Whether to output the materialized attention scores of shape [B, H, T, T]. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + attn (torch.Tensor): + Attention scores of shape `[B, H, T, T]` if `output_attentions=True` else `None` + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + s = (1 - q.new_tensor(2., dtype=torch.float).pow(-5. - q.new_tensor(range(q.shape[2]), dtype=torch.float))).log() + g = s[None, None, :].expand(q.shape[0], q.shape[1], q.shape[2]) + + o, attn = parallel_simple_gla( + q=q, + k=k, + v=v, + scale=scale, + g=g, + output_attentions=output_attentions, + cu_seqlens=cu_seqlens, + ) + return o, attn diff --git a/code/flash-linear-attention/fla/ops/rwkv4/__init__.py b/code/flash-linear-attention/fla/ops/rwkv4/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..85d31a8ae7b8dfe75f6a03efd8ef3a8f04c557a8 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv4/__init__.py @@ -0,0 +1,6 @@ + +from .fused_recurrent import fused_recurrent_rwkv4 + +__all__ = [ + 'fused_recurrent_rwkv4', +] diff --git a/code/flash-linear-attention/fla/ops/rwkv4/fused_recurrent.py b/code/flash-linear-attention/fla/ops/rwkv4/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..f4b959ff0b73040a18eea97366b8987c8835ee82 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv4/fused_recurrent.py @@ -0,0 +1,471 @@ +# Copyright (c) 2024, Songlin Yang, Yu Zhang + +from typing import Any, cast + +import torch +import triton +import triton.language as tl +from torch import Tensor +from torch.autograd.function import Function, FunctionCtx, once_differentiable + +from fla.ops.utils.op import exp +from fla.utils import input_guard + + +def get_block_size_c(chans: int) -> int: + if chans < 32: + return 32 + if chans < 64: + return 64 + return 128 + + +@triton.jit +def fused_recurrent_rwkv4_forward_kernel( + # W + w_ptr, + w_s_c, + # U + u_ptr, + u_s_c, + # K + k_ptr, + k_s_b, + k_s_t, + k_s_c, + # V + v_ptr, + v_s_b, + v_s_t, + v_s_c, + # State + state_ptr, + state_s_b, + state_s_abe, + state_s_c, + # WKV + wkv_ptr, + wkv_s_b, + wkv_s_t, + wkv_s_c, + # Output state + state_out_ptr, + state_out_s_b, + state_out_s_abe, + state_out_s_t, + state_out_s_c, + # Params + chans, + tsz, + BLOCK_SIZE_C: tl.constexpr, +): + # Parallelize over the batch dimension. + b_idx = tl.program_id(0) + c_idx = tl.program_id(1) + + cs = (c_idx * BLOCK_SIZE_C) + tl.arange(0, BLOCK_SIZE_C) + cmask = cs < chans + + # Pointers to the batch (and possibly channel) for the input tensors. + k_ptr = k_ptr + b_idx * k_s_b + v_ptr = v_ptr + b_idx * v_s_b + alpha_ptr = state_ptr + b_idx * state_s_b + beta_ptr = state_ptr + b_idx * state_s_b + state_s_abe + eps_ptr = state_ptr + b_idx * state_s_b + 2 * state_s_abe + + # Pointers to the batch (and possibly channel) for the output tensors. + wkv_ptr = wkv_ptr + b_idx * wkv_s_b + alpha_out_ptr = state_out_ptr + b_idx * state_out_s_b + beta_out_ptr = state_out_ptr + b_idx * state_out_s_b + state_out_s_abe + eps_out_ptr = state_out_ptr + b_idx * state_out_s_b + 2 * state_out_s_abe + + # Loads parameters. + alpha = tl.load(alpha_ptr + cs * state_s_c, mask=cmask).to(tl.float32) + beta = tl.load(beta_ptr + cs * state_s_c, mask=cmask).to(tl.float32) + eps = tl.load(eps_ptr + cs * state_s_c, mask=cmask).to(tl.float32) + w = tl.load(w_ptr + cs * w_s_c, mask=cmask).to(tl.float32) + u = tl.load(u_ptr + cs * u_s_c, mask=cmask).to(tl.float32) + + for t in range(tsz): + kt = tl.load(k_ptr + t * k_s_t + cs * k_s_c, mask=cmask).to(tl.float32) + vt = tl.load(v_ptr + t * v_s_t + cs * v_s_c, mask=cmask).to(tl.float32) + + ukt = u + kt + tau = tl.maximum(ukt, eps) + e1a = exp(eps - tau) + e2a = exp(ukt - tau) + wkv = (e1a * alpha + e2a * vt) / (e1a * beta + e2a) + tl.store(wkv_ptr + t * wkv_s_t + cs * wkv_s_c, wkv, mask=cmask) + + w_eps = w + eps + eps = tl.maximum(w_eps, kt) + e1b = exp(w_eps - eps) + e2b = exp(kt - eps) + alpha = e1b * alpha + e2b * vt + beta = e1b * beta + e2b + tl.store(alpha_out_ptr + t * state_out_s_t + cs * state_out_s_c, alpha, mask=cmask) + tl.store(beta_out_ptr + t * state_out_s_t + cs * state_out_s_c, beta, mask=cmask) + tl.store(eps_out_ptr + t * state_out_s_t + cs * state_out_s_c, eps, mask=cmask) + + +def fused_recurrent_rwkv4_forward( + w: Tensor, + u: Tensor, + k: Tensor, + v: Tensor, + state: Tensor, +) -> tuple[Tensor, Tensor]: + (bsz, tsz, chans) = k.shape + + # New tensors to output. + wkvs = k.new_empty(bsz, tsz, chans) + state_out = k.new_empty(bsz, 3, tsz, chans) + + # Constants. + block_size_c = get_block_size_c(chans) + + def grid(meta: dict[str, Any]) -> tuple[int, ...]: + return (bsz, triton.cdiv(chans, meta["BLOCK_SIZE_C"])) + + fused_recurrent_rwkv4_forward_kernel[grid]( + # W + w, + w.stride(0), + # U + u, + u.stride(0), + # K + k, + k.stride(0), + k.stride(1), + k.stride(2), + # V + v, + v.stride(0), + v.stride(1), + v.stride(2), + # State + state, + state.stride(0), + state.stride(1), + state.stride(3), + # WKV + wkvs, + wkvs.stride(0), + wkvs.stride(1), + wkvs.stride(2), + # Output state + state_out, + state_out.stride(0), + state_out.stride(1), + state_out.stride(2), + state_out.stride(3), + # Params + chans, + tsz, + BLOCK_SIZE_C=block_size_c, + ) + + state_out = torch.cat((state, state_out), dim=2) + + return wkvs, state_out + + +@triton.jit +def fused_recurrent_rwkv4_backward_kernel( + # W + w_ptr, + w_s_c, + # U + u_ptr, + u_s_c, + # K + k_ptr, + k_s_b, + k_s_t, + k_s_c, + # V + v_ptr, + v_s_b, + v_s_t, + v_s_c, + # State + state_ptr, + state_s_b, + state_s_abe, + state_s_t, + state_s_c, + # WKV grad + gwkv_ptr, + gwkv_s_b, + gwkv_s_t, + gwkv_s_c, + # Output state grad + gstate_out_ptr, + gstate_out_s_b, + gstate_out_s_abe, + gstate_out_s_c, + # W grad + gw_ptr, + gw_s_b, + gw_s_c, + # U grad + gu_ptr, + gu_s_b, + gu_s_c, + # K grad + gk_ptr, + gk_s_b, + gk_s_t, + gk_s_c, + # V grad + gv_ptr, + gv_s_b, + gv_s_t, + gv_s_c, + # State grad + gstate_ptr, + gstate_s_b, + gstate_s_abe, + gstate_s_c, + # Params + tsz, + chans, + BLOCK_SIZE_C: tl.constexpr, +): + # Parallelize over the batch dimension. + b_idx = tl.program_id(0) + c_idx = tl.program_id(1) + + cs = (c_idx * BLOCK_SIZE_C) + tl.arange(0, BLOCK_SIZE_C) + cmask = cs < chans + + # Pointers to the batch (and possibly channel) for the input tensors. + k_ptr = k_ptr + b_idx * k_s_b + v_ptr = v_ptr + b_idx * v_s_b + alpha_ptr = state_ptr + b_idx * state_s_b + beta_ptr = state_ptr + b_idx * state_s_b + state_s_abe + eps_ptr = state_ptr + b_idx * state_s_b + 2 * state_s_abe + + # Pointers to the batch (and possibly channel) for the output tensors. + gk_ptr = gk_ptr + b_idx * gk_s_b + gv_ptr = gv_ptr + b_idx * gv_s_b + + # Pointers to gradients which were recieved by the function. + gwkv_ptr = gwkv_ptr + b_idx * gwkv_s_b + galpha_out_ptr = gstate_out_ptr + b_idx * gstate_out_s_b + gbeta_out_ptr = gstate_out_ptr + b_idx * gstate_out_s_b + gstate_out_s_abe + geps_out_ptr = gstate_out_ptr + b_idx * gstate_out_s_b + 2 * gstate_out_s_abe + + # Loads parameters. + galpha = tl.load(galpha_out_ptr + gstate_out_s_c * cs, mask=cmask).to(tl.float32) + gbeta = tl.load(gbeta_out_ptr + gstate_out_s_c * cs, mask=cmask).to(tl.float32) + geps = tl.load(geps_out_ptr + gstate_out_s_c * cs, mask=cmask).to(tl.float32) + w = tl.load(w_ptr + w_s_c * cs, mask=cmask).to(tl.float32) + u = tl.load(u_ptr + u_s_c * cs, mask=cmask).to(tl.float32) + + # Gradient accumulators. + gw = tl.zeros_like(w) + gu = tl.zeros_like(u) + + alpha_prev = tl.load(alpha_ptr + tsz * state_s_t + state_s_c * cs, mask=cmask).to(tl.float32) + beta_prev = tl.load(beta_ptr + tsz * state_s_t + state_s_c * cs, mask=cmask).to(tl.float32) + eps_prev = tl.load(eps_ptr + tsz * state_s_t + state_s_c * cs, mask=cmask).to(tl.float32) + + for t in range(tsz): + tc = tsz - t - 1 + + kt = tl.load(k_ptr + tc * k_s_t + k_s_c * cs, mask=cmask).to(tl.float32) + vt = tl.load(v_ptr + tc * v_s_t + v_s_c * cs, mask=cmask).to(tl.float32) + + alpha_curr = alpha_prev + beta_curr = beta_prev + eps_curr = eps_prev + + alpha_prev = tl.load(alpha_ptr + tc * state_s_t + state_s_c * cs, mask=cmask).to(tl.float32) + beta_prev = tl.load(beta_ptr + tc * state_s_t + state_s_c * cs, mask=cmask).to(tl.float32) + eps_prev = tl.load(eps_ptr + tc * state_s_t + state_s_c * cs, mask=cmask).to(tl.float32) + + ukt = u + kt + tau = tl.maximum(ukt, eps_prev) + e1 = exp(eps_prev - tau) + e2 = exp(ukt - tau) + + euke = exp(ukt + eps_prev - 2 * tau) + + denom = e1 * beta_prev + e2 + denom_sq = denom * denom + + gwkvt = tl.load(gwkv_ptr + tc * gwkv_s_t + gwkv_s_c * cs, mask=cmask).to(tl.float32) + + # Backpropagates wkv gradients. + guk = gwkvt * e2 * (e1 * beta_prev * vt - e1 * alpha_prev) / denom_sq + gu += guk + gk = guk + gv = gwkvt * e2 / denom + + galpha_wkv = gwkvt * e1 / denom + gbeta_wkv = -gwkvt * e1 * (e2 * vt + e1 * alpha_prev) / denom_sq + geps_wkv_denom = e1 * beta_prev + e2 + geps_wkv = gwkvt * euke * (alpha_prev - vt * beta_prev) / (geps_wkv_denom * geps_wkv_denom) + + e1 = exp(w + eps_prev - eps_curr) + e2 = exp(kt - eps_curr) + + # Backpropagates alpha gradients. + galpha_we = galpha * e1 * alpha_prev + gw += galpha_we + gk += galpha * e2 * vt + gv += galpha * e2 + geps += galpha * -alpha_curr + + # Backpropagates beta gradients. + gbeta_we = gbeta * e1 * beta_prev + gw += gbeta_we + gk += gbeta * e2 + geps += gbeta * -beta_curr + + # Backpropagates epsilon gradients. + geps_mask = w + eps_prev > kt + geps_we = tl.where(geps_mask, geps, tl.zeros_like(geps)) + gw += geps_we + gk += tl.where(geps_mask, tl.zeros_like(geps), geps) + + # Stores the gradients for k and v. + tl.store(gk_ptr + tc * gk_s_t + gk_s_c * cs, gk, mask=cmask) + tl.store(gv_ptr + tc * gv_s_t + gv_s_c * cs, gv, mask=cmask) + + # Computes new gradients for alpha and beta. + galpha = galpha * e1 + galpha_wkv + gbeta = gbeta * e1 + gbeta_wkv + geps = galpha_we + gbeta_we + geps_we + geps_wkv + + # Stores final gradients for alpha and beta. + galpha_ptr = gstate_ptr + b_idx * gstate_s_b + gbeta_ptr = gstate_ptr + b_idx * gstate_s_b + gstate_s_abe + geps_ptr = gstate_ptr + b_idx * gstate_s_b + 2 * gstate_s_abe + tl.store(galpha_ptr + gstate_s_c * cs, galpha, mask=cmask) + tl.store(gbeta_ptr + gstate_s_c * cs, gbeta, mask=cmask) + tl.store(geps_ptr + gstate_s_c * cs, geps, mask=cmask) + + # Stores final gradients for w and u. + tl.store(gw_ptr + gw_s_b * b_idx + gw_s_c * cs, gw*w, mask=cmask) + tl.store(gu_ptr + gu_s_b * b_idx + gu_s_c * cs, gu, mask=cmask) + + +def fused_recurrent_rwkv4_backward( + w: Tensor, + u: Tensor, + k: Tensor, + v: Tensor, + state: Tensor, + grad_wkv: Tensor, + grad_state: Tensor, +) -> tuple[Tensor, Tensor, Tensor, Tensor, Tensor]: + bsz, tsz, chans = k.shape + + gw = w.new_empty(bsz, chans, dtype=torch.float) # New tensors to output. + gu = u.new_empty(bsz, chans, dtype=torch.float) + gk = torch.empty_like(k) + gv = torch.empty_like(v) + gstate = k.new_empty(bsz, 3, 1, chans) + + block_size_c = get_block_size_c(chans) # Constants. + + def grid(meta: dict[str, Any]) -> tuple[int, ...]: + return (bsz, triton.cdiv(chans, meta["BLOCK_SIZE_C"])) + + fused_recurrent_rwkv4_backward_kernel[grid]( + # W + w, + w.stride(0), + # U + u, + u.stride(0), + # K + k, + k.stride(0), + k.stride(1), + k.stride(2), + # V + v, + v.stride(0), + v.stride(1), + v.stride(2), + # State + state, + state.stride(0), + state.stride(1), + state.stride(2), + state.stride(3), + # WKV grad + grad_wkv, + grad_wkv.stride(0), + grad_wkv.stride(1), + grad_wkv.stride(2), + # Output state grad + grad_state, + grad_state.stride(0), + grad_state.stride(1), + grad_state.stride(3), + # W grad + gw, + gw.stride(0), + gw.stride(1), + # U grad + gu, + gu.stride(0), + gu.stride(1), + # K grad + gk, + gk.stride(0), + gk.stride(1), + gk.stride(2), + # V grad + gv, + gv.stride(0), + gv.stride(1), + gv.stride(2), + # State grad + gstate, + gstate.stride(0), + gstate.stride(1), + gstate.stride(3), + # Params + tsz, + chans, + BLOCK_SIZE_C=block_size_c, + ) + + return gw.sum(0), gu.sum(0), gk, gv, gstate + + +class FusedRecurrentRWKV4Function(Function): + + @staticmethod + @input_guard + def forward( + ctx: FunctionCtx, + w: Tensor, + u: Tensor, + k: Tensor, + v: Tensor, + state: Tensor, + ) -> tuple[Tensor, Tensor]: + ctx.w_dtype = w.dtype + w = -torch.exp(w.float()) + wkv, state_out = fused_recurrent_rwkv4_forward(w, u, k, v, state) + ctx.save_for_backward(w, u, k, v, state_out[:, :, :-1]) + return wkv, state_out[:, :, -1:] + + @staticmethod + @once_differentiable + @input_guard + def backward(ctx: FunctionCtx, gwkv: Tensor, gstate: Tensor) -> tuple[Tensor, Tensor, Tensor, Tensor, Tensor]: + w, u, k, v, state = cast("tuple[Tensor, ...]", ctx.saved_tensors) + gw, gu, gk, gv, gstate = fused_recurrent_rwkv4_backward(w, u, k, v, state, gwkv, gstate) + return gw.to(ctx.w_dtype), gu.to(u), gk.to(k), gv.to(v), gstate.to(state) + + +def fused_recurrent_rwkv4(w: Tensor, u: Tensor, k: Tensor, v: Tensor, state: Tensor) -> tuple[Tensor, Tensor]: + return FusedRecurrentRWKV4Function.apply(w, u, k, v, state) diff --git a/code/flash-linear-attention/fla/ops/rwkv6/__init__.py b/code/flash-linear-attention/fla/ops/rwkv6/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..19e34655216beb8d2d4918c4a95bdf750c805e57 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv6/__init__.py @@ -0,0 +1,8 @@ + +from .chunk import chunk_rwkv6 +from .fused_recurrent import fused_recurrent_rwkv6 + +__all__ = [ + 'chunk_rwkv6', + 'fused_recurrent_rwkv6', +] diff --git a/code/flash-linear-attention/fla/ops/rwkv6/chunk.py b/code/flash-linear-attention/fla/ops/rwkv6/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..5907a3060a4239040c1a946cc79f8b79f5633d50 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv6/chunk.py @@ -0,0 +1,1313 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl + +from fla.ops.common.chunk_h import chunk_fwd_h +from fla.ops.gla.chunk import chunk_gla_bwd_dA, chunk_gla_bwd_dv, chunk_gla_fwd_o_gk +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import ( + autocast_custom_bwd, + autocast_custom_fwd, + autotune_cache_kwargs, + check_shared_mem, + input_guard, + use_cuda_graph, +) + +BK_LIST = [32, 64] if check_shared_mem() else [16, 32] +BV_LIST = [32, 64] if check_shared_mem() else [16, 32] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BS': BS}, num_warps=num_warps, num_stages=num_stages) + for BS in [16, 32, 64] + for num_warps in [4, 8, 16] + for num_stages in [2, 3, 4] + ], + key=['S', 'BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_fwd_cumsum_kernel( + s, + oi, + oe, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + S: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_s, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_i = tl.arange(0, BT) + m_i = tl.where(o_i[:, None] >= o_i[None, :], 1., 0.).to(tl.float32) + m_e = tl.where(o_i[:, None] > o_i[None, :], 1., 0.).to(tl.float32) + + p_s = tl.make_block_ptr(s + (bos * H + i_h) * S, (T, S), (H*S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + p_oi = tl.make_block_ptr(oi + (bos * H + i_h) * S, (T, S), (H*S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + p_oe = tl.make_block_ptr(oe + (bos * H + i_h) * S, (T, S), (H*S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + # [BT, BS] + b_s = tl.load(p_s, boundary_check=(0, 1)).to(tl.float32) + b_oi = tl.dot(m_i, b_s) + b_oe = tl.dot(m_e, b_s) + tl.store(p_oi, b_oi.to(p_oi.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_oe, b_oe.to(p_oe.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + + +def chunk_rwkv6_fwd_cumsum( + g: torch.Tensor, + chunk_size: int, + cu_seqlens: torch.Tensor | None = None, +) -> torch.Tensor: + B, T, H, S = g.shape + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + gi, ge = torch.empty_like(g, dtype=torch.float), torch.empty_like(g, dtype=torch.float) + def grid(meta): return (triton.cdiv(meta['S'], meta['BS']), NT, B * H) + # keep cummulative normalizer in fp32 + chunk_rwkv6_fwd_cumsum_kernel[grid]( + g, + gi, + ge, + cu_seqlens, + chunk_indices, + T=T, + H=H, + S=S, + BT=BT, + ) + return gi, ge + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BC'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_fwd_A_kernel_intra_sub_inter( + q, + k, + gi, # cumulative decay inclusive + ge, # cumulative decay exclusive + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_i, i_j = i_c // NC, i_c % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + if i_i <= i_j: + return + + m_i = i_t * BT + i_i * BC + tl.arange(0, BC) < T + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + p_q = tl.make_block_ptr(q + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_gq = tl.make_block_ptr(ge + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + p_gk = tl.make_block_ptr(gi + (bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + p_gn = gi + (bos + i_t * BT + i_i * BC - 1) * H*K + i_h * K + o_k + + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_gq = tl.where(m_i[:, None] & m_k, tl.load(p_gq, boundary_check=(0, 1)), float('-inf')) + b_qg = b_q * exp(b_gq - b_gn[None, :]) * scale + # [BK, BC] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kg = b_k * exp(b_gn[:, None] - b_gk) + # [BC, BC] using tf32 to improve precision here. + b_A += tl.dot(b_qg, b_kg) + + p_A = tl.make_block_ptr(A + (bos*H + i_h)*BT, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + tl.store(p_A, b_A.to(A.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['BK', 'BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_fwd_A_kernel_intra_sub_intra( + q, + k, + gi, + ge, + u, + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_i, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_j = i_i + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = tl.arange(0, BK) + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + o_A = (bos + i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BT + i_h * BT + i_j * BC + p_q = tl.make_block_ptr(q + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(ge + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + p_qj = q + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + p_kj = k + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + p_gk = gi + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + + p_u = tl.make_block_ptr(u + i_h * K, (K,), (1,), (0,), (BK,), (0,)) + b_u = tl.load(p_u, boundary_check=(0,)) + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_qj = tl.load(p_qj, mask=m_k, other=0).to(tl.float32) + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_A = tl.sum(b_q * b_kj[None, :] * exp(b_g - b_gk[None, :]), 1) + b_A = tl.where(o_i > j, b_A * scale, 0.) + b_A = tl.where(o_i != j, b_A, tl.sum(b_qj * b_kj * b_u * scale)) + tl.store(A + o_A + j, b_A, mask=m_A) + p_qj += H*K + p_kj += H*K + p_gk += H*K + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + triton.Config({}, num_warps=8), + ], + key=['BC', 'BK'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_fwd_A_kernel_intra_sub_intra_split( + q, + k, + gi, + ge, + u, + A, + cu_seqlens, + chunk_indices, + scale, + B: tl.constexpr, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_tc, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_t, i_i = i_tc // NC, i_tc % NC + i_j = i_i + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + + o_A = (i_k * all + bos + i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BC + i_h * BC + p_q = tl.make_block_ptr(q + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(ge + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_qj = q + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + p_kj = k + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + p_gk = gi + (bos + i_t * BT + i_j * BC) * H*K + i_h * K + o_k + + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + + p_u = tl.make_block_ptr(u + i_h * K, (K,), (1,), (i_k * BK), (BK,), (0,)) + b_u = tl.load(p_u, boundary_check=(0,)) + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_qj = tl.load(p_qj, mask=m_k, other=0).to(tl.float32) + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_A = tl.sum(b_q * b_kj[None, :] * exp(b_g - b_gk[None, :]), 1) + b_A = tl.where(o_i > j, b_A * scale, 0.) + b_A = tl.where(o_i != j, b_A, tl.sum(b_qj * b_kj * b_u * scale)) + tl.store(A + o_A + j, b_A, mask=m_A) + p_qj += H*K + p_kj += H*K + p_gk += H*K + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + triton.Config({}, num_warps=8), + ], + key=['BC'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_fwd_A_kernel_intra_sub_intra_merge( + A, + A2, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + NK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + if i_t * BT + i_c * BC >= T: + return + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + for i_k in range(0, NK): + p_A = tl.make_block_ptr(A + (i_k*all+bos)*H*BC+i_h*BC, (T, BC), (H*BC, 1), (i_t*BT + i_c*BC, 0), (BC, BC), (1, 0)) + b_A += tl.load(p_A, boundary_check=(0, 1)) + p_A2 = tl.make_block_ptr(A2 + (bos*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t * BT + i_c * BC, i_c * BC), (BC, BC), (1, 0)) + tl.store(p_A2, b_A.to(A2.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_bwd_kernel_dh( + q, + gi, + ge, + do, + dh, + dht, + dh0, + cu_seqlens, + chunk_offsets, + scale, + T, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // NG + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_dht = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dht, boundary_check=(0, 1)).to(tl.float32) + + for i_t in range(NT - 1, -1, -1): + p_dh = tl.make_block_ptr(dh + ((boh+i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + last_idx = min(i_t * BT + BT, T) - 1 + # [BK, BT] + p_q = tl.make_block_ptr(q + (bos*HQ + i_hq) * K, (K, T), (1, HQ*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_do = tl.make_block_ptr(do + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + + p_gk = tl.make_block_ptr(ge + (bos*H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_gk_last = gi + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_q = (b_q * exp(b_gk) * scale).to(b_q.dtype) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_dh *= exp(b_gk_last)[:, None] + b_dh += tl.dot(b_q, b_do) + + if STORE_INITIAL_STATE_GRADIENT: + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['BK', 'NC', 'BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_bwd_kernel_intra( + q, + k, + gi, + ge, + dA, + dq, + dk, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_t, i_i = i_c // NC, i_c % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + if i_t * BT + i_i * BC >= T: + return + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + p_ge = tl.make_block_ptr(ge + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + # [BC, BK] + b_ge = tl.load(p_ge, boundary_check=(0, 1)) + b_dq = tl.zeros([BC, BK], dtype=tl.float32) + if i_i > 0: + p_gn = gi + (bos + i_t * BT + i_i * BC - 1) * H*K + i_h*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(0, i_i): + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT+i_j*BC, i_k * BK), (BC, BK), (1, 0)) + p_gk = tl.make_block_ptr(gi+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT+i_j*BC, i_k * BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA+(bos*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t*BT+i_i*BC, i_j * BC), (BC, BC), (1, 0)) + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kg = b_k * exp(b_gn[None, :] - b_gk) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BC, BK] + b_dq += tl.dot(b_dA, b_kg) + b_dq *= exp(b_ge - b_gn[None, :]) + + o_i = tl.arange(0, BC) + m_dA = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + o_dA = bos*H*BT + (i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BT + i_h * BT + i_i * BC + p_kj = k + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gkj = gi + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_dq = tl.make_block_ptr(dq + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dA = tl.load(dA + o_dA + j, mask=m_dA, other=0) + # [BK,] + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] > j + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dq += tl.where(m_i, b_dA[:, None] * b_kj[None, :] * exp(b_ge - b_gkj[None, :]), 0.) + p_kj += H*K + p_gkj += H*K + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + tl.debug_barrier() + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_gk = tl.make_block_ptr(gi + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_dk = tl.zeros([BC, BK], dtype=tl.float32) + + NC = min(NC, tl.cdiv(T - i_t * BT, BC)) + if i_i < NC - 1: + p_gn = gi + (bos + min(i_t * BT + i_i * BC + BC, T) - 1) * H*K + i_h*K + o_k + + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(i_i + 1, NC): + m_j = (i_t * BT + i_j * BC + tl.arange(0, BC)) < T + p_q = tl.make_block_ptr(q + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k*BK), (BC, BK), (1, 0)) + p_gq = tl.make_block_ptr(ge + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k*BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA + (bos*H+i_h)*BT, (BT, T), (1, H*BT), (i_i*BC, i_t*BT + i_j*BC), (BC, BC), (0, 1)) + # [BC, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_gq = tl.where(m_j[:, None] & m_k, tl.load(p_gq, boundary_check=(0, 1)), float('-inf')) + b_qg = b_q * exp(b_gq - b_gn[None, :]) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dk += tl.dot(b_dA, b_qg) + b_dk *= exp(b_gn[None, :] - b_gk) + o_dA = bos*H*BT + (i_t * BT + i_i * BC) * H*BT + i_h * BT + i_i * BC + tl.arange(0, BC) + p_qj = q + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gqj = ge + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_dk = tl.make_block_ptr(dk + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dA = tl.load(dA + o_dA + j * H*BT) + # [BK,] + b_qj = tl.load(p_qj, mask=m_k, other=0).to(tl.float32) + b_gqj = tl.load(p_gqj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] < j + b_dk += tl.where(m_i, b_dA[:, None] * b_qj[None, :] * exp(b_gqj[None, :] - b_gk), 0.) + p_qj += H*K + p_gqj += H*K + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in [2, 4, 8] + ], + key=['BT'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_rwkv6_bwd_kernel_inter( + q, + k, + v, + h, + gi, + ge, + u, + do, + dh, + dA, + dq, + dk, + dq2, + dk2, + dg, + du, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + p_gk = tl.make_block_ptr(ge + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_gi = tl.make_block_ptr(gi + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_gn = gi + (bos + min(T, i_t * BT + BT)-1) * H*K + i_h * K + o_k + b_gn = tl.load(p_gn, mask=m_k, other=0) + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dgk = tl.zeros([BK], dtype=tl.float32) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh + (i_tg * H + i_h) * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + # [BK] + b_dgk += tl.sum(b_h * b_dh, axis=0) + # [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) + b_dgk *= exp(b_gn) + b_dq *= scale + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_gi = tl.load(p_gi, boundary_check=(0, 1)) + b_dq = b_dq * exp(b_gk) + b_dk = b_dk * exp(b_gn[None, :] - b_gi) + + o_i = tl.arange(0, BT) + p_q = tl.make_block_ptr(q + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dA_dig = dA + ((bos + i_t * BT + o_i) * H + i_h) * BT + o_i + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dgk += tl.sum(b_dk * b_k, axis=0) + + b_dq += tl.load(p_dq, boundary_check=(0, 1)) + b_dk += tl.load(p_dk, boundary_check=(0, 1)) + b_dg = b_q * b_dq - b_k * b_dk + b_dg = b_dg - tl.cumsum(b_dg, axis=0) + tl.sum(b_dg, axis=0)[None, :] + b_dgk[None, :] - b_q * b_dq + # [BT,] + b_dA_dig = tl.load(p_dA_dig, mask=(i_t * BT + o_i) < T, other=0) + + p_u = tl.make_block_ptr(u + i_h * K, (K,), (1,), (i_k * BK,), (BK,), (0,)) + b_u = tl.load(p_u, boundary_check=(0,)) + # scale is already applied to b_dA_diag + b_dq += (b_dA_dig[:, None] * b_u[None, :] * b_k) + b_dk += (b_dA_dig[:, None] * b_u[None, :] * b_q) + b_du = tl.sum(b_dA_dig[:, None] * b_q * b_k, axis=0) + p_du = tl.make_block_ptr(du + (i_tg * H + i_h) * K, (K,), (1,), (i_k * BK,), (BK,), (0,)) + tl.store(p_du, b_du, boundary_check=(0,)) + + p_dq = tl.make_block_ptr(dq2 + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk2 + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dg = tl.make_block_ptr(dg + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_rwkv6_fwd_intra( + q: torch.Tensor, + k: torch.Tensor, + gi: torch.Tensor, + ge: torch.Tensor, + u: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K = k.shape + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BC = min(16, BT) + NC = triton.cdiv(BT, BC) + + A = q.new_empty(B, T, H, BT, dtype=torch.float) + grid = (NT, NC * NC, B * H) + chunk_rwkv6_fwd_A_kernel_intra_sub_inter[grid]( + q, + k, + gi, + ge, + A, + cu_seqlens, + chunk_indices, + scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + NC=NC, + ) + + grid = (NT, NC, B * H) + # load the entire [BC, K] blocks into SRAM at once + if K <= 256: + BK = max(triton.next_power_of_2(K), 16) + chunk_rwkv6_fwd_A_kernel_intra_sub_intra[grid]( + q, + k, + gi, + ge, + u, + A, + cu_seqlens, + chunk_indices, + scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + ) + # split then merge + else: + BK = min(128, triton.next_power_of_2(K)) + NK = triton.cdiv(K, BK) + A_intra = q.new_empty(NK, B, T, H, BC, dtype=torch.float) + + grid = (NK, NT * NC, B * H) + chunk_rwkv6_fwd_A_kernel_intra_sub_intra_split[grid]( + q, + k, + gi, + ge, + u, + A_intra, + cu_seqlens, + chunk_indices, + scale, + B=B, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + + grid = (NT, NC, B * H) + chunk_rwkv6_fwd_A_kernel_intra_sub_intra_merge[grid]( + A_intra, + A, + cu_seqlens, + chunk_indices, + B=B, + T=T, + H=H, + BT=BT, + BC=BC, + NK=NK, + ) + return A + + +def chunk_rwkv6_bwd_dh( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gi: torch.Tensor, + ge: torch.Tensor, + do: torch.Tensor, + h0: torch.Tensor, + dht: torch.Tensor, + scale: float, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + states_in_fp32: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HQ = q.shape[2] + BT = chunk_size + # N: the actual number of sequences in the batch with either equal or variable lengths + # NG: number of groups in GQA + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT = len(cu_seqlens) - 1, len(chunk_indices) + chunk_offsets = prepare_chunk_offsets(cu_seqlens, BT) + NG = HQ // H + + dh = k.new_empty(B, NT, HQ, K, V, dtype=k.dtype if not states_in_fp32 else torch.float) + dh0 = torch.empty_like(h0, dtype=torch.float) if h0 is not None else None + + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * H) + chunk_rwkv6_bwd_kernel_dh[grid]( + q=q, + gi=gi, + ge=ge, + do=do, + dh=dh, + dht=dht, + dh0=dh0, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + scale=scale, + T=T, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + NG=NG, + ) + return dh, dh0 + + +def chunk_rwkv6_bwd_dqk_intra( + q: torch.Tensor, + k: torch.Tensor, + gi: torch.Tensor, + ge: torch.Tensor, + dA: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K = q.shape + BT = chunk_size + BC = min(16, BT) + BK = min(64, triton.next_power_of_2(K)) + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + NK = triton.cdiv(K, BK) + + dq = torch.empty_like(q, dtype=torch.float) + dk = torch.empty_like(k, dtype=torch.float) + grid = (NK, NT * NC, B * H) + chunk_rwkv6_bwd_kernel_intra[grid]( + q, + k, + gi, + ge, + dA, + dq, + dk, + cu_seqlens, + chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + return dq, dk + + +def chunk_rwkv6_bwd_dqkgu( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + g: torch.Tensor, + gi: torch.Tensor, + ge: torch.Tensor, + u: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + dA: torch.Tensor, + dq: torch.Tensor, + dk: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dq2 = torch.empty_like(dq) + dk2 = torch.empty_like(dk) + dg = torch.empty_like(g) + du = u.new_empty(B * NT, H, K, dtype=torch.float) + def grid(meta): return (triton.cdiv(K, meta['BK']), NT, B * H) + chunk_rwkv6_bwd_kernel_inter[grid]( + q, + k, + v, + h, + gi, + ge, + u, + do, + dh, + dA, + dq, + dk, + dq2, + dk2, + dg, + du, + cu_seqlens, + chunk_indices, + scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + du = du.sum(0) + return dq2, dk2, dg, du + + +def chunk_rwkv6_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + u: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + gi, ge = chunk_rwkv6_fwd_cumsum(g, chunk_size=chunk_size, cu_seqlens=cu_seqlens) + h, ht = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=gi, + gv=None, + h0=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=True, + ) + # the intra A is kept in fp32 + # the computation has very marginal effect on the entire throughput + A = chunk_rwkv6_fwd_intra( + q=q, + k=k, + gi=gi, + ge=ge, + u=u, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + o = chunk_gla_fwd_o_gk( + q=q, + v=v, + g=ge, + A=A, + h=h, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return A, h, ht, o + + +def chunk_rwkv6_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + u: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + A: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +): + gi, ge = chunk_rwkv6_fwd_cumsum(g, chunk_size=chunk_size, cu_seqlens=cu_seqlens) + h, _ = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=gi, + gv=None, + h0=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=True, + ) + dh, dh0 = chunk_rwkv6_bwd_dh( + q=q, + k=k, + v=v, + gi=gi, + ge=ge, + do=do, + h0=initial_state, + dht=dht, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=True, + ) + + # dq dk in fp32 + dA = chunk_gla_bwd_dA( + v=v, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dv = chunk_gla_bwd_dv( + k=k, + g=gi, + A=A, + do=do, + dh=dh, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dq, dk = chunk_rwkv6_bwd_dqk_intra( + q=q, + k=k, + gi=gi, + ge=ge, + dA=dA, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dq, dk, dg, du = chunk_rwkv6_bwd_dqkgu( + q=q, + k=k, + v=v, + h=h, + g=g, + gi=gi, + ge=ge, + u=u, + do=do, + dh=dh, + dA=dA, + dq=dq, + dk=dk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return dq, dk, dv, dg, du, dh0 + + +class ChunkRWKV6Function(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q, + k, + v, + g, + u, + scale, + initial_state, + output_final_state, + cu_seqlens, + ): + T = q.shape[1] + if check_shared_mem(): + chunk_size = min(32, max(32, triton.next_power_of_2(T))) + else: + chunk_size = min(64, max(32, triton.next_power_of_2(T))) + + A, h, ht, o = chunk_rwkv6_fwd( + q=q, + k=k, + v=v, + g=g, + u=u, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + + ctx.save_for_backward(q, k, v, g, initial_state, A, u) + + ctx.chunk_size = chunk_size + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + return o, ht + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht): + q, k, v, g, initial_state, A, u = ctx.saved_tensors + chunk_size, scale, cu_seqlens = ctx.chunk_size, ctx.scale, ctx.cu_seqlens + dq, dk, dv, dg, du, dh0 = chunk_rwkv6_bwd( + q=q, + k=k, + v=v, + g=g, + u=u, + scale=scale, + initial_state=initial_state, + A=A, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return dq.to(q), dk.to(k), dv.to(v), dg.to(g), du.to(u), None, dh0, None, None + + +@torch.compiler.disable +def chunk_rwkv6( + r: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + scale: int | None = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + r (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + w (torch.Tensor): + Forget gates of shape `[B, T, H, K]`. applied to keys. + u (torch.Tensor): + bonus representations of shape `[H]`. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (Optional[torch.Tensor]): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.rwkv6 import chunk_rwkv6 + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> r = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> w = F.logsigmoid(torch.randn(B, T, H, K, device='cuda')) + >>> u = torch.randn(H, K, device='cuda') + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = chunk_rwkv6( + r, k, v, w, u, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> r, k, v, w = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (r, k, v, w)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = r.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = chunk_rwkv6( + r, k, v, w, u, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + >>> assert o.allclose(o_var.view(o.shape)) + >>> assert ht.allclose(ht_var) + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and r.shape[1] < r.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({r.shape[1]}) < num_heads ({r.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if r.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {r.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = r.shape[-1] ** -0.5 + o, final_state = ChunkRWKV6Function.apply( + r, + k, + v, + w, + u, + scale, + initial_state, + output_final_state, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/rwkv6/chunk_naive.py b/code/flash-linear-attention/fla/ops/rwkv6/chunk_naive.py new file mode 100644 index 0000000000000000000000000000000000000000..bf3519992f2fffa2be797fc06550698a6fcefc50 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv6/chunk_naive.py @@ -0,0 +1,42 @@ + +import torch +from einops import rearrange + + +def naive_chunk_rwkv6( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + chunk_size: int = 32, +): + assert q.shape[-2] % chunk_size == 0 + orig_dtype = q.dtype + num_chunk = q.shape[-2] // chunk_size + u = u.unsqueeze(0) + + q, k, v, w = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size).float(), (q, k, v, w)) + + w_cumsum = w.cumsum(-2) + + kw = k * (w_cumsum[..., -1, None, :] - w_cumsum).exp() + wkv = kw.transpose(-1, -2) @ v + + wkv_new = torch.zeros_like(wkv) + + for i in range(num_chunk - 1): + wkv_new[:, :, i+1] = (wkv_new[:, :, i] * w_cumsum[:, :, i, -1, :, None].exp()) + wkv[:, :, i] + + o_inter = torch.einsum('b h n d p, b h n c d -> b h n c p', wkv_new, (q * (w_cumsum - w).exp())) + + o_intra = torch.zeros_like(o_inter) + for i in range(chunk_size): + attn = (q[:, :, :, i, None] * k * (w_cumsum[:, :, :, i, None] - w[:, :, :, i, None] - w_cumsum).exp()).sum(-1) + mask = (torch.arange(0, chunk_size) < i).to(attn.device) + attn.masked_fill_(~mask, 0) + intra_inter_o = (attn.unsqueeze(-1) * v).sum(-2) + intra_intra_o = (q[:, :, :, i] * u.unsqueeze(2) * k[:, :, :, i]).sum(-1).unsqueeze(-1) * v[:, :, :, i] + o_intra[:, :, :, i] = intra_inter_o + intra_intra_o + o = o_inter + o_intra + return rearrange(o, 'b h n c d -> b h (n c) d').to(orig_dtype) diff --git a/code/flash-linear-attention/fla/ops/rwkv6/fused_recurrent.py b/code/flash-linear-attention/fla/ops/rwkv6/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..7efab4a723b4c257087f042f51f6b48ccee42fe5 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv6/fused_recurrent.py @@ -0,0 +1,675 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8, 16] + ], + key=['BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_rwkv6_fwd_kernel( + q, # query [B, H, T, K]/[B, T, H, K] + k, # key [B, H, T, K]/[B, T, H, K] + v, # value [B, H, T, V]/[B, T, H, V] + w, # log gate [B, H, T]/[B, T, H] or None + u, # bonus [B, H, K] + o, # output [NK, B, H, T, V]/[NK, B, T, H, V] + h0, # initial hidden state [B, H, K, V] + ht, # final hidden state [B, H, K, V] + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + REVERSE: tl.constexpr, # whether to reverse the recurrence + USE_INITIAL_STATE: tl.constexpr, # whether to use initial state + STORE_FINAL_STATE: tl.constexpr, # whether to store final state + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + all = B * T + + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + p_q = q + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_k = k + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_w = w + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_o = o + ((i_k * all + bos) + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_u = u + i_h * K + o_k + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + + b_u = tl.load(p_u, mask=mask_k, other=0).to(tl.float32) + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) * scale + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_w = tl.load(p_w, mask=mask_k, other=0).to(tl.float32) + b_kv = b_k[:, None] * b_v[None, :] + b_o = tl.sum((b_h + b_kv * b_u[:, None]) * b_q[:, None], 0) + b_h = b_h * exp(b_w)[:, None] + b_kv + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + p_q += (-1 if REVERSE else 1) * H*K + p_k += (-1 if REVERSE else 1) * H*K + p_v += (-1 if REVERSE else 1) * H*V + p_w += (-1 if REVERSE else 1) * H*K + p_o += (-1 if REVERSE else 1) * H*V + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + ], + key=['BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_rwkv6_bwd_kernel_dq( + k, # key [B, H, T, V]/[B, T, H, V] + v, # value [B, H, T, V]/[B, T, H, V] + w, # log gate [B, H, T]/[B, T, H] + u, # bonus [B, H, K] + do, # gradient of output [B, H, T, V]/[B, T, H, V] + dq, # gradient of query [NV, B, H, T, K]/[NV, B, T, H, K] + dq1, # gradient of query_aux [NV, B, H, T, K]/[NV, B, T, H, K] + h0, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + REVERSE: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + all = B * T + + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + p_k = k + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_w = w + (bos + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_do = do + (bos + ((T-1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_dq = dq + ((i_v * all + bos) + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_dq1 = dq1 + ((i_v * all + bos) + ((T-1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_u = u + i_h * K + o_k + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + + b_u = tl.load(p_u, mask=mask_k, other=0).to(tl.float32) + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_w = tl.load(p_w, mask=mask_k, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask_v, other=0).to(tl.float32) + b_kv = b_k[:, None] * b_v[None, :] + + b_hq = b_h * b_do[None, :] + b_dq = tl.sum(b_hq + b_kv * b_u[:, None] * b_do[None, :], 1) * scale + b_dq1 = tl.sum(b_hq, 1) + b_h = b_h * exp(b_w)[:, None] + b_h += b_kv + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), mask=mask_k) + tl.store(p_dq1, b_dq1.to(p_dq1.dtype.element_ty), mask=mask_k) + + p_k += (-1 if REVERSE else 1) * H*K + p_v += (-1 if REVERSE else 1) * H*V + p_w += (-1 if REVERSE else 1) * H*K + p_do += (-1 if REVERSE else 1) * H*V + p_dq += (-1 if REVERSE else 1) * H*K + p_dq1 += (-1 if REVERSE else 1) * H*K + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + ], + key=['BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_rwkv6_bwd_kernel_dkv( + q, # query [B, H, T, K]/[B, T, H, K] + k, # key [B, H, T, V]/[B, T, H, V] + v, # value [B, H, T, V]/[B, T, H, V] + w, # log gate [B, H, T]/[B, T, H] + u, # bonus [B, H, K] + do, # gradient of output [B, H, T, V]/[B, T, H, V] + dk, # gradient of key [NV, B, H, T, K]/[NK, B, T, H, K] + dk1, # gradient of key_aux [NV, B, H, T, K]/[NK, B, T, H, K] + dv, # gradient of value [NK, B, H, T, V]/[NV, B, T, H, V] + dh0, # gradient of initial hidden state [N, H, K, V] + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + REVERSE: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_k, i_nh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + all = B * T + + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + p_q = q + (bos + ((T-1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_k = k + (bos + ((T-1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T-1) if not REVERSE else 0)) * H*V + i_h * V + o_v + p_w = w + (bos + ((T-1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_do = do + (bos + ((T-1) if not REVERSE else 0)) * H*V + i_h * V + o_v + p_dk = dk + ((i_v * all + bos) + ((T-1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_dk1 = dk1 + ((i_v * all + bos) + ((T-1) if not REVERSE else 0)) * H*K + i_h * K + o_k + p_dv = dv + ((i_k * all + bos) + ((T-1) if not REVERSE else 0)) * H*V + i_h * V + o_v + p_u = u + i_h * K + o_k + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + + b_u = tl.load(p_u, mask=mask_k, other=0).to(tl.float32) + + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + for _ in range(T - 1, -1, -1): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) * scale + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_w = tl.load(p_w, mask=mask_k, other=0).to(tl.float32) + b_do = tl.load(p_do, mask=mask_v, other=0).to(tl.float32) + b_dkv = b_q[:, None] * b_do[None, :] + b_dk = tl.sum(b_dh * b_v[None, :], 1) + tl.store(p_dk1, b_dk.to(p_dk1.dtype.element_ty), mask=mask_k) + b_dk += tl.sum(b_dkv * b_u[:, None] * b_v[None, :], 1) + b_dv = tl.sum((b_dh + (b_dkv * b_u[:, None])) * b_k[:, None], 0) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=mask_k) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), mask=mask_v) + b_dh *= exp(b_w)[:, None] + b_dh += b_dkv + + p_q += (-1 if not REVERSE else 1) * H*K + p_k += (-1 if not REVERSE else 1) * H*K + p_v += (-1 if not REVERSE else 1) * H*V + p_w += (-1 if not REVERSE else 1) * H*K + p_do += (-1 if not REVERSE else 1) * H*V + p_dk += (-1 if not REVERSE else 1) * H*K + p_dk1 += (-1 if not REVERSE else 1) * H*K + p_dv += (-1 if not REVERSE else 1) * H*V + + if USE_INITIAL_STATE: + p_dh0 = dh0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), mask=mask_h) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT, 'BK': BK}, num_warps=num_warps) + for BT in [16, 32, 64] + for BK in [32, 64] + for num_warps in [1, 2, 4, 8] + ], + key=['K'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_rwkv6_bwd_kernel_dw( + q, + k, + dq, + dk, + dw, + cu_seqlens, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + REVERSE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + T = eos - bos + NT = tl.cdiv(T, BT) + + o_i = tl.arange(0, BT) + m_i = tl.where(o_i[:, None] >= o_i[None, :], 1., 0.) if not REVERSE else tl.where(o_i[:, None] <= o_i[None, :], 1., 0.) + + b_z = tl.zeros([BK], dtype=tl.float32) + + i_t = 0 if not REVERSE else NT - 1 + for _ in range(NT): + p_q = tl.make_block_ptr(q + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + 1, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T-1, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dq = tl.make_block_ptr(dq + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + 1, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T-1, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dw = tl.make_block_ptr(dw + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)).to(tl.float32) + b_dq = tl.load(p_dq, boundary_check=(0, 1)).to(tl.float32) + b_k = tl.load(p_k, boundary_check=(0, 1)).to(tl.float32) + b_dk = tl.load(p_dk, boundary_check=(0, 1)).to(tl.float32) + b_dw = (b_q * b_dq * scale) - b_k * b_dk + b_c = b_z[None, :] + tl.dot(m_i, b_dw, allow_tf32=False) + tl.store(p_dw, b_c.to(p_dw.dtype.element_ty), boundary_check=(0, 1)) + if i_t >= 0: + b_z += tl.sum(b_dw, 0) + + i_t += (1 if not REVERSE else -1) + + +def fused_recurrent_rwkv6_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK, BV = min(triton.next_power_of_2(K), 32), min(triton.next_power_of_2(V), 32) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + + h0 = initial_state + ht = q.new_empty(N, H, K, V, dtype=torch.float) if output_final_state else None + o = q.new_empty(NK, *v.shape, dtype=torch.float) + + grid = (NV, NK, N * H) + fused_recurrent_rwkv6_fwd_kernel[grid]( + q, + k, + v, + w, + u, + o, + h0, + ht, + cu_seqlens, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + REVERSE=reverse, + ) + o = o.sum(0) + return o, ht + + +def fused_recurrent_rwkv6_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + do: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + + BK, BV = min(triton.next_power_of_2(K), 16), min(triton.next_power_of_2(V), 64) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + + dq = q.new_empty(NV, *q.shape, dtype=torch.float) + dq1 = torch.empty_like(dq) + + grid = (NV, NK, N * H) + fused_recurrent_rwkv6_bwd_kernel_dq[grid]( + k, + v, + w, + u, + do, + dq, + dq1, + initial_state, + cu_seqlens, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + REVERSE=reverse, + ) + dq = dq.sum(0) + dq1 = dq1.sum(0) + + BK, BV = min(triton.next_power_of_2(K), 32), min(triton.next_power_of_2(V), 32) + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) + + dk = q.new_empty(NV, *k.shape, dtype=torch.float) + dk1 = q.new_empty(NV, *k.shape, dtype=torch.float) + dv = q.new_empty(NK, *v.shape, dtype=torch.float) + + dh0 = torch.empty_like(initial_state) if initial_state is not None else None + grid = (NV, NK, N * H) + fused_recurrent_rwkv6_bwd_kernel_dkv[grid]( + q, + k, + v, + w, + u, + do, + dk, + dk1, + dv, + dh0, + cu_seqlens, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + BV=BV, + REVERSE=reverse, + ) + dk = dk.sum(0) + dk1 = dk1.sum(0) + dv = dv.sum(0) + + dw = torch.empty_like(w) + def grid(meta): return (triton.cdiv(meta['K'], meta['BK']), N * H) + fused_recurrent_rwkv6_bwd_kernel_dw[grid]( + q, + k, + dq1, + dk1, + dw, + cu_seqlens, + scale, + T=T, + H=H, + K=K, + REVERSE=not reverse, + ) + du = (do.float() * v).sum(-1, True, dtype=torch.float) * q * k * scale + du = du.sum((0, 1)) + return dq, dk, dv, dw, du, dh0 + + +class FusedRecurrentRWKV6Function(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ): + o, ht = fused_recurrent_rwkv6_fwd( + q=q, + k=k, + v=v, + w=w, + u=u, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, k, v, w, u, initial_state) + ctx.scale = scale + ctx.reverse = reverse + ctx.cu_seqlens = cu_seqlens + return o.to(v), ht + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht): + q, k, v, w, u, initial_state = ctx.saved_tensors + + dq, dk, dv, dw, du, dh0 = fused_recurrent_rwkv6_bwd( + q=q, + k=k, + v=v, + w=w, + u=u, + do=do, + scale=ctx.scale, + initial_state=initial_state, + reverse=ctx.reverse, + cu_seqlens=ctx.cu_seqlens, + ) + dh0 = dh0.to(initial_state) if dh0 is not None else dh0 + return dq.to(q), dk.to(k), dv.to(v), dw.to(w), du.to(u), None, dh0, None, None, None + + +def fused_recurrent_rwkv6( + r: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + scale: int | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + r (torch.Tensor): + reception of shape `[B, T, H, K]`. + Alias: q, query in linear attention. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + w (torch.Tensor): + data-dependent decays of shape `[B, T, H, K]`. in log space! Alias: g. + u (torch.Tensor): + bonus of shape `[H, K]` + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + reverse (Optional[bool]): + If `True`, process the state passing in reverse order. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (Optional[torch.Tensor]): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.rwkv6 import fused_recurrent_rwkv6 + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, K, device='cuda')) + >>> u = torch.randn(H, K, device='cuda') + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = fused_recurrent_rwkv6( + q, k, v, g, u, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = fused_recurrent_rwkv6( + q, k, v, g, u, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + >>> assert o.allclose(o_var.view(o.shape)) + >>> assert ht.allclose(ht_var) + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and r.shape[1] < r.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({r.shape[1]}) < num_heads ({r.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if r.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {r.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = FusedRecurrentRWKV6Function.apply( + r, + k, + v, + w, + u, + scale, + initial_state, + output_final_state, + reverse, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/rwkv6/recurrent_naive.py b/code/flash-linear-attention/fla/ops/rwkv6/recurrent_naive.py new file mode 100644 index 0000000000000000000000000000000000000000..535da909fae6d73bbf05199c9b620d70efdf830c --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv6/recurrent_naive.py @@ -0,0 +1,101 @@ + + +import torch + + +def naive_recurrent_rwkv6( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool | None = False, +): + orig_dtype = q.dtype + B, H, T, K, V = *q.shape, v.shape[-1] + q, k, v, w, u = map(lambda x: x.float(), (q, k, v, w, u)) + h = torch.zeros(B, H, K, V, dtype=torch.float32, device=q.device) + o = torch.zeros_like(v) + + if scale is None: + scale = K ** -0.5 + + if initial_state is not None: + h += initial_state + + for i in range(T): + q_i = q[:, :, i, :] * scale + k_i = k[:, :, i] + v_i = v[:, :, i, :] + w_i = w[:, :, i].exp() + kv_i = k_i[..., None] * v_i[..., None, :] + o_i = (h + u[None, ..., None] * kv_i) * q_i[..., None] + o[:, :, i] = o_i.sum(-2) + h = h * w_i[..., None] + kv_i + ht = h if output_final_state else None + return o.to(orig_dtype), ht + + +@torch.no_grad +@torch.jit.script +def naive_recurrent_rwkv6_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + o: torch.Tensor, + do: torch.Tensor, + initial_state: torch.Tensor | None = None, +): + q, k, v, w, u, o, do = (x.to(dtype=torch.float32) for x in (q, k, v, w, u, o, do)) + B, H, T, K, V = q.shape[0], q.shape[1], q.shape[2], q.shape[3], v.shape[-1] + h = torch.zeros(B, H, K, V, dtype=torch.float32, device=q.device) + dq = torch.zeros_like(q) + dq_aux = torch.zeros_like(q) + + if initial_state is not None: + h += initial_state + + for i in range(T): + k_i = k[:, :, i] + v_i = v[:, :, i] + w_i = w[:, :, i].exp() + kv_i = k_i[..., None] * v_i[..., None, :] + h_i = (h + u[None, ..., None] * kv_i) + dq_i = (do[:, :, i, None, :] * h_i).sum(-1) + dq_aux_i = (do[:, :, i, None, :] * h).sum(-1) + dq[:, :, i] = dq_i + dq_aux[:, :, i] = dq_aux_i + h = h * w_i[..., None] + kv_i + + du = torch.zeros_like(u) + dh = torch.zeros_like(h) + dk = torch.zeros_like(k) + dk_aux = torch.zeros_like(k) + dv = torch.zeros_like(v) + + for i in range(T - 1, -1, -1): + d_kv_i = do[:, :, i, None, :] * q[:, :, i, :, None] + k_i = k[:, :, i] + v_i = v[:, :, i] + du_i = (d_kv_i * k_i[..., None] * v_i[..., None, :]).sum(-1) + du += du_i.sum(0) + dk_i = (dh * v_i[..., None, :]).sum(-1) + dk_aux[:, :, i] = dk_i + dk_i += (d_kv_i * u[None, ..., None] * v_i[..., None, :]).sum(-1) + dv_i = (d_kv_i * u[None, ..., None] * k_i[..., None]).sum(-2) + dv_i += (dh * k_i[..., None]).sum(-2) + + dk[:, :, i] = dk_i + dv[:, :, i] = dv_i + dh = dh * w[:, :, i, :, None].exp() + d_kv_i + + # dw = q * dq_aux - k * dk_aux + dw = torch.zeros_like(w) + for i in range(T - 2, -1, -1): + dw[:, :, i] = dw[:, :, i+1] + dq_aux[:, :, i+1] * q[:, :, i+1] - dk_aux[:, :, i] * k[:, :, i] + + return dq, dk, dv, dw, du, dh diff --git a/code/flash-linear-attention/fla/ops/rwkv7/RWKV7(Goose).md b/code/flash-linear-attention/fla/ops/rwkv7/RWKV7(Goose).md new file mode 100644 index 0000000000000000000000000000000000000000..b432f81c0bc4e363e3e7974033b7d59d942e1a52 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/RWKV7(Goose).md @@ -0,0 +1,603 @@ +# RWKV7 (Goose) Mechanism: Mathematical Derivation + +Zhiyuan Li + +>Special thanks to [Sonta](https://github.com/sustcsonglin) and [Beortust](https://github.com/Beortext), Sonta pointed out the correct notation for the outer product in the formulas, and Beortust corrected a considerable number of typos and also helped to improve the formatting. + +## Introduction to RWKV-7 Architecture + +RWKV-7 employs **Dynamic State Evolution** that transcends the fundamental TC0 expressivity limitations of attention/linear attention paradigms. RWKV-7 possesses NC1 expressivity, allowing it to solve many problems that attention mechanisms cannot. + +In simple terms, traditional attention mechanisms (like Transformer's QKV-softmax-attention) store multiple $\{k,v\}$ (key and value vector pairs), matching queries ($q$ alias named $r$ in RWKV) against keys to retrieve corresponding values. + +RWKV-7 takes a different approach - rather than directly storing $\{k,v\}$ pairs, it dynamically updates a state by learning relationships between keys and values from context. This updated state then processes new input queries ($q$, or $r$ in RWKV terminology) to produce outputs[^1]. + +[^1]: For a more detailed explanation of this approach, see the original article by the RWKV author: https://mp.weixin.qq.com/s/kC_Z3vuQ5B4PiRwZVeIvHQ + +Specifically, RWKV-7 maintains an internal model $v \approx k^{\top} S$. It aims to fit a simple objective: for given vector sequences $\{k\}$ and $\{v\}$, use state $S$ to transform $k_i$ into $v_i$, making the output $v$ as close as possible to the target $v$. + +For clarity on dimensions: + +$S_t \in \mathbb{R}^{d_v \times d_k}$ is the state matrix + +$k_t \in \mathbb{R}^{d_k}$ is the key vector + +$v_t \in \mathbb{R}^{d_v}$ is the value vector + +$q_t \in \mathbb{R}^{d_k}$ is the query vector (named $r$ in RWKV terminology) + +To achieve this, during inference with an L2 loss function $L=\frac{1}{2} \left\Vert v − k^{\top} S \right\Vert^2$, RWKV-7 automatically simulates dynamic gradient descent to continuously train its internal model $v \approx k^{\top} S$. + +The gradient of the L2 loss function with respect to the state matrix $S$ is: $\frac{\partial L}{\partial S} = S k k^{\top} - v k^{\top}$ + +Applying stochastic gradient descent (SGD) with this gradient yields a recurrent update formula that forms the foundation of RWKV-7's mechanism. In standard SGD, we would update the parameters by subtracting the gradient scaled by a learning rate: + +$$ +S_t = S_{t-1} - \eta_t \cdot \frac{\partial L}{\partial S} , \text{ where } L=L_t \quad S=S_{t-1} +$$ + +Incorporating weight decay factors $d_t = \exp(-\exp(w_t))$ as a form of time-dependent regularization and learning rate $\eta_t$, the gradient descent update becomes: + +$$S_t = S_{t-1} \text{Diag}(d_t) - \eta_t \cdot (S_{t-1} k_t k_t^{\top} - v_t k_t^{\top})$$ + +This can be expanded and rearranged as follows: + +$$S_t = S_{t-1} \text{Diag}(d_t) - \eta_t \cdot S_{t-1} k_t k_t^{\top} + \eta_t \cdot v_t k_t^{\top}$$ + +For notational simplicity, we denote $\text{Diag}(d_t)$ as $D_t$ (the diagonal decay matrix): + +$$S_t = S_{t-1} D_t - \eta_t \cdot S_{t-1} k_t k_t^{\top} + \eta_t \cdot v_t k_t^{\top}$$ + +In the full RWKV-7 implementation, this update rule is generalized through several key transformations: + +1. The diagonal decay term $D_t$ remains as a component-wise multiplication with $S_{t-1}$ + +2. The term $-\eta_t \cdot k_t k_t^{\top}$ is generalized to $\alpha_t \beta_t^{\top}$, where: + + - $\alpha_t$ can be initialized as $-k_t$ + - $\beta_t$ can be initialized as $\eta_t \cdot k_t$ + +3. The term $-\eta_t \cdot S_{t-1} k_t k_t^{\top}$ can be factorized and computed efficiently: + + - First compute $u_t = S_{t-1} k_t$ (matrix-vector product) + - Then compute $-\eta_t \cdot u_t k_t^{\top}$ (scaled outer product) + +4. The term $\eta_t \cdot v_t k_t^{\top}$ is directly implemented as the outer product between the value vector $v_t$ and key vector $k_t$, resulting in a rank-1 update matrix + +This leads to the final recurrence equation[^2]: + +$$ +S_t = S_{t-1} D_t + S_{t-1} \alpha_t \beta_t^{\top} + v_t k_t^{\top} \in \mathbb{R}^{d_v \times d_k} +$$ + +The output at each timestep is computed as: +$o_t = S_t r_t$ + +Where $r_t \in \mathbb{R}^{d_k}$ is the query vector (named $r$ in RWKV terminology), typically scaled by a factor of $\frac{1}{\sqrt{d_k}}$. This formulation allows RWKV-7 to continuously adapt its internal representation based on context, transcending the limitations of traditional attention mechanisms. + +[^2]: For a more detailed explanation, see the triton codes. Note: In the optimized Triton implementation, `w` is already the log of the decay factor, so there's only one exponential operation needed. https://github.com/fla-org/flash-linear-attention/blob/main/fla/ops/rwkv7/fused_recurrent.py#L94 + +This formulation allows more flexibility in how the state evolves while maintaining the core gradient descent learning dynamics. + +## 1. Forward Pass Recurrence Equation + +In the implementation, the state update is defined as: + +For each batch (bi) and head (hi), at time step t: + +```python +w_t = torch.exp(-torch.exp(w[bi, hi, t])) # shape [K] +sa = (state[bi, hi] * a_t[None, :]).sum(dim=1) # shape [V] +state[bi, hi] = w_t[None, :] * state[bi, hi] + sa[:, None] * b_t[None, :] + k_t[None, :] * v_t[:, None] +``` + +Where state[bi, hi] has shape [V, K], representing a state matrix that maps from K-dimensional keys to V-dimensional values. + +## 2. Backward Pass Derivation + +### 2.1 Gradient of Loss w.r.t. State + +For time step t, if L is the loss function, dstate_curr = ∂L/∂state[bi, hi, t+1] is the gradient of the current state: + +``` +dstate_curr = dstate[bi, hi] + q_t[None, :] * doutput[bi, hi, t][:, None] +``` + +This includes gradients propagated from future time steps dstate[bi, hi] and gradients from the current output. + +### 2.2 Gradient w.r.t. Query q_t + +``` +dq[bi, hi, t] = torch.matmul(doutput[bi, hi, t], curr_state) * scale +``` + +### 2.3 Gradient w.r.t. Decay Parameter w_t + +For the gradient of w_t, we need to consider how it affects the state update: + +1. For the `w_t[None, :] * state[bi, hi]` component of the state update: + +First, compute the derivative of L with respect to w_t: + +``` +∂L/∂w_t[k] = ∑_v (dstate_curr[v,k] * prev_state[v,k]) +``` + +This equation sums over the v dimension for each position k, resulting in a vector of shape [K]. + +Then, compute the derivative of w_t with respect to w: + +``` +∂w_t[k]/∂w[k] = -exp(w[k]) * exp(-exp(w[k])) = -exp(w[k]) * w_t[k] +``` + +Finally, apply the chain rule: + +``` +∂L/∂w[k] = ∂L/∂w_t[k] * ∂w_t[k]/∂w[k] + = (∑_v dstate_curr[v,k] * prev_state[v,k]) * (-exp(w[k]) * w_t[k]) +``` + +In code, this is expressed as: + +```python +dw[bi, hi, t] += -torch.sum(dstate_curr * prev_state, dim=0) * torch.exp(w[bi, hi, t]) * w_t +``` + +Or equivalently: + +```python +dw[bi, hi, t] += -torch.sum(dstate_curr * prev_state, dim=0) * torch.exp(w[bi, hi, t]) * torch.exp(-torch.exp(w[bi, hi, t])) +``` + +### 2.4 Gradient w.r.t. k_t and v_t + +For the `k_t[None, :] * v_t[:, None]` component: + +```python +dk[bi, hi, t] += torch.sum(dstate_curr * v_t[:, None], dim=0) +dv[bi, hi, t] += torch.sum(dstate_curr * k_t[None, :], dim=1) +``` + +### 2.5 Gradient w.r.t. α_t and β_t (a_t and b_t in code) + +For the `sa[:, None] * b_t[None, :]` component, where `sa = (state[bi, hi] * a_t[None, :]).sum(dim=1)`: + +```python +db[bi, hi, t] += torch.sum(dstate_curr * sa[:, None], dim=0) +dsa = torch.sum(dstate_curr * b_t[None, :], dim=1) +da[bi, hi, t] += torch.sum(prev_state * dsa[:, None], dim=0) +``` + +### 2.6 Gradient w.r.t. Previous State S\_{t-1} + +Finally, we compute the gradient of the previous state for backpropagation: + +```python +dstate_from_sa = a_t[None, :] * dsa[:, None] +dstate_from_decay = dstate_curr * w_t[None, :] +dstate[bi, hi] = dstate_from_sa + dstate_from_decay +``` + +```python +# -*- coding: utf-8 -*- +from typing import Optional, Tuple + +import torch + +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def naive_recurrent_rwkv7( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + a: torch.Tensor, # Dynamic learning rate modulator + b: torch.Tensor, # State update modulator + scale: float = 1.0, + initial_state: Optional[torch.Tensor] = None, + output_final_state: bool = True, +): + """ + Naive recurrent implementation of RWKV-7 (Goose) attention mechanism. + Modified from bo's code. + https://github.com/BlinkDL/RWKV-LM/blob/main/RWKV-v7/rwkv_v7_demo.py#L170 + + Args: + q, k, v: Query, Key, and Value tensors + w: Time decay weights + a: Dynamic learning rate modulator, influences the in-context learning rate + b: State update modulator, directly participates in state update calculation + scale: Scaling factor for attention scores + initial_state: Initial state for the recurrent computation + output_final_state: Whether to output the final state + + Returns: + Attention output and optionally the final state + """ + torch_dtype = q.dtype if q.dtype in [torch.float64, torch.float] else torch.float + orig_dtype = q.dtype + B, H, L, N, V = q.shape[0], q.shape[1], q.shape[2], q.shape[3], v.shape[-1] + q, k, v, w, a, b = (x.to(dtype=torch_dtype) for x in (q, k, v, w, a, b)) + # q, k, v, a, b, w, + # shape: (B, H, L, D), (B, H, L, D), (B, H, T, V), (B, H, L, D), (B, H, L, D), (B, H, L, D) + state = torch.zeros(B, H, V, N, dtype=torch_dtype, device=q.device) + o = torch.zeros_like(v) + + if scale == -1.0: + scale = N ** -0.5 + + if initial_state is not None: + state += initial_state.to(dtype=torch_dtype) + + for t in range(L): + q_t = q[:, :, t] * scale + k_t = k[:, :, t] + v_t = v[:, :, t] + a_t = a[:, :, t] + b_t = b[:, :, t] + + # from bo's code + sab = torch.einsum('bhik,bhk,bhj->bhij', state, a_t, b_t) + state = state * torch.exp(-torch.exp(w[:, :, t, None, :])) + sab + torch.einsum('bhj,bhi->bhij', k_t, v_t) + o[:, :, t] = torch.einsum('bhj,bhij->bhi', q_t, state) + + if not output_final_state: + ht = None + elif initial_state is not None: + ht = state.to(initial_state.dtype) + else: + ht = state.to(orig_dtype) + + return o.to(orig_dtype), ht + + +def naive_recurrent_rwkv7_2( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + a: torch.Tensor, # Dynamic learning rate modulator + b: torch.Tensor, # State update modulator + scale: float = 1.0, + initial_state: Optional[torch.Tensor] = None, + output_final_state: bool = True, +): + """ + Naive recurrent implementation of RWKV-7 (Goose) attention mechanism. + + Args: + q, k, v: Query, Key, and Value tensors + w: Time decay weights + a: Dynamic learning rate modulator, influences the in-context learning rate + b: State update modulator, directly participates in state update calculation + scale: Scaling factor for attention scores + initial_state: Initial state for the recurrent computation + output_final_state: Whether to output the final state + + Returns: + Attention output and optionally the final state + """ + torch_dtype = q.dtype if q.dtype in [torch.float64, torch.float] else torch.float + orig_dtype = q.dtype + B, H, L, N, V = q.shape[0], q.shape[1], q.shape[2], q.shape[3], v.shape[-1] + q, k, v, w, a, b = (x.to(dtype=torch_dtype) for x in (q, k, v, w, a, b)) + # q, k, v, a, b, w, + # shape: (B, H, L, D), (B, H, L, D), (B, H, T, V), (B, H, L, D), (B, H, L, D), (B, H, L, D) + state = torch.zeros(B, H, V, N, dtype=torch_dtype, device=q.device) + o = torch.zeros_like(v) + + if scale == -1.0: + scale = N ** -0.5 + + if initial_state is not None: + state += initial_state.to(dtype=torch_dtype) + + for t in range(L): + for bi in range(B): + for hi in range(H): + q_t = q[bi, hi, t] * scale + k_t = k[bi, hi, t] + v_t = v[bi, hi, t] + a_t = a[bi, hi, t] + b_t = b[bi, hi, t] + w_t = torch.exp(-torch.exp(w[bi, hi, t])) + + # h: [V, K], a_t [K] -> [1, K] + # sa: [V] + sa = (state[bi, hi] * a_t[None, :]).sum(dim=1) + + state[bi, hi] = w_t[None, :] * state[bi, hi] + sa[:, None] * b_t[None, :] + k_t[None, :] * v_t[:, None] + y = (state[bi, hi] * q_t[None, :]).sum(dim=1) + + o[bi, hi, t] = y + + ht = state if output_final_state else None + return o.to(orig_dtype), ht + + +@torch.no_grad() +def naive_recurrent_rwkv7_2_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + doutput: torch.Tensor, + dh_t: Optional[torch.Tensor] = None, + scale: float = 1.0, + dtype: Optional[torch.dtype] = None +): + """ + Backward pass for the naive_recurrent_rwkv7_2 implementation. + + Args: + q, k, v, w, a, b: Original forward pass inputs + doutput: Gradient of the loss with respect to the output + dh_t: Gradient of the loss with respect to the final state (if any) + scale: Scaling factor used in the forward pass + dtype: Optional dtype for computation + + Returns: + Gradients with respect to all inputs + """ + torch_dtype = q.dtype if q.dtype in [torch.float64, torch.float] else torch.float + q, k, v, w, a, b, doutput = (x.to(dtype=torch_dtype) for x in (q, k, v, w, a, b, doutput)) + if dh_t is not None: + dh_t = dh_t.to(dtype=torch_dtype) + + B, H, L, N, V = q.shape[0], q.shape[1], q.shape[2], q.shape[3], v.shape[-1] + + # Initialize gradients + dq = torch.empty_like(q) + dk = torch.empty_like(k) + dv = torch.empty_like(v) + dw = torch.empty_like(w) + da = torch.empty_like(a) + db = torch.empty_like(b) + + # Initialize state gradients + dstate = torch.zeros(B, H, V, N, dtype=torch_dtype, device=q.device) + if dh_t is not None: + dstate += dh_t + + if scale == -1.0: + scale = N ** -0.5 + + # First rebuild all states from forward pass + states = [] + state = torch.zeros(B, H, V, N, dtype=torch_dtype, device=q.device) + states.append(state.clone()) + + # In practice, we don't recompute all states from the beginning. + # Instead, we use checkpointing: we save states at regular intervals (e.g., every 16 tokens) + # during the forward pass, then reconstruct intermediate states during the backward pass + # by working backwards from the nearest checkpoint. + # + # For example, to get state[t-1] from state[t]: + # state[t-1] = (state[t] - (sa * b_t + k_t * v_t)) / w_t + # + # This approach balances memory usage and computational efficiency: + # - Reduces memory by not storing every state + # - Maintains numerical stability by limiting the number of backward steps from each checkpoint + # - Allows efficient gradient computation without recomputing the entire sequence + for t in range(L): + for bi in range(B): + for hi in range(H): + q_t = q[bi, hi, t] * scale + k_t = k[bi, hi, t] + v_t = v[bi, hi, t] + a_t = a[bi, hi, t] + b_t = b[bi, hi, t] + w_t = torch.exp(-torch.exp(w[bi, hi, t])) + + sa = (state[bi, hi] * a_t[None, :]).sum(dim=1) + + state[bi, hi] = w_t[None, :] * state[bi, hi] + sa[:, None] * b_t[None, :] + k_t[None, :] * v_t[:, None] + states.append(state.clone()) + + # Backward pass through time + for t in range(L-1, -1, -1): + for bi in range(B): + for hi in range(H): + q_t = q[bi, hi, t] * scale + k_t = k[bi, hi, t] + v_t = v[bi, hi, t] + a_t = a[bi, hi, t] + b_t = b[bi, hi, t] + w_scalar = w[bi, hi, t] + w_exp = torch.exp(w_scalar) + w_t = torch.exp(-w_exp) + + curr_state = states[t+1][bi, hi] # State after update [V, K] + prev_state = states[t][bi, hi] # State before update [V, K] + + dq[bi, hi, t] = (doutput[bi, hi, t][:, None] * curr_state).sum(dim=0) * scale + + dstate_from_out = q_t[None, :] * doutput[bi, hi, t][:, None] # [V, K] + + dstate_curr = dstate[bi, hi] + dstate_from_out + + sa = (prev_state * a_t[None, :]).sum(dim=1) # [V] + + # state[bi, hi] = w_t[None, :] * prev_state + ... + dw[bi, hi, t] = -torch.sum(dstate_curr * prev_state, dim=0) * \ + w_t * w_exp + + # k_t[None, :] * v_t[:, None] -> [V, K] + dk[bi, hi, t] = torch.sum(dstate_curr * v_t[:, None], dim=0) + dv[bi, hi, t] = torch.sum(dstate_curr * k_t[None, :], dim=1) + + # sa[:, None] * b_t[None, :] -> [V, K] + db[bi, hi, t] = torch.sum(dstate_curr * sa[:, None], dim=0) + dsa = torch.sum(dstate_curr * b_t[None, :], dim=1) # [V] + + # sa = (prev_state * a_t[None, :]).sum(dim=1) + da[bi, hi, t] = torch.sum(prev_state * dsa[:, None], dim=0) + dstate_from_sa = a_t[None, :] * dsa[:, None] # [V, K] + + # w_t[None, :] * prev_state + dstate_from_decay = dstate_curr * w_t[None, :] # [V, K] + + dstate[bi, hi] = dstate_from_sa + dstate_from_decay + + return dq, dk, dv, dw, da, db, dstate + + +class NativeRecurrentRWKV7Function(torch.autograd.Function): + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, w, a, b, scale, initial_state, + training: bool = True, dtype: Optional[torch.dtype] = None, + state_ckpt_interval: int = 16): + o, ht = naive_recurrent_rwkv7_2(q, k, v, w, a, b, scale=scale, initial_state=initial_state) + if training: + ctx.save_for_backward(q, k, v, w, a, b) + ctx.scale = scale + ctx.dtype = dtype + ctx.ckpt_interval = state_ckpt_interval + ctx.use_initial_state = initial_state is not None + return o, ht + + @staticmethod + @autocast_custom_bwd + def backward(ctx, do, dht): + q, k, v, w, a, b = ctx.saved_tensors + dq, dk, dv, dw, da, db, dh = naive_recurrent_rwkv7_2_bwd( + q, k, v, w, a, b, do, dht, ctx.scale, dtype=ctx.dtype) + dh = dh if ctx.use_initial_state else None + return dq, dk, dv, dw, da, db, None, dh, None, None + + +def recurrent_rwkv7( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float = 1.0, + initial_state: torch.Tensor = None, + output_final_state: bool = True, + cu_seqlens: Optional[torch.LongTensor] = None, + head_first: bool = True +) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Args: + r (torch.Tensor): + r of shape `[B, H, T, K]` if `head_first=True` else `[B, T, H, K]`. + k (torch.Tensor): + k of shape `[B, H, T, K]` if `head_first=True` else `[B, T, H, K]`. + v (torch.Tensor): + v of shape `[B, H, T, V]` if `head_first=True` else `[B, T, H, V]`. + a (torch.Tensor): + a of shape `[B, H, T, K]` if `head_first=True` else `[B, T, H, K]`. + b (torch.Tensor): + b of shape `[B, H, T, K]` if `head_first=True` else `[B, T, H, K]`. + w (torch.Tensor): + decay of shape `[B, H, T, K]` if `head_first=True` else `[B, T, H, K]`, kernel + will apply log_w = -torch.exp(w) + log_w (torch.Tensor): + log decay of shape `[B, H, T, K]` if `head_first=True` else `[B, T, H, K]`. + scale (float): + scale of the attention. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (bool): + whether to use head first. Recommended to be False to avoid extra transposes. + """ + assert cu_seqlens is None + assert head_first is True + assert w is not None + if scale == -1.0: + scale = q.shape[-1] ** -0.5 + o, final_state = NativeRecurrentRWKV7Function.apply(q, k, v, w, a, b, scale, initial_state) + + return o, final_state + + +def test_autograd_function(): + """Test the custom autograd function implementation""" + # Set random seed for reproducibility + torch.manual_seed(42) + + # Define test dimensions + B, H, T, D = 1, 1, 128, 64 + V = N = D + device = 'cpu' + dtype = torch.float64 + + # Create random test inputs + q = torch.empty(B, H, T, D, device=device).uniform_(-8, 8).to(dtype=dtype).requires_grad_(True) + k = torch.empty(B, H, T, D, device=device).uniform_(-8, 8).to(dtype=dtype).requires_grad_(True) + v = torch.empty(B, H, T, D, device=device).uniform_(-8, 8).to(dtype=dtype).requires_grad_(True) + w = torch.empty(B, H, T, D, device=device).uniform_(-8, -6).to(dtype=dtype).requires_grad_(True) + + kk = torch.empty(B, H, T, D, device=device).uniform_(-8, 8) + kk = torch.nn.functional.normalize(kk, dim=-1).to(dtype=dtype) + + a = -kk.clone().requires_grad_(True) # -kk + a_scale = torch.empty(B, H, T, D, device=device).uniform_(0, 0.1).to(dtype=dtype) + b = (kk * a_scale).requires_grad_(True) # kk*a + + # Create initial state + initial_state = torch.zeros(B, H, V, N).to(torch.float64) + + # Clone inputs for the two paths we're testing + q1, k1, v1, w1, a1, b1 = q.clone().detach().requires_grad_(True), k.clone().detach().requires_grad_(True), v.clone().detach().requires_grad_( + True), w.clone().detach().requires_grad_(True), a.clone().detach().requires_grad_(True), b.clone().detach().requires_grad_(True) + q2, k2, v2, w2, a2, b2 = q.clone().detach().requires_grad_(True), k.clone().detach().requires_grad_(True), v.clone().detach().requires_grad_( + True), w.clone().detach().requires_grad_(True), a.clone().detach().requires_grad_(True), b.clone().detach().requires_grad_(True) + + # Path 1: Using naive implementation with autograd + + output1, state1 = naive_recurrent_rwkv7(q1, k1, v1, w1, a1, b1, initial_state=initial_state.clone()) + + output2, state2 = recurrent_rwkv7(q2, k2, v2, w2, a2, b2, 1.0, initial_state.clone()) + + # Check forward pass equivalence + output_diff = torch.max(torch.abs(output1 - output2)).item() + state_diff = torch.max(torch.abs(state1 - state2)).item() + + print(f"\nAutograd Function test (forward):") + print(f" Max output difference: {output_diff:.6e}") + print(f" Max state difference: {state_diff:.6e}") + + # Create loss function to test backward pass + def compute_loss(output, state): + return output.sum() # + state.sum() + + # Compute loss and gradients for both paths + loss1 = compute_loss(output1, state1) + loss1.backward() + + loss2 = compute_loss(output2, state2) + loss2.backward() + + # Compare gradients + grad_diffs = { + 'q': torch.max(torch.abs(q1.grad - q2.grad)).item(), + 'k': torch.max(torch.abs(k1.grad - k2.grad)).item(), + 'v': torch.max(torch.abs(v1.grad - v2.grad)).item(), + 'w': torch.max(torch.abs(w1.grad - w2.grad)).item(), + 'a': torch.max(torch.abs(a1.grad - a2.grad)).item(), + 'b': torch.max(torch.abs(b1.grad - b2.grad)).item(), + } + + print(f"\nAutograd Function test (backward):") + for param, diff in grad_diffs.items(): + print(f" Max {param} gradient difference: {diff:.6e}") + + +test_autograd_function() +``` diff --git a/code/flash-linear-attention/fla/ops/rwkv7/__init__.py b/code/flash-linear-attention/fla/ops/rwkv7/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..05c58664f765449d61238ce566714a82a219811a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/__init__.py @@ -0,0 +1,9 @@ + +from .chunk import chunk_rwkv7 +from .fused_recurrent import fused_mul_recurrent_rwkv7, fused_recurrent_rwkv7 + +__all__ = [ + 'chunk_rwkv7', + 'fused_recurrent_rwkv7', + 'fused_mul_recurrent_rwkv7', +] diff --git a/code/flash-linear-attention/fla/ops/rwkv7/channel_mixing.py b/code/flash-linear-attention/fla/ops/rwkv7/channel_mixing.py new file mode 100644 index 0000000000000000000000000000000000000000..1a88650c21d9059ee648d1f20e9d75cd2afa9a7c --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/channel_mixing.py @@ -0,0 +1,332 @@ +import logging + +import torch +import triton +import triton.language as tl + +from fla.utils import ( + autocast_custom_bwd, + autocast_custom_fwd, + autotune_cache_kwargs, + check_pytorch_version, + input_guard, + use_cuda_graph, +) + +logger = logging.getLogger(__name__) + +if not check_pytorch_version('2.4'): + logger.warning('PyTorch < 2.4 detected - computations may be slower due to lack of optimizations') + + +@triton.autotune( + configs=[ + triton.Config({'BLOCK_SIZE': block_size}) + for block_size in [128, 256, 512, 1024, 2048, 4096, 8192] + ], + key=['hidden_dim'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit +def rwkv_seq_mix_kernel( + x_ptr, + x_prev_ptr, + mix_k_ptr, + output_ptr, + batch_size: tl.constexpr, + token_length, + hidden_dim: tl.constexpr, + BLOCK_SIZE: tl.constexpr, +): + block_start = tl.program_id(0) * BLOCK_SIZE + block_idx = block_start + tl.arange(0, BLOCK_SIZE)[:] + + total_seq_dim = token_length * hidden_dim + batch_idx = block_idx // total_seq_dim + seq_and_feat = block_idx % total_seq_dim + seq_idx = seq_and_feat // hidden_dim + feat_idx = seq_and_feat % hidden_dim + + is_valid = (batch_idx < batch_size) & (seq_idx < token_length) + + x_idx = batch_idx * total_seq_dim + seq_idx * hidden_dim + feat_idx + + curr_x = tl.load(x_ptr + x_idx, mask=is_valid, other=0.0).to(tl.float32) + k_value = tl.load(mix_k_ptr + feat_idx).to(tl.float32) + + is_first = seq_idx < 1 + prev_state_idx = batch_idx * hidden_dim + feat_idx + prev_state = tl.load(x_prev_ptr + prev_state_idx, + mask=(is_first & is_valid), + other=0.0).to(tl.float32) + + prev_x_idx = x_idx - hidden_dim + prev_x = tl.load(x_ptr + prev_x_idx, + mask=(~is_first & is_valid), + other=0.0).to(tl.float32) + + prev_value = tl.where(is_first, prev_state, prev_x) + state_diff = prev_value - curr_x + mixed = state_diff * k_value + result = tl.cast(curr_x + mixed, dtype=output_ptr.dtype.element_ty, fp_downcast_rounding='rtne') + tl.store(output_ptr + x_idx, result, mask=is_valid) + + +@triton.jit +def rwkv_channel_mixing_pow_and_relu( + in_ptr, + out_ptr, + BLOCK_SIZE: tl.constexpr, +): + """Fused ReLU and Power operation: x = ReLU(x)^2""" + xoffset = tl.program_id(0) * BLOCK_SIZE + xindex = xoffset + tl.arange(0, BLOCK_SIZE) + x0 = xindex + x = tl.load(in_ptr + (x0), None) + x = tl.maximum(x, 0.0).to(tl.float32) + x = tl.cast(x * x, dtype=out_ptr.dtype.element_ty, fp_downcast_rounding='rtne') + tl.store(out_ptr + (x0), x, None) + + +def rwkv_mix_torch(x: torch.Tensor, x_prev: torch.Tensor, x_k: torch.Tensor): + if x_prev.dim() == 2: + x_prev = x_prev.unsqueeze(1) # (batch_size, 1, hidden_dim) + xx = torch.cat((x_prev, x[:, :-1, :]), dim=1) - x + k = x.addcmul(xx, x_k) + return k + + +def rwkv_relu_and_square_torch(x: torch.Tensor): + return torch.relu(x) ** 2 + + +def rwkv_mix_fwd(x, x_prev, x_k): + has_batch = x.dim() == 3 + + if has_batch: + batch_size, token_length, hidden_dim = x.shape + else: + token_length, hidden_dim = x.shape + batch_size = 1 + x = x.unsqueeze(0) + x_prev = x_prev.unsqueeze(0) + + token_length = x.shape[1] + hidden_dim = x.shape[2] + total_elements = batch_size * token_length * hidden_dim + + output = torch.empty_like(x) + + def grid(meta): return ( + (total_elements + meta['BLOCK_SIZE'] - 1) // meta['BLOCK_SIZE'], # grid_0 + 1, # grid_1 + 1, # grid_2 + ) + + rwkv_seq_mix_kernel[grid]( + x.contiguous(), + x_prev.contiguous(), + x_k.squeeze(), + output, + batch_size=batch_size, + token_length=token_length, + hidden_dim=hidden_dim, + ) + if not has_batch: + output = output.squeeze(0) + return output + + +def rwkv_relu_and_square_fwd(x: torch.Tensor, inplace: bool = True): + """ + Triton implementation of RWKV's ReLU and square operation + Args: + x: Input tensor + Returns: + Tensor after ReLU and square operations + """ + x = x.contiguous() + output = x if inplace else torch.empty_like(x) + + def grid(meta): return ( + (output.numel() + meta['BLOCK_SIZE'] - 1) // meta['BLOCK_SIZE'], # grid_0 + 1, # grid_1 + 1, # grid_2 + ) + rwkv_channel_mixing_pow_and_relu[grid]( + x, + output, + BLOCK_SIZE=4096, + ) + + return output + + +@triton.jit +def relu_square_bwd_kernel( + out_ptr, + forward_input_ptr, + BLOCK_SIZE: tl.constexpr, +): + """ReLU(x)^2 backward kernel + grad_input = grad_output * 2 * x if x > 0 else 0 + """ + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + x = tl.load(forward_input_ptr + offsets).to(tl.float32) + grad = tl.load(out_ptr + offsets).to(tl.float32) + + x = tl.maximum(x, 0.0) + + grad_input = grad * 2 * x + + tl.store(out_ptr + offsets, grad_input.to(out_ptr.dtype.element_ty)) + + +@triton.autotune( + configs=[ + triton.Config({'BLOCK_SIZE': block_size}) + for block_size in [128, 256, 512, 1024, 2048, 4096, 8192] + ], + key=['hidden_dim'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit +def rwkv_mix_bwd_kenel( + dk1_ptr0, + xk_ptr, + dx_ptr, + dx_prev_ptr, + batch_size, + token_length, + hidden_dim: tl.constexpr, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0) + offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + + batch_idx = offsets // (token_length * hidden_dim) + seq_feat = offsets % (token_length * hidden_dim) + seq_idx = seq_feat // hidden_dim + feat_idx = seq_feat % hidden_dim + + is_valid = offsets < (batch_size * token_length * hidden_dim) + + dk1 = tl.load(dk1_ptr0 + offsets, mask=is_valid) + xk = tl.load(xk_ptr + feat_idx, mask=is_valid) + prod = dk1 * xk + + mask_next = seq_idx < (token_length - 1) + next_offset = offsets + hidden_dim + dk1_next = tl.load(dk1_ptr0 + next_offset, mask=mask_next & is_valid, other=0.0) + prod_next = dk1_next * xk + dx_val = dk1 - prod + tl.where(mask_next, prod_next, 0.0) + dx_val = tl.cast(dx_val, dtype=dx_ptr.dtype.element_ty, fp_downcast_rounding='rtne') + tl.store(dx_ptr + offsets, dx_val, mask=is_valid) + + dx_prev_offset = batch_idx * hidden_dim + feat_idx + is_first_step = seq_idx == 0 + + tl.store( + dx_prev_ptr + dx_prev_offset, + tl.cast(prod, dtype=dx_prev_ptr.dtype.element_ty), + mask=is_first_step, + ) + + +@torch.compile(fullgraph=True) +def compute_x_k_grad(dk1, x, x_prev): + """ + Args: + dk1: (batch*seq_len, hidden_dim) + x: (batch, seq_len, hidden_dim) + x_prev: (batch, hidden_dim) or (batch, 1, hidden_dim) + """ + + if x_prev.dim() == 2: + x_prev = x_prev.unsqueeze(1) # (batch, 1, hidden_dim) + xx = torch.cat((x_prev, x[:, :-1, :]), dim=1) - x # (batch, seq_len, hidden_dim) + + # (hidden_dim,) --> (1, 1, hidden_dim) + grad_x_k = (dk1 * xx.reshape(-1, x.shape[2])).sum(dim=0).view(1, 1, -1) + return grad_x_k + + +def rwkv_channel_mixing_bwd(grad_output, x, x_prev, x_k, key_weight, value_weight, k1, k1_K, k, inplace=True): + batch_size = x.shape[0] if x.dim() == 3 else 1 + seq_len, n_embd = x.shape[-2], x.shape[-1] + + dV = k.transpose(-2, -1) @ grad_output + dk = grad_output @ value_weight.transpose(-2, -1) + + BLOCK_SIZE = 4096 + grid = ((dk.numel() + BLOCK_SIZE - 1) // BLOCK_SIZE,) + relu_square_bwd_kernel[grid]( + dk, + k1_K, + BLOCK_SIZE=BLOCK_SIZE, + ) + + dK = k1.transpose(-2, -1) @ dk + dk1 = dk @ key_weight.transpose(-2, -1) + dk1 = dk1.view(-1, n_embd).contiguous() + + dk_reduced = compute_x_k_grad(dk1, x, x_prev) + dx_prev = torch.empty_like(x_prev) if not inplace else x_prev + dx = torch.empty_like(x) if not inplace else x + + def grid(meta): return ((batch_size * seq_len * n_embd + meta['BLOCK_SIZE'] - 1) // meta['BLOCK_SIZE'], 1, 1) + rwkv_mix_bwd_kenel[grid]( + dk1, + x_k.squeeze(), + dx, + dx_prev, + batch_size, + seq_len, + n_embd, + ) + # dx_prev.shape batch_size, seq_len, n_embd + return dx, dx_prev, dk_reduced, dK, dV + + +class Rwkv7ChannelMixing(torch.autograd.Function): + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, x, x_prev, x_k, key_weight, value_weight, inplace: bool = True): + k1 = rwkv_mix_fwd(x, x_prev, x_k) + k1_K = k1 @ key_weight + k = rwkv_relu_and_square_fwd(k1_K, inplace=True) + ctx.save_for_backward(x, x_prev, x_k, key_weight, value_weight) + ctx.inplace = inplace + return k @ value_weight + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, dkv): + x, x_prev, x_k, key_weight, value_weight = ctx.saved_tensors + k1 = rwkv_mix_fwd(x, x_prev, x_k) + k1_K = k1 @ key_weight + k = rwkv_relu_and_square_fwd(k1_K, inplace=False) + dx, dx_prev, dk_reduced, dK, dV = rwkv_channel_mixing_bwd( + dkv, x, x_prev, x_k, key_weight, value_weight, k1, k1_K, k, ctx.inplace) + return dx, dx_prev, dk_reduced, dK, dV, None + + +def channel_mixing_rwkv7(x: torch.Tensor, x_prev: torch.Tensor, x_k: torch.Tensor, + key_weight: torch.Tensor, value_weight: torch.Tensor, inplace: bool = True): + assert x.dim() == 3 + + return Rwkv7ChannelMixing.apply(x, x_prev, x_k, key_weight, value_weight, inplace), x[-1, :] + + +def channel_mixing_rwkv7_torch(x, x_prev, x_k, key_weight, value_weight): + k1 = rwkv_mix_torch(x, x_prev, x_k) + k1_K = k1 @ key_weight + k = rwkv_relu_and_square_torch(k1_K) + return k @ value_weight, x[-1, :] diff --git a/code/flash-linear-attention/fla/ops/rwkv7/chunk.py b/code/flash-linear-attention/fla/ops/rwkv7/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..f067e894e30f5691dc12fad50229c6d88cf7c74e --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/chunk.py @@ -0,0 +1,76 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch + +from fla.ops.generalized_delta_rule import chunk_dplr_delta_rule + + +def chunk_rwkv7( + r: torch.Tensor, + w: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float = 1.0, + initial_state: torch.Tensor = None, + output_final_state: bool = True, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +): + """ + Args: + r (torch.Tensor): + r of shape `[B, T, H, K]`. + w (torch.Tensor): + log decay of shape `[B, T, H, K]`. + k (torch.Tensor): + k of shape `[B, T, H, K]`. + v (torch.Tensor): + v of shape `[B, T, H, V]`. + a (torch.Tensor): + a of shape `[B, T, H, K]`. + b (torch.Tensor): + b of shape `[B, T, H, K]`. + scale (float): + scale of the attention. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and r.shape[1] < r.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({r.shape[1]}) < num_heads ({r.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + return chunk_dplr_delta_rule( + q=r, + k=k, + v=v, + a=a, + b=b, + gk=w, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + head_first=head_first, + ) diff --git a/code/flash-linear-attention/fla/ops/rwkv7/fused_addcmul.py b/code/flash-linear-attention/fla/ops/rwkv7/fused_addcmul.py new file mode 100644 index 0000000000000000000000000000000000000000..eb44cd3eed034efaa31dac8eaec049cf248b12da --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/fused_addcmul.py @@ -0,0 +1,293 @@ + +import logging +import os +import sys + +import torch +import triton +import triton.language as tl +from packaging.version import Version + +from fla.utils import autotune_cache_kwargs, check_pytorch_version, input_guard, is_amd, use_cuda_graph + +logger = logging.getLogger(__name__) + +if not check_pytorch_version('2.4'): + logger.warning('PyTorch < 2.4 detected - computations may be slower due to lack of optimizations') + + +def identity_decorator(fn): + return fn + + +current_python_version = Version(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") +min_torch_compile_version = Version("3.11") +fla_use_compile = os.getenv('FLA_USE_COMPILE', '1').lower() in ('1', 'true', 'yes') + +if current_python_version >= min_torch_compile_version and fla_use_compile: + torch_compile = torch.compile(fullgraph=True) +else: + logger.warning('torch.compile is not available in Python 3.10, using identity decorator instead') + torch_compile = identity_decorator + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [1, 2, 3] + for BT in [2, 4, 8] + ], + key=['BD'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit +def fused_addcmul_fwd_kernel( + hidden, + delta, + ixr, ixw, ixk, ixv, ixa, ixg, + oxr, oxw, oxk, oxv, oxa, oxg, + use_xg: tl.constexpr, + T, + T_OFFSET, + BT: tl.constexpr, + D: tl.constexpr, + BD: tl.constexpr, +): + i_b, i_t = tl.program_id(0), tl.program_id(1) * BT + + bos = i_b * (T + T_OFFSET) + t_vec = i_t + T_OFFSET + tl.arange(0, BT) + mask_t = t_vec < (T + T_OFFSET) + o_d = tl.arange(0, BD)[None, :] + off_vec = (bos + t_vec)[:, None] * D + o_d + m_d = o_d < D + mask = mask_t[:, None] & m_d + + b_h = tl.load(hidden + off_vec, mask=mask, other=0.) + b_x = tl.load(delta + off_vec, mask=mask, other=0.) + b_r = tl.load(ixr + o_d, mask=m_d) + b_w = tl.load(ixw + o_d, mask=m_d) + b_k = tl.load(ixk + o_d, mask=m_d) + b_v = tl.load(ixv + o_d, mask=m_d) + b_a = tl.load(ixa + o_d, mask=m_d) + + o_r = tl.fma(b_x, b_r, b_h) + o_w = tl.fma(b_x, b_w, b_h) + o_k = tl.fma(b_x, b_k, b_h) + o_v = tl.fma(b_x, b_v, b_h) + o_a = tl.fma(b_x, b_a, b_h) + + tl.store(oxr + off_vec, o_r.to(oxr.dtype.element_ty), mask=mask) + tl.store(oxw + off_vec, o_w.to(oxw.dtype.element_ty), mask=mask) + tl.store(oxk + off_vec, o_k.to(oxk.dtype.element_ty), mask=mask) + tl.store(oxv + off_vec, o_v.to(oxv.dtype.element_ty), mask=mask) + tl.store(oxa + off_vec, o_a.to(oxa.dtype.element_ty), mask=mask) + + if use_xg: + b_g = tl.load(ixg + o_d, mask=m_d) + o_g = tl.fma(b_x, b_g, b_h) + tl.store(oxg + off_vec, o_g.to(oxg.dtype.element_ty), mask=mask) + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [1, 2, 3] + for BT in [2, 4, 8] + ], + key=['BD'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit +def addcmul_bwd_kernel1( + ixr, + ixw, + ixk, + ixv, + ixa, + ixg, + dxr, + dxw, + dxk, + dxv, + dxa, + dxg, + ghidden, + gx, + use_xg: tl.constexpr, + T, + T_OFFSET, + BT: tl.constexpr, + D: tl.constexpr, + BD: tl.constexpr, + DTYPE: tl.constexpr, +): + i_b, i_t = tl.program_id(0), tl.program_id(1) + + t_idx = T_OFFSET + i_t * BT + tl.arange(0, BT)[:, None] + mask_t = t_idx < (T + T_OFFSET) + + d_idx = tl.arange(0, BD)[None, :] + mask_d = d_idx < D + mask = mask_t & mask_d + + offset_base = i_b * (T + T_OFFSET) * D + x_idx = (offset_base + t_idx * D + d_idx).to(tl.uint32) + + b_dxr = tl.load(dxr + x_idx, mask=mask).to(DTYPE) + b_dxw = tl.load(dxw + x_idx, mask=mask).to(DTYPE) + b_dxk = tl.load(dxk + x_idx, mask=mask).to(DTYPE) + b_dxv = tl.load(dxv + x_idx, mask=mask).to(DTYPE) + b_dxa = tl.load(dxa + x_idx, mask=mask).to(DTYPE) + + b_ixr = tl.load(ixr + d_idx, mask=mask_d).to(DTYPE) + b_ixw = tl.load(ixw + d_idx, mask=mask_d).to(DTYPE) + b_ixk = tl.load(ixk + d_idx, mask=mask_d).to(DTYPE) + b_ixv = tl.load(ixv + d_idx, mask=mask_d).to(DTYPE) + b_ixa = tl.load(ixa + d_idx, mask=mask_d).to(DTYPE) + + g_hidden = b_dxr + b_dxw + b_dxk + b_dxv + b_dxa + g_x = b_dxr * b_ixr + b_dxw * b_ixw + b_dxk * b_ixk + b_dxv * b_ixv + b_dxa * b_ixa + + if use_xg: + b_dxg = tl.load(dxg + x_idx, mask=mask).to(DTYPE) + b_ixg = tl.load(ixg + d_idx, mask=mask_d).to(DTYPE) + g_hidden += b_dxg + g_x += b_dxg * b_ixg + + tl.store(ghidden + x_idx, g_hidden.to(ghidden.dtype.element_ty), mask=mask) + tl.store(gx + x_idx, g_x.to(gx.dtype.element_ty), mask=mask) + + +def addcmul_bwd1(d_xr, d_xw, d_xk, d_xv, d_xa, d_xg, + x_r, x_w, x_k, x_v, x_a, x_g, hidden_states, delta, use_xg, inplace=True): + B, T, D = hidden_states.size() + g_hiddn = hidden_states if inplace else torch.empty_like(hidden_states) + g_delta = torch.empty_like(delta) + for t in range(0, T, 65536): + T_OFFSET = t + T_SIZE = min(65536, T - t) + def grid(meta): return (B, triton.cdiv(T_SIZE, meta['BT'])) + addcmul_bwd_kernel1[grid]( + ixr=x_r, + ixw=x_w, + ixk=x_k, + ixv=x_v, + ixa=x_a, + ixg=x_g, + dxr=d_xr, + dxw=d_xw, + dxk=d_xk, + dxv=d_xv, + dxa=d_xa, + dxg=d_xg, + ghidden=g_hiddn, + gx=g_delta, + use_xg=use_xg, + T=T_SIZE, + T_OFFSET=T_OFFSET, + D=D, + BD=triton.next_power_of_2(D), + DTYPE=tl.float16 if hidden_states.dtype == torch.float16 else tl.float32, + ) + return g_hiddn, g_delta + + +@torch_compile +def addcmul_bwd2(d_oxr, d_xw, d_xk, d_xv, d_xa, d_xg, delta, use_xg: bool): + g_xr = (d_oxr * delta).sum(dim=(0, 1), keepdim=True, dtype=torch.float32) + g_xw = (d_xw * delta).sum(dim=(0, 1), keepdim=True, dtype=torch.float32) + g_xk = (d_xk * delta).sum(dim=(0, 1), keepdim=True, dtype=torch.float32) + g_xv = (d_xv * delta).sum(dim=(0, 1), keepdim=True, dtype=torch.float32) + g_xa = (d_xa * delta).sum(dim=(0, 1), keepdim=True, dtype=torch.float32) + g_xg = (d_xg * delta).sum(dim=(0, 1), keepdim=True, dtype=torch.float32) if use_xg else None + return g_xr, g_xw, g_xk, g_xv, g_xa, g_xg + + +class Rwkv7FusedAddcmul(torch.autograd.Function): + @staticmethod + @input_guard + def forward( + ctx, hidden_states, delta, + x_r, x_w, x_k, x_v, x_a, x_g, + ): + B, T, D = hidden_states.size() + oxr = torch.empty_like(hidden_states) + oxw = torch.empty_like(hidden_states) + oxk = torch.empty_like(hidden_states) + oxv = torch.empty_like(hidden_states) + oxa = torch.empty_like(hidden_states) + if x_g is not None: + use_xg = True + oxg = torch.empty_like(hidden_states) + else: + use_xg = False + oxg = None + + for t in range(0, T, 65536): + T_OFFSET = t + T_SIZE = min(65536, T - t) + def grid(meta): return (B, triton.cdiv(T_SIZE, meta['BT'])) + fused_addcmul_fwd_kernel[grid]( + hidden_states, delta, + x_r, x_w, x_k, x_v, x_a, x_g, + oxr, oxw, oxk, oxv, oxa, oxg, + use_xg, + T=T_SIZE, + T_OFFSET=T_OFFSET, + D=D, + BD=triton.next_power_of_2(D), + ) + + ctx.save_for_backward(hidden_states, delta, + x_r, x_w, x_k, x_v, x_a, x_g) + ctx.use_xg = use_xg + return oxr, oxw, oxk, oxv, oxa, oxg + + @staticmethod + @input_guard + def backward(ctx, dxr, + dxw, dxk, dxv, dxa, dxg): + hidden_states, delta, x_r, x_w, x_k, x_v, x_a, x_g = ctx.saved_tensors + + d_hiddn, d_xx = addcmul_bwd1(dxr, dxw, dxk, dxv, dxa, dxg, x_r, x_w, x_k, x_v, x_a, x_g, + hidden_states, delta, ctx.use_xg) + + d_ixr, d_ixw, d_ixk, d_ixv, d_ixa, d_ixg = addcmul_bwd2(dxr, dxw, dxk, dxv, dxa, dxg, delta, ctx.use_xg) + + return d_hiddn, d_xx, d_ixr, d_ixw, d_ixk, d_ixv, d_ixa, d_ixg + + +def fused_addcmul_rwkv7( + hidden_states: torch.Tensor, + delta: torch.Tensor, + xr: torch.Tensor, + xw: torch.Tensor, + xk: torch.Tensor, + xv: torch.Tensor, + xa: torch.Tensor, + xg: torch.Tensor | None = None, +): + if hidden_states.shape[1] == 1: + # Special case for decode + return torch_addcmul_rwkv7(hidden_states, delta, xr, xw, xk, xv, xa, xg) + return Rwkv7FusedAddcmul.apply(hidden_states, delta, xr, xw, xk, xv, xa, xg) + + +def torch_addcmul_rwkv7(hidden_states, delta, xr, xw, xk, xv, xa, xg=None): + oxr = torch.addcmul(hidden_states, delta, xr) + oxw = torch.addcmul(hidden_states, delta, xw) + oxk = torch.addcmul(hidden_states, delta, xk) + oxv = torch.addcmul(hidden_states, delta, xv) + oxa = torch.addcmul(hidden_states, delta, xa) + if xg is not None: + oxg = torch.addcmul(hidden_states, delta, xg) + return oxr, oxw, oxk, oxv, oxa, oxg + else: + return oxr, oxw, oxk, oxv, oxa, None diff --git a/code/flash-linear-attention/fla/ops/rwkv7/fused_k_update.py b/code/flash-linear-attention/fla/ops/rwkv7/fused_k_update.py new file mode 100644 index 0000000000000000000000000000000000000000..795996d66584c75b2c27f779be6f5d481e6aff70 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/fused_k_update.py @@ -0,0 +1,348 @@ + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs, get_multiprocessor_count, input_guard, is_amd + +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [2, 4, 8, 16, 32] + + +@torch.jit.script +def k_update_ref(k: torch.Tensor, a: torch.Tensor, ka: torch.Tensor) -> torch.Tensor: + return k.addcmul(k * (a - 1), ka) + + +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=w, num_stages=s) + for w in NUM_WARPS_AUTOTUNE + for s in [1, 2, 3] + ], + key=['BD'], + **autotune_cache_kwargs, +) +@triton.jit +def k_update_fwd_kernel_short( + k, a, ka, out, + cu_seqlens, + T, D, + BD: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_b, i_t = tl.program_id(0), tl.program_id(1) + + if IS_VARLEN: + bos = tl.load(cu_seqlens + i_b).to(tl.int32) + eos = tl.load(cu_seqlens + i_b + 1).to(tl.int32) + g_t = bos + i_t + if g_t >= eos: + return + offset = g_t * D + else: + g_t = i_t + offset = i_b * T * D + g_t * D + + o_d = tl.arange(0, BD) + m_d = o_d < D + off = offset + o_d + + b_k = tl.load(k + off, mask=m_d, other=0.).to(tl.float32) + b_a = tl.load(a + off, mask=m_d, other=0.).to(tl.float32) + b_ka = tl.load(ka + o_d, mask=m_d, eviction_policy='evict_last').to(tl.float32) + + out_val = b_k * (1 + (b_a - 1) * b_ka) + tl.store(out + off, out_val.to(out.dtype.element_ty), mask=m_d) + + +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=w, num_stages=s) + for w in NUM_WARPS_AUTOTUNE + for s in [1, 2, 3] + ], + key=['BD', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit +def k_update_fwd_kernel_long( + k, a, ka, out, + cu_seqlens, chunk_indices, + T, D, + BD: tl.constexpr, BT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_t_blk, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + if IS_VARLEN: + i_n, i_t_blk = tl.load(chunk_indices + i_t_blk * 2).to(tl.int32), \ + tl.load(chunk_indices + i_t_blk * 2 + 1).to(tl.int32) + bos = tl.load(cu_seqlens + i_n).to(tl.int32) + eos = tl.load(cu_seqlens + i_n + 1).to(tl.int32) + t_start = i_t_blk * BT + t_end = tl.minimum(t_start + BT, eos - bos) + else: + bos = i_b * T + eos = (i_b + 1) * T + t_start = i_t_blk * BT + t_end = tl.minimum(t_start + BT, T) + + o_d = i_d * BD + tl.arange(0, BD) + m_d = o_d < D + + for t in range(t_start, t_end): + global_t = bos + t + off = global_t * D + o_d + b_k = tl.load(k + off, mask=m_d, other=0.).to(tl.float32) + b_a = tl.load(a + off, mask=m_d, other=0.).to(tl.float32) + b_ka = tl.load(ka + o_d, mask=m_d, eviction_policy='evict_last').to(tl.float32) + out_val = b_k * (1 + (b_a - 1) * b_ka) + tl.store(out + off, out_val.to(out.dtype.element_ty), mask=m_d) + + +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=w, num_stages=s) + for w in NUM_WARPS_AUTOTUNE + for s in [1, 2, 3] + for BT in [2, 4, 8] + ], + key=['BD'], + **autotune_cache_kwargs, +) +@triton.jit +def k_update_bwd_kernel_short( + grad_out, k, a, ka, + dk, da, dka, + cu_seqlens, + T, D, + BT: tl.constexpr, + BD: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_b, i_t_base = tl.program_id(0), tl.program_id(1) * BT + + if IS_VARLEN: + bos = tl.load(cu_seqlens + i_b).to(tl.int32) + eos = tl.load(cu_seqlens + i_b + 1).to(tl.int32) + seq_len = eos - bos + else: + bos = i_b * T + eos = (i_b + 1) * T + seq_len = T + + t_vec = i_t_base + tl.arange(0, BT) + mask_t = t_vec < seq_len + global_t_vec = bos + t_vec + + o_d = tl.arange(0, BD)[None, :] + m_d = o_d < D + off = global_t_vec[:, None] * D + o_d + + b_go = tl.load(grad_out + off, mask=mask_t[:, None] & m_d, other=0.).to(tl.float32) + b_k = tl.load(k + off, mask=mask_t[:, None] & m_d, other=0.).to(tl.float32) + b_a = tl.load(a + off, mask=mask_t[:, None] & m_d, other=0.).to(tl.float32) + b_ka = tl.load(ka + o_d, mask=m_d, eviction_policy='evict_last').to(tl.float32) # [1, BD] + + dk_vec = b_go * (1 + (b_a - 1) * b_ka) + da_vec = b_go * b_k * b_ka + dka_vec = b_go * b_k * (b_a - 1) + tl.store(dk + off, dk_vec.to(dk.dtype.element_ty), mask=mask_t[:, None] & m_d) + tl.store(da + off, da_vec.to(da.dtype.element_ty), mask=mask_t[:, None] & m_d) + tl.store(dka + off, dka_vec.to(dka.dtype.element_ty), mask=mask_t[:, None] & m_d) + + +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=w, num_stages=s) + for w in NUM_WARPS_AUTOTUNE + for s in [1, 2, 3] + ], + key=['BD', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit +def k_update_bwd_kernel_long( + grad_out, k, a, ka, + dk, da, dka, + cu_seqlens, chunk_indices, + T, D, + BD: tl.constexpr, BT: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_t_blk, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + if IS_VARLEN: + i_n, i_t_blk = tl.load(chunk_indices + i_t_blk * 2).to(tl.int32), \ + tl.load(chunk_indices + i_t_blk * 2 + 1).to(tl.int32) + bos = tl.load(cu_seqlens + i_n).to(tl.int32) + eos = tl.load(cu_seqlens + i_n + 1).to(tl.int32) + t_start = i_t_blk * BT + t_end = tl.minimum(t_start + BT, eos - bos) + else: + bos = i_b * T + eos = (i_b + 1) * T + t_start = i_t_blk * BT + t_end = tl.minimum(t_start + BT, T) + + o_d = i_d * BD + tl.arange(0, BD) + m_d = o_d < D + + for t in range(t_start, t_end): + global_t = bos + t + off = global_t * D + o_d + + b_go = tl.load(grad_out + off, mask=m_d, other=0.).to(tl.float32) + b_k = tl.load(k + off, mask=m_d, other=0.).to(tl.float32) + b_a = tl.load(a + off, mask=m_d, other=0.).to(tl.float32) + b_ka = tl.load(ka + o_d, mask=m_d, eviction_policy='evict_last').to(tl.float32) + + tl.store(dk + off, (b_go * (1 + (b_a - 1) * b_ka)).to(dk.dtype.element_ty), mask=m_d) + tl.store(da + off, (b_go * b_k * b_ka).to(da.dtype.element_ty), mask=m_d) + tl.store(dka + off, (b_go * b_k * (b_a - 1)).to(dka.dtype.element_ty), mask=m_d) + + +def k_update_fwd( + k: torch.Tensor, + a: torch.Tensor, + ka: torch.Tensor, + cu_seqlens: torch.Tensor | None = None, +) -> torch.Tensor: + B, T, D = k.shape + out = torch.empty_like(k) + use_short = T <= 512 + + if use_short: + if cu_seqlens is not None: + N = len(cu_seqlens) - 1 + else: + N = B + BD = triton.next_power_of_2(D) + grid = (N, T) + k_update_fwd_kernel_short[grid]( + k, a, ka, out, + cu_seqlens, + T, D, + BD=BD, + ) + else: + BT = min(64, triton.next_power_of_2( + triton.cdiv(max(16, B * T), get_multiprocessor_count(k.device.index)), + )) + if cu_seqlens is not None: + chunk_idx = prepare_chunk_indices(cu_seqlens, BT) + NT = len(chunk_idx) + N = len(cu_seqlens) - 1 + else: + chunk_idx = None + NT = triton.cdiv(T, BT) + N = B + + BD = triton.next_power_of_2(D) + + def grid(meta): + return (triton.cdiv(D, meta['BD']), NT, N) + + k_update_fwd_kernel_long[grid]( + k, a, ka, out, + cu_seqlens, chunk_idx, + T, D, + BD=BD, BT=BT, + ) + + return out, use_short, N, T + + +def k_update_bwd( + grad_out: torch.Tensor, + k: torch.Tensor, + a: torch.Tensor, + ka: torch.Tensor, + cu_seqlens: torch.Tensor | None, + use_short: bool, + N: int, + T: int, +): + B, _, D = grad_out.shape + dk = torch.empty_like(k) + da = torch.empty_like(a) + dka_tmp = torch.empty_like(k, dtype=torch.float32) + + if use_short: + BD = triton.next_power_of_2(D) + def grid(meta): return (N, triton.cdiv(T, meta['BT'])) + k_update_bwd_kernel_short[grid]( + grad_out, k, a, ka, + dk, da, dka_tmp, + cu_seqlens, + T, D, + BD=BD, + ) + else: + BT = min(64, triton.next_power_of_2( + triton.cdiv(max(16, B * T), get_multiprocessor_count(grad_out.device.index)), + )) + if cu_seqlens is not None: + chunk_idx = prepare_chunk_indices(cu_seqlens, BT) + NT = len(chunk_idx) + else: + chunk_idx = None + NT = triton.cdiv(T, BT) + + BD = triton.next_power_of_2(D) + + def grid(meta): + return (triton.cdiv(D, meta['BD']), NT, N) + + k_update_bwd_kernel_long[grid]( + grad_out, k, a, ka, + dk, da, dka_tmp, + cu_seqlens, chunk_idx, + T, D, + BD=BD, BT=BT, + ) + + if dka_tmp.dim() == 3: + dka = dka_tmp.sum(dim=(0, 1), keepdim=True).type_as(ka) + else: + dka = dka_tmp.sum(dim=(0, 1)).type_as(ka) + + return dk, da, dka + + +class KUpdateFunction(torch.autograd.Function): + @staticmethod + @input_guard + def forward(ctx, k, a, ka, cu_seqlens=None): + out, use_short, N, T = k_update_fwd(k, a, ka, cu_seqlens) + ctx.save_for_backward(k, a, ka) + ctx.use_short = use_short + ctx.N = N + ctx.T = T + ctx.cu_seqlens = cu_seqlens + return out + + @staticmethod + @input_guard + def backward(ctx, grad_output): + k, a, ka = ctx.saved_tensors + dk, da, dka = k_update_bwd( + grad_output, k, a, ka, + ctx.cu_seqlens, + ctx.use_short, + ctx.N, + ctx.T, + ) + return dk, da, dka, None + + +def fused_k_rwkv7(k, a, ka, cu_seqlens=None): + if k.shape[1] == 1: + return k_update_ref(k, a, ka) + return KUpdateFunction.apply(k, a, ka, cu_seqlens) diff --git a/code/flash-linear-attention/fla/ops/rwkv7/fused_recurrent.py b/code/flash-linear-attention/fla/ops/rwkv7/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..5b67df2e7e79f72f4580e59dfa17264376b8fec2 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/fused_recurrent.py @@ -0,0 +1,333 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl + +from fla.ops.generalized_delta_rule import fused_recurrent_dplr_delta_rule +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, input_guard, use_cuda_graph + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BV in [16, 32, 64] + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3, 4] + ], + key=['BK'], + use_cuda_graph=use_cuda_graph, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_rwkv7_fwd_kernel( + r, + w, + k, + v, + kk, + a, + o, + h0, + ht, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + REVERSE: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, + IS_DECODE: tl.constexpr, +): + i_v, i_nh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + o_k = tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + p_r = r + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_w = w + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_k = k + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_v = v + (bos + ((T - 1) if REVERSE else 0)) * H*V + i_h * V + o_v + p_a = a + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + p_kk = kk + (bos + ((T - 1) if REVERSE else 0)) * H*K + i_h * K + o_k + + p_o = o + (bos + ((T - 1) if REVERSE else 0)) * H*V + i_h * V + o_v + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + if IS_DECODE: + b_r = tl.load(p_r, mask=mask_k, other=0).to(tl.float32) * scale + b_w = tl.load(p_w, mask=mask_k, other=0).to(tl.float32) + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_a = tl.load(p_a, mask=mask_k, other=0).to(tl.float32) + b_kk = tl.load(p_kk, mask=mask_k, other=0).to(tl.float32) + b_act_a = -b_kk + b_b = b_kk * b_a + + b_h = exp(b_w)[:, None] * b_h + b_b[:, None] * tl.sum(b_act_a[:, None] * b_h, 0)[None, :] + b_h += b_k[:, None] * b_v[None, :] + b_o = tl.sum(b_h * b_r[:, None], 0) + + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + else: + for _ in range(0, T): + b_r = tl.load(p_r, mask=mask_k, other=0).to(tl.float32) * scale + b_w = tl.load(p_w, mask=mask_k, other=0).to(tl.float32) + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + b_a = tl.load(p_a, mask=mask_k, other=0).to(tl.float32) + b_kk = tl.load(p_kk, mask=mask_k, other=0).to(tl.float32) + b_act_a = -b_kk + b_b = b_kk * b_a + + b_h = exp(b_w)[:, None] * b_h + b_b[:, None] * tl.sum(b_act_a[:, None] * b_h, 0)[None, :] + b_h += b_k[:, None] * b_v[None, :] + b_o = tl.sum(b_h * b_r[:, None], 0) + + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + p_r += (-1 if REVERSE else 1) * H*K + p_w += (-1 if REVERSE else 1) * H*K + p_k += (-1 if REVERSE else 1) * H*K + p_v += (-1 if REVERSE else 1) * H*V + p_a += (-1 if REVERSE else 1) * H*K + p_kk += (-1 if REVERSE else 1) * H*K + p_o += (-1 if REVERSE else 1) * H*V + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +@input_guard +def fused_recurrent_rwkv7_fwd( + r: torch.Tensor, + w: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + kk: torch.Tensor, + a: torch.Tensor, + scale: float | None = 1.0, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK = triton.next_power_of_2(K) + IS_DECODE = (T == 1) + + h0 = initial_state + if not output_final_state: + ht = None + else: + ht = r.new_empty(N, H, K, V, dtype=torch.float32) + o = torch.empty_like(v) + + def grid(meta): return (triton.cdiv(V, meta['BV']), N * H) + fused_recurrent_rwkv7_fwd_kernel[grid]( + r, + w, + k, + v, + kk, + a, + o, + h0, + ht, + cu_seqlens, + scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BK=BK, + REVERSE=reverse, + IS_DECODE=IS_DECODE, + ) + return o, ht + + +def fused_recurrent_rwkv7( + r: torch.Tensor, + w: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor = None, + output_final_state: bool = True, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +): + """ + Args: + r (torch.Tensor): + r of shape `[B, T, H, K]`. + w (torch.Tensor): + log decay of shape `[B, T, H, K]`. + k (torch.Tensor): + k of shape `[B, T, H, K]`. + v (torch.Tensor): + v of shape `[B, T, H, V]`. + a (torch.Tensor): + a of shape `[B, T, H, K]`. + b (torch.Tensor): + b of shape `[B, T, H, K]`. + scale (float): + scale of the attention. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (torch.Tensor): + initial state of shape `[B, H, K, V]` if cu_seqlens is None else `[N, H, K, V]` where N = len(cu_seqlens) - 1. + output_final_state (bool): + whether to output the final state. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + elif r.shape[1] < r.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({r.shape[1]}) < num_heads ({r.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + return fused_recurrent_dplr_delta_rule( + q=r, + k=k, + v=v, + a=a, + b=b, + gk=w, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + + +def fused_mul_recurrent_rwkv7( + r: torch.Tensor, + w: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + kk: torch.Tensor, + a: torch.Tensor, + scale: float | None = 1.0, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + This function computes the recurrence S_t = S_t @ (I + a_t b_t^T) + v_t k_t^T in a recurrent manner. + + Args: + r (torch.Tensor): + queries of shape `[B, T, H, K]`. + w (torch.Tensor): + keys of shape `[B, T, H, K]`. + k (torch.Tensor): + values of shape `[B, T, H, V]`. + v (torch.Tensor): + a of shape `[B, T, H, K]`. + kk (torch.Tensor): + b of shape `[B, T, H, K]`. + a (torch.Tensor): + gk of shape `[B, T, H, K]`. decay term in log space! + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: 1. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + reverse (Optional[bool]): + If `True`, process the state passing in reverse order. Default: `False`. + cu_seqlens (Optional[torch.Tensor]): + Cumulative sequence lengths of shape `[N + 1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + elif r.shape[1] < r.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({r.shape[1]}) < num_heads ({r.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if r.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {r.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = r.shape[-1] ** -0.5 + o, final_state = fused_recurrent_rwkv7_fwd( + r, + w, + k, + v, + kk, + a, + scale, + initial_state, + output_final_state, + reverse, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/rwkv7/gate_output_correction.py b/code/flash-linear-attention/fla/ops/rwkv7/gate_output_correction.py new file mode 100644 index 0000000000000000000000000000000000000000..151071c81cb71198e353c45036d75eb4dd7c9b82 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/rwkv7/gate_output_correction.py @@ -0,0 +1,245 @@ + +import torch +import triton +import triton.language as tl + +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard + + +def gate_output_correction_ref( + o: torch.Tensor, + r: torch.Tensor, + k: torch.Tensor, + r_k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, +): + """ + o: [B, T, H*D] + r: [B, T, H, D] + k: [B, T, H, D] + r_k: [H, D] + v: [B, T, H, D] + g: [B, T, H*D] + """ + # Unsqueeze r_k for broadcasting across batch and time + correction_term = ((r * k * r_k.unsqueeze(0).unsqueeze(0)).sum(-1, keepdim=True) * v).view(o.shape) + output = (o + correction_term) * g + return output + + +def gate_output_correction_backward_ref(grad_output, o, r, k, r_k, v, g): + """ + Reference backward pass implementation in pure PyTorch. + """ + B, T, HD = o.shape + H, D = r.shape[-2], r.shape[-1] + + # Unsqueeze r_k for broadcasting + r_k_b = r_k.unsqueeze(0).unsqueeze(0) + correction_scalar = (r * k * r_k_b).sum(-1, keepdim=True) + gated_input = o + (correction_scalar * v).view(B, T, HD) + + grad_g = grad_output * gated_input + grad_gated_input = grad_output * g + grad_o = grad_gated_input + grad_correction = grad_gated_input + grad_correction_reshaped = grad_correction.view(B, T, H, D) + grad_v = grad_correction_reshaped * correction_scalar + grad_correction_scalar = (grad_correction_reshaped * v).sum(-1, keepdim=True) + grad_r_mul_k_mul_rk = grad_correction_scalar.expand_as(r) + grad_r = grad_r_mul_k_mul_rk * k * r_k_b + grad_k = grad_r_mul_k_mul_rk * r * r_k_b + # Sum over batch and time, keep the head dimension + grad_r_k = (grad_r_mul_k_mul_rk * r * k).sum(dim=(0, 1)) + return grad_o, grad_r, grad_k, grad_r_k, grad_v, grad_g + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for num_warps in [2, 4, 8] + for BT in [2, 4, 8] + ], + key=['num_heads', 'head_dim', 'BLOCK_SIZE_D'], + **autotune_cache_kwargs, +) +@triton.jit +def gate_output_correction_fwd_kernel( + o_ptr, r_ptr, k_ptr, r_k_ptr, v_ptr, g_ptr, output_ptr, + o_b_stride, o_t_stride, + r_b_stride, r_t_stride, r_h_stride, + v_b_stride, v_t_stride, v_h_stride, + r_k_h_stride, + T, + T_OFFSET, + num_heads: tl.constexpr, + head_dim: tl.constexpr, + BLOCK_SIZE_D: tl.constexpr, + BT: tl.constexpr, +): + pid_b, pid_t_block = tl.program_id(0), tl.program_id(1) + pid_h = tl.program_id(2) + t_start = pid_t_block * BT + T_OFFSET + t_idx = t_start + tl.arange(0, BT)[:, None] + mask_t = t_idx < T + + d_idx = tl.arange(0, BLOCK_SIZE_D)[None, :] + mask_d = d_idx < head_dim + mask = mask_t & mask_d + + offset_rk_h = pid_h * r_k_h_stride + vec_r_k = tl.load(r_k_ptr + offset_rk_h + d_idx, mask=mask_d, other=0.0).to(tl.float32) + + offset_rh = pid_b * r_b_stride + t_idx * r_t_stride + pid_h * r_h_stride + offset_vh = pid_b * v_b_stride + t_idx * v_t_stride + pid_h * v_h_stride + vec_r = tl.load(r_ptr + offset_rh + d_idx, mask=mask, other=0.0).to(tl.float32) + vec_k = tl.load(k_ptr + offset_rh + d_idx, mask=mask, other=0.0).to(tl.float32) + vec_v = tl.load(v_ptr + offset_vh + d_idx, mask=mask, other=0.0).to(tl.float32) + correction = tl.sum(vec_r * vec_k * vec_r_k, axis=1)[:, None] * vec_v + + offset_o = pid_b * o_b_stride + t_idx * o_t_stride + pid_h * head_dim + vec_o = tl.load(o_ptr + offset_o + d_idx, mask=mask, other=0.0).to(tl.float32) + vec_g = tl.load(g_ptr + offset_o + d_idx, mask=mask, other=0.0).to(tl.float32) + final_output = (vec_o + correction) * vec_g + + tl.store(output_ptr + offset_o + d_idx, final_output.to(output_ptr.dtype.element_ty), mask=mask) + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [1, 2, 4] + for BT in [2, 4, 8] + ], + key=['num_heads', 'head_dim', 'BLOCK_SIZE_D'], + **autotune_cache_kwargs, +) +@triton.jit +def gate_output_correction_bwd_kernel( + grad_output_ptr, o_ptr, r_ptr, k_ptr, r_k_ptr, v_ptr, g_ptr, + grad_o_ptr, grad_r_ptr, grad_k_ptr, grad_r_k_intermediate_ptr, grad_v_ptr, grad_g_ptr, + r_b_stride, r_t_stride, r_h_stride, o_b_stride, o_t_stride, r_k_h_stride, + T, + T_OFFSET, + num_heads: tl.constexpr, + head_dim: tl.constexpr, + BLOCK_SIZE_D: tl.constexpr, + BT: tl.constexpr, +): + pid_b, pid_t_block = tl.program_id(0), tl.program_id(1) + pid_h = tl.program_id(2) + + t_idx = pid_t_block * BT + T_OFFSET + tl.arange(0, BT)[:, None] + mask_t = t_idx < T + + d_idx = tl.arange(0, BLOCK_SIZE_D)[None, :] + mask_d = d_idx < head_dim + mask = mask_t & mask_d + + rkv_offset = pid_b * r_b_stride + t_idx * r_t_stride + pid_h * r_h_stride + vec_r = tl.load(r_ptr + rkv_offset + d_idx, mask=mask, other=0.0).to(tl.float32) + vec_k = tl.load(k_ptr + rkv_offset + d_idx, mask=mask, other=0.0).to(tl.float32) + vec_v = tl.load(v_ptr + rkv_offset + d_idx, mask=mask, other=0.0).to(tl.float32) + + og_offset = pid_b * o_b_stride + t_idx * o_t_stride + pid_h * head_dim + vec_o = tl.load(o_ptr + og_offset + d_idx, mask=mask, other=0.0).to(tl.float32) + vec_g = tl.load(g_ptr + og_offset + d_idx, mask=mask, other=0.0).to(tl.float32) + vec_grad_output = tl.load(grad_output_ptr + og_offset + d_idx, mask=mask, other=0.0).to(tl.float32) + + offset_rk_h = pid_h * r_k_h_stride + vec_r_k = tl.load(r_k_ptr + offset_rk_h + d_idx, mask=mask_d, other=0.0).to(tl.float32) + + prod_r_k_rk = vec_r * vec_k * vec_r_k + corr_scalar = tl.sum(prod_r_k_rk, axis=1) + corr_vec = corr_scalar[:, None] * vec_v + gated_input = vec_o + corr_vec + + vec_grad_g = vec_grad_output * gated_input + vec_grad_gate = vec_grad_output * vec_g + vec_grad_o = vec_grad_gate + vec_grad_corr = vec_grad_gate + vec_grad_v = vec_grad_corr * corr_scalar[:, None] + grad_corr_s = tl.sum(vec_grad_corr * vec_v, axis=1)[:, None] + vec_grad_r = grad_corr_s * vec_k * vec_r_k + vec_grad_k = grad_corr_s * vec_r * vec_r_k + local_grad_rk = grad_corr_s * vec_r * vec_k + + tl.store(grad_o_ptr + og_offset + d_idx, vec_grad_o.to(grad_o_ptr.dtype.element_ty), mask=mask) + tl.store(grad_g_ptr + og_offset + d_idx, vec_grad_g.to(grad_g_ptr.dtype.element_ty), mask=mask) + tl.store(grad_r_ptr + rkv_offset + d_idx, vec_grad_r.to(grad_r_ptr.dtype.element_ty), mask=mask) + tl.store(grad_k_ptr + rkv_offset + d_idx, vec_grad_k.to(grad_k_ptr.dtype.element_ty), mask=mask) + tl.store(grad_v_ptr + rkv_offset + d_idx, vec_grad_v.to(grad_v_ptr.dtype.element_ty), mask=mask) + tl.store(grad_r_k_intermediate_ptr + rkv_offset + d_idx, + local_grad_rk.to(grad_r_k_intermediate_ptr.dtype.element_ty), mask=mask) + + +def gate_output_correction_backward_triton(grad_output, o, r, k, r_k, v, g): + batch_size, seq_len, _ = o.shape + num_heads, head_dim = r.shape[-2], r.shape[-1] + + grad_o = torch.empty_like(o) + grad_r = torch.empty_like(r) + grad_k = torch.empty_like(k) + grad_v = torch.empty_like(v) + grad_g = torch.empty_like(g) + # Keep intermediate in float32 for precision + grad_r_k = torch.empty_like(r, dtype=torch.float32) + + BLOCK_SIZE_D = triton.next_power_of_2(head_dim) + + for t_offset in range(0, seq_len, 65536): + T_SIZE = min(65536, seq_len - t_offset) + def grid(meta): return (batch_size, triton.cdiv(T_SIZE, meta['BT']), num_heads) + + gate_output_correction_bwd_kernel[grid]( + grad_output, o, r, k, r_k, v, g, + grad_o, grad_r, grad_k, grad_r_k, grad_v, grad_g, + r.stride(0), r.stride(1), r.stride(2), + o.stride(0), o.stride(1), + r_k.stride(0), + T_SIZE, t_offset, + num_heads=num_heads, head_dim=head_dim, BLOCK_SIZE_D=BLOCK_SIZE_D, + ) + # Sum over batch and time to get the final gradient for r_k + grad_r_k = grad_r_k.sum(dim=(0, 1)).type_as(r_k) + return grad_o, grad_r, grad_k, grad_r_k, grad_v, grad_g + + +class GateOutputCorrection(torch.autograd.Function): + @staticmethod + @autocast_custom_fwd + @input_guard + def forward(ctx, o, r, k, r_k, v, g): + assert r_k.dim() == 2 and r_k.shape[0] == r.shape[-2] and r_k.shape[1] == r.shape[-1] + + batch_size, seq_len, _ = o.shape + num_heads, head_dim = r.shape[-2], r.shape[-1] + output = torch.empty_like(o) + ctx.save_for_backward(o, r, k, r_k, v, g) + for t in range(0, seq_len, 65536): + T_OFFSET = t + T_SIZE = min(65536, seq_len - t) + def grid(meta): return (batch_size, triton.cdiv(T_SIZE, meta['BT']), num_heads) + + gate_output_correction_fwd_kernel[grid]( + o, r, k, r_k, v, g, output, + o.stride(0), o.stride(1), + r.stride(0), r.stride(1), r.stride(2), + v.stride(0), v.stride(1), v.stride(2), + r_k.stride(0), + T_SIZE, T_OFFSET, + num_heads, head_dim, BLOCK_SIZE_D=triton.next_power_of_2(head_dim), + ) + return output + + @staticmethod + @autocast_custom_bwd + @input_guard + def backward(ctx, grad_output): + o, r, k, r_k, v, g = ctx.saved_tensors + return gate_output_correction_backward_triton(grad_output, o, r, k, r_k, v, g) + + +gate_output_correction = GateOutputCorrection.apply diff --git a/code/flash-linear-attention/fla/ops/simple_gla/README.md b/code/flash-linear-attention/fla/ops/simple_gla/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c359ced5ed1304fdb6bf3edb76cc37470064abf0 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/simple_gla/README.md @@ -0,0 +1,10 @@ +# Simple GLA + +Gating mechanism in [Gated RFA](https://arxiv.org/abs/2103.02143), [Mamba2](https://arxiv.org/abs/2405.21060) and [YOCO](https://arxiv.org/abs/2405.05254) (a.k.a., Gated RetNet). + +Compared to GLA, the gating is head-wise instead of elementwise. +As a result, we can adapt the RetNet kernel for training using matmul w/o numerical instability. +It is faster than GLA but has less expressive power. +I will use it as a baseline for the GLA. + +$S_{t+1} = g_{t+1} \odot S_{t} + K_{t+1} V_{t+1}^{\top}$ where $g$ is a scalar. diff --git a/code/flash-linear-attention/fla/ops/simple_gla/__init__.py b/code/flash-linear-attention/fla/ops/simple_gla/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a0e62619139ef18b7ef030354d5347c4b4062a37 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/simple_gla/__init__.py @@ -0,0 +1,12 @@ + +from .chunk import chunk_simple_gla +from .fused_chunk import fused_chunk_simple_gla +from .fused_recurrent import fused_recurrent_simple_gla +from .parallel import parallel_simple_gla + +__all__ = [ + 'chunk_simple_gla', + 'fused_chunk_simple_gla', + 'fused_recurrent_simple_gla', + 'parallel_simple_gla', +] diff --git a/code/flash-linear-attention/fla/ops/simple_gla/chunk.py b/code/flash-linear-attention/fla/ops/simple_gla/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..a9f050befa2b2f9d00e204ada4b412d7529f04ee --- /dev/null +++ b/code/flash-linear-attention/fla/ops/simple_gla/chunk.py @@ -0,0 +1,301 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton + +from fla.ops.common.chunk_h import chunk_bwd_dh, chunk_fwd_h +from fla.ops.common.chunk_o import chunk_bwd_dqkwg, chunk_bwd_dv, chunk_fwd_o +from fla.ops.utils import chunk_local_cumsum +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +def chunk_simple_gla_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + h, ht = chunk_fwd_h( + k=k, + v=v, + g=g, + g_gamma=g_gamma, + gk=None, + gv=None, + h0=initial_state, + output_final_state=output_final_state, + states_in_fp32=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + o = chunk_fwd_o( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + h=h, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return o, ht + + +def chunk_simple_gla_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + g_gamma: torch.Tensor, + initial_state: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + # (SY 09/22) states_in_fp32 seems not affecting the error of dg but for safety, set to True + h, _ = chunk_fwd_h( + k=k, + v=v, + g=g, + g_gamma=g_gamma, + gk=None, + gv=None, + h0=initial_state, + output_final_state=False, + states_in_fp32=True, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dh, dh0 = chunk_bwd_dh( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + gk=None, + gv=None, + do=do, + h0=initial_state, + dht=dht, + scale=scale, + states_in_fp32=True, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dq, dk, _, dg = chunk_bwd_dqkwg( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + h=h, + do=do, + dh=dh, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + dv = chunk_bwd_dv( + q=q, + k=k, + g=g, + g_gamma=g_gamma, + do=do, + dh=dh, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + return dq, dk, dv, dg, dh0 + + +class ChunkSimpleGLAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q, + k, + v, + g, + g_gamma, + scale, + initial_state, + output_final_state, + cu_seqlens, + ): + T = q.shape[1] + chunk_size = min(64, max(16, triton.next_power_of_2(T))) + + g = chunk_local_cumsum(g, chunk_size=chunk_size, cu_seqlens=cu_seqlens) if g is not None else None + o, ht = chunk_simple_gla_fwd( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + ctx.save_for_backward(q, k, v, g, g_gamma, initial_state) + ctx.chunk_size = chunk_size + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + return o.to(q.dtype), ht + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht): + chunk_size, scale, cu_seqlens = ctx.chunk_size, ctx.scale, ctx.cu_seqlens + q, k, v, g, g_gamma, initial_state = ctx.saved_tensors + dq, dk, dv, dg, dh0 = chunk_simple_gla_bwd( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + initial_state=initial_state, + do=do, + dht=dht, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + ) + if g is not None: + dg = chunk_local_cumsum(dg, chunk_size=chunk_size, reverse=True, cu_seqlens=cu_seqlens).to(g) + else: + dg = None + return dq.to(q), dk.to(k), dv.to(v), dg, None, None, dh0, None, None + + +@torch.compiler.disable +def chunk_simple_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H]`. + Compared to GLA, the gating is head-wise instead of elementwise. + g_gamma (torch.Tensor): + Log decay of shape `[H]`. + Head-wise data-independent decay is used if `g_gamma` is provided. + Only one of `g` or `g_gamma` should be provided. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.simple_gla import chunk_simple_gla + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, device='cuda')) + >>> o, ht = chunk_simple_gla( + q, k, v, g, + initial_state=None, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = chunk_simple_gla( + q, k, v, g, + initial_state=None, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = ChunkSimpleGLAFunction.apply( + q, + k, + v, + g, + g_gamma, + scale, + initial_state, + output_final_state, + cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/simple_gla/fused_chunk.py b/code/flash-linear-attention/fla/ops/simple_gla/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..21d8f9c10ce83ffa1713e0610c91e5c7659c3cc3 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/simple_gla/fused_chunk.py @@ -0,0 +1,106 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.common.fused_chunk import fused_chunk + + +def fused_chunk_simple_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor = None, + g_gamma: torch.Tensor = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H]`. + Compared to GLA, the gating is head-wise instead of elementwise. + g_gamma (torch.Tensor): + Log decay of shape `[H]`. + Head-wise data-independent decay is used if `g_gamma` is provided. + Only one of `g` or `g_gamma` should be provided. + scale (Optional[int]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.simple_gla import fused_chunk_simple_gla + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = fused_chunk_simple_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = fused_chunk_simple_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = fused_chunk( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/simple_gla/fused_recurrent.py b/code/flash-linear-attention/fla/ops/simple_gla/fused_recurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..ebdd8807d432ccfb4e0e5d55d5af7f25b13453c3 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/simple_gla/fused_recurrent.py @@ -0,0 +1,110 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch + +from fla.ops.common.fused_recurrent import fused_recurrent + + +def fused_recurrent_simple_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor = None, + g_gamma: torch.Tensor = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + reverse: bool = False, + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H]`. + Compared to GLA, the gating is head-wise instead of elementwise. + g_gamma (torch.Tensor): + Log decay of shape `[H]`. + Head-wise data-independent decay is used if `g_gamma` is provided. + Only one of `g` or `g_gamma` should be provided. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. + reverse (Optional[bool]): + If `True`, process the state passing in reverse order. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.simple_gla import fused_recurrent_simple_gla + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, K, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = fused_recurrent_simple_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = fused_recurrent_simple_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = fused_recurrent( + q=q, + k=k, + v=v, + g=g, + g_gamma=g_gamma, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + reverse=reverse, + cu_seqlens=cu_seqlens, + ) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/simple_gla/naive.py b/code/flash-linear-attention/fla/ops/simple_gla/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..ab452f590e2d6bf443dfe2dba479e037b0863208 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/simple_gla/naive.py @@ -0,0 +1,112 @@ + + +import torch +import torch.nn.functional as F +from einops import rearrange + + +def naive_chunk_simple_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + chunk_size: int = 64, + scale: float | None = None, +): + q, k, v, g = map(lambda x: rearrange(x, 'b t h ... -> b h t ...').to(torch.float32), [q, k, v, g]) + if scale is None: + scale = 1.0 / q.shape[-1] ** 0.5 + + T = q.shape[-2] + BT = chunk_size + pad_len = (BT - (T % BT)) % BT + if pad_len > 0: + # Pad all tensors + q = F.pad(q, (0, 0, 0, pad_len)) + k = F.pad(k, (0, 0, 0, pad_len)) + v = F.pad(v, (0, 0, 0, pad_len)) + g = F.pad(g, (0, pad_len)) + decay = g + B, H, T1, K = q.shape + V = v.shape[-1] + q = q * scale + q, k, v, decay = map(lambda x: rearrange(x, 'b h (n c) d -> b h n c d', c=chunk_size), [q, k, v, decay.unsqueeze(-1)]) + decay = decay.squeeze(-1).cumsum(-1) + L_mask = ((decay.unsqueeze(-1) - decay.unsqueeze(-2)).tril().exp().float()).tril() + S = k.new_zeros(B, H, K, V) + if initial_state is not None: + S = initial_state + o = torch.zeros_like(v) + for i in range(0, T1 // chunk_size): + q_i, k_i, v_i = q[:, :, i], k[:, :, i], v[:, :, i] + attn = (q_i @ k_i.transpose(-1, -2) * L_mask[:, :, i]) + o_inter = (q_i * decay[:, :, i, :, None].exp()) @ S + o[:, :, i] = o_inter + attn @ v_i + S = S * decay[:, :, i, -1, None, None].exp() + \ + (k_i * (decay[:, :, i, -1, None] - decay[:, :, i]).exp()[..., None]).transpose(-1, -2) @ v_i + if not output_final_state: + S = None + # unpad + o = rearrange(o, 'b h n c d -> b (n c) h d')[:, :T] + return o, S + + +def naive_recurrent_simple_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = True, +): + dtype = q.dtype + q, k, v, g = map(lambda x: x.transpose(1, 2).float(), (q, k, v, g)) + B, H, T, K = q.shape + V = v.shape[-1] + if scale is None: + scale = K ** -0.5 + q = q * scale + o = v.new_zeros(B, H, T, V) + + S = q.new_zeros(B, H, K, V) + if initial_state is not None: + S += initial_state + + for i in range(T): + gate = g[:, :, i].exp() + key = k[:, :, i] + value = v[:, :, i] + kv = key.unsqueeze(-1) * value.unsqueeze(-2) + S = S * gate.unsqueeze(-1).unsqueeze(-1) + kv + q_i = q[:, :, i, :] + o_i = (q_i.unsqueeze(-1) * S).sum(-2) + o[:, :, i] = o_i + if not output_final_state: + S = None + return o.transpose(1, 2).to(dtype), S + + +def naive_parallel_simple_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: float | None = None, +): + q, k, v, g = map(lambda x: rearrange(x, 'b t h ... -> b h t ...').to(torch.float32), [q, k, v, g]) + if scale is None: + scale = 1.0 / q.shape[-1] ** 0.5 + dtype = q.dtype + A = (q @ k.transpose(-1, -2) * scale) + if g is not None: + g = g.cumsum(-1) + D = (g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().tril() + A = A * D + else: + A = A.tril() + o = A @ v + o = o.transpose(1, 2) + return o.to(dtype), A diff --git a/code/flash-linear-attention/fla/ops/simple_gla/parallel.py b/code/flash-linear-attention/fla/ops/simple_gla/parallel.py new file mode 100644 index 0000000000000000000000000000000000000000..cf1fff825f65182a7135fbd16c78ea2c105c1ab2 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/simple_gla/parallel.py @@ -0,0 +1,723 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.cumsum import chunk_global_cumsum, chunk_local_cumsum +from fla.ops.utils.op import exp +from fla.utils import ( + autocast_custom_bwd, + autocast_custom_fwd, + autotune_cache_kwargs, + check_shared_mem, + input_guard, + is_intel_alchemist, + is_nvidia_hopper, +) + +# https://github.com/intel/intel-xpu-backend-for-triton/issues/3449 +triton_config = {'grf_mode': 'large'} if is_intel_alchemist else {} +NUM_WARPS = [2, 4, 8] if is_nvidia_hopper else [2, 4, 8, 16] + + +@triton.heuristics({ + 'NV': lambda args: triton.cdiv(args['V'], args['BV']), + 'OUTPUT_ATTENTIONS': lambda args: args['attn'] is not None, + 'USE_G': lambda args: args['g'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8, 16] + for num_stages in [2, 3, 4] + ], + key=["BT", "BS", "BK", "BV", "USE_G"], + **autotune_cache_kwargs, +) +@triton.jit +def parallel_simple_gla_fwd_kernel( + q, + k, + v, + g, + o, + attn, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NV: tl.constexpr, + OUTPUT_ATTENTIONS: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_G: tl.constexpr, +): + i_kv, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_k, i_v = i_kv // NV, i_kv % NV + i_b, i_h = i_bh // H, i_bh % H + + all = B * T + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + o += ((i_k * all + bos) * H + i_h) * V + if USE_G: + g += bos * H + i_h + if OUTPUT_ATTENTIONS: + attn += i_k * B * H * T * T + (bos * H + i_h * T) * T + + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + + # the Q block is kept in the shared memory throughout the whole kernel + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + b_o = tl.zeros([BT, BV], dtype=tl.float32) + + # [BT] + o_q = i_t * BT + tl.arange(0, BT) + m_q = o_q < T + # Q block and K block have overlap. + # masks required + if USE_G: + # [BT,] + b_gq = tl.load(g + o_q * H, mask=m_q, other=float('-inf')).to(tl.float32) + # rescale interchunk output + else: + b_gq = None + + for i_s in range(i_t * BT, min((i_t + 1) * BT, T), BS): + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (i_k * BK, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BS] + m_s = (o_q[:, None] >= o_k[None, :]) & (m_q[:, None] & m_k[None, :]) + b_s = tl.dot(b_q, b_k) + if USE_G: + b_gk = tl.load(g + o_k * H, mask=m_k, other=0) + b_s *= exp(b_gq[:, None] - b_gk[None, :]) + b_s = tl.where(m_s, b_s, 0) + # [BT, BV] + if i_s >= 0: + b_o += tl.dot(b_s.to(b_q.dtype), b_v) + if OUTPUT_ATTENTIONS: + p_a = tl.make_block_ptr(attn, (T, T), (T, 1), (i_t * BT, i_s), (BT, BS), (1, 0)) + tl.store(p_a, b_s.to(p_a.dtype.element_ty), boundary_check=(0, 1)) + for i_s in range(i_t * BT - BS, -BS, -BS): + p_k = tl.make_block_ptr(k, (K, T), (1, H*K), (i_k * BK, i_s), (BK, BS), (0, 1)) + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + # [BK, BS] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BS, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BS] + m_s = m_q[:, None] & m_k[None, :] + b_s = tl.dot(b_q, b_k) + if USE_G: + b_g = tl.load(g + o_k * H, mask=m_k, other=0) + b_gn = tl.load(g + (min(i_s + BS, T) - 1) * H) + b_gp = tl.load(g + (i_s-1) * H) if i_s % BT > 0 else 0. + # No concrete meaning. Just to avoid some layout bugs. + b_s *= exp(b_gq[:, None] + (b_gn - b_g)[None, :]) + b_gq += b_gn - b_gp + b_s = tl.where(m_s, b_s, 0) + if OUTPUT_ATTENTIONS: + p_a = tl.make_block_ptr(attn, (T, T), (T, 1), (i_t * BT, i_s), (BT, BS), (1, 0)) + tl.store(p_a, b_s.to(p_a.dtype.element_ty), boundary_check=(0, 1)) + if i_s >= 0: + b_o += tl.dot(b_s.to(b_v.dtype), b_v) + p_o = tl.make_block_ptr(o, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.jit(do_not_specialize=['T']) +def parallel_simple_gla_bwd_kernel_dq( + i_t, + i_k, + i_v, + q, + k, + v, + g, + do, + dq, + dg, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, +): + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BT, BK] + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + + # [BT] + o_q = i_t * BT + tl.arange(0, BT) + m_q = o_q < T + for i_s in range(0, i_t * BT, BS): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_s, i_k * BK), (BS, BK), (1, 0)) + p_v = tl.make_block_ptr(v, (V, T), (1, H*V), (i_v * BV, i_s), (BV, BS), (0, 1)) + + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + # [BS, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BV] @ [BV, BS] = [BT, BS] + b_ds = tl.dot(b_do, b_v) + if USE_G: + b_g = tl.load(g + o_k * H, mask=m_k, other=0) + b_gn = tl.load(g + (min(i_s + BS, T) - 1) * H) + b_gp = tl.load(g + (i_s - 1) * H) if i_s % BT > 0 else 0. + b_ds *= tl.where(m_k, exp(b_gn - b_g), 0)[None, :] + if i_s > 0: + b_dq *= exp(b_gn - b_gp) + # [BT, BS] @ [BS, BK] = [BT, BK] + b_dq += tl.dot(b_ds.to(b_v.dtype), b_k) + + if USE_G: + # [BT,] + b_gq = tl.load(g + o_q * H, mask=m_q, other=float('-inf')) + # [BT, BK] + b_dq *= exp(b_gq)[:, None] + + # Q block and K block have overlap. masks required + for i_s in range(i_t * BT, min((i_t + 1) * BT, T), BS): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_s, i_k * BK), (BS, BK), (1, 0)) + p_v = tl.make_block_ptr(v, (V, T), (1, H*V), (i_v * BV, i_s), (BV, BS), (0, 1)) + + o_k = i_s + tl.arange(0, BS) + m_k = o_k < T + # [BS, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BV, BS] + b_v = tl.load(p_v, boundary_check=(0, 1)) + # [BT, BV] @ [BV, BS] = [BT, BS] + b_ds = tl.dot(b_do, b_v) + if USE_G: + b_gk = tl.load(g + o_k * H, mask=m_k, other=0) + b_ds *= exp(b_gq[:, None] - b_gk[None, :]) + m_s = (o_q[:, None] >= o_k[None, :]) & (m_q[:, None] & m_k[None, :]) + b_ds = tl.where(m_s, b_ds, 0) + # [BT, BK] + b_dq += tl.dot(b_ds.to(b_k.dtype), b_k) + + b_dq *= scale + p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + if USE_G: + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_dg = tl.sum(b_dq * b_q, 1) + p_dg = tl.make_block_ptr(dg, (T,), (H,), (i_t * BT,), (BT,), (0,)) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,)) + + +@triton.jit(do_not_specialize=['T']) +def parallel_simple_gla_bwd_kernel_dkv( + i_t, + i_k, + i_v, + q, + k, + v, + g, + do, + dk, + dv, + dg, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, +): + o_k = i_t * BT + tl.arange(0, BT) + m_k = o_k < T + # [BT, BK] + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + # [BT, BV] + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dv = tl.zeros([BT, BV], dtype=tl.float32) + if USE_G: + b_gk = tl.load(g + o_k * H, mask=m_k, other=0) + NTS = tl.cdiv(T, BS) + # [BT, BK] + for i_s in range(NTS * BS - BS, (i_t + 1) * BT - BS, -BS): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_s, i_k * BK), (BS, BK), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + + o_q = i_s + tl.arange(0, BS) + m_q = o_q < T + # [BS, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BS, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BT, BS] + b_ds = tl.dot(b_v, tl.trans(b_do)) + b_s = tl.dot(b_k, tl.trans(b_q)) + if USE_G: + b_gq = tl.load(g + o_q * H, mask=m_q, other=float('-inf')) + b_gp = tl.load(g + (min(i_s + BS, T) - 1) * H) + b_gn = tl.load(g + (i_s - 1) * H) if i_s % BT > 0 else 0. + if i_s >= 0: + b_gpn = exp(b_gp - b_gn) + b_dk *= b_gpn + b_dv *= b_gpn + b_gqn = exp(b_gq - b_gn) + b_ds *= b_gqn[None, :] + b_s *= b_gqn[None, :] + # [BT, BK] + b_dk += tl.dot(b_ds.to(b_q.dtype), b_q) + # [BT, BV] + b_dv += tl.dot(b_s.to(b_do.dtype), b_do) + + if USE_G: + b_gn = tl.load(g + (min(i_t * BT + BT, T) - 1) * H) + if i_t >= 0: + b_gpn = exp(b_gn - b_gk)[:, None] + b_dk *= b_gpn + b_dv *= b_gpn + + for i_s in range(i_t * BT, min((i_t + 1) * BT, T), BS): + p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_s, i_k * BK), (BS, BK), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_s, i_v * BV), (BS, BV), (1, 0)) + + o_q = i_s + tl.arange(0, BS) + m_q = o_q < T + # [BS, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + # [BS, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BS] + b_s = tl.dot(b_k, tl.trans(b_q)) + b_ds = tl.dot(b_v, tl.trans(b_do)) + if USE_G: + b_gq = tl.load(g + o_q * H, mask=m_q, other=float('-inf')) + if i_s >= 0: + b_gkq = exp(-b_gk[:, None] + b_gq[None, :]) + b_ds *= b_gkq + b_s *= b_gkq + m_s = o_k[:, None] <= o_q[None, :] + b_s = tl.where(m_s, b_s, 0) + b_ds = tl.where(m_s, b_ds, 0) + # [BT, BK] + b_dk += tl.dot(b_ds.to(b_q.dtype), b_q) + b_dv += tl.dot(b_s.to(b_do.dtype), b_do) + b_dk *= scale + b_dv *= scale + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + if USE_G: + b_dg = tl.load(dg + o_k * H, mask=m_k, other=0) + b_dg -= tl.sum(b_dk * b_k, 1) + tl.store(dg + o_k * H, b_dg.to(dg.dtype.element_ty), mask=m_k) + + +@triton.heuristics({ + 'NV': lambda args: triton.cdiv(args['V'], args['BV']), + 'USE_G': lambda args: args['g'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config(triton_config, num_warps=num_warps) + for num_warps in NUM_WARPS + ], + key=['BT', 'BS', 'BK', 'BV', 'USE_G'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def parallel_simple_gla_bwd_kernel( + q, + k, + v, + g, + do, + dq, + dk, + dv, + dg, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NV: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_G: tl.constexpr, +): + i_kv, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_k, i_v = i_kv // NV, i_kv % NV + i_b, i_h = i_bh // H, i_bh % H + dq += i_v * B * H * T * K + dk += i_v * B * H * T * K + dv += i_k * B * H * T * V + if USE_G: + dg += i_kv * B * H * T + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + do += (bos * H + i_h) * V + dq += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + dv += (bos * H + i_h) * V + if USE_G: + g += bos * H + i_h + dg += bos * H + i_h + + parallel_simple_gla_bwd_kernel_dq( + i_t=i_t, + i_k=i_k, + i_v=i_v, + q=q, + k=k, + v=v, + g=g, + do=do, + dq=dq, + dg=dg, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + USE_G=USE_G, + ) + tl.debug_barrier() + parallel_simple_gla_bwd_kernel_dkv( + i_t=i_t, + i_k=i_k, + i_v=i_v, + q=q, + k=k, + v=v, + g=g, + do=do, + dk=dk, + dv=dv, + dg=dg, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + USE_G=USE_G, + ) + + +def parallel_simple_gla_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: float, + output_attentions: bool = False, + chunk_size: int = 128, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT, BS = chunk_size, 32 + if check_shared_mem('hopper', k.device.index): + BK = min(256, triton.next_power_of_2(K)) + BV = min(256, triton.next_power_of_2(V)) + elif check_shared_mem('ampere', k.device.index): + BK = min(128, triton.next_power_of_2(K)) + BV = min(128, triton.next_power_of_2(V)) + else: + BK = min(64, triton.next_power_of_2(K)) + BV = min(64, triton.next_power_of_2(V)) + + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert BT % BS == 0 + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + # local cumulative decay in log space + if g is not None: + g = chunk_local_cumsum(g, chunk_size, cu_seqlens=cu_seqlens) + grid = (NK * NV, NT, B * H) + o = torch.empty(NK, *v.shape, dtype=v.dtype if NK == 1 else torch.float, device=q.device) + attn = q.new_zeros(NK, B, H, T, T) if output_attentions else None + + parallel_simple_gla_fwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + o=o, + attn=attn, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + H=H, + T=T, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + ) + o = o.sum(0) + + if output_attentions: + attn = attn.sum(0) + return o, g, attn + + +def parallel_simple_gla_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + do: torch.Tensor, + scale: float, + chunk_size: int = 128, + cu_seqlens: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT, BS = chunk_size, 32 + if check_shared_mem('hopper', k.device.index): + BK = min(256, triton.next_power_of_2(K)) + BV = min(256, triton.next_power_of_2(V)) + elif check_shared_mem('ampere', k.device.index): + BK = min(128, triton.next_power_of_2(K)) + BV = min(128, triton.next_power_of_2(V)) + elif check_shared_mem('ada', k.device.index): + BK = min(64, triton.next_power_of_2(K)) + BV = min(64, triton.next_power_of_2(V)) + else: + BK = min(32, triton.next_power_of_2(K)) + BV = min(32, triton.next_power_of_2(V)) + + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert BT % BS == 0 + + dq = torch.empty(NV, * q.shape, dtype=q.dtype if NV == 1 else torch.float, device=q.device) + dk = torch.empty(NV, * k.shape, dtype=k.dtype if NV == 1 else torch.float, device=q.device) + dv = torch.empty(NK, * v.shape, dtype=v.dtype if NK == 1 else torch.float, device=q.device) + dg = torch.empty(NK*NV, *g.shape, dtype=torch.float, device=q.device) if g is not None else None + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + grid = (NK * NV, NT, B * H) + parallel_simple_gla_bwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + do=do, + dq=dq, + dk=dk, + dv=dv, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + BK=BK, + BV=BV, + ) + dq = dq.sum(0) + dk = dk.sum(0) + dv = dv.sum(0) + dg = chunk_global_cumsum(dg.sum(0), reverse=True, cu_seqlens=cu_seqlens) if g is not None else None + return dq, dk, dv, dg + + +class ParallelSimpleGLAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, g, scale, output_attentions, cu_seqlens): + chunk_size = 128 + ctx.dtype = q.dtype + + o, g, attn = parallel_simple_gla_fwd( + q=q, + k=k, + v=v, + g=g, + scale=scale, + output_attentions=output_attentions, + chunk_size=chunk_size, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, k, v, g, cu_seqlens) + ctx.scale = scale + ctx.chunk_size = chunk_size + return o.to(q.dtype), attn + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, da=None): + q, k, v, g, cu_seqlens = ctx.saved_tensors + dq, dk, dv, dg = parallel_simple_gla_bwd( + q=q, + k=k, + v=v, + g=g, + do=do, + scale=ctx.scale, + chunk_size=ctx.chunk_size, + cu_seqlens=cu_seqlens, + ) + return dq.to(q), dk.to(k), dv.to(v), dg.to(ctx.dtype) if dg is not None else None, None, None, None + + +def parallel_simple_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + scale: float | None = None, + output_attentions: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H]`. + Compared to GLA, the gating is head-wise instead of elementwise. + scale (Optional[float]): + Scale factor for attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + output_attentions (bool): + Whether to output the materialized attention scores of shape [B, H, T, T]. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + attn (torch.Tensor): + Attention scores of shape `[B, H, T, T]` if `output_attentions=True` else `None` + """ + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if output_attentions: + assert cu_seqlens is None, "output_attentions=True is not supported with variable-length sequences" + + if scale is None: + scale = k.shape[-1] ** -0.5 + o, attn = ParallelSimpleGLAFunction.apply( + q, + k, + v, + g, + scale, + output_attentions, + cu_seqlens, + ) + return o, attn diff --git a/code/flash-linear-attention/fla/ops/sse/__init__.py b/code/flash-linear-attention/fla/ops/sse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4bdf12d163aaa2dc83b177eacad087b778a72c94 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/sse/__init__.py @@ -0,0 +1,7 @@ +from .index import prepare_sample_relpos_global_index_flat +from .mask import softmax_and_mask + +__all__ = [ + "prepare_sample_relpos_global_index_flat", + "softmax_and_mask", +] diff --git a/code/flash-linear-attention/fla/ops/sse/index.py b/code/flash-linear-attention/fla/ops/sse/index.py new file mode 100644 index 0000000000000000000000000000000000000000..9a8cdb68b9dd0ca6969789590742cf7caf13e211 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/sse/index.py @@ -0,0 +1,29 @@ +import torch +from typing import Tuple + +from fla.utils import tensor_cache + + +@tensor_cache +def prepare_sample_relpos_global_index( + offsets: torch.Tensor +) -> Tuple[torch.LongTensor, torch.LongTensor, torch.LongTensor]: + lengths = offsets[1:] - offsets[:-1] + S = lengths.numel() + sample_idx_per_token = torch.repeat_interleave(torch.arange(S, device=offsets.device), lengths) # [L] + token_global_idx = torch.arange(offsets[-1], device=offsets.device) # [L] + token_start_idx = offsets[:-1].index_select(0, sample_idx_per_token) # [L] + relpos_in_sample = token_global_idx - token_start_idx + return sample_idx_per_token, relpos_in_sample, token_global_idx, lengths + + +@tensor_cache +def prepare_sample_relpos_global_index_flat( + offsets: torch.Tensor, + K: int +) -> Tuple[torch.LongTensor, torch.LongTensor, torch.LongTensor]: + sample_idx_per_token, relpos_in_sample, token_global_idx, lengths = prepare_sample_relpos_global_index(offsets) + sample_idx_flat = sample_idx_per_token[:, None].expand(-1, K).reshape(-1) # [L*K] + relpos_flat = relpos_in_sample[:, None].expand(-1, K).reshape(-1) # [L*K] + global_idx_flat = token_global_idx[:, None].expand(-1, K).reshape(-1) # [L*K] + return sample_idx_flat, relpos_flat, global_idx_flat, lengths diff --git a/code/flash-linear-attention/fla/ops/sse/mask.py b/code/flash-linear-attention/fla/ops/sse/mask.py new file mode 100644 index 0000000000000000000000000000000000000000..2f38dd00adb8ec6296cc09b2c9eb9323e5d7d5f1 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/sse/mask.py @@ -0,0 +1,352 @@ +import torch +import triton +import triton.language as tl +import torch.nn.functional as F + +from fla.utils import input_guard +from fla.ops.utils.softmax import softmax_bwd + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3] + ], + key=['BN'], +) +@triton.jit +def _fused_softmax_topk_fwd_kernel( + e, + e_o, + mw, + mr, + stride_e_b, + stride_e_l, + B, + T, + N, + NUM_WRITER: tl.constexpr, + NUM_READER: tl.constexpr, + BN: tl.constexpr, +): + i_b, i_t = tl.program_id(0), tl.program_id(1) + + offsets_n = tl.arange(0, BN) + mask_n = offsets_n < N + p_e = e + i_b * stride_e_b + i_t * stride_e_l + offsets_n + p_e_o = e_o + i_b * stride_e_b + i_t * stride_e_l + offsets_n + p_mw = mw + i_b * stride_e_b + i_t * stride_e_l + offsets_n + p_mr = mr + i_b * stride_e_b + i_t * stride_e_l + offsets_n + + ### stable softmax and topk ### + b_e = tl.load(p_e, mask=mask_n, other=-float('inf')).to(tl.float32) + b_m = tl.max(b_e, axis=0) + b_e = tl.exp(b_e - b_m) + b_p = b_e / tl.sum(b_e, axis=0) + b_p = tl.where(mask_n, b_p.to(p_e.dtype.element_ty), -float('inf')) + b_ps = tl.sort(b_p, descending=True) + tl.store(p_e_o, b_p.to(p_e_o.dtype.element_ty), mask=mask_n) + + mask_w = tl.full((BN,), 1, dtype=b_p.dtype) + if NUM_WRITER < N: + threshold_w = tl.sum(b_ps * (offsets_n == NUM_WRITER - 1)) + mask_w_gr = b_p > threshold_w + need = NUM_WRITER - tl.sum(mask_w_gr.to(tl.int32)) + mask_w_eq = b_p == threshold_w + mask_w_eq_need = mask_w_eq & (tl.cumsum(mask_w_eq.to(tl.int32), axis=0) <= need) + mask_w = mask_w_gr | mask_w_eq_need + mask_w = mask_w.to(b_p.dtype) + tl.store(p_mw, mask_w.to(p_mw.dtype.element_ty), mask=mask_n) + + mask_r = tl.full((BN,), 1, dtype=b_p.dtype) + if NUM_READER < N: + threshold_r = tl.sum(b_ps * (offsets_n == NUM_READER - 1)) + mask_r_gr = b_p > threshold_r + need = NUM_READER - tl.sum(mask_r_gr.to(tl.int32)) + mask_r_eq = b_p == threshold_r + mask_r_eq_need = mask_r_eq & (tl.cumsum(mask_r_eq.to(tl.int32), axis=0) <= need) + mask_r = mask_r_gr | mask_r_eq_need + mask_r = mask_r.to(b_p.dtype) + tl.store(p_mr, mask_r.to(p_mr.dtype.element_ty), mask=mask_n) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3] + ], + key=['BN', 'BK', 'BV'], +) +@triton.jit +def _fused_mask_fwd_kernel( + q, k, v, g, e, mw, mr, + q_o, k_o, v_o, g_o, + stride_k_b, stride_k_l, stride_k_h, + stride_v_b, stride_v_l, stride_v_h, + stride_e_b, stride_e_l, + B, T, N, H, K, V, + BN: tl.constexpr, BK: tl.constexpr, BV: tl.constexpr, +): + i_b, i_t, i_h = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + offsets_n = tl.arange(0, BN) + offsets_k = tl.arange(0, BK) + offsets_v = tl.arange(0, BV) + mask_n = offsets_n < N + mask_k = offsets_k < K + mask_v = offsets_v < V + + p_e = e + i_b * stride_e_b + i_t * stride_e_l + offsets_n + p_mw = mw + i_b * stride_e_b + i_t * stride_e_l + offsets_n + p_mr = mr + i_b * stride_e_b + i_t * stride_e_l + offsets_n + + p_q = q + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_k = k + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_g = g + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_v = v + i_b * stride_v_b + i_t * stride_v_l + i_h * stride_v_h + offsets_v + p_q_o = q_o + i_b * stride_k_b * N + i_t * stride_k_l * N + i_h * stride_k_h \ + + offsets_n[:, None] * H * K + offsets_k[None, :] + p_k_o = k_o + i_b * stride_k_b * N + i_t * stride_k_l * N + i_h * stride_k_h \ + + offsets_n[:, None] * H * K + offsets_k[None, :] + p_g_o = g_o + i_b * stride_k_b * N + i_t * stride_k_l * N + i_h * stride_k_h \ + + offsets_n[:, None] * H * K + offsets_k[None, :] + p_v_o = v_o + i_b * stride_v_b * N + i_t * stride_v_l * N + i_h * stride_v_h \ + + offsets_n[:, None] * H * V + offsets_v[None, :] + + b_e = tl.load(p_e, mask=mask_n, other=0.) + mask_w = tl.load(p_mw, mask=mask_n, other=0.).to(b_e.dtype) + mask_r = tl.load(p_mr, mask=mask_n, other=0.).to(b_e.dtype) + b_e_topk_w = b_e * mask_w + b_e_topk_r = b_e * mask_r + + ### mask qkvg ### + b_q = tl.load(p_q, mask=mask_k, other=0.) + b_q = b_q[None, :] * b_e_topk_r[:, None] + + b_k = tl.load(p_k, mask=mask_k, other=0.) + b_k = b_k[None, :] * b_e_topk_w[:, None] + + b_g = tl.load(p_g, mask=mask_k, other=0.) + b_g = b_g[None, :] * mask_w[:, None] + + b_v = tl.load(p_v, mask=mask_v, other=0.) + b_v = b_v[None, :] * mask_w[:, None] + + mask_nk = mask_n[:, None] & mask_k[None, :] + mask_nv = mask_n[:, None] & mask_v[None, :] + tl.store(p_q_o, b_q.to(p_q_o.dtype.element_ty), mask=mask_nk) + tl.store(p_k_o, b_k.to(p_k_o.dtype.element_ty), mask=mask_nk) + tl.store(p_g_o, b_g.to(p_g_o.dtype.element_ty), mask=mask_nk) + tl.store(p_v_o, b_v.to(p_v_o.dtype.element_ty), mask=mask_nv) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3] + ], + key=['BN', 'BK', 'BV'], +) +@triton.jit +def _fused_mask_bwd_kernel( + q, k, e, mw, mr, + dq_o, dk_o, dv_o, dg_o, + dq, dk, dv, dg, de, + stride_k_b, stride_k_l, stride_k_h, + stride_v_b, stride_v_l, stride_v_h, + stride_e_b, stride_e_l, + B, T, N, H, K, V, + BN: tl.constexpr, BK: tl.constexpr, BV: tl.constexpr, +): + i_b, i_t, i_h = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + offsets_n = tl.arange(0, BN) + offsets_k = tl.arange(0, BK) + offsets_v = tl.arange(0, BV) + mask_n = offsets_n < N + mask_k = offsets_k < K + mask_v = offsets_v < V + + p_e = e + i_b * stride_e_b + i_t * stride_e_l + offsets_n + p_de = de + (i_b * stride_e_b + i_t * stride_e_l + offsets_n) * H + i_h + p_mw = mw + i_b * stride_e_b + i_t * stride_e_l + offsets_n + p_mr = mr + i_b * stride_e_b + i_t * stride_e_l + offsets_n + + p_q = q + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_k = k + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_dq = dq + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_dk = dk + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_dg = dg + i_b * stride_k_b + i_t * stride_k_l + i_h * stride_k_h + offsets_k + p_dv = dv + i_b * stride_v_b + i_t * stride_v_l + i_h * stride_v_h + offsets_v + p_dq_o = dq_o + i_b * stride_k_b * N + i_t * stride_k_l * N + i_h * stride_k_h \ + + offsets_n[:, None] * H * K + offsets_k[None, :] + p_dk_o = dk_o + i_b * stride_k_b * N + i_t * stride_k_l * N + i_h * stride_k_h \ + + offsets_n[:, None] * H * K + offsets_k[None, :] + p_dg_o = dg_o + i_b * stride_k_b * N + i_t * stride_k_l * N + i_h * stride_k_h \ + + offsets_n[:, None] * H * K + offsets_k[None, :] + p_dv_o = dv_o + i_b * stride_v_b * N + i_t * stride_v_l * N + i_h * stride_v_h \ + + offsets_n[:, None] * H * V + offsets_v[None, :] + + b_e = tl.load(p_e, mask=mask_n, other=0.) + mask_w = tl.load(p_mw, mask=mask_n, other=0.).to(b_e.dtype) + mask_r = tl.load(p_mr, mask=mask_n, other=0.).to(b_e.dtype) + b_e_topk_w = b_e * mask_w + b_e_topk_r = b_e * mask_r + + mask_nk = mask_n[:, None] & mask_k[None, :] + mask_nv = mask_n[:, None] & mask_v[None, :] + b_dq_o = tl.load(p_dq_o, mask=mask_nk, other=0.) + b_dk_o = tl.load(p_dk_o, mask=mask_nk, other=0.) + b_dg_o = tl.load(p_dg_o, mask=mask_nk, other=0.) + b_dv_o = tl.load(p_dv_o, mask=mask_nv, other=0.) + b_dq = tl.sum((b_dq_o * b_e_topk_r[:, None]).to(tl.float32), axis=0).to(b_dq_o.dtype) + b_dk = tl.sum((b_dk_o * b_e_topk_w[:, None]).to(tl.float32), axis=0).to(b_dk_o.dtype) + b_dg = tl.sum((b_dg_o * mask_w[:, None]).to(tl.float32), axis=0).to(b_dg_o.dtype) + b_dv = tl.sum((b_dv_o * mask_w[:, None]).to(tl.float32), axis=0).to(b_dv_o.dtype) + + b_q = tl.load(p_q, mask=mask_k, other=0.) + b_k = tl.load(p_k, mask=mask_k, other=0.) + b_de = b_dq_o * b_q[None, :] * mask_r[:, None] + b_dk_o * b_k[None, :] * mask_w[:, None] + b_de = tl.sum(b_de.to(tl.float32), axis=1).to(b_de.dtype) + + tl.store(p_de, b_de.to(p_de.dtype.element_ty), mask=mask_n) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), mask=mask_k) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=mask_k) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), mask=mask_k) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), mask=mask_v) + + +class SoftmaxAndMask(torch.autograd.Function): + r""" + Applies softmax to router weights, repeats and masks inputs, + scales queries and keys with the router weights, and generates reader/writer masks. + + Notation: + B: batch size + T: sequence length + H: number of attention heads + K: key/query head dimension + V: value head dimension + N: number of state partitions + + Args: + q (torch.Tensor): + Queries of shape `(B, T, H, K)`. + k (torch.Tensor): + Keys of shape `(B, T, H, K)`. + v (torch.Tensor): + Values of shape `(B, T, H, V)`. + g (torch.Tensor): + Gates of shape `(B, T, H, V)`. + e (torch.Tensor): + Router weights before softmax of shape `(B, T, N)`. + num_writer (int): + Number of state partitions to write. + num_reader (int): + Number of state partitions to read. + + Returns: + q_out (torch.Tensor): + Repeated and masked queries of shape `(B, T, N * H, K)`. + k_out (torch.Tensor): + Repeated and masked keys of shape `(B, T, N * H, K)`. + v_out (torch.Tensor): + Repeated and masked values of shape `(B, T, N * H, V)`. + g_out (torch.Tensor): + Repeated and masked gates of shape `(B, T, N * H, V)`. + e_out (torch.Tensor): + Router weights after softmax of shape `(B, T, N)`. + mask_w (torch.Tensor): + Writer mask of shape `(B, T, N)`. + mask_r (torch.Tensor): + Reader mask of shape `(B, T, N)`. + """ + + @staticmethod + @input_guard + def forward(ctx, q, k, v, g, e, num_writer, num_reader): + B, T, H, K, V, N = *k.shape, v.shape[-1], e.shape[-1] + BN = triton.next_power_of_2(N) + BK = triton.next_power_of_2(K) + BV = triton.next_power_of_2(V) + + q_out = q.new_empty(B, T, N * H, K) + k_out = k.new_empty(B, T, N * H, K) + v_out = v.new_empty(B, T, N * H, V) + g_out = g.new_empty(B, T, N * H, K) + e_out = torch.empty_like(e) + mask_w = torch.empty_like(e, dtype=torch.int32) + mask_r = torch.empty_like(e, dtype=torch.int32) + + _fused_softmax_topk_fwd_kernel[(B, T)]( + e, + e_out, + mask_w, + mask_r, + e.stride(0), + e.stride(1), + B, + T, + N, + NUM_WRITER=num_writer, + NUM_READER=num_reader, + BN=BN, + ) + + _fused_mask_fwd_kernel[(B, T, H)]( + q, k, v, g, e_out, mask_w, mask_r, + q_out, k_out, v_out, g_out, + k.stride(0), k.stride(1), k.stride(2), + v.stride(0), v.stride(1), v.stride(2), + e.stride(0), e.stride(1), + B, T, N, H, K, V, + BN=BN, + BK=BK, + BV=BV, + ) + + ctx.save_for_backward(q, k, v, g, e_out, mask_w, mask_r) + ctx.num_writer = num_writer + ctx.num_reader = num_reader + return q_out, k_out, v_out, g_out, e_out, mask_w, mask_r + + @staticmethod + @input_guard + def backward(ctx, dq_out, dk_out, dv_out, dg_out, de_out, dmask_w, dmask_r): + q, k, v, g, e_out, mask_w, mask_r = ctx.saved_tensors + + B, T, H, K, V, N = *k.shape, v.shape[-1], e_out.shape[-1] + BN = triton.next_power_of_2(N) + BK = triton.next_power_of_2(K) + BV = triton.next_power_of_2(V) + + dq = torch.empty_like(q) + dk = torch.empty_like(k) + dv = torch.empty_like(v) + dg = torch.empty_like(g) + de = g.new_empty(B, T, N, H) + + grid = (B, T, H) + + _fused_mask_bwd_kernel[grid]( + q, k, e_out, mask_w, mask_r, + dq_out, dk_out, dv_out, dg_out, + dq, dk, dv, dg, de, + k.stride(0), k.stride(1), k.stride(2), + v.stride(0), v.stride(1), v.stride(2), + e_out.stride(0), e_out.stride(1), + B, T, N, H, K, V, + BN=BN, + BK=BK, + BV=BV, + ) + + de = de.sum(dim=-1).add_(de_out) + de = softmax_bwd(e_out, de, dtype=de.dtype) + + return dq.to(q), dk.to(k), dv.to(v), dg.to(g), de.to(e_out), None, None + +softmax_and_mask = SoftmaxAndMask.apply diff --git a/code/flash-linear-attention/fla/ops/titans/__init__.py b/code/flash-linear-attention/fla/ops/titans/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dd3f45dee4123a33bed73f04edf170ec70a7bd71 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/titans/__init__.py @@ -0,0 +1,6 @@ + +from .naive import chunk_titans_linear + +__all__ = [ + 'chunk_titans_linear', +] diff --git a/code/flash-linear-attention/fla/ops/titans/log_impl.py b/code/flash-linear-attention/fla/ops/titans/log_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..49e6a90bb29058ae7f4924cc23891cd3abd4f4ee --- /dev/null +++ b/code/flash-linear-attention/fla/ops/titans/log_impl.py @@ -0,0 +1,153 @@ +import torch + + +def cal_n_log(log_theta, log_eta, seq_len): + """ + calculate n_{i,j} in log space + log(n_{i,j}) = log(θ_j) + sum_{k=j+1}^i log(η_k) + """ + # create log(n) + log_n = torch.zeros(*log_theta.shape, seq_len, dtype=log_eta.dtype).to( + log_eta.device, + ) # [batch_size, num_heads, seq_len, seq_len] + for i in range(seq_len): + for j in range(i + 1): + if i == j: + log_n[..., j, i] = log_theta[..., j] + else: + log_n[..., j, i] = log_theta[..., j] + torch.sum( + log_eta[..., j + 1: i + 1], dim=-1, + ) + + return log_n + + +def cal_f_log(log_beta, seq_len, log_m): + """ + cal_f_log(log_beta, seq_len, log_m) -> f + log(f_t) = log(sum_{i=1}^t exp(sum_{k=i+1}^t log(1-α_k) + sum_{k=1}^i log(η_k))) + """ + # create f + # f = torch.zeros_like(log_beta) + # for t in range(seq_len): + # for i in range(t + 1): + # f[..., t] += torch.exp(log_beta[..., t] - log_beta[..., i] + log_m[..., i]) + log_f = torch.zeros_like(log_beta) + for t in range(seq_len): + a_i = log_beta[..., t: t + 1] - log_beta[..., : t + 1] + log_m[..., : t + 1] + log_f[..., t] = torch.logsumexp(a_i, dim=-1) + f = torch.exp(log_f) + + # this version overflow and even slower + # t_indices = torch.arange(seq_len, device=log_beta.device) + # i_indices = torch.arange(seq_len, device=log_beta.device) + # + # mask = i_indices.unsqueeze(0) <= t_indices.unsqueeze(1) + # log_beta_t = log_beta.unsqueeze(-1) # [..., seq_len, 1] + # log_beta_i = log_beta.unsqueeze(-2) # [..., 1, seq_len] + # log_m_i = log_m.unsqueeze(-2) + # a_i = log_beta_t - log_beta_i + log_m_i + # masked_a_i = torch.where(mask, a_i, torch.tensor(-float('inf'), device=a_i.device, dtype=a_i.dtype)) + # log_f = torch.logsumexp(masked_a_i, dim=-1) # [..., seq_len] + # + # f = torch.exp(log_f) + return f + + +def cal_G_log(log_beta, log_n, seq_len): + """ + calculate G_{i,j} + log(G_{i,j}) = log(sum_{k=j}^i exp(log(β_i/β_k) + log(n_{k,j}))) + """ + # G = torch.zeros(*log_beta.shape[:-1], seq_len, seq_len, device = log_beta.device) + # # Fill in the lower triangular part + # for i in range(seq_len): # row + # for j in range(i + 1): # column + # # Sum from k=j to i + # for k in range(j, i + 1): + # G[..., i, j] += torch.exp(log_beta[..., i] - log_beta[..., k] + log_n[..., j, k]) + + log_G = torch.full( + (*log_beta.shape[:-1], seq_len, seq_len), float("-inf"), device=log_beta.device, + ) + # fill in the lower triangular part + for i in range(seq_len): # row + for j in range(i + 1): # column + terms = ( + log_beta[..., i: i + 1] + - log_beta[..., j: i + 1] + + log_n[..., j: j + 1, j: i + 1].squeeze(-2) + ) + # use logsumexp to avoid overflow + log_G[..., i, j] = torch.logsumexp(terms, dim=-1) + + G = torch.exp(log_G) + return G + + +def _combine_params_log(log_theta, log_alpha_complement, log_eta, seq_len): + """ + Update rule for Titans in log space + + Parameters: + - log_theta: log(θ) + - log_alpha_complement: log(1-α) + - log_eta: log(η) + - seq_len: sequence length + + Returns: + - log_beta, beta_T, log_f, f_T, log_g, log_G, m_T, n_T + """ + # calculate log(β_t) = sum_{k=1}^t log(1-α_k) + log_beta = torch.cumsum(log_alpha_complement, dim=-1) + + # get β_T + beta_T = torch.exp(log_beta[..., -1]) + + # calculate log(m_i) = sum_{k=1}^i log(η_k) + log_m = torch.cumsum(log_eta, dim=-1) + m_T = torch.exp(log_m[..., -1]) + + # cal log(n_{i,j}) + log_n = cal_n_log(log_theta, log_eta, seq_len) + n_T = torch.exp(log_n[..., -1]) + + # cal log(f_t) + f = cal_f_log(log_beta, seq_len, log_m) + f_T = f[..., -1] + + # cal log(G_{i,j}) + G = cal_G_log(log_beta, log_n, seq_len) + # get log(g_j) = log(G_{T,j}) + g = G[..., -1, :] + + return log_beta, beta_T, f, f_T, g, G, m_T, n_T + + +def combine_params_log(theta, alpha, eta, seq_len): + """ + log space Titians + + Parameters: + - theta: θ + - alpha: α + - eta: η + - seq_len: sequence length + + Returns: + - beta, beta_T, f, f_T, g, G, m_T, n_T + """ + # convert to log space + log_theta = torch.log(theta.squeeze(-1)) + log_alpha_complement = torch.log(1 - alpha.squeeze(-1)) + log_eta = torch.log(eta.squeeze(-1)) + + # combine params in log space + log_beta, beta_T, f, f_T, g, G, m_T, n_T = _combine_params_log( + log_theta, log_alpha_complement, log_eta, seq_len, + ) + + # convert back to normal space + beta = torch.exp(log_beta) + + return beta, beta_T, f, f_T, g, G, m_T, n_T diff --git a/code/flash-linear-attention/fla/ops/titans/naive.py b/code/flash-linear-attention/fla/ops/titans/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..39875d4ad680baf4c4a2cd5af22df875cf5a2e10 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/titans/naive.py @@ -0,0 +1,374 @@ + +import torch +import torch.nn.functional as F + +from fla.ops.titans.log_impl import combine_params_log + + +def cal_n(theta, eta, seq_len): + n = torch.zeros(*theta.shape, seq_len, dtype=theta.dtype).to( + theta.device, + ) # [batch_size, num_heads, seq_len, seq_len] + + # 1. deal with diagonal elements + indices = torch.arange(seq_len, device=theta.device) + n[..., indices, indices] = theta[..., indices] + + # 2. Create a cumulative product matrix + # First create a mask to mark the positions where eta needs to be multiplied + mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).to(theta.device) + # Convert mask to boolean type + mask = mask.bool() + # Expand eta to match the target shape + eta_expanded = eta.unsqueeze(-2).expand(*theta.shape[:-1], seq_len, seq_len) + # Create a matrix filled with 1s for cumulative product + cumulative = torch.ones_like(eta_expanded) + cumulative = torch.where(mask, eta_expanded, cumulative) + # Calculate the cumulative product + cumulative_prod = torch.cumprod(cumulative, dim=-1) + + # 3. Calculate non-diagonal elements + # Create an expanded version of theta + theta_expanded = theta.unsqueeze(-1).expand(*theta.shape[:-1], seq_len, seq_len) + # Create a mask to keep only the upper triangular part (excluding the diagonal) + upper_triangular = torch.triu(torch.ones_like(n), diagonal=1).bool() + # Combine theta and cumulative product + n = torch.where(upper_triangular, theta_expanded * cumulative_prod, n) + return n + + +def cal_f(beta, seq_len, m): + a = torch.tril(beta.to(torch.float32).unsqueeze(-1).expand(*beta.shape, seq_len), 0) + ratio = (m.to(torch.float32) / beta.to(torch.float32)).unsqueeze(-1) + f = torch.matmul(a, ratio).squeeze(-1) + return f.to(beta.dtype) + + +def cal_G(beta, n, seq_len): + i_indices = torch.arange(seq_len, device=beta.device) + j_indices = torch.arange(seq_len, device=beta.device) + k_indices = torch.arange(seq_len, device=beta.device) + beta_ratio = beta[..., :, None] / beta[..., None, :] # [..., i, k] + + # create mask + k_mask = (k_indices[None, None, :] >= j_indices[None, :, None]) & ( + k_indices[None, None, :] <= i_indices[:, None, None] + ) + + # use mask to filter out invalid values + masked_beta_ratio = beta_ratio[..., :, None, :] * k_mask # [..., i, j, k] + masked_n = n[..., None, :, :] * k_mask # [..., i, j, k] + # calculate G + G = torch.sum(masked_beta_ratio * masked_n, dim=-1) # [..., i, j] + return G + + +def combine_params(theta, alpha, eta, seq_len): + theta = theta.squeeze(-1) + eta = eta.squeeze(-1) + alpha = alpha.squeeze(-1) + beta = torch.cumprod(1 - alpha, dim=-1) # β_t = ∏(1 - α_t) in titans paper + beta_T = beta[..., -1] # β_T + # Calculate m_i = ∏(k=1 to i) η_k + m = torch.cumprod(eta, dim=-1) # [batch_size, num_heads, seq_len] + m_T = m[..., -1] # m_T + # Calculate n_{i,j} + # We need to calculate ∏(k=j+1 to i) η_k for each i,j pair + # # this may be optimized + # n = torch.zeros(*theta.shape, seq_len, dtype = theta.dtype).to( + # theta.device) # [batch_size, num_heads, seq_len, seq_len] + # for i in range(seq_len): + # for j in range(i + 1): + # if i == j: + # n[..., j, i] = theta[..., j] + # else: + # # Calculate product of eta from j+1 to i + # eta_product = torch.prod(eta[..., j + 1:i + 1], dim = -1) + # n[..., j, i] = theta[..., j] * eta_product + + n = cal_n(theta, eta, seq_len) + n_T = n[..., -1] # [batch_size, num_heads, seq_len] + # Calculate f_t = ∑(i=1 to t) (β_t/β_i) m_i + # f = torch.zeros_like(theta) + # for t in range(seq_len): + # for i in range(t + 1): + # f[..., t] += (beta[..., t] / beta[..., i]) * m[..., i] + f = cal_f(beta, seq_len, m) + f_T = f[..., -1] # [batch_size, num_heads, seq_len] + # Calculate g_j = ∑(i=j to t) (β_t/β_i) n_{i,j} + # g = torch.zeros_like(theta) # [batch_size, num_heads, seq_len] + # for j in range(seq_len): + # for i in range(j, seq_len): + # g[..., j] += (beta[..., -1] / beta[..., i]) * n[..., j, i] + # G = torch.zeros(*beta.shape[:-1], seq_len, seq_len, device = beta.device) + # # Fill in the lower triangular part + # for i in range(seq_len): # row + # for j in range(i + 1): # column + # # Sum from k=j to i + # for k in range(j, i + 1): + # G[..., i, j] += (beta[..., i] / beta[..., k]) * n[..., j, k] + G = cal_G(beta, n, seq_len) + g = G[:, :, -1, :] # [batch_size, num_heads, seq_len] + # g2, G2 = compute_g_and_G(beta, n, seq_len) + return beta, beta_T, f, f_T, g, G, m_T, n_T + + +def titans_linear( + q, k, v, w, b, theta, alpha, eta, eps, chunk_size, initial_state, output_final_state, +): + """ + Implementation of Titans Linear function based on the update rules: + M_t = (1 - alpha_t) * M_{t-1} + S_t + S_t = eta_t * S_{t-1} - theta_t * nabla_l(M_{t-1}; x_t) + + Args: + q: Query tensor + k: Key tensor + v: Value tensor + w: Weight tensor + b: Bias tensor + theta: Learning rate tensor + alpha: Momentum decay tensor + eta: Step size tensor + eps: Epsilon for numerical stability + initial_state: Initial state M_0 + output_final_state: Whether to output the final state + + Returns: + Tuple of (output tensor, final state) + """ + B, H, T, D = q.shape + device = q.device + w = w.reshape(H, 1, D).to(torch.float32) + b = b.reshape(H, 1, D).to(torch.float32) + # Initialize states + if initial_state is None: + M_prev = torch.zeros(B, H, D, D, device=device) + else: + M_prev = initial_state + M_prev_nabla = M_prev.clone() + S_prev = torch.zeros_like(M_prev) + outputs = [] + + # Process sequence step by step + for t in range(T): + # Get current step inputs + q_t = q[:, :, t: t + 1, :] # (batch_size, num_heads, 1, dim) + k_t = k[:, :, t: t + 1, :] # (batch_size, num_heads, 1, dim) + v_t = v[:, :, t: t + 1, :] # (batch_size, num_heads, 1, dim) + theta_t = theta[:, :, t: t + 1, :] # (batch_size, num_heads, 1, dim) + alpha_t = alpha[:, :, t: t + 1, :] # (batch_size, num_heads, 1, dim) + eta_t = eta[:, :, t: t + 1, :] # (batch_size, num_heads, 1, dim) + + # Compute gradient + km = k_t @ M_prev_nabla # (batch_size, num_heads, 1, dim) + reconstruction_target = v_t - k_t + mean = km.mean(-1, keepdim=True) + var = km.var(-1, unbiased=False, keepdim=True).to(torch.float32) + rstd = torch.sqrt(var + eps).to(torch.float32) + km_hat = (km - mean) / rstd + + grad = w * km_hat + b - reconstruction_target + grad = grad * w + # v_new = (D * grad - grad.sum(-1, keepdim = True) - km_hat * (grad * km_hat).sum(-1, keepdim = True)) / ( + # rstd * D) + v_new = D * grad - grad.sum(-1, keepdim=True) / (rstd * D) + proj_term = km_hat * (grad * km_hat).sum(-1, keepdim=True) / (rstd * D) + v_new = v_new - proj_term + # v_new = grad + + # Update S_t + S_t = eta_t * S_prev - 2 * theta_t * k_t.transpose(-2, -1) @ v_new + + # Update M_t + M_t = (1 - alpha_t) * M_prev + S_t + + # Store output + output_t = q_t @ M_t # (batch_size, num_heads, seq_len, dim) + mean = output_t.mean(dim=-1, keepdim=True) + var = output_t.var(dim=-1, unbiased=False, keepdim=True).to(torch.float32) + rstd = torch.sqrt(var + eps).to(torch.float32) + output_t = output_t + (output_t - mean) / rstd * w + b + outputs.append(output_t) + + # Update states for next step + if (t + 1) % chunk_size == 0: + M_prev_nabla = M_t.clone() + M_prev = M_t + S_prev = S_t + + # Stack outputs along sequence dimension + output = torch.stack(outputs, dim=-2).squeeze( + -3, + ) # (batch_size, num_heads, seq_len, dim) + + if output_final_state: + return output, M_prev + return output, None + + +def chunk_titans_linear( + q, k, v, w, b, theta, alpha, eta, eps, chunk_size, initial_state, output_final_state, +): + B, H, T, D = q.shape + num_batch = T // chunk_size + # [num_batch, B, num_heads, mini_batch_size, head_dim] + _q = q.reshape(B, H, num_batch, chunk_size, D).permute(2, 0, 1, 3, 4) + _k = k.reshape(B, H, num_batch, chunk_size, D).permute(2, 0, 1, 3, 4) + _v = v.reshape(B, H, num_batch, chunk_size, D).permute(2, 0, 1, 3, 4) + # [num_batch, B, num_heads, mini_batch_size, 1] + _eta = eta.reshape(B, H, num_batch, chunk_size, 1).permute(2, 0, 1, 3, 4) + _theta = theta.reshape(B, H, num_batch, chunk_size, 1).permute(2, 0, 1, 3, 4) + _alpha = alpha.reshape(B, H, num_batch, chunk_size, 1).permute(2, 0, 1, 3, 4) + # [H, 1, D] + w = w.reshape(H, 1, D).to(torch.float32) + b = b.reshape(H, 1, D).to(torch.float32) + # [num_heads, 1, head_dim] + if initial_state is None: + M_prev = torch.zeros((B, H, D, D), device=v.device, dtype=v.dtype).to( + torch.float32, + ) + else: + M_prev = initial_state + + S_prev = torch.zeros_like(M_prev) + + # [num_batch, B, num_heads, mini_batch_size, head_dim] + o = torch.empty_like(_v) + + for i in range(num_batch): + q_i, k_i, v_i, eta_i, theta_i, alpha_i = [ + x[i] for x in [_q, _k, _v, _eta, _theta, _alpha] + ] + + # beta, beta_T, f, f_T, g, G, m_T, n = combine_params(theta_i, alpha_i, eta_i, chunk_size) + beta, beta_T, f, f_T, g, G, m_T, n = combine_params_log( + theta_i, alpha_i, eta_i, chunk_size, + ) + + m_T = m_T.unsqueeze(-1).unsqueeze(-1) + beta_T = beta_T.unsqueeze(-1).unsqueeze(-1) + f_T = f_T.unsqueeze(-1).unsqueeze(-1) + g_diag = torch.diag_embed(g).to(q_i.dtype) + n = torch.diag_embed(n).to(q_i.dtype) + beta = torch.diag_embed(beta).to(q_i.dtype) + f = torch.diag_embed(f).to(q_i.dtype) + km = k_i @ M_prev + reconstruction_target = v_i - k_i + + mean = km.mean(-1, True) + var = km.var(-1, unbiased=False, keepdim=True).to(torch.float32) + rstd = torch.sqrt(var + eps).to(torch.float32) + km_hat = (km - mean) / rstd + + grad = w * km_hat + b - reconstruction_target + grad *= w + v_new = D * grad - grad.sum(-1, keepdim=True) / (rstd * D) + proj_term = km_hat * (grad * km_hat).sum(-1, keepdim=True) / (rstd * D) + v_new = v_new - proj_term + # v_new = (D * grad - grad.sum(-1, True)) + # print(f"Projection term stats: min={torch.abs(beta_T).min()}") + + # v_new = grad + + Attn = torch.tril(q_i @ k_i.transpose(-2, -1)) * G + + # o_i + output_t = beta @ q_i @ M_prev + f @ q_i @ S_prev - 2 * Attn @ v_new + + M_t = ( + beta_T * M_prev + + f_T * S_prev + - 2 * (g_diag @ k_i).transpose(-1, -2) @ v_new + ) + # cal S_T from S_0 + S_t = m_T * S_prev - 2 * (n @ k_i).transpose(-1, -2) @ v_new + # layer norm with residuals + mean = output_t.mean(dim=-1, keepdim=True) + var = output_t.var(dim=-1, unbiased=False, keepdim=True).to(torch.float32) + rstd = torch.sqrt(var + eps).to(torch.float32) + output_t = output_t + (output_t - mean) / rstd * w + b + o[i] = output_t + S_prev = S_t + M_prev = M_t + + # [B, num_mini_batch, mini_batch_size, num_heads, head_dim] + o = o.permute(1, 2, 0, 3, 4).reshape(B, H, T, D) + M_prev = M_prev if output_final_state else None + return o, M_prev + + +# most of the code is copied from ttt +def chunk_titans_linear_ref( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + theta: torch.Tensor, + alpha: torch.Tensor, + eta: torch.Tensor, + eps: float = 1e-6, + chunk_size: int = 16, # chunk size + initial_state: torch.Tensor = None, + output_final_state: bool = False, + head_first: bool = False, + use_chunk: bool = True, +): + assert q.dtype == k.dtype == v.dtype + assert k.shape[-1] == v.shape[-1], "DK must equal to DV." + if not head_first: + q = q.transpose(1, 2) + k = k.transpose(1, 2) + v = v.transpose(1, 2) + eta = eta.transpose(1, 2) + alpha = alpha.transpose(1, 2) + theta = theta.transpose(1, 2) + seq_len = q.shape[-2] + pad_len = (chunk_size - (seq_len % chunk_size)) % chunk_size + if pad_len > 0: + q = F.pad(q, (0, 0, 0, pad_len)) + k = F.pad(k, (0, 0, 0, pad_len)) + v = F.pad(v, (0, 0, 0, pad_len)) + theta = F.pad(theta, (0, 0, 0, pad_len)) + alpha = F.pad(alpha, (0, 0, 0, pad_len)) + eta = F.pad(eta, (0, 0, 0, pad_len)) + theta[:, :, -1, :] = theta[:, :, -(pad_len + 1), :] + alpha[:, :, -1, :] = alpha[:, :, -(pad_len + 1), :] + eta[:, :, -1, :] = eta[:, :, -(pad_len + 1), :] + assert q.shape[-2] % chunk_size == 0, "Sequence length should be a multiple of BT." + q, k, v, w, b = map(lambda x: x.to(torch.float32), [q, k, v, w, b]) + if use_chunk: + o, final_state = chunk_titans_linear( + q, + k, + v, + w, + b, + theta, + alpha, + eta, + eps, + chunk_size, + initial_state, + output_final_state, + ) + else: + o, final_state = titans_linear( + q, + k, + v, + w, + b, + theta, + alpha, + eta, + eps, + chunk_size, + initial_state, + output_final_state, + ) + o = o[:, :, :seq_len, :] + if not head_first: + o = o.transpose(1, 2) + return o, final_state diff --git a/code/flash-linear-attention/fla/ops/ttt/__init__.py b/code/flash-linear-attention/fla/ops/ttt/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..109b0e425a25bfbaf48802b0c9e74d3b4f62ebbb --- /dev/null +++ b/code/flash-linear-attention/fla/ops/ttt/__init__.py @@ -0,0 +1,8 @@ + +from .chunk import chunk_ttt_linear +from .fused_chunk import fused_chunk_ttt_linear + +__all__ = [ + 'fused_chunk_ttt_linear', + 'chunk_ttt_linear', +] diff --git a/code/flash-linear-attention/fla/ops/ttt/chunk.py b/code/flash-linear-attention/fla/ops/ttt/chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..b2060eb4b89dc4a9e68d09de154d173e66858a9f --- /dev/null +++ b/code/flash-linear-attention/fla/ops/ttt/chunk.py @@ -0,0 +1,1445 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang, Yuqi Pan + +import warnings + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl + +from fla.modules.layernorm import group_norm +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'USE_INITIAL_STATE_B': lambda args: args['hb0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_ttt_linear_fwd_kernel_h( + k, + v, + v_new, + eta, + w, + b, + eps, + h, + hb, + h0, + hb0, + ht, + hbt, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_INITIAL_STATE_B: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + # [BV] + b_hb = tl.zeros([BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1), padding_option="zero").to(tl.float32) + if USE_INITIAL_STATE_B: + p_hb0 = tl.make_block_ptr(hb0 + i_nh * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + b_hb = tl.load(p_hb0, boundary_check=(0,), padding_option="zero").to(tl.float32) + + offs = tl.arange(0, BV) + b_w = tl.load(w + i_h * V + offs, mask=offs < V, other=0.) + b_b = tl.load(b + i_h * V + offs, mask=offs < V, other=0.) + + for i_t in range(NT): + p_h = tl.make_block_ptr(h + ((boh + i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_hb = tl.make_block_ptr(hb + ((boh + i_t) * H + i_h) * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_hb, b_hb.to(p_hb.dtype.element_ty), boundary_check=(0,)) + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, i_v * BV), (BT, BV), (1, 0)) + p_eta_last = eta+bos*H+i_h + (T-1)*H if i_t == NT-1 else eta+bos*H+i_h + (i_t*BT+BT-1)*H + b_k = tl.load(p_k, boundary_check=(0, 1), padding_option="zero") + b_v = tl.load(p_v, boundary_check=(0, 1), padding_option="zero") + + b_kh = tl.dot(tl.trans(b_k), b_h.to(b_k.dtype), allow_tf32=False).to(tl.float32) + b_hb[None, :] + b_kh = tl.where((offs < V)[None, :], b_kh, 0.) + mean = tl.sum(b_kh, axis=1, keep_dims=True) / V + xbar = tl.where((offs < V)[None, :], b_kh - mean, 0.) + var = tl.sum(xbar * xbar, axis=1, keep_dims=True) / V + rstd = 1 / tl.sqrt(var.to(tl.float32) + eps) + b_kh_hat = (b_kh - mean) * rstd + + b_v = b_kh_hat.to(b_k.dtype) * b_w[None, :].to(b_k.dtype) + \ + b_b[None, :].to(b_k.dtype) - b_v.to(b_k.dtype) + tl.trans(b_k) + b_v = tl.where((offs < V)[None, :], b_v * b_w[None, :].to(b_k.dtype), 0.) + b_v2 = rstd * (V * b_v - tl.sum(b_v, axis=1, keep_dims=True) - b_kh_hat.to(b_k.dtype) + * tl.sum(b_v * b_kh_hat.to(b_k.dtype), axis=1, keep_dims=True)) / V + tl.store(p_v_new, b_v2.to(p_v_new.dtype.element_ty), boundary_check=(0, 1)) + b_eta_last = tl.load(p_eta_last) + b_h = b_h - tl.dot(b_eta_last * b_k, b_v2.to(b_k.dtype), allow_tf32=False) + b_hb = b_hb - tl.sum(b_eta_last * b_v2.to(b_k.dtype), axis=0) + + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_hbt = tl.make_block_ptr(hbt + i_nh * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_hbt, b_hb.to(p_hbt.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_ttt_linear_fwd_kernel_o( + q, + k, + v, + eta, + h, + hb, + o, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + eta += bos * H + i_h + o += (bos * H + i_h) * V + h += (i_tg * H + i_h) * K * V + hb += (i_tg * H + i_h) * V + stride_qk = H*K + stride_vo = H*V + stride_eta = H + + p_q = tl.make_block_ptr(q, (T, K), (stride_qk, 1), (i_t * BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k, (K, T), (1, stride_qk), (0, i_t * BT), (BK, BT), (0, 1)) + p_eta = tl.make_block_ptr(eta, (T,), (stride_eta,), (i_t * BT,), (BT,), (0,)) + p_h = tl.make_block_ptr(h, (K, V), (V, 1), (0, i_v * BV), (BK, BV), (1, 0)) + p_hb = tl.make_block_ptr(hb, (V,), (1,), (i_v * BV,), (BV,), (0,)) + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1), padding_option="zero") + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1), padding_option="zero") + # [BT, 1] + b_eta = tl.load(p_eta, boundary_check=(0,), padding_option="zero") + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1), padding_option="zero") + # [BV] + b_hb = tl.load(p_hb, boundary_check=(0,), padding_option="zero") + # [BT, BK] @ [BK, BV] -> [BT, BV] + b_o = tl.dot(b_q, b_h, allow_tf32=False) + # [BT, BK] @ [BK, BT] -> [BT, BT] + b_A = tl.dot(b_q, b_k, allow_tf32=False) + + o_i = tl.arange(0, BT) + m_A = o_i[:, None] >= o_i[None, :] + b_A = tl.where(m_A, b_A, 0) + b_Ae = tl.where(m_A, b_eta[:, None], 0.0) + + p_v = tl.make_block_ptr(v, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1), padding_option="zero") + b_o = (b_o - tl.dot(b_eta[:, None] * b_A.to(b_v.dtype), b_v, allow_tf32=False)) * scale + b_o += b_hb[None, :] - tl.dot(b_Ae.to(b_v.dtype), b_v, allow_tf32=False) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'USE_INITIAL_STATE_B': lambda args: args['hb0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_ttt_linear_bwd_kernel_h( + k, + v, + v_new, + eta, + w, + b, + eps, + h, + h0, + hb0, + x, + y, + r, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NT: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_INITIAL_STATE_B: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + # [BV] + b_hb = tl.zeros([BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K * V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1), padding_option="zero").to(tl.float32) + if USE_INITIAL_STATE_B: + p_hb0 = tl.make_block_ptr(hb0 + i_nh * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + b_hb = tl.load(p_hb0, boundary_check=(0,), padding_option="zero").to(tl.float32) + + offs = tl.arange(0, BV) + b_w = tl.load(w + i_h * V + offs, mask=offs < V, other=0.) + b_b = tl.load(b + i_h * V + offs, mask=offs < V, other=0.) + + for i_t in range(NT): + p_h = tl.make_block_ptr(h + ((boh + i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, i_v * BV), (BT, BV), (1, 0)) + p_x = tl.make_block_ptr(x+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, i_v * BV), (BT, BV), (1, 0)) + p_y = tl.make_block_ptr(y+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, i_v * BV), (BT, BV), (1, 0)) + p_r = tl.make_block_ptr(r+bos*H+i_h, (T, 1), (H, 1), (i_t*BT, 0), (BT, 1), (1, 0)) + p_eta_last = eta+bos*H+i_h + (T-1)*H if i_t == NT-1 else eta+bos*H+i_h + (i_t*BT+BT-1)*H + b_k = tl.load(p_k, boundary_check=(0, 1), padding_option="zero") + b_v = tl.load(p_v, boundary_check=(0, 1), padding_option="zero") + + b_kh = tl.dot(tl.trans(b_k), b_h.to(b_k.dtype), allow_tf32=False).to(tl.float32) + b_hb[None, :] + b_kh = tl.where((offs < V)[None, :], b_kh, 0.) + mean = tl.sum(b_kh, axis=1, keep_dims=True) / V + xbar = tl.where((offs < V)[None, :], b_kh - mean, 0.) + var = tl.sum(xbar * xbar, axis=1, keep_dims=True) / V + rstd = 1 / tl.sqrt(var.to(tl.float32) + eps) + b_kh_hat = (b_kh - mean) * rstd + + b_v = b_kh_hat.to(b_k.dtype) * b_w[None, :].to(b_k.dtype) + \ + b_b[None, :].to(b_k.dtype) - b_v.to(b_k.dtype) + tl.trans(b_k) + b_v = tl.where((offs < V)[None, :], b_v * b_w[None, :].to(b_k.dtype), 0.) + b_v2 = rstd * (V * b_v - tl.sum(b_v, axis=1, keep_dims=True) - b_kh_hat.to(b_k.dtype) + * tl.sum(b_v * b_kh_hat.to(b_k.dtype), axis=1, keep_dims=True)) / V + tl.store(p_x, b_kh_hat.to(p_x.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_y, b_v.to(p_y.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_r, rstd.to(p_r.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_v_new, b_v2.to(p_v_new.dtype.element_ty), boundary_check=(0, 1)) + b_eta_last = tl.load(p_eta_last) + b_h = b_h - tl.dot(b_eta_last * b_k, b_v2.to(b_k.dtype), allow_tf32=False) + b_hb = b_hb - tl.sum(b_eta_last * b_v2.to(b_k.dtype), axis=0) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [4] + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_ttt_linear_bwd_kernel_dv_local( + q, + k, + eta, + do, + dv, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + eta += bos * H + i_h + do += (bos * H + i_h) * V + dv += (bos * H + i_h) * V + stride_qk = H*K + stride_vo = H*V + stride_eta = H + + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_q = tl.make_block_ptr(q, (K, T), (1, stride_qk), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_A += tl.dot(b_k, b_q) + + p_eta = tl.make_block_ptr(eta, (T,), (stride_eta,), (i_t * BT,), (BT,), (0,)) + b_eta = tl.load(p_eta, boundary_check=(0,)) + mask = (tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :]) + b_A = - tl.where(mask, b_A * scale * b_eta[None, :], 0).to(do.dtype.element_ty) + b_Ae = - tl.where(mask, b_eta[None, :], 0).to(do.dtype.element_ty) + + for i_v in range(tl.cdiv(V, BV)): + p_do = tl.make_block_ptr(do, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_dv = tl.dot(b_A.to(b_do.dtype), b_do) + tl.dot(b_Ae.to(b_do.dtype), b_do) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'USE_FINAL_STATE_GRADIENT_B': lambda args: args['dhbt'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_INITIAL_STATE_B': lambda args: args['dhb0'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4, 8, 16] + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_ttt_linear_bwd_kernel_norm( + q, + k, + v, + v_new, + x, + y, + r, + w, + b, + eta, + h, + dht, + dhbt, + dh0, + dhb0, + do, + dh, + dhb, + dv, + dv_new, + dk, + dw, + db, + cu_seqlens, + chunk_offsets, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + USE_FINAL_STATE_GRADIENT_B: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_INITIAL_STATE_B: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + # [BV] + b_dhb = tl.zeros([BV], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_dht = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_dh += tl.load(p_dht, boundary_check=(0, 1), padding_option="zero") + if USE_FINAL_STATE_GRADIENT_B: + p_dhbt = tl.make_block_ptr(dhbt + i_nh * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + b_dhb += tl.load(p_dhbt, boundary_check=(0,), padding_option="zero") + + # [BV] + offs_v = tl.arange(0, BV) + offs_t = tl.arange(0, BT) + b_w = tl.load(w + i_h * V + offs_v, mask=offs_v < V, other=0.) + b_b = tl.load(b + i_h * V + offs_v, mask=offs_v < V, other=0.) + b_dw = tl.zeros([BV], dtype=b_w.dtype) + b_db = tl.zeros([BV], dtype=b_b.dtype) + p_dw = tl.make_block_ptr(dw + i_nh * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + p_db = tl.make_block_ptr(db + i_nh * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + + for i_t in range(NT - 1, -1, -1): + p_h = tl.make_block_ptr(h + ((boh+i_t) * H + i_h) * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh + ((boh+i_t) * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_dhb = tl.make_block_ptr(dhb + ((boh+i_t) * H + i_h) * V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + tl.store(p_dh, b_dh.to(p_dh.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dhb, b_dhb.to(p_dhb.dtype.element_ty), boundary_check=(0,)) + p_q = tl.make_block_ptr(q+(bos*H+i_h)*K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_v_new = tl.make_block_ptr(v_new+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_x = tl.make_block_ptr(x+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_y = tl.make_block_ptr(y+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv_new = tl.make_block_ptr(dv_new+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, i_v * BV), (BT, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT, i_k * BK), (BT, BK), (1, 0)) + p_do = tl.make_block_ptr(do+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, i_v * BV), (BT, BV), (1, 0)) + p_r = tl.make_block_ptr(r+bos*H+i_h, (T, 1), (H, 1), (i_t*BT, 0), (BT, 1), (1, 0)) + p_eta_last = eta+bos*H+i_h + (T-1)*H if i_t == NT-1 else eta+bos*H+i_h + (i_t*BT+BT-1)*H + b_k = tl.load(p_k, boundary_check=(0, 1), padding_option="zero") + b_dv_new = tl.load(p_dv_new, boundary_check=(0, 1), padding_option="zero").to(b_k.dtype) + b_eta_last = tl.load(p_eta_last) + b_dv_new -= tl.dot(b_eta_last * b_k, b_dh.to(b_k.dtype)) + b_dv_new -= b_eta_last * b_dhb.to(b_k.dtype)[None, :] + + b_v_new = tl.load(p_v_new, boundary_check=(0, 1), padding_option="zero") + b_x = tl.load(p_x, boundary_check=(0, 1), padding_option="zero").to(b_k.dtype) + b_y = tl.load(p_y, boundary_check=(0, 1), padding_option="zero").to(b_k.dtype) + b_rstd = tl.load(p_r, boundary_check=(0, 1), padding_option="zero").to(tl.float32) + b_dy = b_rstd * (b_dv_new * V - tl.sum(b_dv_new, axis=1, keep_dims=True) - + b_x * tl.sum(b_dv_new * b_x, axis=1, keep_dims=True)) / V + b_dx = -b_rstd * (b_dv_new * tl.sum(b_x * b_y, axis=1, keep_dims=True) + + b_y * tl.sum(b_dv_new * b_x, axis=1, keep_dims=True)) / V + b_drstd = tl.sum(b_dv_new.to(b_rstd.dtype) * b_v_new.to(b_rstd.dtype) / b_rstd, axis=1, keep_dims=True) + + b_v = tl.load(p_v, boundary_check=(0, 1), padding_option="zero") + b_w = b_w.to(b_k.dtype) + b_b = b_b.to(b_k.dtype) + b_dv = -b_w * b_dy.to(b_k.dtype) + b_dk = b_w * b_dy.to(b_k.dtype) + b_dw += tl.sum(2 * b_w * b_x * b_dy.to(b_k.dtype) + + (b_b - b_v.to(b_k.dtype) + b_k) * b_dy.to(b_k.dtype), axis=0).to(b_dw.dtype) + b_db += tl.sum(b_w * b_dy.to(b_k.dtype), axis=0).to(b_db.dtype) + b_dx = b_dx.to(b_k.dtype) + b_w * b_w * b_dy.to(b_k.dtype) + + # d_rstd, dx --> dkh --> dk, dh + b_q = tl.load(p_q, boundary_check=(0, 1), padding_option="zero") + b_h = tl.load(p_h, boundary_check=(0, 1), padding_option="zero") + b_do = tl.load(p_do, boundary_check=(0, 1), padding_option="zero") + b_q = (b_q * scale).to(b_q.dtype) + b_dkh = b_rstd * (V * b_dx - tl.sum(b_dx, axis=1, keep_dims=True) - + b_x * tl.sum(b_x * b_dx, axis=1, keep_dims=True)) / V + b_dkh -= b_rstd * b_rstd * b_drstd * b_x / V + b_dkh = tl.where((offs_v < V)[None, :] * (offs_t < T-i_t*BT)[:, None], b_dkh, 0.) + b_dk += tl.dot(b_dkh, b_h.to(b_dkh.dtype)).to(b_k.dtype) + b_dh += tl.dot(b_q, b_do.to(b_q.dtype)) + tl.dot(tl.trans(b_k).to(b_dkh.dtype), b_dkh) + b_dhb += tl.sum(b_do + b_dkh, axis=0) + b_dh = tl.where((offs_v < V)[None, :], b_dh, 0.) + b_dhb = tl.where((offs_v < V), b_dhb, 0.) + + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dw, b_dw.to(p_dw.dtype.element_ty), boundary_check=(0,)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0,)) + + if USE_INITIAL_STATE: + p_dh0 = tl.make_block_ptr(dh0 + i_nh * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + if USE_INITIAL_STATE_B: + p_dhb0 = tl.make_block_ptr(dhb0+i_nh*V, (V,), (1,), (i_v * BV,), (BV,), (0,)) + tl.store(p_dhb0, b_dhb.to(p_dhb0.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3] + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dqke( + q, + k, + v, + e, + h, + do, + dh, + dhb, + dq, + dk, + de, + cu_seqlens, + chunk_indices, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + # offset calculation + v += (bos * H + i_h) * V + do += (bos * H + i_h) * V + h += (i_tg * H + i_h) * K * V + dh += (i_tg * H + i_h) * K * V + dhb += (i_tg * H + i_h) * V + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + dq += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + e += bos * H + i_h + de += bos * H + i_h + stride_qk = H*K + stride_vo = H*V + stride_e = H + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_ds = tl.zeros([BT, BT], dtype=tl.float32) + b_de = tl.zeros([BT], dtype=tl.float32) + + p_k = tl.make_block_ptr(k, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + p_e_last = (e + (i_t*BT+BT-1)*stride_e) if (i_t*BT+BT) <= T else (e + (T-1)*stride_e) + i_last = (BT-1) if (i_t*BT+BT) <= T else (T % BT-1) + mask = (tl.arange(0, BT) == i_last) + b_e_last = tl.load(p_e_last) + + for i_v in range(tl.cdiv(V, BV)): + p_v = tl.make_block_ptr(v, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_do = tl.make_block_ptr(do, (T, V), (stride_vo, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dhb = tl.make_block_ptr(dhb, (V,), (1,), (i_v * BV,), (BV,), (0,)) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + # [BV] + b_dhb = tl.load(p_dhb, boundary_check=(0,)) + # [BT, BV] @ [BV, BT] -> [BT, BT] + b_ds += tl.dot(b_do, tl.trans(b_v)) + # [BT, BV] @ [BV, BK] -> [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + # [BT, BV] @ [BV, BK] -> [BT, BK] + b_dk -= b_e_last * tl.dot(b_v, b_dh.to(b_v.dtype)) + b_de -= mask * tl.sum(tl.trans(b_dh) * tl.dot(tl.trans(b_k), b_v.to(b_k.dtype))) + b_de -= mask * tl.sum(b_dhb * tl.sum(b_v, axis=0).to(b_k.dtype)) + + o_i = tl.arange(0, BT) + p_q = tl.make_block_ptr(q, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_e = tl.make_block_ptr(e, (T,), (stride_e,), (i_t * BT,), (BT,), (0,)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_e = tl.load(p_e, boundary_check=(0,)) + + p_dq = tl.make_block_ptr(dq, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk, (T, K), (stride_qk, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_de = tl.make_block_ptr(de, (T,), (stride_e,), (i_t * BT,), (BT,), (0,)) + + b_ds = tl.where(o_i[:, None] >= o_i[None, :], b_ds, 0) + b_ds = b_ds.to(b_k.dtype) + b_dq -= tl.dot(b_ds, b_k) * b_e[:, None] + b_dk -= tl.dot(tl.trans(b_ds), b_q * b_e[:, None]) * scale + b_de -= tl.sum(scale * tl.dot(b_ds, b_k) * b_q, axis=1) + b_de -= tl.sum(b_ds, axis=1) + b_dq *= scale + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_de, b_de.to(p_de.dtype.element_ty), boundary_check=(0,)) + + +def chunk_ttt_linear_fwd_h( + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + eps: float, + initial_state: torch.Tensor | None = None, + initial_state_bias: torch.Tensor | None = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 16, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + assert max(BK, BV) <= 128, "current kernel does not support head dimension larger than 128." + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert NK == 1, 'NK > 1 is not supported because it involves time-consuming synchronization' + assert NV == 1, 'NV > 1 is not supported by TTT update rule.' + + h = k.new_empty(B, NT, H, K, V) + hb = k.new_empty(B, NT, H, 1, V) + final_state = k.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + final_state_bias = k.new_empty(N, H, 1, V, dtype=torch.float32) if output_final_state else None + + v_new = torch.empty_like(v) + grid = (NK, NV, N * H) + + chunk_ttt_linear_fwd_kernel_h[grid]( + k=k, + v=v, + v_new=v_new, + eta=eta, + w=w, + b=b, + eps=eps, + h=h, + hb=hb, + h0=initial_state, + hb0=initial_state_bias, + ht=final_state, + hbt=final_state_bias, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return h, hb, v_new, final_state, final_state_bias + + +def chunk_ttt_linear_fwd_o( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + eta: torch.Tensor, + h: torch.Tensor, + hb: torch.Tensor, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, +) -> torch.Tensor: + B, T, H, K, V = *q.shape, v.shape[-1] + if scale is None: + scale = k.shape[-1] ** -0.5 + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert NK == 1, 'NK > 1 is not supported because it involves time-consuming synchronization' + assert NV == 1, 'NV > 1 is not supported by TTT update rule.' + + o = torch.empty_like(v) + + grid = (NV, NT, B * H) + chunk_ttt_linear_fwd_kernel_o[grid]( + q, + k, + v, + eta, + h, + hb, + o, + cu_seqlens, + chunk_indices, + scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return o + + +def chunk_ttt_linear_bwd_h( + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + eps: float, + initial_state: torch.Tensor | None = None, + initial_state_bias: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 16, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + assert max(BK, BV) <= 128, "current kernel does not support head dimension larger than 128." + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert NK == 1, 'NK > 1 is not supported because it involves time-consuming synchronization' + assert NV == 1, 'NV > 1 is not supported by TTT update rule.' + + h = k.new_empty(B, NT, H, K, V) + rstd = v.new_empty(B, T, H, 1, dtype=torch.float32) + x = torch.empty_like(v) + y = torch.empty_like(v) + + v_new = torch.empty_like(v) + grid = (NK, NV, N * H) + + chunk_ttt_linear_bwd_kernel_h[grid]( + k=k, + v=v, + v_new=v_new, + eta=eta, + w=w, + b=b, + eps=eps, + h=h, + h0=initial_state, + hb0=initial_state_bias, + x=x, + y=y, + r=rstd, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + NT=NT, + ) + return h, v_new, x, y, rstd + + +def chunk_ttt_linear_bwd_dv_local( + q: torch.Tensor, + k: torch.Tensor, + eta: torch.Tensor, + do: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 16, +) -> torch.Tensor: + B, T, H, K, V = *k.shape, do.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BK = min(max(triton.next_power_of_2(K), 16), 128) + BV = min(max(triton.next_power_of_2(V), 16), 128) + + dv = torch.empty_like(do) + grid = (NT, B * H) + chunk_ttt_linear_bwd_kernel_dv_local[grid]( + q, + k, + eta, + do, + dv, + cu_seqlens, + chunk_indices, + scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dv + + +def chunk_ttt_linear_bwd_norm( + q: torch.Tensor, # [B, H, L, D] + k: torch.Tensor, # [B, H, L, D] + v: torch.Tensor, # [B, H, L, D] + v_new: torch.Tensor, # [B, H, L, D] + x: torch.Tensor, # [B, H, L, D] + y: torch.Tensor, # [B, H, L, D] + rstd: torch.Tensor, # [B, H, L, 1] + w: torch.Tensor, # [H, D] + b: torch.Tensor, # [H, D] + eta: torch.Tensor, # [B, H, L, 1] + h0: torch.Tensor, # [B, H, D, D] + hb0: torch.Tensor, # [B, H, 1, D] + h: torch.Tensor, # [B, H, NT, D, D] + dht: torch.Tensor | None, # [B, H, D, D] + dhbt: torch.Tensor | None, # [B, H, 1, D] + dv_new: torch.Tensor | None, # [B, H, L, D] + do: torch.Tensor, # [B, H, L, D] + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 16, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + # torch implementation of `dkh, dw, db, dk, dv` for LN^2 + assert cu_seqlens is None, "bwd of varlen is not implemented yet." + B, T, H, K, V = *q.shape, do.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + BK = max(triton.next_power_of_2(K), 16) + BV = max(triton.next_power_of_2(V), 16) + NK = triton.cdiv(K, BK) + NV = triton.cdiv(V, BV) + assert NK == 1, 'NK > 1 is not supported by TTT.' + assert NV == 1, 'NV > 1 is not supported by TTT.' + + dh = q.new_empty(B, NT, H, K, V) + dhb = q.new_empty(B, NT, H, 1, V) + dh0 = torch.empty_like(h0, dtype=torch.float32) if h0 is not None else None + dhb0 = torch.empty_like(hb0, dtype=torch.float32) if hb0 is not None else None + dv = torch.empty_like(v) + dk = torch.empty_like(k) + dw = w.new_empty(B, H, V) + db = b.new_empty(B, H, V) + + grid = (NK, NV, N * H) + chunk_ttt_linear_bwd_kernel_norm[grid]( + q=q, + k=k, + v=v, + v_new=v_new, + x=x, + y=y, + r=rstd, + w=w, + b=b, + eta=eta, + h=h, + dht=dht, + dhbt=dhbt, + dh0=dh0, + dhb0=dhb0, + do=do, + dh=dh, + dhb=dhb, + dv=dv, + dv_new=dv_new, + dk=dk, + dw=dw, + db=db, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + dw = dw.sum(dim=0) + db = db.sum(dim=0) + return dh, dhb, dh0, dhb0, dv, dk, dw, db + + +def chunk_ttt_linear_bwd_norm_ref( + q: torch.Tensor, # [B, H, L, D] + k: torch.Tensor, # [B, H, L, D] + v: torch.Tensor, # [B, H, L, D] + v_new: torch.Tensor, # [B, H, L, D] + kh: torch.Tensor, # [B, H, L, D] + y: torch.Tensor, # [B, H, L, D] + w: torch.Tensor, # [H, D] + b: torch.Tensor, # [H, D] + eta: torch.Tensor, # [B, H, L, 1] + h0: torch.Tensor, # [B, H, D, D] + h: torch.Tensor, # [B, H, NT, D, D] + dht: torch.Tensor | None, # [B, H, D, D] + dv_new: torch.Tensor | None, # [B, H, L, D] + do: torch.Tensor, # [B, H, L, D] + scale: float, + eps: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 16, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + # torch implementation of `dkh, dw, db, dk, dv` for LN^2 + assert cu_seqlens is None, "bwd of varlen is not implemented yet." + B, T, H, K, V = *q.shape, do.shape[-1] + # [B, L, H, D] -> [B, H, L, D] + q, k, v, v_new, kh, y, h, eta, dv_new, do = [ + x.transpose(1, 2) for x in + [q, k, v, v_new, kh, y, h, eta, dv_new, do] + ] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + pad_len = (BT - (T % BT)) % BT + if pad_len > 0: + q, k, v, v_new, kh, y, eta, dv_new, do = [ + F.pad(x, (0, 0, 0, pad_len)) for x in + [q, k, v, v_new, kh, y, eta, dv_new, do] + ] + eta[:, :, -1, :] = eta[:, :, -(pad_len+1), :] + # [NT, B, H, BT, D] + q, k, v, v_new, kh, y, eta, dv_new, do = [ + x.reshape(B, H, NT, BT, -1).permute(2, 0, 1, 3, 4) for x in + [q, k, v, v_new, kh, y, eta, dv_new, do] + ] + h = h.permute(2, 0, 1, 3, 4) + + # allocate + dh = q.new_zeros(NT, B, H, K, V) + dv = torch.zeros_like(v) + dk = torch.zeros_like(k) + dw = torch.zeros_like(w) + db = torch.zeros_like(b) + # recurrent state + b_dh = dht if dht is not None else torch.zeros_like(dh[0]) + b_dh = b_dh.to(torch.float32) + + # [H, 1, D] + _w = w.reshape(H, 1, V).to(torch.float32) + _b = b.reshape(H, 1, V).to(torch.float32) + + # d_state passing + for i_t in range(NT - 1, -1, -1): + dh[i_t] = b_dh.to(dh.dtype) + # [B, H, BT, D] + _q, _k, _v, _v_new, _kh, _y, _h, _eta, _dv_new, _do = [ + x[i_t].to(torch.float32) for x in + (q, k, v, v_new, kh, y, h, eta, dv_new, do) + ] + _dv_new -= (_eta[:, :, -1, :, None] * _k) @ b_dh + + mean = _kh.mean(dim=-1, keepdim=True) + var = _kh.var(dim=-1, unbiased=False, keepdim=True).to(torch.float32) + rstd = 1 / torch.sqrt(var + eps).to(torch.float32) + x = (_kh - mean) * rstd + # [B, H, BT, D] + dy = rstd * (_dv_new*V - _dv_new.sum(dim=-1, keepdim=True) - x*(x*_dv_new).sum(dim=-1, keepdim=True)) / V + dx = -rstd * (_dv_new*(x*_y).sum(dim=-1, keepdim=True) + _y*(x*_dv_new).sum(dim=-1, keepdim=True)) / V + d_rstd = (_dv_new * _v_new / rstd).sum(dim=-1, keepdim=True) + + dv[i_t] = (-_w*dy).to(dv.dtype) + dk[i_t] += (_w*dy).to(dk.dtype) + dw += (2*_w*x*dy+(_b-_v+_k)*dy).sum(dim=(0, 2)).to(dw.dtype) + db += (_w*dy).sum(dim=(0, 2)).to(db.dtype) + dx += _w*_w*dy + + # d_rstd, dx --> dkh --> dk, dh + dkh = rstd * (V * dx - dx.sum(dim=-1, keepdim=True) - x * (x * dx).sum(dim=-1, keepdim=True)) / V + dkh -= rstd**2 * d_rstd * x / V + dk[i_t] += (dkh @ _h.transpose(-2, -1)).to(dk.dtype) + b_dh += (_q.transpose(-2, -1) * scale) @ _do + _k.transpose(-2, -1) @ dkh + dh0 = b_dh.to(torch.float32) if h0 is not None else None + + # [NT, B, H, BT, D] -> [B, H, T, D] + dv = dv.permute(1, 2, 0, 3, 4).reshape(B, H, -1, V)[:, :, :T, :] + dk = dk.permute(1, 2, 0, 3, 4).reshape(B, H, -1, K)[:, :, :T, :] + # [B, H, NT, D, D] + dh = dh.permute(1, 2, 0, 3, 4) + dv, dk, dh = [x.transpose(1, 2) for x in (dv, dk, dh)] + dh, dv, dk, dw, db = [x.contiguous() for x in (dh, dv, dk, dw, db)] + dh0 = dh0.contiguous() if h0 is not None else None + return dh, dh0, dv, dk, dw, db + + +def chunk_ttt_linear_bwd_dqke( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + eta: torch.Tensor, + h: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + dhb: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 16, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + BK = max(triton.next_power_of_2(K), 16) + BV = min(max(triton.next_power_of_2(V), 16), 64) + NK = triton.cdiv(K, BK) + assert NK == 1, "NK > 1 is not supported." + + dq = torch.empty_like(q) + dk = torch.empty_like(k) + de = torch.empty_like(eta) + grid = (NK, NT, B * H) + + chunk_bwd_kernel_dqke[grid]( + q=q, + k=k, + v=v, + e=eta, + h=h, + do=do, + dh=dh, + dhb=dhb, + dq=dq, + dk=dk, + de=de, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dq, dk, de + + +def chunk_ttt_linear_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float, + eps: float, + initial_state: torch.Tensor, + initial_state_bias: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 16, +): + BT = chunk_size + h, hb, v_new, final_state, final_state_bias = chunk_ttt_linear_fwd_h( + k=k, + v=v, + w=w, + b=b, + eta=eta, + eps=eps, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=BT, + ) + o = chunk_ttt_linear_fwd_o( + q=q, + k=k, + v=v_new, + eta=eta, + h=h, + hb=hb, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=BT, + ) + return o, final_state, final_state_bias + + +def chunk_ttt_linear_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float, + eps: float, + do: torch.Tensor, + dht: torch.Tensor, + dhbt: torch.Tensor, + chunk_size: int = 16, + initial_state: torch.Tensor = None, + initial_state_bias: torch.Tensor = None, + cu_seqlens: torch.LongTensor | None = None, +): + BT = chunk_size + h, v_new, x, y, rstd = chunk_ttt_linear_bwd_h( + k=k, + v=v, + w=w, + b=b, + eta=eta, + eps=eps, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + cu_seqlens=cu_seqlens, + chunk_size=BT, + ) + dv_new = chunk_ttt_linear_bwd_dv_local( + q=q, + k=k, + eta=eta, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=BT, + ) + dh, dhb, dh0, dhb0, dv, dk, dw, db = chunk_ttt_linear_bwd_norm( + q=q, + k=k, + v=v, + v_new=v_new, + x=x, + y=y, + rstd=rstd, + w=w, + b=b, + eta=eta, + h0=initial_state, + hb0=initial_state_bias, + h=h, + dht=dht, + dhbt=dhbt, + dv_new=dv_new, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=BT, + ) + dq, dk2, de = chunk_ttt_linear_bwd_dqke( + q=q, + k=k, + v=v_new, + eta=eta, + h=h, + do=do, + dh=dh, + dhb=dhb, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=BT, + ) + dk.add_(dk2) + return dq, dk, dv, de, dw, db, dh0, dhb0 + + +class ChunkTTTLinearFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q, + k, + v, + w, + b, + chunk_size, + eta, + scale, + eps, + initial_state, + initial_state_bias, + output_final_state, + cu_seqlens, + ): + o, final_state, final_state_bias = chunk_ttt_linear_fwd( + q=q, + k=k, + v=v, + w=w, + b=b, + eta=eta, + scale=scale, + eps=eps, + chunk_size=chunk_size, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, k, v, eta, w, b, initial_state, initial_state_bias) + ctx.chunk_size = chunk_size + ctx.scale = scale + ctx.eps = eps + ctx.cu_seqlens = cu_seqlens + return o.to(q.dtype), final_state, final_state_bias + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht, dhbt): + q, k, v, eta, w, b, initial_state, initial_state_bias = ctx.saved_tensors + dq, dk, dv, de, dw, db, dh0, dhb0 = chunk_ttt_linear_bwd( + q=q, + k=k, + v=v, + w=w, + b=b, + eta=eta, + scale=ctx.scale, + eps=ctx.eps, + do=do, + dht=dht, + dhbt=dhbt, + chunk_size=ctx.chunk_size, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + cu_seqlens=ctx.cu_seqlens, + ) + return dq.to(q), dk.to(k), dv.to(v), dw.to(w), db.to(b), None, de.to(eta), None, None, dh0, dhb0, None, None, None + + +def norm_residual(x, weight, bias, eps): + # GroupNorm and Residual + B, T, H, D = x.shape + x += group_norm( + x.reshape(B, T, -1).clone(), + weight=weight.reshape(-1).clone(), + bias=bias.reshape(-1).clone(), + eps=eps, + num_groups=H, + ).reshape(x.shape) + return x + + +def chunk_ttt_linear( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float = None, + eps: float = 1e-6, + chunk_size: int = 16, + initial_state: torch.Tensor = None, + initial_state_bias: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +): + r""" + Args: + q (torch.Tensor): + queries of shape `(B, H, T, K)` + k (torch.Tensor): + keys of shape `(B, H, T, K)` + v (torch.Tensor): + values of shape `(B, H, T, V)` + w (torch.Tensor): + layer norm weight of shape `(H, V)` + b (torch.Tensor): + layer norm bias of shape `(H, V)` + eta (torch.Tensor): + Learning rate for hidden state, of shape `(B, H, T, 1)`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + chunk_size (int): + chunk size. Default: `16`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `(B, H, K, V)`. Default: `None`. + initial_state_bias (Optional[torch.Tensor]): + Initial state bias of shape `(B, H, 1, V)`. Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `(B, H, K, V)`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, H, T, V]` + final_state (torch.Tensor): + Final state of shape `[B, H, K, V]` if `output_final_state=True` else `None` + """ + assert q.dtype == k.dtype == v.dtype + assert k.shape[-1] == v.shape[-1], "DK must equal to DV." + if isinstance(eta, float): + eta = torch.full_like(q[:, :, :, :1], eta) + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + else: + assert scale > 0, "Scale must be positive." + o, final_state, final_state_bias = ChunkTTTLinearFunction.apply( + q, + k, + v, + w, + b, + chunk_size, + eta, + scale, + eps, + initial_state, + initial_state_bias, + output_final_state, + cu_seqlens, + ) + o = norm_residual(o, w, b, eps) + return o, final_state, final_state_bias diff --git a/code/flash-linear-attention/fla/ops/ttt/fused_chunk.py b/code/flash-linear-attention/fla/ops/ttt/fused_chunk.py new file mode 100644 index 0000000000000000000000000000000000000000..06e9ee1af7c819451073152dc869ad465218c636 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/ttt/fused_chunk.py @@ -0,0 +1,832 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang, Yuqi Pan + +import warnings + +import torch +import triton +import triton.language as tl + +from fla.modules.layernorm import group_norm +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard, is_nvidia_hopper + +NUM_WARPS = [1, 2] if is_nvidia_hopper else [1, 2, 4, 8] + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'USE_INITIAL_STATE_B': lambda args: args['hb0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_chunk_ttt_linear_fwd_kernel( + q, + k, + v, + eta, + w, + b, + o, + scale, + eps, + h0, + hb0, + ht, + hbt, + cu_seqlens, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_INITIAL_STATE_B: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_nh = tl.program_id(0) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + + o_i = tl.arange(0, BT) + v_i = tl.arange(0, BV) + m_A = o_i[:, None] >= o_i[None, :] + b_w = tl.load(w + i_h * V + v_i, mask=v_i < V, other=0.) + b_b = tl.load(b + i_h * V + v_i, mask=v_i < V, other=0.) + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + # [BV] + b_hb = tl.zeros([BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K * V, (K, V), (V, 1), (0, 0), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1), padding_option="zero").to(tl.float32) + if USE_INITIAL_STATE_B: + p_hb0 = tl.make_block_ptr(hb0 + i_nh * V, (V,), (1,), (0,), (BV,), (0,)) + b_hb = tl.load(p_hb0, boundary_check=(0,), padding_option="zero").to(tl.float32) + + for i_t in range(NT): + p_q = tl.make_block_ptr(q+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT, 0), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (K, T), (1, H*K), (0, i_t*BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_e = tl.make_block_ptr(eta+(bos*H+i_h), (T,), (H,), (i_t*BT,), (BT,), (0,)) + p_e_last = eta+bos*H+i_h + (T-1)*H if i_t == NT-1 else eta+bos*H+i_h + (i_t*BT+BT-1)*H + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1), padding_option="zero") + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1), padding_option="zero") + + # [BT, BV] + b_kh = tl.dot(tl.trans(b_k), b_h.to(b_k.dtype), allow_tf32=False).to(tl.float32) + b_hb[None, :] + b_kh = tl.where((v_i < V)[None, :], b_kh, 0.) + mean = tl.sum(b_kh, axis=1, keep_dims=True) / V + xbar = tl.where((v_i < V)[None, :], b_kh - mean, 0.) + var = tl.sum(xbar * xbar, axis=1, keep_dims=True) / V + rstd = 1 / tl.sqrt(var.to(tl.float32) + eps) + b_kh_hat = (b_kh - mean) * rstd + + b_v = b_kh_hat.to(b_k.dtype) * b_w[None, :].to(b_k.dtype) + \ + b_b[None, :].to(b_k.dtype) - b_v.to(b_k.dtype) + tl.trans(b_k) + b_v = tl.where((v_i < V)[None, :], b_v * b_w[None, :].to(b_k.dtype), 0.) + b_v2 = rstd * (V * b_v - tl.sum(b_v, axis=1, keep_dims=True) - b_kh_hat.to(b_k.dtype) + * tl.sum(b_v * b_kh_hat.to(b_k.dtype), axis=1, keep_dims=True)) / V + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1), padding_option="zero") + # [BT] + b_e = tl.load(p_e, boundary_check=(0,), padding_option="zero") + b_q = (b_q * scale).to(b_k.dtype) + + # [BT, BT] + b_A = tl.dot(b_q, b_k, allow_tf32=False) + b_A = tl.where(m_A, b_A, 0) + b_Ae = tl.where(m_A, b_e[:, None], 0.0) + + b_o = - tl.dot(b_e[:, None] * b_A.to(b_v2.dtype), b_v2, allow_tf32=False) + b_o += b_hb[None, :] - tl.dot(b_Ae.to(b_v2.dtype), b_v2, allow_tf32=False) + b_o += tl.dot(b_q, b_h.to(b_q.dtype), allow_tf32=False) + b_e_last = tl.load(p_e_last) + b_h = b_h - tl.dot(b_e_last * b_k, b_v2.to(b_k.dtype), allow_tf32=False) + b_hb = b_hb - tl.sum(b_e_last * b_v2.to(b_k.dtype), axis=0) + b_h = tl.where((v_i < V)[None, :], b_h, 0.) + b_hb = tl.where((v_i < V), b_hb, 0.) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht + i_nh * K*V, (K, V), (V, 1), (0, 0), (BK, BV), (1, 0)) + p_hbt = tl.make_block_ptr(hbt + i_nh * V, (V,), (1,), (0,), (BV,), (0,)) + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_hbt, b_hb.to(p_hbt.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'USE_INITIAL_STATE_B': lambda args: args['hb0'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_chunk_ttt_linear_bwd_kernel_h( + k, + v, + v2, + x, + y, + r, + w, + b, + eta, + h0, + hb0, + h, + do, + dq, + scale, + eps, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_INITIAL_STATE_B: tl.constexpr, +): + i_nh = tl.program_id(0) + i_n, i_h = i_nh // H, i_nh % H + bos, _ = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + o_i = tl.arange(0, BT) + v_i = tl.arange(0, BV) + m_A = o_i[:, None] >= o_i[None, :] + b_w = tl.load(w + i_h * V + v_i, mask=v_i < V, other=0.) + b_b = tl.load(b + i_h * V + v_i, mask=v_i < V, other=0.) + + # [BK, BV] + b_h = tl.zeros([BK, BV], dtype=tl.float32) + # [BV] + b_hb = tl.zeros([BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = tl.make_block_ptr(h0 + i_nh * K * V, (K, V), (V, 1), (0, 0), (BK, BV), (1, 0)) + b_h = tl.load(p_h0, boundary_check=(0, 1), padding_option="zero").to(tl.float32) + if USE_INITIAL_STATE_B: + p_hb0 = tl.make_block_ptr(hb0 + i_nh * V, (V,), (1,), (0,), (BV,), (0,)) + b_hb = tl.load(p_hb0, boundary_check=(0,), padding_option="zero").to(tl.float32) + + for i_t in range(NT): + p_h = tl.make_block_ptr(h+((boh+i_t)*H+i_h)*K*V, (K, V), (V, 1), (0, 0), (BK, BV), (1, 0)) + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (K, T), (1, H*K), (0, i_t*BT), (BK, BT), (0, 1)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_v2 = tl.make_block_ptr(v2+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_x = tl.make_block_ptr(x+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_y = tl.make_block_ptr(y+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_r = tl.make_block_ptr(r+bos*H+i_h, (T, 1), (H, 1), (i_t*BT, 0), (BT, 1), (1, 0)) + p_e = tl.make_block_ptr(eta+(bos*H+i_h), (T,), (H,), (i_t*BT,), (BT,), (0,)) + p_dq = tl.make_block_ptr(dq+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT, 0), (BT, BK), (1, 0)) + p_do = tl.make_block_ptr(do+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_e_last = eta+bos*H+i_h + (T-1)*H if i_t == NT-1 else eta+bos*H+i_h + (i_t*BT+BT-1)*H + tl.store(p_h, b_h.to(p_h.dtype.element_ty), boundary_check=(0, 1)) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1), padding_option="zero") + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1), padding_option="zero") + + b_kh = tl.dot(tl.trans(b_k), b_h.to(b_k.dtype), allow_tf32=False).to(tl.float32) + b_hb[None, :] + b_kh = tl.where((v_i < V)[None, :], b_kh, 0.) + mean = tl.sum(b_kh, axis=1, keep_dims=True) / V + xbar = tl.where((v_i < V)[None, :], b_kh - mean, 0.) + var = tl.sum(xbar * xbar, axis=1, keep_dims=True) / V + rstd = 1 / tl.sqrt(var.to(tl.float32) + eps) + b_kh_hat = (b_kh - mean) * rstd + + b_v = b_kh_hat.to(b_k.dtype) * b_w[None, :].to(b_k.dtype) + \ + b_b[None, :].to(b_k.dtype) - b_v.to(b_k.dtype) + tl.trans(b_k) + b_v = tl.where((v_i < V)[None, :], b_v * b_w[None, :].to(b_k.dtype), 0.) + b_v2 = rstd * (V * b_v - tl.sum(b_v, axis=1, keep_dims=True) - b_kh_hat.to(b_k.dtype) + * tl.sum(b_v * b_kh_hat.to(b_k.dtype), axis=1, keep_dims=True)) / V + tl.store(p_x, b_kh_hat.to(p_x.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_y, b_v.to(p_y.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_r, rstd.to(p_r.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_v2, b_v2.to(p_v2.dtype.element_ty), boundary_check=(0, 1)) + + b_e = tl.load(p_e, boundary_check=(0,), padding_option="zero") + b_do = tl.load(p_do, boundary_check=(0, 1), padding_option="zero") + + b_v2 = tl.where((v_i < V)[None, :], b_v2, 0.) + b_ds = tl.dot(b_do, tl.trans(b_v2).to(b_do.dtype)) + b_ds = tl.where(m_A, b_ds, 0) + b_ds = b_ds.to(b_k.dtype) + b_dq = tl.dot(b_do, tl.trans(b_h).to(b_do.dtype)) + b_dq -= tl.dot(b_ds, tl.trans(b_k)) * b_e[:, None] + b_dq *= scale + + b_e_last = tl.load(p_e_last) + b_h = b_h - tl.dot(b_e_last * b_k, b_v2.to(b_k.dtype), allow_tf32=False) + b_hb = b_hb - tl.sum(b_e_last * b_v2.to(b_k.dtype), axis=0) + b_h = tl.where((v_i < V)[None, :], b_h, 0.) + b_hb = tl.where((v_i < V), b_hb, 0.) + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_INITIAL_STATE_B': lambda args: args['dhb0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'USE_FINAL_STATE_GRADIENT_B': lambda args: args['dhbt'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS + ], + key=['BT', 'BK', 'BV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def fused_chunk_ttt_linear_bwd_kernel_dh( + q, + k, + v, + v2, + x, + y, + r, + w, + b, + eta, + h, + dht, + dhbt, + dh0, + dhb0, + do, + dk, + dv, + de, + dw, + db, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_INITIAL_STATE_B: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + USE_FINAL_STATE_GRADIENT_B: tl.constexpr, +): + i_nh = tl.program_id(0) + i_n, i_h = i_nh // H, i_nh % H + bos, _ = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + # [BV] + b_dhb = tl.zeros([BV], dtype=tl.float32) + if USE_FINAL_STATE_GRADIENT: + p_dht = tl.make_block_ptr(dht + i_nh * K*V, (K, V), (V, 1), (0, 0), (BK, BV), (1, 0)) + b_dh += tl.load(p_dht, boundary_check=(0, 1), padding_option="zero") + if USE_FINAL_STATE_GRADIENT_B: + p_dhbt = tl.make_block_ptr(dhbt + i_nh * V, (V,), (1,), (0,), (BV,), (0,)) + b_dhb += tl.load(p_dhbt, boundary_check=(0,), padding_option="zero") + + # [BV] + o_i = tl.arange(0, BT) + v_i = tl.arange(0, BV) + m_A = o_i[:, None] >= o_i[None, :] + m_A_t = o_i[:, None] <= o_i[None, :] + b_w = tl.load(w + i_h * V + v_i, mask=v_i < V, other=0.) + b_b = tl.load(b + i_h * V + v_i, mask=v_i < V, other=0.) + b_dw = tl.zeros([BV], dtype=b_w.dtype) + b_db = tl.zeros([BV], dtype=b_b.dtype) + p_dw = tl.make_block_ptr(dw + i_nh * V, (V,), (1,), (0,), (BV,), (0,)) + p_db = tl.make_block_ptr(db + i_nh * V, (V,), (1,), (0,), (BV,), (0,)) + + for i_t in range(NT - 1, -1, -1): + p_h = tl.make_block_ptr(h+((boh+i_t)*H+i_h)*K*V, (V, K), (1, V), (0, 0), (BV, BK), (0, 1)) + p_q = tl.make_block_ptr(q+(bos*H+i_h)*K, (K, T), (1, H*K), (0, i_t*BT), (BK, BT), (0, 1)) + p_k = tl.make_block_ptr(k+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT, 0), (BT, BK), (1, 0)) + p_v = tl.make_block_ptr(v+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_v2 = tl.make_block_ptr(v2+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_x = tl.make_block_ptr(x+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_y = tl.make_block_ptr(y+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_r = tl.make_block_ptr(r+bos*H+i_h, (T, 1), (H, 1), (i_t*BT, 0), (BT, 1), (1, 0)) + p_e = tl.make_block_ptr(eta+(bos*H+i_h), (T,), (H,), (i_t*BT,), (BT,), (0,)) + p_dv = tl.make_block_ptr(dv+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_dk = tl.make_block_ptr(dk+(bos*H+i_h)*K, (T, K), (H*K, 1), (i_t*BT, 0), (BT, BK), (1, 0)) + p_do = tl.make_block_ptr(do+(bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT, 0), (BT, BV), (1, 0)) + p_de = tl.make_block_ptr(de+(bos*H+i_h), (T,), (H,), (i_t*BT,), (BT,), (0,)) + p_e_last = eta+bos*H+i_h + (T-1)*H if i_t == NT-1 else eta+bos*H+i_h + (i_t*BT+BT-1)*H + b_q = tl.load(p_q, boundary_check=(0, 1), padding_option="zero") + b_k = tl.load(p_k, boundary_check=(0, 1), padding_option="zero") + b_e = tl.load(p_e, boundary_check=(0,), padding_option="zero") + b_do = tl.load(p_do, boundary_check=(0, 1), padding_option="zero") + b_e_last = tl.load(p_e_last) + b_A = tl.dot(b_k, b_q) + b_A = - tl.where(m_A_t, b_A * scale * b_e[None, :], 0).to(do.dtype.element_ty) + b_Ae = - tl.where(m_A_t, b_e[None, :], 0).to(do.dtype.element_ty) + b_dv_new = tl.dot(b_A.to(b_do.dtype), b_do) + tl.dot(b_Ae.to(b_do.dtype), b_do) + b_dv_new -= tl.dot(b_e_last * b_k, b_dh.to(b_k.dtype)) + b_dv_new -= b_e_last * b_dhb.to(b_k.dtype)[None, :] + + b_v2 = tl.load(p_v2, boundary_check=(0, 1), padding_option="zero").to(b_k.dtype) + b_x = tl.load(p_x, boundary_check=(0, 1), padding_option="zero").to(b_k.dtype) + b_y = tl.load(p_y, boundary_check=(0, 1), padding_option="zero").to(b_k.dtype) + b_rstd = tl.load(p_r, boundary_check=(0, 1), padding_option="zero").to(tl.float32) + b_dy = b_rstd * (b_dv_new * V - tl.sum(b_dv_new, axis=1, keep_dims=True) - + b_x * tl.sum(b_dv_new * b_x, axis=1, keep_dims=True)) / V + b_dx = -b_rstd * (b_dv_new * tl.sum(b_x * b_y, axis=1, keep_dims=True) + + b_y * tl.sum(b_dv_new * b_x, axis=1, keep_dims=True)) / V + b_drstd = tl.sum(b_dv_new.to(b_rstd.dtype) * b_v2.to(b_rstd.dtype) / b_rstd, axis=1, keep_dims=True) + + b_v = tl.load(p_v, boundary_check=(0, 1), padding_option="zero") + b_w = b_w.to(b_k.dtype) + b_b = b_b.to(b_k.dtype) + b_dv = -b_w * b_dy.to(b_k.dtype) + b_dk = b_w * b_dy.to(b_k.dtype) + b_dw += tl.sum(2 * b_w * b_x * b_dy.to(b_k.dtype) + + (b_b - b_v.to(b_k.dtype) + b_k) * b_dy.to(b_k.dtype), axis=0).to(b_dw.dtype) + b_db += tl.sum(b_w * b_dy.to(b_k.dtype), axis=0).to(b_db.dtype) + b_dx = b_dx.to(b_k.dtype) + b_w * b_w * b_dy.to(b_k.dtype) + + b_h = tl.load(p_h, boundary_check=(0, 1), padding_option="zero") + b_q = (b_q * scale).to(b_q.dtype) + b_dkh = b_rstd * (V * b_dx - tl.sum(b_dx, axis=1, keep_dims=True) - + b_x * tl.sum(b_x * b_dx, axis=1, keep_dims=True)) / V + b_dkh -= b_rstd * b_rstd * b_drstd * b_x / V + b_dkh = tl.where((v_i < V)[None, :] * (o_i < T-i_t*BT)[:, None], b_dkh, 0.) + b_dk += tl.dot(b_dkh, b_h.to(b_dkh.dtype)).to(b_k.dtype) + + b_ds = tl.dot(b_do, tl.trans(b_v2)) + b_ds = tl.where(m_A, b_ds, 0) + b_ds = b_ds.to(b_k.dtype) + i_last = (BT-1) if (i_t*BT+BT) <= T else (T % BT-1) + mask = (o_i == i_last) + b_dk -= b_e_last * tl.dot(b_v2, tl.trans(b_dh).to(b_v2.dtype)) + b_dk -= tl.dot(tl.trans(b_ds), tl.trans(b_q) * b_e[:, None]) + b_de = mask * tl.sum(- b_dh * tl.trans(tl.dot(tl.trans(b_v2), b_k))).to(b_k.dtype) + b_de -= mask * tl.sum(b_dhb * tl.sum(b_v2, axis=0)).to(b_k.dtype) + b_de -= tl.sum(tl.dot(b_ds, b_k) * tl.trans(b_q).to(b_k.dtype), axis=1) + b_de -= tl.sum(b_ds, axis=1) + b_dh += tl.dot(b_q, b_do.to(b_q.dtype)) + tl.dot(tl.trans(b_k).to(b_dkh.dtype), b_dkh) + b_dhb += tl.sum(b_do + b_dkh, axis=0) + b_dh = tl.where((v_i < V)[None, :], b_dh, 0.) + b_dhb = tl.where((v_i < V), b_dhb, 0.) + + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_de, b_de.to(p_de.dtype.element_ty), boundary_check=(0,)) + tl.store(p_dw, b_dw.to(p_dw.dtype.element_ty), boundary_check=(0,)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0,)) + + if USE_INITIAL_STATE: + p_dh0 = tl.make_block_ptr(dh0+i_nh*K*V, (K, V), (V, 1), (0, 0), (BK, BV), (1, 0)) + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + if USE_INITIAL_STATE_B: + p_dhb0 = tl.make_block_ptr(dhb0+i_nh*V, (V,), (1,), (0,), (BV,), (0,)) + tl.store(p_dhb0, b_dhb.to(p_dhb0.dtype.element_ty), boundary_check=(0,)) + + +def fused_chunk_ttt_linear_bwd_h( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float, + eps: float, + do: torch.Tensor, + BT: int = 16, + initial_state: torch.Tensor = None, + initial_state_bias: torch.Tensor = None, + cu_seqlens: torch.LongTensor | None = None, +): + assert cu_seqlens is None, "bwd of varlen is not implemented yet." + B, T, H, K, V = *k.shape, v.shape[-1] + # N: the actual number of sequences in the batch with either equal or variable lengths + N, NT = B, triton.cdiv(T, BT) + BK, BV = max(triton.next_power_of_2(K), 16), max(triton.next_power_of_2(V), 16) + assert max(BK, BV) <= 128, "current kernel does not support head dimension larger than 128." + + h = k.new_empty(B, NT, H, K, V) + r = v.new_empty(B, T, H, 1, dtype=torch.float32) + v2 = torch.empty_like(v) + x = torch.empty_like(v) + y = torch.empty_like(v) + dq = torch.empty_like(q) + + grid = (N * H,) + fused_chunk_ttt_linear_bwd_kernel_h[grid]( + k=k, + v=v, + v2=v2, + x=x, + y=y, + r=r, + w=w, + b=b, + eta=eta, + h0=initial_state, + hb0=initial_state_bias, + h=h, + do=do, + dq=dq, + scale=scale, + eps=eps, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dq, h, v2, x, y, r + + +def fused_chunk_ttt_linear_bwd_dh( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + v2: torch.Tensor, + x: torch.Tensor, + y: torch.Tensor, + r: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float, + h: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + dhbt: torch.Tensor, + BT: int = 16, + initial_state: torch.Tensor = None, + initial_state_bias: torch.Tensor = None, + cu_seqlens: torch.LongTensor | None = None, +): + assert cu_seqlens is None, "bwd of varlen is not implemented yet." + B, T, H, K, V = *k.shape, v.shape[-1] + # N: the actual number of sequences in the batch with either equal or variable lengths + N = B + BK, BV = max(triton.next_power_of_2(K), 16), max(triton.next_power_of_2(V), 16) + assert max(BK, BV) <= 128, "current kernel does not support head dimension larger than 128." + + dh0 = torch.empty_like(initial_state, dtype=torch.float32) if initial_state is not None else None + dhb0 = torch.empty_like(initial_state_bias, dtype=torch.float32) if initial_state_bias is not None else None + dk = torch.empty_like(k) + dv = torch.empty_like(v) + de = torch.empty_like(eta) + dw = w.new_empty(B, H, V) + db = b.new_empty(B, H, V) + + grid = (N * H,) + fused_chunk_ttt_linear_bwd_kernel_dh[grid]( + q=q, + k=k, + v=v, + v2=v2, + x=x, + y=y, + r=r, + w=w, + b=b, + eta=eta, + h=h, + dht=dht, + dhbt=dhbt, + dh0=dh0, + dhb0=dhb0, + do=do, + dk=dk, + dv=dv, + de=de, + dw=dw, + db=db, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + dw = dw.sum(dim=0) + db = db.sum(dim=0) + return dk, dv, de, dw, db, dh0, dhb0 + + +def fused_chunk_ttt_linear_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float, + eps: float, + initial_state: torch.Tensor, + initial_state_bias: torch.Tensor, + output_final_state: bool, + cu_seqlens: torch.LongTensor | None = None, + BT: int = 16, +): + B, T, H, K, V = *k.shape, v.shape[-1] + # N: the actual number of sequences in the batch with either equal or variable lengths + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK, BV = max(triton.next_power_of_2(K), 16), max(triton.next_power_of_2(V), 16) + assert max(BK, BV) <= 128, "current kernel does not support head dimension larger than 128." + o = torch.empty_like(v) + final_state = k.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + final_state_bias = k.new_empty(N, H, 1, V, dtype=torch.float32) if output_final_state else None + + grid = (N * H,) + fused_chunk_ttt_linear_fwd_kernel[grid]( + q=q, + k=k, + v=v, + eta=eta, + w=w, + b=b, + o=o, + scale=scale, + eps=eps, + h0=initial_state, + hb0=initial_state_bias, + ht=final_state, + hbt=final_state_bias, + cu_seqlens=cu_seqlens, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return o, final_state, final_state_bias + + +def fused_chunk_ttt_linear_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float, + eps: float, + do: torch.Tensor, + dht: torch.Tensor, + dhbt: torch.Tensor, + BT: int = 16, + initial_state: torch.Tensor = None, + initial_state_bias: torch.Tensor = None, + cu_seqlens: torch.LongTensor | None = None, +): + assert cu_seqlens is None, "bwd of varlen is not implemented yet." + dq, h, v2, x, y, rstd = fused_chunk_ttt_linear_bwd_h( + q=q, + k=k, + v=v, + w=w, + b=b, + eta=eta, + scale=scale, + eps=eps, + do=do, + BT=BT, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + cu_seqlens=cu_seqlens, + ) + dk, dv, de, dw, db, dh0, dhb0 = fused_chunk_ttt_linear_bwd_dh( + q=q, + k=k, + v=v, + v2=v2, + x=x, + y=y, + r=rstd, + w=w, + b=b, + eta=eta, + scale=scale, + h=h, + do=do, + dht=dht, + dhbt=dhbt, + BT=BT, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + cu_seqlens=cu_seqlens, + ) + return dq, dk, dv, de, dw, db, dh0, dhb0 + + +class FusedChunkTTTLinearFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, q, k, v, w, b, BT, eta, scale, eps, initial_state, + initial_state_bias, output_final_state, cu_seqlens): + o, final_state, final_state_bias = fused_chunk_ttt_linear_fwd( + q=q, + k=k, + v=v, + w=w, + b=b, + eta=eta, + scale=scale, + eps=eps, + BT=BT, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, k, v, eta, w, b, initial_state, initial_state_bias) + ctx.BT = BT + ctx.scale = scale + ctx.eps = eps + ctx.cu_seqlens = cu_seqlens + return o.to(q.dtype), final_state, final_state_bias + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, do, dht, dhbt): + q, k, v, eta, w, b, initial_state, initial_state_bias = ctx.saved_tensors + dq, dk, dv, de, dw, db, dh0, dhb0 = fused_chunk_ttt_linear_bwd( + q=q, + k=k, + v=v, + w=w, + b=b, + eta=eta, + scale=ctx.scale, + eps=ctx.eps, + do=do, + dht=dht, + dhbt=dhbt, + BT=ctx.BT, + initial_state=initial_state, + initial_state_bias=initial_state_bias, + cu_seqlens=ctx.cu_seqlens, + ) + return dq.to(q), dk.to(k), dv.to(v), dw.to(w), db.to(b), None, de.to(eta), None, None, dh0, dhb0, None, None + + +def norm_residual(x, weight, bias, eps): + # GroupNorm and Residual + B, T, H, D = x.shape + x += group_norm( + x.reshape(B, T, -1).clone(), + weight=weight.reshape(-1).clone(), + bias=bias.reshape(-1).clone(), + eps=eps, + num_groups=H, + ).reshape(x.shape) + return x + + +def fused_chunk_ttt_linear( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float = None, + eps: float = 1e-6, + chunk_size: int = 16, + initial_state: torch.Tensor = None, + initial_state_bias: torch.Tensor = None, + output_final_state: bool = False, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +): + r""" + Args: + q (torch.Tensor): + queries of shape `(B, H, T, K)` + k (torch.Tensor): + keys of shape `(B, H, T, K)` + v (torch.Tensor): + values of shape `(B, H, T, V)` + w (torch.Tensor): + layer norm weight of shape `(H, V)` + b (torch.Tensor): + layer norm bias of shape `(H, V)` + eta (torch.Tensor): + Learning rate for hidden state, of shape `(B, H, T, 1)`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + chunk_size (int): + chunk size. Default: `16`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `(B, H, K, V)`. Default: `None`. + initial_state_bias (Optional[torch.Tensor]): + Initial state bias of shape `(B, H, 1, V)`. Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `(B, H, K, V)`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + head_first (Optional[bool]): + Whether the inputs are in the head-first format. Default: `False`. + This argument has been deprecated. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, H, T, V]` + final_state (torch.Tensor): + Final state of shape `[B, H, K, V]` if `output_final_state=True` else `None`. + final_state_bias (torch.Tensor): + Final state bias of shape `[B, H, 1, V]` if `output_final_state=True` else `None`. + """ + assert q.dtype == k.dtype == v.dtype + assert k.shape[-1] == v.shape[-1], "DK must equal to DV." + if isinstance(eta, float): + eta = torch.full_like(q[:, :, :, :1], eta) + if head_first: + raise DeprecationWarning( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead.", + ) + if not head_first and q.shape[1] < q.shape[2]: + warnings.warn( + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + "This may indicate the inputs were passed in head-first format [B, H, T, ...] " + "when head_first=False was specified. " + "Please verify your input tensor format matches the expected shape [B, T, H, ...].", + ) + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + else: + assert scale > 0, "Scale must be positive." + o, final_state, final_state_bias = FusedChunkTTTLinearFunction.apply( + q, + k, + v, + w, + b, + chunk_size, + eta, + scale, + eps, + initial_state, + initial_state_bias, + output_final_state, + cu_seqlens, + ) + o = norm_residual(o, w, b, eps) + return o, final_state, final_state_bias diff --git a/code/flash-linear-attention/fla/ops/ttt/naive.py b/code/flash-linear-attention/fla/ops/ttt/naive.py new file mode 100644 index 0000000000000000000000000000000000000000..7681f11d8d553a18594930a6865bbf1a87e54325 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/ttt/naive.py @@ -0,0 +1,125 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang, Yuqi Pan + +import torch +import torch.nn.functional as F + + +def ttt_linear( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float, + eps: float, + mini_batch_size: int, + initial_state: torch.Tensor, + initial_state_bias: torch.Tensor, + output_final_state: bool, +): + B, H, T, D = q.shape + BT = mini_batch_size + NT = T // BT + # [NT, B, H, mini_batch_size, D] + _q = q.reshape(B, H, NT, BT, D).permute(2, 0, 1, 3, 4) + _k = k.reshape(B, H, NT, BT, D).permute(2, 0, 1, 3, 4) + _v = v.reshape(B, H, NT, BT, D).permute(2, 0, 1, 3, 4) + # [NT, B, H, BT, 1] + _eta = eta.reshape(B, H, NT, BT, 1).permute(2, 0, 1, 3, 4) + # [H, 1, D] + w = w.reshape(H, 1, D).to(torch.float32) + b = b.reshape(H, 1, D).to(torch.float32) + + h = torch.zeros((B, H, D, D), device=v.device, dtype=torch.float32) if initial_state is None else initial_state + hb = torch.zeros((B, H, 1, D), device=v.device, dtype=torch.float32) if initial_state_bias is None else initial_state_bias + q *= scale + # [NT, B, H, BT, D] + o = torch.empty_like(_v) + + for i in range(NT): + q_i, k_i, v_i, eta_i = [x[i] for x in [_q, _k, _v, _eta]] + kh = k_i @ h + hb + reconstruction_target = v_i - k_i + + mean = kh.mean(-1, True) + var = kh.var(-1, unbiased=False, keepdim=True).to(torch.float32) + rstd = torch.sqrt(var + eps).to(torch.float32) + kh_hat = (kh - mean) / rstd + + g = w * kh_hat + b - reconstruction_target + g *= w + v_new = (D * g - g.sum(-1, True) - kh_hat * (g * kh_hat).sum(-1, True)) / (rstd * D) + + Attn = torch.tril(q_i @ k_i.transpose(-2, -1)) + o_i = q_i @ h - (eta_i * Attn) @ v_new + hb - torch.tril(eta_i.expand_as(Attn)) @ v_new + h = h - (eta_i[:, :, -1, :, None] * k_i).transpose(-1, -2) @ v_new + hb = hb - torch.sum(eta_i[:, :, -1, :, None] * v_new, dim=-2, keepdim=True) + # layer norm with residuals + + mean = o_i.mean(dim=-1, keepdim=True) + var = o_i.var(dim=-1, unbiased=False, keepdim=True).to(torch.float32) + rstd = torch.sqrt(var + eps).to(torch.float32) + o[i] = o_i + (o_i - mean) / rstd * w + b + + # [B, H, T, D] + o = o.permute(1, 2, 0, 3, 4).reshape(B, H, T, D) + h = h if output_final_state else None + hb = hb if output_final_state else None + return o, h, hb + + +def chunk_ttt_linear_ref( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + w: torch.Tensor, + b: torch.Tensor, + eta: torch.Tensor, + scale: float = None, + eps: float = 1e-6, + mini_batch_size: int = 16, + initial_state: torch.Tensor = None, + initial_state_bias: torch.Tensor = None, + output_final_state: bool = False, + head_first: bool = False, +): + assert q.dtype == k.dtype == v.dtype + assert k.shape[-1] == v.shape[-1], "The key and value dimension must be the same." + if isinstance(eta, float): + eta = torch.full_like(q[:, :, :, :1], eta) + if scale is None: + scale = k.shape[-1] ** -0.5 + if not head_first: + q = q.transpose(1, 2) + k = k.transpose(1, 2) + v = v.transpose(1, 2) + eta = eta.transpose(1, 2) + T = q.shape[-2] + padded = (mini_batch_size - (T % mini_batch_size)) % mini_batch_size + if padded > 0: + q = F.pad(q, (0, 0, 0, padded)) + k = F.pad(k, (0, 0, 0, padded)) + v = F.pad(v, (0, 0, 0, padded)) + eta = F.pad(eta, (0, 0, 0, padded)) + eta[:, :, -1, :] = eta[:, :, -(padded+1), :] + assert q.shape[-2] % mini_batch_size == 0, "Sequence length should be a multiple of mini_batch_size." + q, k, v, eta, w, b = map(lambda x: x.to(torch.float32), [q, k, v, eta, w, b]) + o, final_state, final_state_bias = ttt_linear( + q, + k, + v, + w, + b, + eta, + scale, + eps, + mini_batch_size, + initial_state, + initial_state_bias, + output_final_state, + ) + o = o[:, :, :T, :].contiguous() + if not head_first: + o = o.transpose(1, 2) + return o, final_state, final_state_bias diff --git a/code/flash-linear-attention/fla/ops/utils/__init__.py b/code/flash-linear-attention/fla/ops/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..df4ee64ef8252b86675478a53bff4b4265d1de56 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/__init__.py @@ -0,0 +1,57 @@ + +from .cumsum import ( + chunk_global_cumsum, + chunk_global_cumsum_scalar, + chunk_global_cumsum_vector, + chunk_local_cumsum, + chunk_local_cumsum_scalar, + chunk_local_cumsum_vector, +) +from .index import ( + get_max_num_splits, + prepare_chunk_indices, + prepare_chunk_offsets, + prepare_cu_seqlens_from_lens, + prepare_cu_seqlens_from_mask, + prepare_lens, + prepare_lens_from_cu_seqlens, + prepare_lens_from_mask, + prepare_position_ids, + prepare_sequence_ids, + prepare_token_indices, +) +from .logsumexp import logsumexp_fwd +from .matmul import addmm, matmul +from .pack import pack_sequence, unpack_sequence +from .pooling import mean_pooling +from .softmax import softmax_bwd, softmax_fwd +from .solve_tril import solve_tril + +__all__ = [ + 'chunk_global_cumsum', + 'chunk_global_cumsum_scalar', + 'chunk_global_cumsum_vector', + 'chunk_local_cumsum', + 'chunk_local_cumsum_scalar', + 'chunk_local_cumsum_vector', + 'pack_sequence', + 'unpack_sequence', + 'prepare_chunk_indices', + 'prepare_chunk_offsets', + 'prepare_cu_seqlens_from_lens', + 'prepare_cu_seqlens_from_mask', + 'prepare_lens', + 'prepare_lens_from_cu_seqlens', + 'prepare_lens_from_mask', + 'prepare_position_ids', + 'prepare_sequence_ids', + 'prepare_token_indices', + 'logsumexp_fwd', + 'addmm', + 'matmul', + 'mean_pooling', + 'softmax_bwd', + 'softmax_fwd', + 'solve_tril', + 'get_max_num_splits', +] diff --git a/code/flash-linear-attention/fla/ops/utils/constant.py b/code/flash-linear-attention/fla/ops/utils/constant.py new file mode 100644 index 0000000000000000000000000000000000000000..ce7a2ccb15ea34f9b7002f74f71a35922bfa08f8 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/constant.py @@ -0,0 +1,5 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +# Approximate value of 1/ln(2), used for log/exp base conversion +# Best FP32 approximation: 1.4426950216 (hex 0x3FB8AA3B) +RCP_LN2 = 1.4426950216 diff --git a/code/flash-linear-attention/fla/ops/utils/cumsum.py b/code/flash-linear-attention/fla/ops/utils/cumsum.py new file mode 100644 index 0000000000000000000000000000000000000000..7b1801d5daa691fc0a47fe3b5d22df7f7f1ffb67 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/cumsum.py @@ -0,0 +1,478 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.index import prepare_chunk_indices +from fla.utils import autotune_cache_kwargs, check_shared_mem, input_guard + +BS_LIST = [32, 64] if check_shared_mem() else [16, 32] + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['B', 'H', 'BT', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_local_cumsum_scalar_kernel( + s, + o, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, + HEAD_FIRST: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if HEAD_FIRST: + p_s = tl.make_block_ptr(s + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + p_o = tl.make_block_ptr(o + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + else: + p_s = tl.make_block_ptr(s + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_o = tl.make_block_ptr(o + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + # [BT] + b_s = tl.load(p_s, boundary_check=(0,)).to(tl.float32) + b_o = tl.cumsum(b_s, axis=0) + if REVERSE: + b_z = tl.sum(b_s, axis=0) + b_o = -b_o + b_z[None] + b_s + if HAS_SCALE: + b_o *= scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BS': BS}, num_warps=num_warps) + for BS in BS_LIST + for num_warps in [2, 4, 8] + ], + key=['B', 'H', 'S', 'BT', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_local_cumsum_vector_kernel( + s, + o, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + S: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, + HEAD_FIRST: tl.constexpr, +): + i_s, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_i = tl.arange(0, BT) + if REVERSE: + m_s = tl.where(o_i[:, None] <= o_i[None, :], 1., 0.) + else: + m_s = tl.where(o_i[:, None] >= o_i[None, :], 1., 0.) + + if HEAD_FIRST: + p_s = tl.make_block_ptr(s + (bos * H + i_h*T)*S, (T, S), (S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * H + i_h*T)*S, (T, S), (S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + else: + p_s = tl.make_block_ptr(s + (bos * H + i_h) * S, (T, S), (H*S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * H + i_h) * S, (T, S), (H*S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + # [BT, BS] + b_s = tl.load(p_s, boundary_check=(0, 1)).to(tl.float32) + b_o = tl.dot(m_s, b_s, allow_tf32=False) + if HAS_SCALE: + b_o *= scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps, num_stages=num_stages) + for BT in [32, 64, 128, 256] + for num_warps in [2, 4, 8] + for num_stages in [1, 2, 3, 4] + ], + key=['B', 'H', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_global_cumsum_scalar_kernel( + s, + o, + scale, + cu_seqlens, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, + HEAD_FIRST: tl.constexpr, +): + i_nh = tl.program_id(0) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + T = eos - bos + + b_z = tl.zeros([], dtype=tl.float32) + NT = tl.cdiv(T, BT) + for i_c in range(NT): + i_t = NT - 1 - i_c if REVERSE else i_c + if HEAD_FIRST: + p_s = tl.make_block_ptr(s + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + p_o = tl.make_block_ptr(o + bos*H + i_h*T, (T,), (1,), (i_t * BT,), (BT,), (0,)) + else: + p_s = tl.make_block_ptr(s + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + p_o = tl.make_block_ptr(o + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_s = tl.load(p_s, boundary_check=(0,)).to(tl.float32) + b_o = tl.cumsum(b_s, axis=0) + b_ss = tl.sum(b_s, 0) + if REVERSE: + b_o = -b_o + b_ss + b_s + b_o += b_z + if i_c >= 0: + b_z += b_ss + if HAS_SCALE: + b_o *= scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps, num_stages=num_stages) + for BT in [16, 32, 64, 128] + for num_warps in [2, 4, 8] + for num_stages in [1, 2, 3, 4] + ], + key=['B', 'H', 'S', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_global_cumsum_vector_kernel( + s, + o, + scale, + cu_seqlens, + T, + B: tl.constexpr, + H: tl.constexpr, + S: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, + HEAD_FIRST: tl.constexpr, +): + i_s, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + T = eos - bos + + o_i = tl.arange(0, BT) + if REVERSE: + m_s = tl.where(o_i[:, None] <= o_i[None, :], 1., 0.) + else: + m_s = tl.where(o_i[:, None] >= o_i[None, :], 1., 0.) + + b_z = tl.zeros([BS], dtype=tl.float32) + NT = tl.cdiv(T, BT) + for i_c in range(NT): + i_t = NT - 1 - i_c if REVERSE else i_c + if HEAD_FIRST: + p_s = tl.make_block_ptr(s + (bos * H + i_h*T)*S, (T, S), (S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * H + i_h*T)*S, (T, S), (S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + else: + p_s = tl.make_block_ptr(s + (bos * H + i_h) * S, (T, S), (H*S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * H + i_h) * S, (T, S), (H*S, 1), (i_t * BT, i_s * BS), (BT, BS), (1, 0)) + # [BT, BS] + b_s = tl.load(p_s, boundary_check=(0, 1)).to(tl.float32) + b_c = b_z[None, :] + tl.dot(m_s, b_s, allow_tf32=False) + if HAS_SCALE: + b_c *= scale + tl.store(p_o, b_c.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + b_z += tl.sum(b_s, 0) + + +def chunk_local_cumsum_scalar( + g: torch.Tensor, + chunk_size: int, + reverse: bool = False, + scale: float = None, + cu_seqlens: torch.Tensor | None = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + if head_first: + B, H, T = g.shape + else: + B, T, H = g.shape + assert chunk_size == 2**(chunk_size.bit_length()-1), "chunk_size must be a power of 2" + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + g_org, g = g, torch.empty_like(g, dtype=output_dtype or g.dtype) + grid = (NT, B * H) + chunk_local_cumsum_scalar_kernel[grid]( + s=g_org, + o=g, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + B=B, + H=H, + BT=BT, + HEAD_FIRST=head_first, + REVERSE=reverse, + ) + return g + + +def chunk_local_cumsum_vector( + g: torch.Tensor, + chunk_size: int, + reverse: bool = False, + scale: float = None, + cu_seqlens: torch.Tensor | None = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + if head_first: + B, H, T, S = g.shape + else: + B, T, H, S = g.shape + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + assert chunk_size == 2**(chunk_size.bit_length()-1), "chunk_size must be a power of 2" + + # --- PyTorch fallback (replaces Triton kernel for driver compat) --- + out_dtype = output_dtype or g.dtype + if cu_seqlens is not None: + # varlen path: fall back to original Triton kernel + g_org, g = g, torch.empty_like(g, dtype=out_dtype) + def grid(meta): return (triton.cdiv(meta['S'], meta['BS']), NT, B * H) + chunk_local_cumsum_vector_kernel[grid]( + s=g_org, o=g, scale=scale, cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, T=T, B=B, H=H, S=S, BT=BT, + HEAD_FIRST=head_first, REVERSE=reverse, + ) + return g + pad = BT - (T % BT) if T % BT != 0 else 0 + if head_first: + g_f = g.float() + if pad > 0: + g_f = torch.nn.functional.pad(g_f, (0, 0, 0, pad)) + g_c = g_f.view(B, H, -1, BT, S) + g_c = g_c.flip(3).cumsum(3).flip(3) if reverse else g_c.cumsum(3) + if scale is not None: + g_c = g_c * scale + return g_c.view(B, H, -1, S)[:, :, :T, :].to(out_dtype) + else: + g_f = g.float() + if pad > 0: + g_f = torch.nn.functional.pad(g_f, (0, 0, 0, 0, 0, pad)) + g_c = g_f.view(B, -1, BT, H, S) + g_c = g_c.flip(2).cumsum(2).flip(2) if reverse else g_c.cumsum(2) + if scale is not None: + g_c = g_c * scale + return g_c.view(B, -1, H, S)[:, :T, :, :].to(out_dtype) + + +@input_guard +def chunk_global_cumsum_scalar( + s: torch.Tensor, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, + scale: float = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + if head_first: + B, H, T = s.shape + else: + B, T, H = s.shape + N = len(cu_seqlens) - 1 if cu_seqlens is not None else B + + z = torch.empty_like(s, dtype=output_dtype or s.dtype) + grid = (N * H,) + chunk_global_cumsum_scalar_kernel[grid]( + s=s, + o=z, + scale=scale, + cu_seqlens=cu_seqlens, + T=T, + B=B, + H=H, + HEAD_FIRST=head_first, + REVERSE=reverse, + ) + return z + + +@input_guard +def chunk_global_cumsum_vector( + s: torch.Tensor, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, + scale: float = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + if head_first: + B, H, T, S = s.shape + else: + B, T, H, S = s.shape + N = len(cu_seqlens) - 1 if cu_seqlens is not None else B + BS = min(32, triton.next_power_of_2(S)) + + z = torch.empty_like(s, dtype=output_dtype or s.dtype) + grid = (triton.cdiv(S, BS), N * H) + chunk_global_cumsum_vector_kernel[grid]( + s=s, + o=z, + scale=scale, + cu_seqlens=cu_seqlens, + T=T, + B=B, + H=H, + S=S, + BS=BS, + HEAD_FIRST=head_first, + REVERSE=reverse, + ) + return z + + +@input_guard +def chunk_global_cumsum( + s: torch.Tensor, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, + scale: float = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + if cu_seqlens is not None: + assert s.shape[0] == 1, "Only batch size 1 is supported when cu_seqlens are provided" + if len(s.shape) == 3: + return chunk_global_cumsum_scalar( + s=s, + reverse=reverse, + cu_seqlens=cu_seqlens, + scale=scale, + head_first=head_first, + output_dtype=output_dtype, + ) + elif len(s.shape) == 4: + return chunk_global_cumsum_vector( + s=s, + reverse=reverse, + cu_seqlens=cu_seqlens, + scale=scale, + head_first=head_first, + output_dtype=output_dtype, + ) + else: + raise ValueError( + f"Unsupported input shape {s.shape}, " + f"which should be [B, T, H]/[B, T, H, D] if `head_first=False` " + f"or [B, H, T]/[B, H, T, D] otherwise", + ) + + +@input_guard +def chunk_local_cumsum( + g: torch.Tensor, + chunk_size: int, + reverse: bool = False, + scale: float = None, + cu_seqlens: torch.Tensor | None = None, + head_first: bool = False, + output_dtype: torch.dtype | None = torch.float, + **kwargs, +) -> torch.Tensor: + if cu_seqlens is not None: + assert g.shape[0] == 1, "Only batch size 1 is supported when cu_seqlens are provided" + if len(g.shape) == 3: + return chunk_local_cumsum_scalar( + g=g, + chunk_size=chunk_size, + reverse=reverse, + scale=scale, + cu_seqlens=cu_seqlens, + head_first=head_first, + output_dtype=output_dtype, + ) + elif len(g.shape) == 4: + return chunk_local_cumsum_vector( + g=g, + chunk_size=chunk_size, + reverse=reverse, + scale=scale, + cu_seqlens=cu_seqlens, + head_first=head_first, + output_dtype=output_dtype, + ) + else: + raise ValueError( + f"Unsupported input shape {g.shape}, " + f"which should be (B, T, H, D) if `head_first=False` " + f"or (B, H, T, D) otherwise", + ) diff --git a/code/flash-linear-attention/fla/ops/utils/index.py b/code/flash-linear-attention/fla/ops/utils/index.py new file mode 100644 index 0000000000000000000000000000000000000000..c5ba67854c63e396fd1e63107b8342a062a6983d --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/index.py @@ -0,0 +1,132 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl + +from fla.utils import autotune_cache_kwargs, tensor_cache + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [4, 8, 16, 32] + ], + key=['B'], + **autotune_cache_kwargs, +) +@triton.jit +def prepare_position_ids_kernel( + y, + cu_seqlens, + B: tl.constexpr, +): + i_n = tl.program_id(0) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + + o = tl.arange(0, B) + for i in range(0, tl.cdiv(T, B) * B, B): + o_i = o + i + tl.store(y + bos + o_i, o_i, o_i < T) + + +@tensor_cache +def prepare_lens(cu_seqlens: torch.LongTensor) -> torch.LongTensor: + return cu_seqlens[1:] - cu_seqlens[:-1] + + +@tensor_cache +def prepare_lens_from_mask(mask: torch.BoolTensor) -> torch.LongTensor: + return mask.sum(dim=-1, dtype=torch.int32) + + +@tensor_cache +def prepare_cu_seqlens_from_lens( + lens: torch.LongTensor, + dtype: torch.dtype | None = torch.int32, +) -> torch.LongTensor: + return F.pad(lens.cumsum(dim=0, dtype=dtype), (1, 0)) + + +@tensor_cache +def prepare_cu_seqlens_from_mask( + mask: torch.BoolTensor, + dtype: torch.dtype | None = torch.int32, +) -> torch.LongTensor: + return prepare_cu_seqlens_from_lens(prepare_lens_from_mask(mask), dtype) + + +@tensor_cache +def prepare_lens_from_cu_seqlens( + cu_seqlens: torch.LongTensor, +) -> torch.LongTensor: + return cu_seqlens[1:] - cu_seqlens[:-1] + + +@tensor_cache +def prepare_split_cu_seqlens( + batch_size: int, + seq_len: int, + split_size: int, + cu_seqlens: torch.LongTensor | None = None, + dtype: torch.dtype | None = torch.int32, + device: torch.device | None = torch.device('cpu'), +) -> torch.LongTensor: + if cu_seqlens is None: + total_tokens = batch_size * seq_len + cu_seqlens = list(range(0, total_tokens, seq_len)) + [total_tokens] + else: + cu_seqlens = cu_seqlens.tolist() + return torch.tensor( + [ + i + for bos, eos in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False) + for i in range(bos, eos, split_size) + ] + [cu_seqlens[-1]], + dtype=dtype, + device=device, + ) + + +@tensor_cache +def prepare_position_ids(cu_seqlens: torch.LongTensor) -> torch.LongTensor: + return torch.cat([ + torch.arange(n, dtype=cu_seqlens.dtype, device=cu_seqlens.device) + for n in prepare_lens(cu_seqlens).unbind() + ]) + + +@tensor_cache +def prepare_sequence_ids(cu_seqlens: torch.LongTensor) -> torch.LongTensor: + return prepare_position_ids(cu_seqlens).eq(0).cumsum(0) - 1 + + +@tensor_cache +def prepare_token_indices(cu_seqlens: torch.LongTensor) -> torch.LongTensor: + position_ids = prepare_position_ids(cu_seqlens) + return torch.stack([prepare_sequence_ids(cu_seqlens), position_ids], 1).to(cu_seqlens) + + +@tensor_cache +def prepare_chunk_indices( + cu_seqlens: torch.LongTensor, + chunk_size: int, +) -> torch.LongTensor: + indices = torch.cat([torch.arange(n) for n in triton.cdiv(prepare_lens(cu_seqlens), chunk_size).tolist()]) + return torch.stack([indices.eq(0).cumsum(0) - 1, indices], 1).to(cu_seqlens) + + +@tensor_cache +def prepare_chunk_offsets( + cu_seqlens: torch.LongTensor, + chunk_size: int, +) -> torch.LongTensor: + return torch.cat([cu_seqlens.new_tensor([0]), triton.cdiv(prepare_lens(cu_seqlens), chunk_size)]).cumsum(-1) + + +@tensor_cache +def get_max_num_splits(cu_seqlens: torch.LongTensor, chunk_size: int) -> int: + return triton.cdiv(int(max(prepare_lens(cu_seqlens))), chunk_size) diff --git a/code/flash-linear-attention/fla/ops/utils/logcumsumexp.py b/code/flash-linear-attention/fla/ops/utils/logcumsumexp.py new file mode 100644 index 0000000000000000000000000000000000000000..b7c19de6748164e5342043ee4010444a206e88d6 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/logcumsumexp.py @@ -0,0 +1,53 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import triton +import triton.language as tl + +from fla.ops.utils.op import exp, log +from fla.utils import autotune_cache_kwargs + + +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps) + for BT in [16, 32, 64] + for num_warps in [2, 4, 8] + ], + key=['S'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def logcumsumexp_fwd_kernel( + s, + z, + T, + S: tl.constexpr, + BT: tl.constexpr, +): + i_bh = tl.program_id(0) + o_i = tl.arange(0, BT) + m_s = tl.where(o_i[:, None] >= o_i[None, :], 1., 0.) + + b_mp = tl.full([S], float('-inf'), dtype=tl.float32) + b_zp = tl.zeros([S], dtype=tl.float32) + for i_t in range(tl.cdiv(T, BT)): + p_s = tl.make_block_ptr(s + i_bh * T*S, (T, S), (S, 1), (i_t * BT, 0), (BT, S), (1, 0)) + p_z = tl.make_block_ptr(z + i_bh * T*S, (T, S), (S, 1), (i_t * BT, 0), (BT, S), (1, 0)) + + # [BT, S] + b_s = tl.load(p_s, boundary_check=(0, 1)).to(tl.float32) + # [S,] + b_mc = tl.max(b_s, 0) + b_mc = tl.maximum(b_mp, b_mc) + b_zp = b_zp * exp(b_mp - b_mc) + # [BT, S] + b_s = exp(b_s - b_mc) + b_z = tl.dot(m_s, b_s, allow_tf32=False) + b_zp + # [S,] + b_zc = tl.max(b_z, 0) + b_mp = b_mc + b_zp = b_zc + # [BT, BS] + # small eps to prevent underflows + b_z = log(tl.where(b_z != 0, b_z, 1e-20)) + b_mc + tl.store(p_z, b_z.to(p_z.dtype.element_ty), boundary_check=(0, 1)) diff --git a/code/flash-linear-attention/fla/ops/utils/logsumexp.py b/code/flash-linear-attention/fla/ops/utils/logsumexp.py new file mode 100644 index 0000000000000000000000000000000000000000..f8f0e664a06f6608a4f0b39ad9cb63ffae1801b2 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/logsumexp.py @@ -0,0 +1,80 @@ +# Copyright (c) 2023-2024, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp, log +from fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8, 16, 32] + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit +def logsumexp_fwd_kernel( + x, + z, + scale, + D: tl.constexpr, + B: tl.constexpr, + HAS_SCALE: tl.constexpr, +): + i_n, i_d = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + o_d = i_d * B + tl.arange(0, B) + m_d = o_d < D + + b_x = tl.load(x + i_n * D + o_d, mask=m_d, other=-float('inf')) + if HAS_SCALE: + b_x = b_x * scale + b_m = tl.max(b_x, 0) + b_z = log(tl.sum(exp(b_x - b_m), 0)) + b_m + tl.store(z + i_n * tl.cdiv(D, B) + i_d, b_z) + + +def logsumexp_fwd( + x, + scale: float | None = None, + dtype: torch.dtype | None = None, +): + r""" + Compute the logsumexp of the input tensor over the last dimension. + + Args: + x (Tensor): + The input tensor of any shape. + scale (Optional[float]): + The scale applied to the input tensor. Default: `None`. + dtype (Optional[torch.dtype]): + The data type of the output tensor. Default: `None`. + Returns: + Tensor: The logsumexp of the input tensor. + """ + + shape = x.shape + x = x.view(-1, shape[-1]) + N, D = x.shape + B = min(triton.next_power_of_2(D), 64 * 1024) + ND = triton.cdiv(D, B) + + z = x.new_empty(N, ND, dtype=torch.float) + logsumexp_fwd_kernel[(N, ND)]( + x=x, + z=z, + scale=scale, + D=D, + B=B, + ) + z = z.logsumexp(-1).view(*shape[:-1]) + if dtype is not None and dtype != torch.float: + z = z.to(dtype) + return z diff --git a/code/flash-linear-attention/fla/ops/utils/matmul.py b/code/flash-linear-attention/fla/ops/utils/matmul.py new file mode 100644 index 0000000000000000000000000000000000000000..b565f8c0efb5f32c4978fe14ec548c4bed62572a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/matmul.py @@ -0,0 +1,244 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +# code adapted from +# https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, input_guard + + +# `triton.jit`'ed functions can be auto-tuned by using the `triton.autotune` decorator, which consumes: +# - A list of `triton.Config` objects that define different configurations of +# meta-parameters (e.g., `BM`) and compilation options (e.g., `num_warps`) to try +# - An auto-tuning *key* whose change in values will trigger evaluation of all the +# provided configs +@triton.heuristics({ + 'HAS_ALPHA': lambda args: args['alpha'] is not None, + 'HAS_BETA': lambda args: args['beta'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BM': 128, 'BK': 64, 'BN': 256, 'G': 4}, num_stages=3, num_warps=8), + triton.Config({'BM': 64, 'BK': 32, 'BN': 256, 'G': 4}, num_stages=4, num_warps=4), + triton.Config({'BM': 128, 'BK': 32, 'BN': 128, 'G': 4}, num_stages=4, num_warps=4), + triton.Config({'BM': 128, 'BK': 32, 'BN': 64, 'G': 4}, num_stages=4, num_warps=4), + triton.Config({'BM': 64, 'BK': 32, 'BN': 128, 'G': 4}, num_stages=4, num_warps=4), + triton.Config({'BM': 128, 'BK': 32, 'BN': 32, 'G': 4}, num_stages=4, num_warps=4), + triton.Config({'BM': 64, 'BK': 32, 'BN': 32, 'G': 4}, num_stages=5, num_warps=2), + triton.Config({'BM': 32, 'BK': 32, 'BN': 64, 'G': 4}, num_stages=5, num_warps=2), + # Good config for fp8 inputs. + # triton.Config({'BM': 128, 'BK': 128, 'BN': 256, 'G': 4}, num_stages=3, num_warps=8), + # triton.Config({'BM': 256, 'BK': 128, 'BN': 128, 'G': 4}, num_stages=3, num_warps=8), + # triton.Config({'BM': 256, 'BK': 128, 'BN': 64, 'G': 4}, num_stages=4, num_warps=4), + # triton.Config({'BM': 64, 'BK': 128, 'BN': 256, 'G': 4}, num_stages=4, num_warps=4), + # triton.Config({'BM': 128, 'BK': 128, 'BN': 128, 'G': 4}, num_stages=4, num_warps=4), + # triton.Config({'BM': 128, 'BK': 64, 'BN': 64, 'G': 4}, num_stages=4, num_warps=4), + # triton.Config({'BM': 64, 'BK': 64, 'BN': 128, 'G': 4}, num_stages=4, num_warps=4), + # triton.Config({'BM': 128, 'BK': 64, 'BN': 32, 'G': 4}, num_stages=4, num_warps=4) + ], + key=['M', 'N', 'K'], + **autotune_cache_kwargs, +) +@triton.jit +def matmul_kernel( + # Pointers to matrices + a, + b, + c, + input, + alpha, + beta, + # Matrix dimensions + M, + N, + K, + # The stride variables represent how much to increase the ptr by when moving by 1 + # element in a particular dimension. E.g. `s_am` is how much to increase `a` + # by to get the element one row down (A has M rows). + stride_ab, stride_am, stride_ak, # a: batch, M, K + stride_bk, stride_bn, # b: K, N + stride_cb, stride_cm, stride_cn, # c: batch, M, N + # Meta-parameters + BM: tl.constexpr, + BK: tl.constexpr, + BN: tl.constexpr, + G: tl.constexpr, + ACTIVATION: tl.constexpr, + HAS_INPUT: tl.constexpr, + HAS_ALPHA: tl.constexpr, + HAS_BETA: tl.constexpr, + ALLOW_TF32: tl.constexpr, + X_DIM: tl.constexpr = 1, +): + """Kernel for computing the matmul C = A x B. + A has shape (M, K), B has shape (K, N) and C has shape (M, N) + """ + # ----------------------------------------------------------- + # Map program ids `pid` to the block of C it should compute. + # This is done in a grouped ordering to promote L2 data reuse. + # See above `L2 Cache Optimizations` section for details. + i_b, i_m, i_n = tl.program_id(0), tl.program_id(1), tl.program_id(2) + + NM, NN = tl.num_programs(1), tl.num_programs(2) + i_m, i_n = tl.swizzle2d(i_m, i_n, NM, NN, G) + + # ---------------------------------------------------------- + # Create pointers for the first blocks of A and B. + # We will advance this pointer as we move in the K direction + # and accumulate + # `p_a` is a block of [BM, BK] pointers + # `p_b` is a block of [BK, BN] pointers + # See above `Pointer Arithmetic` section for details + a_batch_ptr = a + i_b * stride_ab + o_am = (i_m * BM + tl.arange(0, BM)) % M + o_bn = (i_n * BN + tl.arange(0, BN)) % N + o_k = tl.arange(0, BK) + + p_a = a_batch_ptr + (o_am[:, None] * stride_am + o_k[None, :] * stride_ak) + p_b = b + (o_k[:, None] * stride_bk + o_bn[None, :] * stride_bn) + + b_acc = tl.zeros((BM, BN), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BK)): + # Load the next block of A and B, generate a mask by checking the K dimension. + # If it is out of bounds, set it to 0. + b_a = tl.load(p_a, mask=o_k[None, :] < K - k * BK, other=0.0) + b_b = tl.load(p_b, mask=o_k[:, None] < K - k * BK, other=0.0) + # We accumulate along the K dimension. + b_acc = tl.dot(b_a, b_b, acc=b_acc, allow_tf32=ALLOW_TF32) + # Advance the ptrs to the next K block. + p_a += BK * stride_ak + p_b += BK * stride_bk + + o_cm = i_m * BM + tl.arange(0, BM) + o_cn = i_n * BN + tl.arange(0, BN) + mask = (o_cm[:, None] < M) & (o_cn[None, :] < N) + + b_c = b_acc + # You can fuse arbitrary activation functions here + # while the b_acc is still in FP32! + if ACTIVATION == "leaky_relu": + b_c = leaky_relu(b_c) + elif ACTIVATION == "relu": + b_c = relu(b_c) + elif ACTIVATION == "sigmoid": + b_c = sigmoid(b_c) + elif ACTIVATION == "tanh": + b_c = tanh(b_c) + + if HAS_ALPHA: + b_c *= tl.load(alpha) + + if HAS_INPUT: + p_i = input + (stride_cm * o_cm[:, None] if X_DIM == 2 else 0) + stride_cn * o_cn[None, :] + mask_p = (o_cn[None, :] < N) if X_DIM == 1 else mask + b_i = tl.load(p_i, mask=mask_p, other=0.0).to(tl.float32) + if HAS_BETA: + b_i *= tl.load(beta) + b_c += b_i + + # ----------------------------------------------------------- + # Write back the block of the output matrix C with masks. + c_batch_ptr = c + i_b * stride_cb + p_c = c_batch_ptr + stride_cm * o_cm[:, None] + stride_cn * o_cn[None, :] + tl.store(p_c, b_c.to(c.dtype.element_ty), mask=mask) + + +# We can fuse `leaky_relu` by providing it as an `ACTIVATION` meta-parameter in `matmul_kernel`. +@triton.jit +def leaky_relu(x): + return tl.where(x >= 0, x, 0.01 * x) + + +@triton.jit +def sigmoid(x): + # σ(x) = 1 / (1 + exp(-x)) + return 1.0 / (1.0 + exp(-x)) + + +@triton.jit +def tanh(x): + # tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x)) + # 2 * sigmoid(2x) - 1 + return (exp(x) - exp(-x)) / (exp(x) + exp(-x)) + + +@triton.jit +def relu(x): + # ReLU(x) = max(0, x) + return tl.maximum(x, 0.0) + + +@input_guard +def matmul(a, b, activation=''): + assert a.dim() in [2, 3], "a must be 2D or 3D" + assert b.dim() == 2, "b must be 2D" + assert a.shape[-1] == b.shape[0], f"Incompatible dimensions: A {a.shape}, B {b.shape}" + + if a.dim() == 2: + a_dim = 2 + a = a.unsqueeze(0).contiguous() # (1, M, K) + else: + a_dim = 3 + allow_tf32 = False if a.dtype == torch.float32 else True + + B, M, K = a.shape[0], a.shape[1], a.shape[2] + K_b, N = b.shape + assert K_b == K, f"Incompatible K dimension: A {K} vs B {K_b}" + c = a.new_empty(B, M, N) + + def grid(meta): return (B, triton.cdiv(M, meta['BM']), triton.cdiv(N, meta['BN'])) + matmul_kernel[grid]( + a, b, c, None, None, None, + M, N, K, + a.stride(0), a.stride(1), a.stride(2), # stride_ab, stride_am, stride_ak + b.stride(0), b.stride(1), # stride_bk, stride_bn (b.dim() == 2) + c.stride(0), c.stride(1), c.stride(2), # stride_cb, stride_cm, stride_cn + ACTIVATION=activation, + ALLOW_TF32=allow_tf32, + HAS_INPUT=False, + ) + return c.squeeze(0) if a_dim == 2 else c + + +@input_guard +def addmm( + x: torch.Tensor, + a: torch.Tensor, + b: torch.Tensor, + alpha: float | None = None, + beta: float | None = None, +) -> torch.Tensor: + assert a.dim() in [2, 3], "a must be 2D or 3D" + assert b.dim() == 2, "b must be 2D" + assert a.shape[-1] == b.shape[0], f"Incompatible dimensions: A {a.shape}, B {b.shape}" + + if a.dim() == 2: + a_dim = 2 + a = a.unsqueeze(0).contiguous() # (1, M, K) + else: + a_dim = 3 + allow_tf32 = False if a.dtype == torch.float32 else True + + B, M, K = a.shape[0], a.shape[1], a.shape[2] + K_b, N = b.shape + assert K_b == K, f"Incompatible K dimension: A {K} vs B {K_b}" + c = a.new_empty(B, M, N) + + def grid(meta): return (B, triton.cdiv(M, meta['BM']), triton.cdiv(N, meta['BN'])) + matmul_kernel[grid]( + a, b, c, x, alpha, beta, + M, N, K, + a.stride(0), a.stride(1), a.stride(2), # stride_ab, stride_am, stride_ak + b.stride(0), b.stride(1), # stride_bk, stride_bn (b.dim() == 2) + c.stride(0), c.stride(1), c.stride(2), # stride_cb, stride_cm, stride_cn + ACTIVATION=None, + ALLOW_TF32=allow_tf32, + HAS_INPUT=True, + X_DIM=x.dim(), + ) + return c.squeeze(0) if a_dim == 2 else c diff --git a/code/flash-linear-attention/fla/ops/utils/op.py b/code/flash-linear-attention/fla/ops/utils/op.py new file mode 100644 index 0000000000000000000000000000000000000000..c4ed104b9a24196d26022e3cba34b521763c3860 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/op.py @@ -0,0 +1,61 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import os + +import triton +import triton.language as tl +import triton.language.extra.libdevice as tldevice + +from fla.utils import is_gather_supported + +if os.environ.get('FLA_USE_FAST_OPS', '0') == '1': + exp = tldevice.fast_expf + exp2 = tldevice.exp2 + log = tldevice.fast_logf + log2 = tldevice.fast_log2f +else: + exp = tl.exp + exp2 = tl.math.exp2 + log = tl.log + log2 = tl.log2 + + +@triton.jit +def safe_exp(x): + return exp(tl.where(x <= 0, x, float('-inf'))) + + +if not is_gather_supported: + @triton.jit + def gather(src, index, axis, _builder=None): + """ + Gather operation that works when tl.gather is not supported. + This is a fallback implementation that returns None. + Just to make triton compiler happy. + """ + return None +else: + gather = tl.gather + + +if hasattr(triton.language, '_experimental_make_tensor_descriptor'): + # For Triton 3.3.x + make_tensor_descriptor = triton.language._experimental_make_tensor_descriptor +elif hasattr(triton.language, 'make_tensor_descriptor'): + # For Triton 3.4.x and later + make_tensor_descriptor = triton.language.make_tensor_descriptor +else: + """ + Fallback implementation when TMA is not supported. + Returns None to indicate TMA descriptors are unavailable. + Just make triton compiler happy. + """ + @triton.jit + def make_tensor_descriptor( + base, + shape, + strides, + block_shape, + _builder=None, + ): + return None diff --git a/code/flash-linear-attention/fla/ops/utils/pack.py b/code/flash-linear-attention/fla/ops/utils/pack.py new file mode 100644 index 0000000000000000000000000000000000000000..6bb14607a39a3e124d75ecec985a057cc189cc92 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/pack.py @@ -0,0 +1,207 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +# Code adapted from https://github.com/mayank31398/cute-kernels + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.index import prepare_lens +from fla.utils import autotune_cache_kwargs, input_guard + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [4, 8, 16, 32] + ], + key=['D', 'PADDING_SIDE', 'PACK'], + **autotune_cache_kwargs, +) +@triton.jit +def packunpack_sequence_kernel( + x, + y, + cu_seqlens, + S, + D, + BD: tl.constexpr, + PADDING_SIDE: tl.constexpr, + PACK: tl.constexpr, +): + i_d, i_s, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2) + bos, eos = tl.load(cu_seqlens + i_b), tl.load(cu_seqlens + i_b + 1) + + T = eos - bos + if PADDING_SIDE == 'left': + NP = S - T + if i_s < NP: + return + i_t = bos + (i_s - NP) + else: + if i_s >= T: + return + i_t = bos + i_s + + o_d = i_d * BD + tl.arange(0, BD) + mask = o_d < D + + if PACK: + b_x = tl.load(x + (i_b * S + i_s) * D + o_d, mask=mask) + tl.store(y + i_t * D + o_d, b_x, mask=mask) + else: + b_x = tl.load(x + i_t * D + o_d, mask=mask) + tl.store(y + (i_b * S + i_s) * D + o_d, b_x, mask=mask) + + +def pack_sequence_fwdbwd( + x: torch.Tensor, + cu_seqlens: torch.Tensor, + padding_side: str, +) -> torch.Tensor: + B, S = x.shape[:2] + D = x.numel() // (B * S) + BD = min(triton.next_power_of_2(D), 4096) + ND = triton.cdiv(D, BD) + + y = torch.empty(cu_seqlens[-1].item(), *x.shape[2:], device=x.device, dtype=x.dtype) + packunpack_sequence_kernel[ND, S, B]( + x=x, + y=y, + cu_seqlens=cu_seqlens, + S=S, + D=D, + BD=BD, + PADDING_SIDE=padding_side, + PACK=True, + ) + return y + + +def unpack_sequence_fwdbwd( + x: torch.Tensor, + cu_seqlens: torch.Tensor, + padding_side: str, + desired_shape: torch.Size, +) -> torch.Tensor: + if desired_shape is None: + desired_shape = (len(cu_seqlens) - 1, prepare_lens(cu_seqlens).max().item(), *x.shape[1:]) + y = torch.zeros(desired_shape, device=x.device, dtype=x.dtype) + B, S = y.shape[:2] + D = y.numel() // (B * S) + BD = min(triton.next_power_of_2(D), 4096) + ND = triton.cdiv(D, BD) + + packunpack_sequence_kernel[ND, S, B]( + x=x, + y=y, + cu_seqlens=cu_seqlens, + S=S, + D=D, + BD=BD, + PADDING_SIDE=padding_side, + PACK=False, + ) + return y + + +class PackSequenceFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + cu_seqlens: torch.Tensor, + padding_side: str, + ) -> torch.Tensor: + assert padding_side in ['left', 'right'] + assert x.ndim >= 2 + + ctx.cu_seqlens = cu_seqlens + ctx.padding_side = padding_side + ctx.desired_shape = x.shape + + y = pack_sequence_fwdbwd( + x=x, + cu_seqlens=cu_seqlens, + padding_side=padding_side, + ) + return y + + @staticmethod + @input_guard + def backward(ctx, dy: torch.Tensor) -> tuple[torch.Tensor | None]: + dx = unpack_sequence_fwdbwd( + x=dy, + cu_seqlens=ctx.cu_seqlens, + padding_side=ctx.padding_side, + desired_shape=ctx.desired_shape, + ) + return dx, *[None] * 10 + + +class UnpackSequenceFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + x: torch.Tensor, + cu_seqlens: torch.Tensor, + padding_side: str, + desired_shape: torch.Size | None = None, + ) -> torch.Tensor: + assert padding_side in ['left', 'right'] + assert x.ndim >= 2 + if desired_shape is not None: + assert desired_shape[0] == cu_seqlens.shape[0] - 1 + assert desired_shape[2:] == x.shape[1:] + + ctx.cu_seqlens = cu_seqlens + ctx.padding_side = padding_side + + y = unpack_sequence_fwdbwd( + x=x, + cu_seqlens=cu_seqlens, + padding_side=padding_side, + desired_shape=desired_shape, + ) + return y + + @staticmethod + @input_guard + def backward(ctx, dy: torch.Tensor) -> tuple[torch.Tensor | None]: + dx = pack_sequence_fwdbwd( + x=dy, + cu_seqlens=ctx.cu_seqlens, + padding_side=ctx.padding_side, + ) + return dx, None, None, None + + +def pack_sequence( + x: torch.Tensor, + cu_seqlens: torch.Tensor, + padding_side: str = 'left', +) -> torch.Tensor: + return PackSequenceFunction.apply( + x, + cu_seqlens, + padding_side, + ) + + +def unpack_sequence( + x: torch.Tensor, + cu_seqlens: torch.Tensor, + padding_side: str = 'left', + desired_shape: torch.Size | None = None, +) -> torch.Tensor: + return UnpackSequenceFunction.apply( + x, + cu_seqlens, + padding_side, + desired_shape, + ) diff --git a/code/flash-linear-attention/fla/ops/utils/pooling.py b/code/flash-linear-attention/fla/ops/utils/pooling.py new file mode 100644 index 0000000000000000000000000000000000000000..964f6dd2eb31110f4fcb58c95bb2812b88e4788a --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/pooling.py @@ -0,0 +1,207 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.index import prepare_chunk_indices +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, input_guard + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BD': BD}, num_warps=num_warps) + for BD in [16, 32, 64, 128] + for num_warps in [1, 2, 4, 8] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def mean_pooling_fwd_kernel( + x, + o, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + p_x = tl.make_block_ptr(x + (bos * H + i_h) * D, (T, D), (H*D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + p_o = tl.make_block_ptr(o + (i_tg * H + i_h) * D, (D,), (1,), (i_d * BD,), (BD,), (0,)) + # [BT, BD] + b_x = tl.load(p_x, boundary_check=(0, 1)).to(tl.float32) + # [BD] + b_o = tl.sum(b_x, axis=0) / min(BT, T - i_t * BT) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0,)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BD': BD}, num_warps=num_warps) + for BD in [16, 32, 64, 128] + for num_warps in [1, 2, 4, 8] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def mean_pooling_bwd_kernel( + do, + dx, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_d, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + p_dx = tl.make_block_ptr(dx + (bos * H + i_h) * D, (T, D), (H*D, 1), (i_t * BT, i_d * BD), (BT, BD), (1, 0)) + p_do = tl.make_block_ptr(do + (i_tg * H + i_h) * D, (D,), (1,), (i_d * BD,), (BD,), (0,)) + # [BD] + b_do = tl.load(p_do, boundary_check=(0,)).to(tl.float32) + # [BT, BD] + b_dx = b_do / tl.full((BT,), min(BT, T - i_t * BT), dtype=tl.float32)[:, None] + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), boundary_check=(0, 1)) + + +def mean_pooling_fwd( + x: torch.Tensor, + chunk_size: int, + cu_seqlens: torch.LongTensor | None = None, +) -> torch.Tensor: + B, T, H, D = x.shape + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + o = x.new_empty(B, NT, H, D) + def grid(meta): return (triton.cdiv(D, meta['BD']), NT, B * H) + mean_pooling_fwd_kernel[grid]( + x, + o, + cu_seqlens, + chunk_indices, + T=T, + H=H, + D=D, + BT=BT, + ) + return o + + +def mean_pooling_bwd( + do: torch.Tensor, + batch_size: int, + seq_len: int, + chunk_size: int, + cu_seqlens: torch.LongTensor | None = None, +) -> torch.Tensor: + B, T, H, D = batch_size, seq_len, *do.shape[-2:] + BT = chunk_size + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dx = do.new_empty(B, T, H, D) + def grid(meta): return (triton.cdiv(D, meta['BD']), NT, B * H) + mean_pooling_bwd_kernel[grid]( + do, + dx, + cu_seqlens, + chunk_indices, + T=T, + H=H, + D=D, + BT=BT, + ) + return dx + + +class MeanPoolingFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + x: torch.Tensor, + chunk_size: int, + cu_seqlens: torch.LongTensor | None = None, + ) -> torch.Tensor: + o = mean_pooling_fwd(x, chunk_size, cu_seqlens) + ctx.batch_size = x.shape[0] + ctx.seq_len = x.shape[1] + ctx.chunk_size = chunk_size + ctx.cu_seqlens = cu_seqlens + return o + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, do, + ) -> tuple[torch.Tensor, None, None]: + batch_size = ctx.batch_size + seq_len = ctx.seq_len + chunk_size = ctx.chunk_size + cu_seqlens = ctx.cu_seqlens + dx = mean_pooling_bwd(do, batch_size, seq_len, chunk_size, cu_seqlens) + return dx, None, None + + +def mean_pooling( + x: torch.Tensor, + chunk_size: int, + cu_seqlens: torch.LongTensor | None = None, + head_first: bool = False, +) -> torch.Tensor: + if head_first: + x = x.transpose(1, 2) + if cu_seqlens is not None: + if x.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {x.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + o = MeanPoolingFunction.apply(x, chunk_size, cu_seqlens) + if head_first: + o = o.transpose(1, 2) + return o diff --git a/code/flash-linear-attention/fla/ops/utils/softmax.py b/code/flash-linear-attention/fla/ops/utils/softmax.py new file mode 100644 index 0000000000000000000000000000000000000000..74ca67352aa2364cbd85526e4bb36e90ebbec129 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/softmax.py @@ -0,0 +1,106 @@ +# Copyright (c) 2023-2024, Songlin Yang, Yu Zhang + + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import autotune_cache_kwargs, is_amd + +NUM_WARPS_AUTOTUNE = [1, 2, 4, 8, 16] if is_amd else [1, 2, 4, 8, 16, 32] + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit +def softmax_fwd_kernel( + x, + p, + D: tl.constexpr, + B: tl.constexpr, +): + i_n = tl.program_id(0) + o_d = tl.arange(0, B) + m_d = o_d < D + + b_x = tl.load(x + i_n * D + o_d, mask=m_d, other=-float('inf')) + b_m = tl.max(b_x, 0) + b_x = exp(b_x - b_m) + b_p = b_x / tl.sum(b_x, 0) + + tl.store(p + i_n * D + o_d, b_p.to(p.dtype.element_ty), mask=m_d) + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in NUM_WARPS_AUTOTUNE + ], + key=['D'], + **autotune_cache_kwargs, +) +@triton.jit +def softmax_bwd_kernel( + p, + dp, + ds, + D: tl.constexpr, + B: tl.constexpr, +): + i_n = tl.program_id(0) + o_d = tl.arange(0, B) + m_d = o_d < D + + b_p = tl.load(p + i_n * D + o_d, mask=m_d, other=0.) + b_dp = tl.load(dp + i_n * D + o_d, mask=m_d, other=0.) + b_pp = tl.sum(b_p * b_dp, 0) + b_ds = b_p * b_dp - b_p * b_pp + tl.store(ds + i_n * D + o_d, b_ds.to(ds.dtype.element_ty), mask=m_d) + + +def softmax_fwd( + x: torch.Tensor, + dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + shape = x.shape + x = x.view(-1, x.shape[-1]) + + N, D = x.shape + B = triton.next_power_of_2(D) + + p = torch.empty_like(x, dtype=dtype) + softmax_fwd_kernel[(N,)]( + x=x, + p=p, + D=D, + B=B, + ) + return p.view(*shape) + + +def softmax_bwd( + p: torch.Tensor, + dp: torch.Tensor, + dtype: torch.dtype | None = torch.float, +) -> torch.Tensor: + shape = p.shape + p = p.view(-1, p.shape[-1]) + ds = torch.empty_like(p, dtype=dtype) + + N, D = p.shape + B = triton.next_power_of_2(D) + softmax_bwd_kernel[(N,)]( + p=p, + dp=dp, + ds=ds, + D=D, + B=B, + ) + return ds.view(*shape) diff --git a/code/flash-linear-attention/fla/ops/utils/solve_tril.py b/code/flash-linear-attention/fla/ops/utils/solve_tril.py new file mode 100644 index 0000000000000000000000000000000000000000..b5c6f48afb20f65274f29557fc3fad6c552b9f34 --- /dev/null +++ b/code/flash-linear-attention/fla/ops/utils/solve_tril.py @@ -0,0 +1,382 @@ +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import os + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.index import prepare_chunk_indices +from fla.ops.utils.op import make_tensor_descriptor +from fla.utils import autotune_cache_kwargs, input_guard, is_amd, is_tma_supported + +FLA_TRIL_PRECISION = os.environ.get('FLA_TRIL_PRECISION', 'ieee') +ALLOWED_TRIL_PRECISIONS = ['ieee', 'tf32'] if is_amd else ['ieee', 'tf32', 'tf32x3'] +assert FLA_TRIL_PRECISION in ALLOWED_TRIL_PRECISIONS, \ + f'FLA_TRIL_PRECISION must be one of {ALLOWED_TRIL_PRECISIONS}, but got {FLA_TRIL_PRECISION}' + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4, 5] + ], + key=['BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def solve_tril_16x16_kernel( + A, + Ai, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + BT: tl.constexpr, + USE_TMA: tl.constexpr, + IS_VARLEN: tl.constexpr, + DOT_PRECISION: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + o_i = tl.arange(0, 16) + m_A = o_i[:, None] > o_i[None, :] + m_I = o_i[:, None] == o_i[None, :] + + A = A + (bos*H + i_h) * BT + Ai = Ai + (bos*H + i_h) * 16 + + offset = (i_t * 16) % BT + if not USE_TMA: + p_A = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * 16, offset), (16, 16), (1, 0)) + # [16, 16] + b_A = tl.load(p_A, boundary_check=(0, 1)).to(tl.float32) + else: + desc = make_tensor_descriptor(A, [T, BT], [H*BT, 1], [16, 16]) + desc_o = make_tensor_descriptor(Ai, [T, 16], [H*16, 1], [16, 16]) + b_A = desc.load([i_t * 16, offset]).to(tl.float32) + b_A = -tl.where(m_A, b_A, 0) + + for i in range(2, min(16, T - i_t * 16)): + # [16] + b_a = -tl.load(A + (i_t * 16 + i) * H*BT + o_i + offset) + b_a = b_a + tl.sum(b_a[:, None] * b_A, 0) + b_A = tl.where((o_i == i)[:, None], b_a, b_A) + b_A += m_I + if not USE_TMA: + p_Ai = tl.make_block_ptr(Ai, (T, 16), (H*16, 1), (i_t * 16, 0), (16, 16), (1, 0)) + tl.store(p_Ai, b_A.to(p_Ai.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + else: + desc_o.store([i_t * 16, 0], b_A.to(desc_o.dtype, fp_downcast_rounding="rtne")) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4, 5] + ], + key=['H', 'BT', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def merge_16x16_to_32x32_inverse_kernel( + A, + Ai, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + BT: tl.constexpr, + USE_TMA: tl.constexpr, + IS_VARLEN: tl.constexpr, + DOT_PRECISION: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_i = tl.arange(0, 16) + m_A = o_i[:, None] > o_i[None, :] + m_I = o_i[:, None] == o_i[None, :] + A += (bos * H + i_h) * BT + Ai += (bos * H + i_h) * BT + + if not USE_TMA: + p_A_11 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT, 0), (16, 16), (1, 0)) + p_A_22 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 16, 16), (16, 16), (1, 0)) + b_Ai_11 = tl.load(p_A_11, boundary_check=(0, 1)).to(tl.float32) + b_Ai_22 = tl.load(p_A_22, boundary_check=(0, 1)).to(tl.float32) + else: + desc = make_tensor_descriptor(A, [T, BT], [H*BT, 1], [16, 16]) + desc_o = make_tensor_descriptor(Ai, [T, BT], [H*BT, 1], [16, 16]) + b_Ai_11 = desc.load([i_t * BT + 0, 0]).to(tl.float32) + b_Ai_22 = desc.load([i_t * BT + 16, 16]).to(tl.float32) + + # [16, 16] + b_Ai_11 = -tl.where(m_A, b_Ai_11, 0) + b_Ai_22 = -tl.where(m_A, b_Ai_22, 0) + + for i in range(2, min(16, T - i_t * BT)): + b_a_11 = -tl.load(A + (i_t * BT + i) * H*BT + o_i) + b_a_11 += tl.sum(b_a_11[:, None] * b_Ai_11, 0) + b_Ai_11 = tl.where((o_i == i)[:, None], b_a_11, b_Ai_11) + for i in range(16 + 2, min(32, T - i_t * BT)): + b_a_22 = -tl.load(A + (i_t * BT + i) * H*BT + o_i + 16) + b_a_22 += tl.sum(b_a_22[:, None] * b_Ai_22, 0) + b_Ai_22 = tl.where((o_i == i - 16)[:, None], b_a_22, b_Ai_22) + + b_Ai_11 += m_I + b_Ai_22 += m_I + + if not USE_TMA: + p_A_21 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 16, 0), (16, 16), (1, 0)) + b_A_21 = tl.load(p_A_21, boundary_check=(0, 1)).to(tl.float32) + else: + b_A_21 = desc.load([i_t * BT + 16, 0]).to(tl.float32) + + b_Ai_21 = -tl.dot(tl.dot(b_Ai_22, b_A_21, input_precision=DOT_PRECISION), b_Ai_11, input_precision=DOT_PRECISION) + + if not USE_TMA: + p_Ai_11 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT, 0), (16, 16), (1, 0)) + p_Ai_21 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 16, 0), (16, 16), (1, 0)) + p_Ai_22 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 16, 16), (16, 16), (1, 0)) + tl.store(p_Ai_11, b_Ai_11.to(p_Ai_11.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_22, b_Ai_22.to(p_Ai_22.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_21, b_Ai_21.to(p_Ai_21.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + else: + desc_o.store([i_t * BT + 0, 0], b_Ai_11.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 16, 0], b_Ai_21.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 16, 16], b_Ai_22.to(desc_o.dtype, fp_downcast_rounding="rtne")) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4, 5] + ], + key=['H', 'BT', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def merge_16x16_to_64x64_inverse_kernel( + A, + Ai, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + BT: tl.constexpr, + USE_TMA: tl.constexpr, + IS_VARLEN: tl.constexpr, + DOT_PRECISION: tl.constexpr, +): + i_t, i_bh = tl.program_id(0), tl.program_id(1) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_i = tl.arange(0, 16) + m_A = o_i[:, None] > o_i[None, :] + m_I = o_i[:, None] == o_i[None, :] + A += (bos * H + i_h) * BT + Ai += (bos * H + i_h) * BT + + if not USE_TMA: + p_A_11 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT, 0), (16, 16), (1, 0)) + p_A_22 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 16, 16), (16, 16), (1, 0)) + p_A_33 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 32, 32), (16, 16), (1, 0)) + p_A_44 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 48, 48), (16, 16), (1, 0)) + b_Ai_11 = tl.load(p_A_11, boundary_check=(0, 1)).to(tl.float32) + b_Ai_22 = tl.load(p_A_22, boundary_check=(0, 1)).to(tl.float32) + b_Ai_33 = tl.load(p_A_33, boundary_check=(0, 1)).to(tl.float32) + b_Ai_44 = tl.load(p_A_44, boundary_check=(0, 1)).to(tl.float32) + else: + desc = make_tensor_descriptor(A, [T, BT], [H*BT, 1], [16, 16]) + desc_o = make_tensor_descriptor(Ai, [T, BT], [H*BT, 1], [16, 16]) + b_Ai_11 = desc.load([i_t * BT + 0, 0]).to(tl.float32) + b_Ai_22 = desc.load([i_t * BT + 16, 16]).to(tl.float32) + b_Ai_33 = desc.load([i_t * BT + 32, 32]).to(tl.float32) + b_Ai_44 = desc.load([i_t * BT + 48, 48]).to(tl.float32) + + # [16, 16] + b_Ai_11 = -tl.where(m_A, b_Ai_11, 0) + b_Ai_22 = -tl.where(m_A, b_Ai_22, 0) + b_Ai_33 = -tl.where(m_A, b_Ai_33, 0) + b_Ai_44 = -tl.where(m_A, b_Ai_44, 0) + + for i in range(2, min(16, T - i_t * BT)): + b_a_11 = -tl.load(A + (i_t * BT + i) * H*BT + o_i) + b_a_11 += tl.sum(b_a_11[:, None] * b_Ai_11, 0) + b_Ai_11 = tl.where((o_i == i)[:, None], b_a_11, b_Ai_11) + for i in range(16 + 2, min(32, T - i_t * BT)): + b_a_22 = -tl.load(A + (i_t * BT + i) * H*BT + o_i + 16) + b_a_22 += tl.sum(b_a_22[:, None] * b_Ai_22, 0) + b_Ai_22 = tl.where((o_i == i - 16)[:, None], b_a_22, b_Ai_22) + for i in range(32 + 2, min(48, T - i_t * BT)): + b_a_33 = -tl.load(A + (i_t * BT + i) * H*BT + o_i + 32) + b_a_33 += tl.sum(b_a_33[:, None] * b_Ai_33, 0) + b_Ai_33 = tl.where((o_i == i - 32)[:, None], b_a_33, b_Ai_33) + for i in range(48 + 2, min(64, T - i_t * BT)): + b_a_44 = -tl.load(A + (i_t * BT + i) * H*BT + o_i + 48) + b_a_44 += tl.sum(b_a_44[:, None] * b_Ai_44, 0) + b_Ai_44 = tl.where((o_i == i - 48)[:, None], b_a_44, b_Ai_44) + b_Ai_11 += m_I + b_Ai_22 += m_I + b_Ai_33 += m_I + b_Ai_44 += m_I + + if not USE_TMA: + p_A_21 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 16, 0), (16, 16), (1, 0)) + p_A_31 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 32, 0), (16, 16), (1, 0)) + p_A_32 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 32, 16), (16, 16), (1, 0)) + p_A_41 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 48, 0), (16, 16), (1, 0)) + p_A_42 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 48, 16), (16, 16), (1, 0)) + p_A_43 = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + 48, 32), (16, 16), (1, 0)) + b_A_21 = tl.load(p_A_21, boundary_check=(0, 1)).to(tl.float32) + b_A_31 = tl.load(p_A_31, boundary_check=(0, 1)).to(tl.float32) + b_A_32 = tl.load(p_A_32, boundary_check=(0, 1)).to(tl.float32) + b_A_41 = tl.load(p_A_41, boundary_check=(0, 1)).to(tl.float32) + b_A_42 = tl.load(p_A_42, boundary_check=(0, 1)).to(tl.float32) + b_A_43 = tl.load(p_A_43, boundary_check=(0, 1)).to(tl.float32) + else: + b_A_21 = desc.load([i_t * BT + 16, 0]).to(tl.float32) + b_A_31 = desc.load([i_t * BT + 32, 0]).to(tl.float32) + b_A_32 = desc.load([i_t * BT + 32, 16]).to(tl.float32) + b_A_41 = desc.load([i_t * BT + 48, 0]).to(tl.float32) + b_A_42 = desc.load([i_t * BT + 48, 16]).to(tl.float32) + b_A_43 = desc.load([i_t * BT + 48, 32]).to(tl.float32) + + b_Ai_21 = -tl.dot(tl.dot(b_Ai_22, b_A_21, input_precision=DOT_PRECISION), b_Ai_11, input_precision=DOT_PRECISION) + b_Ai_32 = -tl.dot(tl.dot(b_Ai_33, b_A_32, input_precision=DOT_PRECISION), b_Ai_22, input_precision=DOT_PRECISION) + b_Ai_43 = -tl.dot(tl.dot(b_Ai_44, b_A_43, input_precision=DOT_PRECISION), b_Ai_33, input_precision=DOT_PRECISION) + + b_Ai_31 = -tl.dot( + b_Ai_33, + tl.dot(b_A_31, b_Ai_11, input_precision=DOT_PRECISION) + + tl.dot(b_A_32, b_Ai_21, input_precision=DOT_PRECISION), + input_precision=DOT_PRECISION, + ) + b_Ai_42 = -tl.dot( + b_Ai_44, + tl.dot(b_A_42, b_Ai_22, input_precision=DOT_PRECISION) + + tl.dot(b_A_43, b_Ai_32, input_precision=DOT_PRECISION), + input_precision=DOT_PRECISION, + ) + b_Ai_41 = -tl.dot( + b_Ai_44, + tl.dot(b_A_41, b_Ai_11, input_precision=DOT_PRECISION) + + tl.dot(b_A_42, b_Ai_21, input_precision=DOT_PRECISION) + + tl.dot(b_A_43, b_Ai_31, input_precision=DOT_PRECISION), + input_precision=DOT_PRECISION, + ) + + if not USE_TMA: + p_Ai_11 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT, 0), (16, 16), (1, 0)) + p_Ai_22 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 16, 16), (16, 16), (1, 0)) + p_Ai_33 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 32, 32), (16, 16), (1, 0)) + p_Ai_44 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 48, 48), (16, 16), (1, 0)) + p_Ai_21 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 16, 0), (16, 16), (1, 0)) + p_Ai_31 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 32, 0), (16, 16), (1, 0)) + p_Ai_32 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 32, 16), (16, 16), (1, 0)) + p_Ai_41 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 48, 0), (16, 16), (1, 0)) + p_Ai_42 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 48, 16), (16, 16), (1, 0)) + p_Ai_43 = tl.make_block_ptr(Ai, (T, BT), (H*BT, 1), (i_t * BT + 48, 32), (16, 16), (1, 0)) + tl.store(p_Ai_11, b_Ai_11.to(p_Ai_11.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_22, b_Ai_22.to(p_Ai_22.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_33, b_Ai_33.to(p_Ai_33.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_44, b_Ai_44.to(p_Ai_44.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_21, b_Ai_21.to(p_Ai_21.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_31, b_Ai_31.to(p_Ai_31.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_32, b_Ai_32.to(p_Ai_32.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_41, b_Ai_41.to(p_Ai_41.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_42, b_Ai_42.to(p_Ai_42.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + tl.store(p_Ai_43, b_Ai_43.to(p_Ai_43.dtype.element_ty, fp_downcast_rounding="rtne"), boundary_check=(0, 1)) + else: + desc_o.store([i_t * BT + 0, 0], b_Ai_11.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 16, 16], b_Ai_22.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 32, 32], b_Ai_33.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 48, 48], b_Ai_44.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 16, 0], b_Ai_21.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 32, 0], b_Ai_31.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 32, 16], b_Ai_32.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 48, 0], b_Ai_41.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 48, 16], b_Ai_42.to(desc_o.dtype, fp_downcast_rounding="rtne")) + desc_o.store([i_t * BT + 48, 32], b_Ai_43.to(desc_o.dtype, fp_downcast_rounding="rtne")) + + +@input_guard +def solve_tril( + A: torch.Tensor, + cu_seqlens: torch.Tensor | None = None, + output_dtype: torch.dtype = torch.float, +) -> torch.Tensor: + """ + Compute the inverse of the matrix I + A + A should be strictly lower triangular, i.e., A.triu() == 0. + + Args: + A (torch.Tensor): + [B, T, H, BT], where BT should only be 16, 32, or 64. + cu_seqlens (torch.Tensor): + The cumulative sequence lengths of the input tensor. Default: `None`. + output_dtype (torch.dtype): + The dtype of the output tensor. Default: `torch.float`. + If `None`, the output dtype will be the same as the input dtype. + + Returns: + (I + A)^-1 with the same shape as A + """ + assert A.shape[-1] in [16, 32, 64] + output_dtype = A.dtype if output_dtype is None else output_dtype + + B, T, H, BT = A.shape + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None + NT = len(chunk_indices) if cu_seqlens is not None else triton.cdiv(T, BT) + + Ai = torch.zeros_like(A, dtype=output_dtype) + if BT == 16: + merge_fn = solve_tril_16x16_kernel + elif BT == 32: + merge_fn = merge_16x16_to_32x32_inverse_kernel + elif BT == 64: + merge_fn = merge_16x16_to_64x64_inverse_kernel + + merge_fn[NT, B * H]( + A=A, + Ai=Ai, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + BT=BT, + USE_TMA=is_tma_supported, + DOT_PRECISION=FLA_TRIL_PRECISION, + ) + return Ai diff --git a/code/flash-linear-attention/fla/utils.py b/code/flash-linear-attention/fla/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..6d21544e6db4ca667d8c29507fe742230694e8dc --- /dev/null +++ b/code/flash-linear-attention/fla/utils.py @@ -0,0 +1,467 @@ + +import contextlib +import functools +import inspect +import logging +import os +import sys +import warnings +from collections.abc import Callable +from enum import Enum +from functools import lru_cache +from typing import TYPE_CHECKING, Any + +import torch +import triton +from packaging import version + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from fla import __version__ + +FLA_CI_ENV = os.getenv("FLA_CI_ENV") == "1" +FLA_CACHE_RESULTS = os.getenv('FLA_CACHE_RESULTS', '1') == '1' + + +supports_autotune_cache = "cache_results" in inspect.signature(triton.autotune).parameters +autotune_cache_kwargs = {"cache_results": FLA_CACHE_RESULTS} if supports_autotune_cache else {} + + +@lru_cache(maxsize=1) +def check_environments(): + """ + Checks the current operating system, Triton version, and Python version, + issuing warnings if they don't meet recommendations. + This function's body only runs once due to lru_cache. + """ + # Check Operating System + if sys.platform == 'win32': + logger.warning( + "Detected Windows operating system. Triton does not have an official Windows release, " + "thus FLA will not be adapted for Windows, and any potential errors will not be fixed. " + "Please consider using a Linux environment for compatibility.", + ) + + triton_version = version.parse(triton.__version__) + required_triton_version = version.parse("3.2.0") + + if triton_version < required_triton_version: + logger.warning( + f"Current Triton version {triton_version} is below the recommended 3.2.0 version. " + "Errors may occur and these issues will not be fixed. " + "Please consider upgrading Triton.", + ) + + # Check Python version + py_version = version.parse(f"{sys.version_info.major}.{sys.version_info.minor}") + required_py_version = version.parse("3.11") + + if py_version < required_py_version: + logger.warning( + f"Current Python version {py_version} is below the recommended 3.11 version. " + "It is recommended to upgrade to Python 3.11 or higher for the best experience.", + ) + + return None + + +check_environments() + + +def get_abs_err(x, y): + return (x.detach()-y.detach()).flatten().abs().max().item() + + +def get_err_ratio(x, y): + err = (x.detach()-y.detach()).flatten().square().mean().sqrt().item() + base = (x.detach()).flatten().square().mean().sqrt().item() + return err / (base + 1e-8) + + +def assert_close(prefix, ref, tri, ratio, warning=False, err_atol=1e-6): + abs_atol = get_abs_err(ref, tri) + msg = f"{prefix:>16} diff: {abs_atol:.6f} ratio: {get_err_ratio(ref, tri):.6f}" + logger.info(msg) + error_rate = get_err_ratio(ref, tri) + if abs_atol <= err_atol: + return + if warning or (FLA_CI_ENV and (error_rate < 0.01 or abs_atol <= 0.3)): + if error_rate > ratio: + warnings.warn(msg) + else: + assert error_rate < ratio, msg + + +def tensor_cache( + fn: Callable[..., torch.Tensor], +) -> Callable[..., torch.Tensor]: + """ + A decorator that caches the most recent result of a function with tensor inputs. + + This decorator will store the output of the decorated function for the most recent set of input tensors. + If the function is called again with the same input tensors, it will return the cached result. + + + Args: + fn (Callable[..., torch.Tensor]): + The function to be decorated. It should take tensor inputs and return tensor outputs. + + Returns: + Callable[..., torch.Tensor]: + A wrapped version of the input function with single-entry caching. + """ + last_args: tuple | None = None + last_kwargs: dict | None = None + last_result: Any = None + + @functools.wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> Any: + nonlocal last_args, last_kwargs, last_result + + if last_args is not None and last_kwargs is not None: + if len(args) == len(last_args) and len(kwargs) == len(last_kwargs): + if all(a is b for a, b in zip(args, last_args, strict=False)) and \ + all(k in last_kwargs and v is last_kwargs[k] for k, v in kwargs.items()): + return last_result + + result = fn(*args, **kwargs) + last_args, last_kwargs, last_result = args, kwargs, result + return result + + return wrapper + + +def input_guard( + fn: Callable[..., torch.Tensor], +) -> Callable[..., torch.Tensor]: + """ + A decorator to make sure all input tensors are contiguous and set the device based on input tensors. + """ + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + contiguous_args = (i if not isinstance(i, torch.Tensor) else i.contiguous() for i in args) + contiguous_kwargs = {k: (v if not isinstance(v, torch.Tensor) else v.contiguous()) for k, v in kwargs.items()} + + tensor = None + for arg in args: + if isinstance(arg, torch.Tensor): + tensor = arg + break + if tensor is None: + for value in kwargs.values(): + if isinstance(value, torch.Tensor): + tensor = value + break + + if tensor is not None: + ctx = custom_device_ctx(tensor.device.index) + else: + ctx = contextlib.nullcontext() + + with ctx: + return fn(*contiguous_args, **contiguous_kwargs) + + return wrapper + + +contiguous = input_guard + + +def require_version(version, hint): + """ + Perform a runtime check of the dependency versions, using the exact same syntax used by pip. + """ + def decorator(fn): + @functools.wraps(fn) + def wrapper(ctx, *args, **kwargs): + from transformers.utils.versions import require_version + require_version(version, hint) + return fn(ctx, + *(i if not isinstance(i, torch.Tensor) else i.contiguous() for i in args), + **{k: (v if not isinstance(v, torch.Tensor) else v.contiguous()) for k, v in kwargs.items()}) + return wrapper + return decorator + + +class Action(Enum): + NONE = "none" + NOTIFY = "notify" + NOTIFY_ALWAYS = "notify_always" + RAISE = "raise" + + +def deprecate_kwarg( + old_name: str, + version: str, + new_name: str | None = None, + warn_if_greater_or_equal_version: bool = False, + raise_if_greater_or_equal_version: bool = False, + raise_if_both_names: bool = False, + additional_message: str | None = None, +): + """ + Decorator to notify users about deprecated keyword arguments, replacing them with a new name if specified. + + This decorator allows you to: + - Notify users when a keyword argument is deprecated. + - Automatically replace deprecated keyword arguments with new ones. + - Raise an error if deprecated arguments are used, depending on the specified conditions. + + By default, the decorator notifies the user about the deprecated argument while the `fla.__version__` < specified `version` + in the decorator. To keep notifications with any version `warn_if_greater_or_equal_version=True` can be set. + + Args: + old_name (`str`): + Name of the deprecated keyword argument. + version (`str`): + The version in which the keyword argument was (or will be) deprecated. + new_name (`Optional[str]`, *optional*): + The new name for the deprecated keyword argument. + If specified, the deprecated keyword argument will be replaced with this new name. + warn_if_greater_or_equal_version (`bool`, *optional*, defaults to `False`): + Whether to show warning if current `fla` version is greater or equal to the deprecated version. + raise_if_greater_or_equal_version (`bool`, *optional*, defaults to `False`): + Whether to raise `ValueError` if current `fla` version is greater or equal to the deprecated version. + raise_if_both_names (`bool`, *optional*, defaults to `False`): + Whether to raise `ValueError` if both deprecated and new keyword arguments are set. + additional_message (`Optional[str]`, *optional*): + An additional message to append to the default deprecation message. + + Raises: + ValueError: + If `raise_if_greater_or_equal_version` is `True` and the current version >= the deprecated one, + or if `raise_if_both_names` is `True` and both old and new keyword arguments are provided. + + Returns: + Callable: + A wrapped function that handles the deprecated keyword arguments according to the specified parameters. + + Example usage with renaming argument: + + ```python + @deprecate_kwarg("reduce_labels", new_name="do_reduce_labels", version="6.0.0") + def my_function(do_reduce_labels): + print(do_reduce_labels) + + my_function(reduce_labels=True) # Will show a deprecation warning and use do_reduce_labels=True + ``` + + Example usage without renaming argument: + + ```python + @deprecate_kwarg("max_size", version="6.0.0") + def my_function(max_size): + print(max_size) + + my_function(max_size=1333) # Will show a deprecation warning + ``` + + """ + deprecated_version = version.parse(version) + current_version = version.parse(__version__) + is_greater_or_equal_version = current_version >= deprecated_version + + if is_greater_or_equal_version: + version_message = f"and removed starting from version {version}" + else: + version_message = f"and will be removed in version {version}" + + def wrapper(func): + # Required for better warning message + sig = inspect.signature(func) + function_named_args = set(sig.parameters.keys()) + is_instance_method = "self" in function_named_args + is_class_method = "cls" in function_named_args + + @functools.wraps(func) + def wrapped_func(*args, **kwargs): + # Get class + function name (just for better warning message) + func_name = func.__name__ + if is_instance_method: + func_name = f"{args[0].__class__.__name__}.{func_name}" + elif is_class_method: + func_name = f"{args[0].__name__}.{func_name}" + + minimum_action = Action.NONE + message = None + + # deprecated kwarg and its new version are set for function call -> replace it with new name + if old_name in kwargs and new_name in kwargs: + minimum_action = Action.RAISE if raise_if_both_names else Action.NOTIFY_ALWAYS + message = ( + f"Both `{old_name}` and `{new_name}` are set for `{func_name}`. " + f"Using `{new_name}={kwargs[new_name]}` and ignoring deprecated `{old_name}={kwargs[old_name]}`." + ) + kwargs.pop(old_name) + + # only deprecated kwarg is set for function call -> replace it with new name + elif old_name in kwargs and new_name is not None and new_name not in kwargs: + minimum_action = Action.NOTIFY + message = ( + f"`{old_name}` is deprecated {version_message} for `{func_name}`. " + f"Use `{new_name}` instead." + ) + kwargs[new_name] = kwargs.pop(old_name) + + # deprecated kwarg is not set for function call and new name is not specified -> just notify + elif old_name in kwargs: + minimum_action = Action.NOTIFY + message = f"`{old_name}` is deprecated {version_message} for `{func_name}`." + + if message is not None and additional_message is not None: + message = f"{message} {additional_message}" + + # update minimum_action if argument is ALREADY deprecated (current version >= deprecated version) + if is_greater_or_equal_version: + # change to (NOTIFY, NOTIFY_ALWAYS) -> RAISE if specified + # in case we want to raise error for already deprecated arguments + if raise_if_greater_or_equal_version and minimum_action != Action.NONE: + minimum_action = Action.RAISE + + # change to NOTIFY -> NONE if specified (NOTIFY_ALWAYS can't be changed to NONE) + # in case we want to ignore notifications for already deprecated arguments + elif not warn_if_greater_or_equal_version and minimum_action == Action.NOTIFY: + minimum_action = Action.NONE + + # raise error or notify user + if minimum_action == Action.RAISE: + raise ValueError(message) + elif minimum_action in (Action.NOTIFY, Action.NOTIFY_ALWAYS): + # DeprecationWarning is ignored by default, so we use FutureWarning instead + warnings.warn(message, FutureWarning, stacklevel=2) + + return func(*args, **kwargs) + + return wrapped_func + + return wrapper + + +def checkpoint(fn): + def wrapper(*args, **kwargs): + return torch.utils.checkpoint.checkpoint(fn, *args, **kwargs) + return wrapper + + +@functools.cache +def check_pytorch_version(version_s: str = '2.4') -> bool: + return version.parse(torch.__version__) >= version.parse(version_s) + + +def _cpu_device_warning(): + warnings.warn(('Triton is not supported on current platform, roll back to CPU.'), stacklevel=1) + + +@functools.cache +def get_multiprocessor_count(tensor_idx: int = 0) -> int: + try: + return triton.runtime.driver.active.utils.get_device_properties(tensor_idx)['multiprocessor_count'] + except BaseException: + # Maybe we use a NPU device. + if triton.runtime.driver.active.get_current_target().backend == 'npu': + return triton.runtime.driver.active.utils.get_device_properties(tensor_idx)['num_vectorcore'] + else: + return 1 + + +@functools.cache +def get_available_device() -> str: + try: + return triton.runtime.driver.active.get_current_target().backend + except BaseException: + _cpu_device_warning() + return 'cpu' + + +def map_triton_backend_to_torch_device() -> str: + backend = get_available_device() # 'cuda' | 'hip' | 'xpu' | 'cpu' | ... + return {'cuda': 'cuda', 'hip': 'cuda', 'xpu': 'xpu'}.get(backend, backend) + + +# For AMD GPUs, the triton backend is 'hip', while for Nvidia GPUs, the triton backend is 'cuda'. +# However, the torch backend is 'cuda' for both Nvidia and AMD GPUs. +# Therefore, we need to check the triton backend to determine the actual GPU vendor. +device = get_available_device() if get_available_device() != 'hip' else 'cuda' +device_torch_lib = getattr(torch, device) +device_platform = get_available_device() +device_name = map_triton_backend_to_torch_device() + +is_amd = (device_platform == 'hip') +is_intel = (device_platform == 'xpu') +is_nvidia = (device_platform == 'cuda') +is_intel_alchemist = (is_intel and 'Intel(R) Arc(TM) A' in torch.xpu.get_device_name(0)) +is_nvidia_hopper = (is_nvidia and ('NVIDIA H' in torch.cuda.get_device_name(0) or torch.cuda.get_device_capability()[0] >= 9)) +use_cuda_graph = (is_nvidia and os.environ.get('FLA_USE_CUDA_GRAPH', '0') == '1') + +# Nvidia Ampere or newer, haven't check AMD and intel yet. +is_tf32_supported = (is_nvidia and torch.cuda.get_device_capability(0)[0] >= 8) +is_gather_supported = hasattr(triton.language, 'gather') +is_tma_supported = (is_nvidia and torch.cuda.get_device_capability(0)[0] >= 9) \ + and os.environ.get('FLA_USE_TMA', '0') == '1' and \ + (hasattr(triton.language, '_experimental_make_tensor_descriptor') or hasattr(triton.language, 'make_tensor_descriptor')) + +if is_nvidia and not is_tf32_supported: + # Make old card happy, since triton will use tf32 by default. + # This is a workaround for old nvidia card. + os.environ['TRITON_F32_DEFAULT'] = 'ieee' + +if is_tma_supported: + logger.info('TMA is supported, using TMA by default.') + + def alloc_fn(size: int, alignment: int, stream: int | None): + return torch.empty(size, device=torch.device(device_name, device_torch_lib.current_device()), dtype=torch.int8) + + triton.set_allocator(alloc_fn) + + +def get_all_max_shared_mem(): + try: + return [ + triton.runtime.driver.active.utils.get_device_properties(i)['max_shared_mem'] + for i in range(device_torch_lib.device_count()) + ] + except BaseException: + _cpu_device_warning() + return [-1] + + +class Backend(Enum): + ADA = 101376 # RTX 4090 + AMPERE = 166912 # A100 + HOPPER = 232448 # H100 + DEFAULT = 102400 # Default + + @classmethod + def get_shared_memory(cls, arch: str) -> int: + try: + return cls[arch.upper()].value + except KeyError: + return cls.DEFAULT.value + + +@functools.cache +def check_shared_mem(arch: str = "none", tensor_idx: int = 0) -> bool: + try: + device_shared_mem_list = get_all_max_shared_mem() + max_shared_memory = device_shared_mem_list[tensor_idx] + return max_shared_memory >= Backend.get_shared_memory(arch) + except Exception: + return False + + +if check_pytorch_version('2.4'): + device = 'cuda' if device == 'cpu' else device + autocast_custom_fwd = functools.partial(torch.amp.custom_fwd, device_type=device) + autocast_custom_bwd = functools.partial(torch.amp.custom_bwd, device_type=device) + + def custom_device_ctx(index: int): + return device_torch_lib.device(index) +else: + assert device == 'cuda', 'Only cuda device is supported for PyTorch version < 2.4.0.' + autocast_custom_fwd = device_torch_lib.amp.custom_fwd + autocast_custom_bwd = device_torch_lib.amp.custom_bwd + + def custom_device_ctx(index: int): + return torch.cuda.device(index) diff --git a/code/flash-linear-attention/legacy/training/README.md b/code/flash-linear-attention/legacy/training/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9a21de23407d265381ecfeca2bf35f0742e50182 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/README.md @@ -0,0 +1,179 @@ +
+ +# 🔥 Flame: Flash Linear Attention Made Easy + +
+ +> [!IMPORTANT] +> The `flame` project has been migrated to a new project built on torchtitan. +> Please visit the [new repository](https://github.com/fla-org/flame) for details and updates. +> +> The code here is now **archived as legacy**, and no future updates will be synchronized here. + +A minimal framework for training FLA models, whether from scratch or through finetuning. + +Built on the robust infrastructure of 🤗, `flame` enables you to train large language models with just a few lines of code: +we use `datasets` for data processing, `transformers` for model definitions, and `accelerate`[^1] for seamless distributed training. + +In this README, we will guide you through the process of using `flame` to train GLA models. + +## Setup + +To get started, you'll need to install the required packages. +Both `fla` and `flame` have minimal dependencies. +Clone the `fla` repository and install the necessary packages as follows: + +```bash +git clone https://github.com/sustcsonglin/flash-linear-attention.git +pip install . +pip install accelerate +``` + +> [!CAUTION] +> The 🤗 `tokenizers` have some [memory leak issues](https://github.com/huggingface/tokenizers/issues/1539) when processing very long documents. +> To address this, please ensure you install `tokenizers>=0.20.4`. + +## Preprocessing + +Before training, you need to download and pre-tokenize your dataset. +We provide a straightforward script for this. +For instance, to tokenize a 10B sample of the `fineweb-edu` dataset, run: + +```bash +python preprocess.py \ + --dataset HuggingFaceFW/fineweb-edu \ + --name sample-10BT \ + --split train \ + --context_length 2048 +``` + +This will cache the processed dataset at `data/HuggingFaceFW/fineweb-edu/sample-10BT/train`. + +GLA utilizes a subset of Slimpajama for pretraining [in the paper](https://proceedings.mlr.press/v235/yang24ab.html). +Given the size of the dataset, the fastest way to download it is using `git lfs` (refer to [this issue](https://huggingface.co/datasets/cerebras/SlimPajama-627B/discussions/2)). +```bash +git lfs install +git clone https://huggingface.co/datasets/cerebras/SlimPajama-627B --depth 1 +python preprocess.py \ + --dataset SlimPajama-627B \ + --split train \ + --context_length 2048 +``` + +## Training from scratch + +To train your 340M model from scratch, execute the following command: + +```bash +bash train.sh \ + type=gla \ + lr=3e-4 \ + scheduler=cosine_with_min_lr \ + batch=32 \ + update=1 \ + warmup=1024 \ + steps=20480 \ + context=2048 \ + gpus=8 \ + nodes=1 \ + path=exp/gla-340M-10B \ + project=fla \ + model=configs/gla_340M.json \ + data=HuggingFaceFW/fineweb-edu \ + name=sample-10BT \ + cache=data/HuggingFaceFW/fineweb-edu/sample-10BT/train +``` + +Key parameters: + +| | Description | Default | +| :-------- | :---------------------------- | -------------------- | +| lr | `learning_rate` | `3e-4` | +| scheduler | `lr_scheduler_type` | `cosine_with_min_lr` | +| batch | `batch_size` | `32` | +| update | `gradient_accumulation_steps` | `1` | +| context | `context_length` | `2048` | +| gpus | `num_gpus_per_node` | `8` | +| nodes | `num_nodes` | `1` | +| warmup | `warmup_steps` | `1024` | +| steps | `max_steps` | `20480` | + +The learning rate is set to `3e-4` by default, equipped with a cosine scheduler. +Other scheduler types like WSD (`warmup_stable_decay`)[^2] are also supported. + +The total number of tokens processed per batch, referred to as `global_batch_size`, is calculated as +`batch_size × gradient_accumulation_steps × context_length × num_gpus_per_node × num_nodes`. +For instance, in the 340M model example, the `global_batch_size` calculates to $32 \times 1 \times 2048 \times 8 \times 1 = 524,288$ (0.5M tokens). + +The `warmup_steps` parameter indicates the number of steps for the learning rate warmup phase, while `max_steps` represents the maximum number of training steps. +Each step processes `global_batch_size` tokens. +Consequently, `512` and `20480` correspond to processing 0.5B and 10B tokens, respectively. + +:warning: Monitor the value of `global_batch_size`, `warmup_steps`, and `max_steps` carefully when modifying any of the hyperparameters!! + +`flame` also supports resuming interrupted training by specifying the checkpoint path. +Simply use the following command: + +```bash +bash train.sh \ + type=gla \ + lr=3e-4 \ + steps=20480 \ + batch=32 \ + update=1 \ + warmup=1024 \ + context=2048 \ + gpus=8 \ + nodes=1 \ + path=exp/gla-340M-10B \ + project=fla \ + model=configs/gla_340M.json \ + data=HuggingFaceFW/fineweb-edu \ + name=sample-10BT \ + cache=data/HuggingFaceFW/fineweb-edu/sample-10BT/train \ + checkpoint=exp/gla-340M-10B/checkpoint-8192 +``` + +You can also use `wandb` to monitor your training process effectively. + +![wandb](https://github.com/user-attachments/assets/05ca031c-1cae-41c9-bfcb-5b6b6d0df729) + +## Continual Pretraining + +`flame` supports continual training from a pretrained checkpoint. +Below, we provide an example of how to finetune Mistral-7B to GLA. +You can follow similar steps to reproduce the results in the [GSA paper](https://arxiv.org/abs/2409.07146): + +1. Initialize a brand-new GLA-7B model from the config and copy the mathced pretrained weights from Mistral-7B: +```bash +cd ../utils +python convert_from_llama.py \ + --model mistralai/Mistral-7B-v0.1 \ + --config ../training/configs/gla_7B.json \ + --output ../training/converted/gla-7B +cd - +``` + +2. Directly launch training from the converted checkpoint: +```bash +bash train.sh \ + type=gla \ + lr=3e-5 \ + steps=10240 \ + batch=4 \ + update=8 \ + warmup=512 \ + context=2048 \ + path=exp/gla-7B-20B \ + project=fla \ + model=converted/gla-7B \ + data=SlimPajama-627B \ + cache=data/SlimPajama-627B/train +``` + +Please be aware that finetuning on a single node may not be the most efficient approach. +If available, consider leveraging multi-node GPUs for optimal performance. +You can find guidance on how to launch a multi-node job in the [accelerate tutorial](https://github.com/huggingface/accelerate/blob/main/examples/slurm/submit_multinode.sh). + +[^1]: The `accelerate` library supports various distributed frameworks, like `deepspeed` and `megatron` for large-scale training. We use `deepspeed` in our case. +[^2]: https://arxiv.org/abs/2404.06395 diff --git a/code/flash-linear-attention/legacy/training/configs/gla_1B.json b/code/flash-linear-attention/legacy/training/configs/gla_1B.json new file mode 100644 index 0000000000000000000000000000000000000000..95ef599456b186900c100958f12dc2a8660a6e40 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/configs/gla_1B.json @@ -0,0 +1,25 @@ +{ + "attn_mode": "chunk", + "bos_token_id": 1, + "clamp_min": null, + "eos_token_id": 2, + "expand_k": 0.5, + "expand_v": 1, + "fuse_cross_entropy": true, + "fuse_norm": true, + "hidden_act": "swish", + "hidden_ratio": 4, + "hidden_size": 2048, + "initializer_range": 0.02, + "intermediate_size": null, + "model_type": "gla", + "num_heads": 4, + "num_hidden_layers": 24, + "norm_eps": 1e-06, + "tie_word_embeddings": false, + "transformers_version": "4.45.0", + "use_cache": true, + "use_gk": true, + "use_gv": false, + "vocab_size": 32000 +} diff --git a/code/flash-linear-attention/legacy/training/configs/gla_340M.json b/code/flash-linear-attention/legacy/training/configs/gla_340M.json new file mode 100644 index 0000000000000000000000000000000000000000..bcb3fc3b02615ca0b5210f034e80936ded78a538 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/configs/gla_340M.json @@ -0,0 +1,24 @@ +{ + "attn_mode": "chunk", + "bos_token_id": 1, + "clamp_min": null, + "eos_token_id": 2, + "expand_k": 0.5, + "expand_v": 1, + "fuse_cross_entropy": true, + "fuse_norm": true, + "hidden_act": "swish", + "hidden_ratio": 4, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": null, + "model_type": "gla", + "num_heads": 4, + "num_hidden_layers": 24, + "norm_eps": 1e-06, + "tie_word_embeddings": true, + "use_cache": true, + "use_gk": true, + "use_gv": false, + "vocab_size": 32000 +} diff --git a/code/flash-linear-attention/legacy/training/configs/gla_7B.json b/code/flash-linear-attention/legacy/training/configs/gla_7B.json new file mode 100644 index 0000000000000000000000000000000000000000..c321d3d722a4feebcfd9256e32ecd13fd4cbe345 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/configs/gla_7B.json @@ -0,0 +1,28 @@ +{ + "attn_mode": "chunk", + "bos_token_id": 1, + "clamp_min": null, + "eos_token_id": 2, + "expand_k": 1, + "expand_v": 1, + "feature_map": "relu", + "fuse_cross_entropy": true, + "fuse_norm": true, + "hidden_act": "swish", + "hidden_ratio": 4, + "hidden_size": 4096, + "initializer_range": 0.02, + "intermediate_size": 14336, + "model_type": "gla", + "num_heads": 32, + "num_kv_heads": 8, + "num_hidden_layers": 32, + "norm_eps": 1e-05, + "tie_word_embeddings": false, + "transformers_version": "4.45.0", + "use_cache": true, + "use_output_gate": false, + "use_gk": true, + "use_gv": false, + "vocab_size": 32000 +} diff --git a/code/flash-linear-attention/legacy/training/configs/transformer_340M.json b/code/flash-linear-attention/legacy/training/configs/transformer_340M.json new file mode 100644 index 0000000000000000000000000000000000000000..08356de26f25c51c1c6136ca71d7701cc8516303 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/configs/transformer_340M.json @@ -0,0 +1,18 @@ +{ + "attention_bias": false, + "bos_token_id": 1, + "eos_token_id": 2, + "fuse_cross_entropy": true, + "fuse_norm": true, + "hidden_act": "swish", + "hidden_size": 1024, + "initializer_range": 0.02, + "max_position_embeddings": 8192, + "model_type": "transformer", + "num_heads": 16, + "num_hidden_layers": 24, + "norm_eps": 1e-06, + "tie_word_embeddings": true, + "use_cache": true, + "vocab_size": 32000 +} diff --git a/code/flash-linear-attention/legacy/training/flame/__init__.py b/code/flash-linear-attention/legacy/training/flame/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/code/flash-linear-attention/legacy/training/flame/data.py b/code/flash-linear-attention/legacy/training/flame/data.py new file mode 100644 index 0000000000000000000000000000000000000000..957349e23282b9c94c80298040379af2a69e2e9c --- /dev/null +++ b/code/flash-linear-attention/legacy/training/flame/data.py @@ -0,0 +1,247 @@ + +from __future__ import annotations + +from collections.abc import Iterable +from copy import deepcopy +from dataclasses import dataclass +from typing import Any + +import numpy as np +import torch +from datasets import Dataset, IterableDataset +from transformers import PreTrainedTokenizer + +from flame.logging import get_logger + +logger = get_logger(__name__) + + +class HuggingfaceDataset(IterableDataset): + + def __init__( + self, + dataset: Dataset, + tokenizer: PreTrainedTokenizer, + context_len: int = 2048, + rank: int = 0, + world_size: int = 1, + buffer_size: int = 1024, + ) -> HuggingfaceDataset: + + self.dataset = dataset + self.tokenizer = tokenizer + + self.data = dataset.shard(world_size, rank) + self.context_len = context_len + self.rank = rank + self.world_size = world_size + self.buffer_size = buffer_size + + if tokenizer.vocab_size < torch.iinfo(torch.int16).max: + self.dtype = torch.int16 + elif tokenizer.vocab_size < torch.iinfo(torch.int32).max: + self.dtype = torch.int32 + else: + self.dtype = torch.int64 + self.states = None + self.buffer = torch.tensor([], dtype=self.dtype) + self.tokens = [] + self.rand_id = 0 + self.token_id = 0 + self.rng_state = None + self._epoch = 0 + + def __iter__(self): + g = torch.Generator() + g.manual_seed(self._epoch + self.rank) + if self.rng_state is not None: + g.set_state(self.rng_state) + + rand_it = self.randint(0, self.buffer_size, g=g) + if self.states is not None: + self.data.load_state_dict(self.states) + + # max number of tokens allowed in the chunk buffer + n_tokens = self.buffer_size * self.context_len + + while True: + for sample in self.tokenize(self.data): + # keep appending the samples to the token buffer + self.tokens += sample + # if the token buffer is full, start sampling + # NOTE: we first convert the token ids to a tensor of shape [n_chunks, context_len] for efficiency + if len(self.buffer) == 0 and len(self.tokens) >= n_tokens: + self.buffer = torch.tensor(self.tokens[:n_tokens], dtype=self.dtype).view(self.buffer_size, -1) + self.tokens = self.tokens[n_tokens:] + if len(self.buffer) == self.buffer_size: + yield from self.sample(rand_it) + + n_chunks = len(self.tokens) // self.context_len + # handle the left tokens in the buffer + if n_chunks > 0: + n_tokens = n_chunks * self.context_len + indices = torch.randperm(n_chunks, generator=g).tolist() + self.buffer = torch.tensor(self.tokens[:n_tokens], dtype=torch.long).view(n_chunks, -1) + self.tokens = self.tokens[n_tokens:] + for i in indices: + yield {'input_ids': self.buffer[i]} + + def tokenize(self, data, batch_size: int = 64): + texts, states = [], [] + for sample in data: + texts.append(sample['text']) + states.append(self.data.state_dict()) + if len(texts) == batch_size: + for s, tokenized in zip(states, self.tokenizer(texts, return_attention_mask=False)['input_ids'], strict=False): + self.states = s + yield tokenized + texts, states = [], [] + if len(texts) > 0: + for s, tokenized in zip(states, self.tokenizer(texts, return_attention_mask=False)['input_ids'], strict=False): + self.states = s + yield tokenized + + def sample(self, indices): + n_tokens = (len(self.tokens) // self.context_len) * self.context_len + while self.token_id < n_tokens: + i = next(indices) + start, end = self.token_id, self.token_id + self.context_len + self.token_id += self.context_len + yield {'input_ids': self.buffer[i].to(torch.long)} + self.buffer[i] = torch.tensor(self.tokens[start:end], dtype=self.dtype) + self.token_id = 0 + self.tokens = self.tokens[n_tokens:] + + def randint( + self, + low: int, + high: int, + batch_size: int = 1024, + g: torch.Generator = torch.Generator(), + ) -> Iterable[int]: + indices = torch.empty(batch_size, dtype=torch.long) + while True: + # record the generator states before sampling + self.rng_state = g.get_state() + indices = torch.randint(low, high, (batch_size,), out=indices, generator=g) + for i in indices[self.rand_id:].tolist(): + self.rand_id += 1 + yield i + self.rand_id = 0 + + def set_epoch(self, epoch): + self._epoch = epoch + if hasattr(self.dataset, "set_epoch"): + self.dataset.set_epoch(epoch) + + def state_dict(self): + return { + 'states': self.states, + 'buffer': self.buffer.clone(), + 'tokens': deepcopy(self.tokens), + 'rand_id': self.rand_id, + 'token_id': self.token_id, + 'rng_state': self.rng_state, + 'epoch': self._epoch, + } + + def load_state_dict(self, state_dict): + self.states = state_dict['states'] + self.buffer = state_dict['buffer'].clone() + self.tokens = deepcopy(state_dict['tokens']) + self.rand_id = state_dict['rand_id'] + self.token_id = state_dict['token_id'] + self.rng_state = state_dict['rng_state'].clone() if state_dict['rng_state'] is not None else None + self._epoch = state_dict['epoch'] + + +@dataclass +class DataCollatorForLanguageModeling: + """ + Data collator used for language modeling. + + Args: + tokenizer ([`PreTrainedTokenizer`] or [`PreTrainedTokenizerFast`]): + The tokenizer used for encoding the data. + varlen (`bool`): + Whether to return sequences with variable lengths. + If `True`, the offsets indicating the start and end of each sequence will be returned. + For example, if the sequence lengths are `[4, 8, 12]`, + the returned `input_ids` will be a long flattened tensor of shape `[1, 24]`, with `offsets` being `[0, 4, 12, 24]`. + If `False`, the `input_ids` with shape `[batch_size, seq_len]` will be returned directly. + return_tensors (`str`): + The type of Tensor to return. Allowable values are "pt". + """ + + tokenizer: PreTrainedTokenizer + varlen: bool = False + return_tensors: str = "pt" + + def __call__( + self, + examples: list[list[int] | dict[str, Any]], + ) -> dict[str, Any]: + if not isinstance(examples[0], dict): + examples = [{'input_ids': example} for example in examples] + + def tensorize(example: dict[str, Any]) -> dict[str, Any]: + tensorized = {} + for key in ['input_ids', 'offsets']: + if key not in example: + continue + if isinstance(example[key], list): + tensorized[key] = torch.tensor(example[key], dtype=torch.long) + elif isinstance(example[key], np.ndarray): + tensorized[key] = torch.from_numpy(example[key]) + else: + tensorized[key] = example[key] + return tensorized + + examples = list(map(tensorize, examples)) + + if not self.varlen: + length_of_first = examples[0]['input_ids'].size(0) + # Check if padding is necessary. + if all(example['input_ids'].size(0) == length_of_first for example in examples): + batch = { + 'input_ids': torch.stack([example['input_ids'] for example in examples], dim=0), + } + else: + # If yes, check if we have a `pad_token`. + if self.tokenizer._pad_token is None: + raise ValueError( + f"You are attempting to pad samples but the tokenizer you are using " + f"({self.tokenizer.__class__.__name__}) does not have a pad token.", + ) + batch = self.tokenizer.pad(examples, return_tensors=self.return_tensors, return_attention_mask=False) + else: + if len(examples) > 1: + raise ValueError("The batch size must be 1 for variable length inputs.") + batch = { + 'input_ids': torch.cat([example['input_ids'] for example in examples], dim=0).unsqueeze(0), + } + if 'offsets' in examples[0]: + batch['offsets'] = torch.cat([example['offsets'] for example in examples], dim=0).unsqueeze(0) + else: + # determine boundaries by bos/eos positions + if self.tokenizer.add_bos_token: + offsets = [] + if batch['input_ids'][0, 0] != self.tokenizer.bos_token_id: + offsets.append(torch.tensor([0], dtype=torch.long)) + offsets.append(torch.where(batch['input_ids'].eq(self.tokenizer.bos_token_id))[1]) + offsets.append(torch.tensor([len(batch['input_ids'][0])], dtype=torch.long)) + batch['offsets'] = torch.cat(offsets, dim=0) + elif self.tokenizer.add_eos_token: + offsets = [torch.tensor([0], dtype=torch.long)] + offsets.append(torch.where(batch['input_ids'].eq(self.tokenizer.eos_token_id))[1] + 1) + if batch['input_ids'][0, -1] != self.tokenizer.eos_token_id: + offsets.append(torch.tensor([len(batch['input_ids'][0])], dtype=torch.long)) + batch['offsets'] = torch.cat(offsets, dim=0) + else: + raise ValueError("You must allow the tokenizer to add either a bos or eos token as separators.") + + labels = batch['input_ids'].clone() + if self.tokenizer.pad_token_id is not None: + labels[labels == self.tokenizer.pad_token_id] = -100 + batch["labels"] = labels + return batch diff --git a/code/flash-linear-attention/legacy/training/flame/logging.py b/code/flash-linear-attention/legacy/training/flame/logging.py new file mode 100644 index 0000000000000000000000000000000000000000..f85bf42291986f1ff3b08f8641e9fc18561b20b5 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/flame/logging.py @@ -0,0 +1,116 @@ + +import json +import logging +import os +import sys +import time + +from transformers.trainer_callback import ExportableState, TrainerCallback, TrainerControl, TrainerState +from transformers.training_args import TrainingArguments + + +def get_logger(name: str = None) -> logging.Logger: + formatter = logging.Formatter( + fmt="%(asctime)s - %(levelname)s - %(name)s - %(message)s", datefmt="%m/%d/%Y %H:%M:%S", + ) + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(formatter) + + logger = logging.getLogger(name) + if 'RANK' in os.environ and int(os.environ['RANK']) == 0: + logger.setLevel(logging.INFO) + logger.addHandler(handler) + + return logger + + +logger = get_logger(__name__) + +LOG_FILE_NAME = "trainer_log.jsonl" + + +class LogCallback(TrainerCallback, ExportableState): + def __init__(self, start_time: float = None, elapsed_time: float = None): + + self.start_time = time.time() if start_time is None else start_time + self.elapsed_time = 0 if elapsed_time is None else elapsed_time + self.last_time = self.start_time + + def on_train_begin( + self, + args: TrainingArguments, + state: TrainerState, + control: TrainerControl, + **kwargs, + ): + r""" + Event called at the beginning of training. + """ + if state.is_local_process_zero: + if not args.resume_from_checkpoint: + self.start_time = time.time() + self.elapsed_time = 0 + else: + self.start_time = state.stateful_callbacks['LogCallback']['start_time'] + self.elapsed_time = state.stateful_callbacks['LogCallback']['elapsed_time'] + + if args.save_on_each_node: + if not state.is_local_process_zero: + return + else: + if not state.is_world_process_zero: + return + + self.last_time = time.time() + if os.path.exists(os.path.join(args.output_dir, LOG_FILE_NAME)) and args.overwrite_output_dir: + logger.warning("Previous log file in this folder will be deleted.") + os.remove(os.path.join(args.output_dir, LOG_FILE_NAME)) + + def on_log( + self, + args: TrainingArguments, + state: TrainerState, + control: TrainerControl, + logs, + **kwargs, + ): + if args.save_on_each_node: + if not state.is_local_process_zero: + return + else: + if not state.is_world_process_zero: + return + + self.elapsed_time += time.time() - self.last_time + self.last_time = time.time() + if 'num_input_tokens_seen' in logs: + logs['num_tokens'] = logs.pop('num_input_tokens_seen') + state.log_history[-1].pop('num_input_tokens_seen') + throughput = logs['num_tokens'] / args.world_size / self.elapsed_time + state.log_history[-1]['throughput'] = logs['throughput'] = throughput + state.stateful_callbacks["LogCallback"] = self.state() + + logs = dict( + current_steps=state.global_step, + total_steps=state.max_steps, + loss=state.log_history[-1].get("loss", None), + eval_loss=state.log_history[-1].get("eval_loss", None), + predict_loss=state.log_history[-1].get("predict_loss", None), + learning_rate=state.log_history[-1].get("learning_rate", None), + epoch=state.log_history[-1].get("epoch", None), + percentage=round(state.global_step / state.max_steps * 100, 2) if state.max_steps != 0 else 100, + ) + + os.makedirs(args.output_dir, exist_ok=True) + with open(os.path.join(args.output_dir, "trainer_log.jsonl"), "a", encoding="utf-8") as f: + f.write(json.dumps(logs) + "\n") + + def state(self) -> dict: + return { + 'start_time': self.start_time, + 'elapsed_time': self.elapsed_time, + } + + @classmethod + def from_state(cls, state): + return cls(state['start_time'], state['elapsed_time']) diff --git a/code/flash-linear-attention/legacy/training/flame/parser.py b/code/flash-linear-attention/legacy/training/flame/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..b8d23e1630f7c0c2343d0213e3423236cb6e9817 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/flame/parser.py @@ -0,0 +1,92 @@ + +from __future__ import annotations + +from dataclasses import dataclass, field + +import transformers +from transformers import HfArgumentParser, TrainingArguments + +from flame.logging import get_logger + +logger = get_logger(__name__) + + +@dataclass +class TrainingArguments(TrainingArguments): + + model_name_or_path: str = field( + default=None, + metadata={ + "help": "Path to the model weight or identifier from huggingface.co/models or modelscope.cn/models.", + }, + ) + tokenizer: str = field( + default="fla-hub/gla-1.3B-100B", + metadata={"help": "Name of the tokenizer to use."}, + ) + use_fast_tokenizer: bool = field( + default=False, + metadata={"help": "Whether or not to use one of the fast tokenizer (backed by the tokenizers library)."}, + ) + from_config: bool = field( + default=True, + metadata={"help": "Whether to initialize models from scratch."}, + ) + dataset: str | None = field( + default=None, + metadata={"help": "The dataset(s) to use. Use commas to separate multiple datasets."}, + ) + dataset_name: str | None = field( + default=None, + metadata={"help": "The name of provided dataset(s) to use."}, + ) + cache_dir: str = field( + default=None, + metadata={"help": "Path to the cached tokenized dataset."}, + ) + split: str = field( + default="train", + metadata={"help": "Which dataset split to use for training and evaluation."}, + ) + streaming: bool = field( + default=False, + metadata={"help": "Enable dataset streaming."}, + ) + hf_hub_token: str | None = field( + default=None, + metadata={"help": "Auth token to log in with Hugging Face Hub."}, + ) + preprocessing_num_workers: int | None = field( + default=None, + metadata={"help": "The number of processes to use for the pre-processing."}, + ) + buffer_size: int = field( + default=2048, + metadata={"help": "Size of the buffer to randomly sample examples from in dataset streaming."}, + ) + context_length: int = field( + default=2048, + metadata={"help": "The context length of the tokenized inputs in the dataset."}, + ) + varlen: bool = field( + default=False, + metadata={"help": "Enable training with variable length inputs."}, + ) + + +def get_train_args(): + parser = HfArgumentParser(TrainingArguments) + args, unknown_args = parser.parse_args_into_dataclasses(return_remaining_strings=True) + + if unknown_args: + print(parser.format_help()) + print(f"Got unknown args, potentially deprecated arguments: {unknown_args}") + raise ValueError(f"Some specified arguments are not used by the HfArgumentParser: {unknown_args}") + + if args.should_log: + transformers.utils.logging.set_verbosity(args.get_process_log_level()) + transformers.utils.logging.enable_default_handler() + transformers.utils.logging.enable_explicit_format() + # set seeds manually + transformers.set_seed(args.seed) + return args diff --git a/code/flash-linear-attention/legacy/training/preprocess.py b/code/flash-linear-attention/legacy/training/preprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..353a9748bc070d046375e6816366198f9044dbf6 --- /dev/null +++ b/code/flash-linear-attention/legacy/training/preprocess.py @@ -0,0 +1,159 @@ + +from __future__ import annotations + +import argparse +from itertools import chain +from typing import Any + +import torch +from datasets import load_dataset +from transformers import AutoTokenizer +from transformers.utils import logging + +logger = logging.get_logger(__name__) + + +def tokenize( + examples: dict[str, list[Any]], + tokenizer: AutoTokenizer, + seq_len: int = 2048, + ctx_len: int = None, + return_offsets: bool = False, +) -> dict[str, list[list[int]]]: + """ + Tokenize the input text and split into chunks of specified context length. + + Args: + examples: + Dictionary containing the input text. + tokenizer: + Initialized tokenizer. + seq_len: + Total sequence length for each training sample. Default: 2048. + ctx_len: + Max contiguous length to preserve (will not be split). Default: `None`. + return_offsets: + Return cumulative offsets for concatenated inputs. Default: `False`. + + Returns: + Dictionary containing tokenized and chunked input ids, and optionally offsets. + """ + text = examples['text'] + input_ids = tokenizer(text)['input_ids'] + # further split each input into chunks of length `ctx_len` if provided + if ctx_len is not None: + input_ids = [seq[i:i+ctx_len] for seq in input_ids for i in range(0, len(seq), ctx_len)] + lens = torch.tensor([len(seq) for seq in input_ids]).cumsum(0) + total_len = lens[-1] // seq_len * seq_len + + input_ids = list(chain(*input_ids)) + # each yielded sample is of length `seq_len` + input_ids = [input_ids[i:i+seq_len] for i in range(0, total_len, seq_len)] + + if not return_offsets: + return {'input_ids': input_ids} + + # insert boundaries into cumulative offsets + offsets = torch.cat((lens, torch.arange(0, total_len, seq_len))).unique().sort()[0] % seq_len + # split offsets according the start positions + offsets = [i.tolist() + [seq_len] for i in offsets.tensor_split(torch.where(offsets.eq(0))[0][1:])][:len(input_ids)] + return {'input_ids': input_ids, 'offsets': offsets} + + +def preprocess( + dataset: str, + name: str | None = None, + split: str = 'train', + seed: int = 42, + output: str = 'data', + tokenizer: str = 'fla-hub/gla-1.3B-100B', + num_proc: int = 64, + batch_size: int = 2048, + seq_len: int = 2048, + ctx_len: int = None, + return_offsets: bool = False, +) -> None: + """ + Load, tokenize, and save the processed dataset. + + Args: + dataset: + Path or name of the dataset. Default: 'HuggingFaceFW/fineweb-edu'. + name: + Name of the dataset configuration. Default: `None`. + split: + Dataset split to process. Default: 'train'. + seed: + Random seed for shuffling the dataset. Default: 42. + output: + Output directory. Default: 'data'. + tokenizer: + Tokenizer name. Default: 'fla-hub/gla-1.3B-100B'. + num_proc: + Number of processes for parallel processing. Default: 64. + batch_size: + Batch size for processing. Default: 2048. + seq_len: + Total sequence length for each training sample. Default: 2048. + ctx_len: + Max contiguous length to preserve (will not be split). Default: `None`. + return_offsets: + Return cumulative offsets for concatenated inputs. Default: `False`. + """ + tokenized_path = f'{output}/{dataset}/{name}/{split}' if name is not None else f'{output}/{dataset}/{split}' + + if ctx_len is not None and ctx_len > seq_len: + raise ValueError(f'ctx_len ({ctx_len}) must be less than or equal to seq_len ({seq_len})') + + logger.info(f'Loading tokenizer {tokenizer}') + tokenizer = AutoTokenizer.from_pretrained(tokenizer, trust_remote_code=True) + logger.info(f'Tokenizer initialized:\n {tokenizer}') + + logger.info(f'Loading dataset: {dataset}') + dataset = load_dataset(dataset, name=name, split=split) + dataset = dataset.shuffle(seed=seed) + logger.info(f'Dataset loaded: {dataset}') + + remove_columns = list(next(iter(dataset)).keys()) + logger.info(f'Tokenizing and processing the dataset with batch size {batch_size}') + dataset = dataset.map( + lambda examples: tokenize(examples, tokenizer, seq_len, ctx_len, return_offsets), + batched=True, + batch_size=batch_size, + remove_columns=remove_columns, + num_proc=num_proc, + desc="Running tokenizer on dataset", + ) + + logger.info(f'Saving processed dataset to {tokenized_path}') + dataset.save_to_disk(tokenized_path, num_proc=num_proc) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Preprocess and tokenize dataset") + parser.add_argument("--dataset", default="HuggingFaceFW/fineweb-edu", help="Path or name of the dataset") + parser.add_argument("--name", default=None, help="Name of the dataset configuration") + parser.add_argument("--split", default="train", help="Dataset split to process") + parser.add_argument("--seed", type=int, default=42, help="Random seed") + parser.add_argument("--output", default="data", help="Output directory") + parser.add_argument("--tokenizer", default="fla-hub/gla-1.3B-100B", help="Tokenizer name") + parser.add_argument("--num_proc", type=int, default=64, help="Number of processes for parallel processing") + parser.add_argument("--batch_size", type=int, default=2048, help="Batch size for processing") + parser.add_argument("--seq_len", type=int, default=2048, help="Total sequence length for each training sample") + parser.add_argument("--ctx_len", type=int, default=None, help="Max contiguous length to preserve (will not be split)") + parser.add_argument("--return_offsets", action="store_true", help="Return cumulative offsets for concatenated inputs") + args = parser.parse_args() + + preprocess( + dataset=args.dataset, + name=args.name, + split=args.split, + seed=args.seed, + output=args.output, + tokenizer=args.tokenizer, + num_proc=args.num_proc, + batch_size=args.batch_size, + seq_len=args.seq_len, + ctx_len=args.ctx_len, + return_offsets=args.return_offsets, + ) diff --git a/code/flash-linear-attention/legacy/training/run.py b/code/flash-linear-attention/legacy/training/run.py new file mode 100644 index 0000000000000000000000000000000000000000..3c6d98c76e21577df154f161162b7a6cdcef0b5c --- /dev/null +++ b/code/flash-linear-attention/legacy/training/run.py @@ -0,0 +1,75 @@ + +from datasets import load_from_disk +from flame.data import DataCollatorForLanguageModeling +from flame.logging import LogCallback, get_logger +from flame.parser import get_train_args +from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer, Trainer + +import fla # noqa + +logger = get_logger(__name__) + + +def main(): + args = get_train_args() + logger.info(args) + + tokenizer = AutoTokenizer.from_pretrained( + args.tokenizer, + use_fast=args.use_fast_tokenizer, + trust_remote_code=True, + add_bos_token=True, + add_eos_token=False, + ) + if tokenizer.pad_token_id is None: + tokenizer.pad_token = tokenizer.eos_token + logger.info(f"Add pad token: {tokenizer.pad_token}") + if args.from_config: + logger.info("All model params are randomly initialized for from-scratch training.") + model = AutoModelForCausalLM.from_config(AutoConfig.from_pretrained(args.model_name_or_path)) + else: + logger.info(f"Loading pretrained checkpoint {args.model_name_or_path}") + model = AutoModelForCausalLM.from_pretrained(args.model_name_or_path) + model.train() + + trainable_params, all_param = model.num_parameters(only_trainable=True), model.num_parameters() + logger.info(f"% of trainable params: {trainable_params:d} / {all_param:d} = {trainable_params / all_param:.2%}") + logger.info(f"{tokenizer}\n{model}\n{model.config}") + + logger.info(f"Loading the `{args.split}` split directly from the cache {args.cache_dir}...") + dataset = load_from_disk(args.cache_dir) + logger.info(f"{dataset}") + logger.info(f"Shuffling the dataset with seed {args.seed}") + dataset = dataset.shuffle(seed=args.seed) + logger.info("Creating the data collator") + data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, varlen=args.varlen) + logger.info(f"{data_collator}") + + if args.lr_scheduler_type == 'cosine_with_min_lr': + args.lr_scheduler_kwargs = {'min_lr_rate': 0.1} + if args.lr_scheduler_type == 'warmup_stable_decay': + args.lr_scheduler_kwargs = { + 'num_stable_steps': args.max_steps * 0.9 - args.warmup_steps, + 'num_decay_steps': args.max_steps * 0.1, + } + + trainer = Trainer( + model=model, + args=args, + processing_class=tokenizer, + data_collator=data_collator, + callbacks=[LogCallback()], + train_dataset=dataset, + ) + + results = trainer.train(resume_from_checkpoint=args.resume_from_checkpoint) + trainer.save_model() + tokenizer.save_pretrained(trainer.args.output_dir) + + trainer.log_metrics("train", results.metrics) + trainer.save_metrics("train", results.metrics) + trainer.save_state() + + +if __name__ == "__main__": + main() diff --git a/code/flash-linear-attention/pyproject.toml b/code/flash-linear-attention/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..e72683cf169f97800688f93af04ab69b7f04215c --- /dev/null +++ b/code/flash-linear-attention/pyproject.toml @@ -0,0 +1,94 @@ +[project] +name = "flash-linear-attention" +dynamic = ["version"] +description = "Fast Triton-based implementations of causal linear attention" +readme = "README.md" +authors = [ + { name = "Songlin Yang", email = "yangsl66@mit.edu" }, + { name = "Yu Zhang", email = "yzhang.cs@outlook.com" }, +] +license = { file = "LICENSE" } +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering :: Artificial Intelligence", +] +requires-python = ">=3.10" +dependencies = [ + "torch", + "transformers", + "einops", +] + +[project.optional-dependencies] +conv1d = ["causal-conv1d>=1.4.0"] +benchmark = ["matplotlib", "datasets>=3.3.0"] +test = ["pytest"] + +[project.urls] +Homepage = "https://github.com/fla-org/flash-linear-attention" +Repository = "https://github.com/fla-org/flash-linear-attention" + +[build-system] +requires = ["setuptools>=45", "wheel"] + +[tool.ruff.lint.isort] +known-first-party = ["fla"] +force-sort-within-sections = false + +[tool.pytest.ini_options] +log_cli = true +log_cli_level = "INFO" +pythonpath = [ + "." +] + +[tool.ruff] +target-version = "py310" +line-length = 127 + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # Pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "SIM", # flake8-simplify + "I", # isort + "C4", # flake8-comprehensions + "TCH", # flake8-type-checking + "COM", # flake8-commas + "T", # flake8-debugger +] +ignore = [ + "E501", + "E741", + "B023", + "B006", + "B007", + "B008", + "B028", + "B904", + "C408", + "C416", + "C417", + "T201", + "TC002", + "TC003", + "SIM102", + "SIM108", + "SIM118", + "SIM211", +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] +"fla/utils.py" = ["TCH004"] +"evals/harness.py" = ["I", "TCH"] +"tests/*/*.py" = ["UP030"] +"scripts/*.py" = ["C414"] +"egacy/training/flame/*.py" = ["C408"] diff --git a/code/flash-linear-attention/scripts/build_packages.py b/code/flash-linear-attention/scripts/build_packages.py new file mode 100644 index 0000000000000000000000000000000000000000..85156953661883bb460b2ae877877e5ac2d7ab4b --- /dev/null +++ b/code/flash-linear-attention/scripts/build_packages.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python3 +"""Build split packages with proper dependency management and copy them to target directory.""" + +import ast +import re +import shutil +import subprocess +import sys +from pathlib import Path + + +def extract_dependencies(): + """Extract dependencies from setup.py using AST.""" + # Get script directory and find setup.py in parent directory + script_dir = Path(__file__).parent + setup_py = script_dir.parent / 'setup.py' + + with open(setup_py, encoding='utf-8') as f: + tree = ast.parse(f.read(), filename=str(setup_py)) + + all_deps = [] + extras = {} + + for node in ast.walk(tree): + if isinstance(node, ast.Call) and getattr(node.func, 'id', '') == 'setup': + for keyword in node.keywords: + if (keyword.arg == 'install_requires' and + isinstance(keyword.value, (ast.List, ast.Tuple))): + all_deps.extend([ + elt.value for elt in keyword.value.elts + if isinstance(elt, ast.Constant) and isinstance(elt.value, str) + ]) + elif (keyword.arg == 'extras_require' and + isinstance(keyword.value, ast.Dict)): + for key_node, val_node in zip(keyword.value.keys, keyword.value.values, strict=False): + if (isinstance(key_node, ast.Constant) and + isinstance(key_node.value, str) and + isinstance(val_node, (ast.List, ast.Tuple))): + key = key_node.value + values = [ + elt.value for elt in val_node.elts + if isinstance(elt, ast.Constant) and isinstance(elt.value, str) + ] + extras[key] = values + break # Assume only one setup() call + + return all_deps, extras + + +def categorize_dependencies(deps): + """Categorize dependencies based on core vs extension.""" + core_deps = [] + ext_deps = [] + + for dep in deps: + if any(core in dep for core in ['torch', 'einops']): + core_deps.append(dep) + else: + ext_deps.append(dep) + + return core_deps, ext_deps + + +def create_pyproject_toml(package_dir, name, version, dependencies, extras=None): + """Create pyproject.toml for a package.""" + if extras is None: + extras = {} + + extras_content = "" + if extras: + extras_content = "\n[project.optional-dependencies]\n" + for key, values in extras.items(): + values_str = ', '.join(f'"{v}"' for v in values) + extras_content += f"{key} = [{values_str}]\n" + + deps_content = ', '.join(f'"{dep}"' for dep in dependencies) + + # Create description text + if name == 'fla-core': + desc_text = 'Core operations for flash-linear-attention' + else: + desc_text = 'Fast linear attention models and layers' + + content = f"""[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "{name}" +version = "{version}" +description = "{desc_text}" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [{deps_content}] + +[project.urls] +Homepage = "https://github.com/fla-org/flash-linear-attention" +Repository = "https://github.com/fla-org/flash-linear-attention" +""" + + content += extras_content + + # Add setuptools namespace package configuration for extension package + if name == 'flash-linear-attention': + content += """ + +[tool.setuptools.packages.find] +include = ["fla*"] +namespaces = true +""" + + with open(package_dir / 'pyproject.toml', 'w') as f: + f.write(content) + + +def build_split_packages(): + """Build split packages with proper dependency management.""" + # Get script directory and find files relative to it + script_dir = Path(__file__).parent + root_dir = script_dir.parent + + # Get current version + init_file = root_dir / 'fla' / '__init__.py' + with open(init_file, encoding='utf-8') as f: + content = f.read() + version_match = re.search(r"^__version__\s*=\s*['\"]([^'\"]+)['\"]\s*$", content, re.MULTILINE) + if not version_match: + raise RuntimeError(f"Could not find __version__ in {init_file}") + version = version_match.group(1) + + # Extract dependencies + all_deps, extras = extract_dependencies() + core_deps, ext_deps = categorize_dependencies(all_deps) + + # Add version constraint for fla-core in extension package + ext_deps.insert(0, f'fla-core=={version}') + + # Create output directory + output_dir = script_dir / 'dist' + output_dir.mkdir(exist_ok=True) + + # Create fla-core package + core_dir = output_dir / 'fla-core' + if core_dir.exists(): + shutil.rmtree(core_dir) + core_dir.mkdir() + + # Copy core files + fla_core = core_dir / 'fla' + shutil.copytree(root_dir / 'fla' / 'ops', fla_core / 'ops') + shutil.copytree(root_dir / 'fla' / 'modules', fla_core / 'modules') + shutil.copy(root_dir / 'fla' / 'utils.py', fla_core / 'utils.py') + + # Create fla-core __init__.py + with open(fla_core / '__init__.py', 'w') as f: + f.write(f"""# -*- coding: utf-8 -*- + +__path__ = __import__('pkgutil').extend_path(__path__, __name__) +__version__ = '{version}' +""") + + # Copy ancillary files (README.md, LICENSE) to core package + for fname in ("README.md", "LICENSE"): + src = root_dir / fname + if src.exists(): + shutil.copy(src, core_dir / fname) + + # Create fla-core configs + create_pyproject_toml(core_dir, 'fla-core', version, core_deps) + + # Create flash-linear-attention package + ext_dir = output_dir / 'flash-linear-attention' + if ext_dir.exists(): + shutil.rmtree(ext_dir) + ext_dir.mkdir() + + # Copy extension files + fla_ext = ext_dir / 'fla' + shutil.copytree(root_dir / 'fla' / 'models', fla_ext / 'models') + shutil.copytree(root_dir / 'fla' / 'layers', fla_ext / 'layers') + + # Intentionally do NOT create fla/__init__.py in the extension package. + # The top-level package is provided by fla-core (namespace via pkgutil). + + # Copy ancillary files (README.md, LICENSE) to extension package + for fname in ("README.md", "LICENSE"): + src = root_dir / fname + if src.exists(): + shutil.copy(src, ext_dir / fname) + + # Create extension configs + create_pyproject_toml(ext_dir, 'flash-linear-attention', version, ext_deps, extras) + + # Create build script + build_script = output_dir / 'build.sh' + with open(build_script, 'w') as f: + f.write("""#!/bin/bash +# Build both packages + +echo "Building fla-core..." +cd fla-core +pip install -U build +python -m build + +echo "Building flash-linear-attention..." +cd ../flash-linear-attention +python -m build + +echo "Build complete! Packages in dist/" +""") + + build_script.chmod(0o755) + + print(f"✅ Split packages created in {output_dir}") + print(f"✅ fla-core dependencies: {len(core_deps)} packages") + print(f"✅ flash-linear-attention dependencies: {len(ext_deps)} packages") + print(f"✅ Version: {version}") + + return output_dir, version + + +def build_packages(dist_dir): + """Build wheels and source distributions for both packages.""" + print("Building packages...") + + # Build fla-core (both wheel and sdist) + print("Building fla-core packages...") + try: + subprocess.run( + [sys.executable, "-m", "build", str(dist_dir / "fla-core")], + check=True, + timeout=1800, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + except subprocess.CalledProcessError as e: + print("Failed to build fla-core packages:") + print(e.stdout) + return False + except subprocess.TimeoutExpired: + print("Timed out building fla-core packages") + return False + + # Build flash-linear-attention (both wheel and sdist) + print("Building flash-linear-attention packages...") + try: + subprocess.run( + [sys.executable, "-m", "build", str(dist_dir / "flash-linear-attention")], + check=True, + timeout=1800, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + except subprocess.CalledProcessError as e: + print("Failed to build flash-linear-attention packages:") + print(e.stdout) + return False + except subprocess.TimeoutExpired: + print("Timed out building flash-linear-attention packages") + return False + + print("✅ Packages built successfully") + return True + + +def copy_packages_to_output(dist_dir): + """Copy wheels and source distributions to output directory.""" + # Get script directory (relative to this file) + script_dir = Path(__file__).parent + root_dir = script_dir.parent + + # Create output directory (relative to root) + output_dir = root_dir / 'dist-packages' + output_dir.mkdir(exist_ok=True) + + # Find wheels and source distributions + core_wheels = list((dist_dir / 'fla-core' / 'dist').glob('*.whl')) + core_sdist = list((dist_dir / 'fla-core' / 'dist').glob('*.tar.gz')) + ext_wheels = list((dist_dir / 'flash-linear-attention' / 'dist').glob('*.whl')) + ext_sdist = list((dist_dir / 'flash-linear-attention' / 'dist').glob('*.tar.gz')) + + if not core_wheels: + print("No fla-core wheel found") + return False + if not ext_wheels: + print("No flash-linear-attention wheel found") + return False + + # Copy all packages to output directory + all_packages = core_wheels + core_sdist + ext_wheels + ext_sdist + for package in all_packages: + target = output_dir / package.name + shutil.copy2(package, target) + if package.suffix == ".whl": + package_type = "wheel" + elif package.suffixes[-2:] == [".tar", ".gz"]: + package_type = "sdist" + else: + package_type = "source" + print(f"📦 Copied {package_type} package {package.name} to {output_dir}") + + print(f"\n✅ All packages copied to: {output_dir}") + print("You can install wheels with:") + print(" pip install dist-packages/*.whl") + print("Source distributions are also available in:", output_dir) + + return True + + +def main(): + """Build split packages and copy to target directory.""" + + print("Building split packages...") + + # Build the split packages + dist_dir, _ = build_split_packages() + + print("\nTo build packages manually:") + print(f"cd {dist_dir}") + print("./build.sh") + + # Build packages (wheels and source distributions) + if not build_packages(dist_dir): + return 1 + + # Copy packages to output directory + if not copy_packages_to_output(dist_dir): + return 1 + + return 0 + + +if __name__ == "__main__": + exit(main()) diff --git a/code/flash-linear-attention/scripts/check_gpu.py b/code/flash-linear-attention/scripts/check_gpu.py new file mode 100644 index 0000000000000000000000000000000000000000..e8b281cb1cfbde0f163ea6849f854de95d979daa --- /dev/null +++ b/code/flash-linear-attention/scripts/check_gpu.py @@ -0,0 +1,83 @@ +import subprocess +import time + +from fla.utils import device_platform + + +def get_xpu_memory_usage(): + """Run `xpu-smi stats -d 0` and parse its output.""" + try: + result = subprocess.run( + ["xpu-smi", "stats", "-d", "0"], + capture_output=True, + text=True, + check=True, + ) + return extract_gpu_memory_used(result.stdout) + except subprocess.CalledProcessError as e: + print(f"Failed to run xpu-smi: {e}") + return None + + +def extract_gpu_memory_used(output): + """Extract 'GPU Memory Used (MiB)' from xpu-smi output.""" + # Use regex to find the line containing "GPU Memory Used (MiB)" + output = output.strip().replace(" ", "") + + prefix = "|GPUMemoryUsed(MiB)|" + start_idx = output.find(prefix) + value_start = start_idx + len(prefix) + end_idx = output.find("|", value_start+2) + value_str = output[value_start:end_idx-2].strip() + if value_str: + return int(value_str) + else: + print("Could not find GPU memory usage in xpu-smi output.") + return None + + +def get_nvgpu_memory_usage(): + result = subprocess.run( + ["nvidia-smi", "--query-gpu=memory.used", "--format=csv,noheader,nounits"], + capture_output=True, + text=True, + ) + memory_used = int(result.stdout.strip()) + return memory_used + + +def check_gpu_memory(): + max_memory_mib = 4096 # Threshold in MiB (4 GB) + max_wait_time = 3600 # 60 minutes in seconds + sleep_time = 30 # Sleep for 30 seconds + + start_time = time.time() + + while True: + + # Extract GPU memory usage + if device_platform == 'intel': + # memory_used_mib = get_xpu_memory_usage() + # since xpu-smi have conflicts in apt + memory_used_mib = 0 + elif device_platform == 'nvidia': + memory_used_mib = get_nvgpu_memory_usage() + if memory_used_mib is None: + exit(1) + + print(f"Current GPU memory usage: {memory_used_mib} MiB") + + if memory_used_mib > max_memory_mib: + print(f"GPU memory usage exceeds {max_memory_mib} MiB. Sleeping for {sleep_time} seconds...") + time.sleep(sleep_time) + else: + print("GPU memory usage is within limits.") + exit(0) + + if time.time() - start_time > max_wait_time: + print("GPU memory usage remains high for 10 minutes. Skipping this action.") + exit(1) + + +if __name__ == "__main__": + check_gpu_memory() diff --git a/code/flash-linear-attention/scripts/find_dependent_tests.py b/code/flash-linear-attention/scripts/find_dependent_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..a407c81438e85a1550f5ead522fd034d62e856b6 --- /dev/null +++ b/code/flash-linear-attention/scripts/find_dependent_tests.py @@ -0,0 +1,210 @@ +import ast +import os +import sys +from collections import defaultdict +from functools import cache +from pathlib import Path + +DEBUG_MODE = os.environ.get("DEBUG_MODE", "False").lower() in ("true", "1", "yes") +DEBUG_TEST_FILE = os.environ.get("DEBUG_TEST_FILE", "NULL").lower() + + +@cache +def parse_file(file_path): + try: + with open(file_path, encoding="utf-8") as f: + return ast.parse(f.read(), filename=file_path) + except (SyntaxError, FileNotFoundError, UnicodeDecodeError): + return None + + +def get_definitions_from_tree(tree) -> set: + if not tree: + return set() + definitions = set() + for node in tree.body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + definitions.add(node.name) + return definitions + + +def get_imports_from_tree(tree) -> set: + if not tree: + return set() + imports = set() + for node in tree.body: + if isinstance(node, ast.ImportFrom): + for alias in node.names: + imports.add(alias.asname or alias.name) + elif isinstance(node, ast.Import): + for alias in node.names: + imports.add(alias.asname or alias.name.split('.')[0]) + return imports + + +class DependencyFinder: + def __init__(self, search_dirs, test_dir): + self.test_dir = Path(test_dir).resolve() + models_test_dir = self.test_dir / "models" + + source_files = [p for s_dir in search_dirs for p in Path(s_dir).resolve().rglob("*.py") if p.name != '__init__.py'] + test_scope = os.environ.get("TEST_SCOPE", "ALL").upper() + if test_scope == "MODELS_ONLY": + test_files = [p for p in models_test_dir.rglob("*.py") if p.name != '__init__.py'] + elif test_scope == "EXCLUDE_MODELS": + all_files = self.test_dir.rglob("*.py") + test_files = [p for p in all_files if p.name != '__init__.py' and models_test_dir not in p.parents] + else: + test_files = [p for p in self.test_dir.rglob("*.py") if p.name != '__init__.py'] + self.all_project_files = source_files + test_files + self.all_test_files = set(test_files) + + self.file_to_definitions = {} + self.file_to_imports = {} + self.symbol_to_file_map = defaultdict(set) + for file_path in self.all_project_files: + tree = parse_file(file_path) + definitions = get_definitions_from_tree(tree) + imports = get_imports_from_tree(tree) + self.file_to_definitions[file_path] = definitions + self.file_to_imports[file_path] = imports + for defn in definitions: + self.symbol_to_file_map[defn].add(file_path) + + def _print_dependency_chain(self, symbol, symbol_chain_map): + chain = [] + current_symbol = symbol + while current_symbol is not None: + # Find the file where the symbol is defined + # In case of multiple definitions, we take the first one found + file_path = next(iter(self.symbol_to_file_map.get(current_symbol, ["Unknown File"])), "Unknown File") + chain.append(f"{current_symbol} @ {file_path}") + current_symbol = symbol_chain_map.get(current_symbol) + + chain.reverse() + print(" - Dependency Chain:", " -> ".join(chain), file=sys.stderr) + + def find_dependent_tests(self, changed_files_str: list, max_depth=4) -> set: + changed_files = {Path(f).resolve() for f in changed_files_str} + + initial_configs_to_add = set() + for file in changed_files: + if 'modeling_' in file.stem and 'models' in str(file): + model_name = file.stem.replace('modeling_', '') + config_file = file.parent / f"configuration_{model_name}.py" + if config_file.is_file(): + initial_configs_to_add.add(config_file) + changed_files.update(initial_configs_to_add) + + symbol_chain_map = {} + all_affected_symbols = set() + symbols_to_trace = set() + + for file_path in changed_files: + if file_path.name == '__init__.py': + continue + new_defs = self.file_to_definitions.get(file_path, set()) + symbols_to_trace.update(new_defs) + all_affected_symbols.update(new_defs) + for defn in new_defs: + symbol_chain_map[defn] = None + + for i in range(max_depth): + if not symbols_to_trace: + break + + next_layer_files = set() + + # Find files that import the current symbols to trace + # And for each new definition, link it to the symbol that triggered it + newly_added_definitions = set() + for file_path, imported_symbols in self.file_to_imports.items(): + triggers = symbols_to_trace.intersection(imported_symbols) + if triggers: + next_layer_files.add(file_path) + defs_in_file = self.file_to_definitions.get(file_path, set()) + # For simplicity, we link all new definitions in this file to the first trigger found + first_trigger = next(iter(triggers)) + for defn in defs_in_file: + if defn not in all_affected_symbols: + symbol_chain_map[defn] = first_trigger + newly_added_definitions.add(defn) + + # This heuristic is now also applied at each dependency level + config_files_to_add = set() + for file in next_layer_files: + if 'modeling_' in file.stem and 'models' in str(file): + model_name = file.stem.replace('modeling_', '') + config_file = file.parent / f"configuration_{model_name}.py" + if config_file.is_file(): + config_files_to_add.add(config_file) + + # For config files, we don't have a clear trigger, so we can't map their chain + for config_file in config_files_to_add: + defs_in_file = self.file_to_definitions.get(config_file, set()) + for defn in defs_in_file: + if defn not in all_affected_symbols: + symbol_chain_map[defn] = "CONFIG_HEURISTIC" # Special marker + newly_added_definitions.add(defn) + + next_layer_files.update(config_files_to_add) + + symbols_to_trace = newly_added_definitions + all_affected_symbols.update(symbols_to_trace) + + dependent_tests = set() + + affected_source_file_stems = set() + for s in all_affected_symbols: + if s in self.symbol_to_file_map: + for file_path in self.symbol_to_file_map[s]: + affected_source_file_stems.add(file_path.stem) + + for test_file in self.all_test_files: + imported_in_test = self.file_to_imports.get(test_file, set()) + + if not all_affected_symbols.isdisjoint(imported_in_test): + if DEBUG_MODE and DEBUG_TEST_FILE in str(test_file).lower(): + imported_symbols = [s for s in imported_in_test if s in all_affected_symbols] + print( + f"DEBUG: Test file {test_file} is included because it imports affected symbols: {imported_symbols}", file=sys.stderr) # noqa: E501 + for symbol in imported_symbols: + self._print_dependency_chain(symbol, symbol_chain_map) + dependent_tests.add(str(test_file)) + continue + + if not affected_source_file_stems.isdisjoint(imported_in_test): + if DEBUG_MODE and DEBUG_TEST_FILE in str(test_file).lower(): + imported_files = [f for f in imported_in_test if f in affected_source_file_stems] + print( + f"DEBUG: Test file {test_file} is included because it imports a symbol matching an affected file stem: {imported_files}", file=sys.stderr) # noqa: E501 + dependent_tests.add(str(test_file)) + for changed_file in changed_files: + if changed_file in self.all_test_files: + dependent_tests.add(str(changed_file)) + + return dependent_tests + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python find_dependent_tests.py ...") + sys.exit(1) + + all_args_string = " ".join(sys.argv[1:]) + changed_files = all_args_string.split() + + BLACKLIST = ['fla/utils.py', 'utils/convert_from_llama.py', 'utils/convert_from_rwkv6.py', 'utils/convert_from_rwkv7.py'] + changed_files = [file for file in changed_files if not any(file.endswith(b) for b in BLACKLIST)] + + changed_files = [file for file in changed_files if file.endswith('.py')] + + current_dir = Path(__file__).parent.resolve() + test_dir = current_dir.parent / "tests" + search_dir = current_dir.parent / "fla" + + finder = DependencyFinder(search_dirs=[search_dir], test_dir=test_dir) + dependent_tests = finder.find_dependent_tests(changed_files) + + if dependent_tests: + print(" ".join(sorted(list(dependent_tests)))) diff --git a/code/flash-linear-attention/setup.py b/code/flash-linear-attention/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..23ad0f6def84af1fda69e4e78de9531da23038ba --- /dev/null +++ b/code/flash-linear-attention/setup.py @@ -0,0 +1,50 @@ + +import ast +import os +import re +from pathlib import Path + +from setuptools import find_packages, setup + +with open('README.md') as f: + long_description = f.read() + + +def get_package_version(): + init_file = Path(os.path.dirname(os.path.abspath(__file__))) / 'fla' / '__init__.py' + with open(init_file) as f: + version_match = re.search(r"^__version__\s*=\s*(.*)$", f.read(), re.MULTILINE) + if version_match is None: + raise RuntimeError(f"Could not find `__version__` in the file {init_file}") + return ast.literal_eval(version_match.group(1)) + + +setup( + name='flash-linear-attention', + version=get_package_version(), + description='Fast Triton-based implementations of causal linear attention', + long_description=long_description, + long_description_content_type='text/markdown', + author='Songlin Yang, Yu Zhang', + author_email='yangsl66@mit.edu, yzhang.cs@outlook.com', + url='https://github.com/fla-org/flash-linear-attention', + packages=find_packages(), + license='MIT', + classifiers=[ + 'Programming Language :: Python :: 3', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + ], + python_requires='>=3.10', + install_requires=[ + 'torch', + 'transformers', + 'einops', + ], + extras_require={ + 'conv1d': ['causal-conv1d>=1.4.0'], + 'benchmark': ['matplotlib', 'datasets>=3.3.0'], + 'test': ['pytest'], + }, +) diff --git a/code/flash-linear-attention/tests/models/__init__.py b/code/flash-linear-attention/tests/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/code/flash-linear-attention/tests/models/test_modeling_abc.py b/code/flash-linear-attention/tests/models/test_modeling_abc.py new file mode 100644 index 0000000000000000000000000000000000000000..22c2ec02488dc802802464923c4516e3a30c65bf --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_abc.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import ABCConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, ABCConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, ABCConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_base.py b/code/flash-linear-attention/tests/models/test_modeling_base.py new file mode 100644 index 0000000000000000000000000000000000000000..754c119254f7c25ad0e1cc55d489c3c9ec3444cb --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_base.py @@ -0,0 +1,124 @@ + + +import pytest +import torch +from transformers.configuration_utils import PretrainedConfig + +from fla.utils import assert_close, device, is_intel_alchemist, is_nvidia_hopper + +from .test_modeling_utils import ( + GENERATION_UNSUPPORTED, + HOPPER_EXCLUSIVE, + MODELING_UNSUPPORTED_VARLEN, + NOT_READY_FOR_TESTING, + create_model_and_config, +) + + +# =================================================================================== +# BASE TEST FOR MODELING (FORWARD/BACKWARD PASS) +# =================================================================================== +@pytest.mark.skipif( + is_intel_alchemist, + reason="Skipping test on Intel Alchemist due to known issues with SRAM.", +) +def run_test_model_forward_backward( + L: int, + B: int, + T: int, + H: int, + D: int, + config_class: type, + use_l2warp: bool, + dtype: torch.dtype, +): + """ + A foundational test for the forward and backward passes of a model. + """ + if not is_nvidia_hopper and D == 128: + pytest.skip("D=128 is only tested on Hopper GPUs to save CI time.") + if not is_nvidia_hopper and config_class.__name__ in HOPPER_EXCLUSIVE: + pytest.skip(f"{config_class.__name__} requires Hopper-specific features.") + if config_class.__name__ in NOT_READY_FOR_TESTING: + pytest.skip(f"{config_class.__name__} is not yet ready for testing.") + + model, config = create_model_and_config(config_class, L, H, D, use_l2warp=use_l2warp, dtype=dtype) + input_ids = torch.randint(low=0, high=config.vocab_size, size=(B, T), device=device) + output_fixed = model(input_ids, output_hidden_states=True).hidden_states[-1] + assert output_fixed.shape == (B, T, config.hidden_size) + + if config_class.__name__ in MODELING_UNSUPPORTED_VARLEN: + pytest.skip(f"Variable length not supported for {config_class.__name__}.") + + cu_seqlens = torch.arange(0, B * T + 1, T, dtype=torch.int32, device=device) + output_var = model( + input_ids.view(1, B * T), output_hidden_states=True, cu_seqlens=cu_seqlens, + ).hidden_states[-1] + assert output_var.shape == (1, B * T, config.hidden_size) + assert_close("output", output_fixed.view(1, B * T, -1), output_var, 1e-3) + output_var.backward(torch.randn_like(output_var)) + + +# =================================================================================== +# BASE TEST FOR GENERATION (K/V CACHE) +# =================================================================================== +def run_test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + config_class: type, + dtype: torch.dtype, + use_l2warp: bool = False, + model: torch.nn.Module | None = None, + config: PretrainedConfig | None = None, + tol: float = 2e-3, +): + """ + A foundational test for K/V cache-based generation. + """ + torch.manual_seed(42) + if config_class.__name__ in GENERATION_UNSUPPORTED: + pytest.skip(f"Generation test not supported for {config_class.__name__}.") + if config_class.__name__ in NOT_READY_FOR_TESTING: + pytest.skip(f"{config_class.__name__} is not yet ready for testing.") + + if model is None: + model, config = create_model_and_config(config_class, L, H, D, use_l2warp=use_l2warp, dtype=dtype) + model.eval() + model = model.to(dtype).to(device) + + num_chunks = 4 + chunk_size = T // num_chunks + input_ids = torch.randint(low=0, high=config.vocab_size, size=(B, T)).to(device) + attention_mask = torch.ones((B, T), dtype=torch.bool).to(device) + seq_start = torch.randint(low=1, high=chunk_size - 1, size=(B,)) + attention_mask[torch.arange(T) < seq_start[:, None]] = False + ref = torch.cat([ + model(input_ids=input_ids[i:i+1, start:], use_cache=False).logits + for i, start in enumerate(seq_start) + ], dim=1) + + logits = [] + out = model( + input_ids=input_ids[:, :chunk_size], + attention_mask=attention_mask[:, :chunk_size], + use_cache=True, + past_key_values=None, + ) + logits, past_key_values = [out.logits], out.past_key_values + for i in range(1, num_chunks): + start, end = i * chunk_size, (i + 1) * chunk_size + for j in range(start, end): + out = model( + input_ids=input_ids[:, j:j+1], + attention_mask=attention_mask[:, :j+1], + use_cache=True, + past_key_values=past_key_values, + ) + logits.append(out.logits) + past_key_values = out.past_key_values + gen = torch.cat(logits, 1) + gen = torch.cat([gen[i:i+1, start:] for i, start in enumerate(seq_start)], 1) + assert_close('logits', ref, gen, tol) diff --git a/code/flash-linear-attention/tests/models/test_modeling_bitnet.py b/code/flash-linear-attention/tests/models/test_modeling_bitnet.py new file mode 100644 index 0000000000000000000000000000000000000000..74d947e849342ca7a15ea4abb7803fbd5930befb --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_bitnet.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import BitNetConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, BitNetConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, BitNetConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_comba.py b/code/flash-linear-attention/tests/models/test_modeling_comba.py new file mode 100644 index 0000000000000000000000000000000000000000..04049cc9858429b4c0d7a5efab5ee1a87f32cc87 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_comba.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import CombaConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, CombaConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, CombaConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_deltaformer.py b/code/flash-linear-attention/tests/models/test_modeling_deltaformer.py new file mode 100644 index 0000000000000000000000000000000000000000..e3c65192383835207f664eb2bdc57355fe381c38 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_deltaformer.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import DeltaFormerConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, DeltaFormerConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, DeltaFormerConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_deltanet.py b/code/flash-linear-attention/tests/models/test_modeling_deltanet.py new file mode 100644 index 0000000000000000000000000000000000000000..735b8380ea69804435faa5d520aa069c69c070c1 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_deltanet.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import DeltaNetConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, DeltaNetConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, DeltaNetConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_forgetting_transformer.py b/code/flash-linear-attention/tests/models/test_modeling_forgetting_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..92c69809adf423fa6a8fad03a2688d46191c01f4 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_forgetting_transformer.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import ForgettingTransformerConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, ForgettingTransformerConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, ForgettingTransformerConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_gated_deltanet.py b/code/flash-linear-attention/tests/models/test_modeling_gated_deltanet.py new file mode 100644 index 0000000000000000000000000000000000000000..2ce32df65e3a6c978d54630796cbd543fb59b647 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_gated_deltanet.py @@ -0,0 +1,55 @@ + +import pytest +import torch + +from fla.models import GatedDeltaNetConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, GatedDeltaNetConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, GatedDeltaNetConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_gated_deltaproduct.py b/code/flash-linear-attention/tests/models/test_modeling_gated_deltaproduct.py new file mode 100644 index 0000000000000000000000000000000000000000..c1b299b11dca2d7192c1ececbff7fafabd7eea75 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_gated_deltaproduct.py @@ -0,0 +1,67 @@ + +import pytest +import torch +from transformers import AutoModelForCausalLM + +from fla.models import GatedDeltaProductConfig +from fla.utils import device + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward +from .test_modeling_utils import init_weights_recursively + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, GatedDeltaProductConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'use_forget_gate', 'num_householders', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-use_forget_gate{}-num_householders{}".format(*test)) + for test in [ + (1, 3, 2000, False, 2, torch.float16), + (2, 4, 4000, True, 3, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + use_forget_gate: bool, + num_householders: int, + dtype: torch.dtype, +): + config = GatedDeltaProductConfig() + config.num_hidden_layers = L + config.use_forget_gate = use_forget_gate + config.num_householders = num_householders + model = AutoModelForCausalLM.from_config(config) + model.apply(init_weights_recursively) + model = model.to(dtype).to(device) + run_test_generation(L, B, T, None, None, GatedDeltaProductConfig, dtype, model=model, config=config, tol=3e-3) diff --git a/code/flash-linear-attention/tests/models/test_modeling_gla.py b/code/flash-linear-attention/tests/models/test_modeling_gla.py new file mode 100644 index 0000000000000000000000000000000000000000..b2a30507c2983ce36af459f462d9c67ea108f184 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_gla.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import GLAConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, GLAConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, GLAConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_gsa.py b/code/flash-linear-attention/tests/models/test_modeling_gsa.py new file mode 100644 index 0000000000000000000000000000000000000000..df480f1e11cfb358eb05016f908b35e66ff89fea --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_gsa.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import GSAConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, GSAConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, GSAConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_hgrn.py b/code/flash-linear-attention/tests/models/test_modeling_hgrn.py new file mode 100644 index 0000000000000000000000000000000000000000..b46f750e8f70d4ad64de4ae8ed03605fdb6ec6ef --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_hgrn.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import HGRNConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, HGRNConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, HGRNConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_hgrn2.py b/code/flash-linear-attention/tests/models/test_modeling_hgrn2.py new file mode 100644 index 0000000000000000000000000000000000000000..60c7c52f8df34abeda8b670b2efb6cdbb30efae8 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_hgrn2.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import HGRN2Config + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_hgrn2_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, HGRN2Config, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, HGRN2Config, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_kda.py b/code/flash-linear-attention/tests/models/test_modeling_kda.py new file mode 100644 index 0000000000000000000000000000000000000000..88f7566399e032d6aa5ea43a4bfd11399a2ec8d4 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_kda.py @@ -0,0 +1,55 @@ + +import pytest +import torch + +from fla.models import KDAConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, KDAConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, KDAConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_lightnet.py b/code/flash-linear-attention/tests/models/test_modeling_lightnet.py new file mode 100644 index 0000000000000000000000000000000000000000..c9ac3b02106bb5304f9264099d87911d47956a32 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_lightnet.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import LightNetConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, LightNetConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, LightNetConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_linear_attn.py b/code/flash-linear-attention/tests/models/test_modeling_linear_attn.py new file mode 100644 index 0000000000000000000000000000000000000000..003c20ac39db9801a7ba12d5499911c338f30f1a --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_linear_attn.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import LinearAttentionConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, LinearAttentionConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, LinearAttentionConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_log_linear_mamba2.py b/code/flash-linear-attention/tests/models/test_modeling_log_linear_mamba2.py new file mode 100644 index 0000000000000000000000000000000000000000..0bb2d6055cfe43672320413eed560e934425d24c --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_log_linear_mamba2.py @@ -0,0 +1,69 @@ + +import os + +import pytest +import torch + +from fla.models import LogLinearMamba2Config, LogLinearMamba2ForCausalLM +from fla.utils import device + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype', 'conv_backend'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}-conv-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, torch.bfloat16, 'cuda'), + (4, 4, 1024, 4, 64, torch.bfloat16, 'triton'), + (4, 4, 1024, 4, 128, torch.bfloat16, 'cuda'), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, + conv_backend: str, +): + """ + Test the forward and backward pass of the Mamba2 model by manually + instantiating the configuration and the model. + """ + os.environ['FLA_CONV_BACKEND'] = conv_backend + + # Manually create a consistent configuration + # The key relationship is: num_heads = expand * hidden_size / head_dim + # To ensure consistency, we derive hidden_size from other parameters. + expand = 2 + hidden_size = H * D // expand + + config = LogLinearMamba2Config( + num_hidden_layers=L, + hidden_size=hidden_size, + expand=expand, + num_heads=H, + head_dim=D, + vocab_size=1000, # dummy vocab size + ) + + model = LogLinearMamba2ForCausalLM(config).to(device=device, dtype=dtype) + model.eval() + + # Create random input tensor + x = torch.randint(0, config.vocab_size, (B, T), device=device) + + # Forward pass + y = model(x) + + # Assert output shape is correct + assert y.logits.shape == (B, T, config.vocab_size) + + # Backward pass + y.logits.sum().backward() + print(f"Test test_modeling passed with H={H}, D={D}, backend={conv_backend}.") diff --git a/code/flash-linear-attention/tests/models/test_modeling_mamba.py b/code/flash-linear-attention/tests/models/test_modeling_mamba.py new file mode 100644 index 0000000000000000000000000000000000000000..2a74b46df52b13099e416a8d4da9ddb305b913b3 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_mamba.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import MambaConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, MambaConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, MambaConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_mamba2.py b/code/flash-linear-attention/tests/models/test_modeling_mamba2.py new file mode 100644 index 0000000000000000000000000000000000000000..c7871d213ffb703a493834d4f35ed49ae43dcab4 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_mamba2.py @@ -0,0 +1,71 @@ + +import os + +import pytest +import torch + +from fla.models import Mamba2Config, Mamba2ForCausalLM +from fla.utils import device + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype', 'conv_backend'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}-conv-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16, 'cuda'), + (4, 4, 1024, 4, 64, False, torch.bfloat16, 'cuda'), + (4, 4, 1024, 4, 128, False, torch.bfloat16, 'cuda'), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, + conv_backend: str, +): + """ + Test the forward and backward pass of the Mamba2 model by manually + instantiating the configuration and the model. + """ + os.environ['FLA_CONV_BACKEND'] = conv_backend + + # Manually create a consistent configuration + # The key relationship is: num_heads = expand * hidden_size / head_dim + # To ensure consistency, we derive hidden_size from other parameters. + expand = 2 + hidden_size = H * D // expand + + config = Mamba2Config( + num_hidden_layers=L, + hidden_size=hidden_size, + expand=expand, + num_heads=H, + head_dim=D, + use_l2warp=use_l2warp, + vocab_size=1000, # dummy vocab size + ) + + model = Mamba2ForCausalLM(config).to(device=device, dtype=dtype) + model.eval() + + # Create random input tensor + x = torch.randint(0, config.vocab_size, (B, T), device=device) + + # Forward pass + y = model(x) + + # Assert output shape is correct + assert y.logits.shape == (B, T, config.vocab_size) + + # Backward pass + y.logits.sum().backward() + print(f"Test test_modeling passed with H={H}, D={D}, backend={conv_backend}.") diff --git a/code/flash-linear-attention/tests/models/test_modeling_mesanet.py b/code/flash-linear-attention/tests/models/test_modeling_mesanet.py new file mode 100644 index 0000000000000000000000000000000000000000..c1116209ea4b5607172f5ce716c0db46877a9d04 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_mesanet.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import MesaNetConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, MesaNetConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, MesaNetConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_mla.py b/code/flash-linear-attention/tests/models/test_modeling_mla.py new file mode 100644 index 0000000000000000000000000000000000000000..50a7ae14149e43d5f824ead9c9dda96a1d367b7a --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_mla.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import MLAConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, MLAConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, MLAConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_mom.py b/code/flash-linear-attention/tests/models/test_modeling_mom.py new file mode 100644 index 0000000000000000000000000000000000000000..7c8b3fcebc3061de6db22081a10525d749b837bc --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_mom.py @@ -0,0 +1,58 @@ + +import pytest +import torch + +from fla.models import MomConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.skip(reason="Bug not fixed yet") +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, MomConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + pytest.skip("Known bugs in mom") + run_test_generation(L, B, T, H, D, MomConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_nsa.py b/code/flash-linear-attention/tests/models/test_modeling_nsa.py new file mode 100644 index 0000000000000000000000000000000000000000..b9c93642e6af8b940a05c04f90c5d81f7f1f9ac8 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_nsa.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import NSAConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, NSAConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, NSAConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_path_attn.py b/code/flash-linear-attention/tests/models/test_modeling_path_attn.py new file mode 100644 index 0000000000000000000000000000000000000000..a5f6f35562ff0afcf52f4190c937469cbae14737 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_path_attn.py @@ -0,0 +1,57 @@ + +import pytest +import torch + +from fla.models import PaTHAttentionConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, False, torch.float16), + (4, 4, 1024, 4, 128, False, torch.float16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, PaTHAttentionConfig, use_l2warp=use_l2warp, dtype=dtype) + +# =================================================================================== +# Test for Generation +# =================================================================================== + + +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + (2, 2, 2000, 8, 128, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, PaTHAttentionConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_retnet.py b/code/flash-linear-attention/tests/models/test_modeling_retnet.py new file mode 100644 index 0000000000000000000000000000000000000000..e6ac6873baadbfb00f3be7c11ec1d2378304ca30 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_retnet.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import RetNetConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, RetNetConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, RetNetConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_rodimus.py b/code/flash-linear-attention/tests/models/test_modeling_rodimus.py new file mode 100644 index 0000000000000000000000000000000000000000..021525f0dd4ce637912c1b5a682a15a5c2c26d39 --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_rodimus.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import RodimusConfig + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, RodimusConfig, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, RodimusConfig, dtype) diff --git a/code/flash-linear-attention/tests/models/test_modeling_rwkv6.py b/code/flash-linear-attention/tests/models/test_modeling_rwkv6.py new file mode 100644 index 0000000000000000000000000000000000000000..252216184ec9de5fe5dec71a9c1b452674f8a6fb --- /dev/null +++ b/code/flash-linear-attention/tests/models/test_modeling_rwkv6.py @@ -0,0 +1,56 @@ + +import pytest +import torch + +from fla.models import RWKV6Config + +from .test_modeling_base import run_test_generation, run_test_model_forward_backward + + +# =================================================================================== +# Test for Modeling (Forward/Backward Pass) +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'use_l2warp', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-use_l2warp{}-{}".format(*test)) + for test in [ + (4, 4, 1024, 4, 64, True, torch.bfloat16), + (4, 4, 1024, 4, 64, False, torch.bfloat16), + (4, 4, 1024, 4, 128, False, torch.bfloat16), + ] + ], +) +def test_modeling( + L: int, + B: int, + T: int, + H: int, + D: int, + use_l2warp: bool, + dtype: torch.dtype, +): + run_test_model_forward_backward(L, B, T, H, D, RWKV6Config, use_l2warp=use_l2warp, dtype=dtype) + + +# =================================================================================== +# Test for Generation +# =================================================================================== +@pytest.mark.parametrize( + ['L', 'B', 'T', 'H', 'D', 'dtype'], + [ + pytest.param(*test, id="L{}-B{}-T{}-H{}-D{}-{}".format(*test)) + for test in [ + (2, 4, 2000, 8, 64, torch.float16), + ] + ], +) +def test_generation( + L: int, + B: int, + T: int, + H: int, + D: int, + dtype: torch.dtype, +): + run_test_generation(L, B, T, H, D, RWKV6Config, dtype)