Skip to content

Commit e1be068

Browse files
AbecidDavids048
andcommitted
[feat] GenRL: add runtime and memory stability helpers (#1402)
Co-authored-by: Davids048 <jundasu@ucsd.edu>
1 parent cd69575 commit e1be068

6 files changed

Lines changed: 76 additions & 35 deletions

File tree

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/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 "

fastvideo/train/methods/rl/utils/evaluation.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def eval_once(
4040
rank: int,
4141
is_main_process: bool,
4242
tracker: Any | None = None,
43+
max_batches: int | None = None,
44+
seed: int = 0,
4345
) -> dict[str, float]:
4446
"""Run evaluation on test set.
4547
@@ -65,20 +67,27 @@ def eval_once(
6567
ctx = nullcontext()
6668

6769
with ctx:
70+
generator = torch.Generator(device=device)
6871
for batch_idx, (
6972
_epoch_tag,
7073
prompts,
7174
metadata,
7275
) in enumerate(test_dataloader):
76+
if max_batches is not None and batch_idx >= max_batches:
77+
break
7378
prompt_embeds = compute_text_embeddings(
7479
prompts,
7580
text_encoder,
7681
tokenizer,
7782
max_sequence_length=512,
7883
device=device,
7984
)
85+
neg_prompt_embeds = sample_neg_prompt_embeds[
86+
: len(prompts)
87+
]
8088

8189
with torch.no_grad():
90+
generator.manual_seed(seed + batch_idx)
8291
(
8392
videos,
8493
_latents,
@@ -90,13 +99,14 @@ def eval_once(
9099
scheduler,
91100
prompt_embeds=prompt_embeds,
92101
negative_prompt_embeds=(
93-
sample_neg_prompt_embeds
102+
neg_prompt_embeds
94103
),
95104
num_inference_steps=eval_num_steps,
96105
guidance_scale=eval_guidance_scale,
97106
height=height,
98107
width=width,
99108
num_frames=num_frames,
109+
generator=generator,
100110
deterministic=True,
101111
sde_type="flow_sde",
102112
)

fastvideo/train/methods/rl/utils/pipeline.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,8 @@ def wan_denoising_with_logprob(
280280
encoder_hidden_states=prompt_embeds,
281281
return_dict=False,
282282
)
283-
ref_noise = ref_noise.to(dtype)
284-
if do_cfg:
285-
with ref_ctx:
283+
ref_noise = ref_noise.to(dtype)
284+
if do_cfg:
286285
ref_uncond = ref_model(
287286
hidden_states=latents_ori.to(
288287
dtype
@@ -293,11 +292,11 @@ def wan_denoising_with_logprob(
293292
),
294293
return_dict=False,
295294
)
296-
ref_noise = (
297-
ref_uncond
298-
+ guidance_scale
299-
* (ref_noise - ref_uncond)
300-
)
295+
ref_noise = (
296+
ref_uncond
297+
+ guidance_scale
298+
* (ref_noise - ref_uncond)
299+
)
301300

302301
(
303302
_,

fastvideo/train/methods/rl/utils/sampling.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from __future__ import annotations
66

7+
import hashlib
78
import time
89
from collections.abc import Callable
910
from typing import Any
@@ -31,8 +32,15 @@ def create_generator(
3132
"""Create deterministic generators seeded by prompt."""
3233
generators = []
3334
for prompt in prompts:
35+
prompt_seed = int.from_bytes(
36+
hashlib.blake2b(
37+
prompt.encode("utf-8"),
38+
digest_size=8,
39+
).digest(),
40+
"big",
41+
)
3442
g = torch.Generator(device=device)
35-
g.manual_seed(base_seed + hash(prompt) % (2**31))
43+
g.manual_seed(base_seed + prompt_seed % (2**31))
3644
generators.append(g)
3745
return generators
3846

@@ -72,13 +80,13 @@ def sample_epoch(
7280
ref_transformer: torch.nn.Module | None = None,
7381
lora_model: Any | None = None,
7482
tracker: Any | None = None,
83+
async_reward_scoring: bool = True,
7584
) -> tuple[
7685
list[dict[str, Any]],
7786
list[torch.Tensor],
7887
list[list[str]],
7988
]:
80-
"""Run one sampling epoch: generate videos, compute
81-
rewards asynchronously.
89+
"""Run one sampling epoch: generate videos and compute rewards.
8290
8391
Returns:
8492
Tuple of (samples, all_videos, all_prompts):
@@ -131,7 +139,7 @@ def sample_epoch(
131139
if same_latent:
132140
gen = create_generator(
133141
prompts,
134-
base_seed=epoch * SEED_EPOCH_STRIDE + i,
142+
base_seed=seed + epoch * SEED_EPOCH_STRIDE + i,
135143
device=device,
136144
)
137145
else:
@@ -188,19 +196,25 @@ def sample_epoch(
188196
.repeat(sample_batch_size, 1)
189197
)
190198

199+
videos_cpu = videos.detach().cpu()
200+
191201
# Collect decoded videos and prompts for logging.
192-
all_videos.append(videos)
202+
all_videos.append(videos_cpu)
193203
all_prompts.append(list(prompts))
194204

195-
# Async reward computation.
196-
rewards_future = executor.submit(
197-
reward_fn,
198-
videos,
199-
prompts,
200-
prompt_metadata,
201-
True,
202-
)
203-
time.sleep(0)
205+
if async_reward_scoring:
206+
rewards = executor.submit(
207+
reward_fn,
208+
videos_cpu,
209+
prompts,
210+
prompt_metadata,
211+
True,
212+
)
213+
time.sleep(0)
214+
else:
215+
rewards = (videos_cpu, list(prompts), prompt_metadata)
216+
217+
del videos
204218

205219
logger.info(
206220
"[sample_epoch] batch %d/%d: "
@@ -225,15 +239,22 @@ def sample_epoch(
225239
"next_latents": latents[:, 1:],
226240
"log_probs": log_probs,
227241
"kl": kl,
228-
"rewards": rewards_future,
242+
"rewards": rewards,
229243
}
230244
)
231245

232246
# Wait for all rewards.
233247
torch.cuda.synchronize()
234248
_t_reward_wait = time.perf_counter()
235249
for sample in samples:
236-
rewards, _ = sample["rewards"].result()
250+
if async_reward_scoring:
251+
rewards, _ = sample["rewards"].result()
252+
else:
253+
videos_cpu, prompts, prompt_metadata = sample["rewards"]
254+
torch.cuda.empty_cache()
255+
rewards, _ = reward_fn(
256+
videos_cpu, prompts, prompt_metadata, True
257+
)
237258
sample["rewards"] = {
238259
key: torch.as_tensor(value, device=device).float()
239260
for key, value in rewards.items()

fastvideo/train/methods/rl/utils/sde.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,22 @@ def sde_step_with_logprob(
115115
if deterministic:
116116
prev_sample = sample + dt * model_output
117117

118-
log_prob = (
119-
-(
120-
(prev_sample.detach() - prev_sample_mean) ** 2
118+
std_scale = std_dev_t * torch.sqrt(-1 * dt)
119+
if torch.all(std_scale == 0):
120+
log_prob = torch.zeros_like(prev_sample)
121+
else:
122+
std_scale = torch.clamp(
123+
std_scale,
124+
min=math.sqrt(torch.finfo(std_scale.dtype).tiny),
121125
)
122-
/ (2 * ((std_dev_t * torch.sqrt(-1 * dt)) ** 2))
123-
- torch.log(std_dev_t * torch.sqrt(-1 * dt))
124-
- torch.log(
125-
torch.sqrt(2 * torch.as_tensor(math.pi))
126+
log_prob = (
127+
-((prev_sample.detach() - prev_sample_mean) ** 2)
128+
/ (2 * (std_scale**2))
129+
- torch.log(std_scale)
130+
- torch.log(
131+
torch.sqrt(2 * torch.as_tensor(math.pi))
132+
)
126133
)
127-
)
128134

129135
elif sde_type == "flow_cps":
130136
std_dev_t = sigma_prev * math.sin(

0 commit comments

Comments
 (0)