iamthinbaker commited on
Commit
eb4af6f
·
verified ·
1 Parent(s): baf4bf2

fix: update chars_in_row before computing remaining to prevent 64th pixel per row

Browse files
Files changed (1) hide show
  1. conditioned_gpt2.py +145 -46
conditioned_gpt2.py CHANGED
@@ -12,20 +12,41 @@ NUM_HAS_EVOLUTION = 2
12
  NUM_COLOR_SHIFTS = 6 # 0 = no shift, 1-5 = ColorShift permutations
13
 
14
  _TYPES = [
15
- "normal", "fire", "water", "electric", "grass", "ice",
16
- "fighting", "poison", "ground", "flying", "psychic", "bug",
17
- "rock", "ghost", "dragon", "dark", "steel", "fairy",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  ]
19
- _TYPE1_UNK = 18 # unknown primary type
20
  _TYPE2_NONE = 18 # no secondary type
21
- _TYPE2_UNK = 19 # unknown secondary type
22
 
23
 
24
- def _resolve_type(val: "str | int | None", default_none_idx: int) -> "int | None":
 
 
25
  if val is None:
26
  return None
27
  if isinstance(val, str):
28
- return _TYPES.index(val.lower()) if val.lower() in _TYPES else default_none_idx
 
 
 
 
29
  return int(val)
30
 
31
 
@@ -45,7 +66,9 @@ class ConditionedGPT2(GPT2LMHeadModel):
45
  ):
46
  num_pokemon = num_pokemon or getattr(config, "num_pokemon", None)
47
  if num_pokemon is None:
48
- raise ValueError("num_pokemon must be provided or present in config")
 
 
49
  config.num_pokemon = num_pokemon
50
  super().__init__(config)
51
  self.conditioning = nn.Embedding(num_pokemon, config.n_embd)
@@ -92,7 +115,8 @@ class ConditionedGPT2(GPT2LMHeadModel):
92
  # Store row marker token ids as a buffer so they're saved with the model
93
  _ids = row_marker_token_ids or [0] * 64
94
  self.register_buffer(
95
- "row_marker_ids", torch.tensor(_ids, dtype=torch.long)
 
96
  )
97
 
98
  def _ids_to_row_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
@@ -115,7 +139,9 @@ class ConditionedGPT2(GPT2LMHeadModel):
115
 
116
  in_row = is_assigned.long().cumsum(dim=1) >= 1
117
  return torch.where(
118
- in_row, row_ids_filled, input_ids.new_full((B, T), 64)
 
 
119
  )
120
 
121
  def _ids_to_col_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
@@ -129,7 +155,9 @@ class ConditionedGPT2(GPT2LMHeadModel):
129
  # Cumulative pixel count (inclusive) and the baseline at the last marker
130
  pixel_cumsum = is_pixel.long().cumsum(dim=1)
131
  marker_base = torch.where(
132
- is_marker, pixel_cumsum, torch.zeros_like(pixel_cumsum)
 
 
133
  )
134
  t_idx = torch.arange(T, device=device).unsqueeze(0).expand(B, -1)
