Train a small language model (Qwen2.5-0.5B) to generate attractive, concise commercial product descriptions from product names and features. This project integrates all learned concepts: QLoRA model loading, LoRA configuration, dataset preparation, SFTTrainer training, evaluation, and adapter saving.
Create a synthetic dataset of 50 examples in Alpaca format:
import json
dataset = [
{
"instruction": "Write an attractive commercial description for a technology product.",
"input": "Name: Foldable 4K Drone. Features: Stabilized camera, 30 min flight time, app control, auto-tracking mode. Price: $349.99.",
"output": "Capture the world from above with our Foldable 4K Drone! Lightweight, powerful, and ready to travel. Enjoy ultra-sharp video thanks to its stabilized camera, fly up to 30 minutes worry-free, and control everything from your smartphone. With auto-tracking mode, never lose sight of the action. Professional tech within reach. Only $349.99."
},
{
"instruction": "Write an attractive commercial description for a technology product.",
"input": "Name: RGB Mechanical Keyboard. Features: Tactile switches, customizable backlighting, ergonomic design, anti-ghosting. Price: $89.99.",
"output": "Dominate every game or project with our RGB Mechanical Keyboard. Designed for those who demand precision and style. Tactile switches respond to your touch, RGB backlighting lets you create your ideal ambiance, and an ergonomic design protects your hands during long sessions. Anti-ghosting ensures every keypress counts. Performance and elegance in one device. Only $89.99."
},
# ... 48 more examples
]
# Save as JSON
with open("product_descriptions_dataset.json", "w") as f:
json.dump(dataset, f, indent=2)
# --- Imports ---
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments
)
from peft import LoraConfig, get_peft_model, PeftModel
from trl import SFTTrainer
from datasets import Dataset
import json
import wandb
# --- Initial Setup ---
wandb.login() # Optional
# --- Load Dataset ---
with open("product_descriptions_dataset.json", "r") as f:
raw_data = json.load(f)
dataset = Dataset.from_list(raw_data)
# --- Load Model and Tokenizer with QLoRA ---
model_name = "Qwen/Qwen2.5-0.5B-Instruct"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token # Required for padding
# --- Configure LoRA ---
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# --- Formatting Function ---
def formatting_prompts_func(examples):
instructions = examples["instruction"]
inputs = examples["input"]
outputs = examples["output"]
texts = []
for instruction, input_text, output in zip(instructions, inputs, outputs):
text = f"### Instruction:\n{instruction}\n\n### Input:\n{input_text}\n\n### Response:\n{output}"
texts.append(text)
return texts
# --- Training Configuration ---
training_args = TrainingArguments(
output_dir="./product_desc_results",
num_train_epochs=5,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
optim="paged_adamw_8bit",
save_steps=100,
logging_steps=25,
learning_rate=2e-4,
weight_decay=0.01,
fp16=True,
max_grad_norm=0.3,
warmup_ratio=0.03,
lr_scheduler_type="cosine",
report_to="wandb",
evaluation_strategy="no", # Skip evaluation for simplicity
save_total_limit=2,
)
# --- Create and Run Trainer ---
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
formatting_func=formatting_prompts_func,
max_seq_length=512,
tokenizer=tokenizer,
packing=False,
)
trainer.train()
# --- Save Adapter ---
model.save_pretrained("./product_desc_adapter")
tokenizer.save_pretrained("./product_desc_adapter")
# --- Optionally Merge and Save Full Model ---
# model = model.merge_and_unload()
# model.save_pretrained("./product_desc_merged")
# tokenizer.save_pretrained("./product_desc_merged")
After training, test the model on new products:
def generate_product_description(product_name, features, price):
instruction = "Write an attractive commercial description for a technology product."
input_text = f"Name: {product_name}. Features: {features}. Price: ${price}."
prompt = f"### Instruction:\n{instruction}\n\n### Input:\n{input_text}\n\n### Response:\n"
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=200,
temperature=0.7,
top_p=0.9,
do_sample=True
)
full_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
response = full_text.split("### Response:")[-1].strip()
return response
# Test
desc = generate_product_description(
"Waterproof Bluetooth Speaker",
"20W power, 15-hour battery, multi-point connection, portable design",
"79.99"
)
print(desc)
Expected Output (Example):
“Take the party anywhere with our Waterproof Bluetooth Speaker! Pure 20W power fills any space with immersive sound. Enjoy up to 15 hours of uninterrupted music, connect two devices simultaneously, and take it to the beach, pool, or mountains with its rugged, portable design. Unrestricted fun wherever you go. Only $79.99.”