Skip to content

Commit 3bfb311

Browse files
authored
Merge pull request #122 from foundation-model-stack/chunked_prefill
Chunked prefill support for paged attention
2 parents 150b68f + c52e04b commit 3bfb311

5 files changed

Lines changed: 152 additions & 30 deletions

File tree

aiu_fms_testing_utils/scripts/drive_paged_programs.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@
128128
choices=["paged", "paged_fp8"],
129129
help="The attention type to use",
130130
)
131+
parser.add_argument(
132+
"--prefill_chunk_size",
133+
type=int,
134+
default=0,
135+
help="if > 0, activate chunked prefill, with chunk_size=this_argument. Only works with paged attention variants.",
136+
)
131137
parser.add_argument(
132138
"--stagger_load",
133139
type=int,
@@ -397,6 +403,7 @@ def __load_validation_info(
397403
max_new_tokens=max_new_tokens,
398404
compile_dynamic_sendnn=True,
399405
stagger_update_lazyhandle=args.stagger_update_lazyhandle,
406+
prefill_chunk_size=args.prefill_chunk_size,
400407
**extra_kwargs,
401408
)
402409

@@ -682,6 +689,7 @@ def __metric_calculator(r: torch.Tensor, t: torch.Tensor):
682689
GoldenTokenHook(cpu_validation_info.get_info("tokens")),
683690
last_n_tokens=64,
684691
timing=TIMING,
692+
prefill_chunk_size=args.prefill_chunk_size,
685693
**extra_kwargs,
686694
)
687695

@@ -726,6 +734,7 @@ def __metric_calculator(r: torch.Tensor, t: torch.Tensor):
726734
None,
727735
last_n_tokens=64,
728736
timing=TIMING,
737+
prefill_chunk_size=args.prefill_chunk_size,
729738
**extra_kwargs,
730739
)
731740

@@ -768,6 +777,7 @@ def __metric_calculator(r: torch.Tensor, t: torch.Tensor):
768777
None,
769778
last_n_tokens=64,
770779
timing=TIMING,
780+
prefill_chunk_size=args.prefill_chunk_size,
771781
**extra_kwargs,
772782
)
773783

