Spaces:
Running
Running
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()
|