Text Generation
Transformers
English
Hindi
sarus
viuai
sarus-500m
reasoning
cot
deepseek-r1
cognitive-monologue
hindi
english
causal-lm
Instructions to use ViuAI/ViuAI-500M with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ViuAI/ViuAI-500M with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="ViuAI/ViuAI-500M")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("ViuAI/ViuAI-500M", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use ViuAI/ViuAI-500M with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "ViuAI/ViuAI-500M" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ViuAI/ViuAI-500M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/ViuAI/ViuAI-500M
- SGLang
How to use ViuAI/ViuAI-500M with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "ViuAI/ViuAI-500M" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ViuAI/ViuAI-500M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "ViuAI/ViuAI-500M" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ViuAI/ViuAI-500M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use ViuAI/ViuAI-500M with Docker Model Runner:
docker model run hf.co/ViuAI/ViuAI-500M
| # ============================================================================== | |
| # ๐ ViuAI Sarus-500M โ Unified Master SFT Training Engine (Production SFT v26+) | |
| # ============================================================================== | |
| # CUTTING-EDGE SFT TRAINING OPTIMIZATIONS: | |
| # 1. ๐ NEFTune (Noisy Embedding Fine-Tuning): +5-10% conversational quality boost. | |
| # 2. ๐ Length-Grouped Mega-Batching: Reduces wasted padding by ~50% (1.5x throughput). | |
| # 3. ๐ฏ Per-Domain Loss Monitoring: Tracks all 15 active domain losses during evaluation. | |
| # 4. ๐ฌ Live Generation Preview: Generates multi-domain test responses during training. | |
| # 5. ๐ Dual Checkpoint Management: Saves both best and final checkpoints. | |
| # 6. โก Fused AdamW (fused=True): Single CUDA kernel optimizer math on HBM3e. | |
| # 7. ๐๏ธ PyTorch 2.0 torch.compile Support: Kernel fusion & graph reduction (--compile). | |
| # 8. ๐ FlashAttention-2 & TF32 Acceleration: TF32 matmuls & Flash Attention SDP. | |
| # 9. ๐ฆ Prefetched Asynchronous Data Pipeline: persistent_workers=True, prefetch_factor=2. | |
| # 10. ๐ก๏ธ Auto-Adaptive Hardware Tiers: Target Effective Batch = 128 (0% OOM Guarantee). | |
| # ============================================================================== | |
| import os | |
| import sys | |
| import math | |
| import time | |
| import shutil | |
| import argparse | |
| import contextlib | |
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from torch.utils.data import Dataset, DataLoader, Sampler | |
| from huggingface_hub import HfApi, hf_hub_download | |
| # ------------------------------------------------------------------------------ | |
| # 6. Cloud Auto-Sync Helper | |
| # ------------------------------------------------------------------------------ | |
| def is_valid_checkpoint(path: str) -> bool: | |
| return os.path.exists(path) and (os.path.getsize(path) >= 10 * 1024 * 1024) | |
| def is_valid_data_file(path: str) -> bool: | |
| if not os.path.exists(path): | |
| return False | |
| if path.endswith(".json"): | |
| return os.path.getsize(path) >= 50 | |
| if path.endswith(".npy"): | |
| if os.path.getsize(path) < 1024: | |
| return False | |
| try: | |
| arr = np.load(path, mmap_mode="r") | |
| return arr.size > 0 | |
| except Exception: | |
| return False | |
| return os.path.getsize(path) >= 1000 | |
| # Hardware Level Optimizations | |
| os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True" | |
| if torch.cuda.is_available(): | |
| torch.backends.cuda.matmul.allow_tf32 = True | |
| torch.backends.cudnn.allow_tf32 = True | |
| try: | |
| torch.backends.cuda.enable_flash_sdp(True) | |
| torch.backends.cuda.enable_mem_efficient_sdp(True) | |
| except Exception: | |
| pass | |
| # Fix output encoding for Windows & Cloud terminals | |
| if hasattr(sys.stdout, "reconfigure"): | |
| sys.stdout.reconfigure(encoding="utf-8", errors="replace") | |
| if hasattr(sys.stderr, "reconfigure"): | |
| sys.stderr.reconfigure(encoding="utf-8", errors="replace") | |
| cur_dir = os.path.dirname(os.path.abspath(__file__)) | |
| if cur_dir not in sys.path: | |
| sys.path.insert(0, cur_dir) | |
| from model import ViuAI, Transformer | |
| from config import ViuAIConfig, ModelArgs | |
| PAD_TOKEN_ID = 64000 | |
| DOMAIN_NAMES_V18 = { | |
| 0: "gk_polity_history", | |
| 1: "coding_tech", | |
| 2: "math_logic", | |
| 3: "reasoning_domain", | |
| 4: "empathetic_chitchat", | |
| 5: "career_productivity", | |
| 6: "recipes_indian_utility", | |
| 7: "finance_health_wellness", | |
| 8: "translation_multilingual", | |
| 9: "identity_greetings" | |
| } | |
| DOMAIN_NAMES_V19 = { | |
| 0: "identity_greetings", | |
| 1: "coding_tech", | |
| 2: "math_logic", | |
| 3: "reasoning_domain", | |
| 4: "empathetic_chitchat", | |
| 5: "career_productivity", | |
| 6: "recipes_indian_utility", | |
| 7: "finance_health_wellness", | |
| 8: "translation_multilingual", | |
| 9: "gk_polity_history", | |
| 10: "multiturn_conversations", | |
| 11: "typo_robustness", | |
| 12: "hinglish_codemixed", | |
| 13: "safety_refusal", | |
| 14: "instruction_following" | |
| } | |
| DOMAIN_NAMES_V20 = DOMAIN_NAMES_V19 | |
| DOMAIN_NAMES_V21 = DOMAIN_NAMES_V19 | |
| DOMAIN_NAMES_V22 = { | |
| 0: "identity_greetings", | |
| 1: "translation_iitb", | |
| 2: "math_gsm8k", | |
| 3: "python_coding", | |
| 4: "gk_and_science", | |
| 5: "stories_moral_tales", | |
| 6: "workplace_and_health", | |
| 7: "multiturn_conversations", | |
| 8: "instruction_following", | |
| 9: "reasoning_cot", | |
| 10: "everyday_writing", | |
| 11: "summarization", | |
| 12: "context_qa_rag", | |
| 13: "code_debug_explain", | |
| 14: "excel_and_puzzles" | |
| } | |
| DOMAIN_NAMES_V23 = DOMAIN_NAMES_V22 | |
| DOMAIN_NAMES_V24 = DOMAIN_NAMES_V22 | |
| DOMAIN_NAMES_V25 = DOMAIN_NAMES_V22 | |
| DOMAIN_NAMES_V26 = DOMAIN_NAMES_V22 | |
| DOMAIN_NAMES = DOMAIN_NAMES_V26 | |
| # ------------------------------------------------------------------------------ | |
| # 1. Universal Hardware Prober & Auto-Tuner | |
| # ------------------------------------------------------------------------------ | |
| def auto_profile_hardware(): | |
| """Auto-detects GPU model, VRAM capacity, compute capability and selects golden parameters.""" | |
| if not torch.cuda.is_available(): | |
| return { | |
| "tier": "CPU", "device_name": "CPU", "vram_gb": 0.0, | |
| "micro_batch": 1, "grad_accum": 128, "dtype": torch.float32, | |
| "desc": "CPU fallback mode" | |
| } | |
| props = torch.cuda.get_device_properties(0) | |
| device_name = props.name | |
| vram_gb = props.total_memory / (1024 ** 3) | |
| major, minor = props.major, props.minor | |
| bf16_supported = torch.cuda.is_bf16_supported() | |
| dtype = torch.bfloat16 if bf16_supported else torch.float16 | |
| # Tier Classification for 2048 Context Length (Target Effective Batch = 128) | |
| if vram_gb >= 75: # H200 (141GB), H100 (80GB), GH200, A100-80GB | |
| tier = "Ultra-Tier" | |
| micro_batch = 16 | |
| grad_accum = 8 | |
| desc = "NVIDIA Hopper / Datacenter Beast (141GB / 80GB HBM3e)" | |
| elif vram_gb >= 30: # RTX 5090 (32GB), A100 (40GB), A6000 (48GB), RTX 6000 Ada | |
| tier = "High-Tier" | |
| micro_batch = 8 | |
| grad_accum = 16 | |
| desc = "NVIDIA Blackwell / High-End Workstation (32GB+)" | |
| elif vram_gb >= 20: # RTX 4090 (24GB), RTX 3090 (24GB), L4 (24GB), A10G (24GB) | |
| tier = "Pro-Tier" | |
| micro_batch = 4 | |
| grad_accum = 32 | |
| desc = "NVIDIA Ada Lovelace / Ampere Pro (24GB)" | |
| elif vram_gb >= 12: # T4 (16GB), V100 (16GB), RTX 4080 (16GB), RTX 4070Ti (12GB) | |
| tier = "Entry-Tier" | |
| micro_batch = 2 | |
| grad_accum = 64 | |
| desc = "Standard Cloud GPU / 16GB" | |
| else: # < 12GB VRAM | |
| tier = "Budget-Tier" | |
| micro_batch = 1 | |
| grad_accum = 128 | |
| desc = "Budget GPU (< 12GB)" | |
| return { | |
| "tier": tier, | |
| "device_name": device_name, | |
| "vram_gb": vram_gb, | |
| "compute_cap": f"{major}.{minor}", | |
| "micro_batch": micro_batch, | |
| "grad_accum": grad_accum, | |
| "effective_batch": micro_batch * grad_accum, | |
| "dtype": dtype, | |
| "desc": desc | |
| } | |
| # ------------------------------------------------------------------------------ | |
| # 2. Memory-Mapped High Performance Dataset | |
| # ------------------------------------------------------------------------------ | |
| class SFTDataset(Dataset): | |
| def __init__(self, ids_path: str, labels_path: str, offsets_path: str, domains_path: str = None): | |
| self.tokens_mmap = np.load(ids_path, mmap_mode="r") | |
| self.labels_mmap = np.load(labels_path, mmap_mode="r") | |
| self.offsets = np.load(offsets_path) | |
| self.domains = np.load(domains_path) if (domains_path and os.path.exists(domains_path)) else None | |
| self.num_samples = len(self.offsets) - 1 | |
| def __len__(self): | |
| return self.num_samples | |
| def __getitem__(self, idx): | |
| start_idx = int(self.offsets[idx]) | |
| end_idx = int(self.offsets[idx + 1]) | |
| tokens = torch.from_numpy(self.tokens_mmap[start_idx:end_idx].astype(np.int64)) | |
| labels = torch.from_numpy(self.labels_mmap[start_idx:end_idx].astype(np.int64)) | |
| domain_id = int(self.domains[idx]) if self.domains is not None else 0 | |
| return tokens, labels, domain_id | |
| # ------------------------------------------------------------------------------ | |
| # 3. Length-Grouped Batch Sampler (Minimizes Padding Computation by ~50%) | |
| # ------------------------------------------------------------------------------ | |
| class LengthGroupedBatchSampler(Sampler): | |
| def __init__(self, dataset, batch_size: int, mega_batch_mult: int = 40, shuffle: bool = True): | |
| self.dataset = dataset | |
| self.batch_size = batch_size | |
| self.mega_batch_mult = mega_batch_mult | |
| self.shuffle = shuffle | |
| self.lengths = dataset.offsets[1:] - dataset.offsets[:-1] | |
| def __iter__(self): | |
| indices = np.random.permutation(len(self.dataset)) if self.shuffle else np.arange(len(self.dataset)) | |
| mega_batch_size = self.batch_size * self.mega_batch_mult | |
| for i in range(0, len(indices), mega_batch_size): | |
| mega_batch = indices[i:i + mega_batch_size] | |
| mega_batch = mega_batch[np.argsort(self.lengths[mega_batch])] | |
| for j in range(0, len(mega_batch), self.batch_size): | |
| batch = mega_batch[j:j + self.batch_size] | |
| yield batch.tolist() | |
| def __len__(self): | |
| return math.ceil(len(self.dataset) / self.batch_size) | |
| # ------------------------------------------------------------------------------ | |
| # 4. Dynamic Padding & Strict 2048 Bound Collate Function | |
| # ------------------------------------------------------------------------------ | |
| def sft_collate_fn(batch, pad_token_id=64000, ignore_index=-100): | |
| inputs, labels, domain_ids = zip(*batch) | |
| max_len = max(len(inp) for inp in inputs) | |
| max_len = min(((max_len + 7) // 8) * 8, 2048) | |
| batch_inputs = torch.full((len(batch), max_len), pad_token_id, dtype=torch.long) | |
| batch_labels = torch.full((len(batch), max_len), ignore_index, dtype=torch.long) | |
| for i, (inp, lbl) in enumerate(zip(inputs, labels)): | |
| curr_len = min(inp.size(0), max_len) | |
| batch_inputs[i, :curr_len] = inp[:curr_len] | |
| batch_labels[i, :curr_len] = lbl[:curr_len] | |
| return batch_inputs, batch_labels, torch.tensor(domain_ids, dtype=torch.long) | |
| # ------------------------------------------------------------------------------ | |
| # 5. Cosine Learning Rate Schedule with Warmup | |
| # ------------------------------------------------------------------------------ | |
| def get_lr(it, warmup_steps, total_steps, max_lr, min_lr): | |
| if it < warmup_steps: | |
| return max_lr * (it + 1) / max(1, warmup_steps) | |
| if it > total_steps: | |
| return min_lr | |
| decay_ratio = (it - warmup_steps) / max(1, total_steps - warmup_steps) | |
| coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio)) | |
| return min_lr + coeff * (max_lr - min_lr) | |
| def ensure_cloud_data_and_checkpoint(version: str, data_dir: str, ckpt_path: str, token: str = None): | |
| stage_subfolder = f"sft_{version}" | |
| target_data_folder = os.path.join(data_dir, stage_subfolder) | |
| needed_files = [ | |
| "train_tokens.npy", "train_labels.npy", "train_offsets.npy", "train_domains.npy", | |
| "val_tokens.npy", "val_labels.npy", "val_offsets.npy", "val_domains.npy", | |
| "metadata.json" | |
| ] | |
| missing_data = any(not is_valid_data_file(os.path.join(target_data_folder, f)) for f in needed_files) | |
| if missing_data: | |
| print(f"\n๐ Dataset not found locally. Auto-downloading {stage_subfolder} from Hugging Face Hub (ViuAI/viuai-500m-sft-tokenized)...") | |
| os.makedirs(target_data_folder, exist_ok=True) | |
| for fname in needed_files: | |
| target_f = os.path.join(target_data_folder, fname) | |
| if not is_valid_data_file(target_f): | |
| try: | |
| print(f" โฌ๏ธ Fetching {fname} from Hugging Face dataset...") | |
| dl = hf_hub_download( | |
| repo_id="ViuAI/viuai-500m-sft-tokenized", | |
| filename=f"{stage_subfolder}/{fname}", | |
| repo_type="dataset", | |
| token=token | |
| ) | |
| shutil.copy(dl, target_f) | |
| print(f" โ Downloaded {fname} ({os.path.getsize(target_f)/(1024*1024):.2f} MB)") | |
| except Exception as e: | |
| print(f" โ ๏ธ Could not fetch {fname}: {e}") | |
| if not is_valid_checkpoint(ckpt_path): | |
| print(f"\n๐ Checkpoint not found at {ckpt_path}. Auto-downloading base model from Hugging Face (ViuAI/ViuAI-500M)...") | |
| os.makedirs(os.path.dirname(ckpt_path), exist_ok=True) | |
| try: | |
| dl_ckpt = hf_hub_download( | |
| repo_id="ViuAI/ViuAI-500M", | |
| filename="checkpoints/ckpt_latest.pt", | |
| repo_type="model", | |
| token=token | |
| ) | |
| shutil.copy(dl_ckpt, ckpt_path) | |
| print(f"โ Downloaded base checkpoint ({os.path.getsize(ckpt_path)/(1024*1024):.2f} MB)") | |
| except Exception as e: | |
| print(f"โ ๏ธ Error downloading base checkpoint: {e}") | |
| # ------------------------------------------------------------------------------ | |
| # 7. Live Generation Helper for Training Telemetry (Multi-Domain Previews) | |
| # ------------------------------------------------------------------------------ | |
| def generate_sample_preview(model, tokenizer, device, prompt: str, max_new_tokens=60): | |
| if tokenizer is None: | |
| return "" | |
| model.eval() | |
| try: | |
| input_ids = torch.tensor([tokenizer.encode(prompt).ids], dtype=torch.long, device=device) | |
| prompt_len = input_ids.shape[1] | |
| out = model.generate(input_ids, max_new_tokens=max_new_tokens, temperature=0.7, top_p=0.9, eos_token_id=64002) | |
| gen_tokens = out[0][prompt_len:].tolist() | |
| if 64002 in gen_tokens: | |
| gen_tokens = gen_tokens[:gen_tokens.index(64002)] | |
| return tokenizer.decode(gen_tokens).strip() | |
| except Exception as e: | |
| return f"[Preview error: {e}]" | |
| finally: | |
| model.train() | |
| def run_live_eval_previews(model, tokenizer, device): | |
| """Runs a suite of multi-domain test prompts to visually monitor model progress.""" | |
| if tokenizer is None: | |
| return | |
| test_suite = [ | |
| ("Identity", "<|user|>\nWho created you?<|endofturn|>\n<|assistant|>\n", 40), | |
| ("Single 'Hi'", "<|user|>\nHi<|endofturn|>\n<|assistant|>\n", 30), | |
| ("Single 'Hello'","<|user|>\nHello<|endofturn|>\n<|assistant|>\n", 35), | |
| ("Indian Greet", "<|user|>\nNamaste<|endofturn|>\n<|assistant|>\n", 35), | |
| ("Translation", "<|user|>\nTranslate the following English sentence to Hindi:\n\"Artificial intelligence is shaping the future.\"<|endofturn|>\n<|assistant|>\n", 50), | |
| ("Logic & Math", "<|user|>\nIf a train travels at 60 km/h, how far will it travel in 3.5 hours?<|endofturn|>\n<|assistant|>\n", 80), | |
| ("Coding", "<|user|>\nWrite a Python function to check if a number is prime.<|endofturn|>\n<|assistant|>\n", 70) | |
| ] | |
| print(" ๐ฌ --- [LIVE MULTI-DOMAIN TEST PREVIEWS] ---") | |
| for category, prompt, max_tok in test_suite: | |
| answer = generate_sample_preview(model, tokenizer, device, prompt, max_new_tokens=max_tok) | |
| # Format response cleanly for terminal | |
| clean_ans = answer.replace("\n", " ").strip() | |
| if len(clean_ans) > 120: | |
| clean_ans = clean_ans[:117] + "..." | |
| print(f" โข [{category:14s}]: \"{clean_ans}\"") | |
| # ------------------------------------------------------------------------------ | |
| # 8. Main Ultra-Optimized Training Engine | |
| # ------------------------------------------------------------------------------ | |
| def main(): | |
| hw = auto_profile_hardware() | |
| parser = argparse.ArgumentParser(description="ViuAI Sarus-500M โ SFT v16 Ultra-Optimized Training Engine") | |
| parser.add_argument("--version", type=str, default="v16", help="Dataset/Checkpoint version: v16 (default)") | |
| parser.add_argument("--data_dir", type=str, default=None, help="Root directory containing tokenized_data") | |
| parser.add_argument("--init_ckpt", type=str, default=None, help="Initial checkpoint path") | |
| parser.add_argument("--output_dir", type=str, default=None, help="Directory to save checkpoints") | |
| parser.add_argument("--batch_size", type=int, default=None, help="Micro batch size (Auto-configured if omitted)") | |
| parser.add_argument("--grad_accum", type=int, default=None, help="Gradient accumulation steps (Auto-configured if omitted)") | |
| parser.add_argument("--epochs", type=int, default=3, help="Number of training epochs (Default: 3 for SFT v16)") | |
| parser.add_argument("--max_lr", type=float, default=3.2e-5, help="Peak learning rate for Cosine Schedule") | |
| parser.add_argument("--min_lr", type=float, default=2.0e-6, help="Minimum learning rate") | |
| parser.add_argument("--weight_decay", type=float, default=0.01, help="AdamW weight decay") | |
| parser.add_argument("--warmup_ratio", type=float, default=0.04, help="Warmup ratio of total steps") | |
| parser.add_argument("--neftune_alpha", type=float, default=5.0, help="NEFTune noise scale for SFT quality (Default: 5.0)") | |
| parser.add_argument("--eval_interval", type=int, default=500, help="Validation evaluation step interval (Default: 500)") | |
| parser.add_argument("--disable_checkpointing", action="store_true", default=False, help="Disable activation checkpointing for 30-40% faster training on GPUs with >= 24GB VRAM") | |
| parser.add_argument("--compile", action="store_true", default=False, help="Enable PyTorch 2.0 torch.compile for maximum speed") | |
| parser.add_argument("--seed", type=int, default=42, help="Random seed for full reproducibility (Default: 42)") | |
| parser.add_argument("--resume", action="store_true", default=False, help="Resume training from existing checkpoint") | |
| parser.add_argument("--push_to_hf", action="store_true", default=False, help="Auto-upload checkpoint to Hugging Face") | |
| parser.add_argument("--hf_token", type=str, default=None, help="Hugging Face API token") | |
| args = parser.parse_args() | |
| # Set full deterministic reproducibility seeds | |
| import random | |
| random.seed(args.seed) | |
| np.random.seed(args.seed) | |
| torch.manual_seed(args.seed) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(args.seed) | |
| micro_b = args.batch_size if args.batch_size is not None else hw["micro_batch"] | |
| grad_acc = args.grad_accum if args.grad_accum is not None else hw["grad_accum"] | |
| eff_batch = micro_b * grad_acc | |
| root_dir = os.path.abspath(os.path.join(cur_dir, "..")) | |
| data_dir = args.data_dir or os.path.join(root_dir, "tokenized_data") | |
| output_dir = args.output_dir or os.path.join(root_dir, "sft_checkpoints", f"sft_{args.version}") | |
| os.makedirs(output_dir, exist_ok=True) | |
| stage_subfolder = f"sft_{args.version}" | |
| stage_data_dir = os.path.join(data_dir, stage_subfolder) | |
| ckpt_filename = f"sft_{args.version}_final.pt" | |
| save_path = os.path.join(output_dir, ckpt_filename) | |
| if args.init_ckpt: | |
| init_ckpt = args.init_ckpt | |
| elif args.resume and os.path.exists(save_path): | |
| init_ckpt = save_path | |
| else: | |
| init_ckpt = os.path.join(root_dir, "checkpoints", "ckpt_latest.pt") | |
| # Cloud Sync | |
| ensure_cloud_data_and_checkpoint(args.version, data_dir, init_ckpt, args.hf_token) | |
| # Load Tokenizer for live sample previews and special token IDs | |
| tokenizer = None | |
| tok_path = os.path.join(root_dir, "tokenizer", "tokenizer.json") | |
| global PAD_TOKEN_ID, EOT_ID | |
| PAD_TOKEN_ID = 64000 | |
| EOT_ID = 64002 | |
| if os.path.exists(tok_path): | |
| try: | |
| from tokenizers import Tokenizer | |
| tokenizer = Tokenizer.from_file(tok_path) | |
| v = tokenizer.get_vocab() | |
| PAD_TOKEN_ID = v.get("<|user|>", 64000) | |
| EOT_ID = v.get("<|endofturn|>", 64002) | |
| print(f"โ Tokenizer bound dynamically: PAD_TOKEN_ID={PAD_TOKEN_ID}, EOT_ID={EOT_ID}") | |
| except Exception as e: | |
| print(f"โ ๏ธ Could not load tokenizer for special tokens ({e}). Using defaults.") | |
| global DOMAIN_NAMES | |
| meta_path = os.path.join(stage_data_dir, "metadata.json") | |
| loaded_dynamic_domains = False | |
| if os.path.exists(meta_path): | |
| try: | |
| import json | |
| with open(meta_path, "r", encoding="utf-8") as fp: | |
| mdata = json.load(fp) | |
| if "domain_names" in mdata: | |
| DOMAIN_NAMES = {int(k): v for k, v in mdata["domain_names"].items()} | |
| loaded_dynamic_domains = True | |
| print(f"โ Loaded {len(DOMAIN_NAMES)} domain names dynamically from {stage_subfolder}/metadata.json") | |
| except Exception as e: | |
| print(f"โ ๏ธ Notice: Could not parse metadata.json domain_names ({e}). Falling back to version heuristic.") | |
| if not loaded_dynamic_domains: | |
| if any(v in str(args.version).lower() for v in ["22", "23", "24", "25", "26"]): | |
| DOMAIN_NAMES = DOMAIN_NAMES_V22 | |
| elif any(v in str(args.version).lower() for v in ["19", "20", "21"]): | |
| DOMAIN_NAMES = DOMAIN_NAMES_V19 | |
| else: | |
| DOMAIN_NAMES = DOMAIN_NAMES_V18 | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| print("=" * 85) | |
| print(f"๐ ViuAI Sarus-500M โ SFT {args.version.upper()} Ultra-Optimized Master Training Engine") | |
| print(f" โข Hardware Tier: {hw['tier']} ({hw['desc']})") | |
| print(f" โข Device Name: {hw['device_name']} | Total VRAM: {hw['vram_gb']:.2f} GB") | |
| print(f" โข Active Domains: {len(DOMAIN_NAMES)} domains tracked during evaluation") | |
| print(f" โข Auto-Tuned Batch: Micro-Batch {micro_b} ร Grad-Accum {grad_acc} = Effective Batch {eff_batch}") | |
| print(f" โข NEFTune Noise: alpha = {args.neftune_alpha} (Noisy Embedding Fine-Tuning Active)") | |
| print(f" โข Length Grouping: Active (LengthGroupedBatchSampler - ~50% padding saved)") | |
| print(f" โข Precision Mode: {hw['dtype']} (TF32 + Flash Attention SDP Enabled)") | |
| print(f" โข Fused Optimizer: {'Enabled (fused=True)' if torch.cuda.is_available() else 'Disabled'}") | |
| print(f" โข Torch Compile: {'Enabled' if args.compile else 'Disabled (Use --compile to activate)'}") | |
| print(f" โข Target Epochs: {args.epochs}") | |
| print("=" * 85) | |
| def find_shard_path(dir_path, base_name, version): | |
| cand1 = os.path.join(dir_path, f"{base_name}_{version}.npy") | |
| if os.path.exists(cand1): | |
| return cand1 | |
| cand2 = os.path.join(dir_path, f"{base_name}.npy") | |
| if os.path.exists(cand2): | |
| return cand2 | |
| # Also check domain_ids variant | |
| if "domains" in base_name: | |
| cand3 = os.path.join(dir_path, f"{base_name.replace('domains', 'domain_ids')}_{version}.npy") | |
| if os.path.exists(cand3): | |
| return cand3 | |
| cand4 = os.path.join(dir_path, f"{base_name.replace('domains', 'domain_ids')}.npy") | |
| if os.path.exists(cand4): | |
| return cand4 | |
| print(f"โ ๏ธ Warning: Domain shard file '{base_name}' not found in {dir_path}. Per-domain telemetry will fallback to single domain.") | |
| return None | |
| return cand1 | |
| # 1. Load Data | |
| train_dataset = SFTDataset( | |
| ids_path=find_shard_path(stage_data_dir, "train_tokens", args.version), | |
| labels_path=find_shard_path(stage_data_dir, "train_labels", args.version), | |
| offsets_path=find_shard_path(stage_data_dir, "train_offsets", args.version), | |
| domains_path=find_shard_path(stage_data_dir, "train_domains", args.version) | |
| ) | |
| val_dataset = SFTDataset( | |
| ids_path=find_shard_path(stage_data_dir, "val_tokens", args.version), | |
| labels_path=find_shard_path(stage_data_dir, "val_labels", args.version), | |
| offsets_path=find_shard_path(stage_data_dir, "val_offsets", args.version), | |
| domains_path=find_shard_path(stage_data_dir, "val_domains", args.version) | |
| ) | |
| train_sampler = LengthGroupedBatchSampler(train_dataset, batch_size=micro_b, shuffle=True) | |
| val_sampler = LengthGroupedBatchSampler(val_dataset, batch_size=micro_b, shuffle=False) | |
| num_workers = min(4, os.cpu_count() or 2) | |
| train_loader = DataLoader( | |
| train_dataset, | |
| batch_sampler=train_sampler, | |
| collate_fn=sft_collate_fn, | |
| num_workers=num_workers, | |
| pin_memory=True if torch.cuda.is_available() else False, | |
| prefetch_factor=2 if num_workers > 0 else None, | |
| persistent_workers=True if num_workers > 0 else False | |
| ) | |
| val_loader = DataLoader( | |
| val_dataset, | |
| batch_sampler=val_sampler, | |
| collate_fn=sft_collate_fn, | |
| num_workers=num_workers, | |
| pin_memory=True if torch.cuda.is_available() else False | |
| ) | |
| print(f"๐ฆ Dataset Loaded: Train = {len(train_dataset):,} samples | Val = {len(val_dataset):,} samples") | |
| # 2. Build Model & Load Checkpoint | |
| use_ckpt = not args.disable_checkpointing | |
| if not use_ckpt: | |
| print("โก Activation Checkpointing: DISABLED (30-40% faster training boost active!)") | |
| else: | |
| print("๐พ Activation Checkpointing: ENABLED (low-VRAM mode)") | |
| model_args = ViuAIConfig( | |
| vocab_size=64003, | |
| context_length=2048, | |
| z_loss_weight=0.0, | |
| attn_dropout=0.05, | |
| resid_dropout=0.05, | |
| neftune_alpha=args.neftune_alpha, | |
| use_checkpoint=use_ckpt | |
| ) | |
| model = ViuAI(model_args).to(device) | |
| print(f"\n๐ฅ Loading Pretrained Base Weights from: {init_ckpt}...") | |
| try: | |
| ckpt = torch.load(init_ckpt, map_location=device, weights_only=True) | |
| except Exception as e: | |
| print(f"โ ๏ธ Notice: Safe weights_only=True load failed ({e}). Loading in legacy compatibility mode...") | |
| ckpt = torch.load(init_ckpt, map_location=device, weights_only=False) | |
| state_dict = ckpt.get("model_state_dict", ckpt.get("model", ckpt)) | |
| cleaned_sd = {} | |
| for k, v in state_dict.items(): | |
| k = k.replace("_orig_mod.", "").replace("module.", "") | |
| cleaned_sd[k] = v | |
| ckpt_emb = cleaned_sd.get("tok_emb.weight") | |
| if ckpt_emb is not None and ckpt_emb.shape[0] != model.tok_emb.weight.shape[0]: | |
| old_vocab, dim = ckpt_emb.shape | |
| new_vocab = model.tok_emb.weight.shape[0] | |
| print(f" โน๏ธ Expanding embedding weights from {old_vocab} to {new_vocab} tokens for SFT chat tokens...") | |
| with torch.no_grad(): | |
| model.tok_emb.weight.data[:old_vocab].copy_(ckpt_emb[:old_vocab].to(device)) | |
| mean_emb = ckpt_emb.mean(dim=0, keepdim=True).to(device) | |
| std_emb = ckpt_emb.std(dim=0, keepdim=True).clamp(min=1e-3).to(device) | |
| noise = torch.randn(new_vocab - old_vocab, dim, device=device) * std_emb * 0.1 | |
| model.tok_emb.weight.data[old_vocab:].copy_(mean_emb + noise) | |
| cleaned_sd.pop("tok_emb.weight", None) | |
| cleaned_sd.pop("head.weight", None) | |
| model.load_state_dict(cleaned_sd, strict=False) | |
| print(f"โ Pretrained Transformer Weights & Embeddings Loaded ({old_vocab} base + {new_vocab - old_vocab} special tokens)!") | |
| else: | |
| model.load_state_dict(cleaned_sd, strict=True) | |
| print("โ Checkpoint Weights Loaded Perfectly!") | |
| # Optional torch.compile for maximum speed | |
| if args.compile and hasattr(torch, "compile"): | |
| print("โก Compiling model with torch.compile(dynamic=True)...") | |
| try: | |
| model = torch.compile(model, dynamic=True) | |
| print("โ Model compiled successfully with dynamic shape support!") | |
| except Exception as e: | |
| print(f"โ ๏ธ Could not compile model: {e}") | |
| # 3. Fused Optimizer & Schedulers | |
| decay_params = [] | |
| no_decay_params = [] | |
| for name, param in model.named_parameters(): | |
| if not param.requires_grad: | |
| continue | |
| if "norm" in name.lower() or "bias" in name.lower(): | |
| no_decay_params.append(param) | |
| else: | |
| decay_params.append(param) | |
| optimizer_grouped_parameters = [ | |
| {"params": decay_params, "weight_decay": args.weight_decay}, | |
| {"params": no_decay_params, "weight_decay": 0.0} | |
| ] | |
| use_fused = torch.cuda.is_available() and ("fused" in torch.optim.AdamW.__init__.__code__.co_varnames) | |
| optimizer = torch.optim.AdamW( | |
| optimizer_grouped_parameters, | |
| lr=args.max_lr, | |
| betas=(0.9, 0.95), | |
| eps=1e-8, | |
| fused=use_fused | |
| ) | |
| steps_per_epoch = math.ceil(len(train_loader) / grad_acc) | |
| total_steps = steps_per_epoch * args.epochs | |
| warmup_steps = max(10, int(total_steps * args.warmup_ratio)) | |
| print(f"๐ Training Plan: {steps_per_epoch} steps/epoch | Total: {total_steps} steps | Warmup: {warmup_steps} steps") | |
| start_epoch = 1 | |
| global_step = 0 | |
| best_val_loss = float("inf") | |
| if args.resume and "optimizer_state_dict" in ckpt: | |
| try: | |
| optimizer.load_state_dict(ckpt["optimizer_state_dict"]) | |
| global_step = ckpt.get("global_step", 0) | |
| start_epoch = ckpt.get("epoch", 1) | |
| best_val_loss = ckpt.get("best_val_loss", float("inf")) | |
| print(f"๐ Resumed Training State: Global Step {global_step}, Start Epoch {start_epoch}, Best Val Loss {best_val_loss:.4f}") | |
| except Exception as e: | |
| print(f"โ ๏ธ Could not resume optimizer state: {e}") | |
| # Mixed Precision Setup | |
| if torch.cuda.is_available(): | |
| autocast_ctx = torch.amp.autocast(device_type="cuda", dtype=hw["dtype"]) | |
| else: | |
| autocast_ctx = contextlib.nullcontext() | |
| # Evaluation Helper with Per-Domain Loss Tracking | |
| def evaluate(): | |
| model.eval() | |
| total_val_loss = 0.0 | |
| val_tokens = 0 | |
| domain_loss_sum = {d: 0.0 for d in DOMAIN_NAMES} | |
| domain_token_cnt = {d: 0 for d in DOMAIN_NAMES} | |
| for inps, lbls, d_ids in val_loader: | |
| inps = inps.to(device, non_blocking=True) | |
| lbls = lbls.to(device, non_blocking=True) | |
| with autocast_ctx: | |
| logits, loss = model(inps, targets=lbls, pad_id=PAD_TOKEN_ID, shift_labels=True) | |
| shift_logits = logits[..., :-1, :].contiguous() | |
| shift_labels = lbls[..., 1:].contiguous() | |
| sum_loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1), ignore_index=-100, reduction="sum") | |
| num_active = (shift_labels != -100).sum().item() | |
| # Per-sample domain loss tracking | |
| for b_i in range(inps.size(0)): | |
| d_id = d_ids[b_i].item() | |
| s_lbl = shift_labels[b_i] | |
| act = (s_lbl != -100).sum().item() | |
| if act > 0 and d_id in domain_loss_sum: | |
| s_logit = shift_logits[b_i] | |
| s_loss = F.cross_entropy(s_logit, s_lbl, ignore_index=-100, reduction="sum").item() | |
| domain_loss_sum[d_id] += s_loss | |
| domain_token_cnt[d_id] += act | |
| total_val_loss += sum_loss.item() | |
| val_tokens += num_active | |
| model.train() | |
| avg_loss = total_val_loss / max(1, val_tokens) | |
| ppl = math.exp(min(avg_loss, 20.0)) | |
| # Domain loss summary | |
| domain_results = {} | |
| for d_id, name in DOMAIN_NAMES.items(): | |
| if domain_token_cnt[d_id] > 0: | |
| domain_results[name] = domain_loss_sum[d_id] / domain_token_cnt[d_id] | |
| return avg_loss, ppl, domain_results | |
| # Initial Validation | |
| print("\n๐ Running initial pre-training validation...") | |
| val_loss, val_ppl, dom_losses = evaluate() | |
| print(f"๐ Initial Validation Loss: {val_loss:.4f} | Perplexity: {val_ppl:.2f}") | |
| # Training Loop | |
| start_time = time.time() | |
| total_tokens_trained = 0 | |
| model.train() | |
| print("\n" + "=" * 85) | |
| print(f"๐ STARTING SFT {args.version.upper()} MASTER TRAINING (ULTRA-OPTIMIZED)") | |
| print("=" * 85) | |
| for epoch in range(start_epoch, args.epochs + 1): | |
| print(f"\n--- Epoch {epoch}/{args.epochs} ---") | |
| epoch_loss = 0.0 | |
| epoch_batches = 0 | |
| accum_loss = 0.0 | |
| optimizer.zero_grad(set_to_none=True) | |
| micro_idx = -1 | |
| for micro_idx, (inputs, labels, _) in enumerate(train_loader): | |
| inputs = inputs.to(device, non_blocking=True) | |
| labels = labels.to(device, non_blocking=True) | |
| active_tokens_count = (labels != -100).sum().item() | |
| total_tokens_trained += active_tokens_count | |
| with autocast_ctx: | |
| logits, loss = model(inputs, targets=labels, pad_id=PAD_TOKEN_ID, shift_labels=True) | |
| loss_scaled = loss / grad_acc | |
| loss_scaled.backward() | |
| accum_loss += loss.item() | |
| epoch_loss += loss.item() | |
| epoch_batches += 1 | |
| if (micro_idx + 1) % grad_acc == 0: | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| lr = get_lr(global_step, warmup_steps, total_steps, args.max_lr, args.min_lr) | |
| for param_group in optimizer.param_groups: | |
| param_group["lr"] = lr | |
| optimizer.step() | |
| optimizer.zero_grad(set_to_none=True) | |
| global_step += 1 | |
| step_avg_loss = accum_loss / grad_acc | |
| accum_loss = 0.0 | |
| if global_step % 10 == 0 or global_step == 1: | |
| elapsed = time.time() - start_time | |
| tokens_per_sec = total_tokens_trained / max(1.0, elapsed) | |
| remaining_steps = max(0, total_steps - global_step) | |
| eta_seconds = (remaining_steps / max(1, global_step)) * elapsed | |
| eta_mins = eta_seconds / 60 | |
| vram_used = torch.cuda.memory_allocated() / (1024**3) if torch.cuda.is_available() else 0.0 | |
| print( | |
| f"Step {global_step:4d}/{total_steps} | " | |
| f"Epoch {epoch} | " | |
| f"Loss: {step_avg_loss:.4f} | " | |
| f"LR: {lr:.2e} | " | |
| f"Speed: {tokens_per_sec:,.0f} tok/s | " | |
| f"VRAM: {vram_used:.1f}GB | " | |
| f"ETA: {eta_mins:.1f}m" | |
| ) | |
| # Validation, Domain Breakdown & Live Preview | |
| if global_step % args.eval_interval == 0: | |
| v_loss, v_ppl, d_losses = evaluate() | |
| print(f"\n๐ [Eval @ Step {global_step}] Validation Loss: {v_loss:.4f} | Perplexity: {v_ppl:.2f}") | |
| # Print Domain Loss Breakdown | |
| print(" ๐ Domain Breakdown: " + " | ".join([f"{k[:6]}: {v:.3f}" for k, v in d_losses.items()])) | |
| # Live Multi-Domain Sample Generation Previews | |
| if tokenizer is not None: | |
| run_live_eval_previews(model, tokenizer, device) | |
| is_best = v_loss < best_val_loss | |
| if is_best: | |
| best_val_loss = v_loss | |
| print(f" ๐ New Best Validation Loss: {best_val_loss:.4f}! Saving checkpoint...") | |
| save_payload = { | |
| "model_state_dict": model.state_dict(), | |
| "optimizer_state_dict": optimizer.state_dict(), | |
| "global_step": global_step, | |
| "epoch": epoch, | |
| "best_val_loss": best_val_loss, | |
| "domain_losses": d_losses, | |
| "args": vars(args), | |
| "model_args": vars(model_args), | |
| "timestamp": time.strftime("%Y-%m-%d %H:%M:%S") | |
| } | |
| torch.save(save_payload, save_path) | |
| print(f" ๐พ Saved checkpoint -> {save_path}\n") | |
| # End of Epoch Handling | |
| if epoch_batches > 0 and (micro_idx + 1) % grad_acc != 0: | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| lr = get_lr(global_step, warmup_steps, total_steps, args.max_lr, args.min_lr) | |
| for param_group in optimizer.param_groups: | |
| param_group["lr"] = lr | |
| optimizer.step() | |
| optimizer.zero_grad(set_to_none=True) | |
| global_step += 1 | |
| if epoch_batches == 0: | |
| raise RuntimeError(f"Error: Epoch {epoch} yielded 0 batches. Verify dataset and batch configuration.") | |
| avg_epoch_loss = epoch_loss / epoch_batches | |
| print(f"\nโ Finished Epoch {epoch}/{args.epochs} | Avg Epoch Loss: {avg_epoch_loss:.4f}") | |
| # Final Evaluation & Save | |
| final_val_loss, final_val_ppl, final_d_losses = evaluate() | |
| print("\n" + "=" * 85) | |
| print(f"๐ SFT {args.version.upper()} MASTER TRAINING COMPLETED!") | |
| print(f" โข Best Validation Loss: {min(best_val_loss, final_val_loss):.4f}") | |
| print(f" โข Final Validation Loss: {final_val_loss:.4f}") | |
| print(f" โข Final Perplexity: {final_val_ppl:.2f}") | |
| print(f" โข Total Active Tokens: {total_tokens_trained:,}") | |
| print(f" โข Total Time Taken: {(time.time() - start_time)/60:.2f} minutes") | |
| print("=" * 85) | |
| final_payload = { | |
| "model_state_dict": model.state_dict(), | |
| "optimizer_state_dict": optimizer.state_dict(), | |
| "global_step": global_step, | |
| "epoch": args.epochs, | |
| "best_val_loss": min(best_val_loss, final_val_loss), | |
| "final_val_loss": final_val_loss, | |
| "domain_losses": final_d_losses, | |
| "args": vars(args), | |
| "model_args": vars(model_args), | |
| "timestamp": time.strftime("%Y-%m-%d %H:%M:%S") | |
| } | |
| torch.save(final_payload, save_path) | |
| print(f"๐พ Final master checkpoint saved to: {save_path}") | |
| # Push to Hugging Face Hub (Only Final Checkpoint) | |
| if args.push_to_hf: | |
| print("\n๐ Pushing Final Checkpoint to Hugging Face Model Hub (ViuAI/ViuAI-500M)...") | |
| token = args.hf_token or os.environ.get("HF_TOKEN") | |
| if token: | |
| try: | |
| api = HfApi(token=token) | |
| if os.path.exists(save_path): | |
| api.upload_file( | |
| path_or_fileobj=save_path, | |
| path_in_repo=f"sft_checkpoints/sft_{args.version}/{ckpt_filename}", | |
| repo_id="ViuAI/ViuAI-500M", | |
| repo_type="model" | |
| ) | |
| print(f"โ Successfully uploaded {ckpt_filename} to ViuAI/ViuAI-500M (sft_checkpoints/sft_{args.version}/)!") | |
| except Exception as e: | |
| print(f"โ ๏ธ Error uploading to Hugging Face: {e}") | |
| else: | |
| print("โ ๏ธ Skipping HF upload: No HF_TOKEN provided.") | |
| if __name__ == "__main__": | |
| main() | |