Rorical commited on
Commit
be5cdc5
·
verified ·
1 Parent(s): 770f6be

Upload models/lm_loss.py

Browse files
Files changed (1) hide show
  1. models/lm_loss.py +629 -0
models/lm_loss.py ADDED
@@ -0,0 +1,629 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Memory-efficient LM-head cross entropy helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional, Tuple
6
+
7
+ import torch
8
+ import torch.nn.functional as F
9
+
10
+
11
+ def token_superposition_embeddings(
12
+ token_emb,
13
+ input_ids: torch.Tensor,
14
+ bag_size: int,
15
+ ) -> torch.Tensor:
16
+ """Average contiguous token embeddings into training-time bags.
17
+
18
+ This is the input-side fold from Token Superposition Training. A raw
19
+ sequence of length ``L = l * s`` becomes ``l`` latent positions, each the
20
+ mean embedding of ``s`` contiguous source tokens.
21
+ """
22
+ bag_size = int(bag_size)
23
+ x = token_emb(input_ids)
24
+ if bag_size <= 1:
25
+ return x
26
+ if input_ids.size(1) % bag_size != 0:
27
+ raise ValueError(
28
+ "token superposition requires sequence length divisible by "
29
+ f"bag_size; got seq_len={input_ids.size(1)}, bag_size={bag_size}"
30
+ )
31
+ batch, seq_len, d_model = x.shape
32
+ return x.reshape(batch, seq_len // bag_size, bag_size, d_model).mean(dim=2)
33
+
34
+
35
+ def token_superposition_attention_mask(
36
+ attention_mask: Optional[torch.Tensor],
37
+ bag_size: int,
38
+ ) -> Optional[torch.Tensor]:
39
+ if attention_mask is None or int(bag_size) <= 1:
40
+ return attention_mask
41
+ bag_size = int(bag_size)
42
+ if attention_mask.size(1) % bag_size != 0:
43
+ raise ValueError(
44
+ "token superposition requires attention_mask length divisible by "
45
+ f"bag_size; got seq_len={attention_mask.size(1)}, "
46
+ f"bag_size={bag_size}"
47
+ )
48
+ batch, seq_len = attention_mask.shape
49
+ folded = attention_mask.reshape(batch, seq_len // bag_size, bag_size)
50
+ return folded.any(dim=2).to(attention_mask.dtype)
51
+
52
+
53
+ def _autocast_state(device_type: str) -> Tuple[bool, torch.dtype | None]:
54
+ try:
55
+ enabled = torch.is_autocast_enabled(device_type)
56
+ except TypeError:
57
+ enabled = torch.is_autocast_enabled()
58
+ if not enabled:
59
+ return False, None
60
+ try:
61
+ return True, torch.get_autocast_dtype(device_type)
62
+ except AttributeError:
63
+ if device_type == "cuda":
64
+ return True, torch.get_autocast_gpu_dtype()
65
+ if device_type == "cpu":
66
+ return True, torch.get_autocast_cpu_dtype()
67
+ return True, None
68
+
69
+
70
+ def _chunk_logits(
71
+ hidden: torch.Tensor,
72
+ weight: torch.Tensor,
73
+ autocast_enabled: bool,
74
+ autocast_dtype: torch.dtype | None,
75
+ ) -> torch.Tensor:
76
+ device_type = hidden.device.type
77
+ if autocast_dtype is not None:
78
+ with torch.amp.autocast(
79
+ device_type=device_type,
80
+ enabled=autocast_enabled,
81
+ dtype=autocast_dtype,
82
+ ):
83
+ return hidden @ weight.t()
84
+ if autocast_enabled:
85
+ with torch.amp.autocast(device_type=device_type, enabled=True):
86
+ return hidden @ weight.t()
87
+ if hidden.dtype != weight.dtype:
88
+ return hidden @ weight.to(hidden.dtype).t()
89
+ return hidden @ weight.t()
90
+
91
+
92
+ def _build_padded_targets(
93
+ labels: torch.Tensor,
94
+ seq_len: int,
95
+ loss_t: int,
96
+ ignore_index: int,
97
+ ) -> torch.Tensor:
98
+ """Pack labels into a ``(B*seq_len,)`` target tensor aligned with a
99
+ ``hidden.reshape(B*seq_len, D)`` view.
100
+
101
+ The last token per sequence (``t == seq_len - 1``) gets ``ignore_index``
102
+ so a flat view of the full ``(B, T, D)`` activation can be passed to the
103
+ kernels directly — no slice-and-reshape copy required.
104
+ """
105
+ batch = labels.size(0)
106
+ targets = labels.new_full((batch, seq_len), ignore_index)
107
+ targets[:, :loss_t] = labels[:, 1: 1 + loss_t]
108
+ return targets.reshape(-1)
109
+
110
+
111
+ def _build_tst_padded_targets(
112
+ labels: torch.Tensor,
113
+ seq_len: int,
114
+ bag_size: int,
115
+ ignore_index: int,
116
+ ) -> torch.Tensor:
117
+ """Pack next-bag TST labels into ``(B*seq_len, bag_size)`` targets.
118
+
119
+ Row ``(b, t)`` in the flattened hidden tensor predicts every token in
120
+ source bag ``t + 1``. The final latent row has no next bag, so every target
121
+ there is padded with ``ignore_index``.
122
+ """
123
+ batch = labels.size(0)
124
+ target_bags = labels.reshape(batch, seq_len, bag_size)
125
+ targets = labels.new_full((batch, seq_len, bag_size), ignore_index)
126
+ if seq_len > 1:
127
+ targets[:, : seq_len - 1, :] = target_bags[:, 1:, :]
128
+ return targets.reshape(-1, bag_size)
129
+
130
+
131
+ def _validate_tst_hidden_labels(
132
+ hidden: torch.Tensor,
133
+ labels: torch.Tensor,
134
+ bag_size: int,
135
+ ) -> None:
136
+ if labels.size(0) != hidden.size(0):
137
+ raise ValueError(
138
+ "token superposition hidden/labels batch mismatch; "
139
+ f"got hidden_batch={hidden.size(0)}, labels_batch={labels.size(0)}"
140
+ )
141
+ if labels.size(1) % bag_size != 0:
142
+ raise ValueError(
143
+ "token superposition requires label length divisible by bag_size; "
144
+ f"got seq_len={labels.size(1)}, bag_size={bag_size}"
145
+ )
146
+ latent_len = labels.size(1) // bag_size
147
+ if hidden.size(1) != latent_len:
148
+ raise ValueError(
149
+ "token superposition hidden/labels length mismatch; "
150
+ f"got hidden_len={hidden.size(1)}, label_bags={latent_len}"
151
+ )
152
+
153
+
154
+ @torch.library.custom_op(
155
+ "logos::chunked_linear_cross_entropy", mutates_args=(),
156
+ )
157
+ def _chunked_lce_op(
158
+ hidden: torch.Tensor,
159
+ weight: torch.Tensor,
160
+ labels: torch.Tensor,
161
+ chunk_size: int,
162
+ ignore_index: int,
163
+ autocast_enabled: bool,
164
+ autocast_dtype: Optional[torch.dtype],
165
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
166
+ seq_len = hidden.size(1)
167
+ loss_t = seq_len - 1
168
+ # Pad targets at the (T-1) position per batch so a flat view of the full
169
+ # (B, T, D) activation aligns with target indices. Avoids the
170
+ # hidden[:, :loss_t, :].reshape(...) copy the previous layout required.
171
+ targets = _build_padded_targets(labels, seq_len, loss_t, ignore_index)
172
+ if hidden.is_contiguous():
173
+ hidden_flat = hidden.reshape(-1, hidden.size(-1))
174
+ else:
175
+ hidden_flat = hidden.contiguous().reshape(-1, hidden.size(-1))
176
+ chunk_size = max(1, int(chunk_size))
177
+
178
+ loss_sum = torch.zeros((), device=hidden.device, dtype=torch.float32)
179
+ count = (targets != ignore_index).sum().to(torch.float32)
180
+ for start in range(0, hidden_flat.size(0), chunk_size):
181
+ end = min(start + chunk_size, hidden_flat.size(0))
182
+ target_chunk = targets[start:end]
183
+ valid = target_chunk != ignore_index
184
+ safe_target = target_chunk.clamp_min(0)
185
+ logits = _chunk_logits(
186
+ hidden_flat[start:end],
187
+ weight,
188
+ autocast_enabled,
189
+ autocast_dtype,
190
+ ).float()
191
+ log_z = torch.logsumexp(logits, dim=-1)
192
+ target_logits = logits.gather(1, safe_target[:, None]).squeeze(1)
193
+ loss_sum = loss_sum + ((log_z - target_logits) * valid).sum()
194
+
195
+ loss = loss_sum / count.clamp_min(1.0)
196
+ empty_lse = hidden.new_empty((0,), dtype=torch.float32)
197
+ return loss, empty_lse, count
198
+
199
+
200
+ @_chunked_lce_op.register_fake
201
+ def _chunked_lce_fake(
202
+ hidden: torch.Tensor,
203
+ weight: torch.Tensor,
204
+ labels: torch.Tensor,
205
+ chunk_size: int,
206
+ ignore_index: int,
207
+ autocast_enabled: bool,
208
+ autocast_dtype: Optional[torch.dtype],
209
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
210
+ n_rows = hidden.size(0) * hidden.size(1)
211
+ return (
212
+ hidden.new_empty((), dtype=torch.float32),
213
+ hidden.new_empty((n_rows,), dtype=torch.float32),
214
+ hidden.new_empty((), dtype=torch.float32),
215
+ )
216
+
217
+
218
+ def _chunked_lce_setup_context(ctx, inputs, output):
219
+ (
220
+ hidden,
221
+ weight,
222
+ labels,
223
+ chunk_size,
224
+ ignore_index,
225
+ autocast_enabled,
226
+ autocast_dtype,
227
+ ) = inputs
228
+ _loss, lse, count = output
229
+ ctx.save_for_backward(hidden, weight, labels, lse, count)
230
+ ctx.chunk_size = max(1, int(chunk_size))
231
+ ctx.ignore_index = int(ignore_index)
232
+ ctx.autocast_enabled = bool(autocast_enabled)
233
+ ctx.autocast_dtype = autocast_dtype
234
+
235
+
236
+ def _chunked_lce_backward(
237
+ ctx,
238
+ grad_output: torch.Tensor,
239
+ _grad_lse: Optional[torch.Tensor] = None,
240
+ _grad_count: Optional[torch.Tensor] = None,
241
+ ):
242
+ hidden, weight, labels, _lse, count = ctx.saved_tensors
243
+ chunk_size = ctx.chunk_size
244
+ ignore_index = ctx.ignore_index
245
+
246
+ seq_len = hidden.size(1)
247
+ loss_t = seq_len - 1
248
+ d_model = hidden.size(-1)
249
+ targets = _build_padded_targets(
250
+ labels, seq_len, loss_t, ignore_index,
251
+ )
252
+ if hidden.is_contiguous():
253
+ hidden_flat = hidden.reshape(-1, d_model)
254
+ else:
255
+ hidden_flat = hidden.contiguous().reshape(-1, d_model)
256
+ # ``count`` was already produced by the forward and saved in ctx; the
257
+ # previous code retrieved it as ``_count`` and recomputed
258
+ # ``(targets != ignore_index).sum()`` — a full GPU reduction that
259
+ # forced a host sync on every backward call.
260
+
261
+ grad_hidden = None
262
+ grad_hidden_flat = None
263
+ if ctx.needs_input_grad[0]:
264
+ grad_hidden_contig = torch.zeros(
265
+ hidden.size(0), hidden.size(1), d_model,
266
+ device=hidden.device,
267
+ dtype=hidden.dtype,
268
+ )
269
+ grad_hidden_flat = grad_hidden_contig.reshape(-1, d_model)
270
+ grad_weight = None
271
+ if ctx.needs_input_grad[1]:
272
+ grad_weight = torch.zeros_like(weight)
273
+
274
+ scale = (grad_output.float() / count.clamp_min(1.0)).to(torch.float32)
275
+ weight_f = weight.float()
276
+ for start in range(0, hidden_flat.size(0), chunk_size):
277
+ end = min(start + chunk_size, hidden_flat.size(0))
278
+ h_chunk = hidden_flat[start:end]
279
+ target_chunk = targets[start:end]
280
+ valid = target_chunk != ignore_index
281
+ valid_f = valid.to(torch.float32)
282
+ safe_target = target_chunk.clamp_min(0)
283
+
284
+ logits = _chunk_logits(
285
+ h_chunk,
286
+ weight,
287
+ ctx.autocast_enabled,
288
+ ctx.autocast_dtype,
289
+ ).float()
290
+ d_logits = torch.softmax(logits, dim=-1)
291
+ d_logits = d_logits * valid_f[:, None]
292
+ rows = torch.arange(
293
+ end - start, device=hidden.device, dtype=torch.long,
294
+ )
295
+ d_logits[rows, safe_target] -= valid_f
296
+ d_logits = d_logits * scale
297
+
298
+ if grad_hidden_flat is not None:
299
+ grad_hidden_flat[start:end] = (
300
+ d_logits @ weight_f
301
+ ).to(hidden.dtype)
302
+ if grad_weight is not None:
303
+ grad_weight = grad_weight + (
304
+ d_logits.t() @ h_chunk.float()
305
+ ).to(grad_weight.dtype)
306
+
307
+ grad_hidden = grad_hidden_contig.as_strided(
308
+ hidden.size(), hidden.stride(), hidden.storage_offset()
309
+ ) if hidden.is_contiguous() else grad_hidden_contig
310
+
311
+ return grad_hidden, grad_weight, None, None, None, None, None
312
+
313
+
314
+ _chunked_lce_op.register_autograd(
315
+ _chunked_lce_backward,
316
+ setup_context=_chunked_lce_setup_context,
317
+ )
318
+
319
+
320
+ def chunked_linear_cross_entropy(
321
+ hidden: torch.Tensor,
322
+ weight: torch.Tensor,
323
+ labels: torch.Tensor,
324
+ *,
325
+ chunk_size: int = 1024,
326
+ ignore_index: int = -100,
327
+ ) -> torch.Tensor:
328
+ """Compute tied LM-head CE without materializing all logits at once.
329
+
330
+ ``hidden[..., i, :]`` predicts ``labels[..., i + 1]``. The final hidden
331
+ position is dropped to match standard next-token CE semantics. Backward
332
+ recomputes one logits chunk at a time, trading extra matmuls for much
333
+ lower peak activation memory.
334
+ """
335
+ if hidden.size(1) < 2:
336
+ return hidden.new_zeros((), dtype=torch.float32)
337
+ autocast_enabled, autocast_dtype = _autocast_state(hidden.device.type)
338
+ loss, _lse, _count = _chunked_lce_op(
339
+ hidden,
340
+ weight,
341
+ labels,
342
+ int(chunk_size),
343
+ int(ignore_index),
344
+ bool(autocast_enabled),
345
+ autocast_dtype,
346
+ )
347
+ return loss
348
+
349
+
350
+ @torch.library.custom_op(
351
+ "logos::chunked_token_superposition_cross_entropy", mutates_args=(),
352
+ )
353
+ def _chunked_tst_lce_op(
354
+ hidden: torch.Tensor,
355
+ weight: torch.Tensor,
356
+ labels: torch.Tensor,
357
+ bag_size: int,
358
+ chunk_size: int,
359
+ ignore_index: int,
360
+ autocast_enabled: bool,
361
+ autocast_dtype: Optional[torch.dtype],
362
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
363
+ bag_size = max(1, int(bag_size))
364
+ _validate_tst_hidden_labels(hidden, labels, bag_size)
365
+ seq_len = hidden.size(1)
366
+ targets = _build_tst_padded_targets(
367
+ labels, seq_len, bag_size, int(ignore_index),
368
+ )
369
+ if hidden.is_contiguous():
370
+ hidden_flat = hidden.reshape(-1, hidden.size(-1))
371
+ else:
372
+ hidden_flat = hidden.contiguous().reshape(-1, hidden.size(-1))
373
+ chunk_size = max(1, int(chunk_size))
374
+
375
+ loss_sum = torch.zeros((), device=hidden.device, dtype=torch.float32)
376
+ valid_targets = targets != int(ignore_index)
377
+ count = valid_targets.sum().to(torch.float32)
378
+ for start in range(0, hidden_flat.size(0), chunk_size):
379
+ end = min(start + chunk_size, hidden_flat.size(0))
380
+ target_chunk = targets[start:end]
381
+ valid = valid_targets[start:end]
382
+ safe_target = target_chunk.clamp_min(0)
383
+ logits = _chunk_logits(
384
+ hidden_flat[start:end],
385
+ weight,
386
+ autocast_enabled,
387
+ autocast_dtype,
388
+ ).float()
389
+ log_z = torch.logsumexp(logits, dim=-1)
390
+ target_logits = logits.gather(1, safe_target)
391
+ loss_sum = loss_sum + ((log_z[:, None] - target_logits) * valid).sum()
392
+
393
+ loss = loss_sum / count.clamp_min(1.0)
394
+ empty_lse = hidden.new_empty((0,), dtype=torch.float32)
395
+ return loss, empty_lse, count
396
+
397
+
398
+ @_chunked_tst_lce_op.register_fake
399
+ def _chunked_tst_lce_fake(
400
+ hidden: torch.Tensor,
401
+ weight: torch.Tensor,
402
+ labels: torch.Tensor,
403
+ bag_size: int,
404
+ chunk_size: int,
405
+ ignore_index: int,
406
+ autocast_enabled: bool,
407
+ autocast_dtype: Optional[torch.dtype],
408
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
409
+ n_rows = hidden.size(0) * hidden.size(1)
410
+ return (
411
+ hidden.new_empty((), dtype=torch.float32),
412
+ hidden.new_empty((n_rows,), dtype=torch.float32),
413
+ hidden.new_empty((), dtype=torch.float32),
414
+ )
415
+
416
+
417
+ def _chunked_tst_lce_setup_context(ctx, inputs, output):
418
+ (
419
+ hidden,
420
+ weight,
421
+ labels,
422
+ bag_size,
423
+ chunk_size,
424
+ ignore_index,
425
+ autocast_enabled,
426
+ autocast_dtype,
427
+ ) = inputs
428
+ _loss, lse, count = output
429
+ ctx.save_for_backward(hidden, weight, labels, lse, count)
430
+ ctx.bag_size = max(1, int(bag_size))
431
+ ctx.chunk_size = max(1, int(chunk_size))
432
+ ctx.ignore_index = int(ignore_index)
433
+ ctx.autocast_enabled = bool(autocast_enabled)
434
+ ctx.autocast_dtype = autocast_dtype
435
+
436
+
437
+ def _chunked_tst_lce_backward(
438
+ ctx,
439
+ grad_output: torch.Tensor,
440
+ _grad_lse: Optional[torch.Tensor] = None,
441
+ _grad_count: Optional[torch.Tensor] = None,
442
+ ):
443
+ hidden, weight, labels, _lse, count = ctx.saved_tensors
444
+ bag_size = ctx.bag_size
445
+ chunk_size = ctx.chunk_size
446
+ ignore_index = ctx.ignore_index
447
+
448
+ seq_len = hidden.size(1)
449
+ d_model = hidden.size(-1)
450
+ targets = _build_tst_padded_targets(labels, seq_len, bag_size, ignore_index)
451
+ if hidden.is_contiguous():
452
+ hidden_flat = hidden.reshape(-1, d_model)
453
+ else:
454
+ hidden_flat = hidden.contiguous().reshape(-1, d_model)
455
+
456
+ grad_hidden = None
457
+ grad_hidden_flat = None
458
+ if ctx.needs_input_grad[0]:
459
+ grad_hidden = torch.zeros(
460
+ hidden.size(0), hidden.size(1), d_model,
461
+ device=hidden.device,
462
+ dtype=hidden.dtype,
463
+ )
464
+ grad_hidden_flat = grad_hidden.reshape(-1, d_model)
465
+ grad_weight = torch.zeros_like(weight) if ctx.needs_input_grad[1] else None
466
+
467
+ scale = (grad_output.float() / count.clamp_min(1.0)).to(torch.float32)
468
+ weight_f = weight.float()
469
+ for start in range(0, hidden_flat.size(0), chunk_size):
470
+ end = min(start + chunk_size, hidden_flat.size(0))
471
+ h_chunk = hidden_flat[start:end]
472
+ target_chunk = targets[start:end]
473
+ valid = target_chunk != ignore_index
474
+ valid_f = valid.to(torch.float32)
475
+ safe_target = target_chunk.clamp_min(0)
476
+
477
+ logits = _chunk_logits(
478
+ h_chunk,
479
+ weight,
480
+ ctx.autocast_enabled,
481
+ ctx.autocast_dtype,
482
+ ).float()
483
+ d_logits = torch.softmax(logits, dim=-1)
484
+ d_logits = d_logits * valid_f.sum(dim=1, keepdim=True)
485
+ d_logits.scatter_add_(1, safe_target, -valid_f)
486
+ d_logits = d_logits * scale
487
+
488
+ if grad_hidden_flat is not None:
489
+ grad_hidden_flat[start:end] = (
490
+ d_logits @ weight_f
491
+ ).to(hidden.dtype)
492
+ if grad_weight is not None:
493
+ grad_weight = grad_weight + (
494
+ d_logits.t() @ h_chunk.float()
495
+ ).to(grad_weight.dtype)
496
+
497
+ return grad_hidden, grad_weight, None, None, None, None, None, None
498
+
499
+
500
+ _chunked_tst_lce_op.register_autograd(
501
+ _chunked_tst_lce_backward,
502
+ setup_context=_chunked_tst_lce_setup_context,
503
+ )
504
+
505
+
506
+ def chunked_token_superposition_cross_entropy(
507
+ hidden: torch.Tensor,
508
+ weight: torch.Tensor,
509
+ labels: torch.Tensor,
510
+ bag_size: int,
511
+ *,
512
+ chunk_size: int = 1024,
513
+ ignore_index: int = -100,
514
+ ) -> torch.Tensor:
515
+ """Compute TST next-bag CE without materializing ``[B, T, V]`` logits."""
516
+ bag_size = int(bag_size)
517
+ if bag_size <= 1:
518
+ return chunked_linear_cross_entropy(
519
+ hidden, weight, labels,
520
+ chunk_size=chunk_size,
521
+ ignore_index=ignore_index,
522
+ )
523
+ _validate_tst_hidden_labels(hidden, labels, bag_size)
524
+ if hidden.size(1) < 2:
525
+ return hidden.new_zeros((), dtype=torch.float32)
526
+ autocast_enabled, autocast_dtype = _autocast_state(hidden.device.type)
527
+ loss, _lse, _count = _chunked_tst_lce_op(
528
+ hidden,
529
+ weight,
530
+ labels,
531
+ bag_size,
532
+ int(chunk_size),
533
+ int(ignore_index),
534
+ bool(autocast_enabled),
535
+ autocast_dtype,
536
+ )
537
+ return loss
538
+
539
+
540
+ def standard_lm_cross_entropy(
541
+ logits: torch.Tensor,
542
+ labels: torch.Tensor,
543
+ *,
544
+ ignore_index: int = -100,
545
+ ) -> torch.Tensor:
546
+ # Branchless: tensor-value `if .any()` would force a graph break under
547
+ # torch.compile. With reduction='sum' / clamped count we get 0 when
548
+ # everything is masked, matching the old all-ignored fallback.
549
+ shift_logits = logits[..., :-1, :]
550
+ shift_labels = labels[..., 1:]
551
+ flat_labels = shift_labels.reshape(-1)
552
+ loss_sum = F.cross_entropy(
553
+ shift_logits.reshape(-1, shift_logits.size(-1)),
554
+ flat_labels,
555
+ ignore_index=ignore_index,
556
+ reduction="sum",
557
+ )
558
+ count = (flat_labels != ignore_index).sum().clamp_min(1)
559
+ return loss_sum / count
560
+
561
+
562
+ def token_superposition_cross_entropy(
563
+ logits: torch.Tensor,
564
+ labels: torch.Tensor,
565
+ bag_size: int,
566
+ *,
567
+ ignore_index: int = -100,
568
+ ) -> torch.Tensor:
569
+ """Mean CE over the next bag of ``bag_size`` targets per latent step.
570
+
571
+ ``logits[:, k]`` predicts every token in source bag ``k + 1``. The loss is
572
+ equivalent to a multi-hot target distribution with probability mass
573
+ ``1 / bag_size`` assigned to each token in that next bag, implemented as a
574
+ sum of ordinary cross-entropies so existing CE kernels are reused.
575
+ """
576
+ bag_size = int(bag_size)
577
+ if bag_size <= 1:
578
+ return standard_lm_cross_entropy(
579
+ logits, labels, ignore_index=ignore_index,
580
+ )
581
+ if labels.size(1) % bag_size != 0:
582
+ raise ValueError(
583
+ "token superposition requires label length divisible by bag_size; "
584
+ f"got seq_len={labels.size(1)}, bag_size={bag_size}"
585
+ )
586
+ latent_len = labels.size(1) // bag_size
587
+ if logits.size(1) != latent_len:
588
+ raise ValueError(
589
+ "token superposition logits/labels length mismatch; "
590
+ f"got logits_len={logits.size(1)}, label_bags={latent_len}"
591
+ )
592
+ if latent_len < 2:
593
+ return logits.new_zeros((), dtype=torch.float32)
594
+
595
+ target_bags = labels.reshape(labels.size(0), latent_len, bag_size)[:, 1:, :]
596
+ flat_logits = logits[:, :-1, :].reshape(-1, logits.size(-1))
597
+
598
+ loss_sum = None
599
+ count = torch.zeros((), device=labels.device, dtype=torch.long)
600
+ for offset in range(bag_size):
601
+ flat_targets = target_bags[:, :, offset].reshape(-1)
602
+ part = F.cross_entropy(
603
+ flat_logits,
604
+ flat_targets,
605
+ ignore_index=ignore_index,
606
+ reduction="sum",
607
+ )
608
+ loss_sum = part if loss_sum is None else loss_sum + part
609
+ count = count + (flat_targets != ignore_index).sum()
610
+
611
+ assert loss_sum is not None
612
+ return loss_sum / count.clamp_min(1)
613
+
614
+
615
+ def lm_cross_entropy_from_logits(
616
+ logits: torch.Tensor,
617
+ labels: torch.Tensor,
618
+ *,
619
+ token_superposition_bag_size: int = 1,
620
+ ignore_index: int = -100,
621
+ ) -> torch.Tensor:
622
+ if int(token_superposition_bag_size) > 1:
623
+ return token_superposition_cross_entropy(
624
+ logits,
625
+ labels,
626
+ int(token_superposition_bag_size),
627
+ ignore_index=ignore_index,
628
+ )
629
+ return standard_lm_cross_entropy(logits, labels, ignore_index=ignore_index)