---
title: causal_cd
---

<div class="python-reference">
<a id="fastvideo.train.methods.consistency_model.causal_cd-classes"></a>
<a id="fastvideo.train.methods.consistency_model.causal_cd-functions"></a>
<a id="fastvideo.train.methods.consistency_model.causal_cd"></a>
<p>Causal consistency distillation method (algorithm layer).</p>
<h2 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler</code></h2>
<pre class="doc-signature"><code>SelfForcingFlowMatchScheduler(num_inference_steps = 100, num_train_timesteps = 1000, shift = 3.0, sigma_max = 1.0, sigma_min = 0.003 / 1.002, inverse_timesteps = False, extra_one_step = False, reverse_sigmas = False, training = False)</code></pre>


<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.set_timesteps"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.set_timesteps</code></h3>
<pre class="doc-signature"><code>set_timesteps(num_inference_steps = 100, denoising_strength = 1.0, training = False, return_dict = False, **kwargs)</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.step"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.step</code></h3>
<pre class="doc-signature"><code>step(model_output: torch.FloatTensor, timestep: torch.FloatTensor, sample: torch.FloatTensor, to_final = False, return_dict = False, **kwargs)</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.calculate_alpha_beta_high"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.calculate_alpha_beta_high</code></h3>
<pre class="doc-signature"><code>calculate_alpha_beta_high(sigma, sigma_bound)</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.add_noise"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.add_noise</code></h3>
<pre class="doc-signature"><code>add_noise(original_samples, noise, timestep)</code></pre>
<p>Diffusion forward corruption process.
Input:
    - clean_latent: the clean latent with shape [B<em>T, C, H, W]
    - noise: the noise with shape [B</em>T, C, H, W]
    - timestep: the timestep with shape [B<em>T]
Output: the corrupted latent with shape [B</em>T, C, H, W]</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.add_noise_high"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.add_noise_high</code></h3>
<pre class="doc-signature"><code>add_noise_high(original_samples, noise, timestep, boundary_timestep)</code></pre>
<p>Diffusion forward corruption process.
Input:
    - clean_latent: the clean latent with shape [B<em>T, C, H, W]
    - noise: the noise with shape [B</em>T, C, H, W]
    - timestep: the timestep with shape [B<em>T]
Output: the corrupted latent with shape [B</em>T, C, H, W]</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.training_target"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.training_target</code></h3>
<pre class="doc-signature"><code>training_target(sample, noise, timestep)</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.training_weight"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.training_weight</code></h3>
<pre class="doc-signature"><code>training_weight(timestep)</code></pre>
<h4>Input</h4>
<ul>
<li>timestep: the timestep with shape [B*T]</li>
</ul>
<p>Output: the corresponding weighting [B*T]</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.scale_model_input"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.scale_model_input</code></h3>
<pre class="doc-signature"><code>scale_model_input(sample: torch.Tensor, timestep: int | None = None) -&gt; torch.Tensor</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.set_shift"><code>fastvideo.train.methods.consistency_model.causal_cd.SelfForcingFlowMatchScheduler.set_shift</code></h3>
<pre class="doc-signature"><code>set_shift(shift: float) -&gt; None</code></pre>

