77import unittest
88
99import torch
10- from executorch .examples .models .llama .attention import RingKVCache
10+ from executorch .examples .models .llama .attention import KVCache , RingKVCache
1111
1212
1313class 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 (
0 commit comments