nathanael-fijalkow commited on
Commit
116756e
·
1 Parent(s): a02c4e5

updates + solution

Browse files
Files changed (3) hide show
  1. app.py +184 -22
  2. solution.py +174 -0
  3. test_cases.json +4 -4
app.py CHANGED
@@ -8,13 +8,19 @@ from transformers import AutoModelForCausalLM, AutoTokenizer
8
  from functools import wraps
9
  import signal
10
  import threading
 
 
11
 
12
  # 1. SETUP
13
  EVAL_MODEL = "HuggingFaceTB/SmolLM2-1.7B-Instruct"
 
14
  tokenizer = AutoTokenizer.from_pretrained(EVAL_MODEL)
 
 
 
15
  model = AutoModelForCausalLM.from_pretrained(
16
  EVAL_MODEL,
17
- torch_dtype=torch.float16,
18
  device_map="auto"
19
  )
20
 
@@ -26,9 +32,9 @@ class TimeoutException(Exception):
26
  pass
27
 
28
  def timeout_handler(signum, frame):
29
- raise TimeoutException("Prompt evaluation timed out (20s limit exceeded)")
30
 
31
- def run_with_timeout(func, args=(), kwargs=None, timeout_sec=20):
32
  """Run a function with a timeout."""
33
  if kwargs is None:
34
  kwargs = {}
@@ -48,7 +54,7 @@ def run_with_timeout(func, args=(), kwargs=None, timeout_sec=20):
48
  thread.join(timeout=timeout_sec)
49
 
50
  if thread.is_alive():
51
- raise TimeoutException("Prompt evaluation timed out (20s limit exceeded)")
52
 
53
  if exception[0]:
54
  raise exception[0]
@@ -56,14 +62,113 @@ def run_with_timeout(func, args=(), kwargs=None, timeout_sec=20):
56
  return result[0]
57
 
58
 
59
- def evaluate_submission(file_obj):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  if file_obj is None:
61
  return "No file provided."
62
 
63
  try:
64
  # 2. ISOLATED LOADING
65
  # We use a unique name for each import to avoid namespace collisions
66
- spec = importlib.util.spec_from_file_location("student_module", file_obj.name)
 
67
  student_module = importlib.util.module_from_spec(spec)
68
  spec.loader.exec_module(student_module)
69
 
@@ -72,51 +177,87 @@ def evaluate_submission(file_obj):
72
  # --- EXERCISE 1 ---
73
  ex1_passed = 0
74
  ex1_timeout = False
 
75
  try:
76
  ex1_instance = student_module.LaDisparition(model, tokenizer)
77
- for prompt in TEST_CASES["exercise_1"]:
78
  try:
79
  # We limit tokens to keep evaluation fast
80
  output = run_with_timeout(
81
  ex1_instance,
82
  args=(prompt,),
83
  kwargs={"max_tokens": 20},
84
- timeout_sec=20
85
  )
86
- if 'e' not in output.lower() and len(output.strip()) > 10:
 
 
 
87
  ex1_passed += 1
 
 
 
 
 
 
88
  except TimeoutException:
89
  ex1_timeout = True
 
 
 
90
  break
91
  if ex1_timeout:
92
- report.append(f" **Ex 1 (No 'e'):** TIMEOUT - evaluation exceeded 20s limit")
93
  else:
94
  report.append(f" **Ex 1 (No 'e'):** {ex1_passed}/5 correct")
 
 
 
 
 
95
  except Exception as e:
96
  report.append(f" **Ex 1 Error:** {str(e)}")
97
 
98
  # --- EXERCISE 2 ---
99
  ex2_passed = 0
100
  ex2_timeout = False
 
101
  try:
102
  ex2_instance = student_module.ToulouseSequence(model, tokenizer)
103
- for prompt in TEST_CASES["exercise_2"]:
104
  try:
105
  output = run_with_timeout(
106
  ex2_instance,
107
  args=(prompt,),
108
  kwargs={"max_tokens": 20},
109
- timeout_sec=20
110
  )
111
- if "toulouse" not in output.lower() and len(output.strip()) > 10:
 
 
 
112
  ex2_passed += 1
 
 
 
 
 
 
113
  except TimeoutException:
114
  ex2_timeout = True
 
 
 
115
  break
116
  if ex2_timeout:
117
- report.append(f" **Ex 2 (No Toulouse):** TIMEOUT - evaluation exceeded 20s limit")
118
  else:
119
  report.append(f" **Ex 2 (No Toulouse):** {ex2_passed}/5 correct")
 
 
 
 
 