135
  last_marker_t, _ = torch.where(
@@ -190,7 +218,10 @@ class ConditionedGPT2(GPT2LMHeadModel):
190
  def _rand_or_use(val, emb):
191
  if val is None:
192
  val = torch.randint(
193
- 0, emb.num_embeddings, (B,), device=device
 
 
 
194
  )
195
  return emb(val)
196
 
@@ -244,7 +275,10 @@ class ConditionedGPT2(GPT2LMHeadModel):
244
  return outputs
245
 
246
  def prepare_inputs_for_generation(
247
- self, input_ids, past_key_values=None, **kwargs
 
 
 
248
  ):
249
  # Compute positional ids from the full sequence before the parent trims for KV cache
250
  row_ids_full = self._ids_to_row_ids(input_ids)
@@ -283,7 +317,9 @@ class ConditionedGPT2(GPT2LMHeadModel):
283
  def sample_conditioning(self, idx: int | None = None) -> torch.Tensor:
284
  if idx is None:
285
  idx = torch.randint(
286
- 0, self.conditioning.num_embeddings, (1,)
 
 
287
  ).item()
288
  with torch.no_grad():
289
  return self.conditioning(
@@ -293,65 +329,118 @@ class ConditionedGPT2(GPT2LMHeadModel):
293
  def sample_random_conditioning(self, device: str = "cpu") -> dict:
294
  return {
295
  "pokemon_idx": torch.randint(
296
- 0, self.conditioning.num_embeddings, (1,), device=device
 
 
 
297
  ),
298
  "type1": torch.randint(
299
- 0, self.type1_emb.num_embeddings, (1,), device=device
 
 
 
300
  ),
301
  "type2": torch.randint(
302
- 0, self.type2_emb.num_embeddings, (1,), device=device
 
 
 
303
  ),
304
  "is_shiny": torch.randint(
305
- 0, self.is_shiny_emb.num_embeddings, (1,), device=device
 
 
 
306
  ),
307
  "generation": torch.randint(
308
- 0, self.generation_emb.num_embeddings, (1,), device=device
 
 
 
309
  ),
310
  "evolution_stage": torch.randint(
311
- 0, self.evo_stage_emb.num_embeddings, (1,), device=device
 
 
 
312
  ),
313
  "has_evolution": torch.randint(
314
- 0, self.has_evolution_emb.num_embeddings, (1,), device=device
 
 
 
315
  ),
316
  "color_shift": torch.randint(
317
- 0, NUM_COLOR_SHIFTS, (1,), dtype=torch.long, device=device
 
 
 
 
318
  ),
319
  }
320
 
321
  def sample_novel_conditioning(
322
- self, n_mix: int = 3, device: str = "cpu"
 
 
323
  ) -> dict:
324
  """Blend n_mix random Pokémon embeddings to produce a novel conditioning vector."""
325
  with torch.no_grad():
326
  idxs = torch.randint(
327
- 0, self.conditioning.num_embeddings, (n_mix,), device=device
 
 
 
328
  )
329
  weights = torch.softmax(torch.randn(n_mix, device=device), dim=0)
330
  pokemon_cond = (weights.unsqueeze(1) * self.conditioning(idxs)).sum(
331
- 0, keepdim=True
 
332
  )
333
  return {
334
  "pokemon_cond": pokemon_cond,
335
  "type1": torch.randint(
336
- 0, self.type1_emb.num_embeddings, (1,), device=device
 
 
 
337
  ),
338
  "type2": torch.randint(
339
- 0, self.type2_emb.num_embeddings, (1,), device=device
 
 
 
340
  ),
341
  "is_shiny": torch.randint(
342
- 0, self.is_shiny_emb.num_embeddings, (1,), device=device
 
 
 
343
  ),
344
  "generation": torch.randint(
345
- 0, self.generation_emb.num_embeddings, (1,), device=device
 
 
 
346
  ),
347
  "evolution_stage": torch.randint(
348
- 0, self.evo_stage_emb.num_embeddings, (1,), device=device
 
 
 
349
  ),
350
  "has_evolution": torch.randint(
351
- 0, self.has_evolution_emb.num_embeddings, (1,), device=device
 
 
 
352
  ),
353
  "color_shift": torch.randint(
354
- 0, NUM_COLOR_SHIFTS, (1,), dtype=torch.long, device=device
 
 
 
 
355
  ),
356
  }
357
 
@@ -391,7 +480,9 @@ class ConditionedGPT2(GPT2LMHeadModel):
391
  tokenizer.convert_tokens_to_ids(f"[ROW_{i:02d}]") for i in range(64)
392
  ]
393
  inputs = tokenizer(
394
- "[ROW_00]", return_tensors="pt", add_special_tokens=False
 
 
395
  )
396
  inputs = {k: v.to(device) for k, v in inputs.items()}
397
  inputs.update(cond)
@@ -401,9 +492,15 @@ class ConditionedGPT2(GPT2LMHeadModel):
401
  from transformers import TextStreamer
402
 
403
  class _CompactStreamer(TextStreamer):
404
- def on_finalized_text(self, text: str, stream_end: bool = False):
 
 
405
  prefix = "\n" if "[ROW" in text else ""
406
- print(prefix + text, end="" if not stream_end else "\n", flush=True)
 
 
 
 
407
 
408
  streamer = _CompactStreamer(tokenizer, skip_special_tokens=False)
409
 
@@ -418,19 +515,22 @@ class ConditionedGPT2(GPT2LMHeadModel):
418
  pad_token_id=tokenizer.pad_token_id,
419
  eos_token_id=tokenizer.eos_token_id,
420
  logits_processor=[
421
- _RowLengthLogitsProcessor(tokenizer, row_marker_ids)
422
  ],
423
  streamer=streamer,
424
  )
