Skip to content

Commit dbd631b

Browse files
authored
Reduce graph breaks and recompiles (#547)
<!-- markdownlint-disable --> PLEASE FILL IN THE PR DESCRIPTION HERE ENSURING ALL CHECKLIST ITEMS (AT THE BOTTOM) HAVE BEEN CONSIDERED. ## Purpose Late in epoch graph recompiles can cause training to crash after a substantial number of batches have been processed. This pr works to decrease these occurrences and also reduce the number of graph breaks in the compiled code. ## Related Issue <!--- Link related issue if applicable --> I think this should resolve #544. ## Tests <!--- Please describe in detail how you tested your changes. --> Will add some tests to this pr. I have filled in: - [x] The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)". - [x] The test plan/results, such as providing test command and pasting the results. - [ ] (Optional) The necessary documentation update. - [x] I (a human) have written or reviewed the code in this pr to the best of my ability. --------- Signed-off-by: Fynn Schmitt-Ulms <fschmitt@redhat.com>
1 parent 04dd376 commit dbd631b

12 files changed

Lines changed: 88 additions & 175 deletions

File tree

src/speculators/models/dflash/attention.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
def create_anchor_block_mask_mod(
8-
lengths: torch.Tensor,
8+
document_ids: torch.Tensor,
99
total_seq_len: int,
1010
anchor_positions: torch.Tensor,
1111
block_size: int,
@@ -29,7 +29,7 @@ def create_anchor_block_mask_mod(
2929
- may not attend to other synthetic blocks or later base tokens
3030
3131
Args:
32-
lengths: [num_docs] lengths of packed documents
32+
document_ids: [total_seq_len] maps each position to its doc index, pad -1
3333
total_seq_len: padded packed sequence width
3434
anchor_positions: [n_anchors] absolute positions into the packed base sequence
3535
block_size: number of query tokens per anchor block
@@ -42,7 +42,7 @@ def create_anchor_block_mask_mod(
4242
# Always use non_causal for full attn
4343
non_causal = sliding_window is None or sliding_window_non_causal
4444

45-
device = lengths.device
45+
device = document_ids.device
4646
anchor_positions = anchor_positions.to(device=device, dtype=torch.long).contiguous()
4747

4848
if anchor_positions.ndim != 1:
@@ -54,33 +54,6 @@ def create_anchor_block_mask_mod(
5454
q_len = n_anchors * block_size
5555
kv_len = total_seq_len + q_len
5656

57-
# Map each base-sequence position -> document id, padding -> -1
58-
document_ids = torch.repeat_interleave(
59-
torch.arange(lengths.shape[0], device=device, dtype=torch.long),
60-
lengths,
61-
)
62-
if document_ids.numel() > total_seq_len:
63-
raise ValueError(
64-
f"sum(lengths)={document_ids.numel()} exceeds total_seq_len={total_seq_len}"
65-
)
66-
if document_ids.numel() < total_seq_len:
67-
document_ids = torch.cat(
68-
[
69-
document_ids,
70-
-1
71-
* torch.ones(
72-
total_seq_len - document_ids.numel(),
73-
device=device,
74-
dtype=torch.long,
75-
),
76-
]
77-
).contiguous()
78-
79-
if (oob := (anchor_positions < 0) | (anchor_positions >= total_seq_len)).any():
80-
raise ValueError(
81-
f"anchor_positions out of range: {anchor_positions[oob].tolist()}"
82-
)
83-
8457
# For each query position, which anchor does it belong to?
8558
# query q in [j*block_size, (j+1)*block_size) belongs to anchor_positions[j]
8659
query_anchor_positions = torch.repeat_interleave(anchor_positions, block_size)

src/speculators/models/dflash/core.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -203,17 +203,18 @@ def mask_token_id(self) -> int:
203203
)
204204
return self.config.mask_token_id
205205

206+
@torch.compiler.disable
206207
def _create_attention_mask(
207208
self,
208-
lengths: torch.Tensor,
209+
document_ids: torch.Tensor,
209210
total_seq_len: int,
210211
anchor_positions: torch.Tensor,
211212
device: torch.device,
212213
sliding_window: int | None = None,
213214
sliding_window_non_causal: bool = False,
214215
):
215216
mask_mod, q_len, kv_len = create_anchor_block_mask_mod(
216-
lengths=lengths.to(device),
217+
document_ids=document_ids.squeeze(0).to(device),
217218
total_seq_len=total_seq_len,
218219
anchor_positions=anchor_positions,
219220
block_size=self.block_size,
@@ -230,7 +231,7 @@ def _create_attention_mask(
230231
)
231232

232233
@torch.compiler.disable
233-
def _build_attention_mask(self, loss_mask, lengths, device):
234+
def _build_attention_mask(self, loss_mask, document_ids, device):
234235
total_seq_len = loss_mask.shape[1]
235236

236237
anchor_positions, anchor_valid = select_anchors(
@@ -240,7 +241,7 @@ def _build_attention_mask(self, loss_mask, lengths, device):
240241
full_attn_mask = None
241242
if self.uses_full_attn:
242243
full_attn_mask = self._create_attention_mask(
243-
lengths=lengths,
244+
document_ids=document_ids,
244245
total_seq_len=total_seq_len,
245246
anchor_positions=anchor_positions,
246247
device=device,
@@ -250,7 +251,7 @@ def _build_attention_mask(self, loss_mask, lengths, device):
250251
sliding_window_attn_mask = None
251252
if self.uses_sliding_window_attn:
252253
sliding_window_attn_mask = self._create_attention_mask(
253-
lengths=lengths,
254+
document_ids=document_ids,
254255
total_seq_len=total_seq_len,
255256
anchor_positions=anchor_positions,
256257
device=device,
@@ -267,7 +268,7 @@ def forward(
267268
input_ids: torch.Tensor, # shape: [1, total_seq_len]
268269
loss_mask: torch.Tensor, # shape: [1, total_seq_len]
269270
verifier_last_hidden_states: torch.Tensor, # shape: [1, total_seq_len, hidden_size] # noqa: E501
270-
lengths: torch.Tensor | None = None, # shape: [batch_size]
271+
document_ids: torch.Tensor, # shape: [1, total_seq_len]
271272
position_ids: torch.Tensor | None = None, # shape: [1, total_seq_len]
272273
loss_fn=kl_div_loss,
273274
**kwargs,
@@ -276,15 +277,13 @@ def forward(
276277
total_seq_len = hidden_states.shape[1]
277278
num_anchors = self.config.max_anchors
278279

279-
if lengths is None:
280-
lengths = torch.tensor([total_seq_len], dtype=torch.long, device=device)
281280
if position_ids is None:
282281
position_ids = 1 + torch.arange(
283282
total_seq_len, dtype=torch.long, device=device
284283
).unsqueeze(0)
285284

286285
full_attn_mask, sliding_window_attn_mask, anchor_positions, anchor_valid = (
287-
self._build_attention_mask(loss_mask, lengths, device)
286+
self._build_attention_mask(loss_mask, document_ids, device)
288287
)
289288

290289
mask_tokens_size = num_anchors * self.block_size
@@ -304,7 +303,7 @@ def forward(
304303
# shape: [1, total_seq_len, hidden_size]
305304

306305
mask_position_ids = get_base_indices_for_anchored_blocks(
307-
position_ids[:, anchor_positions], self.block_size, input_ids.numel()
306+
position_ids[0, anchor_positions], self.block_size
308307
)
309308
position_ids = torch.cat([position_ids, mask_position_ids.unsqueeze(0)], dim=1)
310309
# shape: [1, total_seq_len + num_anchors*block_size]
@@ -314,7 +313,7 @@ def forward(
314313
position_embeddings = self.rotary_emb(hidden_states, position_ids)
315314

316315
anchored_block_indices = get_base_indices_for_anchored_blocks(
317-
anchor_positions, self.block_size, input_ids.numel()
316+
anchor_positions, self.block_size
318317
) # shape: [num_anchors*block_size]
319318

320319
with torch.no_grad():

src/speculators/models/dflash/utils.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
def get_base_indices_for_anchored_blocks(
77
anchor_positions: torch.Tensor, # shape: [1, num_anchors]
88
block_size: int,
9-
total_seq_len: int | None = None,
109
) -> torch.Tensor: # shape: [num_anchors*block_size]
1110
anchor_positions = anchor_positions.to(dtype=torch.long).view(-1)
1211
# dtype: long, shape: [num_anchors]
@@ -16,12 +15,6 @@ def get_base_indices_for_anchored_blocks(
1615
anchor_positions[:, None] + offsets[None, :]
1716
) # shape: [num_anchors, block_size]
1817

19-
if (idx < 0).any() or (total_seq_len and (idx >= total_seq_len).any()):
20-
raise ValueError(
21-
"Some anchor_positions + offsets are out of range for total_seq_len"
22-
f"={total_seq_len}. Max={idx.max().item()}, min={idx.min().item()}"
23-
)
24-
2518
return idx.reshape(-1)
2619

2720

@@ -60,10 +53,14 @@ def select_anchors(
6053
anchor_valid = torch.zeros(num_anchors, dtype=torch.bool, device=device)
6154

6255
k = min(num_anchors, valid_indices.numel())
63-
if k > 0:
64-
perm = torch.randperm(valid_indices.numel(), device=loss_mask.device)
65-
anchors[:k] = valid_indices[perm[:k]]
66-
anchor_valid[:k] = True
56+
57+
# Constrain value of k for torch dynamo
58+
torch._check(k <= valid_indices.numel()) # noqa: SLF001
59+
torch._check(k >= 0) # noqa: SLF001
60+
61+
perm = torch.randperm(valid_indices.numel(), device=loss_mask.device)
62+
anchors[:k] = torch.gather(valid_indices, 0, perm[:k])
63+
anchor_valid[:k] = True
6764

6865
return anchors, anchor_valid
6966
# shape: [num_anchors], [num_anchors]

src/speculators/models/eagle3/attention.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,7 @@
88
from speculators.models.attention import ALL_ATTENTION_FUNCTIONS # noqa: F401
99

1010

11-
def create_combined_mask_mod(lengths: torch.Tensor, total_seq_len: int):
12-
document_ids = torch.repeat_interleave(
13-
torch.arange(lengths.shape[0], device=lengths.device, dtype=torch.long), lengths
14-
)
15-
# Pad ids with -1 to indicate padding
16-
document_ids = torch.cat(
17-
[
18-
document_ids,
19-
-1
20-
* torch.ones(
21-
total_seq_len - document_ids.shape[0],
22-
device=lengths.device,
23-
dtype=torch.long,
24-
),
25-
]
26-
).contiguous()
27-
11+
def create_combined_mask_mod(document_ids: torch.Tensor, total_seq_len: int):
2812
def causal_mask_mod(_b, _h, q_idx, kv_idx):
2913
return q_idx >= kv_idx
3014

src/speculators/models/eagle3/core.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ def load_verifier_weights(self):
129129
)
130130

131131
@conditional_torch_compile
132-
def forward( # noqa: C901
132+
def forward(
133133
self,
134134
hidden_states: torch.Tensor, # shape: [1, total_seq_len, 3 * hidden_size]
135135
input_ids: torch.Tensor, # shape: [1, total_seq_len]
136-
lengths: torch.Tensor | None = None, # shape: [batch_size]
136+
document_ids: torch.Tensor, # shape: [1, total_seq_len]
137137
loss_mask: torch.Tensor | None = None, # shape: [1, total_seq_len]
138138
position_ids: torch.Tensor | None = None, # shape: [1, total_seq_len]
139139
verifier_last_hidden_states: torch.Tensor
@@ -148,8 +148,6 @@ def forward( # noqa: C901
148148
device = hidden_states.device
149149
total_seq_len = hidden_states.shape[1]
150150

151-
if lengths is None:
152-
lengths = torch.tensor([total_seq_len], dtype=torch.long, device=device)
153151
if position_ids is None:
154152
position_ids = 1 + torch.arange(
155153
total_seq_len, dtype=torch.long, device=device
@@ -158,7 +156,9 @@ def forward( # noqa: C901
158156

159157
past_key_values = DynamicCache(config=self.config.transformer_layer_config)
160158

161-
combined_mask_mod = create_combined_mask_mod(lengths.to(device), total_seq_len)
159+
combined_mask_mod = create_combined_mask_mod(
160+
document_ids.squeeze(0).to(device), total_seq_len
161+
)
162162
# Note: Attention mask is stored as a BlockMask object
163163
attention_mask = create_block_mask(
164164
combined_mask_mod,

src/speculators/models/peagle/attention.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
def create_peagle_mask_mod(
77
anchor_pos: torch.Tensor, # shape: [total_sampled]
88
depth: torch.Tensor, # shape: [total_sampled]
9-
lengths: torch.Tensor, # shape: [batch_size]
10-
total_seq_len: int,
9+
document_ids: torch.Tensor, # shape: [total_seq_len]
1110
):
1211
"""
1312
Create a flex attention mask modifier for P-EAGLE parallel groups.
@@ -23,9 +22,7 @@ def create_peagle_mask_mod(
2322
anchor_pos: The starting position in the original sequence the current
2423
sampling chain started from.
2524
depth: Which COD sampling round each element belongs to
26-
lengths: The length of each document. Used to produce a document mask to prevent
27-
cross contamination
28-
total_seq_len: int, combined padded length of the original sequences
25+
document_ids: Maps each position to its document index, -1 for padding
2926
3027
Args example:
3128
@@ -45,23 +42,6 @@ def create_peagle_mask_mod(
4542
A mask_mod function compatible with flex_attention create_block_mask
4643
"""
4744

48-
# Generate sample_ids to prevent cross-sample attention
49-
document_ids = torch.repeat_interleave(
50-
torch.arange(lengths.shape[0], device=lengths.device, dtype=torch.long), lengths
51-
)
52-
# Pad ids with -1 to indicate padding
53-
document_ids = torch.cat(
54-
[
55-
document_ids,
56-
-1
57-
* torch.ones(
58-
total_seq_len - document_ids.shape[0],
59-
device=lengths.device,
60-
dtype=torch.long,
61-
),
62-
]
63-
).contiguous()
64-
6545
def peagle_mask_mod(_b, _h, q_idx, kv_idx):
6646
q_anchor_pos = anchor_pos[q_idx]
6747
kv_anchor_pos = anchor_pos[kv_idx]

src/speculators/models/peagle/core.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def forward(
5252
self,
5353
hidden_states: torch.Tensor,
5454
input_ids: torch.Tensor,
55-
lengths: torch.Tensor | None = None,
55+
document_ids: torch.Tensor,
5656
position_ids: torch.Tensor | None = None,
5757
loss_mask: torch.Tensor | None = None,
5858
verifier_last_hidden_states: torch.Tensor | None = None,
@@ -62,12 +62,10 @@ def forward(
6262
"""
6363
Forward pass for P-EAGLE model training with parallel group prediction.
6464
65-
Matches p-eagle-train implementation but accepts standard EAGLE3 data format.
66-
6765
Args:
6866
hidden_states: Verifier hidden states [batch, seq_len, 3*hidden_size]
6967
input_ids: Input token IDs [batch, seq_len]
70-
lengths: Sequence lengths for each sample in batch [batch_size]
68+
document_ids: Document IDs [1, seq_len], maps positions to doc index, pad -1
7169
position_ids: Position IDs [batch, seq_len] (optional)
7270
loss_mask: Loss mask for which tokens to compute loss on
7371
[batch, seq_len]
@@ -83,8 +81,6 @@ def forward(
8381
device = hidden_states.device
8482
seq_length = input_ids.shape[1]
8583

86-
if lengths is None:
87-
lengths = torch.tensor([seq_length], dtype=torch.long, device=device)
8884
if loss_mask is None:
8985
loss_mask = torch.ones_like(input_ids, dtype=torch.float32)
9086

@@ -133,8 +129,7 @@ def forward(
133129
mask_mod = create_peagle_mask_mod(
134130
anchor_pos=anchor_pos,
135131
depth=depth,
136-
lengths=lengths,
137-
total_seq_len=seq_length,
132+
document_ids=document_ids.squeeze(0).to(device),
138133
)
139134

140135
attention_mask = create_block_mask( # type: ignore[assignment]

src/speculators/train/data.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ def collate_fn(batch: list[BatchType | None]) -> BatchType:
531531
# Include lengths until while they fit in max_len
532532
# The last included length is (if necessary) truncated
533533
# Any additional lengths are discarded
534-
lengths = collated_data["lengths"]
534+
lengths = collated_data.pop("lengths")
535535
new_lengths = []
536536
cum_length = 0
537537
for length in lengths:
@@ -540,7 +540,21 @@ def collate_fn(batch: list[BatchType | None]) -> BatchType:
540540
break
541541
new_lengths.append(length)
542542
cum_length += length
543-
collated_data["lengths"] = torch.tensor(new_lengths, dtype=torch.long)
543+
lengths = torch.tensor(new_lengths, dtype=torch.long)
544+
545+
# Create document_ids: maps each position to its document index, -1 for padding
546+
document_ids = torch.repeat_interleave(
547+
torch.arange(lengths.shape[0], dtype=torch.long), lengths
548+
)
549+
document_ids = torch.cat(
550+
[
551+
document_ids,
552+
-1 * torch.ones(max_len - document_ids.shape[0], dtype=torch.long),
553+
]
554+
).unsqueeze(0)
555+
# shape: [1, max_len]
556+
collated_data["document_ids"] = document_ids
557+
544558
return collated_data
545559

546560
return collate_fn

0 commit comments

Comments
 (0)