Skip to content

worker

Classes

fastvideo.worker.Executor

Executor(fastvideo_args: FastVideoArgs, *, log_queue=None)

Bases: ABC

Source code in fastvideo/worker/executor.py
def __init__(
    self,
    fastvideo_args: FastVideoArgs,
    *,
    log_queue=None,
):
    self.fastvideo_args = fastvideo_args
    self._log_queue = log_queue

    self._init_executor()

Methods:

fastvideo.worker.Executor.clear_log_queue abstractmethod
clear_log_queue() -> None

Stop forwarding worker logs to the queue. Call after generate_video.

Source code in fastvideo/worker/executor.py
@abstractmethod
def clear_log_queue(self) -> None:
    """Stop forwarding worker logs to the queue. Call after generate_video."""
    self.collective_rpc("clear_log_queue")
fastvideo.worker.Executor.collective_rpc abstractmethod
collective_rpc(method: str | Callable[..., _R], timeout: float | None = None, args: tuple = (), kwargs: dict[str, Any] | None = None) -> list[_R]

Execute an RPC call on all workers.

Parameters:

Name Type Description Default
method str | Callable[..., _R]

Name of the worker method to execute, or a callable that is serialized and sent to all workers to execute.

If the method is a callable, it should accept an additional self argument, in addition to the arguments passed in args and kwargs. The self argument will be the worker object.

required
timeout float | None

Maximum time in seconds to wait for execution. Raises a :exc:TimeoutError on timeout. None means wait indefinitely.

None
args tuple

Positional arguments to pass to the worker method.

()
kwargs dict[str, Any] | None

Keyword arguments to pass to the worker method.

None

Returns:

Type Description
list[_R]

A list containing the results from each worker.

Note

It is recommended to use this API to only pass control messages, and set up data-plane communication to pass data.

Source code in fastvideo/worker/executor.py
@abstractmethod
def collective_rpc(self,
                   method: str | Callable[..., _R],
                   timeout: float | None = None,
                   args: tuple = (),
                   kwargs: dict[str, Any] | None = None) -> list[_R]:
    """
    Execute an RPC call on all workers.

    Args:
        method: Name of the worker method to execute, or a callable that
            is serialized and sent to all workers to execute.

            If the method is a callable, it should accept an additional
            `self` argument, in addition to the arguments passed in `args`
            and `kwargs`. The `self` argument will be the worker object.
        timeout: Maximum time in seconds to wait for execution. Raises a
            :exc:`TimeoutError` on timeout. `None` means wait indefinitely.
        args: Positional arguments to pass to the worker method.
        kwargs: Keyword arguments to pass to the worker method.

    Returns:
        A list containing the results from each worker.

    Note:
        It is recommended to use this API to only pass control messages,
        and set up data-plane communication to pass data.
    """
    raise NotImplementedError
fastvideo.worker.Executor.merge_lora_weights abstractmethod
merge_lora_weights() -> None

Merge the LoRA weights for the workers.

Source code in fastvideo/worker/executor.py
@abstractmethod
def merge_lora_weights(self) -> None:
    """
    Merge the LoRA weights for the workers.
    """
    raise NotImplementedError
fastvideo.worker.Executor.set_log_queue abstractmethod
set_log_queue(log_queue: Queue | None) -> None

Forward worker logs to the given queue. Call before generate_video.

Source code in fastvideo/worker/executor.py
@abstractmethod
def set_log_queue(self, log_queue: Queue | None) -> None:
    """Forward worker logs to the given queue. Call before generate_video."""
    self.collective_rpc("set_log_queue", kwargs={"log_queue": log_queue})
fastvideo.worker.Executor.set_lora_adapter abstractmethod
set_lora_adapter(lora_nickname: str, lora_path: str | None = None, strength: float = 1.0, accumulate: bool = False) -> None

Set the LoRA adapter for the workers.

Source code in fastvideo/worker/executor.py
@abstractmethod
def set_lora_adapter(self,
                     lora_nickname: str,
                     lora_path: str | None = None,
                     strength: float = 1.0,
                     accumulate: bool = False) -> None:
    """
    Set the LoRA adapter for the workers.
    """
    raise NotImplementedError
fastvideo.worker.Executor.shutdown abstractmethod
shutdown() -> None

Shutdown the executor.

Source code in fastvideo/worker/executor.py
@abstractmethod
def shutdown(self) -> None:
    """
    Shutdown the executor.
    """
    raise NotImplementedError
fastvideo.worker.Executor.unmerge_lora_weights abstractmethod
unmerge_lora_weights() -> None

