amonshano's picture
Add Echo-Memory codebase used for this run (CC BY 4.0, JD Echo Team) (part 4)
c335050 verified
Raw
History Blame Contribute Delete
25.2 kB
import pytest
import torch
import torch.nn.functional as F
from einops import rearrange
from fla.modules.convolution import ShortConvolution, causal_conv1d, causal_conv1d_update
from fla.utils import assert_close, device
try:
from causal_conv1d import causal_conv1d_fn
except ImportError:
causal_conv1d_fn = None
def causal_conv1d_ref_torch(
x,
weight,
bias=None,
initial_state=None,
output_final_state=False,
final_states_out=None,
activation=None,
):
"""
x: (batch, dim, seqlen)
weight: (dim, width)
bias: (dim,)
initial_state: (batch, dim, width - 1)
final_states_out: (batch, dim, width - 1)
out: (batch, dim, seqlen)
"""
if activation not in [None, "silu", "swish"]:
raise NotImplementedError("activation must be None, silu, or swish")
dtype_in = x.dtype
x = x.to(weight.dtype)
seqlen = x.shape[-1]
dim, width = weight.shape
if initial_state is None:
out = F.conv1d(x, weight.unsqueeze(1), bias, padding=width - 1, groups=dim)
else:
x = torch.cat([initial_state, x], dim=-1)
out = F.conv1d(x, weight.unsqueeze(1), bias, padding=0, groups=dim)
out = out[..., :seqlen]
if output_final_state:
final_states = F.pad(x, (width - 1 - x.shape[-1], 0)).to(
dtype_in,
) # (batch, dim, width - 1)
if final_states_out is not None:
final_states_out.copy_(final_states)
else:
final_states_out = final_states
out = (out if activation is None else F.silu(out)).to(dtype=dtype_in)
return out if not output_final_state else (out, final_states_out)
def causal_conv1d_update_ref_torch(x, conv_state, weight, bias=None, activation=None, cache_seqlens=None):
"""
x: (batch, dim) or (batch, dim, seqlen)
conv_state: (batch, dim, state_len), where state_len >= width - 1
weight: (dim, width)
bias: (dim,)
cache_seqlens: (batch,), dtype int32.
If not None, the conv_state is treated as a circular buffer.
The conv_state will be updated by copying x to the conv_state starting at the index
@cache_seqlens % state_len before performing the convolution.
out: (batch, dim) or (batch, dim, seqlen)
"""
if activation not in [None, "silu", "swish"]:
raise NotImplementedError("activation must be None, silu, or swish")
dtype_in = x.dtype
unsqueeze = x.dim() == 2
if unsqueeze:
x = x.unsqueeze(-1)
batch, dim, seqlen = x.shape
width = weight.shape[1]
state_len = conv_state.shape[-1]
assert conv_state.shape == (batch, dim, state_len)
assert weight.shape == (dim, width)
if cache_seqlens is None:
x_new = torch.cat([conv_state, x], dim=-1).to(weight.dtype) # (batch, dim, state_len + seqlen)
conv_state.copy_(x_new[:, :, -state_len:])
else:
width_idx = torch.arange(-(width - 1), 0, dtype=torch.long, device=x.device).unsqueeze(0) + cache_seqlens.unsqueeze(1)
width_idx = torch.remainder(width_idx, state_len).unsqueeze(1).expand(-1, dim, -1)
x_new = torch.cat([conv_state.gather(2, width_idx), x], dim=-1).to(weight.dtype)
copy_idx = torch.arange(seqlen, dtype=torch.long, device=x.device).unsqueeze(0) + cache_seqlens.unsqueeze(1)
copy_idx = torch.remainder(copy_idx, state_len).unsqueeze(1).expand(-1, dim, -1)
conv_state.scatter_(2, copy_idx, x)
out = F.conv1d(x_new, weight.unsqueeze(1), bias, padding=0, groups=dim)[:, :, -seqlen:]
if unsqueeze:
out = out.squeeze(-1)
return (out if activation is None else F.silu(out)).to(dtype=dtype_in)
@pytest.mark.parametrize(
('B', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype'),
[
pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}".format(*test))
for test in [
(2, 64, 128, 3, "swish", True, True, torch.float32),
(2, 128, 128, 4, "swish", False, True, torch.float32),
(2, 64, 128, 3, "swish", True, False, torch.float32),
(2, 128, 128, 4, "swish", False, False, torch.float32),
(2, 500, 1024, 3, None, True, True, torch.float32),
(2, 1024, 1024, 4, None, False, True, torch.float32),
(2, 64, 128, 3, None, True, False, torch.float16),
(2, 128, 128, 4, None, False, False, torch.float16),
]
],
)
def test_conv(
B: int,
T: int,
D: int,
W: int,
activation: str,
has_bias: bool,
has_residual: bool,
dtype: torch.dtype,
):
torch.manual_seed(42)
x = torch.randn(B, T, D).to(device, dtype).requires_grad_(True)
weight = torch.randn(D, W).to(device, dtype).requires_grad_(True)
bias = torch.randn(D).to(device, dtype).requires_grad_(True) if has_bias else None
residual = x.detach().clone().requires_grad_(True) if has_residual else None
dy = torch.randn(B, T, D).to(device, dtype)
ref = causal_conv1d_ref_torch(
x=rearrange(x, "b t d -> b d t"),
weight=weight,
bias=bias,
activation=activation,
)
ref = rearrange(ref, "b d t -> b t d")
if has_residual:
ref += residual
ref.backward(dy)
ref_dx, x.grad = x.grad, None
ref_dw, weight.grad = weight.grad, None
if has_bias:
ref_db, bias.grad = bias.grad, None
if has_residual:
ref_dr, residual.grad = residual.grad, None
tri, _ = causal_conv1d(x, weight, bias, residual=residual, activation=activation)
tri.backward(dy)
tri_dx, x.grad = x.grad, None
tri_dw, weight.grad = weight.grad, None
if has_bias:
tri_db, bias.grad = bias.grad, None
if has_residual:
tri_dr, residual.grad = residual.grad, None
assert_close(" y", ref, tri, 1e-3)
assert_close("dx", ref_dx, tri_dx, 1e-3)
assert_close("dw", ref_dw, tri_dw, 1e-3)
if has_bias:
assert_close("db", ref_db, tri_db, 1e-3)
if has_residual:
assert_close("dr", ref_dr, tri_dr, 1e-3)
@pytest.mark.parametrize(
('N', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype'),
[
pytest.param(*test, id="N{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}".format(*test))
for test in [
(4, 500, 128, 3, "swish", True, True, torch.float32),
(4, 1024, 200, 4, "swish", False, True, torch.float32),
(4, 500, 128, 3, None, True, False, torch.float16),
(4, 1024, 1024, 4, None, False, False, torch.float16),
]
],
)
def test_conv_varlen(
N: int,
T: int,
D: int,
W: int,
activation: str,
has_bias: bool,
has_residual: bool,
dtype: torch.dtype,
):
torch.manual_seed(42)
cu_seqlens = torch.cat([
torch.tensor([0], dtype=torch.long),
torch.arange(16, T)[torch.randperm(T - 16)[:N-1]],
torch.tensor([T], dtype=torch.long),
], 0).to(device).sort()[0]
x = torch.randn(1, T, D).to(device, dtype).requires_grad_(True)
weight = torch.randn(D, W).to(device, dtype).requires_grad_(True)
bias = torch.randn(D).to(device, dtype).requires_grad_(True) if has_bias else None
residual = x.detach().clone().requires_grad_(True) if has_residual else None
dy = torch.randn(1, T, D).to(device, dtype)
ref = torch.cat([
rearrange(
causal_conv1d_ref_torch(
x=rearrange(x[:, bos:eos].contiguous(), "b t d -> b d t"),
weight=weight,
bias=bias,
activation=activation,
),
"b t d -> b d t",
) + (residual[:, bos:eos] if has_residual else torch.zeros_like(x[:, bos:eos]))
for bos, eos in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)
], 1)
ref.backward(dy)
ref_dx, x.grad = x.grad, None
ref_dw, weight.grad = weight.grad, None
if has_bias:
ref_db, bias.grad = bias.grad, None
if has_residual:
ref_dr, residual.grad = residual.grad, None
tri, _ = causal_conv1d(x, weight, bias, residual=residual, activation=activation, cu_seqlens=cu_seqlens)
tri.backward(dy)
tri_dx, x.grad = x.grad, None
tri_dw, weight.grad = weight.grad, None
if has_bias:
tri_db, bias.grad = bias.grad, None
if has_residual:
tri_dr, residual.grad = residual.grad, None
assert_close(" y", ref, tri, 1e-3)
assert_close("dx", ref_dx, tri_dx, 1e-3)
assert_close("dw", ref_dw, tri_dw, 1e-3)
if has_bias:
assert_close("db", ref_db, tri_db, 1e-3)
if has_residual:
assert_close("dr", ref_dr, tri_dr, 1e-3)
@pytest.mark.parametrize(
('B', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype'),
[
pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}".format(*test))
for test in [
(2, 64, 128, 3, "swish", True, True, torch.float32),
(2, 128, 128, 4, "swish", False, True, torch.float32),
(2, 64, 128, 3, "swish", True, False, torch.float32),
(2, 128, 128, 4, "swish", False, False, torch.float32),
(2, 500, 1024, 3, None, True, True, torch.float32),
(2, 1024, 1024, 4, None, False, True, torch.float32),
(2, 64, 128, 3, None, True, False, torch.float16),
(2, 128, 128, 4, None, False, False, torch.float16),
]
],
)
@torch.no_grad
def test_conv_decoding(
B: int,
T: int,
D: int,
W: int,
activation: str,
has_bias: bool,
has_residual: bool,
dtype: torch.dtype,
):
torch.manual_seed(42)
x = torch.randn(B, T, D).to(device, dtype)
weight = torch.randn(D, W).to(device, dtype) * 0
bias = torch.randn(D).to(device, dtype) if has_bias else None
residual = x.clone() if has_residual else None
ref = causal_conv1d_ref_torch(
x=rearrange(x, "b t d -> b d t"),
weight=weight,
bias=bias,
activation=activation,
)
ref = rearrange(ref, "b d t -> b t d")
if has_residual:
ref += residual
ref_cache = x.new_zeros(B, D, W)
ref_cache[:, :, -min(W, T):].copy_(rearrange(x[..., -min(W, T):, :], 'n w d -> n d w'))
tri = torch.zeros_like(x)
tri_cache = x.new_zeros(B, D, W)
for i in range(T):
y, tri_cache = causal_conv1d_update(
x=x[:, i:i+1, :],
cache=tri_cache,
residual=residual[:, i:i+1, :] if has_residual else None,
weight=weight,
bias=bias,
activation=activation,
)
tri[:, i:i+1, :] = y
assert_close(" y", ref, tri, 1e-3)
assert_close("cache", ref_cache, tri_cache, 1e-3)
@pytest.mark.parametrize(
('B', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype', 'backend'),
[
pytest.param(
*test, id="B{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}_{8}".format(*test))
for test in [
(2, 64, 128, 3, "swish", True, True, torch.float32, 'triton'),
(2, 128, 128, 4, "swish", False, True, torch.float32, 'triton'),
(2, 64, 128, 3, "swish", True, False, torch.float32, 'triton'),
(2, 128, 128, 4, "swish", False, False, torch.float32, 'triton'),
(2, 500, 1024, 3, None, True, True, torch.float32, 'triton'),
(2, 1024, 1024, 4, None, False, True, torch.float32, 'triton'),
(2, 64, 128, 3, None, True, False, torch.float16, 'triton'),
(2, 128, 128, 4, None, False, False, torch.float16, 'triton'),
(2, 64, 128, 3, "swish", True, True, torch.float32, 'cuda'),
(2, 128, 128, 4, "swish", False, True, torch.float32, 'cuda'),
(2, 64, 128, 3, "swish", True, False, torch.float32, 'cuda'),
(2, 128, 128, 4, "swish", False, False, torch.float32, 'cuda'),
(2, 2, 128, 4, "swish", True, True, torch.float32, 'cuda'), # T_prefill < W
(2, 2, 128, 4, "swish", True, True, torch.float32, 'triton'),
(2, 3, 128, 4, "swish", True, True, torch.float32, 'triton'),
(2, 4, 128, 4, "swish", True, True, torch.float32, 'triton'),
(2, 2, 128, 3, "swish", True, True, torch.float32, 'triton'),
]
],
)
@torch.no_grad
def test_conv_with_cache_prefill_fwd(
B: int,
T: int,
D: int,
W: int,
activation: str,
has_bias: bool,
has_residual: bool,
dtype: torch.dtype,
backend: str,
):
if causal_conv1d_fn is None and backend == 'cuda':
pytest.skip("causal_conv1d is not installed for CUDA backend")
torch.manual_seed(42)
x = torch.randn(B, T, D).to(device, dtype)
residual = torch.randn(B, T, D).to(device, dtype) if has_residual else None
conv = ShortConvolution(
hidden_size=D,
kernel_size=W,
bias=has_bias,
activation=activation,
backend=backend,
device=device,
dtype=dtype,
)
cache = torch.randn(B, D, W - 1).to(device, dtype)
ref = causal_conv1d_ref_torch(
x=x.transpose(1, 2), # (B, D, T)
weight=rearrange(conv.weight, "d 1 w -> d w"),
bias=conv.bias,
initial_state=cache, # (B, D, W-1)
activation=activation,
).transpose(1, 2) # (B, T, D)
if has_residual:
ref += residual
zero_padding = torch.zeros(B, D, 1).to(device, dtype)
tri_cache = torch.cat([zero_padding, cache], dim=-1) # (B, D, W)
tri, cache_out = conv(x, residual=residual, cache=tri_cache.clone(), output_final_state=True)
assert_close("y", ref, tri, 1e-3)
for p in range(1, W):
if p <= T:
expected = x[:, -p, :]
else:
expected = tri_cache[:, :, -(p - T)]
torch.testing.assert_close(
cache_out[:, :, -p],
expected,
atol=1e-3, rtol=1e-3,
)
@pytest.mark.parametrize(
('N', 'T', 'D', 'W', 'activation', 'has_bias', 'has_residual', 'dtype', 'backend'),
[
pytest.param(
*test,
id="N{0}_T{1}_D{2}_W{3}_activation{4}_has_bias{5}_has_residual{6}_{7}_{8}".format(*test),
)
for test in [
(3, 128, 64, 4, "swish", True, True, torch.float32, 'triton'),
(4, 256, 128, 3, None, False, True, torch.float32, 'triton'),
(2, 64, 128, 4, "swish", True, False, torch.float16, 'cuda'),
(3, 200, 64, 3, None, False, False, torch.float16, 'cuda'),
(2, 3, 64, 4, "swish", True, True, torch.float32, 'triton'), # T < W
(2, 3, 64, 3, None, False, True, torch.float32, 'cuda'), # T < W
]
],
)
@torch.no_grad
def test_conv_varlen_with_cache_prefill_fwd(
N: int,
T: int,
D: int,
W: int,
activation: str,
has_bias: bool,
has_residual: bool,
dtype: torch.dtype,
backend: str,
):
if causal_conv1d_fn is None and backend == 'cuda':
pytest.skip("causal_conv1d is not installed for CUDA backend")
torch.manual_seed(42)
min_len_each = max(1, T // N)
lengths = [min_len_each] * N
lengths[-1] += T % N
assert all(length >= 1 for length in lengths), "all lengths must >= 1"
cu_seqlens = torch.tensor([0] + torch.cumsum(torch.tensor(lengths), 0).tolist(),
device=device, dtype=torch.int32)
x = torch.randn(1, T, D).to(device, dtype)
residual = torch.randn(1, T, D).to(device, dtype) if has_residual else None
conv = ShortConvolution(
hidden_size=D,
kernel_size=W,
bias=has_bias,
activation=activation,
backend=backend,
device=device,
dtype=dtype,
)
cache = torch.randn(N, D, W - 1).to(device, dtype)
ref_list = []
for i, (bos, eos) in enumerate(zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)):
xi = x[:, bos:eos, :].transpose(1, 2) # (1, D, l)
ci = cache[i:i + 1] # (1, D, W-1)
refi = causal_conv1d_ref_torch(
x=xi,
weight=rearrange(conv.weight, "d 1 w -> d w"),
bias=conv.bias,
initial_state=ci,
activation=activation,
).transpose(1, 2) # (1, l, D)
if has_residual:
refi += residual[:, bos:eos, :]
ref_list.append(refi)
ref = torch.cat(ref_list, dim=1) # (1, T, D)
zero_pad = torch.zeros(N, D, 1, device=device, dtype=dtype)
tri_cache = torch.cat([zero_pad, cache], dim=-1) # (N, D, W)
tri, cache_out = conv(x,
residual=residual,
cache=tri_cache.clone(),
cu_seqlens=cu_seqlens,
output_final_state=True)
assert_close("varlen y", ref, tri, 1e-3)
for i, (bos, eos) in enumerate(zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False)):
length = eos - bos
for p in range(1, W):
if p <= length:
expected = x[0, eos - p, :]
else:
expected = tri_cache[i, :, -(p - length)]
torch.testing.assert_close(
cache_out[i, :, -p],
expected,
atol=1e-3,
rtol=1e-3,
)
@pytest.mark.parametrize(
('B', 'D', 'W', 'has_bias', 'has_residual', 'activation', 'dtype', 'backend'),
[
pytest.param(*test, id="B{0}_D{1}_W{2}_has_bias{3}_has_residual{4}_activation{5}_{6}_{7}".format(*test))
for test in [
(2, 128, 3, True, True, "swish", torch.float32, 'triton'),
(2, 128, 4, False, True, "swish", torch.float32, 'triton'),
(2, 128, 3, True, False, "swish", torch.float32, 'triton'),
(2, 128, 4, False, False, "swish", torch.float32, 'triton'),
(2, 128, 3, True, True, "swish", torch.float32, 'cuda'),
(2, 128, 4, False, True, "swish", torch.float32, 'cuda'),
(2, 128, 3, True, False, "swish", torch.float32, 'cuda'),
(2, 128, 4, False, False, "swish", torch.float32, 'cuda'),
(2, 128, 4, False, False, None, torch.float32, 'cuda'),
(2, 128, 4, False, False, None, torch.float32, 'triton'),
]
],
)
@torch.no_grad
def test_conv_decoding_with_cache(
B: int,
D: int,
W: int,
activation: str,
has_bias: bool,
has_residual: bool,
dtype: torch.dtype,
backend: str,
):
if causal_conv1d_fn is None and backend == 'cuda':
pytest.skip("causal_conv1d is not installed for CUDA backend")
torch.manual_seed(42)
x = torch.randn(B, 1, D).to(device, dtype) # (B, 1, D)
residual = x.clone() if has_residual else None
conv = ShortConvolution(
hidden_size=D,
kernel_size=W,
bias=has_bias,
activation=activation,
backend=backend,
device=device,
dtype=dtype,
)
state = torch.randn(B, D, W).to(device, dtype)
# reference
ref = causal_conv1d_update_ref_torch(
x.squeeze(1), # (B, D)
conv_state=state.clone(),
weight=rearrange(conv.weight, "d 1 w -> d w"),
bias=conv.bias,
activation=activation,
).unsqueeze(1) # (B, 1, D)
if has_residual:
ref += residual
# ShortConvolution step
with torch.no_grad():
y, _ = conv.step(x, residual, state.clone())
assert_close("y", ref, y, 1e-3)
@pytest.mark.parametrize(
('B', 'T', 'D', 'W', 'has_bias', 'has_residual', 'activation', 'dtype'),
[
pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_has_bias{4}_has_residual{5}_activation{6}_{7}".format(*test))
for test in [
(2, 64, 128, 3, True, True, "swish", torch.float32),
(2, 128, 128, 4, False, True, "swish", torch.float32),
(2, 64, 128, 3, True, False, "swish", torch.float32),
(2, 128, 128, 4, False, False, "swish", torch.float32),
]
],
)
@torch.no_grad
def test_mixed_backend(
B: int,
T: int,
D: int,
W: int,
has_bias: bool,
has_residual: bool,
activation: str,
dtype: torch.dtype,
):
torch.manual_seed(42)
T_decode = 1
x = torch.randn(B, T + T_decode, D, device=device, dtype=dtype)
residual = torch.randn_like(x) if has_residual else None
conv = ShortConvolution(
hidden_size=D,
kernel_size=W,
bias=has_bias,
activation=activation,
backend="cuda",
device=device,
dtype=dtype,
)
cache = torch.randn(B, D, W-1, device=device, dtype=dtype)
y_cuda_prefill, final_state = conv(
x[:, :T],
residual=residual[:, :T] if has_residual else None,
cache=cache,
output_final_state=True,
)
conv.backend = "triton"
y_triton_decode, _ = conv(
x[:, T:],
residual=residual[:, T:] if has_residual else None,
cache=final_state,
output_final_state=True,
)
conv.backend = "triton"
cache = torch.cat((torch.zeros_like(cache[..., :1]), cache), -1)
y_triton_full, _ = conv(x, residual=residual, cache=cache)
y_mixed = torch.cat([y_cuda_prefill, y_triton_decode], dim=1)
assert_close("cuda→triton vs triton", y_mixed, y_triton_full, 1e-3)
conv.backend = "triton"
y_triton_prefill, final_state = conv(
x[:, :T],
residual=residual[:, :T] if has_residual else None,
cache=cache,
output_final_state=True,
)
conv.backend = "cuda"
y_cuda_decode, _ = conv(
x[:, T:],
residual=residual[:, T:] if has_residual else None,
cache=final_state,
output_final_state=True,
)
y_mixed2 = torch.cat([y_triton_prefill, y_cuda_decode], dim=1)
assert_close("triton→cuda vs triton", y_mixed2, y_triton_full, 1e-3)
@pytest.mark.parametrize(
('B', 'T', 'D', 'W', 'has_bias', 'has_residual', 'activation', 'dtype'),
[
pytest.param(*test, id="B{0}_T{1}_D{2}_W{3}_has_bias{4}_has_residual{5}_activation{6}_{7}".format(*test))
for test in [
(2, 64, 100, 3, True, True, "swish", torch.float32),
(2, 128, 128, 4, True, True, "swish", torch.float32),
(3, 128, 128, 4, True, True, "swish", torch.float32),
(3, 128, 256, 4, True, True, "swish", torch.float32),
(3, 128, 512, 4, True, True, "swish", torch.float32),
(2, 128, 1024, 4, True, True, "swish", torch.float32),
(2, 128, 2048, 3, True, True, "swish", torch.float32),
(2, 128, 4096, 4, True, True, "swish", torch.float32),
(2, 128, 8192, 4, True, True, "swish", torch.float32),
]
],
)
def test_conv_cache_backward(
B: int,
T: int,
D: int,
W: int,
has_bias: bool,
has_residual: bool,
activation: str,
dtype: torch.dtype,
):
torch.manual_seed(42)
x = torch.randn(B, T, D, device=device, dtype=dtype, requires_grad=True)
weight = torch.randn(D, W, device=device, dtype=dtype, requires_grad=True)
bias = torch.randn(D, device=device, dtype=dtype, requires_grad=True) if has_bias else None
residual = torch.randn(B, T, D, device=device, dtype=dtype, requires_grad=True) if has_residual else None
cache = torch.randn(B, D, W - 1, device=device, dtype=dtype, requires_grad=True)
def ref_func(x, weight, bias, residual, cache):
out, cache_out = causal_conv1d_ref_torch(
x.transpose(1, 2),
weight,
bias,
initial_state=cache,
output_final_state=True,
activation=activation,
)
out = out.transpose(1, 2)
if residual is not None:
out += residual
return out, cache_out
def triton_func(x, weight, bias, residual, cache):
zero_padding = torch.zeros(B, D, 1, device=device, dtype=dtype)
triton_cache = torch.cat([zero_padding, cache], dim=-1).contiguous()
tri, cache_out_triton = causal_conv1d(
x,
weight=weight,
bias=bias,
residual=residual,
initial_state=triton_cache,
output_final_state=True,
activation=activation,
)
cache_out_triton = cache_out_triton[..., 1:].clone() # [B, D, W-1]
return tri, cache_out_triton
d_tri = torch.randn_like(x)
d_cache_out = torch.randn_like(cache)
def get_grads(func, *inputs):
out, cache_out = func(*inputs)
loss = (out * d_tri).sum() + (cache_out * d_cache_out).sum()
grads = torch.autograd.grad(
loss,
inputs,
retain_graph=True,
create_graph=False,
)
return grads
inputs = (x, weight, bias, residual, cache)
grads_ref = get_grads(ref_func, *inputs)
grads_tri = get_grads(triton_func, *inputs)
names = ["x", "weight", "bias", "residual", "cache"]
for name, g_ref, g_tri in zip(names, grads_ref, grads_tri, strict=False):
assert_close(name, g_ref, g_tri, ratio=1e-3)