120
  except Exception as e:
121
  report.append(f" **Ex 2 Error:** {str(e)}")
122
 
@@ -131,11 +272,32 @@ def evaluate_submission(file_obj):
131
  return f"### System Error during import:\n{str(e)}"
132
 
133
  # 4. LAUNCH WITH CONCURRENCY CONTROL
134
- demo = gr.Interface(
135
- fn=evaluate_submission,
136
- inputs=gr.File(label="Submission File"),
137
- outputs="markdown",
138
- api_name="predict"
139
- )
140
-
141
- demo.queue(default_concurrency_limit=1).launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from functools import wraps
9
  import signal
10
  import threading
11
+ import sys
12
+ import argparse
13
 
14
  # 1. SETUP
15
  EVAL_MODEL = "HuggingFaceTB/SmolLM2-1.7B-Instruct"
16
+ TIMEOUT_SECONDS = 30
17
  tokenizer = AutoTokenizer.from_pretrained(EVAL_MODEL)
18
+ # Set pad token to prevent warnings and ensure proper attention masking
19
+ if tokenizer.pad_token is None:
20
+ tokenizer.pad_token = tokenizer.eos_token
21
  model = AutoModelForCausalLM.from_pretrained(
22
  EVAL_MODEL,
23
+ dtype=torch.float16,
24
  device_map="auto"
25
  )
26
 
 
32
  pass
33
 
34
  def timeout_handler(signum, frame):
35
+ raise TimeoutException(f"Prompt evaluation timed out ({TIMEOUT_SECONDS}s limit exceeded)")
36
 
37
+ def run_with_timeout(func, args=(), kwargs=None, timeout_sec=TIMEOUT_SECONDS):
38
  """Run a function with a timeout."""
39
  if kwargs is None:
40
  kwargs = {}
 
54
  thread.join(timeout=timeout_sec)
55
 
56
  if thread.is_alive():
57
+ raise TimeoutException(f"Prompt evaluation timed out ({TIMEOUT_SECONDS}s limit exceeded)")
58
 
59
  if exception[0]:
60
  raise exception[0]
 
62
  return result[0]
63
 
64
 
65
+ def strip_prompt_from_output(output, prompt):
66
+ """Remove the prompt from the beginning of the output if present."""
67
+ # Normalize whitespace for comparison
68
+ output_stripped = output.strip()
69
+ prompt_stripped = prompt.strip()
70
+
71
+ # Check if output starts with the prompt
72
+ if output_stripped.startswith(prompt_stripped):
73
+ result = output_stripped[len(prompt_stripped):].strip()
74
+ return result
75
+
76
+ # If exact match didn't work, try finding where prompt ends in output
77
+ # This handles cases where there might be formatting differences
78
+ prompt_words = prompt_stripped.split()
79
+ if prompt_words and output_stripped.split()[:len(prompt_words)] == prompt_words:
80
+ # Remove matching words at the beginning
81
+ result = ' '.join(output_stripped.split()[len(prompt_words):])
82
+ return result
83
+
84
+ return output
85
+
86
+
87
+ def extract_assistant_response(text):
88
+ """Extract only the assistant's response from the chat format output."""
89
+ lines = text.split('\n')
90
+ result = []
91
+ in_assistant = False
92
+
93
+ for line in lines:
94
+ stripped = line.strip()
95
+
96
+ # Start collecting when we see "assistant"
97
+ if stripped == "assistant":
98
+ in_assistant = True
99
+ continue
100
+
101
+ # Stop collecting when we see "user" or "system"
102
+ if stripped in ("user", "system"):
103
+ break
104
+
105
+ # Collect lines that are part of the assistant response
106
+ if in_assistant and stripped:
107
+ result.append(line)
108
+
109
+ return '\n'.join(result).strip()
110
+
111
+
112
+ def test_raw_outputs(debug=False):
113
+ """Test raw model outputs without any mask for debugging."""
114
+ print(f"\n{'='*60}")
115
+ print("RAW MODEL OUTPUTS")
116
+ print(f"{'='*60}\n")
117
+
118
+ # --- EXERCISE 1 RAW ---
119
+ print("### Exercise 1 - Raw Outputs:")
120
+ for i, prompt in enumerate(TEST_CASES["exercise_1"]):
121
+ try:
122
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
123
+ output = model.generate(
124
+ inputs["input_ids"],
125
+ attention_mask=inputs["attention_mask"],
126
+ max_new_tokens=20,
127
+ do_sample=True,
128
+ temperature=0.7,
129
+ top_p=0.9,
130
+ eos_token_id=None,
131
+ pad_token_id=tokenizer.pad_token_id
132
+ )
133
+ decoded = tokenizer.decode(output[0], skip_special_tokens=True)
134
+ cleaned = strip_prompt_from_output(decoded, prompt)
135
+ assistant_response = extract_assistant_response(cleaned)
136
+ print(f"{i+1}. {assistant_response}")
137
+ except Exception as e:
138
+ print(f"{i+1}. ERROR: {str(e)}")
139
+
140
+ # --- EXERCISE 2 RAW ---
141
+ print("\n### Exercise 2 - Raw Outputs:")
142
+ for i, prompt in enumerate(TEST_CASES["exercise_2"]):
143
+ try:
144
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
145
+ output = model.generate(
146
+ inputs["input_ids"],
147
+ attention_mask=inputs["attention_mask"],
148
+ max_new_tokens=20,
149
+ do_sample=True,
150
+ temperature=0.7,
151
+ top_p=0.9,
152
+ eos_token_id=None,
153
+ pad_token_id=tokenizer.pad_token_id
154
+ )
155
+ decoded = tokenizer.decode(output[0], skip_special_tokens=True)
156
+ cleaned = strip_prompt_from_output(decoded, prompt)
157
+ assistant_response = extract_assistant_response(cleaned)
158
+ print(f"{i+1}. {assistant_response}")
159
+ except Exception as e:
160
+ print(f"{i+1}. ERROR: {str(e)}")
161
+
162
+
163
+ def evaluate_submission(file_obj, debug=False):
164
  if file_obj is None:
