Skip to content

Commit a504eea

Browse files
committed
Switch to producing document_ids in collate fn
Signed-off-by: Fynn Schmitt-Ulms <fschmitt@redhat.com>
1 parent 7c4ea98 commit a504eea

9 files changed

Lines changed: 52 additions & 96 deletions

File tree

src/speculators/models/dflash/attention.py

Lines changed: 3 additions & 25 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 document index, -1 for padding
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,28 +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-
7957
if (oob := (anchor_positions < 0) | (anchor_positions >= total_seq_len)).any():
8058
raise ValueError(
8159
f"anchor_positions out of range: {anchor_positions[oob].tolist()}"

src/speculators/models/dflash/core.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,15 @@ def mask_token_id(self) -> int:
207207
@torch.compiler.disable
208208
def _create_attention_mask(
209209
self,
210-
lengths: torch.Tensor,
210+
document_ids: torch.Tensor,
211211
total_seq_len: int,
212212
anchor_positions: torch.Tensor,
213213
device: torch.device,
214214
sliding_window: int | None = None,
215215
sliding_window_non_causal: bool = False,
216216
):
217217
mask_mod, q_len, kv_len = create_anchor_block_mask_mod(
218-
lengths=lengths.to(device),
218+
document_ids=document_ids.squeeze(0).to(device),
219219
total_seq_len=total_seq_len,
220220
anchor_positions=anchor_positions,
221221
block_size=self.block_size,
@@ -232,7 +232,7 @@ def _create_attention_mask(
232232
)
233233

234234
@torch.compiler.disable
235-
def _build_attention_mask(self, loss_mask, lengths, device):
235+
def _build_attention_mask(self, loss_mask, document_ids, device):
236236
total_seq_len = loss_mask.shape[1]
237237

238238
anchor_positions, anchor_valid = select_anchors(
@@ -242,7 +242,7 @@ def _build_attention_mask(self, loss_mask, lengths, device):
242242
full_attn_mask = None
243243
if self.uses_full_attn:
244244
full_attn_mask = self._create_attention_mask(
245-
lengths=lengths,
245+
document_ids=document_ids,
246246
total_seq_len=total_seq_len,
247247
anchor_positions=anchor_positions,
248248
device=device,
@@ -252,7 +252,7 @@ def _build_attention_mask(self, loss_mask, lengths, device):
252252
sliding_window_attn_mask = None
253253
if self.uses_sliding_window_attn:
254254
sliding_window_attn_mask = self._create_attention_mask(
255-
lengths=lengths,
255+
document_ids=document_ids,
256256
total_seq_len=total_seq_len,
257257
anchor_positions=anchor_positions,
258258
device=device,
@@ -269,7 +269,7 @@ def forward(
269269
input_ids: torch.Tensor, # shape: [1, total_seq_len]
270270
loss_mask: torch.Tensor, # shape: [1, total_seq_len]
271271
verifier_last_hidden_states: torch.Tensor, # shape: [1, total_seq_len, hidden_size] # noqa: E501
272-
lengths: torch.Tensor | None = None, # shape: [batch_size]
272+
document_ids: torch.Tensor, # shape: [1, total_seq_len]
273273
position_ids: torch.Tensor | None = None, # shape: [1, total_seq_len]
274274
loss_fn=kl_div_loss,
275275
**kwargs,
@@ -278,15 +278,13 @@ def forward(
278278
total_seq_len = hidden_states.shape[1]
279279
num_anchors = self.config.max_anchors
280280

281-
if lengths is None:
282-
lengths = torch.tensor([total_seq_len], dtype=torch.long, device=device)
283281
if position_ids is None:
284282
position_ids = 1 + torch.arange(
285283
total_seq_len, dtype=torch.long, device=device
286284
).unsqueeze(0)
287285

288286
full_attn_mask, sliding_window_attn_mask, anchor_positions, anchor_valid = (
289-
self._build_attention_mask(loss_mask, lengths, device)
287+
self._build_attention_mask(loss_mask, document_ids, device)
290288
)
291289

292290
mask_tokens_size = num_anchors * self.block_size

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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def forward( # noqa: C901
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 document index, -1 for padding
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: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ def collate_fn(batch: list[BatchType | None]) -> BatchType:
524524
# Include lengths until while they fit in max_len
525525
# The last included length is (if necessary) truncated
526526
# Any additional lengths are discarded
527-
lengths = collated_data["lengths"]
527+
lengths = collated_data.pop("lengths")
528528
new_lengths = []
529529
cum_length = 0
530530
for length in lengths:
@@ -534,9 +534,20 @@ def collate_fn(batch: list[BatchType | None]) -> BatchType:
534534
new_lengths.append(length)
535535
cum_length += length
536536
lengths = torch.tensor(new_lengths, dtype=torch.long)
537-
# Pad to at least size 4 to avoid torch.compile recompiling on sizes 0/1
538-
pad_to = max(4, lengths.shape[0])
539-
collated_data["lengths"] = F.pad(lengths, (0, pad_to - lengths.shape[0]))
537+
538+
# Create document_ids: maps each position to its document index, -1 for padding
539+
document_ids = torch.repeat_interleave(
540+
torch.arange(lengths.shape[0], dtype=torch.long), lengths
541+
)
542+
document_ids = torch.cat(
543+
[
544+
document_ids,
545+
-1 * torch.ones(max_len - document_ids.shape[0], dtype=torch.long),
546+
]
547+
).unsqueeze(0)
548+
# shape: [1, max_len]
549+
collated_data["document_ids"] = document_ids
550+
540551
return collated_data
541552

542553
return collate_fn

tests/unit/train/test_data.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ def test_collate_fn_basic():
188188
[[[2.0], [3.0], [10.0], [11.0], [12.0], [13.0], [14.0], [15.0], [-1], [-1]]]
189189
),
190190
"loss_mask": torch.tensor([[0, 1, 0, 0, 1, 0, 1, 1, -1, -1]], dtype=torch.long),
191-
"lengths": torch.tensor([2, 6], dtype=torch.long),
191+
"document_ids": torch.tensor(
192+
[[0, 0, 1, 1, 1, 1, 1, 1, -1, -1]], dtype=torch.long
193+
),
192194
"position_ids": torch.tensor(
193195
[[0, 1, 0, 1, 2, 3, 4, 5, -1, -1]], dtype=torch.long
194196
),
@@ -235,11 +237,13 @@ def test_collate_fn_length_truncation():
235237

236238
collated = collate_fn(batch)
237239

238-
# Last length is truncated to fit in max_len
239-
expected_lengths = torch.tensor([5, 6], dtype=torch.long)
240+
# document_ids: doc 0 has length 5, doc 1 truncated to length 6, rest is padding
241+
expected_document_ids = torch.tensor(
242+
[[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]], dtype=torch.long
243+
)
244+
assert torch.equal(collated["document_ids"], expected_document_ids)
245+
assert "lengths" not in collated
240246

241-
# All tensors (other than lengths) are concatenated then truncated to max_len
242-
assert torch.equal(collated["lengths"], expected_lengths)
243247
for key in [
244248
"input_ids",
245249
"hidden_states",

tests/unit/train/test_eagle3_attention.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010

1111
def test_create_combined_mask_mod():
1212
lengths = torch.tensor([1, 2, 3])
13+
document_ids = torch.repeat_interleave(
14+
torch.arange(lengths.shape[0], dtype=torch.long), lengths
15+
)
1316
mask_mod = create_combined_mask_mod(
14-
lengths, total_seq_len=int(lengths.sum().item())
17+
document_ids, total_seq_len=int(lengths.sum().item())
1518
)
1619

1720
# Creates causal document mask mod that supports extended diagonals
@@ -44,7 +47,10 @@ def test_diagonal_draft_tokens_mask_mod(lengths):
4447
# If kv_idx > N (N = orig seq len = num query inds), only the diagonal tokens are
4548
# in the mask. Diagonal tokens are those where kv_idx % N == q_idx
4649

47-
mask_mod = create_combined_mask_mod(lengths, total_seq_len=lengths.sum().item())
50+
document_ids = torch.repeat_interleave(
51+
torch.arange(lengths.shape[0], dtype=torch.long), lengths
52+
)
53+
mask_mod = create_combined_mask_mod(document_ids, total_seq_len=lengths.sum().item())
4854

4955
N = lengths.sum().item()
5056

0 commit comments

Comments
 (0)