Spaces:
Running
Running
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- _full_check.py +152 -0
- 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,
|