mirror of
https://github.com/zenlm/zen-vl.git
synced 2026-10-30 14:57:35 +00:00
251 lines
8.2 KiB
Python
251 lines
8.2 KiB
Python
"""
|
|
Train Zen VL agents with BOTH ADP and xLAM datasets.
|
|
|
|
This combines:
|
|
1. ADP (Agent Data Protocol) - 1.3M trajectories
|
|
- Web browsing, coding, SWE, tool use
|
|
- 18 diverse configs from neulab
|
|
|
|
2. xLAM (Large Action Models) - 60k trajectories
|
|
- High-quality function calling
|
|
- Multi-step reasoning
|
|
- Complex API interactions
|
|
|
|
Total: ~1.36M training trajectories for state-of-the-art agent capabilities.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import random
|
|
from pathlib import Path
|
|
from typing import List, Dict, Any
|
|
from datasets import Dataset
|
|
from tqdm import tqdm
|
|
|
|
|
|
class DualDatasetTrainer:
|
|
"""Train with combined ADP + xLAM datasets."""
|
|
|
|
def __init__(
|
|
self,
|
|
adp_dir="/Users/z/work/zen/zen-vl/data/adp_full",
|
|
xlam_dir="/Users/z/work/zen/zen-vl/data/xlam",
|
|
output_dir="/Users/z/work/zen/zen-vl/data/combined"
|
|
):
|
|
self.adp_dir = Path(adp_dir)
|
|
self.xlam_dir = Path(xlam_dir)
|
|
self.output_dir = Path(output_dir)
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Mixing strategy
|
|
# ADP: broad coverage, diverse domains
|
|
# xLAM: specialized function calling expertise
|
|
# Ratio: 85% ADP, 15% xLAM (to emphasize function calling)
|
|
self.adp_weight = 0.85
|
|
self.xlam_weight = 0.15
|
|
|
|
self.stats = {
|
|
"adp_examples": 0,
|
|
"xlam_examples": 0,
|
|
"total_combined": 0,
|
|
"by_source": {}
|
|
}
|
|
|
|
def load_adp_data(self) -> List[Dict]:
|
|
"""Load all ADP dataset configs."""
|
|
print("="*80)
|
|
print("Loading ADP Dataset (18 configs)")
|
|
print("="*80)
|
|
|
|
all_examples = []
|
|
|
|
# Load each ADP config
|
|
for json_file in self.adp_dir.glob("*.json"):
|
|
if json_file.name in ["download_stats.json", "stats.json"]:
|
|
continue
|
|
|
|
config_name = json_file.stem
|
|
print(f"📂 Loading {config_name}...")
|
|
|
|
try:
|
|
with open(json_file) as f:
|
|
data = json.load(f)
|
|
|
|
for item in data:
|
|
item["source"] = "adp"
|
|
item["config"] = config_name
|
|
all_examples.append(item)
|
|
|
|
print(f" ✅ {len(data):,} examples")
|
|
self.stats["by_source"][f"adp_{config_name}"] = len(data)
|
|
|
|
except Exception as e:
|
|
print(f" ❌ Error: {e}")
|
|
|
|
self.stats["adp_examples"] = len(all_examples)
|
|
print(f"\n📊 Total ADP: {len(all_examples):,} examples")
|
|
|
|
return all_examples
|
|
|
|
def load_xlam_data(self) -> List[Dict]:
|
|
"""Load xLAM function calling dataset."""
|
|
print("\n" + "="*80)
|
|
print("Loading xLAM Function Calling Dataset (60k)")
|
|
print("="*80)
|
|
|
|
xlam_file = self.xlam_dir / "xlam_adp_format.json"
|
|
|
|
if not xlam_file.exists():
|
|
print(f"⚠️ xLAM data not found at {xlam_file}")
|
|
print("Run download_xlam.py first")
|
|
return []
|
|
|
|
with open(xlam_file) as f:
|
|
data = json.load(f)
|
|
|
|
for item in data:
|
|
item["source"] = "xlam"
|
|
|
|
self.stats["xlam_examples"] = len(data)
|
|
self.stats["by_source"]["xlam"] = len(data)
|
|
|
|
print(f"✅ Loaded {len(data):,} xLAM examples")
|
|
|
|
return data
|
|
|
|
def create_balanced_mixture(
|
|
self,
|
|
adp_data: List[Dict],
|
|
xlam_data: List[Dict]
|
|
) -> List[Dict]:
|
|
"""
|
|
Create balanced training mixture.
|
|
|
|
Strategy:
|
|
- Sample ADP to get 85% of final dataset
|
|
- Sample xLAM to get 15% of final dataset
|
|
- This emphasizes function calling while maintaining broad coverage
|
|
"""
|
|
print("\n" + "="*80)
|
|
print("Creating Balanced Dataset Mixture")
|
|
print("="*80)
|
|
|
|
# Determine target size
|
|
# Use smaller dataset size as base to avoid extreme upsampling
|
|
target_size = min(len(adp_data), len(xlam_data) * 6) # Rough 85/15 ratio
|
|
|
|
adp_target = int(target_size * self.adp_weight)
|
|
xlam_target = int(target_size * self.xlam_weight)
|
|
|
|
print(f"\n🎯 Target mixture:")
|
|
print(f" ADP: {adp_target:,} examples ({self.adp_weight*100:.0f}%)")
|
|
print(f" xLAM: {xlam_target:,} examples ({self.xlam_weight*100:.0f}%)")
|
|
print(f" Total: {adp_target + xlam_target:,}")
|
|
|
|
# Sample with replacement if needed
|
|
print("\n🔀 Sampling datasets...")
|
|
|
|
if adp_target <= len(adp_data):
|
|
adp_sample = random.sample(adp_data, adp_target)
|
|
else:
|
|
adp_sample = random.choices(adp_data, k=adp_target)
|
|
|
|
if xlam_target <= len(xlam_data):
|
|
xlam_sample = random.sample(xlam_data, xlam_target)
|
|
else:
|
|
xlam_sample = random.choices(xlam_data, k=xlam_target)
|
|
|
|
# Combine and shuffle
|
|
combined = adp_sample + xlam_sample
|
|
random.shuffle(combined)
|
|
|
|
self.stats["total_combined"] = len(combined)
|
|
|
|
print(f"\n✅ Created combined dataset: {len(combined):,} examples")
|
|
|
|
return combined
|
|
|
|
def save_training_data(self, combined_data: List[Dict]):
|
|
"""Save combined training data."""
|
|
print("\n💾 Saving combined dataset...")
|
|
|
|
# Split into train/val (90/10)
|
|
split_idx = int(len(combined_data) * 0.9)
|
|
train_data = combined_data[:split_idx]
|
|
val_data = combined_data[split_idx:]
|
|
|
|
# Save
|
|
train_file = self.output_dir / "train_adp_xlam.json"
|
|
val_file = self.output_dir / "val_adp_xlam.json"
|
|
stats_file = self.output_dir / "mixture_stats.json"
|
|
|
|
with open(train_file, 'w') as f:
|
|
json.dump(train_data, f)
|
|
|
|
with open(val_file, 'w') as f:
|
|
json.dump(val_data, f)
|
|
|
|
with open(stats_file, 'w') as f:
|
|
json.dump(self.stats, f, indent=2)
|
|
|
|
print(f"✅ Train: {len(train_data):,} → {train_file}")
|
|
print(f"✅ Val: {len(val_data):,} → {val_file}")
|
|
print(f"✅ Stats: {stats_file}")
|
|
|
|
def create_combined_dataset(self):
|
|
"""Main pipeline to create combined dataset."""
|
|
# Load both datasets
|
|
adp_data = self.load_adp_data()
|
|
xlam_data = self.load_xlam_data()
|
|
|
|
if not adp_data:
|
|
print("❌ No ADP data found, run download_full_adp.py first")
|
|
return
|
|
|
|
if not xlam_data:
|
|
print("⚠️ No xLAM data found, will train on ADP only")
|
|
print("Run download_xlam.py to add xLAM data")
|
|
combined_data = adp_data
|
|
else:
|
|
# Create balanced mixture
|
|
combined_data = self.create_balanced_mixture(adp_data, xlam_data)
|
|
|
|
# Save
|
|
self.save_training_data(combined_data)
|
|
|
|
# Print summary
|
|
print("\n" + "="*80)
|
|
print("📊 Dataset Summary")
|
|
print("="*80)
|
|
print(f"ADP examples: {self.stats['adp_examples']:,}")
|
|
print(f"xLAM examples: {self.stats['xlam_examples']:,}")
|
|
print(f"Combined: {self.stats['total_combined']:,}")
|
|
print(f"\nMixture ratio: {self.adp_weight*100:.0f}% ADP, {self.xlam_weight*100:.0f}% xLAM")
|
|
|
|
return combined_data
|
|
|
|
|
|
def main():
|
|
"""Create combined ADP + xLAM training dataset."""
|
|
print("="*80)
|
|
print("Dual Dataset Preparation: ADP + xLAM")
|
|
print("="*80)
|
|
print("This creates a powerful training mixture combining:")
|
|
print(" • ADP: Broad agent capabilities (web, coding, SWE, tools)")
|
|
print(" • xLAM: Specialized function calling expertise")
|
|
print("="*80)
|
|
|
|
trainer = DualDatasetTrainer()
|
|
combined = trainer.create_combined_dataset()
|
|
|
|
if combined:
|
|
print("\n✅ Dual dataset ready for training!")
|
|
print(f"📁 Location: {trainer.output_dir}")
|
|
print("\nNext steps:")
|
|
print(" 1. Train with: train_with_adp.py --dual-dataset")
|
|
print(" 2. Expected gain: +20-25% over base models")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|