---
title: streaming_long_tuning
---

<div class="python-reference">
<a id="fastvideo.train.methods.distribution_matching.streaming_long_tuning-classes"></a>
<a id="fastvideo.train.methods.distribution_matching.streaming_long_tuning-functions"></a>
<a id="fastvideo.train.methods.distribution_matching.streaming_long_tuning"></a>
<p>LongLive-style multi-stage self-forcing for streaming rollouts.</p>
<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.init_logger"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.init_logger</code></h2>
<pre class="doc-signature"><code>init_logger(name: str) -&gt; _FastvideoLogger</code></pre>
<p>The main purpose of this function is to ensure that loggers are
retrieved in such a way that we can be sure the root fastvideo logger has
already been configured.</p>
<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.pred_noise_to_pred_video"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.pred_noise_to_pred_video</code></h2>
<pre class="doc-signature"><code>pred_noise_to_pred_video(pred_noise: torch.Tensor, noise_input_latent: torch.Tensor, timestep: torch.Tensor, scheduler: Any) -&gt; torch.Tensor</code></pre>
<p>Convert predicted noise to clean latent.</p>
<p>pred_noise: the predicted noise with shape [B, C, H, W]
    where B is batch_size or batch_size * num_frames
noise_input_latent: the noisy latent with shape [B, C, H, W],
timestep: the timestep with shape [1] or [bs * num_frames] or [bs, num_frames]
scheduler: the scheduler</p>
<h3>Returns</h3>
<dl>
<dt><code>torch.Tensor</code></dt><dd><p>the predicted video with shape [B, C, H, W]</p></dd>
</dl>
<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.SelfForcingMethod"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.SelfForcingMethod</code></h2>
<pre class="doc-signature"><code>SelfForcingMethod(*, cfg: Any, role_models: dict&#91;str, ModelBase&#93;)</code></pre>
<p>Self-Forcing DMD2 (distribution matching) method.</p>
<p>Requires a causal student implementing <code>CausalModelBase</code>.</p>

<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase</code></h2>
<pre class="doc-signature"><code>CausalModelBase(*, trainable: bool = True, lora: LoraConfig | dict&#91;str, Any&#93; | None = None, attention_backend: AttentionBackendEnum | str | None = None)</code></pre>
<p>Extension for causal / streaming model plugins.</p>
<p>Cache state is internal to the model instance and keyed by
<em>cache_tag</em> (no role handle needed).</p>
<h3 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.clear_caches"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.clear_caches</code></h3>
<pre class="doc-signature"><code>clear_caches(*, cache_tag: str = &#x27;pos&#x27;) -&gt; None</code></pre>
<p>Clear internal caches before starting a new rollout.</p>
<h3 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.predict_noise_streaming"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.predict_noise_streaming</code></h3>
<pre class="doc-signature"><code>predict_noise_streaming(noisy_latents: torch.Tensor, timestep: torch.Tensor, batch: TrainingBatch, *, conditional: bool, cache_tag: str = &#x27;pos&#x27;, store_kv: bool = False, cur_start_frame: int = 0, 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 | None</code></pre>
<p>Streaming predict-noise that may update internal caches.</p>
<h3 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.predict_x0_streaming"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.predict_x0_streaming</code></h3>
<pre class="doc-signature"><code>predict_x0_streaming(noisy_latents: torch.Tensor, timestep: torch.Tensor, batch: TrainingBatch, *, conditional: bool, cache_tag: str = &#x27;pos&#x27;, store_kv: bool = False, cur_start_frame: int = 0, 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 | None</code></pre>
<p>Predict x0 streaming via
<code>predict_noise_streaming</code> + conversion.</p>
<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.get_optional_float"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.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.streaming_long_tuning.get_optional_int"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.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.streaming_long_tuning.DistillStage"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.DistillStage</code></h2>
<pre class="doc-signature"><code>DistillStage(name: str, start_step: int, end_step: int | None, num_latent_t: int, streaming_training: bool, streaming_chunk_size: int | None = None, streaming_max_length: int | None = None, streaming_min_new_frame: int | None = None, streaming_fixed_overlap_latents: int | None = None)</code></pre>
<p>Resolved multi-stage distillation stage.</p>

<details><summary>Source</summary><pre><code>@dataclass(frozen=True, slots=True)
class DistillStage:
    &quot;&quot;&quot;Resolved multi-stage distillation stage.&quot;&quot;&quot;

    name: str
    start_step: int
    end_step: int | None
    num_latent_t: int
    streaming_training: bool
    streaming_chunk_size: int | None = None
    streaming_max_length: int | None = None
    streaming_min_new_frame: int | None = None
    streaming_fixed_overlap_latents: int | None = None</code></pre></details>
<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.parse_multi_phased_distill_schedule"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.parse_multi_phased_distill_schedule</code></h2>
<pre class="doc-signature"><code>parse_multi_phased_distill_schedule(raw: Any, *, default_num_latent_t: int, default_streaming_chunk_size: int | None = None, default_streaming_max_length: int | None = None) -&gt; list&#91;DistillStage&#93;</code></pre>
<p>Parse a compact/list multi-stage schedule.</p>
<p>Preferred YAML form:</p>
<p><code>[&#123;stage: self_forcing, end_step: 700, num_latent_t: 21&#125;, ...]</code></p>
<p>Compact string form accepts <code>"700:21,3000:240"</code> and treats the
first stage as non-streaming self-forcing and later stages as streaming.</p>
<details><summary>Source</summary><pre><code>def parse_multi_phased_distill_schedule(
    raw: Any,
    *,
    default_num_latent_t: int,
    default_streaming_chunk_size: int | None = None,
    default_streaming_max_length: int | None = None,
) -&gt; list&#91;DistillStage&#93;:
    &quot;&quot;&quot;Parse a compact/list multi-stage schedule.

    Preferred YAML form:

    ``&#91;&#123;stage: self_forcing, end_step: 700, num_latent_t: 21&#125;, ...&#93;``

    Compact string form accepts ``&quot;700:21,3000:240&quot;`` and treats the
    first stage as non-streaming self-forcing and later stages as streaming.
    &quot;&quot;&quot;

    if raw is None or raw == &quot;&quot;:
        max_length = default_streaming_max_length or default_num_latent_t
        return &#91;
            DistillStage(
                name=&quot;streaming_long&quot;,
                start_step=0,
                end_step=None,
                num_latent_t=int(max_length),
                streaming_training=True,
                streaming_chunk_size=default_streaming_chunk_size,
                streaming_max_length=int(max_length),
            )
        &#93;

    stages: list&#91;DistillStage&#93; = &#91;&#93;
    previous_end = 0

    if isinstance(raw, str):
        parts = &#91;part.strip() for part in raw.split(&quot;,&quot;) if part.strip()&#93;
        for idx, part in enumerate(parts):
            fields = &#91;field.strip() for field in part.split(&quot;:&quot;)&#93;
            end_step: int | None
            if len(fields) == 2:
                start_step = previous_end
                end_step = _as_int(
                    fields&#91;0&#93;,
                    where=&quot;multi_phased_distill_schedule.end_step&quot;,
                )
                num_latent_t = _as_int(
                    fields&#91;1&#93;,
                    where=&quot;multi_phased_distill_schedule.num_latent_t&quot;,
                )
            elif len(fields) == 3:
                start_step = _as_int(
                    fields&#91;0&#93;,
                    where=&quot;multi_phased_distill_schedule.start_step&quot;,
                )
                end_step = _as_int(
                    fields&#91;1&#93;,
                    where=&quot;multi_phased_distill_schedule.end_step&quot;,
                )
                num_latent_t = _as_int(
                    fields&#91;2&#93;,
                    where=&quot;multi_phased_distill_schedule.num_latent_t&quot;,
                )
            else:
                raise ValueError(&quot;multi_phased_distill_schedule string entries must be &quot;
                                 &quot;&#x27;end_step:num_latent_t&#x27; or &#x27;start:end:num_latent_t&#x27;, &quot;
                                 f&quot;got &#123;part!r&#125;&quot;)
            streaming = idx &gt; 0
            stages.append(
                DistillStage(
                    name=&quot;streaming_long&quot; if streaming else &quot;self_forcing&quot;,
                    start_step=start_step,
                    end_step=end_step,
                    num_latent_t=num_latent_t,
                    streaming_training=streaming,
                    streaming_chunk_size=(default_streaming_chunk_size if streaming else None),
                    streaming_max_length=num_latent_t if streaming else None,
                ))
            previous_end = end_step
    elif isinstance(raw, list | tuple):
        for idx, stage_raw in enumerate(raw):
            if not isinstance(stage_raw, dict):
                raise ValueError(&quot;multi_phased_distill_schedule list entries must be dicts&quot;)
            name = str(stage_raw.get(&quot;stage&quot;, &quot;&quot;) or stage_raw.get(&quot;name&quot;, &quot;&quot;)).strip()
            streaming_raw = stage_raw.get(&quot;streaming_training&quot;, None)
            if streaming_raw is None:
                streaming = name in &#123;&quot;streaming_long&quot;, &quot;long&quot;, &quot;streaming&quot;&#125;
            else:
                streaming = _as_bool(
                    streaming_raw,
                    where=f&quot;multi_phased_distill_schedule&#91;&#123;idx&#125;&#93;.streaming_training&quot;,
                )
            if not name:
                name = &quot;streaming_long&quot; if streaming else &quot;self_forcing&quot;

            start_step = _as_int(
                stage_raw.get(&quot;start_step&quot;, previous_end),
                where=f&quot;multi_phased_distill_schedule&#91;&#123;idx&#125;&#93;.start_step&quot;,
            )
            end_raw = stage_raw.get(&quot;end_step&quot;, None)
            end_step = (None if end_raw is None else _as_int(
                end_raw,
                where=f&quot;multi_phased_distill_schedule&#91;&#123;idx&#125;&#93;.end_step&quot;,
            ))
            num_latent_t = _as_int(
                stage_raw.get(
                    &quot;num_latent_t&quot;,
                    stage_raw.get(
                        &quot;streaming_max_length&quot;,
                        stage_raw.get(&quot;max_length&quot;, default_num_latent_t),
                    ),
                ),
                where=f&quot;multi_phased_distill_schedule&#91;&#123;idx&#125;&#93;.num_latent_t&quot;,
            )
            streaming_max = stage_raw.get(&quot;streaming_max_length&quot;, None)
            chunk_size = stage_raw.get(&quot;streaming_chunk_size&quot;, None)
            min_new = stage_raw.get(&quot;streaming_min_new_frame&quot;, None)
            fixed_overlap = stage_raw.get(&quot;streaming_fixed_overlap_latents&quot;, None)
            stages.append(
                DistillStage(
                    name=name,
                    start_step=start_step,
                    end_step=end_step,
                    num_latent_t=num_latent_t,
                    streaming_training=streaming,
                    streaming_chunk_size=(None if chunk_size is None else _as_int(
                        chunk_size,
                        where=(&quot;multi_phased_distill_schedule&quot;
                               f&quot;&#91;&#123;idx&#125;&#93;.streaming_chunk_size&quot;),
                    )),
                    streaming_max_length=(None if streaming_max is None else _as_int(
                        streaming_max,
                        where=(&quot;multi_phased_distill_schedule&quot;
                               f&quot;&#91;&#123;idx&#125;&#93;.streaming_max_length&quot;),
                    )),
                    streaming_min_new_frame=(None if min_new is None else _as_int(
                        min_new,
                        where=(&quot;multi_phased_distill_schedule&quot;
                               f&quot;&#91;&#123;idx&#125;&#93;.streaming_min_new_frame&quot;),
                    )),
                    streaming_fixed_overlap_latents=(None if fixed_overlap is None else _as_int(
                        fixed_overlap,
                        where=(&quot;multi_phased_distill_schedule&quot;
                               f&quot;&#91;&#123;idx&#125;&#93;.streaming_fixed_overlap_latents&quot;),
                    )),
                ))
            if end_step is not None:
                previous_end = end_step
    else:
        raise ValueError(&quot;multi_phased_distill_schedule must be a list, string, or empty&quot;)

    if not stages:
        raise ValueError(&quot;multi_phased_distill_schedule produced no stages&quot;)

    previous_end = 0
    for stage in stages:
        if stage.start_step &lt; previous_end:
            raise ValueError(&quot;Distillation stages must be ordered and non-overlapping&quot;)
        if stage.end_step is not None and stage.end_step &lt;= stage.start_step:
            raise ValueError(&quot;Distillation stage end_step must be &gt; start_step&quot;)
        if stage.num_latent_t &lt;= 0:
            raise ValueError(&quot;Distillation stage num_latent_t must be positive&quot;)
        if stage.streaming_training:
            max_length = stage.streaming_max_length or stage.num_latent_t
            chunk_size = stage.streaming_chunk_size or default_streaming_chunk_size
            if max_length &lt;= 0:
                raise ValueError(&quot;streaming_max_length must be positive&quot;)
            if chunk_size is None or chunk_size &lt;= 0:
                raise ValueError(&quot;streaming_chunk_size must be positive&quot;)
            if stage.streaming_fixed_overlap_latents is not None:
                if stage.streaming_fixed_overlap_latents &lt; 0:
                    raise ValueError(&quot;streaming_fixed_overlap_latents must be non-negative&quot;)
                if stage.streaming_fixed_overlap_latents &gt;= chunk_size:
                    raise ValueError(&quot;streaming_fixed_overlap_latents must be smaller than &quot;
                                     &quot;streaming_chunk_size&quot;)
        if stage.end_step is not None:
            previous_end = stage.end_step
    return stages</code></pre></details>
<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.select_distill_stage"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.select_distill_stage</code></h2>
<pre class="doc-signature"><code>select_distill_stage(stages: list&#91;DistillStage&#93;, iteration: int) -&gt; DistillStage</code></pre>
<p>Select the active stage for <code>iteration</code>.</p>
<details><summary>Source</summary><pre><code>def select_distill_stage(
    stages: list&#91;DistillStage&#93;,
    iteration: int,
) -&gt; DistillStage:
    &quot;&quot;&quot;Select the active stage for ``iteration``.&quot;&quot;&quot;

    step = int(iteration)
    for stage in stages:
        if stage.end_step is None:
            if step &gt;= stage.start_step:
                return stage
        elif stage.start_step &lt;= step &lt; stage.end_step:
            return stage
    return stages&#91;-1&#93;</code></pre></details>
<h2 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.StreamingLongTuningMethod"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.StreamingLongTuningMethod</code></h2>
<pre class="doc-signature"><code>StreamingLongTuningMethod(*, cfg: Any, role_models: dict&#91;str, Any&#93;)</code></pre>
<p>Two-stage MatrixGame/LongLive-style self-forcing method.</p>
<p>Stage 1 uses the existing full self-forcing rollout over a short latent
horizon. Stage 2 uses a persistent streaming sequence and trains on chunks
generated with the causal student cache.</p>

<details><summary>Source</summary><pre><code>class StreamingLongTuningMethod(SelfForcingMethod):
    &quot;&quot;&quot;Two-stage MatrixGame/LongLive-style self-forcing method.

    Stage 1 uses the existing full self-forcing rollout over a short latent
    horizon. Stage 2 uses a persistent streaming sequence and trains on chunks
    generated with the causal student cache.
    &quot;&quot;&quot;

    def __init__(
        self,
        *,
        cfg: Any,
        role_models: dict&#91;str, Any&#93;,
    ) -&gt; None:
        pipeline_config = getattr(cfg.training, &quot;pipeline_config&quot;, None)
        vae_config = getattr(pipeline_config, &quot;vae_config&quot;, None)
        if vae_config is not None:
            vae_config.load_encoder = True
            vae_config.load_decoder = True

        super().__init__(cfg=cfg, role_models=role_models)
        if not isinstance(self.student, CausalModelBase):
            raise ValueError(&quot;StreamingLongTuningMethod requires a causal student&quot;)

        mcfg = self.method_config
        default_chunk = get_optional_int(
            mcfg,
            &quot;streaming_chunk_size&quot;,
            where=&quot;method.streaming_chunk_size&quot;,
        )
        default_max = get_optional_int(
            mcfg,
            &quot;streaming_max_length&quot;,
            where=&quot;method.streaming_max_length&quot;,
        )
        default_num_latent_t = int(self.training_config.data.num_latent_t)
        self._distill_stages = parse_multi_phased_distill_schedule(
            mcfg.get(&quot;multi_phased_distill_schedule&quot;, None),
            default_num_latent_t=default_num_latent_t,
            default_streaming_chunk_size=default_chunk,
            default_streaming_max_length=default_max,
        )
        self._streaming_state: _StreamingState | None = None
        self._streaming_anchor_inject_k = int(
            get_optional_int(
                mcfg,
                &quot;streaming_anchor_inject_k&quot;,
                where=&quot;method.streaming_anchor_inject_k&quot;,
            ) or 1)
        if self._streaming_anchor_inject_k &lt; 1:
            raise ValueError(&quot;method.streaming_anchor_inject_k must be &gt;= 1&quot;)
        reencode_anchor_raw = mcfg.get(&quot;streaming_reencode_overlap_anchor&quot;, True)
        if reencode_anchor_raw is None:
            reencode_anchor_raw = True
        self._streaming_reencode_overlap_anchor = _as_bool(
            reencode_anchor_raw,
            where=&quot;method.streaming_reencode_overlap_anchor&quot;,
        )
        require_full_blocks_raw = mcfg.get(&quot;streaming_require_full_blocks&quot;, False)
        if require_full_blocks_raw is None:
            require_full_blocks_raw = False
        self._streaming_require_full_blocks = _as_bool(
            require_full_blocks_raw,
            where=&quot;method.streaming_require_full_blocks&quot;,
        )
        real_score_cfg_raw = mcfg.get(&quot;real_score_cfg&quot;, True)
        if real_score_cfg_raw is None:
            real_score_cfg_raw = True
        self._real_score_cfg = _as_bool(
            real_score_cfg_raw,
            where=&quot;method.real_score_cfg&quot;,
        )
        self._score_min_timestep = self._score_timestep_from_ratio(&quot;min_timestep_ratio&quot;)
        self._score_max_timestep = self._score_timestep_from_ratio(&quot;max_timestep_ratio&quot;)

        max_stage_latents = max(s.num_latent_t for s in self._distill_stages)
        if max_stage_latents &gt; default_num_latent_t:
            raise ValueError(&quot;training.data.num_latent_t must be at least the largest &quot;
                             &quot;multi-stage horizon so prepared conditioning is not trimmed &quot;
                             &quot;before streaming: &quot;
                             f&quot;num_latent_t=&#123;default_num_latent_t&#125;, &quot;
                             f&quot;max_stage_latents=&#123;max_stage_latents&#125;&quot;)

    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;:
        stage = select_distill_stage(self._distill_stages, iteration)
        if not stage.streaming_training:
            self._streaming_state = None
            return self._single_train_step_short(batch, iteration, stage)
        return self._single_train_step_streaming(batch, iteration, stage)

    def _single_train_step_short(
        self,
        batch: dict&#91;str, Any&#93;,
        iteration: int,
        stage: DistillStage,
    ) -&gt; tuple&#91;
            dict&#91;str, torch.Tensor&#93;,
            dict&#91;str, Any&#93;,
            dict&#91;str, LogScalar&#93;,
    &#93;:
        batch = self._with_padded_first_frame_latent(
            batch,
            target_latent_frames=int(self.training_config.data.num_latent_t),
        )
        training_batch = self.student.prepare_batch(
            batch,
            generator=self.cuda_generator,
            latents_source=&quot;zeros&quot;,
        )
        self._truncate_batch_latents(training_batch, stage.num_latent_t)
        return self._losses_for_batch(
            training_batch,
            iteration,
            stage=stage,
            chunk_mask=None,
        )

    def _single_train_step_streaming(
        self,
        batch: dict&#91;str, Any&#93;,
        iteration: int,
        stage: DistillStage,
    ) -&gt; tuple&#91;
            dict&#91;str, torch.Tensor&#93;,
            dict&#91;str, Any&#93;,
            dict&#91;str, LogScalar&#93;,
    &#93;:
        state = self._ensure_streaming_state(batch, stage)
        update_student = self._should_update_student(iteration)

        generator_loss = torch.zeros(
            (),
            device=state.batch.latents.device,
            dtype=state.batch.latents.dtype,
        )
        student_ctx = None
        generator_outputs: dict&#91;str, Any&#93; = &#123;&#125;
        pred_x0, chunk_mask, chunk_info = self._generate_streaming_chunk(
            state,
            with_grad=update_student,
        )
        if update_student:
            student_ctx = (
                state.batch.timesteps,
                state.batch.attn_metadata_vsa,
            )
            generator_loss = self._dmd_loss_masked(
                pred_x0,
                state.batch,
                chunk_mask=chunk_mask,
            )
            generator_outputs&#91;&quot;dmd_latent_vis_dict&quot;&#93; = &#123;
                &quot;generator_pred_video&quot;: pred_x0.detach(),
                &quot;streaming_chunk_mask&quot;: chunk_mask.detach(),
            &#125;
            self.student.backward(
                generator_loss,
                student_ctx,
                grad_accum_rounds=self._gradient_accumulation_steps(),
            )
            generator_loss = generator_loss.detach()
            student_ctx = None

        fake_score_loss, critic_ctx, critic_outputs = (self._critic_flow_matching_loss_for_x0(
            pred_x0.detach(),
            state.batch,
            chunk_mask=chunk_mask,
        ))
        self._log_train_chunk(
            iteration=iteration,
            stage=stage,
            update_student=update_student,
            chunk_info=chunk_info,
        )

        total_loss = generator_loss + fake_score_loss
        outputs: dict&#91;str, Any&#93; = dict(generator_outputs)
        outputs.update(critic_outputs)
        outputs&#91;&quot;_fv_backward&quot;&#93; = &#123;
            &quot;update_student&quot;: False,
            &quot;student_ctx&quot;: student_ctx,
            &quot;critic_ctx&quot;: critic_ctx,
        &#125;
        metrics: dict&#91;str, LogScalar&#93; = &#123;
            &quot;update_student&quot;: float(update_student),
            &quot;distill_stage_index&quot;: float(self._distill_stages.index(stage)),
            &quot;streaming_current_length&quot;: float(state.current_length),
            &quot;streaming_max_length&quot;: float(self._stage_max_length(stage)),
        &#125;
        return (
            &#123;
                &quot;total_loss&quot;: total_loss,
                &quot;generator_loss&quot;: generator_loss,
                &quot;fake_score_loss&quot;: fake_score_loss,
            &#125;,
            outputs,
            metrics,
        )

    def _losses_for_batch(
        self,
        training_batch: Any,
        iteration: int,
        *,
        stage: DistillStage,
        chunk_mask: torch.Tensor | None,
    ) -&gt; tuple&#91;
            dict&#91;str, torch.Tensor&#93;,
            dict&#91;str, Any&#93;,
            dict&#91;str, LogScalar&#93;,
    &#93;:
        update_student = self._should_update_student(iteration)

        generator_loss = torch.zeros(
            (),
            device=training_batch.latents.device,
            dtype=training_batch.latents.dtype,
        )
        student_ctx = None
        if update_student:
            generator_pred_x0 = self._student_rollout(
                training_batch,
                with_grad=True,
            )
            student_ctx = (
                training_batch.timesteps,
                training_batch.attn_metadata_vsa,
            )
            generator_loss = self._dmd_loss_masked(
                generator_pred_x0,
                training_batch,
                chunk_mask=chunk_mask,
            )
            self.student.backward(
                generator_loss,
                student_ctx,
                grad_accum_rounds=self._gradient_accumulation_steps(),
            )
            generator_loss = generator_loss.detach()
            student_ctx = None

        with torch.no_grad():
            generator_pred_x0 = self._student_rollout(
                training_batch,
                with_grad=False,
            )

        fake_score_loss, critic_ctx, critic_outputs = (self._critic_flow_matching_loss_for_x0(
            generator_pred_x0,
            training_batch,
            chunk_mask=chunk_mask,
        ))

        total_loss = generator_loss + fake_score_loss
        outputs: dict&#91;str, Any&#93; = dict(critic_outputs)
        outputs&#91;&quot;_fv_backward&quot;&#93; = &#123;
            &quot;update_student&quot;: False,
            &quot;student_ctx&quot;: student_ctx,
            &quot;critic_ctx&quot;: critic_ctx,
        &#125;
        metrics: dict&#91;str, LogScalar&#93; = &#123;
            &quot;update_student&quot;: float(update_student),
            &quot;distill_stage_index&quot;: float(self._distill_stages.index(stage)),
            &quot;active_num_latent_t&quot;: float(stage.num_latent_t),
        &#125;
        self._log_train_chunk(
            iteration=iteration,
            stage=stage,
            update_student=update_student,
            chunk_info=_StreamingChunkInfo(
                chunk_start=0,
                chunk_end=int(stage.num_latent_t) - 1,
                train_start=0,
                train_end=int(stage.num_latent_t) - 1,
                new_frames=int(stage.num_latent_t),
                overlap=0,
                current_length=int(stage.num_latent_t),
                max_length=int(stage.num_latent_t),
            ),
        )
        return (
            &#123;
                &quot;total_loss&quot;: total_loss,
                &quot;generator_loss&quot;: generator_loss,
                &quot;fake_score_loss&quot;: fake_score_loss,
            &#125;,
            outputs,
            metrics,
        )

    def _gradient_accumulation_steps(self) -&gt; int:
        loop_config = getattr(self.training_config, &quot;loop&quot;, None)
        return max(1, int(getattr(loop_config, &quot;gradient_accumulation_steps&quot;, 1) or 1))

    def _student_rollout_streaming(
        self,
        batch: Any,
        *,
        with_grad: bool,
    ) -&gt; torch.Tensor:
        # Short-stage rollout should respect the truncated batch length above.
        return super()._student_rollout_streaming(batch, with_grad=with_grad)

    def _ensure_streaming_state(
        self,
        raw_batch: dict&#91;str, Any&#93;,
        stage: DistillStage,
    ) -&gt; _StreamingState:
        state = self._streaming_state
        if (state is None or state.stage != stage or not self._can_generate_more(state)):
            if state is not None:
                self.student.clear_caches(cache_tag=state.cache_tag)
            raw_batch = self._with_padded_first_frame_latent(
                raw_batch,
                target_latent_frames=int(self.training_config.data.num_latent_t),
            )
            training_batch = self.student.prepare_batch(
                raw_batch,
                generator=self.cuda_generator,
                latents_source=&quot;zeros&quot;,
            )
            self._truncate_batch_latents(
                training_batch,
                self._stage_max_length(stage),
            )
            cache_tag = f&quot;streaming_long_&#123;id(training_batch)&#125;&quot;
            self.student.clear_caches(cache_tag=cache_tag)
            state = _StreamingState(
                stage=stage,
                batch=training_batch,
                current_length=0,
                previous_latents=None,
                cache_tag=cache_tag,
            )
            self._streaming_state = state
        return state

    def _with_padded_first_frame_latent(
        self,
        raw_batch: dict&#91;str, Any&#93;,
        *,
        target_latent_frames: int,
    ) -&gt; dict&#91;str, Any&#93;:
        first_frame_latent = raw_batch.get(&quot;first_frame_latent&quot;)
        if first_frame_latent is None:
            return raw_batch
        if first_frame_latent.ndim != 5:
            raise ValueError(&quot;first_frame_latent must have shape &#91;B, C, T, H, W&#93;, &quot;
                             f&quot;got &#123;tuple(first_frame_latent.shape)&#125;&quot;)
        target_latent_frames = int(target_latent_frames)
        if first_frame_latent.shape&#91;2&#93; &gt;= target_latent_frames:
            return raw_batch

        # Long tuning passes first-frame-only MatrixGame I2V condition as a full-length latent tensor.
        pad_frames = target_latent_frames - int(first_frame_latent.shape&#91;2&#93;)
        pad = torch.zeros(
            first_frame_latent.shape&#91;0&#93;,
            first_frame_latent.shape&#91;1&#93;,
            pad_frames,
            first_frame_latent.shape&#91;3&#93;,
            first_frame_latent.shape&#91;4&#93;,
            device=first_frame_latent.device,
            dtype=first_frame_latent.dtype,
        )
        batch = dict(raw_batch)
        batch&#91;&quot;first_frame_latent&quot;&#93; = torch.cat(&#91;first_frame_latent, pad&#93;, dim=2)
        return batch

    def _can_generate_more(self, state: _StreamingState) -&gt; bool:
        max_length = self._stage_max_length(state.stage)
        return state.current_length &lt; max_length

    def _generate_streaming_chunk(
        self,
        state: _StreamingState,
        *,
        with_grad: bool,
    ) -&gt; tuple&#91;torch.Tensor, torch.Tensor, _StreamingChunkInfo&#93;:
        assert isinstance(self.student, CausalModelBase)

        batch = state.batch
        latents = batch.latents
        if latents is None:
            raise RuntimeError(&quot;TrainingBatch.latents is required&quot;)

        device = latents.device
        dtype = latents.dtype
        batch_size = int(latents.shape&#91;0&#93;)
        max_length = self._stage_max_length(state.stage)
        chunk_size = self._stage_chunk_size(state.stage)
        current = int(state.current_length)
        remaining = max_length - current
        if remaining &lt;= 0:
            raise RuntimeError(&quot;Streaming sequence has no remaining frames&quot;)

        if state.previous_latents is None:
            new_frames = min(chunk_size, remaining)
            overlap = 0
        else:
            new_frames = self._select_new_frame_count(
                state.stage,
                remaining=remaining,
                device=device,
            )
            overlap = min(
                chunk_size - new_frames,
                int(state.previous_latents.shape&#91;1&#93;),
            )

        if new_frames &lt;= 0:
            raise RuntimeError(&quot;Streaming new frame count must be positive&quot;)
        if current + new_frames &gt; max_length:
            raise RuntimeError(&quot;Streaming chunk would exceed max length&quot;)

        rollout_block_size = int(self._chunk_size)
        if rollout_block_size &lt;= 0:
            raise ValueError(&quot;method.chunk_size must be positive&quot;)
        if self._streaming_require_full_blocks and new_frames % rollout_block_size != 0:
            raise ValueError(&quot;Streaming new-frame count must be divisible by &quot;
                             &quot;method.chunk_size: &quot;
                             f&quot;new_frames=&#123;new_frames&#125;, &quot;
                             f&quot;chunk_size=&#123;rollout_block_size&#125;&quot;)

        denoising_steps = self._get_denoising_step_list(device)
        num_steps = int(denoising_steps.numel())
        noise_full = torch.randn(
            (
                batch_size,
                new_frames,
                int(latents.shape&#91;2&#93;),
                int(latents.shape&#91;3&#93;),
                int(latents.shape&#91;4&#93;),
            ),
            device=device,
            dtype=dtype,
            generator=self.cuda_generator,
        )

        block_lengths: list&#91;int&#93; = &#91;&#93;
        remaining_block_frames = int(new_frames)
        while remaining_block_frames &gt; 0:
            current_block = min(rollout_block_size, remaining_block_frames)
            block_lengths.append(current_block)
            remaining_block_frames -= current_block

        exit_indices = self._sample_exit_indices(
            num_blocks=len(block_lengths),
            num_steps=num_steps,
            device=device,
        )
        block0_exit_idx = int(exit_indices&#91;0&#93;)
        denoised_from, denoised_to = self._get_self_forcing_timestep_bounds(
            denoising_steps,
            block0_exit_idx,
        )

        denoised_blocks: list&#91;torch.Tensor&#93; = &#91;&#93;
        local_start = 0
        for block_idx, block_frames in enumerate(block_lengths):
            block_start = current + local_start
            noisy_block = noise_full&#91;:, local_start:local_start + block_frames&#93;
            exit_idx = int(exit_indices&#91;block_idx&#93;)
            pred_x0_block: torch.Tensor | None = None

            for step_idx, current_timestep in enumerate(denoising_steps):
                timestep_block = current_timestep * torch.ones(
                    (batch_size, block_frames),
                    device=device,
                    dtype=torch.float32,
                )
                exit_flag = step_idx == exit_idx
                enable_grad = (bool(with_grad) and bool(self._enable_gradient_in_rollout) and torch.is_grad_enabled())

                if not exit_flag:
                    with torch.no_grad():
                        pred_noise = self.student.predict_noise_streaming(
                            noisy_block,
                            timestep_block,
                            batch,
                            conditional=True,
                            cache_tag=state.cache_tag,
                            store_kv=False,
                            cur_start_frame=block_start,
                            cfg_uncond=self._cfg_uncond,
                            attn_kind=&quot;vsa&quot;,
                        )
                        if pred_noise is None:
                            raise RuntimeError(&quot;predict_noise_streaming returned None&quot;)
                        pred_x0_block = pred_noise_to_pred_video(
                            pred_noise=pred_noise.flatten(0, 1),
                            noise_input_latent=noisy_block.flatten(0, 1),
                            timestep=timestep_block,
                            scheduler=self._sf_scheduler,
                        ).unflatten(0, pred_noise.shape&#91;:2&#93;)

                    if step_idx + 1 &gt;= num_steps:
                        break
                    next_timestep = denoising_steps&#91;step_idx + 1&#93;
                    if self._student_sample_type == &quot;sde&quot;:
                        noisy_block = self._sf_add_noise(
                            pred_x0_block,
                            torch.randn(
                                pred_x0_block.shape,
                                device=device,
                                dtype=pred_x0_block.dtype,
                                generator=self.cuda_generator,
                            ),
                            next_timestep * torch.ones(
                                (batch_size, block_frames),
                                device=device,
                                dtype=torch.float32,
                            ),
                        )
                    else:
                        next_timestep_block = next_timestep * torch.ones(
                            (batch_size, block_frames),
                            device=device,
                            dtype=torch.float32,
                        )
                        sigma_cur = self._timestep_to_sigma(timestep_block).view(
                            batch_size,
                            block_frames,
                            1,
                            1,
                            1,
                        )
                        sigma_next = self._timestep_to_sigma(next_timestep_block).view(
                            batch_size,
                            block_frames,
                            1,
                            1,
                            1,
                        )
                        eps = (noisy_block - (1 - sigma_cur) * pred_x0_block) / sigma_cur.clamp_min(1e-8)
                        noisy_block = (1 - sigma_next) * pred_x0_block + sigma_next * eps
                    continue

                with torch.set_grad_enabled(enable_grad):
                    pred_noise = self.student.predict_noise_streaming(
                        noisy_block,
                        timestep_block,
                        batch,
                        conditional=True,
                        cache_tag=state.cache_tag,
                        store_kv=False,
                        cur_start_frame=block_start,
                        cfg_uncond=self._cfg_uncond,
                        attn_kind=&quot;vsa&quot;,
                    )
                    if pred_noise is None:
                        raise RuntimeError(&quot;predict_noise_streaming returned None&quot;)
                    pred_x0_block = pred_noise_to_pred_video(
                        pred_noise=pred_noise.flatten(0, 1),
                        noise_input_latent=noisy_block.flatten(0, 1),
                        timestep=timestep_block,
                        scheduler=self._sf_scheduler,
                    ).unflatten(0, pred_noise.shape&#91;:2&#93;)
                break

            if pred_x0_block is None:
                raise RuntimeError(&quot;Streaming rollout produced no block&quot;)
            denoised_blocks.append(pred_x0_block)

            with torch.no_grad():
                context_timestep = torch.ones(
                    (batch_size, block_frames),
                    device=device,
                    dtype=torch.float32,
                ) * float(self._context_noise)
                context_latents = self._sf_add_noise(
                    pred_x0_block.detach(),
                    torch.randn(
                        pred_x0_block.shape,
                        device=device,
                        dtype=pred_x0_block.dtype,
                        generator=self.cuda_generator,
                    ),
                    context_timestep,
                )

                _ = self.student.predict_noise_streaming(
                    context_latents,
                    context_timestep,
                    batch,
                    conditional=True,
                    cache_tag=state.cache_tag,
                    store_kv=True,
                    cur_start_frame=block_start,
                    cfg_uncond=self._cfg_uncond,
                    attn_kind=&quot;vsa&quot;,
                )

            local_start += block_frames

        if not denoised_blocks:
            raise RuntimeError(&quot;Streaming rollout produced no chunk&quot;)
        pred_x0_chunk = torch.cat(denoised_blocks, dim=1)
        batch.denoised_timestep_from = denoised_from
        batch.denoised_timestep_to = denoised_to

        if overlap &gt; 0 and state.previous_latents is not None:
            overlap_latents = state.previous_latents&#91;:, -overlap:&#93;.detach()
            if self._streaming_reencode_overlap_anchor:
                anchor_source = torch.cat(
                    &#91;state.previous_latents.detach(), pred_x0_chunk&#93;,
                    dim=1,
                )
                full_chunk = self._process_first_frame_anchor(
                    anchor_source,
                    target_latents=chunk_size,
                )
            else:
                full_chunk = torch.cat(&#91;overlap_latents, pred_x0_chunk&#93;, dim=1)
            chunk_mask = torch.zeros(
                (batch_size, chunk_size, 1, 1, 1),
                device=device,
                dtype=torch.bool,
            )
            chunk_mask&#91;:, overlap:&#93; = True
        else:
            full_chunk = pred_x0_chunk
            chunk_mask = torch.ones(
                (batch_size, new_frames, 1, 1, 1),
                device=device,
                dtype=torch.bool,
            )

        state.current_length = current + new_frames
        temporal_compression_ratio = int(self.training_config.pipeline_config.vae_config.arch_config.
                                         temporal_compression_ratio  # type: ignore&#91;union-attr&#93;
                                         )
        chunk_info = _StreamingChunkInfo(
            chunk_start=current - overlap,
            chunk_end=current + new_frames - 1,
            train_start=current,
            train_end=current + new_frames - 1,
            new_frames=new_frames,
            overlap=overlap,
            current_length=state.current_length,
            max_length=max_length,
        )
        score_frame_start = max(0, current - overlap)
        batch.matrixgame_streaming_chunk_info = &#123;
            &quot;frame_start&quot;: score_frame_start,
            &quot;frame_end&quot;: score_frame_start + int(full_chunk.shape&#91;1&#93;),
            &quot;new_frame_start&quot;: int(overlap),
            &quot;new_frames&quot;: int(new_frames),
            &quot;gradient_mask&quot;: chunk_mask,
            &quot;condition_image_latents&quot;: None,
            &quot;condition_image_embeds&quot;: None,
            &quot;generator_chunk_start&quot;: int(current),
            &quot;generator_chunk_end&quot;: int(current + new_frames),
            &quot;generator_action_frame_end&quot;: int((current + new_frames - 1) * temporal_compression_ratio + 1),
            &quot;window_label&quot;: f&quot;window_&#123;score_frame_start&#125;_&#123;score_frame_start + int(full_chunk.shape&#91;1&#93;)&#125;&quot;,
        &#125;
        state.previous_latents = full_chunk.detach()&#91;:, -chunk_size:&#93;

        if not dist.is_initialized() or dist.get_rank() == 0:
            logger.info(
                &quot;&#91;StreamingLong&#93; stage=%s start=%s new=%s overlap=%s &quot;
                &quot;blocks=%s current_length=%s/%s&quot;,
                state.stage.name,
                current,
                new_frames,
                overlap,
                block_lengths,
                state.current_length,
                max_length,
            )

        return full_chunk, chunk_mask, chunk_info

    def _get_self_forcing_timestep_bounds(
        self,
        step_list: torch.Tensor,
        exit_idx: int,
    ) -&gt; tuple&#91;int | None, int | None&#93;:
        scheduler_timesteps = self._sf_scheduler.timesteps.to(
            device=step_list.device,
            dtype=torch.float32,
        )
        exit_idx = min(int(exit_idx), int(step_list.numel()) - 1)
        denoising_timestep = step_list&#91;exit_idx&#93;.to(dtype=scheduler_timesteps.dtype)
        denoised_timestep_from = int(self.student.num_train_timesteps) - int(
            torch.argmin(
                (scheduler_timesteps - denoising_timestep).abs(),
                dim=0,
            ).item())
        if exit_idx == int(step_list.numel()) - 1:
            return denoised_timestep_from, 0

        next_denoising_timestep = step_list&#91;exit_idx + 1&#93;.to(dtype=scheduler_timesteps.dtype)
        denoised_timestep_to = int(self.student.num_train_timesteps) - int(
            torch.argmin(
                (scheduler_timesteps - next_denoising_timestep).abs(),
                dim=0,
            ).item())
        return denoised_timestep_from, denoised_timestep_to

    def _process_first_frame_anchor(
        self,
        frames: torch.Tensor,
        *,
        target_latents: int,
    ) -&gt; torch.Tensor:
        if frames.shape&#91;1&#93; &lt;= 1:
            return frames

        process_latents = min(int(target_latents), int(frames.shape&#91;1&#93;))
        if process_latents &lt;= 1:
            return frames&#91;:, -process_latents:&#93;

        vae = getattr(self.student, &quot;vae&quot;, None)
        if vae is None:
            raise RuntimeError(&quot;Streaming first-frame anchor requires student.vae&quot;)
        self._require_vae_encoder(vae)

        inject_k = min(int(self._streaming_anchor_inject_k), process_latents)
        if inject_k == 1:
            with torch.no_grad():
                latents_to_decode = frames&#91;:, :-(process_latents - 1), ...&#93;
                decode_latents = self._denormalize_wan_latents(
                    latents_to_decode,
                    vae=vae,
                )
                pixels = vae.decode(decode_latents)
                last_frame_pixel = pixels&#91;:, :, -1:, :, :&#93;.float()
                image_latent = self._encode_wan_latent(
                    vae,
                    last_frame_pixel,
                    like=frames,
                )

            remaining = frames&#91;:, -(process_latents - 1):, ...&#93;
            return torch.cat(&#91;image_latent, remaining&#93;, dim=1)

        out_slice = frames&#91;:, -process_latents:, ...&#93;
        with torch.no_grad():
            first_k = out_slice&#91;:, :inject_k, ...&#93;
            decode_latents = self._denormalize_wan_latents(
                first_k,
                vae=vae,
            )
            pixels = vae.decode(decode_latents).float()
            new_first_k = self._encode_wan_latent(
                vae,
                pixels,
                like=frames,
            )

        remaining = out_slice&#91;:, inject_k:, ...&#93;
        return torch.cat(&#91;new_first_k, remaining&#93;, dim=1)

    def _denormalize_wan_latents(
        self,
        frames: torch.Tensor,
        *,
        vae: Any,
    ) -&gt; torch.Tensor:
        latents = frames.permute(0, 2, 1, 3, 4).float()
        cfg = getattr(vae, &quot;config&quot;, None)
        if (cfg is not None and hasattr(cfg, &quot;latents_mean&quot;) and hasattr(cfg, &quot;latents_std&quot;)):
            latents_mean = torch.tensor(
                cfg.latents_mean,
                device=latents.device,
                dtype=latents.dtype,
            ).view(1, -1, 1, 1, 1)
            latents_std = torch.tensor(
                cfg.latents_std,
                device=latents.device,
                dtype=latents.dtype,
            ).view(1, -1, 1, 1, 1)
            return latents * latents_std + latents_mean

        if hasattr(vae, &quot;scaling_factor&quot;):
            scaling_factor = self._vae_tensor_attr(
                vae,
                &quot;scaling_factor&quot;,
                like=latents,
                default=1.0,
            )
            latents = latents / scaling_factor
        shift_factor = self._vae_tensor_attr(
            vae,
            &quot;shift_factor&quot;,
            like=latents,
            default=0.0,
        )
        return latents + shift_factor

    def _normalize_wan_latents(
        self,
        latents: torch.Tensor,
        *,
        vae: Any,
    ) -&gt; torch.Tensor:
        cfg = getattr(vae, &quot;config&quot;, None)
        if (cfg is not None and hasattr(cfg, &quot;latents_mean&quot;) and hasattr(cfg, &quot;latents_std&quot;)):
            latents_mean = torch.tensor(
                cfg.latents_mean,
                device=latents.device,
                dtype=latents.dtype,
            ).view(1, -1, 1, 1, 1)
            latents_std = torch.tensor(
                cfg.latents_std,
                device=latents.device,
                dtype=latents.dtype,
            ).view(1, -1, 1, 1, 1)
            return (latents - latents_mean) / latents_std

        shift_factor = self._vae_tensor_attr(
            vae,
            &quot;shift_factor&quot;,
            like=latents,
            default=0.0,
        )
        latents = latents - shift_factor
        if hasattr(vae, &quot;scaling_factor&quot;):
            scaling_factor = self._vae_tensor_attr(
                vae,
                &quot;scaling_factor&quot;,
                like=latents,
                default=1.0,
            )
            latents = latents * scaling_factor
        return latents

    def _encode_wan_latent(
        self,
        vae: Any,
        pixels: torch.Tensor,
        *,
        like: torch.Tensor,
    ) -&gt; torch.Tensor:
        encoded = vae.encode(pixels)
        if isinstance(encoded, torch.Tensor):
            raw_latent = encoded
        elif hasattr(encoded, &quot;mode&quot;):
            raw_latent = encoded.mode()
        else:
            latent_dist = getattr(encoded, &quot;latent_dist&quot;, None)
            if latent_dist is not None:
                raw_latent = (latent_dist.mode() if hasattr(latent_dist, &quot;mode&quot;) else latent_dist.mean)
            else:
                mean = getattr(encoded, &quot;mean&quot;, None)
                if not isinstance(mean, torch.Tensor):
                    raise RuntimeError(&quot;Unsupported VAE encode output&quot;)
                raw_latent = mean

        raw_latent = raw_latent.to(device=like.device, dtype=torch.float32)
        normalized = self._normalize_wan_latents(
            raw_latent,
            vae=vae,
        )
        return normalized.permute(0, 2, 1, 3, 4).to(dtype=like.dtype)

    def _require_vae_encoder(self, vae: Any) -&gt; None:
        target = getattr(vae, &quot;module&quot;, vae)
        encoder = getattr(target, &quot;encoder&quot;, None)
        if encoder is None:
            raise RuntimeError(&quot;Streaming first-frame anchor requires a VAE &quot;
                               &quot;loaded with encoder weights&quot;)
        if (getattr(target, &quot;use_feature_cache&quot;, False) and not hasattr(target, &quot;_enc_feat_map&quot;)):
            config = getattr(target, &quot;config&quot;, None)
            if config is not None:
                config.load_encoder = True
            clear_cache = getattr(target, &quot;clear_cache&quot;, None)
            if callable(clear_cache):
                clear_cache()
        if (getattr(target, &quot;use_feature_cache&quot;, False) and not hasattr(target, &quot;_enc_feat_map&quot;)):
            raise RuntimeError(&quot;Streaming first-frame anchor requires VAE &quot;
                               &quot;encoder feature-cache buffers&quot;)

    def _vae_tensor_attr(
        self,
        vae: Any,
        name: str,
        *,
        like: torch.Tensor,
        default: float,
    ) -&gt; torch.Tensor:
        value = getattr(vae, name, None)
        if value is None and hasattr(vae, &quot;module&quot;):
            value = getattr(vae.module, name, None)
        if value is None:
            value = default
        if isinstance(value, torch.Tensor):
            return value.to(device=like.device, dtype=like.dtype)
        return torch.tensor(value, device=like.device, dtype=like.dtype)

    def _log_train_chunk(
        self,
        *,
        iteration: int,
        stage: DistillStage,
        update_student: bool,
        chunk_info: _StreamingChunkInfo,
    ) -&gt; None:
        if dist.is_initialized() and dist.get_rank() != 0:
            return

        generator_state = &quot;on&quot; if update_student else &quot;off&quot;
        train_target = &quot;generator+critic&quot; if update_student else &quot;critic&quot;
        logger.info(
            &quot;&#91;StreamingLong&#93; step=%s stage=%s chunk_latents=%s-%s &quot;
            &quot;train_latents=%s-%s new=%s overlap=%s current_length=%s/%s &quot;
            &quot;train=%s generator=%s critic=on&quot;,
            iteration,
            stage.name,
            chunk_info.chunk_start,
            chunk_info.chunk_end,
            chunk_info.train_start,
            chunk_info.train_end,
            chunk_info.new_frames,
            chunk_info.overlap,
            chunk_info.current_length,
            chunk_info.max_length,
            train_target,
            generator_state,
        )

    def _select_new_frame_count(
        self,
        stage: DistillStage,
        *,
        remaining: int,
        device: torch.device,
    ) -&gt; int:
        chunk_size = self._stage_chunk_size(stage)
        fixed_overlap = stage.streaming_fixed_overlap_latents
        if fixed_overlap is None:
            fixed_overlap = get_optional_int(
                self.method_config,
                &quot;streaming_fixed_overlap_latents&quot;,
                where=&quot;method.streaming_fixed_overlap_latents&quot;,
            )
        if fixed_overlap is not None:
            return min(chunk_size - int(fixed_overlap), remaining)

        min_new = stage.streaming_min_new_frame
        if min_new is None:
            min_new = get_optional_int(
                self.method_config,
                &quot;streaming_min_new_frame&quot;,
                where=&quot;method.streaming_min_new_frame&quot;,
            )
        if min_new is None:
            min_new = chunk_size
        min_new = max(1, int(min_new))

        max_new = min(chunk_size, remaining)
        if max_new &lt;= min_new:
            return max_new

        # Match LongLive&#x27;s step-by-num_frame_per_block selection. With the
        # official 21/18 setting this chooses 18 until the final short chunk.
        block = int(self._chunk_size)
        choices = list(range(min_new, max_new, block))
        if not choices:
            choices = &#91;max_new&#93;

        if not dist.is_initialized() or dist.get_rank() == 0:
            idx = torch.randint(
                low=0,
                high=len(choices),
                size=(1, ),
                device=device,
                dtype=torch.long,
                generator=self.cuda_generator,
            )
        else:
            idx = torch.empty((1, ), device=device, dtype=torch.long)
        if dist.is_initialized():
            dist.broadcast(idx, src=0)
        return int(choices&#91;int(idx.item())&#93;)

    def _stage_max_length(self, stage: DistillStage) -&gt; int:
        return int(stage.streaming_max_length or stage.num_latent_t)

    def _stage_chunk_size(self, stage: DistillStage) -&gt; int:
        chunk_size = stage.streaming_chunk_size
        if chunk_size is None:
            chunk_size = get_optional_int(
                self.method_config,
                &quot;streaming_chunk_size&quot;,
                where=&quot;method.streaming_chunk_size&quot;,
            )
        if chunk_size is None:
            chunk_size = self._chunk_size
        if int(chunk_size) &lt;= 0:
            raise ValueError(&quot;streaming_chunk_size must be positive&quot;)
        return int(chunk_size)

    def _truncate_batch_latents(self, batch: Any, num_latent_t: int) -&gt; None:
        num_latent_t = int(num_latent_t)
        if batch.latents is not None:
            batch.latents = batch.latents&#91;:, :num_latent_t&#93;
            batch.raw_latent_shape = (
                int(batch.latents.shape&#91;0&#93;),
                int(batch.latents.shape&#91;2&#93;),
                int(batch.latents.shape&#91;1&#93;),
                int(batch.latents.shape&#91;3&#93;),
                int(batch.latents.shape&#91;4&#93;),
            )
        if batch.noise_latents is not None:
            batch.noise_latents = batch.noise_latents&#91;:, :num_latent_t&#93;
        if batch.noisy_model_input is not None:
            batch.noisy_model_input = batch.noisy_model_input&#91;:, :num_latent_t&#93;
        if batch.noise is not None:
            batch.noise = batch.noise&#91;:, :num_latent_t&#93;
        if batch.timesteps is not None and batch.timesteps.ndim == 2:
            batch.timesteps = batch.timesteps&#91;:, :num_latent_t&#93;
        if batch.sigmas is not None and batch.sigmas.ndim &gt;= 1:
            if batch.sigmas.ndim == 1:
                batch.sigmas = batch.sigmas&#91;:num_latent_t&#93;
            else:
                batch.sigmas = batch.sigmas&#91;:, :num_latent_t&#93;
        self._rebuild_attention_metadata(batch)

    def _rebuild_attention_metadata(self, batch: Any) -&gt; None:
        build_metadata = getattr(self.student, &quot;_build_attention_metadata&quot;, None)
        if not callable(build_metadata):
            return
        batch.attn_metadata = None
        batch.attn_metadata_vsa = None
        build_metadata(batch)
        batch.attn_metadata_vsa = copy.copy(batch.attn_metadata)
        if batch.attn_metadata is not None:
            batch.attn_metadata.VSA_sparsity = 0.0

    def _critic_flow_matching_loss_for_x0(
        self,
        generator_pred_x0: torch.Tensor,
        batch: Any,
        *,
        chunk_mask: torch.Tensor | None,
    ) -&gt; tuple&#91;torch.Tensor, Any, dict&#91;str, Any&#93;&#93;:
        device = generator_pred_x0.device
        batch_size = int(generator_pred_x0.shape&#91;0&#93;)
        fake_score_timestep = self._sample_score_timestep(
            batch_size,
            device=device,
        )

        noise = torch.randn(
            generator_pred_x0.shape,
            device=device,
            dtype=generator_pred_x0.dtype,
            generator=self.cuda_generator,
        )
        noisy_x0 = self._sf_add_noise(generator_pred_x0, noise, fake_score_timestep)

        pred_noise = self.critic.predict_noise(
            noisy_x0,
            fake_score_timestep,
            batch,
            conditional=True,
            cfg_uncond=self._cfg_uncond,
            attn_kind=&quot;dense&quot;,
        )
        target = noise - generator_pred_x0
        flow_matching_loss = self._masked_mean(
            (pred_noise - target)**2,
            chunk_mask,
        )

        batch.fake_score_latent_vis_dict = &#123;
            &quot;generator_pred_video&quot;: generator_pred_x0,
            &quot;fake_score_timestep&quot;: fake_score_timestep,
        &#125;
        outputs = &#123;&quot;fake_score_latent_vis_dict&quot;: batch.fake_score_latent_vis_dict&#125;
        return (
            flow_matching_loss,
            (batch.timesteps, batch.attn_metadata),
            outputs,
        )

    def _dmd_loss_masked(
        self,
        generator_pred_x0: torch.Tensor,
        batch: Any,
        *,
        chunk_mask: torch.Tensor | None,
    ) -&gt; torch.Tensor:
        guidance_scale = get_optional_float(
            self.method_config,
            &quot;real_score_guidance_scale&quot;,
            where=&quot;method.real_score_guidance_scale&quot;,
        )
        if guidance_scale is None:
            guidance_scale = 1.0
        device = generator_pred_x0.device
        batch_size = int(generator_pred_x0.shape&#91;0&#93;)

        with torch.no_grad():
            timestep = self._sample_score_timestep(
                batch_size,
                device=device,
            )
            noise = torch.randn(
                generator_pred_x0.shape,
                device=device,
                dtype=generator_pred_x0.dtype,
                generator=self.cuda_generator,
            )
            noisy_latents = self._sf_add_noise(generator_pred_x0, noise, timestep)

            faker_x0 = self._predict_x0_with_scheduler(
                self.critic,
                noisy_latents,
                timestep,
                batch,
                conditional=True,
                attn_kind=&quot;dense&quot;,
            )
            real_cond_x0 = self._predict_x0_with_scheduler(
                self.teacher,
                noisy_latents,
                timestep,
                batch,
                conditional=True,
                attn_kind=&quot;dense&quot;,
            )
            if self._real_score_cfg:
                real_uncond_x0 = self._predict_x0_with_scheduler(
                    self.teacher,
                    noisy_latents,
                    timestep,
                    batch,
                    conditional=False,
                    attn_kind=&quot;dense&quot;,
                )
                real_cfg_x0 = real_uncond_x0 + (real_cond_x0 - real_uncond_x0) * guidance_scale
            else:
                real_cfg_x0 = real_cond_x0
            denom = torch.abs(generator_pred_x0.float() - real_cfg_x0.float(), ).mean(dim=&#91;1, 2, 3, 4&#93;,
                                                                                      keepdim=True).clamp_min(1e-6)
            grad = (faker_x0 - real_cfg_x0) / denom
            grad = torch.nan_to_num(grad)

        loss = 0.5 * (generator_pred_x0.float() - (generator_pred_x0.float() - grad.float()).detach())**2
        return self._masked_mean(loss, chunk_mask)

    def _score_timestep_from_ratio(self, key: str) -&gt; int | None:
        ratio = get_optional_float(
            self.method_config,
            key,
            where=f&quot;method.&#123;key&#125;&quot;,
        )
        if ratio is None:
            return None
        if not 0.0 &lt;= ratio &lt;= 1.0:
            raise ValueError(f&quot;method.&#123;key&#125; must be in &#91;0, 1&#93;, got &#123;ratio&#125;&quot;)
        return int(round(float(ratio) * int(self.student.num_train_timesteps)))

    def _sample_score_timestep(
        self,
        batch_size: int,
        *,
        device: torch.device,
    ) -&gt; torch.Tensor:
        timestep = torch.randint(
            0,
            int(self.student.num_train_timesteps),
            &#91;batch_size&#93;,
            device=device,
            dtype=torch.long,
            generator=self.cuda_generator,
        )
        timestep = self.student.shift_and_clamp_timestep(timestep)
        min_timestep = self._score_min_timestep
        max_timestep = self._score_max_timestep
        if min_timestep is not None or max_timestep is not None:
            timestep = timestep.clamp(
                min=0 if min_timestep is None else int(min_timestep),
                max=(int(self.student.num_train_timesteps) if max_timestep is None else int(max_timestep)),
            )
        return timestep

    def _masked_mean(
        self,
        values: torch.Tensor,
        mask: torch.Tensor | None,
    ) -&gt; torch.Tensor:
        if mask is None:
            return values.mean()
        mask_f = mask.to(device=values.device, dtype=values.dtype)
        while mask_f.ndim &lt; values.ndim:
            mask_f = mask_f.unsqueeze(-1)
        denom = mask_f.expand_as(values).sum().clamp_min(1.0)
        return (values * mask_f).sum() / denom</code></pre></details>
<h3 id="fastvideo.train.methods.distribution_matching.streaming_long_tuning.StreamingLongTuningMethod.single_train_step"><code>fastvideo.train.methods.distribution_matching.streaming_long_tuning.StreamingLongTuningMethod.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;:
    stage = select_distill_stage(self._distill_stages, iteration)
    if not stage.streaming_training:
        self._streaming_state = None
        return self._single_train_step_short(batch, iteration, stage)
    return self._single_train_step_streaming(batch, iteration, stage)</code></pre></details>
</div>
