Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
b1623ca
feat: add unified LightSpec speculative decoding
flyinglandlord Aug 4, 2026
0b00906
add qwen3.5 dflash support
Owleye4 Aug 4, 2026
464bc48
remove dflash dynamic draft & add qwen35 dspark
shihaobai Aug 5, 2026
8a75a1f
WIP: simplify LightSpec implementation (not ready for review)
shihaobai Aug 10, 2026
6aea8ce
fix: read Qwen3.5 target layers from text config
shihaobai Aug 10, 2026
3f2ee16
refactor: simplify LightSpec draft integration
shihaobai Aug 11, 2026
9aa6dfc
refactor: simplify speculative decoding pipeline
shihaobai Aug 11, 2026
f5293ba
fix: align speculative draft inputs and DSpark RoPE
shihaobai Aug 11, 2026
f713b82
refactor: simplify dynamic speculative scheduling
shihaobai Aug 13, 2026
640036f
Merge remote-tracking branch 'origin/main' into lightspec
shihaobai Aug 13, 2026
2870d32
fix: stabilize dynamic speculative scheduling
shihaobai Aug 14, 2026
ee0ab34
refactor: centralize MTP decode batch layout
hiworldwzj Aug 14, 2026
c65ae9c
refactor: clarify MTP CUDA graph batch sizing
hiworldwzj Aug 14, 2026
dee4749
refactor: centralize MTP decode draft step
hiworldwzj Aug 14, 2026
fe70b2f
refactor: isolate hidden collector inference state
hiworldwzj Aug 17, 2026
249667f
refactor: reuse forward in autotune warmup
hiworldwzj Aug 17, 2026
55cf376
fix
hiworldwzj Aug 17, 2026
56421db
refactor: initialize decode group metadata on demand
hiworldwzj Aug 17, 2026
b25e38f
refactor: split FA3 decode state initialization
hiworldwzj Aug 17, 2026
8f32f61
refactor: move FA3 causality into attention state
hiworldwzj Aug 17, 2026
538189a
refactor: align linear attention MTP state naming
hiworldwzj Aug 17, 2026
552b02a
refactor: clarify MTP SSM buffer shape
hiworldwzj Aug 17, 2026
a423012
refactor: share GPU attention workspaces
hiworldwzj Aug 17, 2026
4e18cee
fix
hiworldwzj Aug 17, 2026
027ce10
refactor: move FA3 utility test to file end
hiworldwzj Aug 17, 2026
bf339c1
refactor: align MTP utility naming
hiworldwzj Aug 17, 2026
35cc0ff
fix: clarify MRoPE positions for draft cache extension
hiworldwzj Aug 18, 2026
7fa888f
refactor: split draft model registry
hiworldwzj Aug 18, 2026
9704c20
refactor: rename dynamic MTP flag
hiworldwzj Aug 18, 2026
7b708d5
refactor: remove unused MTP Gloo group
hiworldwzj Aug 18, 2026
d3e657f
refactor: build MTP group markers from request IDs
hiworldwzj Aug 18, 2026
dca9fea
refactor: simplify padded MTP group markers
hiworldwzj Aug 18, 2026
c82f283
refactor: rename speculative package
hiworldwzj Aug 18, 2026
ef43e58
refactor: align MTP metric method names
hiworldwzj Aug 18, 2026
e5f81a0
refactor: make MTP KV layer counts explicit
hiworldwzj Aug 18, 2026
637719e
refactor: unify MTP model outputs
hiworldwzj Aug 18, 2026
76faef9
refactor: clarify MTP output collector naming
hiworldwzj Aug 18, 2026
3dce9ac
refactor: clarify proposal coverage state
hiworldwzj Aug 18, 2026
ce15142
refactor: split speculative planners into package
hiworldwzj Aug 18, 2026
cec8624
refactor: pass decode requests to planners
hiworldwzj Aug 18, 2026
5963199
refactor: move draft step selection to planner
hiworldwzj Aug 18, 2026
6e25ddc
refactor: move MTP cost modeling to planners
hiworldwzj Aug 18, 2026
9a3cb61
refactor: require CUDA graphs for MTP planning
hiworldwzj Aug 19, 2026
4921565
refactor: add common MTP planner interface
hiworldwzj Aug 19, 2026
46b04e8
chore: add DP MTP extension packages
hiworldwzj Aug 19, 2026
45a21d4
refactor: separate DP MTP engines and proposers
hiworldwzj Aug 19, 2026
addeab4
refactor: unify fixed and dynamic MTP decode plans
hiworldwzj Aug 19, 2026
4113a46
refactor: extract shared MTP decode utilities
hiworldwzj Aug 19, 2026
f78238f
refactor: simplify MTP decode request handling
hiworldwzj Aug 19, 2026
29940cb
refactor: clarify MTP planner interfaces
hiworldwzj Aug 19, 2026
c5c941a
refactor: type MTP backend dependencies
hiworldwzj Aug 19, 2026
f3b0b44
refactor: centralize MTP memory allocation
hiworldwzj Aug 19, 2026
6455a15
refactor: specialize MTP proposal handling
hiworldwzj Aug 19, 2026
accdcdf
refactor: unify MTP proposal memory release
hiworldwzj Aug 19, 2026
9a00892
refactor: simplify dynamic MTP memory handling
hiworldwzj Aug 19, 2026
b135abe
refactor: separate dynamic MTP kernels
hiworldwzj Aug 19, 2026
36261cf
refactor: clarify MTP verify batch shape
hiworldwzj Aug 19, 2026
1dffe62
refactor: unify MTP proposal token layout
hiworldwzj Aug 19, 2026
14e7bba
refactor: clarify MTP metric and memory handling
hiworldwzj Aug 19, 2026
79e6196
refactor: rename draft KV state initialization
hiworldwzj Aug 19, 2026
b868bf7
refactor: clarify target token inputs for draft KV fill
hiworldwzj Aug 19, 2026
4696ba9
refactor: clarify target proposal inputs
hiworldwzj Aug 19, 2026
5743624
refactor: skip vanilla no-att draft state fill
hiworldwzj Aug 20, 2026
46c8bda
refactor: build MTP attention groups in backends
hiworldwzj Aug 20, 2026
ee69b52
refactor: rebuild diverse groups from radix metadata
hiworldwzj Aug 20, 2026
b3bf319
refactor: tighten model input phase contracts
hiworldwzj Aug 20, 2026
8aead20
perf: fuse vanilla MTP row selection
hiworldwzj Aug 20, 2026
69768e0
refactor: localize MTP prefill state preparation
hiworldwzj Aug 20, 2026
2ed5212
fix: keep vanilla attention draft depth fixed
hiworldwzj Aug 20, 2026
515d71d
fix: cascade vanilla attention decode inputs
hiworldwzj Aug 20, 2026
6fde882
refactor: isolate vanilla proposer implementations
hiworldwzj Aug 20, 2026
d0def76
refactor: specialize eagle no-att proposal flow
hiworldwzj Aug 20, 2026
5059c4d
refactor: specialize eagle attention proposal flow
hiworldwzj Aug 20, 2026
8442a18
refactor: centralize MTP proposal types
hiworldwzj Aug 20, 2026
320d7f9
refactor: relocate DP EAGLE helpers
hiworldwzj Aug 20, 2026
6cd7d8a
refactor: specialize parallel MTP proposers
hiworldwzj Aug 20, 2026
487f448
refactor: derive prefill token count from input ids
hiworldwzj Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions lightllm/common/basemodel/attention/base_att.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,79 @@
import threading

