Views
No views yet
tuandunghcmut/Qwen25_Coder_MultipleChoice_v4, a model fine-tuned for multiple-choice coding questions.1# Install core dependencies
2pip install transformers torch
3
4# For faster inference (important)
5pip install unsloth bitsandbytes
6
7# Flash Attention (highly recommended for speed)
8pip install flash-attn --no-build-isolation
9
10# For dataset handling and YAML parsing
11pip install datasets.env file in the root directory with the following variables:# API Keys for authentication
OPENAI_API_KEY=your_openai_api_key_here
HF_TOKEN=your_huggingface_token_here
WANDB_API_KEY=your_wandb_api_key_here.env.example file and fill in your credentials:1cp .env.example .env
2# Edit the .env file with your actual API keysHF_TOKEN: Accessing Hugging Face models and datasetsWANDB_API_KEY: Logging experiments to Weights & BiasesOPENAI_API_KEY: Used if generating teacher completions with OpenAI models1class QwenModelHandler:
2 """Handler for Qwen models with inference and saving capabilities using Unsloth"""
3
4 def __init__(self, model_name="unsloth/Qwen2.5-7B", max_seq_length=768,
5 quantization=None, device_map="auto", cache_dir=None):
6 """
7 Initialize model and tokenizer using Unsloth
8
9 Args:
10 model_name: Name or path of the model (preferably an unsloth model)
11 max_seq_length: Maximum sequence length for the model
12 quantization: Quantization type (None, '4bit', '8bit') - for compatibility
13 device_map: Device mapping strategy
14 cache_dir: Cache directory for models
15 """1class PromptCreator:
2 """Creates and formats prompts for multiple choice questions"""
3
4 # Prompt types
5 BASIC = "basic" # Simple answer-only format
6 YAML_REASONING = "yaml" # YAML formatted reasoning
7 TEACHER_REASONED = "teacher" # Same YAML format but using teacher completions1class ResponseParser:
2 """Parser for model responses with support for different formats"""
3
4 # Parser modes
5 BASIC = "basic" # Extract single letter answer
6 YAML = "yaml" # Parse YAML formatted response with reasoning1class MultipleChoiceTester:
2 """Framework for testing Qwen models on multiple choice questions"""
3
4 def __init__(self, model_handler, prompt_creator=None):
5 """
6 Initialize with model handler and prompt configuration
7
8 Args:
9 model_handler: The QwenModelHandler instance
10 prompt_creator: Optional PromptCreator instance
11 """1class QwenModelHandler:
2 """Handler for Qwen models with inference and saving capabilities using Unsloth"""
3
4 def __init__(self, model_name="unsloth/Qwen2.5-7B", max_seq_length=768,
5 quantization=None, device_map="auto", cache_dir=None):
6 self.model_name = model_name
7 self.max_seq_length = max_seq_length
8 self.device_map = device_map
9 self.quantization = quantization
10 self.cache_dir = cache_dir
11
12 # Convert quantization parameter to load_in_4bit parameter for Unsloth
13 self.load_in_4bit = quantization == "4bit"
14
15 # Load tokenizer and model
16 self.tokenizer, self.model = self._load_model()
17 self.response_parser = ResponseParser()
18
19 def _load_model(self):
20 """Load model and tokenizer with Unsloth for optimization"""
21 from unsloth import FastLanguageModel
22 import torch
23
24 print(f"Loading {self.model_name} with Unsloth, max_seq_length={self.max_seq_length}")
25
26 # Set dtype based on hardware
27 dtype = None # None for auto detection
28
29 # Load model and tokenizer with Unsloth
30 model, tokenizer = FastLanguageModel.from_pretrained(
31 model_name=self.model_name,
32 max_seq_length=self.max_seq_length,
33 dtype=dtype,
34 load_in_4bit=self.load_in_4bit,
35 cache_dir=self.cache_dir,
36 )
37
38 return tokenizer, model
39
40 def generate_with_streaming(self, prompt, temperature=0.7, max_tokens=1024, stream=True):
41 """Generate completion with optional streaming using Unsloth's optimized inference"""
42 # Enable faster inference
43 from unsloth import FastLanguageModel
44 FastLanguageModel.for_inference(self.model)
45
46 # Format as chat
47 messages = [{"role": "user", "content": prompt}]
48 chat_text = self.tokenizer.apply_chat_template(
49 messages,
50 tokenize=False,
51 add_generation_prompt=True
52 )
53
54 # Tokenize input
55 model_inputs = self.tokenizer([chat_text], return_tensors="pt").to(self.model.device)
56
57 # Generate with streaming if requested
58 if stream:
59 from transformers import TextIteratorStreamer
60 import threading
61
62 # Set up streamer
63 streamer = TextIteratorStreamer(
64 self.tokenizer,
65 skip_prompt=True,
66 skip_special_tokens=True
67 )
68
69 # Start generation in a thread
70 generation_kwargs = {
71 "input_ids": model_inputs.input_ids,
72 "attention_mask": model_inputs.attention_mask,
73 "temperature": temperature,
74 "max_new_tokens": max_tokens,
75 "streamer": streamer,
76 "do_sample": temperature > 0.0,
77 "use_cache": True,
78 "min_p": 0.1 if temperature > 0.0 else None,
79 }
80
81 thread = threading.Thread(target=self.model.generate, kwargs=generation_kwargs)
82 thread.start()
83
84 return streamer
85 else:
86 # Generate without streaming
87 generated_ids = self.model.generate(
88 input_ids=model_inputs.input_ids,
89 attention_mask=model_inputs.attention_mask,
90 temperature=temperature,
91 max_new_tokens=max_tokens,
92 do_sample=temperature > 0.0,
93 use_cache=True,
94 min_p=0.1 if temperature > 0.0 else None,
95 )
96
97 # Decode the generated text
98 generated_text = self.tokenizer.decode(
99 generated_ids[0][model_inputs.input_ids.shape[1]:],
100 skip_special_tokens=True
101 )
102
103 return generated_text
104
105 def calculate_perplexity(self, prompt, answer, temperature=0.0):
106 """Calculate perplexity for a prompt and answer pair"""
107 import torch
108
109 # Format chat for perplexity calculation
110 messages = [
111 {"role": "user", "content": prompt},
112 {"role": "assistant", "content": answer}
113 ]
114 chat_text = self.tokenizer.apply_chat_template(
115 messages,
116 tokenize=False
117 )
118
119 # Tokenize the text
120 encodings = self.tokenizer(chat_text, return_tensors="pt").to(self.model.device)
121
122 # Calculate loss
123 with torch.no_grad():
124 outputs = self.model(**encodings, labels=encodings.input_ids)
125
126 # Get loss and calculate perplexity
127 neg_log_likelihood = outputs.loss.item()
128 perplexity = torch.exp(torch.tensor(neg_log_likelihood)).item()
129
130 return perplexity
131
132 def save_model(self, output_dir, save_method="lora"):
133 """Save model to disk using Unsloth's optimized methods"""
134 import os
135
136 os.makedirs(output_dir, exist_ok=True)
137
138 # Use Unsloth's saving methods
139 if save_method == "lora":
140 self.model.save_pretrained(output_dir)
141 self.tokenizer.save_pretrained(output_dir)
142 elif save_method == "merged_16bit":
143 self.model.save_pretrained_merged(output_dir, self.tokenizer, save_method="merged_16bit")
144 elif save_method == "merged_4bit":
145 self.model.save_pretrained_merged(output_dir, self.tokenizer, save_method="merged_4bit")
146 elif save_method == "gguf":
147 self.model.save_pretrained_gguf(output_dir, self.tokenizer, quantization_method="q4_k_m")
148 else:
149 raise ValueError(f"Unknown save method: {save_method}")
150
151 print(f"Model saved to {output_dir} using method {save_method}")
152 return output_dir
153
154 def push_to_hub(self, repo_id, token=None, save_method="lora", private=False):
155 """Push model to Hugging Face Hub using Unsloth's optimized methods"""
156 if save_method == "lora":
157 self.model.push_to_hub_merged(repo_id, self.tokenizer, save_method="lora", token=token)
158 elif save_method == "merged_16bit":
159 self.model.push_to_hub_merged(repo_id, self.tokenizer, save_method="merged_16bit", token=token)
160 elif save_method == "merged_4bit":
161 self.model.push_to_hub_merged(repo_id, self.tokenizer, save_method="merged_4bit", token=token)
162 elif save_method == "gguf":
163 self.model.push_to_hub_gguf(
164 repo_id,
165 self.tokenizer,
166 quantization_method=["q4_k_m", "q5_k_m"],
167 token=token
168 )
169 else:
170 raise ValueError(f"Unknown save method: {save_method}")
171
172 print(f"Model successfully pushed to: https://huggingface.co/{repo_id}")
173 return f"https://huggingface.co/{repo_id}"1class PromptCreator:
2 """Creates and formats prompts for multiple choice questions"""
3
4 # Prompt types
5 BASIC = "basic" # Simple answer-only format
6 YAML_REASONING = "yaml" # YAML formatted reasoning
7 TEACHER_REASONED = "teacher" # Same YAML format but using teacher completions
8
9 def __init__(self, prompt_type=BASIC):
10 if prompt_type == self.TEACHER_REASONED:
11 prompt_type = self.YAML_REASONING
12 self.prompt_type = prompt_type
13 self.original_type = prompt_type
14
15 def format_choices(self, choices):
16 """Format choices as a lettered list"""
17 return "\n".join(
18 [f"{chr(65 + i)}. {choice}" for i, choice in enumerate(choices)]
19 )
20
21 def get_max_letter(self, choices):
22 """Get the maximum letter based on number of choices"""
23 return chr(65 + len(choices) - 1)
24
25 def create_inference_prompt(self, question, choices):
26 """Create a prompt for inference based on current prompt type"""
27 formatted_choices = self.format_choices(choices)
28 max_letter = self.get_max_letter(choices)
29
30 if self.prompt_type == self.YAML_REASONING:
31 return self._create_yaml_prompt(question, formatted_choices, max_letter)
32 else:
33 return self._create_basic_prompt(question, formatted_choices, max_letter)
34
35 def _create_basic_prompt(self, question, formatted_choices, max_letter):
36 """Create a basic prompt asking for just the answer letter"""
37 return f"""
38QUESTION:
39{question}
40
41CHOICES:
42{formatted_choices}
43
44Answer with a single letter from A through {max_letter} without any additional explanation or commentary.
45"""
46
47 def _create_yaml_prompt(self, question, formatted_choices, max_letter):
48 """Create a prompt requesting YAML-formatted reasoning"""
49 return f"""
50QUESTION:
51{question}
52
53CHOICES:
54{formatted_choices}
55
56Analyze this question step-by-step and provide a detailed explanation.
57Your response MUST be in YAML format as follows:
58
59understanding: |
60 <your understanding of what the question is asking>
61analysis: |
62 <your analysis of each option>
63reasoning: |
64 <your step-by-step reasoning process>
65conclusion: |
66 <your final conclusion>
67answer: <single letter A through {max_letter}>
68
69The answer field MUST contain ONLY a single character letter.
70"""
71
72 def create_training_prompt(self, question, choices):
73 """Create a prompt for training with the current prompt type"""
74 formatted_choices = self.format_choices(choices)
75 max_letter = self.get_max_letter(choices)
76
77 if self.prompt_type == self.YAML_REASONING:
78 return self._create_yaml_training_prompt(
79 question, formatted_choices, max_letter
80 )
81 else:
82 return self._create_basic_training_prompt(
83 question, formatted_choices, max_letter
84 )
85
86 def _create_basic_training_prompt(self, question, formatted_choices, max_letter):
87 """Create a basic training prompt"""
88 return f"""
89QUESTION:
90{question}
91
92CHOICES:
93{formatted_choices}
94
95The answer is a single letter (A, B, C, etc.). Only provide ONE character as your answer:
96"""
97
98 def _create_yaml_training_prompt(self, question, formatted_choices, max_letter):
99 """Create a YAML-formatted training prompt"""
100 return f"""
101QUESTION:
102{question}
103
104CHOICES:
105{formatted_choices}
106
107Analyze this question step-by-step and provide a detailed explanation.
108Follow the YAML format in your response:
109
110understanding: |
111 <your understanding of the question>
112analysis: |
113 <your analysis of each option>
114reasoning: |
115 <your reasoning about the correct answer>
116conclusion: |
117 <your final conclusion>
118answer: <single letter A through {max_letter}>
119"""
120
121 def set_prompt_type(self, prompt_type):
122 """Set the prompt type"""
123 self.original_type = prompt_type
124 if prompt_type == self.TEACHER_REASONED:
125 pass
126 self.prompt_type = prompt_type
127 return self
128
129 def is_teacher_mode(self):
130 """Check if we're using teacher mode"""
131 return self.original_type == self.TEACHER_REASONED1class ResponseParser:
2 """Parser for model responses with support for different formats"""
3
4 # Parser modes
5 BASIC = "basic" # Extract single letter answer
6 YAML = "yaml" # Parse YAML formatted response with reasoning
7
8 def __init__(self, parser_mode=BASIC):
9 self.parser_mode = parser_mode
10
11 def parse(self, response_text):
12 """Parse the model's response according to the current mode"""
13 if self.parser_mode == self.YAML:
14 return self._parse_yaml_response(response_text)
15 else:
16 return self._parse_basic_response(response_text)
17
18 def _parse_basic_response(self, response_text):
19 """Parse basic response looking for a letter answer"""
20 import re
21
22 # Try to extract a single letter answer (A-Z)
23 answer_match = re.search(r"(?:^|\s)([A-Z])(?:\s|$|\.)", response_text)
24 if answer_match:
25 answer = answer_match.group(1)
26 else:
27 # Take first character if it's a letter
28 if response_text and response_text[0].isalpha():
29 answer = response_text[0].upper()
30 else:
31 answer = None
32
33 # For basic mode, we don't extract detailed reasoning
34 reasoning = ""
35
36 return answer, reasoning
37
38 def _parse_yaml_response(self, response_text):
39 """Parse YAML formatted response extracting answer and reasoning"""
40 import re
41 import yaml
42
43 # First try to find answer in YAML format
44 yaml_match = re.search(r"answer:\s*([A-Z])", response_text)
45 if yaml_match:
46 answer = yaml_match.group(1)
47 else:
48 # Fall back to basic extraction if YAML parsing fails
49 answer_match = re.search(r"(?:^|\s)([A-Z])(?:\s|$|\.)", response_text)
50 if answer_match:
51 answer = answer_match.group(1)
52 elif response_text and response_text[0].isalpha():
53 answer = response_text[0].upper()
54 else:
55 answer = None
56
57 # Try to parse reasoning from YAML format
58 reasoning = ""
59 if "reasoning:" in response_text:
60 yaml_content = yaml.safe_load("---\n" + response_text)
61 if isinstance(yaml_content, dict) and "reasoning" in yaml_content:
62 reasoning = yaml_content["reasoning"]
63
64 # Add other YAML fields if available
65 if "understanding" in yaml_content:
66 reasoning = f"Understanding: {yaml_content['understanding']}\n\n{reasoning}"
67 if "conclusion" in yaml_content:
68 reasoning = f"{reasoning}\n\nConclusion: {yaml_content['conclusion']}"
69 else:
70 # Use the full response as reasoning if not in YAML format
71 reasoning = response_text
72
73 return answer, reasoning
74
75 def set_parser_mode(self, parser_mode):
76 """Set the parser mode"""
77 self.parser_mode = parser_mode
78 return self
79
80 @classmethod
81 def from_prompt_type(cls, prompt_type):
82 """Create a parser instance with mode matching the prompt type"""
83 if prompt_type == PromptCreator.YAML_REASONING or prompt_type == PromptCreator.TEACHER_REASONED:
84 return cls(parser_mode=cls.YAML)
85 else:
86 return cls(parser_mode=cls.BASIC)1class MultipleChoiceTester:
2 """Framework for testing Qwen models on multiple choice questions"""
3
4 def __init__(self, model_handler, prompt_creator=None):
5 self.model_handler = model_handler
6 self.prompt_creator = prompt_creator or PromptCreator(PromptCreator.BASIC)
7 self.response_parser = ResponseParser.from_prompt_type(self.prompt_creator.prompt_type)
8
9 def infer_example(self, example, temperature=0.7, max_tokens=1024, prompt_type=None, stream=False):
10 """Inference on a single example for visualization/demonstration"""
11 # Allow temporary override of prompt type
12 original_prompt_type = None
13 if prompt_type is not None:
14 original_prompt_type = self.prompt_creator.prompt_type
15 self.prompt_creator.set_prompt_type(prompt_type)
16 self.response_parser = ResponseParser.from_prompt_type(prompt_type)
17
18 # Prepare data
19 question = example["question"]
20
21 # Handle different formats of choices
22 if isinstance(example["choices"], list):
23 choices = example["choices"]
24 elif isinstance(example["choices"], str) and example["choices"].startswith("["):
25 import ast
26 choices = ast.literal_eval(example["choices"]) if "[" in example["choices"] else example["choices"].split(",")
27 else:
28 choices = str(example["choices"]).split(",")
29
30 # Generate the prompt using prompt creator
31 prompt = self.prompt_creator.create_inference_prompt(question, choices)
32
33 # Start timing
34 start_time = time.time()
35
36 if stream:
37 # Use streaming generation
38 streamer = self.model_handler.generate_with_streaming(
39 prompt=prompt,
40 temperature=temperature,
41 max_tokens=max_tokens,
42 stream=True
43 )
44
45 # Collect output from streamer
46 raw_response = ""
47 print("Model response:")
48 for text_chunk in streamer:
49 print(text_chunk, end="", flush=True)
50 raw_response += text_chunk
51 print("\n")
52 else:
53 # Generate without streaming
54 raw_response = self.model_handler.generate_with_streaming(
55 prompt=prompt,
56 temperature=temperature,
57 max_tokens=max_tokens,
58 stream=False
59 )
60
61 response_time = time.time() - start_time
62
63 # Parse the response using the response parser
64 predicted_answer, reasoning = self.response_parser.parse(raw_response)
65
66 # Prepare results
67 result = {
68 "question": question,
69 "choices": choices,
70 "predicted_answer": predicted_answer,
71 "reasoning": reasoning,
72 "response_time": response_time,
73 "raw_response": raw_response,
74 "prompt_type": self.prompt_creator.prompt_type,
75 }
76
77 # Add task_id if available
78 if "task_id" in example:
79 result["task_id"] = example["task_id"]
80
81 # Calculate metrics if label is provided
82 if "answer" in example:
83 label = example["answer"]
84 result["correct_answer"] = label
85 result["is_correct"] = predicted_answer == label
86
87 # Calculate perplexity if requested
88 if hasattr(self.model_handler, "calculate_perplexity"):
89 perplexity = self.model_handler.calculate_perplexity(prompt, raw_response)
90 result["perplexity"] = perplexity
91
92 # Restore original prompt type if it was overridden
93 if original_prompt_type is not None:
94 self.prompt_creator.set_prompt_type(original_prompt_type)
95 self.response_parser = ResponseParser.from_prompt_type(original_prompt_type)
96
97 return result
98
99 def infer_batch(self, examples, temperature=0.7, max_tokens=1024, prompt_type=None, batch_size=4):
100 """Inference on a batch of examples"""
101 # Allow temporary override of prompt type
102 original_prompt_type = None
103 if prompt_type is not None:
104 original_prompt_type = self.prompt_creator.prompt_type
105 self.prompt_creator.set_prompt_type(prompt_type)
106 self.response_parser = ResponseParser.from_prompt_type(prompt_type)
107
108 # Prepare all prompts
109 prompts = []
110 metadata = []
111
112 for i, example in enumerate(examples):
113 # Extract data
114 question = example["question"]
115
116 # Handle different formats of choices
117 if isinstance(example["choices"], list):
118 choices = example["choices"]
119 elif isinstance(example["choices"], str) and example["choices"].startswith("["):
120 import ast
121 choices = ast.literal_eval(example["choices"]) if "[" in example["choices"] else example["choices"].split(",")
122 else:
123 choices = str(example["choices"]).split(",")
124
125 # Generate the prompt using prompt creator
126 prompt = self.prompt_creator.create_inference_prompt(question, choices)
127 prompts.append(prompt)
128
129 # Store metadata for later
130 meta = {
131 "question": question,
132 "choices": choices,
133 "index": i,
134 }
135
136 # Add label if available
137 if "answer" in example:
138 meta["label"] = example["answer"]
139
140 if "task_id" in example:
141 meta["task_id"] = example["task_id"]
142
143 metadata.append(meta)
144
145 # Process in batches
146 results = []
147 correct_count = 0
148 total_count = 0
149 perplexities = []
150
151 for i in range(0, len(prompts), batch_size):
152 batch_prompts = prompts[i:i+batch_size]
153 batch_meta = metadata[i:i+batch_size]
154
155 # Process batch
156 start_time = time.time()
157 batch_responses = []
158
159 for prompt in batch_prompts:
160 response = self.model_handler.generate_with_streaming(
161 prompt=prompt,
162 temperature=temperature,
163 max_tokens=max_tokens,
164 stream=False
165 )
166 batch_responses.append(response)
167
168 batch_time = time.time() - start_time
169
170 # Process each response in the batch
171 for j, (response, meta) in enumerate(zip(batch_responses, batch_meta)):
172 # Parse response
173 predicted_answer, reasoning = self.response_parser.parse(response)
174
175 # Create result
176 result = {
177 "question": meta["question"],
178 "choices": meta["choices"],
179 "predicted_answer": predicted_answer,
180 "reasoning": reasoning,
181 "raw_response": response,
182 "prompt_type": self.prompt_creator.prompt_type,
183 "response_time": batch_time / len(batch_prompts),
184 }
185
186 # Add task_id if available
187 if "task_id" in meta:
188 result["task_id"] = meta["task_id"]
189
190 # Add metrics if label available
191 if "label" in meta:
192 label = meta["label"]
193 result["correct_answer"] = label
194 result["is_correct"] = predicted_answer == label
195
196 # Update counts for accuracy
197 total_count += 1
198 if result["is_correct"]:
199 correct_count += 1
200
201 # Calculate perplexity if possible
202 if hasattr(self.model_handler, "calculate_perplexity"):
203 prompt = batch_prompts[j]
204 perplexity = self.model_handler.calculate_perplexity(prompt, response)
205 result["perplexity"] = perplexity
206 perplexities.append(perplexity)
207
208 results.append(result)
209
210 # Calculate aggregate metrics
211 summary_metrics = {}
212 if total_count > 0:
213 summary_metrics["accuracy"] = correct_count / total_count
214 summary_metrics["correct_count"] = correct_count
215 summary_metrics["total_count"] = total_count
216
217 if perplexities:
218 summary_metrics["avg_perplexity"] = sum(perplexities) / len(perplexities)
219 summary_metrics["min_perplexity"] = min(perplexities)
220 summary_metrics["max_perplexity"] = max(perplexities)
221
222 # Restore original prompt type if it was overridden
223 if original_prompt_type is not None:
224 self.prompt_creator.set_prompt_type(original_prompt_type)
225 self.response_parser = ResponseParser.from_prompt_type(original_prompt_type)
226
227 return results, summary_metrics
228
229 def evaluate_dataset(self, dataset, temperature=0.7, max_tokens=1024, num_examples=None,
230 verbose=True, prompt_type=None, batch_size=4, log_to_wandb=False):
231 """Inference on a whole dataset with metrics calculation"""
232 # Allow overriding the prompt type for this evaluation
233 original_prompt_type = self.prompt_creator.prompt_type
234 if prompt_type is not None:
235 self.prompt_creator.set_prompt_type(prompt_type)
236 self.response_parser = ResponseParser.from_prompt_type(prompt_type)
237
238 # Select subset if specified
239 if num_examples is not None:
240 dataset = dataset.select(range(min(num_examples, len(dataset))))
241
242 results = []
243 correct_count = 0
244 total_count = 0
245 perplexities = []
246
247 # Process examples in batches
248 for i in range(0, len(dataset), batch_size):
249 batch_examples = dataset[i:i+batch_size]
250
251 if verbose:
252 batch_desc = f"Batch {i//batch_size + 1}/{(len(dataset) + batch_size - 1) // batch_size}"
253 print(f"\nProcessing {batch_desc} with {len(batch_examples)} examples...")
254
255 # Infer batch
256 batch_results, batch_metrics = self.infer_batch(
257 examples=batch_examples,
258 temperature=temperature,
259 max_tokens=max_tokens,
260 batch_size=batch_size
261 )
262
263 # Update metrics
264 results.extend(batch_results)
265 if "correct_count" in batch_metrics:
266 correct_count += batch_metrics["correct_count"]
267 total_count += batch_metrics["total_count"]
268
269 if verbose:
270 batch_accuracy = batch_metrics["accuracy"]
271 overall_accuracy = correct_count / total_count
272 print(f"Batch accuracy: {batch_accuracy:.2%}, Overall: {overall_accuracy:.2%} ({correct_count}/{total_count})")
273
274 # Collect perplexities
275 if "avg_perplexity" in batch_metrics:
276 for result in batch_results:
277 if "perplexity" in result:
278 perplexities.append(result["perplexity"])
279
280 # Calculate final accuracy
281 accuracy = correct_count / total_count if total_count > 0 else 0.0
282
283 if verbose:
284 prompt_type_str = self.prompt_creator.prompt_type
285 print(f"\nFinal accuracy with {prompt_type_str} prompts: {accuracy:.2%} ({correct_count}/{total_count})")
286 if perplexities:
287 avg_perplexity = sum(perplexities) / len(perplexities)
288 print(f"Average perplexity: {avg_perplexity:.4f}")
289
290 # Prepare comprehensive summary
291 summary = {
292 "accuracy": accuracy,
293 "correct_count": correct_count,
294 "total_count": total_count,
295 "prompt_type": self.prompt_creator.prompt_type,
296 "results": results,
297 }
298
299 # Add perplexity metrics if available
300 if perplexities:
301 summary["avg_perplexity"] = sum(perplexities) / len(perplexities)
302 summary["min_perplexity"] = min(perplexities)
303 summary["max_perplexity"] = max(perplexities)
304
305 # Log results to wandb if requested
306 if log_to_wandb and wandb.run is not None:
307 metrics = {
308 "test/accuracy": accuracy,
309 "test/correct_count": correct_count,
310 "test/total_count": total_count,
311 }
312 if perplexities:
313 metrics["test/avg_perplexity"] = summary["avg_perplexity"]
314 metrics["test/min_perplexity"] = summary["min_perplexity"]
315 metrics["test/max_perplexity"] = summary["max_perplexity"]
316
317 wandb.log(metrics)
318
319 # Create a table of results for visualization if task_id exists
320 if "task_id" in dataset.features:
321 columns = ["task_id", "question", "correct_answer", "predicted_answer", "is_correct"]
322 table = wandb.Table(columns=columns)
323
324 for res in results[:min(100, len(results))]:
325 table.add_data(
326 res.get("task_id", "unknown"),
327 res["question"][:100] + "...",
328 res.get("correct_answer", ""),
329 res.get("predicted_answer", ""),
330 res.get("is_correct", False)
331 )
332
333 wandb.log({"test_samples": table})
334
335 # Restore original prompt type
336 self.prompt_creator.set_prompt_type(original_prompt_type)
337 self.response_parser = ResponseParser.from_prompt_type(original_prompt_type)
338
339 return summary
340
341 def save_results(self, results, output_dir="./results"):
342 """Save evaluation results to file"""
343 os.makedirs(output_dir, exist_ok=True)
344
345 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
346 results_file = os.path.join(output_dir, f"results_{timestamp}.json")
347
348 # Create serializable results
349 serializable_results = {
350 "accuracy": results.get("accuracy", 0.0),
351 "correct_count": results.get("correct_count", 0),
352 "total_count": results.get("total_count", 0),
353 "timestamp": timestamp,
354 "prompt_type": results.get("prompt_type", "unknown"),
355 }
356
357 # Add perplexity metrics if available
358 if "avg_perplexity" in results:
359 serializable_results["avg_perplexity"] = results["avg_perplexity"]
360 serializable_results["min_perplexity"] = results["min_perplexity"]
361 serializable_results["max_perplexity"] = results["max_perplexity"]
362
363 # Process individual results
364 serializable_results["individual_results"] = []
365 for result in results["results"]:
366 # Skip perplexity in individual results to save space
367 result_copy = result.copy()
368 if "perplexity" in result_copy:
369 del result_copy["perplexity"]
370
371 # Convert choices if needed
372 choices = result_copy["choices"]
373 if not isinstance(choices, list):
374 try:
375 import ast
376 result_copy["choices"] = ast.literal_eval(choices)
377 except (SyntaxError, ValueError):
378 pass
379
380 serializable_results["individual_results"].append(result_copy)
381
382 # Save to file
383 with open(results_file, "w") as f:
384 import json
385 json.dump(serializable_results, f, indent=2)
386
387 print(f"Results saved to {results_file}")
388 return results_file1from transformers import AutoModelForCausalLM, AutoTokenizer
2import torch
3
4# Load the model and tokenizer
5model_id = "tuandunghcmut/Qwen25_Coder_MultipleChoice"
6tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
7model = AutoModelForCausalLM.from_pretrained(
8 model_id,
9 torch_dtype=torch.bfloat16,
10 device_map="auto",
11 trust_remote_code=True
12)
13
14# Example question
15question = "What is the correct way to open a file in Python for reading?"
16choices = [
17 "open('file.txt', 'r')",
18 "file.open('file.txt', 'read')",
19 "read('file.txt')",
20 "File.open('file.txt')"
21]
22
23# Format the prompt
24prompt = f"""
25QUESTION:
26{question}
27
28CHOICES:
29{chr(65 + i)}. {choice}
30for i, choice in enumerate(choices)}
31
32Answer with a single letter from A through {chr(65 + len(choices) - 1)} without any additional explanation or commentary.
33"""
34
35# Generate response
36inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
37outputs = model.generate(**inputs, max_new_tokens=10)
38response = tokenizer.decode(outputs[0], skip_special_tokens=True)
39
40print(f"Model's answer: {response}")MultipleChoiceTester framework:1from save import QwenModelHandler, MultipleChoiceTester, PromptCreator
2
3# Initialize the model handler
4model_handler = QwenModelHandler(
5 model_name="tuandunghcmut/Qwen25_Coder_MultipleChoice",
6 max_seq_length=2048,
7 quantization="4bit",
8 device_map="auto"
9)
10
11# Create a prompt creator with YAML reasoning format
12prompt_creator = PromptCreator(PromptCreator.YAML_REASONING)
13
14# Initialize the tester
15tester = MultipleChoiceTester(model_handler, prompt_creator=prompt_creator)
16
17# Example question
18example = {
19 "question": "What is the correct way to open a file in Python for reading?",
20 "choices": [
21 "open('file.txt', 'r')",
22 "file.open('file.txt', 'read')",
23 "read('file.txt')",
24 "File.open('file.txt')"
25 ],
26 "answer": "A" # Optional ground truth
27}
28
29# Get prediction with reasoning
30result = tester.infer_example(example, temperature=0.0001, stream=True)
31print(f"Predicted answer: {result['predicted_answer']}")
32print("Reasoning:")
33print(result['reasoning'])1# List of examples
2examples = [
3 {
4 "question": "What is the correct way to open a file in Python for reading?",
5 "choices": ["open('file.txt', 'r')", "file.open('file.txt', 'read')", "read('file.txt')", "File.open('file.txt')"],
6 "answer": "A"
7 },
8 # Add more examples...
9]
10
11# Process batch
12results, metrics = tester.infer_batch(examples, batch_size=4)
13print(f"Batch accuracy: {metrics['accuracy']:.2%}")1# Initialize model handler and tester as before
2model_handler = QwenModelHandler(
3 model_name="tuandunghcmut/Qwen25_Coder_MultipleChoice",
4 max_seq_length=2048
5)
6tester = MultipleChoiceTester(model_handler)
7
8# Example with streaming
9example = {
10 "question": "Which Python method is used to remove whitespace from both ends of a string?",
11 "choices": [
12 "strip()",
13 "trim()",
14 "clean()",
15 "remove_whitespace()"
16 ],
17 "answer": "A"
18}
19
20# Enable streaming with stream=True
21result = tester.infer_example(
22 example,
23 temperature=0.0001,
24 max_tokens=1024,
25 stream=True # Enable streaming
26)
27
28# The output will be printed in real-time as the model generates it
29# You can also access the complete response after generation
30print("\nFinal result:")
31print(f"Predicted answer: {result['predicted_answer']}")
32print("Complete reasoning:")
33print(result['reasoning'])1def process_stream(streamer):
2 """Custom stream processing function"""
3 collected_text = ""
4 for chunk in streamer:
5 # Process each chunk as it arrives
6 collected_text += chunk
7 # You can do custom processing here
8 # For example, parse partial YAML, update UI, etc.
9 yield chunk, collected_text
10
11# Use custom stream processing
12result = tester.infer_example(
13 example,
14 temperature=0.0001,
15 stream=True
16)
17
18# Process the stream with custom logic
19for chunk, full_text in process_stream(result['stream']):
20 # Do something with each chunk
21 print(f"Chunk: {chunk}")
22 print(f"Full text so far: {full_text}")1import yaml
2from io import StringIO
3
4def parse_yaml_stream(streamer):
5 """Parse YAML content as it streams"""
6 buffer = StringIO()
7 for chunk in streamer:
8 buffer.write(chunk)
9 try:
10 # Try to parse the current buffer as YAML
11 yaml_content = yaml.safe_load(buffer.getvalue())
12 if yaml_content:
13 yield chunk, yaml_content
14 except yaml.YAMLError:
15 # Not enough content for valid YAML yet
16 continue
17
18# Use YAML streaming with parsing
19result = tester.infer_example(
20 example,
21 temperature=0.0001,
22 prompt_type=PromptCreator.YAML_REASONING,
23 stream=True
24)
25
26# Process YAML content as it streams
27for chunk, yaml_content in parse_yaml_stream(result['stream']):
28 if isinstance(yaml_content, dict):
29 # Access YAML fields as they become available
30 if 'understanding' in yaml_content:
31 print(f"Understanding: {yaml_content['understanding']}")
32 if 'reasoning' in yaml_content:
33 print(f"Reasoning: {yaml_content['reasoning']}")
34 if 'answer' in yaml_content:
35 print(f"Answer: {yaml_content['answer']}")1import time
2
3def stream_with_progress(streamer):
4 """Stream with progress tracking"""
5 start_time = time.time()
6 tokens_generated = 0
7
8 for chunk in streamer:
9 tokens_generated += len(chunk.split())
10 elapsed = time.time() - start_time
11 tokens_per_second = tokens_generated / elapsed if elapsed > 0 else 0
12
13 yield {
14 'chunk': chunk,
15 'tokens': tokens_generated,
16 'tokens_per_second': tokens_per_second,
17 'elapsed': elapsed
18 }
19
20# Use streaming with progress tracking
21result = tester.infer_example(
22 example,
23 temperature=0.0001,
24 stream=True
25)
26
27for progress in stream_with_progress(result['stream']):
28 print(f"Generated {progress['tokens']} tokens "
29 f"({progress['tokens_per_second']:.2f} tokens/sec)")
30 print(f"Chunk: {progress['chunk']}")FastLanguageModel for optimized inferenceTextIteratorStreameruse_cache=True for faster generationmin_p sampling for better quality