165
  return "No file provided."
166
 
167
  try:
168
  # 2. ISOLATED LOADING
169
  # We use a unique name for each import to avoid namespace collisions
170
+ file_path = file_obj if isinstance(file_obj, str) else file_obj.name
171
+ spec = importlib.util.spec_from_file_location("student_module", file_path)
172
  student_module = importlib.util.module_from_spec(spec)
173
  spec.loader.exec_module(student_module)
174
 
 
177
  # --- EXERCISE 1 ---
178
  ex1_passed = 0
179
  ex1_timeout = False
180
+ ex1_outputs = []
181
  try:
182
  ex1_instance = student_module.LaDisparition(model, tokenizer)
183
+ for i, prompt in enumerate(TEST_CASES["exercise_1"]):
184
  try:
185
  # We limit tokens to keep evaluation fast
186
  output = run_with_timeout(
187
  ex1_instance,
188
  args=(prompt,),
189
  kwargs={"max_tokens": 20},
190
+ timeout_sec=TIMEOUT_SECONDS
191
  )
192
+ # Remove prompt from output to only validate generated text
193
+ cleaned_output = strip_prompt_from_output(output, prompt)
194
+ passed = 'e' not in cleaned_output.lower() and len(cleaned_output.strip()) > 10
195
+ if passed:
196
  ex1_passed += 1
197
+ ex1_outputs.append({"prompt": prompt, "output": cleaned_output, "passed": passed})
198
+ if debug:
199
+ print(f"Ex1 Test {i+1}: {'✓' if passed else '✗'}")
200
+ print(f" Prompt: {prompt}")
201
+ print(f" Output: {cleaned_output}")
202
+ print()
203
  except TimeoutException:
204
  ex1_timeout = True
205
+ ex1_outputs.append({"prompt": prompt, "output": "TIMEOUT", "passed": False})
206
+ if debug:
207
+ print(f"Ex1 Test {i+1}: ✗ TIMEOUT")
208
  break
209
  if ex1_timeout:
210
+ report.append(f" **Ex 1 (No 'e'):** TIMEOUT - evaluation exceeded {TIMEOUT_SECONDS}s limit")
211
  else:
212
  report.append(f" **Ex 1 (No 'e'):** {ex1_passed}/5 correct")
213
+
214
+ if debug:
215
+ report.append("\n### Ex 1 Outputs:")
216
+ for i, out in enumerate(ex1_outputs):
217
+ report.append(f"{i+1}. {'✓' if out['passed'] else '✗'} `{out['output']}`")
218
  except Exception as e:
219
  report.append(f" **Ex 1 Error:** {str(e)}")
220
 
221
  # --- EXERCISE 2 ---
222
  ex2_passed = 0
223
  ex2_timeout = False
224
+ ex2_outputs = []
225
  try:
226
  ex2_instance = student_module.ToulouseSequence(model, tokenizer)