Unmerge the LoRA weights for the workers.

Source code in fastvideo/worker/executor.py
@abstractmethod
def unmerge_lora_weights(self) -> None:
    """
    Unmerge the LoRA weights for the workers.
    """
    raise NotImplementedError

fastvideo.worker.MultiprocExecutor

MultiprocExecutor(fastvideo_args: FastVideoArgs, *, log_queue=None)

Bases: Executor

Source code in fastvideo/worker/executor.py
def __init__(
    self,
    fastvideo_args: FastVideoArgs,
    *,
    log_queue=None,
):
    self.fastvideo_args = fastvideo_args
    self._log_queue = log_queue

    self._init_executor()

Methods:

fastvideo.worker.MultiprocExecutor.__del__
__del__()

Ensure cleanup on garbage collection

Source code in fastvideo/worker/multiproc_executor.py
def __del__(self):
    """Ensure cleanup on garbage collection"""
    self.shutdown()
fastvideo.worker.MultiprocExecutor.__enter__
__enter__()

Support for context manager protocol

Source code in fastvideo/worker/multiproc_executor.py
def __enter__(self):
    """Support for context manager protocol"""
    return self
fastvideo.worker.MultiprocExecutor.__exit__
__exit__(exc_type, exc_val, exc_tb)

Ensure cleanup when exiting context

Source code in fastvideo/worker/multiproc_executor.py
def __exit__(self, exc_type, exc_val, exc_tb):
    """Ensure cleanup when exiting context"""
    self.shutdown()
fastvideo.worker.MultiprocExecutor.clear_log_queue
clear_log_queue() -> None

Stop forwarding worker logs to the queue. Call after generate_video.

Source code in fastvideo/worker/multiproc_executor.py
def clear_log_queue(self) -> None:
    """Stop forwarding worker logs to the queue. Call after generate_video."""
    self.collective_rpc("clear_log_queue")
fastvideo.worker.MultiprocExecutor.set_log_queue
set_log_queue(log_queue: Queue | None) -> None

Forward worker logs to the given queue. Call before generate_video.

Source code in fastvideo/worker/multiproc_executor.py
def set_log_queue(self, log_queue: Queue | None) -> None:
    """Forward worker logs to the given queue. Call before generate_video."""
    self.collective_rpc("set_log_queue", kwargs={"log_queue": log_queue})
fastvideo.worker.MultiprocExecutor.shutdown
shutdown() -> None

Properly shut down the executor and its workers

Source code in fastvideo/worker/multiproc_executor.py
def shutdown(self) -> None:
    """Properly shut down the executor and its workers"""
    if hasattr(self, 'shutting_down') and self.shutting_down:
        return  # Prevent multiple shutdown calls

    logger.info("Shutting down MultiprocExecutor...")

    # Check if workers were initialized (they might not be if initialization failed)
    if not hasattr(self, 'workers') or not self.workers:
        logger.info("No workers to shut down.")
        return

    self.shutting_down = True

    # First try gentle termination
    try:
        # Send termination message to all workers
        for worker in self.workers:
            with contextlib.suppress(Exception):
                worker.pipe.send({"method": "shutdown", "args": (), "kwargs": {}})

        # Give workers some time to exit gracefully
        start_time = time.perf_counter()
        while time.perf_counter() - start_time < 5.0:  # 5 seconds timeout
            if all(not worker.proc.is_alive() for worker in self.workers):
                break
            time.sleep(0.1)

        # Force terminate any remaining workers
        for worker in self.workers:
            if worker.proc.is_alive():
                worker.proc.terminate()

        # Final timeout for terminate
        start_time = time.perf_counter()
        while time.perf_counter() - start_time < 2.0:  # 2 seconds timeout
            if all(not worker.proc.is_alive() for worker in self.workers):
                break
            time.sleep(0.1)

        # Kill if still alive
        for worker in self.workers:
            if worker.proc.is_alive():
                worker.proc.kill()
            worker.proc.join(timeout=1.0)

    except Exception as e:
        logger.error("Error during shutdown: %s", e)
        # Last resort, try to kill all workers
        for worker in self.workers:
            with contextlib.suppress(Exception):
                if worker.proc.is_alive():
                    worker.proc.kill()

    # Clean up pipes
    for worker in self.workers:
        with contextlib.suppress(Exception):
            worker.pipe.close()

    self.workers = []
    logger.info("MultiprocExecutor shutdown complete")

Functions:

fastvideo.worker.initialize_ray_cluster

