Skip to content

Commit 7d163c9

Browse files
authored
Fix LGA cache devices and skipped-attention residuals (pytorch#22087)
Differential Revision: D117006258 Pull Request resolved: pytorch#22087
1 parent adabd63 commit 7d163c9

5 files changed

Lines changed: 153 additions & 14 deletions

File tree

‎examples/models/llama/attention.py‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def update(
131131
torch._check(start_pos < self.max_context_length)
132132
dim_to_slice = 2
133133
seq_length = k_val.size(dim_to_slice)
134-
indices = torch.arange(seq_length) + start_pos
134+
indices = torch.arange(seq_length, device=self.k_cache.device) + start_pos
135135
self.k_cache.index_copy_(dim_to_slice, indices, k_val)
136136
self.v_cache.index_copy_(dim_to_slice, indices, v_val)
137137
return self.k_cache, self.v_cache
@@ -181,7 +181,9 @@ def forward(
181181
def _create_causal_mask_for_ring_buffer(
182182
cache_positions, window_size, start_pos, seq_len
183183
):
184-
pos_q = start_pos + torch.arange(seq_len, dtype=torch.long).view(-1, 1)
184+
pos_q = start_pos + torch.arange(
185+
seq_len, dtype=torch.long, device=cache_positions.device
186+
).view(-1, 1)
185187
delta = pos_q - cache_positions
186188
attn_mask = (cache_positions >= 0) & (delta >= 0) & (delta < window_size)
187189
attn_mask = torch.where(attn_mask == True, 0, float("-inf")) # noqa E712
@@ -239,11 +241,18 @@ def calculate_positions_and_update_indices(self, input_pos: torch.Tensor, seq_le
239241
"""
240242
start_pos = input_pos[0].item()
241243
torch._check_is_size(start_pos)
242-
orig_indices = torch.arange(seq_len, dtype=torch.long) + start_pos
244+
device = self.cache_positions.device
245+
orig_indices = (
246+
torch.arange(seq_len, dtype=torch.long, device=device) + start_pos
247+
)
243248
indices = orig_indices % self.max_context_length
244249

245-
full_t = torch.full((self.max_context_length,), -1, dtype=torch.long)
246-
arange_tensor = torch.arange(self.max_context_length, dtype=torch.long)
250+
full_t = torch.full(
251+
(self.max_context_length,), -1, dtype=torch.long, device=device
252+
)
253+
arange_tensor = torch.arange(
254+
self.max_context_length, dtype=torch.long, device=device
255+
)
247256
cache_positions = torch.where(
248257
arange_tensor < start_pos, self.cache_positions, full_t
249258
)

‎examples/models/llama/llama_transformer.py‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -267,17 +267,22 @@ def from_type(cls, layer_id, args, rope) -> "TransformerBlock":
267267
attention = cls(args, layer_id, rope, **args.attention_kwargs)
268268
return TransformerBlock(args, attention, mlp_type=mlp_type, layer_id=layer_id)
269269

270+
def _apply_attention_residual(self, x, attention_output):
271+
if isinstance(self.attention, AttentionSkip):
272+
if not self.use_residual_gate:
273+
return x
274+
attention_output = torch.zeros_like(x)
275+
if self.use_residual_gate:
276+
if hasattr(self, "post_attn_norm"):
277+
attention_output = self.post_attn_norm(attention_output)
278+
return self.add_attn(stream=x, branch=attention_output)
279+
return x + attention_output
280+
270281
def forward(self, x, freqs_cos, freqs_sin, attn_options: ForwardOptions): # x: 1xN
271-
h, attn_options_update = self.attention(
282+
attention_output, attn_options_update = self.attention(
272283
self.attention_norm(x), freqs_cos, freqs_sin, **attn_options
273284
)
274-
if not isinstance(self.attention, AttentionSkip):
275-
if self.use_residual_gate:
276-
if hasattr(self, "post_attn_norm"):
277-
h = self.post_attn_norm(h)
278-
h = self.add_attn(stream=x, branch=h)
279-
else:
280-
h = x + h
285+
h = self._apply_attention_residual(x, attention_output)
281286

282287
if self.mlp_type == "skip":
283288
out = h

‎examples/models/llama/tests/BUCK‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ fbcode_target(_kind = python_unittest,
6767
],
6868
)
6969

70+
fbcode_target(_kind = python_unittest,
71+
name = "test_transformer_block",
72+
srcs = [
73+
"test_transformer_block.py",
74+
],
75+
deps = [
76+
"//caffe2:torch",
77+
"//executorch/examples/models/llama:llama_transformer",
78+
],
79+
)
80+
7081
fbcode_target(_kind = python_unittest,
7182
name = "test_ring_attention",
7283
srcs = [

‎examples/models/llama/tests/test_ring_kv_cache.py‎

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import unittest
88

99
import torch
10-
from executorch.examples.models.llama.attention import RingKVCache
10+
from executorch.examples.models.llama.attention import KVCache, RingKVCache
1111

1212

1313
class TestRingKVCache(unittest.TestCase):
@@ -20,6 +20,80 @@ def setUp(self):
2020
self.enable_dynamic_shape = True
2121
self.dtype = torch.float32
2222

23+
def _require_usable_cuda(self):
24+
if not torch.cuda.is_available():
25+
self.skipTest("CUDA is not available")
26+
try:
27+
torch.zeros(1, device="cuda").cpu()
28+
except (RuntimeError, torch.AcceleratorError) as error:
29+
self.skipTest(f"CUDA kernels are not usable: {error}")
30+
31+
def test_dynamic_kv_cache_update_on_cuda(self):
32+
self._require_usable_cuda()
33+
cache = KVCache(
34+
max_batch_size=1,
35+
max_context_length=self.max_context_length,
36+
n_heads=self.n_heads,
37+
head_dim=self.head_dim,
38+
enable_dynamic_shape=True,
39+
dtype=self.dtype,
40+
).cuda()
41+
input_pos = torch.tensor([2], dtype=torch.long, device="cuda")
42+
k_val = torch.randn(
43+
1, self.n_heads, 3, self.head_dim, device="cuda", dtype=self.dtype
44+
)
45+
v_val = torch.randn_like(k_val)
46+
47+
k_out, v_out = cache.update(input_pos, k_val, v_val)
48+
49+
self.assertEqual(k_out.device.type, "cuda")
50+
self.assertEqual(v_out.device.type, "cuda")
51+
torch.testing.assert_close(k_out[:, :, 2:5], k_val)
52+
torch.testing.assert_close(v_out[:, :, 2:5], v_val)
53+
54+
def test_ring_cache_positions_and_mask_on_cuda(self):
55+
self._require_usable_cuda()
56+
cache = RingKVCache(
57+
max_batch_size=1,
58+
max_context_length=self.max_context_length,
59+
n_heads=self.n_heads,
60+
head_dim=self.head_dim,
61+
enable_dynamic_shape=True,
62+
dtype=self.dtype,
63+
).cuda()
64+
input_pos = torch.tensor([0], dtype=torch.long, device="cuda")
65+
seq_len = 3
66+
k_val = torch.randn(
67+
1,
68+
self.n_heads,
69+
seq_len,
70+
self.head_dim,
71+
device="cuda",
72+
dtype=self.dtype,
73+
)
74+
v_val = torch.randn_like(k_val)
75+
76+
cache.update(input_pos, k_val, v_val)
77+
mask = cache.create_causal_mask_for_ring_buffer(start_pos=0, seq_len=seq_len)
78+
79+
expected_positions = torch.tensor(
80+
[0, 1, 2] + [-1] * 13, dtype=torch.long, device="cuda"
81+
)
82+
expected_mask = torch.full(
83+
(seq_len, 16), float("-inf"), device="cuda", dtype=self.dtype
84+
)
85+
expected_mask[0, 0] = 0
86+
expected_mask[1, :2] = 0
87+
expected_mask[2, :3] = 0
88+
self.assertEqual(
89+
cache.cache_positions_manager.cache_positions.device.type, "cuda"
90+
)
91+
torch.testing.assert_close(
92+
cache.cache_positions_manager.cache_positions, expected_positions
93+
)
94+
self.assertEqual(mask.device.type, "cuda")
95+
torch.testing.assert_close(mask, expected_mask)
96+
2397
def test_basic_update(self):
2498
"""Test basic update functionality of RingKVCache."""
2599
cache = RingKVCache(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import unittest
8+
9+
import torch
10+
from executorch.examples.models.llama.attention import AttentionSkip
11+
from executorch.examples.models.llama.llama_transformer import TransformerBlock
12+
from executorch.examples.models.llama.model_args import ModelArgs
13+
14+
15+
class TestTransformerBlock(unittest.TestCase):
16+
def test_attention_skip_applies_gated_zero_branch_residual(self) -> None:
17+
args = ModelArgs(
18+
dim=4,
19+
hidden_dim=8,
20+
n_heads=1,
21+
n_kv_heads=1,
22+
head_dim=4,
23+
use_residual_gate=True,
24+
)
25+
block = TransformerBlock(
26+
args,
27+
AttentionSkip(),
28+
mlp_type="skip",
29+
layer_id=2,
30+
).eval()
31+
x = torch.randn(1, 3, args.dim)
32+
freqs = torch.empty(0)
33+
34+
output, update = block(x, freqs, freqs, {})
35+
assert block.add_attn is not None
36+
expected = block.add_attn(stream=x, branch=torch.zeros_like(x))
37+
38+
self.assertIsNone(update)
39+
torch.testing.assert_close(output, expected)
40+
self.assertFalse(torch.equal(output, x))

0 commit comments

Comments
 (0)