import math
import os
import sys
import unittest
from contextlib import nullcontext
from pathlib import Path
from unittest.mock import patch
import torch
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from utils.utils.precision_compare import data_compare
from mindiesd.utils.exception import ParametersInvalid
try:
from mindiesd.layers.flash_attn.sparse_linear_attn import (
SparseLinearAttention,
_ASCENDC_BLKK_ALIGN,
_ASCENDC_SUPPORTED_HEAD_DIMS,
_ascendc_shape_supported,
_SUPPORTED_SPARSE_BACKENDS,
_TRITON_SUPPORTED_BLOCK_SIZES,
_TRITON_SUPPORTED_HEAD_DIMS,
_triton_shape_supported,
_resolve_sparse_attn_backend,
_validate_inner_precise,
get_block_map,
)
_IMPORT_OK = True
_IMPORT_ERR = None
except ImportError as exc:
SparseLinearAttention = None
_ASCENDC_BLKK_ALIGN = 128
_ASCENDC_SUPPORTED_HEAD_DIMS = ()
_ascendc_shape_supported = None
_SUPPORTED_SPARSE_BACKENDS = ()
_TRITON_SUPPORTED_BLOCK_SIZES = (64, 128)
_TRITON_SUPPORTED_HEAD_DIMS = ()
_triton_shape_supported = None
_resolve_sparse_attn_backend = None
_validate_inner_precise = None
get_block_map = None
_IMPORT_OK = False
_IMPORT_ERR = exc
_SKIP_NO_TRITON = unittest.skipIf(
not _IMPORT_OK,
f'sparse_linear_attn import failed (need triton / sparse_linear_attn_triton): {_IMPORT_ERR}',
)
_SKIP_NPU = unittest.skipIf(
os.environ.get('MINDIE_TEST_MODE', 'ALL') == 'CPU',
'Skip NPU-dependent tests when MINDIE_TEST_MODE is CPU.',
)
BATCH = 1
HEAD = 2
HEADDIM = 64
HEADDIM_128 = 128
SEQ = 256
TOPK = 0.25
BLK = 128
BLK_TRITON = 64
_UNSUPPORTED_HEAD_DIMS = (96, 48, 192, 256, 512)
_UNSUPPORTED_BLK_COMBOS = ((16, 64), (32, 64), (100, 64), (64, 96), (128, 96))
_TRITON_BLK_COMBOS = (
(64, 64),
(64, 128),
(128, 64),
(128, 128),
)
_ASCENDC_BLK_COMBOS = (
(64, 128),
(128, 128),
(100, 128),
(128, 256),
)
_RESOLVE_PATCH = 'mindiesd.layers.flash_attn.sparse_linear_attn._resolve_sparse_attn_backend'
_ATTN_PATCH = 'mindiesd.layers.flash_attn.sparse_linear_attn._attention.apply'
_BSA_PATCH = 'mindiesd.layers.flash_attn.sparse_linear_attn.block_sparse_attention'
PRECISION_BATCH = 1
PRECISION_HEAD = 1
PRECISION_SEQ_LEN = 1048576
PRECISION_HEAD_DIM = 128
PRECISION_BLK = 128
PRECISION_INNER_PRECISE = 0
PRECISION_SEED = 42
def _backend_for(mod):
return _resolve_sparse_attn_backend(mod.proj_l.in_features, mod.BLKQ, mod.BLKK)
def _iter_ascendc_shape_matrix():
for head_dim in _ASCENDC_SUPPORTED_HEAD_DIMS:
for blkq, blkk in _ASCENDC_BLK_COMBOS:
yield head_dim, blkq, blkk
def _iter_triton_shape_matrix():
for head_dim in _TRITON_SUPPORTED_HEAD_DIMS:
for blkq in _TRITON_SUPPORTED_BLOCK_SIZES:
for blkk in _TRITON_SUPPORTED_BLOCK_SIZES:
yield head_dim, blkq, blkk
def _ascendc_npu_runnable(head_dim, blkq, blkk):
return not (head_dim == 128 and blkk == 256)
def _triton_npu_runnable(head_dim, blkq, blkk):
return not (head_dim == 128 and blkq == 64 and blkk == 128)
def _iter_ascendc_npu_shapes():
for head_dim, blkq, blkk in _iter_ascendc_shape_matrix():
if _ascendc_npu_runnable(head_dim, blkq, blkk):
yield head_dim, blkq, blkk
def _iter_triton_npu_shapes():
for head_dim, blkq, blkk in _iter_triton_shape_matrix():
if _triton_npu_runnable(head_dim, blkq, blkk):
yield head_dim, blkq, blkk
def _mock_block_map_return(seq_len=SEQ, batch=BATCH, head=HEAD, blk=BLK):
q_blocks = math.ceil(seq_len / blk)
kv_blocks = math.ceil(seq_len / blk)
return (
torch.zeros(batch, head, q_blocks, kv_blocks, dtype=torch.int8),
torch.zeros(batch, head, q_blocks, 1, dtype=torch.int32),
1,
)
@_SKIP_NO_TRITON
class TestSparseLinearAttentionInit(unittest.TestCase):
def test_default_backend_falls_back_to_triton_when_blkk_64(self):
mod = SparseLinearAttention(head_dim=HEADDIM, topk=TOPK)
self.assertEqual(mod.BLKQ, BLK_TRITON)
self.assertEqual(mod.BLKK, BLK_TRITON)
self.assertEqual(_backend_for(mod), 'triton')
def test_auto_selects_ascendc_when_shape_supported(self):
mod = SparseLinearAttention(head_dim=HEADDIM, topk=TOPK, BLKQ=BLK, BLKK=BLK)
self.assertEqual(_backend_for(mod), 'ascendc')
def test_supported_backends(self):
self.assertEqual(_SUPPORTED_SPARSE_BACKENDS, ('triton', 'ascendc'))
def test_ascendc_supported_head_dims_constant(self):
self.assertEqual(_ASCENDC_SUPPORTED_HEAD_DIMS, (64, 128))
def test_block_size_constants(self):
self.assertEqual(_ASCENDC_BLKK_ALIGN, 128)
self.assertEqual(_TRITON_SUPPORTED_BLOCK_SIZES, (64, 128))
def test_resolve_prefers_ascendc_when_both_supported(self):
self.assertEqual(_resolve_sparse_attn_backend(64, 64, 128), 'ascendc')
self.assertEqual(_resolve_sparse_attn_backend(128, 128, 128), 'ascendc')
def test_resolve_falls_back_to_triton_when_ascendc_unsupported(self):
for head_dim, blkq, blkk in ((64, 64, 64), (32, 64, 128), (16, 64, 64)):
with self.subTest(head_dim=head_dim, blkq=blkq, blkk=blkk):
self.assertEqual(
_resolve_sparse_attn_backend(head_dim, blkq, blkk),
'triton',
)
mod = SparseLinearAttention(head_dim=HEADDIM, topk=TOPK, BLKQ=100, BLKK=BLK)
self.assertEqual(_backend_for(mod), 'ascendc')
def test_ascendc_accepts_supported_head_dims_at_init(self):
for head_dim in (HEADDIM, HEADDIM_128):
with self.subTest(head_dim=head_dim):
mod = SparseLinearAttention(
head_dim=head_dim,
topk=TOPK,
BLKQ=BLK,
BLKK=BLK,
)
self.assertEqual(_backend_for(mod), 'ascendc')
self.assertEqual(mod.proj_l.in_features, head_dim)
def test_ascendc_accepts_supported_block_sizes_at_init(self):
for blkq, blkk in ((BLK, BLK), (64, BLK), (128, 256)):
with self.subTest(blkq=blkq, blkk=blkk):
mod = SparseLinearAttention(
head_dim=HEADDIM,
topk=TOPK,
BLKQ=blkq,
BLKK=blkk,
)
self.assertEqual(_backend_for(mod), 'ascendc')
self.assertEqual(mod.BLKQ, blkq)
self.assertEqual(mod.BLKK, blkk)
def test_triton_fallback_for_small_head_dim(self):
for head_dim in (16, 32):
with self.subTest(head_dim=head_dim):
mod = SparseLinearAttention(
head_dim=head_dim,
topk=TOPK,
BLKQ=BLK_TRITON,
BLKK=BLK_TRITON,
)
self.assertEqual(_backend_for(mod), 'triton')
def test_triton_fallback_when_blkk_not_aligned(self):
mod = SparseLinearAttention(
head_dim=HEADDIM,
topk=TOPK,
BLKQ=BLK_TRITON,
BLKK=BLK_TRITON,
)
self.assertEqual(_backend_for(mod), 'triton')
def test_rejects_unsupported_head_dim_at_init(self):
for head_dim in _UNSUPPORTED_HEAD_DIMS:
with self.subTest(head_dim=head_dim):
with self.assertRaises(ParametersInvalid):
SparseLinearAttention(
head_dim=head_dim,
topk=TOPK,
BLKQ=BLK_TRITON,
BLKK=BLK_TRITON,
)
def test_rejects_unsupported_block_combo_at_init(self):
for blkq, blkk in _UNSUPPORTED_BLK_COMBOS:
with self.subTest(blkq=blkq, blkk=blkk):
with self.assertRaises(ParametersInvalid):
SparseLinearAttention(
head_dim=HEADDIM,
topk=TOPK,
BLKQ=blkq,
BLKK=blkk,
)
def test_triton_supported_head_dims_constant(self):
self.assertEqual(_TRITON_SUPPORTED_HEAD_DIMS, (16, 32, 64, 128))
def test_triton_accepts_supported_head_dims_via_fallback(self):
for head_dim in _TRITON_SUPPORTED_HEAD_DIMS:
with self.subTest(head_dim=head_dim):
blkk = BLK if head_dim in _ASCENDC_SUPPORTED_HEAD_DIMS else BLK_TRITON
blkq = BLK_TRITON if blkk == BLK_TRITON else BLK
mod = SparseLinearAttention(
head_dim=head_dim,
topk=TOPK,
BLKQ=blkq,
BLKK=blkk,
)
expected = 'ascendc' if head_dim in _ASCENDC_SUPPORTED_HEAD_DIMS and blkk == BLK else 'triton'
self.assertEqual(_backend_for(mod), expected)
self.assertEqual(mod.proj_l.in_features, head_dim)
@_SKIP_NO_TRITON
class TestInnerPreciseValidation(unittest.TestCase):
def test_bf16_non_950_only_accepts_inner_precise_0(self):
_validate_inner_precise(0, is_950=False, use_bf16=True)
for invalid in (1, 4):
with self.subTest(inner_precise=invalid):
with self.assertRaises(ParametersInvalid):
_validate_inner_precise(invalid, is_950=False, use_bf16=True)
def test_fp16_non_950_accepts_inner_precise_0_and_1(self):
for val in (0, 1):
with self.subTest(inner_precise=val):
_validate_inner_precise(val, is_950=False, use_bf16=False)
with self.assertRaises(ParametersInvalid):
_validate_inner_precise(4, is_950=False, use_bf16=False)
def test_950_only_accepts_inner_precise_4(self):
_validate_inner_precise(4, is_950=True, use_bf16=True)
_validate_inner_precise(4, is_950=True, use_bf16=False)
for invalid in (0, 1):
with self.subTest(inner_precise=invalid):
with self.assertRaises(ParametersInvalid):
_validate_inner_precise(invalid, is_950=True, use_bf16=True)
@_SKIP_NPU
@_SKIP_NO_TRITON
class TestGetBlockMap(unittest.TestCase):
def test_sparse_map_dtype_and_shape(self):
device = torch.device('npu:0')
q = torch.randn(BATCH, HEAD, SEQ, HEADDIM, device=device)
k = torch.randn(BATCH, HEAD, SEQ, HEADDIM, device=device)
sparse_map, lut, real_topk = get_block_map(q, k, topk_ratio=0.5, BLKQ=BLK, BLKK=BLK)
q_blocks = math.ceil(SEQ / BLK)
kv_blocks = math.ceil(SEQ / BLK)
self.assertEqual(sparse_map.dtype, torch.int8)
self.assertEqual(tuple(sparse_map.shape), (BATCH, HEAD, q_blocks, kv_blocks))
self.assertEqual(lut.shape[-1], real_topk)
self.assertGreater(real_topk, 0)
def test_sparse_map_dtype_and_shape_head_dim_128(self):
device = torch.device('npu:0')
q = torch.randn(BATCH, HEAD, SEQ, HEADDIM_128, dtype=torch.float16, device=device)
k = torch.randn(BATCH, HEAD, SEQ, HEADDIM_128, dtype=torch.float16, device=device)
sparse_map, lut, real_topk = get_block_map(q, k, topk_ratio=0.5, BLKQ=BLK, BLKK=BLK)
q_blocks = math.ceil(SEQ / BLK)
kv_blocks = math.ceil(SEQ / BLK)
self.assertEqual(sparse_map.dtype, torch.int8)
self.assertEqual(tuple(sparse_map.shape), (BATCH, HEAD, q_blocks, kv_blocks))
self.assertEqual(lut.shape[-1], real_topk)
self.assertGreater(real_topk, 0)
def test_sparse_map_dtype_and_shape_blk_64(self):
device = torch.device('npu:0')
q = torch.randn(BATCH, HEAD, SEQ, HEADDIM, dtype=torch.float16, device=device)
k = torch.randn(BATCH, HEAD, SEQ, HEADDIM, dtype=torch.float16, device=device)
sparse_map, lut, real_topk = get_block_map(q, k, topk_ratio=0.5, BLKQ=BLK_TRITON, BLKK=BLK_TRITON)
q_blocks = math.ceil(SEQ / BLK_TRITON)
kv_blocks = math.ceil(SEQ / BLK_TRITON)
self.assertEqual(sparse_map.dtype, torch.int8)
self.assertEqual(tuple(sparse_map.shape), (BATCH, HEAD, q_blocks, kv_blocks))
self.assertEqual(lut.shape[-1], real_topk)
self.assertGreater(real_topk, 0)
@_SKIP_NO_TRITON
class TestSparseAttentionForward(unittest.TestCase):
@patch('mindiesd.layers.flash_attn.sparse_linear_attn.get_block_map')
def test_rejects_non_npu_device(self, mock_get_block_map):
mock_get_block_map.return_value = _mock_block_map_return()
q = torch.randn(BATCH, HEAD, SEQ, HEADDIM)
k = torch.randn_like(q)
v = torch.randn_like(q)
mod = SparseLinearAttention(
head_dim=HEADDIM,
topk=TOPK,
BLKQ=BLK,
BLKK=BLK,
)
with self.assertRaises(ParametersInvalid):
mod(q, k, v)
mock_get_block_map.assert_not_called()
@patch('mindiesd.layers.flash_attn.sparse_linear_attn.get_block_map')
def test_rejects_head_dim_mismatch_in_forward(self, mock_get_block_map):
mock_get_block_map.return_value = _mock_block_map_return()
q = torch.randn(BATCH, HEAD, SEQ, 32)
k = torch.randn_like(q)
v = torch.randn_like(q)
mod = SparseLinearAttention(
head_dim=HEADDIM,
topk=TOPK,
BLKQ=BLK,
BLKK=BLK,
)
with self.assertRaises(ParametersInvalid):
mod(q, k, v)
mock_get_block_map.assert_not_called()
@_SKIP_NPU
@_SKIP_NO_TRITON
class TestSparseLinearAttentionNPU(unittest.TestCase):
def setUp(self):
self.device = torch.device('npu:0')
torch.npu.set_device(self.device)
dev_name = torch.npu.get_device_properties(self.device).name
self.is_950 = '950' in dev_name
self.inner_precise = 4 if self.is_950 else 0
self.seq_len = 1024
def _run_npu_forward(
self,
head_dim,
blkq,
blkk,
*,
use_bf16=False,
inner_precise=None,
input_dtype=None,
force_backend=None,
):
if input_dtype is None:
input_dtype = torch.bfloat16 if use_bf16 else torch.float16
q = torch.randn(
BATCH,
HEAD,
self.seq_len,
head_dim,
dtype=input_dtype,
device=self.device,
)
k = torch.randn_like(q)
v = torch.randn_like(q)
mod = SparseLinearAttention(
head_dim=head_dim,
topk=0.5,
BLKQ=blkq,
BLKK=blkk,
use_bf16=use_bf16,
inner_precise=inner_precise,
).to(self.device)
expected_backend = force_backend or _resolve_sparse_attn_backend(head_dim, blkq, blkk)
resolve_ctx = patch(_RESOLVE_PATCH, return_value=force_backend) if force_backend is not None else nullcontext()
with resolve_ctx:
if expected_backend == 'triton':
with patch(_BSA_PATCH) as mock_bsa:
out = mod(q, k, v)
mock_bsa.assert_not_called()
else:
with patch(_ATTN_PATCH) as mock_attn:
out = mod(q, k, v)
mock_attn.assert_not_called()
self.assertEqual(tuple(out.shape), (BATCH, HEAD, self.seq_len, head_dim))
self.assertEqual(out.dtype, input_dtype)
return out
def test_ascendc_npu_all_supported_shapes_fp16(self):
"""实机:ascendc 自然路径覆盖其支持的全部 shape。"""
for head_dim, blkq, blkk in _iter_ascendc_npu_shapes():
with self.subTest(head_dim=head_dim, blkq=blkq, blkk=blkk):
self.assertTrue(_ascendc_shape_supported(head_dim, blkq, blkk))
self.assertEqual(_resolve_sparse_attn_backend(head_dim, blkq, blkk), 'ascendc')
self._run_npu_forward(
head_dim,
blkq,
blkk,
inner_precise=self.inner_precise,
)
def test_ascendc_npu_all_supported_shapes_bf16(self):
"""实机:ascendc bf16 覆盖其支持的全部 shape。"""
for head_dim, blkq, blkk in _iter_ascendc_npu_shapes():
with self.subTest(head_dim=head_dim, blkq=blkq, blkk=blkk):
self._run_npu_forward(
head_dim,
blkq,
blkk,
use_bf16=True,
inner_precise=None,
)
def test_triton_npu_natural_fallback_shapes_fp16(self):
"""实机:triton 自然 fallback 覆盖 ascendc 不支持的 shape。"""
for head_dim, blkq, blkk in _iter_triton_npu_shapes():
if _ascendc_shape_supported(head_dim, blkq, blkk):
continue
with self.subTest(head_dim=head_dim, blkq=blkq, blkk=blkk):
self.assertTrue(_triton_shape_supported(head_dim, blkq, blkk))
self.assertEqual(_resolve_sparse_attn_backend(head_dim, blkq, blkk), 'triton')
self._run_npu_forward(head_dim, blkq, blkk)
def test_triton_npu_natural_fallback_shapes_bf16(self):
"""实机:triton bf16 自然 fallback 覆盖 ascendc 不支持的 shape。"""
for head_dim, blkq, blkk in _iter_triton_npu_shapes():
if _ascendc_shape_supported(head_dim, blkq, blkk):
continue
with self.subTest(head_dim=head_dim, blkq=blkq, blkk=blkk):
self._run_npu_forward(head_dim, blkq, blkk, use_bf16=True)
def test_triton_npu_mock_all_supported_overlap_shapes_fp16(self):
"""实机:mock resolve 强制走 triton,覆盖 ascendc 也支持但默认不会选中的 shape。"""
for head_dim, blkq, blkk in _iter_triton_npu_shapes():
if not _ascendc_shape_supported(head_dim, blkq, blkk):
continue
with self.subTest(head_dim=head_dim, blkq=blkq, blkk=blkk):
self.assertTrue(_triton_shape_supported(head_dim, blkq, blkk))
self._run_npu_forward(
head_dim,
blkq,
blkk,
force_backend='triton',
)
def test_triton_npu_mock_all_supported_overlap_shapes_bf16(self):
"""实机:mock resolve 强制走 triton(bf16),覆盖 ascendc 也支持的 shape。"""
for head_dim, blkq, blkk in _iter_triton_npu_shapes():
if not _ascendc_shape_supported(head_dim, blkq, blkk):
continue
with self.subTest(head_dim=head_dim, blkq=blkq, blkk=blkk):
self._run_npu_forward(
head_dim,
blkq,
blkk,
use_bf16=True,
force_backend='triton',
)
def _expect_triton_compile_error(self):
try:
from triton.compiler.errors import MLIRCompilationError
return (MLIRCompilationError,)
except ImportError:
return (Exception,)
def test_triton_head_dim_256_get_block_map_ub_overflow(self):
"""head_dim=256:init 已拒绝;get_block_map compress_kernel 在 910B 仍 UB 溢出。"""
q = torch.randn(
BATCH,
HEAD,
self.seq_len,
256,
dtype=torch.float16,
device=self.device,
)
k = torch.randn_like(q)
with self.assertRaises(self._expect_triton_compile_error()):
get_block_map(q, k, topk_ratio=0.5, BLKQ=BLK, BLKK=BLK)
def test_ascendc_hd128_blkk256_get_block_map_ub_overflow(self):
"""ascendc 声明 BLKK=256,但 head_dim=128 时 get_block_map 在 910B UB 溢出。"""
q = torch.randn(
BATCH,
HEAD,
self.seq_len,
HEADDIM_128,
dtype=torch.float16,
device=self.device,
)
k = torch.randn_like(q)
with self.assertRaises(self._expect_triton_compile_error()):
get_block_map(q, k, topk_ratio=0.5, BLKQ=BLK, BLKK=256)
def test_triton_hd128_blk64_128_attention_ub_overflow(self):
"""triton 声明支持 (128,64,128),但 _attention 在 910B UB 溢出;mock 强制走 triton。"""
q = torch.randn(
BATCH,
HEAD,
self.seq_len,
HEADDIM_128,
dtype=torch.float16,
device=self.device,
)
k = torch.randn_like(q)
v = torch.randn_like(q)
mod = SparseLinearAttention(
head_dim=HEADDIM_128,
topk=0.5,
BLKQ=64,
BLKK=128,
).to(self.device)
with patch(_RESOLVE_PATCH, return_value='triton'):
with patch(_BSA_PATCH) as mock_bsa:
with self.assertRaises(self._expect_triton_compile_error()):
mod(q, k, v)
mock_bsa.assert_not_called()
def test_rejects_wrong_inner_precise_for_device(self):
if self.is_950:
invalid_cases = ((0, True), (0, False), (1, True), (1, False))
else:
invalid_cases = ((4, True), (4, False), (1, True))
for val, use_bf16 in invalid_cases:
with self.subTest(inner_precise=val, use_bf16=use_bf16, is_950=self.is_950):
input_dtype = torch.bfloat16 if use_bf16 else torch.float16
q = torch.randn(
BATCH,
HEAD,
self.seq_len,
HEADDIM,
dtype=input_dtype,
device=self.device,
)
k = torch.randn_like(q)
v = torch.randn_like(q)
mod = SparseLinearAttention(
head_dim=HEADDIM,
topk=TOPK,
BLKQ=BLK,
BLKK=BLK,
inner_precise=val,
use_bf16=use_bf16,
).to(self.device)
self.assertEqual(_backend_for(mod), 'ascendc')
with self.assertRaises(ParametersInvalid):
mod(q, k, v)
def test_accepts_correct_inner_precise_for_device(self):
q = torch.randn(BATCH, HEAD, self.seq_len, HEADDIM, dtype=torch.float16, device=self.device)
k = torch.randn_like(q)
v = torch.randn_like(q)
mod = SparseLinearAttention(
head_dim=HEADDIM,
topk=TOPK,
BLKQ=BLK,
BLKK=BLK,
use_bf16=False,
inner_precise=self.inner_precise,
).to(self.device)
self.assertEqual(_backend_for(mod), 'ascendc')
out = mod(q, k, v)
self.assertEqual(tuple(out.shape), (BATCH, HEAD, self.seq_len, HEADDIM))
def _run_precision_forward(mod, q, k, v, *, force_backend=None):
ctx = patch(_RESOLVE_PATCH, return_value=force_backend) if force_backend is not None else nullcontext()
with ctx:
return mod(q, k, v)
@_SKIP_NPU
@_SKIP_NO_TRITON
class TestSparseLinearAttentionPrecision(unittest.TestCase):
"""Compare ascendc (default) and triton (mock-forced) forward outputs on NPU."""
def setUp(self):
self.device = torch.device('npu:0')
torch.npu.set_device(self.device)
dev_name = torch.npu.get_device_properties(self.device).name
if '950' in dev_name:
self.skipTest('inner_precise=0 with bf16 is unsupported on 950 series devices.')
def test_ascendc_vs_triton_bf16_large_seq(self):
"""ascendc vs triton on (1,1,1048576,128), BLKQ=BLKK=128, bf16, inner_precise=0."""
torch.manual_seed(PRECISION_SEED)
q = torch.randn(
PRECISION_BATCH,
PRECISION_HEAD,
PRECISION_SEQ_LEN,
PRECISION_HEAD_DIM,
dtype=torch.bfloat16,
device=self.device,
)
k = torch.randn_like(q)
v = torch.randn_like(q)
mod = SparseLinearAttention(
head_dim=PRECISION_HEAD_DIM,
topk=TOPK,
BLKQ=PRECISION_BLK,
BLKK=PRECISION_BLK,
use_bf16=True,
inner_precise=PRECISION_INNER_PRECISE,
).to(self.device)
self.assertEqual(
_resolve_sparse_attn_backend(PRECISION_HEAD_DIM, PRECISION_BLK, PRECISION_BLK),
'ascendc',
)
torch.npu.synchronize()
out_ascendc = _run_precision_forward(mod, q, k, v)
torch.npu.synchronize()
torch.npu.synchronize()
out_triton = _run_precision_forward(mod, q, k, v, force_backend='triton')
torch.npu.synchronize()
self.assertEqual(
tuple(out_ascendc.shape),
(PRECISION_BATCH, PRECISION_HEAD, PRECISION_SEQ_LEN, PRECISION_HEAD_DIM),
)
self.assertEqual(tuple(out_triton.shape), tuple(out_ascendc.shape))
self.assertEqual(out_ascendc.dtype, torch.bfloat16)
self.assertEqual(out_triton.dtype, torch.bfloat16)
result, fulfill_pct, max_err = data_compare(
out_triton.detach().cpu(),
out_ascendc.detach().cpu(),
)
self.assertEqual(
result,
'success',
msg=(f'ascendc vs triton mismatch: fulfill={fulfill_pct:.4f}%, max_relative_err={max_err}'),
)
if __name__ == '__main__':
unittest.main()