import torch
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional, TYPE_CHECKING, Tuple, Union, Dict

from lightllm.utils.dist_utils import get_current_device_id
from lightllm.utils.envs_utils import get_env_start_args

if TYPE_CHECKING:
from lightllm.common.basemodel.basemodel import TpPartBaseModel
from lightllm.common.basemodel.infer_struct import InferStateInfo


class BaseAttBackend:
"""
用于创建支持各种不同的AttBackend, 如 fa3, flashinfer, triton 实现等
这个是单列模式, 每种backend只有一个实例
用于创建支持各种不同的AttBackend, 如 fa3, flashinfer, triton 实现等
每个 model 复用一个 backend 实例。
"""

_instances = {}
_workspace_buffers = {}
_workspace_buffer_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
"""
重写__new__方法实现单例模式
Main 和 speculative draft model 可能使用不同的 CUDA graph 上限
和缓存布局,不能只按 backend class 共享实例。
"""
# 检查是否已经有该类的实例
if cls not in cls._instances:
# 创建新实例并存储
model = kwargs.get("model", args[0] if args else None)
instance_key = (cls, model)
if instance_key not in cls._instances:
instance = super().__new__(cls)
cls._instances[cls] = instance
# 返回已有的实例
return cls._instances[cls]
cls._instances[instance_key] = instance
return cls._instances[instance_key]