227
+ for i, prompt in enumerate(TEST_CASES["exercise_2"]):
228
  try:
229
  output = run_with_timeout(
230
  ex2_instance,
231
  args=(prompt,),
232
  kwargs={"max_tokens": 20},
233
+ timeout_sec=TIMEOUT_SECONDS
234
  )
235
+ # Remove prompt from output to only validate generated text
236
+ cleaned_output = strip_prompt_from_output(output, prompt)
237
+ passed = "toulouse" not in cleaned_output.lower() and len(cleaned_output.strip()) > 10
238
+ if passed:
239
  ex2_passed += 1
240
+ ex2_outputs.append({"prompt": prompt, "output": cleaned_output, "passed": passed})
241
+ if debug:
242
+ print(f"Ex2 Test {i+1}: {'✓' if passed else '✗'}")
243
+ print(f" Prompt: {prompt}")
244
+ print(f" Output: {cleaned_output}")
245
+ print()
246
  except TimeoutException:
247
  ex2_timeout = True
248
+ ex2_outputs.append({"prompt": prompt, "output": "TIMEOUT", "passed": False})
249
+ if debug:
250
+ print(f"Ex2 Test {i+1}: ✗ TIMEOUT")
251
  break
252
  if ex2_timeout:
253
+ report.append(f" **Ex 2 (No Toulouse):** TIMEOUT - evaluation exceeded {TIMEOUT_SECONDS}s limit")
254
  else:
255
  report.append(f" **Ex 2 (No Toulouse):** {ex2_passed}/5 correct")
256
+
257
+ if debug:
258
+ report.append("\n### Ex 2 Outputs:")
259
+ for i, out in enumerate(ex2_outputs):
260
+ report.append(f"{i+1}. {'✓' if out['passed'] else '✗'} `{out['output']}`")
261
  except Exception as e:
262
  report.append(f" **Ex 2 Error:** {str(e)}")
263
 
 
272
  return f"### System Error during import:\n{str(e)}"
273
 
274
  # 4. LAUNCH WITH CONCURRENCY CONTROL