425
 
426
  return _tokens_to_image(
427
- tokenizer.decode(output_ids[0], skip_special_tokens=False)
428
  )
429
 
430
 
431
  class _RowLengthLogitsProcessor(LogitsProcessor):
432
  def __init__(
433
- self, tokenizer, row_marker_ids: list[int], row_width: int = 63,
 
 
 
434
  ):
435
  vocab = tokenizer.get_vocab()
436
  self.row_marker_set = set(row_marker_ids)
@@ -451,7 +551,9 @@ class _RowLengthLogitsProcessor(LogitsProcessor):
451
  self.current_row = self.chars_in_row = 0
452
 
453
  def __call__(
454
- self, input_ids: torch.Tensor, scores: torch.Tensor
 
 
455
  ) -> torch.Tensor:
456
  device = scores.device
457
  pixel_len = self.pixel_len.to(device)
@@ -459,6 +561,8 @@ class _RowLengthLogitsProcessor(LogitsProcessor):
459
  if last_id in self.row_marker_set:
460
  self.current_row = self.row_marker_ids.index(last_id)
461
  self.chars_in_row = 0
 
 
462
  remaining = self.row_width - self.chars_in_row
463
  if remaining > 0:
464
  mask = (pixel_len > remaining) | (pixel_len == 0)
@@ -472,11 +576,6 @@ class _RowLengthLogitsProcessor(LogitsProcessor):
472
  else:
473
  allowed[self.eos_id] = False
474
  scores = scores.masked_fill(allowed.unsqueeze(0), float("-inf"))
475
- if last_id not in self.row_marker_set and last_id not in {
476
- self.eos_id,
477
- self.bos_id,
478
- }:
479
- self.chars_in_row += int(pixel_len[last_id].item())
480
  return scores
481
 
482
 
@@ -505,7 +604,7 @@ def _tokens_to_image(text: str) -> np.ndarray:
505
 
506
  if __name__ == "__main__":
507
  from pathlib import Path
508
- from PIL import Image
509
  from huggingface_hub import snapshot_download
510
  from transformers import AutoModelForCausalLM, PreTrainedTokenizerFast
511
 
@@ -525,5 +624,5 @@ if __name__ == "__main__":
525
  print(f"\n[{i + 1}/{N_SAMPLES}] generando...")
526
  image = model.generate_sprite(tokenizer, verbose=True)
527
  path = OUTPUT_DIR / f"pokemon_{i + 1:02d}.png"
528
- Image.fromarray(image).save(path)
529
  print(f"guardado en {path}")
 
12
  NUM_COLOR_SHIFTS = 6 # 0 = no shift, 1-5 = ColorShift permutations
13
 
14
  _TYPES = [
15
+ "normal",
16
+ "fire",
17
+ "water",
18
+ "electric",
19
+ "grass",
20
+ "ice",
21
+ "fighting",
22
+ "poison",
23
+ "ground",
24
+ "flying",
25
+ "psychic",
26
+ "bug",
27
+ "rock",
28
+ "ghost",
29
+ "dragon",
30
+ "dark",
31
+ "steel",
32
+ "fairy",
33
  ]
34
+ _TYPE1_UNK = 18 # unknown primary type
35
  _TYPE2_NONE = 18 # no secondary type
36
+ _TYPE2_UNK = 19 # unknown secondary type
37
 
38
 
39
+ def _resolve_type(
40
+ val: "str | int | None", default_none_idx: int
41
+ ) -> "int | None":
42
  if val is None:
43
  return None
44
  if isinstance(val, str):
45
+ return (
46
+ _TYPES.index(val.lower())
47
+ if val.lower() in _TYPES
48
+ else default_none_idx
49
+ )
50
  return int(val)
51
 
52
 
 
66
  ):
67
  num_pokemon = num_pokemon or getattr(config, "num_pokemon", None)
68
  if num_pokemon is None:
69
+ raise ValueError(
70
+ "num_pokemon must be provided or present in config"
71
+ )
72
  config.num_pokemon = num_pokemon
73
  super().__init__(config)
74
  self.conditioning = nn.Embedding(num_pokemon, config.n_embd)
 
115
  # Store row marker token ids as a buffer so they're saved with the model
116
  _ids = row_marker_token_ids or [0] * 64