def __init__(self, model: "TpPartBaseModel"):
self.model = model

@staticmethod
def get_gpu_workspace_buffer(key_name: str, workspace_size: int, dtype: torch.dtype = torch.int8) -> torch.Tensor:
"""Return a process-local workspace shared by key name and CUDA device."""
if not key_name:
raise ValueError("workspace key_name must not be empty")
if workspace_size <= 0:
raise ValueError(f"workspace_size must be positive, got {workspace_size}")

device_id = get_current_device_id()
buffer_key = (device_id, key_name, workspace_size, dtype)
with BaseAttBackend._workspace_buffer_lock:
workspace_buffer = BaseAttBackend._workspace_buffers.get(buffer_key)
if workspace_buffer is None:
workspace_buffer = torch.empty(workspace_size, dtype=dtype, device=device_id)
BaseAttBackend._workspace_buffers[buffer_key] = workspace_buffer
return workspace_buffer

def create_att_prefill_state(self) -> "BasePrefillAttState":
raise NotImplementedError("not impl")

def create_att_decode_state(self) -> "BaseDecodeAttState":
raise NotImplementedError("not impl")

def uses_dynamic_spec_verify_layout(self) -> bool:
args = get_env_start_args()
draft_step = self.model.mtp_manager.get_decode_draft_step(self.model.is_mtp_draft_model)
is_main_model = not self.model.is_mtp_draft_model
has_decode_draft_step = draft_step > 0
dynamic_verify_enabled = args.mtp_dynamic_verify
return is_main_model and has_decode_draft_step and dynamic_verify_enabled

def uses_causal_attention(self) -> bool:
args = get_env_start_args()
is_parallel_block_draft = self.model.is_mtp_draft_model and args.mtp_mode in ("dspark", "dflash")
return not is_parallel_block_draft

def _find_layer_index(
self, k: torch.Tensor, v: torch.Tensor, att_state: Union["BasePrefillAttState", "BaseDecodeAttState"]
) -> int:
Expand Down
132 changes: 86 additions & 46 deletions lightllm/common/basemodel/attention/fa3/fp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
from typing import Optional, TYPE_CHECKING
from lightllm.utils.dist_utils import get_current_device_id
from lightllm.utils.sgl_utils import flash_attn_with_kvcache, flash_attn_with_kvcache_autotune
from lightllm.utils.envs_utils import get_env_start_args
from lightllm.common.basemodel.triton_kernel.fa3_utils import page_table_copy
from lightllm.common.basemodel.triton_kernel.fa3_utils import (
build_dynamic_spec_fa3_decode_params,
page_table_copy,
)
from lightllm.common.basemodel.triton_kernel.gen_prefill_params import gen_cumsum_pad0_tensor
from lightllm.common.basemodel.triton_kernel.mtp_utils import build_mtp_shared_group_markers
from lightllm.utils.envs_utils import get_env_start_args


