gpu-goblin / workloads /train_qwen_lora.py
bharathtelu's picture
Deploy auto-tune UI + scripts (work-from-91d0cf0)
a9aa4ae verified
Raw
History Blame Contribute Delete
6.58 kB
# GPU Goblin canonical demo workload.
#
# Qwen2.5-7B-Instruct + LoRA fine-tune on the alpaca-cleaned dataset, staged
# with *deliberately* sub-optimal defaults so the goblin has something to fix
# in the demo. This script does NOT need to actually execute on a host — it
# exists so `parse_config` can extract a realistic WorkloadConfig from it.
#
# Expected findings when audited:
# - precision.bf16_over_fp16_on_mi300x (fp16=True)
# - attention.flash_rocm_over_eager (attn_implementation="eager")
# - data.dataloader_workers_zero (dataloader_num_workers=0)
# - memory.batch_too_small_for_192gb (per_device_train_batch_size=4)
import os
import sys
import time
# Bootstrap the repo root onto sys.path so `from workloads._runtime import ...`
# works regardless of where the script is invoked from.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import torch
from datasets import load_dataset
from peft import LoraConfig, get_peft_model
from torch.utils.data import DataLoader
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
Trainer,
TrainingArguments,
)
from workloads._runtime import emit_torch_profile, parse_runtime_args
# Parse the goblin_runner.sh injected flags (--max_steps, --torch_profile_out).
_runtime = parse_runtime_args()
# A redactable secret so parse_config has something to scrub during the demo.
os.environ["HF_TOKEN"] = "hf_aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
HF_TOKEN = os.environ["HF_TOKEN"]
# ROCm-flavored env knobs the agent should pick up into env_vars.
os.environ["HSA_FORCE_FINE_GRAIN_PCIE"] = "1"
os.environ["MIOPEN_FIND_MODE"] = "3"
MODEL_ID = "Qwen/Qwen2.5-7B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.float16,
attn_implementation="eager", # naive attention -- goblin should swap to flash_rocm
token=HF_TOKEN,
)
# LoRA — rank 16, attached to attention projections.
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
dataset = load_dataset("yahma/alpaca-cleaned", split="train")
# Hand-rolled DataLoader so parse_config sees the dataloader kwargs explicitly.
# NOTE: PyTorch raises ValueError if you set prefetch_factor while num_workers=0
# ("could only be specified in multiprocessing"). The audit is supposed to
# spot this misconfiguration, not crash on it — so the canonical demo keeps
# num_workers=0 (the deliberate badness) and lets prefetch_factor default,
# which parse_config will see as `dataloader_prefetch_factor=None`. The KB
# rule data.prefetch_factor_default still fires once num_workers is bumped.
train_loader = DataLoader(
dataset,
batch_size=4,
num_workers=0, # leaves the GPU starved during training -- data_wait waste
pin_memory=False,
persistent_workers=False,
)
# NOTE: keep TrainingArguments(...) called with LITERAL kwargs — parse_config
# walks the AST and only extracts kwargs whose values are literals (or simple
# identifiers it has seen). A `**dict_var` splat or a runtime expression hides
# the value from the parser, which then falls back to HF defaults
# (batch_size=1, lr=5e-5, etc.) and the agent reasons over the wrong config.
# `--max_steps` is the one runtime override, but it isn't a WorkloadConfig
# field so the parser doesn't need to see it; passing it as an expression is
# fine.
_RUNTIME_MAX_STEPS = _runtime.max_steps if _runtime.max_steps > 0 else -1
training_args = TrainingArguments(
output_dir="./out",
per_device_train_batch_size=4, # leaves HBM on the floor at 192 GB
gradient_accumulation_steps=8,
num_train_epochs=3,
learning_rate=2e-4,
warmup_steps=100,
fp16=True, # bf16 is the right call on CDNA3
optim="adamw_torch",
logging_steps=10,
save_steps=500,
dataloader_num_workers=0,
dataloader_pin_memory=False,
gradient_checkpointing=False,
torch_compile=False,
report_to="none",
push_to_hub=False,
# Alpaca columns are [instruction, input, output] — keep them so our
# toy collator below can still see them. Without this, HF Trainer drops
# them and the dataset becomes empty before forward(). This is purely a
# fix-the-script-so-rocprofv3-can-actually-trace-it concern; it has no
# bearing on the audit's findings.
remove_unused_columns=False,
# Runtime override only (parser ignores non-literals; HF Trainer treats
# max_steps=-1 as "use num_train_epochs"):
max_steps=_RUNTIME_MAX_STEPS,
)
# Tiny collator turning the alpaca rows into input_ids / labels so the
# Trainer can call forward(). It's intentionally trivial — the goal is to
# be runnable enough for rocprofv3 to capture a few real training steps,
# not to actually train anything useful.
def _toy_collate(rows):
texts = [
(r.get("instruction") or "")
+ ("\n" + r["input"] if r.get("input") else "")
+ "\n"
+ (r.get("output") or "")
for r in rows
]
enc = tokenizer(
texts,
padding=True,
truncation=True,
max_length=512,
return_tensors="pt",
)
enc["labels"] = enc["input_ids"].clone()
return enc
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer,
data_collator=_toy_collate,
)
if __name__ == "__main__":
# Total parameter count drives MFU. For a peft-wrapped model this
# includes both the frozen base (which still does forward) and the
# tiny LoRA adapters. The forward+backward FLOPs estimate uses the
# full count (the standard 6N rule lives inside emit_torch_profile).
_model_params = sum(p.numel() for p in model.parameters())
_t0 = time.time()
trainer.train()
_elapsed = time.time() - _t0
# trainer.state.global_step is the actual number of optimization steps
# (honors max_steps); fall back to the runtime arg if the trainer
# bailed before bumping it.
_n_steps = int(getattr(trainer.state, "global_step", 0) or _runtime.max_steps)
emit_torch_profile(
_runtime.torch_profile_out,
elapsed=_elapsed,
n_steps=_n_steps,
per_device_batch=training_args.per_device_train_batch_size,
grad_accum=training_args.gradient_accumulation_steps,
seq_len_cap=512,
model_params=_model_params,
)