Skip to content

Commit bb5f60e

Browse files
committed
[bugfix]: stabilize GenRL reward and PPO training
1 parent 78cbbeb commit bb5f60e

17 files changed

Lines changed: 1343 additions & 275 deletions

File tree

examples/train/configs/genrl_wan2.1_t2v_1.3B_longcat.yaml

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@
22
#
33
# Ported from GenRL/config/longcat.yaml.
44
#
5-
# - Student: trainable (LoRA or full finetune)
6-
# - Reference: frozen copy for KL penalty (optional, only for full finetune with beta > 0)
5+
# - Student: trainable full-parameter model by default.
6+
# - LoRA is still available via models.student.use_lora=true.
7+
# - Full fine-tuning with beta > 0 requires models.reference and much
8+
# more memory; keep beta at 0.0 for the 4xH100 probe run.
79
#
810
# Usage:
9-
# torchrun --nnodes=1 --nproc_per_node=8 \
10-
# fastvideo/train/entrypoint/train.py \
11+
# torchrun --nnodes=1 --nproc_per_node=4 \
12+
# -m fastvideo.train.entrypoint.train \
1113
# --config examples/train/configs/genrl_wan2.1_t2v_1.3B_longcat.yaml
1214

1315
models:
1416
student:
1517
_target_: fastvideo.train.models.wan.wan_genrl.GenRLWanModel
1618
init_from: Wan-AI/Wan2.1-T2V-1.3B-Diffusers
1719
trainable: true
20+
# Set true for LoRA. LoRA can use method.beta with disable_adapter().
21+
use_lora: false
22+
lora_r: 128
23+
lora_alpha: 64
24+
lora_init_weights: gaussian
25+
lora_path: null
26+
lora_target_modules:
27+
- to_k
28+
- to_out
29+
- to_q
30+
- to_v
31+
- ffn.fc_in
32+
- ffn.fc_out
1833
enable_gradient_checkpointing_type: full
1934

2035
method:
@@ -30,13 +45,20 @@ method:
3045
reward_on_gpu: true
3146

3247
# ---- Data ----
33-
prompt_dataset_path: data/filtered_prompts
48+
prompt_dataset_path: GenRL/datasets/filtered_prompts
3449
prompt_fn: filtered_prompts
3550

3651
# ---- Sampling ----
3752
sample_batch_size: 4
3853
eval_batch_size: 2
39-
num_batches_per_epoch: 1
54+
# Sample multiple rollout microbatches, average their PPO losses, then
55+
# apply one optimizer update. This reduces reward/advantage variance.
56+
num_batches_per_epoch: 4
57+
accumulate_ppo_microbatches: true
58+
eval_every_steps: 20
59+
eval_num_batches: 1
60+
eval_num_steps: 16
61+
eval_guidance_scale: 4.5
4062
num_inference_steps: 16
4163
guidance_scale: 4.5
4264
num_video_per_prompt: 4
@@ -57,12 +79,21 @@ method:
5779
# ---- PPO training ----
5880
train_batch_size: 4
5981
num_inner_epochs: 1
60-
clip_range: 1.0e-3
82+
clip_range: 1.0e-4
6183
adv_clip_max: 5.0
62-
beta: 3.0e-4
84+
# Official LoRA LongCat uses beta: 3.0e-4 with disable_adapter().
85+
# For full fine-tuning on 4 H100s, avoid a second frozen Wan copy.
86+
beta: 0.0
6387
use_cfg: true
64-
loss_reweighting: longcat
88+
# Flash-GRPO-style temporal gradient rectification: avoid the large
89+
# LongCat sigma/dt multiplier while debugging full-FT stability.
90+
loss_reweighting: flash_tgr
91+
loss_reweighting_clip: null
6592
weight_advantages: true
93+
# Match official GenRL PPO cadence. With sde_window_size: 1 this is
94+
# equivalent to one optimizer step per sampled trajectory timestep.
95+
optimizer_step_per_timestep: true
96+
log_post_update_kl: true
6697
max_grad_norm: 1.0
6798
seed: 42
6899

@@ -77,8 +108,9 @@ training:
77108
num_gpus: 4
78109
sp_size: 1
79110
tp_size: 1
80-
hsdp_replicate_dim: 4
81-
hsdp_shard_dim: 1
111+
# Full fine-tuning needs FSDP/HSDP sharding across all 4 GPUs.
112+
hsdp_replicate_dim: 1
113+
hsdp_shard_dim: 4
82114

83115
data:
84116
# Not used by GenRL (prompt dataloaders are in method config)
@@ -91,7 +123,9 @@ training:
91123
num_frames: 81
92124

93125
optimizer:
94-
learning_rate: 1.0e-4
126+
# Full-parameter visual GRPO is much more sensitive than LoRA.
127+
# DanceGRPO reports 5e-6 to 2e-5 as the practical range.
128+
learning_rate: 1.0e-5
95129
betas: [0.9, 0.999]
96130
weight_decay: 1.0e-4
97131
lr_scheduler: constant
@@ -109,7 +143,8 @@ training:
109143

