ArushBuilds commited on
Commit
e58aace
Β·
1 Parent(s): 5a4beb9

Update inference.py

Browse files
Files changed (1) hide show
  1. inference.py +72 -19
inference.py CHANGED
@@ -97,28 +97,77 @@ def load_model(checkpoint_path: str, device: torch.device) -> tuple[GPT, GPTConf
97
  # Sampling
98
  # ─────────────────────────────────────────────────────────────────────────────
99
 
100
- def _apply_repetition_penalty(logits: torch.Tensor, generated_ids: list[int], penalty: float) -> torch.Tensor:
101
- """CTRL-style penalty, COUNT-SCALED: a token seen N times gets divided
102
- by penalty**N (if positive) / multiplied by penalty**N (if negative),
103
- not a single flat divide regardless of occurrence count.
104
-
105
- The original flat version (divide once no matter how many times a
106
- token has recurred) is too weak on small/repetitive-domain models
107
- (e.g. TinyStories-style data where "so so so happy!" is a genuinely
108
- common training pattern): once the model locks onto a token, the
109
- penalty never gets any stronger no matter how many times it fires,
110
- so a token that's only mildly favored over the runner-up never gets
111
- pushed low enough to escape the loop. Scaling by occurrence count
112
- means each additional repeat compounds the penalty, so a loop that's
113
- 3-4 tokens deep gets meaningfully suppressed even if a single
114
- application wouldn't have been enough.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  """
116
  if penalty == 1.0 or not generated_ids:
117
  return logits
118
  from collections import Counter
119
- counts = Counter(generated_ids)
120
- seen = torch.tensor(sorted(counts.keys()), device=logits.device, dtype=torch.long)
121
- reps = torch.tensor([counts[t] for t in sorted(counts.keys())], device=logits.device, dtype=logits.dtype)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  vals = logits[0, seen]
123
  factor = penalty ** reps
124
  logits[0, seen] = torch.where(vals > 0, vals / factor, vals * factor)
@@ -197,6 +246,10 @@ def generate(
197
  block_size = model.config.block_size
198
  eos_id = getattr(tokenizer, "eos_id", None)
199
 
 
 
 
 
200
  generated_ids = [list(prompt_ids) for _ in range(num_samples)]
201
  finished = [False] * num_samples
202
  # Decode the FULL sequence each step and print only the new suffix,
@@ -222,7 +275,7 @@ def generate(
222
  if finished[b]:
223
  continue
224
  row = logits[b:b + 1]
225
- row = _apply_repetition_penalty(row, generated_ids[b], repetition_penalty)
226
  row = _block_repeated_ngrams(row, generated_ids[b], no_repeat_ngram_size)
227
  row = row / max(temperature, 1e-5)
228
  if top_k is not None:
 
97
  # Sampling
98
  # ─────────────────────────────────────────────────────────────────────────────
99
 
100
+ def _apply_repetition_penalty(
101
+ logits: torch.Tensor,
102
+ generated_ids: list[int],
103
+ penalty: float,
104
+ prompt_id_set: "set[int] | None" = None,
105
+ max_count: int = 6,
106
+ ) -> torch.Tensor:
107
+ """CTRL-style penalty, COUNT-SCALED: a token seen N times in the
108
+ *generated* portion gets divided by penalty**N (if positive) /
109
+ multiplied by penalty**N (if negative).
110
+
111
+ Count-scaling vs flat penalty
112
+ ------------------------------
113
+ The flat version (divide once regardless of occurrence count) is too
114
+ weak on small/repetitive-domain models (e.g. TinyStories-style data
115
+ where "so so so happy!" is a genuinely common training pattern): once
116
+ the model locks onto a token, the penalty never gets any stronger, so
117
+ a mildly favoured looping token never gets pushed low enough to escape.
118
+ Scaling by occurrence count means each additional repeat compounds the
119
+ penalty, so a 3-4 token-deep loop gets meaningfully suppressed even if
120
+ a single application wouldn't have been enough.
121
+
122
+ Why penalty counts are clamped to max_count
123
+ --------------------------------------------
124
+ Without a cap, a legitimate word that happens to appear many times in
125
+ a long generation (e.g. "the", "a", punctuation) accumulates a factor
126
+ of penalty**N that grows without bound and can suppress even
127
+ high-confidence logits into noise. max_count=6 lets count-scaling do
128
+ its job on real loops (which repeat in short runs) while preventing
129
+ runaway suppression of common tokens over a long context.
130
+
131
+ Why prompt tokens are excluded from the count
132
+ -----------------------------------------------
133
+ generated_ids is seeded with the full prompt before the first token is
134
+ produced. Without the exclusion, common tokens in a 20-token prompt
135
+ already arrive at the first generation step with count >= 2-3, giving
136
+ them a standing penalty of penalty**3 before the model has emitted a
137
+ single token. That's not repetition suppression -- it's prompt
138
+ interference. prompt_id_set (built once per generate() call from the
139
+ original prompt_ids) lets us count only within the generated portion.
140
  """
141
  if penalty == 1.0 or not generated_ids:
142
  return logits
143
  from collections import Counter
144
+ if prompt_id_set:
145
+ # Count only tokens that appear in the generated suffix (after the
146
+ # prompt). generated_ids includes the prompt prefix, so we walk the
147
+ # list and tally only tokens not in prompt_id_set -- tokens that the
148
+ # model itself produced. Prompt tokens still receive the flat
149
+ # penalty**1 if they recur in the generated portion (they're not
150
+ # immune; we just don't pre-charge them for being in the prompt).
151
+ gen_counts: Counter[int] = Counter()
152
+ prompt_remaining = Counter(prompt_id_set)
153
+ for tid in generated_ids:
154
+ if prompt_remaining.get(tid, 0) > 0:
155
+ prompt_remaining[tid] -= 1
156
+ continue
157
+ gen_counts[tid] += 1
158
+ counts = gen_counts
159
+ else:
160
+ counts = Counter(generated_ids)
161
+
162
+ if not counts:
163
+ return logits
164
+
165
+ sorted_tokens = sorted(counts.keys())
166
+ seen = torch.tensor(sorted_tokens, device=logits.device, dtype=torch.long)
167
+ # Clamp counts to max_count so common tokens don't accumulate a
168
+ # runaway factor over a long generation.
169
+ raw_reps = [min(counts[t], max_count) for t in sorted_tokens]
170
+ reps = torch.tensor(raw_reps, device=logits.device, dtype=logits.dtype)
171
  vals = logits[0, seen]
172
  factor = penalty ** reps
173
  logits[0, seen] = torch.where(vals > 0, vals / factor, vals * factor)
 
246
  block_size = model.config.block_size
247
  eos_id = getattr(tokenizer, "eos_id", None)
248
 
249
+ # Built once from the original prompt so _apply_repetition_penalty can
250
+ # exclude prompt tokens from the count -- see that function's docstring.
251
+ prompt_id_set: set[int] = set(prompt_ids)
252
+
253
  generated_ids = [list(prompt_ids) for _ in range(num_samples)]
254
  finished = [False] * num_samples
255
  # Decode the FULL sequence each step and print only the new suffix,
 
275
  if finished[b]:
276
  continue
277
  row = logits[b:b + 1]
278
+ row = _apply_repetition_penalty(row, generated_ids[b], repetition_penalty, prompt_id_set=prompt_id_set)
279
  row = _block_repeated_ngrams(row, generated_ids[b], no_repeat_ngram_size)
280
  row = row / max(temperature, 1e-5)
281
  if top_k is not None: