"""Phase 3 — continued pretraining of Qwen3-1.7B-Base on the packed RU corpus. Fits a 16GB RX 7900 GRE (gfx1100). Full-weight CPT of 1.7B with plain Adam does NOT fit; we use adafactor (no momentum states) + gradient checkpointing (path A, plan §3.1). If it still OOMs, set PATH="dora" for high-rank DoRA on all linears. NO bitsandbytes (ROCm flaky) → no 8-bit Adam, hence adafactor. Critical hyperparams (plan §3.2): lr=1e-5 (10x lower than LoRA — nudge, don't forget), 1 epoch (more = catastrophic forgetting). Checkpoints every 500 steps; resume with --resume. Usage: HSA_OVERRIDE_GFX_VERSION=11.0.0 python train_cpt.py 2>&1 | tee cpt_run.log HSA_OVERRIDE_GFX_VERSION=11.0.0 python train_cpt.py --resume """ import os import sys os.environ.setdefault("HF_HOME", "/mnt/D/.cache/huggingface") os.environ.setdefault("HF_DATASETS_CACHE", "/mnt/D/.cache/huggingface/datasets") os.environ.setdefault("TMPDIR", "/mnt/D/tmp") os.environ.setdefault("HSA_OVERRIDE_GFX_VERSION", "11.0.0") # lever 2: kill allocator fragmentation so the caching allocator doesn't hoard the card os.environ.setdefault("PYTORCH_HIP_ALLOC_CONF", "expandable_segments:True") import torch from datasets import load_from_disk from transformers import ( AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments, DataCollatorForLanguageModeling, ) from corpus_common import PACKED MODEL = "Qwen/Qwen3-1.7B-Base" # BASE, not Instruct (plan hard rule 4) OUTPUT = "./Qwen3-1.7B-ru-cpt" PATH = os.environ.get("CPT_PATH", "full") # "full" (adafactor) | "dora" def main(): resume = "--resume" in sys.argv tok = AutoTokenizer.from_pretrained(MODEL) if tok.pad_token is None: tok.pad_token = tok.eos_token ds = load_from_disk(str(PACKED)) print(f"[cpt] {len(ds)} blocks x 2048 = {len(ds)*2048/1e6:.0f}M tokens") # lever 3: hard cap VRAM so an OOM raises (recoverable) instead of starving the compositor if torch.cuda.is_available(): torch.cuda.set_per_process_memory_fraction(0.85) model = AutoModelForCausalLM.from_pretrained( MODEL, torch_dtype=torch.bfloat16, attn_implementation="sdpa") # lever 1: flash/mem-efficient model.config.use_cache = False model.gradient_checkpointing_enable() optim = "adafactor" if PATH == "dora": # fallback if full-weight OOMs (plan §3.1B) from peft import LoraConfig, get_peft_model cfg = LoraConfig( r=64, lora_alpha=128, lora_dropout=0.05, use_dora=True, target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], task_type="CAUSAL_LM", ) model = get_peft_model(model, cfg) model.print_trainable_parameters() optim = "adamw_torch_fused" # LoRA params are tiny, Adam fits args = TrainingArguments( output_dir=OUTPUT, per_device_train_batch_size=1, gradient_accumulation_steps=16, # eff batch 16 x 2048 tok num_train_epochs=1, # ONE pass (hard rule 3) learning_rate=1e-5, # LOW (hard rule 3) lr_scheduler_type="cosine", warmup_ratio=0.03, optim=optim, bf16=True, gradient_checkpointing=True, logging_steps=20, save_steps=200, # ~1.1h between ckpts → clean daily stop/resume, low loss on interrupt save_total_limit=3, report_to="none", dataloader_num_workers=0, # py3.14 mp leaks semaphores + crashes; data is pre-tokenized so workers buy nothing ) collator = DataCollatorForLanguageModeling(tok, mlm=False) trainer = Trainer(model=model, args=args, train_dataset=ds, data_collator=collator) trainer.train(resume_from_checkpoint=resume) if PATH == "dora": model = model.merge_and_unload() model.save_pretrained(OUTPUT) tok.save_pretrained(OUTPUT) print(f"[cpt] DONE -> {OUTPUT}") if __name__ == "__main__": main()