110144
tracker:
111145
project_name: VideoRL
112-
run_name: wan_2_1_t2v_1_3b_longcat
146+
# Leave blank so W&B auto-generates a unique display name per run.
147+
run_name: ""
113148

114149
model:
115150
enable_gradient_checkpointing_type: full
@@ -119,6 +154,7 @@ callbacks:
119154
ema:
120155
decay: 0.9
121156
start_iter: 0
157+
update_interval: 8
122158
log_rl_samples:
123159
every_steps: 1
124160
max_videos: 4

examples/train/configs/genrl_wan2.1_t2v_1.3B_ocr.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# Usage:
44
# torchrun --nnodes=1 --nproc_per_node=4 \
5-
# fastvideo/train/entrypoint/train.py \
5+
# -m fastvideo.train.entrypoint.train \
66
# --config examples/train/configs/genrl_wan2.1_t2v_1.3B_ocr.yaml
77

88
models:
@@ -21,7 +21,7 @@ method:
2121
reward_module: null
2222

2323
# ---- Data ----
24-
prompt_dataset_path: data/ocr
24+
prompt_dataset_path: GenRL/datasets/ocr
2525
prompt_fn: general_ocr
2626

2727
# ---- Sampling ----
@@ -50,7 +50,8 @@ method:
5050
num_inner_epochs: 1
5151
clip_range: 1.0e-3
5252
adv_clip_max: 5.0
53-
beta: 3.0e-4
53+
# No frozen reference model is configured in this launch.
54+
beta: 0.0
5455
use_cfg: true
5556
loss_reweighting: longcat
5657
weight_advantages: false

examples/train/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ python -m torch.distributed.run \
5555
--nproc_per_node "${NUM_GPUS}" \
5656
--master_addr "${MASTER_ADDR}" \
5757
--master_port "${MASTER_PORT}" \
58-
fastvideo/train/entrypoint/train.py \
58+
-m fastvideo.train.entrypoint.train \
5959
--config "${CONFIG}" \
6060
"$@" \
6161
2>&1 | tee "${LOG_FILE}"

examples/train/run_slurm.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ srun torchrun \\
107107
--node_rank \$SLURM_PROCID \\
108108
--rdzv_backend=c10d \\
109109
--rdzv_endpoint="\$MASTER_ADDR:\$MASTER_PORT" \\
110-
fastvideo/train/entrypoint/train.py \\
110+
-m fastvideo.train.entrypoint.train \\
111111
--config ${CONFIG} \\
112112
--training.distributed.num_gpus ${TOTAL_GPUS} \\
113113
${EXTRA_ARGS[*]:-}

fastvideo/layers/lora/linear.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,24 @@ def forward(self, input_: torch.Tensor):
336336
output_parallel = self.base_layer.quant_method.apply(
337337
self.base_layer, input_parallel)
338338

339-
if self.set_lora:
340-
output_parallel = self.apply_lora(output_parallel, input_parallel)
339+
if not self.merged and not self.disable_lora:
340+
lora_A = self.lora_A
341+
lora_B = self.lora_B
342+
assert lora_A is not None and lora_B is not None
343+
if isinstance(lora_B, DTensor):
344+
lora_B = lora_B.to_local()
345+
lora_A = lora_A.to_local()
346+
347+
lora_A_sliced = self.slice_lora_a_weights(
348+
lora_A.to(input_parallel, non_blocking=True))
349+
lora_B_sliced = self.slice_lora_b_weights(
350+
lora_B.to(output_parallel, non_blocking=True))
351+
delta = input_parallel @ lora_A_sliced.T @ lora_B_sliced.T
352+
if self.lora_alpha != self.lora_rank:
353+
delta = delta * (
354+
self.lora_alpha / self.lora_rank # type: ignore
355+
)
356+
output_parallel = output_parallel + delta
341357

342358
if self.base_layer.reduce_results and self.base_layer.tp_size > 1:
343359
output_ = tensor_model_parallel_all_reduce(output_parallel)

fastvideo/train/callbacks/ema.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ def __init__(
4747
*,
4848
decay: float = 0.9999,
4949
start_iter: int = 0,
50+
update_interval: int = 1,
5051
) -> None:
5152
self._decay = float(decay)
5253
self._start_iter = int(start_iter)
54+
self._update_interval = max(1, int(update_interval))
5355
self._ema_started = False
5456
self.student_ema: EMA_FSDP | None = None
5557

@@ -78,9 +80,10 @@ def on_train_start(
7880
)
7981
logger.info(
8082
"EMA callback enabled (decay=%s, "
81-
"start_iter=%d).",
83+
"start_iter=%d, update_interval=%d).",
8284
self._decay,
8385
self._start_iter,
86+
self._update_interval,
8487
)
8588

8689
def on_training_step_end(
@@ -94,6 +97,8 @@ def on_training_step_end(
9497

9598
if iteration < self._start_iter:
9699
return
100+
if (iteration - self._start_iter) % self._update_interval != 0:
101+
return
97102
if not self._ema_started:
98103
logger.info(
99104
"Starting EMA updates at iteration %d "

0 commit comments

Comments
 (0)