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