muyo commited on
Commit
259909a
·
verified ·
1 Parent(s): 3264b41

Fix generate(): full 2048 canvas + EOSBOS anchor + trim_at_eos for coherent generation

Browse files

generate() now denoises a full canvas_length (default 2048) canvas instead of prompt+max_new_tokens, anchors the <|endoftext|><|beginoftext|> delimiter at prompt+max_new_tokens (max_new_tokens is the content budget), and trims output at the first EOS (trim_at_eos). Full untrimmed canvas returned as out.canvas. New params canvas_length/anchor_eosbos/trim_at_eos all resolve via generation_config.

Files changed (1) hide show
  1. generation_sumi.py +57 -8
generation_sumi.py CHANGED
@@ -50,6 +50,7 @@ class SumiGenerationOutput(ModelOutput):
50
  past_key_values: Cache | None = None
51
  logits: None = None
52
  hidden_states: None = None
 
53
 
54
 
55
  class SumiGenerationConfig(GenerationConfig):
@@ -333,6 +334,32 @@ class SumiGenerationMixin(GenerationMixin):
333
  return resolved
334
  return default
335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  @torch.no_grad()
337
  def generate(
338
  self,
@@ -351,6 +378,9 @@ class SumiGenerationMixin(GenerationMixin):
351
  tokens_per_step: Optional[int] = None,
352
  frozen: Optional[List] = None,
353
  denoise_end: Optional[List[int]] = None,
 
 
 
354
  progress_callback: Optional[Callable] = None,
355
  **kwargs,
356
  ) -> SumiGenerationOutput:
@@ -384,6 +414,9 @@ class SumiGenerationMixin(GenerationMixin):
384
  tokens_per_step = self._resolve(
385
  tokens_per_step, "tokens_per_step", 1, generation_config, self_generation_config
386
  )
 
 
 
387
 
388
  device = self.model.embed_tokens.weight.device
389
  if input_ids is None:
@@ -422,14 +455,29 @@ class SumiGenerationMixin(GenerationMixin):
422
  if max_new_tokens < 0:
423
  raise ValueError("max_new_tokens must be non-negative.")
424
  if max_new_tokens == 0:
425
- return SumiGenerationOutput(sequences=input_ids)
426
-
427
- total_length = prompt_length + max_new_tokens
428
- if total_length > self.config.max_position_embeddings:
 
 
 
 
 
 
 
429
  raise ValueError(
430
- f"Requested sequence length {total_length} exceeds max_position_embeddings "
431
- f"({self.config.max_position_embeddings})."
432
  )
 
 
 
 
 
 
 
 
 
433
 
434
  if attention_mask is None:
435
  attention_mask = torch.ones_like(input_ids)
@@ -449,7 +497,7 @@ class SumiGenerationMixin(GenerationMixin):
449
  generator = torch.Generator()
450
  generator.manual_seed(seed)
451
 
452
- completion_shape = (batch_size, max_new_tokens)
453
  try:
