---
title: anyflow
---

<div class="python-reference">
<a id="fastvideo.train.methods.distribution_matching.anyflow-classes"></a>
<a id="fastvideo.train.methods.distribution_matching.anyflow"></a>
<p>AnyFlow on-policy distillation method.</p>
<p>Stage 2 of the AnyFlow two-stage recipe. Continues from a pretrained
flow-map student and refines it via distribution-matching distillation
(DMD2) where the student is rolled out for <code>student_sample_steps</code>
Euler-flow steps from pure noise. One randomly-chosen step in the
rollout is gradient-enabled (and broadcast across ranks so every worker
agrees on which step to gradient-enable); the rest run under
<code>torch.no_grad</code>.</p>
<p>Inherits <code>DMD2Method</code> for the alternating student / critic update
machinery and the existing DMD VSD-with-fake-score loss. Overrides
<code>_student_rollout</code> to drive the multi-step Euler-flow rollout with
<code>r = t_next</code> (mean-velocity sampling — matches the AnyFlow paper's
<code>WanAnyFlowPipeline.training_rollout</code> with <code>use_mean_velocity=True</code>).</p>
<p>Reference: <code>pipeline_wan_anyflow.py::training_rollout</code> in
NVlabs/AnyFlow at commit <code>549236a</code>.</p>
<h2 id="fastvideo.train.methods.distribution_matching.anyflow.DMD2Method"><code>fastvideo.train.methods.distribution_matching.anyflow.DMD2Method</code></h2>
<pre class="doc-signature"><code>DMD2Method(*, cfg: Any, role_models: dict&#91;str, ModelBase&#93;)</code></pre>
<p>DMD2 distillation algorithm (method layer).</p>
<p>Owns role model instances directly:
- <code>self.student</code> — trainable student :class:<code>ModelBase</code>
- <code>self.teacher</code> — frozen teacher :class:<code>ModelBase</code>
- <code>self.critic</code> — trainable critic :class:<code>ModelBase</code></p>

<h3 id="fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.single_train_step"><code>fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.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.distribution_matching.anyflow.DMD2Method.backward"><code>fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.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.distribution_matching.anyflow.DMD2Method.get_optimizers"><code>fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.get_optimizers</code></h3>
<pre class="doc-signature"><code>get_optimizers(iteration: int) -&gt; list&#91;torch.optim.Optimizer&#93;</code></pre>

<h3 id="fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.get_lr_schedulers"><code>fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.get_lr_schedulers</code></h3>
<pre class="doc-signature"><code>get_lr_schedulers(iteration: int) -&gt; list&#91;Any&#93;</code></pre>

<h3 id="fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.get_grad_clip_targets"><code>fastvideo.train.methods.distribution_matching.anyflow.DMD2Method.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>

<h2 id="fastvideo.train.methods.distribution_matching.anyflow.get_optional_float"><code>fastvideo.train.methods.distribution_matching.anyflow.get_optional_float</code></h2>
<pre class="doc-signature"><code>get_optional_float(mapping: dict&#91;str, Any&#93;, key: str, *, where: str) -&gt; float | None</code></pre>

<h2 id="fastvideo.train.methods.distribution_matching.anyflow.get_optional_int"><code>fastvideo.train.methods.distribution_matching.anyflow.get_optional_int</code></h2>
<pre class="doc-signature"><code>get_optional_int(mapping: dict&#91;str, Any&#93;, key: str, *, where: str) -&gt; int | None</code></pre>

<h2 id="fastvideo.train.methods.distribution_matching.anyflow.AnyFlowMethod"><code>fastvideo.train.methods.distribution_matching.anyflow.AnyFlowMethod</code></h2>
<pre class="doc-signature"><code>AnyFlowMethod(*, cfg: Any, role_models: dict&#91;str, Any&#93;)</code></pre>
<p>AnyFlow on-policy distillation (multi-step rollout).</p>

<details><summary>Source</summary><pre><code>class AnyFlowMethod(DMD2Method):
    &quot;&quot;&quot;AnyFlow on-policy distillation (multi-step rollout).&quot;&quot;&quot;

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

        student_sample_steps = get_optional_int(mcfg, &quot;student_sample_steps&quot;, where=&quot;method.student_sample_steps&quot;)
        if student_sample_steps is None:
            student_sample_steps = 4
        if int(student_sample_steps) &lt;= 0:
            raise ValueError(&quot;method.student_sample_steps must be positive, &quot;
                             f&quot;got &#123;student_sample_steps&#125;&quot;)
        self._student_sample_steps = int(student_sample_steps)

        use_mean_velocity_raw = mcfg.get(&quot;use_mean_velocity&quot;, True)
        if not isinstance(use_mean_velocity_raw, bool):
            raise ValueError(&quot;method.use_mean_velocity must be a bool, &quot;
                             f&quot;got &#123;type(use_mean_velocity_raw).__name__&#125;&quot;)
        self._use_mean_velocity = bool(use_mean_velocity_raw)

        # Optional pinned rollout schedule (descending, absolute t-units).
        # Falls back to dmd_denoising_steps when absent.
        raw_t_list = mcfg.get(&quot;t_list_override&quot;, None)
        if raw_t_list is None:
            self._t_list_override: list&#91;float&#93; | None = None
        else:
            if not isinstance(raw_t_list, list) or not raw_t_list:
                raise ValueError(&quot;method.t_list_override must be a non-empty list of &quot;
                                 f&quot;floats when set, got &#123;raw_t_list!r&#125;&quot;)
            t_list = &#91;float(x) for x in raw_t_list&#93;
            for i in range(len(t_list) - 1):
                if t_list&#91;i&#93; &lt; t_list&#91;i + 1&#93;:
                    raise ValueError(&quot;method.t_list_override must be descending, &quot;
                                     f&quot;got &#123;t_list!r&#125;&quot;)
            self._t_list_override = t_list

        # Scoring conditioning: AnyFlow scores against r=0 for the DMD branch.
        score_r_raw = mcfg.get(&quot;dmd_score_r_value&quot;, 0.0)
        try:
            self._dmd_score_r = float(score_r_raw)
        except (TypeError, ValueError) as exc:
            raise ValueError(&quot;method.dmd_score_r_value must be numeric, &quot;
                             f&quot;got &#123;score_r_raw!r&#125;&quot;) from exc

        # Optional teacher guidance scale for the DMD loss (carry over from
        # DMD2Method&#x27;s behavior; default 1.0).
        guidance = get_optional_float(mcfg, &quot;real_score_guidance_scale&quot;, where=&quot;method.real_score_guidance_scale&quot;)
        self._real_score_guidance = float(guidance) if guidance is not None else 1.0

    # ------------------------------------------------------------------
    # Rollout schedule

    def _get_rollout_schedule(self, *, device: torch.device) -&gt; torch.Tensor:
        &quot;&quot;&quot;Build the descending timestep schedule used by the on-policy
        rollout. Length is ``num_steps + 1`` so ``num_steps`` Euler steps
        consume the full range.

        Order of precedence:
          1. ``method.t_list_override`` — used verbatim (absolute units).
          2. ``method.dmd_denoising_steps`` (inherited from DMD2) appended
             with a final 0 boundary if the last entry isn&#x27;t already 0.
        &quot;&quot;&quot;
        if self._t_list_override is not None:
            return torch.tensor(self._t_list_override, device=device, dtype=torch.float32)

        steps = self._get_denoising_step_list(device).to(dtype=torch.float32)
        if float(steps&#91;-1&#93;.item()) != 0.0:
            zero = torch.zeros(1, device=device, dtype=torch.float32)
            steps = torch.cat(&#91;steps, zero&#93;, dim=0)
        return steps

    def _broadcast_grad_step_index(
        self,
        num_steps: int,
        *,
        device: torch.device,
    ) -&gt; int:
        &quot;&quot;&quot;Pick the rollout step that gets gradient enabled. In distributed
        runs the choice is broadcast from rank 0 so every worker agrees.&quot;&quot;&quot;
        if num_steps &lt;= 0:
            raise ValueError(&quot;num_steps must be positive&quot;)
        if dist.is_initialized() and dist.get_rank() != 0:
            idx_tensor = torch.empty((1, ), dtype=torch.long, device=device)
        else:
            idx_tensor = torch.randint(0,
                                       num_steps, (1, ),
                                       device=device,
                                       dtype=torch.long,
                                       generator=self.cuda_generator)
        if dist.is_initialized():
            dist.broadcast(idx_tensor, src=0)
        return int(idx_tensor.item())

    # ------------------------------------------------------------------
    # Rollout

    def _student_rollout(
        self,
        batch: Any,
        *,
        with_grad: bool,
    ) -&gt; torch.Tensor:
        &quot;&quot;&quot;Multi-step Euler-flow rollout from pure noise.

        Returns the predicted clean latent ``x_0`` after the chosen
        gradient step (or the final ``x`` after the last step if
        ``with_grad`` is False — used by the critic path).
        &quot;&quot;&quot;
        latents = batch.latents
        if latents is None or latents.ndim != 5:
            raise RuntimeError(&quot;AnyFlow on-policy rollout requires TrainingBatch.latents &quot;
                               &quot;of shape &#91;B, T, C, H, W&#93; for shape templating&quot;)
        device = latents.device
        dtype = latents.dtype

        schedule = self._get_rollout_schedule(device=device)
        num_entries = int(schedule.numel())
        num_steps = num_entries - 1
        if num_steps &lt;= 0:
            raise RuntimeError(&quot;rollout schedule must have at least two entries &quot;
                               f&quot;(got &#123;num_entries&#125;)&quot;)
        if num_steps &gt; self._student_sample_steps:
            # Trim to the configured cap, keeping the last (=0) boundary.
            schedule = torch.cat(&#91;schedule&#91;:self._student_sample_steps&#93;, schedule&#91;-1:&#93;&#93;, dim=0)
            num_steps = self._student_sample_steps

        grad_step = self._broadcast_grad_step_index(num_steps, device=device) if with_grad else -1

        attn_kind: Literal&#91;&quot;dense&quot;, &quot;vsa&quot;&#93; = &quot;vsa&quot;
        n_train = float(self.student.num_train_timesteps)

        x = torch.randn(latents.shape, device=device, dtype=dtype, generator=self.cuda_generator)
        last_pred_x0: torch.Tensor | None = None
        batch_size = int(latents.shape&#91;0&#93;)

        for i in range(num_steps):
            t_cur = schedule&#91;i&#93;.expand(batch_size)
            t_next = schedule&#91;i + 1&#93;.expand(batch_size)
            r = t_next if self._use_mean_velocity else t_cur

            enable_grad = bool(with_grad) and (i == grad_step)
            with torch.set_grad_enabled(enable_grad):
                v = self.student.predict_velocity_with_r(
                    x,
                    t_cur,
                    r,
                    batch,
                    conditional=True,
                    cfg_uncond=self._cfg_uncond,
                    attn_kind=attn_kind,
                )
            # Euler step in absolute units: x ← x - ((t_cur - t_next) / N) * v.
            view = &#91;-1&#93; + &#91;1&#93; * (x.ndim - 1)
            dt = ((t_cur - t_next) / n_train).view(*view)
            x = x - dt * v

            if enable_grad:
                # We treat the rollout output (post-step) as a predicted
                # clean latent — AnyFlow&#x27;s last Euler step lands at t=0.
                last_pred_x0 = x

        if last_pred_x0 is None:
            # No gradient step taken (with_grad=False path).
            last_pred_x0 = x

        if hasattr(batch, &quot;dmd_latent_vis_dict&quot;):
            batch.dmd_latent_vis_dict&#91;&quot;generator_timestep&quot;&#93; = (schedule&#91;-1&#93;.detach().clone())
        return last_pred_x0</code></pre></details>
</div>
