Update latest_finetune.py
Browse files- latest_finetune.py +84 -45
latest_finetune.py
CHANGED
|
@@ -1,19 +1,27 @@
|
|
| 1 |
"""
|
| 2 |
================================================================================
|
| 3 |
-
Tiny-Aya
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
Purpose
|
| 7 |
-------
|
| 8 |
-
Continue fine-tuning the
|
| 9 |
-
|
| 10 |
narrower, structured task: NADRA/HBL-style pipe-separated Urdu records
|
| 11 |
(name | father/husband name | guardian name | address | address | city,district)
|
| 12 |
-> the equivalent Roman Urdu record, preserving field order and separators
|
| 13 |
EXACTLY.
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
quantized to 4-bit, and a FRESH LoRA adapter is trained on top of them for
|
| 18 |
this specific task. That is the correct pattern for "continue fine-tuning
|
| 19 |
a merged checkpoint."
|
|
@@ -34,9 +42,11 @@ How to use
|
|
| 34 |
and run once to see real token-length stats for your NADRA records,
|
| 35 |
then set MAX_LENGTH accordingly and set this back to False.
|
| 36 |
3. Run: python finetune_v4_nadra.py
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
| 40 |
"""
|
| 41 |
|
| 42 |
import os
|
|
@@ -71,9 +81,12 @@ from peft import (
|
|
| 71 |
# ==============================================================================
|
| 72 |
|
| 73 |
# --- Paths -------------------------------------------------------------------
|
| 74 |
-
# Local path to your
|
| 75 |
# tokenizer files + generation_config.json + chat_template.jinja).
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
# Folder containing splits/train.csv, splits/val.csv, splits/test.csv
|
| 79 |
DATA_DIR = r"./splits"
|
|
@@ -103,11 +116,15 @@ MAX_LENGTH = 384 # starting default; confirm/adjust using the analysis pass
|
|
| 103 |
SEED = 42
|
| 104 |
|
| 105 |
# --- LoRA config ---------------------------------------------------------------
|
| 106 |
-
# Rank/alpha
|
| 107 |
-
#
|
| 108 |
-
#
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
LORA_DROPOUT = 0.02
|
| 112 |
LORA_TARGET_MODULES = [
|
| 113 |
"q_proj", "k_proj", "v_proj", "o_proj",
|
|
@@ -115,16 +132,35 @@ LORA_TARGET_MODULES = [
|
|
| 115 |
]
|
| 116 |
|
| 117 |
# --- Training hyperparameters ---------------------------------------------------
|
| 118 |
-
# LR
|
| 119 |
-
#
|
| 120 |
-
#
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
| 122 |
NUM_TRAIN_EPOCHS = 1 # with ~2.9M rows, one epoch is already a large amount
|
| 123 |
-
# of gradient updates
|
| 124 |
-
#
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
WARMUP_RATIO = 0.03 # ratio, not fixed steps -- scales with LIMIT_ROWS
|
| 129 |
|
| 130 |
LOGGING_STEPS = 20
|
|
@@ -140,7 +176,7 @@ USE_BNB_PAGED_OPTIMIZER = False
|
|
| 140 |
|
| 141 |
# --- Merging ---------------------------------------------------------------
|
| 142 |
# If True, the script merges the trained LoRA adapter into a fresh copy of
|
| 143 |
-
# the
|
| 144 |
# after training finishes, in the same process/run. Set False if you'd
|
| 145 |
# rather train now and merge later as a separate step (e.g. to free up the
|
| 146 |
# GPU sooner, or to merge from a specific earlier checkpoint by hand).
|
|
@@ -255,7 +291,7 @@ def format_example(urdu: str, roman: str, eos_token: str) -> str:
|
|
| 255 |
|
| 256 |
|
| 257 |
# ==============================================================================
|
| 258 |
-
# 4. Completion-only masking (offset-mapping based, same approach as
|
| 259 |
# ==============================================================================
|
| 260 |
def build_tokenize_fn(tokenizer, max_length):
|
| 261 |
def tokenize_function(examples):
|
|
@@ -324,13 +360,13 @@ def analyze_lengths(tokenizer, texts, sample_size):
|
|
| 324 |
|
| 325 |
|
| 326 |
# ==============================================================================
|
| 327 |
-
# 6. Model loading:
|
| 328 |
# ==============================================================================
|
| 329 |
def load_model_and_tokenizer():
|
| 330 |
-
if not os.path.isdir(
|
| 331 |
raise FileNotFoundError(
|
| 332 |
-
f"
|
| 333 |
-
f"This must be a local directory containing the
|
| 334 |
f"checkpoint (model.safetensors, config.json, tokenizer files)."
|
| 335 |
)
|
| 336 |
|
|
@@ -341,9 +377,9 @@ def load_model_and_tokenizer():
|
|
| 341 |
bnb_4bit_use_double_quant=True,
|
| 342 |
)
|
| 343 |
|
| 344 |
-
print(f"Loading
|
| 345 |
model = AutoModelForCausalLM.from_pretrained(
|
| 346 |
-
|
| 347 |
quantization_config=bnb_config,
|
| 348 |
torch_dtype=torch.float16,
|
| 349 |
device_map={"": 0},
|
|
@@ -352,10 +388,10 @@ def load_model_and_tokenizer():
|
|
| 352 |
)
|
| 353 |
model.config.use_cache = False
|
| 354 |
model = prepare_model_for_kbit_training(model)
|
| 355 |
-
model.gradient_checkpointing_enable()
|
| 356 |
|
| 357 |
tokenizer = AutoTokenizer.from_pretrained(
|
| 358 |
-
|
| 359 |
)
|
| 360 |
tokenizer.pad_token = tokenizer.eos_token
|
| 361 |
tokenizer.padding_side = "right"
|
|
@@ -396,7 +432,7 @@ def main():
|
|
| 396 |
# --- Tokenizer needed for both the length-analysis path and formatting ---
|
| 397 |
# Loaded here (unquantized/tokenizer-only) so ANALYZE_LENGTHS_ONLY can run
|
| 398 |
# without paying the cost of loading the full 4-bit model.
|
| 399 |
-
tokenizer = AutoTokenizer.from_pretrained(
|
| 400 |
tokenizer.pad_token = tokenizer.eos_token
|
| 401 |
|
| 402 |
train_texts = [
|
|
@@ -440,7 +476,7 @@ def main():
|
|
| 440 |
"before starting an unattended run."
|
| 441 |
)
|
| 442 |
|
| 443 |
-
# --- Load model (
|
| 444 |
model, model_tokenizer = load_model_and_tokenizer()
|
| 445 |
# tokenizer used for data collation must match the one used for tokenizing
|
| 446 |
assert model_tokenizer.get_vocab() == tokenizer.get_vocab(), (
|
|
@@ -472,7 +508,10 @@ def main():
|
|
| 472 |
remove_unused_columns=False,
|
| 473 |
optim=optim_name,
|
| 474 |
gradient_checkpointing=True,
|
| 475 |
-
gradient_checkpointing_kwargs={"use_reentrant":
|
|
|
|
|
|
|
|
|
|
| 476 |
# No push_to_hub / hub_model_id / hub_token: server has no internet.
|
| 477 |
)
|
| 478 |
|
|
@@ -518,7 +557,7 @@ def main():
|
|
| 518 |
print(f"Training complete. LoRA adapter saved to: {ADAPTER_DIR}")
|
| 519 |
|
| 520 |
# --- Free the 4-bit training model's GPU memory before merging ---
|
| 521 |
-
# Merging loads a fresh, unquantized copy of
|
| 522 |
# and quantized model from this run are still holding CUDA memory/cache
|
| 523 |
# at this point. Explicitly drop references and clear the allocator
|
| 524 |
# cache so the merge step (or anything run after it in this process)
|
|
@@ -529,7 +568,7 @@ def main():
|
|
| 529 |
torch.cuda.empty_cache()
|
| 530 |
|
| 531 |
if AUTO_MERGE_AFTER_TRAINING:
|
| 532 |
-
print("Proceeding to merge the trained adapter into
|
| 533 |
merge_adapter()
|
| 534 |
else:
|
| 535 |
print(
|
|
@@ -539,7 +578,7 @@ def main():
|
|
| 539 |
|
| 540 |
|
| 541 |
# ==============================================================================
|
| 542 |
-
# 8. Merge LoRA adapter into
|
| 543 |
# ==============================================================================
|
| 544 |
def merge_adapter():
|
| 545 |
"""
|
|
@@ -548,12 +587,12 @@ def merge_adapter():
|
|
| 548 |
trained with AUTO_MERGE_AFTER_TRAINING = False, or want to re-merge from
|
| 549 |
a specific checkpoint.
|
| 550 |
|
| 551 |
-
Loads
|
| 552 |
into it, and saves a standalone model that can be loaded directly with
|
| 553 |
AutoModelForCausalLM.from_pretrained() at deployment time -- no PEFT or
|
| 554 |
bitsandbytes required for inference.
|
| 555 |
"""
|
| 556 |
-
# Priority order for adapter source, mirroring
|
| 557 |
adapter_path = None
|
| 558 |
if os.path.exists(os.path.join(ADAPTER_DIR, "adapter_config.json")):
|
| 559 |
adapter_path = ADAPTER_DIR
|
|
@@ -585,15 +624,15 @@ def merge_adapter():
|
|
| 585 |
gc.collect()
|
| 586 |
torch.cuda.empty_cache()
|
| 587 |
|
| 588 |
-
print(f"Loading
|
| 589 |
base_model = AutoModelForCausalLM.from_pretrained(
|
| 590 |
-
|
| 591 |
torch_dtype=torch.float16,
|
| 592 |
device_map="cpu",
|
| 593 |
trust_remote_code=True,
|
| 594 |
local_files_only=True,
|
| 595 |
)
|
| 596 |
-
tokenizer = AutoTokenizer.from_pretrained(
|
| 597 |
|
| 598 |
print("Merging LoRA weights into base...")
|
| 599 |
merged_model = PeftModel.from_pretrained(base_model, adapter_path)
|
|
|
|
| 1 |
"""
|
| 2 |
================================================================================
|
| 3 |
+
Tiny-Aya v2 -> v4 Sequential Fine-Tuning: NADRA/HBL Structured Transliteration
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
Purpose
|
| 7 |
-------
|
| 8 |
+
Continue fine-tuning the v2 checkpoint (CohereLabs/tiny-aya-global + LoRA
|
| 9 |
+
trained on ~50K synthetic Urdu-Roman name/address pairs, merged) on a
|
| 10 |
narrower, structured task: NADRA/HBL-style pipe-separated Urdu records
|
| 11 |
(name | father/husband name | guardian name | address | address | city,district)
|
| 12 |
-> the equivalent Roman Urdu record, preserving field order and separators
|
| 13 |
EXACTLY.
|
| 14 |
|
| 15 |
+
Note: v2 is being used here (instead of the better-performing v3) purely
|
| 16 |
+
for practical reasons -- it's already on the HBL GPU server, whereas v3's
|
| 17 |
+
~6.2GB model.safetensors would take a long time to transfer over a slow
|
| 18 |
+
connection. Because v2's baseline transliteration quality is weaker (only
|
| 19 |
+
50K synthetic examples, output was "not that good"), this run is doing more
|
| 20 |
+
substantial learning than a pure refinement pass would -- see the LORA_R/
|
| 21 |
+
LORA_ALPHA and LEARNING_RATE comments below for how that changes things.
|
| 22 |
+
|
| 23 |
+
This is NOT resumed from the old v2 LoRA adapter (that adapter is already
|
| 24 |
+
merged into the v2 weights). Instead: v2's merged weights are frozen and
|
| 25 |
quantized to 4-bit, and a FRESH LoRA adapter is trained on top of them for
|
| 26 |
this specific task. That is the correct pattern for "continue fine-tuning
|
| 27 |
a merged checkpoint."
|
|
|
|
| 42 |
and run once to see real token-length stats for your NADRA records,
|
| 43 |
then set MAX_LENGTH accordingly and set this back to False.
|
| 44 |
3. Run: python finetune_v4_nadra.py
|
| 45 |
+
This trains, saves the LoRA adapter to OUTPUT_DIR/final_adapter, then
|
| 46 |
+
(by default, AUTO_MERGE_AFTER_TRAINING = True) merges it into v2 and
|
| 47 |
+
saves a standalone deployable model to OUTPUT_DIR/merged_model, all in
|
| 48 |
+
this one run. Set AUTO_MERGE_AFTER_TRAINING = False if you'd rather
|
| 49 |
+
merge later as a separate step instead.
|
| 50 |
"""
|
| 51 |
|
| 52 |
import os
|
|
|
|
| 81 |
# ==============================================================================
|
| 82 |
|
| 83 |
# --- Paths -------------------------------------------------------------------
|
| 84 |
+
# Local path to your v2 checkpoint (merged model.safetensors + config.json +
|
| 85 |
# tokenizer files + generation_config.json + chat_template.jinja).
|
| 86 |
+
# Local path to your v2 checkpoint on the HBL server (merged
|
| 87 |
+
# model.safetensors + config.json + tokenizer files + generation_config.json
|
| 88 |
+
# + chat_template.jinja). Point this at wherever v2 already lives locally.
|
| 89 |
+
V2_MODEL_PATH = r"./tiny-aya-v2-merged"
|
| 90 |
|
| 91 |
# Folder containing splits/train.csv, splits/val.csv, splits/test.csv
|
| 92 |
DATA_DIR = r"./splits"
|
|
|
|
| 116 |
SEED = 42
|
| 117 |
|
| 118 |
# --- LoRA config ---------------------------------------------------------------
|
| 119 |
+
# Rank/alpha bumped up vs. the original run (32/64 -> 48/96): v2 was only
|
| 120 |
+
# trained on 50K synthetic name/address pairs and its output quality was
|
| 121 |
+
# admittedly weak. This adapter now has to do more of the real learning
|
| 122 |
+
# (both phonetic mapping AND the NADRA structural constraints) rather than
|
| 123 |
+
# just refining an already-solid base, so it gets more capacity.
|
| 124 |
+
# Dropout kept low (0.02): 2.9M rows makes overfitting on one epoch unlikely
|
| 125 |
+
# regardless of starting checkpoint quality.
|
| 126 |
+
LORA_R = 48
|
| 127 |
+
LORA_ALPHA = 96
|
| 128 |
LORA_DROPOUT = 0.02
|
| 129 |
LORA_TARGET_MODULES = [
|
| 130 |
"q_proj", "k_proj", "v_proj", "o_proj",
|
|
|
|
| 132 |
]
|
| 133 |
|
| 134 |
# --- Training hyperparameters ---------------------------------------------------
|
| 135 |
+
# LR raised back up vs. a "gentle refinement" rate (1e-4 -> 1.8e-4): v2 only
|
| 136 |
+
# saw 50K synthetic examples and its transliteration quality was weak, so
|
| 137 |
+
# there's less already-good behavior worth protecting with a low LR. This
|
| 138 |
+
# run needs to do real, substantial learning (phonetic mapping AND NADRA
|
| 139 |
+
# structure), not just nudge an already-capable model -- closer to the
|
| 140 |
+
# original from-scratch rate (2e-4) than a fine-refinement rate.
|
| 141 |
+
LEARNING_RATE = 1.8e-4
|
| 142 |
NUM_TRAIN_EPOCHS = 1 # with ~2.9M rows, one epoch is already a large amount
|
| 143 |
+
# of gradient updates. Given v2's weaker starting
|
| 144 |
+
# point, watch the eval loss curve from a LIMIT_ROWS
|
| 145 |
+
# pilot run -- if it's still improving steadily at
|
| 146 |
+
# the end of the epoch, a 2nd epoch is worth the
|
| 147 |
+
# extra compute time here more than it would have
|
| 148 |
+
# been for a v3-based refinement pass.
|
| 149 |
+
PER_DEVICE_TRAIN_BATCH_SIZE = 2 # kept small deliberately -- see note below
|
| 150 |
+
PER_DEVICE_EVAL_BATCH_SIZE = 2
|
| 151 |
+
GRADIENT_ACCUMULATION_STEPS = 8 # effective batch size = 16 (unchanged)
|
| 152 |
+
# Batch size lowered vs. the original (4 -> 2) and accumulation raised
|
| 153 |
+
# (4 -> 8) to keep the SAME effective batch of 16 while cutting per-step
|
| 154 |
+
# activation memory roughly in half. Two things increased memory pressure
|
| 155 |
+
# vs. the original v3-refinement config: MAX_LENGTH went up (224 -> 384,
|
| 156 |
+
# ~1.7x longer sequences) and LORA_R doubled (32 -> 48, more adapter
|
| 157 |
+
# activations at every targeted layer). The A16 vGPU slice here is a fixed
|
| 158 |
+
# 16GB budget with no headroom to discover through trial and error mid-run,
|
| 159 |
+
# so this trades a bit of throughput for a solid safety margin. If a
|
| 160 |
+
# LIMIT_ROWS pilot run shows plenty of free VRAM (check with `nvidia-smi`
|
| 161 |
+
# while it's training), PER_DEVICE_TRAIN_BATCH_SIZE can be raised back
|
| 162 |
+
# toward 4 (halve GRADIENT_ACCUMULATION_STEPS to match, keeping effective
|
| 163 |
+
# batch at 16).
|
| 164 |
WARMUP_RATIO = 0.03 # ratio, not fixed steps -- scales with LIMIT_ROWS
|
| 165 |
|
| 166 |
LOGGING_STEPS = 20
|
|
|
|
| 176 |
|
| 177 |
# --- Merging ---------------------------------------------------------------
|
| 178 |
# If True, the script merges the trained LoRA adapter into a fresh copy of
|
| 179 |
+
# the v2 base model and saves a standalone deployable model immediately
|
| 180 |
# after training finishes, in the same process/run. Set False if you'd
|
| 181 |
# rather train now and merge later as a separate step (e.g. to free up the
|
| 182 |
# GPU sooner, or to merge from a specific earlier checkpoint by hand).
|
|
|
|
| 291 |
|
| 292 |
|
| 293 |
# ==============================================================================
|
| 294 |
+
# 4. Completion-only masking (offset-mapping based, same approach as v2)
|
| 295 |
# ==============================================================================
|
| 296 |
def build_tokenize_fn(tokenizer, max_length):
|
| 297 |
def tokenize_function(examples):
|
|
|
|
| 360 |
|
| 361 |
|
| 362 |
# ==============================================================================
|
| 363 |
+
# 6. Model loading: v2 (local, merged) -> 4-bit quantized + fresh LoRA adapter
|
| 364 |
# ==============================================================================
|
| 365 |
def load_model_and_tokenizer():
|
| 366 |
+
if not os.path.isdir(V2_MODEL_PATH):
|
| 367 |
raise FileNotFoundError(
|
| 368 |
+
f"V2_MODEL_PATH does not exist: {V2_MODEL_PATH}. "
|
| 369 |
+
f"This must be a local directory containing the v2 merged "
|
| 370 |
f"checkpoint (model.safetensors, config.json, tokenizer files)."
|
| 371 |
)
|
| 372 |
|
|
|
|
| 377 |
bnb_4bit_use_double_quant=True,
|
| 378 |
)
|
| 379 |
|
| 380 |
+
print(f"Loading v2 checkpoint from local path: {V2_MODEL_PATH}")
|
| 381 |
model = AutoModelForCausalLM.from_pretrained(
|
| 382 |
+
V2_MODEL_PATH,
|
| 383 |
quantization_config=bnb_config,
|
| 384 |
torch_dtype=torch.float16,
|
| 385 |
device_map={"": 0},
|
|
|
|
| 388 |
)
|
| 389 |
model.config.use_cache = False
|
| 390 |
model = prepare_model_for_kbit_training(model)
|
| 391 |
+
model.gradient_checkpointing_enable(gradient_checkpointing_kwargs={"use_reentrant": False})
|
| 392 |
|
| 393 |
tokenizer = AutoTokenizer.from_pretrained(
|
| 394 |
+
V2_MODEL_PATH, local_files_only=True
|
| 395 |
)
|
| 396 |
tokenizer.pad_token = tokenizer.eos_token
|
| 397 |
tokenizer.padding_side = "right"
|
|
|
|
| 432 |
# --- Tokenizer needed for both the length-analysis path and formatting ---
|
| 433 |
# Loaded here (unquantized/tokenizer-only) so ANALYZE_LENGTHS_ONLY can run
|
| 434 |
# without paying the cost of loading the full 4-bit model.
|
| 435 |
+
tokenizer = AutoTokenizer.from_pretrained(V2_MODEL_PATH, local_files_only=True)
|
| 436 |
tokenizer.pad_token = tokenizer.eos_token
|
| 437 |
|
| 438 |
train_texts = [
|
|
|
|
| 476 |
"before starting an unattended run."
|
| 477 |
)
|
| 478 |
|
| 479 |
+
# --- Load model (v2 -> 4-bit -> fresh LoRA) ---
|
| 480 |
model, model_tokenizer = load_model_and_tokenizer()
|
| 481 |
# tokenizer used for data collation must match the one used for tokenizing
|
| 482 |
assert model_tokenizer.get_vocab() == tokenizer.get_vocab(), (
|
|
|
|
| 508 |
remove_unused_columns=False,
|
| 509 |
optim=optim_name,
|
| 510 |
gradient_checkpointing=True,
|
| 511 |
+
gradient_checkpointing_kwargs={"use_reentrant": False}, # more memory-
|
| 512 |
+
# efficient than reentrant checkpointing, and the current recommended
|
| 513 |
+
# setting for PEFT + quantized models -- matters more here given the
|
| 514 |
+
# fixed 16GB A16 budget.
|
| 515 |
# No push_to_hub / hub_model_id / hub_token: server has no internet.
|
| 516 |
)
|
| 517 |
|
|
|
|
| 557 |
print(f"Training complete. LoRA adapter saved to: {ADAPTER_DIR}")
|
| 558 |
|
| 559 |
# --- Free the 4-bit training model's GPU memory before merging ---
|
| 560 |
+
# Merging loads a fresh, unquantized copy of v2 on CPU, but the trainer
|
| 561 |
# and quantized model from this run are still holding CUDA memory/cache
|
| 562 |
# at this point. Explicitly drop references and clear the allocator
|
| 563 |
# cache so the merge step (or anything run after it in this process)
|
|
|
|
| 568 |
torch.cuda.empty_cache()
|
| 569 |
|
| 570 |
if AUTO_MERGE_AFTER_TRAINING:
|
| 571 |
+
print("Proceeding to merge the trained adapter into v2 (same run)...")
|
| 572 |
merge_adapter()
|
| 573 |
else:
|
| 574 |
print(
|
|
|
|
| 578 |
|
| 579 |
|
| 580 |
# ==============================================================================
|
| 581 |
+
# 8. Merge LoRA adapter into v2 base -> standalone v4 model for deployment
|
| 582 |
# ==============================================================================
|
| 583 |
def merge_adapter():
|
| 584 |
"""
|
|
|
|
| 587 |
trained with AUTO_MERGE_AFTER_TRAINING = False, or want to re-merge from
|
| 588 |
a specific checkpoint.
|
| 589 |
|
| 590 |
+
Loads v2 in fp16 (not quantized) on CPU, merges the trained LoRA adapter
|
| 591 |
into it, and saves a standalone model that can be loaded directly with
|
| 592 |
AutoModelForCausalLM.from_pretrained() at deployment time -- no PEFT or
|
| 593 |
bitsandbytes required for inference.
|
| 594 |
"""
|
| 595 |
+
# Priority order for adapter source, mirroring v2's approach:
|
| 596 |
adapter_path = None
|
| 597 |
if os.path.exists(os.path.join(ADAPTER_DIR, "adapter_config.json")):
|
| 598 |
adapter_path = ADAPTER_DIR
|
|
|
|
| 624 |
gc.collect()
|
| 625 |
torch.cuda.empty_cache()
|
| 626 |
|
| 627 |
+
print(f"Loading v2 base model (unquantized, CPU) from: {V2_MODEL_PATH}")
|
| 628 |
base_model = AutoModelForCausalLM.from_pretrained(
|
| 629 |
+
V2_MODEL_PATH,
|
| 630 |
torch_dtype=torch.float16,
|
| 631 |
device_map="cpu",
|
| 632 |
trust_remote_code=True,
|
| 633 |
local_files_only=True,
|
| 634 |
)
|
| 635 |
+
tokenizer = AutoTokenizer.from_pretrained(V2_MODEL_PATH, local_files_only=True)
|
| 636 |
|
| 637 |
print("Merging LoRA weights into base...")
|
| 638 |
merged_model = PeftModel.from_pretrained(base_model, adapter_path)
|