Download scripts/fix_projections.py from CrashOverrideX/Quillan-Ronin: direct link, hf CLI and curl.
- Browser
- Download file 7.09 kB
-
https://huggingface.co/CrashOverrideX/Quillan-Ronin/resolve/main/scripts/fix_projections.py
- Command line
-
hf download hf://CrashOverrideX/Quillan-Ronin/scripts/fix_projections.py
-
curl -L -o fix_projections.py https://huggingface.co/CrashOverrideX/Quillan-Ronin/resolve/main/scripts/fix_projections.py
7.09 kB
| #!/usr/bin/env python3 | |
| """Phase 1: Fix expert projections by directly copying source weights. | |
| The 3 source models were sliced into Quillan's 34 experts. | |
| The problem: dimension mismatches were handled with random SVD projections | |
| that corrupted the signal. Fix: pad/trim source weights directly to target dims. | |
| """ | |
| import os, sys, torch, gc | |
| sys.path.insert(0, r'C:\Users\Admin\Quillan-Ronin') | |
| from safetensors import safe_open | |
| from pathlib import Path | |
| BASE = Path(r'C:\Users\Admin\Quillan-Ronin') | |
| MODEL_DIR = BASE / 'Quillan-v4.2-model' | |
| CKPT_PATH = BASE / 'checkpoints' / 'quillan_finetuned.pt' | |
| OUT_PATH = BASE / 'checkpoints' / 'quillan_fixed.pt' | |
| H, F = 2048, 4096 | |
| def dequantize_bitnet(w_uint8, w_scale): | |
| """BitNet stores uint8 with per-tensor scale. | |
| Formula: w_fp = (uint8 - 128.0) * scale""" | |
| return (w_uint8.float() - 128.0) * w_scale.float() | |
| def pad_to_target(w, target_in, target_out): | |
| """Pad or trim a weight to target dimensions. | |
| w: [src_in, src_out] in Quillan [in, out] convention | |
| Returns: [target_in, target_out] | |
| """ | |
| w = w.float() | |
| in_dim, out_dim = w.shape | |
| # Pad or trim in dimension | |
| if in_dim < target_in: | |
| pad_in = target_in - in_dim | |
| zeros = torch.zeros(pad_in, out_dim) | |
| w = torch.cat([w, zeros], dim=0) | |
| elif in_dim > target_in: | |
| w = w[:target_in, :] | |
| # Pad or trim out dimension | |
| if out_dim < target_out: | |
| pad_out = target_out - out_dim | |
| zeros = torch.zeros(target_in, pad_out) | |
| w = torch.cat([w, zeros], dim=1) | |
| elif out_dim > target_out: | |
| w = w[:, :target_out] | |
| return w | |
| # βββ Load source models βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| print('=== PHASE 1: FIX EXPERT WEIGHTS ===') | |
| print('\n[1] Loading source models...') | |
| # Llama (C0-C7): direct copies, no changes needed | |
| # Qwen (C8-C21): 14 experts, hidden=1024, ffn=3584 | |
| # BitNet (C22-C33): 12 experts, hidden=2560, ffn=1728 | |
| # Qwen FFN weights | |
| print(' Qwen FFN weights...') | |
| q_w1, q_wg, q_w2 = [], [], [] | |
| with safe_open(str(MODEL_DIR / 'qwen model.safetensors'), framework='pt') as f: | |
| for i in range(14): | |
| q_w1.append(f.get_tensor(f'model.language_model.layers.{i}.mlp.gate_proj.weight').float()) | |
| q_wg.append(f.get_tensor(f'model.language_model.layers.{i}.mlp.up_proj.weight').float()) | |
| q_w2.append(f.get_tensor(f'model.language_model.layers.{i}.mlp.down_proj.weight').float()) | |
| q_w1 = torch.stack(q_w1) # [14, 3584, 1024] β [out, in] | |
| q_wg = torch.stack(q_wg) | |
| q_w2 = torch.stack(q_w2) # [14, 1024, 3584] β [out, in] | |
| print(f' Qwen w1: {list(q_w1.shape)}, w2: {list(q_w2.shape)}') | |
| # BitNet FFN weights (dequantize from uint8) | |
| print(' BitNet FFN weights...') | |
| b_w1, b_wg, b_w2 = [], [], [] | |
| with safe_open(str(MODEL_DIR / 'bitnet model.safetensors'), framework='pt') as f: | |
| for i in range(12): | |
| w1_u = f.get_tensor(f'model.layers.{i}.mlp.gate_proj.weight') | |
| w1_s = f.get_tensor(f'model.layers.{i}.mlp.gate_proj.weight_scale') | |
| b_w1.append(dequantize_bitnet(w1_u, w1_s)) | |
| wg_u = f.get_tensor(f'model.layers.{i}.mlp.up_proj.weight') | |
| wg_s = f.get_tensor(f'model.layers.{i}.mlp.up_proj.weight_scale') | |
| b_wg.append(dequantize_bitnet(wg_u, wg_s)) | |
| w2_u = f.get_tensor(f'model.layers.{i}.mlp.down_proj.weight') | |
| w2_s = f.get_tensor(f'model.layers.{i}.mlp.down_proj.weight_scale') | |
| b_w2.append(dequantize_bitnet(w2_u, w2_s)) | |
| b_w1 = torch.stack(b_w1) # [12, 1728, 2560] β [out, in] | |
| b_wg = torch.stack(b_wg) | |
| b_w2 = torch.stack(b_w2) # [12, 2560, 1728] β [out, in] | |
| print(f' BitNet w1: {list(b_w1.shape)}, w2: {list(b_w2.shape)}') | |
| # βββ Convert to Quillan format [experts, in_dim, out_dim] βββββββββββββ | |
| print('\n[2] Converting to Quillan format...') | |
| # Qwen: [14, 3584, 1024] [out, in] β transpose β [14, 1024, 3584] [in, out] β pad β [14, 2048, 4096] | |
| q_w1 = torch.stack([pad_to_target(w, H, F) for w in q_w1.transpose(1, 2)]) # [14, H, F] | |
| q_wg = torch.stack([pad_to_target(w, H, F) for w in q_wg.transpose(1, 2)]) | |
| q_w2 = torch.stack([pad_to_target(w, F, H) for w in q_w2.transpose(1, 2)]) # down: [in=F, out=H] β pad to [4096, 2048] | |
| # BitNet: [12, 1728, 2560] [out, in] β transpose β [12, 2560, 1728] [in, out] β trim/pad β [12, 2048, 4096] | |
| # In this case, in_dim=2560 > target=2048, so we TRIM the first 2048 dims | |
| b_w1 = torch.stack([pad_to_target(w, H, F) for w in b_w1.transpose(1, 2)]) | |
| b_wg = torch.stack([pad_to_target(w, H, F) for w in b_wg.transpose(1, 2)]) | |
| b_w2 = torch.stack([pad_to_target(w, F, H) for w in b_w2.transpose(1, 2)]) # down: [in=F, out=H] | |
| print(f' Qwen converted: w1={list(q_w1.shape)}') | |
| print(f' BitNet converted: w1={list(b_w1.shape)}') | |
| # βββ Load checkpoint and replace experts βββββββββββββββββββββββββββββββ | |
| print('\n[3] Loading checkpoint...') | |
| ckpt = torch.load(CKPT_PATH, map_location='cpu', weights_only=True) | |
| sd = ckpt.get('state_dict', ckpt) | |
| if 'model_state_dict' in ckpt: sd = ckpt['model_state_dict'] | |
| print(' Replacing C8-C21 (Qwen)...') | |
| sd['moe.w1'][8:22] = q_w1.to(sd['moe.w1'].dtype) | |
| sd['moe.wgate'][8:22] = q_wg.to(sd['moe.wgate'].dtype) | |
| sd['moe.w2'][8:22] = q_w2.to(sd['moe.w2'].dtype) | |
| # BitNet weights have ~1000x larger magnitude than Llama. Rescale to match. | |
| llama_norm = sd['moe.w1'][:8].norm() | |
| bitnet_norm = b_w1.norm() | |
| scale_factor = llama_norm / bitnet_norm | |
| print(f' Rescaling BitNet by {scale_factor:.4f} ({llama_norm:.1f} / {bitnet_norm:.1f})') | |
| b_w1 = b_w1 * scale_factor | |
| b_wg = b_wg * scale_factor | |
| b_w2 = b_w2 * scale_factor | |
| print(' Replacing C22-C33 (BitNet)...') | |
| sd['moe.w1'][22:34] = b_w1.to(sd['moe.w1'].dtype) | |
| sd['moe.wgate'][22:34] = b_wg.to(sd['moe.wgate'].dtype) | |
| sd['moe.w2'][22:34] = b_w2.to(sd['moe.w2'].dtype) | |
| del q_w1, q_wg, q_w2, b_w1, b_wg, b_w2; gc.collect() | |
| # βββ Verify βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| s1 = str(tuple(sd['moe.w1'].shape)) | |
| s2 = str(tuple(sd['moe.w2'].shape)) | |
| sg = str(tuple(sd['moe.wgate'].shape)) | |
| dtype = sd['moe.w1'].dtype | |
| print(f' w1: {s1} {dtype}') | |
| print(f' w2: {s2} {dtype}') | |
| print(f' wgate: {sg} {dtype}') | |
| # Quick sanity: check norm of Llama vs Qwen vs BitNet experts | |
| for name, start, end in [('Llama C0', 0, 8), ('Qwen C8', 8, 22), ('BitNet C22', 22, 34)]: | |
| n = sd['moe.w1'][start:end].norm().item() | |
| print(f' {name}: w1_norm={n:.2f}') | |
| # βββ Save βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| print(f'\n[4] Saving to {OUT_PATH.name}...') | |
| torch.save(dict(state_dict=sd, step=0, loss=0.0, version='v5.3.1-fixed'), OUT_PATH) | |
| gb = os.path.getsize(OUT_PATH) / 1e9 | |
| print(f' Saved: {gb:.2f} GB') | |
| print('\nPHASE 1 COMPLETE') | |