unsloth/scripts/benchmarks/qwen3_grpo_vllm.py
2026-04-19 14:45:48 +00:00

184 lines
5.7 KiB
Python

"""Qwen3-4B GRPO baseline (vLLM colocated) derived from the notebook.
Run:
CUDA_VISIBLE_DEVICES=2 python scripts/qwen3_grpo_vllm.py \
--max_steps 61 --output_dir outputs/grpo_vllm \
--stats_path logs/vllm_stats.json
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
from pathlib import Path
# Unsloth must be imported before transformers / trl.
os.environ.setdefault("UNSLOTH_VLLM_STANDBY", "1")
# Allow sibling import of the common module.
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
from unsloth import FastLanguageModel # noqa: E402
import torch # noqa: E402
from unsloth_grpo_common import ( # noqa: E402
apply_chat_template_to_tokenizer,
build_dataset,
build_reward_funcs,
build_grpo_kwargs,
)
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--model_name", default = "unsloth/Qwen3-4B-Base")
p.add_argument("--max_seq_length", type = int, default = 2048)
p.add_argument("--lora_rank", type = int, default = 32)
p.add_argument("--max_steps", type = int, default = 61)
p.add_argument("--num_generations", type = int, default = 4)
p.add_argument("--per_device_train_batch_size", type = int, default = 1)
p.add_argument("--gradient_accumulation_steps", type = int, default = 1)
p.add_argument("--gpu_memory_utilization", type = float, default = 0.8)
p.add_argument("--output_dir", default = "outputs/grpo_vllm")
p.add_argument("--stats_path", default = "logs/vllm_stats.json")
return p.parse_args()
def main():
args = parse_args()
os.makedirs(args.output_dir, exist_ok = True)
os.makedirs(os.path.dirname(os.path.abspath(args.stats_path)) or ".", exist_ok = True)
# 1. Load model with vLLM fast inference enabled.
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = args.model_name,
max_seq_length = args.max_seq_length,
load_in_4bit = False,
fast_inference = True,
max_lora_rank = args.lora_rank,
gpu_memory_utilization = args.gpu_memory_utilization,
)
model = FastLanguageModel.get_peft_model(
model,
r = args.lora_rank,
target_modules = [
"q_proj",
"k_proj",
"v_proj",
"o_proj",
"gate_proj",
"up_proj",
"down_proj",
],
lora_alpha = args.lora_rank * 2,
use_gradient_checkpointing = "unsloth",
random_state = 3407,
)
apply_chat_template_to_tokenizer(tokenizer)
# 2. Dataset + rewards.
dataset, maximum_length = build_dataset(
tokenizer, max_seq_length = args.max_seq_length
)
print(f"[vllm] Max prompt length (p90): {maximum_length}")
reward_funcs = build_reward_funcs(tokenizer)
# 3. vLLM sampling params match the notebook.
from vllm import SamplingParams
vllm_sampling_params = SamplingParams(
min_p = 0.1,
top_p = 1.0,
top_k = -1,
seed = 3407,
stop = [tokenizer.eos_token],
include_stop_str_in_output = True,
)
# 4. Build GRPOConfig.
from trl import GRPOConfig, GRPOTrainer
shared = build_grpo_kwargs(
tokenizer,
maximum_length,
max_seq_length = args.max_seq_length,
max_steps = args.max_steps,
num_generations = args.num_generations,
per_device_train_batch_size = args.per_device_train_batch_size,
gradient_accumulation_steps = args.gradient_accumulation_steps,
output_dir = args.output_dir,
)
training_args = GRPOConfig(
use_vllm = True,
vllm_mode = "colocate",
vllm_sampling_params = vllm_sampling_params,
vllm_gpu_memory_utilization = args.gpu_memory_utilization,
**shared,
)
# 5. Timing callback.
from transformers import TrainerCallback
timings = {"step_wall": [], "loss": [], "reward": []}
class StepTimer(TrainerCallback):
def __init__(self):
self.t0 = None
def on_step_begin(self, _args, state, control, **kwargs):
torch.cuda.synchronize()
self.t0 = time.perf_counter()
def on_log(self, _args, state, control, logs = None, **kwargs):
if logs is None:
return
if "loss" in logs:
timings["loss"].append(float(logs["loss"]))
if "reward" in logs:
timings["reward"].append(float(logs["reward"]))
def on_step_end(self, _args, state, control, **kwargs):
if self.t0 is not None:
torch.cuda.synchronize()
timings["step_wall"].append(time.perf_counter() - self.t0)
trainer = GRPOTrainer(
model = model,
processing_class = tokenizer,
reward_funcs = reward_funcs,
args = training_args,
train_dataset = dataset,
callbacks = [StepTimer()],
)
torch.cuda.reset_peak_memory_stats()
t_start = time.perf_counter()
trainer.train()
t_train = time.perf_counter() - t_start
peak = torch.cuda.max_memory_allocated() / 1024**3
stats = {
"backend": "vllm_colocated",
"train_wall_s": t_train,
"peak_memory_gb": peak,
"step_wall_s": timings["step_wall"],
"losses": timings["loss"],
"rewards": timings["reward"],
"max_prompt_length": shared["max_prompt_length"],
"max_completion_length": shared["max_completion_length"],
"num_generations": args.num_generations,
"max_steps": args.max_steps,
}
with open(args.stats_path, "w") as f:
json.dump(stats, f, indent = 2)
print(f"[vllm] Wrote stats to {args.stats_path}")
print(f"[vllm] Total train wall: {t_train:.1f}s Peak mem: {peak:.2f} GB")
if __name__ == "__main__":
main()