275
+ if __name__ == "__main__":
276
+ parser = argparse.ArgumentParser(description="Evaluate lipogram solutions")
277
+ parser.add_argument("--local", type=str, help="Path to solution file for local testing")
278
+ parser.add_argument("--debug", action="store_true", help="Enable debug output")
279
+ parser.add_argument("--raw", action="store_true", help="Test raw model outputs without mask")
280
+ args = parser.parse_args()
281
+
282
+ if args.raw:
283
+ # Raw output testing mode
284
+ test_raw_outputs()
285
+ elif args.local:
286
+ # Local testing mode
287
+ print(f"\n{'='*60}")
288
+ print(f"Testing solution: {args.local}")
289
+ print(f"{'='*60}\n")
290
+ result = evaluate_submission(args.local, debug=args.debug)
291
+ print(f"\n{'='*60}")
292
+ print("FINAL REPORT:")
293
+ print(f"{'='*60}")
294
+ print(result)
295
+ else:
296
+ # Gradio web interface mode
297
+ demo = gr.Interface(
298
+ fn=evaluate_submission,
299
+ inputs=gr.File(label="Submission File"),
300
+ outputs="markdown",
301
+ api_name="predict"
302
+ )
303
+ demo.queue(default_concurrency_limit=1).launch()
solution.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Tuple
2
+ import torch
3
+ import torch.nn.functional as F
4
+ from transformers import AutoModelForCausalLM, AutoTokenizer
5
+
6
+ # SETUP
7
+ MODEL_NAME = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
8
+ # MODEL_NAME = "HuggingFaceTB/SmolLM2-1.7B-Instruct"
9
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
10
+ model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, dtype=torch.float16, device_map="auto")
11
+
12
+ # --- EXERCISE 1: La disparition (No 'e' or 'E) ---
13
+ class LaDisparition:
14
+ """
15
+ Generate text without ever using the letter 'e' or 'E'.
16
+ For this, you must use model() directly: model(input_ids) yields logits.
17
+ You need to manually adjust the logits to forbid tokens containing 'e' or 'E'.
18
+ REQUIREMENT: Do NOT use model.generate().
19
+ """
20
+ def __init__(self, model, tokenizer, debug=False):
21
+ self.model = model
22
+ self.tokenizer = tokenizer
23
+ self.debug = debug
24
+ # Pre-calculate forbidden token IDs (tokens that decode to contain 'e' or 'E' or non-ASCII)
25
+ # Check decoded output, not just the vocab string representation
26
+ self.forbidden_token_ids = set()
27
+ vocab = self.tokenizer.get_vocab()
28
+ for token_id in range(len(vocab)):
29
+ # Decode the token to see what it actually produces
30
+ decoded = self.tokenizer.decode([token_id])
31
+ # Forbid if contains 'e'/'E' or contains non-ASCII (which might hide 'e' or be weird like Cyrillic)
32
+ if 'e' in decoded.lower() or not all(ord(c) < 128 for c in decoded):
33
+ self.forbidden_token_ids.add(token_id)
34
+
35
+ # Warning: The evaluation server uses a different model and tokenizer than the template. Do not hard-code Token IDs. Use self.tokenizer.get_vocab() or self.tokenizer.encode() to find the IDs relevant to the current model.
36
+
37
+
38
+ def __call__(self, prompt, max_tokens=10, beam_width=5):
39
+ # Tokenize input prompt using chat template:
40
+ inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
41
+ input_ids = inputs["input_ids"]
42
+ prompt_len = input_ids.shape[1]
43
+
44
+ # Beam search: maintain multiple hypotheses
45
+ # Each hypothesis: (sequence, log_prob)
46
+ beams: List[Tuple[List[int], float]] = [(input_ids[0].tolist(), 0.0)]
47
+
48
+ for step in range(max_tokens):
49
+ candidates = []
50
+
51
+ for seq, log_prob in beams:
52
+ input_tensor = torch.tensor([seq], device=self.model.device)
53
+
54
+ # Get logits from model
55
+ with torch.no_grad():
56
+ outputs = self.model(input_tensor)
57
+ logits = outputs.logits[0, -1, :].clone()
58
+
59
+ # Create mask for forbidden tokens
60
+ forbidden_mask = torch.zeros_like(logits, dtype=torch.bool)
61
+ forbidden_mask[list(self.forbidden_token_ids)] = True
62
+
63
+ # Set forbidden tokens to a very negative value (safe for float16)
64
+ logits[forbidden_mask] = torch.finfo(logits.dtype).min / 2
65
+
66
+ # Convert to log probabilities
67
+ log_probs = F.log_softmax(logits, dim=-1)
68
+
69
+ # Ensure forbidden tokens stay at -inf in log space
70
+ log_probs[forbidden_mask] = -float('inf')
71
+
72
+ # Get top-k tokens for this beam, excluding -inf values
73
+ top_k = min(beam_width, (~forbidden_mask).sum().item())
74
+ if top_k > 0:
75
+ top_log_probs, top_indices = torch.topk(log_probs, top_k)
76
+ else:
77
+ # No valid tokens available, skip this beam
78
+ continue
79
+
80
+ for token_id, token_log_prob in zip(top_indices.tolist(), top_log_probs.tolist()):
81
+ if token_id == self.tokenizer.eos_token_id:
82
+ # Add as candidate with bonus for finishing
83
+ candidates.append((seq, log_prob + token_log_prob))
84
+ else:
85
+ candidates.append((seq + [token_id], log_prob + token_log_prob))
86
+
87
+ # Keep top beam_width candidates by log probability
88
+ candidates.sort(key=lambda x: x[1], reverse=True)
89
+ beams = candidates[:beam_width]
90
+
91
+ # Stop if all beams ended
92
+ if all(seq[-1] == self.tokenizer.eos_token_id for seq, _ in beams):
93
+ break
94
+
95
+ # Debug: print all beams
96
+ if self.debug:
97
+ print(f"\n[DEBUG Ex1] Total beams: {len(beams)}")
98
+ for i, (seq, log_prob) in enumerate(beams):
99
+ decoded = self.tokenizer.decode(seq, skip_special_tokens=True)
100
+ print(f" Beam {i}: log_prob={log_prob:.4f} | {decoded}")
101
+
102
+ # Return the best hypothesis
103
+ best_seq = beams[0][0]
104
+ return self.tokenizer.decode(best_seq, skip_special_tokens=True)
105
+
106
+
107
+ # --- EXERCISE 2: The Toulouse Sequence ---
108
+ class ToulouseSequence:
109
+ """
110
+ Generate text without ever using the word 'Toulouse'.
111
+ For this, you must use model() directly: model(input_ids) yields logits.
112
+ You need to manually adjust the logits to forbid the first token of 'Toulouse'.
113
+ REQUIREMENT: Do NOT use model.generate().
114
+ """
115
+ def __init__(self, model, tokenizer, debug=False):
116
+ self.model = model
117
+ self.tokenizer = tokenizer
118
+ self.debug = debug
119
+ # Pre-calculate forbidden first token of "Toulouse"
120
+ # Try with a space prefix to catch how it appears mid-sentence
121
+ toulouse_tokens = self.tokenizer.encode(" Toulouse", add_special_tokens=False)
122
+ toulouse_tokens_no_space = self.tokenizer.encode("Toulouse", add_special_tokens=False)
123
+
124
+ # Collect all possible first tokens
125
+ self.forbidden_tokens = set()
126
+ if toulouse_tokens:
127
+ self.forbidden_tokens.add(toulouse_tokens[0])
128
+ if toulouse_tokens_no_space:
129
+ self.forbidden_tokens.add(toulouse_tokens_no_space[0])
130
+
131
+ if self.debug:
132
+ print(f"Forbidden tokens for 'Toulouse': {self.forbidden_tokens}")
133
+ print(f" ' Toulouse' tokens: {toulouse_tokens}")
134
+ print(f" 'Toulouse' tokens: {toulouse_tokens_no_space}")
135
+
136
+ def __call__(self, prompt, max_tokens=20):
137
+ # Tokenize input prompt using chat template:
138
+ inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
139
+ input_ids = inputs["input_ids"]
140
+ prompt_length = input_ids.shape[1]
141
+
142
+ # Generate tokens one by one, forbidding the first token of "Toulouse"
143
+ seq = input_ids[0].tolist()
144
+
145
+ for step in range(max_tokens):
146
+ input_tensor = torch.tensor([seq], device=self.model.device)
147
+
148
+ # Get logits from model
149
+ with torch.no_grad():
150
+ outputs = self.model(input_tensor)
151
+ logits = outputs.logits[0, -1, :].clone()
152
+
153
+ # Forbid the first token of "Toulouse" (all variants)
154
+ for forbidden_token in self.forbidden_tokens:
155
+ logits[forbidden_token] = torch.finfo(logits.dtype).min / 2
156
+
157
+ # Apply temperature and sample instead of greedy
158
+ # logits = logits / 0.7 # temperature
159
+ # probs = torch.softmax(logits, dim=-1)
160
+ # next_token = torch.multinomial(probs, num_samples=1).item()
161
+
162
+ next_token = torch.argmax(logits).item() # Greedy decoding
163
+ seq.append(next_token)
164
+
165
+ # Extract only the generated tokens (skip the input prompt tokens)
166
+ generated_tokens = seq[prompt_length:]
167
+ generated_text = self.tokenizer.decode(generated_tokens, skip_special_tokens=True)
168
+ return generated_text.strip()
169
+
170
+ if __name__ == "__main__":
171
+ la_disparition_generator = LaDisparition(model, tokenizer)
172
+ print("Ex 1 (No 'e'):", la_disparition_generator("Who are you?"))
173
+ toulouse_sequence_generator = ToulouseSequence(model, tokenizer)
174
+ print("Ex 2 (No 'Toulouse'):", toulouse_sequence_generator("Where is the headquarters of Airbus located?"))
test_cases.json CHANGED
@@ -1,16 +1,16 @@
1
  {
2
  "exercise_1": [
3
  "Who is the king of the jungle?",
 
4
  "What is the opposite of 'always'?",
5
  "Name a fruit that is red.",
6
- "What do you use to see things?",
7
- "Complete this: Once upon a..."
8
  ],
9
  "exercise_2": [
10
  "Which French city is known as the 'Ville Rose'?",
11
  "Where is the headquarters of Airbus located?",
12
- "Name a major city in the Occitanie region.",
13
- "Where would you find the Place du Capitole?",
14
  "If you are at the Cité de l'Espace, which city are you in?"
15
  ]
16
  }
 
1
  {
2
  "exercise_1": [
3
  "Who is the king of the jungle?",
4
+ "Complete this: Once upon a...",
5
  "What is the opposite of 'always'?",
6
  "Name a fruit that is red.",
7
+ "What do you use to see things?"
 
8
  ],
9
  "exercise_2": [
10
  "Which French city is known as the 'Ville Rose'?",
11
  "Where is the headquarters of Airbus located?",
12
+ "Name a major city in the Occitanie region crossed by the Garonne River.",
13
+ "What French city is famous for its aerospace industry and has a historic basilica called Saint-Sernin?",
14
  "If you are at the Cité de l'Espace, which city are you in?"
15
  ]
16
  }