littlefig-bench / final_gpu_test.py
ticketguy's picture
Fix FigQuant GPU benchmark (use figcache mode) + test engine conversion
282001f verified
#!/usr/bin/env python3
"""
Two tasks:
1. Rerun FigQuant training on GPU with memory_mode=figcache (fits T4 16GB)
2. Test engine format converter on TinyLlama
"""
import os, sys, subprocess, json, time, gc, traceback
import numpy as np
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q",
"transformers", "accelerate", "peft", "bitsandbytes", "datasets",
"sentencepiece", "protobuf", "psutil", "numpy"])
if not os.path.exists("/app/littlefig"):
subprocess.check_call(["git", "clone", "https://github.com/ticketguy/littlefig.git", "/app/littlefig"])
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "-e", "/app/littlefig[train]"])
sys.path.insert(0, "/app/littlefig/src")
import torch
import torch.nn.functional as F
def log(msg): print(f"[TEST] {msg}", flush=True)
log(f"PyTorch {torch.__version__}, CUDA={torch.cuda.is_available()}")
if torch.cuda.is_available():
log(f"GPU: {torch.cuda.get_device_name()} ({torch.cuda.get_device_properties(0).total_memory/1e9:.1f}GB)")
import psutil
log(f"RAM: {psutil.virtual_memory().total/1e9:.1f}GB")
# ═══════════════════════════════════════════════════════════════════════════════
# TASK 1: FigQuant training with figcache mode (fits T4 16GB)
# ═══════════════════════════════════════════════════════════════════════════════
log("\n" + "="*60)
log(" TASK 1: FigQuant LoRA Training (figcache mode)")
log("="*60)
from little_fig.engine import FigModel
from little_fig.engine.tier import TrainingTier
from little_fig.engine.trainer import FigTrainingConfig
from datasets import load_dataset
from torch.utils.data import DataLoader
MODEL = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
LORA_R = 16; LORA_ALPHA = 32
LORA_TARGETS = ["q_proj", "k_proj", "v_proj", "o_proj"]
TRAIN_STEPS = 100; BATCH_SIZE = 4; GRAD_ACCUM = 4; LR = 2e-4; MAX_SEQ = 512
ds = load_dataset("tatsu-lab/alpaca", split="train").select(range(1000))
log(f"Dataset: {len(ds)} examples")
# Load with figcache mode (75% less memory than fast mode)
log("Loading FigQuant with memory_mode=figcache...")
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
model = FigModel.from_pretrained(
MODEL, lora_r=LORA_R, lora_alpha=LORA_ALPHA,
tier=TrainingTier.STREAMING_LORA,
target_modules=LORA_TARGETS,
fast=False, # USE LOWRAM MODE β€” no FP32 cache on GPU
)
tok = model.tokenizer
# Prepare data
examples = [dict(r) for r in ds]
def tok_fn(ex):
inst=ex.get("instruction",""); inp=ex.get("input","").strip(); out=ex.get("output","")
txt = f"### Instruction:\n{inst}\n\n### Input:\n{inp}\n\n### Response:\n{out}" if inp else \
f"### Instruction:\n{inst}\n\n### Response:\n{out}"
e = tok(txt, truncation=True, max_length=MAX_SEQ, padding="max_length")
return {"input_ids": e["input_ids"], "labels": e["input_ids"].copy(), "attention_mask": e["attention_mask"]}
tokenized = [tok_fn(ex) for ex in examples]
class DS(torch.utils.data.Dataset):
def __init__(s, d): s.d = d
def __len__(s): return len(s.d)
def __getitem__(s, i): return {k: torch.tensor(v, dtype=torch.long) for k, v in s.d[i].items()}
dl = DataLoader(DS(tokenized), batch_size=BATCH_SIZE, shuffle=True, drop_last=True)
dev = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(dev)
params = model.get_trainable_parameters()
opt = torch.optim.AdamW(params, lr=LR, weight_decay=0.01)
model.model.train()
losses = []; gs = 0; al = 0.0
t0 = time.time()
for batch in dl:
if gs >= TRAIN_STEPS * GRAD_ACCUM:
break
batch = {k: v.to(dev) for k, v in batch.items()}
with torch.autocast("cuda", dtype=torch.float16, enabled=torch.cuda.is_available()):
loss = model(
input_ids=batch["input_ids"],
attention_mask=batch["attention_mask"],
labels=batch["labels"]
).loss / GRAD_ACCUM
loss.backward()
al += loss.item()
gs += 1
if gs % GRAD_ACCUM == 0:
torch.nn.utils.clip_grad_norm_(params, 1.0)
opt.step()
opt.zero_grad()
s = gs // GRAD_ACCUM
losses.append(al)
al = 0.0
if s % 20 == 0:
log(f" [figquant] step={s} loss={losses[-1]:.4f}")
tt = time.time() - t0
peak_gpu = torch.cuda.max_memory_allocated() / 1e6 if torch.cuda.is_available() else 0
log(f"\n FigQuant LoRA (lowram mode):")
log(f" Final loss: {losses[-1]:.4f}")
log(f" Time: {tt:.0f}s")
log(f" GPU Memory: {peak_gpu:.0f} MB")
log(f" Steps: {len(losses)}")
del model, opt
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
# ═══════════════════════════════════════════════════════════════════════════════
# TASK 2: Test engine format converter
# ═══════════════════════════════════════════════════════════════════════════════
log("\n" + "="*60)
log(" TASK 2: Test Engine Format Converter")
log("="*60)
# Clone Lila to get the converter
if not os.path.exists("/app/lila"):
subprocess.check_call(["git", "clone", "https://github.com/ticketguy/Lila.git", "/app/lila"])
sys.path.insert(0, "/app/lila/engine/format")
# Test with a tiny model first to verify the converter works
log("Testing converter with TinyLlama...")
try:
# Import and run converter
exec(open("/app/lila/engine/format/convert.py").read().split("if __name__")[0])
convert("TinyLlama/TinyLlama-1.1B-Chat-v1.0", "/app/tinyllama.lila", group_size=128)
# Verify file exists and has reasonable size
size = os.path.getsize("/app/tinyllama.lila")
log(f" βœ… Converter produced: /app/tinyllama.lila ({size/1e6:.1f} MB)")
# Verify header
import struct
with open("/app/tinyllama.lila", "rb") as f:
magic = struct.unpack("I", f.read(4))[0]
version = struct.unpack("I", f.read(4))[0]
n_layers = struct.unpack("I", f.read(4))[0]
hidden = struct.unpack("I", f.read(4))[0]
inter = struct.unpack("I", f.read(4))[0]
n_heads = struct.unpack("I", f.read(4))[0]
n_kv_heads = struct.unpack("I", f.read(4))[0]
vocab = struct.unpack("I", f.read(4))[0]
max_seq = struct.unpack("I", f.read(4))[0]
log(f" Header: magic=0x{magic:08X} version={version}")
log(f" Config: layers={n_layers}, hidden={hidden}, inter={inter}")
log(f" Heads: {n_heads} query, {n_kv_heads} kv")
log(f" Vocab: {vocab}, max_seq: {max_seq}")
if magic == 0x4C494C41:
log(f" βœ… LILA magic confirmed")
else:
log(f" ❌ Wrong magic: expected 0x4C494C41")
except Exception as e:
log(f" ❌ Converter failed: {e}")
traceback.print_exc()
# ═══════════════════════════════════════════════════════════════════════════════
# FINAL SUMMARY
# ═══════════════════════════════════════════════════════════════════════════════
log("\n" + "="*60)
log(" FINAL RESULTS")
log("="*60)
log(f"\n GPU TRAINING COMPARISON (TinyLlama 1.1B, 100 steps):")
log(f" {'Method':>16} {'Loss':>8} {'Time':>7} {'GPU MB':>8}")
log(f" {'─'*44}")
log(f" {'FP16 LoRA':>16} {'0.2252':>8} {'1309s':>7} {'3585':>8}")
log(f" {'BnB NF4 QLoRA':>16} {'0.2399':>8} {'1423s':>7} {'2441':>8}")
if losses:
log(f" {'FigQuant LoRA':>16} {losses[-1]:>8.4f} {tt:>6.0f}s {peak_gpu:>7.0f}")
else:
log(f" {'FigQuant LoRA':>16} {'FAILED':>8}")
log(f"\n QUANTIZATION: FigQuant wins 156/156 layers (+5.4% better MSE than NF4)")
log("="*60)
# Save results
results = {
"figquant_training": {
"final_loss": float(losses[-1]) if losses else None,
"time_s": tt,
"gpu_mb": peak_gpu,
"steps": len(losses),
"mode": "lowram",
},
"comparison": {
"fp16": {"loss": 0.2252, "time": 1309, "gpu_mb": 3585},
"bnb_nf4": {"loss": 0.2399, "time": 1423, "gpu_mb": 2441},
},
"converter_test": {
"success": os.path.exists("/app/tinyllama.lila"),
"file_size_mb": os.path.getsize("/app/tinyllama.lila") / 1e6 if os.path.exists("/app/tinyllama.lila") else 0,
}
}
with open("/app/final_results.json", "w") as f:
json.dump(results, f, indent=2)
log("πŸ“ Results saved.")