Skip to content

Commit 7057e0e

Browse files
committed
feat(qwen3next): GDN spec-decode verify path + linear-att cache split
Gated-delta-net (linear attention) speculative-decode verify path for qwen3next: a per-sequence spec causal_conv1d kernel; a widened conv working slot split from the committed (narrow) persisted slot; MTP draft full-attn KV-slot accounting across the linear-att cache config, mem operator and req manager; and removal of the dead gen_b_req_mtp_start_loc kernel.
1 parent f03a5b0 commit 7057e0e

11 files changed

Lines changed: 688 additions & 119 deletions

File tree

lightllm/common/basemodel/triton_kernel/linear_att_copy.py

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55

66
@triton.jit
77
def _copy_linear_att_state_to_kv_buffer(
8-
gpu_conv_ptr, # [linear_layer_num, size_num, xdim]
8+
gpu_conv_ptr, # [linear_layer_num, size_num, conv_dim * gpu_widened_width] (uint8 tail)
99
gpu_ssm_ptr, # [linear_layer_num, size_num, xxdim]
10-
cpu_kv_conv_ptr, # [size, linear_layer_num, xdim]
10+
cpu_kv_conv_ptr, # [size, linear_layer_num, conv_dim * width_narrow] (uint8 tail)
1111
cpu_kv_ssm_ptr, # [size, linear_layer_num, xxdim]
1212
b_req_idx, # [batch_size,]
1313
big_page_buffer_ids, # [batch_size,]
14+
num_accepted_tokens_ptr, # [batch_size,]
1415
gpu_conv_stride_l,
1516
gpu_conv_stride_s,
1617
gpu_conv_stride_d,
@@ -24,7 +25,9 @@ def _copy_linear_att_state_to_kv_buffer(
2425
cpu_kv_ssm_stride_l,
2526
cpu_kv_ssm_stride_d,
2627
mtp_step,
27-
gpu_conv_tail_dim,
28+
conv_dim, # number of conv rows (the d dimension)
29+
gpu_conv_row_bytes, # widened per-row byte length: gpu_widened_width * itemsize
30+
conv_narrow_row_bytes, # narrow per-row byte length: width_narrow * itemsize
2831
gpu_ssm_tail_dim,
2932
BLOCK: tl.constexpr,
3033
):
@@ -40,28 +43,26 @@ def _copy_linear_att_state_to_kv_buffer(
4043
return
4144

4245
cur_req_idx = tl.load(b_req_idx + cur_batch).to(tl.int64)
43-
cur_state_req_idx = (cur_req_idx * (mtp_step + 1)).to(tl.int64)
46+
accept_len = tl.load(num_accepted_tokens_ptr + cur_batch).to(tl.int64)
47+
canonical_off = accept_len - 1
4448

45-
for i in range(tl.cdiv(gpu_conv_tail_dim, BLOCK)):
46-
gpu_start_off = i * BLOCK + tl.arange(0, BLOCK)
47-
mask = gpu_start_off < gpu_conv_tail_dim
48-
conv_data = tl.load(
49-
gpu_conv_ptr + cur_layer * gpu_conv_stride_l + cur_state_req_idx * gpu_conv_stride_s + gpu_start_off,
50-
mask=mask,
51-
)
52-
dest_conv_ptr = (
53-
cpu_kv_conv_ptr
54-
+ big_page_buffer_idx * cpu_kv_conv_stride_s
55-
+ cur_layer * cpu_kv_conv_stride_l
56-
+ gpu_start_off
57-
)
58-
tl.store(dest_conv_ptr, conv_data, mask=mask)
49+
conv_src_slot = cur_req_idx
50+
conv_off_bytes = canonical_off * gpu_conv_stride_d
51+
gpu_conv_base = gpu_conv_ptr + cur_layer * gpu_conv_stride_l + conv_src_slot * gpu_conv_stride_s + conv_off_bytes
52+
cpu_conv_base = cpu_kv_conv_ptr + big_page_buffer_idx * cpu_kv_conv_stride_s + cur_layer * cpu_kv_conv_stride_l
53+
for d in range(conv_dim):
54+
for i in range(tl.cdiv(conv_narrow_row_bytes, BLOCK)):
55+
off = i * BLOCK + tl.arange(0, BLOCK)
56+
mask = off < conv_narrow_row_bytes
57+
conv_data = tl.load(gpu_conv_base + d * gpu_conv_row_bytes + off, mask=mask)
58+
tl.store(cpu_conv_base + d * cpu_kv_conv_stride_d + off, conv_data, mask=mask)
5959

60+
ssm_src_slot = (cur_req_idx * (mtp_step + 1) + canonical_off).to(tl.int64)
6061
for i in range(tl.cdiv(gpu_ssm_tail_dim, BLOCK)):
6162
gpu_start_off = i * BLOCK + tl.arange(0, BLOCK)
6263
mask = gpu_start_off < gpu_ssm_tail_dim
6364
ssm_data = tl.load(
64-
gpu_ssm_ptr + cur_layer * gpu_ssm_stride_l + cur_state_req_idx * gpu_ssm_stride_s + gpu_start_off,
65+
gpu_ssm_ptr + cur_layer * gpu_ssm_stride_l + ssm_src_slot * gpu_ssm_stride_s + gpu_start_off,
6566
mask=mask,
6667
)
6768
dest_ssm_ptr = (
@@ -75,32 +76,51 @@ def _copy_linear_att_state_to_kv_buffer(
7576
def copy_linear_att_state_to_kv_buffer(
7677
b_req_idx: torch.Tensor,
7778
big_page_buffer_ids: torch.Tensor,
78-
gpu_conv_state: torch.Tensor, # [linear_layer_num, s, ...]
79-
gpu_ssm_state: torch.Tensor, # [linear_layer_num, s, ...]
80-
cpu_kv_conv_state: torch.Tensor, # [s, linear_layer_num, ...]
81-
cpu_kv_ssm_state: torch.Tensor, # [s, linear_layer_num, ...]
79+
gpu_conv_state: torch.Tensor, # [linear_layer_num, s_widened, conv_dim, gpu_widened_width]
80+
gpu_ssm_state: torch.Tensor, # [linear_layer_num, s_block, ...]
81+
cpu_kv_conv_state: torch.Tensor, # [size, linear_layer_num, conv_dim, width_narrow]
82+
cpu_kv_ssm_state: torch.Tensor, # [size, linear_layer_num, ...]
8283
mtp_step: int,
84+
b_num_accepted_tokens: torch.Tensor, # [batch_size,] per-req post-accept count (>=1)
8385
):
8486
assert len(b_req_idx) == big_page_buffer_ids.shape[0]
87+
assert len(b_req_idx) == b_num_accepted_tokens.shape[0]
8588
BLOCK = 4096
86-
gpu_conv_state = gpu_conv_state.view(gpu_conv_state.shape[0], gpu_conv_state.shape[1], -1).view(dtype=torch.uint8)
87-
gpu_ssm_state = gpu_ssm_state.view(gpu_ssm_state.shape[0], gpu_ssm_state.shape[1], -1).view(dtype=torch.uint8)
88-
cpu_kv_conv_state = cpu_kv_conv_state.view(cpu_kv_conv_state.shape[0], cpu_kv_conv_state.shape[1], -1).view(
89-
dtype=torch.uint8
89+
90+
assert gpu_conv_state.dim() >= 4, "gpu_conv_state must be [layer, s, conv_dim, widened_width]"
91+
assert cpu_kv_conv_state.dim() >= 4, "cpu_kv_conv_state must be [size, layer, conv_dim, width_narrow]"
92+
# #6: the byte snapshot hardcodes gpu_conv_stride_d=conv_itemsize, which is only valid when the
93+
# widened-width axis is element-contiguous (stride 1). Fail fast instead of snapshotting wrong bytes.
94+
assert gpu_conv_state.stride(3) == 1, (
95+
"gpu_conv_state widened-width axis must be element-contiguous (stride 1); "
96+
"gpu_conv_stride_d=conv_itemsize assumes it"
97+
)
98+
# #18: canonical_off = accept_len - 1 indexes into the widened slot; bound it to [0, mtp_step]
99+
# (accept_len in [1, mtp_step+1]) so a stale/oversized accept-count can't slice past the slot.
100+
assert int(b_num_accepted_tokens.min()) >= 1 and int(b_num_accepted_tokens.max()) <= mtp_step + 1, (
101+
f"b_num_accepted_tokens out of range [1, {mtp_step + 1}]: "
102+
f"min={int(b_num_accepted_tokens.min())} max={int(b_num_accepted_tokens.max())}"
90103
)
104+
conv_itemsize = gpu_conv_state.element_size()
105+
gpu_conv_state = gpu_conv_state.view(
106+
gpu_conv_state.shape[0], gpu_conv_state.shape[1], gpu_conv_state.shape[2], -1
107+
).view(dtype=torch.uint8)
108+
cpu_kv_conv_state = cpu_kv_conv_state.view(
109+
cpu_kv_conv_state.shape[0], cpu_kv_conv_state.shape[1], cpu_kv_conv_state.shape[2], -1
110+
).view(dtype=torch.uint8)
111+
112+
gpu_ssm_state = gpu_ssm_state.view(gpu_ssm_state.shape[0], gpu_ssm_state.shape[1], -1).view(dtype=torch.uint8)
91113
cpu_kv_ssm_state = cpu_kv_ssm_state.view(cpu_kv_ssm_state.shape[0], cpu_kv_ssm_state.shape[1], -1).view(
92114
dtype=torch.uint8
93115
)
94-
assert gpu_conv_state.shape[-1] == cpu_kv_conv_state.shape[-1]
116+
117+
assert gpu_conv_state.shape[2] == cpu_kv_conv_state.shape[2], "conv_dim mismatch between gpu and cpu conv buffers"
95118
assert gpu_ssm_state.shape[-1] == cpu_kv_ssm_state.shape[-1]
96-
assert (
97-
gpu_conv_state.stride(-1)
98-
== gpu_ssm_state.stride(-1)
99-
== cpu_kv_conv_state.stride(-1)
100-
== cpu_kv_ssm_state.stride(-1)
101-
)
102119

103-
gpu_conv_tail_dim = gpu_conv_state.shape[-1]
120+
conv_dim = gpu_conv_state.shape[2]
121+
gpu_conv_row_bytes = gpu_conv_state.shape[-1] # widened per-row byte length
122+
conv_narrow_row_bytes = cpu_kv_conv_state.shape[-1] # narrow per-row byte length
123+
assert conv_narrow_row_bytes <= gpu_conv_row_bytes
104124
gpu_ssm_tail_dim = gpu_ssm_state.shape[-1]
105125

106126
layer_num = gpu_conv_state.shape[0]
@@ -114,9 +134,10 @@ def copy_linear_att_state_to_kv_buffer(
114134
cpu_kv_ssm_ptr=cpu_kv_ssm_state,
115135
b_req_idx=b_req_idx,
116136
big_page_buffer_ids=big_page_buffer_ids,
137+
num_accepted_tokens_ptr=b_num_accepted_tokens,
117138
gpu_conv_stride_l=gpu_conv_state.stride(0),
118139
gpu_conv_stride_s=gpu_conv_state.stride(1),
119-
gpu_conv_stride_d=gpu_conv_state.stride(2),
140+
gpu_conv_stride_d=conv_itemsize,
120141
gpu_ssm_stride_l=gpu_ssm_state.stride(0),
121142
gpu_ssm_stride_s=gpu_ssm_state.stride(1),
122143
gpu_ssm_stride_d=gpu_ssm_state.stride(2),
@@ -127,7 +148,9 @@ def copy_linear_att_state_to_kv_buffer(
127148
cpu_kv_ssm_stride_l=cpu_kv_ssm_state.stride(1),
128149
cpu_kv_ssm_stride_d=cpu_kv_ssm_state.stride(2),
129150
mtp_step=mtp_step,
130-
gpu_conv_tail_dim=gpu_conv_tail_dim,
151+
conv_dim=conv_dim,
152+
gpu_conv_row_bytes=gpu_conv_row_bytes,
153+
conv_narrow_row_bytes=conv_narrow_row_bytes,
131154
gpu_ssm_tail_dim=gpu_ssm_tail_dim,
132155
BLOCK=BLOCK,
133156
)

lightllm/common/basemodel/triton_kernel/linear_att_cpu_cache_copy.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,7 @@ def copy_kv_buffer_to_cpu_cache(
193193
cpu_kv_ssm_tail_dim = cpu_kv_ssm_state.shape[-1]
194194
full_att_layer_num = gpu_kv_full_att_state.shape[-2]
195195

196-
assert (
197-
full_att_layer_num
198-
== (linear_config.all_layer_num // linear_config.full_attention_interval)
199-
== (linear_config.all_layer_num - linear_config.linear_layer_num)
200-
)
196+
assert full_att_layer_num == linear_config.get_persisted_full_att_layer_num()
201197
assert gpu_full_att_tail_dim == cpu_cache_full_att.shape[-1]
202198
assert cpu_cache_conv.shape[-1] == cpu_kv_conv_state.shape[-1]
203199
assert cpu_cache_ssm.shape[-1] == cpu_kv_ssm_state.shape[-1]
@@ -428,6 +424,7 @@ def copy_cpu_cache_to_kv_buffer(
428424
cpu_kv_ssm_tail_dim = cpu_kv_ssm_state.shape[-1]
429425
full_att_layer_num = gpu_full_att_kv_state.shape[-2]
430426

427+
assert full_att_layer_num == linear_config.get_persisted_full_att_layer_num()
431428
assert gpu_full_att_tail_dim == cpu_cache_full_att.shape[-1]
432429
assert cpu_cache_conv.shape[-1] == cpu_kv_conv_state.shape[-1]
433430
assert cpu_cache_ssm.shape[-1] == cpu_kv_ssm_state.shape[-1]

lightllm/common/basemodel/triton_kernel/mtp_utils.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -148,38 +148,6 @@ def mtp_scatter_next_token_ids(
148148
)
149149

150150

151-
@triton.jit
152-
def _fwd_kernel_gen_b_req_mtp_start_loc(
153-
b_mtp_index,
154-
b_req_mtp_start_loc,
155-
num_reqs: tl.constexpr,
156-
batch_size: tl.constexpr,
157-
BLOCK_SIZE: tl.constexpr,
158-
):
159-
offset = tl.arange(0, BLOCK_SIZE)
160-
cur_mtp_index = tl.load(b_mtp_index + offset, mask=offset < batch_size, other=-1)
161-
non_zero_mask = tl.where(cur_mtp_index == 0, 1, 0) # 1 0 1 0 0
162-
output_offset = tl.cumsum(non_zero_mask) - 1
163-
tl.store(b_req_mtp_start_loc + output_offset, offset, mask=non_zero_mask == 1)
164-
return
165-
166-
167-
def gen_b_req_mtp_start_loc(b_mtp_index: torch.Tensor, num_reqs: int):
168-
b_req_mtp_start_loc = torch.empty((num_reqs,), dtype=torch.int32, device=b_mtp_index.device)
169-
BLOCK_SIZE = triton.next_power_of_2(b_mtp_index.shape[0])
170-
batch_size = b_mtp_index.shape[0]
171-
grid = (1,)
172-
_fwd_kernel_gen_b_req_mtp_start_loc[grid](
173-
b_mtp_index=b_mtp_index,
174-
b_req_mtp_start_loc=b_req_mtp_start_loc,
175-
num_reqs=num_reqs,
176-
batch_size=batch_size,
177-
BLOCK_SIZE=BLOCK_SIZE,
178-
num_warps=8,
179-
)
180-
return b_req_mtp_start_loc
181-
182-
183151
def test_mtp_verify():
184152
req_to_next_token_ids = torch.tensor(
185153
[[1, 2, -2, -1, -1], [1, 2, 0, -1, -1], [1, 3, 4, 4, 5]], dtype=torch.int32, device="cuda"
@@ -201,13 +169,5 @@ def test_mtp_verify():
201169
print(accepted_index)
202170

203171

204-
def test_gen_b_req_mtp_start_loc():
205-
b_mtp_index = torch.tensor([0, 1, 0, 1, 2], dtype=torch.int32, device="cuda")
206-
gt_output = torch.where(b_mtp_index == 0)[0]
207-
b_req_mtp_start_loc = gen_b_req_mtp_start_loc(b_mtp_index, 2)
208-
print(b_req_mtp_start_loc, gt_output)
209-
210-
211172
if __name__ == "__main__":
212173
test_mtp_verify()
213-
# test_gen_b_req_mtp_start_loc()

lightllm/common/kv_cache_mem_manager/operator/linear_att.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ def __init__(self, mem_manager):
2424
super().__init__(mem_manager)
2525
self.linear_config = LinearAttCacheConfig.load_from_args()
2626

27+
@staticmethod
28+
def _get_persisted_full_att_layer_num(mem_manager) -> int:
29+
persisted_full_att = getattr(mem_manager, "persisted_full_att_layer_num", None)
30+
if persisted_full_att is None:
31+
main_full_att = getattr(mem_manager, "main_full_att_layer_num", mem_manager.kv_buffer.shape[0])
32+
draft_full_att = getattr(mem_manager, "draft_full_att_layers", 0)
33+
persisted_full_att = main_full_att + draft_full_att
34+
assert 0 < persisted_full_att <= mem_manager.kv_buffer.shape[0]
35+
return int(persisted_full_att)
36+
2737
def load_cpu_cache_to_gpu(
2838
self,
2939
mem_indexes: torch.Tensor,
@@ -76,11 +86,14 @@ def load_cpu_cache_to_gpu(
7686
copy_cpu_cache_to_kv_buffer,
7787
)
7888

89+
# Restore the persisted full-attn slice: main slots followed by MTP draft slots.
90+
persisted_full_att = self._get_persisted_full_att_layer_num(mem_manager)
91+
7992
copy_cpu_cache_to_kv_buffer(
8093
mem_indexes=mem_indexes,
8194
big_page_buffer_ids=big_page_buffer_ids_gpu,
8295
page_indexes=page_indexes,
83-
gpu_full_att_kv_state=mem_manager.kv_buffer,
96+
gpu_full_att_kv_state=mem_manager.kv_buffer[:persisted_full_att],
8497
cpu_kv_conv_state=mem_manager.linear_att_big_page_buffers.conv_state_cache.buffer,
8598
cpu_kv_ssm_state=mem_manager.linear_att_big_page_buffers.ssm_state_cache.buffer,
8699
cpu_cache_tensor=cpu_cache_client.cpu_kv_cache_tensor,
@@ -169,12 +182,15 @@ def offload_gpu_kv_to_cpu_cache(
169182
copy_kv_buffer_to_cpu_cache,
170183
)
171184

185+
# Persist the full-attn slice used for prefix reuse: main slots followed by MTP draft slots.
186+
persisted_full_att = self._get_persisted_full_att_layer_num(mem_manager)
187+
172188
copy_kv_buffer_to_cpu_cache(
173189
mem_indexes=mem_indexes,
174190
page_indexes=page_indexes,
175191
page_readies=page_readies,
176192
big_page_buffer_ids=big_page_buffer_ids_gpu,
177-
gpu_kv_full_att_state=mem_manager.kv_buffer,
193+
gpu_kv_full_att_state=mem_manager.kv_buffer[:persisted_full_att],
178194
cpu_kv_conv_state=mem_manager.linear_att_big_page_buffers.conv_state_cache.buffer,
179195
cpu_kv_ssm_state=mem_manager.linear_att_big_page_buffers.ssm_state_cache.buffer,
180196
cpu_cache_tensor=cpu_cache_client.cpu_kv_cache_tensor,

lightllm/common/linear_att_cache_manager/config_objs.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import torch
22
import dataclasses
33
import triton
4-
from lightllm.utils.envs_utils import get_env_start_args
4+
from lightllm.utils.envs_utils import get_env_start_args, _mtp_added_layer_num
55
from lightllm.utils.log_utils import init_logger
66
from lightllm.utils.torch_dtype_utils import get_torch_dtype
77

88
logger = init_logger(__name__)
99

1010

11+
def get_mtp_draft_full_att_layer_num(args) -> int:
12+
# Delegates to the single source of truth in envs_utils (#9).
13+
return _mtp_added_layer_num(getattr(args, "mtp_mode", None), getattr(args, "mtp_step", 0))
14+
15+
1116
@dataclasses.dataclass
1217
class LinearAttCacheConfig:
1318
tp_world_size: int
@@ -30,6 +35,7 @@ class LinearAttCacheConfig:
3035
ssm_state_dtype: torch.dtype
3136
full_attention_interval: int
3237
all_layer_num: int # 包括 linear att 和 full att 的层加起来的层数
38+
draft_full_att_layer_num: int = 0
3339

3440
def get_conv_dim(self):
3541
# 第一项对应q的参数,第二项对应k的参数,第三项对应v的参数
@@ -41,9 +47,25 @@ def get_conv_dim(self):
4147
+ self.head_linear_v_dim * self.num_linear_v_heads
4248
)
4349

44-
def get_conv_state_shape(self):
50+
def get_main_full_att_layer_num(self):
51+
main_full_att_layer_num = self.all_layer_num - self.linear_layer_num
52+
assert main_full_att_layer_num == self.all_layer_num // self.full_attention_interval
53+
return main_full_att_layer_num
54+
55+
def get_persisted_full_att_layer_num(self):
56+
return self.get_main_full_att_layer_num() + self.draft_full_att_layer_num
57+
58+
def get_persisted_conv_state_shape(self):
59+
# NARROW shape used for the CPU/disk persisted page and ALL byte math.
60+
# Persisted state is always the committed (narrow) sliding window.
4561
return (self.get_conv_dim(), self.conv_kernel_size - 1)
4662

63+
def get_gpu_conv_state_shape(self, mtp_step: int):
64+
# WIDENED working shape for the GPU buffer: holds the tentatively
65+
# rolled-in S speculative tokens before acceptance. width-1 + S, where
66+
# S = mtp_step (a verify step has seqlen=S+1 -> width-1+(seqlen-1)).
67+
return (self.get_conv_dim(), (self.conv_kernel_size - 1) + mtp_step)
68+
4769
def get_ssm_state_shape(self):
4870
return (self.num_linear_v_heads, self.head_linear_k_dim, self.head_linear_v_dim)
4971

@@ -66,7 +88,7 @@ def get_cpu_cache_full_att_bytes(self):
6688
)
6789
assert big_page_token_num == get_env_start_args().cpu_cache_token_page_size
6890
full_att_bytes = 2 * self.full_att_all_num_kv_heads * self.full_att_head_dim * self.full_att_dtype.itemsize
69-
a = full_att_bytes * (self.all_layer_num - self.linear_layer_num) * big_page_token_num
91+
a = full_att_bytes * self.get_persisted_full_att_layer_num() * big_page_token_num
7092
return a
7193

7294
def get_cpu_cache_conv_bytes(self):
@@ -113,4 +135,5 @@ def load_from_args() -> "LinearAttCacheConfig":
113135
ssm_state_dtype=get_torch_dtype(args.linear_att_ssm_data_type),
114136
full_attention_interval=llm_config["full_attention_interval"],
115137
all_layer_num=n_layer,
138+
draft_full_att_layer_num=get_mtp_draft_full_att_layer_num(args),
116139
)

lightllm/common/linear_att_cache_manager/linear_att_buffer_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(
2424
self.conv_state_cache = LayerCache(
2525
size=self.size,
2626
dtype=self.linear_config.conv_state_dtype,
27-
shape=self.linear_config.get_conv_state_shape(),
27+
shape=self.linear_config.get_persisted_conv_state_shape(),
2828
layer_num=self.linear_config.linear_layer_num,
2929
device="cpu",
3030
size_first=True,

0 commit comments

Comments
 (0)