aiu_fms_testing_utils/scripts/inference.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,12 @@
239239
default="sdpa",
240240
help="which backend attention to use in mha",
241241
)
242+
parser.add_argument(
243+
"--prefill_chunk_size",
244+
type=int,
245+
default=0,
246+
help="if > 0, activate chunked prefill, with chunk_size=this_argument. Only works with paged attention variants",
247+
)
242248
parser.add_argument(
243249
"--stagger_load",
244250
type=int,
@@ -782,6 +788,8 @@ def infer(use_cache, do_sample, warmup):
782788
if attn_name == "sdpa_causal":
783789
attention_specific_kwargs["contiguous_cache"] = True
784790
attention_specific_kwargs["max_seq_len"] = ids.shape[1] + args.max_new_tokens
791+
elif "paged" in attn_name:
792+
attention_specific_kwargs["prefill_chunk_size"] = args.prefill_chunk_size
785793

786794
result = generate(
787795
model,
@@ -844,6 +852,7 @@ def infer(use_cache, do_sample, warmup):
844852
args.compile_dynamic_sendnn,
845853
use_cache=cache,
846854
stagger_update_lazyhandle=args.stagger_update_lazyhandle,
855+
prefill_chunk_size=args.prefill_chunk_size,
847856
**extra_generation_kwargs,
848857
)
849858
if (

aiu_fms_testing_utils/testing/validation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,14 @@ def extract_validation_information(
263263
eos_token_id=None,
264264
last_n_tokens=0,
265265
timing="",
266+
prefill_chunk_size=0,
266267
**extra_kwargs,
267268
):
268269
attention_specific_kwargs = {}
269270
if "paged" in extra_kwargs.get("attn_name", "sdpa"):
270271
from aiu_fms_testing_utils.utils.paged import generate
272+
273+
attention_specific_kwargs["prefill_chunk_size"] = prefill_chunk_size
271274
else:
272275
# TODO: Add a unified generation dependent on attn_type
273276
from fms.utils.generation import generate

aiu_fms_testing_utils/utils/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def warmup_model(
5454
compile_dynamic_sendnn: bool = False,
5555
use_cache: bool = True,
5656
stagger_update_lazyhandle: int = 0,
57+
prefill_chunk_size: int = 0,
5758
**extra_kwargs,
5859
):
5960
import torch_sendnn
@@ -62,6 +63,8 @@ def warmup_model(
6263
attn_name = extra_kwargs.get("attn_name", "sdpa")
6364
if "paged" in attn_name:
6465
from aiu_fms_testing_utils.utils.paged import generate, adjust_inputs_to_batch
66+
67+
attention_specific_kwargs["prefill_chunk_size"] = prefill_chunk_size
6568
else:
6669
# TODO: Add a unified generation dependent on attn_type
6770
from fms.utils.generation import generate

aiu_fms_testing_utils/utils/paged.py

Lines changed: 127 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import os
23
import random
34
import time
@@ -35,6 +36,7 @@ def generate(
3536
do_sample: bool = True,
3637
num_beams: int = 1,
3738
use_cache: bool = False,
39+
prefill_chunk_size: int = 0,
3840
eos_token_id: Optional[int] = None,
3941
timing: str = "",
4042
post_iteration_hook: Optional[
@@ -117,6 +119,10 @@ def generate(
117119
max_possible_context_length = input_ids.size(1) + max_new_tokens
118120

119121
BLOCK_SIZE = 64
122+
if prefill_chunk_size > 0:
123+
assert prefill_chunk_size % BLOCK_SIZE == 0, (
124+
"Chunk size must be a multiple of the page size"
125+
)
120126

121127
# these variables are guaranteed to be set in another location (inference.py, test_decoders.py, etc.)
122128
# if we set these variables here, we run the risk of warming up and generating with different sizes
@@ -229,7 +235,6 @@ def generate(
229235
for pos_i in range(input_ids.shape[1] - seq_tkv, input_ids.shape[1]):
230236
# we may have already popped a block, so index to the proper block
231237
block_number = block_table_i[pos_i // BLOCK_SIZE]
232-
233238
block_offset = pos_i % BLOCK_SIZE
234239
slot = block_number * BLOCK_SIZE + block_offset
235240
slot_mapping_i.append(slot)
@@ -264,57 +269,149 @@ def generate(
264269
# remove extra pads from the input_ids, slot_mapping, position_ids, mask to account for empty pages
265270
# each input should be padded to its smallest multiple of BLOCK_SIZE (64)
266271
# we need to clone these tensors to ensure the pointer offset is 0
267-
input_ids_i = input_ids[seq_i][-current_tkv:].unsqueeze(0).clone()
268-
slot_mapping_i = (
272+
input_ids_seq = input_ids[seq_i][-current_tkv:].unsqueeze(0).clone()
273+
slot_mapping_seq = (
269274
torch.tensor(slot_mapping[seq_i][-current_tkv:], dtype=torch.int64)
270275
.unsqueeze(0)
271276
.clone()
272277
)
273-
position_ids_i = (
278+
position_ids_seq = (
274279
kwargs["position_ids"][seq_i][-current_tkv:].unsqueeze(0).clone()
275280
)
276281

277282
# This view will result in a discontiguous tensor (creates a new graph during compile)
278283
# For this reason, we must explicitly make contiguous
279-
mask_i = (
284+
mask_seq = (
280285
kwargs["mask"][seq_i][:, -current_tkv:, -current_tkv:]
281286
.unsqueeze(0)
282287
.contiguous()
283288
)
284289

285-
# batch dynamic
286-
torch._dynamo.mark_static(input_ids_i, 0)
287-
torch._dynamo.mark_static(slot_mapping_i, 0)
288-
torch._dynamo.mark_static(position_ids_i, 0)
289-
torch._dynamo.mark_static(mask_i, 0)
290-
291-
# seq dynamic
292-
torch._dynamo.mark_dynamic(input_ids_i, 1)
293-
torch._dynamo.mark_dynamic(slot_mapping_i, 1)
294-
torch._dynamo.mark_dynamic(position_ids_i, 1)
295-
torch._dynamo.mark_dynamic(mask_i, 2)
296-
torch._dynamo.mark_dynamic(mask_i, 3)
297-
298290
# FP8 per-sentence scale handling
299291
if "fp8" in kwargs["attn_name"]:
300292
for layer_idx, (t1, t2) in enumerate(current_kv_cache):
301293
t1._scale = current_kv_scales[layer_idx][0][seq_i].reshape(-1)
302294
t2._scale = current_kv_scales[layer_idx][1][seq_i].reshape(-1)
303295

304296
last_n_tokens = kwargs.get("last_n_tokens", 0)
305-
output, current_kv_cache = model(
306-
input_ids_i,
307-
slot_mapping=slot_mapping_i,
308-
position_ids=position_ids_i,
309-
mask=mask_i,
310-
past_key_value_states=current_kv_cache,
311-
use_cache=kwargs["use_cache"],
312-
last_n_tokens=last_n_tokens,
313-
attn_name=kwargs["attn_name"],
314-
)
315297

316-
# only last token must be handled here to properly stack the tensors
317-
output = output[:, -1, :]
298+
if prefill_chunk_size > 0:
299+
left_padded_prompt_mask_seq_chunk = None
300+
# Chunked prefill
301+
for chunk_j in range(math.ceil(current_tkv / prefill_chunk_size)):
302+
chunk_start = -current_tkv + chunk_j * prefill_chunk_size
303+
chunk_end = -current_tkv + min(
304+
(chunk_j + 1) * prefill_chunk_size, current_tkv
305+
)
306+
307+
ids_length = input_ids[seq_i].shape[0]
308+
input_ids_seq_chunk = (
309+
input_ids[seq_i][
310+
chunk_start + ids_length : chunk_end + ids_length
311+
]
312+
.unsqueeze(0)
313+
.clone()
314+
)
315+
slots_length = len(slot_mapping[seq_i])
316+
slot_mapping_seq_chunk = (
317+
torch.tensor(
318+
slot_mapping[seq_i][
319+
chunk_start + slots_length : chunk_end
320+
+ slots_length
321+
],
322+
dtype=torch.int64,
323+
)
324+
.unsqueeze(0)
325+
.clone()
326+
)
327+
pids_length = kwargs["position_ids"][seq_i].shape[0]
328+
position_ids_seq_chunk = (
329+
kwargs["position_ids"][seq_i][
330+
chunk_start + pids_length : chunk_end + pids_length
331+
]
332+
.unsqueeze(0)
333+
.clone()
334+
)
335+
336+
# This view will result in a discontiguous tensor (creates a new graph during compile)
337+
# For this reason, we must explicitly make contiguous
338+
if left_padded_prompt_mask_seq_chunk is None:
339+
left_padded_prompt_mask_seq_chunk = (
340+
position_ids_seq_chunk == 0
341+
).sum(dim=1) - 1
342+
current_tkv_mask_seq_chunk = torch.min(
343+
torch.tensor(
344+
(chunk_j + 1) * prefill_chunk_size, dtype=torch.int64
345+
),
346+
current_tkv,
347+
).unsqueeze(0)
348+
349+
table_length = len(block_table[seq_i])
350+
block_start = -current_tkv // BLOCK_SIZE + table_length
351+
block_end = chunk_end // BLOCK_SIZE + table_length
352+
block_table_seq_chunk = torch.tensor(
353+
block_table[seq_i][block_start:block_end], dtype=torch.int64
354+
).unsqueeze(0)
355+
356+
chunked_kwargs = {
357+
"slot_mapping": slot_mapping_seq_chunk,
358+
"position_ids": position_ids_seq_chunk,
359+
"past_key_value_states": current_kv_cache,
360+
"use_cache": kwargs["use_cache"],
361+
"last_n_tokens": kwargs["last_n_tokens"],
362+
"attn_name": kwargs["attn_name"],
363+
"left_padded_prompt_mask": left_padded_prompt_mask_seq_chunk,
364+
"current_tkv_mask": current_tkv_mask_seq_chunk,
365+
"block_table": block_table_seq_chunk,
366+
}
367+
368+
# batch static
369+
torch._dynamo.mark_static(input_ids_seq_chunk, 0)
370+
torch._dynamo.mark_static(slot_mapping_seq_chunk, 0)
371+
torch._dynamo.mark_static(position_ids_seq_chunk, 0)
372+
torch._dynamo.mark_static(block_table_seq_chunk, 0)
373+
374+
# seq dynamic
375+
torch._dynamo.mark_dynamic(input_ids_seq_chunk, 1)
376+
torch._dynamo.mark_dynamic(slot_mapping_seq_chunk, 1)
377+
torch._dynamo.mark_dynamic(position_ids_seq_chunk, 1)
378+
torch._dynamo.mark_dynamic(block_table_seq_chunk, 1)
379+
380+
logits, current_kv_cache = model(
381+
input_ids_seq_chunk, **chunked_kwargs
382+
)
383+
384+
# only last token must be handled here to properly stack the tensors
385+
logits = logits[:, -1, :]
386+
387+
output = (logits, current_kv_cache)
388+
389+
else:
390+
# batch static
391+
torch._dynamo.mark_static(input_ids_seq, 0)
392+
torch._dynamo.mark_static(slot_mapping_seq, 0)
393+
torch._dynamo.mark_static(position_ids_seq, 0)
394+
torch._dynamo.mark_static(mask_seq, 0)
395+
396+
# seq dynamic
397+
torch._dynamo.mark_dynamic(input_ids_seq, 1)
398+
torch._dynamo.mark_dynamic(slot_mapping_seq, 1)
399+
torch._dynamo.mark_dynamic(position_ids_seq, 1)
400+
torch._dynamo.mark_dynamic(mask_seq, 2)
401+
torch._dynamo.mark_dynamic(mask_seq, 3)
402+
output, current_kv_cache = model(
403+
input_ids_seq,
404+
slot_mapping=slot_mapping_seq,
405+
position_ids=position_ids_seq,
406+
mask=mask_seq,
407+
past_key_value_states=current_kv_cache,
408+
use_cache=kwargs["use_cache"],
409+
last_n_tokens=last_n_tokens,
410+
attn_name=kwargs["attn_name"],
411+
)
412+
413+
# only last token must be handled here to properly stack the tensors
414+
output = output[:, -1, :]
318415

319416
# TODO: Figure out how to do this cleanly
320417
if "fp8" in kwargs["attn_name"]:

0 commit comments

Comments
 (0)