dataopsnick commited on
Commit
763a9f6
·
verified ·
1 Parent(s): f580dce

Create infer.py

Browse files

Extract infer.py from the model card (for readability)

Files changed (1) hide show
  1. infer.py +437 -0
infer.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ADAPT-DIFF Inference & Benchmark Script
3
+ Downloads 'dataopsnick/adapt-diff-qwen-0.8b' and compares it with 'Qwen/Qwen3.5-0.8B'.
4
+ """
5
+
6
+ import os
7
+ import gc
8
+ import time
9
+ import re
10
+ from collections import defaultdict
11
+ import torch
12
+ import torch.nn as nn
13
+ import torch.nn.functional as F
14
+
15
+ # 1. Install/Update Dependencies
16
+ print("Ensuring dependencies are installed...")
17
+ os.system("pip install -q transformers>=4.40.0 datasets>=2.18.0 accelerate>=0.29.0 huggingface_hub")
18
+
19
+ import transformers
20
+ from transformers import AutoTokenizer, AutoConfig, AutoModel, AutoModelForCausalLM
21
+ from transformers.cache_utils import DynamicCache
22
+ from transformers.modeling_outputs import BaseModelOutputWithPast
23
+ from transformers.modeling_attn_mask_utils import _prepare_4d_attention_mask
24
+ from datasets import load_dataset
25
+ from huggingface_hub import hf_hub_download
26
+
27
+ # Clean up GPU cache before running
28
+ gc.collect()
29
+ torch.cuda.empty_cache()
30
+
31
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
32
+ BASE_MODEL_ID = "Qwen/Qwen3.5-0.8B"
33
+ ADAPT_DIFF_ID = "dataopsnick/adapt-diff-qwen-0.8b"
34
+
35
+ print(f"Loading {BASE_MODEL_ID} metadata to dynamically resolve architecture classes...")
36
+ src_tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_ID)
37
+ if src_tokenizer.pad_token is None:
38
+ src_tokenizer.pad_token = src_tokenizer.eos_token
39
+
40
+ # Load temporary instance to resolve base classes exactly as in your environment
41
+ temp_model = AutoModelForCausalLM.from_pretrained(
42
+ BASE_MODEL_ID,
43
+ torch_dtype=torch.bfloat16,
44
+ device_map="cpu"
45
+ )
46
+ src_config = temp_model.config
47
+
48
+ BaseConfig = src_config.__class__
49
+ BaseModel = temp_model.model.__class__
50
+ BaseCausalLM = temp_model.__class__
51
+
52
+ BasePreTrainedModel = next(
53
+ (cls for cls in BaseCausalLM.__mro__ if cls.__name__.endswith("PreTrainedModel")),
54
+ None
55
+ )
56
+ if BasePreTrainedModel is None:
57
+ BasePreTrainedModel = BaseCausalLM.__bases__[0]
58
+
59
+ # Free temporary model memory
60
+ del temp_model
61
+ gc.collect()
62
+
63
+
64
+ # ==============================================================================
65
+ # Custom ADAPT-DIFF Architecture Classes
66
+ # ==============================================================================
67
+ class A2DQwenConfig(BaseConfig):
68
+ model_type = "a2d-qwen"
69
+
70
+ class A2DQwenModel(BaseModel):
71
+ def forward(
72
+ self,
73
+ input_ids = None,
74
+ attention_mask = None,
75
+ position_ids = None,
76
+ past_key_values = None,
77
+ inputs_embeds = None,
78
+ use_cache = None,
79
+ cache_position = None,
80
+ **kwargs,
81
+ ):
82
+ if (input_ids is None) ^ (inputs_embeds is not None):
83
+ raise ValueError("Specify exactly one of input_ids or inputs_embeds")
84
+
85
+ if inputs_embeds is None:
86
+ inputs_embeds = self.embed_tokens(input_ids)
87
+
88
+ if use_cache and past_key_values is None:
89
+ past_key_values = DynamicCache(config=self.config)
90
+
91
+ if cache_position is None:
92
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
93
+ cache_position = torch.arange(
94
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
95
+ )
96
+
97
+ if position_ids is None:
98
+ position_ids = cache_position.unsqueeze(0)
99
+
100
+ # Core ADAPT-DIFF modification: replace causal mask with bidirectional/padding-only mask
101
+ if not isinstance(causal_mask_mapping := attention_mask, dict):
102
+ if attention_mask is None:
103
+ attention_mask = torch.ones(
104
+ inputs_embeds.shape[:2], device=inputs_embeds.device, dtype=torch.long
105
+ )
106
+ if not (isinstance(attention_mask, torch.Tensor) and attention_mask.ndim == 4):
107
+ attention_mask = _prepare_4d_attention_mask(attention_mask, self.dtype)
108
+ causal_mask_mapping = defaultdict(lambda: attention_mask)
109
+
110
+ hidden_states = inputs_embeds
111
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
112
+
113
+ for decoder_layer in self.layers[: self.config.num_hidden_layers]:
114
+ attn_type = getattr(decoder_layer, "attention_type", "self_attn")
115
+ hidden_states = decoder_layer(
116
+ hidden_states,
117
+ attention_mask=causal_mask_mapping[attn_type],
118
+ position_ids=position_ids,
119
+ past_key_values=past_key_values,
120
+ use_cache=use_cache,
121
+ cache_position=cache_position,
122
+ position_embeddings=position_embeddings,
123
+ **kwargs,
124
+ )
125
+
126
+ hidden_states = self.norm(hidden_states)
127
+ return BaseModelOutputWithPast(
128
+ last_hidden_state=hidden_states,
129
+ past_key_values=past_key_values if use_cache else None,
130
+ )
131
+
132
+ class A2DQwenLMHeadModel(BaseCausalLM):
133
+ config_class = A2DQwenConfig
134
+ def __init__(self, config):
135
+ BasePreTrainedModel.__init__(self, config)
136
+ self.model = A2DQwenModel(config)
137
+ self.vocab_size = config.vocab_size
138
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
139
+ self.post_init()
140
+
141
+
142
+ # Register custom classes with Hugging Face AutoClasses
143
+ transformers.AutoConfig.register("a2d-qwen", A2DQwenConfig)
144
+ transformers.AutoModel.register(A2DQwenConfig, A2DQwenLMHeadModel)
145
+ transformers.AutoModelForCausalLM.register(A2DQwenConfig, A2DQwenLMHeadModel)
146
+
147
+
148
+ # ==============================================================================
149
+ # Custom Projection and Search Pipeline Components
150
+ # ==============================================================================
151
+ class StackedLDMHeads(nn.Module):
152
+ def __init__(self, hidden_size, vocab_size, block_size=12):
153
+ super().__init__()
154
+ self.block_size = block_size
155
+ self.proj = nn.Linear(hidden_size, block_size * hidden_size, dtype=torch.bfloat16)
156
+ self.head = nn.Linear(hidden_size, vocab_size, dtype=torch.bfloat16)
157
+
158
+ def forward(self, hidden_states):
159
+ batch_size, seq_len, hidden_size = hidden_states.shape
160
+ forecast = self.proj(hidden_states)
161
+ forecast = forecast.view(batch_size, seq_len, self.block_size, hidden_size)
162
+ logits = self.head(forecast)
163
+ return logits
164
+
165
+ class LogitUncertaintyFilter(nn.Module):
166
+ def compute_entropy(self, logits: torch.Tensor) -> torch.Tensor:
167
+ probs = F.softmax(logits.float(), dim=-1)
168
+ entropy = -torch.sum(probs * torch.log(probs + 1e-9), dim=-1)
169
+ return entropy
170
+
171
+ def forward(self, logits: torch.Tensor, threshold: float):
172
+ entropy = self.compute_entropy(logits)
173
+ mask = entropy >= threshold
174
+ return mask, entropy
175
+
176
+ class ActorCriticPruner:
177
+ def __init__(self, lm_head, lambda_reg=0.1):
178
+ self.lm_head = lm_head
179
+ self.lambda_reg = lambda_reg
180
+
181
+ def evaluate_sequence_value(self, candidate_tokens, logits):
182
+ log_probs = F.log_softmax(logits.float(), dim=-1)
183
+ gathered = torch.gather(log_probs, -1, candidate_tokens.unsqueeze(-1)).squeeze(-1)
184
+ return gathered.mean().item()
185
+
186
+ def recursive_refine(self, sequence, logits, mask, entropy, depth, alpha, beta):
187
+ refined_sequence = sequence.clone()
188
+ if depth == 0 or mask.sum() == 0:
189
+ return refined_sequence, self.evaluate_sequence_value(sequence, logits)
190
+
191
+ high_unc_positions = torch.where(mask)[0]
192
+ if len(high_unc_positions) == 0:
193
+ return refined_sequence, self.evaluate_sequence_value(sequence, logits)
194
+
195
+ target_pos = high_unc_positions[0].item()
196
+ top_logits, top_tokens = torch.topk(logits[target_pos], k=3)
197
+
198
+ best_val = float('-inf')
199
+ for token_opt in top_tokens:
200
+ candidate = sequence.clone()
201
+ candidate[target_pos] = token_opt
202
+
203
+ approx_val = self.evaluate_sequence_value(candidate, logits) - (self.lambda_reg * entropy[target_pos].item())
204
+ if approx_val < alpha:
205
+ continue
206
+
207
+ new_mask = mask.clone()
208
+ new_mask[target_pos] = False
209
+
210
+ _, path_val = self.recursive_refine(candidate, logits, new_mask, entropy, depth - 1, alpha, beta)
211
+ if path_val > alpha:
212
+ alpha = path_val
213
+ best_val = path_val
214
+ refined_sequence = candidate
215
+
216
+ if alpha >= beta:
217
+ break
218
+
219
+ return refined_sequence, best_val
220
+
221
+
222
+ class ADAPTDIFFPipeline(nn.Module):
223
+ def __init__(self, base_lm_model, block_size=12, entropy_threshold=1.5):
224
+ super().__init__()
225
+ self.base_model = base_lm_model.model
226
+ self.lm_head = base_lm_model.lm_head
227
+ self.block_size = block_size
228
+ self.entropy_threshold = entropy_threshold
229
+
230
+ self.ldm_heads = StackedLDMHeads(
231
+ hidden_size=base_lm_model.config.hidden_size,
232
+ vocab_size=base_lm_model.config.vocab_size,
233
+ block_size=block_size
234
+ ).to(DEVICE)
235
+
236
+ self.router = LogitUncertaintyFilter()
237
+ self.pruner = ActorCriticPruner(self.lm_head)
238
+
239
+ def generate_adapt_diff(self, input_ids, max_new_tokens=128):
240
+ current_seq = input_ids.clone()
241
+ generated_count = 0
242
+ total_full_transformer_evals = 0
243
+
244
+ while generated_count < max_new_tokens:
245
+ outputs = self.base_model(input_ids=current_seq)
246
+ total_full_transformer_evals += 1
247
+ last_hidden = outputs.last_hidden_state[:, -1:, :]
248
+
249
+ block_logits = self.ldm_heads(last_hidden).squeeze(0).squeeze(0)
250
+ draft_tokens = torch.argmax(block_logits, dim=-1)
251
+
252
+ mask, entropy = self.router(block_logits, self.entropy_threshold)
253
+
254
+ if not mask.any():
255
+ final_block = draft_tokens
256
+ else:
257
+ total_full_transformer_evals += 1
258
+ final_block, _ = self.pruner.recursive_refine(
259
+ sequence=draft_tokens,
260
+ logits=block_logits,
261
+ mask=mask,
262
+ entropy=entropy,
263
+ depth=2,
264
+ alpha=float('-inf'),
265
+ beta=float('inf')
266
+ )
267
+
268
+ current_seq = torch.cat([current_seq, final_block.unsqueeze(0)], dim=-1)
269
+ generated_count += self.block_size
270
+
271
+ return current_seq[0, input_ids.shape[1]:], total_full_transformer_evals
272
+
273
+
274
+ # ==============================================================================
275
+ # Model Loading & LDM Weights Initialization
276
+ # ==============================================================================
277
+ print(f"Downloading custom bidirectional model {ADAPT_DIFF_ID} from Hugging Face...")
278
+ a2d_model = AutoModelForCausalLM.from_pretrained(
279
+ ADAPT_DIFF_ID,
280
+ torch_dtype=torch.bfloat16,
281
+ device_map=DEVICE
282
+ )
283
+
284
+ print(f"Downloading baseline model {BASE_MODEL_ID} for comparative evaluation...")
285
+ baseline_model = AutoModelForCausalLM.from_pretrained(
286
+ BASE_MODEL_ID,
287
+ torch_dtype=torch.bfloat16,
288
+ device_map=DEVICE
289
+ )
290
+
291
+ # Initialize generation pipeline and load pre-trained custom LDM weights
292
+ pipeline = ADAPTDIFFPipeline(a2d_model, block_size=12, entropy_threshold=1.5)
293
+ print("Downloading LDM head projection weights...")
294
+ ldm_weights_path = hf_hub_download(repo_id=ADAPT_DIFF_ID, filename="ldm_heads.pt")
295
+ pipeline.ldm_heads.load_state_dict(torch.load(ldm_weights_path, map_location=DEVICE))
296
+ pipeline.eval()
297
+
298
+
299
+ # ==============================================================================
300
+ # Sub-Sampled Benchmark Initialization
301
+ # ==============================================================================
302
+ print("\nLoading GSM8K and MBPP evaluation datasets...")
303
+ gsm8k_ds = load_dataset("openai/gsm8k", "main", split="test")
304
+ mbpp_ds = load_dataset("google-research-datasets/mbpp", split="test")
305
+
306
+ val_math = []
307
+ for item in gsm8k_ds:
308
+ val_math.append((f"Problem: {item['question']}\nSolution:", item['answer']))
309
+ if len(val_math) >= 10: # Fast benchmark slice
310
+ break
311
+
312
+ val_code = []
313
+ for item in mbpp_ds:
314
+ val_code.append((f"Write a Python function to solve this task:\n{item['text']}\nSolution:\n", item['code'], item['test_list']))
315
+ if len(val_code) >= 10:
316
+ break
317
+
318
+
319
+ # ==============================================================================
320
+ # Validation Helpers
321
+ # ==============================================================================
322
+ def extract_answer(text):
323
+ if "####" in text:
324
+ text = text.split("####")[-1]
325
+ matches = re.findall(r'-?[\d,]*\.?\d+', text)
326
+ return matches[-1].replace(',', '') if matches else None
327
+
328
+ def verify_math(generated_text, ref_ans):
329
+ pred_val = extract_answer(generated_text)
330
+ ref_val = extract_answer(ref_ans)
331
+ if pred_val is None or ref_val is None:
332
+ return 0.0
333
+ try:
334
+ return 1.0 if float(pred_val) == float(ref_val) else 0.0
335
+ except ValueError:
336
+ return 1.0 if str(pred_val).strip() == str(ref_val).strip() else 0.0
337
+
338
+ def verify_code(generated_text, test_list):
339
+ code_block = generated_text
340
+ if "```python" in generated_text:
341
+ code_block = generated_text.split("```python")[-1].split("```")[0]
342
+ elif "```" in generated_text:
343
+ code_block = generated_text.split("```")[-1].split("```")[0]
344
+
345
+ local_scope = {}
346
+ try:
347
+ compiled_code = compile(code_block, "<string>", "exec")
348
+ exec(compiled_code, local_scope, local_scope)
349
+ for test in test_list:
350
+ exec(test, local_scope, local_scope)
351
+ return 1.0
352
+ except Exception:
353
+ return 0.0
354
+
355
+
356
+ # ==============================================================================
357
+ # Evaluation Loop
358
+ # ==============================================================================
359
+ def run_benchmark(pipeline, base_model, dataset, is_code=False):
360
+ ar_correct = 0
361
+ ad_correct = 0
362
+ total = len(dataset)
363
+
364
+ ar_total_tokens = 0
365
+ ad_total_tokens = 0
366
+ ar_total_time = 0.0
367
+ ad_total_time = 0.0
368
+ ad_total_evals = 0
369
+
370
+ for idx, item in enumerate(dataset):
371
+ prompt = item[0]
372
+ inputs = src_tokenizer(prompt, return_tensors="pt").to(DEVICE)
373
+ max_new_tokens = 48
374
+
375
+ # Autoregressive generation
376
+ t_start = time.time()
377
+ with torch.no_grad():
378
+ ar_outputs = base_model.generate(
379
+ **inputs,
380
+ max_new_tokens=max_new_tokens,
381
+ pad_token_id=src_tokenizer.pad_token_id,
382
+ eos_token_id=src_tokenizer.eos_token_id,
383
+ do_sample=False
384
+ )
385
+ ar_total_time += (time.time() - t_start)
386
+ ar_gen_tokens = ar_outputs[0][inputs.input_ids.shape[1]:]
387
+ ar_total_tokens += len(ar_gen_tokens)
388
+ ar_text = src_tokenizer.decode(ar_gen_tokens, skip_special_tokens=True)
389
+
390
+ # ADAPT-DIFF speculative generation
391
+ t_start = time.time()
392
+ with torch.no_grad():
393
+ ad_gen_tokens, step_evals = pipeline.generate_adapt_diff(
394
+ input_ids=inputs.input_ids,
395
+ max_new_tokens=max_new_tokens
396
+ )
397
+ ad_total_time += (time.time() - t_start)
398
+ ad_total_tokens += len(ad_gen_tokens)
399
+ ad_total_evals += step_evals
400
+ ad_text = src_tokenizer.decode(ad_gen_tokens, skip_special_tokens=True)
401
+
402
+ if is_code:
403
+ ar_correct += verify_code(ar_text, item[2])
404
+ ad_correct += verify_code(ad_text, item[2])
405
+ else:
406
+ ar_correct += verify_math(ar_text, item[1])
407
+ ad_correct += verify_math(ad_text, item[1])
408
+
409
+ ar_throughput = ar_total_tokens / (ar_total_time + 1e-9)
410
+ ad_throughput = ad_total_tokens / (ad_total_time + 1e-9)
411
+ ad_flops_per_token = ad_total_evals / (ad_total_tokens + 1e-9)
412
+
413
+ return {
414
+ "ar_acc": ar_correct / total,
415
+ "ad_acc": ad_correct / total,
416
+ "ar_speed": ar_throughput,
417
+ "ad_speed": ad_throughput,
418
+ "ar_flops": 1.0,
419
+ "ad_flops": ad_flops_per_token
420
+ }
421
+
422
+ print("\nStarting evaluation run...")
423
+ math_results = run_benchmark(pipeline, baseline_model, val_math, is_code=False)
424
+ code_results = run_benchmark(pipeline, baseline_model, val_code, is_code=True)
425
+
426
+ # Print comparative results
427
+ print("\n" + "="*95)
428
+ print(" ADAPT-DIFF INFERENCE BENCHMARK RESULTS (Block Size L = 12)")
429
+ print("="*95)
430
+ print(f"{'Task / Strategy':<30} | {'Throughput (tok/s)':<20} | {'Task Acc':<15} | {'Relative FLOPs/Tok':<20}")
431
+ print("-"*95)
432
+ print(f"{'GSM8K (Autoregressive Baseline)':<30} | {math_results['ar_speed']:<20.2f} | {math_results['ar_acc']:<15.2%} | {math_results['ar_flops']:<20.4f}")
433
+ print(f"{'GSM8K (ADAPT-DIFF Speculative)':<30} | {math_results['ad_speed']:<20.2f} | {math_results['ad_acc']:<15.2%} | {math_results['ad_flops']:<20.4f}")
434
+ print("-"*95)
435
+ print(f"{'MBPP (Autoregressive Baseline)':<30} | {code_results['ar_speed']:<20.2f} | {code_results['ar_acc']:<15.2%} | {code_results['ar_flops']:<20.4f}")
436
+ print(f"{'MBPP (ADAPT-DIFF Speculative)':<30} | {code_results['ad_speed']:<20.2f} | {code_results['ad_acc']:<15.2%} | {code_results['ad_flops']:<20.4f}")
437
+ print("="*95)