Skip to content

Commit 609f649

Browse files
[OP] Add flashmla baseline implementation and precision test (#7477)
1 parent 3c8c82d commit 609f649

2 files changed

Lines changed: 108 additions & 0 deletions

File tree

fastdeploy/model_executor/layers/attention/mla_attention_backend.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,33 @@ def forward_mixed(
713713
)
714714

715715
return final_res
716+
717+
@staticmethod
718+
def flashmla_baseline(decoder_q, latent_cache, block_table, cache_seqlens, attn_softmax_scale):
719+
page_size = 64
720+
q_num_heads = decoder_q.shape[2]
721+
assert decoder_q.shape[1:] == [1, q_num_heads, 576]
722+
assert latent_cache.shape[1:] == [1, page_size, 576]
723+
724+
res_baseline = paddle.zeros([decoder_q.shape[0], 1, q_num_heads, 512])
725+
for batch_id in range(decoder_q.shape[0]):
726+
kv_len = cache_seqlens[batch_id].item()
727+
extract_k = paddle.zeros([kv_len, 576], dtype=decoder_q.dtype)
728+
extract_v = paddle.zeros([kv_len, 512], dtype=decoder_q.dtype)
729+
730+
for local_seq_id in range(0, kv_len, page_size):
731+
start = local_seq_id
732+
end = min(local_seq_id + page_size, kv_len)
733+
physical_id = block_table[batch_id, local_seq_id // page_size].item()
734+
735+
page_end = page_size if end % page_size == 0 else end % page_size
736+
extract_k[start:end, :] = latent_cache[physical_id, 0, :page_end, :]
737+
extract_v[start:end, :] = latent_cache[physical_id, 0, :page_end, :512]
738+
739+
this_batch_q = decoder_q[batch_id, 0, :, :]
740+
p = paddle.matmul(this_batch_q, extract_k.transpose([1, 0]).contiguous())
741+
p = p * attn_softmax_scale
742+
p = paddle.nn.functional.softmax(p, -1)
743+
res_baseline[batch_id, 0, :, :] = paddle.matmul(p, extract_v).contiguous()
744+
745+
return res_baseline
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""
16+
17+
import unittest
18+
19+
import paddle
20+
21+
paddle.set_default_dtype("bfloat16")
22+
23+
from fastdeploy.model_executor.layers.attention.mla_attention_backend import (
24+
MLAAttentionBackend,
25+
)
26+
27+
28+
class TestFlashMLA(unittest.TestCase):
29+
def setUp(self):
30+
pass
31+
32+
def test_flashmla(self):
33+
bsz = 128
34+
kv_len = 1000
35+
decoder_q = paddle.randn([bsz, 1, 128, 576], dtype="bfloat16")
36+
cache_seqlens = paddle.zeros([bsz], dtype="int32") + kv_len
37+
block_tables = paddle.arange((kv_len // 64 + 1) * bsz, dtype="int32").reshape([bsz, -1])
38+
latent_cache = paddle.randn([10000, 1, 64, 576], dtype="bfloat16")
39+
# copy from dsv3
40+
attn_softmax_scale = 0.1352337788608801
41+
42+
baseline_out = MLAAttentionBackend.flashmla_baseline(
43+
decoder_q, latent_cache, block_tables, cache_seqlens, attn_softmax_scale
44+
)
45+
46+
paddle.enable_compat(scope={"flash_mla"}) # Enable paddle.enable_compat before importing flash_mla
47+
try:
48+
import flash_mla
49+
except ImportError:
50+
print(100 * "Please install flash_mla first")
51+
return
52+
53+
tile_scheduler_metadata, num_splits = flash_mla.get_mla_metadata()
54+
55+
new_cache_shape = latent_cache.shape
56+
assert new_cache_shape[1] == 1
57+
new_cache_shape[1], new_cache_shape[2] = new_cache_shape[2], new_cache_shape[1]
58+
59+
decoder_res, _ = flash_mla.flash_mla_with_kvcache(
60+
decoder_q,
61+
# 外面的开源仓库的kv cache存储格式和FD的不同
62+
# 幸好这里缓存的头是1,直接view即可,否则上上下下要改很多!
63+
latent_cache.view(new_cache_shape),
64+
block_tables,
65+
cache_seqlens,
66+
512, # t.dv,
67+
tile_scheduler_metadata,
68+
num_splits,
69+
softmax_scale=attn_softmax_scale,
70+
causal=True,
71+
)
72+
73+
max_diff = (decoder_res - baseline_out).abs().max().item()
74+
self.assertLessEqual(max_diff, 0.1)
75+
76+
77+
if __name__ == "__main__":
78+
unittest.main()

0 commit comments

Comments
 (0)