mirror of
https://github.com/zenlm/zen-family.git
synced 2026-11-02 03:10:56 +00:00
397 lines
12 KiB
Python
397 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Zen Model Training Pipeline
|
|
Main training script with unified interface for all model variants
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional, List
|
|
from dataclasses import dataclass, field
|
|
|
|
import torch
|
|
from transformers import (
|
|
AutoModelForCausalLM,
|
|
AutoTokenizer,
|
|
TrainingArguments,
|
|
Trainer,
|
|
DataCollatorForLanguageModeling,
|
|
BitsAndBytesConfig
|
|
)
|
|
from peft import (
|
|
LoraConfig,
|
|
get_peft_model,
|
|
prepare_model_for_kbit_training,
|
|
TaskType
|
|
)
|
|
from datasets import load_dataset, Dataset
|
|
|
|
from utils.data_preparation import prepare_datasets
|
|
from utils.model_loader import load_base_model
|
|
from utils.training_utils import setup_training, save_checkpoint
|
|
from configs.model_configs import get_model_config
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class ZenTrainingConfig:
|
|
"""Unified configuration for Zen model training"""
|
|
model_variant: str = "zen-nano"
|
|
base_model: str = "Qwen/Qwen3-4B"
|
|
training_stage: str = "instruct" # base, instruct, thinking
|
|
|
|
# Training parameters
|
|
num_epochs: int = 3
|
|
batch_size: int = 4
|
|
gradient_accumulation_steps: int = 4
|
|
learning_rate: float = 2e-4
|
|
warmup_ratio: float = 0.1
|
|
max_seq_length: int = 2048
|
|
|
|
# LoRA parameters
|
|
use_lora: bool = True
|
|
lora_r: int = 16
|
|
lora_alpha: int = 32
|
|
lora_dropout: float = 0.05
|
|
lora_target_modules: List[str] = field(default_factory=lambda: ["q_proj", "v_proj"])
|
|
|
|
# QLoRA parameters
|
|
use_qlora: bool = False
|
|
quantization_bits: int = 4
|
|
|
|
# BitDelta parameters
|
|
use_bitdelta: bool = False
|
|
bitdelta_ratio: float = 0.1
|
|
|
|
# Data parameters
|
|
dataset_path: str = "./data"
|
|
train_split: float = 0.9
|
|
validation_split: float = 0.05
|
|
test_split: float = 0.05
|
|
|
|
# Output parameters
|
|
output_dir: str = "./checkpoints"
|
|
logging_dir: str = "./logs"
|
|
save_steps: int = 500
|
|
eval_steps: int = 100
|
|
|
|
# Hardware optimization
|
|
fp16: bool = True
|
|
bf16: bool = False
|
|
gradient_checkpointing: bool = True
|
|
optim: str = "adamw_torch"
|
|
|
|
# Advanced features
|
|
use_flash_attention: bool = True
|
|
use_deepspeed: bool = False
|
|
deepspeed_config: Optional[str] = None
|
|
|
|
@classmethod
|
|
def from_yaml(cls, path: str) -> "ZenTrainingConfig":
|
|
"""Load configuration from YAML file"""
|
|
import yaml
|
|
with open(path, 'r') as f:
|
|
config_dict = yaml.safe_load(f)
|
|
return cls(**config_dict)
|
|
|
|
|
|
class ZenTrainer:
|
|
"""Main trainer class for Zen models"""
|
|
|
|
def __init__(self, config: ZenTrainingConfig):
|
|
self.config = config
|
|
self.model = None
|
|
self.tokenizer = None
|
|
self.trainer = None
|
|
|
|
def setup_model(self):
|
|
"""Initialize model with LoRA/QLoRA if configured"""
|
|
logger.info(f"Loading base model: {self.config.base_model}")
|
|
|
|
# Setup quantization config if using QLoRA
|
|
bnb_config = None
|
|
if self.config.use_qlora:
|
|
bnb_config = BitsAndBytesConfig(
|
|
load_in_4bit=True,
|
|
bnb_4bit_use_double_quant=True,
|
|
bnb_4bit_quant_type="nf4",
|
|
bnb_4bit_compute_dtype=torch.bfloat16
|
|
)
|
|
|
|
# Load model
|
|
self.model = AutoModelForCausalLM.from_pretrained(
|
|
self.config.base_model,
|
|
quantization_config=bnb_config,
|
|
device_map="auto",
|
|
trust_remote_code=True,
|
|
use_flash_attention_2=self.config.use_flash_attention
|
|
)
|
|
|
|
# Load tokenizer
|
|
self.tokenizer = AutoTokenizer.from_pretrained(
|
|
self.config.base_model,
|
|
trust_remote_code=True
|
|
)
|
|
self.tokenizer.pad_token = self.tokenizer.eos_token
|
|
|
|
# Setup LoRA if configured
|
|
if self.config.use_lora or self.config.use_qlora:
|
|
if self.config.use_qlora:
|
|
self.model = prepare_model_for_kbit_training(self.model)
|
|
|
|
lora_config = LoraConfig(
|
|
r=self.config.lora_r,
|
|
lora_alpha=self.config.lora_alpha,
|
|
target_modules=self.config.lora_target_modules,
|
|
lora_dropout=self.config.lora_dropout,
|
|
bias="none",
|
|
task_type=TaskType.CAUSAL_LM
|
|
)
|
|
|
|
self.model = get_peft_model(self.model, lora_config)
|
|
self.model.print_trainable_parameters()
|
|
|
|
# Enable gradient checkpointing
|
|
if self.config.gradient_checkpointing:
|
|
self.model.gradient_checkpointing_enable()
|
|
|
|
def load_datasets(self):
|
|
"""Load and prepare training datasets"""
|
|
logger.info("Loading datasets...")
|
|
|
|
# Load datasets based on training stage
|
|
if self.config.training_stage == "base":
|
|
train_dataset = self._load_base_dataset()
|
|
elif self.config.training_stage == "instruct":
|
|
train_dataset = self._load_instruct_dataset()
|
|
elif self.config.training_stage == "thinking":
|
|
train_dataset = self._load_thinking_dataset()
|
|
else:
|
|
raise ValueError(f"Unknown training stage: {self.config.training_stage}")
|
|
|
|
# Split into train/val/test
|
|
train_size = int(len(train_dataset) * self.config.train_split)
|
|
val_size = int(len(train_dataset) * self.config.validation_split)
|
|
|
|
train_dataset = train_dataset.select(range(train_size))
|
|
val_dataset = train_dataset.select(range(train_size, train_size + val_size))
|
|
|
|
return train_dataset, val_dataset
|
|
|
|
def _load_base_dataset(self):
|
|
"""Load dataset for base model training"""
|
|
# Load from local files or HuggingFace
|
|
dataset = load_dataset(
|
|
"json",
|
|
data_files=f"{self.config.dataset_path}/base_training.jsonl",
|
|
split="train"
|
|
)
|
|
return self._tokenize_dataset(dataset)
|
|
|
|
def _load_instruct_dataset(self):
|
|
"""Load dataset for instruction tuning"""
|
|
dataset = load_dataset(
|
|
"json",
|
|
data_files=f"{self.config.dataset_path}/instruct_training.jsonl",
|
|
split="train"
|
|
)
|
|
return self._tokenize_dataset(dataset)
|
|
|
|
def _load_thinking_dataset(self):
|
|
"""Load dataset for thinking/CoT training"""
|
|
dataset = load_dataset(
|
|
"json",
|
|
data_files=f"{self.config.dataset_path}/thinking_training.jsonl",
|
|
split="train"
|
|
)
|
|
return self._tokenize_dataset(dataset)
|
|
|
|
def _tokenize_dataset(self, dataset):
|
|
"""Tokenize dataset"""
|
|
def tokenize_function(examples):
|
|
return self.tokenizer(
|
|
examples["text"],
|
|
padding="max_length",
|
|
truncation=True,
|
|
max_length=self.config.max_seq_length
|
|
)
|
|
|
|
tokenized = dataset.map(
|
|
tokenize_function,
|
|
batched=True,
|
|
remove_columns=dataset.column_names
|
|
)
|
|
return tokenized
|
|
|
|
def setup_trainer(self, train_dataset, val_dataset):
|
|
"""Setup HuggingFace Trainer"""
|
|
training_args = TrainingArguments(
|
|
output_dir=self.config.output_dir,
|
|
num_train_epochs=self.config.num_epochs,
|
|
per_device_train_batch_size=self.config.batch_size,
|
|
per_device_eval_batch_size=self.config.batch_size,
|
|
gradient_accumulation_steps=self.config.gradient_accumulation_steps,
|
|
learning_rate=self.config.learning_rate,
|
|
warmup_ratio=self.config.warmup_ratio,
|
|
logging_dir=self.config.logging_dir,
|
|
logging_steps=10,
|
|
save_steps=self.config.save_steps,
|
|
eval_steps=self.config.eval_steps,
|
|
evaluation_strategy="steps",
|
|
save_strategy="steps",
|
|
load_best_model_at_end=True,
|
|
metric_for_best_model="eval_loss",
|
|
greater_is_better=False,
|
|
fp16=self.config.fp16,
|
|
bf16=self.config.bf16,
|
|
optim=self.config.optim,
|
|
gradient_checkpointing=self.config.gradient_checkpointing,
|
|
deepspeed=self.config.deepspeed_config,
|
|
report_to=["tensorboard"],
|
|
push_to_hub=False,
|
|
)
|
|
|
|
# Data collator
|
|
data_collator = DataCollatorForLanguageModeling(
|
|
tokenizer=self.tokenizer,
|
|
mlm=False,
|
|
)
|
|
|
|
# Initialize trainer
|
|
self.trainer = Trainer(
|
|
model=self.model,
|
|
args=training_args,
|
|
train_dataset=train_dataset,
|
|
eval_dataset=val_dataset,
|
|
data_collator=data_collator,
|
|
)
|
|
|
|
def train(self):
|
|
"""Main training loop"""
|
|
logger.info(f"Starting training for {self.config.model_variant}")
|
|
|
|
# Setup model
|
|
self.setup_model()
|
|
|
|
# Load datasets
|
|
train_dataset, val_dataset = self.load_datasets()
|
|
|
|
# Setup trainer
|
|
self.setup_trainer(train_dataset, val_dataset)
|
|
|
|
# Train
|
|
self.trainer.train()
|
|
|
|
# Save final model
|
|
self.save_model()
|
|
|
|
logger.info("Training completed!")
|
|
|
|
def save_model(self):
|
|
"""Save trained model and configuration"""
|
|
output_path = Path(self.config.output_dir) / self.config.model_variant
|
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Save model
|
|
self.trainer.save_model(str(output_path))
|
|
|
|
# Save tokenizer
|
|
self.tokenizer.save_pretrained(str(output_path))
|
|
|
|
# Save training config
|
|
with open(output_path / "training_config.json", "w") as f:
|
|
json.dump(self.config.__dict__, f, indent=2, default=str)
|
|
|
|
logger.info(f"Model saved to {output_path}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Train Zen models")
|
|
parser.add_argument(
|
|
"--model",
|
|
type=str,
|
|
default="zen-nano",
|
|
choices=["zen-nano", "zen-omni", "zen-coder", "zen-next"],
|
|
help="Model variant to train"
|
|
)
|
|
parser.add_argument(
|
|
"--stage",
|
|
type=str,
|
|
default="instruct",
|
|
choices=["base", "instruct", "thinking"],
|
|
help="Training stage"
|
|
)
|
|
parser.add_argument(
|
|
"--config",
|
|
type=str,
|
|
help="Path to configuration file"
|
|
)
|
|
parser.add_argument(
|
|
"--use-lora",
|
|
action="store_true",
|
|
help="Use LoRA for efficient training"
|
|
)
|
|
parser.add_argument(
|
|
"--use-qlora",
|
|
action="store_true",
|
|
help="Use QLoRA for memory-efficient training"
|
|
)
|
|
parser.add_argument(
|
|
"--use-bitdelta",
|
|
action="store_true",
|
|
help="Use BitDelta for personalization"
|
|
)
|
|
parser.add_argument(
|
|
"--epochs",
|
|
type=int,
|
|
default=3,
|
|
help="Number of training epochs"
|
|
)
|
|
parser.add_argument(
|
|
"--batch-size",
|
|
type=int,
|
|
default=4,
|
|
help="Training batch size"
|
|
)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
type=str,
|
|
default="./checkpoints",
|
|
help="Output directory for checkpoints"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Load or create configuration
|
|
if args.config:
|
|
config = ZenTrainingConfig.from_yaml(args.config)
|
|
else:
|
|
# Get model-specific config
|
|
model_config = get_model_config(args.model)
|
|
|
|
config = ZenTrainingConfig(
|
|
model_variant=args.model,
|
|
base_model=model_config["base_model"],
|
|
training_stage=args.stage,
|
|
use_lora=args.use_lora,
|
|
use_qlora=args.use_qlora,
|
|
use_bitdelta=args.use_bitdelta,
|
|
num_epochs=args.epochs,
|
|
batch_size=args.batch_size,
|
|
output_dir=args.output_dir,
|
|
**model_config.get("training_params", {})
|
|
)
|
|
|
|
# Initialize and run trainer
|
|
trainer = ZenTrainer(config)
|
|
trainer.train()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |