Views
No views yet
1################################################################################
2# We first load the model for QAT using the mobile CPU friendly int8-int4 scheme
3################################################################################
4
5from unsloth import FastLanguageModel
6from unsloth.chat_templates import (
7 get_chat_template,
8)
9import torch
10
11MODEL_ID = "unsloth/Qwen3-4B"
12QAT_SCHEME = "int8-int4"
13
14model, tokenizer = FastLanguageModel.from_pretrained(
15 model_name = MODEL_ID,
16 max_seq_length = 2048,
17 dtype = torch.bfloat16,
18 load_in_4bit = False,
19 full_finetuning = True,
20 # ExecuTorch CPU quantization scheme
21 # Quantize embedding to 8-bits, and quantize linear layers to 4-bits
22 # with 8-bit dynamically quantized activations
23 qat_scheme = QAT_SCHEME,
24)
25tokenizer = get_chat_template(tokenizer, chat_template = "qwen3")
26
27
28################################################################################
29# Data prep
30################################################################################
31
32from datasets import load_dataset
33reasoning_dataset = load_dataset("unsloth/OpenMathReasoning-mini", split = "cot")
34non_reasoning_dataset = load_dataset("mlabonne/FineTome-100k", split = "train")
35
36# Convert the dataset into a conversational format
37def generate_conversation(examples):
38 problems = examples["problem"]
39 solutions = examples["generated_solution"]
40 conversations = []
41 for problem, solution in zip(problems, solutions):
42 conversations.append([
43 {"role" : "user", "content" : problem},
44 {"role" : "assistant", "content" : solution},
45 ])
46 return { "conversations": conversations, }
47
48reasoning_conversations = tokenizer.apply_chat_template(
49 list(reasoning_dataset.map(generate_conversation, batched = True)["conversations"]),
50 tokenize = False,
51)
52
53from unsloth.chat_templates import standardize_sharegpt
54dataset = standardize_sharegpt(non_reasoning_dataset)
55non_reasoning_conversations = tokenizer.apply_chat_template(
56 list(dataset["conversations"]),
57 tokenize = False,
58)
59
60# Let's create a combined dataset that mixes 25% conversational vs. 75% reasoning
61chat_percentage = 0.25
62import pandas as pd
63non_reasoning_subset = pd.Series(non_reasoning_conversations)
64non_reasoning_subset = non_reasoning_subset.sample(
65 int(len(reasoning_conversations)*(chat_percentage/(1 - chat_percentage))),
66 random_state=2407,
67)
68print(len(reasoning_conversations))
69print(len(non_reasoning_subset))
70print(len(non_reasoning_subset) / (len(non_reasoning_subset) + len(reasoning_conversations)))
71
72
73data = pd.concat([
74 pd.Series(reasoning_conversations),
75 pd.Series(non_reasoning_subset)
76])
77data.name = "text"
78
79from datasets import Dataset
80combined_dataset = Dataset.from_pandas(pd.DataFrame(data))
81combined_dataset = combined_dataset.shuffle(seed = 3407)
82
83
84################################################################################
85# Define trainer
86################################################################################
87
88from trl import SFTTrainer, SFTConfig
89trainer = SFTTrainer(
90 model = model,
91 tokenizer = tokenizer,
92 train_dataset = combined_dataset,
93 eval_dataset = None, # Can set up evaluation!
94 args = SFTConfig(
95 dataset_text_field = "text",
96 per_device_train_batch_size = 2,
97 gradient_accumulation_steps = 4, # Use GA to mimic batch size!
98 warmup_steps = 5,
99 # num_train_epochs = 1, # Set this for 1 full training run.
100 max_steps = 30,
101 learning_rate = 2e-5,
102 logging_steps = 1,
103 optim = "adamw_8bit",
104 weight_decay = 0.001,
105 lr_scheduler_type = "linear",
106 seed = 3407,
107 report_to = "none", # Use TrackIO/WandB etc
108 ),
109)
110
111
112################################################################################
113# Do fine tuning
114################################################################################
115trainer_stats = trainer.train()
116print(f"{trainer_stats.metrics['train_runtime']} seconds used for training.")
117print(
118 f"{round(trainer_stats.metrics['train_runtime']/60, 2)} minutes used for training."
119)
120
121
122################################################################################
123# Inference
124################################################################################
125messages = [
126 {"role" : "user", "content" : "Solve (x + 2)^2 = 0."}
127]
128text = tokenizer.apply_chat_template(
129 messages,
130 tokenize = False,
131 add_generation_prompt = True, # Must add for generation
132 enable_thinking = False, # Disable thinking
133)
134
135from transformers import TextStreamer
136_ = model.generate(
137 **tokenizer(text, return_tensors = "pt").to("cuda"),
138 max_new_tokens = 256, # Increase for longer outputs!
139 temperature = 0.7, top_p = 0.8, top_k = 20, # For non thinking
140 streamer = TextStreamer(tokenizer, skip_prompt = True),
141)
142
143
144# ################################################################################
145# # Convert model to torchao format and save
146# ################################################################################
147
148from unsloth.models._utils import _convert_torchao_model
149_convert_torchao_model(model)
150
151model_name = MODEL_ID.split("/")[-1]
152save_to = f"{model_name}-{QAT_SCHEME}-unsloth-v3"
153
154# Save locally
155# model.save_pretrained(save_to, safe_serialization=False)
156# tokenizer.save_pretrained(save_to)
157
158# Or save to hub
159from huggingface_hub import get_token, whoami
160def _get_username():
161 token = get_token()
162 username = whoami(token=token)["name"]
163 return username
164username = _get_username()
165model.push_to_hub(f"{username}/{save_to}", safe_serialization=False)
166tokenizer.push_to_hub(f"{username}/{save_to}")1# 1. Install ExecuTorch
2pip install executorch pytorch_tokenizers torchtune
3
4# 2. Download finetuned weights we uploaded to HuggingFace (or use local directory we saved to)
5HF_DIR=metascroy/Qwen3-4B-int8-int4-unsloth-v3
6WEIGHT_DIR=$(hf download ${HF_DIR})
7
8# 3. Convert the weight checkpoint state dict keys to one that ExecuTorch expects
9python -m executorch.examples.models.qwen3.convert_weights $WEIGHT_DIR pytorch_model_converted.bin
10
11# 4. Download model config from ExecuTorch repo
12curl -L -o 4b_config.json https://raw.githubusercontent.com/pytorch/executorch/main/examples/models/qwen3/config/4b_config.json
13
14# 5. Export to ExecuTorch pte file
15python -m executorch.examples.models.llama.export_llama \
16 --model "qwen3_4b" \
17 --checkpoint pytorch_model_converted.bin \
18 --params 4b_config.json \
19 --output_name qwen3_model.pte \
20 -kv \
21 --use_sdpa_with_kv_cache \
22 -X \
23 --xnnpack-extended-ops \
24 --max_context_length 1024 \
25 --max_seq_length 128 \
26 --dtype fp32 \
27 --metadata '{"get_bos_id":199999, "get_eos_ids":[200020,199999]}'
28
29# 6. (optional) Upload pte file to HuggingFace
30hf upload ${HF_DIR} qwen3_model.pte