muscriptor 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- muscriptor/__init__.py +7 -0
- muscriptor/__main__.py +3 -0
- muscriptor/events.py +208 -0
- muscriptor/main.py +327 -0
- muscriptor/models/__init__.py +0 -0
- muscriptor/models/lm.py +514 -0
- muscriptor/modules/__init__.py +0 -0
- muscriptor/modules/conditioners.py +321 -0
- muscriptor/modules/mel_spectrogram.py +106 -0
- muscriptor/modules/streaming.py +68 -0
- muscriptor/modules/transformer.py +202 -0
- muscriptor/server.py +267 -0
- muscriptor/tokenizer/__init__.py +3 -0
- muscriptor/tokenizer/mt3.py +231 -0
- muscriptor/tokenizer/notes.py +344 -0
- muscriptor/transcription_model.py +629 -0
- muscriptor/utils/__init__.py +0 -0
- muscriptor/utils/audio.py +112 -0
- muscriptor/utils/auralization.py +145 -0
- muscriptor/utils/download.py +68 -0
- muscriptor/utils/midi.py +25 -0
- muscriptor/utils/resample.py +191 -0
- muscriptor/utils/sampling.py +89 -0
- muscriptor-0.1.0.dist-info/METADATA +336 -0
- muscriptor-0.1.0.dist-info/RECORD +28 -0
- muscriptor-0.1.0.dist-info/WHEEL +4 -0
- muscriptor-0.1.0.dist-info/entry_points.txt +2 -0
- muscriptor-0.1.0.dist-info/licenses/LICENSE +21 -0
muscriptor/models/lm.py
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"""Language model for MIDI token generation.
|
|
2
|
+
|
|
3
|
+
Adapted from audiocraft/models/lm.py.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
from torch import nn
|
|
12
|
+
|
|
13
|
+
from muscriptor.modules.conditioners import (
|
|
14
|
+
ConditioningProvider,
|
|
15
|
+
ConditioningAttributes,
|
|
16
|
+
ConditionType,
|
|
17
|
+
nullify_all_conditions,
|
|
18
|
+
)
|
|
19
|
+
from muscriptor.modules.streaming import (
|
|
20
|
+
ModelState,
|
|
21
|
+
increment_steps,
|
|
22
|
+
init_states,
|
|
23
|
+
)
|
|
24
|
+
from muscriptor.modules.transformer import StreamingTransformer
|
|
25
|
+
import muscriptor.utils.sampling as utils
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
ConditionTensors = dict[str, ConditionType]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# ScaledEmbedding (used for token embeddings, keeps weight compatible with ckpt)
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ScaledEmbedding(nn.Embedding):
|
|
38
|
+
"""Embedding that maps zero_idx (a negative index) to a zero vector."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, *args, zero_idx: int = -1, **kwargs):
|
|
41
|
+
super().__init__(*args, **kwargs)
|
|
42
|
+
assert zero_idx < 0
|
|
43
|
+
self.zero_idx = zero_idx
|
|
44
|
+
|
|
45
|
+
def forward(self, input, *args, **kwargs):
|
|
46
|
+
is_zero = input == self.zero_idx
|
|
47
|
+
input = input.clamp(min=0)
|
|
48
|
+
y = super().forward(input, *args, **kwargs)
|
|
49
|
+
return torch.where(is_zero[..., None], torch.zeros_like(y), y)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# TorchAutocast
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TorchAutocast:
|
|
58
|
+
"""Minimal autocast context manager (matches the audiocraft interface)."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
enabled: bool = False,
|
|
63
|
+
device_type: str = "cuda",
|
|
64
|
+
dtype: torch.dtype | None = None,
|
|
65
|
+
):
|
|
66
|
+
self.enabled = enabled
|
|
67
|
+
self.device_type = device_type
|
|
68
|
+
self.dtype = dtype
|
|
69
|
+
self._ctx = None
|
|
70
|
+
|
|
71
|
+
def __enter__(self):
|
|
72
|
+
if self.enabled:
|
|
73
|
+
self._ctx = torch.autocast(device_type=self.device_type, dtype=self.dtype)
|
|
74
|
+
self._ctx.__enter__()
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def __exit__(self, *args):
|
|
78
|
+
if self.enabled and self._ctx is not None:
|
|
79
|
+
self._ctx.__exit__(*args)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# LMModel
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class LMModel(nn.Module):
|
|
88
|
+
"""Causal transformer LM for MIDI token generation.
|
|
89
|
+
|
|
90
|
+
Single-stream
|
|
91
|
+
Supports classifier-free guidance at inference time.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
condition_provider: ConditioningProvider,
|
|
97
|
+
card: int = 1024,
|
|
98
|
+
dim: int = 128,
|
|
99
|
+
num_heads: int = 8,
|
|
100
|
+
hidden_scale: int = 4,
|
|
101
|
+
cfg_coef: float = 1.0,
|
|
102
|
+
autocast: TorchAutocast | None = None,
|
|
103
|
+
device=None,
|
|
104
|
+
dtype=None,
|
|
105
|
+
**kwargs,
|
|
106
|
+
):
|
|
107
|
+
super().__init__()
|
|
108
|
+
self.condition_provider = condition_provider
|
|
109
|
+
self.card = card
|
|
110
|
+
self.dim = dim
|
|
111
|
+
self.cfg_coef = cfg_coef
|
|
112
|
+
self.autocast = (
|
|
113
|
+
autocast if autocast is not None else TorchAutocast(enabled=True)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
self.emb = ScaledEmbedding(
|
|
117
|
+
self.card + 1,
|
|
118
|
+
dim,
|
|
119
|
+
device=device,
|
|
120
|
+
dtype=dtype,
|
|
121
|
+
zero_idx=self.zero_token_id,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
self.transformer = StreamingTransformer(
|
|
125
|
+
d_model=dim,
|
|
126
|
+
num_heads=num_heads,
|
|
127
|
+
dim_feedforward=int(hidden_scale * dim),
|
|
128
|
+
device=device,
|
|
129
|
+
dtype=dtype,
|
|
130
|
+
**kwargs,
|
|
131
|
+
)
|
|
132
|
+
self.out_norm = nn.LayerNorm(dim, eps=1e-5)
|
|
133
|
+
self.linear = nn.Linear(dim, card, bias=False)
|
|
134
|
+
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
# Token ID properties
|
|
137
|
+
# ------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def initial_token_id(self) -> int:
|
|
141
|
+
return self.card
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def zero_token_id(self) -> int:
|
|
145
|
+
return -1
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def ungenerated_token_id(self) -> int:
|
|
149
|
+
return -2
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
# Forward
|
|
153
|
+
# ------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
def forward(
|
|
156
|
+
self,
|
|
157
|
+
sequence: torch.Tensor, # [B, S]
|
|
158
|
+
condition_tensors: ConditionTensors,
|
|
159
|
+
first_step: bool = False,
|
|
160
|
+
model_state: ModelState | None = None,
|
|
161
|
+
) -> torch.Tensor: # [B, S, card]
|
|
162
|
+
B, S = sequence.shape
|
|
163
|
+
|
|
164
|
+
input_ = self.emb(sequence) # [B, S, D]
|
|
165
|
+
|
|
166
|
+
prepend_length = 0
|
|
167
|
+
if first_step:
|
|
168
|
+
for cond, _ in condition_tensors.values():
|
|
169
|
+
input_ = torch.cat([cond, input_], dim=1)
|
|
170
|
+
prepend_length = input_.shape[1] - S
|
|
171
|
+
|
|
172
|
+
transformer_out = self.transformer(
|
|
173
|
+
input_,
|
|
174
|
+
prepend_length=prepend_length,
|
|
175
|
+
model_state=model_state,
|
|
176
|
+
)
|
|
177
|
+
if self.out_norm:
|
|
178
|
+
transformer_out = self.out_norm(transformer_out)
|
|
179
|
+
|
|
180
|
+
# Remove prepended conditioning tokens
|
|
181
|
+
if prepend_length > 0:
|
|
182
|
+
transformer_out = transformer_out[:, -S:]
|
|
183
|
+
|
|
184
|
+
logits = self.linear(transformer_out)
|
|
185
|
+
return logits # [B, S, card]
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
# Sampling helpers
|
|
189
|
+
# ------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def _compute_logits(
|
|
192
|
+
self,
|
|
193
|
+
sequence: torch.Tensor,
|
|
194
|
+
cfg_conditions: ConditionTensors,
|
|
195
|
+
model_state: ModelState,
|
|
196
|
+
first_step: bool,
|
|
197
|
+
cfg_coef: float | None = None,
|
|
198
|
+
) -> torch.Tensor: # [B, card]
|
|
199
|
+
"""Run the forward pass and return masked logits at the last timestep."""
|
|
200
|
+
B = sequence.shape[0]
|
|
201
|
+
cfg_coef = self.cfg_coef if cfg_coef is None else cfg_coef
|
|
202
|
+
|
|
203
|
+
if cfg_coef == 1.0:
|
|
204
|
+
logits = self(
|
|
205
|
+
sequence,
|
|
206
|
+
cfg_conditions,
|
|
207
|
+
first_step=first_step,
|
|
208
|
+
model_state=model_state,
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
doubled = torch.cat([sequence, sequence], dim=0)
|
|
212
|
+
all_logits = self(
|
|
213
|
+
doubled,
|
|
214
|
+
cfg_conditions,
|
|
215
|
+
first_step=first_step,
|
|
216
|
+
model_state=model_state,
|
|
217
|
+
)
|
|
218
|
+
cond_logits, uncond_logits = all_logits.split(B, dim=0)
|
|
219
|
+
logits = uncond_logits + (cond_logits - uncond_logits) * cfg_coef
|
|
220
|
+
|
|
221
|
+
logits = logits[:, -1, :].float() # [B, card] — last timestep
|
|
222
|
+
logits[:, 1393:] = -torch.inf # mask reserved / OOV tokens
|
|
223
|
+
return logits
|
|
224
|
+
|
|
225
|
+
def _sample_next_token(
|
|
226
|
+
self,
|
|
227
|
+
sequence: torch.Tensor,
|
|
228
|
+
cfg_conditions: ConditionTensors,
|
|
229
|
+
model_state: ModelState,
|
|
230
|
+
first_step: bool,
|
|
231
|
+
use_sampling: bool = False,
|
|
232
|
+
temp: float = 1.0,
|
|
233
|
+
top_k: int = 0,
|
|
234
|
+
top_p: float = 0.0,
|
|
235
|
+
cfg_coef: float | None = None,
|
|
236
|
+
) -> torch.Tensor: # [B]
|
|
237
|
+
logits = self._compute_logits(
|
|
238
|
+
sequence, cfg_conditions, model_state, first_step, cfg_coef
|
|
239
|
+
)
|
|
240
|
+
if use_sampling and temp > 0.0:
|
|
241
|
+
probs = torch.softmax(logits / temp, dim=-1)
|
|
242
|
+
next_tokens = utils.sample_from_probs(probs, top_p=top_p, top_k=top_k)[:, 0]
|
|
243
|
+
else:
|
|
244
|
+
next_tokens = torch.argmax(logits, dim=-1) # [B]
|
|
245
|
+
return next_tokens # [B]
|
|
246
|
+
|
|
247
|
+
# ------------------------------------------------------------------
|
|
248
|
+
# Generation
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
@torch.no_grad()
|
|
252
|
+
def generate(
|
|
253
|
+
self,
|
|
254
|
+
prompt: torch.Tensor | None = None,
|
|
255
|
+
conditions: list[ConditioningAttributes] = [],
|
|
256
|
+
num_samples: int | None = None,
|
|
257
|
+
max_gen_len: int = 256,
|
|
258
|
+
use_sampling: bool = True,
|
|
259
|
+
temp: float = 1.0,
|
|
260
|
+
top_k: int = 0,
|
|
261
|
+
top_p: float = 0.0,
|
|
262
|
+
cfg_coef: float | None = None,
|
|
263
|
+
early_stop_on_token: int | None = None,
|
|
264
|
+
beam_size: int = 1,
|
|
265
|
+
beam_length_score_alpha: float = 0.75,
|
|
266
|
+
) -> Iterator[torch.Tensor]:
|
|
267
|
+
"""Autoregressively generate tokens, yielding one timestep at a time.
|
|
268
|
+
|
|
269
|
+
Each yield is a ``[num_samples]`` tensor. For beam_size == 1 (default),
|
|
270
|
+
tokens are yielded as they are generated. For beam_size > 1, beam search
|
|
271
|
+
is run non-streamingly and all tokens are yielded at the end.
|
|
272
|
+
"""
|
|
273
|
+
assert not self.training
|
|
274
|
+
if beam_size > 1:
|
|
275
|
+
assert early_stop_on_token is not None, "beam search requires early_stop_on_token"
|
|
276
|
+
device = self.emb.weight.device
|
|
277
|
+
|
|
278
|
+
if num_samples is None:
|
|
279
|
+
num_samples = (
|
|
280
|
+
len(conditions)
|
|
281
|
+
if conditions
|
|
282
|
+
else (prompt.shape[0] if prompt is not None else 1)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
cfg_coef = self.cfg_coef if cfg_coef is None else cfg_coef
|
|
286
|
+
|
|
287
|
+
# Build condition tensors (with null conditions appended for CFG)
|
|
288
|
+
if conditions:
|
|
289
|
+
if cfg_coef == 1.0:
|
|
290
|
+
prepared = self.condition_provider.tokenize(conditions)
|
|
291
|
+
if torch.cuda.is_available():
|
|
292
|
+
torch.cuda.synchronize()
|
|
293
|
+
_t = time.perf_counter()
|
|
294
|
+
cfg_conditions: ConditionTensors = self.condition_provider(prepared)
|
|
295
|
+
if torch.cuda.is_available():
|
|
296
|
+
torch.cuda.synchronize()
|
|
297
|
+
print(
|
|
298
|
+
f"[muscriptor] encode conditions (total): {time.perf_counter() - _t:.3f}s"
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
null_conditions = nullify_all_conditions(conditions)
|
|
302
|
+
all_conditions = conditions + null_conditions
|
|
303
|
+
prepared = self.condition_provider.tokenize(all_conditions)
|
|
304
|
+
print(
|
|
305
|
+
"[muscriptor] instrument_group tokens:",
|
|
306
|
+
prepared.get("instrument_group"),
|
|
307
|
+
)
|
|
308
|
+
print(
|
|
309
|
+
"[muscriptor] dataset_name tokens: ",
|
|
310
|
+
prepared.get("dataset_name"),
|
|
311
|
+
)
|
|
312
|
+
if torch.cuda.is_available():
|
|
313
|
+
torch.cuda.synchronize()
|
|
314
|
+
_t = time.perf_counter()
|
|
315
|
+
cfg_conditions = self.condition_provider(prepared)
|
|
316
|
+
if torch.cuda.is_available():
|
|
317
|
+
torch.cuda.synchronize()
|
|
318
|
+
print(
|
|
319
|
+
f"[muscriptor] encode conditions (total): {time.perf_counter() - _t:.3f}s"
|
|
320
|
+
)
|
|
321
|
+
else:
|
|
322
|
+
cfg_conditions = {}
|
|
323
|
+
|
|
324
|
+
eff_batch = num_samples * beam_size
|
|
325
|
+
|
|
326
|
+
# Expand conditions so each beam gets its own copy (interleaved for CFG).
|
|
327
|
+
if beam_size > 1 and cfg_conditions:
|
|
328
|
+
cfg_conditions = {
|
|
329
|
+
k: (
|
|
330
|
+
torch.repeat_interleave(cond, beam_size, dim=0),
|
|
331
|
+
torch.repeat_interleave(mask, beam_size, dim=0),
|
|
332
|
+
)
|
|
333
|
+
for k, (cond, mask) in cfg_conditions.items()
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
# Initialise generation buffer (eff_batch rows = num_samples × beam_size)
|
|
337
|
+
ungenerated = self.ungenerated_token_id
|
|
338
|
+
gen_sequence = torch.full(
|
|
339
|
+
(eff_batch, max_gen_len + 1),
|
|
340
|
+
ungenerated,
|
|
341
|
+
device=device,
|
|
342
|
+
dtype=torch.long,
|
|
343
|
+
)
|
|
344
|
+
gen_sequence[:, 0] = self.initial_token_id
|
|
345
|
+
|
|
346
|
+
start_offset = 0
|
|
347
|
+
if prompt is not None:
|
|
348
|
+
PT = prompt.shape[-1]
|
|
349
|
+
if beam_size > 1:
|
|
350
|
+
prompt = torch.repeat_interleave(prompt, beam_size, dim=0)
|
|
351
|
+
gen_sequence[:, 1 : 1 + PT] = prompt
|
|
352
|
+
ungenerated_steps = (gen_sequence == ungenerated).nonzero()[:, 1]
|
|
353
|
+
start_offset = max(0, int(ungenerated_steps.amin()) - 1)
|
|
354
|
+
|
|
355
|
+
prepend_length = sum(cond.shape[1] for cond, _ in cfg_conditions.values())
|
|
356
|
+
cache_batch_size = eff_batch * (1 if cfg_coef == 1.0 else 2)
|
|
357
|
+
cache_seq_len = prepend_length + max_gen_len
|
|
358
|
+
model_state = init_states(
|
|
359
|
+
self, batch_size=cache_batch_size, sequence_length=cache_seq_len
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Accumulated log-prob scores, one per beam row.
|
|
363
|
+
beam_scores = torch.zeros(eff_batch, device=device, dtype=torch.float)
|
|
364
|
+
|
|
365
|
+
# For greedy/sampling emit prompt steps now; beam search emits at the end.
|
|
366
|
+
if beam_size == 1:
|
|
367
|
+
for t in range(start_offset):
|
|
368
|
+
yield gen_sequence[:, t + 1]
|
|
369
|
+
|
|
370
|
+
last_offset = start_offset - 1
|
|
371
|
+
with self.autocast:
|
|
372
|
+
for offset in range(start_offset, max_gen_len):
|
|
373
|
+
last_offset = offset
|
|
374
|
+
first_iter = offset == start_offset
|
|
375
|
+
input_ = (
|
|
376
|
+
gen_sequence[:, : offset + 1]
|
|
377
|
+
if first_iter
|
|
378
|
+
else gen_sequence[:, offset : offset + 1]
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if beam_size == 1:
|
|
382
|
+
# ── Standard greedy / sampling path ──────────────────
|
|
383
|
+
if early_stop_on_token is not None:
|
|
384
|
+
done = (gen_sequence == early_stop_on_token).any(dim=1).all()
|
|
385
|
+
if done:
|
|
386
|
+
break
|
|
387
|
+
|
|
388
|
+
next_token = self._sample_next_token(
|
|
389
|
+
input_,
|
|
390
|
+
cfg_conditions,
|
|
391
|
+
model_state,
|
|
392
|
+
first_step=first_iter,
|
|
393
|
+
use_sampling=use_sampling,
|
|
394
|
+
temp=temp,
|
|
395
|
+
top_k=top_k,
|
|
396
|
+
top_p=top_p,
|
|
397
|
+
cfg_coef=cfg_coef,
|
|
398
|
+
) # [B]
|
|
399
|
+
|
|
400
|
+
input_T = input_.shape[-1]
|
|
401
|
+
increment_steps(
|
|
402
|
+
self.transformer,
|
|
403
|
+
model_state,
|
|
404
|
+
increment=input_T + (prepend_length if first_iter else 0),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
this_gen_step = gen_sequence[:, offset + 1]
|
|
408
|
+
next_token = torch.where(
|
|
409
|
+
this_gen_step == ungenerated, next_token, this_gen_step
|
|
410
|
+
)
|
|
411
|
+
gen_sequence[:, offset + 1] = next_token
|
|
412
|
+
|
|
413
|
+
yield gen_sequence[:, offset + 1] # [num_samples]
|
|
414
|
+
|
|
415
|
+
else:
|
|
416
|
+
# ── Beam search step ──────────────────────────────────
|
|
417
|
+
logits = self._compute_logits(
|
|
418
|
+
input_, cfg_conditions, model_state,
|
|
419
|
+
first_step=first_iter, cfg_coef=cfg_coef,
|
|
420
|
+
) # [eff_batch, card]
|
|
421
|
+
input_T = input_.shape[-1]
|
|
422
|
+
increment_steps(
|
|
423
|
+
self.transformer,
|
|
424
|
+
model_state,
|
|
425
|
+
increment=input_T + (prepend_length if first_iter else 0),
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
log_probs = torch.log_softmax(logits.float(), dim=-1)
|
|
429
|
+
|
|
430
|
+
# Top beam_size candidate tokens per current beam
|
|
431
|
+
topk_scores, topk_tokens = torch.topk(log_probs, k=beam_size, dim=-1)
|
|
432
|
+
|
|
433
|
+
# Track which beams have already emitted EOS
|
|
434
|
+
eos_mask = gen_sequence == early_stop_on_token
|
|
435
|
+
beam_has_ended = eos_mask.any(dim=-1)
|
|
436
|
+
eos_pos = eos_mask.int().argmax(dim=-1).clamp(min=1)
|
|
437
|
+
beam_lengths = torch.where(
|
|
438
|
+
beam_has_ended, eos_pos,
|
|
439
|
+
torch.full_like(eos_pos, offset + 1),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Finished beams: don't expand further
|
|
443
|
+
topk_scores = torch.where(
|
|
444
|
+
beam_has_ended.unsqueeze(-1),
|
|
445
|
+
torch.zeros_like(topk_scores),
|
|
446
|
+
topk_scores,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Length-normalized candidate scores: [eff_batch, beam_size]
|
|
450
|
+
lp = 1.0 / (beam_lengths.float() ** beam_length_score_alpha)
|
|
451
|
+
cand = (beam_scores.unsqueeze(-1) + topk_scores) * lp.unsqueeze(-1)
|
|
452
|
+
|
|
453
|
+
# Reshape to [num_samples, beam_size²] for cross-beam selection
|
|
454
|
+
cand_2d = cand.reshape(num_samples, beam_size * beam_size)
|
|
455
|
+
|
|
456
|
+
if offset == start_offset:
|
|
457
|
+
# All beams identical at start — take first beam_size tokens
|
|
458
|
+
new_scores = cand_2d[:, :beam_size]
|
|
459
|
+
best_idx = (
|
|
460
|
+
torch.arange(beam_size, device=device)
|
|
461
|
+
.unsqueeze(0)
|
|
462
|
+
.expand(num_samples, -1)
|
|
463
|
+
)
|
|
464
|
+
else:
|
|
465
|
+
new_scores, best_idx = torch.topk(cand_2d, k=beam_size, dim=-1)
|
|
466
|
+
|
|
467
|
+
# Decode flat index → (prev_beam_within_sample, token_rank)
|
|
468
|
+
prev_local = (best_idx // beam_size).reshape(-1)
|
|
469
|
+
tok_rank = (best_idx % beam_size).reshape(-1)
|
|
470
|
+
|
|
471
|
+
# Map to global row indices in [eff_batch, …] tensors
|
|
472
|
+
sample_base = (
|
|
473
|
+
torch.arange(num_samples, device=device)
|
|
474
|
+
.repeat_interleave(beam_size) * beam_size
|
|
475
|
+
)
|
|
476
|
+
prev_global = sample_base + prev_local
|
|
477
|
+
|
|
478
|
+
# Token for each new beam
|
|
479
|
+
next_token = topk_tokens[prev_global, tok_rank]
|
|
480
|
+
|
|
481
|
+
# Update beam scores (store un-normalized for the next step)
|
|
482
|
+
beam_scores = new_scores.reshape(-1) / lp[prev_global]
|
|
483
|
+
|
|
484
|
+
# Reorder generation sequences to match winning beams
|
|
485
|
+
gen_sequence = gen_sequence[prev_global]
|
|
486
|
+
|
|
487
|
+
# Reorder KV caches — shape is [2, batch, T, heads, head_dim]
|
|
488
|
+
for state in model_state.values():
|
|
489
|
+
if "cache" in state:
|
|
490
|
+
cache = state["cache"]
|
|
491
|
+
if cache.shape[1] == 2 * eff_batch: # CFG-doubled cache
|
|
492
|
+
reorder = torch.cat([prev_global, prev_global + eff_batch])
|
|
493
|
+
else:
|
|
494
|
+
reorder = prev_global
|
|
495
|
+
state["cache"] = cache[:, reorder, :, :, :]
|
|
496
|
+
|
|
497
|
+
# Write next token (respecting pre-filled prompt positions)
|
|
498
|
+
this_step = gen_sequence[:, offset + 1]
|
|
499
|
+
next_token = torch.where(this_step == ungenerated, next_token, this_step)
|
|
500
|
+
gen_sequence[:, offset + 1] = next_token
|
|
501
|
+
|
|
502
|
+
# Early stop when every beam in every sample has emitted EOS
|
|
503
|
+
if (gen_sequence == early_stop_on_token).any(dim=-1).all():
|
|
504
|
+
break
|
|
505
|
+
|
|
506
|
+
# Beam search: select best beam per sample and yield all tokens at once
|
|
507
|
+
if beam_size > 1:
|
|
508
|
+
best_beam = beam_scores.reshape(num_samples, beam_size).argmax(dim=-1)
|
|
509
|
+
best_global = (
|
|
510
|
+
torch.arange(num_samples, device=device) * beam_size + best_beam
|
|
511
|
+
)
|
|
512
|
+
best_sequence = gen_sequence[best_global] # [num_samples, T]
|
|
513
|
+
for t in range(last_offset + 1):
|
|
514
|
+
yield best_sequence[:, t + 1]
|
|
File without changes
|