initialize_ray_cluster(fastvideo_args: FastVideoArgs, ray_address: str | None = None)

Initialize the distributed cluster with Ray.

it will connect to the Ray cluster and create a placement group for the workers, which includes the specification of the resources for each distributed worker.

Parameters:

Name Type Description Default
parallel_config

The configurations for parallel execution.

required
ray_address str | None

The address of the Ray cluster. If None, uses the default Ray cluster address.

None
Source code in fastvideo/worker/ray_utils.py
def initialize_ray_cluster(
    fastvideo_args: FastVideoArgs,
    ray_address: str | None = None,
):
    """Initialize the distributed cluster with Ray.

    it will connect to the Ray cluster and create a placement group
    for the workers, which includes the specification of the resources
    for each distributed worker.

    Args:
        parallel_config: The configurations for parallel execution.
        ray_address: The address of the Ray cluster. If None, uses
            the default Ray cluster address.
    """
    assert_ray_available()
    from fastvideo.platforms import current_platform

    if ray.is_initialized():
        logger.info("Ray is already initialized. Skipping Ray initialization.")
    elif current_platform.is_rocm() or current_platform.is_xpu():
        # Try to connect existing ray instance and create a new one if not found
        try:
            ray.init("auto")
        except ConnectionError:
            logger.warning("No existing RAY instance detected. "
                           "A new instance will be launched with current node resources.")
            ray.init(address=ray_address, num_gpus=fastvideo_args.num_gpus, runtime_env=fastvideo_args.ray_runtime_env)
    else:
        ray.init(address=ray_address, runtime_env=fastvideo_args.ray_runtime_env)

    device_str = current_platform.ray_device_key
    if not device_str:
        raise ValueError(f"current platform {current_platform.device_name} does not "
                         "support ray.")

    # Create or get the placement group for worker processes
    current_placement_group = fastvideo_args.ray_placement_group or ray.util.get_current_placement_group()

    if current_placement_group:
        logger.info("Using the existing placement group")

        # We are in a placement group
        bundles = current_placement_group.bundle_specs
        # Verify that we can use the placement group.
        device_bundles = 0
        for bundle in bundles:
            bundle_devices = bundle.get(device_str, 0)
            if bundle_devices > 1:
                raise ValueError("Placement group bundle cannot have more than 1 "
                                 f"{device_str}.")
            if bundle_devices:
                device_bundles += 1
        if fastvideo_args.num_gpus > device_bundles:
            raise ValueError(f"The number of required {device_str}s exceeds the total "
                             f"number of available {device_str}s in the placement group. "
                             f"Required number of devices: {fastvideo_args.num_gpus}. "
                             f"Total number of devices: {device_bundles}.")
    else:
        logger.info("No current placement group found. "
                    "Creating a new placement group.")
        num_devices_in_cluster = ray.cluster_resources().get(device_str, 0)
        # Log a warning message and delay resource allocation failure response.
        # Avoid immediate rejection to allow user-initiated placement group
        # created and wait cluster to be ready
        if fastvideo_args.num_gpus > num_devices_in_cluster:
            logger.warning(
                "The number of required %ss exceeds the total "
                "number of available %ss in the placement group.", device_str, device_str)
        # Create a new placement group
        placement_group_specs: list[dict[str, float]] = ([{device_str: 1.0} for _ in range(fastvideo_args.num_gpus)])

        # FastVideo engine is also a worker to execute model with an accelerator,
        # so it requires to have the device in a current node. Check if
        # the current node has at least one device.
        current_ip = get_ip()
        current_node_id = ray.get_runtime_context().get_node_id()
        current_node_resource = available_resources_per_node()[current_node_id]
        if current_node_resource.get(device_str, 0) < 1:
            raise ValueError(f"Current node has no {device_str} available. "
                             f"{current_node_resource=}. FastVideo engine cannot start without "
                             f"{device_str}. Make sure you have at least 1 {device_str} "
                             f"available in a node {current_node_id=} {current_ip=}.")
        # This way, at least bundle is required to be created in a current
        # node.
        placement_group_specs[0][f"node:{current_ip}"] = 0.001

        # By default, Ray packs resources as much as possible.
        current_placement_group = ray.util.placement_group(placement_group_specs, strategy="PACK")
        _wait_until_pg_ready(current_placement_group)

    assert current_placement_group is not None
    _verify_bundles(current_placement_group, fastvideo_args, device_str)
    # Set the placement group in the fastvideo args
    fastvideo_args.ray_placement_group = current_placement_group