forked from fla-org/flash-linear-attention
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathshort_conv.py
More file actions
252 lines (226 loc) 路 9.95 KB
/
Copy pathshort_conv.py
File metadata and controls
252 lines (226 loc) 路 9.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
# For a list of all contributors, visit:
# https://github.com/fla-org/flash-linear-attention/graphs/contributors
"""Short convolution implementation for efficient causal convolutions."""
from __future__ import annotations
import warnings
import torch
import torch.nn as nn
from einops import rearrange
try:
from causal_conv1d import causal_conv1d_fn as causal_conv1d_fn_cuda
from causal_conv1d import causal_conv1d_update as causal_conv1d_update_cuda
except ImportError:
causal_conv1d_fn_cuda = None
causal_conv1d_update_cuda = None
class ShortConvolution(nn.Conv1d):
"""Short convolution layer for efficient causal convolution operations.
This class implements a depthwise 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_cuda 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,
chunk_indices: torch.LongTensor | None = None,
**kwargs,
) -> tuple[torch.Tensor, torch.Tensor | None]:
"""
Args:
x (`torch.Tensor`):
Tensor of shape `[B, T, D]`. `B` must be 1 if `cu_seqlens` 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]
chunk_indices (Optional[torch.LongTensor]):
Chunk indices for variable-length sequences. Default: `None`.
Returns:
Tensor of shape `[B, T, D]`.
"""
# Import here to avoid circular dependency
from fla.modules.conv.causal_conv1d import causal_conv1d
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
# and other small issues
# to simplify the implementation, we just switch to triton backend
if self.backend == 'cuda' and cache is not None:
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,
chunk_indices=chunk_indices,
**kwargs,
)
def step(
self,
x: torch.Tensor,
residual: torch.Tensor | None,
cache: torch.Tensor | None,
output_final_state: bool = False,
cu_seqlens: torch.LongTensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor | None]:
from fla.modules.conv.triton.ops import causal_conv1d_update
B, _, D, W = *x.shape, self.kernel_size[0]
N = B if cu_seqlens is None else len(cu_seqlens) - 1
# Always initialise cache when None so the Triton kernel never
# receives a None tensor. Return value still respects output_final_state
# to maintain consistency with the non-step path in forward().
if 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':
y, cache = causal_conv1d_update(
x=x,
cache=cache,
residual=residual,
weight=rearrange(self.weight, "d 1 w -> d w"),
bias=self.bias,
activation=self.activation,
)
return y, (cache if output_final_state else None)
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 if output_final_state else None)
@property
def state_size(self) -> int:
return self.hidden_size * self.kernel_size