class Fa3AttBackend(BaseAttBackend):
Expand All @@ -20,13 +24,19 @@ def get_page_table_buffer(self):
"""
model = self.model
if not hasattr(self, "_shared_page_table_buffer"):
max_att_batch_size = model.graph_max_batch_size
if not get_env_start_args().mtp_dynamic_verify:
# FA3 merges each fixed speculative block into one attention sequence.
max_att_batch_size //= model.mtp_manager.get_decode_batch_multiplier(model.is_mtp_draft_model)

buffer_count = 2 if model.args.enable_decode_microbatch_overlap else 1
self._shared_page_table_buffer = [
torch.empty(model.graph_max_batch_size * model.graph_max_len_in_batch, dtype=torch.int32).to(
get_current_device_id()
),
torch.empty(model.graph_max_batch_size * model.graph_max_len_in_batch, dtype=torch.int32).to(
get_current_device_id()
),
torch.empty(
max_att_batch_size * model.graph_max_len_in_batch,
dtype=torch.int32,
device=get_current_device_id(),
)
for _ in range(buffer_count)
]
return self._shared_page_table_buffer

Expand All @@ -42,8 +52,10 @@ class Fa3PrefillAttState(BasePrefillAttState):
cu_seqlens_q: torch.Tensor = None
cu_seqlens_k: torch.Tensor = None
page_table: torch.Tensor = None
causal: bool = None

def init_state(self):
self.causal = self.backend.uses_causal_attention()
self.cu_seqlens_q = self.infer_state.b1_cu_q_seq_len.int()
self.cu_seqlens_k = self.infer_state.b1_cu_kv_seq_len.int()
self.page_table = torch.empty(
Expand Down Expand Up @@ -102,7 +114,7 @@ def _nomarl_prefill_att(
cu_seqlens_k_new=self.cu_seqlens_k,
max_seqlen_q=self.infer_state.max_q_seq_len,
softmax_scale=sm_scale,
causal=True,
causal=self.causal,
window_size=window_size,
softcap=0.0,
k_descale=k_descale,
Expand All @@ -121,31 +133,71 @@ class Fa3DecodeAttState(BaseDecodeAttState):
b_att_seq_len: torch.Tensor = None
# 在是否开启mtp 的不同模式下,其设置不同的值,可以加速算子的运行。
decode_max_q_seq_len: int = None
causal: bool = None

def init_state(self):
self.backend: Fa3AttBackend = self.backend
args_mtp_step = get_env_start_args().mtp_step

if args_mtp_step > 0:
# 修正 mtp 在 fa3 下的输入。
mtp_size = args_mtp_step + 1
b_q_seq_len = torch.full(
(self.infer_state.b_seq_len.shape[0] // mtp_size,),
fill_value=mtp_size,
dtype=torch.int32,
device=self.infer_state.b_seq_len.device,
)
b_kv_seq_len = self.infer_state.b_seq_len[mtp_size - 1 :: mtp_size]
b1_cu_q_seq_len, b1_cu_kv_seq_len = gen_cumsum_pad0_tensor(b_q_seq_len, b_kv_seq_len)
self.cu_seqlens_q = b1_cu_q_seq_len.int()
self.cu_seqlens_k = b1_cu_kv_seq_len.int()
self.causal = self.backend.uses_causal_attention()
draft_step = self.backend.model.mtp_manager.get_decode_draft_step(self.backend.model.is_mtp_draft_model)
if self.backend.uses_dynamic_spec_verify_layout():
b_att_req_idx = self._init_dynamic_spec_verify_state(draft_step)
elif draft_step > 0:
b_att_req_idx = self._init_fixed_spec_decode_state(draft_step)
else:
self.cu_seqlens_q = self.infer_state.b1_cu_q_seq_len.int()
self.cu_seqlens_k = self.infer_state.b1_cu_kv_seq_len.int()
b_att_req_idx = self._init_normal_decode_state()

att_batch_size = self.infer_state.batch_size // (args_mtp_step + 1)
assert self.infer_state.batch_size % (args_mtp_step + 1) == 0
self._init_page_table(b_att_req_idx)

def _init_dynamic_spec_verify_state(self, draft_step: int) -> torch.Tensor:
b_mark_mtp_shared_group = build_mtp_shared_group_markers(
self.infer_state.b_req_idx,
hold_req_id=self.backend.model.req_manager.HOLD_REQUEST_ID,
)
b_q_seq_len, b_kv_seq_len, b_att_req_idx, self.b_att_seq_len = build_dynamic_spec_fa3_decode_params(
b_req_idx=self.infer_state.b_req_idx,
b_seq_len=self.infer_state.b_seq_len,
b_mark_mtp_shared_group=b_mark_mtp_shared_group,
att_batch_size=self.infer_state.batch_size,
hold_req_id=self.backend.model.req_manager.HOLD_REQUEST_ID,
)
self._init_spec_decode_cu_seqlens(b_q_seq_len, b_kv_seq_len)
self.decode_max_q_seq_len = draft_step + 1
return b_att_req_idx

def _init_fixed_spec_decode_state(self, draft_step: int) -> torch.Tensor:
mtp_size = draft_step + 1
assert self.infer_state.batch_size % mtp_size == 0, (
"FA3 fixed-layout decode requires batch_size to be divisible by draft_step + 1, "
f"got batch_size={self.infer_state.batch_size}, draft_step={draft_step}."
)

b_q_seq_len = torch.full(
(self.infer_state.b_seq_len.shape[0] // mtp_size,),
fill_value=mtp_size,
dtype=torch.int32,
device=self.infer_state.b_seq_len.device,
)
b_kv_seq_len = self.infer_state.b_seq_len[draft_step::mtp_size]
b_att_req_idx = self.infer_state.b_req_idx[draft_step::mtp_size]
self.b_att_seq_len = b_kv_seq_len.contiguous()
self._init_spec_decode_cu_seqlens(b_q_seq_len, b_kv_seq_len)
self.decode_max_q_seq_len = mtp_size
return b_att_req_idx

def _init_normal_decode_state(self) -> torch.Tensor:
self.cu_seqlens_q = self.infer_state.b1_cu_q_seq_len.int()
self.cu_seqlens_k = self.infer_state.b1_cu_kv_seq_len.int()
self.b_att_seq_len = self.infer_state.b_seq_len
self.decode_max_q_seq_len = 1
return self.infer_state.b_req_idx

def _init_spec_decode_cu_seqlens(self, b_q_seq_len: torch.Tensor, b_kv_seq_len: torch.Tensor):
b1_cu_q_seq_len, b1_cu_kv_seq_len = gen_cumsum_pad0_tensor(b_q_seq_len, b_kv_seq_len)
self.cu_seqlens_q = b1_cu_q_seq_len.int()
self.cu_seqlens_k = b1_cu_kv_seq_len.int()

def _init_page_table(self, b_att_req_idx: torch.Tensor):
att_batch_size = b_att_req_idx.shape[0]
model = self.backend.model
# 可以使用 cuda graph的时候从 buffer中申请
if (
Expand All @@ -163,23 +215,11 @@ def init_state(self):
device=self.infer_state.input_ids.device,
)

if args_mtp_step > 0:
page_table_copy(
page_table=self.page_table[:, : self.infer_state.max_kv_seq_len],
req_to_token_indexs=model.req_manager.req_to_token_indexs,
b_req_idx=self.infer_state.b_req_idx[args_mtp_step :: (args_mtp_step + 1)],
)
self.b_att_seq_len = self.infer_state.b_seq_len[args_mtp_step :: (args_mtp_step + 1)].contiguous()
self.decode_max_q_seq_len = args_mtp_step + 1
else:
page_table_copy(
page_table=self.page_table[:, : self.infer_state.max_kv_seq_len],
req_to_token_indexs=model.req_manager.req_to_token_indexs,
b_req_idx=self.infer_state.b_req_idx,
)
self.b_att_seq_len = self.infer_state.b_seq_len
self.decode_max_q_seq_len = 1
return
page_table_copy(
page_table=self.page_table[:, : self.infer_state.max_kv_seq_len],
req_to_token_indexs=model.req_manager.req_to_token_indexs,
b_req_idx=b_att_req_idx,
)

def copy_for_decode_cuda_graph(self, new_state: "Fa3DecodeAttState"):
super().copy_for_decode_cuda_graph(new_state)
Expand Down Expand Up @@ -232,7 +272,7 @@ def _normal_decode_att(
cu_seqlens_k_new=self.cu_seqlens_k,
max_seqlen_q=self.decode_max_q_seq_len,
softmax_scale=sm_scale,
causal=True,
causal=self.causal,
window_size=window_size,
softcap=0.0,
k_descale=k_descale,
Expand Down
15 changes: 5 additions & 10 deletions lightllm/common/basemodel/attention/fa3/fp8.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from ..base_att import AttControl
from typing import Optional, TYPE_CHECKING
from lightllm.utils.sgl_utils import flash_attn_with_kvcache
from lightllm.utils.envs_utils import get_env_start_args
from lightllm.common.basemodel.triton_kernel.quantization.q_per_head_fp8_quant import q_per_head_fp8_quant
from lightllm.utils.vllm_utils import HAS_VLLM, vllm_ops
from typing import Union
Expand Down Expand Up @@ -99,7 +98,7 @@ def _fp8_prefill_att(
cu_seqlens_q=self.cu_seqlens_q,
cu_seqlens_k_new=self.cu_seqlens_k,
max_seqlen_q=self.infer_state.max_q_seq_len,
causal=True,
causal=self.causal,
window_size=(-1, -1),
softcap=0.0,
q_descale=q_scale,
Expand All @@ -119,22 +118,18 @@ def init_state(self):
super().init_state()
self.backend: Fp8Fa3AttBackend = self.backend

args_mtp_step = get_env_start_args().mtp_step
att_batch_size = self.infer_state.batch_size // (args_mtp_step + 1)
assert self.infer_state.batch_size % (args_mtp_step + 1) == 0

batch_size = att_batch_size
att_batch_size = self.b_att_seq_len.shape[0]
mem_manager = self.backend.model.mem_manager

offline_scales: torch.Tensor = mem_manager.scales
head_num = mem_manager.head_num

# 为了减少推理计算量,在推理外部初始化k_descale和v_descale
self.k_descale = (
offline_scales[:, :head_num].view(-1, 1, head_num).expand(offline_scales.shape[0], batch_size, head_num)
offline_scales[:, :head_num].view(-1, 1, head_num).expand(offline_scales.shape[0], att_batch_size, head_num)
)
self.v_descale = (
offline_scales[:, head_num:].view(-1, 1, head_num).expand(offline_scales.shape[0], batch_size, head_num)
offline_scales[:, head_num:].view(-1, 1, head_num).expand(offline_scales.shape[0], att_batch_size, head_num)
)

return
Expand Down Expand Up @@ -190,7 +185,7 @@ def _fp8_decode_att(
cu_seqlens_q=self.cu_seqlens_q,
cu_seqlens_k_new=self.cu_seqlens_k,
max_seqlen_q=self.decode_max_q_seq_len,
causal=True,
causal=self.causal,
window_size=(-1, -1),
softcap=0.0,
q_descale=q_scale.view(self.infer_state.batch_size, k_head_num),
Expand Down
Loading
Loading