<h2 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod</code></h2>
<pre class="doc-signature"><code>TrainingMethod(*, cfg: Any, role_models: dict&#91;str, ModelBase&#93;)</code></pre>
<p>Base training method (algorithm layer).</p>
<p>Subclasses own their role models (student, teacher, critic, …) as
plain attributes and manage optimizers directly — no <code>RoleManager</code>
or <code>RoleHandle</code>.</p>
<p>The constructor receives <em>role_models</em> (a <code>dict[str, ModelBase]</code>)
and a <em>cfg</em> object.  It calls <code>init_preprocessors</code> on the student
and builds <code>self.role_modules</code> for FSDP wrapping.</p>
<p>A single shared CUDA RNG generator (<code>cuda_generator</code>) is
created in :meth:<code>on_train_start</code>.  All <code>torch.randn</code> /
<code>torch.randint</code> calls in methods <strong>and</strong> models must use this
generator instead of relying on global RNG state.</p>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.set_tracker"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.set_tracker</code></h3>
<pre class="doc-signature"><code>set_tracker(tracker: Any) -&gt; None</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.single_train_step"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.single_train_step</code></h3>
<pre class="doc-signature"><code>single_train_step(batch: dict&#91;str, Any&#93;, iteration: int) -&gt; tuple&#91;dict&#91;str, torch.Tensor&#93;, dict&#91;str, Any&#93;, dict&#91;str, LogScalar&#93;&#93;</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_optimizers"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_optimizers</code></h3>
<pre class="doc-signature"><code>get_optimizers(iteration: int) -&gt; Sequence&#91;torch.optim.Optimizer&#93;</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_lr_schedulers"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_lr_schedulers</code></h3>
<pre class="doc-signature"><code>get_lr_schedulers(iteration: int) -&gt; Sequence&#91;Any&#93;</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.checkpoint_state"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.checkpoint_state</code></h3>
<pre class="doc-signature"><code>checkpoint_state() -&gt; dict&#91;str, Any&#93;</code></pre>
<p>Return DCP-ready checkpoint state for all trainable roles.</p>
<p>Keys follow the convention:
<code>roles.&lt;role&gt;.&lt;module&gt;</code>, <code>optimizers.&lt;role&gt;</code>,
<code>schedulers.&lt;role&gt;</code>, <code>random_state.*</code>.</p>
<p>EMA state is managed by the <code>EMACallback</code> and is
checkpointed through the callback state mechanism.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.backward"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.backward</code></h3>
<pre class="doc-signature"><code>backward(loss_map: dict&#91;str, torch.Tensor&#93;, outputs: dict&#91;str, Any&#93;, *, grad_accum_rounds: int = 1) -&gt; None</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.optimizers_schedulers_step"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.optimizers_schedulers_step</code></h3>
<pre class="doc-signature"><code>optimizers_schedulers_step(iteration: int) -&gt; None</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.optimizers_zero_grad"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.optimizers_zero_grad</code></h3>
<pre class="doc-signature"><code>optimizers_zero_grad(iteration: int) -&gt; None</code></pre>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.seed_optimizer_state_for_resume"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.seed_optimizer_state_for_resume</code></h3>
<pre class="doc-signature"><code>seed_optimizer_state_for_resume() -&gt; None</code></pre>
<p>Seed optimizer state so DCP can load saved state.</p>
<p>A fresh optimizer has empty state (exp_avg, exp_avg_sq,
step are only created on the first optimizer.step()).
DCP needs matching entries to load into; without them
the saved optimizer state is silently dropped.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.manages_optimization"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.manages_optimization</code></h3>
<pre class="doc-signature"><code>manages_optimization() -&gt; bool</code></pre>
<p>Whether the method owns backward/optimizer stepping internally.</p>
<p>Most methods return loss tensors and let :class:<code>Trainer</code> handle
gradient accumulation, callbacks, optimizer stepping, and scheduler
stepping. RL-style methods such as DiffusionNFT need to preserve their
own sample-then-inner-train loop, so they can provide a specific
<code>managed_train_step</code>.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.managed_train_step"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.managed_train_step</code></h3>
<pre class="doc-signature"><code>managed_train_step(data_stream: Any, iteration: int) -&gt; tuple&#91;dict&#91;str, torch.Tensor&#93;, dict&#91;str, Any&#93;, dict&#91;str, LogScalar&#93;&#93;</code></pre>
<p>Run one method-managed step.</p>
<p>Subclasses that return <code>True</code> from :meth:<code>manages_optimization</code>
should override this. The fallback consumes one dataloader batch and
delegates to <code>single_train_step</code> so tests can exercise the hook with
tiny fake methods.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.on_validation_begin"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.on_validation_begin</code></h3>
<pre class="doc-signature"><code>on_validation_begin(iteration: int = 0) -&gt; dict&#91;str, LogScalar&#93;</code></pre>
<p>Run method-owned validation, if any.</p>
<p>Pipeline-style validation should remain in callbacks. Methods that
intentionally avoid inference pipelines, such as RL methods with their
own sampler/reward loop, can override this hook and return metrics for
the trainer to log at <code>iteration</code>.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_grad_clip_targets"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_grad_clip_targets</code></h3>
<pre class="doc-signature"><code>get_grad_clip_targets(iteration: int) -&gt; dict&#91;str, torch.nn.Module&#93;</code></pre>
<p>Return modules whose gradients should be clipped.</p>
<p>Override in subclasses to add/conditionally include
modules (e.g. critic, conditionally student).
Default: student transformer.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.apply_configured_lrs"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.apply_configured_lrs</code></h3>
<pre class="doc-signature"><code>apply_configured_lrs() -&gt; None</code></pre>
<p>Re-apply the config's learning rates to live optimizers/schedulers.</p>
<p>Called after a checkpoint resume when
<code>training.checkpoint.reset_lr_on_resume</code> is set: DCP restores
param-group <code>lr</code>/<code>initial_lr</code> and scheduler <code>base_lrs</code> from the
checkpoint, which would otherwise silently override an LR change in
the YAML. Methods with per-role LRs override this.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.inference_checkpoint_modules"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.inference_checkpoint_modules</code></h3>
<pre class="doc-signature"><code>inference_checkpoint_modules(role: str = &#x27;student&#x27;) -&gt; dict&#91;str, torch.nn.Module&#93;</code></pre>
<p>Return the complete modules that form an inference checkpoint.</p>
<p>This intentionally does not reuse :meth:<code>checkpoint_state</code>: resumable
state includes optimizers and every trainable role, while inference
exports select one explicit role and must retain frozen parameters too.
Methods with a different deployable role contract may override this
hook; EMA is never selected implicitly.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.inference_checkpoint_base_model_path"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.inference_checkpoint_base_model_path</code></h3>
<pre class="doc-signature"><code>inference_checkpoint_base_model_path(role: str = &#x27;student&#x27;) -&gt; str</code></pre>
<p>Return the immutable model directory supplying non-trained parts.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_grad_sanitize_targets"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.get_grad_sanitize_targets</code></h3>
<pre class="doc-signature"><code>get_grad_sanitize_targets(iteration: int) -&gt; dict&#91;str, torch.nn.Module&#93;</code></pre>
<p>Return active modules whose gradients must be made finite.</p>
<p>Most methods sanitize and clip the same modules. Methods that leave
an optimizer role unclipped can override this independently.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.set_is_last_backward"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.set_is_last_backward</code></h3>
<pre class="doc-signature"><code>set_is_last_backward(iteration: int, is_last_backward: bool) -&gt; None</code></pre>
<p>Mark an FSDP2 accumulation boundary without changing reductions.</p>
<p>Intermediate backwards still reduce-scatter their gradients. Only
FSDP's pending-reduction waits and backward-prefetch cleanup are
deferred. Use the active gradient targets, including roles whose
gradients are sanitized but deliberately not clipped. Non-FSDP
modules are unchanged.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.set_requires_gradient_sync"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.set_requires_gradient_sync</code></h3>
<pre class="doc-signature"><code>set_requires_gradient_sync(iteration: int, requires_gradient_sync: bool) -&gt; None</code></pre>
<p>Control FSDP gradient communication for accumulation rounds.</p>
<p>FSDP2 normally reduces gradients after every backward. During
gradient accumulation only the final microbatch needs that
communication; earlier rounds can accumulate local gradients. The
active gradient targets include roles that are deliberately not clipped.</p>
<p>Non-FSDP modules do not expose <code>set_requires_gradient_sync</code> and are
intentionally left unchanged.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.supports_gradient_accumulation_no_sync"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.supports_gradient_accumulation_no_sync</code></h3>
<pre class="doc-signature"><code>supports_gradient_accumulation_no_sync(iteration: int) -&gt; bool</code></pre>
<p>Whether every active role safely retains unsharded gradients.</p>
<p>FSDP2 no-sync holds full, unreduced gradients between accumulation
rounds. Model plugins must opt in explicitly; this prevents the
optimization from adding roughly one full model's FP32 gradients to
memory for ordinary full-weight training.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.on_train_start"><code>fastvideo.train.methods.consistency_model.causal_cd.TrainingMethod.on_train_start</code></h3>
<pre class="doc-signature"><code>on_train_start() -&gt; None</code></pre>

<h2 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase</code></h2>
<pre class="doc-signature"><code>ModelBase(*, trainable: bool = True, lora: LoraConfig | dict&#91;str, Any&#93; | None = None, attention_backend: AttentionBackendEnum | str | None = None)</code></pre>
<p>Per-role model instance.</p>
<p>Every role (student, teacher, critic, …) gets its own <code>ModelBase</code>
instance.  Each instance owns its own <code>transformer</code> and
<code>noise_scheduler</code>.  Heavyweight resources (VAE, dataloader, RNG
seeds) are loaded lazily via :meth:<code>init_preprocessors</code>, which the
method calls <strong>only on the student</strong>.</p>

<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.attention_backend_name"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.attention_backend_name</code></h3>
<pre class="doc-signature"><code>attention_backend_name: str | None</code></pre>
<p>Explicit per-role backend name, or <code>None</code> for global/default.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.transformer_module_type"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.transformer_module_type</code></h3>
<pre class="doc-signature"><code>transformer_module_type: str</code></pre>
<p>Physical checkpoint component that owns this role's transformer.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.device"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.device</code></h3>
<pre class="doc-signature"><code>device: torch.device</code></pre>
<p>The local CUDA device for this rank.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.init_preprocessors"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.init_preprocessors</code></h3>
<pre class="doc-signature"><code>init_preprocessors(training_config: TrainingConfig) -&gt; None</code></pre>
<p>Load VAE, build dataloader, seed RNGs.</p>
<p>Called only on the student by the method's <code>__init__</code>.
Default is a no-op so teacher/critic instances skip this.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.on_train_start"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.on_train_start</code></h3>
<pre class="doc-signature"><code>on_train_start() -&gt; None</code></pre>
<p>Called once before the training loop begins.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.eager_optimizer_state_parameters"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.eager_optimizer_state_parameters</code></h3>
<pre class="doc-signature"><code>eager_optimizer_state_parameters() -&gt; tuple&#91;torch.nn.Parameter, ...&#93;</code></pre>
<p>Parameters whose optimizer state must exist before their first grad.</p>
<p>Most models return an empty tuple. Models with intermittently used
trainable parameters can override this so strict distributed
checkpoints retain a stable optimizer-state schema across steps.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.decode_latents"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.decode_latents</code></h3>
<pre class="doc-signature"><code>decode_latents(latents_b_t_c_h_w: torch.Tensor) -&gt; torch.Tensor</code></pre>
<p>Decode <code>[B, T, C, H, W]</code> latents to <code>[B, C, T, H, W]</code> media.</p>
<p>RL reward methods call this hook instead of reaching into
model-specific VAE normalization details.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.num_train_timesteps"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.num_train_timesteps</code></h3>
<pre class="doc-signature"><code>num_train_timesteps: int</code></pre>
<p>Return the scheduler's training timestep horizon.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.shift_and_clamp_timestep"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.shift_and_clamp_timestep</code></h3>
<pre class="doc-signature"><code>shift_and_clamp_timestep(timestep: torch.Tensor) -&gt; torch.Tensor</code></pre>
<p>Apply model/pipeline timestep shifting and clamp.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.prepare_batch"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.prepare_batch</code></h3>
<pre class="doc-signature"><code>prepare_batch(raw_batch: dict&#91;str, Any&#93;, *, generator: torch.Generator, latents_source: Literal&#91;&#x27;data&#x27;, &#x27;zeros&#x27;&#93; = &#x27;data&#x27;) -&gt; TrainingBatch</code></pre>
<p>Convert a dataloader batch into forward primitives.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.add_noise"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.add_noise</code></h3>
<pre class="doc-signature"><code>add_noise(clean_latents: torch.Tensor, noise: torch.Tensor, timestep: torch.Tensor) -&gt; torch.Tensor</code></pre>
<p>Apply forward-process noise at <em>timestep</em>.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.add_noise_for_batch"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.add_noise_for_batch</code></h3>
<pre class="doc-signature"><code>add_noise_for_batch(clean_latents: torch.Tensor, noise: torch.Tensor, timestep: torch.Tensor, batch: TrainingBatch) -&gt; torch.Tensor</code></pre>
<p>Apply noise with optional immutable geometry carried by <code>batch</code>.</p>
<p>Video-only and fixed-shape models keep their existing behavior. Joint
packed models override this hook when splitting the tensor requires
batch-local shape context.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.predict_noise"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.predict_noise</code></h3>
<pre class="doc-signature"><code>predict_noise(noisy_latents: torch.Tensor, timestep: torch.Tensor, batch: TrainingBatch, *, conditional: bool, cfg_uncond: dict&#91;str, Any&#93; | None = None, attn_kind: Literal&#91;&#x27;dense&#x27;, &#x27;vsa&#x27;&#93; = &#x27;dense&#x27;) -&gt; NoisePrediction</code></pre>
<p>Predict video flow or an ordered <code>(video, audio)</code> flow pair.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.predict_x0"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.predict_x0</code></h3>
<pre class="doc-signature"><code>predict_x0(noisy_latents: torch.Tensor, timestep: torch.Tensor, batch: TrainingBatch, *, conditional: bool, cfg_uncond: dict&#91;str, Any&#93; | None = None, attn_kind: Literal&#91;&#x27;dense&#x27;, &#x27;vsa&#x27;&#93; = &#x27;dense&#x27;) -&gt; torch.Tensor</code></pre>
<p>Convert a video-only flow prediction to clean video latents.</p>
<p>This helper owns one noisy video tensor and one video scheduler. Joint
video/audio callers apply their modality-specific conversions where
both noisy tensors and both schedulers are available.</p>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.ModelBase.backward"><code>fastvideo.train.methods.consistency_model.causal_cd.ModelBase.backward</code></h3>
<pre class="doc-signature"><code>backward(loss: torch.Tensor, ctx: Any, *, grad_accum_rounds: int) -&gt; None</code></pre>
<p>Backward that may restore forward-context.</p>
<h2 id="fastvideo.train.methods.consistency_model.causal_cd.build_optimizer_and_scheduler"><code>fastvideo.train.methods.consistency_model.causal_cd.build_optimizer_and_scheduler</code></h2>
<pre class="doc-signature"><code>build_optimizer_and_scheduler(*, params: list&#91;torch.nn.Parameter&#93;, optimizer_config: OptimizerConfig, loop_config: TrainingLoopConfig, learning_rate: float, betas: tuple&#91;float, float&#93;, scheduler_name: str, weight_decay: float | None = None) -&gt; tuple&#91;torch.optim.Optimizer, object&#93;</code></pre>
<p>Build an AdamW optimizer and LR scheduler.</p>
<p>Returns <code>(optimizer, lr_scheduler)</code> so the caller can store them
as method-level attributes.</p>
<h2 id="fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod"><code>fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod</code></h2>
<pre class="doc-signature"><code>CausalConsistencyDistillationMethod(*, cfg: Any, role_models: dict&#91;str, ModelBase&#93;)</code></pre>


<details><summary>Source</summary><pre><code>class CausalConsistencyDistillationMethod(TrainingMethod):

    def __init__(
        self,
        *,
        cfg: Any,
        role_models: dict&#91;str, ModelBase&#93;,
    ) -&gt; None:
        super().__init__(cfg=cfg, role_models=role_models)

        for role in (&quot;student&quot;, &quot;teacher&quot;, &quot;ema&quot;):
            if role not in role_models:
                raise ValueError(f&quot;Causal-CD requires role &#123;role!r&#125; &quot;
                                 &quot;(student trainable; teacher + ema frozen, &quot;
                                 &quot;both initialized from the student&#x27;s &quot;
                                 &quot;checkpoint)&quot;)
        if not self.student._trainable:
            raise ValueError(&quot;Causal-CD requires student to be trainable&quot;)
        self.teacher = role_models&#91;&quot;teacher&quot;&#93;
        self.ema_model = role_models&#91;&quot;ema&quot;&#93;

        self._attn_kind = self._infer_attn_kind()
        self._guidance_scale = float(self.method_config.get(&quot;guidance_scale&quot;, 3.0))
        self._discrete_cd_n = int(self.method_config.get(&quot;discrete_cd_N&quot;, 48))
        if self._discrete_cd_n &lt; 2:
            raise ValueError(&quot;method.discrete_cd_N must be &gt;= 2&quot;)
        self._ema_decay = float(self.method_config.get(&quot;ema_decay&quot;, 0.99))
        self._ema_start_step = int(self.method_config.get(&quot;ema_start_step&quot;, 200))
        shift = getattr(self.training_config.pipeline_config, &quot;flow_shift&quot;, None)
        self._flow_shift = float(shift) if shift else 5.0

        self.student.init_preprocessors(self.training_config)
        self._sf_scheduler = SelfForcingFlowMatchScheduler(
            num_inference_steps=self._discrete_cd_n,
            num_train_timesteps=int(self.student.num_train_timesteps),
            shift=self._flow_shift,
            sigma_min=0.0,
            sigma_max=1.0,
            extra_one_step=True,
            training=False,
        )
        self._init_optimizers_and_schedulers()

    # ------------------------------------------------------------------

    @property
    def _optimizer_dict(self) -&gt; dict&#91;str, Any&#93;:
        return &#123;&quot;student&quot;: self._student_optimizer&#125;

    @property
    def _lr_scheduler_dict(self) -&gt; dict&#91;str, Any&#93;:
        return &#123;&quot;student&quot;: self._student_lr_scheduler&#125;

    def get_optimizers(self, iteration: int) -&gt; list&#91;torch.optim.Optimizer&#93;:
        del iteration
        return &#91;self._student_optimizer&#93;

    def get_lr_schedulers(self, iteration: int) -&gt; list&#91;Any&#93;:
        del iteration
        return &#91;self._student_lr_scheduler&#93;

    def checkpoint_state(self) -&gt; dict&#91;str, Any&#93;:
        # The EMA role is frozen (so the base class skips it) but mutated by
        # _update_ema every step; without persisting it a resume reloads the
        # EMA from init_from and the consistency target snaps back to the
        # base checkpoint. Mirrors DiffusionNFT&#x27;s frozen &quot;old&quot; role.
        states = super().checkpoint_state()
        states&#91;&quot;roles.ema.transformer&quot;&#93; = _FullModelState(self.ema_model.transformer)
        return states

    # ------------------------------------------------------------------

    def single_train_step(
        self,
        batch: dict&#91;str, Any&#93;,
        iteration: int,
    ) -&gt; tuple&#91;dict&#91;str, torch.Tensor&#93;, dict&#91;str, Any&#93;, dict&#91;str, LogScalar&#93;&#93;:
        del iteration
        training_batch = self.student.prepare_batch(
            batch,
            generator=self.cuda_generator,
            latents_source=&quot;data&quot;,
        )
        clean_latents = training_batch.latents
        if not torch.is_tensor(clean_latents) or clean_latents.ndim != 5:
            raise ValueError(&quot;Causal-CD expects &#91;B, T, C, H, W&#93; latents&quot;)

        batch_size, num_latents = int(clean_latents.shape&#91;0&#93;), int(clean_latents.shape&#91;1&#93;)
        device = clean_latents.device

        sigmas = self._sf_scheduler.sigmas.to(device)
        timesteps = self._sf_scheduler.timesteps.to(device)
        idx = torch.randint(0, self._discrete_cd_n - 1, (1, ), generator=self.cuda_generator, device=device).squeeze(0)
        t, t_next = timesteps&#91;idx&#93;, timesteps&#91;idx + 1&#93;
        sigma_t, sigma_t_next = sigmas&#91;idx&#93;, sigmas&#91;idx + 1&#93;
        t_pf = t * torch.ones(batch_size, num_latents, device=device)
        t_next_pf = t_next * torch.ones(batch_size, num_latents, device=device)

        noise = torch.randn(
            clean_latents.shape,
            generator=self.cuda_generator,
            device=device,
            dtype=clean_latents.dtype,
        )
        latent_t = (1.0 - sigma_t) * clean_latents + sigma_t * noise

        # Set before any forward: predict_noise feeds batch.timesteps into
        # set_forward_context (VSA sparsity gating), so the teacher CFG
        # passes below must not see the stale timesteps from prepare_batch.
        training_batch.timesteps = t_pf

        with torch.no_grad():
            v_cond = self._predict_flow(self.teacher,
                                        latent_t,
                                        t_pf,
                                        training_batch,
                                        conditional=True,
                                        clean_x=clean_latents)
            v_uncond = self._predict_flow(self.teacher,
                                          latent_t,
                                          t_pf,
                                          training_batch,
                                          conditional=False,
                                          clean_x=clean_latents)
            v_pred = v_uncond + self._guidance_scale * (v_cond - v_uncond)
            dt = ((t - t_next) / float(self.student.num_train_timesteps))
            latent_t_next = latent_t - dt * v_pred

        flow_student = self._predict_flow(self.student,
                                          latent_t,
                                          t_pf,
                                          training_batch,
                                          conditional=True,
                                          clean_x=clean_latents)
        x0_t = latent_t - sigma_t * flow_student

        with torch.no_grad():
            flow_ema = self._predict_flow(self.ema_model,
                                          latent_t_next,
                                          t_next_pf,
                                          training_batch,
                                          conditional=True,
                                          clean_x=clean_latents)
            x0_t_next = latent_t_next - sigma_t_next * flow_ema

        loss = F.mse_loss(x0_t.float(), x0_t_next.float())

        loss_map = &#123;&quot;total_loss&quot;: loss, &quot;causal_cd_loss&quot;: loss&#125;
        attn_metadata = (training_batch.attn_metadata_vsa if self._attn_kind == &quot;vsa&quot; else training_batch.attn_metadata)
        outputs: dict&#91;str, Any&#93; = &#123;&quot;_fv_backward&quot;: (t_pf, attn_metadata)&#125;
        metrics: dict&#91;str, LogScalar&#93; = &#123;&#125;
        return loss_map, outputs, metrics

    # ------------------------------------------------------------------

    def backward(
        self,
        loss_map: dict&#91;str, torch.Tensor&#93;,
        outputs: dict&#91;str, Any&#93;,
        *,
        grad_accum_rounds: int = 1,
    ) -&gt; None:
        grad_accum_rounds = max(1, int(grad_accum_rounds))
        ctx = outputs.get(&quot;_fv_backward&quot;)
        if ctx is None:
            super().backward(loss_map, outputs, grad_accum_rounds=grad_accum_rounds)
            return
        self.student.backward(loss_map&#91;&quot;total_loss&quot;&#93;, ctx, grad_accum_rounds=grad_accum_rounds)

    def optimizers_schedulers_step(self, iteration: int) -&gt; None:
        super().optimizers_schedulers_step(iteration)
        if iteration &gt;= self._ema_start_step:
            self._update_ema()

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _predict_flow(
        self,
        model: ModelBase,
        latents: torch.Tensor,
        timestep: torch.Tensor,
        batch: Any,
        *,
        conditional: bool,
        clean_x: torch.Tensor,
    ) -&gt; torch.Tensor:
        return model.predict_noise(latents,
                                   timestep,
                                   batch,
                                   conditional=conditional,
                                   cfg_uncond=None,
                                   attn_kind=self._attn_kind,
                                   clean_x=clean_x)

    @torch.no_grad()
    def _update_ema(self) -&gt; None:
        decay = self._ema_decay
        for ema_p, p in zip(self.ema_model.transformer.parameters(), self.student.transformer.parameters(),
                            strict=True):
            ema_p.mul_(decay).add_(p.detach().to(ema_p.dtype), alpha=1.0 - decay)

    def _init_optimizers_and_schedulers(self) -&gt; None:
        tc = self.training_config
        student_lr = float(tc.optimizer.learning_rate)
        if student_lr &lt;= 0.0:
            raise ValueError(&quot;training.learning_rate must be &gt; 0 for causal-cd&quot;)
        student_params = &#91;p for p in self.student.transformer.parameters() if p.requires_grad&#93;
        (
            self._student_optimizer,
            self._student_lr_scheduler,
        ) = build_optimizer_and_scheduler(
            params=student_params,
            optimizer_config=tc.optimizer,
            loop_config=tc.loop,
            learning_rate=student_lr,
            betas=tc.optimizer.betas,
            scheduler_name=str(tc.optimizer.lr_scheduler),
        )</code></pre></details>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.get_optimizers"><code>fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.get_optimizers</code></h3>
<pre class="doc-signature"><code>get_optimizers(iteration: int) -&gt; list&#91;torch.optim.Optimizer&#93;</code></pre>

<details><summary>Source</summary><pre><code>def get_optimizers(self, iteration: int) -&gt; list&#91;torch.optim.Optimizer&#93;:
    del iteration
    return &#91;self._student_optimizer&#93;</code></pre></details>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.get_lr_schedulers"><code>fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.get_lr_schedulers</code></h3>
<pre class="doc-signature"><code>get_lr_schedulers(iteration: int) -&gt; list&#91;Any&#93;</code></pre>

<details><summary>Source</summary><pre><code>def get_lr_schedulers(self, iteration: int) -&gt; list&#91;Any&#93;:
    del iteration
    return &#91;self._student_lr_scheduler&#93;</code></pre></details>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.checkpoint_state"><code>fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.checkpoint_state</code></h3>
<pre class="doc-signature"><code>checkpoint_state() -&gt; dict&#91;str, Any&#93;</code></pre>

<details><summary>Source</summary><pre><code>def checkpoint_state(self) -&gt; dict&#91;str, Any&#93;:
    # The EMA role is frozen (so the base class skips it) but mutated by
    # _update_ema every step; without persisting it a resume reloads the
    # EMA from init_from and the consistency target snaps back to the
    # base checkpoint. Mirrors DiffusionNFT&#x27;s frozen &quot;old&quot; role.
    states = super().checkpoint_state()
    states&#91;&quot;roles.ema.transformer&quot;&#93; = _FullModelState(self.ema_model.transformer)
    return states</code></pre></details>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.single_train_step"><code>fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.single_train_step</code></h3>
<pre class="doc-signature"><code>single_train_step(batch: dict&#91;str, Any&#93;, iteration: int) -&gt; tuple&#91;dict&#91;str, torch.Tensor&#93;, dict&#91;str, Any&#93;, dict&#91;str, LogScalar&#93;&#93;</code></pre>

<details><summary>Source</summary><pre><code>def single_train_step(
    self,
    batch: dict&#91;str, Any&#93;,
    iteration: int,
) -&gt; tuple&#91;dict&#91;str, torch.Tensor&#93;, dict&#91;str, Any&#93;, dict&#91;str, LogScalar&#93;&#93;:
    del iteration
    training_batch = self.student.prepare_batch(
        batch,
        generator=self.cuda_generator,
        latents_source=&quot;data&quot;,
    )
    clean_latents = training_batch.latents
    if not torch.is_tensor(clean_latents) or clean_latents.ndim != 5:
        raise ValueError(&quot;Causal-CD expects &#91;B, T, C, H, W&#93; latents&quot;)

    batch_size, num_latents = int(clean_latents.shape&#91;0&#93;), int(clean_latents.shape&#91;1&#93;)
    device = clean_latents.device

    sigmas = self._sf_scheduler.sigmas.to(device)
    timesteps = self._sf_scheduler.timesteps.to(device)
    idx = torch.randint(0, self._discrete_cd_n - 1, (1, ), generator=self.cuda_generator, device=device).squeeze(0)
    t, t_next = timesteps&#91;idx&#93;, timesteps&#91;idx + 1&#93;
    sigma_t, sigma_t_next = sigmas&#91;idx&#93;, sigmas&#91;idx + 1&#93;
    t_pf = t * torch.ones(batch_size, num_latents, device=device)
    t_next_pf = t_next * torch.ones(batch_size, num_latents, device=device)

    noise = torch.randn(
        clean_latents.shape,
        generator=self.cuda_generator,
        device=device,
        dtype=clean_latents.dtype,
    )
    latent_t = (1.0 - sigma_t) * clean_latents + sigma_t * noise

    # Set before any forward: predict_noise feeds batch.timesteps into
    # set_forward_context (VSA sparsity gating), so the teacher CFG
    # passes below must not see the stale timesteps from prepare_batch.
    training_batch.timesteps = t_pf

    with torch.no_grad():
        v_cond = self._predict_flow(self.teacher,
                                    latent_t,
                                    t_pf,
                                    training_batch,
                                    conditional=True,
                                    clean_x=clean_latents)
        v_uncond = self._predict_flow(self.teacher,
                                      latent_t,
                                      t_pf,
                                      training_batch,
                                      conditional=False,
                                      clean_x=clean_latents)
        v_pred = v_uncond + self._guidance_scale * (v_cond - v_uncond)
        dt = ((t - t_next) / float(self.student.num_train_timesteps))
        latent_t_next = latent_t - dt * v_pred

    flow_student = self._predict_flow(self.student,
                                      latent_t,
                                      t_pf,
                                      training_batch,
                                      conditional=True,
                                      clean_x=clean_latents)
    x0_t = latent_t - sigma_t * flow_student

    with torch.no_grad():
        flow_ema = self._predict_flow(self.ema_model,
                                      latent_t_next,
                                      t_next_pf,
                                      training_batch,
                                      conditional=True,
                                      clean_x=clean_latents)
        x0_t_next = latent_t_next - sigma_t_next * flow_ema

    loss = F.mse_loss(x0_t.float(), x0_t_next.float())

    loss_map = &#123;&quot;total_loss&quot;: loss, &quot;causal_cd_loss&quot;: loss&#125;
    attn_metadata = (training_batch.attn_metadata_vsa if self._attn_kind == &quot;vsa&quot; else training_batch.attn_metadata)
    outputs: dict&#91;str, Any&#93; = &#123;&quot;_fv_backward&quot;: (t_pf, attn_metadata)&#125;
    metrics: dict&#91;str, LogScalar&#93; = &#123;&#125;
    return loss_map, outputs, metrics</code></pre></details>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.backward"><code>fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.backward</code></h3>
<pre class="doc-signature"><code>backward(loss_map: dict&#91;str, torch.Tensor&#93;, outputs: dict&#91;str, Any&#93;, *, grad_accum_rounds: int = 1) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def backward(
    self,
    loss_map: dict&#91;str, torch.Tensor&#93;,
    outputs: dict&#91;str, Any&#93;,
    *,
    grad_accum_rounds: int = 1,
) -&gt; None:
    grad_accum_rounds = max(1, int(grad_accum_rounds))
    ctx = outputs.get(&quot;_fv_backward&quot;)
    if ctx is None:
        super().backward(loss_map, outputs, grad_accum_rounds=grad_accum_rounds)
        return
    self.student.backward(loss_map&#91;&quot;total_loss&quot;&#93;, ctx, grad_accum_rounds=grad_accum_rounds)</code></pre></details>
<h3 id="fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.optimizers_schedulers_step"><code>fastvideo.train.methods.consistency_model.causal_cd.CausalConsistencyDistillationMethod.optimizers_schedulers_step</code></h3>
<pre class="doc-signature"><code>optimizers_schedulers_step(iteration: int) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def optimizers_schedulers_step(self, iteration: int) -&gt; None:
    super().optimizers_schedulers_step(iteration)
    if iteration &gt;= self._ema_start_step:
        self._update_ema()</code></pre></details>
</div>