454
  completion_ids = torch.randint(
455
  low=0,
@@ -540,7 +588,8 @@ class SumiGenerationMixin(GenerationMixin):
540
  if was_training:
541
  self.train()
542
 
543
- return SumiGenerationOutput(sequences=generated_ids)
 
544
 
545
 
546
  __all__ = [
 
50
  past_key_values: Cache | None = None
51
  logits: None = None
52
  hidden_states: None = None
53
+ canvas: torch.LongTensor | None = None # full untrimmed denoised canvas
54
 
55
 
56
  class SumiGenerationConfig(GenerationConfig):
 
334
  return resolved
335
  return default
336
 
337
+ def _trim_at_eos(self, generated_ids, prompt_length):
338
+ """Cut each row at the first EOS in its generated region (dropping the EOS, the
339
+ anchored BOS delimiter, and the denoised tail), then right-pad the batch back into
340
+ a rectangular tensor. Prompt tokens are kept. Mirrors the eval harness's
341
+ ``_extract_text`` (`gen_ids[:gen_ids.index(eos)]`). The pad filler is a special
342
+ token, so ``decode(..., skip_special_tokens=True)`` yields clean text."""
343
+ eos_id = self.config.eos_token_id
344
+ if eos_id is None:
345
+ return generated_ids
346
+ pad_id = self.config.pad_token_id
347
+ pad_id = eos_id if pad_id is None else pad_id
348
+ batch_size = generated_ids.shape[0]
349
+ rows, max_len = [], prompt_length
350
+ for i in range(batch_size):
351
+ row = generated_ids[i]
352
+ hit = (row[prompt_length:] == eos_id).nonzero(as_tuple=True)[0]
353
+ cut = prompt_length + int(hit[0].item()) if hit.numel() > 0 else row.shape[0]
354
+ rows.append(row[:cut])
355
+ max_len = max(max_len, cut)
356
+ out = torch.full((batch_size, max(max_len, 1)), pad_id,
357
+ dtype=generated_ids.dtype, device=generated_ids.device)
358
+ for i, row in enumerate(rows):
359
+ if row.shape[0] > 0:
360
+ out[i, : row.shape[0]] = row
361
+ return out
362
+
363
  @torch.no_grad()
364
  def generate(
365
  self,
 
378
  tokens_per_step: Optional[int] = None,
379
  frozen: Optional[List] = None,
380
  denoise_end: Optional[List[int]] = None,
381
+ canvas_length: Optional[int] = None,
382
+ anchor_eosbos: Optional[bool] = None,
383
+ trim_at_eos: Optional[bool] = None,
384
  progress_callback: Optional[Callable] = None,
385
  **kwargs,
386
  ) -> SumiGenerationOutput:
 
414
  tokens_per_step = self._resolve(
415
  tokens_per_step, "tokens_per_step", 1, generation_config, self_generation_config
416
  )
417
+ canvas_length = self._resolve(canvas_length, "canvas_length", 2048, generation_config, self_generation_config)
418
+ anchor_eosbos = self._resolve(anchor_eosbos, "anchor_eosbos", True, generation_config, self_generation_config)
419
+ trim_at_eos = self._resolve(trim_at_eos, "trim_at_eos", True, generation_config, self_generation_config)
420
 
421
  device = self.model.embed_tokens.weight.device
422
  if input_ids is None:
 
455
  if max_new_tokens < 0:
456
  raise ValueError("max_new_tokens must be non-negative.")
457
  if max_new_tokens == 0:
458
+ return SumiGenerationOutput(sequences=input_ids, canvas=input_ids)
459
+
460
+ # The model is trained on a packed, fixed-length canvas, so generation runs on a
461
+ # full `canvas_length` canvas (default 2048) rather than just prompt+max_new_tokens.
462
+ # `max_new_tokens` is the content budget: the EOS,BOS document delimiter is anchored
463
+ # at prompt_length+max_new_tokens, the rest of the canvas is denoised as context, and
464
+ # decoding is cut at the first EOS (trim_at_eos, default True).
465
+ ceiling = self.config.max_position_embeddings
466
+ canvas_length = min(int(canvas_length), ceiling)
467
+ reserve = 2 if anchor_eosbos else 0
468
+ if prompt_length + reserve >= canvas_length:
469
  raise ValueError(
470
+ f"prompt_length ({prompt_length}) leaves no room in canvas_length ({canvas_length})."
 
471
  )
472
+ budget = max(1, min(max_new_tokens, canvas_length - prompt_length - reserve))
473
+ total_length = canvas_length
474
+ completion_length = total_length - prompt_length
475
+
476
+ # Anchor the EOS,BOS delimiter at the end of the content budget. The caller can
477
+ # override by passing an explicit `frozen` set (then it takes precedence).
478
+ if anchor_eosbos and frozen is None:
479
+ eos_pos = prompt_length + budget
480
+ frozen = [(eos_pos, self.config.eos_token_id), (eos_pos + 1, self.config.bos_token_id)]
481
 
482
  if attention_mask is None:
483
  attention_mask = torch.ones_like(input_ids)
 
497
  generator = torch.Generator()
498
  generator.manual_seed(seed)
499
 
500
+ completion_shape = (batch_size, completion_length)
501
  try:
502
  completion_ids = torch.randint(
503
  low=0,
 
588
  if was_training:
589
  self.train()
590
 
591
+ sequences = self._trim_at_eos(generated_ids, prompt_length) if trim_at_eos else generated_ids
592
+ return SumiGenerationOutput(sequences=sequences, canvas=generated_ids)
593
 
594
 
595
  __all__ = [