-
Notifications
You must be signed in to change notification settings - Fork 7
feat(diffusion): add SFT loss hub and pre-encoded data manager #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
de4c35a
4c9ff87
1616581
e1e8710
ce29764
2fb0a6c
e5d7afc
0f026e8
abcc73a
34f1cb2
d974b27
f1ceb9d
795e5d7
cd44bab
68c88f8
4d2450c
8f792a0
6ebacad
f049028
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| """SFT batch preparation and loss formula (rectified-flow velocity MSE).""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import torch | ||
| import torch.nn as nn | ||
|
|
||
| from miles.backends.fsdp_utils.loss_hub.types import DiffusionLossContext, PreparedBatch | ||
| from miles.backends.fsdp_utils.loss_hub.utils import cast_cond_to_dtype | ||
| from miles.utils.metric_buffer import MetricBuffer | ||
|
|
||
|
|
||
| def sample_grid_indices(ctx: DiffusionLossContext, bsz: int) -> tuple[str, nn.Module, torch.Tensor]: | ||
| """Pick one DiT component per micro-batch (phase-pure), then grid indices within its range.""" | ||
| num_grid = len(ctx.scheduler.timesteps) | ||
| if len(ctx.models) == 1: | ||
| component_name, model = next(iter(ctx.models.items())) | ||
| return component_name, model, torch.randint(num_grid, (bsz,)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may break determinism for load/save |
||
|
|
||
| # A uniform anchor index picks each expert with probability equal to its share of the | ||
| # grid, keeping the marginal over indices uniform while the micro-batch stays single-expert. | ||
| num_train_timesteps = int(ctx.scheduler.config.num_train_timesteps) | ||
| config = ctx.train_pipeline_config | ||
| components = [config.component_for_timestep(float(t), num_train_timesteps) for t in ctx.scheduler.timesteps] | ||
| component_name = components[int(torch.randint(num_grid, (1,)))] | ||
| pool = torch.tensor([i for i, name in enumerate(components) if name == component_name]) | ||
| return component_name, ctx.models[component_name], pool[torch.randint(len(pool), (bsz,))] | ||
|
|
||
|
|
||
| def prepare_sft_batch( | ||
| ctx: DiffusionLossContext, | ||
| batch: list[dict], | ||
| *, | ||
| pad_to_len: int | None = None, | ||
| ) -> PreparedBatch: | ||
| """Corrupt cached clean latents at sampled grid sigmas; CFG-free cached cond.""" | ||
| device = ctx.device | ||
| config = ctx.train_pipeline_config | ||
| bsz = len(batch) | ||
|
|
||
| x0 = torch.stack([pair["latent"] for pair in batch]).to(device=device, dtype=torch.float32) | ||
| component_name, model, idx = sample_grid_indices(ctx, bsz) | ||
| idx = idx.to(device) | ||
| timesteps = ctx.scheduler.timesteps[idx].to(dtype=torch.float32) | ||
| sigmas = ctx.scheduler.sigmas[idx].to(dtype=torch.float32) | ||
|
|
||
| noise = torch.randn(x0.shape, device=device, dtype=torch.float32) | ||
| sigma_exp = sigmas.view(bsz, *([1] * (x0.ndim - 1))) | ||
| latents = (1.0 - sigma_exp) * x0 + sigma_exp * noise | ||
|
|
||
| num_train_timesteps = int(ctx.scheduler.config.num_train_timesteps) | ||
| if config.needs_timestep_scaling: | ||
| timesteps_for_model = timesteps / float(num_train_timesteps) | ||
| else: | ||
| timesteps_for_model = timesteps | ||
|
|
||
| cond_list = [{key: value.to(device) for key, value in pair["cond_kwargs"].items()} for pair in batch] | ||
| pos_cond = cast_cond_to_dtype( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rockdu TODO: integrate SFT&NFT into input dtype precision control |
||
| config.collate_cond_for_sample_batch(cond_list, device, pad_to_len=pad_to_len), | ||
| ctx.forward_dtype, | ||
| ) | ||
|
|
||
| return PreparedBatch( | ||
| latents=latents, | ||
| timesteps=timesteps, | ||
| timesteps_for_model=timesteps_for_model, | ||
| model=model, | ||
| component_name=component_name, | ||
| guidance_scale=0.0, | ||
| use_cfg=False, | ||
| cfg_batching=False, | ||
| true_cfg_scale=None, | ||
| pos_cond=pos_cond, | ||
| neg_cond=None, | ||
| joint_cond=None, | ||
| advantage=torch.ones(bsz, device=device, dtype=torch.float32), | ||
| extras={"target": noise - x0}, | ||
| ) | ||
|
|
||
|
|
||
| def sft_loss_formula( | ||
| ctx: DiffusionLossContext, | ||
| batch: list[dict], | ||
| prepared: PreparedBatch, | ||
| *, | ||
| new_pred: torch.Tensor, | ||
| ref_pred: torch.Tensor | None, | ||
| metrics: MetricBuffer, | ||
| write_old_log_prob: bool = False, | ||
| old_log_prob_from_new: bool = False, | ||
| ) -> torch.Tensor: | ||
| """Velocity-target MSE: ``||pred - (eps - x0)||^2`` averaged per pair.""" | ||
| target = prepared.extras["target"] | ||
| per_pair = ((new_pred.float() - target) ** 2).mean(dim=tuple(range(1, target.ndim))) | ||
| loss_sum = per_pair.sum() | ||
|
|
||
| with torch.no_grad(): | ||
| metrics.emit_mean("loss", total=loss_sum, count=len(batch)) | ||
| return loss_sum | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,13 +82,16 @@ def create_placement_groups(args): | |
| """Create placement groups for actor and rollout engines. | ||
|
|
||
| Two topologies: | ||
| - Colocate (or --debug-{train,rollout}-only): one combined placement | ||
| group; both roles see the same bundle list. | ||
| - Disaggregate (the else branch): two separate placement groups so | ||
| train and rollout each own a disjoint GPU pool — avoids bundle | ||
| overlap / scheduling deadlock when running side-by-side. | ||
| - Colocate: one combined placement group; both roles see the same bundles. | ||
| Train-only jobs use this by default. | ||
| - Disaggregate: separate actor and rollout placement groups. A train-only | ||
| job opts into this by setting --rollout-num-gpus, reserving those GPUs for | ||
| its rollout-side producer (for example, the SFT encoder pool). | ||
| """ | ||
| if not args.colocate and not args.debug_train_only and not args.debug_rollout_only: | ||
| disaggregate = ( | ||
| not args.colocate and not args.debug_rollout_only and (not args.train_only or bool(args.rollout_num_gpus)) | ||
| ) | ||
| if disaggregate: | ||
| logger.info("Creating placement groups (separate actor/rollout)...") | ||
| actor_gpus = args.actor_num_nodes * args.actor_num_gpus_per_node | ||
| rollout_gpus = args.rollout_num_gpus | ||
|
|
@@ -107,14 +110,11 @@ def create_placement_groups(args): | |
| logger.info(f"Creating placement group with {num_gpus} GPUs...") | ||
| pg, all_reordered_bundle_indices, all_reordered_gpu_ids = _create_placement_group(num_gpus) | ||
|
|
||
| actor_pg_reordered_bundle_indices = all_reordered_bundle_indices | ||
| actor_pg_reordered_gpu_ids = all_reordered_gpu_ids | ||
| rollout_pg_reordered_bundle_indices = all_reordered_bundle_indices if not args.debug_train_only else [] | ||
| rollout_pg_reordered_gpu_ids = all_reordered_gpu_ids if not args.debug_train_only else [] | ||
|
|
||
| # The rollout view keeps its seats under train_only: engine startup is gated by | ||
| # args.train_only, and rollout-side actor pools (e.g. the SFT encoder pool) seat here. | ||
| return { | ||
| "actor": (pg, actor_pg_reordered_bundle_indices, actor_pg_reordered_gpu_ids), | ||
| "rollout": (pg, rollout_pg_reordered_bundle_indices, rollout_pg_reordered_gpu_ids), | ||
| "actor": (pg, all_reordered_bundle_indices, all_reordered_gpu_ids), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From my understanding, this assumes all settings are collocated training, which is not good for later framework evolution. |
||
| "rollout": (pg, all_reordered_bundle_indices, all_reordered_gpu_ids), | ||
| } | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: centralize forward noising logic (in NFT/SFT) into diffusers/local-maintained schedulers and add new args for train-side scheduler designation