Skip to content
FastVideo
Esc
↑↓navigate↵open⌘Jpreview
On this page

streaming_long_tuning

LongLive-style multi-stage self-forcing for streaming rollouts.

fastvideo.train.methods.distribution_matching.streaming_long_tuning.init_logger

init_logger(name: str) -> _FastvideoLogger

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.

fastvideo.train.methods.distribution_matching.streaming_long_tuning.pred_noise_to_pred_video

pred_noise_to_pred_video(pred_noise: torch.Tensor, noise_input_latent: torch.Tensor, timestep: torch.Tensor, scheduler: Any) -> torch.Tensor

Convert predicted noise to clean latent.

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

Returns

torch.Tensor

the predicted video with shape [B, C, H, W]

fastvideo.train.methods.distribution_matching.streaming_long_tuning.SelfForcingMethod

SelfForcingMethod(*, cfg: Any, role_models: dict[str, ModelBase])

Self-Forcing DMD2 (distribution matching) method.

Requires a causal student implementing CausalModelBase.

fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase

CausalModelBase(*, trainable: bool = True, lora: LoraConfig | dict[str, Any] | None = None, attention_backend: AttentionBackendEnum | str | None = None)

Extension for causal / streaming model plugins.

Cache state is internal to the model instance and keyed by cache_tag (no role handle needed).

fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.clear_caches

clear_caches(*, cache_tag: str = 'pos') -> None

Clear internal caches before starting a new rollout.

fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.predict_noise_streaming

predict_noise_streaming(noisy_latents: torch.Tensor, timestep: torch.Tensor, batch: TrainingBatch, *, conditional: bool, cache_tag: str = 'pos', store_kv: bool = False, cur_start_frame: int = 0, cfg_uncond: dict[str, Any] | None = None, attn_kind: Literal['dense', 'vsa'] = 'dense') -> torch.Tensor | None

Streaming predict-noise that may update internal caches.

fastvideo.train.methods.distribution_matching.streaming_long_tuning.CausalModelBase.predict_x0_streaming

predict_x0_streaming(noisy_latents: torch.Tensor, timestep: torch.Tensor, batch: TrainingBatch, *, conditional: bool, cache_tag: str = 'pos', store_kv: bool = False, cur_start_frame: int = 0, cfg_uncond: dict[str, Any] | None = None, attn_kind: Literal['dense', 'vsa'] = 'dense') -> torch.Tensor | None

Predict x0 streaming via predict_noise_streaming + conversion.

fastvideo.train.methods.distribution_matching.streaming_long_tuning.get_optional_float

get_optional_float(mapping: dict[str, Any], key: str, *, where: str) -> float | None

fastvideo.train.methods.distribution_matching.streaming_long_tuning.get_optional_int

get_optional_int(mapping: dict[str, Any], key: str, *, where: str) -> int | None

fastvideo.train.methods.distribution_matching.streaming_long_tuning.DistillStage

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)

Resolved multi-stage distillation stage.

Source
@dataclass(frozen=True, slots=True)
class DistillStage:
    """Resolved multi-stage distillation stage."""
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>

fastvideo.train.methods.distribution_matching.streaming_long_tuning.parse_multi_phased_distill_schedule

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) -> list[DistillStage]

Parse a compact/list multi-stage schedule.

Preferred YAML form:

[{stage: self_forcing, end_step: 700, num_latent_t: 21}, ...]

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

Source
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,
) -> list[DistillStage]:
    """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>

fastvideo.train.methods.distribution_matching.streaming_long_tuning.select_distill_stage

select_distill_stage(stages: list[DistillStage], iteration: int) -> DistillStage

Select the active stage for iteration.

Source
def select_distill_stage(
    stages: list[DistillStage],
    iteration: int,
) -> DistillStage:
    """Select the active stage for ``iteration``."""
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>

fastvideo.train.methods.distribution_matching.streaming_long_tuning.StreamingLongTuningMethod

StreamingLongTuningMethod(*, cfg: Any, role_models: dict[str, Any])

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.

Source
class StreamingLongTuningMethod(SelfForcingMethod):
    """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>

fastvideo.train.methods.distribution_matching.streaming_long_tuning.StreamingLongTuningMethod.single_train_step

single_train_step(batch: dict[str, Any], iteration: int) -> tuple[dict[str, torch.Tensor], dict[str, Any], dict[str, LogScalar]]
Source
def single_train_step(
    self,
    batch: dict[str, Any],
    iteration: int,
) -> tuple[
        dict[str, torch.Tensor],
        dict[str, Any],
        dict[str, LogScalar],
]:
    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)

Was this page helpful?