chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,394 @@
# Standard library imports
import logging
import time
from typing import Dict, Tuple, Iterator, Generator, Optional, Union
# Third-party imports
import torch
import torchvision
import pyarrow
import ray
import ray.train
from ray.data.collate_fn import ArrowBatchCollateFn, CollateFn
from concurrent.futures import ThreadPoolExecutor
from ray.data.dataset import TorchDeviceType
# Local imports
from benchmark_factory import BenchmarkFactory
from config import BenchmarkConfig, DataloaderType, ImageClassificationConfig
from dataloader_factory import BaseDataLoaderFactory
from torch_dataloader_factory import TorchDataLoaderFactory
from ray_dataloader_factory import RayDataLoaderFactory
from logger_utils import ContextLoggerAdapter
logger = ContextLoggerAdapter(logging.getLogger(__name__))
def mock_dataloader(
num_batches: int = 64, batch_size: int = 32
) -> Generator[Tuple[torch.Tensor, torch.Tensor], None, None]:
"""Generate mock image and label tensors for testing.
Args:
num_batches: Number of batches to generate
batch_size: Number of samples per batch
Yields:
Tuple of (image_tensor, label_tensor) for each batch
"""
device = ray.train.torch.get_device()
images = torch.randn(batch_size, 3, 224, 224).to(device)
labels = torch.randint(0, 1000, (batch_size,)).to(device)
for _ in range(num_batches):
yield images, labels
class ImageClassificationTorchDataLoaderFactory(TorchDataLoaderFactory):
"""Factory for creating PyTorch DataLoaders for image classification tasks.
Features:
- Distributed file reading with round-robin worker distribution
- Device transfer and error handling for data batches
- Configurable row limits per worker for controlled processing
- Performance monitoring and logging
"""
def __init__(self, benchmark_config: BenchmarkConfig):
super().__init__(benchmark_config)
def _calculate_rows_per_worker(
self, total_rows: int, num_workers: int
) -> Optional[int]:
"""Calculate rows per worker for balanced data distribution.
Args:
total_rows: Total rows to process across all workers (-1 for unlimited)
num_workers: Total workers (Ray workers × Torch workers)
Returns:
Rows per worker or None if no limit. Each worker gets at least 1 row.
"""
if total_rows < 0:
return None
if num_workers == 0:
return total_rows
return max(1, total_rows // num_workers)
def _get_worker_row_limits(self) -> Tuple[Optional[int], Optional[int]]:
"""Calculate row limits per worker for training and validation.
Returns:
Tuple of (training_rows_per_worker, validation_rows_per_worker)
"""
dataloader_config = self.get_dataloader_config()
num_workers = max(1, dataloader_config.num_torch_workers)
total_workers = self.benchmark_config.num_workers * num_workers
limit_training_rows_per_worker = self._calculate_rows_per_worker(
self.get_dataloader_config().limit_training_rows, total_workers
)
limit_validation_rows_per_worker = self._calculate_rows_per_worker(
self.get_dataloader_config().limit_validation_rows, total_workers
)
return limit_training_rows_per_worker, limit_validation_rows_per_worker
def create_batch_iterator(
self, dataloader: torch.utils.data.DataLoader, device: torch.device
) -> Iterator[Tuple[torch.Tensor, torch.Tensor]]:
"""Create iterator with device transfer and error handling.
Args:
dataloader: PyTorch DataLoader to iterate over
device: Target device for tensor transfer
Returns:
Iterator yielding (image_tensor, label_tensor) on target device
"""
worker_rank = ray.train.get_context().get_world_rank()
logger.info(f"Worker {worker_rank}: Starting batch iteration")
try:
last_batch_time = time.time()
for batch_idx, batch in enumerate(dataloader):
try:
# Monitor batch processing delays
current_time = time.time()
time_since_last_batch = current_time - last_batch_time
if time_since_last_batch > 10:
logger.warning(
f"Worker {worker_rank}: Long delay ({time_since_last_batch:.2f}s) "
f"between batches {batch_idx-1} and {batch_idx}"
)
# Process and transfer batch to device
images, labels = batch
logger.info(
f"Worker {worker_rank}: Processing batch {batch_idx} (shape: {images.shape}, "
f"time since last: {time_since_last_batch:.2f}s)"
)
# Transfer tensors to target device
transfer_start = time.time()
dataloader_config = self.get_dataloader_config()
images = images.to(
device, non_blocking=dataloader_config.torch_non_blocking
)
labels = labels.to(
device, non_blocking=dataloader_config.torch_non_blocking
)
transfer_time = time.time() - transfer_start
# Monitor device transfer performance
if transfer_time > 5:
logger.warning(
f"Worker {worker_rank}: Slow device transfer ({transfer_time:.2f}s) "
f"for batch {batch_idx}"
)
logger.info(
f"Worker {worker_rank}: Completed device transfer for batch {batch_idx} in "
f"{transfer_time:.2f}s"
)
last_batch_time = time.time()
yield images, labels
except Exception as e:
logger.error(
f"Worker {worker_rank}: Error processing batch {batch_idx}: {str(e)}",
exc_info=True,
)
raise
except Exception as e:
logger.error(
f"Worker {worker_rank}: Error in batch iterator: {str(e)}",
exc_info=True,
)
raise
class CustomArrowCollateFn(ArrowBatchCollateFn):
"""Custom collate function for converting Arrow batches to PyTorch tensors."""
_DEFAULT_NUM_WORKERS = 4
def __init__(
self,
dtypes: Optional[Union["torch.dtype", Dict[str, "torch.dtype"]]] = None,
device: Optional["TorchDeviceType"] = None,
pin_memory: bool = False,
num_workers: int = _DEFAULT_NUM_WORKERS,
):
"""Initialize the collate function.
Args:
dtypes: Optional torch dtype(s) for the tensors
device: Optional device to place tensors on
pin_memory: Whether to pin the memory of the created tensors
num_workers: Number of worker threads for parallel tensor conversion
Defaults to `_DEFAULT_NUM_WORKERS`.
"""
import torch
self.dtypes = dtypes
if isinstance(device, (str, int)):
self.device = torch.device(device)
else:
self.device = device
self.pin_memory = pin_memory
self.num_workers = num_workers
self._threadpool: Optional[ThreadPoolExecutor] = None
def __del__(self):
"""Clean up threadpool on destruction."""
if getattr(self, "_threadpool", None):
self._threadpool.shutdown(wait=False)
def __call__(self, batch: "pyarrow.Table") -> Tuple[torch.Tensor, torch.Tensor]:
"""Convert an Arrow batch to PyTorch tensors.
Args:
batch: PyArrow Table to convert
Returns:
Tuple of (image_tensor, label_tensor)
"""
from ray.data.util.torch_utils import (
arrow_batch_to_tensors,
)
if self.num_workers > 0 and self._threadpool is None:
self._threadpool = ThreadPoolExecutor(max_workers=self.num_workers)
# For GPU transfer, we can skip the combining chunked arrays. This is because
# we can convert the chunked arrays to corresponding numpy format and then to
# Tensors and transfer the corresponding list of Tensors to GPU directly.
# However, for CPU transfer, we need to combine the chunked arrays first
# before converting to numpy format and then to Tensors.
combine_chunks = self.device is not None and self.device.type == "cpu"
tensors = arrow_batch_to_tensors(
batch,
dtypes=self.dtypes,
combine_chunks=combine_chunks,
pin_memory=self.pin_memory,
threadpool=self._threadpool,
)
return tensors["image"], tensors["label"]
class ImageClassificationRayDataLoaderFactory(RayDataLoaderFactory):
"""Factory for creating Ray DataLoader for image classification tasks."""
def __init__(self, benchmark_config: BenchmarkConfig):
super().__init__(benchmark_config)
def _get_collate_fn(self) -> Optional[CollateFn]:
return CustomArrowCollateFn(
device=ray.train.torch.get_device(),
pin_memory=self.get_dataloader_config().ray_data_pin_memory,
)
class ImageClassificationMockDataLoaderFactory(BaseDataLoaderFactory):
"""Factory for creating mock dataloaders for testing.
Provides mock implementations of training and validation dataloaders
that generate random image and label tensors.
"""
def get_train_dataloader(
self,
) -> Generator[Tuple[torch.Tensor, torch.Tensor], None, None]:
"""Get mock training dataloader.
Returns:
Generator yielding (image_tensor, label_tensor) batches
"""
dataloader_config = self.get_dataloader_config()
return mock_dataloader(
num_batches=1024, batch_size=dataloader_config.train_batch_size
)
def get_val_dataloader(
self,
) -> Generator[Tuple[torch.Tensor, torch.Tensor], None, None]:
"""Get mock validation dataloader.
Returns:
Generator yielding (image_tensor, label_tensor) batches
"""
dataloader_config = self.get_dataloader_config()
return mock_dataloader(
num_batches=512, batch_size=dataloader_config.validation_batch_size
)
def get_imagenet_data_dirs(task_config: ImageClassificationConfig) -> Dict[str, str]:
"""Returns a dict with the root imagenet dataset directories for train/val/test,
corresponding to the data format and local/s3 dataset location."""
from image_classification.imagenet import IMAGENET_LOCALFS_SPLIT_DIRS
from image_classification.jpeg.imagenet import (
IMAGENET_JPEG_SPLIT_S3_DIRS,
)
from image_classification.parquet.imagenet import (
IMAGENET_PARQUET_SPLIT_S3_DIRS,
IMAGENET_PARQUET_SPLIT_1T_S3_DIRS,
)
from image_classification.s3_url.imagenet import (
IMAGENET_S3_URL_SPLIT_DIRS,
)
data_format = task_config.image_classification_data_format
if task_config.image_classification_local_dataset:
return IMAGENET_LOCALFS_SPLIT_DIRS
if data_format == ImageClassificationConfig.ImageFormat.JPEG:
return IMAGENET_JPEG_SPLIT_S3_DIRS
elif data_format == ImageClassificationConfig.ImageFormat.PARQUET:
if task_config.image_classification_use_1t_dataset:
return IMAGENET_PARQUET_SPLIT_1T_S3_DIRS
return IMAGENET_PARQUET_SPLIT_S3_DIRS
elif data_format == ImageClassificationConfig.ImageFormat.S3_URL:
return IMAGENET_S3_URL_SPLIT_DIRS
else:
raise ValueError(f"Unknown data format: {data_format}")
class ImageClassificationFactory(BenchmarkFactory):
def get_dataloader_factory(self) -> BaseDataLoaderFactory:
dataloader_type = self.benchmark_config.dataloader_type
task_config = self.benchmark_config.task_config
assert isinstance(task_config, ImageClassificationConfig)
data_dirs = get_imagenet_data_dirs(task_config)
data_format = task_config.image_classification_data_format
if dataloader_type == DataloaderType.MOCK:
return ImageClassificationMockDataLoaderFactory(self.benchmark_config)
elif dataloader_type == DataloaderType.RAY_DATA:
if data_format == ImageClassificationConfig.ImageFormat.JPEG:
from image_classification.jpeg.factory import (
ImageClassificationJpegRayDataLoaderFactory,
)
return ImageClassificationJpegRayDataLoaderFactory(
self.benchmark_config, data_dirs
)
elif data_format == ImageClassificationConfig.ImageFormat.PARQUET:
from image_classification.parquet.factory import (
ImageClassificationParquetRayDataLoaderFactory,
)
return ImageClassificationParquetRayDataLoaderFactory(
self.benchmark_config, data_dirs
)
elif data_format == ImageClassificationConfig.ImageFormat.S3_URL:
# NOTE: This format downloads images via ray data expressions,
# which is less efficient than native Ray Data S3 reading (JPEG format or Parquet format).
# Use this primarily for testing the S3 URL download pattern.
from image_classification.s3_url.factory import (
ImageClassificationS3UrlRayDataLoaderFactory,
)
return ImageClassificationS3UrlRayDataLoaderFactory(
self.benchmark_config, data_dirs
)
elif dataloader_type == DataloaderType.TORCH:
if data_format == ImageClassificationConfig.ImageFormat.JPEG:
from image_classification.jpeg.factory import (
ImageClassificationJpegTorchDataLoaderFactory,
)
return ImageClassificationJpegTorchDataLoaderFactory(
self.benchmark_config, data_dirs
)
elif data_format == ImageClassificationConfig.ImageFormat.PARQUET:
from image_classification.parquet.factory import (
ImageClassificationParquetTorchDataLoaderFactory,
)
return ImageClassificationParquetTorchDataLoaderFactory(
self.benchmark_config, data_dirs
)
raise ValueError(
f"Invalid dataloader configuration: {dataloader_type}\n"
f"{task_config}\n{self.benchmark_config.dataloader_config}"
)
def get_model(self) -> torch.nn.Module:
return torchvision.models.resnet50(weights=None)
def get_loss_fn(self) -> torch.nn.Module:
return torch.nn.CrossEntropyLoss()