VideoGenerator.from_pretrained(model_path, config) takes the same nested
settings as from_config; the 24 flat keywords, from_pretrained_kwargs_to_config,
FROM_PRETRAINED_KWARGS, and every flat_field in the schema are deleted.
nvfp4_fa4 is the typed field engine.attention.nvfp4_fa4, applied by a
resolution step. 65 call sites and the docs use the nested form; the three
kwargs golden cases are config cases with byte-identical results.
Every value is decided during resolution; no runtime code calls
with_override. The device offload policy (unified memory, MPS, layerwise
conflicts, lazy module load) runs as resolution steps in the main process
(fastvideo/api/device_policy.py), and the worker runs on the config it
receives. The LTX-2 refine defaults from model_index.json and the MiniMax-H3
schedule from fastvideo_inference.json are resolution steps
(fastvideo/api/checkpoint_defaults.py) that read local checkpoints or download
only those files; the H3 pipeline validates the schedule against the loaded
schedulers but no longer writes it. The preprocessing entry point passes the
downloaded local path as run state instead of a config override. Teacher and
critic models load with override_transformer_cls_name as a load argument.
The test isolation turns the device policy off, as it blocks downloads, so
that the goldens do not depend on the machine.
Against the 3f6893a0 goldens, every field still matches except boundary_ratio
and ltx2_vae_tiling (DESIGN section 9) and the fields that no longer exist.
The launcher and trainer YAML verifications report 0 unexplained differences.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
148 lines
7.0 KiB
Python
148 lines
7.0 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
from typing import Any, cast
|
|
|
|
import torch
|
|
|
|
import fastvideo.envs as envs
|
|
from fastvideo.api.resolution import ResolvedGeneratorConfig
|
|
from fastvideo.distributed import (cleanup_dist_env_and_memory, maybe_init_distributed_environment_and_model_parallel)
|
|
from fastvideo.distributed.parallel_state import get_local_torch_device
|
|
from fastvideo.logger import init_logger
|
|
from fastvideo.pipelines import ForwardBatch, LoRAPipeline, build_pipeline
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
def _log_cuda_device_uuid(rank: int, device: torch.device) -> None:
|
|
"""Record an NVIDIA worker UUID when external NVTX profiling is enabled."""
|
|
if not envs.FASTVIDEO_NVTX_PROFILE.get():
|
|
return
|
|
device_uuid = torch.cuda.get_device_properties(device).uuid
|
|
logger.info("Worker %d CUDA device UUID: GPU-%s", rank, device_uuid, local_main_process_only=False)
|
|
|
|
|
|
class Worker:
|
|
|
|
def __init__(self, resolved_config: ResolvedGeneratorConfig, local_rank: int, rank: int,
|
|
distributed_init_method: str):
|
|
self.resolved_config = resolved_config
|
|
self.local_rank = local_rank
|
|
self.rank = rank
|
|
self.distributed_init_method = distributed_init_method
|
|
|
|
# Init request dispatcher
|
|
# TODO(will): add request dispatcher: use TypeBasedDispatcher from
|
|
# utils.py
|
|
# self._request_dispatcher = TypeBasedDispatcher(
|
|
# [
|
|
# (RpcReqInput, self.handle_rpc_request),
|
|
# (GenerateRequest, self.handle_generate_request),
|
|
# (ExpertDistributionReq, self.expert_distribution_handle),
|
|
# ]
|
|
# )
|
|
|
|
def init_device(self) -> None:
|
|
"""Initialize the device for the worker."""
|
|
|
|
# torch.distributed.all_reduce does not free the input tensor until
|
|
# the synchronization point. This causes the memory usage to grow
|
|
# as the number of all_reduce calls increases. This env var disables
|
|
# this behavior.
|
|
# Related issue:
|
|
# https://discuss.pytorch.org/t/cuda-allocation-lifetime-for-inputs-to-distributed-all-reduce/191573
|
|
envs.set_external("TORCH_NCCL_AVOID_RECORD_STREAMS", "1")
|
|
# This env var set by Ray causes exceptions with graph building.
|
|
envs.unset_external("NCCL_ASYNC_ERROR_HANDLING")
|
|
|
|
# Set environment variables BEFORE calling get_local_torch_device()
|
|
# so that each worker uses the correct device
|
|
# Both multiprocessing and Ray pass the worker-local rank explicitly.
|
|
# Ray deliberately excludes LOCAL_RANK from the copied driver
|
|
# environment and exposes all GPUs assigned to the node, so leaving an
|
|
# inherited or missing value here would bind every Ray actor to cuda:0.
|
|
envs.set_external("LOCAL_RANK", str(self.local_rank))
|
|
envs.set_external("RANK", str(self.rank))
|
|
envs.set_external("WORLD_SIZE", str(self.resolved_config.engine.num_gpus))
|
|
|
|
# Platform-agnostic device initialization
|
|
self.device = get_local_torch_device()
|
|
|
|
from fastvideo.platforms import current_platform
|
|
|
|
# Set the CUDA device BEFORE any CUDA calls
|
|
if current_platform.is_cuda_alike():
|
|
torch.cuda.set_device(self.device)
|
|
self.init_gpu_memory = torch.cuda.mem_get_info(self.device)[0]
|
|
if current_platform.is_cuda():
|
|
_log_cuda_device_uuid(self.rank, self.device)
|
|
else:
|
|
# For MPS, we can't get memory info the same way
|
|
self.init_gpu_memory = 0
|
|
|
|
# Initialize the distributed environment.
|
|
maybe_init_distributed_environment_and_model_parallel(self.resolved_config.engine.parallelism.tp_size,
|
|
self.resolved_config.engine.parallelism.sp_size,
|
|
self.distributed_init_method)
|
|
|
|
self.pipeline = build_pipeline(self.resolved_config)
|
|
|
|
def execute_forward(self, forward_batch: ForwardBatch, resolved_config: ResolvedGeneratorConfig) -> ForwardBatch:
|
|
output_batch = self.pipeline.forward(forward_batch, self.resolved_config)
|
|
needs_output = forward_batch.return_frames or (forward_batch.save_video
|
|
and resolved_config.pipeline.output_type != "latent"
|
|
and not output_batch.extra.get("audio_only"))
|
|
if output_batch.output is not None and not needs_output:
|
|
# Drop the decoded tensor before multiprocessing or Ray transports
|
|
# the worker result back to the generator.
|
|
output_batch.output = torch.empty(0, device="cpu")
|
|
return cast(ForwardBatch, output_batch)
|
|
|
|
def shutdown(self) -> dict[str, Any]:
|
|
"""Gracefully shut down the worker process"""
|
|
logger.info("Worker %d shutting down...", self.rank, local_main_process_only=False)
|
|
# Clean up resources
|
|
if hasattr(self, 'pipeline') and self.pipeline is not None:
|
|
# Clean up pipeline resources if needed
|
|
pass
|
|
|
|
# Destroy the distributed environment
|
|
cleanup_dist_env_and_memory(shutdown_ray=False)
|
|
|
|
logger.info("Worker %d shutdown complete", self.rank, local_main_process_only=False)
|
|
return {"status": "shutdown_complete"}
|
|
|
|
def set_lora_adapter(self,
|
|
lora_nickname: str,
|
|
lora_path: str | None = None,
|
|
strength: float = 1.0,
|
|
accumulate: bool = False) -> dict[str, Any]:
|
|
if isinstance(self.pipeline, LoRAPipeline):
|
|
self.pipeline.set_lora_adapter(lora_nickname, lora_path, strength=strength, accumulate=accumulate)
|
|
logger.info("Worker %d set LoRA adapter %s with path %s", self.rank, lora_nickname, lora_path)
|
|
return {"status": "lora_adapter_set"}
|
|
return {"status": "failed: pipeline is not a LoRAPipeline"}
|
|
|
|
def unmerge_lora_weights(self) -> dict[str, Any]:
|
|
if isinstance(self.pipeline, LoRAPipeline):
|
|
self.pipeline.unmerge_lora_weights()
|
|
return {"status": "lora_adapter_unmerged"}
|
|
return {"status": "failed: pipeline is not a LoRAPipeline"}
|
|
|
|
def execute_streaming_reset(self, forward_batch: ForwardBatch,
|
|
resolved_config: ResolvedGeneratorConfig) -> dict[str, Any]:
|
|
self.pipeline.streaming_reset(forward_batch, self.pipeline.resolved_config)
|
|
return {"status": "reset_complete"}
|
|
|
|
def execute_streaming_step(self, keyboard_action: torch.Tensor, mouse_action: torch.Tensor) -> ForwardBatch:
|
|
return self.pipeline.streaming_step(keyboard_action, mouse_action)
|
|
|
|
def execute_streaming_clear(self) -> dict[str, Any]:
|
|
self.pipeline.streaming_clear()
|
|
return {"status": "cleared"}
|
|
|
|
def merge_lora_weights(self) -> dict[str, Any]:
|
|
if isinstance(self.pipeline, LoRAPipeline):
|
|
self.pipeline.merge_lora_weights()
|
|
return {"status": "lora_adapter_merged"}
|
|
return {"status": "failed: pipeline is not a LoRAPipeline"}
|