117
  self.register_buffer(
118
+ "row_marker_ids",
119
+ torch.tensor(_ids, dtype=torch.long),
120
  )
121
 
122
  def _ids_to_row_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
 
139
 
140
  in_row = is_assigned.long().cumsum(dim=1) >= 1
141
  return torch.where(
142
+ in_row,
143
+ row_ids_filled,
144
+ input_ids.new_full((B, T), 64),
145
  )
146
 
147
  def _ids_to_col_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
 
155
  # Cumulative pixel count (inclusive) and the baseline at the last marker
156
  pixel_cumsum = is_pixel.long().cumsum(dim=1)
157
  marker_base = torch.where(
158
+ is_marker,
159
+ pixel_cumsum,
160
+ torch.zeros_like(pixel_cumsum),
161
  )
162
  t_idx = torch.arange(T, device=device).unsqueeze(0).expand(B, -1)
163
  last_marker_t, _ = torch.where(
 
218
  def _rand_or_use(val, emb):
219
  if val is None:
220
  val = torch.randint(
221
+ 0,
222
+ emb.num_embeddings,
223
+ (B,),
224
+ device=device,
225
  )
226
  return emb(val)
227
 
 
275
  return outputs
276
 
277
  def prepare_inputs_for_generation(
278
+ self,
279
+ input_ids,
280
+ past_key_values=None,
281
+ **kwargs,
282
  ):
283
  # Compute positional ids from the full sequence before the parent trims for KV cache
284
  row_ids_full = self._ids_to_row_ids(input_ids)
 
317
  def sample_conditioning(self, idx: int | None = None) -> torch.Tensor:
318
  if idx is None:
319
  idx = torch.randint(
320
+ 0,
321
+ self.conditioning.num_embeddings,
322
+ (1,),
323
  ).item()
324
  with torch.no_grad():
325
  return self.conditioning(
 
329
  def sample_random_conditioning(self, device: str = "cpu") -> dict:
330
  return {
331
  "pokemon_idx": torch.randint(
332
+ 0,
333
+ self.conditioning.num_embeddings,
334
+ (1,),
335
+ device=device,
336
  ),
337
  "type1": torch.randint(
338
+ 0,
339
+ self.type1_emb.num_embeddings,
340
+ (1,),
341
+ device=device,
342
  ),
343
  "type2": torch.randint(
344
+ 0,
345
+ self.type2_emb.num_embeddings,
346
+ (1,),
347
+ device=device,
348
  ),
349
  "is_shiny": torch.randint(
350
+ 0,
351
+ self.is_shiny_emb.num_embeddings,
352
+ (1,),
353
+ device=device,
354
  ),
355
  "generation": torch.randint(
356
+ 0,
357
+ self.generation_emb.num_embeddings,
358
+ (1,),
359
+ device=device,
360
  ),
361
  "evolution_stage": torch.randint(
362
+ 0,
363
+ self.evo_stage_emb.num_embeddings,
364
+ (1,),
365
+ device=device,
366
  ),
367
  "has_evolution": torch.randint(
368
+ 0,
369
+ self.has_evolution_emb.num_embeddings,
370
+ (1,),
371
+ device=device,
372
  ),
373
  "color_shift": torch.randint(
374
+ 0,
375
+ NUM_COLOR_SHIFTS,
376
+ (1,),
377
+ dtype=torch.long,
378
+ device=device,
379
  ),
380
  }
381
 
382
  def sample_novel_conditioning(
383
+ self,
384
+ n_mix: int = 3,
385
+ device: str = "cpu",
386
  ) -> dict:
387
  """Blend n_mix random Pokémon embeddings to produce a novel conditioning vector."""
388
  with torch.no_grad():
389
  idxs = torch.randint(
390
+ 0,
391
+ self.conditioning.num_embeddings,
392
+ (n_mix,),
393
+ device=device,
394
  )
395
  weights = torch.softmax(torch.randn(n_mix, device=device), dim=0)
396
  pokemon_cond = (weights.unsqueeze(1) * self.conditioning(idxs)).sum(
397
+ 0,
398
+ keepdim=True,
399
  )
400
  return {
401
  "pokemon_cond": pokemon_cond,
402
  "type1": torch.randint(
403
+ 0,
404
+ self.type1_emb.num_embeddings,
405
+ (1,),
406
+ device=device,
407
  ),
408
  "type2": torch.randint(
409
+ 0,
410
+ self.type2_emb.num_embeddings,
411
+ (1,),
412
+ device=device,
413
  ),
414
  "is_shiny": torch.randint(
415
+ 0,
416
+ self.is_shiny_emb.num_embeddings,
417
+ (1,),
418
+ device=device,
419
  ),
420
  "generation": torch.randint(
421
+ 0,
422
+ self.generation_emb.num_embeddings,
423
+ (1,),
424
+ device=device,
425
  ),
426
  "evolution_stage": torch.randint(
427
+ 0,
428
+ self.evo_stage_emb.num_embeddings,
429
+ (1,),
430
+ device=device,
431
  ),
432
  "has_evolution": torch.randint(
433
+ 0,
434
+ self.has_evolution_emb.num_embeddings,
435
+ (1,),
436
+ device=device,
437
  ),
438
  "color_shift": torch.randint(
439
+ 0,
440
+ NUM_COLOR_SHIFTS,
441
+ (1,),
442
+ dtype=torch.long,
443
+ device=device,
444
  ),
445
  }
446
 
 
480
  tokenizer.convert_tokens_to_ids(f"[ROW_{i:02d}]") for i in range(64)
481
  ]
482
  inputs = tokenizer(
483
+ "[ROW_00]",
484
+ return_tensors="pt",
485
+ add_special_tokens=False,
486
  )
487
  inputs = {k: v.to(device) for k, v in inputs.items()}
488
  inputs.update(cond)
 
492
  from transformers import TextStreamer
493
 
494
  class _CompactStreamer(TextStreamer):
495
+ def on_finalized_text(
496
+ self, text: str, stream_end: bool = False
497
+ ):
498
  prefix = "\n" if "[ROW" in text else ""
499
+ print(
500
+ prefix + text,
501
+ end="" if not stream_end else "\n",
502
+ flush=True,
503
+ )
504
 
505
  streamer = _CompactStreamer(tokenizer, skip_special_tokens=False)
506
 
 
515
  pad_token_id=tokenizer.pad_token_id,
516
  eos_token_id=tokenizer.eos_token_id,
517
  logits_processor=[
518
+ _RowLengthLogitsProcessor(tokenizer, row_marker_ids),
519
  ],
520
  streamer=streamer,
521
  )
