|
16 | 16 | import torch |
17 | 17 | import triton |
18 | 18 |
|
| 19 | +from flydsl.runtime.device import get_rocm_arch |
| 20 | + |
19 | 21 | try: |
20 | 22 | import aiter |
21 | 23 | from aiter import dtypes, per_tensor_quant, pertoken_quant |
|
80 | 82 | BATCH_SIZE_OPTIONS = [3, 81] |
81 | 83 | SLIDING_WINDOW_OPTIONS = [0] |
82 | 84 |
|
| 85 | +_ARCH = get_rocm_arch() |
| 86 | +_requires_tile_pa = pytest.mark.skipif( |
| 87 | + not (_ARCH == "gfx942" or _ARCH.startswith("gfx95")), |
| 88 | + reason=f"tile PA decode requires gfx942 or gfx95*, got {_ARCH}", |
| 89 | +) |
| 90 | + |
83 | 91 |
|
84 | 92 | def setup_seed(seed: int) -> None: |
85 | 93 | random.seed(seed) |
@@ -1117,6 +1125,224 @@ def test_normal_accuracy( |
1117 | 1125 | ) |
1118 | 1126 |
|
1119 | 1127 |
|
| 1128 | +@pytest.mark.l2_device |
| 1129 | +@pytest.mark.rocm_lower |
| 1130 | +@_requires_tile_pa |
| 1131 | +@pytest.mark.parametrize("compute_type", ["fp8"]) |
| 1132 | +@pytest.mark.parametrize("num_partitions", [4]) |
| 1133 | +@pytest.mark.parametrize("head_size", [192]) |
| 1134 | +@pytest.mark.parametrize("num_heads", [(16, 1)]) |
| 1135 | +@pytest.mark.parametrize("query_length", [4]) |
| 1136 | +@pytest.mark.parametrize("quant_mode", ["per_tensor"]) |
| 1137 | +@pytest.mark.parametrize("context_length", [1027]) |
| 1138 | +@pytest.mark.parametrize("batch_size", [2]) |
| 1139 | +@pytest.mark.parametrize("trans_v", [True]) |
| 1140 | +@pytest.mark.parametrize("kv_varlen", [False]) |
| 1141 | +@pytest.mark.parametrize("block_size", [64]) |
| 1142 | +@pytest.mark.parametrize("sliding_window", [0]) |
| 1143 | +def test_fp8_head_dim_192_matches_torch( |
| 1144 | + compute_type: str, |
| 1145 | + num_partitions: int, |
| 1146 | + head_size: int, |
| 1147 | + num_heads: Tuple[int, int], |
| 1148 | + query_length: int, |
| 1149 | + quant_mode: str, |
| 1150 | + context_length: int, |
| 1151 | + batch_size: int, |
| 1152 | + trans_v: bool, |
| 1153 | + kv_varlen: bool, |
| 1154 | + block_size: int, |
| 1155 | + sliding_window: int, |
| 1156 | +) -> None: |
| 1157 | + from kernels.attention.pa_decode_tile import pa_decode_tile |
| 1158 | + |
| 1159 | + assert quant_mode == "per_tensor" |
| 1160 | + assert not kv_varlen |
| 1161 | + num_query_heads, num_kv_heads = num_heads |
| 1162 | + blocks_per_sequence = triton.cdiv(context_length, block_size) |
| 1163 | + total_blocks = batch_size * blocks_per_sequence |
| 1164 | + device = torch.device("cuda:0") |
| 1165 | + cache_dtype = dtypes.d_dtypes[compute_type] |
| 1166 | + |
| 1167 | + setup_seed(20260821) |
| 1168 | + query = torch.empty( |
| 1169 | + batch_size * query_length, |
| 1170 | + num_query_heads, |
| 1171 | + head_size, |
| 1172 | + dtype=torch.bfloat16, |
| 1173 | + device=device, |
| 1174 | + ).uniform_(-0.5, 0.5) |
| 1175 | + key_caches, value_caches = create_kv_cache( |
| 1176 | + total_blocks, |
| 1177 | + block_size, |
| 1178 | + 1, |
| 1179 | + num_kv_heads, |
| 1180 | + head_size, |
| 1181 | + "auto", |
| 1182 | + torch.bfloat16, |
| 1183 | + seed=20260821, |
| 1184 | + device=str(device), |
| 1185 | + itemsize=1, |
| 1186 | + ) |
| 1187 | + ( |
| 1188 | + key_cache, |
| 1189 | + key_scale_flat, |
| 1190 | + value_cache, |
| 1191 | + value_scale_flat, |
| 1192 | + key_scale, |
| 1193 | + value_scale, |
| 1194 | + ) = quantize_kv_cache_per_tensor(key_caches[0], value_caches[0], quant_dtype=cache_dtype) |
| 1195 | + block_tables = torch.arange(total_blocks - 1, -1, -1, dtype=torch.int32, device=device).reshape( |
| 1196 | + batch_size, blocks_per_sequence |
| 1197 | + ) |
| 1198 | + context_lengths = torch.full((batch_size,), context_length, dtype=torch.int32, device=device) |
| 1199 | + query_output_indptr = torch.arange( |
| 1200 | + 0, |
| 1201 | + (batch_size + 1) * query_length, |
| 1202 | + query_length, |
| 1203 | + dtype=torch.int32, |
| 1204 | + device=device, |
| 1205 | + ) |
| 1206 | + expected = torch_mha_extend( |
| 1207 | + query, |
| 1208 | + key_cache, |
| 1209 | + value_cache, |
| 1210 | + block_tables, |
| 1211 | + context_lengths, |
| 1212 | + query_output_indptr, |
| 1213 | + key_scale_flat, |
| 1214 | + value_scale_flat, |
| 1215 | + sliding_window=sliding_window, |
| 1216 | + ) |
| 1217 | + actual = torch.full_like(query, float("nan")) |
| 1218 | + pa_decode_tile( |
| 1219 | + output=actual, |
| 1220 | + query=query, |
| 1221 | + key_cache=key_cache, |
| 1222 | + value_cache=shuffle_value_cache_layout(value_cache) if trans_v else value_cache, |
| 1223 | + block_tables=block_tables, |
| 1224 | + context_lengths=context_lengths, |
| 1225 | + key_scale=key_scale, |
| 1226 | + value_scale=value_scale, |
| 1227 | + softmax_scale=head_size**-0.5, |
| 1228 | + num_partitions=num_partitions, |
| 1229 | + ) |
| 1230 | + torch.cuda.synchronize() |
| 1231 | + |
| 1232 | + assert bool(torch.isfinite(actual).all().item()) |
| 1233 | + torch.testing.assert_close(actual, expected, rtol=2.0e-2, atol=2.0e-2) |
| 1234 | + |
| 1235 | + |
| 1236 | +@pytest.mark.large_shape |
| 1237 | +@pytest.mark.l2_device |
| 1238 | +@pytest.mark.rocm_lower |
| 1239 | +@_requires_tile_pa |
| 1240 | +@pytest.mark.parametrize("compute_type", ["fp8"]) |
| 1241 | +@pytest.mark.parametrize("num_partitions", [1]) |
| 1242 | +@pytest.mark.parametrize("head_size", [192]) |
| 1243 | +@pytest.mark.parametrize("num_heads", [(16, 1)]) |
| 1244 | +@pytest.mark.parametrize("query_length", [1]) |
| 1245 | +@pytest.mark.parametrize("context_length", [2]) |
| 1246 | +@pytest.mark.parametrize("batch_size", [1]) |
| 1247 | +@pytest.mark.parametrize("trans_v", [True]) |
| 1248 | +@pytest.mark.parametrize("block_size", [64]) |
| 1249 | +def test_fp8_cache_offset_above_2gib( |
| 1250 | + compute_type: str, |
| 1251 | + num_partitions: int, |
| 1252 | + head_size: int, |
| 1253 | + num_heads: Tuple[int, int], |
| 1254 | + query_length: int, |
| 1255 | + context_length: int, |
| 1256 | + batch_size: int, |
| 1257 | + trans_v: bool, |
| 1258 | + block_size: int, |
| 1259 | +) -> None: |
| 1260 | + assert trans_v |
| 1261 | + num_query_heads, num_kv_heads = num_heads |
| 1262 | + cache_dtype = dtypes.d_dtypes[compute_type] |
| 1263 | + page_bytes = num_kv_heads * head_size * block_size * cache_dtype.itemsize |
| 1264 | + high_page = triton.cdiv(2**31, page_bytes) |
| 1265 | + total_blocks = high_page + 1 |
| 1266 | + device = torch.device("cuda:0") |
| 1267 | + |
| 1268 | + query = torch.ones( |
| 1269 | + batch_size * query_length, |
| 1270 | + num_query_heads, |
| 1271 | + head_size, |
| 1272 | + dtype=torch.bfloat16, |
| 1273 | + device=device, |
| 1274 | + ) |
| 1275 | + key_cache = torch.empty( |
| 1276 | + total_blocks, |
| 1277 | + num_kv_heads, |
| 1278 | + head_size // 16, |
| 1279 | + block_size, |
| 1280 | + 16, |
| 1281 | + dtype=cache_dtype, |
| 1282 | + device=device, |
| 1283 | + ) |
| 1284 | + value_cache = torch.empty( |
| 1285 | + total_blocks, |
| 1286 | + num_kv_heads, |
| 1287 | + block_size // 16, |
| 1288 | + head_size, |
| 1289 | + 16, |
| 1290 | + dtype=cache_dtype, |
| 1291 | + device=device, |
| 1292 | + ) |
| 1293 | + # Rounded-up tiles use page 0 for bounded fallback loads. Keep both that |
| 1294 | + # page and the selected high page finite so masked PV lanes cannot see NaNs. |
| 1295 | + key_cache[0].zero_() |
| 1296 | + value_cache[0].zero_() |
| 1297 | + key_cache[high_page].zero_() |
| 1298 | + value_cache[high_page].zero_() |
| 1299 | + |
| 1300 | + # Make the output sensitive to both selected K/V token addresses. If K |
| 1301 | + # wraps to page 0, the opposing values average to zero. |
| 1302 | + key_cache[high_page, 0, :, 0, :].fill_(-0.25) |
| 1303 | + key_cache[high_page, 0, :, 1, :].fill_(0.25) |
| 1304 | + value_cache[high_page, 0, 0, :, 0].fill_(-1.0) |
| 1305 | + value_cache[high_page, 0, 0, :, 1].fill_(1.0) |
| 1306 | + |
| 1307 | + selected_keys = ( |
| 1308 | + key_cache[high_page].permute(2, 0, 1, 3).reshape(block_size, num_kv_heads, head_size)[:context_length] |
| 1309 | + ) |
| 1310 | + selected_values = ( |
| 1311 | + value_cache[high_page].permute(1, 3, 0, 2).reshape(block_size, num_kv_heads, head_size)[:context_length] |
| 1312 | + ) |
| 1313 | + expected = reference_masked_attention( |
| 1314 | + query, |
| 1315 | + selected_keys, |
| 1316 | + selected_values, |
| 1317 | + head_size**-0.5, |
| 1318 | + query.dtype, |
| 1319 | + ) |
| 1320 | + assert expected.float().abs().min().item() > 0.5 |
| 1321 | + |
| 1322 | + block_tables = torch.full((batch_size, 1), high_page, dtype=torch.int32, device=device) |
| 1323 | + context_lengths = torch.full((batch_size,), context_length, dtype=torch.int32, device=device) |
| 1324 | + scale = torch.ones(1, dtype=torch.float32, device=device) |
| 1325 | + actual = torch.full_like(query, float("nan")) |
| 1326 | + from kernels.attention.pa_decode_tile import pa_decode_tile |
| 1327 | + |
| 1328 | + pa_decode_tile( |
| 1329 | + output=actual, |
| 1330 | + query=query, |
| 1331 | + key_cache=key_cache, |
| 1332 | + value_cache=value_cache, |
| 1333 | + block_tables=block_tables, |
| 1334 | + context_lengths=context_lengths, |
| 1335 | + key_scale=scale, |
| 1336 | + value_scale=scale, |
| 1337 | + softmax_scale=head_size**-0.5, |
| 1338 | + num_partitions=num_partitions, |
| 1339 | + ) |
| 1340 | + torch.cuda.synchronize() |
| 1341 | + |
| 1342 | + assert bool(torch.isfinite(actual).all().item()) |
| 1343 | + torch.testing.assert_close(actual, expected, rtol=2.0e-2, atol=2.0e-2) |
| 1344 | + |
| 1345 | + |
1120 | 1346 | @pytest.mark.parametrize(("query_length", "quant_mode"), [(1, "per_tensor"), (4, "per_token")]) |
1121 | 1347 | def test_metadata_accuracy(query_length: int, quant_mode: str) -> None: |
1122 | 1348 | """Exercise the block-1024 persistent worklist decode and split reducer.""" |
|
0 commit comments