Skip to content

Commit 1eb4810

Browse files
feat(autotune): separate general and decode attention warmup (#1549)
Co-authored-by: shihaobai <42648726+shihaobai@users.noreply.github.com>
1 parent 196bcb4 commit 1eb4810

36 files changed

Lines changed: 3332 additions & 235 deletions

lightllm/common/basemodel/attention/fa3/fp.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ class Fa3DecodeAttState(BaseDecodeAttState):
158158
b_att_seq_len: torch.Tensor = None
159159
# 在是否开启mtp 的不同模式下,其设置不同的值,可以加速算子的运行。
160160
decode_max_q_seq_len: int = None
161+
decode_max_kv_seq_len: int = None
161162
causal: bool = None
162163

163164
def init_state(self):
@@ -225,6 +226,8 @@ def _init_page_table(self, b_att_req_idx: torch.Tensor):
225226
att_batch_size = b_att_req_idx.shape[0]
226227
model = self.backend.model
227228
actual_max_kv_len = self.infer_state.max_kv_seq_len
229+
# Graph 捕获会将 infer_state.max_kv_seq_len 改为容量上限,提前保存真实长度用于 FA3 配置查找。
230+
self.decode_max_kv_seq_len = actual_max_kv_len
228231
page_table_width = actual_max_kv_len
229232
if model.graph is not None and model.graph.can_run(
230233
batch_size=self.infer_state.batch_size,
@@ -295,8 +298,9 @@ def _normal_decode_att(
295298
page_table=self.page_table,
296299
cache_seqlens=self.b_att_seq_len,
297300
cu_seqlens_q=self.cu_seqlens_q,
298-
cu_seqlens_k_new=self.cu_seqlens_k,
301+
cu_seqlens_k_new=None, # KV 已提前写入缓存,此处不追加新的 K/V。
299302
max_seqlen_q=self.decode_max_q_seq_len,
303+
max_seqlen_k=self.decode_max_kv_seq_len,
300304
softmax_scale=sm_scale,
301305
causal=self.causal,
302306
window_size=window_size,

lightllm/common/basemodel/attention/triton/fp.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,11 @@ def _nomarl_prefill_att(
9595
@dataclasses.dataclass
9696
class TritonDecodeAttState(BaseDecodeAttState):
9797
b_mark_mtp_shared_group: torch.Tensor = None
98+
decode_max_kv_seq_len: int = None
9899

99100
def init_state(self):
101+
# Graph 捕获会改写 infer_state 的长度上限,提前保存真实长度用于 GQA decode 配置查找。
102+
self.decode_max_kv_seq_len = self.infer_state.max_kv_seq_len
100103
draft_step = self.backend.model.mtp_manager.get_decode_draft_step(self.backend.model.is_mtp_draft_model)
101104
if draft_step > 0:
102105
self.b_mark_mtp_shared_group = build_mtp_shared_group_markers(
@@ -212,6 +215,7 @@ def _normal_decode_gqa_flash_decoding_att(
212215
infer_state=self.infer_state,
213216
cache_k=k,
214217
cache_v=v,
218+
max_len_in_batch=self.decode_max_kv_seq_len,
215219
out=out,
216220
alloc_tensor_func=alloc_func,
217221
sliding_window=sliding_window,
@@ -238,6 +242,7 @@ def _spec_decode_gqa_att(
238242
B_req_idx=self.infer_state.b_req_idx,
239243
b_seq_len=self.infer_state.b_seq_len,
240244
b_mark_shared_group=self.b_mark_mtp_shared_group,
245+
max_kv_len=self.decode_max_kv_seq_len,
241246
alloc_tensor_func=alloc_func,
242247
)
243248

lightllm/common/basemodel/attention/triton/int4kv.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,11 @@ def _groupsize_quant_prefill_att(
115115

116116
@dataclasses.dataclass
117117
class Int4kvTritonDecodeAttState(BaseDecodeAttState):
118+
decode_max_kv_seq_len: int = None
119+
118120
def init_state(self):
119-
pass
121+
# Graph 捕获会改写 infer_state 的长度上限,提前保存真实长度用于配置查找。
122+
self.decode_max_kv_seq_len = self.infer_state.max_kv_seq_len
120123

121124
def copy_for_decode_cuda_graph(self, new_state: "Int4kvTritonDecodeAttState"):
122125
super().copy_for_decode_cuda_graph(new_state)
@@ -166,5 +169,6 @@ def ppl_int4kv_decode_att(
166169
cache_k_scale=k_scale,
167170
cache_v=v,
168171
cache_v_scale=v_scale,
172+
max_kv_seq_len=self.decode_max_kv_seq_len,
169173
alloc_tensor_func=alloc_func,
170174
)

lightllm/common/basemodel/attention/triton/int8kv.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,11 @@ def _groupsize_quant_prefill_att(
118118
class Int8kvTritonDecodeAttState(BaseDecodeAttState):
119119
b_shared_seq_len: torch.Tensor = None
120120
b_mark_shared_group: torch.Tensor = None
121+
decode_max_kv_seq_len: int = None
121122

122123
def init_state(self):
124+
# Graph 捕获会改写 infer_state 的长度上限,提前保存真实长度用于普通 decode 配置查找。
125+
self.decode_max_kv_seq_len = self.infer_state.max_kv_seq_len
123126
if enable_diverse_mode_gqa_decode_fast_kernel():
124127
self.b_mark_shared_group = build_diverse_shared_group_markers(
125128
b_shared_radix_node_id=self.infer_state.b_shared_radix_node_id,
@@ -203,5 +206,6 @@ def normal_decode_att(
203206
cache_k_scale=k_scale,
204207
cache_v=v,
205208
cache_v_scale=v_scale,
209+
max_len_in_batch=self.decode_max_kv_seq_len,
206210
alloc_tensor_func=alloc_func,
207211
)

lightllm/common/basemodel/basemodel.py

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,8 @@
3838
)
3939
from lightllm.common.basemodel.mtp_manager import MtpManager
4040
from lightllm.utils.custom_kernel_utis import pad2dim_tensor_to_new_batch
41-
from lightllm.utils.envs_utils import (
42-
set_model_init_status,
43-
enable_full_att_decode_tune,
44-
)
45-
from lightllm.common.triton_utils.autotuner import Autotuner
41+
from lightllm.utils.envs_utils import set_model_init_status
42+
from lightllm.common.triton_utils.autotuner import Autotuner, AutotuneKernelType
4643
from lightllm.utils.infer_utils import post_empty_cache
4744
from lightllm.utils.torch_memory_saver_utils import (
4845
TorchMemorySaverWrapper,
@@ -140,7 +137,6 @@ def __init__(self, kvargs):
140137

141138
self._init_hidden_collector()
142139
self._autotune_warmup()
143-
self._full_att_decode_autotune()
144140
self._init_padded_req()
145141
self._init_cudagraph()
146142
self._init_prefill_cuda_graph()
@@ -308,60 +304,6 @@ def _init_prefill_cuda_graph(self):
308304
else:
309305
self.prefill_graph.warmup(self)
310306

311-
@final
312-
@torch.no_grad()
313-
@post_empty_cache
314-
def _full_att_decode_autotune(self):
315-
"""
316-
Warm up / autotune FA3 full-attention decode ``num_splits`` before CUDA Graph capture.
317-
318-
Runs only when all of the following hold:
319-
- CUDA Graph is enabled (``disable_cudagraph`` is False)
320-
- this is the main model (MTP draft models are skipped)
321-
- ``ENABLE_FULL_ATT_DECODE_TUNE`` is set to 1/ON/TRUE (default off)
322-
- decode attention backend is ``Fa3AttBackend``
323-
324-
Candidate batch sizes follow the same schedule as CUDA Graph capture.
325-
Actual benchmarking is delegated to ``fa3_decode_autotune`` in ``sgl_utils``.
326-
"""
327-
if self.disable_cudagraph:
328-
return
329-
# Only tune on the main model; MTP draft models skip this path.
330-
if self.is_mtp_draft_model:
331-
return
332-
333-
# Opt-in switch for FA3 full-attention decode num_splits tuning.
334-
# Set ENABLE_FULL_ATT_DECODE_TUNE=1/ON/TRUE to enable; default is off.
335-
if not enable_full_att_decode_tune():
336-
return
337-
338-
# Only Fa3AttBackend decode path needs this num_splits warmup.
339-
decode_backends = [
340-
self.decode_att_backend,
341-
self.decode_att_backend1,
342-
]
343-
if not any(
344-
backend is not None and backend.__class__.__name__ == "Fa3AttBackend" for backend in decode_backends
345-
):
346-
return
347-
348-
from lightllm.utils.sgl_utils import fa3_decode_autotune
349-
350-
decode_batch_multiplier = self.mtp_manager.get_decode_batch_multiplier(self.is_mtp_draft_model)
351-
cuda_graph_grow_step_size = self.mtp_manager.get_decode_cuda_graph_grow_step_size(self.is_mtp_draft_model)
352-
cuda_graph_batch_sizes = CudaGraph.gen_cuda_graph_batch_sizes(
353-
batch_step_size_before_split=cuda_graph_grow_step_size,
354-
split_batch_size=self.args.graph_split_batch_size * decode_batch_multiplier,
355-
batch_step_size_after_split=self.args.graph_grow_step_size * cuda_graph_grow_step_size,
356-
max_batch_size=self.graph_max_batch_size,
357-
tp_world_size=self.tp_world_size_,
358-
)
359-
cuda_graph_batch_sizes = [
360-
batch_size for batch_size in cuda_graph_batch_sizes if batch_size % decode_batch_multiplier == 0
361-
]
362-
fa3_decode_autotune(self, cuda_graph_batch_sizes, batch_multiplier=decode_batch_multiplier)
363-
return
364-
365307
def _init_custom(self):
366308
pass
367309

@@ -1155,7 +1097,7 @@ def autotune_layers(self):
11551097
@torch.no_grad()
11561098
@post_empty_cache
11571099
def _autotune_warmup(self):
1158-
Autotuner.start_autotune_warmup()
1100+
Autotuner.start_autotune_warmup(AutotuneKernelType.GENERAL)
11591101
torch.distributed.barrier()
11601102

11611103
warmup_lengths = [1, 4, 8, 16, 32, 64, 128, 256, 1024, 2048, 4096]

lightllm/common/basemodel/cuda_graph.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from lightllm.utils.envs_utils import get_env_start_args
1010
from lightllm.distributed import dist_group_manager
1111
from lightllm.common.basemodel.batch_objs import ModelInput, ModelOutput
12+
from lightllm.common.triton_utils.autotuner import Autotuner, AutotuneKernelType
1213
from lightllm.utils.torch_memory_saver_utils import (
1314
TorchMemorySaverWrapper,
1415
MemoryTag,
@@ -117,7 +118,9 @@ def _capture_decode(self, decode_func, infer_state: InferStateInfo):
117118
# 记录原始存在的变量
118119
pure_para_set = set(vars(infer_state).keys())
119120
torch.cuda.synchronize()
120-
decode_func(copy.copy(infer_state))
121+
# 在正式捕获前调优 decode attention,退出作用域后再捕获选定的配置。
122+
with Autotuner.autotune_warmup(AutotuneKernelType.DECODE_ATTENTION):
123+
decode_func(copy.copy(infer_state))
121124
torch.cuda.synchronize()
122125
for param_name in set(vars(infer_state).keys()):
123126
if param_name not in pure_para_set:
@@ -149,7 +152,8 @@ def _capture_decode_overlap(
149152
pure_para_set = set(vars(infer_state).keys())
150153
pure_para_set1 = set(vars(infer_state1).keys())
151154
torch.cuda.synchronize()
152-
decode_func(copy.copy(infer_state), copy.copy(infer_state1))
155+
with Autotuner.autotune_warmup(AutotuneKernelType.DECODE_ATTENTION):
156+
decode_func(copy.copy(infer_state), copy.copy(infer_state1))
153157
torch.cuda.synchronize()
154158
for para_name in set(vars(infer_state).keys()):
155159
if para_name not in pure_para_set:

lightllm/common/basemodel/layer_weights/meta_weights/fused_moe/impl/deepgemm_impl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
quantize_fused_experts_input,
1616
)
1717
from lightllm.common.basemodel.triton_kernel.fused_moe.moe_silu_and_mul import silu_and_mul_fwd
18-
from lightllm.common.triton_utils.autotuner import Autotuner
18+
from lightllm.common.triton_utils.autotuner import Autotuner, AutotuneKernelType
1919
from lightllm.common.basemodel.triton_kernel.redundancy_topk_ids_repair import redundancy_topk_ids_repair
2020

2121

@@ -250,7 +250,7 @@ def prefilled_group_gemm(
250250
# A rank may receive no tokens during autotune warmup. Run one dummy token through
251251
# silu_and_mul_fwd so the empty rank matches the first kernel call made by non-empty ranks.
252252
# This branch does not synchronize additional calls caused by different positive chunk counts.
253-
if Autotuner.is_autotune_warmup():
253+
if Autotuner.is_kernel_autotune_warmup(AutotuneKernelType.GENERAL):
254254
N = w13_weight.shape[1]
255255
_gemm_out_a = torch.zeros((1, N), device=recv_x[0].device, dtype=hidden_dtype)
256256
_silu_out = torch.zeros((1, N // 2), device=recv_x[0].device, dtype=hidden_dtype)

lightllm/common/basemodel/triton_kernel/att/decode_att/gqa/flash_decoding/gqa_flash_decoding.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ def gqa_token_decode_attention_flash_decoding(
66
infer_state,
77
cache_k: torch.Tensor,
88
cache_v: torch.Tensor,
9+
max_len_in_batch: int,
910
out=None,
1011
alloc_tensor_func=torch.empty,
1112
sliding_window=(-1, -1),
@@ -41,7 +42,7 @@ def gqa_token_decode_attention_flash_decoding(
4142
Req_to_tokens=infer_state.req_manager.req_to_token_indexs,
4243
B_req_idx=infer_state.b_req_idx,
4344
B_Seqlen=infer_state.b_seq_len,
44-
max_len_in_batch=infer_state.max_kv_seq_len,
45+
max_len_in_batch=max_len_in_batch,
4546
mid_out=mid_o,
4647
mid_out_logsumexp=mid_o_logexpsum,
4748
block_seq=BLOCK_SEQ,

lightllm/common/basemodel/triton_kernel/att/decode_att/gqa/flash_decoding/gqa_flash_decoding_stage1.py

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import triton
33
import triton.language as tl
44
from typing import Optional
5-
from lightllm.common.triton_utils.autotuner import autotune, Autotuner
5+
from lightllm.common.triton_utils.autotuner import autotune, Autotuner, AutotuneKernelType, AutotuneLevel
6+
from lightllm.utils.envs_utils import get_decode_attn_autotune_seq_len, get_triton_autotune_level
67

78

89
@triton.jit
@@ -138,27 +139,92 @@ def get_test_configs():
138139
return configs
139140

140141

141-
def get_static_key(q, k, block_seq):
142+
def get_static_key(q, k, block_seq, sliding_window):
142143
key_params = {
143144
"gqa_group_size": int(q.shape[1] // k.shape[1]),
144145
"q_head_dim": int(q.shape[2]),
145146
"block_seq": block_seq,
147+
"sliding_window": tuple(sliding_window),
146148
"out_dtype": str(q.dtype),
147149
}
148150
return key_params
149151

150152

151153
def get_run_key(q, max_len_in_batch):
152154
batch_size = q.shape[0]
153-
return batch_size * 1000 * 1000 * 1000 + max_len_in_batch
155+
# 正常执行使用调用方在 CPU 上保存的真实 KV 长度,不读取 GPU 长度张量或 Graph 的容量上限。
156+
max_kv_len = int(max_len_in_batch)
157+
if Autotuner.is_kernel_autotune_warmup(AutotuneKernelType.DECODE_ATTENTION) and get_triton_autotune_level() in [
158+
AutotuneLevel.ADAPTIVE_AUTOTUNE,
159+
AutotuneLevel.FORCE_AUTOTUNE,
160+
]:
161+
max_kv_len = get_decode_attn_autotune_seq_len()
162+
# 调优和正常查找统一按 512 token 向上分桶,同一区间复用配置匹配结果。
163+
max_kv_len = (max_kv_len + 511) // 512 * 512
164+
return batch_size * 1000 * 1000 * 1000 + max_kv_len
165+
166+
167+
def rebuild_inputs(
168+
q: torch.Tensor,
169+
k: torch.Tensor,
170+
v: torch.Tensor,
171+
Req_to_tokens: torch.Tensor,
172+
B_req_idx: torch.Tensor,
173+
B_Seqlen: torch.Tensor,
174+
max_len_in_batch: int,
175+
mid_out: torch.Tensor,
176+
mid_out_logsumexp: torch.Tensor,
177+
block_seq: int,
178+
sliding_window=(-1, -1),
179+
**kwargs,
180+
):
181+
# Graph 初始化时真实请求很短,Req_to_tokens 的宽度则是容量上限,都不代表期望调优的长度。
182+
# 仅在实际搜索配置前重建一次输入,构造开销不计入 benchmark;正常执行和 Graph 捕获使用原输入。
183+
batch_size = q.shape[0]
184+
# 与调优时的 run key 共用该环境变量,默认 32768 token;实际计算保持精确长度,不做 512 分桶。
185+
max_len_in_batch = get_decode_attn_autotune_seq_len()
186+
assert k.shape[0] == v.shape[0], "K/V caches must have the same number of tokens"
187+
num_tokens = k.shape[0]
188+
if num_tokens == 0:
189+
raise ValueError("GQA decode autotuning requires a non-empty KV cache")
190+
191+
# 新建每个请求到物理 token 的映射,不能直接扩展 B_Seqlen 后读取原映射中未初始化的条目。
192+
# 物理 token 充足时各请求使用不同位置,不足时取模循环复用,保证所有索引均落在 K/V 缓存内。
193+
# 复用已有 K/V 可以避免分配完整的长请求缓存,但可能提高 GPU 缓存命中率,影响调优的访存特征。
194+
Req_to_tokens = torch.arange(batch_size * max_len_in_batch, dtype=Req_to_tokens.dtype, device=Req_to_tokens.device)
195+
Req_to_tokens = Req_to_tokens.remainder_(num_tokens).view(batch_size, max_len_in_batch)
196+
# 新映射只有 batch_size 行,请求索引也必须重建,避免继续使用原全局请求表中的行号。
197+
B_req_idx = torch.arange(batch_size, dtype=B_req_idx.dtype, device=B_req_idx.device)
198+
B_Seqlen = torch.full_like(B_Seqlen, max_len_in_batch)
199+
200+
# 保留 Q、滑窗语义、BLOCK_SEQ 和中间缓冲区布局;一个 program 可循环处理多个 KV 块,
201+
# 无需按调优长度扩容 mid_out。调优结束后 stage1 使用原始输入重新覆盖有效中间块,
202+
# stage2 仍按相同 BLOCK_SEQ 和缓冲区中的 block_num 归约。
203+
return (
204+
q,
205+
k,
206+
v,
207+
Req_to_tokens,
208+
B_req_idx,
209+
B_Seqlen,
210+
max_len_in_batch,
211+
mid_out,
212+
mid_out_logsumexp,
213+
block_seq,
214+
sliding_window,
215+
), kwargs
154216

155217

156218
@autotune(
157-
kernel_name="_fwd_kernel_gqa_flash_decode_stage1:v3",
219+
kernel_name="_fwd_kernel_gqa_flash_decode_stage1:v4",
220+
kernel_type=AutotuneKernelType.DECODE_ATTENTION,
158221
configs_gen_func=get_test_configs,
159222
static_key_func=get_static_key,
160223
run_key_func=get_run_key,
161-
mutates_args=["mid_out", "mid_out_logsumexp"],
224+
rebuild_input_func=rebuild_inputs,
225+
# stage1 对有效中间块执行覆盖写,候选配置不会读取已有输出,正式执行也会重新覆盖真实请求的有效块。
226+
# 不标记这两个大缓冲区,避免每个候选配置 benchmark 时反复 clone,增加显存峰值和拷贝开销。
227+
# mutates_args=["mid_out", "mid_out_logsumexp"],
162228
)
163229
@torch.no_grad()
164230
def flash_decode_stage1(
@@ -246,8 +312,6 @@ def flash_decode_stage1(
246312

247313

248314
if __name__ == "__main__":
249-
from lightllm.utils.envs_utils import get_triton_autotune_level
250-
251315
if get_triton_autotune_level() != 2:
252316
raise Exception("you need set env LIGHTLLM_TRITON_AUTOTUNE_LEVEL=2 to start program.")
253317

@@ -258,11 +322,11 @@ def flash_decode_stage1(
258322
out_dtype = torch.bfloat16
259323

260324
batch_sizes = [1, 8, 16, 32, 64, 128]
261-
decode_lengths = [1024, 2048, 8192, 16384]
325+
decode_lengths = [get_decode_attn_autotune_seq_len()]
262326

263327
q_head_num = gqa_group_size
264328

265-
Autotuner.start_autotune_warmup()
329+
Autotuner.start_autotune_warmup(AutotuneKernelType.DECODE_ATTENTION)
266330
# autotuing kernel
267331
for batch_size in batch_sizes:
268332
for length in decode_lengths:

0 commit comments

Comments
 (0)