522
 
523
  return _tokens_to_image(
524
+ tokenizer.decode(output_ids[0], skip_special_tokens=False),
525
  )
526
 
527
 
528
  class _RowLengthLogitsProcessor(LogitsProcessor):
529
  def __init__(
530
+ self,
531
+ tokenizer,
532
+ row_marker_ids: list[int],
533
+ row_width: int = 63,
534
  ):
535
  vocab = tokenizer.get_vocab()
536
  self.row_marker_set = set(row_marker_ids)
 
551
  self.current_row = self.chars_in_row = 0
552
 
553
  def __call__(
554
+ self,
555
+ input_ids: torch.Tensor,
556
+ scores: torch.Tensor,
557
  ) -> torch.Tensor:
558
  device = scores.device
559
  pixel_len = self.pixel_len.to(device)
 
561
  if last_id in self.row_marker_set:
562
  self.current_row = self.row_marker_ids.index(last_id)
563
  self.chars_in_row = 0
564
+ elif last_id not in {self.eos_id, self.bos_id}:
565
+ self.chars_in_row += int(pixel_len[last_id].item())
566
  remaining = self.row_width - self.chars_in_row
567
  if remaining > 0:
568
  mask = (pixel_len > remaining) | (pixel_len == 0)
 
576
  else:
577
  allowed[self.eos_id] = False
578
  scores = scores.masked_fill(allowed.unsqueeze(0), float("-inf"))
 
 
 
 
 
579
  return scores
580
 
581
 
 
604
 
605
  if __name__ == "__main__":
606
  from pathlib import Path
607
+ import cv2
608
  from huggingface_hub import snapshot_download
609
  from transformers import AutoModelForCausalLM, PreTrainedTokenizerFast
610
 
 
624
  print(f"\n[{i + 1}/{N_SAMPLES}] generando...")
625
  image = model.generate_sprite(tokenizer, verbose=True)
626
  path = OUTPUT_DIR / f"pokemon_{i + 1:02d}.png"
627
+ cv2.imwrite(str(path), cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
628
  print(f"guardado en {path}")