Views
No views yet
1# =========================================================================================
2# Fine-tuning script based on https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Llama3.2_%281B_and_3B%29-Conversational.ipynb
3#
4# This script and HF checkpoint are only intended to showcase how to do finetuning in a way compatible with ExecuTorch
5# Only 100 steps are done, and quality of the finetuned model is not evaluated
6# =========================================================================================
7
8from unsloth import FastLanguageModel
9from unsloth.chat_templates import (
10 get_chat_template,
11 standardize_data_formats,
12 standardize_sharegpt,
13 train_on_responses_only,
14)
15
16from datasets import load_dataset
17from trl import SFTConfig, SFTTrainer
18from transformers import DataCollatorForSeq2Seq
19import torch
20import torch.nn as nn
21
22batch_size = 2
23learning_rate = 2e-5
24gradient_accumulation_steps = 4
25max_steps = 100
26full_finetuning = True
27qat_scheme = "int8-int4"
28output_dir = "/tmp/unsloth_example"
29
30
31model_id = "unsloth/Llama-3.2-1B-Instruct"
32chat_template = "llama-3.1"
33max_seq_length = 2048
34dtype = torch.bfloat16
35load_in_4bit = False
36
37################################################################################
38# Define model/tokenizer
39################################################################################
40
41model, tokenizer = FastLanguageModel.from_pretrained(
42 model_name=model_id,
43 max_seq_length=max_seq_length,
44 dtype=dtype,
45 load_in_4bit =load_in_4bit,
46 full_finetuning=full_finetuning,
47 qat_scheme=qat_scheme,
48)
49tokenizer = get_chat_template(tokenizer, chat_template = chat_template)
50data_collator = DataCollatorForSeq2Seq(tokenizer = tokenizer)
51
52print("MODEL AFTER LOADING")
53print(model)
54
55################################################################################
56# Untie model weights
57################################################################################
58
59def untie_word_embeddings_(model):
60 """Untie input and output embeddings in a Hugging Face causal LM."""
61 # 1) Persist setting in config
62 if hasattr(model.config, "tie_word_embeddings"):
63 model.config.tie_word_embeddings = False
64
65 # 2) Find input and output embeddings
66 in_emb = model.get_input_embeddings() # nn.Embedding
67 out_proj = model.get_output_embeddings() or getattr(model, "lm_head", None)
68 if out_proj is None:
69 raise AttributeError("Couldn't locate output projection (lm_head).")
70
71 # (Optional) sanity: shapes should match [vocab, hidden]
72 assert out_proj.weight.shape == in_emb.weight.shape, (
73 f"Shape mismatch: out_proj {out_proj.weight.shape} vs in_emb {in_emb.weight.shape}"
74 )
75
76 # 3) Only clone if they are actually tied (shared storage)
77 if out_proj.weight.data_ptr() == in_emb.weight.data_ptr():
78 with torch.no_grad():
79 W = in_emb.weight.detach().clone()
80 out_proj.weight = nn.Parameter(W) # new storage, keeps dtype/device
81
82 # 4) Prevent future automatic re-tying
83 def _no_tie(self):
84 return
85 model.tie_weights = _no_tie.__get__(model, model.__class__)
86
87 # 5) Verify no shared storage
88 assert out_proj.weight.data_ptr() != in_emb.weight.data_ptr(), "Embeddings still tied!"
89
90 return model
91
92model = untie_word_embeddings_(model)
93
94print("MODEL AFTER UNTYING")
95print(model)
96print(model.config)
97
98
99################################################################################
100# Process dataset
101################################################################################
102
103def formatting_prompts_func(examples):
104 convos = examples["conversations"]
105 texts = [tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False) for convo in convos]
106 return { "text" : texts, }
107dataset = load_dataset("mlabonne/FineTome-100k", split = "train")
108dataset = standardize_sharegpt(dataset)
109dataset = dataset.map(formatting_prompts_func, batched = True,)
110
111print("DATASET ENTRY")
112print(dataset[0])
113print("\n\n")
114
115################################################################################
116# Define trainer
117################################################################################
118
119trainer = SFTTrainer(
120 model=model,
121 tokenizer=tokenizer,
122 train_dataset=dataset,
123 dataset_text_field="text",
124 max_seq_length=max_seq_length,
125 data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer),
126 packing=False,
127 args=SFTConfig(
128 per_device_train_batch_size=batch_size,
129 gradient_accumulation_steps=gradient_accumulation_steps,
130 warmup_steps=5,
131 num_train_epochs=1,
132 max_steps=max_steps,
133 learning_rate=learning_rate,
134 logging_steps=1,
135 optim="adamw_8bit",
136 weight_decay=0.01,
137 lr_scheduler_type="linear",
138 seed=3407,
139 output_dir="outputs",
140 report_to="none",
141 ),
142)
143trainer = train_on_responses_only(
144 trainer,
145 instruction_part = "<|start_header_id|>user<|end_header_id|>\n\n",
146 response_part = "<|start_header_id|>assistant<|end_header_id|>\n\n",
147)
148
149print("VERIFYING PROMPT MASKING ON EXAMPLE")
150idx = 5
151print("Original: ", tokenizer.decode(trainer.train_dataset[idx]["input_ids"]))
152space = tokenizer(" ", add_special_tokens = False).input_ids[0]
153print("Masked: ", tokenizer.decode([space if x == -100 else x for x in trainer.train_dataset[idx]["labels"]]))
154print("\n\n")
155
156
157################################################################################
158# Do fine tuning
159################################################################################
160print("DOING FINETUNING")
161trainer_stats = trainer.train()
162print(f"{trainer_stats.metrics['train_runtime']} seconds used for training.")
163print(
164 f"{round(trainer_stats.metrics['train_runtime']/60, 2)} minutes used for training."
165)
166
167################################################################################
168# Save model
169################################################################################
170model.save_pretrained(output_dir)
171tokenizer.save_pretrained(output_dir)
172
173################################################################################
174# Convert model
175################################################################################
176from torchao.quantization import Int8DynamicActivationIntxWeightConfig, IntxWeightOnlyConfig, ModuleFqnToConfig, quantize_
177from torchao.quantization.qat import QATConfig
178from torchao.quantization.granularity import PerGroup, PerAxis
179from transformers import TorchAoConfig
180
181base_config = Int8DynamicActivationIntxWeightConfig(weight_dtype=torch.int4, weight_granularity=PerGroup(32))
182quantize_(model, QATConfig(base_config, step="convert"))
183
184################################################################################
185# Quantize embeddings to 8-bit with PTQ since they are not supported by QAT yet
186################################################################################
187
188embedding_fqn = "model.embed_tokens"
189embedding_config = IntxWeightOnlyConfig(weight_dtype=torch.int8, granularity=PerAxis(0))
190quantize_(model, embedding_config, lambda m, fqn: fqn == embedding_fqn)
191
192################################################################################
193# Attach quantization config to model
194################################################################################
195
196quant_config = ModuleFqnToConfig({"_default": base_config, embedding_fqn: embedding_config})
197quantization_config = TorchAoConfig(quant_type=quant_config, include_input_output_embeddings=True, modules_to_not_convert=[])
198model.config.quantization_config = TorchAoConfig(base_config)
199
200print('MODEL AFTER CONVERT', model)
201
202################################################################################
203# Push converted model to hub
204################################################################################
205from huggingface_hub import get_token, whoami
206
207def _get_username():
208 token = get_token()
209 username = whoami(token=token)["name"]
210 return username
211
212username = _get_username()
213model_name = model_id.split("/")[-1]
214save_to = f"{username}/{model_name}-{qat_scheme}"
215model.push_to_hub(save_to, safe_serialization=False)
216tokenizer.push_to_hub(save_to)
217
218################################################################################
219# Load converted from hub and inspect
220################################################################################
221from transformers import AutoModelForCausalLM
222model = AutoModelForCausalLM.from_pretrained(save_to)
223print('model', model)
224print("model.embed_tokens.weight", model.model.embed_tokens.weight)
225print("model.layers[0].self_attn.q_proj.weight", model.model.layers[0].self_attn.q_proj.weight)
226print("lm_head.weight", model.lm_head.weight)
227