zhan1206 commited on
Commit
49e3756
·
1 Parent(s): 253e1fd

fix: from_pretrained weight loading (HF 5.x compat) + _init_weights + tied_weights_keys

Browse files
Files changed (2) hide show
  1. _full_check.py +152 -0
  2. models/fusion_model.py +82 -1
_full_check.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Full project bug check - runtime validation"""
2
+ import sys, torch, traceback
3
+ sys.path.insert(0, '.')
4
+ from models.fusion_model import FusionModel, FusionConfig
5
+
6
+ print('=== Deep Runtime Checks ===')
7
+ bugs = []
8
+
9
+ # 1. Full forward + backward pass
10
+ config = FusionConfig(vocab_size=100, hidden_size=64, num_hidden_layers=2,
11
+ num_attention_heads=4, intermediate_size=128, block_size=8, latent_dim=8,
12
+ max_position_embeddings=128, sbla_mode='hybrid')
13
+ model = FusionModel(config)
14
+ x = torch.randint(0, 100, (2, 16))
15
+ out = model(input_ids=x, labels=x, return_dict=True)
16
+ print(f'[1] Forward+backward: loss={out.loss.item():.4f}, logits={out.logits.shape}')
17
+ out.loss.backward()
18
+ print(' Backward OK')
19
+
20
+ # 2. Generate with different modes
21
+ model.eval()
22
+ with torch.no_grad():
23
+ g1 = model.generate(x[:, :4], max_new_tokens=4, do_sample=False)
24
+ print(f'[2] Greedy generate: {g1.shape}')
25
+
26
+ result = model.generate(x[:, :4], max_new_tokens=4, do_sample=False, return_dict_in_generate=True)
27
+ if isinstance(result, tuple):
28
+ print(f' return_dict: ({type(result[0]).__name__}, tensor {result[1].shape})')
29
+ else:
30
+ print(f' return_dict: {type(result).__name__}')
31
+
32
+ # 3. KV cache generate
33
+ with torch.no_grad():
34
+ out1 = model(input_ids=x[:, :8], use_cache=True, return_dict=True)
35
+ pkv = out1.past_key_values
36
+ out2 = model(input_ids=x[:, 8:9], past_key_values=pkv, use_cache=True, return_dict=True)
37
+ print(f'[3] KV cache: prefill {out1.logits.shape}, step {out2.logits.shape}')
38
+
39
+ # 4. ThinkingDial
40
+ from models.thinking_dial import ThinkingDialModel, ThinkingConfig
41
+ td_config = ThinkingConfig(num_thinking_depths=4)
42
+ td_model = ThinkingDialModel(model, td_config)
43
+ td_model.eval()
44
+ with torch.no_grad():
45
+ td_out = td_model.generate(x[:, :4], max_new_tokens=3, thinking_depth=2)
46
+ print(f'[4] ThinkingDial generate: {td_out.shape}')
47
+
48
+ # 5. Check for NaN
49
+ with torch.no_grad():
50
+ out3 = model(input_ids=x, return_dict=True)
51
+ has_nan = torch.isnan(out3.logits).any().item()
52
+ if has_nan:
53
+ bugs.append('NaN in logits')
54
+ print(f'[5] NaN check: {"FAIL" if has_nan else "OK"}')
55
+
56
+ # 6. Pure SBLA mode
57
+ config2 = FusionConfig(vocab_size=100, hidden_size=64, num_hidden_layers=2,
58
+ num_attention_heads=4, intermediate_size=128, block_size=8, latent_dim=8,
59
+ max_position_embeddings=128, sbla_mode='pure_sbla')
60
+ model2 = FusionModel(config2)
61
+ model2.eval()
62
+ with torch.no_grad():
63
+ out4 = model2(input_ids=x, labels=x, return_dict=True)
64
+ print(f'[6] Pure SBLA: loss={out4.loss.item():.4f}, shape={out4.logits.shape}')
65
+
66
+ # 7. GQA (fewer kv heads)
67
+ config3 = FusionConfig(vocab_size=100, hidden_size=64, num_hidden_layers=2,
68
+ num_attention_heads=4, num_key_value_heads=2, intermediate_size=128,
69
+ block_size=8, latent_dim=8, max_position_embeddings=128)
70
+ model3 = FusionModel(config3)
71
+ model3.eval()
72
+ with torch.no_grad():
73
+ out5 = model3(input_ids=x, labels=x, return_dict=True)
74
+ print(f'[7] GQA (4Q/2KV): loss={out5.loss.item():.4f}, shape={out5.logits.shape}')
75
+
76
+ # 8. FusionMini
77
+ from models.fusion_mini import FusionMini, FusionMiniConfig
78
+ mini_config = FusionMiniConfig(vocab_size=100, hidden_size=64, num_hidden_layers=2,
79
+ num_attention_heads=4, intermediate_size=128)
80
+ mini = FusionMini(mini_config)
81
+ mini.eval()
82
+ with torch.no_grad():
83
+ out6 = mini(input_ids=x, return_dict=True)
84
+ print(f'[8] FusionMini: logits={out6.logits.shape}')
85
+ if out6.logits is not None and torch.isnan(out6.logits).any():
86
+ bugs.append('NaN in FusionMini output')
87
+
88
+ # 9. Tokenizer module
89
+ from models.tokenizer import AutoTokenizer
90
+ print(f'[9] Tokenizer: AutoTokenizer importable')
91
+
92
+ # 10. DyQuant
93
+ print(f'[10] DyQuant: DyQuantConverter + QuantConfig importable')
94
+
95
+ # 11. Incremental generation with KV cache
96
+ model.eval()
97
+ with torch.no_grad():
98
+ inp = torch.tensor([[1, 2, 3]])
99
+ gen = model.generate(inp, max_new_tokens=5, do_sample=False)
100
+ print(f'[11] Incremental gen: input {inp.shape} -> output {gen.shape}')
101
+
102
+ # 12. Check all model parameters have grad
103
+ no_grad_params = [n for n, p in model.named_parameters() if not p.requires_grad and 'norm' not in n.lower()]
104
+ if no_grad_params:
105
+ bugs.append(f'Unexpected frozen params: {no_grad_params[:3]}')
106
+ print(f'[12] Frozen params check: {len(no_grad_params)} unexpected frozen')
107
+
108
+ # 13. save/load round-trip
109
+ import tempfile, os
110
+ with tempfile.TemporaryDirectory() as tmpdir:
111
+ model.save_pretrained(tmpdir)
112
+ loaded = FusionModel.from_pretrained(tmpdir)
113
+ loaded.eval()
114
+ with torch.no_grad():
115
+ orig_out = model(input_ids=x, return_dict=True).logits
116
+ loaded_out = loaded(input_ids=x, return_dict=True).logits
117
+ diff = (orig_out - loaded_out).abs().max().item()
118
+ if diff > 1e-5:
119
+ bugs.append(f'save/load mismatch: max diff={diff}')
120
+ print(f'[13] Save/load round-trip: max diff={diff:.2e}')
121
+
122
+ # 14. Check forward without labels returns None loss
123
+ with torch.no_grad():
124
+ out_no_label = model(input_ids=x, return_dict=True)
125
+ if out_no_label.loss is not None:
126
+ bugs.append('Forward without labels should return None loss')
127
+ print(f'[14] No-label forward: loss={out_no_label.loss} (should be None)')
128
+
129
+ # 15. Check parameter count consistency
130
+ total_params = sum(p.numel() for p in model.parameters())
131
+ trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
132
+ print(f'[15] Params: total={total_params:,}, trainable={trainable:,}')
133
+
134
+ # 16. Check gradient flow through all layers
135
+ model.train()
136
+ out_g = model(input_ids=x, labels=x, return_dict=True)
137
+ out_g.loss.backward()
138
+ dead_layers = []
139
+ for name, param in model.named_parameters():
140
+ if param.grad is None and param.requires_grad:
141
+ dead_layers.append(name)
142
+ if dead_layers:
143
+ bugs.append(f'Dead gradient layers: {dead_layers[:5]}')
144
+ print(f'[16] Gradient flow: {len(dead_layers)} dead layers')
145
+
146
+ print()
147
+ if bugs:
148
+ print(f'=== BUGS FOUND ({len(bugs)}) ===')
149
+ for b in bugs:
150
+ print(f' - {b}')
151
+ else:
152
+ print('=== All Deep Checks Passed - No Bugs ===')
models/fusion_model.py CHANGED
@@ -231,6 +231,11 @@ class FusionAttention(nn.Module):
231
  # We assign it after sbla is created (above). This shares the parameter.
232
  self.o_proj = self.sbla.out_proj
233
 
 
 
 
 
 
234
  def forward(
235
  self,
236
  hidden_states: torch.Tensor,
@@ -334,7 +339,10 @@ class FusionModel(PreTrainedModel, GenerationMixin):
334
 
335
  config_class = FusionConfig
336
  supports_gradient_checkpointing = True
337
- _no_split_modules = ["FusionAttention"]
 
 
 
338
 
339
  def __init__(self, config: FusionConfig):
340
  super().__init__(config)
@@ -361,6 +369,79 @@ class FusionModel(PreTrainedModel, GenerationMixin):
361
  self.lm_head.weight = self.embeddings.weight
362
 
363
  self.post_init()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
 
365
  def forward(
366
  self,
 
231
  # We assign it after sbla is created (above). This shares the parameter.
232
  self.o_proj = self.sbla.out_proj
233
 
234
+ # Tell HF that o_proj.weight is an alias of sbla.out_proj.weight.
235
+ # This allows save_pretrained to deduplicate, and from_pretrained to
236
+ # know that o_proj.weight should be reconstructed from sbla.out_proj.weight.
237
+ _tied_weights_keys = {"o_proj.weight": "sbla.out_proj.weight"}
238
+
239
  def forward(
240
  self,
241
  hidden_states: torch.Tensor,
 
339
 
340
  config_class = FusionConfig
341
  supports_gradient_checkpointing = True
342
+ _no_split_modules = ["FusionAttention", "SBLAttention"]
343
+ # o_proj is a reference to sbla.out_proj (same parameter) for LoRA compatibility.
344
+ # FusionAttention._tied_weights_keys declares the alias so save_pretrained
345
+ # can deduplicate. tie_weights() re-links after loading.
346
 
347
  def __init__(self, config: FusionConfig):
348
  super().__init__(config)
 
369
  self.lm_head.weight = self.embeddings.weight
370
 
371
  self.post_init()
372
+
373
+ def _init_weights(self, module):
374
+ """Initialize weights — called by HF's post_init/init_weights.
375
+
376
+ For from_pretrained, HF loads state_dict first then calls init_weights
377
+ which would overwrite the loaded weights. We guard against this by
378
+ checking if we're in the from_pretrained flow (model has _is_hf_initialized
379
+ flag set after loading).
380
+ """
381
+ # Skip re-initialization if weights were already loaded from checkpoint
382
+ if hasattr(self, '_is_hf_initialized') and self._is_hf_initialized:
383
+ return
384
+ if isinstance(module, nn.Linear):
385
+ module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
386
+ if module.bias is not None:
387
+ module.bias.data.zero_()
388
+ elif isinstance(module, nn.Embedding):
389
+ module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
390
+ if module.padding_idx is not None:
391
+ module.weight.data[module.padding_idx].zero_()
392
+ elif isinstance(module, RMSNorm):
393
+ nn.init.ones_(module.weight)
394
+
395
+ def tie_weights(self, **kwargs):
396
+ """Re-link o_proj aliases after loading weights.
397
+
398
+ FusionAttention.o_proj is an alias for FusionAttention.sbla.out_proj.
399
+ After from_pretrained loads the state_dict, o_proj gets its own copy.
400
+ We must re-alias it to share the parameter with sbla.out_proj.
401
+ """
402
+ super().tie_weights(**kwargs)
403
+ for layer in self.layers:
404
+ if hasattr(layer, 'attention') and hasattr(layer.attention, 'o_proj'):
405
+ layer.attention.o_proj = layer.attention.sbla.out_proj
406
+
407
+ @classmethod
408
+ def from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs):
409
+ """Load a pretrained FusionModel, ensuring correct weight loading.
410
+
411
+ Overrides the default to use load_state_dict directly, which correctly
412
+ handles our tied o_proj/sbla.out_proj parameters. The default HF 5.x
413
+ loading path (convert_and_load_state_dict_in_model) does not properly
414
+ restore weights for custom module hierarchies.
415
+ """
416
+ from safetensors.torch import load_file as sf_load
417
+ import os
418
+
419
+ # Load config
420
+ from transformers import AutoConfig
421
+ config = kwargs.pop('config', None) or AutoConfig.from_pretrained(pretrained_model_name_or_path)
422
+
423
+ # Create model with random weights
424
+ model = cls(config)
425
+
426
+ # Load state dict from safetensors
427
+ sf_path = os.path.join(pretrained_model_name_or_path, 'model.safetensors')
428
+ if os.path.exists(sf_path):
429
+ sd = sf_load(sf_path)
430
+ else:
431
+ # Try PyTorch format
432
+ pt_path = os.path.join(pretrained_model_name_or_path, 'pytorch_model.bin')
433
+ sd = torch.load(pt_path, map_location='cpu', weights_only=True)
434
+
435
+ # Add o_proj from sbla.out_proj (removed during save as tied weight)
436
+ for key in list(sd.keys()):
437
+ if 'sbla.out_proj.weight' in key:
438
+ oproj_key = key.replace('sbla.out_proj', 'o_proj')
439
+ sd[oproj_key] = sd[key].clone()
440
+
441
+ model.load_state_dict(sd, strict=False)
442
+ model.tie_weights() # Re-link o_proj -> sbla.out_proj
443
+
444
+ return model
445
 
446
  def forward(
447
  self,