forge-500m-chat / config.py
bobbydeveloper2014's picture
Upload folder using huggingface_hub
4d74557 verified
Raw History Blame Contribute Delete
20.4 kB
"""
FORGE-3B: Master Configuration
All hyperparameters, paths, and GPU optimization settings in one place.
"""
from __future__ import annotations
import os
import json
import math
from dataclasses import dataclass, field, asdict
from typing import Optional, List, Literal
from pathlib import Path
# ─────────────────────────────────────────────────────────────────────────────
# GPU GLOBAL OPTIMIZATION FLAGS
# These are applied at import time across the entire process.
# ─────────────────────────────────────────────────────────────────────────────
import torch
# Allow TF32 for matmuls and convolutions β€” massive speedup on Ampere/Hopper
# with negligible precision loss for language model training.
if torch.cuda.is_available():
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
torch.backends.cudnn.benchmark = True # auto-tune cuDNN kernels
try:
torch.backends.cuda.enable_flash_sdp(True) # Flash SDP in PyTorch 2.x
torch.backends.cuda.enable_mem_efficient_sdp(True)
torch.backends.cuda.enable_math_sdp(False) # disable slow math SDP
except Exception:
pass
# NCCL tuning for 64x H100 GPU cluster (8x 8-GPU nodes via NVLink + 3.2 Tbps InfiniBand)
os.environ.setdefault("NCCL_MIN_NCHANNELS", "32")
os.environ.setdefault("NCCL_MAX_NCHANNELS", "64")
os.environ.setdefault("NCCL_NSOCKS_PERTHREAD", "8")
os.environ.setdefault("NCCL_SOCKET_NTHREADS", "8")
os.environ.setdefault("NCCL_BUFFSIZE", "16777216") # 16MB ring buffer for high-bandwidth AllReduce
os.environ.setdefault("NCCL_NET_GDR_LEVEL", "5") # GPUDirect RDMA Level 5 for NVLink + InfiniBand
os.environ.setdefault("NCCL_CROSS_NIC", "1") # Multi-NIC rail-optimized routing
os.environ.setdefault("NCCL_IB_DISABLE", "0") # InfiniBand enabled
os.environ.setdefault("NCCL_P2P_DISABLE", "0") # NVLink P2P enabled
os.environ.setdefault("NCCL_NVLS_ENABLE", "1") # NVLink SHARP collective offload
os.environ.setdefault("CUDA_DEVICE_MAX_CONNECTIONS", "1") # Precise CUDA stream scheduling
# CUDA memory allocator β€” reduce fragmentation for large model + optimizer states
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF",
"expandable_segments:True,max_split_size_mb:128,garbage_collection_threshold:0.8")
# Tokenizer parallelism β€” disable HF tokenizer parallelism (we use CRAYON)
os.environ["TOKENIZERS_PARALLELISM"] = "false"
# ─────────────────────────────────────────────────────────────────────────────
# MODEL CONFIGURATION
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class ForgeModelConfig:
"""Complete FORGE-500M architecture configuration. 500.02M total params."""
# ── Core Dimensions ──────────────────────────────────────────────────────
vocab_size: int = 206_464 # CRAYON standard profile (206,373) padded to Γ—128
d_model: int = 1024 # 500M: 1024 (down from 1280)
n_layers: int = 18 # 500M: 18 layers (15 ARG + 3 MHA)
max_seq_len: int = 4096
# ── Layer Distribution ────────────────────────────────────────────────────
# Layer 0-4 = ARG; Layer 5 = MHA; repeat Γ—3 across 18 layers
mha_layer_indices: List[int] = field(default_factory=lambda: [5, 11, 17])
# Dense FFN on odd layers, HSE on even layers
dense_ffn_layer_indices: List[int] = field(default_factory=lambda: list(range(1, 18, 2)))
hse_ffn_layer_indices: List[int] = field(default_factory=lambda: list(range(0, 18, 2)))
# ── ARG (Adaptive Recurrent Gating) ──────────────────────────────────────
arg_d_inner: int = 1024 # matches d_model for 500M
arg_d_state: int = 48
arg_d_rank: int = 48 # dt projection rank
arg_conv_kernel: int = 4
arg_local_window: int = 64 # local attention window (phase 1+2)
arg_local_n_heads: int = 8
arg_local_n_kv_heads: int = 2
arg_head_dim: int = 64 # 1024 / 16 heads
arg_scalar_gate: bool = True # scalar gate per token (vs d_model-dim)
# ── MHA (Multi-Head Attention) ────────────────────────────────────────────
mha_n_heads: int = 16
mha_n_kv_heads: int = 4 # GQA: 4:1 ratio
mha_head_dim: int = 64 # 1024 / 16 heads
rope_theta: float = 500_000.0 # long-context RoPE base
rope_scaling_type: Optional[str] = None # None | "yarn"
rope_scaling_factor: float = 1.0
# ── Dense FFN (SwiGLU) ────────────────────────────────────────────────────
dense_d_ff: int = 2560 # 2.5 Γ— d_model, multiple of 128
# ── HSE FFN (Hierarchical Sparse Expert) ──────────────────────────────────
hse_n_domains: int = 4
hse_n_experts_per_domain: int = 6 # total 24 experts (down from 32)
hse_top_k: int = 2 # active per token
hse_d_ff_expert: int = 210 # scaled for 500M total params
hse_capacity_factor: float = 1.25
hse_expert_dropout: float = 0.1
hse_aux_loss_alpha: float = 0.01
hse_gumbel_tau_init: float = 1.0
hse_gumbel_tau_final: float = 0.1
hse_gumbel_tau_warmup_tokens: int = 10_000_000_000 # anneal over 10B tokens
# ── Normalization ─────────────────────────────────────────────────────────
norm_type: str = "dgn" # "dgn" or "rmsnorm"
dgn_n_groups: int = 16 # 1024 / 16 = 64 per group
norm_eps: float = 1e-6
# ── Initialization ────────────────────────────────────────────────────────
init_std: float = 0.02
init_std_residual_scale: bool = True # scale by 1/sqrt(2n_layers)
# ── Misc ──────────────────────────────────────────────────────────────────
dropout: float = 0.0 # 0.0 during pretraining
tie_word_embeddings: bool = True
use_cache: bool = True # KV cache for inference
pad_token_id: int = 0
bos_token_id: int = 1
eos_token_id: int = 2
# ── Compile/Optimization Flags ────────────────────────────────────
use_flash_attention: bool = True
use_triton_kernels: bool = True # Triton fused ops
use_torch_compile: bool = True
compile_mode: str = "max-autotune" # max throughput on H100
use_gradient_checkpointing: bool = False # 500M fits easily in 80GB without checkpointing
gradient_checkpointing_ratio: float = 0.0
def n_params_total(self) -> int:
"""Approximate total parameter count."""
embed = self.vocab_size * self.d_model
n_arg = len([i for i in range(self.n_layers) if i not in self.mha_layer_indices])
n_mha = len(self.mha_layer_indices)
n_dense = len(self.dense_ffn_layer_indices)
n_hse = len(self.hse_ffn_layer_indices)
arg_per = (
self.d_model * 2 * self.arg_d_inner + # in_proj
self.arg_d_inner * self.arg_conv_kernel + # conv1d
self.arg_d_inner * (self.arg_d_rank + 2 * self.arg_d_state) + # x_proj
self.arg_d_rank * self.arg_d_inner + # dt_proj
self.arg_d_state * 2 + # nu, theta
self.arg_d_inner + # D
self.arg_d_inner * self.d_model + # out_proj
self.arg_local_n_heads * self.arg_head_dim * self.d_model + # Q
self.arg_local_n_kv_heads * self.arg_head_dim * self.d_model * 2 + # KV
self.arg_local_n_heads * self.arg_head_dim * self.d_model + # O
self.d_model * self.arg_d_state # CPB
)
mha_per = (
self.d_model * self.mha_n_heads * self.mha_head_dim + # Q
self.d_model * self.mha_n_kv_heads * self.mha_head_dim * 2 + # KV
self.d_model * self.mha_n_heads * self.mha_head_dim # O
)
dense_per = 3 * self.d_model * self.dense_d_ff
n_experts_total = self.hse_n_domains * self.hse_n_experts_per_domain
hse_per = n_experts_total * 3 * self.d_model * self.hse_d_ff_expert
total = embed + n_arg * arg_per + n_mha * mha_per + n_dense * dense_per + n_hse * hse_per
return total
def to_json(self, path: str):
with open(path, "w") as f:
json.dump(asdict(self), f, indent=2)
@classmethod
def from_json(cls, path: str) -> "ForgeModelConfig":
import inspect
with open(path) as f:
data = json.load(f)
sig = inspect.signature(cls)
filtered_data = {k: v for k, v in data.items() if k in sig.parameters}
return cls(**filtered_data)
# ─────────────────────────────────────────────────────────────────────────────
# TRAINING CONFIGURATION
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class PretrainConfig:
"""Phase-aware pretraining configuration for FORGE-500M.
Budget math (1x H100 SXM @ $3.29/hr):
- Phase 1 (vocab warmup): 2B tokens @ 80k tok/s β†’ ~7h β†’ $23
- Phase 2 (core pretrain): 16B tokens @ 80k tok/s β†’ ~56h β†’ $184
- Phase 3 (ctx extension): 2B tokens @ 60k tok/s β†’ ~9h β†’ $30
PRETRAIN TOTAL: ~72h β†’ ~$237
SFT: 1.4B tokens β†’ ~5h β†’ $16
DPO: 0.8B tokens β†’ ~3h β†’ $10
GRAND TOTAL: ~$263 of $400 budget. $137 buffer.
"""
output_dir: str = "./checkpoints/forge_500m_pretrain"
data_dir: str = "Phase-Technologies/forge-3b-pretrain-data" # same tokenized data
resume_from_checkpoint: Optional[str] = None
# ── Token Budget (15B Tokens for 500M params: ~$245.59 total cost @ $3.50/GPU/hr on 32x H100) ──
# 15B tokens directly in Phase 2 at seq=2048 (Phase 1 & Phase 3 omitted)
phase1_tokens: int = 0 # Omitted (start directly on Phase 2 @ seq=2048)
phase2_tokens: int = 15_000_000_000 # 15.0B core pretrain (seq=2048) -> ~2.08h ($233.33)
phase3_tokens: int = 0 # Omitted (pure seq=2048 pretraining)
total_tokens: int = 15_000_000_000 # 15.0B total pretrain
# ── Batch Config (Tuned for 32x H100 SXM β€” 1,048,576 tokens/step) ─────────
# With 32 GPUs @ micro_batch=16 (seq=2048): 32 Γ— 16 Γ— 2048 = 1,048,576 tokens per pass!
# Exactly 1 gradient accumulation step (zero idle wait, maximum NVLink saturation)
phase1_global_batch_tokens: int = 1_048_576 # 1M tokens/step
phase2_global_batch_tokens: int = 1_048_576 # 1M tokens/step (Phase 2)
phase3_global_batch_tokens: int = 1_048_576 # 1M tokens/step (Phase 3)
micro_batch_size_per_gpu: int = 16 # default fallback
phase1_micro_batch_per_gpu: int = 64 # 64 seqs Γ— 512 = 32,768 tokens/GPU
phase2_micro_batch_per_gpu: int = 16 # 16 seqs Γ— 2048 = 32,768 tokens/GPU (1 accum step across 32 GPUs)
phase3_micro_batch_per_gpu: int = 8 # 8 seqs Γ— 4096 = 32,768 tokens/GPU
# ── Context Lengths ───────────────────────────────────────────────────────
phase1_seq_len: int = 512
phase2_seq_len: int = 2048
phase3_seq_len: int = 2048
# ── Learning Rate ─────────────────────────────────────────────────────────
lr_max: float = 3e-4
lr_min: float = 3e-5
lr_warmup_tokens: int = 500_000_000 # 500M tokens warmup directly in Phase 2
lr_schedule: str = "cosine"
# ── AdamW ─────────────────────────────────────────────────────────────────
beta1: float = 0.9
beta2: float = 0.95
eps: float = 1e-8
weight_decay: float = 0.1
grad_clip: float = 1.0
# ── Parameter Groups LR Multipliers ──────────────────────────────────────
embedding_lr_mult: float = 0.5
ssm_lr_mult: float = 0.3
router_lr_mult: float = 0.5
# ── Checkpointing (Hyper-Resilient: saves every 20-30 mins OR 500M tokens)
save_every_n_tokens: int = 500_000_000 # checkpoint every 500M tokens (~8 mins on 32x GPUs)
save_every_minutes: float = 20.0 # time-based checkpoint guard (every 20 minutes)
keep_last_n_checkpoints: int = 10 # keep 10 historical snapshots
# ── Logging ───────────────────────────────────────────────────────────────
log_every_n_steps: int = 1 # log every step for full metrics visibility
eval_every_n_steps: int = 200 # eval perplexity every 200 steps
wandb_project: str = "forge_500m_pretrain"
wandb_entity: Optional[str] = None
# ── GPU/Distributed ───────────────────────────────────────────────────────
num_gpus: int = 1 # default 1x H100; set to 16 for 16x
bf16: bool = True
fp8: bool = False # H100 FP8 (experimental)
deepspeed_config: str = "./configs/ds_zero3.json"
torch_compile: bool = True
compile_mode: str = "max-autotune" # peak throughput
# ── Data ──────────────────────────────────────────────────────────────────
num_dataloader_workers: int = 4
prefetch_factor: int = 4
seed: int = 42
@property
def gradient_accumulation_steps_phase2(self) -> int:
"""Accumulation steps for phase 2 global batch."""
tokens_per_gpu_step = self.micro_batch_size_per_gpu * self.phase2_seq_len
tokens_per_step_all_gpus = tokens_per_gpu_step * self.num_gpus
return self.phase2_global_batch_tokens // tokens_per_step_all_gpus
@dataclass
class SFTConfig:
"""Supervised Fine-Tuning configuration for FORGE-500M."""
base_model_path: str = "./checkpoints/forge_500m_pretrain/final"
output_dir: str = "./checkpoints/forge_500m_sft"
data_dir: str = "Phase-Technologies/forge-3b-sft-data"
# ── Training ──────────────────────────────────────────────────────────────
total_tokens: int = 1_400_000_000
global_batch_tokens: int = 524_288 # 512K per step
micro_batch_size_per_gpu: int = 4
seq_len: int = 4096
# ── Optimizer ─────────────────────────────────────────────────────────────
lr_max: float = 1e-5
lr_min: float = 1e-6
lr_schedule: str = "cosine"
beta1: float = 0.9
beta2: float = 0.95
eps: float = 1e-8
weight_decay: float = 0.0
grad_clip: float = 0.5
epochs: int = 1
loss_on_prompt: bool = False # only compute loss on assistant turns
save_every_n_steps: int = 100
wandb_project: str = "forge_500m_sft"
deepspeed_config: str = "./configs/ds_zero3_sft.json"
num_gpus: int = 1
bf16: bool = True
torch_compile: bool = True
compile_mode: str = "max-autotune"
@dataclass
class DPOConfig:
"""Direct Preference Optimization configuration for FORGE-500M."""
base_model_path: str = "./checkpoints/forge_500m_sft/final"
output_dir: str = "./checkpoints/forge_500m_dpo"
data_dir: str = "Phase-Technologies/forge-3b-dpo-data"
# ── DPO ───────────────────────────────────────────────────────────────────
beta: float = 0.1 # KL penalty coefficient
loss_type: str = "dpo" # "dpo" | "ipo" | "cdpo"
reference_free: bool = False
# ── Training ──────────────────────────────────────────────────────────────
n_preference_pairs: int = 200_000
batch_size_pairs: int = 8 # preference pairs per GPU step
gradient_accumulation_steps: int = 4
seq_len: int = 4096
# ── Optimizer ─────────────────────────────────────────────────────────────
lr: float = 5e-7
beta1: float = 0.9
beta2: float = 0.99
eps: float = 1e-8
weight_decay: float = 0.0
grad_clip: float = 0.3
n_epochs: int = 1
save_every_n_steps: int = 100
wandb_project: str = "forge_500m_dpo"
deepspeed_config: str = "./configs/ds_zero3_sft.json"
num_gpus: int = 1
bf16: bool = True
torch_compile: bool = True
compile_mode: str = "max-autotune"
# ─────────────────────────────────────────────────────────────────────────────
# TOKENIZER CONFIGURATION
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class CrayonConfig:
"""CRAYON tokenizer configuration."""
profile: str = "standard" # "standard" (206k) | "lite" (50k)
device: str = "cpu" # "cpu" is faster for tokenization (23M vs 1.2M tok/s)
pad_token: str = "<|pad|>"
bos_token: str = "<|bos|>"
eos_token: str = "<|eos|>"
sep_token: str = "<|sep|>"
sys_token: str = "<|sys|>"
usr_token: str = "<|usr|>"
asst_token: str = "<|asst|>"
tool_token: str = "<|tool|>"
tool_resp_token: str = "<|tool_resp|>"