feat(dataset): harden parquet streaming loader

This commit is contained in:
H1yori233
2026-07-22 21:29:43 -07:00
parent 051d21958d
commit 33eb7ea13c
5 changed files with 232 additions and 138 deletions
+37 -29
View File
@@ -2,17 +2,10 @@
from torchvision import transforms
from torchvision.transforms import Lambda
from fastvideo.dataset.parquet_dataset_map_style import (
build_parquet_map_style_dataloader)
from fastvideo.dataset.parquet_dataset_streaming_style import (
LatentsParquetStreamingDataset,
build_parquet_streaming_style_dataloader,
)
from fastvideo.dataset.ltx2_precomputed_dataset import (
build_ltx2_precomputed_dataloader, LTX2PrecomputedDataset)
from fastvideo.dataset.parquet_dataset_map_style import build_parquet_map_style_dataloader
from fastvideo.dataset.ltx2_precomputed_dataset import build_ltx2_precomputed_dataloader, LTX2PrecomputedDataset
from fastvideo.dataset.preprocessing_datasets import VideoCaptionMergedDataset, TextDataset
from fastvideo.dataset.transform import (CenterCropResizeVideo, Normalize255,
TemporalRandomCrop)
from fastvideo.dataset.transform import CenterCropResizeVideo, Normalize255, TemporalRandomCrop
from fastvideo.dataset.validation_dataset import ValidationDataset
@@ -25,27 +18,31 @@ def getdataset(args) -> VideoCaptionMergedDataset:
resize = [
CenterCropResizeVideo((args.max_height, args.max_width)),
]
transform = transforms.Compose([
# Normalize255(),
*resize,
])
transform_topcrop = transforms.Compose([
Normalize255(),
*resize_topcrop,
norm_fun,
])
return VideoCaptionMergedDataset(data_merge_path=args.data_merge_path,
args=args,
transform=transform,
temporal_sample=temporal_sample,
transform_topcrop=transform_topcrop,
seed=args.seed)
transform = transforms.Compose(
[
# Normalize255(),
*resize,
]
)
transform_topcrop = transforms.Compose(
[
Normalize255(),
*resize_topcrop,
norm_fun,
]
)
return VideoCaptionMergedDataset(
data_merge_path=args.data_merge_path,
args=args,
transform=transform,
temporal_sample=temporal_sample,
transform_topcrop=transform_topcrop,
seed=args.seed,
)
def gettextdataset(args) -> TextDataset:
return TextDataset(data_merge_path=args.data_merge_path,
args=args,
seed=args.seed)
return TextDataset(data_merge_path=args.data_merge_path, args=args, seed=args.seed)
__all__ = [
@@ -58,3 +55,14 @@ __all__ = [
"VideoCaptionMergedDataset",
"TextDataset",
]
def __getattr__(name: str):
if name in {
"LatentsParquetStreamingDataset",
"build_parquet_streaming_style_dataloader",
}:
from fastvideo.dataset import parquet_dataset_streaming_style
return getattr(parquet_dataset_streaming_style, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -15,7 +15,8 @@ import time
from fastvideo.dataset.dataloader.schema import pyarrow_schema_t2v
from fastvideo.dataset.parquet_dataset_streaming_style import (
LatentsParquetStreamingDataset, )
LatentsParquetStreamingDataset,
)
def main() -> None:
@@ -26,8 +27,7 @@ def main() -> None:
parser.add_argument("--read-batch-size", type=int, default=4)
parser.add_argument("--batches", type=int, default=4)
args = parser.parse_args()
manifest_path = args.manifest_path or os.path.join(
tempfile.gettempdir(), "fastvideo-streaming-benchmark.json")
manifest_path = args.manifest_path or os.path.join(tempfile.gettempdir(), "fastvideo-streaming-benchmark.json")
dataset = LatentsParquetStreamingDataset(
args.data_path,
args.batch_size,
@@ -44,13 +44,15 @@ def main() -> None:
if index + 1 >= args.batches:
break
elapsed = time.perf_counter() - started
print({
"batches": index + 1,
"rows": rows,
"seconds": elapsed,
"rows_per_second": rows / elapsed,
"projected_columns": len(pyarrow_schema_t2v.names),
})
print(
{
"batches": index + 1,
"rows": rows,
"seconds": elapsed,
"rows_per_second": rows / elapsed,
"projected_columns": len(pyarrow_schema_t2v.names),
}
)
if __name__ == "__main__":
@@ -8,14 +8,12 @@ the requested T2V columns, and consumes each assigned row group sequentially.
from __future__ import annotations
import fcntl
import hashlib
import json
import os
import random
import tempfile
from collections.abc import Iterator
from pathlib import Path
from typing import Any
import pyarrow as pa
@@ -25,13 +23,13 @@ from torch.utils.data import IterableDataset, get_worker_info
from torchdata.stateful_dataloader import StatefulDataLoader
from fastvideo.dataset.utils import collate_rows_from_parquet_schema
from fastvideo.distributed import (get_sp_world_size, get_world_rank,
get_world_size)
from fastvideo.distributed import get_sp_world_size, get_world_rank, get_world_size
from fastvideo.logger import init_logger
logger = init_logger(__name__)
_MANIFEST_VERSION = 1
_MANIFEST_VERSION = 2
_STATE_VERSION = 2
def _barrier() -> None:
@@ -47,16 +45,54 @@ def _assert_user_owned_manifest(dataset_root: str, manifest_path: str) -> None:
manifest_parent = _real(os.path.dirname(manifest_path) or ".")
if os.path.commonpath([dataset_root, manifest_parent]) == dataset_root:
raise ValueError(
"streaming_manifest_path must be outside the read-only dataset "
f"root ({dataset_root}); got {manifest_path}")
f"streaming_manifest_path must be outside the read-only dataset root ({dataset_root}); got {manifest_path}"
)
def _manifest_fingerprint(row_groups: list[dict[str, Any]]) -> str:
payload = json.dumps(row_groups, sort_keys=True,
separators=(",", ":")).encode("utf-8")
payload = json.dumps(row_groups, sort_keys=True, separators=(",", ":")).encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def _manifest_file_path(dataset_root: str, relative_path: str) -> str:
file_path = _real(os.path.join(dataset_root, relative_path))
if os.path.commonpath([dataset_root, file_path]) != dataset_root:
raise ValueError(f"Manifest file escapes dataset root: {relative_path!r}")
return file_path
def _manifest_matches_source(
dataset_root: str,
manifest: dict[str, Any],
) -> bool:
"""Validate cached metadata without reopening every Parquet footer."""
row_groups = manifest.get("row_groups")
if not isinstance(row_groups, list) or not row_groups:
return False
expected_sample_start = 0
stats: dict[str, os.stat_result] = {}
try:
for item in row_groups:
relative_path = str(item["file"])
rows = int(item["rows"])
row_group = int(item["row_group"])
sample_start = int(item["sample_start"])
if rows <= 0 or row_group < 0 or sample_start != expected_sample_start:
return False
expected_sample_start += rows
if relative_path not in stats:
file_path = _manifest_file_path(dataset_root, relative_path)
stats[relative_path] = os.stat(file_path)
stat = stats[relative_path]
if int(item["file_size"]) != stat.st_size or int(item["file_mtime_ns"]) != stat.st_mtime_ns:
return False
except (KeyError, OSError, TypeError, ValueError):
return False
return int(manifest.get("total_rows", -1)) == expected_sample_start and manifest.get(
"fingerprint"
) == _manifest_fingerprint(row_groups)
def _scan_manifest(dataset_root: str, columns: tuple[str, ...]) -> dict[str, Any]:
parquet_files: list[str] = []
for root, _, files in os.walk(dataset_root):
@@ -65,27 +101,29 @@ def _scan_manifest(dataset_root: str, columns: tuple[str, ...]) -> dict[str, Any
parquet_files.append(_real(os.path.join(root, name)))
parquet_files.sort()
if not parquet_files:
raise FileNotFoundError(
f"No parquet files found under dataset path: {dataset_root}")
raise FileNotFoundError(f"No parquet files found under dataset path: {dataset_root}")
row_groups: list[dict[str, Any]] = []
sample_start = 0
for file_path in parquet_files:
file_stat = os.stat(file_path)
parquet_file = pq.ParquetFile(file_path)
missing = sorted(set(columns) - set(parquet_file.schema_arrow.names))
if missing:
raise ValueError(
f"Parquet file {file_path} is missing projected T2V columns: "
f"{missing}")
raise ValueError(f"Parquet file {file_path} is missing projected T2V columns: {missing}")
relative_path = os.path.relpath(file_path, dataset_root)
for row_group_index in range(parquet_file.num_row_groups):
rows = parquet_file.metadata.row_group(row_group_index).num_rows
row_groups.append({
"file": relative_path,
"row_group": row_group_index,
"rows": rows,
"sample_start": sample_start,
})
row_groups.append(
{
"file": relative_path,
"row_group": row_group_index,
"rows": rows,
"sample_start": sample_start,
"file_size": file_stat.st_size,
"file_mtime_ns": file_stat.st_mtime_ns,
}
)
sample_start += rows
return {
@@ -101,8 +139,7 @@ def _scan_manifest(dataset_root: str, columns: tuple[str, ...]) -> dict[str, Any
def _write_json_atomic(path: str, payload: dict[str, Any]) -> None:
parent = os.path.dirname(path)
os.makedirs(parent, exist_ok=True)
fd, temporary_path = tempfile.mkstemp(
dir=parent, prefix=f".{os.path.basename(path)}.", suffix=".tmp")
fd, temporary_path = tempfile.mkstemp(dir=parent, prefix=f".{os.path.basename(path)}.", suffix=".tmp")
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(payload, handle, sort_keys=True)
@@ -115,8 +152,12 @@ def _write_json_atomic(path: str, payload: dict[str, Any]) -> None:
def load_or_create_streaming_manifest(
dataset_root: str, manifest_path: str,
columns: tuple[str, ...]) -> dict[str, Any]:
dataset_root: str, manifest_path: str, columns: tuple[str, ...]
) -> dict[str, Any]:
if os.name != "posix":
raise RuntimeError("The streaming Parquet loader currently requires POSIX file locks")
import fcntl
dataset_root = _real(dataset_root)
manifest_path = _real(manifest_path)
_assert_user_owned_manifest(dataset_root, manifest_path)
@@ -129,24 +170,22 @@ def load_or_create_streaming_manifest(
if os.path.exists(manifest_path):
with open(manifest_path, encoding="utf-8") as handle:
candidate = json.load(handle)
if (candidate.get("version") == _MANIFEST_VERSION
and candidate.get("dataset_root") == dataset_root
and candidate.get("columns") == list(columns)
and candidate.get("fingerprint") ==
_manifest_fingerprint(candidate.get("row_groups", []))):
if (
candidate.get("version") == _MANIFEST_VERSION
and candidate.get("dataset_root") == dataset_root
and candidate.get("columns") == list(columns)
and _manifest_matches_source(dataset_root, candidate)
):
manifest = candidate
if manifest is None:
logger.info("Building safe JSON Parquet manifest at %s",
manifest_path)
logger.info("Building safe JSON Parquet manifest at %s", manifest_path)
manifest = _scan_manifest(dataset_root, columns)
_write_json_atomic(manifest_path, manifest)
fcntl.flock(lock_handle.fileno(), fcntl.LOCK_UN)
return manifest
def _contiguous_row_group_shards(
row_groups: list[dict[str, Any]], num_shards: int
) -> list[list[dict[str, Any]]]:
def _contiguous_row_group_shards(row_groups: list[dict[str, Any]], num_shards: int) -> list[list[dict[str, Any]]]:
"""Split ordered row groups into row-balanced contiguous shards."""
if num_shards < 1:
raise ValueError("num_shards must be positive")
@@ -161,8 +200,7 @@ def _contiguous_row_group_shards(
item = row_groups[cursor]
item_rows = int(item["rows"])
items_left = len(row_groups) - cursor
if (shard_rows and shard_rows + item_rows > target
and items_left >= shards_left - 1):
if shard_rows and shard_rows + item_rows > target and items_left >= shards_left - 1:
break
shards[shard_index].append(item)
shard_rows += item_rows
@@ -205,8 +243,8 @@ class LatentsParquetStreamingDataset(IterableDataset):
raise ValueError("batch_size and read_batch_size must be positive")
if not manifest_path:
raise ValueError(
"streaming_manifest_path is required; place it in user-owned "
"storage, never inside the shared dataset")
"streaming_manifest_path is required; place it in user-owned storage, never inside the shared dataset"
)
self.path = _real(path)
self.batch_size = int(batch_size)
@@ -236,8 +274,7 @@ class LatentsParquetStreamingDataset(IterableDataset):
self.sp_group_index = self.global_rank // self.sp_world_size
if self.global_rank == 0:
load_or_create_streaming_manifest(
self.path, self.manifest_path, self.columns)
load_or_create_streaming_manifest(self.path, self.manifest_path, self.columns)
_barrier()
with open(self.manifest_path, encoding="utf-8") as handle:
self.manifest = json.load(handle)
@@ -248,14 +285,11 @@ class LatentsParquetStreamingDataset(IterableDataset):
self.manifest_fingerprint = str(self.manifest["fingerprint"])
num_shards = self.num_sp_groups * self.num_workers
self.shards = _contiguous_row_group_shards(
self.manifest["row_groups"], num_shards)
shard_rows = [sum(int(item["rows"]) for item in shard)
for shard in self.shards]
self.shards = _contiguous_row_group_shards(self.manifest["row_groups"], num_shards)
shard_rows = [sum(int(item["rows"]) for item in shard) for shard in self.shards]
self.samples_per_worker = min(shard_rows) // self.batch_size * self.batch_size
if self.samples_per_worker == 0:
raise ValueError(
"Dataset is too small for the requested DP/SP/worker layout")
raise ValueError("Dataset is too small for the requested DP/SP/worker layout")
self._epoch = 0
self._row_group_position = 0
@@ -264,8 +298,10 @@ class LatentsParquetStreamingDataset(IterableDataset):
self._loaded_state: dict[str, Any] | None = None
logger.info(
"Streaming parquet: %d rows, %d row groups, %d samples per worker",
int(self.manifest["total_rows"]), len(self.manifest["row_groups"]),
self.samples_per_worker)
int(self.manifest["total_rows"]),
len(self.manifest["row_groups"]),
self.samples_per_worker,
)
def _worker_id(self) -> int:
info = get_worker_info()
@@ -285,14 +321,12 @@ class LatentsParquetStreamingDataset(IterableDataset):
if self._loaded_state is not None:
saved_worker = int(self._loaded_state.get("worker_id", worker_id))
if saved_worker != worker_id:
raise ValueError(
f"Dataloader state belongs to worker {saved_worker}, not {worker_id}")
raise ValueError(f"Dataloader state belongs to worker {saved_worker}, not {worker_id}")
self._loaded_state = None
shard = self._worker_shard(worker_id)
rows: list[dict[str, Any]] = []
while (self._yielded_samples < self.samples_per_worker
and self._row_group_position < len(shard)):
while self._yielded_samples < self.samples_per_worker and self._row_group_position < len(shard):
item = shard[self._row_group_position]
file_path = os.path.join(self.path, item["file"])
parquet_file = pq.ParquetFile(file_path)
@@ -310,8 +344,7 @@ class LatentsParquetStreamingDataset(IterableDataset):
consumed_in_group = record_end
continue
start = max(self._row_offset - consumed_in_group, 0)
for local_index, row in enumerate(
record_batch.slice(start).to_pylist(), start=start):
for local_index, row in enumerate(record_batch.slice(start).to_pylist(), start=start):
global_offset = consumed_in_group + local_index
row["_sample_index"] = int(item["sample_start"]) + global_offset
rows.append(row)
@@ -337,8 +370,7 @@ class LatentsParquetStreamingDataset(IterableDataset):
self._row_offset = 0
if self._yielded_samples != self.samples_per_worker:
raise RuntimeError(
"Streaming shard ended before producing the balanced sample count")
raise RuntimeError("Streaming shard ended before producing the balanced sample count")
self._epoch += 1
self._row_group_position = 0
self._row_offset = 0
@@ -349,8 +381,9 @@ class LatentsParquetStreamingDataset(IterableDataset):
def state_dict(self) -> dict[str, Any]:
return {
"version": 1,
"version": _STATE_VERSION,
"manifest_fingerprint": self.manifest_fingerprint,
"topology": self._resume_topology(),
"worker_id": self._worker_id(),
"epoch": self._epoch,
"row_group_position": self._row_group_position,
@@ -359,28 +392,47 @@ class LatentsParquetStreamingDataset(IterableDataset):
}
def load_state_dict(self, state_dict: dict[str, Any]) -> None:
if int(state_dict.get("version", -1)) != _STATE_VERSION:
raise ValueError("Cannot resume: unsupported streaming dataset state")
if state_dict.get("manifest_fingerprint") != self.manifest_fingerprint:
raise ValueError("Cannot resume: streaming dataset manifest changed")
if state_dict.get("topology") != self._resume_topology():
raise ValueError("Cannot resume: streaming loader topology or sampling config changed")
self._epoch = int(state_dict["epoch"])
self._row_group_position = int(state_dict["row_group_position"])
self._row_offset = int(state_dict["row_offset"])
self._yielded_samples = int(state_dict["yielded_samples"])
self._loaded_state = dict(state_dict)
def get_validation_negative_prompt(
self) -> tuple[Any, Any, str]:
def _resume_topology(self) -> dict[str, Any]:
return {
"global_rank": self.global_rank,
"world_size": self.world_size,
"sp_world_size": self.sp_world_size,
"sp_group_index": self.sp_group_index,
"num_workers": self.num_workers,
"batch_size": self.batch_size,
"read_batch_size": self.read_batch_size,
"seed": self.seed,
"shuffle_row_groups": self.shuffle_row_groups,
"samples_per_worker": self.samples_per_worker,
}
def get_validation_negative_prompt(self) -> tuple[Any, Any, str]:
first = self.manifest["row_groups"][0]
parquet_file = pq.ParquetFile(os.path.join(self.path, first["file"]))
first_batch = next(parquet_file.iter_batches(
batch_size=1,
row_groups=[int(first["row_group"])],
columns=list(self.columns),
use_threads=False,
))
first_batch = next(
parquet_file.iter_batches(
batch_size=1,
row_groups=[int(first["row_group"])],
columns=list(self.columns),
use_threads=False,
)
)
row = first_batch.to_pylist()[0]
batch = collate_rows_from_parquet_schema(
[row], self.parquet_schema, self.text_padding_length, cfg_rate=0.0,
seed=self.seed)
[row], self.parquet_schema, self.text_padding_length, cfg_rate=0.0, seed=self.seed
)
embedding = batch["text_embedding"]
mask = batch["text_attention_mask"]
prompt = batch["info_list"][0]["prompt"]
@@ -41,15 +41,16 @@ def parquet_root(tmp_path: Path) -> Path:
root = tmp_path / "shared-read-only-dataset"
root.mkdir()
schema = pyarrow_schema_t2v.append(pa.field("unused_large_column", pa.binary()))
pq.write_table(pa.Table.from_pylist([_row(i) for i in range(12)], schema=schema),
root / "part-0.parquet", row_group_size=3)
pq.write_table(pa.Table.from_pylist([_row(i) for i in range(12, 24)], schema=schema),
root / "part-1.parquet", row_group_size=3)
pq.write_table(
pa.Table.from_pylist([_row(i) for i in range(12)], schema=schema), root / "part-0.parquet", row_group_size=3
)
pq.write_table(
pa.Table.from_pylist([_row(i) for i in range(12, 24)], schema=schema), root / "part-1.parquet", row_group_size=3
)
return root
def _patch_dist(monkeypatch: pytest.MonkeyPatch, rank: int = 0,
world_size: int = 1, sp_size: int = 1) -> None:
def _patch_dist(monkeypatch: pytest.MonkeyPatch, rank: int = 0, world_size: int = 1, sp_size: int = 1) -> None:
monkeypatch.setattr(streaming.dist, "is_initialized", lambda: True)
monkeypatch.setattr(streaming, "get_world_rank", lambda: rank)
monkeypatch.setattr(streaming, "get_world_size", lambda: world_size)
@@ -72,7 +73,8 @@ def _dataset(parquet_root: Path, cache_root: Path, **kwargs):
def test_workers_zero_projects_columns_and_writes_json_manifest(
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
_patch_dist(monkeypatch)
recorded_columns = []
original = pq.ParquetFile.iter_batches
@@ -95,7 +97,8 @@ def test_workers_zero_projects_columns_and_writes_json_manifest(
def test_state_dict_resumes_at_exact_next_batch(
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
_patch_dist(monkeypatch)
first_dataset = _dataset(parquet_root, tmp_path / "owned-cache")
iterator = iter(first_dataset)
@@ -110,7 +113,8 @@ def test_state_dict_resumes_at_exact_next_batch(
def test_odd_row_group_tails_are_carried_into_next_group(
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
_patch_dist(monkeypatch)
dataset = _dataset(parquet_root, tmp_path / "owned-cache")
ids = [item["id"] for batch in dataset for item in batch["info_list"]]
@@ -118,14 +122,13 @@ def test_odd_row_group_tails_are_carried_into_next_group(
def test_dp_sp_shards_are_identical_within_sp_and_disjoint_across_dp(
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
ids_by_rank = {}
for rank in range(4):
_patch_dist(monkeypatch, rank=rank, world_size=4, sp_size=2)
dataset = _dataset(parquet_root, tmp_path / "owned-cache")
ids_by_rank[rank] = {
item["id"] for batch in dataset for item in batch["info_list"]
}
ids_by_rank[rank] = {item["id"] for batch in dataset for item in batch["info_list"]}
assert ids_by_rank[0] == ids_by_rank[1]
assert ids_by_rank[2] == ids_by_rank[3]
assert ids_by_rank[0].isdisjoint(ids_by_rank[2])
@@ -133,27 +136,26 @@ def test_dp_sp_shards_are_identical_within_sp_and_disjoint_across_dp(
def test_equal_number_of_row_groups_and_dp_shards_stays_nonempty(
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
ids_by_rank = []
for rank in range(4):
_patch_dist(monkeypatch, rank=rank, world_size=4, sp_size=1)
dataset = _dataset(parquet_root, tmp_path / "owned-cache")
ids_by_rank.append({
item["id"] for batch in dataset for item in batch["info_list"]
})
ids_by_rank.append({item["id"] for batch in dataset for item in batch["info_list"]})
assert all(ids for ids in ids_by_rank)
assert len(set().union(*ids_by_rank)) == 24
def test_manifest_must_not_be_written_inside_dataset(
parquet_root: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def test_manifest_must_not_be_written_inside_dataset(parquet_root: Path, monkeypatch: pytest.MonkeyPatch) -> None:
_patch_dist(monkeypatch)
with pytest.raises(ValueError, match="outside the read-only dataset"):
_dataset(parquet_root, parquet_root / "cache")
def test_stateful_dataloader_restores_dataset_cursor(
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
_, loader = streaming.build_parquet_streaming_style_dataloader(
path=str(parquet_root),
batch_size=2,
@@ -182,3 +184,30 @@ def test_stateful_dataloader_restores_dataset_cursor(
resumed.load_state_dict(state)
actual = next(iter(resumed))["info_list"]
assert [item["id"] for item in actual] == [item["id"] for item in expected]
def test_resume_rejects_changed_topology(parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
_patch_dist(monkeypatch)
dataset = _dataset(parquet_root, tmp_path / "owned-cache")
next(iter(dataset))
state = dataset.state_dict()
state["topology"]["batch_size"] = 99
resumed = _dataset(parquet_root, tmp_path / "owned-cache")
with pytest.raises(ValueError, match="topology or sampling config changed"):
resumed.load_state_dict(state)
def test_manifest_is_rebuilt_when_source_file_changes(
parquet_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
_patch_dist(monkeypatch)
dataset = _dataset(parquet_root, tmp_path / "owned-cache")
old_fingerprint = dataset.manifest_fingerprint
source_file = parquet_root / "part-1.parquet"
table = pq.read_table(source_file)
pq.write_table(table, source_file, row_group_size=2)
refreshed = _dataset(parquet_root, tmp_path / "owned-cache")
assert refreshed.manifest_fingerprint != old_fingerprint
assert refreshed.manifest["total_rows"] == 24
+15 -12
View File
@@ -6,7 +6,8 @@ from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from fastvideo.train.utils.training_config import (
DataConfig, )
DataConfig,
)
def build_parquet_t2v_train_dataloader(
@@ -17,11 +18,12 @@ def build_parquet_t2v_train_dataloader(
) -> Any:
"""Build a parquet dataloader for T2V-style datasets."""
from fastvideo.dataset import (build_parquet_map_style_dataloader,
build_parquet_streaming_style_dataloader)
dataloader_type = str(getattr(data_config, "dataloader_type", "map")).strip().lower()
if dataloader_type == "streaming":
from fastvideo.dataset.parquet_dataset_streaming_style import (
build_parquet_streaming_style_dataloader,
)
_dataset, dataloader = build_parquet_streaming_style_dataloader(
data_config.data_path,
data_config.train_batch_size,
@@ -37,11 +39,11 @@ def build_parquet_t2v_train_dataloader(
)
return dataloader
if dataloader_type != "map":
raise ValueError(
f"Unsupported T2V dataloader_type: {dataloader_type!r}; "
"expected 'map' or 'streaming'")
raise ValueError(f"Unsupported T2V dataloader_type: {dataloader_type!r}; expected 'map' or 'streaming'")
_dataset, dataloader = (build_parquet_map_style_dataloader(
from fastvideo.dataset import build_parquet_map_style_dataloader
_dataset, dataloader = build_parquet_map_style_dataloader(
data_config.data_path,
data_config.train_batch_size,
num_data_workers=(data_config.dataloader_num_workers),
@@ -50,7 +52,7 @@ def build_parquet_t2v_train_dataloader(
drop_last=True,
text_padding_length=int(text_len),
seed=int(data_config.seed or 0),
))
)
return dataloader
@@ -62,9 +64,10 @@ def build_parquet_matrixgame2_train_dataloader(
"""Build a parquet dataloader for Matrix-Game 2.0 datasets."""
from fastvideo.dataset import (
build_parquet_map_style_dataloader, )
build_parquet_map_style_dataloader,
)
_dataset, dataloader = (build_parquet_map_style_dataloader(
_dataset, dataloader = build_parquet_map_style_dataloader(
data_config.data_path,
data_config.train_batch_size,
num_data_workers=(data_config.dataloader_num_workers),
@@ -73,5 +76,5 @@ def build_parquet_matrixgame2_train_dataloader(
drop_last=True,
text_padding_length=512,
seed=int(data_config.seed or 0),
))
)
return dataloader