diffusion: remove unused workflows folder (#13114)
This commit is contained in:
@@ -1 +0,0 @@
|
||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||
@@ -1 +0,0 @@
|
||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||
@@ -1,341 +0,0 @@
|
||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||
|
||||
import dataclasses
|
||||
import gc
|
||||
import os
|
||||
import random
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pyarrow as pa
|
||||
import torch
|
||||
from datasets import Dataset, Video, load_dataset
|
||||
|
||||
from sglang.multimodal_gen.configs.configs import (
|
||||
DatasetType,
|
||||
PreprocessConfig,
|
||||
VideoLoaderType,
|
||||
)
|
||||
from sglang.multimodal_gen.configs.sample.base import DataType
|
||||
from sglang.multimodal_gen.dataset.dataloader.parquet_io import (
|
||||
ParquetDatasetWriter,
|
||||
records_to_table,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||
get_world_rank,
|
||||
get_world_size,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines.pipeline_batch_info import PreprocessBatch
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class PreprocessingDataValidator:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_height: int = 1024,
|
||||
max_width: int = 1024,
|
||||
max_h_div_w_ratio: float = 17 / 16,
|
||||
min_h_div_w_ratio: float = 8 / 16,
|
||||
num_frames: int = 16,
|
||||
train_fps: int = 24,
|
||||
speed_factor: float = 1.0,
|
||||
video_length_tolerance_range: float = 5.0,
|
||||
drop_short_ratio: float = 0.0,
|
||||
hw_aspect_threshold: float = 1.5,
|
||||
):
|
||||
self.max_height = max_height
|
||||
self.max_width = max_width
|
||||
self.max_h_div_w_ratio = max_h_div_w_ratio
|
||||
self.min_h_div_w_ratio = min_h_div_w_ratio
|
||||
self.num_frames = num_frames
|
||||
self.train_fps = train_fps
|
||||
self.speed_factor = speed_factor
|
||||
self.video_length_tolerance_range = video_length_tolerance_range
|
||||
self.drop_short_ratio = drop_short_ratio
|
||||
self.hw_aspect_threshold = hw_aspect_threshold
|
||||
self.validators: dict[str, Callable[[dict[str, Any]], bool]] = {}
|
||||
self.filter_counts: dict[str, int] = {}
|
||||
|
||||
self.num_items_before_filtering = 0
|
||||
self.num_items_after_filtering = 0
|
||||
|
||||
self.register_validators()
|
||||
|
||||
def register_validators(self) -> None:
|
||||
self.add_validator("data_type_validator", self._validate_data_type)
|
||||
self.add_validator("resolution_validator", self._validate_resolution)
|
||||
self.add_validator("frame_sampling_validator", self._validate_frame_sampling)
|
||||
|
||||
def add_validator(
|
||||
self, name: str, validator: Callable[[dict[str, Any]], bool]
|
||||
) -> None:
|
||||
self.validators[name] = validator
|
||||
self.filter_counts[name] = 0
|
||||
|
||||
def __call__(self, batch: dict[str, Any]) -> bool:
|
||||
"""
|
||||
Validate whether the preprocessing data batch is valid.
|
||||
"""
|
||||
self.num_items_before_filtering += 1
|
||||
|
||||
for name, validator in self.validators.items():
|
||||
if not validator(batch):
|
||||
self.filter_counts[name] += 1
|
||||
return False
|
||||
|
||||
self.num_items_after_filtering += 1
|
||||
return True
|
||||
|
||||
def _validate_data_type(self, batch: dict[str, Any]) -> bool:
|
||||
"""Validate basic validity of data items"""
|
||||
return not (
|
||||
batch["caption"] is None
|
||||
or batch["caption"] == ""
|
||||
or batch["fps"] is None
|
||||
or batch["fps"] <= 0
|
||||
or batch["num_frames"] is None
|
||||
or batch["num_frames"] <= 0
|
||||
)
|
||||
|
||||
def _validate_resolution(self, batch: dict[str, Any]) -> bool:
|
||||
"""Validate resolution constraints"""
|
||||
|
||||
aspect = self.max_height / self.max_width
|
||||
if batch["resolution"] is not None:
|
||||
height = batch["resolution"].get("height", None)
|
||||
width = batch["resolution"].get("width", None)
|
||||
|
||||
if height is None or width is None:
|
||||
return False
|
||||
|
||||
return self._filter_resolution(
|
||||
height,
|
||||
width,
|
||||
max_h_div_w_ratio=self.hw_aspect_threshold * aspect,
|
||||
min_h_div_w_ratio=1 / self.hw_aspect_threshold * aspect,
|
||||
)
|
||||
|
||||
def _filter_resolution(
|
||||
self, h: int, w: int, max_h_div_w_ratio: float, min_h_div_w_ratio: float
|
||||
) -> bool:
|
||||
"""Filter based on aspect ratio"""
|
||||
return (min_h_div_w_ratio <= h / w <= max_h_div_w_ratio) and (
|
||||
self.min_h_div_w_ratio <= h / w <= self.max_h_div_w_ratio
|
||||
)
|
||||
|
||||
def _validate_frame_sampling(self, batch: dict[str, Any]) -> bool:
|
||||
"""Validate frame sampling constraints"""
|
||||
|
||||
if batch["num_frames"] / batch["fps"] > self.video_length_tolerance_range * (
|
||||
self.num_frames / self.train_fps * self.speed_factor
|
||||
):
|
||||
return False
|
||||
|
||||
frame_interval = batch["fps"] / self.train_fps
|
||||
start_frame_idx = 0
|
||||
frame_indices = np.arange(
|
||||
start_frame_idx, batch["num_frames"], frame_interval
|
||||
).astype(int)
|
||||
return not (
|
||||
len(frame_indices) < self.num_frames
|
||||
and random.random() < self.drop_short_ratio
|
||||
)
|
||||
|
||||
def log_validation_stats(self):
|
||||
info = ""
|
||||
for name, count in self.filter_counts.items():
|
||||
info += f"failed in {name}: {count}, "
|
||||
info += f"number of items before filtering: {self.num_items_before_filtering}, "
|
||||
info += f"number of items after filtering: {self.num_items_after_filtering}"
|
||||
|
||||
logger.info(info)
|
||||
|
||||
|
||||
class VideoForwardBatchBuilder:
|
||||
|
||||
def __init__(self, seed: int):
|
||||
self.seed = seed
|
||||
|
||||
def __call__(self, batch: list) -> PreprocessBatch:
|
||||
forward_batch = PreprocessBatch(
|
||||
video_loader=[item["video"] for item in batch],
|
||||
video_file_name=[item["name"] for item in batch],
|
||||
height=[item["resolution"]["height"] for item in batch],
|
||||
width=[item["resolution"]["width"] for item in batch],
|
||||
fps=[item["fps"] for item in batch],
|
||||
num_frames=[item["num_frames"] for item in batch],
|
||||
prompt=[item["caption"] for item in batch],
|
||||
prompt_attention_mask=[],
|
||||
data_type=DataType.VIDEO,
|
||||
generator=torch.Generator("cpu").manual_seed(self.seed),
|
||||
)
|
||||
return forward_batch
|
||||
|
||||
|
||||
class ParquetDatasetSaver:
|
||||
"""Component for saving and writing Parquet datasets using shared parquet_io."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
flush_frequency: int,
|
||||
samples_per_file: int,
|
||||
schema: pa.Schema,
|
||||
record_creator: Callable[..., list[dict[str, Any]]],
|
||||
):
|
||||
self.flush_frequency = flush_frequency
|
||||
self.samples_per_file = samples_per_file
|
||||
self.schema = schema
|
||||
self.create_records_from_batch = record_creator
|
||||
self.num_processed_samples: int = 0
|
||||
self._writer: ParquetDatasetWriter | None = None
|
||||
|
||||
def save_and_write_parquet_batch(
|
||||
self,
|
||||
batch: PreprocessBatch,
|
||||
output_dir: str,
|
||||
extra_features: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Save and write Parquet dataset batch
|
||||
|
||||
Args:
|
||||
batch: PreprocessBatch containing video and metadata information
|
||||
output_dir: Output directory
|
||||
extra_features: Extra features
|
||||
|
||||
Returns:
|
||||
Number of processed samples
|
||||
"""
|
||||
assert isinstance(batch.latents, torch.Tensor)
|
||||
assert isinstance(batch.prompt_embeds, list)
|
||||
assert isinstance(batch.prompt_attention_mask, list)
|
||||
|
||||
# Process non-padded embeddings (if needed)
|
||||
if batch.prompt_attention_mask is not None:
|
||||
batch.prompt_embeds = self._process_non_padded_embeddings(
|
||||
batch.prompt_embeds[0], batch.prompt_attention_mask[0]
|
||||
)
|
||||
else:
|
||||
raise ValueError("prompt_attention_mask is None")
|
||||
|
||||
# Prepare batch data for Parquet dataset
|
||||
batch_data: list[dict[str, Any]] = []
|
||||
|
||||
for key in dataclasses.fields(batch):
|
||||
value = getattr(batch, key.name)
|
||||
if isinstance(value, list):
|
||||
for idx in range(len(value)):
|
||||
if isinstance(value[idx], torch.Tensor):
|
||||
value[idx] = value[idx].cpu().numpy()
|
||||
elif isinstance(value, torch.Tensor):
|
||||
value = value.cpu().numpy()
|
||||
setattr(batch, key.name, value)
|
||||
|
||||
# Create record for Parquet dataset
|
||||
records = self.create_records_from_batch(batch)
|
||||
batch_data.extend(records)
|
||||
|
||||
if batch_data:
|
||||
self.num_processed_samples += len(batch_data)
|
||||
table = records_to_table(batch_data, self.schema)
|
||||
if self._writer is None:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
self._writer = ParquetDatasetWriter(
|
||||
out_dir=output_dir, samples_per_file=self.samples_per_file
|
||||
)
|
||||
self._writer.append_table(table)
|
||||
logger.debug("Collected batch with %s samples", len(table))
|
||||
|
||||
# If flush is needed
|
||||
if self.num_processed_samples >= self.flush_frequency:
|
||||
self.flush_tables()
|
||||
|
||||
def _process_non_padded_embeddings(
|
||||
self, prompt_embeds: torch.Tensor, prompt_attention_mask: torch.Tensor
|
||||
) -> list[torch.Tensor]:
|
||||
"""Process non-padded embeddings"""
|
||||
assert isinstance(prompt_embeds, torch.Tensor)
|
||||
assert isinstance(prompt_attention_mask, torch.Tensor)
|
||||
assert prompt_embeds.shape[0] == prompt_attention_mask.shape[0]
|
||||
|
||||
# Get sequence lengths from attention masks (number of 1s)
|
||||
seq_lens = prompt_attention_mask.sum(dim=1)
|
||||
|
||||
non_padded_embeds = []
|
||||
|
||||
# Process each item in the batch
|
||||
for i in range(prompt_embeds.size(0)):
|
||||
seq_len = seq_lens[i].item()
|
||||
# Slice the embeddings and masks to keep only non-padding parts
|
||||
non_padded_embeds.append(prompt_embeds[i, :seq_len])
|
||||
|
||||
return non_padded_embeds
|
||||
|
||||
def flush_tables(self, write_remainder: bool = False):
|
||||
"""Flush buffered records to disk.
|
||||
|
||||
Args:
|
||||
output_dir: Directory where parquet files are written. Kept for API
|
||||
symmetry (writer already configured with this path).
|
||||
write_remainder: If True, also write any leftover rows smaller than
|
||||
``samples_per_file`` as a final small file. Useful for the last flush.
|
||||
"""
|
||||
if self._writer is None:
|
||||
return
|
||||
_ = self._writer.flush(write_remainder=write_remainder)
|
||||
# Reset processed sample count modulo samples_per_file
|
||||
remainder = self.num_processed_samples % self.samples_per_file
|
||||
self.num_processed_samples = 0 if write_remainder else remainder
|
||||
|
||||
def clean_up(self) -> None:
|
||||
"""Clean up all tables"""
|
||||
self.flush_tables(write_remainder=True)
|
||||
self._writer = None
|
||||
self.num_processed_samples = 0
|
||||
gc.collect()
|
||||
|
||||
def __del__(self):
|
||||
self.clean_up()
|
||||
|
||||
|
||||
def build_dataset(
|
||||
preprocess_config: PreprocessConfig,
|
||||
split: str,
|
||||
validator: Callable[[dict[str, Any]], bool],
|
||||
) -> Dataset:
|
||||
if preprocess_config.dataset_type == DatasetType.HF:
|
||||
dataset = load_dataset(preprocess_config.dataset_path, split=split)
|
||||
dataset = dataset.filter(validator)
|
||||
dataset = dataset.shard(num_shards=get_world_size(), index=get_world_rank())
|
||||
elif preprocess_config.dataset_type == DatasetType.MERGED:
|
||||
metadata_json_path = os.path.join(
|
||||
preprocess_config.dataset_path, "videos2caption.json"
|
||||
)
|
||||
video_folder = os.path.join(preprocess_config.dataset_path, "videos")
|
||||
dataset = load_dataset("json", data_files=metadata_json_path, split=split)
|
||||
column_names = dataset.column_names
|
||||
# rename columns to match the schema
|
||||
if "cap" in column_names:
|
||||
dataset = dataset.rename_column("cap", "caption")
|
||||
if "path" in column_names:
|
||||
dataset = dataset.rename_column("path", "name")
|
||||
|
||||
dataset = dataset.filter(validator)
|
||||
dataset = dataset.shard(num_shards=get_world_size(), index=get_world_rank())
|
||||
|
||||
# add video column
|
||||
def add_video_column(item: dict[str, Any]) -> dict[str, Any]:
|
||||
item["video"] = os.path.join(video_folder, item["name"])
|
||||
return item
|
||||
|
||||
dataset = dataset.map(add_video_column)
|
||||
if preprocess_config.video_loader_type == VideoLoaderType.TORCHCODEC:
|
||||
dataset = dataset.cast_column("video", Video())
|
||||
else:
|
||||
raise ValueError(f"Invalid dataset type: {preprocess_config.dataset_type}")
|
||||
|
||||
return dataset
|
||||
@@ -1,140 +0,0 @@
|
||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from sglang.multimodal_gen.configs.configs import PreprocessConfig
|
||||
from sglang.multimodal_gen.dataset.dataloader.record_schema import (
|
||||
basic_t2v_record_creator,
|
||||
i2v_record_creator,
|
||||
)
|
||||
from sglang.multimodal_gen.dataset.dataloader.schema import (
|
||||
pyarrow_schema_i2v,
|
||||
pyarrow_schema_t2v,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import get_world_rank
|
||||
from sglang.multimodal_gen.runtime.server_args import ServerArgs, WorkloadType
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.components import (
|
||||
ParquetDatasetSaver,
|
||||
PreprocessingDataValidator,
|
||||
VideoForwardBatchBuilder,
|
||||
build_dataset,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.workflow.workflow_base import WorkflowBase
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class PreprocessWorkflow(WorkflowBase):
|
||||
|
||||
def register_pipelines(self) -> None:
|
||||
self.add_pipeline_config("preprocess_pipeline", self.server_args)
|
||||
|
||||
def register_components(self) -> None:
|
||||
assert self.server_args.preprocess_config is not None
|
||||
preprocess_config: PreprocessConfig = self.server_args.preprocess_config
|
||||
|
||||
# raw data validator
|
||||
raw_data_validator = PreprocessingDataValidator(
|
||||
max_height=preprocess_config.max_height,
|
||||
max_width=preprocess_config.max_width,
|
||||
num_frames=preprocess_config.num_frames,
|
||||
train_fps=preprocess_config.train_fps,
|
||||
speed_factor=preprocess_config.speed_factor,
|
||||
video_length_tolerance_range=preprocess_config.video_length_tolerance_range,
|
||||
drop_short_ratio=preprocess_config.drop_short_ratio,
|
||||
)
|
||||
self.add_component("raw_data_validator", raw_data_validator)
|
||||
|
||||
# training dataset
|
||||
training_dataset = build_dataset(
|
||||
preprocess_config, split="train", validator=raw_data_validator
|
||||
)
|
||||
# we do not use collate_fn here because we use iterable-style Dataset
|
||||
# and want to keep the original type of the dataset
|
||||
training_dataloader = DataLoader(
|
||||
training_dataset,
|
||||
batch_size=preprocess_config.preprocess_video_batch_size,
|
||||
num_workers=preprocess_config.dataloader_num_workers,
|
||||
collate_fn=lambda x: x,
|
||||
)
|
||||
self.add_component("training_dataloader", training_dataloader)
|
||||
|
||||
# try to load validation dataset if it exists
|
||||
try:
|
||||
validation_dataset = build_dataset(
|
||||
preprocess_config, split="validation", validator=raw_data_validator
|
||||
)
|
||||
validation_dataloader = DataLoader(
|
||||
validation_dataset,
|
||||
batch_size=preprocess_config.preprocess_video_batch_size,
|
||||
num_workers=preprocess_config.dataloader_num_workers,
|
||||
collate_fn=lambda x: x,
|
||||
)
|
||||
except ValueError:
|
||||
logger.warning(
|
||||
"Validation dataset not found, skipping validation dataset preprocessing."
|
||||
)
|
||||
validation_dataloader = None
|
||||
|
||||
self.add_component("validation_dataloader", validation_dataloader)
|
||||
|
||||
# forward batch builder
|
||||
video_forward_batch_builder = VideoForwardBatchBuilder(
|
||||
seed=self.server_args.preprocess_config.seed
|
||||
)
|
||||
self.add_component("video_forward_batch_builder", video_forward_batch_builder)
|
||||
|
||||
# record creator
|
||||
if self.server_args.workload_type == WorkloadType.I2V:
|
||||
record_creator = i2v_record_creator
|
||||
schema = pyarrow_schema_i2v
|
||||
else:
|
||||
record_creator = basic_t2v_record_creator
|
||||
schema = pyarrow_schema_t2v
|
||||
processed_dataset_saver = ParquetDatasetSaver(
|
||||
flush_frequency=self.server_args.preprocess_config.flush_frequency,
|
||||
samples_per_file=self.server_args.preprocess_config.samples_per_file,
|
||||
schema=schema,
|
||||
record_creator=record_creator,
|
||||
)
|
||||
self.add_component("processed_dataset_saver", processed_dataset_saver)
|
||||
|
||||
def prepare_system_environment(self) -> None:
|
||||
assert self.server_args.preprocess_config is not None
|
||||
dataset_output_dir = self.server_args.preprocess_config.dataset_output_dir
|
||||
os.makedirs(dataset_output_dir, exist_ok=True)
|
||||
|
||||
validation_dataset_output_dir = os.path.join(
|
||||
dataset_output_dir, "validation_dataset", f"worker_{get_world_rank()}"
|
||||
)
|
||||
os.makedirs(validation_dataset_output_dir, exist_ok=True)
|
||||
self.validation_dataset_output_dir = validation_dataset_output_dir
|
||||
|
||||
training_dataset_output_dir = os.path.join(
|
||||
dataset_output_dir, "training_dataset", f"worker_{get_world_rank()}"
|
||||
)
|
||||
os.makedirs(training_dataset_output_dir, exist_ok=True)
|
||||
self.training_dataset_output_dir = training_dataset_output_dir
|
||||
|
||||
@classmethod
|
||||
def get_workflow_cls(cls, server_args: ServerArgs) -> "PreprocessWorkflow":
|
||||
if server_args.workload_type == WorkloadType.T2V:
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.preprocess_workflow_t2v import (
|
||||
PreprocessWorkflowT2V,
|
||||
)
|
||||
|
||||
return cast(PreprocessWorkflow, PreprocessWorkflowT2V)
|
||||
elif server_args.workload_type == WorkloadType.I2V:
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.preprocess_workflow_i2v import (
|
||||
PreprocessWorkflowI2V,
|
||||
)
|
||||
|
||||
return cast(PreprocessWorkflow, PreprocessWorkflowI2V)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Workload type: {server_args.workload_type} is not supported in preprocessing workflow."
|
||||
)
|
||||
@@ -1,70 +0,0 @@
|
||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
from sglang.multimodal_gen.dataset.preprocessing_datasets import PreprocessBatch
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.components import (
|
||||
ParquetDatasetSaver,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.preprocess_workflow import (
|
||||
PreprocessWorkflow,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from sglang.multimodal_gen.runtime.pipelines.composed_pipeline_base import (
|
||||
ComposedPipelineBase,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.components import (
|
||||
VideoForwardBatchBuilder,
|
||||
)
|
||||
|
||||
|
||||
class PreprocessWorkflowI2V(PreprocessWorkflow):
|
||||
training_dataloader: "DataLoader"
|
||||
validation_dataloader: "DataLoader"
|
||||
preprocess_pipeline: "ComposedPipelineBase"
|
||||
processed_dataset_saver: "ParquetDatasetSaver"
|
||||
video_forward_batch_builder: "VideoForwardBatchBuilder"
|
||||
|
||||
def run(self) -> None:
|
||||
# Training dataset preprocessing
|
||||
for batch in tqdm(
|
||||
self.training_dataloader,
|
||||
desc="Preprocessing training dataset",
|
||||
unit="batch",
|
||||
):
|
||||
forward_batch: PreprocessBatch = self.video_forward_batch_builder(batch)
|
||||
|
||||
forward_batch = self.preprocess_pipeline.forward(
|
||||
forward_batch, self.server_args
|
||||
)
|
||||
|
||||
self.processed_dataset_saver.save_and_write_parquet_batch(
|
||||
forward_batch, self.training_dataset_output_dir
|
||||
)
|
||||
|
||||
self.processed_dataset_saver.flush_tables()
|
||||
self.processed_dataset_saver.clean_up()
|
||||
|
||||
# Validation dataset preprocessing
|
||||
if self.validation_dataloader is not None:
|
||||
for batch in tqdm(
|
||||
self.validation_dataloader,
|
||||
desc="Preprocessing validation dataset",
|
||||
unit="batch",
|
||||
):
|
||||
forward_batch = self.video_forward_batch_builder(batch)
|
||||
|
||||
forward_batch = self.preprocess_pipeline.forward(
|
||||
forward_batch, self.server_args
|
||||
)
|
||||
|
||||
self.processed_dataset_saver.save_and_write_parquet_batch(
|
||||
forward_batch, self.validation_dataset_output_dir
|
||||
)
|
||||
self.processed_dataset_saver.flush_tables()
|
||||
self.processed_dataset_saver.clean_up()
|
||||
@@ -1,70 +0,0 @@
|
||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
from sglang.multimodal_gen.runtime.pipelines.pipeline_batch_info import PreprocessBatch
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.components import (
|
||||
ParquetDatasetSaver,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.preprocess_workflow import (
|
||||
PreprocessWorkflow,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from sglang.multimodal_gen.runtime.pipelines.composed_pipeline_base import (
|
||||
ComposedPipelineBase,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.components import (
|
||||
VideoForwardBatchBuilder,
|
||||
)
|
||||
|
||||
|
||||
class PreprocessWorkflowT2V(PreprocessWorkflow):
|
||||
training_dataloader: "DataLoader"
|
||||
validation_dataloader: Optional["DataLoader"]
|
||||
preprocess_pipeline: "ComposedPipelineBase"
|
||||
processed_dataset_saver: "ParquetDatasetSaver"
|
||||
video_forward_batch_builder: "VideoForwardBatchBuilder"
|
||||
|
||||
def run(self) -> None:
|
||||
# Training dataset preprocessing
|
||||
for batch in tqdm(
|
||||
self.training_dataloader,
|
||||
desc="Preprocessing training dataset",
|
||||
unit="batch",
|
||||
):
|
||||
forward_batch: PreprocessBatch = self.video_forward_batch_builder(batch)
|
||||
|
||||
forward_batch = self.preprocess_pipeline.forward(
|
||||
forward_batch, self.server_args
|
||||
)
|
||||
|
||||
self.processed_dataset_saver.save_and_write_parquet_batch(
|
||||
forward_batch, self.training_dataset_output_dir
|
||||
)
|
||||
|
||||
self.processed_dataset_saver.flush_tables()
|
||||
self.processed_dataset_saver.clean_up()
|
||||
|
||||
# Validation dataset preprocessing
|
||||
if self.validation_dataloader is not None:
|
||||
for batch in tqdm(
|
||||
self.validation_dataloader,
|
||||
desc="Preprocessing validation dataset",
|
||||
unit="batch",
|
||||
):
|
||||
forward_batch = self.video_forward_batch_builder(batch)
|
||||
|
||||
forward_batch = self.preprocess_pipeline.forward(
|
||||
forward_batch, self.server_args
|
||||
)
|
||||
|
||||
self.processed_dataset_saver.save_and_write_parquet_batch(
|
||||
forward_batch, self.validation_dataset_output_dir
|
||||
)
|
||||
self.processed_dataset_saver.flush_tables()
|
||||
self.processed_dataset_saver.clean_up()
|
||||
@@ -1,188 +0,0 @@
|
||||
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Optional
|
||||
|
||||
from sglang.multimodal_gen.runtime.pipelines import ComposedPipelineBase, build_pipeline
|
||||
from sglang.multimodal_gen.runtime.pipelines.pipeline_registry import PipelineType
|
||||
from sglang.multimodal_gen.runtime.server_args import ExecutionMode, ServerArgs
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class WorkflowBase(ABC):
|
||||
"""
|
||||
Abstract base class for defining video processing workflows.
|
||||
|
||||
A workflow serves as the top-level orchestrator that coordinates multiple pipelines
|
||||
and components to accomplish a specific video processing task. The workflow pattern
|
||||
provides several key benefits:
|
||||
|
||||
1. **Separation of Concerns**: Workflows separate high-level orchestration logic
|
||||
from low-level processing implementations in pipelines.
|
||||
|
||||
2. **Modularity**: Different workflows can be created for different execution modes
|
||||
(preprocess, inference, etc.) while sharing common pipeline components.
|
||||
|
||||
3. **Configuration Management**: Workflows manage the configuration and initialization
|
||||
of multiple related pipelines and components in a centralized manner.
|
||||
|
||||
4. **Environment Setup**: Workflows handle system-level setup and resource
|
||||
allocation before pipeline execution begins.
|
||||
|
||||
5. **Lifecycle Management**: Workflows control the complete lifecycle from
|
||||
initialization through execution to cleanup.
|
||||
|
||||
The workflow acts as a factory and coordinator, creating the appropriate pipelines
|
||||
based on configuration, setting up the execution environment, and orchestrating
|
||||
the overall processing flow.
|
||||
"""
|
||||
|
||||
def __init__(self, server_args: ServerArgs):
|
||||
"""
|
||||
Initialize the workflow with configuration arguments.
|
||||
|
||||
Args:
|
||||
server_args: Configuration object containing all parameters
|
||||
needed for workflow and pipeline setup.
|
||||
"""
|
||||
self.server_args = server_args
|
||||
|
||||
# TODO: pipeline_config should be: dict[str, PipelineConfig]
|
||||
# pipeline_type should be included in the PipelineConfig
|
||||
# pipeline_config[pipeline_name] = (pipeline_type, server_args)
|
||||
self._pipeline_configs: dict[str, tuple[PipelineType, ServerArgs]] = {}
|
||||
self._pipelines: dict[str, ComposedPipelineBase] = {}
|
||||
self._components: dict[str, Any] = {}
|
||||
self.register_pipelines()
|
||||
self.register_components()
|
||||
|
||||
self.prepare_system_environment()
|
||||
self.load_pipelines()
|
||||
|
||||
def load_pipelines(self) -> None:
|
||||
"""
|
||||
Create and initialize all registered pipelines.
|
||||
|
||||
This method instantiates pipeline objects from their configurations
|
||||
and makes them available as both dictionary entries and instance
|
||||
attributes for convenient access.
|
||||
"""
|
||||
for pipeline_name, pipeline_config in self._pipeline_configs.items():
|
||||
pipeline_type, server_args = pipeline_config
|
||||
pipeline = build_pipeline(server_args, pipeline_type)
|
||||
self._pipelines[pipeline_name] = pipeline
|
||||
setattr(self, pipeline_name, pipeline)
|
||||
|
||||
def add_pipeline_config(
|
||||
self, pipeline_name: str, pipeline_config: tuple[PipelineType, ServerArgs]
|
||||
) -> None:
|
||||
"""
|
||||
Register a pipeline configuration for later instantiation.
|
||||
|
||||
Args:
|
||||
pipeline_name: Unique identifier for the pipeline.
|
||||
pipeline_config: Tuple containing the pipeline type and
|
||||
configuration arguments.
|
||||
"""
|
||||
self._pipeline_configs[pipeline_name] = pipeline_config
|
||||
|
||||
def add_component(self, component_name: str, component: Any) -> None:
|
||||
"""
|
||||
Register a component instance with the workflow.
|
||||
|
||||
Components are auxiliary objects that may be shared across pipelines
|
||||
or used for workflow-level functionality (e.g., databases, caches,
|
||||
external services).
|
||||
|
||||
Args:
|
||||
component_name: Unique identifier for the component.
|
||||
component: The component instance to register.
|
||||
"""
|
||||
self._components[component_name] = component
|
||||
setattr(self, component_name, component)
|
||||
|
||||
def get_component(self, component_name: str) -> Any:
|
||||
"""
|
||||
Retrieve a registered component by name.
|
||||
|
||||
Args:
|
||||
component_name: The name of the component to retrieve.
|
||||
|
||||
Returns:
|
||||
The component instance.
|
||||
"""
|
||||
return self._components[component_name]
|
||||
|
||||
@abstractmethod
|
||||
def register_components(self) -> None:
|
||||
"""
|
||||
Register workflow-specific components.
|
||||
|
||||
Subclasses must implement this method to register any components
|
||||
needed for their specific workflow (e.g., databases, external APIs,
|
||||
shared resources).
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def register_pipelines(self) -> None:
|
||||
"""
|
||||
Register workflow-specific pipelines.
|
||||
|
||||
Subclasses must implement this method to define which pipelines
|
||||
are needed for their specific workflow and how they should be
|
||||
configured.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def prepare_system_environment(self) -> None:
|
||||
"""
|
||||
Prepare the system environment for workflow execution.
|
||||
|
||||
Subclasses must implement this method to handle any system-level
|
||||
setup required before pipeline execution (e.g., GPU initialization,
|
||||
temporary directories, resource allocation).
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
"""
|
||||
Execute the main workflow logic.
|
||||
|
||||
Subclasses must implement this method to define the specific
|
||||
execution flow for their workflow, coordinating the registered
|
||||
pipelines and components to accomplish the desired task.
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_workflow_cls(cls, server_args: ServerArgs) -> Optional["WorkflowBase"]:
|
||||
"""
|
||||
Factory method to get the appropriate workflow class based on execution mode.
|
||||
|
||||
This method acts as a workflow factory, returning the appropriate
|
||||
workflow class implementation based on the specified execution mode
|
||||
in the configuration arguments.
|
||||
|
||||
Args:
|
||||
server_args: Configuration object containing the execution mode
|
||||
and other parameters.
|
||||
|
||||
Returns:
|
||||
The appropriate workflow class for the specified execution mode,
|
||||
or None if no workflow is available for the given mode.
|
||||
"""
|
||||
if server_args.mode == ExecutionMode.PREPROCESS:
|
||||
from sglang.multimodal_gen.runtime.workflow.preprocess.preprocess_workflow import (
|
||||
PreprocessWorkflow,
|
||||
)
|
||||
|
||||
return PreprocessWorkflow.get_workflow_cls(server_args)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Execution mode: {server_args.mode} is not supported in workflow."
|
||||
)
|
||||
Reference in New Issue
Block a user