Files
huggingface--datasets/src/datasets/iterable_dataset.py
T
wehub-resource-sync ee3943b5b1
Build documentation / build (push) Failing after 0s
CI / check_code_quality (push) Has been cancelled
CI / test (deps-latest, ubuntu-latest, integration) (push) Has been cancelled
CI / test (deps-latest, ubuntu-latest, unit) (push) Has been cancelled
CI / test (deps-latest, windows-latest, integration) (push) Has been cancelled
CI / test (deps-latest, windows-latest, unit) (push) Has been cancelled
CI / test (deps-minimum, ubuntu-latest, integration) (push) Has been cancelled
CI / test (deps-minimum, ubuntu-latest, unit) (push) Has been cancelled
CI / test (deps-minimum, windows-latest, integration) (push) Has been cancelled
CI / test (deps-minimum, windows-latest, unit) (push) Has been cancelled
CI / test_py314 (deps-latest, ubuntu-latest, unit) (push) Has been cancelled
CI / test_py314 (deps-latest, windows-latest, unit) (push) Has been cancelled
CI / test_py314_future (deps-latest, ubuntu-latest, unit) (push) Has been cancelled
CI / test_py314_future (deps-latest, windows-latest, unit) (push) Has been cancelled
Secret Leaks / trufflehog (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:32 +08:00

5401 lines
232 KiB
Python

import asyncio
import concurrent.futures
import contextlib
import inspect
import itertools
import multiprocessing.pool
import re
import sys
import tempfile
import time
from collections import Counter
from collections.abc import Iterable, Iterator
from copy import copy, deepcopy
from dataclasses import dataclass
from functools import partial
from itertools import cycle, islice
from pathlib import Path
from typing import TYPE_CHECKING, Any, BinaryIO, Callable, Optional, Union
import fsspec.asyn
import multiprocess as mp
import numpy as np
import pandas as pd
import pyarrow as pa
import pyarrow.dataset as pds
import pyarrow.parquet as pq
from huggingface_hub import (
CommitInfo,
CommitOperationAdd,
HfApi,
HfFileSystem,
HfFileSystemResolvedPath,
)
from huggingface_hub.utils import RepositoryNotFoundError
from packaging import version
from . import __version__, config
from .arrow_dataset import Dataset, DatasetInfoMixin, _push_to_bucket, _push_to_repo
from .features import Features
from .features.features import (
FeatureType,
List,
Value,
_align_features,
_check_if_features_can_be_aligned,
_fix_for_backward_compatible_features,
_visit,
cast_to_python_objects,
require_decoding,
)
from .formatting import (
ArrowFormatter,
PythonFormatter,
TableFormatter,
TensorFormatter,
get_format_type_from_alias,
get_formatter,
)
from .info import DatasetInfo
from .naming import _split_re
from .splits import NamedSplit, Split, SplitInfo
from .table import (
_batch_accumulate_arrow_table_by_columns,
_batch_arrow_table,
cast_table_to_features,
embed_table_storage,
read_schema_from_file,
table_cast,
)
from .utils import tqdm as hf_tqdm
from .utils.logging import get_logger
from .utils.py_utils import (
Literal,
convert_file_size_to_int,
iflatmap_unordered,
)
from .utils.sharding import _merge_gen_kwargs, _number_of_shards_in_gen_kwargs, _shuffle_gen_kwargs, _split_gen_kwargs
from .utils.typing import PathLike
if config.HF_HUB_VERSION >= version.parse("1.6.0"):
from huggingface_hub.errors import BucketNotFoundError
from huggingface_hub.hf_file_system import HfFileSystemResolvedBucketPath, HfFileSystemResolvedRepositoryPath
else:
BucketNotFoundError = None
HfFileSystemResolvedBucketPath = None
HfFileSystemResolvedRepositoryPath = HfFileSystemResolvedPath
if TYPE_CHECKING:
import sqlite3
import polars as pl
import sqlalchemy
import torch
from .builder import Key as BuilderKey
logger = get_logger(__name__)
Key = Union[int, str, tuple[int, int], "BuilderKey"]
def identity_func(x):
return x
def _rename_columns_fn(example: dict, column_mapping: dict[str, str]):
if any(col not in example for col in column_mapping):
raise ValueError(
f"Error when renaming {list(column_mapping)} to {list(column_mapping.values())}: columns {set(column_mapping) - set(example)} are not in the dataset."
)
if any(col in example for col in column_mapping.values()):
raise ValueError(
f"Error when renaming {list(column_mapping)} to {list(column_mapping.values())}: columns {set(example) - set(column_mapping.values())} are already in the dataset."
)
return {
new_column_name: example[original_column_name]
for original_column_name, new_column_name in column_mapping.items()
}
def add_column_fn(example: dict, idx: int, name: str, column: list[dict]):
if name in example:
raise ValueError(f"Error when adding {name}: column {name} is already in the dataset.")
return {name: column[idx]}
def _infer_features_from_batch(batch: dict[str, list], try_features: Optional[Features] = None) -> Features:
pa_table = pa.Table.from_pydict(batch)
if try_features is not None:
try:
pa_table = table_cast(pa_table, pa.schema(try_features.type))
except (TypeError, pa.ArrowInvalid, pa.ArrowNotImplementedError):
pass
return Features.from_arrow_schema(pa_table.schema)
def _examples_to_batch(examples: list[dict[str, Any]]) -> dict[str, list]:
# we order the columns by order of appearance
# to do so, we use a dict as an ordered set
cols = {col: None for example in examples for col in example}
# when an example is missing a column, we set the value to None with .get()
arrays = [[example.get(col) for example in examples] for col in cols]
return dict(zip(cols, arrays))
def _batch_to_examples(batch: dict[str, list]) -> Iterator[dict[str, Any]]:
"""Convert a batch (dict of examples) to examples list"""
n_examples = 0 if len(batch) == 0 else len(batch[next(iter(batch))])
for i in range(n_examples):
yield {col: array[i] for col, array in batch.items()}
def _convert_to_arrow(
iterable: Iterable[tuple[Key, dict]],
batch_size: int,
drop_last_batch: bool = False,
) -> Iterator[tuple[Key, pa.Table]]:
"""Convert and group examples in Arrow tables of size `batch_size`.
Args:
iterable (`Iterable[Tuple[Key, dict]]`):
An examples iterable containing tuples (example_key, example) of type (int/str, dict)
batch_size (`Optional[int]`):
Size of each sub-table to yield. If None or <= 0, yields the full table.
drop_last_batch (`bool`, defaults to `False`):
Drop the last batch if it is smaller than `batch_size`.
"""
if batch_size is None or batch_size <= 0:
yield (
"all",
pa.Table.from_pylist(cast_to_python_objects([example for _, example in iterable], only_1d_for_numpy=True)),
)
return
iterator = iter(iterable)
for key, example in iterator:
iterator_batch = islice(iterator, batch_size - 1)
key_examples_list = [(key, example)] + list(iterator_batch)
if len(key_examples_list) < batch_size and drop_last_batch:
return
keys, examples = zip(*key_examples_list)
new_key = "_".join(str(key) for key in keys)
yield new_key, pa.Table.from_pylist(cast_to_python_objects(examples, only_1d_for_numpy=True))
def shift_ex_examples_rngs(ex_iterable: "_BaseExamplesIterable", value: int) -> "_BaseExamplesIterable":
"""We need to go through the ex_iterables recursively, create a new seed and return a new iterable, then set it to the containing ex_iterable."""
def set_seed_recursively(ex_iterable):
if hasattr(ex_iterable, "shift_rngs"):
ex_iterable = ex_iterable.shift_rngs(value)
if hasattr(ex_iterable, "ex_iterable"):
ex_iterable.ex_iterable = set_seed_recursively(ex_iterable.ex_iterable)
if hasattr(ex_iterable, "ex_iterables"):
ex_iterable.ex_iterables = [set_seed_recursively(ei) for ei in ex_iterable.ex_iterables]
return ex_iterable
return set_seed_recursively(ex_iterable)
class _BaseExamplesIterable:
"""Base class for the examples iterable used by an IterableDataset"""
def __init__(self) -> None:
self._state_dict: Optional[Union[list, dict]] = None
def __iter__(self) -> Iterator[tuple[Key, dict]]:
"""An examples iterable should yield tuples (example_key, example) of type (int/str, dict)"""
raise NotImplementedError(f"{type(self)} doesn't implement __iter__ yet")
@property
def iter_arrow(self) -> Optional[Callable[[], Iterator[tuple[Key, pa.Table]]]]:
return None
@property
def is_typed(self) -> bool:
return False
@property
def features(self) -> Optional[Features]:
return None
def shuffle_data_sources(self, generator: np.random.Generator) -> "_BaseExamplesIterable":
"""
Either shuffle the shards/sources of the dataset, or propagate the shuffling to the underlying iterable.
If the order of the shards must stay fixed (when using .skip or .take for example), then this method returns self.
"""
raise NotImplementedError(f"{type(self)} doesn't implement shuffle_data_sources yet")
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "_BaseExamplesIterable":
"""Either keep only the requested shard, or propagate the request to the underlying iterable."""
raise NotImplementedError(f"{type(self)} doesn't implement shard_data_sources yet")
def reshard_data_sources(self) -> "_BaseExamplesIterable":
"""
Either reshard the shards/sources of the dataset, i.e. further split the current shards into more shards,
or propagate the resharding to the underlying iterable.
If the examples iterable can't be further resharded, then this method returns self.
"""
raise NotImplementedError(f"{type(self)} doesn't implement reshard_data_sources yet")
def split_shard_indices_by_worker(self, num_shards: int, index: int, contiguous=True) -> list[int]:
if contiguous:
div = self.num_shards // num_shards
mod = self.num_shards % num_shards
start = div * index + min(index, mod)
end = start + div + (1 if index < mod else 0)
return list(range(start, end))
else:
return list(range(index, self.num_shards, num_shards))
@property
def num_shards(self) -> int:
raise NotImplementedError(f"{type(self)} doesn't implement num_shards yet")
def _init_state_dict(self) -> dict:
raise NotImplementedError(f"{type(self)} doesn't implement _init_state_dict yet")
def load_state_dict(self, state_dict: dict) -> dict:
def _inner_load_state_dict(state, new_state):
if new_state is not None and isinstance(state, dict):
for key in new_state:
state[key] = _inner_load_state_dict(state[key], new_state[key])
return state
elif new_state is not None and isinstance(state, list):
for i in range(len(state)):
state[i] = _inner_load_state_dict(state[i], new_state[i])
return state
return deepcopy(new_state)
self._init_state_dict()
return _inner_load_state_dict(self._state_dict, state_dict)
def state_dict(self) -> dict:
if self._state_dict:
return deepcopy(self._state_dict)
raise RuntimeError("State dict is not initialized, please call ex_iterable._init_state_dict() first.")
@property
def sleep_on_threads_shutdown(self):
if hasattr(self, "_sleep_on_threads_shutdown"):
return self._sleep_on_threads_shutdown
else:
ex_iterables = [self.ex_iterable] if hasattr(self, "ex_iterable") else self.ex_iterables
return any(ex_iterable.sleep_on_threads_shutdown for ex_iterable in ex_iterables)
class ExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
generate_examples_fn: Callable[..., Iterator[tuple[Key, dict]]],
kwargs: dict,
generate_more_kwargs_fn: Optional[Callable[..., Iterator[dict]]] = None,
sleep_on_threads_shutdown: bool = False,
):
super().__init__()
self.generate_examples_fn = generate_examples_fn
self.kwargs = kwargs
# for resharding
self.generate_more_kwargs_fn = generate_more_kwargs_fn
# for threads shutdowns
self._sleep_on_threads_shutdown = sleep_on_threads_shutdown
def _init_state_dict(self) -> dict:
self._state_dict = {"shard_idx": 0, "shard_example_idx": 0, "type": self.__class__.__name__}
return self._state_dict
def __iter__(self):
shard_idx_start = self._state_dict["shard_idx"] if self._state_dict else 0
for gen_kwargs in islice(_split_gen_kwargs(self.kwargs, max_num_jobs=self.num_shards), shard_idx_start, None):
shard_example_idx_start = self._state_dict["shard_example_idx"] if self._state_dict else 0
for key_example in islice(self.generate_examples_fn(**gen_kwargs), shard_example_idx_start, None):
if self._state_dict:
self._state_dict["shard_example_idx"] += 1
yield key_example
if self._state_dict:
self._state_dict["shard_idx"] += 1
self._state_dict["shard_example_idx"] = 0
def shuffle_data_sources(self, generator: np.random.Generator) -> "ExamplesIterable":
return ExamplesIterable(
self.generate_examples_fn,
_shuffle_gen_kwargs(deepcopy(generator), self.kwargs),
self.generate_more_kwargs_fn,
self.sleep_on_threads_shutdown,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "ExamplesIterable":
"""Keep only the requested shard."""
gen_kwargs_list = _split_gen_kwargs(self.kwargs, max_num_jobs=self.num_shards)
shard_indices = self.split_shard_indices_by_worker(num_shards, index, contiguous=contiguous)
requested_gen_kwargs = _merge_gen_kwargs([gen_kwargs_list[i] for i in shard_indices])
return ExamplesIterable(
self.generate_examples_fn,
requested_gen_kwargs,
self.generate_more_kwargs_fn,
self.sleep_on_threads_shutdown,
)
def reshard_data_sources(self) -> "ExamplesIterable":
"""Split shars into more shards if possible."""
if not self.generate_more_kwargs_fn:
return ExamplesIterable(
self.generate_examples_fn, self.kwargs, self.generate_more_kwargs_fn, self.sleep_on_threads_shutdown
)
gen_kwargs_list = _split_gen_kwargs(self.kwargs, max_num_jobs=self.num_shards)
new_gen_kwargs = _merge_gen_kwargs(
[
new_gen_kwargs
for gen_kwargs in gen_kwargs_list
for new_gen_kwargs in self.generate_more_kwargs_fn(**gen_kwargs)
]
)
return ExamplesIterable(
self.generate_examples_fn, new_gen_kwargs, self.generate_more_kwargs_fn, self.sleep_on_threads_shutdown
)
@property
def num_shards(self) -> int:
return _number_of_shards_in_gen_kwargs(self.kwargs)
class ArrowExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
generate_tables_fn: Callable[..., Iterator[tuple[Key, pa.Table]]],
kwargs: dict,
generate_more_kwargs_fn: Optional[Callable[..., Iterator[dict]]] = None,
sleep_on_threads_shutdown: bool = False,
):
super().__init__()
self.generate_tables_fn = generate_tables_fn
self.kwargs = kwargs
# for resharding
self.generate_more_kwargs_fn = generate_more_kwargs_fn
# for threads shutdowns
self._sleep_on_threads_shutdown = sleep_on_threads_shutdown
@property
def iter_arrow(self):
return self._iter_arrow
def _init_state_dict(self) -> dict:
self._state_dict = {"shard_idx": 0, "shard_example_idx": 0, "type": self.__class__.__name__}
return self._state_dict
def __iter__(self):
formatter = PythonFormatter()
shard_idx_start = self._state_dict["shard_idx"] if self._state_dict else 0
for gen_kwags in islice(_split_gen_kwargs(self.kwargs, max_num_jobs=self.num_shards), shard_idx_start, None):
shard_example_idx_start = self._state_dict["shard_example_idx"] if self._state_dict else 0
shard_example_idx = 0
for key, pa_table in self.generate_tables_fn(**gen_kwags):
if shard_example_idx + len(pa_table) <= shard_example_idx_start:
shard_example_idx += len(pa_table)
continue
for pa_subtable in pa_table.to_reader(max_chunksize=config.ARROW_READER_BATCH_SIZE_IN_DATASET_ITER):
formatted_batch = formatter.format_batch(pa_subtable)
for example in _batch_to_examples(formatted_batch):
if shard_example_idx >= shard_example_idx_start:
if self._state_dict:
self._state_dict["shard_example_idx"] += 1
yield key, example
shard_example_idx += 1
if self._state_dict:
self._state_dict["shard_idx"] += 1
self._state_dict["shard_example_idx"] = 0
def _iter_arrow(self):
shard_idx_start = self._state_dict["shard_idx"] if self._state_dict else 0
for gen_kwags in islice(_split_gen_kwargs(self.kwargs, max_num_jobs=self.num_shards), shard_idx_start, None):
shard_example_idx_start = self._state_dict["shard_example_idx"] if self._state_dict else 0
shard_example_idx = 0
for key, pa_table in self.generate_tables_fn(**gen_kwags):
shard_example_idx += len(pa_table)
if shard_example_idx <= shard_example_idx_start:
continue
if self._state_dict:
self._state_dict["shard_example_idx"] += len(pa_table)
yield key, pa_table
if self._state_dict:
self._state_dict["shard_idx"] += 1
self._state_dict["shard_example_idx"] = 0
def shuffle_data_sources(self, generator: np.random.Generator) -> "ArrowExamplesIterable":
return ArrowExamplesIterable(
self.generate_tables_fn,
_shuffle_gen_kwargs(deepcopy(generator), self.kwargs),
self.generate_more_kwargs_fn,
self.sleep_on_threads_shutdown,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "ArrowExamplesIterable":
"""Keep only the requested shard."""
gen_kwargs_list = _split_gen_kwargs(self.kwargs, max_num_jobs=self.num_shards)
shard_indices = self.split_shard_indices_by_worker(num_shards, index, contiguous=contiguous)
requested_gen_kwargs = _merge_gen_kwargs([gen_kwargs_list[i] for i in shard_indices])
return ArrowExamplesIterable(
self.generate_tables_fn, requested_gen_kwargs, self.generate_more_kwargs_fn, self.sleep_on_threads_shutdown
)
def reshard_data_sources(self) -> "ArrowExamplesIterable":
"""Split shars into more shards if possible."""
if not self.generate_more_kwargs_fn:
return ArrowExamplesIterable(self.generate_tables_fn, self.kwargs, self.generate_more_kwargs_fn)
gen_kwargs_list = _split_gen_kwargs(self.kwargs, max_num_jobs=self.num_shards)
new_gen_kwargs = _merge_gen_kwargs(
[
new_gen_kwargs
for gen_kwargs in gen_kwargs_list
for new_gen_kwargs in self.generate_more_kwargs_fn(**gen_kwargs)
]
)
return ArrowExamplesIterable(
self.generate_tables_fn, new_gen_kwargs, self.generate_more_kwargs_fn, self.sleep_on_threads_shutdown
)
@property
def num_shards(self) -> int:
return _number_of_shards_in_gen_kwargs(self.kwargs)
class RebatchedArrowExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
batch_size: Optional[int],
drop_last_batch: bool = False,
force_convert_to_arrow: bool = False,
):
super().__init__()
self.ex_iterable = ex_iterable
self.batch_size = batch_size
self.drop_last_batch = drop_last_batch
self.force_convert_to_arrow = force_convert_to_arrow
@property
def iter_arrow(self):
return self._iter_arrow if self.ex_iterable.iter_arrow or self.force_convert_to_arrow else None
@property
def is_typed(self):
return self.ex_iterable.is_typed
@property
def features(self):
return self.ex_iterable.features
def _init_state_dict(self) -> dict:
self._state_dict = {
"examples_iterable": self.ex_iterable._init_state_dict(),
"previous_state": None,
"batch_idx": 0,
"num_chunks_since_previous_state": 0,
"cropped_chunk_length": 0,
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
yield from self.ex_iterable
def _iter_arrow(self) -> Iterator[tuple[Key, pa.Table]]:
"""Iterate over sub-tables of size `batch_size`."""
if self._state_dict and self._state_dict["previous_state"]:
self.ex_iterable.load_state_dict(self._state_dict["previous_state"])
if self.ex_iterable.iter_arrow:
iterator = self.ex_iterable.iter_arrow()
elif self.force_convert_to_arrow:
iterator = _convert_to_arrow(self.ex_iterable, batch_size=1)
else:
raise RuntimeError(
"_iter_arrow is not available in RebatchedArrowExamplesIterable, use an examples iterable that implements _iter_arrow() or pass force_convert_to_arrow=True"
)
if self.batch_size is None or self.batch_size <= 0:
if self._state_dict and self._state_dict["batch_idx"] > 0:
return
all_pa_table = pa.concat_tables([pa_table for _, pa_table in iterator])
if self._state_dict:
self._state_dict["batch_idx"] = 1
yield "all", all_pa_table
return
keys_buffer = []
chunks_buffer = []
chunks_buffer_size = 0
num_chunks_to_skip = self._state_dict["num_chunks_since_previous_state"] if self._state_dict else 0
chunk_length_to_crop = self._state_dict["cropped_chunk_length"] if self._state_dict else 0
if self._state_dict:
previous_state = self.ex_iterable.state_dict()
self._state_dict["previous_state"] = previous_state
for key, pa_table in iterator:
for num_chunks_since_previous_state, chunk in enumerate(pa_table.to_reader(max_chunksize=self.batch_size)):
if num_chunks_to_skip > 1:
num_chunks_to_skip -= 1
continue
elif num_chunks_to_skip == 1 and chunk_length_to_crop == 0:
num_chunks_to_skip -= 1
continue
elif num_chunks_to_skip == 1 and chunk_length_to_crop > 0:
chunk = chunk.slice(chunk_length_to_crop, len(chunk) - chunk_length_to_crop)
num_chunks_to_skip = 0
chunk_length_to_crop = 0
if len(chunk) == 0:
continue
if chunks_buffer_size + len(chunk) < self.batch_size:
keys_buffer.append(key)
chunks_buffer.append(chunk)
chunks_buffer_size += len(chunk)
continue
elif chunks_buffer_size + len(chunk) == self.batch_size:
keys_buffer.append(key)
chunks_buffer.append(chunk)
new_key = "_".join(str(_key) for _key in keys_buffer)
if self._state_dict:
self._state_dict["batch_idx"] += 1
self._state_dict["num_chunks_since_previous_state"] += len(chunks_buffer)
self._state_dict["cropped_chunk_length"] = 0
yield new_key, pa.Table.from_batches(chunks_buffer)
keys_buffer = []
chunks_buffer = []
chunks_buffer_size = 0
if self._state_dict:
self._state_dict["previous_state"] = previous_state
self._state_dict["num_chunks_since_previous_state"] = num_chunks_since_previous_state + 1
else:
cropped_chunk_length = self.batch_size - chunks_buffer_size
keys_buffer.append(f"{key}[:{cropped_chunk_length}]")
chunks_buffer.append(chunk.slice(0, cropped_chunk_length))
new_key = "_".join(str(_key) for _key in keys_buffer)
if self._state_dict:
self._state_dict["batch_idx"] += 1
self._state_dict["num_chunks_since_previous_state"] += len(chunks_buffer)
self._state_dict["cropped_chunk_length"] = cropped_chunk_length
yield new_key, pa.Table.from_batches(chunks_buffer)
keys_buffer = [f"{key}[{cropped_chunk_length}:]"]
chunks_buffer = [chunk.slice(cropped_chunk_length, len(chunk) - cropped_chunk_length)]
chunks_buffer_size = len(chunk) - cropped_chunk_length
if self._state_dict:
self._state_dict["previous_state"] = previous_state
self._state_dict["num_chunks_since_previous_state"] = num_chunks_since_previous_state
if self._state_dict:
previous_state = self.ex_iterable.state_dict()
if not self.drop_last_batch and chunks_buffer:
new_key = "_".join(str(_key) for _key in keys_buffer)
if self._state_dict:
self._state_dict["previous_state"] = previous_state
self._state_dict["batch_idx"] += 1
self._state_dict["num_chunks_since_previous_state"] = 0
self._state_dict["cropped_chunk_length"] = 0
yield new_key, pa.Table.from_batches(chunks_buffer)
def shuffle_data_sources(self, generator: np.random.Generator) -> "RebatchedArrowExamplesIterable":
return RebatchedArrowExamplesIterable(
self.ex_iterable.shuffle_data_sources(generator),
self.batch_size,
self.drop_last_batch,
self.force_convert_to_arrow,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "RebatchedArrowExamplesIterable":
return RebatchedArrowExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
self.batch_size,
self.drop_last_batch,
self.force_convert_to_arrow,
)
def reshard_data_sources(self) -> "RebatchedArrowExamplesIterable":
return RebatchedArrowExamplesIterable(
self.ex_iterable.reshard_data_sources(), self.batch_size, self.drop_last_batch, self.force_convert_to_arrow
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
class SelectColumnsIterable(_BaseExamplesIterable):
def __init__(self, ex_iterable: _BaseExamplesIterable, column_names: list[str]):
super().__init__()
self.ex_iterable = ex_iterable
self.column_names = column_names
@property
def iter_arrow(self):
if self.ex_iterable.iter_arrow:
return self._iter_arrow
@property
def is_typed(self):
return self.ex_iterable.is_typed
@property
def features(self):
return self.ex_iterable.features
def _init_state_dict(self) -> dict:
self._state_dict = self.ex_iterable._init_state_dict()
return self._state_dict
def __iter__(self):
for idx, row in self.ex_iterable:
yield idx, {c: row[c] for c in self.column_names}
def _iter_arrow(self) -> Iterator[tuple[Key, pa.Table]]:
for idx, pa_table in self.ex_iterable.iter_arrow():
if len(pa_table) > 0: # empty tables have no schema
yield idx, pa_table.select(self.column_names)
def shuffle_data_sources(self, generator: np.random.Generator) -> "SelectColumnsIterable":
return SelectColumnsIterable(self.ex_iterable.shuffle_data_sources(generator), self.column_names)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "SelectColumnsIterable":
return SelectColumnsIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous), self.column_names
)
def reshard_data_sources(self) -> "SelectColumnsIterable":
return SelectColumnsIterable(self.ex_iterable.reshard_data_sources(), self.column_names)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
class StepExamplesIterable(_BaseExamplesIterable):
def __init__(self, ex_iterable: _BaseExamplesIterable, step: int, offset: int):
super().__init__()
self.ex_iterable = ex_iterable
self.step = step
self.offset = offset
@property
def iter_arrow(self):
return self._iter_arrow if self.ex_iterable.iter_arrow else None
@property
def is_typed(self):
return self.ex_iterable.is_typed
@property
def features(self):
return self.ex_iterable.features
def _init_state_dict(self) -> dict:
self._state_dict = {
"examples_iterable": self.ex_iterable._init_state_dict(),
"stepped": 0,
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
ex_iterator = iter(self.ex_iterable)
while True:
batch = list(islice(ex_iterator, self.step))
if len(batch) > self.offset:
yield batch[self.offset]
else:
break
def _iter_arrow(self):
stepped = self._state_dict["stepped"] if self._state_dict else 0
for key, pa_table in self.ex_iterable.iter_arrow():
stepped_pa_table = pa_table.take(
pa.array(range((self.offset - stepped) % self.step, len(pa_table), self.step), type=pa.int64())
)
stepped = (stepped + len(pa_table)) % self.step
if self._state_dict:
self._state_dict["stepped"] = stepped
yield key, stepped_pa_table
def shuffle_data_sources(self, generator: np.random.Generator) -> "StepExamplesIterable":
return StepExamplesIterable(
self.ex_iterable.shuffle_data_sources(generator), step=self.step, offset=self.offset
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "StepExamplesIterable":
return StepExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
step=self.step,
offset=self.offset,
)
def reshard_data_sources(self) -> "StepExamplesIterable":
return StepExamplesIterable(
self.ex_iterable.reshard_data_sources(),
step=self.step,
offset=self.offset,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
class CyclingMultiSourcesExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
ex_iterables: list[_BaseExamplesIterable],
stopping_strategy: Literal[
"first_exhausted", "all_exhausted", "all_exhausted_without_replacement"
] = "first_exhausted",
):
super().__init__()
self.ex_iterables = ex_iterables
self.stopping_strategy = stopping_strategy
# if undersampling ("first_exhausted"), we stop as soon as one dataset is exhausted
# if oversampling ("all_exhausted"), we stop as soons as every dataset is exhausted, i.e as soon as every samples of every dataset has been visited at least once
# if sampling without replacement ("all_exhausted_without_replacement"), we stop once all samples of every dataset has been visited exactly once.
self.bool_strategy_func = (
np.all if (stopping_strategy in ("all_exhausted", "all_exhausted_without_replacement")) else np.any
)
@property
def is_typed(self):
return self.ex_iterables[0].is_typed
@property
def features(self):
return self.ex_iterables[0].features
@property
def iter_arrow(self):
# iterate on arrow tables if all ex_iterables can iterate
return self._iter_arrow if all(ex_iterable.iter_arrow for ex_iterable in self.ex_iterables) else None
def _get_indices_iterator(self):
# this is an infinite iterator to keep track of which iterator we want to pick examples from
ex_iterable_idx = self._state_dict["ex_iterable_idx"] if self._state_dict else 0
for next_ex_iterable_idx in islice(cycle(range(len(self.ex_iterables))), ex_iterable_idx + 1, None):
if self._state_dict:
self._state_dict["ex_iterable_idx"] = next_ex_iterable_idx
yield ex_iterable_idx
ex_iterable_idx = next_ex_iterable_idx
def _init_state_dict(self) -> dict:
for ex_iterable in self.ex_iterables:
ex_iterable._init_state_dict()
self._state_dict = {
"ex_iterable_idx": 0,
"previous_states": [None] * len(self.ex_iterables),
"is_exhausted": [False] * len(self.ex_iterables),
"type": self.__class__.__name__,
}
return self._state_dict
def _iter_arrow(self):
# we use this to buffer one example of each iterator to know if an iterator is exhausted
nexts = [None] * len(self.ex_iterables)
# because of that, we need to rewind 1 example when reloading the state dict
if self._state_dict:
for i in range(len(self.ex_iterables)):
if self._state_dict["previous_states"][i] is not None:
self.ex_iterables[i].load_state_dict(self._state_dict["previous_states"][i])
previous_states = [ex_iterable.state_dict() for ex_iterable in self.ex_iterables]
iterators = [ex_iterable.iter_arrow() for ex_iterable in self.ex_iterables]
# Pre-populate futures for next samples from each iterator using threads for prefetching
def fetch_next_sample(iterator):
return next(iterator, False)
# Use ThreadPoolExecutor to fetch next samples in parallel
executor = concurrent.futures.ThreadPoolExecutor(max_workers=len(self.ex_iterables))
futures = [executor.submit(fetch_next_sample, iterator) for iterator in iterators]
indices_iterator = self._get_indices_iterator()
is_exhausted = (
np.array(self._state_dict["is_exhausted"]) if self._state_dict else np.full(len(self.ex_iterables), False)
)
try:
for i in indices_iterator:
# if the stopping criteria is met, break the main for loop
if self.bool_strategy_func(is_exhausted):
break
# Skip exhausted iterators if we sample without replacement
if is_exhausted[i] and self.stopping_strategy in ["all_exhausted_without_replacement"]:
continue
# let's pick one example from the iterator at index i
# Resolve the future to get the current sample
if nexts[i] is None:
nexts[i] = futures[i].result()
if self._state_dict:
self._state_dict["previous_states"][i] = previous_states[i]
previous_states[i] = self.ex_iterables[i].state_dict()
futures[i] = executor.submit(fetch_next_sample, iterators[i])
result = nexts[i]
# Fetch the next sample for this iterator (prefetching)
nexts[i] = futures[i].result()
if self._state_dict:
self._state_dict["previous_states"][i] = previous_states[i]
previous_states[i] = self.ex_iterables[i].state_dict()
if nexts[i] is not False:
futures[i] = executor.submit(fetch_next_sample, iterators[i])
else:
# the iterator is exhausted
is_exhausted[i] = True
if self._state_dict:
self._state_dict["is_exhausted"][i] = True
# we reset it in case the stopping criteria isn't met yet
if self.stopping_strategy not in ["all_exhausted_without_replacement"]:
if self._state_dict:
self.ex_iterables[i]._init_state_dict()
previous_states[i] = self.ex_iterables[i].state_dict()
self._state_dict["previous_states"][i] = None
iterators[i] = self.ex_iterables[i].iter_arrow()
nexts[i] = None
futures[i] = executor.submit(fetch_next_sample, iterators[i])
if result is not False:
yield result
finally:
# Related to https://github.com/apache/arrow/issues/45214
for future in futures:
future.result()
while iterators:
iterator = iterators.pop()
del iterator
if any(ex_iterable.sleep_on_threads_shutdown for ex_iterable in self.ex_iterables):
time.sleep(config.SLEEP_TIME_ON_THREADS_SHUTDOWN)
def __iter__(self):
# we use this to buffer one example of each iterator to know if an iterator is exhausted
nexts = [None] * len(self.ex_iterables)
# because of that, we need to rewind 1 example when reloading the state dict
if self._state_dict:
for i in range(len(self.ex_iterables)):
if self._state_dict["previous_states"][i] is not None:
self.ex_iterables[i].load_state_dict(self._state_dict["previous_states"][i])
previous_states = [ex_iterable.state_dict() for ex_iterable in self.ex_iterables]
iterators = [iter(ex_iterable) for ex_iterable in self.ex_iterables]
# Pre-populate futures for next samples from each iterator using threads for prefetching
def fetch_next_sample(iterator):
return next(iterator, False)
# Use ThreadPoolExecutor to fetch next samples in parallel
executor = concurrent.futures.ThreadPoolExecutor(max_workers=len(self.ex_iterables))
futures = [executor.submit(fetch_next_sample, iterator) for iterator in iterators]
indices_iterator = self._get_indices_iterator()
is_exhausted = (
np.array(self._state_dict["is_exhausted"]) if self._state_dict else np.full(len(self.ex_iterables), False)
)
try:
for i in indices_iterator:
# if the stopping criteria is met, break the main for loop
if self.bool_strategy_func(is_exhausted):
break
# Skip exhausted iterators if we sample without replacement
if is_exhausted[i] and self.stopping_strategy in ["all_exhausted_without_replacement"]:
continue
# let's pick one example from the iterator at index i
# Resolve the future to get the current sample
if nexts[i] is None:
nexts[i] = futures[i].result()
if self._state_dict:
self._state_dict["previous_states"][i] = previous_states[i]
previous_states[i] = self.ex_iterables[i].state_dict()
futures[i] = executor.submit(fetch_next_sample, iterators[i])
result = nexts[i]
# Fetch the next sample for this iterator (prefetching)
nexts[i] = futures[i].result()
if self._state_dict:
self._state_dict["previous_states"][i] = previous_states[i]
previous_states[i] = self.ex_iterables[i].state_dict()
if nexts[i] is not False:
futures[i] = executor.submit(fetch_next_sample, iterators[i])
else:
# the iterator is exhausted
is_exhausted[i] = True
if self._state_dict:
self._state_dict["is_exhausted"][i] = True
# we reset it in case the stopping criteria isn't met yet
if self.stopping_strategy not in ["all_exhausted_without_replacement"]:
if self._state_dict:
self.ex_iterables[i]._init_state_dict()
previous_states[i] = self.ex_iterables[i].state_dict()
self._state_dict["previous_states"][i] = None
iterators[i] = iter(self.ex_iterables[i])
nexts[i] = None
futures[i] = executor.submit(fetch_next_sample, iterators[i])
if result is not False:
yield result
finally:
# Related to https://github.com/apache/arrow/issues/45214
for future in futures:
future.result()
while iterators:
iterator = iterators.pop()
del iterator
executor.shutdown(wait=True)
if any(ex_iterable.sleep_on_threads_shutdown for ex_iterable in self.ex_iterables):
time.sleep(config.SLEEP_TIME_ON_THREADS_SHUTDOWN)
def shuffle_data_sources(self, generator: np.random.Generator) -> "CyclingMultiSourcesExamplesIterable":
"""Shuffle each underlying examples iterable."""
ex_iterables = [ex_iterable.shuffle_data_sources(generator) for ex_iterable in self.ex_iterables]
return CyclingMultiSourcesExamplesIterable(ex_iterables, self.stopping_strategy)
@property
def num_shards(self) -> int:
return min(ex_iterable.num_shards for ex_iterable in self.ex_iterables) if self.ex_iterables else 0
def shard_data_sources(
self, num_shards: int, index: int, contiguous=True
) -> "CyclingMultiSourcesExamplesIterable":
"""Either keep only the requested shard, or propagate the request to the underlying iterable."""
if num_shards < self.num_shards:
return CyclingMultiSourcesExamplesIterable(
[
iterable.shard_data_sources(num_shards, index, contiguous=contiguous)
for iterable in self.ex_iterables
],
stopping_strategy=self.stopping_strategy,
)
elif index < self.num_shards:
return CyclingMultiSourcesExamplesIterable(
[
iterable.shard_data_sources(self.num_shards, index, contiguous=contiguous)
for iterable in self.ex_iterables
],
stopping_strategy=self.stopping_strategy,
)
else:
return CyclingMultiSourcesExamplesIterable(
[],
stopping_strategy=self.stopping_strategy,
)
def reshard_data_sources(self) -> "CyclingMultiSourcesExamplesIterable":
return CyclingMultiSourcesExamplesIterable(
[iterable.reshard_data_sources() for iterable in self.ex_iterables],
stopping_strategy=self.stopping_strategy,
)
class VerticallyConcatenatedMultiSourcesExamplesIterable(_BaseExamplesIterable):
"""
VerticallyConcatenatedMultiSourcesExamplesIterable simply chains the input iterables.
It doesn't require the examples iterables to always yield the same columns.
Instead, this is handled by the `IterableDataset` class or `FormattedExamplesIterable`.
For information, `IterableDataset` merges the features of all the datasets to concatenate into one.
We use `IterableDataset._resolve_features` to obtain the features of all the datasets to concatenate.
Then for each example, `IterableDataset` and `FormattedExamplesIterable` automatically fill missing columns with None.
This is done with `_apply_feature_types_on_example`.
"""
def __init__(self, ex_iterables: list[_BaseExamplesIterable]):
super().__init__()
self.ex_iterables = ex_iterables
@property
def is_typed(self):
return self.ex_iterables[0].is_typed
@property
def features(self):
return self.ex_iterables[0].features
@property
def iter_arrow(self):
if all(ex_iterable.iter_arrow is not None for ex_iterable in self.ex_iterables):
return self._iter_arrow
def _init_state_dict(self) -> dict:
self._state_dict = {
"ex_iterable_idx": 0,
"ex_iterables": [ex_iterable._init_state_dict() for ex_iterable in self.ex_iterables],
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
ex_iterable_idx_start = self._state_dict["ex_iterable_idx"] if self._state_dict else 0
for ex_iterable in islice(self.ex_iterables, ex_iterable_idx_start, None):
yield from ex_iterable
if self._state_dict:
self._state_dict["ex_iterable_idx"] += 1
def _iter_arrow(self):
ex_iterable_idx_start = self._state_dict["ex_iterable_idx"] if self._state_dict else 0
for ex_iterable in islice(self.ex_iterables, ex_iterable_idx_start, None):
yield from ex_iterable.iter_arrow()
if self._state_dict:
self._state_dict["ex_iterable_idx"] += 1
def shuffle_data_sources(
self, generator: np.random.Generator
) -> "VerticallyConcatenatedMultiSourcesExamplesIterable":
"""Shuffle all shards."""
rng = deepcopy(generator)
single_shard_ex_iterables = [
ex_iterable.shard_data_sources(num_shards=ex_iterable.num_shards, index=index)
for ex_iterable in self.ex_iterables
for index in range(ex_iterable.num_shards)
]
rng.shuffle(single_shard_ex_iterables)
return VerticallyConcatenatedMultiSourcesExamplesIterable(single_shard_ex_iterables)
@property
def num_shards(self) -> int:
return sum(ex_iterable.num_shards for ex_iterable in self.ex_iterables)
def shard_data_sources(
self, num_shards: int, index: int, contiguous=True
) -> "VerticallyConcatenatedMultiSourcesExamplesIterable":
"""Keep only the requested shard"""
single_shard_ex_iterables = [
ex_iterable.shard_data_sources(num_shards=ex_iterable.num_shards, index=index)
for ex_iterable in self.ex_iterables
for index in range(ex_iterable.num_shards)
]
shard_indices = self.split_shard_indices_by_worker(num_shards, index, contiguous=contiguous)
return VerticallyConcatenatedMultiSourcesExamplesIterable(
[single_shard_ex_iterables[i] for i in shard_indices]
)
def reshard_data_sources(self) -> "VerticallyConcatenatedMultiSourcesExamplesIterable":
return VerticallyConcatenatedMultiSourcesExamplesIterable(
[iterable.reshard_data_sources() for iterable in self.ex_iterables]
)
def _check_column_names(column_names: list[str]):
"""Check the column names to make sure they don't contain duplicates."""
counter = Counter(column_names)
if not all(count == 1 for count in counter.values()):
duplicated_columns = [col for col in counter if counter[col] > 1]
raise ValueError(
f"The examples iterables can't have duplicated columns but columns {duplicated_columns} are duplicated."
)
class HorizontallyConcatenatedMultiSourcesExamplesIterable(_BaseExamplesIterable):
"""
HorizontallyConcatenatedMultiSourcesExamplesIterable merges examples together for the input list of iterables.
It also checks that there are no duplicate columns (otherwise we don't know which one to keep).
This check is done once when yielding the first example.
However it doesn't fill missing columns with None.
Instead, this is handled by the `IterableDataset` class or `FormattedExamplesIterable`.
For information, `IterableDataset` merges the features of all the datasets to concatenate into one.
We use `IterableDataset._resolve_features` to obtain the features of all the datasets to concatenate.
Then for each example, `IterableDataset` and `FormattedExamplesIterable` automatically fill missing columns with None.
This is done with `_apply_feature_types_on_example`.
"""
def __init__(self, ex_iterables: list[_BaseExamplesIterable]):
super().__init__()
self.ex_iterables = ex_iterables
@property
def iter_arrow(self):
return (
self._iter_arrow
if all(
isinstance(ex_iterable, RebatchedArrowExamplesIterable) and ex_iterable.ex_iterable.iter_arrow
for ex_iterable in self.ex_iterables
)
or (len(self.ex_iterables) < 2 and all(ex_iterable.iter_arrow for ex_iterable in self.ex_iterables))
else None
)
@property
def is_typed(self):
return self.ex_iterables[0].is_typed
@property
def features(self):
return self.ex_iterables[0].features
def _init_state_dict(self) -> dict:
self._state_dict = {
"ex_iterables": [ex_iterable._init_state_dict() for ex_iterable in self.ex_iterables],
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
ex_iterators = [iter(ex_iterable) for ex_iterable in self.ex_iterables]
for i in itertools.count():
keys = []
examples = []
for ex_iterator in list(ex_iterators):
try:
key, example = next(ex_iterator)
keys.append(key)
examples.append(example)
except StopIteration:
ex_iterators.remove(ex_iterator)
if ex_iterators:
if i == 0:
_check_column_names([column_name for example in examples for column_name in example])
new_example = {}
for example in examples:
new_example.update(example)
new_key = "_".join(str(key) for key in keys)
yield new_key, new_example
else:
break
def _iter_arrow(self):
pa_table_iterators = [iter(ex_iterable.iter_arrow()) for ex_iterable in self.ex_iterables]
for i in itertools.count():
keys = []
pa_tables = []
for pa_table_iterator in list(pa_table_iterators):
try:
key, pa_table = next(pa_table_iterator)
keys.append(key)
pa_tables.append(pa_table)
except StopIteration:
pa_table_iterators.remove(pa_table_iterator)
if pa_table_iterators:
if i == 0:
_check_column_names(
[column_name for pa_table in pa_tables for column_name in pa_table.column_names]
)
for j, table in enumerate(pa_tables):
if j == 0:
new_pa_table = table
else:
for name, col in zip(table.column_names, table.columns):
new_pa_table = pa_table.append_column(name, col)
new_key = "_".join(str(key) for key in keys)
yield new_key, new_pa_table
else:
break
def shuffle_data_sources(
self, generator: np.random.Generator
) -> "HorizontallyConcatenatedMultiSourcesExamplesIterable":
"""Doesn't shuffle the wrapped examples iterable since it would break the alignment between them."""
return self
@property
def num_shards(self) -> int:
return 1
def shard_data_sources(
self, num_shards: int, index: int, contiguous=True
) -> "HorizontallyConcatenatedMultiSourcesExamplesIterable":
"""Doesn't shard the wrapped examples iterable since it would break the alignment between them."""
return self
def reshard_data_sources(self) -> "HorizontallyConcatenatedMultiSourcesExamplesIterable":
"""Doesn't reshard the wrapped examples iterable since it would break the alignment between them."""
return self
class RandomlyCyclingMultiSourcesExamplesIterable(CyclingMultiSourcesExamplesIterable):
def __init__(
self,
ex_iterables: list[_BaseExamplesIterable],
generator: np.random.Generator,
probabilities: Optional[list[float]] = None,
stopping_strategy: Literal[
"first_exhausted", "all_exhausted", "all_exhausted_without_replacement"
] = "first_exhausted",
):
super().__init__(ex_iterables, stopping_strategy)
self.generator = deepcopy(generator)
self.probabilities = probabilities
def shift_rngs(self, value: int) -> "_BaseExamplesIterable":
rng = deepcopy(self.generator)
new_seed = rng.integers(0, 1 << 63) - value
return RandomlyCyclingMultiSourcesExamplesIterable(
ex_iterables=self.ex_iterables,
generator=np.random.default_rng(seed=new_seed),
probabilities=self.probabilities,
stopping_strategy=self.stopping_strategy,
)
@property
def is_typed(self):
return self.ex_iterables[0].is_typed
@property
def features(self):
return self.ex_iterables[0].features
def _get_indices_iterator(self):
rng = deepcopy(self.generator)
num_sources = len(self.ex_iterables)
random_batch_size = 1000
# this is an infinite iterator that randomly samples the index of the source to pick examples from
index_offset = self._state_dict["bit_generator_index_offset"] if self._state_dict else 0
if self._state_dict:
rng.bit_generator.state = self._state_dict["bit_generator_state"]
if self.probabilities is None:
while True:
for i in islice(rng.integers(0, num_sources, size=random_batch_size), index_offset, None):
index_offset = (index_offset + 1) % random_batch_size
if self._state_dict:
self._state_dict["bit_generator_index_offset"] = index_offset
if index_offset == 0:
self._state_dict["bit_generator_state"] = rng.bit_generator.state
yield int(i)
else:
while True:
for i in islice(
rng.choice(num_sources, size=random_batch_size, p=self.probabilities), index_offset, None
):
index_offset = (index_offset + 1) % random_batch_size
if self._state_dict:
self._state_dict["bit_generator_index_offset"] = index_offset
if index_offset == 0:
self._state_dict["bit_generator_state"] = rng.bit_generator.state
yield int(i)
def _init_state_dict(self) -> dict:
for ex_iterable in self.ex_iterables:
ex_iterable._init_state_dict()
self._state_dict = {
"bit_generator_state": self.generator.bit_generator.state,
"bit_generator_index_offset": 0,
"previous_states": [None] * len(self.ex_iterables),
"is_exhausted": [False] * len(self.ex_iterables),
"type": self.__class__.__name__,
}
return self._state_dict
def shuffle_data_sources(self, generator: np.random.Generator) -> "RandomlyCyclingMultiSourcesExamplesIterable":
"""Shuffle the data sources of each wrapped examples iterable."""
ex_iterables = [ex_iterable.shuffle_data_sources(generator) for ex_iterable in self.ex_iterables]
return RandomlyCyclingMultiSourcesExamplesIterable(
ex_iterables,
generator=generator,
probabilities=self.probabilities,
stopping_strategy=self.stopping_strategy,
)
def shard_data_sources(
self, num_shards: int, index: int, contiguous=True
) -> "RandomlyCyclingMultiSourcesExamplesIterable":
"""Either keep only the requested shard, or propagate the request to the underlying iterable."""
if num_shards < self.num_shards:
return RandomlyCyclingMultiSourcesExamplesIterable(
[
iterable.shard_data_sources(num_shards, index, contiguous=contiguous)
for iterable in self.ex_iterables
],
self.generator,
self.probabilities,
self.stopping_strategy,
)
elif index < self.num_shards:
return RandomlyCyclingMultiSourcesExamplesIterable(
[
iterable.shard_data_sources(self.num_shards, index, contiguous=contiguous)
for iterable in self.ex_iterables
],
self.generator,
self.probabilities,
self.stopping_strategy,
)
else:
return RandomlyCyclingMultiSourcesExamplesIterable(
[],
self.generator,
self.probabilities,
self.stopping_strategy,
)
def reshard_data_sources(self) -> "RandomlyCyclingMultiSourcesExamplesIterable":
"""Either keep only the requested shard, or propagate the request to the underlying iterable."""
return RandomlyCyclingMultiSourcesExamplesIterable(
[iterable.reshard_data_sources() for iterable in self.ex_iterables],
self.generator,
self.probabilities,
self.stopping_strategy,
)
def _table_output_to_arrow(output) -> pa.Table:
if isinstance(output, pa.Table):
return output
if isinstance(output, (pd.DataFrame, pd.Series)):
return pa.Table.from_pandas(output)
if config.POLARS_AVAILABLE and "polars" in sys.modules:
import polars as pl
if isinstance(output, (pl.DataFrame, pl.Series)):
return output.to_arrow()
return output
class MappedExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
function: Callable,
with_indices: bool = False,
input_columns: Optional[list[str]] = None,
batched: bool = False,
batch_size: Optional[int] = 1000,
drop_last_batch: bool = False,
remove_columns: Optional[list[str]] = None,
fn_kwargs: Optional[dict] = None,
formatting: Optional["FormattingConfig"] = None,
features: Optional[Features] = None,
max_num_running_async_map_functions_in_parallel: Optional[int] = None,
is_batch_accumulate_arrow_table_function: bool = False,
):
super().__init__()
self.ex_iterable = ex_iterable
self.function = function
self.batched = batched
self.batch_size = batch_size
self.drop_last_batch = drop_last_batch
self.remove_columns = remove_columns
self.with_indices = with_indices
self.input_columns = input_columns
self.fn_kwargs = fn_kwargs or {}
self.formatting = formatting # required for iter_arrow
self._features = features
self.max_num_running_async_map_functions_in_parallel = (
max_num_running_async_map_functions_in_parallel or config.MAX_NUM_RUNNING_ASYNC_MAP_FUNCTIONS_IN_PARALLEL
)
self.is_batch_accumulate_arrow_table_function = is_batch_accumulate_arrow_table_function
# sanity checks
if formatting and formatting.is_table:
# batch_size should match for iter_arrow
if not isinstance(ex_iterable, RebatchedArrowExamplesIterable):
raise ValueError(
f"The {formatting.format_type.capitalize()}-formatted {type(self).__name__} has underlying iterable "
f"that is a {type(ex_iterable).__name__} instead of a RebatchedArrowExamplesIterable."
)
elif not ex_iterable.iter_arrow:
raise ValueError(
f"The {formatting.format_type.capitalize()}-formatted {type(self).__name__} has underlying iterable "
f"that is a {type(ex_iterable).__name__} but doesnt' implement iter_arrow(), a possible fix could be "
"to use RebatchedArrowExamplesIterable(..., force_convert_to_arrow=True)."
)
elif ex_iterable.batch_size != (batch_size if batched else 1):
raise ValueError(
f"The {formatting.format_type.capitalize()}-formatted {type(self).__name__} has batch_size={batch_size if batched else 1} which is "
f"different from {ex_iterable.batch_size=} from its underlying iterable."
)
# to enable graceful ends
self._owned_loops_and_tasks: list[tuple[asyncio.AbstractEventLoop, list[asyncio.Task]]] = []
@property
def iter_arrow(self):
if self.formatting and self.formatting.is_table:
return self._iter_arrow
@property
def is_typed(self):
return self.features is not None # user has extracted features
@property
def features(self):
return self._features
def _init_state_dict(self) -> dict:
self._state_dict = {
"examples_iterable": self.ex_iterable._init_state_dict(),
"previous_state": None,
"num_examples_since_previous_state": 0,
"previous_state_example_idx": 0,
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
if self.formatting and self.formatting.is_table:
formatter = PythonFormatter()
for key, pa_table in self._iter_arrow(max_chunksize=1):
yield key, formatter.format_row(pa_table)
else:
yield from self._iter()
def _iter(self):
current_idx = self._state_dict["previous_state_example_idx"] if self._state_dict else 0
if self._state_dict and self._state_dict["previous_state"]:
self.ex_iterable.load_state_dict(self._state_dict["previous_state"])
num_examples_to_skip = self._state_dict["num_examples_since_previous_state"]
else:
num_examples_to_skip = 0
iterator = iter(self.ex_iterable)
# We use the same logic as in Dataset.map, but with less features/formatting
# since they're handled by FormattedExamplesIterable
if self.formatting:
formatter = get_formatter(self.formatting.format_type)
format_dict = formatter.recursive_tensorize if isinstance(formatter, TensorFormatter) else None
else:
format_dict = None
def iter_batched_inputs():
nonlocal current_idx
for key, example in iterator:
# If `batched`, first build the batch, if `batch_size` is None or <=0, then the batch is the whole dataset
iterator_batch = (
iterator
if self.batch_size is None or self.batch_size <= 0
else islice(iterator, self.batch_size - 1)
)
key_examples_list = [(key, example)] + list(iterator_batch)
keys, examples = zip(*key_examples_list)
# the new key is the concatenation of the examples keys from the batch
key = "_".join(str(key) for key in keys)
if (
self.drop_last_batch
and self.batch_size is not None
and self.batch_size > 0
and len(examples) < self.batch_size
): # ignore last batch
return
batch = _examples_to_batch(examples)
# we need to format here in case we need to stack tensors together
batch = format_dict(batch) if format_dict else batch
indices = [current_idx + i for i in range(len(key_examples_list))]
current_idx += len(indices)
yield indices, (key, batch)
def iter_inputs():
nonlocal current_idx
for key, example in iterator:
# If not batched, we can apply the transform and yield the example directly
# first copy the example, since we might drop some keys
example = dict(example)
# no need to do formatting here
current_idx += 1
yield current_idx - 1, (key, example)
def validate_function_output(processed_inputs):
if self.batched and processed_inputs:
first_col = next(iter(processed_inputs))
bad_cols = [
col for col in processed_inputs if len(processed_inputs[col]) != len(processed_inputs[first_col])
]
if bad_cols:
raise ValueError(
f"Column lengths mismatch: columns {bad_cols} have length {[len(processed_inputs[col]) for col in bad_cols]} "
f"while {first_col} has length {len(processed_inputs[first_col])}."
)
def prepare_inputs(key_example, indices):
key, example = key_example
fn_args = [example] if self.input_columns is None else [example[col] for col in self.input_columns]
additional_args = ()
if self.with_indices:
fn_args += (indices,)
inputs = dict(example)
return inputs, fn_args, additional_args, self.fn_kwargs
def prepare_outputs(key_example, inputs, processed_inputs):
validate_function_output(processed_inputs)
# this logic mimics the one in Dataset.map
if self.remove_columns:
for c in self.remove_columns:
if c in inputs:
del inputs[c]
if processed_inputs is key_example[1] and c in processed_inputs:
del processed_inputs[c]
transformed_inputs = {**inputs, **processed_inputs}
# no need to do features decoding here
return transformed_inputs
def apply_function(key_example, indices):
"""Utility to apply the function on a selection of columns."""
inputs, fn_args, additional_args, fn_kwargs = prepare_inputs(key_example, indices)
processed_inputs = self.function(*fn_args, *additional_args, **fn_kwargs)
return prepare_outputs(key_example, inputs, processed_inputs)
async def async_apply_function(key_example, indices):
"""Utility to apply the function on a selection of columns. Same code but async"""
inputs, fn_args, additional_args, fn_kwargs = prepare_inputs(key_example, indices)
processed_inputs = await self.function(*fn_args, *additional_args, **fn_kwargs)
return prepare_outputs(key_example, inputs, processed_inputs)
tasks: list[asyncio.Task] = []
if inspect.iscoroutinefunction(self.function):
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
self._owned_loops_and_tasks.append((loop, tasks))
else:
loop = None
def iter_outputs():
nonlocal tasks, loop
inputs_iterator = iter_batched_inputs() if self.batched else iter_inputs()
if inspect.iscoroutinefunction(self.function):
if self._state_dict:
previous_state = self.ex_iterable.state_dict()
self._state_dict["previous_state"] = previous_state
previous_state_task = None
previous_state_example_idx = self._state_dict["previous_state_example_idx"]
indices: Union[list[int], list[list[int]]] = []
for i, key_example in inputs_iterator:
indices.append(i)
tasks.append(loop.create_task(async_apply_function(key_example, i)))
# keep the total active tasks under a certain number
if len(tasks) >= self.max_num_running_async_map_functions_in_parallel:
done, pending = loop.run_until_complete(
asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
)
while tasks and len(pending) >= self.max_num_running_async_map_functions_in_parallel:
done, pending = loop.run_until_complete(
asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
)
if len(tasks) >= 10 * self.max_num_running_async_map_functions_in_parallel:
loop.run_until_complete(tasks[0])
# yield finished tasks
while tasks and tasks[0].done():
i, task = indices.pop(0), tasks.pop(0)
yield i, task.result()
if self._state_dict and task is previous_state_task:
self._state_dict["previous_state"] = previous_state
self._state_dict["num_examples_since_previous_state"] = 0
self._state_dict["previous_state_example_idx"] = previous_state_example_idx
previous_state, previous_state_task = None, None
# checkpoint
if self._state_dict and previous_state_task is None and tasks:
previous_state = self.ex_iterable.state_dict()
previous_state_task = tasks[-1]
previous_state_example_idx = current_idx
while tasks:
yield indices[0], loop.run_until_complete(tasks[0])
indices.pop(0), tasks.pop(0)
else:
if self._state_dict:
if self.batched:
self._state_dict["previous_state"] = self.ex_iterable.state_dict()
self._state_dict["num_examples_since_previous_state"] = 0
self._state_dict["previous_state_example_idx"] = current_idx
for i, key_example in inputs_iterator:
if self._state_dict:
if not self.batched:
self._state_dict["previous_state_example_idx"] = current_idx
yield i, apply_function(key_example, i)
if self._state_dict:
if self.batched:
self._state_dict["previous_state"] = self.ex_iterable.state_dict()
self._state_dict["num_examples_since_previous_state"] = 0
self._state_dict["previous_state_example_idx"] = current_idx
try:
outputs = iter_outputs()
if self.batched:
outputs = (
(key, transformed_example)
for key, transformed_batch in outputs
for transformed_example in _batch_to_examples(transformed_batch)
)
for key, transformed_example in outputs:
if self._state_dict and self._state_dict["previous_state"] is not None:
self._state_dict["num_examples_since_previous_state"] += 1
if num_examples_to_skip > 0:
num_examples_to_skip -= 1
continue
yield key, transformed_example
except (Exception, KeyboardInterrupt):
if loop:
logger.debug(f"Canceling {len(tasks)} async tasks.")
for task in tasks:
task.cancel(msg="KeyboardInterrupt")
try:
loop.run_until_complete(asyncio.gather(*tasks))
except (asyncio.CancelledError, ValueError):
logger.debug("Tasks canceled.")
raise
def _iter_arrow(self, max_chunksize: Optional[int] = None) -> Iterator[tuple[Key, pa.Table]]:
formatter: TableFormatter = get_formatter(self.formatting.format_type) if self.formatting else ArrowFormatter()
if self.ex_iterable.iter_arrow:
iterator = self.ex_iterable.iter_arrow()
else:
iterator = _convert_to_arrow(
self.ex_iterable,
batch_size=self.batch_size if self.batched else 1,
drop_last_batch=self.drop_last_batch,
)
if self._state_dict and self._state_dict["previous_state"]:
self.ex_iterable.load_state_dict(self._state_dict["previous_state"])
num_examples_to_skip = self._state_dict["num_examples_since_previous_state"]
else:
num_examples_to_skip = 0
if self._state_dict and max_chunksize is not None:
self._state_dict["previous_state"] = self.ex_iterable.state_dict()
self._state_dict["num_examples_since_previous_state"] = 0
current_idx = self._state_dict["previous_state_example_idx"] if self._state_dict else 0
fn_kwargs = self.fn_kwargs.copy()
if self.is_batch_accumulate_arrow_table_function:
tables_accumulator: list[pa.Table] = []
length: Optional[int] = None
fn_kwargs["tables_accumulator"] = tables_accumulator
fn_kwargs["length"] = length
for key, pa_table in iterator:
if (
self.batched
and self.batch_size is not None
and len(pa_table) < self.batch_size
and self.drop_last_batch
):
return
# first build the batch
function_args = (
[formatter.format_batch(pa_table)]
if self.input_columns is None
else [pa_table[col] for col in self.input_columns]
)
if self.with_indices:
if self.batched:
function_args.append([current_idx + i for i in range(len(pa_table))])
else:
function_args.append(current_idx)
# then apply the transform
output = self.function(*function_args, **fn_kwargs)
output_table = _table_output_to_arrow(output)
if not isinstance(output_table, pa.Table):
raise TypeError(
f"Provided `function` which is applied to {formatter.table_type} returns a variable of type "
f"{type(output)}. Make sure provided `function` returns a {formatter.table_type} to update the dataset."
)
# we don't need to merge results for consistency with Dataset.map which merges iif both input and output are dicts
# then remove the unwanted columns
if self.remove_columns:
for column in self.remove_columns:
if column in output_table.column_names:
output_table = output_table.remove_column(output_table.column_names.index(column))
# return output
if max_chunksize is None:
current_idx += len(pa_table)
if self._state_dict:
self._state_dict["previous_state_example_idx"] += len(pa_table)
yield key, output_table
else:
for i, pa_subtable in enumerate(output_table.to_reader(max_chunksize=max_chunksize)):
current_idx += 1
if self._state_dict:
self._state_dict["num_examples_since_previous_state"] += 1
if num_examples_to_skip > 0:
num_examples_to_skip -= 1
continue
yield f"{key}_{i}", pa_subtable
if self._state_dict:
self._state_dict["previous_state"] = self.ex_iterable.state_dict()
self._state_dict["num_examples_since_previous_state"] = 0
self._state_dict["previous_state_example_idx"] = current_idx
if self.is_batch_accumulate_arrow_table_function and tables_accumulator:
pa_table = tables_accumulator.pop(-1)
indices = [current_idx + i for i in range(len(pa_table))]
function_args = (pa_table, indices)
output_table = self.function(
*function_args, **self.fn_kwargs, tables_accumulator=tables_accumulator, length=indices[-1] + 1
)
yield "last_batch_from_tables_accumulator", output_table
def shuffle_data_sources(self, generator: np.random.Generator) -> "MappedExamplesIterable":
"""Shuffle the wrapped examples iterable."""
return MappedExamplesIterable(
self.ex_iterable.shuffle_data_sources(generator),
function=self.function,
with_indices=self.with_indices,
input_columns=self.input_columns,
batched=self.batched,
batch_size=self.batch_size,
drop_last_batch=self.drop_last_batch,
remove_columns=self.remove_columns,
fn_kwargs=self.fn_kwargs,
formatting=self.formatting,
features=self.features,
max_num_running_async_map_functions_in_parallel=self.max_num_running_async_map_functions_in_parallel,
is_batch_accumulate_arrow_table_function=self.is_batch_accumulate_arrow_table_function,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "MappedExamplesIterable":
"""Keep only the requested shard."""
return MappedExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
function=self.function,
with_indices=self.with_indices,
input_columns=self.input_columns,
batched=self.batched,
batch_size=self.batch_size,
drop_last_batch=self.drop_last_batch,
remove_columns=self.remove_columns,
fn_kwargs=self.fn_kwargs,
formatting=self.formatting,
features=self.features,
max_num_running_async_map_functions_in_parallel=self.max_num_running_async_map_functions_in_parallel,
is_batch_accumulate_arrow_table_function=self.is_batch_accumulate_arrow_table_function,
)
def reshard_data_sources(self) -> "MappedExamplesIterable":
return MappedExamplesIterable(
self.ex_iterable.reshard_data_sources(),
function=self.function,
with_indices=self.with_indices,
input_columns=self.input_columns,
batched=self.batched,
batch_size=self.batch_size,
drop_last_batch=self.drop_last_batch,
remove_columns=self.remove_columns,
fn_kwargs=self.fn_kwargs,
formatting=self.formatting,
features=self.features,
max_num_running_async_map_functions_in_parallel=self.max_num_running_async_map_functions_in_parallel,
is_batch_accumulate_arrow_table_function=self.is_batch_accumulate_arrow_table_function,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
def _add_mask(
input: Union[dict, pa.Table],
mask: Union[bool, list, pa.Array, pa.ChunkedArray, pa.BooleanScalar],
mask_column_name: str,
):
if isinstance(input, pa.Table):
if not isinstance(mask, (list, pa.Array, pa.ChunkedArray)):
mask = pa.array([mask], type=pa.bool_())
return input.append_column(mask_column_name, mask)
else:
return {mask_column_name: mask}
def add_mask(mask_function: Callable, input: Union[dict, pa.Table], *args, mask_column_name: str, **kwargs):
mask = mask_function(input, *args, **kwargs)
return _add_mask(input, mask, mask_column_name)
async def async_add_mask(
mask_function: Callable, input: Union[dict, pa.Table], *args, mask_column_name: str, **kwargs
):
mask = await mask_function(input, *args, **kwargs)
return _add_mask(input, mask, mask_column_name)
class FilteredExamplesIterable(MappedExamplesIterable):
mask_column_name = "===MASK==="
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
function: Callable,
with_indices: bool = False,
input_columns: Optional[list[str]] = None,
batched: bool = False,
batch_size: Optional[int] = 1000,
fn_kwargs: Optional[dict] = None,
formatting: Optional["FormattingConfig"] = None,
):
self.mask_function = function
if ex_iterable.is_typed:
features = Features({**ex_iterable.features, self.mask_column_name: Value("bool")})
else:
features = None
super().__init__(
ex_iterable=ex_iterable,
function=partial(
async_add_mask if inspect.iscoroutinefunction(function) else add_mask,
function,
mask_column_name=self.mask_column_name,
),
with_indices=with_indices,
input_columns=input_columns,
batched=batched,
batch_size=batch_size,
fn_kwargs=fn_kwargs,
formatting=formatting,
features=features,
)
def _iter(self):
for key, example in super()._iter():
example = dict(example)
if example.pop(self.mask_column_name):
yield key, example
def _iter_arrow(self, max_chunksize: Optional[int] = None):
for key, pa_table in super()._iter_arrow(max_chunksize=max_chunksize):
mask = pa_table[self.mask_column_name]
yield key, pa_table.drop(self.mask_column_name).filter(mask)
def shuffle_data_sources(self, seed: Optional[int]) -> "FilteredExamplesIterable":
"""Shuffle the wrapped examples iterable."""
return FilteredExamplesIterable(
self.ex_iterable.shuffle_data_sources(seed),
function=self.mask_function,
with_indices=self.with_indices,
input_columns=self.input_columns,
batched=self.batched,
batch_size=self.batch_size,
fn_kwargs=self.fn_kwargs,
formatting=self.formatting,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "FilteredExamplesIterable":
"""Keep only the requested shard."""
return FilteredExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
function=self.mask_function,
with_indices=self.with_indices,
input_columns=self.input_columns,
batched=self.batched,
batch_size=self.batch_size,
fn_kwargs=self.fn_kwargs,
formatting=self.formatting,
)
def reshard_data_sources(self) -> "FilteredExamplesIterable":
return FilteredExamplesIterable(
self.ex_iterable.reshard_data_sources(),
function=self.mask_function,
with_indices=self.with_indices,
input_columns=self.input_columns,
batched=self.batched,
batch_size=self.batch_size,
fn_kwargs=self.fn_kwargs,
formatting=self.formatting,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
class BufferShuffledExamplesIterable(_BaseExamplesIterable):
def __init__(self, ex_iterable: _BaseExamplesIterable, buffer_size: int, generator: np.random.Generator):
super().__init__()
self.ex_iterable = ex_iterable
self.buffer_size = buffer_size
self.generator = generator
def shift_rngs(self, value: int) -> "_BaseExamplesIterable":
rng = deepcopy(self.generator)
new_seed = rng.integers(0, 1 << 63) - value
return BufferShuffledExamplesIterable(
ex_iterable=self.ex_iterable,
buffer_size=self.buffer_size,
generator=np.random.default_rng(seed=new_seed),
)
@property
def is_typed(self):
return self.ex_iterable.is_typed
@property
def features(self):
return self.ex_iterable.features
@property
def iter_arrow(self):
return self._iter_arrow if self.ex_iterable.iter_arrow else None
def _init_state_dict(self) -> dict:
self._state_dict = self.ex_iterable._init_state_dict()
self._original_state_dict = self.state_dict()
return self._state_dict
def load_state_dict(self, state_dict: dict) -> dict:
if self._state_dict:
if state_dict != self._original_state_dict:
logger.warning(
"Loading a state dict of a shuffle buffer of a dataset without the buffer content."
"The shuffle buffer will be refilled before starting to yield new examples."
)
return super().load_state_dict(state_dict)
@staticmethod
def _iter_random_indices(rng: np.random.Generator, buffer_size: int, random_batch_size=1000) -> Iterator[int]:
while True:
yield from (int(i) for i in rng.integers(0, buffer_size, size=random_batch_size))
def __iter__(self):
buffer_size = self.buffer_size
rng = deepcopy(self.generator)
indices_iterator = self._iter_random_indices(rng, buffer_size)
# this is the shuffle buffer that we keep in memory
mem_buffer = []
for x in self.ex_iterable:
if len(mem_buffer) == buffer_size: # if the buffer is full, pick and example from it
i = next(indices_iterator)
yield mem_buffer[i]
mem_buffer[i] = x # replace the picked example by a new one
else: # otherwise, keep filling the buffer
mem_buffer.append(x)
# when we run out of examples, we shuffle the remaining examples in the buffer and yield them
rng.shuffle(mem_buffer)
yield from mem_buffer
def _iter_arrow(self):
buffer_size = self.buffer_size
rng = deepcopy(self.generator)
indices_iterator = self._iter_random_indices(rng, buffer_size)
# this is the shuffle buffer that we keep in memory
mem_buffer = []
for key, pa_table in self.ex_iterable.iter_arrow():
if len(mem_buffer) == buffer_size: # if the buffer is full, pick and example from it
i = next(indices_iterator)
yield mem_buffer[i]
mem_buffer[i] = (key, pa_table) # replace the picked example by a new one
else: # otherwise, keep filling the buffer
mem_buffer.append((key, pa_table))
# when we run out of examples, we shuffle the remaining examples in the buffer and yield them
rng.shuffle(mem_buffer)
yield from mem_buffer
def shuffle_data_sources(self, generator: np.random.Generator) -> "BufferShuffledExamplesIterable":
"""Shuffle the wrapped examples iterable as well as the shuffling buffer."""
return BufferShuffledExamplesIterable(
self.ex_iterable.shuffle_data_sources(generator), buffer_size=self.buffer_size, generator=self.generator
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "BufferShuffledExamplesIterable":
"""Keep only the requested shard."""
return BufferShuffledExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
buffer_size=self.buffer_size,
generator=self.generator,
)
def reshard_data_sources(self) -> "BufferShuffledExamplesIterable":
return BufferShuffledExamplesIterable(
self.ex_iterable.reshard_data_sources(),
buffer_size=self.buffer_size,
generator=self.generator,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
class DataSourcesShufflingDisallowed(Exception):
"""skip() or take() freeze the order of data sources shards"""
class SkipExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
n: int,
block_sources_order_when_shuffling: bool = True,
split_when_sharding: bool = True,
):
super().__init__()
self.ex_iterable = ex_iterable
self.n = n
self.block_sources_order_when_shuffling = block_sources_order_when_shuffling
self.split_when_sharding = split_when_sharding
@property
def iter_arrow(self):
return self._iter_arrow if self.ex_iterable.iter_arrow else None
@property
def is_typed(self):
return self.ex_iterable.is_typed
@property
def features(self):
return self.ex_iterable.features
def _init_state_dict(self) -> dict:
self._state_dict = {
"skipped": 0,
"examples_iterable": self.ex_iterable._init_state_dict(),
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
skipped = self._state_dict["skipped"] if self._state_dict else 0
for key_example in self.ex_iterable:
if skipped + 1 <= self.n:
skipped += 1
if self._state_dict:
self._state_dict["skipped"] = skipped
else:
yield key_example
def _iter_arrow(self):
skipped = self._state_dict["skipped"] if self._state_dict else 0
for key, pa_table in self.ex_iterable.iter_arrow():
if len(pa_table) == 0:
continue
elif skipped + len(pa_table) <= self.n:
skipped += len(pa_table)
if self._state_dict:
self._state_dict["skipped"] = skipped
elif skipped + 1 <= self.n:
offset = self.n - skipped
skipped = self.n
if self._state_dict:
self._state_dict["skipped"] = skipped
yield key, pa_table.slice(offset, len(pa_table) - offset)
else:
yield key, pa_table
@staticmethod
def split_number(num, n):
quotient = num // n
remainder = num % n
result = [quotient] * n
for i in range(remainder):
result[i] += 1
return result
def shuffle_data_sources(self, generator: np.random.Generator) -> "SkipExamplesIterable":
"""May not shuffle the wrapped examples iterable since it would skip examples from other shards instead."""
if self.block_sources_order_when_shuffling:
raise DataSourcesShufflingDisallowed()
else:
return SkipExamplesIterable(
self.ex_iterable.shuffle_data_sources(generator),
n=self.n,
block_sources_order_when_shuffling=self.block_sources_order_when_shuffling,
split_when_sharding=self.split_when_sharding,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "SkipExamplesIterable":
"""Keep only the requested shard."""
if self.split_when_sharding:
return SkipExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
n=self.split_number(self.n, num_shards)[index],
block_sources_order_when_shuffling=self.block_sources_order_when_shuffling,
split_when_sharding=self.split_when_sharding,
)
else:
return self
def reshard_data_sources(self) -> "SkipExamplesIterable":
return SkipExamplesIterable(
self.ex_iterable.reshard_data_sources(),
n=self.n,
block_sources_order_when_shuffling=self.block_sources_order_when_shuffling,
split_when_sharding=self.split_when_sharding,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
class RepeatExamplesIterable(_BaseExamplesIterable):
"""
Iterable that repeats the underlying iterable a given number of times.
"""
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
num_times: Optional[int],
):
super().__init__()
self.ex_iterable = ex_iterable
self.num_times = num_times
def _init_state_dict(self) -> dict:
self._state_dict = {
"repeat_index": 0,
"examples_iterable": self.ex_iterable._init_state_dict(),
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
repeat_index = self._state_dict["repeat_index"] if self._state_dict else 0
while True:
if self.num_times is not None and repeat_index >= max(self.num_times, 0):
break
yield from self.ex_iterable
repeat_index += 1
if self._state_dict:
self._state_dict["repeat_index"] = repeat_index
self._state_dict["examples_iterable"] = self.ex_iterable._init_state_dict()
def shuffle_data_sources(self, generator: np.random.Generator) -> "RepeatExamplesIterable":
"""Shuffle the underlying iterable, then repeat."""
return RepeatExamplesIterable(self.ex_iterable.shuffle_data_sources(generator), num_times=self.num_times)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "RepeatExamplesIterable":
"""Shard, then repeat shards."""
return RepeatExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
num_times=self.num_times,
)
def reshard_data_sources(self) -> "RepeatExamplesIterable":
return RepeatExamplesIterable(
self.ex_iterable.reshard_data_sources(),
num_times=self.num_times,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
class TakeExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
n: int,
block_sources_order_when_shuffling: bool = True,
split_when_sharding: bool = True,
):
super().__init__()
self.ex_iterable = ex_iterable
self.n = n
self.block_sources_order_when_shuffling = block_sources_order_when_shuffling
self.split_when_sharding = split_when_sharding
@property
def iter_arrow(self):
return self._iter_arrow if self.ex_iterable.iter_arrow else None
@property
def is_typed(self):
return self.ex_iterable.is_typed
@property
def features(self):
return self.ex_iterable.features
def _init_state_dict(self) -> dict:
self._state_dict = {
"taken": 0,
"examples_iterable": self.ex_iterable._init_state_dict(),
"type": self.__class__.__name__,
}
return self._state_dict
def __iter__(self):
taken = self._state_dict["taken"] if self._state_dict else 0
if taken >= self.n:
return
for key_example in self.ex_iterable:
if taken + 1 <= self.n:
taken += 1
if self._state_dict:
self._state_dict["taken"] = taken
yield key_example
else:
break
def _iter_arrow(self):
taken = self._state_dict["taken"] if self._state_dict else 0
if taken >= self.n:
return
for key, pa_table in self.ex_iterable.iter_arrow():
if len(pa_table) == 0:
continue
elif taken + len(pa_table) <= self.n:
taken += len(pa_table)
if self._state_dict:
self._state_dict["taken"] = taken
yield key, pa_table
elif taken + 1 <= self.n:
length = self.n - taken
taken = self.n
if self._state_dict:
self._state_dict["taken"] = taken
yield key, pa_table.slice(0, length)
else:
break
@staticmethod
def split_number(num, n):
quotient = num // n
remainder = num % n
result = [quotient] * n
for i in range(remainder):
result[i] += 1
return result
def shuffle_data_sources(self, generator: np.random.Generator) -> "TakeExamplesIterable":
"""May not shuffle the wrapped examples iterable since it would take examples from other shards instead."""
if self.block_sources_order_when_shuffling:
raise DataSourcesShufflingDisallowed()
else:
return TakeExamplesIterable(
self.ex_iterable.shuffle_data_sources(generator),
n=self.n,
block_sources_order_when_shuffling=self.block_sources_order_when_shuffling,
split_when_sharding=self.split_when_sharding,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "TakeExamplesIterable":
"""Keep only the requested shard."""
if self.split_when_sharding:
return TakeExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
n=self.split_number(self.n, num_shards)[index],
block_sources_order_when_shuffling=self.block_sources_order_when_shuffling,
split_when_sharding=self.split_when_sharding,
)
else:
return TakeExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
n=self.n,
block_sources_order_when_shuffling=self.block_sources_order_when_shuffling,
split_when_sharding=self.split_when_sharding,
)
def reshard_data_sources(self) -> "TakeExamplesIterable":
return TakeExamplesIterable(
self.ex_iterable.reshard_data_sources(),
n=self.n,
block_sources_order_when_shuffling=self.block_sources_order_when_shuffling,
split_when_sharding=self.split_when_sharding,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
def _apply_feature_types_on_example(
example: dict, features: Features, token_per_repo_id: dict[str, Union[str, bool, None]]
) -> dict:
example = dict(example)
# add missing columns
for column_name in features:
if column_name not in example:
example[column_name] = None
# we encode the example for ClassLabel feature types for example
encoded_example = features.encode_example(example)
# Decode example for Audio feature, e.g.
decoded_example = features.decode_example(encoded_example, token_per_repo_id=token_per_repo_id)
return decoded_example
@dataclass
class FormattingConfig:
format_type: Optional[str]
@property
def is_table(self) -> bool:
return isinstance(get_formatter(self.format_type), TableFormatter)
@property
def is_tensor(self) -> bool:
return isinstance(get_formatter(self.format_type), TensorFormatter)
class FormattedExamplesIterable(_BaseExamplesIterable):
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
formatting: Optional[FormattingConfig],
features: Optional[Features],
token_per_repo_id: dict[str, Union[str, bool, None]],
force_convert_to_python: bool = False,
):
super().__init__()
self.ex_iterable = ex_iterable
self._features = features
self.formatting = formatting
self.token_per_repo_id = token_per_repo_id
self.force_convert_to_python = force_convert_to_python
@property
def iter_arrow(self):
if self.ex_iterable.iter_arrow and not self.force_convert_to_python:
return self._iter_arrow
@property
def is_typed(self):
return self.ex_iterable.is_typed or self._features is not None
@property
def features(self):
return self._features
def _init_state_dict(self) -> dict:
self._state_dict = self.ex_iterable._init_state_dict()
return self._state_dict
def __iter__(self):
if not self.formatting or self.formatting.is_table:
formatter = PythonFormatter(
features=self._features if not self.ex_iterable.is_typed else None,
token_per_repo_id=self.token_per_repo_id,
)
else:
formatter = get_formatter(
self.formatting.format_type,
features=self._features if not self.ex_iterable.is_typed else None,
token_per_repo_id=self.token_per_repo_id,
)
# It's ok to use _iter_arrow here without fancy state_dict logic since it's
# used with RebatchedArrowExamplesIterable with the right batch_size to
# never lose examples
if self.ex_iterable.iter_arrow:
# feature casting (inc column addition) handled within self._iter_arrow()
for key, pa_table in self._iter_arrow():
batch = formatter.format_batch(pa_table)
for example in _batch_to_examples(batch):
yield key, example
else:
format_dict = (
formatter.recursive_tensorize
if isinstance(formatter, TensorFormatter)
else None # cast in case features is None
)
for key, example in self.ex_iterable:
# don't apply feature types if already applied by ex_iterable (e.g. in case of chained with_format)
if self.features and not self.ex_iterable.is_typed:
example = _apply_feature_types_on_example(
example, self.features, token_per_repo_id=self.token_per_repo_id
)
if format_dict:
example = format_dict(example)
yield key, example
def _iter_arrow(self) -> Iterator[tuple[Key, pa.Table]]:
if not self.features:
yield from self.ex_iterable._iter_arrow()
return
schema = self.features.arrow_schema
for key, pa_table in self.ex_iterable._iter_arrow():
columns = set(pa_table.column_names)
# add missing columns
for column_name in self.features:
if column_name not in columns:
col = pa.NullArray.from_buffers(pa.null(), len(pa_table), [None])
pa_table = pa_table.append_column(column_name, col)
if pa_table.schema != schema:
pa_table = cast_table_to_features(pa_table, self.features)
yield key, pa_table
def shuffle_data_sources(self, generator: np.random.Generator) -> "FormattedExamplesIterable":
"""Shuffle the wrapped examples iterable."""
return FormattedExamplesIterable(
self.ex_iterable.shuffle_data_sources(generator),
features=self.features,
token_per_repo_id=self.token_per_repo_id,
formatting=self.formatting,
force_convert_to_python=self.force_convert_to_python,
)
def shard_data_sources(self, num_shards: int, index: int, contiguous=True) -> "FormattedExamplesIterable":
"""Keep only the requested shard."""
return FormattedExamplesIterable(
self.ex_iterable.shard_data_sources(num_shards, index, contiguous=contiguous),
features=self.features,
token_per_repo_id=self.token_per_repo_id,
formatting=self.formatting,
force_convert_to_python=self.force_convert_to_python,
)
def reshard_data_sources(self) -> "FormattedExamplesIterable":
return FormattedExamplesIterable(
self.ex_iterable.reshard_data_sources(),
features=self.features,
token_per_repo_id=self.token_per_repo_id,
formatting=self.formatting,
force_convert_to_python=self.force_convert_to_python,
)
@property
def num_shards(self) -> int:
return self.ex_iterable.num_shards
@dataclass
class DistributedConfig:
rank: int
world_size: int
def _maybe_add_torch_iterable_dataset_parent_class(cls):
"""Add torch.utils.data.IterableDataset as a parent class if 'torch' is available"""
if config.TORCH_AVAILABLE:
import torch.utils.data
if torch.utils.data.IterableDataset not in cls.__bases__:
cls.__bases__ += (torch.utils.data.IterableDataset,)
def _maybe_share_with_torch_persistent_workers(value: Union[int, "torch.Tensor"]) -> Union[int, "torch.Tensor"]:
if config.TORCH_AVAILABLE:
import torch
if isinstance(value, torch.Tensor):
return value.share_memory_()
else:
return torch.tensor(value).share_memory_()
else:
return value
class IterableColumn:
"""
An iterable for a specific column of an [`IterableDataset`].
Example:
Iterate on the texts of the "text" column of a dataset:
```python
for text in dataset["text"]:
...
```
It also works with nested columns:
```python
for source in dataset["metadata"]["source"]:
...
```
"""
def __init__(self, source: Union["IterableDataset", "IterableColumn"], column_name: str):
self.source = source
self.column_name = column_name
def __iter__(self) -> Iterator[Any]:
for example in self.source:
yield example[self.column_name]
def __getitem__(self, column_name: str) -> "IterableColumn":
return IterableColumn(self, column_name)
class IterableDataset(DatasetInfoMixin):
"""A Dataset backed by an iterable."""
def __init__(
self,
ex_iterable: _BaseExamplesIterable,
info: Optional[DatasetInfo] = None,
split: Optional[NamedSplit] = None,
formatting: Optional[FormattingConfig] = None,
distributed: Optional[DistributedConfig] = None,
token_per_repo_id: Optional[dict[str, Union[str, bool, None]]] = None,
):
info = info.copy() if info is not None else DatasetInfo()
DatasetInfoMixin.__init__(self, info=info, split=split)
self._ex_iterable = copy(ex_iterable)
self._formatting = formatting
self._distributed = distributed
self._token_per_repo_id: dict[str, Union[str, bool, None]] = token_per_repo_id or {}
self._epoch: Union[int, "torch.Tensor"] = _maybe_share_with_torch_persistent_workers(0)
self._starting_state_dict: Optional[dict] = None
self.__hffs_cache = HfFileSystem._cache # keep the cache on pickling (e.g. for dataloader workers)
self._prepare_ex_iterable_for_iteration() # set state_dict
_maybe_add_torch_iterable_dataset_parent_class(self.__class__) # subclass of torch IterableDataset
@property
def num_columns(self) -> Optional[int]:
"""Number of columns in the dataset.
This can be None if the dataset has unknown features (e.g. after a map() operation).
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="validation")
>>> ds.num_columns
2
```
"""
return None if self.features is None else len(self.features)
@property
def column_names(self) -> Optional[list[str]]:
"""Names of the columns in the dataset.
This can be None if the dataset has unknown features (e.g. after a map() operation).
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="validation", streaming=True)
>>> ds.column_names
['text', 'label']
```
"""
return None if self.features is None else list(self.features)
def state_dict(self) -> dict:
"""Get the current state_dict of the dataset.
It corresponds to the state at the latest example it yielded.
Resuming returns exactly where the checkpoint was saved except in two cases:
1. examples from shuffle buffers are lost when resuming and the buffers are refilled with new data
2. combinations of `.with_format(arrow)` and batched `.map()` may skip one batch.
Returns:
`dict`
Example:
```py
>>> from datasets import Dataset, concatenate_datasets
>>> ds = Dataset.from_dict({"a": range(6)}).to_iterable_dataset(num_shards=3)
>>> for idx, example in enumerate(ds):
... print(example)
... if idx == 2:
... state_dict = ds.state_dict()
... print("checkpoint")
... break
>>> ds.load_state_dict(state_dict)
>>> print(f"restart from checkpoint")
>>> for example in ds:
... print(example)
```
which returns:
```
{'a': 0}
{'a': 1}
{'a': 2}
checkpoint
restart from checkpoint
{'a': 3}
{'a': 4}
{'a': 5}
```
```py
>>> from torchdata.stateful_dataloader import StatefulDataLoader
>>> ds = load_dataset("deepmind/code_contests", streaming=True, split="train")
>>> dataloader = StatefulDataLoader(ds, batch_size=32, num_workers=4)
>>> # checkpoint
>>> state_dict = dataloader.state_dict() # uses ds.state_dict() under the hood
>>> # resume from checkpoint
>>> dataloader.load_state_dict(state_dict) # uses ds.load_state_dict() under the hood
```
"""
return deepcopy(self._state_dict)
def load_state_dict(self, state_dict: dict) -> None:
"""Load the state_dict of the dataset.
The iteration will restart at the next example from when the state was saved.
Resuming returns exactly where the checkpoint was saved except in two cases:
1. examples from shuffle buffers are lost when resuming and the buffers are refilled with new data
2. combinations of `.with_format(arrow)` and batched `.map()` may skip one batch.
Example:
```py
>>> from datasets import Dataset, concatenate_datasets
>>> ds = Dataset.from_dict({"a": range(6)}).to_iterable_dataset(num_shards=3)
>>> for idx, example in enumerate(ds):
... print(example)
... if idx == 2:
... state_dict = ds.state_dict()
... print("checkpoint")
... break
>>> ds.load_state_dict(state_dict)
>>> print(f"restart from checkpoint")
>>> for example in ds:
... print(example)
```
which returns:
```
{'a': 0}
{'a': 1}
{'a': 2}
checkpoint
restart from checkpoint
{'a': 3}
{'a': 4}
{'a': 5}
```
```py
>>> from torchdata.stateful_dataloader import StatefulDataLoader
>>> ds = load_dataset("deepmind/code_contests", streaming=True, split="train")
>>> dataloader = StatefulDataLoader(ds, batch_size=32, num_workers=4)
>>> # checkpoint
>>> state_dict = dataloader.state_dict() # uses ds.state_dict() under the hood
>>> # resume from checkpoint
>>> dataloader.load_state_dict(state_dict) # uses ds.load_state_dict() under the hood
```
"""
self._starting_state_dict = state_dict
def __repr__(self):
return f"IterableDataset({{\n features: {list(self._info.features.keys()) if self._info.features is not None else 'Unknown'},\n num_shards: {self.num_shards}\n}})"
def __getstate__(self):
return self.__dict__
def __setstate__(self, d):
self.__dict__ = d
# Re-add torch shared memory, since shared memory is not always kept when pickling
self._epoch = _maybe_share_with_torch_persistent_workers(self._epoch)
# Re-add the cache to keep on pickling (e.g. for dataloader workers)
self.__hffs_cache = HfFileSystem._cache
# Re-add torch iterable dataset as a parent class, since dynamically added parent classes are not kept when pickling
_maybe_add_torch_iterable_dataset_parent_class(self.__class__)
def _head(self, n=5):
return next(iter(self.iter(batch_size=n)))
@property
def epoch(self) -> int:
return int(self._epoch)
@property
def num_shards(self) -> int:
if self._distributed and self._ex_iterable.num_shards % self._distributed.world_size == 0:
return self._ex_iterable.num_shards // self._distributed.world_size
return self._ex_iterable.num_shards
@property
def n_shards(self) -> int: # backward compatibility
return self.num_shards
def _iter_pytorch(self):
ex_iterable = self._prepare_ex_iterable_for_iteration()
# Fix for fsspec when using multiprocess to avoid hanging in the ML training loop. (only required for fsspec >= 0.9.0)
# See https://github.com/fsspec/gcsfs/issues/379
fsspec.asyn.reset_lock()
# check if there aren't too many workers
import torch.utils.data
worker_info = torch.utils.data.get_worker_info()
if self._is_main_process() and ex_iterable.num_shards < worker_info.num_workers:
logger.warning(
f"Too many dataloader workers: {worker_info.num_workers} (max is dataset.num_shards={ex_iterable.num_shards}). "
f"Stopping {worker_info.num_workers - ex_iterable.num_shards} dataloader workers."
)
logger.info(
f"To parallelize data loading, we give each process some shards (or data sources) to process. "
f"Therefore it's unnecessary to have a number of workers greater than dataset.num_shards={ex_iterable.num_shards}. "
f"To enable more parallelism, please split the dataset in more files than {ex_iterable.num_shards} or try `dataset = dataset.reshard()` which may increase `num_shards` depending on the dataset file format."
)
# split workload
_log_prefix = f"node#{self._distributed.rank} " if self._distributed else ""
shards_indices = ex_iterable.split_shard_indices_by_worker(
num_shards=worker_info.num_workers, index=worker_info.id, contiguous=False
)
if shards_indices:
logger.debug(
f"{_log_prefix}dataloader worker#{worker_info.id}, ': Starting to iterate over {len(shards_indices)}/{ex_iterable.num_shards} shards."
)
ex_iterable = ex_iterable.shard_data_sources(
num_shards=worker_info.num_workers, index=worker_info.id, contiguous=False
)
ex_iterable = shift_ex_examples_rngs(ex_iterable=ex_iterable, value=worker_info.id)
self._state_dict = {
"examples_iterable": ex_iterable._init_state_dict(),
"epoch": self.epoch,
}
if self._starting_state_dict and self.epoch == self._starting_state_dict["epoch"]:
ex_iterable.load_state_dict(self._starting_state_dict["examples_iterable"])
# re-point at the live ex_iterable state so progress tracking
self._state_dict["examples_iterable"] = ex_iterable._state_dict
if self._formatting and (ex_iterable.iter_arrow or self._formatting.is_table):
formatter = get_formatter(self._formatting.format_type, features=self.features)
for key, pa_table in ex_iterable.iter_arrow():
yield formatter.format_row(pa_table)
return
else:
for key, example in ex_iterable:
# no need to format thanks to FormattedExamplesIterable
yield example
logger.debug(
f"{_log_prefix}dataloader worker#{worker_info.id}, ': Finished iterating over {len(shards_indices)}/{ex_iterable.num_shards} shards."
)
else:
logger.debug(
f"{_log_prefix}dataloader worker#{worker_info.id}, ': Stopping... Number of dataset shards < num_workers ({ex_iterable.num_shards}<{worker_info.num_workers})."
)
def _is_main_process(self):
if self._distributed and self._distributed.rank > 0:
return False
if "torch" in sys.modules:
import torch.utils.data
worker_info = torch.utils.data.get_worker_info()
if worker_info is not None and worker_info.id > 0:
return False
return True
def _prepare_ex_iterable_for_iteration(
self, batch_size: int = 1, drop_last_batch: bool = False
) -> _BaseExamplesIterable:
ex_iterable = self._ex_iterable
if self.epoch:
ex_iterable = ex_iterable.shuffle_data_sources(np.random.default_rng(self.epoch))
ex_iterable = shift_ex_examples_rngs(ex_iterable, self.epoch)
if self._distributed:
rank = self._distributed.rank
world_size = self._distributed.world_size
if ex_iterable.num_shards % world_size == 0:
if self._is_main_process():
num_shards_per_node = ex_iterable.num_shards // world_size
plural = "s" if num_shards_per_node > 1 else ""
logger.info(
f"Assigning {num_shards_per_node} shard{plural} (or data source{plural}) of the dataset to each node."
)
ex_iterable = ex_iterable.shard_data_sources(num_shards=world_size, index=rank, contiguous=False)
else:
if self._is_main_process():
logger.info(
f"Assigning 1 out of {world_size} examples of the dataset to each node. The others are skipped during the iteration."
)
logger.info(
f"It is more optimized to distribute the dataset shards (or data sources) across nodes. "
f"You can do that by using a dataset with number of shards that is a factor of world_size={world_size}. "
f"The current dataset has {ex_iterable.num_shards} which is not a factor of {world_size}"
)
ex_iterable = StepExamplesIterable(ex_iterable, step=world_size, offset=rank)
if ex_iterable.iter_arrow:
ex_iterable = RebatchedArrowExamplesIterable(
ex_iterable, batch_size=batch_size, drop_last_batch=drop_last_batch
)
elif self._formatting and self._formatting.is_table:
ex_iterable = RebatchedArrowExamplesIterable(
ex_iterable, batch_size=batch_size, drop_last_batch=drop_last_batch, force_convert_to_arrow=True
)
if self._formatting or (self.features and ex_iterable.features != self.features):
ex_iterable = FormattedExamplesIterable(
ex_iterable,
formatting=self._formatting,
features=self.features,
token_per_repo_id=self._token_per_repo_id,
)
self._state_dict = {
"examples_iterable": ex_iterable._init_state_dict(),
"epoch": self.epoch,
}
if self._starting_state_dict and self.epoch == self._starting_state_dict["epoch"]:
ex_iterable.load_state_dict(self._starting_state_dict["examples_iterable"])
# re-point at the live ex_iterable state so progress tracking
self._state_dict["examples_iterable"] = ex_iterable._state_dict
return ex_iterable
def __iter__(self):
if "torch" in sys.modules:
import torch.utils.data
worker_info = torch.utils.data.get_worker_info()
if isinstance(self, torch.utils.data.IterableDataset) and worker_info is not None:
# We're a torch.utils.data.IterableDataset in a PyTorch worker process
yield from self._iter_pytorch()
return
ex_iterable = self._prepare_ex_iterable_for_iteration()
if self._formatting and (ex_iterable.iter_arrow or self._formatting.is_table):
formatter = get_formatter(self._formatting.format_type, features=self.features)
for key, pa_table in ex_iterable.iter_arrow():
yield formatter.format_row(pa_table)
return
for key, example in ex_iterable:
# no need to format thanks to FormattedExamplesIterable
yield example
def iter(self, batch_size: int, drop_last_batch: bool = False):
"""Iterate through the batches of size `batch_size`.
Args:
batch_size (:obj:`int`): size of each batch to yield.
drop_last_batch (:obj:`bool`, default `False`): Whether a last batch smaller than the batch_size should be
dropped
"""
if self._formatting:
formatter = get_formatter(self._formatting.format_type, features=self.features)
format_dict = formatter.recursive_tensorize if isinstance(formatter, TensorFormatter) else None
else:
format_dict = None
ex_iterable = self._prepare_ex_iterable_for_iteration(batch_size=batch_size, drop_last_batch=drop_last_batch)
if self._formatting and (ex_iterable.iter_arrow or self._formatting.is_table):
for key, pa_table in ex_iterable.iter_arrow():
yield formatter.format_batch(pa_table)
return
iterator = iter(ex_iterable)
for key, example in iterator:
# If batched, first build the batch
examples = [example] + [example for key, example in islice(iterator, batch_size - 1)]
if drop_last_batch and len(examples) < batch_size: # ignore last batch
return
batch = _examples_to_batch(examples)
# we need to format here in case we need to stack tensors together
yield format_dict(batch) if format_dict else batch
def __getitem__(self, column_name: str) -> IterableColumn:
return IterableColumn(self, column_name)
@staticmethod
def from_generator(
generator: Callable,
features: Optional[Features] = None,
gen_kwargs: Optional[dict] = None,
split: NamedSplit = Split.TRAIN,
) -> "IterableDataset":
"""Create an Iterable Dataset from a generator.
Args:
generator (`Callable`):
A generator function that `yields` examples.
features (`Features`, *optional*):
Dataset features.
gen_kwargs(`dict`, *optional*):
Keyword arguments to be passed to the `generator` callable.
You can define a sharded iterable dataset by passing the list of shards in `gen_kwargs`.
This can be used to improve shuffling and when iterating over the dataset with multiple workers.
split ([`NamedSplit`], defaults to `Split.TRAIN`):
Split name to be assigned to the dataset.
<Added version="2.21.0"/>
Returns:
[`IterableDataset`]
Example:
```py
>>> def gen():
... yield {"text": "Good", "label": 0}
... yield {"text": "Bad", "label": 1}
...
>>> ds = IterableDataset.from_generator(gen)
```
```py
>>> def gen(shards):
... for shard in shards:
... with open(shard) as f:
... for line in f:
... yield {"line": line}
...
>>> shards = [f"data{i}.txt" for i in range(32)]
>>> ds = IterableDataset.from_generator(gen, gen_kwargs={"shards": shards})
>>> ds = ds.shuffle(seed=42, buffer_size=10_000) # shuffles the shards order + uses a shuffle buffer
>>> from torch.utils.data import DataLoader
>>> dataloader = DataLoader(ds.with_format("torch"), num_workers=4) # give each worker a subset of 32/4=8 shards
```
"""
from .io.generator import GeneratorDatasetInputStream
return GeneratorDatasetInputStream(
generator=generator, features=features, gen_kwargs=gen_kwargs, streaming=True, split=split
).read()
@staticmethod
def from_spark(
df: "pyspark.sql.DataFrame",
split: Optional[NamedSplit] = None,
features: Optional[Features] = None,
**kwargs,
) -> "IterableDataset":
"""Create an IterableDataset from Spark DataFrame. The dataset is streamed to the driver in batches.
Args:
df (`pyspark.sql.DataFrame`):
The DataFrame containing the desired data.
split (`NamedSplit`, *optional*):
Split name to be assigned to the dataset.
features (`Features`, *optional*):
Dataset features.
Returns:
[`IterableDataset`]
Example:
```py
>>> df = spark.createDataFrame(
>>> data=[[1, "Elia"], [2, "Teo"], [3, "Fang"]],
>>> columns=["id", "name"],
>>> )
>>> ds = IterableDataset.from_spark(df)
```
"""
from .io.spark import SparkDatasetReader
if sys.platform == "win32":
raise OSError("IterableDataset.from_spark is not currently supported on Windows")
return SparkDatasetReader(
df,
split=split,
features=features,
streaming=True,
**kwargs,
).read()
@staticmethod
def from_file(filename: str) -> "IterableDataset":
"""Instantiate a IterableDataset from Arrow table at filename.
Args:
filename (`str`):
File name of the dataset.
Returns:
[`IterableDataset`]
"""
pa_table_schema = read_schema_from_file(filename)
inferred_features = Features.from_arrow_schema(pa_table_schema)
ex_iterable = ArrowExamplesIterable(Dataset._generate_tables_from_cache_file, kwargs={"filename": filename})
return IterableDataset(ex_iterable=ex_iterable, info=DatasetInfo(features=inferred_features))
@classmethod
def from_pandas(
cls,
df: pd.DataFrame,
features: Optional[Features] = None,
info: Optional[DatasetInfo] = None,
split: Optional[NamedSplit] = None,
preserve_index: Optional[bool] = None,
num_shards: Optional[int] = 1,
) -> "IterableDataset":
"""
Convert `pandas.DataFrame` to a `pyarrow.Table` to create an [`IterableDataset`].
The column types in the resulting Arrow Table are inferred from the dtypes of the `pandas.Series` in the
DataFrame. In the case of non-object Series, the NumPy dtype is translated to its Arrow equivalent. In the
case of `object`, we need to guess the datatype by looking at the Python objects in this Series.
Be aware that Series of the `object` dtype don't carry enough information to always lead to a meaningful Arrow
type. In the case that we cannot infer a type, e.g. because the DataFrame is of length 0 or the Series only
contains `None/nan` objects, the type is set to `null`. This behavior can be avoided by constructing explicit
features and passing it to this function.
Important: a dataset created with from_pandas() lives in memory.
This may change in the future, but in the meantime if you
want to reduce memory usage you should write it on disk
and reload using e.g. to_parquet / from_parquet.
Args:
df (`pandas.DataFrame`):
Dataframe that contains the dataset.
features ([`Features`], *optional*):
Dataset features.
info (`DatasetInfo`, *optional*):
Dataset information, like description, citation, etc.
split (`NamedSplit`, *optional*):
Name of the dataset split.
preserve_index (`bool`, *optional*):
Whether to store the index as an additional column in the resulting Dataset.
The default of `None` will store the index as a column, except for `RangeIndex` which is stored as metadata only.
Use `preserve_index=True` to force it to be stored as a column.
num_shards (`int`, default to `1`):
Number of shards to define when instantiating the iterable dataset. This is especially useful for big datasets to be able to shuffle properly,
and also to enable fast parallel loading using a PyTorch DataLoader or in distributed setups for example.
Returns:
[`IterableDataset`]
Example:
```py
>>> ds = IterableDataset.from_pandas(df)
```
"""
return Dataset.from_pandas(
df,
features=features,
info=info,
split=split,
preserve_index=preserve_index,
).to_iterable_dataset(num_shards=num_shards)
@classmethod
def from_polars(
cls,
df: Union["pl.DataFrame", "pl.LazyFrame"],
features: Optional[Features] = None,
info: Optional[DatasetInfo] = None,
split: Optional[NamedSplit] = None,
) -> "IterableDataset":
"""
Create an IterableDataset from a polars DataFrame or LazyFrame.
Iterating over the dataset is mostly zero copy.
Under the hood, the dataset iterates over the polars DataFrame batches/slices.
Data types that do copy:
* CategoricalType
Args:
df (`polars.DataFrame`): DataFrame to convert to Arrow Table
features (`Features`, optional): Dataset features.
info (`DatasetInfo`, optional): Dataset information, like description, citation, etc.
split (`NamedSplit`, optional): Name of the dataset split.
Returns:
[`IterableDataset`]
Examples:
```py
>>> ds = IterableDataset.from_polars(df)
```
"""
import polars as pl
if info is not None and features is not None and info.features != features:
raise ValueError(
f"Features specified in `features` and `info.features` can't be different:\n{features}\n{info.features}"
)
features = features if features is not None else info.features if info is not None else None
if features is not None:
features = _fix_for_backward_compatible_features(features)
if info is None:
info = DatasetInfo()
info.features = features or Features.from_arrow_schema(
(df.collect_schema() if isinstance(df, pl.LazyFrame) else df.schema).to_arrow()
)
return IterableDataset(
ArrowExamplesIterable(_generate_tables_from_polars, kwargs={"df": df}),
info=info,
split=split,
)
@classmethod
def from_dict(
cls,
mapping: dict,
features: Optional[Features] = None,
info: Optional[DatasetInfo] = None,
split: Optional[NamedSplit] = None,
num_shards: Optional[int] = 1,
) -> "IterableDataset":
"""
Convert `dict` to a `pyarrow.Table` to create an [`IterableDataset`].
Important: a dataset created with from_dict() lives in memory.
This may change in the future, but in the meantime if you
want to reduce memory usage you should write it back on disk
and reload using e.g. to_parquet / from_parquet.
Args:
mapping (`Mapping`):
Mapping of strings to Arrays or Python lists.
features ([`Features`], *optional*):
Dataset features.
info (`DatasetInfo`, *optional*):
Dataset information, like description, citation, etc.
split (`NamedSplit`, *optional*):
Name of the dataset split.
num_shards (`int`, default to `1`):
Number of shards to define when instantiating the iterable dataset. This is especially useful for big datasets to be able to shuffle properly,
and also to enable fast parallel loading using a PyTorch DataLoader or in distributed setups for example.
Returns:
[`IterableDataset`]
"""
return Dataset.from_dict(mapping, features=features, info=info, split=split).to_iterable_dataset(
num_shards=num_shards
)
@classmethod
def from_list(
cls,
mapping: list[dict],
features: Optional[Features] = None,
info: Optional[DatasetInfo] = None,
split: Optional[NamedSplit] = None,
num_shards: Optional[int] = 1,
) -> "IterableDataset":
"""
Convert a list of dicts to a `pyarrow.Table` to create an [`IterableDataset`]`.
Note that the keys of the first entry will be used to determine the dataset columns,
regardless of what is passed to features.
Important: a dataset created with from_list() lives in memory.
This may change in the future, but in the meantime if you
want to reduce memory usage you should write it back on disk
and reload using e.g. from_parquet / to_parquet.
Args:
mapping (`List[dict]`): A list of mappings of strings to row values.
features (`Features`, optional): Dataset features.
info (`DatasetInfo`, optional): Dataset information, like description, citation, etc.
split (`NamedSplit`, optional): Name of the dataset split.
num_shards (`int`, default to `1`):
Number of shards to define when instantiating the iterable dataset. This is especially useful for big datasets to be able to shuffle properly,
and also to enable fast parallel loading using a PyTorch DataLoader or in distributed setups for example.
Returns:
[`IterableDataset`]
"""
return Dataset.from_list(
mapping,
features=features,
info=info,
split=split,
).to_iterable_dataset(num_shards=num_shards)
@staticmethod
def from_csv(
path_or_paths: Union[PathLike, list[PathLike]],
split: Optional[NamedSplit] = None,
features: Optional[Features] = None,
keep_in_memory: bool = False,
**kwargs,
) -> "IterableDataset":
"""Create an IterableDataset from CSV file(s).
Args:
path_or_paths (`path-like` or list of `path-like`):
Path(s) of the CSV file(s).
split ([`NamedSplit`], *optional*):
Split name to be assigned to the dataset.
features ([`Features`], *optional*):
Dataset features.
keep_in_memory (`bool`, defaults to `False`):
Whether to copy the data in-memory.
**kwargs (additional keyword arguments):
Keyword arguments to be passed to [`pandas.read_csv`].
Returns:
[`IterableDataset`]
Example:
```py
>>> ds = IterableDataset.from_csv('path/to/dataset.csv')
```
"""
# Dynamic import to avoid circular dependency
from .io.csv import CsvDatasetReader
return CsvDatasetReader(
path_or_paths,
split=split,
features=features,
keep_in_memory=keep_in_memory,
streaming=True,
**kwargs,
).read()
@staticmethod
def from_json(
path_or_paths: Union[PathLike, list[PathLike]],
split: Optional[NamedSplit] = None,
features: Optional[Features] = None,
keep_in_memory: bool = False,
field: Optional[str] = None,
**kwargs,
) -> "IterableDataset":
"""Create an IterableDataset from JSON or JSON Lines file(s).
Args:
path_or_paths (`path-like` or list of `path-like`):
Path(s) of the JSON or JSON Lines file(s).
split ([`NamedSplit`], *optional*):
Split name to be assigned to the dataset.
features ([`Features`], *optional*):
Dataset features.
keep_in_memory (`bool`, defaults to `False`):
Whether to copy the data in-memory.
field (`str`, *optional*):
Field name of the JSON file where the dataset is contained in.
**kwargs (additional keyword arguments):
Keyword arguments to be passed to [`JsonConfig`].
Returns:
[`IterableDataset`]
Example:
```py
>>> ds = IterableDataset.from_json('path/to/dataset.json')
```
"""
# Dynamic import to avoid circular dependency
from .io.json import JsonDatasetReader
return JsonDatasetReader(
path_or_paths,
split=split,
features=features,
keep_in_memory=keep_in_memory,
field=field,
streaming=True,
**kwargs,
).read()
@staticmethod
def from_parquet(
path_or_paths: Union[PathLike, list[PathLike]],
split: Optional[NamedSplit] = None,
features: Optional[Features] = None,
keep_in_memory: bool = False,
columns: Optional[list[str]] = None,
filters: Optional[Union[pds.Expression, list[tuple], list[list[tuple]]]] = None,
fragment_scan_options: Optional[pds.ParquetFragmentScanOptions] = None,
on_bad_files: Literal["error", "warn", "skip"] = "error",
**kwargs,
) -> "IterableDataset":
"""Create an IterableDataset from Parquet file(s).
Args:
path_or_paths (`path-like` or list of `path-like`):
Path(s) of the Parquet file(s).
split (`NamedSplit`, *optional*):
Split name to be assigned to the dataset.
features (`Features`, *optional*):
Dataset features.
keep_in_memory (`bool`, defaults to `False`):
Whether to copy the data in-memory.
columns (`List[str]`, *optional*):
If not `None`, only these columns will be read from the file.
A column name may be a prefix of a nested field, e.g. 'a' will select
'a.b', 'a.c', and 'a.d.e'.
filters (`Union[pyarrow.dataset.Expression, list[tuple], list[list[tuple]]]`, *optional*):
Return only the rows matching the filter.
If possible the predicate will be pushed down to exploit the partition information
or internal metadata found in the data source, e.g. Parquet statistics.
Otherwise filters the loaded RecordBatches before yielding them.
fragment_scan_options (`pyarrow.dataset.ParquetFragmentScanOptions`, *optional*)
Scan-specific options for Parquet fragments.
This is especially useful to configure buffering and caching.
<Added version="4.2.0"/>
on_bad_files (`Literal["error", "warn", "skip"]`, *optional*, defaults to "error")
Specify what to do upon encountering a bad file (a file that can't be read). Allowed values are :
* 'error', raise an Exception when a bad file is encountered.
* 'warn', raise a warning when a bad file is encountered and skip that file.
* 'skip', skip bad files without raising or warning when they are encountered.
<Added version="4.2.0"/>
**kwargs (additional keyword arguments):
Keyword arguments to be passed to [`ParquetConfig`].
Returns:
[`IterableDataset`]
Example:
```py
>>> ds = IterableDataset.from_parquet('path/to/dataset.parquet')
```
Load a subset of columns:
```python
>>> ds = IterableDataset.from_parquet('path/to/dataset.parquet', columns=["col_0", "col_1"])
```
Efficiently filter data, possibly skipping entire files or row groups:
```python
>>> filters = [("col_0", "==", 0)]
>>> ds = IterableDataset.from_parquet(parquet_files_list, filters=filters)
```
"""
# Dynamic import to avoid circular dependency
from .io.parquet import ParquetDatasetReader
return ParquetDatasetReader(
path_or_paths,
split=split,
features=features,
keep_in_memory=keep_in_memory,
columns=columns,
streaming=True,
filters=filters,
fragment_scan_options=fragment_scan_options,
on_bad_files=on_bad_files,
**kwargs,
).read()
@staticmethod
def from_text(
path_or_paths: Union[PathLike, list[PathLike]],
split: Optional[NamedSplit] = None,
features: Optional[Features] = None,
keep_in_memory: bool = False,
keep_linebreaks: bool = False,
sample_by: Literal["line", "paragraph", "document"] = "line",
**kwargs,
) -> "IterableDataset":
"""Create an IterableDataset from text file(s).
Args:
path_or_paths (`path-like` or list of `path-like`):
Path(s) of the text file(s).
split (`NamedSplit`, *optional*):
Split name to be assigned to the dataset.
features (`Features`, *optional*):
Dataset features.
keep_in_memory (`bool`, defaults to `False`):
Whether to copy the data in-memory.
keep_linebreaks: (`bool`, defaults to False):
Whether to keep line breaks.
sample_by (`Literal["line", "paragraph", "document"]`, defaults to "line"):
Whether to load data per line, praragraph or document.
By default one row in the dataset = one line.
**kwargs (additional keyword arguments):
Keyword arguments to be passed to [`TextConfig`].
Returns:
[`IterableDataset`]
Example:
```py
>>> ds = IterableDataset.from_text('path/to/dataset.txt')
```
"""
# Dynamic import to avoid circular dependency
from .io.text import TextDatasetReader
return TextDatasetReader(
path_or_paths,
split=split,
features=features,
keep_in_memory=keep_in_memory,
streaming=True,
keep_linebreaks=keep_linebreaks,
sample_by=sample_by,
**kwargs,
).read()
def with_format(
self,
type: Optional[str] = None,
) -> "IterableDataset":
"""
Return a dataset with the specified format.
Args:
type (`str`, *optional*):
Either output type selected in `[None, 'numpy', 'torch', 'tensorflow', 'jax', 'arrow', 'pandas', 'polars']`.
`None` means it returns python objects (default).
Example:
```py
>>> from datasets import load_dataset
>>> from transformers import AutoTokenizer
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="validation", streaming=True)
>>> tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
>>> ds = ds.map(lambda x: tokenizer(x['text'], truncation=True, padding=True), batched=True)
>>> ds = ds.with_format("torch")
>>> next(iter(ds))
{'text': 'compassionately explores the seemingly irreconcilable situation between conservative christian parents and their estranged gay and lesbian children .',
'label': tensor(1),
'input_ids': tensor([ 101, 18027, 16310, 16001, 1103, 9321, 178, 11604, 7235, 6617,
1742, 2165, 2820, 1206, 6588, 22572, 12937, 1811, 2153, 1105,
1147, 12890, 19587, 6463, 1105, 15026, 1482, 119, 102, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0]),
'token_type_ids': tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
'attention_mask': tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])}
```
"""
type = get_format_type_from_alias(type)
# TODO(QL): add format_kwargs
# TODO(QL): add format_columns and return_all_columns
# TODO(QL): add pandas format
return IterableDataset(
ex_iterable=self._ex_iterable,
info=self._info.copy(),
split=self._split,
formatting=FormattingConfig(format_type=type),
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def map(
self,
function: Optional[Callable] = None,
with_indices: bool = False,
input_columns: Optional[Union[str, list[str]]] = None,
batched: bool = False,
batch_size: Optional[int] = 1000,
drop_last_batch: bool = False,
remove_columns: Optional[Union[str, list[str]]] = None,
features: Optional[Features] = None,
fn_kwargs: Optional[dict] = None,
) -> "IterableDataset":
"""
Apply a function to all the examples in the iterable dataset (individually or in batches) and update them.
If your function returns a column that already exists, then it overwrites it.
The function is applied on-the-fly on the examples when iterating over the dataset.
You can specify whether the function should be batched or not with the `batched` parameter:
- If batched is `False`, then the function takes 1 example in and should return 1 example.
An example is a dictionary, e.g. `{"text": "Hello there !"}`.
- If batched is `True` and `batch_size` is 1, then the function takes a batch of 1 example as input and can return a batch with 1 or more examples.
A batch is a dictionary, e.g. a batch of 1 example is {"text": ["Hello there !"]}.
- If batched is `True` and `batch_size` is `n` > 1, then the function takes a batch of `n` examples as input and can return a batch with `n` examples, or with an arbitrary number of examples.
Note that the last batch may have less than `n` examples.
A batch is a dictionary, e.g. a batch of `n` examples is `{"text": ["Hello there !"] * n}`.
If the function is asynchronous, then `map` will run your function in parallel, with up to one thousand simulatenous calls.
It is recommended to use a `asyncio.Semaphore` in your function if you want to set a maximum number of operations that can run at the same time.
Args:
function (`Callable`, *optional*, defaults to `None`):
Function applied on-the-fly on the examples when you iterate on the dataset.
It must have one of the following signatures:
- `function(example: Dict[str, Any]) -> Dict[str, Any]` if `batched=False` and `with_indices=False`
- `function(example: Dict[str, Any], idx: int) -> Dict[str, Any]` if `batched=False` and `with_indices=True`
- `function(batch: Dict[str, List]) -> Dict[str, List]` if `batched=True` and `with_indices=False`
- `function(batch: Dict[str, List], indices: List[int]) -> Dict[str, List]` if `batched=True` and `with_indices=True`
For advanced usage, the function can also return a `pyarrow.Table`.
If the function is asynchronous, then `map` will run your function in parallel.
Moreover if your function returns nothing (`None`), then `map` will run your function and return the dataset unchanged.
If no function is provided, default to identity function: `lambda x: x`.
with_indices (`bool`, defaults to `False`):
Provide example indices to `function`. Note that in this case the signature of `function` should be `def function(example, idx[, rank]): ...`.
input_columns (`Optional[Union[str, List[str]]]`, defaults to `None`):
The columns to be passed into `function`
as positional arguments. If `None`, a dict mapping to all formatted columns is passed as one argument.
batched (`bool`, defaults to `False`):
Provide batch of examples to `function`.
batch_size (`int`, *optional*, defaults to `1000`):
Number of examples per batch provided to `function` if `batched=True`.
`batch_size <= 0` or `batch_size == None` then provide the full dataset as a single batch to `function`.
drop_last_batch (`bool`, defaults to `False`):
Whether a last batch smaller than the batch_size should be
dropped instead of being processed by the function.
remove_columns (`[List[str]]`, *optional*, defaults to `None`):
Remove a selection of columns while doing the mapping.
Columns will be removed before updating the examples with the output of `function`, i.e. if `function` is adding
columns with names in `remove_columns`, these columns will be kept.
features (`[Features]`, *optional*, defaults to `None`):
Feature types of the resulting dataset.
fn_kwargs (`Dict`, *optional*, default `None`):
Keyword arguments to be passed to `function`.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> def add_prefix(example):
... example["text"] = "Review: " + example["text"]
... return example
>>> ds = ds.map(add_prefix)
>>> list(ds.take(3))
[{'label': 1,
'text': 'Review: the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'},
{'label': 1,
'text': 'Review: the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .'},
{'label': 1, 'text': 'Review: effective but too-tepid biopic'}]
```
"""
return self._map(
function=function,
with_indices=with_indices,
input_columns=input_columns,
batched=batched,
batch_size=batch_size,
drop_last_batch=drop_last_batch,
remove_columns=remove_columns,
features=features,
fn_kwargs=fn_kwargs,
)
def _map(
self,
function: Optional[Callable] = None,
with_indices: bool = False,
input_columns: Optional[Union[str, list[str]]] = None,
batched: bool = False,
batch_size: Optional[int] = 1000,
drop_last_batch: bool = False,
remove_columns: Optional[Union[str, list[str]]] = None,
features: Optional[Features] = None,
fn_kwargs: Optional[dict] = None,
is_batch_accumulate_arrow_table_function: bool = False,
) -> "IterableDataset":
if isinstance(input_columns, str):
input_columns = [input_columns]
if isinstance(remove_columns, str):
remove_columns = [remove_columns]
if function is None:
function = identity_func
if fn_kwargs is None:
fn_kwargs = {}
if features is not None:
features = _fix_for_backward_compatible_features(features)
ex_iterable = self._ex_iterable
# no need to apply features if ex_iterable is typed and if there was no cast_column()
input_features = (
None
if (ex_iterable.is_typed and (self._info.features is None or self._info.features == ex_iterable.features))
else self._info.features
)
if self._formatting and self._formatting.is_table:
# apply formatting before iter_arrow to keep map examples iterable happy
ex_iterable = FormattedExamplesIterable(
ex_iterable,
formatting=deepcopy(self._formatting),
features=input_features,
token_per_repo_id=self._token_per_repo_id,
)
ex_iterable = RebatchedArrowExamplesIterable(
ex_iterable,
batch_size=batch_size if batched else 1,
drop_last_batch=drop_last_batch,
force_convert_to_arrow=True,
)
else:
if self._ex_iterable.iter_arrow:
if self._formatting or input_features:
ex_iterable = RebatchedArrowExamplesIterable(
self._ex_iterable, batch_size=batch_size if batched else 1, drop_last_batch=drop_last_batch
)
if self._formatting or input_features:
# apply formatting after iter_arrow to avoid re-encoding the examples
ex_iterable = FormattedExamplesIterable(
ex_iterable,
formatting=deepcopy(self._formatting),
features=input_features,
token_per_repo_id=self._token_per_repo_id,
force_convert_to_python=True,
)
ex_iterable = MappedExamplesIterable(
ex_iterable,
function=function,
with_indices=with_indices,
input_columns=input_columns,
batched=batched,
batch_size=batch_size,
drop_last_batch=drop_last_batch,
remove_columns=remove_columns,
fn_kwargs=fn_kwargs,
formatting=self._formatting,
features=features,
is_batch_accumulate_arrow_table_function=is_batch_accumulate_arrow_table_function,
)
info = self.info.copy()
info.features = features
return IterableDataset(
ex_iterable=ex_iterable,
info=info,
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def filter(
self,
function: Optional[Callable] = None,
with_indices=False,
input_columns: Optional[Union[str, list[str]]] = None,
batched: bool = False,
batch_size: Optional[int] = 1000,
fn_kwargs: Optional[dict] = None,
) -> "IterableDataset":
"""Apply a filter function to all the elements so that the dataset only includes examples according to the filter function.
The filtering is done on-the-fly when iterating over the dataset.
If the function is asynchronous, then `filter` will run your function in parallel, with up to one thousand simulatenous calls (configurable).
It is recommended to use a `asyncio.Semaphore` in your function if you want to set a maximum number of operations that can run at the same time.
Args:
function (`Callable`):
Callable with one of the following signatures:
- `function(example: Dict[str, Any]) -> bool` if `with_indices=False, batched=False`
- `function(example: Dict[str, Any], indices: int) -> bool` if `with_indices=True, batched=False`
- `function(example: Dict[str, List]) -> List[bool]` if `with_indices=False, batched=True`
- `function(example: Dict[str, List], indices: List[int]) -> List[bool]` if `with_indices=True, batched=True`
If the function is asynchronous, then `filter` will run your function in parallel.
If no function is provided, defaults to an always True function: `lambda x: True`.
with_indices (`bool`, defaults to `False`):
Provide example indices to `function`. Note that in this case the signature of `function` should be `def function(example, idx): ...`.
input_columns (`str` or `List[str]`, *optional*):
The columns to be passed into `function` as
positional arguments. If `None`, a dict mapping to all formatted columns is passed as one argument.
batched (`bool`, defaults to `False`):
Provide batch of examples to `function`.
batch_size (`int`, *optional*, default `1000`):
Number of examples per batch provided to `function` if `batched=True`.
fn_kwargs (`Dict`, *optional*, default `None`):
Keyword arguments to be passed to `function`.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> ds = ds.filter(lambda x: x["label"] == 0)
>>> list(ds.take(3))
[{'label': 0, 'movie_review': 'simplistic , silly and tedious .'},
{'label': 0,
'movie_review': "it's so laddish and juvenile , only teenage boys could possibly find it funny ."},
{'label': 0,
'movie_review': 'exploitative and largely devoid of the depth or sophistication that would make watching such a graphic treatment of the crimes bearable .'}]
```
"""
if isinstance(input_columns, str):
input_columns = [input_columns]
# We need the examples to be decoded for certain feature types like Image or Audio,
# format and type before filtering
ex_iterable = self._ex_iterable
if self._info.features or self._formatting:
ex_iterable = FormattedExamplesIterable(
ex_iterable,
formatting=self._formatting,
features=ex_iterable.features if ex_iterable.is_typed else self._info.features,
token_per_repo_id=self._token_per_repo_id,
)
ex_iterable = FilteredExamplesIterable(
ex_iterable,
function=function,
with_indices=with_indices,
input_columns=input_columns,
batched=batched,
batch_size=batch_size,
fn_kwargs=fn_kwargs,
formatting=self._formatting,
)
return IterableDataset(
ex_iterable=ex_iterable,
info=self._info,
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def shuffle(
self,
seed=None,
generator: Optional[np.random.Generator] = None,
buffer_size: int = 1000,
max_buffer_input_shards: int = 10,
) -> "IterableDataset":
"""
Randomly shuffles the elements of this dataset.
This dataset fills a buffer with `buffer_size` elements, then randomly samples elements from this buffer,
replacing the selected elements with new elements. For perfect shuffling, a buffer size greater than or
equal to the full size of the dataset is required.
For instance, if your dataset contains 10,000 elements but `buffer_size` is set to 1000, then `shuffle` will
initially select a random element from only the first 1000 elements in the buffer. Once an element is
selected, its space in the buffer is replaced by the next (i.e. 1,001-st) element,
maintaining the 1000 element buffer.
If the dataset is made of several shards, it fills the buffer using up to `max_buffer_input_shards` shards
at a time and also does shuffle the order of the shards. This greatly improves the quality of the shuffling.
However if the order has been fixed by using [`~datasets.IterableDataset.skip`]
or [`~datasets.IterableDataset.take`] then the order of the shards is kept unchanged and only one shard at
a time is used to fill the buffer.
Args:
seed (`int`, *optional*, defaults to `None`):
Random seed that will be used to shuffle the dataset.
It is used to sample from the shuffle buffer and also to shuffle the data shards.
generator (`numpy.random.Generator`, *optional*):
Numpy random Generator to use to compute the permutation of the dataset rows.
If `generator=None` (default), uses `np.random.default_rng` (the default BitGenerator (PCG64) of NumPy).
buffer_size (`int`, defaults to `1000`):
Size of the buffer.
max_buffer_input_shards (`int`, defaults to `101000`):
Maximum number of shards to use to feed the buffer at a time.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> list(ds.take(3))
[{'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .',
'label': 1},
{'text': 'the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .',
'label': 1},
{'text': 'effective but too-tepid biopic', 'label': 1}]
>>> shuffled_ds = ds.shuffle(seed=42)
>>> list(shuffled_ds.take(3))
[{'text': "a sports movie with action that's exciting on the field and a story you care about off it .",
'label': 1},
{'text': 'at its best , the good girl is a refreshingly adult take on adultery . . .',
'label': 1},
{'text': "sam jones became a very lucky filmmaker the day wilco got dropped from their record label , proving that one man's ruin may be another's fortune .",
'label': 1}]
>>> resharded_ds = ds.reshard() # useful to shard Parquet datasets by row group instead of by file, improving shuffling quality on dataset with one or few files.
>>> shuffled_resharded_ds = resharded_ds.shuffle(seed=42)
>>> list(shuffled_resharded_ds.take(3))
[{'text': 'this mistaken-identity picture is so film-culture referential that the final product is a ghost .',
'label': 0},
{'text': 'woody allen used to ridicule movies like hollywood ending . now he makes them .',
'label': 0},
{'text': "not only is undercover brother as funny , if not more so , than both austin powers films , but it's also one of the smarter , savvier spoofs to come along in some time .",
'label': 1}]
```
"""
if generator is None:
generator = np.random.default_rng(seed)
else:
generator = deepcopy(generator)
ex_iterable = self._ex_iterable
try:
ex_iterable = ex_iterable.shuffle_data_sources(generator)
except DataSourcesShufflingDisallowed:
max_buffer_input_shards = 1
if ex_iterable.iter_arrow:
ex_iterable = RebatchedArrowExamplesIterable(ex_iterable, batch_size=1)
if max_buffer_input_shards > 1:
num_shards_to_interleave = min(ex_iterable.num_shards, max_buffer_input_shards)
ex_iterable = CyclingMultiSourcesExamplesIterable(
[
ex_iterable.shard_data_sources(num_shards=num_shards_to_interleave, index=index)
for index in range(num_shards_to_interleave)
],
stopping_strategy="all_exhausted_without_replacement",
)
ex_iterable = BufferShuffledExamplesIterable(ex_iterable, buffer_size=buffer_size, generator=generator)
return IterableDataset(
ex_iterable,
info=self._info.copy(),
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def set_epoch(self, epoch: int):
self._epoch += epoch - self._epoch # update torch value in shared memory in-place
def skip(self, n: int) -> "IterableDataset":
"""
Create a new [`IterableDataset`] that skips the first `n` elements.
Args:
n (`int`):
Number of elements to skip.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> list(ds.take(3))
[{'label': 1,
'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'},
{'label': 1,
'text': 'the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .'},
{'label': 1, 'text': 'effective but too-tepid biopic'}]
>>> ds = ds.skip(1)
>>> list(ds.take(3))
[{'label': 1,
'text': 'the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .'},
{'label': 1, 'text': 'effective but too-tepid biopic'},
{'label': 1,
'text': 'if you sometimes like to go to the movies to have fun , wasabi is a good place to start .'}]
```
"""
ex_iterable = SkipExamplesIterable(
self._ex_iterable,
n,
split_when_sharding=self._distributed is None,
)
return IterableDataset(
ex_iterable=ex_iterable,
info=self._info.copy(),
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def repeat(self, num_times: Optional[int]) -> "IterableDataset":
"""
Create a new [`IterableDataset`] that repeats the underlying dataset `num_times` times.
N.B. The effect of calling shuffle after repeat depends significantly on buffer size.
With buffer_size 1, duplicate data is never seen in the same iteration, even after shuffling:
ds.repeat(n).shuffle(seed=42, buffer_size=1) is equivalent to ds.shuffle(seed=42, buffer_size=1).repeat(n),
and only shuffles shard orders within each iteration.
With buffer size >= (num samples in the dataset * num_times), we get full shuffling of the repeated data, i.e. we can observe duplicates in
the same iteration.
Args:
num_times (`int`) or (`None`):
Number of times to repeat the dataset. If `None`, the dataset will be repeated indefinitely.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train")
>>> ds = ds.take(2).repeat(2)
>>> list(ds)
[{'label': 1,
'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'},
{'label': 1,
'text': 'the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .'},
{'label': 1, 'text': 'effective but too-tepid biopic'},
{'label': 1,
'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'},
{'label': 1,
'text': 'the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .'},
{'label': 1, 'text': 'effective but too-tepid biopic'}]
```
"""
return IterableDataset(
ex_iterable=RepeatExamplesIterable(self._ex_iterable, num_times=num_times),
info=self._info,
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def take(self, n: int) -> "IterableDataset":
"""
Create a new [`IterableDataset`] with only the first `n` elements.
Args:
n (`int`):
Number of elements to take.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> small_ds = ds.take(2)
>>> list(small_ds)
[{'label': 1,
'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'},
{'label': 1,
'text': 'the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .'}]
```
"""
ex_iterable = TakeExamplesIterable(
self._ex_iterable,
n,
split_when_sharding=self._distributed is None,
)
return IterableDataset(
ex_iterable=ex_iterable,
info=self._info.copy(),
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def shard(
self,
num_shards: int,
index: int,
contiguous: bool = True,
) -> "IterableDataset":
"""Return the `index`-nth shard from dataset split into `num_shards` pieces.
This shards deterministically. `dataset.shard(n, i)` splits the dataset into contiguous chunks,
so it can be easily concatenated back together after processing. If `dataset.num_shards % n == l`, then the
first `l` datasets each have `(dataset.num_shards // n) + 1` shards, and the remaining datasets have `(dataset.num_shards // n)` shards.
`datasets.concatenate_datasets([dset.shard(n, i) for i in range(n)])` returns a dataset with the same order as the original.
In particular, `dataset.shard(dataset.num_shards, i)` returns a dataset with 1 shard.
Note: n should be less or equal to the number of shards in the dataset `dataset.num_shards`.
On the other hand, `dataset.shard(n, i, contiguous=False)` contains all the shards of the dataset whose index mod `n = i`.
Be sure to shard before using any randomizing operator (such as `shuffle`).
It is best if the shard operator is used early in the dataset pipeline.
Args:
num_shards (`int`):
How many shards to split the dataset into.
index (`int`):
Which shard to select and return.
contiguous: (`bool`, defaults to `True`):
Whether to select contiguous blocks of indices for shards.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("fancyzhx/amazon_polarity", split="train", streaming=True)
>>> ds
IterableDataset({
features: ['label', 'title', 'content'],
num_shards: 4
})
>>> ds.shard(num_shards=2, index=0)
IterableDataset({
features: ['label', 'title', 'content'],
num_shards: 2
})
```
"""
ex_iterable = self._ex_iterable.shard_data_sources(num_shards=num_shards, index=index, contiguous=contiguous)
return IterableDataset(
ex_iterable=ex_iterable,
info=self._info.copy(),
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def reshard(self) -> "IterableDataset":
"""Reshard the dataset if possible, i.e. split the current shards further into more shards.
This increases the number of shards and the resulting dataset has num_shards >= previous_num_shards.
Equality may happen if no shard can be split further.
The resharding mechanism depends on the dataset file format:
* Parquet: shard per row group instead of per file
* Other: not implemented yet (contributions are welcome !)
Be sure to reshard/shard before using any randomizing operator (such as `shuffle`).
It is best if the shard operator is used early in the dataset pipeline.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("fancyzhx/amazon_polarity", split="train", streaming=True)
>>> ds
IterableDataset({
features: ['label', 'title', 'content'],
num_shards: 4
})
>>> ds.reshard()
IterableDataset({
features: ['label', 'title', 'content'],
num_shards: 3600
})
```
"""
ex_iterable = self._ex_iterable.reshard_data_sources()
return IterableDataset(
ex_iterable=ex_iterable,
info=self._info.copy(),
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def add_column(self, name: str, column: Union[list, np.array]) -> "IterableDataset":
"""Add column to Dataset.
Args:
name (str): Column name.
column (list or np.array): Column data to be added.
Returns:
`IterableDataset`
"""
return self.map(partial(add_column_fn, name=name, column=column), with_indices=True)
def rename_column(self, original_column_name: str, new_column_name: str) -> "IterableDataset":
"""
Rename a column in the dataset, and move the features associated to the original column under the new column
name.
Args:
original_column_name (`str`):
Name of the column to rename.
new_column_name (`str`):
New name for the column.
Returns:
`IterableDataset`: A copy of the dataset with a renamed column.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> next(iter(ds))
{'label': 1,
'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'}
>>> ds = ds.rename_column("text", "movie_review")
>>> next(iter(ds))
{'label': 1,
'movie_review': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'}
```
"""
return self.rename_columns({original_column_name: new_column_name})
def rename_columns(self, column_mapping: dict[str, str]) -> "IterableDataset":
"""
Rename several columns in the dataset, and move the features associated to the original columns under
the new column names.
Args:
column_mapping (`Dict[str, str]`): A mapping of columns to rename to their new names
Returns:
`IterableDataset`: A copy of the dataset with renamed columns
"""
original_features = self._info.features.copy() if self._info.features else None
ds_iterable = self.map(
partial(_rename_columns_fn, column_mapping=column_mapping), remove_columns=list(column_mapping)
)
if original_features is not None:
ds_iterable._info.features = Features(
{
column_mapping[col] if col in column_mapping.keys() else col: feature
for col, feature in original_features.items()
}
)
return ds_iterable
def remove_columns(self, column_names: Union[str, list[str]]) -> "IterableDataset":
"""
Remove one or several column(s) in the dataset and the features associated to them.
The removal is done on-the-fly on the examples when iterating over the dataset.
Args:
column_names (`Union[str, List[str]]`):
Name of the column(s) to remove.
Returns:
`IterableDataset`: A copy of the dataset object without the columns to remove.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> next(iter(ds))
{'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .', 'label': 1}
>>> ds = ds.remove_columns("label")
>>> next(iter(ds))
{'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'}
```
"""
original_features = self._info.features.copy() if self._info.features else None
ds_iterable = self.map(remove_columns=column_names)
if original_features is not None:
ds_iterable._info.features = original_features.copy()
for col, _ in original_features.items():
if col in column_names:
del ds_iterable._info.features[col]
return ds_iterable
def select_columns(self, column_names: Union[str, list[str]]) -> "IterableDataset":
"""Select one or several column(s) in the dataset and the features
associated to them. The selection is done on-the-fly on the examples
when iterating over the dataset.
Args:
column_names (`Union[str, List[str]]`):
Name of the column(s) to select.
Returns:
`IterableDataset`: A copy of the dataset object with selected columns.
Example:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> next(iter(ds))
{'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .', 'label': 1}
>>> ds = ds.select_columns("text")
>>> next(iter(ds))
{'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'}
```
"""
if isinstance(column_names, str):
column_names = [column_names]
if self._info:
info = deepcopy(self._info)
if self._info.features is not None:
missing_columns = set(column_names) - set(self._info.features.keys())
if missing_columns:
raise ValueError(
f"Column name {list(missing_columns)} not in the "
"dataset. Columns in the dataset: "
f"{list(self._info.features.keys())}."
)
info.features = Features({c: info.features[c] for c in column_names})
ex_iterable = SelectColumnsIterable(self._ex_iterable, column_names)
return IterableDataset(
ex_iterable=ex_iterable,
info=info,
split=self._split,
formatting=self._formatting,
distributed=self._distributed,
token_per_repo_id=self._token_per_repo_id,
)
def cast_column(self, column: str, feature: FeatureType) -> "IterableDataset":
"""Cast column to feature for decoding.
Args:
column (`str`):
Column name.
feature (`Feature`):
Target feature.
Returns:
`IterableDataset`
Example:
```py
>>> from datasets import load_dataset, Audio
>>> ds = load_dataset("PolyAI/minds14", name="en-US", split="train", streaming=True)
>>> ds.features
{'audio': Audio(sampling_rate=8000, mono=True, decode=True, id=None),
'english_transcription': Value('string'),
'intent_class': ClassLabel(num_classes=14, names=['abroad', 'address', 'app_error', 'atm_limit', 'balance', 'business_loan', 'card_issues', 'cash_deposit', 'direct_debit', 'freeze', 'high_value_payment', 'joint_account', 'latest_transactions', 'pay_bill']),
'lang_id': ClassLabel(num_classes=14, names=['cs-CZ', 'de-DE', 'en-AU', 'en-GB', 'en-US', 'es-ES', 'fr-FR', 'it-IT', 'ko-KR', 'nl-NL', 'pl-PL', 'pt-PT', 'ru-RU', 'zh-CN']),
'path': Value('string'),
'transcription': Value('string')}
>>> ds = ds.cast_column("audio", Audio(sampling_rate=16000))
>>> ds.features
{'audio': Audio(sampling_rate=16000, mono=True, decode=True, id=None),
'english_transcription': Value('string'),
'intent_class': ClassLabel(num_classes=14, names=['abroad', 'address', 'app_error', 'atm_limit', 'balance', 'business_loan', 'card_issues', 'cash_deposit', 'direct_debit', 'freeze', 'high_value_payment', 'joint_account', 'latest_transactions', 'pay_bill']),
'lang_id': ClassLabel(num_classes=14, names=['cs-CZ', 'de-DE', 'en-AU', 'en-GB', 'en-US', 'es-ES', 'fr-FR', 'it-IT', 'ko-KR', 'nl-NL', 'pl-PL', 'pt-PT', 'ru-RU', 'zh-CN']),
'path': Value('string'),
'transcription': Value('string')}
```
"""
feature = _fix_for_backward_compatible_features(feature)
info = self._info.copy()
info.features[column] = feature
return IterableDataset(
ex_iterable=self._ex_iterable,
info=info,
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def cast(
self,
features: Features,
) -> "IterableDataset":
"""
Cast the dataset to a new set of features.
Args:
features ([`Features`]):
New features to cast the dataset to.
The name of the fields in the features must match the current column names.
The type of the data must also be convertible from one type to the other.
For non-trivial conversion, e.g. `string` <-> `ClassLabel` you should use [`~Dataset.map`] to update the Dataset.
Returns:
`IterableDataset`: A copy of the dataset with casted features.
Example:
```py
>>> from datasets import load_dataset, ClassLabel, Value
>>> ds = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train", streaming=True)
>>> ds.features
{'label': ClassLabel(names=['neg', 'pos']),
'text': Value('string')}
>>> new_features = ds.features.copy()
>>> new_features["label"] = ClassLabel(names=["bad", "good"])
>>> new_features["text"] = Value("large_string")
>>> ds = ds.cast(new_features)
>>> ds.features
{'label': ClassLabel(names=['bad', 'good']),
'text': Value('large_string')}
```
"""
features = _fix_for_backward_compatible_features(features)
info = self._info.copy()
info.features = features
return IterableDataset(
ex_iterable=self._ex_iterable,
info=info,
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def decode(self, enable: bool = True, num_threads: int = 0) -> "IterableDataset":
"""
Enable or disable the dataset features decoding for audio, image, video.
When enabled (default), media types are decoded:
* audio -> dict of "array" and "sampling_rate" and "path"
* image -> PIL.Image
* video -> torchcodec.decoders.VideoDecoder
You can enable multithreading using `num_threads`. This is especially useful to speed up remote
data streaming. However it can be slower than `num_threads=0` for local data on fast disks.
Disabling decoding is useful if you want to iterate on the paths or bytes of the media files
without actually decoding their content. To disable decoding you can use `.decode(False)`, which
is equivalent to calling `.cast()` or `.cast_column()` with all the Audio, Image and Video types
set to `decode=False`.
Args:
enable (`bool`, defaults to `True`):
Enable or disable features decoding.
num_threads (`int`, defaults to `0`):
Enable multithreading for features decoding.
Returns:
`IterableDataset`: A copy of the dataset with casted features.
Examples:
Disable decoding:
```py
>>> from datasets import load_dataset
>>> ds = load_dataset("sshh12/planet-textures", split="train", streaming=True)
>>> next(iter(ds))
{'image': <PIL.PngImagePlugin.PngImageFile image mode=RGB size=2048x1024>,
'text': 'A distant celestial object with an icy crust, displaying a light blue shade, covered with round pits and rugged terrains.'}
>>> ds = ds.decode(False)
>>> ds.features
{'image': Image(mode=None, decode=False, id=None),
'text': Value('string')}
>>> next(iter(ds))
{
'image': {
'path': 'hf://datasets/sshh12/planet-textures@69dc4cef7a5c4b2cfe387727ec8ea73d4bff7302/train/textures/0000.png',
'bytes': None
},
'text': 'A distant celestial object with an icy crust, displaying a light blue shade, covered with round pits and rugged terrains.'
}
```
Speed up streaming with multithreading:
```py
>>> import os
>>> from datasets import load_dataset
>>> from tqdm import tqdm
>>> ds = load_dataset("sshh12/planet-textures", split="train", streaming=True)
>>> num_threads = min(32, (os.cpu_count() or 1) + 4)
>>> ds = ds.decode(num_threads=num_threads)
>>> for _ in tqdm(ds): # 20 times faster !
... ...
```
"""
if not self.features:
raise ValueError(
"Features decoding is only available for datasets with known features, but features are Unknown. "
"Please set the datasets features with `ds = ds.cast(features)`."
)
ds = self
def set_decoding(decode: bool, feature):
if hasattr(feature, "decode"):
feature.decode = decode
if enable and num_threads > 0:
disabled_decoding_features = self.features.copy()
enabled_decoding_features = self.features.copy()
_visit(disabled_decoding_features, partial(set_decoding, False))
_visit(enabled_decoding_features, partial(set_decoding, True))
ds = ds.cast(disabled_decoding_features)
pool = multiprocessing.pool.ThreadPool(num_threads)
func = partial(_apply_async, pool, enabled_decoding_features.decode_example)
ds = ds.map(func, features=enabled_decoding_features)
assert isinstance(ds._ex_iterable, MappedExamplesIterable)
ds._ex_iterable.max_num_running_async_map_functions_in_parallel = 2 * num_threads
else:
features = ds.features.copy()
_visit(features, partial(set_decoding, enable))
ds = ds.cast(features)
return ds
def _step(self, step: int, offset: int) -> "IterableDataset":
ex_iterable = StepExamplesIterable(self._ex_iterable, step=step, offset=offset)
return IterableDataset(
ex_iterable=ex_iterable,
info=self._info.copy(),
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def _resolve_features(self):
if self.features is not None:
return self
elif self._ex_iterable.is_typed:
features = self._ex_iterable.features
else:
features = _infer_features_from_batch(self.with_format(None)._head())
info = self.info.copy()
info.features = features
return IterableDataset(
ex_iterable=self._ex_iterable,
info=info,
split=self._split,
formatting=self._formatting,
distributed=deepcopy(self._distributed),
token_per_repo_id=self._token_per_repo_id,
)
def batch(
self,
batch_size: Optional[int] = None,
by_column: Optional[Union[str, list[str]]] = None,
drop_last_batch: bool = False,
) -> "IterableDataset":
"""
Group samples from the dataset into batches.
Args:
batch_size (`int`, optional):
The number of samples in each batch.
by_column (`Union[str, list[str]`, optional):
The column used to batch examples together.
Successive examples with the same value for that column are in grouped the same batch.
This can also be a list of columns if you want to batch by multiple columns.
If batching by column, the batch_size is only used to control the size of the batches
to group together or slice during acculumation.
<Added version="4.9.0"/>
drop_last_batch (`bool`, defaults to `False`):
Whether to drop the last incomplete batch.
Example:
```py
>>> ds = load_dataset("some_dataset", streaming=True)
>>> batched_ds = ds.batch(batch_size=32)
```
"""
if batch_size is None and by_column is None:
raise ValueError("IterableDataset.batch() misses `batch_size` or `by_column` argument.")
if self.features:
features = Features({col: List(feature) for col, feature in self.features.items()})
else:
features = None
if by_column is not None:
columns = [by_column] if isinstance(by_column, str) else by_column
ds = (
self.with_format("arrow")
._map(
partial(_batch_accumulate_arrow_table_by_columns, columns=columns),
with_indices=True,
batched=True,
batch_size=batch_size,
drop_last_batch=drop_last_batch,
features=features,
is_batch_accumulate_arrow_table_function=True,
)
.with_format(self._formatting.format_type if self._formatting else None)
)
return ds
if self._formatting and self._formatting.is_table:
return (
self.with_format("arrow")
.map(
_batch_arrow_table,
batched=True,
batch_size=batch_size,
drop_last_batch=drop_last_batch,
features=features,
)
.with_format(self._formatting.format_type)
)
return self.map(
_batch_fn, batched=True, batch_size=batch_size, drop_last_batch=drop_last_batch, features=features
)
def to_dict(self, batch_size: Optional[int] = None, batched: bool = False) -> Union[dict, Iterator[dict]]:
"""Returns the dataset as a Python dict. Can also return a generator for large datasets.
Args:
batch_size (`int`, *optional*): The size (number of rows) of the batches if `batched` is `True`.
Defaults to `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
Returns:
`dict` or `Iterator[dict]`
Example:
```py
>>> ds.to_dict()
```
"""
if batched:
for table in self.with_format("arrow").iter(batch_size=batch_size):
yield Dataset(table, fingerprint="unset").to_dict()
else:
table = pa.concat_tables(list(self.with_format("arrow").iter(batch_size=1000)))
return Dataset(table, fingerprint="unset").to_dict()
def to_list(self) -> list:
"""Returns the dataset as a Python list.
Returns:
`list`
Example:
```py
>>> ds.to_list()
```
"""
table = pa.concat_tables(list(self.with_format("arrow").iter(batch_size=1000)))
return Dataset(table, fingerprint="unset").to_list()
def to_pandas(
self, batch_size: Optional[int] = None, batched: bool = False
) -> Union[pd.DataFrame, Iterator[pd.DataFrame]]:
"""Returns the dataset as a `pandas.DataFrame`. Can also return a generator for large datasets.
Args:
batch_size (`int`, *optional*):
The size (number of rows) of the batches if `batched` is `True`.
Defaults to `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
batched (`bool`):
Set to `True` to return a generator that yields the dataset as batches
of `batch_size` rows. Defaults to `False` (returns the whole datasets once).
Returns:
`pandas.DataFrame` or `Iterator[pandas.DataFrame]`
Example:
```py
>>> ds.to_pandas()
```
"""
info = DatasetInfo(features=self.features.copy()) if self.features is not None else None
def maybe_cast_to_declared_features(table: pa.Table):
if self.features is not None and table.schema != self.features.arrow_schema:
return cast_table_to_features(table, self.features)
return table
if batched:
return (
Dataset(maybe_cast_to_declared_features(table), info=info, fingerprint="unset").to_pandas()
for table in self.with_format("arrow").iter(batch_size=batch_size)
)
else:
table = pa.concat_tables(
[maybe_cast_to_declared_features(table) for table in self.with_format("arrow").iter(batch_size=1000)]
)
return Dataset(table, info=info, fingerprint="unset").to_pandas()
def to_polars(
self,
batch_size: Optional[int] = None,
batched: bool = False,
schema_overrides: Optional[dict] = None,
rechunk: bool = True,
) -> Union["pl.DataFrame", Iterator["pl.DataFrame"]]:
"""Returns the dataset as a `polars.DataFrame`. Can also return a generator for large datasets.
Args:
batch_size (`int`, *optional*):
The size (number of rows) of the batches if `batched` is `True`.
Defaults to `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
batched (`bool`):
Set to `True` to return a generator that yields the dataset as batches
of `batch_size` rows. Defaults to `False` (returns the whole datasets once).
schema_overrides (`dict`, *optional*):
Support type specification or override of one or more columns; note that
any dtypes inferred from the schema param will be overridden.
rechunk (`bool`):
Make sure that all data is in contiguous memory. Defaults to `True`.
Returns:
`polars.DataFrame` or `Iterator[polars.DataFrame]`
Example:
```py
>>> ds.to_polars()
```
"""
if batched:
for table in self.with_format("arrow").iter(batch_size=batch_size):
yield Dataset(table, fingerprint="unset").to_polars(schema_overrides=schema_overrides, rechunk=rechunk)
else:
table = pa.concat_tables(list(self.with_format("arrow").iter(batch_size=1000)))
return Dataset(table, fingerprint="unset").to_polars(schema_overrides=schema_overrides, rechunk=rechunk)
def to_csv(
self,
path_or_buf: Union[PathLike, BinaryIO],
batch_size: Optional[int] = None,
storage_options: Optional[dict] = None,
**to_csv_kwargs,
) -> int:
"""Exports the dataset to csv.
This iterates on the dataset and loads it completely in memory before writing it.
Args:
path_or_buf (`PathLike` or `FileOrBuffer`):
Either a path to a file (e.g. `file.csv`), a remote URI (e.g. `hf://datasets/username/my_dataset_name/data.csv`),
or a BinaryIO, where the dataset will be saved to in the specified format.
batch_size (`int`, *optional*):
Size of the batch to load in memory and write at once.
Defaults to `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
storage_options (`dict`, *optional*):
Key/value pairs to be passed on to the file-system backend, if any.
**to_csv_kwargs (additional keyword arguments):
Parameters to pass to pandas's [`pandas.DataFrame.to_csv`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_csv.html).
The parameter `index` defaults to `False` if not specified.
If you would like to write the index, pass `index=True` and also set a name for the index column by
passing `index_label`.
Returns:
`int`: The number of characters or bytes written.
Example:
```py
>>> ds.to_csv("path/to/dataset/directory")
```
"""
table = pa.concat_tables(list(self.with_format("arrow").iter(batch_size=1000)))
return Dataset(table, fingerprint="unset").to_csv(
path_or_buf,
batch_size=batch_size,
storage_options=storage_options,
**to_csv_kwargs,
)
def to_json(
self,
path_or_buf: Union[PathLike, BinaryIO],
batch_size: Optional[int] = None,
storage_options: Optional[dict] = None,
**to_json_kwargs,
) -> int:
"""Export the dataset to JSON Lines or JSON.
This iterates on the dataset and loads it completely in memory before writing it.
The default output format is [JSON Lines](https://jsonlines.org/).
To export to [JSON](https://www.json.org), pass `lines=False` argument and the desired `orient`.
Args:
path_or_buf (`PathLike` or `FileOrBuffer`):
Either a path to a file (e.g. `file.json`), a remote URI (e.g. `hf://datasets/username/my_dataset_name/data.json`),
or a BinaryIO, where the dataset will be saved to in the specified format.
batch_size (`int`, *optional*):
Size of the batch to load in memory and write at once.
Defaults to `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
storage_options (`dict`, *optional*):
Key/value pairs to be passed on to the file-system backend, if any.
**to_json_kwargs (additional keyword arguments):
Parameters to pass to pandas's [`pandas.DataFrame.to_json`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_json.html).
Default arguments are `lines=True` and `orient="records".
The parameter `index` defaults to `False` if `orient` is `"split"` or `"table"`.
If you would like to write the index, pass `index=True`.
Returns:
`int`: The number of characters or bytes written.
Example:
```py
>>> ds.to_json("path/to/dataset/directory/filename.jsonl")
```
```py
>>> num_shards = dataset.num_shards
>>> for index in range(num_shards):
... shard = dataset.shard(index, num_shards)
... shard.to_json(f"path/of/my/dataset/data-{index:05d}.jsonl")
```
"""
table = pa.concat_tables(list(self.with_format("arrow").iter(batch_size=1000)))
return Dataset(table, fingerprint="unset").to_json(
path_or_buf,
batch_size=batch_size,
storage_options=storage_options,
**to_json_kwargs,
)
def to_sql(
self,
name: str,
con: Union[str, "sqlalchemy.engine.Connection", "sqlalchemy.engine.Engine", "sqlite3.Connection"],
batch_size: Optional[int] = None,
**sql_writer_kwargs,
) -> int:
"""Exports the dataset to a SQL database.
Args:
name (`str`):
Name of SQL table.
con (`str` or `sqlite3.Connection` or `sqlalchemy.engine.Connection` or `sqlalchemy.engine.Connection`):
A [URI string](https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls) or a SQLite3/SQLAlchemy connection object used to write to a database.
batch_size (`int`, *optional*):
Size of the batch to load in memory and write at once.
Defaults to `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
**sql_writer_kwargs (additional keyword arguments):
Parameters to pass to pandas's [`pandas.DataFrame.to_sql`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_sql.html).
The parameter `index` defaults to `False` if not specified.
If you would like to write the index, pass `index=True` and also set a name for the index column by
passing `index_label`.
Returns:
`int`: The number of records written.
Example:
```py
>>> # con provided as a connection URI string
>>> ds.to_sql("data", "sqlite:///my_own_db.sql")
>>> # con provided as a sqlite3 connection object
>>> import sqlite3
>>> con = sqlite3.connect("my_own_db.sql")
>>> with con:
... ds.to_sql("data", con)
```
"""
table = pa.concat_tables(list(self.with_format("arrow").iter(batch_size=1000)))
return Dataset(table, fingerprint="unset").to_sql(name, con, batch_size=batch_size, **sql_writer_kwargs)
def to_parquet(
self,
path_or_buf: Union[PathLike, BinaryIO],
batch_size: Optional[int] = None,
storage_options: Optional[dict] = None,
**parquet_writer_kwargs,
) -> int:
"""Exports the dataset to parquet
Args:
path_or_buf (`PathLike` or `FileOrBuffer`):
Either a path to a file (e.g. `file.parquet`), a remote URI (e.g. `hf://datasets/username/my_dataset_name/data.parquet`),
or a BinaryIO, where the dataset will be saved to in the specified format.
batch_size (`int`, *optional*):
Size of the batch to load in memory and write at once.
Defaults to `datasets.config.DEFAULT_MAX_BATCH_SIZE`.
storage_options (`dict`, *optional*):
Key/value pairs to be passed on to the file-system backend, if any.
<Added version="2.19.0"/>
**parquet_writer_kwargs (additional keyword arguments):
Parameters to pass to PyArrow's `pyarrow.parquet.ParquetWriter`.
Returns:
`int`: The number of characters or bytes written.
Example:
```py
>>> ds.to_parquet("path/to/dataset/directory")
```
```py
>>> num_shards = dataset.num_shards
>>> for index in range(num_shards):
... shard = dataset.shard(index, num_shards)
... shard.to_parquet(f"path/of/my/dataset/data-{index:05d}.parquet")
```
"""
from .arrow_writer import get_arrow_writer_batch_size_from_features
batch_size = get_arrow_writer_batch_size_from_features(self.features) or config.DEFAULT_MAX_BATCH_SIZE
table = pa.concat_tables(list(self.with_format("arrow").iter(batch_size=batch_size)))
return Dataset(table, fingerprint="unset").to_parquet(
path_or_buf, storage_options=storage_options, **parquet_writer_kwargs
)
def _push_parquet_shards_to_hub_single(
self,
job_id: int,
num_jobs: int,
resolved_output_path: HfFileSystemResolvedPath,
data_dir: str,
split: str,
token: Optional[str],
create_pr: Optional[bool],
# max_shard_size: Optional[Union[int, str]] = None, # TODO(QL): add arg
num_shards: int,
embed_external_files: bool,
) -> Iterable[tuple[list[CommitOperationAdd], list[str], int, int]]:
"""Pushes the dataset shards as Parquet files to the hub.
Returns:
additions (`List[CommitOperation]`): list of the `CommitOperationAdd` of the uploaded shards
new_parquet_paths (`List[str]`): list of the paths of the uploaded parquet files
features (`Features`): features of the uploaded dataset
dataset_nbytes (`int`): approximate size in bytes of the uploaded dataset after uncompression
num_examples (`int`): number of examples of th euploaded shards
"""
div = num_shards // num_jobs
mod = num_shards % num_jobs
start = div * job_id + min(job_id, mod)
end = start + div + (1 if job_id < mod else 0)
index_shards = (
(start + i, self.shard(num_shards=end - start, index=i, contiguous=True)) for i in range(end - start)
)
api = HfApi(endpoint=config.HF_ENDPOINT, token=token, library_name="datasets", library_version=__version__)
dataset_nbytes = 0
num_examples = 0
additions: list[CommitOperationAdd] = []
new_parquet_paths: list[str] = []
features = self.features
for index, shard in index_shards:
if embed_external_files:
from .arrow_writer import get_arrow_writer_batch_size_from_features
shard = shard.with_format("arrow")
shard = shard.map(
partial(embed_table_storage, token_per_repo_id=self._token_per_repo_id),
batched=True,
batch_size=get_arrow_writer_batch_size_from_features(shard.features),
)
shard_path_in_repo = f"{data_dir}/{split}-{index:05d}-of-{num_shards:05d}.parquet"
tmp_file = tempfile.NamedTemporaryFile(suffix=".parquet", delete=False)
try:
shard.to_parquet(tmp_file)
tmp_file.close()
parquet_metadata = pq.read_metadata(tmp_file.name)
if features is None:
features = Features.from_arrow_schema(parquet_metadata.schema.to_arrow_schema())
num_examples += parquet_metadata.num_rows
dataset_nbytes += sum(
parquet_metadata.row_group(i).total_byte_size for i in range(parquet_metadata.num_row_groups)
)
new_parquet_paths.append(shard_path_in_repo)
if (
isinstance(resolved_output_path, HfFileSystemResolvedRepositoryPath)
and not resolved_output_path.path_in_repo
):
shard_addition = CommitOperationAdd(path_in_repo=shard_path_in_repo, path_or_fileobj=tmp_file.name)
api.preupload_lfs_files(
repo_id=resolved_output_path.repo_id,
additions=[shard_addition],
repo_type=resolved_output_path.repo_type,
revision=resolved_output_path.revision,
create_pr=create_pr,
)
additions.append(shard_addition)
elif isinstance(resolved_output_path, HfFileSystemResolvedBucketPath):
if resolved_output_path.path:
shard_path_in_repo = resolved_output_path.path + "/" + shard_path_in_repo
api.batch_bucket_files(
bucket_id=resolved_output_path.bucket_id, add=[(tmp_file.name, shard_path_in_repo)]
)
else:
raise NotImplementedError(f"Bad HF path: {resolved_output_path}")
except (Exception, KeyboardInterrupt):
tmp_file.close()
Path(tmp_file.name).unlink()
raise
tmp_file.close()
Path(tmp_file.name).unlink()
yield job_id, False, 1
yield job_id, True, (additions, new_parquet_paths, features, dataset_nbytes, num_examples)
def _push_parquet_shards_to_hub(
self,
resolved_output_path: HfFileSystemResolvedPath,
data_dir: str,
split: str,
token: Optional[str],
create_pr: Optional[bool],
max_shard_size: Optional[Union[int, str]],
num_shards: Optional[int],
embed_external_files: bool,
num_proc: Optional[int],
) -> tuple[list[CommitOperationAdd], list[str], Features, SplitInfo, int]:
"""Pushes the dataset shards as Parquet files to the hub.
Returns:
additions (`List[CommitOperation]`): list of the `CommitOperationAdd` of the uploaded shards
new_parquet_paths (`List[str]`): list of paths of the new files uploaded to the output path,
relative to output path
features (`features`): features of the uploaded dataset
split_info (`int`): info of the uploaded split, including the approximate size in bytes of
the uploaded dataset after uncompression
uploaded_size (`int`): number of uploaded bytes to the repository or bucket
"""
# Find decodable columns, because if there are any, we need to:
# embed the bytes from the files in the shards
decodable_columns = (
[k for k, v in self._info.features.items() if require_decoding(v, ignore_decode_attribute=True)]
if embed_external_files
else []
)
embed_external_files = embed_external_files and bool(decodable_columns)
if num_shards is None:
if max_shard_size is None:
num_shards = self.num_shards
else:
max_shard_size = convert_file_size_to_int(max_shard_size or config.MAX_SHARD_SIZE)
estimated_nbytes = 0
for pa_table in self.with_format("arrow").iter(batch_size=config.DEFAULT_MAX_BATCH_SIZE):
estimated_nbytes += pa_table.nbytes
num_shards = int(estimated_nbytes / max_shard_size) + 1
num_shards = max(num_shards, num_proc or 1)
additions: list[CommitOperationAdd] = []
new_parquet_paths: list[str] = []
uploaded_size = 0
dataset_nbytes = 0
num_examples = 0
features = self.features
num_jobs = num_proc or 1
if num_shards <= 1:
logger.warning(
f"Setting num_proc from {num_jobs} back to 1 for the {split} split to disable multiprocessing as it only contains one shard."
)
num_proc = None
num_jobs = 1
elif num_shards < num_jobs:
logger.warning(
f"Setting num_proc from {num_jobs} to {num_shards} for the {split} split as it only contains {num_shards} shards."
)
num_proc = num_shards
num_jobs = num_shards
kwargs_iterable = [
{
"self": self.shard(num_shards=num_jobs, index=job_id, contiguous=True),
"job_id": job_id,
"num_jobs": num_jobs,
"resolved_output_path": resolved_output_path,
"data_dir": data_dir,
"split": split,
"token": token,
"create_pr": create_pr,
"num_shards": num_shards,
"embed_external_files": embed_external_files,
}
for job_id in range(num_jobs)
]
desc = "Uploading the dataset shards"
desc += f" (num_proc={num_proc})" if num_proc is not None and num_proc >= 1 else ""
pbar = hf_tqdm(
unit=" shards",
total=num_shards,
desc=desc,
)
with (
contextlib.nullcontext()
if num_proc is None or num_proc < 1
else mp.get_context("spawn").Pool(num_proc) as pool
):
update_stream = (
IterableDataset._push_parquet_shards_to_hub_single(**kwargs_iterable[0])
if pool is None
else iflatmap_unordered(
pool,
IterableDataset._push_parquet_shards_to_hub_single,
kwargs_iterable=kwargs_iterable,
)
)
for job_id, done, content in update_stream:
if not done:
pbar.update(content)
else:
job_additions, job_new_parquet_paths, job_features, job_uploaded_size, job_num_examples = content
additions += job_additions
new_parquet_paths += job_new_parquet_paths
uploaded_size += job_uploaded_size
num_examples += job_num_examples
features = job_features
if pool is not None:
pool.close()
pool.join()
uploaded_size = sum(addition.upload_info.size for addition in additions)
split_info = SplitInfo(split, num_bytes=dataset_nbytes, num_examples=num_examples)
return additions, new_parquet_paths, features, split_info, uploaded_size
def push_to_hub(
self,
repo_id: str,
config_name: str = "default",
set_default: Optional[bool] = None,
split: Optional[str] = None,
data_dir: Optional[str] = None,
commit_message: Optional[str] = None,
commit_description: Optional[str] = None,
private: Optional[bool] = None,
token: Optional[str] = None,
revision: Optional[str] = None,
create_pr: Optional[bool] = False,
max_shard_size: Optional[Union[int, str]] = None,
num_shards: Optional[int] = None,
embed_external_files: bool = True,
num_proc: Optional[int] = None,
) -> CommitInfo:
"""Pushes the dataset to the hub as a Parquet dataset.
The dataset is pushed using HTTP requests and does not need to have neither git or git-lfs installed.
The resulting Parquet files are self-contained by default. If your dataset contains [`Image`], [`Audio`] or [`Video`]
data, the Parquet files will store the bytes of your images or audio files.
You can disable this by setting `embed_external_files` to `False`.
Args:
repo_id (`str`):
The ID of the repository to push to in the following format: `<user>/<dataset_name>` or
`<org>/<dataset_name>`. Also accepts `<dataset_name>`, which will default to the namespace
of the logged-in user.
It could also be a location inside a bucket, e.g. `buckets/<user_or_org>/<bucket_name>/...`
config_name (`str`, defaults to "default"):
The configuration name (or subset) of a dataset. Defaults to "default".
set_default (`bool`, *optional*):
Whether to set this configuration as the default one. Otherwise, the default configuration is the one
named "default".
split (`str`, *optional*):
The name of the split that will be given to that dataset. Defaults to `self.split`.
data_dir (`str`, *optional*):
Directory name that will contain the uploaded data files. Defaults to the `config_name` if different
from "default", else "data".
commit_message (`str`, *optional*):
Message to commit while pushing. Will default to `"Upload dataset"`.
commit_description (`str`, *optional*):
Description of the commit that will be created.
Additionally, description of the PR if a PR is created (`create_pr` is True).
private (`bool`, *optional*):
Whether to make the repo private. If `None` (default), the repo will be public unless the
organization's default is private. This value is ignored if the repo already exists.
token (`str`, *optional*):
An optional authentication token for the Hugging Face Hub. If no token is passed, will default
to the token saved locally when logging in with `huggingface-cli login`. Will raise an error
if no token is passed and the user is not logged-in.
revision (`str`, *optional*):
Branch to push the uploaded files to. Defaults to the `"main"` branch.
create_pr (`bool`, *optional*, defaults to `False`):
Whether to create a PR with the uploaded files or directly commit.
max_shard_size (`int` or `str`, *optional*):
Optional maximum size of the dataset shards to be uploaded to the hub. If expressed as a string, needs to be digits followed
by a unit (like `"5MB"`). If not provided, shard count defaults to this dataset's `.num_shards`.
num_shards (`int`, *optional*):
Number of shards to write. If `max_shard_size` is provided and `num_shards` is not, then the number of shards is estimated
from `max_shard_size`.
embed_external_files (`bool`, defaults to `True`):
Whether to embed file bytes in the shards.
In particular, this will do the following before the push for the fields of type:
- [`Audio`] and [`Image`]: remove local path information and embed file content in the Parquet files.
num_proc (`int`, *optional*, defaults to `None`):
Number of processes when preparing and uploading the dataset.
This is helpful if the dataset is made of many samples and transformations.
I uses "spawn" context to work with hf_xet, the rust client for fast uploads to HF.
Multiprocessing is disabled by default.
Return:
huggingface_hub.CommitInfo
Example:
```python
>>> dataset.push_to_hub("<organization>/<dataset_id>")
>>> dataset_dict.push_to_hub("<organization>/<dataset_id>", private=True)
>>> dataset.push_to_hub("<organization>/<dataset_id>", max_shard_size="1GB")
>>> dataset.push_to_hub("<organization>/<dataset_id>", num_shards=1024)
```
If your dataset has multiple splits (e.g. train/validation/test):
```python
>>> train_dataset.push_to_hub("<organization>/<dataset_id>", split="train")
>>> val_dataset.push_to_hub("<organization>/<dataset_id>", split="validation")
>>> # later
>>> dataset = load_dataset("<organization>/<dataset_id>")
>>> train_dataset = dataset["train"]
>>> val_dataset = dataset["validation"]
```
If you want to add a new configuration (or subset) to a dataset (e.g. if the dataset has multiple tasks/versions/languages):
```python
>>> english_dataset.push_to_hub("<organization>/<dataset_id>", "en")
>>> french_dataset.push_to_hub("<organization>/<dataset_id>", "fr")
>>> # later
>>> english_dataset = load_dataset("<organization>/<dataset_id>", "en")
>>> french_dataset = load_dataset("<organization>/<dataset_id>", "fr")
```
"""
if num_proc is not None and num_proc > self.num_shards:
logger.warning(
f"Too many num_proc: {num_proc} (max is dataset.num_shards={self.num_shards}). "
f"Stopping {num_proc - self.num_shards} processes."
)
logger.info(
f"To parallelize data loading, we give each process some shards (or data sources) to process. "
f"Therefore it's unnecessary to have a number of processes greater than dataset.num_shards={self.num_shards}. "
f"To enable more parallelism, please split the dataset in more files than {self.num_shards}."
)
num_proc = self.num_shards
if config_name == "data":
raise ValueError("`config_name` cannot be 'data'. Please, choose another name for configuration.")
if max_shard_size is not None and num_shards is not None:
raise ValueError(
"Failed to push_to_hub: please specify either max_shard_size or num_shards, but not both."
)
if split is None:
split = str(self.split) if self.split is not None else "train"
if not re.match(_split_re, split):
raise ValueError(f"Split name should match '{_split_re}' but got '{split}'.")
if not data_dir:
data_dir = config_name if config_name != "default" else "data" # for backward compatibility
api = HfApi(endpoint=config.HF_ENDPOINT, token=token, library_name="datasets", library_version=__version__)
if repo_id.startswith("buckets/"):
if BucketNotFoundError is None:
raise ImportError("Pushing datasets to buckets requires huggingface_hub>=1.6.0")
_, _namespace, _bucket_name, *_path_segments = repo_id.split("/")
try:
bucket_id = api.bucket_info(_namespace + "/" + _bucket_name).id
except BucketNotFoundError:
bucket_url = api.create_bucket(_namespace + "/" + _bucket_name, private=private, exist_ok=True)
bucket_id = bucket_url.bucket_id
path = "/".join(s for s in _path_segments if s)
return _push_to_bucket(
self,
bucket_id=bucket_id,
path=path,
config_name=config_name,
set_default=set_default,
split=split,
data_dir=data_dir,
token=token,
max_shard_size=max_shard_size,
num_shards=num_shards,
embed_external_files=embed_external_files,
num_proc=num_proc,
)
else:
try:
repo_id = api.repo_info(repo_id, repo_type="dataset").id
except RepositoryNotFoundError:
repo_url = api.create_repo(
repo_id,
repo_type="dataset",
private=private,
exist_ok=True,
)
repo_id = repo_url.repo_id
if revision is not None and not revision.startswith("refs/pr/"):
# We do not call create_branch for a PR reference: 400 Bad Request
api.create_branch(repo_id, branch=revision, repo_type="dataset", exist_ok=True)
return _push_to_repo(
self,
repo_id=repo_id,
config_name=config_name,
set_default=set_default,
split=split,
data_dir=data_dir,
commit_message=commit_message,
commit_description=commit_description,
token=token,
revision=revision,
create_pr=create_pr,
max_shard_size=max_shard_size,
num_shards=num_shards,
embed_external_files=embed_external_files,
num_proc=num_proc,
)
def _concatenate_iterable_datasets(
dsets: list[IterableDataset],
info: Optional[DatasetInfo] = None,
split: Optional[NamedSplit] = None,
axis: int = 0,
) -> IterableDataset:
"""
Converts a list of `IterableDataset` with the same schema into a single `IterableDataset`.
Missing data are filled with None values.
<Added version="2.4.0"/>
Args:
dsets (`List[datasets.IterableDataset]`): List of Datasets to concatenate.
info (`DatasetInfo`, optional): Dataset information, like description, citation, etc.
split (`NamedSplit`, optional): Name of the dataset split.
axis (``{0, 1}``, default ``0``, meaning over rows):
Axis to concatenate over, where ``0`` means over rows (vertically) and ``1`` means over columns
(horizontally).
*New in version 1.6.0*
Example:
```py
>>> ds3 = _concatenate_iterable_datasets([ds1, ds2])
```
"""
dsets = [d._resolve_features() for d in dsets]
# Perform checks (and a potentional cast if axis=0)
if axis == 0:
_check_if_features_can_be_aligned([dset.features for dset in dsets])
else:
_check_column_names([col_name for dset in dsets for col_name in dset.features])
# Check format is consistent; if so, will set format for concatenated dataset
if all(dset._formatting is None for dset in dsets):
formatting = None
elif any(dset._formatting is None for dset in dsets):
formatting = None
logger.info(
"Some of the datasets have disparate format or format not set. Resetting the format of the concatenated dataset."
)
else:
format_type_set = {dset._formatting.format_type for dset in dsets}
if len(format_type_set) == 1:
format_type = format_type_set.pop()
formatting = FormattingConfig(format_type=format_type)
else:
formatting = None
logger.info(
"Some of the datasets have disparate format or format not set. Resetting the format of the concatenated dataset."
)
# TODO: improve this to account for a mix of ClassLabel and Value for example
# right now it would keep the type of the first dataset in the list
features = Features(
{k: v for features in _align_features([dset.features for dset in dsets]) for k, v in features.items()}
)
ex_iterables = [deepcopy(d._ex_iterable) for d in dsets]
if axis == 0:
ex_iterable = VerticallyConcatenatedMultiSourcesExamplesIterable(ex_iterables)
else:
if all(ex_iterable.iter_arrow for ex_iterable in ex_iterables):
from .arrow_writer import get_arrow_writer_batch_size_from_features
batch_size = get_arrow_writer_batch_size_from_features(features) or config.DEFAULT_MAX_BATCH_SIZE
ex_iterables = [
RebatchedArrowExamplesIterable(ex_iterable, batch_size=batch_size) for ex_iterable in ex_iterables
]
ex_iterable = HorizontallyConcatenatedMultiSourcesExamplesIterable(ex_iterables)
# Set new info - we update the features
# setting the features also ensures to fill missing columns with None
if info is None:
info = DatasetInfo.from_merge([d.info for d in dsets])
else:
info = info.copy()
info.features = features
# Get all the auth tokens per repository - in case the datasets come from different private repositories
token_per_repo_id = {repo_id: token for dataset in dsets for repo_id, token in dataset._token_per_repo_id.items()}
# Return new daset
return IterableDataset(
ex_iterable=ex_iterable,
info=info,
split=split,
token_per_repo_id=token_per_repo_id,
formatting=formatting,
)
def _interleave_iterable_datasets(
datasets: list[IterableDataset],
probabilities: Optional[list[float]] = None,
seed: Optional[int] = None,
info: Optional[DatasetInfo] = None,
split: Optional[NamedSplit] = None,
stopping_strategy: Literal[
"first_exhausted", "all_exhausted", "all_exhausted_without_replacement"
] = "first_exhausted",
) -> IterableDataset:
"""
Interleave several iterable datasets (sources) into a single iterable dataset.
The new iterable dataset alternates between the sources to yield examples.
If `probabilities = None` (default) the iterable dataset will cycles through the sources in order for each next example in the iteration.
If `probabilities` is not `None, the iterable dataset will sample a random source according to the provided probabilities for each next examples in the iteration.
<Added version="2.4.0"/>
Args:
datasets (`List[IterableDataset]`): list of datasets to interleave
probabilities (`List[float]`, optional, default None): If specified, the new iterable dataset samples
examples from one source at a time according to these probabilities.
seed (`int`, optional, default None): The random seed used to choose a source for each example.
stopping_strategy (`str`, defaults to `first_exhausted`):
Two strategies are proposed right now.
By default, `first_exhausted` is an undersampling strategy, i.e the dataset construction is stopped as soon as one dataset has ran out of samples.
If the strategy is `all_exhausted`, we use an oversampling strategy, i.e the dataset construction is stopped as soon as every samples of every dataset has been added at least once.
Note that if the strategy is `all_exhausted`, the interleaved dataset size can get enormous:
- with no probabilities, the resulting dataset will have max_length_datasets*nb_dataset samples.
- with given probabilities, the resulting dataset will have more samples if some datasets have really low probability of visiting.
Output:
`datasets.IterableDataset`
"""
datasets = [d._resolve_features() for d in datasets]
# Perform checks
_check_if_features_can_be_aligned([dset.features for dset in datasets])
for i, dset in enumerate(datasets):
if datasets[0]._distributed != dset._distributed:
raise ValueError(
f"Datasets should be identically split_by_node before interleaving, but got {datasets[0]._distributed}!={dset._distributed} at index 0 and {i}"
)
# TODO: improve this to account for a mix of ClassLabel and Value for example
# right now it would keep the type of the first dataset in the list
features = Features(
{k: v for features in _align_features([dset.features for dset in datasets]) for k, v in features.items()}
)
ex_iterables = [deepcopy(d._ex_iterable) for d in datasets]
if all(ex_iterable.iter_arrow for ex_iterable in ex_iterables):
ex_iterables = [RebatchedArrowExamplesIterable(ex_iterable, batch_size=1) for ex_iterable in ex_iterables]
# Use cycling or random cycling of sources
if probabilities is None:
ex_iterable = CyclingMultiSourcesExamplesIterable(ex_iterables, stopping_strategy=stopping_strategy)
else:
generator = np.random.default_rng(seed)
ex_iterable = RandomlyCyclingMultiSourcesExamplesIterable(
ex_iterables,
generator=generator,
probabilities=probabilities,
stopping_strategy=stopping_strategy,
)
# Set new info - we update the features
# setting the features also ensures to fill missing columns with None
if info is None:
info = DatasetInfo.from_merge([d.info for d in datasets])
else:
info = info.copy()
info.features = features
# Get all the auth tokens per repository - in case the datasets come from different private repositories
token_per_repo_id = {
repo_id: token for dataset in datasets for repo_id, token in dataset._token_per_repo_id.items()
}
# Return new daset
return IterableDataset(
ex_iterable=ex_iterable,
info=info,
split=split,
token_per_repo_id=token_per_repo_id,
distributed=datasets[0]._distributed,
)
def _split_by_node_iterable_dataset(dataset: IterableDataset, rank: int, world_size: int) -> IterableDataset:
"""
Split an iterable dataset for the node at rank `rank` in a pool of nodes of size `world_size`.
If the dataset has a number of shards that is a factor of `world_size` (i.e. if `dataset.num_shards % world_size == 0`),
then the shards are evenly assigned across the nodes, which is the most optimized.
Otherwise, each node keeps 1 example out of `world_size`, skipping the other examples.
Args:
dataset ([`IterableDataset`]):
The iterable dataset to split by node.
rank (`int`):
Rank of the current node.
world_size (`int`):
Total number of nodes.
Returns:
[`IterableDataset`]: The iterable dataset to be used on the node at rank `rank`.
"""
if dataset._distributed:
rank = world_size * dataset._distributed.rank + rank
world_size = world_size * dataset._distributed.world_size
distributed = DistributedConfig(rank=rank, world_size=world_size)
return IterableDataset(
ex_iterable=dataset._ex_iterable,
info=dataset._info.copy(),
split=dataset._split,
formatting=dataset._formatting,
distributed=distributed,
token_per_repo_id=dataset._token_per_repo_id,
)
async def _apply_async(pool, func, x):
future = pool.apply_async(func, (x,))
while True:
if future.ready():
return future.get()
else:
await asyncio.sleep(0)
def _batch_fn(unbatched):
return {k: [v] for k, v in unbatched.items()}
def _generate_tables_from_polars(df: Union["pl.DataFrame", "pl.LazyFrame"]) -> Iterator[tuple["BuilderKey", pa.Table]]:
import polars as pl
from .builder import Key as BuilderKey
for slice_idx, df_slice in enumerate(df.collect_batches() if isinstance(df, pl.LazyFrame) else df.iter_slices()):
yield BuilderKey(0, slice_idx), df_slice.to_arrow()