Phase 0 (canonical reference): - scripts/benchmarks/qwen3_grpo_notebook.py: notebook-to-script port of Qwen3_(4B)-GRPO.ipynb with StatisticsCallback from torch_debugging_utils and equivalence-friendly sampling (temp=0.1, top_p=0.97, min_p=0.5, top_k=5). - scripts/benchmarks/results/notebook_ref_10.md: 10-step reference run table (median step post-warmup = 5.80s, peak 158.9 GB). - scripts/benchmarks/results/stats/notebook_ref_10.json: full per-step logs for downstream compare_training_runs checks. Phase 1 (rollout-only LoRA comparison scaffold): - scripts/benchmarks/make_lora_adapter.py: one-shot that materializes a rank-32 LoRA at outputs/lora_rank32_fresh. Re-initializes lora_B with a tiny gaussian so the adapter isn't a no-op (otherwise LoRA kernels can short-circuit and we'd be measuring the base model). - scripts/benchmarks/cb_vs_vllm_generation.py: extended with --lora_adapter for vLLM (LoRARequest) and tpaged (peft.PeftModel.from_pretrained, no merge_adapter), plus a new unsloth_fi_false backend that exercises the custom HF inference path (cached fp16 LoRA via fast_linear_forward). Sampling knobs are exposed and default to equivalence params. Phase 2 scaffold: - scripts/benchmarks/qwen3_grpo_unified.py: single entry point for all 5 backends (vllm, unsloth_fi_false, cb_paged, cb_sdpa, naive_trl) sharing dataset, reward funcs, sampling, and StatisticsCallback. Skips the first 3 steps when reporting median step wall. No unsloth internals touched.
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""One-shot: materialize a rank-32 LoRA adapter on `unsloth/Qwen3-4B-Base`.
|
|
|
|
Writes a PEFT-style directory so every backend (vLLM `LoRARequest`,
|
|
`peft.PeftModel.from_pretrained`, Unsloth `FastLanguageModel.get_peft_model`)
|
|
can load the SAME weights. Random-init is fine for throughput measurement --
|
|
the goal is to have LoRA kernels active during generation, not a trained
|
|
model.
|
|
|
|
Run:
|
|
CUDA_VISIBLE_DEVICES=6 python scripts/benchmarks/make_lora_adapter.py \
|
|
--output outputs/lora_rank32_fresh
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_args():
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("--model_name", default="unsloth/Qwen3-4B-Base")
|
|
p.add_argument("--output", default="outputs/lora_rank32_fresh")
|
|
p.add_argument("--rank", type=int, default=32)
|
|
return p.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
out_dir = Path(args.output).resolve()
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Use vanilla HF -- PEFT's save_pretrained yields the canonical
|
|
# adapter_config.json + adapter_model.safetensors that vLLM's LoRARequest
|
|
# expects. Loading via Unsloth would leak Unsloth-specific LoRA wrappers.
|
|
import torch
|
|
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
from peft import LoraConfig, get_peft_model
|
|
|
|
tok = AutoTokenizer.from_pretrained(args.model_name)
|
|
if tok.pad_token is None:
|
|
tok.pad_token = tok.eos_token
|
|
|
|
# bf16 base; we only need structure + save. Keep on CPU to avoid a GPU load
|
|
# just for `save_pretrained`.
|
|
print(f"[make_lora_adapter] Loading {args.model_name} on CPU...")
|
|
model = AutoModelForCausalLM.from_pretrained(args.model_name, dtype=torch.bfloat16)
|
|
|
|
peft_cfg = LoraConfig(
|
|
r=args.rank,
|
|
lora_alpha=args.rank * 2,
|
|
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
|
|
"gate_proj", "up_proj", "down_proj"],
|
|
bias="none",
|
|
task_type="CAUSAL_LM",
|
|
lora_dropout=0.0,
|
|
)
|
|
peft_model = get_peft_model(model, peft_cfg)
|
|
peft_model.print_trainable_parameters()
|
|
|
|
# Ensure both A and B matrices are non-zero. PEFT initializes A with
|
|
# kaiming_uniform and B with zeros -- which makes the adapter a no-op and
|
|
# would mask LoRA kernels on some backends. Seed B with tiny random values.
|
|
n_reinit = 0
|
|
with torch.no_grad():
|
|
for name, p in peft_model.named_parameters():
|
|
if "lora_B" in name:
|
|
p.normal_(mean=0.0, std=1e-4)
|
|
n_reinit += 1
|
|
print(f"[make_lora_adapter] Reinitialized {n_reinit} lora_B matrices with tiny gaussian.")
|
|
|
|
peft_model.save_pretrained(str(out_dir))
|
|
tok.save_pretrained(str(out_dir))
|
|
|
|
# Sanity: verify safetensors file present and non-trivial.
|
|
from safetensors import safe_open
|
|
st_path = out_dir / "adapter_model.safetensors"
|
|
n_zero_tensors = 0
|
|
n_tensors = 0
|
|
with safe_open(str(st_path), framework="pt") as f:
|
|
for key in f.keys():
|
|
t = f.get_tensor(key)
|
|
n_tensors += 1
|
|
if (t == 0).all().item():
|
|
n_zero_tensors += 1
|
|
print(f"[make_lora_adapter] Wrote {n_tensors} tensors to {st_path} "
|
|
f"({n_zero_tensors} all-zero).")
|
|
print(f"[make_lora_adapter] Adapter saved to {out_dir}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|