Naphula commited on
Commit
bf21b97
·
verified ·
1 Parent(s): cb7099b

use one of these scripts for merging loras to Gemma 4 safetensor

Browse files
PMPF5_finetune_adapter_mergeG4.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from peft import PeftModel
4
+ from transformers import AutoModelForCausalLM, AutoTokenizer
5
+ import shutil
6
+
7
+ # ================= CONFIGURATION =================
8
+ BASE_MODEL_PATH = os.getcwd() # Where the original safetensors are
9
+ ADAPTER_PATH = "finetuned_adapter" # Where the LoRA files are
10
+ NEW_MODEL_PATH = "My_Finetuned_Model_v1" # The new subfolder for the result
11
+ # =================================================
12
+
13
+ def main():
14
+ # Force CPU processing to save local VRAM during execution
15
+ os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
16
+
17
+ print(f"Loading base model from {BASE_MODEL_PATH}...")
18
+
19
+ # 1. Load the tokenizer and preserve vocabulary
20
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_PATH, local_files_only=True)
21
+ vocab_size = tokenizer.vocab_size # Explicitly retrieve 262144 from tokenizer config
22
+ print(f"Detected tokenizer vocabulary size: {vocab_size}")
23
+
24
+ # 2. Load the base model with nested text config routing
25
+ base_model = AutoModelForCausalLM.from_pretrained(
26
+ BASE_MODEL_PATH,
27
+ low_cpu_mem_usage=True,
28
+ return_dict=True,
29
+ torch_dtype=torch.bfloat16, # Match model dtype from config
30
+ device_map="cpu",
31
+ local_files_only=True
32
+ )
33
+
34
+ # 3. Ensure the base model vocabulary exactly matches the 262144 layout
35
+ print(f"Aligning base model token embeddings to: {vocab_size}...")
36
+ base_model.resize_token_embeddings(vocab_size)
37
+
38
+ print(f"Loading adapter from {ADAPTER_PATH}...")
39
+ # We load the model with custom configuration overrides to force-map
40
+ # the adapter directly to the underlying language_model layers.
41
+ model = PeftModel.from_pretrained(
42
+ base_model,
43
+ ADAPTER_PATH,
44
+ adapter_name="default",
45
+ is_trainable=False,
46
+ )
47
+
48
+ print("Merging weights (this may take a minute)...")
49
+ # Using merge_and_unload to execute the matrix calculations safely.
50
+ # We explicitly load state dict first to resolve any path mismatches
51
+ # between the base model and the target PEFT adapter.
52
+ model.base_model.model.load_state_dict(model.base_model.model.state_dict(), strict=False)
53
+ model = model.merge_and_unload()
54
+
55
+ print(f"Saving merged model to {NEW_MODEL_PATH}...")
56
+ if not os.path.exists(NEW_MODEL_PATH):
57
+ os.makedirs(NEW_MODEL_PATH)
58
+
59
+ model.save_pretrained(
60
+ NEW_MODEL_PATH,
61
+ safe_serialization=True,
62
+ max_shard_size="5GB"
63
+ )
64
+
65
+ print("Saving tokenizer...")
66
+ tokenizer.save_pretrained(NEW_MODEL_PATH)
67
+
68
+ # Copy generation config if it exists
69
+ gen_config = os.path.join(BASE_MODEL_PATH, "generation_config.json")
70
+ if os.path.exists(gen_config):
71
+ shutil.copy(gen_config, os.path.join(NEW_MODEL_PATH, "generation_config.json"))
72
+
73
+ print("Merge complete!")
74
+ print(f"You can now use '{NEW_MODEL_PATH}' in Kobold, Ooba, or MergeKit.")
75
+
76
+ if __name__ == "__main__":
77
+ main()
PMPF_finetune_adapter_merge_G4_12B.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gc
3
+ import json
4
+ import shutil
5
+ import torch
6
+ from safetensors.torch import load_file, save_file
7
+
8
+ BASE_MODEL_PATH = os.getcwd()
9
+ ADAPTER_PATH = "finetuned_adapter"
10
+ NEW_MODEL_PATH = "My_Finetuned_Model_v1"
11
+
12
+ def main():
13
+ print("=" * 80)
14
+ print(" GEMMA 4 DIRECT SAFETENSORS MERGER (BYPASSES CLIPPABLELINEAR)")
15
+ print("=" * 80)
16
+
17
+ # 1. Read LoRA Config
18
+ config_path = os.path.join(ADAPTER_PATH, "adapter_config.json")
19
+ with open(config_path, "r", encoding="utf-8") as f:
20
+ adapter_config = json.load(f)
21
+
22
+ r = adapter_config.get("r", 32)
23
+ alpha = adapter_config.get("lora_alpha", 64)
24
+ scaling = alpha / r
25
+ print(f"LoRA Config: Rank={r}, Alpha={alpha}, Scaling={scaling}")
26
+
27
+ # 2. Load Adapter Tensors
28
+ adapter_file = os.path.join(ADAPTER_PATH, "adapter_model.safetensors")
29
+ adapter_tensors = load_file(adapter_file, device="cpu")
30
+ print(f"Loaded {len(adapter_tensors)} adapter tensors.")
31
+
32
+ # 3. Compute Delta Matrices
33
+ deltas = {}
34
+ for k in list(adapter_tensors.keys()):
35
+ if ".lora_A.weight" in k:
36
+ b_key = k.replace(".lora_A.weight", ".lora_B.weight")
37
+ if b_key in adapter_tensors:
38
+ # Raw base target suffix
39
+ raw_target = k.replace("base_model.model.", "").replace(".lora_A.weight", ".weight")
40
+
41
+ A = adapter_tensors[k].to(torch.float32) # [r, in_features]
42
+ B = adapter_tensors[b_key].to(torch.float32) # [out_features, r]
43
+ delta = (B @ A) * scaling # [out_features, in_features]
44
+
45
+ deltas[raw_target] = delta
46
+
47
+ print(f"Compiled {len(deltas)} LoRA delta updates.")
48
+
49
+ # 4. Stream and Merge Base Shards
50
+ shard_files = sorted([f for f in os.listdir(BASE_MODEL_PATH) if f.startswith("model") and f.endswith(".safetensors")])
51
+ if not os.path.exists(NEW_MODEL_PATH):
52
+ os.makedirs(NEW_MODEL_PATH)
53
+
54
+ total_applied = 0
55
+ for shard_name in shard_files:
56
+ in_shard = os.path.join(BASE_MODEL_PATH, shard_name)
57
+ out_shard = os.path.join(NEW_MODEL_PATH, shard_name)
58
+
59
+ base_weights = load_file(in_shard, device="cpu")
60
+ merged_weights = {}
61
+ shard_applied = 0
62
+
63
+ for k, v in base_weights.items():
64
+ delta = None
65
+
66
+ # 1. Direct match
67
+ if k in deltas:
68
+ delta = deltas[k]
69
+ else:
70
+ # 2. Suffix match (resolves language_model prefix differences)
71
+ for d_k, d_v in deltas.items():
72
+ if k.endswith(d_k) or d_k.endswith(k):
73
+ delta = d_v
74
+ break
75
+
76
+ # 3. Tied weights fallback for embed_tokens / lm_head
77
+ if delta is None and "embed_tokens" in k:
78
+ for d_k, d_v in deltas.items():
79
+ if "lm_head" in d_k:
80
+ delta = d_v
81
+ break
82
+
83
+ if delta is not None:
84
+ if delta.shape == v.shape:
85
+ merged_weights[k] = (v.to(torch.float32) + delta).to(v.dtype)
86
+ shard_applied += 1
87
+ total_applied += 1
88
+ elif delta.T.shape == v.shape:
89
+ merged_weights[k] = (v.to(torch.float32) + delta.T).to(v.dtype)
90
+ shard_applied += 1
91
+ total_applied += 1
92
+ else:
93
+ print(f"⚠️ Shape mismatch for {k}: base {v.shape} vs delta {delta.shape}")
94
+ merged_weights[k] = v
95
+ else:
96
+ merged_weights[k] = v
97
+
98
+ print(f"Shard {shard_name}: Applied {shard_applied} deltas.")
99
+ save_file(merged_weights, out_shard, metadata={"format": "pt"})
100
+ del base_weights
101
+ del merged_weights
102
+ gc.collect()
103
+
104
+ print(f"\n🎉 MERGE COMPLETE! Successfully applied {total_applied} layer deltas.")
105
+
106
+ # 5. Copy Tokenizer and Configs
107
+ print("Copying configuration files...")
108
+ for item in os.listdir(BASE_MODEL_PATH):
109
+ if item.endswith((".json", ".jinja", ".txt", ".model", ".tiktoken")):
110
+ src = os.path.join(BASE_MODEL_PATH, item)
111
+ dst = os.path.join(NEW_MODEL_PATH, item)
112
+ shutil.copy(src, dst)
113
+
114
+ print(f"Ready for conversion! Point convert_hf_to_gguf.py to: {NEW_MODEL_PATH}")
115
+
116
+ if __name__ == "__main__":
117
+ main()