Files
wehub-resource-sync ee3943b5b1
CI / test (deps-minimum, windows-latest, unit) (push) Blocked by required conditions
CI / test_py314 (deps-latest, ubuntu-latest, unit) (push) Blocked by required conditions
CI / test_py314 (deps-latest, windows-latest, unit) (push) Blocked by required conditions
CI / test_py314_future (deps-latest, ubuntu-latest, unit) (push) Blocked by required conditions
CI / test_py314_future (deps-latest, windows-latest, unit) (push) Blocked by required conditions
Secret Leaks / trufflehog (push) Waiting to run
CI / check_code_quality (push) Waiting to run
CI / test (deps-latest, ubuntu-latest, integration) (push) Blocked by required conditions
CI / test (deps-latest, ubuntu-latest, unit) (push) Blocked by required conditions
CI / test (deps-latest, windows-latest, integration) (push) Blocked by required conditions
CI / test (deps-latest, windows-latest, unit) (push) Blocked by required conditions
CI / test (deps-minimum, ubuntu-latest, integration) (push) Blocked by required conditions
CI / test (deps-minimum, ubuntu-latest, unit) (push) Blocked by required conditions
CI / test (deps-minimum, windows-latest, integration) (push) Blocked by required conditions
Build documentation / build (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 13:24:32 +08:00

3185 lines
134 KiB
Python

import asyncio
import pickle
import sys
import time
from copy import deepcopy
from dataclasses import dataclass
from itertools import chain, cycle, islice
from unittest.mock import MagicMock, patch
import numpy as np
import pandas as pd
import pyarrow as pa
import pyarrow.compute as pc
import pytest
from huggingface_hub import HfFileSystemResolvedPath
from packaging import version
from datasets import Dataset, config, load_dataset
from datasets.combine import concatenate_datasets, interleave_datasets
from datasets.distributed import split_dataset_by_node
from datasets.features import (
ClassLabel,
Features,
Image,
List,
Value,
)
from datasets.formatting import Formatter, get_format_type_from_alias
from datasets.info import DatasetInfo
from datasets.iterable_dataset import (
ArrowExamplesIterable,
BufferShuffledExamplesIterable,
CyclingMultiSourcesExamplesIterable,
DataSourcesShufflingDisallowed,
ExamplesIterable,
FilteredExamplesIterable,
FormattedExamplesIterable,
FormattingConfig,
HorizontallyConcatenatedMultiSourcesExamplesIterable,
IterableColumn,
IterableDataset,
MappedExamplesIterable,
RandomlyCyclingMultiSourcesExamplesIterable,
RebatchedArrowExamplesIterable,
RepeatExamplesIterable,
SelectColumnsIterable,
SkipExamplesIterable,
StepExamplesIterable,
TakeExamplesIterable,
VerticallyConcatenatedMultiSourcesExamplesIterable,
_BaseExamplesIterable,
_batch_to_examples,
_convert_to_arrow,
_examples_to_batch,
)
from .utils import (
assert_arrow_memory_doesnt_increase,
require_dill_gt_0_3_2,
require_jax,
require_not_windows,
require_numpy1_on_windows,
require_polars,
require_pyspark,
require_tf,
require_torch,
require_torchdata_stateful_dataloader,
)
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
SAMPLE_DATASET_IDENTIFIER = "hf-internal-testing/dataset_with_data_files"
DEFAULT_N_EXAMPLES = 20
DEFAULT_BATCH_SIZE = 4
DEFAULT_FILEPATH = "file.txt"
def _normalize_batched_output(batch):
def to_python(value):
if isinstance(value, np.ndarray):
return [to_python(item) for item in value.tolist()]
if isinstance(value, list):
return [to_python(item) for item in value]
if isinstance(value, tuple):
return [to_python(item) for item in value]
return value
if isinstance(batch, pa.Table):
return {column: to_python(values) for column, values in batch.to_pydict().items()}
if isinstance(batch, pd.DataFrame):
return {column: to_python(batch[column].tolist()) for column in batch.columns}
if config.POLARS_AVAILABLE and "polars" in sys.modules:
import polars as pl
if isinstance(batch, pl.DataFrame):
return {column: to_python(values) for column, values in batch.to_dict(as_series=False).items()}
return to_python(batch)
def generate_examples_fn(**kwargs):
kwargs = kwargs.copy()
n = kwargs.pop("n", DEFAULT_N_EXAMPLES)
filepaths = kwargs.pop("filepaths", None)
for filepath in filepaths or [DEFAULT_FILEPATH]:
if filepaths is not None:
kwargs["filepath"] = filepath
for i in range(n):
yield f"{filepath}_{i}", {"id": i, **kwargs}
def generate_tables_fn(**kwargs):
kwargs = kwargs.copy()
n = kwargs.pop("n", DEFAULT_N_EXAMPLES)
batch_size = kwargs.pop("batch_size", DEFAULT_BATCH_SIZE)
filepaths = kwargs.pop("filepaths", None)
for filepath in filepaths or [DEFAULT_FILEPATH]:
buffer = []
batch_idx = 0
if filepaths is not None:
kwargs["filepath"] = filepath
for i in range(n):
buffer.append({"id": i, **kwargs})
if len(buffer) == batch_size:
yield f"{filepath}_{batch_idx}", pa.Table.from_pylist(buffer)
buffer = []
batch_idx += 1
yield batch_idx, pa.Table.from_pylist(buffer)
@pytest.fixture
def dataset():
ex_iterable = ExamplesIterable(generate_examples_fn, {})
return IterableDataset(ex_iterable, info=DatasetInfo(description="dummy"), split="train")
@pytest.fixture
def dataset_with_several_columns():
ex_iterable = ExamplesIterable(
generate_examples_fn,
{"filepath": ["data0.txt", "data1.txt", "data2.txt"], "metadata": {"sources": ["https://foo.bar"]}},
)
return IterableDataset(ex_iterable, info=DatasetInfo(description="dummy"), split="train")
@pytest.fixture
def arrow_file(tmp_path_factory, dataset: IterableDataset):
filename = str(tmp_path_factory.mktemp("data") / "file.arrow")
Dataset.from_generator(dataset.__iter__).map(cache_file_name=filename)
return filename
def assert_load_state_dict_resumes_iteration(ex_iterable: _BaseExamplesIterable):
ex_iterable._init_state_dict()
state_dicts = [ex_iterable.state_dict()]
examples = []
for _, example in ex_iterable:
state_dicts.append(ex_iterable.state_dict())
examples.append(example)
for i, state_dict in enumerate(state_dicts):
ex_iterable.load_state_dict(state_dict)
examples_after_resuming = [example for _, example in ex_iterable]
assert examples_after_resuming == examples[i:], f"resuming from idx {i} with {state_dict=}"
def assert_load_state_dict_resumes_arrow_iteration(ex_iterable: _BaseExamplesIterable):
assert ex_iterable.iter_arrow is not None
ex_iterable._init_state_dict()
state_dicts = [ex_iterable.state_dict()]
examples = []
indices = [0]
for _, pa_table in ex_iterable.iter_arrow():
state_dicts.append(ex_iterable.state_dict())
examples.extend(pa_table.to_pylist())
indices.append(indices[-1] + len(pa_table))
for i, state_dict in zip(indices, state_dicts):
ex_iterable.load_state_dict(state_dict)
examples_after_resuming = [
example for _, pa_table in ex_iterable.iter_arrow() for example in pa_table.to_pylist()
]
assert examples_after_resuming == examples[i:], f"resuming from idx {i} with {state_dict=}"
################################
#
# Utilities tests
#
################################
@pytest.mark.parametrize("batch_size", [1, 2, 3, 9, 10, 11, 20])
@pytest.mark.parametrize("drop_last_batch", [False, True])
def test_convert_to_arrow(batch_size, drop_last_batch):
examples = [{"foo": i} for i in range(10)]
full_table = pa.Table.from_pylist(examples)
num_rows = len(full_table) if not drop_last_batch else len(full_table) // batch_size * batch_size
num_batches = (num_rows // batch_size) + 1 if num_rows % batch_size else num_rows // batch_size
subtables = list(
_convert_to_arrow(
list(enumerate(examples)),
batch_size=batch_size,
drop_last_batch=drop_last_batch,
)
)
assert len(subtables) == num_batches
if drop_last_batch:
assert all(len(subtable) == batch_size for _, subtable in subtables)
else:
assert all(len(subtable) == batch_size for _, subtable in subtables[:-1])
assert len(subtables[-1][1]) <= batch_size
if num_rows > 0:
reloaded = pa.concat_tables([subtable for _, subtable in subtables])
assert full_table.slice(0, num_rows).to_pydict() == reloaded.to_pydict()
################################
#
# _BaseExampleIterable tests
#
################################
def test_examples_iterable():
ex_iterable = ExamplesIterable(generate_examples_fn, {})
expected = list(generate_examples_fn())
assert next(iter(ex_iterable)) == expected[0]
assert list(ex_iterable) == expected
assert ex_iterable.iter_arrow is None
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_examples_iterable_with_kwargs():
ex_iterable = ExamplesIterable(generate_examples_fn, {"filepaths": ["0.txt", "1.txt"], "split": "train"})
expected = list(generate_examples_fn(filepaths=["0.txt", "1.txt"], split="train"))
assert list(ex_iterable) == expected
assert all("split" in ex for _, ex in ex_iterable)
assert sorted({ex["filepath"] for _, ex in ex_iterable}) == ["0.txt", "1.txt"]
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_examples_iterable_shuffle_data_sources():
ex_iterable = ExamplesIterable(generate_examples_fn, {"filepaths": ["0.txt", "1.txt"]})
ex_iterable = ex_iterable.shuffle_data_sources(np.random.default_rng(40))
expected = list(generate_examples_fn(filepaths=["1.txt", "0.txt"])) # shuffle the filepaths
assert list(ex_iterable) == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_examples_iterable_shuffle_shards_and_metadata():
def gen(filepaths, all_metadata):
for i, (filepath, metadata) in enumerate(zip(filepaths, all_metadata)):
yield i, {"filepath": filepath, "metadata": metadata}
ex_iterable = ExamplesIterable(
gen,
{
"filepaths": [f"{i}.txt" for i in range(100)],
"all_metadata": [{"id": str(i)} for i in range(100)],
},
)
ex_iterable = ex_iterable.shuffle_data_sources(np.random.default_rng(42))
out = list(ex_iterable)
filepaths_ids = [x["filepath"].split(".")[0] for _, x in out]
metadata_ids = [x["metadata"]["id"] for _, x in out]
assert filepaths_ids == metadata_ids, "entangled lists of shards/metadata should be shuffled the same way"
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_arrow_examples_iterable():
ex_iterable = ArrowExamplesIterable(generate_tables_fn, {})
expected = sum([pa_table.to_pylist() for _, pa_table in generate_tables_fn()], [])
assert next(iter(ex_iterable))[1] == expected[0]
assert [example for _, example in ex_iterable] == expected
expected = list(generate_tables_fn())
assert list(ex_iterable.iter_arrow()) == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_arrow_examples_iterable_with_kwargs():
ex_iterable = ArrowExamplesIterable(generate_tables_fn, {"filepaths": ["0.txt", "1.txt"], "split": "train"})
expected = sum(
[pa_table.to_pylist() for _, pa_table in generate_tables_fn(filepaths=["0.txt", "1.txt"], split="train")], []
)
assert [example for _, example in ex_iterable] == expected
assert all("split" in ex for _, ex in ex_iterable)
assert sorted({ex["filepath"] for _, ex in ex_iterable}) == ["0.txt", "1.txt"]
expected = list(generate_tables_fn(filepaths=["0.txt", "1.txt"], split="train"))
assert list(ex_iterable.iter_arrow()) == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_arrow_examples_iterable_shuffle_data_sources():
ex_iterable = ArrowExamplesIterable(generate_tables_fn, {"filepaths": ["0.txt", "1.txt"]})
ex_iterable = ex_iterable.shuffle_data_sources(np.random.default_rng(40))
expected = sum(
[pa_table.to_pylist() for _, pa_table in generate_tables_fn(filepaths=["1.txt", "0.txt"])], []
) # shuffle the filepaths
assert [example for _, example in ex_iterable] == expected
expected = list(generate_tables_fn(filepaths=["1.txt", "0.txt"]))
assert list(ex_iterable.iter_arrow()) == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"tables",
[
[pa.table({"foo": range(10)})],
[pa.table({"foo": range(5 * i, 5 * (i + 1))}) for i in range(2)],
[pa.table({"foo": range(5 * i, 5 * (i + 1))}) for i in range(7)],
[pa.table({"foo": [i]}) for i in range(10)],
],
)
@pytest.mark.parametrize("batch_size", [1, 2, 3, 7, 9, 10, 11, 13, 20])
@pytest.mark.parametrize("drop_last_batch", [False, True])
def test_rebatched_arrow_examples_iterable(tables, batch_size, drop_last_batch):
full_table = pa.concat_tables(tables)
num_rows = len(full_table) if not drop_last_batch else len(full_table) // batch_size * batch_size
num_batches = (num_rows // batch_size) + 1 if num_rows % batch_size else num_rows // batch_size
def gen(tables):
for i, table in enumerate(tables):
yield str(i), table
ex_iterable = ArrowExamplesIterable(gen, {"tables": tables})
ex_iterable = RebatchedArrowExamplesIterable(ex_iterable, batch_size=batch_size, drop_last_batch=drop_last_batch)
subtables = list(ex_iterable.iter_arrow())
assert len(subtables) == num_batches
if drop_last_batch:
assert all(len(subtable) == batch_size for _, subtable in subtables)
else:
assert all(len(subtable) == batch_size for _, subtable in subtables[:-1])
assert len(subtables[-1][1]) <= batch_size
if num_rows > 0:
reloaded = pa.concat_tables([subtable for _, subtable in subtables])
assert full_table.slice(0, num_rows).to_pydict() == reloaded.to_pydict()
assert_load_state_dict_resumes_iteration(ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
@pytest.mark.parametrize("seed", [42, 1337, 101010, 123456])
def test_buffer_shuffled_examples_iterable(seed):
n, buffer_size = 100, 30
generator = np.random.default_rng(seed)
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = BufferShuffledExamplesIterable(base_ex_iterable, buffer_size=buffer_size, generator=generator)
rng = deepcopy(generator)
expected_indices_used_for_shuffling = list(
islice(BufferShuffledExamplesIterable._iter_random_indices(rng, buffer_size=buffer_size), n - buffer_size)
)
# indices to pick in the shuffle buffer should all be in the right range
assert all(0 <= index_to_pick < buffer_size for index_to_pick in expected_indices_used_for_shuffling)
# it should be random indices
assert expected_indices_used_for_shuffling != list(range(buffer_size))
# The final order of examples is the result of a shuffle buffer.
all_examples = list(generate_examples_fn(n=n))
# We create a buffer and we pick random examples from it.
buffer, rest = all_examples[:buffer_size], all_examples[buffer_size:]
expected = []
for i, index_to_pick in enumerate(expected_indices_used_for_shuffling):
expected.append(buffer[index_to_pick])
# The picked examples are directly replaced by the next examples from the iterable.
buffer[index_to_pick] = rest.pop(0)
# Once we have reached the end of the iterable, we shuffle the buffer and return the remaining examples.
rng.shuffle(buffer)
expected += buffer
assert next(iter(ex_iterable)) == expected[0]
assert list(ex_iterable) == expected
assert sorted(ex_iterable) == sorted(all_examples)
def test_cycling_multi_sources_examples_iterable():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"text": "foo"})
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"text": "bar"})
ex_iterable = CyclingMultiSourcesExamplesIterable([ex_iterable1, ex_iterable2])
expected = list(chain(*zip(generate_examples_fn(text="foo"), generate_examples_fn(text="bar"))))
# The cycling stops as soon as one iterable is out of examples (here ex_iterable1), so the last sample from ex_iterable2 is unecessary
expected = expected[:-1]
assert next(iter(ex_iterable)) == expected[0]
assert list(ex_iterable) == expected
assert all((x["id"], x["text"]) == (i // 2, "bar" if i % 2 else "foo") for i, (_, x) in enumerate(ex_iterable))
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_sharded_cycling_multi_sources_examples_iterable():
ex_iterable1 = ExamplesIterable(
generate_examples_fn, {"text": "foo", "filepaths": [f"{i}.txt" for i in range(3)], "n": 2}
)
ex_iterable2 = ExamplesIterable(
generate_examples_fn, {"text": "bar", "filepaths": [f"{i}.txt" for i in range(2)], "n": 2}
)
ex_iterable = CyclingMultiSourcesExamplesIterable([ex_iterable1, ex_iterable2])
expected = [
x
for i in range(2)
for x in chain(
*zip(
generate_examples_fn(text="foo", filepaths=[f"{i}.txt"], n=2),
generate_examples_fn(text="bar", filepaths=[f"{i}.txt"], n=2),
)
)
]
# The cycling stops as soon as one iterable is out of examples (here ex_iterable2 since it has 2 shards and ex_iterable1 has 3)
assert next(iter(ex_iterable)) == expected[0]
assert list(ex_iterable) == expected
assert all(x["text"] == "bar" if i % 2 else "foo" for i, (_, x) in enumerate(ex_iterable))
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize("probabilities", [None, (0.5, 0.5), (0.9, 0.1)])
def test_randomly_cycling_multi_sources_examples_iterable(probabilities):
seed = 42
generator = np.random.default_rng(seed)
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"text": "foo"})
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"text": "bar"})
ex_iterable = RandomlyCyclingMultiSourcesExamplesIterable(
[ex_iterable1, ex_iterable2], generator=generator, probabilities=probabilities
)
# The source used randomly changes at each example. It stops when one of the iterators is empty.
rng = deepcopy(generator)
iterators = (generate_examples_fn(text="foo"), generate_examples_fn(text="bar"))
indices_iterator = cycle(rng.choice(len(iterators), size=1000, p=probabilities))
expected = []
lengths = [len(list(ex_iterable1)), len(list(ex_iterable2))]
for i in indices_iterator:
if lengths[0] == 0 or lengths[1] == 0:
break
for key, example in iterators[i]:
expected.append((key, example))
lengths[i] -= 1
break
else:
break
assert next(iter(ex_iterable)) == expected[0]
assert list(ex_iterable) == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize("probabilities", [None, (0.5, 0.5), (0.9, 0.1)])
@pytest.mark.parametrize("stopping_strategy", ["first_exhausted", "all_exhausted"])
@pytest.mark.parametrize("step", [-1, 0, 5, 20, 30, 300])
def test_randomly_cycling_multi_sources_examples_iterable_state(probabilities, stopping_strategy, step):
seed = 42
generator = np.random.default_rng(seed)
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"text": "foo"})
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"text": "bar"})
ex_iterable = RandomlyCyclingMultiSourcesExamplesIterable(
[ex_iterable1, ex_iterable2],
generator=generator,
probabilities=probabilities,
stopping_strategy=stopping_strategy,
)
step = min(step, len(list(ex_iterable)) - 1)
ex_iterable._init_state_dict()
state_dict = ex_iterable.state_dict()
examples = []
for i, x in enumerate(ex_iterable):
examples.append(x)
if i == step:
state_dict = ex_iterable.state_dict()
ex_iterable.load_state_dict(state_dict)
assert examples[step + 1 :] == list(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda x: {"id+1": x["id"] + 1}, False, None), # just add 1 to the id
(3, lambda x: {"id+1": [x["id"][0] + 1]}, True, 1), # same with bs=1
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, 10), # same with bs=10
(25, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, 10), # same with bs=10
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, None), # same with bs=None
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, -1), # same with bs<=0
(3, lambda x: {k: v * 2 for k, v in x.items()}, True, 1), # make a duplicate of each example
],
)
def test_mapped_examples_iterable(n, func, batched, batch_size):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = MappedExamplesIterable(base_ex_iterable, func, batched=batched, batch_size=batch_size)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if batched is False:
expected = [{**x, **func(x)} for x in all_examples]
else:
# For batched map we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
all_transformed_examples = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
transformed_batch = func(batch)
all_transformed_examples.extend(_batch_to_examples(transformed_batch))
expected = _examples_to_batch(all_examples)
expected.update(_examples_to_batch(all_transformed_examples))
expected = list(_batch_to_examples(expected))
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda x: {"id+1": x["id"] + 1}, False, None), # just add 1 to the id
(3, lambda x: {"id+1": [x["id"][0] + 1]}, True, 1), # same with bs=1
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, 10), # same with bs=10
(25, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, 10), # same with bs=10
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, None), # same with bs=None
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, -1), # same with bs<=0
(3, lambda x: {k: v * 2 for k, v in x.items()}, True, 1), # make a duplicate of each example
],
)
def test_mapped_examples_iterable_drop_last_batch(n, func, batched, batch_size):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = MappedExamplesIterable(
base_ex_iterable, func, batched=batched, batch_size=batch_size, drop_last_batch=True
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
is_empty = False
if batched is False:
# `drop_last_batch` has no effect here
expected = [{**x, **func(x)} for x in all_examples]
else:
# For batched map we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
all_transformed_examples = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
if len(examples) < batch_size: # ignore last batch
break
batch = _examples_to_batch(examples)
transformed_batch = func(batch)
all_transformed_examples.extend(_batch_to_examples(transformed_batch))
all_examples = all_examples if n % batch_size == 0 else all_examples[: n // batch_size * batch_size]
if all_examples:
expected = _examples_to_batch(all_examples)
expected.update(_examples_to_batch(all_transformed_examples))
expected = list(_batch_to_examples(expected))
else:
is_empty = True
if not is_empty:
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
else:
with pytest.raises(StopIteration):
next(iter(ex_iterable))
def _wrap_async(func, *args, **kwargs):
async def wrapped_func(*args, **kwargs):
return func(*args, **kwargs)
return wrapped_func
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda x, index: {"id+idx": x["id"] + index}, False, None), # add the index to the id
(
25,
lambda x, indices: {"id+idx": [i + j for i, j in zip(x["id"], indices)]},
True,
10,
), # add the index to the id
(5, lambda x, indices: {"id+idx": [i + j for i, j in zip(x["id"], indices)]}, True, None), # same with bs=None
(5, lambda x, indices: {"id+idx": [i + j for i, j in zip(x["id"], indices)]}, True, -1), # same with bs<=0
],
)
@pytest.mark.parametrize("wrapper", [lambda x: x, _wrap_async])
def test_mapped_examples_iterable_with_indices(n, func, batched, batch_size, wrapper):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = MappedExamplesIterable(
base_ex_iterable, wrapper(func), batched=batched, batch_size=batch_size, with_indices=True
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if batched is False:
expected = [{**x, **func(x, idx)} for idx, x in enumerate(all_examples)]
else:
# For batched map we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
all_transformed_examples = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
indices = list(range(batch_offset, batch_offset + len(examples)))
transformed_batch = func(batch, indices)
all_transformed_examples.extend(_batch_to_examples(transformed_batch))
expected = _examples_to_batch(all_examples)
expected.update(_examples_to_batch(all_transformed_examples))
expected = list(_batch_to_examples(expected))
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size, remove_columns",
[
(3, lambda x: {"id+1": x["id"] + 1}, False, None, ["extra_column"]), # just add 1 to the id
(25, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, 10, ["extra_column"]), # same with bs=10
(
50,
lambda x: {"foo": ["bar"] * np.random.default_rng(x["id"][0]).integers(0, 10)},
True,
8,
["extra_column", "id"],
), # make a duplicate of each example
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, None, ["extra_column"]), # same with bs=None
(5, lambda x: {"id+1": [i + 1 for i in x["id"]]}, True, -1, ["extra_column"]), # same with bs<=0
],
)
def test_mapped_examples_iterable_remove_columns(n, func, batched, batch_size, remove_columns):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n, "extra_column": "foo"})
ex_iterable = MappedExamplesIterable(
base_ex_iterable, func, batched=batched, batch_size=batch_size, remove_columns=remove_columns
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
columns_to_remove = remove_columns if isinstance(remove_columns, list) else [remove_columns]
if batched is False:
expected = [{**{k: v for k, v in x.items() if k not in columns_to_remove}, **func(x)} for x in all_examples]
else:
# For batched map we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
all_transformed_examples = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
transformed_batch = func(batch)
all_transformed_examples.extend(_batch_to_examples(transformed_batch))
expected = {k: v for k, v in _examples_to_batch(all_examples).items() if k not in columns_to_remove}
expected.update(_examples_to_batch(all_transformed_examples))
expected = list(_batch_to_examples(expected))
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
# issue #7345 and PR #7353
@pytest.mark.parametrize("batched", [False, True])
@pytest.mark.parametrize("batch_size", [None, 2])
@pytest.mark.parametrize("input_columns", [None, ["i"]])
@pytest.mark.parametrize("remove_columns", [None, ["i"]])
@pytest.mark.parametrize("new_output", [False, True])
def test_iterable_dataset_vs_dataset_map(batched, batch_size, input_columns, remove_columns, new_output):
if input_columns is not None and not new_output:
return
ds1 = Dataset.from_list([{"i": i} for i in range(4)])
if batched:
def f1(i):
return {"i": [j + 1 for j in i]}
else:
def f1(i):
return {"i": i + 1}
if input_columns is None:
def f2(x):
return f1(x["i"])
else:
f2 = f1
if new_output:
f = f2
else:
def f(x):
x["i"] = f2(x)["i"]
return x
r = [
list(
ds2.map(
f,
batch_size=batch_size,
batched=batched,
remove_columns=remove_columns,
input_columns=input_columns,
)
)
for ds2 in [ds1, ds1.to_iterable_dataset()]
]
r[1] = [x for x in r[1] if len(x) > 0]
assert len(r[0]) == len(r[1])
assert all(x == y for x, y in zip(*r))
@pytest.mark.parametrize(
"n, func, batched, batch_size, fn_kwargs",
[
(3, lambda x, y=0: {"id+y": x["id"] + y}, False, None, None),
(3, lambda x, y=0: {"id+y": x["id"] + y}, False, None, {"y": 3}),
(25, lambda x, y=0: {"id+y": [i + y for i in x["id"]]}, True, 10, {"y": 3}),
(5, lambda x, y=0: {"id+y": [i + y for i in x["id"]]}, True, None, {"y": 3}), # same with bs=None
(5, lambda x, y=0: {"id+y": [i + y for i in x["id"]]}, True, -1, {"y": 3}), # same with bs<=0
],
)
def test_mapped_examples_iterable_fn_kwargs(n, func, batched, batch_size, fn_kwargs):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = MappedExamplesIterable(
base_ex_iterable, func, batched=batched, batch_size=batch_size, fn_kwargs=fn_kwargs
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if fn_kwargs is None:
fn_kwargs = {}
if batched is False:
expected = [{**x, **func(x, **fn_kwargs)} for x in all_examples]
else:
# For batched map we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
all_transformed_examples = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
transformed_batch = func(batch, **fn_kwargs)
all_transformed_examples.extend(_batch_to_examples(transformed_batch))
expected = _examples_to_batch(all_examples)
expected.update(_examples_to_batch(all_transformed_examples))
expected = list(_batch_to_examples(expected))
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size, input_columns",
[
(3, lambda id_: {"id+1": id_ + 1}, False, None, ["id"]), # just add 1 to the id
(25, lambda ids_: {"id+1": [i + 1 for i in ids_]}, True, 10, ["id"]), # same with bs=10
(5, lambda ids_: {"id+1": [i + 1 for i in ids_]}, True, None, ["id"]), # same with bs=None
(5, lambda ids_: {"id+1": [i + 1 for i in ids_]}, True, -1, ["id"]), # same with bs<=0
],
)
def test_mapped_examples_iterable_input_columns(n, func, batched, batch_size, input_columns):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = MappedExamplesIterable(
base_ex_iterable, func, batched=batched, batch_size=batch_size, input_columns=input_columns
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
columns_to_input = input_columns if isinstance(input_columns, list) else [input_columns]
if batched is False:
expected = [{**x, **func(*[x[col] for col in columns_to_input])} for x in all_examples]
else:
# For batched map we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
all_transformed_examples = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
transformed_batch = func(*[batch[col] for col in columns_to_input])
all_transformed_examples.extend(_batch_to_examples(transformed_batch))
expected = _examples_to_batch(all_examples)
expected.update(_examples_to_batch(all_transformed_examples))
expected = list(_batch_to_examples(expected))
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), False, None), # just add 1 to the id
(3, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 1), # same with bs=1
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 10), # same with bs=10
(25, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 10), # same with bs=10
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, None), # same with bs=None
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, -1), # same with bs<=0
(3, lambda t: pa.concat_tables([t] * 2), True, 1), # make a duplicate of each example
],
)
def test_mapped_examples_iterable_arrow_format(n, func, batched, batch_size):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
base_ex_iterable = RebatchedArrowExamplesIterable(
base_ex_iterable, batch_size=batch_size if batched else 1, force_convert_to_arrow=True
)
ex_iterable = MappedExamplesIterable(
base_ex_iterable,
func,
batched=batched,
batch_size=batch_size,
formatting=FormattingConfig(format_type="arrow"),
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if batched is False:
expected = [func(pa.Table.from_pylist([x])).to_pylist()[0] for x in all_examples]
else:
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = pa.Table.from_pylist(examples)
expected.extend(func(batch).to_pylist())
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), False, None), # just add 1 to the id
(3, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 1), # same with bs=1
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 10), # same with bs=10
(25, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 10), # same with bs=10
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, None), # same with bs=None
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, -1), # same with bs<=0
(3, lambda t: pa.concat_tables([t] * 2), True, 1), # make a duplicate of each example
],
)
def test_mapped_examples_iterable_arrow_format_from_arrow_examples_iterable(n, func, batched, batch_size):
base_ex_iterable = ArrowExamplesIterable(generate_tables_fn, {"n": n})
base_ex_iterable = RebatchedArrowExamplesIterable(base_ex_iterable, batch_size=batch_size if batched else 1)
ex_iterable = MappedExamplesIterable(
base_ex_iterable,
func,
batched=batched,
batch_size=batch_size,
formatting=FormattingConfig(format_type="arrow"),
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if batched is False:
expected = [func(pa.Table.from_pylist([x])).to_pylist()[0] for x in all_examples]
else:
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = pa.Table.from_pylist(examples)
expected.extend(func(batch).to_pylist())
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), False, None), # just add 1 to the id
(3, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 1), # same with bs=1
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 10), # same with bs=10
(25, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 10), # same with bs=10
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, None), # same with bs=None
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, -1), # same with bs<=0
(3, lambda t: pa.concat_tables([t] * 2), True, 1), # make a duplicate of each example
],
)
def test_mapped_examples_iterable_drop_last_batch_and_arrow_format(n, func, batched, batch_size):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
base_ex_iterable = RebatchedArrowExamplesIterable(
base_ex_iterable, batch_size=batch_size if batched else 1, force_convert_to_arrow=True
)
ex_iterable = MappedExamplesIterable(
base_ex_iterable,
func,
batched=batched,
batch_size=batch_size,
drop_last_batch=True,
formatting=FormattingConfig(format_type="arrow"),
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
is_empty = False
if batched is False:
# `drop_last_batch` has no effect here
expected = [func(pa.Table.from_pylist([x])).to_pylist()[0] for x in all_examples]
else:
all_transformed_examples = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
if len(examples) < batch_size: # ignore last batch
break
batch = pa.Table.from_pylist(examples)
out = func(batch)
all_transformed_examples.extend(
out.to_pylist()
) # we don't merge with input since they're arrow tables and not dictionaries
all_examples = all_examples if n % batch_size == 0 else all_examples[: n // batch_size * batch_size]
if all_examples:
expected = all_transformed_examples
else:
is_empty = True
if not is_empty:
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
else:
with pytest.raises(StopIteration):
next(iter(ex_iterable))
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(
3,
lambda t, index: t.append_column("id+idx", pc.add(t["id"], index)),
False,
None,
), # add the index to the id
(
25,
lambda t, indices: t.append_column("id+idx", pc.add(t["id"], indices)),
True,
10,
), # add the index to the id
(5, lambda t, indices: t.append_column("id+idx", pc.add(t["id"], indices)), True, None), # same with bs=None
(5, lambda t, indices: t.append_column("id+idx", pc.add(t["id"], indices)), True, -1), # same with bs<=0
],
)
def test_mapped_examples_iterable_with_indices_and_arrow_format(n, func, batched, batch_size):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
base_ex_iterable = RebatchedArrowExamplesIterable(
base_ex_iterable, batch_size=batch_size if batched else 1, force_convert_to_arrow=True
)
ex_iterable = MappedExamplesIterable(
base_ex_iterable,
func,
batched=batched,
batch_size=batch_size,
with_indices=True,
formatting=FormattingConfig(format_type="arrow"),
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if batched is False:
expected = [func(pa.Table.from_pylist([x]), i).to_pylist()[0] for i, x in enumerate(all_examples)]
else:
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = pa.Table.from_pylist(examples)
expected.extend(func(batch, list(range(batch_offset, batch_offset + len(batch)))).to_pylist())
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size, remove_columns",
[
(
3,
lambda t: t.append_column("id+1", pc.add(t["id"], 1)),
False,
None,
["extra_column"],
), # just add 1 to the id
(25, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, 10, ["extra_column"]), # same with bs=10
(
50,
lambda t: pa.table({"foo": ["bar"] * np.random.default_rng(t["id"][0].as_py()).integers(0, 10)}),
True,
8,
["extra_column", "id"],
), # make a duplicate of each example
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, None, ["extra_column"]), # same with bs=None
(5, lambda t: t.append_column("id+1", pc.add(t["id"], 1)), True, -1, ["extra_column"]), # same with bs<=0
],
)
def test_mapped_examples_iterable_remove_columns_arrow_format(n, func, batched, batch_size, remove_columns):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n, "extra_column": "foo"})
base_ex_iterable = RebatchedArrowExamplesIterable(
base_ex_iterable, batch_size=batch_size if batched else 1, force_convert_to_arrow=True
)
ex_iterable = MappedExamplesIterable(
base_ex_iterable,
func,
batched=batched,
batch_size=batch_size,
remove_columns=remove_columns,
formatting=FormattingConfig(format_type="arrow"),
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
columns_to_remove = remove_columns if isinstance(remove_columns, list) else [remove_columns]
if batched is False:
expected = [
{**{k: v for k, v in func(pa.Table.from_pylist([x])).to_pylist()[0].items() if k not in columns_to_remove}}
for x in all_examples
]
else:
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = pa.Table.from_pylist(examples)
expected.extend(
[{k: v for k, v in x.items() if k not in columns_to_remove} for x in func(batch).to_pylist()]
)
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size, fn_kwargs",
[
(3, lambda t, y=0: t.append_column("id+idx", pc.add(t["id"], y)), False, None, None),
(3, lambda t, y=0: t.append_column("id+idx", pc.add(t["id"], y)), False, None, {"y": 3}),
(25, lambda t, y=0: t.append_column("id+idx", pc.add(t["id"], y)), True, 10, {"y": 3}),
(5, lambda t, y=0: t.append_column("id+idx", pc.add(t["id"], y)), True, None, {"y": 3}), # same with bs=None
(5, lambda t, y=0: t.append_column("id+idx", pc.add(t["id"], y)), True, -1, {"y": 3}), # same with bs<=0
],
)
def test_mapped_examples_iterable_fn_kwargs_and_arrow_format(n, func, batched, batch_size, fn_kwargs):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
base_ex_iterable = RebatchedArrowExamplesIterable(
base_ex_iterable, batch_size=batch_size if batched else 1, force_convert_to_arrow=True
)
ex_iterable = MappedExamplesIterable(
base_ex_iterable,
func,
batched=batched,
batch_size=batch_size,
fn_kwargs=fn_kwargs,
formatting=FormattingConfig(format_type="arrow"),
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if fn_kwargs is None:
fn_kwargs = {}
if batched is False:
expected = [func(pa.Table.from_pylist([x]), **fn_kwargs).to_pylist()[0] for x in all_examples]
else:
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = pa.Table.from_pylist(examples)
expected.extend(func(batch, **fn_kwargs).to_pylist())
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size, input_columns",
[
(3, lambda id_: pa.table({"id+1": pc.add(id_, 1)}), False, None, ["id"]), # just add 1 to the id
(25, lambda ids_: pa.table({"id+1": pc.add(ids_, 1)}), True, 10, ["id"]), # same with bs=10
(5, lambda ids_: pa.table({"id+1": pc.add(ids_, 1)}), True, None, ["id"]), # same with bs=None
(5, lambda ids_: pa.table({"id+1": pc.add(ids_, 1)}), True, -1, ["id"]), # same with bs<=0
],
)
def test_mapped_examples_iterable_input_columns_and_arrow_format(n, func, batched, batch_size, input_columns):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
base_ex_iterable = RebatchedArrowExamplesIterable(
base_ex_iterable, batch_size=batch_size if batched else 1, force_convert_to_arrow=True
)
ex_iterable = MappedExamplesIterable(
base_ex_iterable,
func,
batched=batched,
batch_size=batch_size,
input_columns=input_columns,
formatting=FormattingConfig(format_type="arrow"),
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
columns_to_input = input_columns if isinstance(input_columns, list) else [input_columns]
if batched is False:
expected = [
func(*[pa.Table.from_pylist([x])[col] for col in columns_to_input]).to_pylist()[0] for x in all_examples
]
else:
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = pa.Table.from_pylist(examples)
expected.extend(func(*[batch[col] for col in columns_to_input]).to_pylist())
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda x: x["id"] % 2 == 0, False, None), # keep even number
(3, lambda x: [x["id"][0] % 2 == 0], True, 1), # same with bs=1
(25, lambda x: [i % 2 == 0 for i in x["id"]], True, 10), # same with bs=10
(5, lambda x: [i % 2 == 0 for i in x["id"]], True, None), # same with bs=None
(5, lambda x: [i % 2 == 0 for i in x["id"]], True, -1), # same with bs<=0
(3, lambda x: False, False, None), # return 0 examples
(3, lambda x: [False] * len(x["id"]), True, 10), # same with bs=10
],
)
def test_filtered_examples_iterable(n, func, batched, batch_size):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = FilteredExamplesIterable(base_ex_iterable, func, batched=batched, batch_size=batch_size)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if batched is False:
expected = [x for x in all_examples if func(x)]
else:
# For batched filter we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
mask = func(batch)
expected.extend([x for x, to_keep in zip(examples, mask) if to_keep])
if expected:
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size",
[
(3, lambda x, index: index % 2 == 0, False, None), # keep even number
(25, lambda x, indices: [idx % 2 == 0 for idx in indices], True, 10), # same with bs=10
(5, lambda x, indices: [idx % 2 == 0 for idx in indices], True, None), # same with bs=None
(5, lambda x, indices: [idx % 2 == 0 for idx in indices], True, -1), # same with bs<=0
],
)
def test_filtered_examples_iterable_with_indices(n, func, batched, batch_size):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = FilteredExamplesIterable(
base_ex_iterable, func, batched=batched, batch_size=batch_size, with_indices=True
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if batched is False:
expected = [x for idx, x in enumerate(all_examples) if func(x, idx)]
else:
# For batched filter we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
indices = list(range(batch_offset, batch_offset + len(examples)))
mask = func(batch, indices)
expected.extend([x for x, to_keep in zip(examples, mask) if to_keep])
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"n, func, batched, batch_size, input_columns",
[
(3, lambda id_: id_ % 2 == 0, False, None, ["id"]), # keep even number
(25, lambda ids_: [i % 2 == 0 for i in ids_], True, 10, ["id"]), # same with bs=10
(3, lambda ids_: [i % 2 == 0 for i in ids_], True, None, ["id"]), # same with bs=None
(3, lambda ids_: [i % 2 == 0 for i in ids_], True, None, ["id"]), # same with bs=None
],
)
def test_filtered_examples_iterable_input_columns(n, func, batched, batch_size, input_columns):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = FilteredExamplesIterable(
base_ex_iterable, func, batched=batched, batch_size=batch_size, input_columns=input_columns
)
all_examples = [x for _, x in generate_examples_fn(n=n)]
columns_to_input = input_columns if isinstance(input_columns, list) else [input_columns]
if batched is False:
expected = [x for x in all_examples if func(*[x[col] for col in columns_to_input])]
else:
# For batched filter we have to format the examples as a batch (i.e. in one single dictionary) to pass the batch to the function
expected = []
# If batch_size is None or <=0, we use the whole dataset as a single batch
if batch_size is None or batch_size <= 0:
batch_size = len(all_examples)
for batch_offset in range(0, len(all_examples), batch_size):
examples = all_examples[batch_offset : batch_offset + batch_size]
batch = _examples_to_batch(examples)
mask = func(*[batch[col] for col in columns_to_input])
expected.extend([x for x, to_keep in zip(examples, mask) if to_keep])
assert next(iter(ex_iterable))[1] == expected[0]
assert [x for _, x in ex_iterable] == expected
assert_load_state_dict_resumes_iteration(ex_iterable)
def test_map_async():
dset = Dataset.from_dict({"x": range(100)}).to_iterable_dataset()
async def f(example):
await asyncio.sleep(0.1)
return {"y": 1}
_start = time.time()
out = dset.map(f)
assert time.time() - _start < 2.0
assert next(iter(out))["y"] == 1
async def f(batch):
await asyncio.sleep(0.1)
return {"y": [1] * len(batch["x"])}
_start = time.time()
out = dset.map(f, batched=True)
assert time.time() - _start < 2.0
assert next(iter(out))["y"] == 1
def test_filter_async():
dset = Dataset.from_dict({"x": range(100)}).to_iterable_dataset()
async def f(example):
await asyncio.sleep(0.1)
return example["x"] == 42
_start = time.time()
out = dset.filter(f)
assert time.time() - _start < 2.0
assert len(list(out)) == 1
async def f(batch):
await asyncio.sleep(0.1)
return [x == 42 for x in batch["x"]]
_start = time.time()
out = dset.filter(f, batched=True)
assert time.time() - _start < 2.0
assert len(list(out)) == 1
def test_skip_examples_iterable():
total, count = 10, 2
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": total})
skip_ex_iterable = SkipExamplesIterable(base_ex_iterable, n=count)
expected = list(generate_examples_fn(n=total))[count:]
assert list(skip_ex_iterable) == expected
with pytest.raises(DataSourcesShufflingDisallowed):
skip_ex_iterable.shuffle_data_sources(np.random.default_rng(42))
assert_load_state_dict_resumes_iteration(skip_ex_iterable)
def test_take_examples_iterable():
total, count = 10, 2
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": total})
take_ex_iterable = TakeExamplesIterable(base_ex_iterable, n=count)
expected = list(generate_examples_fn(n=total))[:count]
assert list(take_ex_iterable) == expected
with pytest.raises(DataSourcesShufflingDisallowed):
take_ex_iterable.shuffle_data_sources(np.random.default_rng(42))
assert_load_state_dict_resumes_iteration(take_ex_iterable)
def test_step_examples_iterable():
total, step, offset = 10, 2, 1
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": total})
step_ex_iterable = StepExamplesIterable(base_ex_iterable, step=step, offset=offset)
expected = list(generate_examples_fn(n=total))[offset::step]
assert list(step_ex_iterable) == expected
assert_load_state_dict_resumes_iteration(step_ex_iterable)
@pytest.mark.parametrize("count", [2, DEFAULT_BATCH_SIZE, DEFAULT_BATCH_SIZE + 2, DEFAULT_BATCH_SIZE * 3])
def test_skip_arrow_examples_iterable(count):
total = 10
base_ex_iterable = ArrowExamplesIterable(generate_tables_fn, {"n": total})
skip_ex_iterable = SkipExamplesIterable(base_ex_iterable, n=count)
expected = [x for _, pa_table in generate_tables_fn(n=total) for x in pa_table.to_pylist()][count:]
assert [example for _, example in skip_ex_iterable] == expected
assert [example for _, pa_table in skip_ex_iterable.iter_arrow() for example in pa_table.to_pylist()] == expected
with pytest.raises(DataSourcesShufflingDisallowed):
skip_ex_iterable.shuffle_data_sources(np.random.default_rng(42))
assert_load_state_dict_resumes_iteration(skip_ex_iterable)
assert_load_state_dict_resumes_arrow_iteration(skip_ex_iterable)
def test_take_arrow_examples_iterable():
total, count = 10, 2
base_ex_iterable = ArrowExamplesIterable(generate_tables_fn, {"n": total})
take_ex_iterable = TakeExamplesIterable(base_ex_iterable, n=count)
expected = [x for _, pa_table in generate_tables_fn(n=total) for x in pa_table.to_pylist()][:count]
assert [example for _, example in take_ex_iterable] == expected
with pytest.raises(DataSourcesShufflingDisallowed):
take_ex_iterable.shuffle_data_sources(np.random.default_rng(42))
assert_load_state_dict_resumes_iteration(take_ex_iterable)
def test_step_arrow_examples_iterable():
total, step, offset = 10, 2, 1
base_ex_iterable = ArrowExamplesIterable(generate_tables_fn, {"n": total})
step_ex_iterable = StepExamplesIterable(base_ex_iterable, step=step, offset=offset)
expected = [x for _, pa_table in generate_tables_fn(n=total) for x in pa_table.to_pylist()][offset::step]
assert [example for _, example in step_ex_iterable] == expected
assert_load_state_dict_resumes_iteration(step_ex_iterable)
@pytest.mark.parametrize(
"n, num_times",
[
(3, None),
(3, 3),
(3, 0),
],
)
def test_repeat_examples_iterable(n, num_times):
base_ex_iterable = ExamplesIterable(generate_examples_fn, {"n": n})
ex_iterable = RepeatExamplesIterable(base_ex_iterable, num_times=num_times)
all_examples = [x for _, x in generate_examples_fn(n=n)]
if num_times is not None:
expected = all_examples * max(num_times, 0)
assert [x for _, x in ex_iterable] == expected
else:
max_iters = 135
iterator = iter(ex_iterable)
for i in range(max_iters):
assert next(iterator)[1] == all_examples[i % len(all_examples)], f"iteration {i} failed,"
def test_vertically_concatenated_examples_iterable():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label": 10})
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label": 5})
concatenated_ex_iterable = VerticallyConcatenatedMultiSourcesExamplesIterable([ex_iterable1, ex_iterable2])
expected = [x for _, x in ex_iterable1] + [x for _, x in ex_iterable2]
assert [x for _, x in concatenated_ex_iterable] == expected
assert_load_state_dict_resumes_iteration(concatenated_ex_iterable)
def test_vertically_concatenated_examples_iterable_with_different_columns():
# having different columns is supported
# Though iterable datasets fill the missing data with nulls
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label": 10})
ex_iterable2 = ExamplesIterable(generate_examples_fn, {})
concatenated_ex_iterable = VerticallyConcatenatedMultiSourcesExamplesIterable([ex_iterable1, ex_iterable2])
expected = [x for _, x in ex_iterable1] + [x for _, x in ex_iterable2]
assert [x for _, x in concatenated_ex_iterable] == expected
assert_load_state_dict_resumes_iteration(concatenated_ex_iterable)
def test_vertically_concatenated_examples_iterable_shuffle_data_sources():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label": 10})
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label": 5})
concatenated_ex_iterable = VerticallyConcatenatedMultiSourcesExamplesIterable([ex_iterable1, ex_iterable2])
rng = np.random.default_rng(42)
shuffled_ex_iterable = concatenated_ex_iterable.shuffle_data_sources(rng)
# make sure the list of examples iterables is shuffled, and each examples iterable is shuffled
expected = [x for _, x in ex_iterable2.shuffle_data_sources(rng)] + [
x for _, x in ex_iterable1.shuffle_data_sources(rng)
]
assert [x for _, x in shuffled_ex_iterable] == expected
assert_load_state_dict_resumes_iteration(shuffled_ex_iterable)
def test_horizontally_concatenated_examples_iterable():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label1": 10})
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label2": 5})
concatenated_ex_iterable = HorizontallyConcatenatedMultiSourcesExamplesIterable([ex_iterable1, ex_iterable2])
with pytest.raises(ValueError): # column "id" is duplicated -> raise an error
list(concatenated_ex_iterable)
ex_iterable2 = MappedExamplesIterable(ex_iterable2, lambda x: x, remove_columns=["id"])
concatenated_ex_iterable = HorizontallyConcatenatedMultiSourcesExamplesIterable([ex_iterable1, ex_iterable2])
expected = [{**x, **y} for (_, x), (_, y) in zip(ex_iterable1, ex_iterable2)]
assert [x for _, x in concatenated_ex_iterable] == expected
assert concatenated_ex_iterable.shuffle_data_sources(np.random.default_rng(42)) is concatenated_ex_iterable, (
"horizontally concatenated examples makes the shards order fixed"
)
assert_load_state_dict_resumes_iteration(concatenated_ex_iterable)
@pytest.mark.parametrize(
"ex_iterable",
[
ExamplesIterable(generate_examples_fn, {}),
SelectColumnsIterable(ExamplesIterable(generate_examples_fn, {}), ["id"]),
StepExamplesIterable(ExamplesIterable(generate_examples_fn, {}), 2, 0),
CyclingMultiSourcesExamplesIterable([ExamplesIterable(generate_examples_fn, {})]),
VerticallyConcatenatedMultiSourcesExamplesIterable([ExamplesIterable(generate_examples_fn, {})]),
HorizontallyConcatenatedMultiSourcesExamplesIterable([ExamplesIterable(generate_examples_fn, {})]),
RandomlyCyclingMultiSourcesExamplesIterable(
[ExamplesIterable(generate_examples_fn, {})], np.random.default_rng(42)
),
MappedExamplesIterable(ExamplesIterable(generate_examples_fn, {}), lambda x: x),
MappedExamplesIterable(ArrowExamplesIterable(generate_tables_fn, {}), lambda x: x),
FilteredExamplesIterable(ExamplesIterable(generate_examples_fn, {}), lambda x: True),
FilteredExamplesIterable(ArrowExamplesIterable(generate_tables_fn, {}), lambda x: True),
BufferShuffledExamplesIterable(ExamplesIterable(generate_examples_fn, {}), 10, np.random.default_rng(42)),
SkipExamplesIterable(ExamplesIterable(generate_examples_fn, {}), 10),
TakeExamplesIterable(ExamplesIterable(generate_examples_fn, {}), 10),
FormattedExamplesIterable(
ExamplesIterable(generate_examples_fn, {}), None, Features({"id": Value("int32")}), token_per_repo_id={}
),
],
)
def test_no_iter_arrow(ex_iterable: _BaseExamplesIterable):
assert ex_iterable.iter_arrow is None
if not isinstance(ex_iterable, BufferShuffledExamplesIterable):
assert_load_state_dict_resumes_iteration(ex_iterable)
@pytest.mark.parametrize(
"ex_iterable",
[
ArrowExamplesIterable(generate_tables_fn, {}),
SelectColumnsIterable(ArrowExamplesIterable(generate_tables_fn, {}), ["id"]),
StepExamplesIterable(ArrowExamplesIterable(generate_tables_fn, {}), 2, 0),
# CyclingMultiSourcesExamplesIterable([ArrowExamplesIterable(generate_tables_fn, {})]), # not implemented
VerticallyConcatenatedMultiSourcesExamplesIterable([ArrowExamplesIterable(generate_tables_fn, {})]),
HorizontallyConcatenatedMultiSourcesExamplesIterable([ArrowExamplesIterable(generate_tables_fn, {})]),
# RandomlyCyclingMultiSourcesExamplesIterable([ArrowExamplesIterable(generate_tables_fn, {})], np.random.default_rng(42)), # not implemented
MappedExamplesIterable(
RebatchedArrowExamplesIterable(
ExamplesIterable(generate_examples_fn, {}), batch_size=1, force_convert_to_arrow=True
),
lambda t: t,
formatting=FormattingConfig(format_type="arrow"),
),
MappedExamplesIterable(
RebatchedArrowExamplesIterable(ArrowExamplesIterable(generate_tables_fn, {}), batch_size=1),
lambda t: t,
formatting=FormattingConfig(format_type="arrow"),
),
FilteredExamplesIterable(
RebatchedArrowExamplesIterable(
ExamplesIterable(generate_examples_fn, {}), batch_size=1, force_convert_to_arrow=True
),
lambda t: True,
formatting=FormattingConfig(format_type="arrow"),
),
FilteredExamplesIterable(
RebatchedArrowExamplesIterable(
ArrowExamplesIterable(generate_tables_fn, {}), batch_size=1, force_convert_to_arrow=True
),
lambda t: True,
formatting=FormattingConfig(format_type="arrow"),
),
# BufferShuffledExamplesIterable(ArrowExamplesIterable(generate_tables_fn, {}), 10, np.random.default_rng(42)), # not implemented
SkipExamplesIterable(ArrowExamplesIterable(generate_tables_fn, {}), 10),
TakeExamplesIterable(ArrowExamplesIterable(generate_tables_fn, {}), 10),
FormattedExamplesIterable(
ArrowExamplesIterable(generate_tables_fn, {}), None, Features({"id": Value("int32")}), token_per_repo_id={}
),
],
)
def test_iter_arrow(ex_iterable: _BaseExamplesIterable):
assert ex_iterable.iter_arrow is not None
key, pa_table = next(ex_iterable.iter_arrow())
assert isinstance(pa_table, pa.Table)
assert_load_state_dict_resumes_arrow_iteration(ex_iterable)
############################
#
# IterableDataset tests
#
############################
def test_iterable_dataset():
dataset = IterableDataset(ExamplesIterable(generate_examples_fn, {}))
expected = [x for _, x in generate_examples_fn()]
assert next(iter(dataset)) == expected[0]
assert list(dataset) == expected
def test_iterable_dataset_push_to_hub_max_shard_size_and_num_shards_are_mutually_exclusive():
dataset = IterableDataset.from_generator(lambda: iter([{"id": 0}]))
with pytest.raises(ValueError, match="either max_shard_size or num_shards"):
dataset.push_to_hub("user/dataset", max_shard_size="1MB", num_shards=2)
def test_iterable_dataset_push_to_hub_single_shard_disables_multiprocessing():
dataset = IterableDataset.from_generator(lambda: iter([{"id": 0}]))
mock_context = MagicMock()
mock_pool = MagicMock()
mock_pool_cls = MagicMock(return_value=mock_pool)
mock_context.Pool = mock_pool_cls
with (
patch("multiprocess.get_context", return_value=mock_context),
patch.object(
IterableDataset,
"_push_parquet_shards_to_hub_single",
return_value=iter([(0, True, ([], [], Features(), 0, 1))]),
),
):
additions, new_parquet_paths, features, spit_info, uploaded_size = dataset._push_parquet_shards_to_hub(
resolved_output_path=HfFileSystemResolvedRepositoryPath(
repo_type="dataset", repo_id="user/dataset", revision="main", path_in_repo=""
),
data_dir="data",
split="train",
token=None,
create_pr=False,
max_shard_size=None,
num_shards=1,
embed_external_files=False,
num_proc=4,
)
mock_pool.assert_not_called()
assert additions == []
assert new_parquet_paths == []
assert features == Features()
assert spit_info.name == "train"
assert spit_info.num_bytes == 0
assert spit_info.num_examples == 1
assert uploaded_size == 0
def test_iterable_dataset_push_to_hub_default_num_shards_uses_dataset_num_shards():
def gen(shard_names):
for shard_name in shard_names:
yield {"shard_name": shard_name}
dataset = IterableDataset.from_generator(gen, gen_kwargs={"shard_names": ["train-0", "train-1", "train-2"]})
captured_num_shards = {}
def mock_push_single(**kwargs):
captured_num_shards["value"] = kwargs["num_shards"]
return iter([(0, True, ([], [], Features(), 0, 0))])
with patch.object(IterableDataset, "_push_parquet_shards_to_hub_single", side_effect=mock_push_single):
dataset._push_parquet_shards_to_hub(
resolved_output_path=HfFileSystemResolvedRepositoryPath(
repo_type="dataset", repo_id="user/dataset", revision="main", path_in_repo=""
),
data_dir="data",
split="train",
token=None,
create_pr=False,
max_shard_size=None,
num_shards=None,
embed_external_files=False,
num_proc=None,
)
assert captured_num_shards["value"] == dataset.num_shards
def test_iterable_dataset_push_to_hub_max_shard_size_computes_num_shards_from_estimated_size():
dataset = Dataset.from_dict({"id": list(range(16)), "text": ["value"] * 16}).to_iterable_dataset()
estimated_nbytes = sum(
table.nbytes for table in dataset.with_format("arrow").iter(batch_size=config.DEFAULT_MAX_BATCH_SIZE)
)
max_shard_size = max(1, estimated_nbytes // 2)
expected_num_shards = max(int(estimated_nbytes / max_shard_size) + 1, 1)
captured_num_shards = {}
def mock_push_single(**kwargs):
captured_num_shards["value"] = kwargs["num_shards"]
return iter([(0, True, ([], [], Features(), 0, 0))])
with patch.object(IterableDataset, "_push_parquet_shards_to_hub_single", side_effect=mock_push_single):
dataset._push_parquet_shards_to_hub(
resolved_output_path=HfFileSystemResolvedRepositoryPath(
repo_type="dataset", repo_id="user/dataset", revision="main", path_in_repo=""
),
data_dir="data",
split="train",
token=None,
create_pr=False,
max_shard_size=max_shard_size,
num_shards=None,
embed_external_files=False,
num_proc=None,
)
assert captured_num_shards["value"] == expected_num_shards
def test_iterable_dataset_push_to_hub_max_shard_size_respects_num_proc_floor():
dataset = IterableDataset.from_generator(
lambda shard_names: ({"shard_name": shard_name} for shard_name in shard_names),
gen_kwargs={"shard_names": ["train-0", "train-1", "train-2"]},
)
estimated_nbytes = sum(
table.nbytes for table in dataset.with_format("arrow").iter(batch_size=config.DEFAULT_MAX_BATCH_SIZE)
)
requested_num_proc = dataset.num_shards
max_shard_size = max(estimated_nbytes * 2, 1)
expected_num_shards = max(int(estimated_nbytes / max_shard_size) + 1, requested_num_proc)
with (
patch(
"datasets.iterable_dataset.iflatmap_unordered",
return_value=iter([(0, True, ([], [], Features(), 0, 0))]),
) as mock_iflatmap_unordered,
):
dataset._push_parquet_shards_to_hub(
resolved_output_path=HfFileSystemResolvedRepositoryPath(
repo_id="user/dataset", path_in_repo="", revision="main", repo_type="dataset"
),
data_dir="data",
split="train",
token=None,
create_pr=False,
max_shard_size=max_shard_size,
num_shards=None,
embed_external_files=False,
num_proc=requested_num_proc,
)
kwargs_iterable = mock_iflatmap_unordered.call_args.kwargs["kwargs_iterable"]
assert len(kwargs_iterable) == requested_num_proc
assert {job_kwargs["num_shards"] for job_kwargs in kwargs_iterable} == {expected_num_shards}
def test_iterable_dataset_from_generator():
data = [
{"col_1": "0", "col_2": 0, "col_3": 0.0},
{"col_1": "1", "col_2": 1, "col_3": 1.0},
{"col_1": "2", "col_2": 2, "col_3": 2.0},
{"col_1": "3", "col_2": 3, "col_3": 3.0},
]
def gen():
yield from data
dataset = IterableDataset.from_generator(gen)
assert isinstance(dataset, IterableDataset)
assert list(dataset) == data
def test_iterable_dataset_from_generator_with_shards():
def gen(shard_names):
for shard_name in shard_names:
for i in range(10):
yield {"shard_name": shard_name, "i": i}
shard_names = [f"data{shard_idx}.txt" for shard_idx in range(4)]
dataset = IterableDataset.from_generator(gen, gen_kwargs={"shard_names": shard_names})
assert isinstance(dataset, IterableDataset)
assert dataset.num_shards == len(shard_names)
def test_iterable_dataset_to_pandas_preserves_declared_features():
features = Features({"col": Value("int32")})
dataset = Dataset.from_dict({"col": [0, None]}, features=features).to_iterable_dataset()
df = dataset.to_pandas()
assert list(df.columns) == ["col"]
assert df["col"].iloc[0] == 0
assert pd.isna(df["col"].iloc[1])
batches = list(dataset.to_pandas(batch_size=1, batched=True))
assert len(batches) == 2
assert batches[0]["col"].iloc[0] == 0
assert pd.isna(batches[1]["col"].iloc[0])
def test_iterable_dataset_to_pandas_casts_when_schema_mismatch():
from datasets.table import cast_table_to_features as original_cast_table_to_features
features = Features({"col": Value("int32")})
dataset = IterableDataset(
ExamplesIterable(lambda: iter([("0", {"col": 0}), ("1", {"col": 1})]), {}),
info=DatasetInfo(features=features),
)
with patch(
"datasets.iterable_dataset.cast_table_to_features",
wraps=original_cast_table_to_features,
) as mock_cast:
df = dataset.to_pandas()
batches = list(dataset.to_pandas(batch_size=1, batched=True))
assert mock_cast.call_count >= 1
assert list(df.columns) == ["col"]
assert df["col"].iloc[0] == 0
assert len(batches) == 2
@require_numpy1_on_windows
def test_iterable_dataset_from_file(dataset: IterableDataset, arrow_file: str):
with assert_arrow_memory_doesnt_increase():
dataset_from_file = IterableDataset.from_file(arrow_file)
expected_features = dataset._resolve_features().features
assert dataset_from_file.features.type == expected_features.type
assert dataset_from_file.features == expected_features
assert isinstance(dataset_from_file, IterableDataset)
assert list(dataset_from_file) == list(dataset)
@require_not_windows
@require_dill_gt_0_3_2
@require_pyspark
def test_from_spark_streaming():
import pyspark
spark = pyspark.sql.SparkSession.builder.master("local[*]").appName("pyspark").getOrCreate()
data = [
("0", 0, 0.0),
("1", 1, 1.0),
("2", 2, 2.0),
("3", 3, 3.0),
]
df = spark.createDataFrame(data, "col_1: string, col_2: int, col_3: float")
dataset = IterableDataset.from_spark(df)
assert isinstance(dataset, IterableDataset)
results = []
for ex in dataset:
results.append(ex)
assert results == [
{"col_1": "0", "col_2": 0, "col_3": 0.0},
{"col_1": "1", "col_2": 1, "col_3": 1.0},
{"col_1": "2", "col_2": 2, "col_3": 2.0},
{"col_1": "3", "col_2": 3, "col_3": 3.0},
]
@require_not_windows
@require_dill_gt_0_3_2
@require_pyspark
def test_from_spark_streaming_features():
import PIL.Image
import pyspark
spark = pyspark.sql.SparkSession.builder.master("local[*]").appName("pyspark").getOrCreate()
data = [(0, np.arange(4 * 4 * 3).reshape(4, 4, 3).tolist())]
df = spark.createDataFrame(data, "idx: int, image: array<array<array<int>>>")
features = Features({"idx": Value("int64"), "image": Image()})
dataset = IterableDataset.from_spark(
df,
features=features,
)
assert isinstance(dataset, IterableDataset)
results = []
for ex in dataset:
results.append(ex)
assert len(results) == 1
isinstance(results[0]["image"], PIL.Image.Image)
@require_torch
def test_iterable_dataset_torch_integration():
ex_iterable = ExamplesIterable(generate_examples_fn, {})
dataset = IterableDataset(ex_iterable)
import torch.utils.data
assert isinstance(dataset, torch.utils.data.IterableDataset)
assert isinstance(dataset, IterableDataset)
@require_torch
def test_iterable_dataset_torch_picklable():
import pickle
ex_iterable = ExamplesIterable(generate_examples_fn, {})
dataset = IterableDataset(ex_iterable, formatting=FormattingConfig(format_type="torch"))
reloaded_dataset = pickle.loads(pickle.dumps(dataset))
import torch.utils.data
assert isinstance(reloaded_dataset, IterableDataset)
assert isinstance(reloaded_dataset, torch.utils.data.IterableDataset)
assert reloaded_dataset._formatting.format_type == "torch"
assert len(list(dataset)) == len(list(reloaded_dataset))
@require_torch
def test_iterable_dataset_with_format_torch():
ex_iterable = ExamplesIterable(generate_examples_fn, {})
dataset = IterableDataset(ex_iterable)
from torch.utils.data import DataLoader
dataloader = DataLoader(dataset)
assert len(list(dataloader)) == len(list(ex_iterable))
@require_torch
def test_iterable_dataset_torch_dataloader_parallel():
from torch.utils.data import DataLoader
ex_iterable = ExamplesIterable(generate_examples_fn, {})
dataset = IterableDataset(ex_iterable)
dataloader = DataLoader(dataset, num_workers=2, batch_size=None)
result = list(dataloader)
expected = [example for _, example in ex_iterable]
assert len(result) == len(expected)
assert {str(x) for x in result} == {str(x) for x in expected}
@require_torch
@pytest.mark.filterwarnings("ignore:This DataLoader will create:UserWarning")
@pytest.mark.parametrize("num_shards, num_workers", [(2, 1), (2, 2), (3, 2), (2, 3)])
def test_sharded_iterable_dataset_torch_dataloader_parallel(num_shards, num_workers):
from torch.utils.data import DataLoader
ex_iterable = ExamplesIterable(generate_examples_fn, {"filepaths": [f"{i}.txt" for i in range(num_shards)]})
dataset = IterableDataset(ex_iterable)
dataloader = DataLoader(dataset, batch_size=None, num_workers=num_workers)
result = list(dataloader)
expected = [example for _, example in ex_iterable]
assert len(result) == len(expected)
assert {str(x) for x in result} == {str(x) for x in expected}
@require_torch
@pytest.mark.integration
@pytest.mark.parametrize("num_workers", [1, 2])
def test_iterable_dataset_from_hub_torch_dataloader_parallel(num_workers, tmp_path):
from torch.utils.data import DataLoader
dataset = load_dataset(SAMPLE_DATASET_IDENTIFIER, cache_dir=str(tmp_path), streaming=True, split="train")
dataloader = DataLoader(dataset, batch_size=None, num_workers=num_workers)
result = list(dataloader)
assert len(result) == 10
def gen_with_worker_info(shard):
from torch.utils.data import get_worker_info
worker_info = get_worker_info()
for i in range(100):
yield {"value": i, "worker_id": worker_info.id}
@require_torch
def test_iterable_dataset_shuffle_with_multiple_workers_different_rng():
# GH 7567
from torch.utils.data import DataLoader
num_workers = 20
ds = IterableDataset.from_generator(gen_with_worker_info, gen_kwargs={"shard": list(range(num_workers))})
ds = ds.shuffle(buffer_size=100, seed=1234)
dataloader = DataLoader(ds, batch_size=None, num_workers=num_workers)
result = list(dataloader)
for single_chunk in [result[x : x + num_workers] for x in range(0, len(result), num_workers)]:
values = [item["value"] for item in single_chunk]
# This will fail with the chance 1/100 ** 20!
assert len(set(values)) != 1, "Make sure not all values are identical"
def test_iterable_dataset_shuffle_buffer_uses_multiple_input_shards():
ds = IterableDataset.from_dict({"i": range(100)}, num_shards=10)
shuffled_ds = ds.shuffle(buffer_size=10, seed=1234)
shard_indices_of_first_ten_examples = {i // 10 for i in shuffled_ds.take(10)["i"]}
assert len(shard_indices_of_first_ten_examples) == 7
shuffled_ds = ds.shuffle(buffer_size=10, seed=1234, max_buffer_input_shards=1)
shard_indices_of_first_ten_examples = {i // 10 for i in shuffled_ds.take(10)["i"]}
assert len(shard_indices_of_first_ten_examples) == 2
shuffled_ds = ds.shuffle(buffer_size=10, seed=1234, max_buffer_input_shards=4)
shard_indices_of_first_ten_examples = {i // 10 for i in shuffled_ds.take(10)["i"]}
assert len(shard_indices_of_first_ten_examples) == 4
def gen_with_value(shard, value):
for i in range(100):
yield {"value": value}
@require_torch
def test_iterable_dataset_interleave_dataset_with_multiple_workers():
# GH 7567
from torch.utils.data import DataLoader
num_workers = 20
ds = [
IterableDataset.from_generator(gen_with_value, gen_kwargs={"shard": list(range(num_workers)), "value": i})
for i in range(10)
]
ds = interleave_datasets(ds, probabilities=[1 / len(ds)] * len(ds), seed=1234)
dataloader = DataLoader(ds, batch_size=None, num_workers=num_workers)
result = list(dataloader)
for single_chunk in [result[x : x + num_workers] for x in range(0, len(result), num_workers)]:
values = [item["value"] for item in single_chunk]
assert len(set(values)) != 1, "Make sure not all values are identical"
def gen_with_id(shard, value):
for i in range(50):
yield {"value": value, "id": i}
@require_torch
def test_iterable_dataset_interleave_dataset_deterministic_across_iterations():
# GH 7567
from torch.utils.data import DataLoader
num_workers = 10
ds = [
IterableDataset.from_generator(gen_with_id, gen_kwargs={"shard": list(range(num_workers)), "value": i})
for i in range(5)
]
ds = interleave_datasets(ds, probabilities=[1 / len(ds)] * len(ds), seed=1234)
dataloader = DataLoader(ds, batch_size=None, num_workers=num_workers)
# First iteration
first_result = list(dataloader)
# Second iteration
second_result = list(dataloader)
assert first_result == second_result, "Results should be identical across iterations when using same seed"
@pytest.mark.parametrize("batch_size", [4, 5])
@pytest.mark.parametrize("drop_last_batch", [False, True])
def test_iterable_dataset_iter_batch(batch_size, drop_last_batch):
n = 25
dataset = IterableDataset(ExamplesIterable(generate_examples_fn, {"n": n}))
all_examples = [ex for _, ex in generate_examples_fn(n=n)]
expected = []
for i in range(0, len(all_examples), batch_size):
if len(all_examples[i : i + batch_size]) < batch_size and drop_last_batch:
continue
expected.append(_examples_to_batch(all_examples[i : i + batch_size]))
assert next(iter(dataset.iter(batch_size, drop_last_batch=drop_last_batch))) == expected[0]
assert list(dataset.iter(batch_size, drop_last_batch=drop_last_batch)) == expected
def test_iterable_dataset_info():
info = DatasetInfo(description="desc", citation="@article{}", size_in_bytes=42)
ex_iterable = ExamplesIterable(generate_examples_fn, {})
dataset = IterableDataset(ex_iterable, info=info)
assert dataset.info == info
assert dataset.description == info.description
assert dataset.citation == info.citation
assert dataset.size_in_bytes == info.size_in_bytes
def test_iterable_dataset_set_epoch(dataset: IterableDataset):
assert dataset._epoch == 0
dataset.set_epoch(42)
assert dataset._epoch == 42
def test_iterable_dataset_set_epoch_resuming(dataset: IterableDataset):
dataset_length = len(list(dataset))
assert len(list(dataset)) == dataset_length > 0
dataset.load_state_dict(dataset.state_dict())
assert len(list(dataset)) == 0
dataset.set_epoch(1)
assert len(list(dataset)) == dataset_length > 0
dataset.load_state_dict(dataset.state_dict())
assert len(list(dataset)) == 0
def test_iterable_dataset_map(
dataset: IterableDataset,
):
func = lambda x: {"id+1": x["id"] + 1} # noqa: E731
mapped_dataset = dataset.map(func)
assert isinstance(mapped_dataset._ex_iterable, MappedExamplesIterable)
assert mapped_dataset._ex_iterable.function is func
assert mapped_dataset._ex_iterable.batched is False
assert next(iter(mapped_dataset)) == {**next(iter(dataset)), **func(next(iter(generate_examples_fn()))[1])}
def test_iterable_dataset_map_batched(
dataset: IterableDataset,
):
func = lambda x: {"id+1": [i + 1 for i in x["id"]]} # noqa: E731
batch_size = 3
dataset = dataset.map(func, batched=True, batch_size=batch_size)
assert isinstance(dataset._ex_iterable, MappedExamplesIterable)
assert dataset._ex_iterable.function is func
assert dataset._ex_iterable.batch_size == batch_size
assert next(iter(dataset)) == {"id": 0, "id+1": 1}
def test_iterable_dataset_map_complex_features(
dataset: IterableDataset,
):
# https://github.com/huggingface/datasets/issues/3505
ex_iterable = ExamplesIterable(generate_examples_fn, {"label": "positive"})
features = Features(
{
"id": Value("int64"),
"label": Value("string"),
}
)
dataset = IterableDataset(ex_iterable, info=DatasetInfo(features=features))
dataset = dataset.cast_column("label", ClassLabel(names=["negative", "positive"]))
dataset = dataset.map(lambda x: {"id+1": x["id"] + 1, **x})
assert isinstance(dataset._ex_iterable, MappedExamplesIterable)
features["label"] = ClassLabel(names=["negative", "positive"])
assert [{k: v for k, v in ex.items() if k != "id+1"} for ex in dataset] == [
features.encode_example(ex) for _, ex in ex_iterable
]
def test_iterable_dataset_map_with_features(dataset: IterableDataset) -> None:
# https://github.com/huggingface/datasets/issues/3888
ex_iterable = ExamplesIterable(generate_examples_fn, {"label": "positive"})
features_before_map = Features(
{
"id": Value("int64"),
"label": Value("string"),
}
)
dataset = IterableDataset(ex_iterable, info=DatasetInfo(features=features_before_map))
assert dataset.info.features is not None
assert dataset.info.features == features_before_map
features_after_map = Features(
{
"id": Value("int64"),
"label": Value("string"),
"target": Value("string"),
}
)
dataset = dataset.map(lambda x: {"target": x["label"]}, features=features_after_map)
assert dataset.info.features is not None
assert dataset.info.features == features_after_map
def test_iterable_dataset_map_with_fn_kwargs(dataset: IterableDataset) -> None:
fn_kwargs = {"y": 1}
mapped_dataset = dataset.map(lambda x, y: {"id+y": x["id"] + y}, fn_kwargs=fn_kwargs)
assert mapped_dataset._ex_iterable.batched is False
assert next(iter(mapped_dataset)) == {"id": 0, "id+y": 1}
batch_size = 3
mapped_dataset = dataset.map(
lambda x, y: {"id+y": [i + y for i in x["id"]]}, batched=True, batch_size=batch_size, fn_kwargs=fn_kwargs
)
assert isinstance(mapped_dataset._ex_iterable, MappedExamplesIterable)
assert mapped_dataset._ex_iterable.batch_size == batch_size
assert next(iter(mapped_dataset)) == {"id": 0, "id+y": 1}
def test_iterable_dataset_filter(dataset: IterableDataset) -> None:
fn_kwargs = {"y": 1}
filtered_dataset = dataset.filter(lambda x, y: x["id"] == y, fn_kwargs=fn_kwargs)
assert filtered_dataset._ex_iterable.batched is False
assert next(iter(filtered_dataset)) == {"id": 1}
def test_iterable_dataset_filter_chaining_does_not_raise() -> None:
"""Chaining two .filter() calls must not raise TypeError.
After the first .filter() the internal ex_iterable becomes typed
(is_typed=True) because FilteredExamplesIterable adds a mask column.
The second .filter() then wraps it in FormattedExamplesIterable.
Previously, features=None was passed when is_typed=True, causing
FilteredExamplesIterable.__init__ to crash with:
TypeError: 'NoneType' object is not a mapping
(issue #8037)
"""
from datasets import IterableDataset
from datasets.features import Features, Value
features = Features({"id": Value("int32"), "text": Value("string")})
def gen():
for i in range(5):
yield {"id": i, "text": f"item-{i}"}
ds = IterableDataset.from_generator(gen, features=features)
ds = ds.filter(lambda x: x["id"] >= 1)
# Second filter must not raise TypeError
ds = ds.filter(lambda x: x["id"] <= 3)
result = list(ds)
assert [row["id"] for row in result] == [1, 2, 3]
@pytest.mark.parametrize("seed", [42, 1337, 101010, 123456])
@pytest.mark.parametrize("epoch", [None, 0, 1])
def test_iterable_dataset_shuffle(dataset: IterableDataset, seed, epoch):
buffer_size = 3
dataset = deepcopy(dataset)
dataset._ex_iterable.kwargs["filepaths"] = ["0.txt", "1.txt"]
dataset = dataset.shuffle(seed, buffer_size=buffer_size, max_buffer_input_shards=1)
# Effective seed is mix of seed and epoch
if epoch is None or epoch == 0:
effective_seed = seed
else:
dataset.set_epoch(epoch)
effective_seed = np.random.default_rng(seed).integers(0, 1 << 63) - epoch
# Shuffling adds a shuffle buffer
expected_first_example_index = next(
iter(BufferShuffledExamplesIterable._iter_random_indices(np.random.default_rng(effective_seed), buffer_size))
)
assert isinstance(dataset._ex_iterable, BufferShuffledExamplesIterable)
# It also shuffles the underlying examples iterable
expected_ex_iterable = ExamplesIterable(
generate_examples_fn, {"filepaths": ["0.txt", "1.txt"]}
).shuffle_data_sources(np.random.default_rng(seed))
if epoch:
expected_ex_iterable = expected_ex_iterable.shuffle_data_sources(np.random.default_rng(epoch))
assert isinstance(dataset._ex_iterable.ex_iterable, ExamplesIterable)
assert next(iter(dataset)) == list(islice(expected_ex_iterable, expected_first_example_index + 1))[-1][1]
@pytest.mark.parametrize(
"features",
[
None,
Features(
{
"id": Value("int64"),
"label": Value("int64"),
}
),
Features(
{
"id": Value("int64"),
"label": ClassLabel(names=["negative", "positive"]),
}
),
],
)
def test_iterable_dataset_features(features):
ex_iterable = ExamplesIterable(generate_examples_fn, {"label": 0})
dataset = IterableDataset(ex_iterable, info=DatasetInfo(features=features))
if features:
expected = [features.encode_example(x) for _, x in ex_iterable]
else:
expected = [x for _, x in ex_iterable]
assert list(dataset) == expected
def test_iterable_dataset_features_cast_to_python():
ex_iterable = ExamplesIterable(
generate_examples_fn, {"timestamp": pd.Timestamp(2020, 1, 1), "array": np.ones(5), "n": 1}
)
features = Features(
{
"id": Value("int64"),
"timestamp": Value("timestamp[us]"),
"array": List(Value("int64")),
}
)
dataset = IterableDataset(ex_iterable, info=DatasetInfo(features=features))
assert list(dataset) == [{"timestamp": pd.Timestamp(2020, 1, 1).to_pydatetime(), "array": [1] * 5, "id": 0}]
@require_torch
@require_tf
@require_jax
@pytest.mark.parametrize(
"format_type", [None, "torch", "python", "tf", "tensorflow", "np", "numpy", "jax", "arrow", "pd", "pandas"]
)
def test_iterable_dataset_with_format(dataset: IterableDataset, format_type):
formatted_dataset = dataset.with_format(format_type)
assert formatted_dataset._formatting.format_type == get_format_type_from_alias(format_type)
@require_torch
def test_iterable_dataset_is_torch_iterable_dataset(dataset: IterableDataset):
from torch.utils.data import DataLoader, _DatasetKind
dataloader = DataLoader(dataset)
assert dataloader._dataset_kind == _DatasetKind.Iterable
out = list(dataloader)
assert len(out) == DEFAULT_N_EXAMPLES
@require_torch
def test_iterable_dataset_persists_epoch_in_torch_workers(dataset: IterableDataset):
from torch.utils.data import DataLoader
dataset = dataset.shuffle(seed=42)
dataloader = DataLoader(dataset, num_workers=1, persistent_workers=True)
epoch0 = list(dataloader)
assert list(dataloader) == epoch0
dataset.set_epoch(1)
assert list(dataloader) != epoch0
# Make sure pickle works even with torch objects in shared memory
dataset_copy: IterableDataset = pickle.loads(pickle.dumps(dataset))
dataloader = DataLoader(dataset_copy, num_workers=1, persistent_workers=True)
epoch1 = list(dataloader)
assert list(dataloader) == epoch1
dataset.set_epoch(2) # this should not affect the copy
assert list(dataloader) == epoch1
dataset_copy.set_epoch(2)
assert list(dataloader) != epoch1
@pytest.mark.parametrize("n", [0, 2, int(1e10)])
def test_iterable_dataset_skip(dataset: IterableDataset, n):
skip_dataset = dataset.skip(n)
assert isinstance(skip_dataset._ex_iterable, SkipExamplesIterable)
assert skip_dataset._ex_iterable.n == n
assert list(skip_dataset) == list(dataset)[n:]
@pytest.mark.parametrize("n", [0, 2, int(1e10)])
def test_iterable_dataset_take(dataset: IterableDataset, n):
take_dataset = dataset.take(n)
assert isinstance(take_dataset._ex_iterable, TakeExamplesIterable)
assert take_dataset._ex_iterable.n == n
assert list(take_dataset) == list(dataset)[:n]
@pytest.mark.parametrize("n", [0, 2])
def test_iterable_dataset_repeat(dataset: IterableDataset, n):
repeat_dataset = dataset.repeat(n)
assert isinstance(repeat_dataset._ex_iterable, RepeatExamplesIterable)
assert repeat_dataset._ex_iterable.num_times == n
assert list(repeat_dataset) == list(dataset) * n
def test_iterable_dataset_shard():
num_examples = 20
num_shards = 5
dataset = Dataset.from_dict({"a": range(num_examples)}).to_iterable_dataset(num_shards=num_shards)
assert sum(dataset.shard(num_shards, i).num_shards for i in range(num_shards)) == dataset.num_shards
assert list(concatenate_datasets([dataset.shard(num_shards, i) for i in range(num_shards)])) == list(dataset)
num_shards = 2
assert sum(dataset.shard(num_shards, i).num_shards for i in range(num_shards)) == dataset.num_shards
assert list(concatenate_datasets([dataset.shard(num_shards, i) for i in range(num_shards)])) == list(dataset)
assert (
sum(dataset.shard(num_shards, i, contiguous=False).num_shards for i in range(num_shards)) == dataset.num_shards
)
assert list(
concatenate_datasets([dataset.shard(num_shards, i, contiguous=False) for i in range(num_shards)])
) != list(dataset)
assert sorted(
concatenate_datasets([dataset.shard(num_shards, i, contiguous=False) for i in range(num_shards)]),
key=lambda x: x["a"],
) == list(dataset)
@pytest.mark.parametrize("method", ["skip", "take"])
@pytest.mark.parametrize("after_shuffle", [False, True])
@pytest.mark.parametrize("count", [2, 5, 11])
def test_iterable_dataset_skip_or_take_after_shuffle(method, after_shuffle, count):
seed = 42
n, num_shards = 3, 10
ex_iterable = ExamplesIterable(
generate_examples_fn, {"n": n, "filepaths": [f"{i}.txt" for i in range(num_shards)]}
)
dataset = IterableDataset(ex_iterable)
shuffled_dataset = dataset
if after_shuffle:
shuffled_dataset = shuffled_dataset.shuffle(seed, buffer_size=DEFAULT_N_EXAMPLES)
shuffled_dataset = shuffled_dataset.skip(count) if method == "skip" else shuffled_dataset.take(count)
# skip/take a shuffled dataset should not keep the same examples and shuffle the shards
key = lambda x: f"{x['filepath']}_{x['id']}" # noqa: E731
assert (len(list(dataset)) - count if method == "skip" else count) == len(list(shuffled_dataset))
assert sorted(list(dataset)[count:] if method == "skip" else list(dataset)[:count], key=key) != sorted(
shuffled_dataset, key=key
)
else:
shuffled_dataset = shuffled_dataset.skip(count) if method == "skip" else shuffled_dataset.take(count)
shuffled_dataset = shuffled_dataset.shuffle(seed, buffer_size=DEFAULT_N_EXAMPLES)
# shuffling a skip/take dataset should keep the same examples and don't shuffle the shards
key = lambda x: f"{x['filepath']}_{x['id']}" # noqa: E731
assert (len(list(dataset)) - count if method == "skip" else count) == len(list(shuffled_dataset))
assert sorted(list(dataset)[count:] if method == "skip" else list(dataset)[:count], key=key) == sorted(
shuffled_dataset, key=key
)
@pytest.mark.parametrize("method", ["skip", "take"])
@pytest.mark.parametrize("after_split_by_node", [False, True])
@pytest.mark.parametrize("count", [2, 5, 11])
def test_iterable_dataset_skip_or_take_after_split_by_node(method, after_split_by_node, count):
n, num_shards = 3, 10
rank, world_size = 1, 2
ex_iterable = ExamplesIterable(
generate_examples_fn, {"n": n, "filepaths": [f"{i}.txt" for i in range(num_shards)]}
)
dataset = IterableDataset(ex_iterable)
distributed_dataset = dataset
true_distributed_dataset = split_dataset_by_node(dataset, rank=rank, world_size=world_size)
if after_split_by_node:
distributed_dataset = split_dataset_by_node(distributed_dataset, rank=rank, world_size=world_size)
distributed_dataset = distributed_dataset.skip(count) if method == "skip" else distributed_dataset.take(count)
assert (
list(true_distributed_dataset)[count:]
if method == "skip"
else list(true_distributed_dataset)[:count] == list(distributed_dataset)
)
else:
distributed_dataset = distributed_dataset.skip(count) if method == "skip" else distributed_dataset.take(count)
distributed_dataset = split_dataset_by_node(distributed_dataset, rank=rank, world_size=world_size)
assert len(
list(true_distributed_dataset)[count // world_size :]
if method == "skip"
else list(true_distributed_dataset)[: count // world_size]
) == len(list(distributed_dataset))
def test_iterable_dataset_add_column(dataset_with_several_columns: IterableDataset):
new_column = list(range(3 * DEFAULT_N_EXAMPLES))
new_dataset = dataset_with_several_columns.add_column("new_column", new_column)
assert list(new_dataset) == [
{**example, "new_column": idx} for idx, example in enumerate(dataset_with_several_columns)
]
new_dataset = new_dataset._resolve_features()
assert "new_column" in new_dataset.column_names
def test_iterable_dataset_rename_column(dataset_with_several_columns: IterableDataset):
new_dataset = dataset_with_several_columns.rename_column("id", "new_id")
assert list(new_dataset) == [
{("new_id" if k == "id" else k): v for k, v in example.items()} for example in dataset_with_several_columns
]
assert new_dataset.features is None
assert new_dataset.column_names is None
# rename the column if ds.features was not None
new_dataset = dataset_with_several_columns._resolve_features().rename_column("id", "new_id")
assert new_dataset.features is not None
assert new_dataset.column_names is not None
assert "id" not in new_dataset.column_names
assert "new_id" in new_dataset.column_names
def test_iterable_dataset_rename_columns(dataset_with_several_columns: IterableDataset):
column_mapping = {"id": "new_id", "filepath": "filename"}
new_dataset = dataset_with_several_columns.rename_columns(column_mapping)
assert list(new_dataset) == [
{column_mapping.get(k, k): v for k, v in example.items()} for example in dataset_with_several_columns
]
assert new_dataset.features is None
assert new_dataset.column_names is None
# rename the columns if ds.features was not None
new_dataset = dataset_with_several_columns._resolve_features().rename_columns(column_mapping)
assert new_dataset.features is not None
assert new_dataset.column_names is not None
assert all(c not in new_dataset.column_names for c in ["id", "filepath"])
assert all(c in new_dataset.column_names for c in ["new_id", "filename"])
def test_iterable_dataset_remove_columns(dataset_with_several_columns: IterableDataset):
new_dataset = dataset_with_several_columns.remove_columns("id")
assert list(new_dataset) == [
{k: v for k, v in example.items() if k != "id"} for example in dataset_with_several_columns
]
assert new_dataset.features is None
new_dataset = dataset_with_several_columns.remove_columns(["id", "filepath"])
assert list(new_dataset) == [
{k: v for k, v in example.items() if k != "id" and k != "filepath"} for example in dataset_with_several_columns
]
assert new_dataset.features is None
assert new_dataset.column_names is None
# remove the columns if ds.features was not None
new_dataset = dataset_with_several_columns._resolve_features().remove_columns(["id", "filepath"])
assert new_dataset.features is not None
assert new_dataset.column_names is not None
assert all(c not in new_dataset.features for c in ["id", "filepath"])
assert all(c not in new_dataset.column_names for c in ["id", "filepath"])
def test_iterable_dataset_select_columns(dataset_with_several_columns: IterableDataset):
new_dataset = dataset_with_several_columns.select_columns("id")
assert list(new_dataset) == [
{k: v for k, v in example.items() if k == "id"} for example in dataset_with_several_columns
]
assert new_dataset.features is None
new_dataset = dataset_with_several_columns.select_columns(["id", "filepath"])
assert list(new_dataset) == [
{k: v for k, v in example.items() if k in ("id", "filepath")} for example in dataset_with_several_columns
]
assert new_dataset.features is None
# select the columns if ds.features was not None
new_dataset = dataset_with_several_columns._resolve_features().select_columns(["id", "filepath"])
assert new_dataset.features is not None
assert new_dataset.column_names is not None
assert all(c in new_dataset.features for c in ["id", "filepath"])
assert all(c in new_dataset.column_names for c in ["id", "filepath"])
def test_iterable_dataset_cast_column():
ex_iterable = ExamplesIterable(generate_examples_fn, {"label": 10})
features = Features({"id": Value("int64"), "label": Value("int64")})
dataset = IterableDataset(ex_iterable, info=DatasetInfo(features=features))
casted_dataset = dataset.cast_column("label", Value("bool"))
casted_features = features.copy()
casted_features["label"] = Value("bool")
assert list(casted_dataset) == [casted_features.encode_example(ex) for _, ex in ex_iterable]
def test_iterable_dataset_cast():
ex_iterable = ExamplesIterable(generate_examples_fn, {"label": 10})
features = Features({"id": Value("int64"), "label": Value("int64")})
dataset = IterableDataset(ex_iterable, info=DatasetInfo(features=features))
new_features = Features({"id": Value("int64"), "label": Value("bool")})
casted_dataset = dataset.cast(new_features)
assert list(casted_dataset) == [new_features.encode_example(ex) for _, ex in ex_iterable]
def test_iterable_dataset_resolve_features():
ex_iterable = ExamplesIterable(generate_examples_fn, {})
dataset = IterableDataset(ex_iterable)
assert dataset.features is None
assert dataset.column_names is None
dataset = dataset._resolve_features()
assert dataset.features == Features(
{
"id": Value("int64"),
}
)
assert dataset.column_names == ["id"]
def test_iterable_dataset_resolve_features_keep_order():
def gen():
yield from zip(range(3), [{"a": 1}, {"c": 1}, {"b": 1}])
ex_iterable = ExamplesIterable(gen, {})
dataset = IterableDataset(ex_iterable)._resolve_features()
# columns appear in order of appearance in the dataset
assert list(dataset.features) == ["a", "c", "b"]
assert dataset.column_names == ["a", "c", "b"]
def test_iterable_dataset_with_features_fill_with_none():
def gen():
yield from zip(range(2), [{"a": 1}, {"b": 1}])
ex_iterable = ExamplesIterable(gen, {})
info = DatasetInfo(features=Features({"a": Value("int32"), "b": Value("int32")}))
dataset = IterableDataset(ex_iterable, info=info)
assert list(dataset) == [{"a": 1, "b": None}, {"b": 1, "a": None}]
def test_concatenate_datasets():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label": 10})
dataset1 = IterableDataset(ex_iterable1)
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label": 5})
dataset2 = IterableDataset(ex_iterable2)
concatenated_dataset = concatenate_datasets([dataset1, dataset2])
assert list(concatenated_dataset) == list(dataset1) + list(dataset2)
def test_concatenate_datasets_resolves_features():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label": 10})
dataset1 = IterableDataset(ex_iterable1)
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label": 5})
dataset2 = IterableDataset(ex_iterable2)
concatenated_dataset = concatenate_datasets([dataset1, dataset2])
assert concatenated_dataset.features is not None
assert sorted(concatenated_dataset.features) == ["id", "label"]
def test_concatenate_datasets_with_different_columns():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label": 10})
dataset1 = IterableDataset(ex_iterable1)
ex_iterable2 = ExamplesIterable(generate_examples_fn, {})
dataset2 = IterableDataset(ex_iterable2)
# missing column "label" -> it should be replaced with nulls
extended_dataset2_list = [{"label": None, **x} for x in dataset2]
concatenated_dataset = concatenate_datasets([dataset1, dataset2])
assert list(concatenated_dataset) == list(dataset1) + extended_dataset2_list
# change order
concatenated_dataset = concatenate_datasets([dataset2, dataset1])
assert list(concatenated_dataset) == extended_dataset2_list + list(dataset1)
def test_concatenate_datasets_axis_1():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label1": 10})
dataset1 = IterableDataset(ex_iterable1)
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label2": 5})
dataset2 = IterableDataset(ex_iterable2)
with pytest.raises(ValueError): # column "id" is duplicated -> raise an error
concatenate_datasets([dataset1, dataset2], axis=1)
concatenated_dataset = concatenate_datasets([dataset1, dataset2.remove_columns("id")], axis=1)
assert list(concatenated_dataset) == [{**x, **y} for x, y in zip(dataset1, dataset2)]
def test_concatenate_datasets_axis_1_resolves_features():
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label1": 10})
dataset1 = IterableDataset(ex_iterable1)
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label2": 5})
dataset2 = IterableDataset(ex_iterable2).remove_columns("id")
concatenated_dataset = concatenate_datasets([dataset1, dataset2], axis=1)
assert concatenated_dataset.features is not None
assert sorted(concatenated_dataset.features) == ["id", "label1", "label2"]
def test_concatenate_datasets_axis_1_with_different_lengths():
n1 = 10
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"label1": 10, "n": n1})
dataset1 = IterableDataset(ex_iterable1)
n2 = 5
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"label2": 5, "n": n2})
dataset2 = IterableDataset(ex_iterable2).remove_columns("id")
# missing rows -> they should be replaced with nulls
extended_dataset2_list = list(dataset2) + [{"label2": None}] * (n1 - n2)
concatenated_dataset = concatenate_datasets([dataset1, dataset2], axis=1)
assert list(concatenated_dataset) == [{**x, **y} for x, y in zip(dataset1, extended_dataset2_list)]
# change order
concatenated_dataset = concatenate_datasets([dataset2, dataset1], axis=1)
assert list(concatenated_dataset) == [{**x, **y} for x, y in zip(extended_dataset2_list, dataset1)]
@require_torch
@require_tf
@require_jax
@pytest.mark.parametrize(
"format_type", [None, "torch", "python", "tf", "tensorflow", "np", "numpy", "jax", "arrow", "pd", "pandas"]
)
def test_concatenate_datasets_with_format(dataset: IterableDataset, format_type):
formatted_dataset = dataset.with_format(format_type)
concatenated_dataset = concatenate_datasets([formatted_dataset])
assert concatenated_dataset._formatting.format_type == get_format_type_from_alias(format_type)
@pytest.mark.parametrize(
"probas, seed, expected_length, stopping_strategy",
[
(None, None, 3 * (DEFAULT_N_EXAMPLES - 1) + 1, "first_exhausted"),
([1, 0, 0], None, DEFAULT_N_EXAMPLES, "first_exhausted"),
([0, 1, 0], None, DEFAULT_N_EXAMPLES, "first_exhausted"),
([0.2, 0.5, 0.3], 42, None, "first_exhausted"),
([0.1, 0.1, 0.8], 1337, None, "first_exhausted"),
([0.5, 0.2, 0.3], 101010, None, "first_exhausted"),
(None, None, 3 * DEFAULT_N_EXAMPLES, "all_exhausted"),
([0.2, 0.5, 0.3], 42, None, "all_exhausted"),
([0.1, 0.1, 0.8], 1337, None, "all_exhausted"),
([0.5, 0.2, 0.3], 101010, None, "all_exhausted"),
],
)
def test_interleave_datasets(dataset: IterableDataset, probas, seed, expected_length, stopping_strategy):
d1 = dataset
d2 = dataset.map(lambda x: {"id+1": x["id"] + 1, **x})
d3 = dataset.with_format("python")
datasets = [d1, d2, d3]
merged_dataset = interleave_datasets(
datasets, probabilities=probas, seed=seed, stopping_strategy=stopping_strategy
)
def fill_default(example):
return {"id": None, "id+1": None, **example}
# Check the examples iterable
assert isinstance(
merged_dataset._ex_iterable, (CyclingMultiSourcesExamplesIterable, RandomlyCyclingMultiSourcesExamplesIterable)
)
# Check that it is deterministic
if seed is not None:
merged_dataset2 = interleave_datasets(
[d1, d2, d3], probabilities=probas, seed=seed, stopping_strategy=stopping_strategy
)
assert list(merged_dataset) == list(merged_dataset2)
# Check features
assert merged_dataset.features == Features({"id": Value("int64"), "id+1": Value("int64")})
# Check first example
if seed is not None:
rng = np.random.default_rng(seed)
i = next(iter(cycle(rng.choice(len(datasets), size=1000, p=probas))))
assert next(iter(merged_dataset)) == fill_default(next(iter(datasets[i])))
else:
assert any(next(iter(merged_dataset)) == fill_default(next(iter(dataset))) for dataset in datasets)
# Compute length it case it's random
if expected_length is None:
expected_length = 0
counts = np.array([len(list(d)) for d in datasets])
bool_strategy_func = np.all if stopping_strategy == "all_exhausted" else np.any
rng = np.random.default_rng(seed)
for i in cycle(rng.choice(len(datasets), size=1000, p=probas)):
counts[i] -= 1
expected_length += 1
if bool_strategy_func(counts <= 0):
break
# Check length
assert len(list(merged_dataset)) == expected_length
def test_interleave_datasets_with_features(
dataset: IterableDataset,
):
features = Features(
{
"id": Value("int64"),
"label": ClassLabel(names=["negative", "positive"]),
}
)
ex_iterable = ExamplesIterable(generate_examples_fn, {"label": 0})
dataset_with_features = IterableDataset(ex_iterable, info=DatasetInfo(features=features))
merged_dataset = interleave_datasets([dataset, dataset_with_features])
assert merged_dataset.features == features
def test_interleave_datasets_with_oversampling():
# Test hardcoded results
d1 = IterableDataset(ExamplesIterable((lambda: (yield from [(i, {"a": i}) for i in [0, 1, 2]])), {}))
d2 = IterableDataset(ExamplesIterable((lambda: (yield from [(i, {"a": i}) for i in [10, 11, 12, 13]])), {}))
d3 = IterableDataset(ExamplesIterable((lambda: (yield from [(i, {"a": i}) for i in [20, 21, 22, 23, 24]])), {}))
expected_values = [0, 10, 20, 1, 11, 21, 2, 12, 22, 0, 13, 23, 1, 10, 24]
# Check oversampling strategy without probabilities
assert [x["a"] for x in interleave_datasets([d1, d2, d3], stopping_strategy="all_exhausted")] == expected_values
# Check oversampling strategy with probabilities
expected_values = [20, 0, 21, 10, 1, 22, 23, 24, 2, 0, 1, 20, 11, 21, 2, 0, 12, 1, 22, 13]
values = [
x["a"]
for x in interleave_datasets(
[d1, d2, d3], probabilities=[0.5, 0.2, 0.3], seed=42, stopping_strategy="all_exhausted"
)
]
assert values == expected_values
@require_torch
def test_with_format_torch(dataset_with_several_columns: IterableDataset):
import torch
dset = dataset_with_several_columns.with_format(type="torch")
example = next(iter(dset))
batch = next(iter(dset.iter(batch_size=3)))
assert len(example) == 3
assert isinstance(example["id"], torch.Tensor)
assert list(example["id"].shape) == []
assert example["id"].item() == 0
assert isinstance(batch["id"], torch.Tensor)
assert isinstance(example["filepath"], list)
assert isinstance(example["filepath"][0], str)
assert example["filepath"][0] == "data0.txt"
assert isinstance(batch["filepath"], list)
assert isinstance(example["metadata"], dict)
assert isinstance(example["metadata"]["sources"], list)
assert isinstance(example["metadata"]["sources"][0], str)
assert isinstance(batch["metadata"], list)
@require_tf
def test_with_format_tf(dataset_with_several_columns: IterableDataset):
import tensorflow as tf
dset = dataset_with_several_columns.with_format(type="tensorflow")
example = next(iter(dset))
batch = next(iter(dset.iter(batch_size=3)))
assert isinstance(example["id"], tf.Tensor)
assert list(example["id"].shape) == []
assert example["id"].numpy().item() == 0
assert isinstance(batch["id"], tf.Tensor)
assert isinstance(example["filepath"], tf.Tensor)
assert example["filepath"][0] == b"data0.txt"
assert isinstance(batch["filepath"], tf.Tensor)
assert isinstance(example["metadata"], dict)
assert isinstance(example["metadata"]["sources"], tf.Tensor)
assert isinstance(batch["metadata"], list)
def test_map_array_are_not_converted_back_to_lists(dataset: IterableDataset):
def func(example):
return {"array": np.array([1, 2, 3])}
dset_test = dataset.map(func)
example = next(iter(dset_test))
# not aligned with Dataset.map because we don't convert back to lists after map()
assert isinstance(example["array"], np.ndarray)
def test_formatted_map(dataset: IterableDataset):
dataset = dataset.with_format("np")
assert isinstance(next(dataset.iter(batch_size=3))["id"], np.ndarray)
dataset = dataset.with_format(None)
assert isinstance(next(dataset.iter(batch_size=3))["id"], list)
def add_one_numpy(example):
assert isinstance(example["id"], np.ndarray)
return {"id": example["id"] + 1}
dataset = dataset.with_format("np")
dataset = dataset.map(add_one_numpy, batched=True)
assert isinstance(next(dataset.iter(batch_size=3))["id"], np.ndarray)
dataset = dataset.with_format(None)
assert isinstance(next(dataset.iter(batch_size=3))["id"], list)
def test_format_from_arrow():
python_arrow_extractor = Formatter.python_arrow_extractor
numpy_arrow_extractor = Formatter.numpy_arrow_extractor
with (
patch.object(Formatter, "python_arrow_extractor") as mock_python_arrow_extractor,
patch.object(Formatter, "numpy_arrow_extractor") as mock_numpy_arrow_extractor,
):
mock_python_arrow_extractor.side_effect = python_arrow_extractor
mock_numpy_arrow_extractor.side_effect = numpy_arrow_extractor
def g():
yield 0, pa.table({"a": range(10)})
ds = IterableDataset(ArrowExamplesIterable(g, {}))
ds = ds.with_format("np")
ds = ds.map(lambda x: x, batched=True)
next(iter(ds))
# we do arrow -> numpy -> python
mock_numpy_arrow_extractor.assert_called()
# we don't do any arrow -> python
mock_python_arrow_extractor.assert_not_called()
def test_format_arrow(dataset: IterableDataset):
ds = dataset.with_format("arrow")
assert isinstance(next(iter(ds)), pa.Table)
assert isinstance(next(iter(ds.iter(batch_size=4))), pa.Table)
assert len(next(iter(ds))) == 1
assert len(next(iter(ds.iter(batch_size=4)))) == 4
ds = ds.map(lambda t: t.append_column("new_col", pa.array([0] * len(t))))
ds = ds.map(lambda t: t.append_column("new_col_batched", pa.array([1] * len(t))), batched=True)
ds = ds.with_format(None)
assert next(iter(ds)) == {**next(iter(dataset)), "new_col": 0, "new_col_batched": 1}
def test_format_pandas(dataset: IterableDataset):
ds = dataset.with_format("pandas")
assert isinstance(next(iter(ds)), pd.DataFrame)
assert isinstance(next(iter(ds.iter(batch_size=4))), pd.DataFrame)
assert len(next(iter(ds))) == 1
assert len(next(iter(ds.iter(batch_size=4)))) == 4
ds = ds.map(lambda df: df.assign(new_col=[0] * len(df)))
ds = ds.map(lambda df: df.assign(new_col_batched=[1] * len(df)), batched=True)
ds = ds.with_format(None)
assert next(iter(ds)) == {**next(iter(dataset)), "new_col": 0, "new_col_batched": 1}
@require_polars
def test_format_polars(dataset: IterableDataset):
import polars as pl
ds = dataset.with_format("polars")
assert isinstance(next(iter(ds)), pl.DataFrame)
assert isinstance(next(iter(ds.iter(batch_size=4))), pl.DataFrame)
assert len(next(iter(ds))) == 1
assert len(next(iter(ds.iter(batch_size=4)))) == 4
ds = ds.map(lambda df: df.with_columns(pl.Series([0] * len(df)).alias("new_col")))
ds = ds.map(lambda df: df.with_columns(pl.Series([1] * len(df)).alias("new_col_batched")), batched=True)
ds = ds.with_format(None)
assert next(iter(ds)) == {**next(iter(dataset)), "new_col": 0, "new_col_batched": 1}
@pytest.mark.parametrize("num_shards1, num_shards2, num_workers", [(2, 1, 1), (2, 2, 2), (1, 3, 1), (4, 3, 3)])
def test_interleave_dataset_with_sharding(num_shards1, num_shards2, num_workers):
from torch.utils.data import DataLoader
ex_iterable1 = ExamplesIterable(generate_examples_fn, {"filepaths": [f"{i}-1.txt" for i in range(num_shards1)]})
dataset1 = IterableDataset(ex_iterable1).with_format("torch")
ex_iterable2 = ExamplesIterable(generate_examples_fn, {"filepaths": [f"{i}-2.txt" for i in range(num_shards2)]})
dataset2 = IterableDataset(ex_iterable2).with_format("torch")
dataset_merged = interleave_datasets([dataset1, dataset2], stopping_strategy="first_exhausted")
assert dataset_merged.num_shards == min(num_shards1, num_shards2)
dataloader = DataLoader(dataset_merged, batch_size=None, num_workers=num_workers)
result = list(dataloader)
expected_length = 2 * min(
len([example for _, example in ex_iterable1]), len([example for _, example in ex_iterable2])
)
# some samples may be missing because the stopping strategy is applied per process
assert expected_length - num_workers <= len(result) <= expected_length
assert len(result) == len({str(x) for x in result})
def filter_func(batch):
return batch["id"] == 4
def map_func(batch):
batch["id"] *= 2
return batch
def test_pickle_after_many_transforms(dataset_with_several_columns):
dataset = dataset_with_several_columns
dataset = dataset.remove_columns(["filepath"])
dataset = dataset.take(5)
dataset = dataset.map(map_func)
dataset = dataset.shuffle()
dataset = dataset.skip(1)
dataset = dataset.filter(filter_func)
dataset = dataset.add_column("additional_col", ["something"])
dataset = dataset.rename_column("metadata", "metadata1")
dataset = dataset.rename_columns({"id": "id1", "metadata1": "metadata2"})
dataset = dataset.select_columns(["id1", "additional_col"])
unpickled_dataset = pickle.loads(pickle.dumps(dataset))
assert list(unpickled_dataset) == list(dataset)
@require_torchdata_stateful_dataloader
def test_resume_dataloader(dataset: IterableDataset):
from torchdata.stateful_dataloader import StatefulDataLoader
dl = StatefulDataLoader(dataset)
remaining = []
for i, x in enumerate(dl):
if i == 2:
state_dict = dl.state_dict()
elif i > 2:
remaining.append(x)
dl = StatefulDataLoader(dataset)
dl.load_state_dict(state_dict)
assert remaining == list(dl)
@require_torchdata_stateful_dataloader
@pytest.mark.parametrize("num_workers", [0, 1, 2])
def test_resume_dataloader_twice(num_workers):
from torchdata.stateful_dataloader import StatefulDataLoader
ex_iterable = ExamplesIterable(generate_examples_fn, {"filepaths": [f"file{i}.txt" for i in range(4)]})
dataset = IterableDataset(ex_iterable)
def make_dataloader():
return StatefulDataLoader(dataset, batch_size=None, num_workers=num_workers)
all_examples = list(make_dataloader())
# consume 2 examples, then checkpoint #1
dl = make_dataloader()
it = iter(dl)
consumed = [next(it) for _ in range(2)]
state_1 = dl.state_dict()
# resume from #1, consume 2 more, then checkpoint #2 (taken from a resumed loader)
dl = make_dataloader()
dl.load_state_dict(state_1)
it = iter(dl)
consumed += [next(it) for _ in range(2)]
state_2 = dl.state_dict()
# resuming from #2 must continue from where it left off, not restart from the beginning
dl = make_dataloader()
dl.load_state_dict(state_2)
remainder = list(dl)
assert consumed + remainder == all_examples
@pytest.mark.parametrize("num_shards", [1, 2, 3, 7])
def test_iterable_dataset_batch(num_shards: int):
# Create a simple IterableDataset
data = {"id": list(range(10)), "text": [f"Text {i}" for i in range(10)]}
ds = IterableDataset.from_dict(data, num_shards=num_shards)
# Test with batch_size=3, drop_last_batch=False
batched_ds = ds.batch(batch_size=3, drop_last_batch=False)
batches = list(batched_ds)
assert len(batches) == 4 # 3 full batches and 1 partial batch
for i, batch in enumerate(batches[:3]): # Check full batches
assert len(batch["id"]) == 3
assert len(batch["text"]) == 3
assert batch["id"] == [3 * i, 3 * i + 1, 3 * i + 2]
assert batch["text"] == [f"Text {3 * i}", f"Text {3 * i + 1}", f"Text {3 * i + 2}"]
# Check last partial batch
assert len(batches[3]["id"]) == 1
assert len(batches[3]["text"]) == 1
assert batches[3]["id"] == [9]
assert batches[3]["text"] == ["Text 9"]
# Test with batch_size=3, drop_last_batch=True
batched_ds = ds.batch(batch_size=3, drop_last_batch=True)
batches = list(batched_ds)
assert len(batches) == 3 # Only full batches
for i, batch in enumerate(batches):
assert len(batch["id"]) == 3
assert len(batch["text"]) == 3
assert batch["id"] == [3 * i, 3 * i + 1, 3 * i + 2]
assert batch["text"] == [f"Text {3 * i}", f"Text {3 * i + 1}", f"Text {3 * i + 2}"]
# Test with batch_size=4 (doesn't evenly divide dataset size)
batched_ds = ds.batch(batch_size=4, drop_last_batch=False)
batches = list(batched_ds)
assert len(batches) == 3 # 2 full batches and 1 partial batch
for i, batch in enumerate(batches[:2]): # Check full batches
assert len(batch["id"]) == 4
assert len(batch["text"]) == 4
assert batch["id"] == [4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3]
assert batch["text"] == [f"Text {4 * i}", f"Text {4 * i + 1}", f"Text {4 * i + 2}", f"Text {4 * i + 3}"]
# Check last partial batch
assert len(batches[2]["id"]) == 2
assert len(batches[2]["text"]) == 2
assert batches[2]["id"] == [8, 9]
assert batches[2]["text"] == ["Text 8", "Text 9"]
# Test with features
batched_ds = ds._resolve_features().batch(batch_size=3)
batches = list(batched_ds)
assert batched_ds.features is not None
assert len(batches) == 4 # 3 full batches and 1 partial batch
for i, batch in enumerate(batches[:1]):
assert len(batch["id"]) == 3
assert len(batch["text"]) == 3
assert batch["id"] == [3 * i, 3 * i + 1, 3 * i + 2]
assert batch["text"] == [f"Text {3 * i}", f"Text {3 * i + 1}", f"Text {3 * i + 2}"]
def test_iterable_dataset_batch_by_column_survives_resharding():
# Re-creating the iterable (shard / shuffle / split_by_node, e.g. inside torch DataLoader
# workers) must keep accumulating whole groups instead of crashing with a missing
# tables_accumulator argument (regression test).
data = {
"id": list(range(10)),
"category": ["A"] * 5 + ["B"] * 5,
}
ds = IterableDataset.from_dict(data, num_shards=2)
batched_ds = ds.batch(by_column="category")
sharded = [batch["category"][0] for i in range(2) for batch in batched_ds.shard(num_shards=2, index=i)]
assert sorted(sharded) == ["A", "B"]
shuffled = list(batched_ds.shuffle(seed=0, buffer_size=2))
assert sorted(batch["category"][0] for batch in shuffled) == ["A", "B"]
assert all(len(set(batch["category"])) == 1 for batch in shuffled)
@pytest.mark.parametrize("num_shards", [1, 2, 3, 7, 10])
def test_iterable_dataset_batch_by_column(num_shards: int):
# Create a Dataset with a column to group by
data = {
"id": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"category": ["A", "A", "B", "B", "B", "C", "B", "B", "B", "B"],
"value": [10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
}
ds = IterableDataset.from_dict(data, num_shards=num_shards)
# Test batching by a single column
batched_ds = ds.batch(by_column="category")
batches = list(batched_ds)
# Should have 4 batches (one for each series of the same category)
assert len(batches) == 4
# Check first batch (category A)
assert batches[0]["id"] == [1, 2]
assert batches[0]["category"] == ["A", "A"]
assert batches[0]["value"] == [10, 20]
# Check second batch (category B)
assert batches[1]["id"] == [3, 4, 5]
assert batches[1]["category"] == ["B", "B", "B"]
assert batches[1]["value"] == [30, 40, 50]
# Check third batch (category C)
assert batches[2]["id"] == [6]
assert batches[2]["category"] == ["C"]
assert batches[2]["value"] == [60]
# Check fourth batch (category B again)
assert batches[3]["id"] == [7, 8, 9, 10]
assert batches[3]["category"] == ["B", "B", "B", "B"]
assert batches[3]["value"] == [70, 80, 90, 100]
# Test batching by multiple columns
data_multi = {
"id": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"category": ["A", "A", "B", "B", "B", "C", "B", "B", "B", "B"],
"subcategory": ["X", "X", "Y", "Y", "Z", "X", "Y", "Y", "Y", "Y"],
"value": [10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
}
ds_multi = IterableDataset.from_dict(data_multi, num_shards=num_shards)
# Batch by both category and subcategory
batched_ds_multi = ds_multi.batch(by_column=["category", "subcategory"])
batches_multi = list(batched_ds_multi)
# Should have 4 batches (A-X, B-Y, B-Z, C-X, B-Y again)
assert len(batches_multi) == 5
# Check first batch (category A, subcategory X)
assert batches_multi[0]["id"] == [1, 2]
assert batches_multi[0]["category"] == ["A", "A"]
assert batches_multi[0]["subcategory"] == ["X", "X"]
assert batches_multi[0]["value"] == [10, 20]
# Check second batch (category B, subcategory Y)
assert batches_multi[1]["id"] == [3, 4]
assert batches_multi[1]["category"] == ["B", "B"]
assert batches_multi[1]["subcategory"] == ["Y", "Y"]
assert batches_multi[1]["value"] == [30, 40]
# Check third batch (category B, subcategory Z)
assert batches_multi[2]["id"] == [5]
assert batches_multi[2]["category"] == ["B"]
assert batches_multi[2]["subcategory"] == ["Z"]
assert batches_multi[2]["value"] == [50]
# Check fourth batch (category C, subcategory X)
assert batches_multi[3]["id"] == [6]
assert batches_multi[3]["category"] == ["C"]
assert batches_multi[3]["subcategory"] == ["X"]
assert batches_multi[3]["value"] == [60]
# Check fifth batch (category B, subcategory Y again)
assert batches_multi[4]["id"] == [7, 8, 9, 10]
assert batches_multi[4]["category"] == ["B", "B", "B", "B"]
assert batches_multi[4]["subcategory"] == ["Y", "Y", "Y", "Y"]
assert batches_multi[4]["value"] == [70, 80, 90, 100]
# Test batching by column with batch_size parameter
# Create a dataset where one category has more elements than batch_size
data_with_large_category = {
"id": list(range(1, 11)), # 10 items
"category": ["A"] * 7 + ["B"] * 3, # 7 items in category A, 3 in category B
"value": list(range(10, 20)),
}
ds_large_category = IterableDataset.from_dict(data_with_large_category, num_shards=num_shards)
# Batch by category with a small batch_size
# The batch_size should only be used for buffering, not for limiting the final batch sizes
batched_ds_with_buffer = ds_large_category.batch(by_column="category", batch_size=3)
batches_with_buffer = list(batched_ds_with_buffer)
# Should still have 2 batches (one for each category), regardless of batch_size
assert len(batches_with_buffer) == 2
# Check first batch (category A) - should contain all 7 items despite batch_size=3
assert batches_with_buffer[0]["id"] == list(range(1, 8))
assert batches_with_buffer[0]["category"] == ["A"] * 7
assert batches_with_buffer[0]["value"] == list(range(10, 17))
# Check second batch (category B) - should contain all 3 items
assert batches_with_buffer[1]["id"] == list(range(8, 11))
assert batches_with_buffer[1]["category"] == ["B"] * 3
assert batches_with_buffer[1]["value"] == list(range(17, 20))
# Check that state_dict() / load_state_dict() works
for batch_size in [1, 2, 3, 7, 10, 20]:
assert_load_state_dict_resumes_arrow_iteration(
batched_ds._prepare_ex_iterable_for_iteration(batch_size=batch_size)
)
assert_load_state_dict_resumes_arrow_iteration(
batched_ds_multi._prepare_ex_iterable_for_iteration(batch_size=batch_size)
)
assert_load_state_dict_resumes_arrow_iteration(
batched_ds_with_buffer._prepare_ex_iterable_for_iteration(batch_size=batch_size)
)
@pytest.mark.parametrize("format_type", ["pyarrow", "pandas"])
def test_iterable_dataset_batch_with_table_format(format_type):
ds = IterableDataset.from_dict({"a": [1, 2, 3, 4]})
left = list(ds.with_format(format_type).batch(2))
right = list(ds.batch(2).with_format(format_type))
assert len(left) == len(right) == 2
assert all(type(lhs) is type(rhs) for lhs, rhs in zip(left, right))
assert [_normalize_batched_output(batch) for batch in left] == [
_normalize_batched_output(batch) for batch in right
]
@require_polars
def test_iterable_dataset_batch_with_polars_format():
ds = IterableDataset.from_dict({"a": [1, 2, 3, 4]})
left = list(ds.with_format("polars").batch(2))
right = list(ds.batch(2).with_format("polars"))
assert len(left) == len(right) == 2
assert [_normalize_batched_output(batch) for batch in left] == [
_normalize_batched_output(batch) for batch in right
]
@dataclass
class DecodableFeature:
decode_example_num_calls = 0
def __init__(self):
self.decode = True
def decode_example(self, example, token_per_repo_id=None):
type(self).decode_example_num_calls += 1
return "decoded" if self.decode else example
def __call__(self):
return pa.string()
def test_decode():
data = [{"i": str(i)} for i in range(10)]
features = Features({"i": DecodableFeature()})
ds = IterableDataset.from_generator(lambda: (x for x in data), features=features)
assert next(iter(ds)) == {"i": "decoded"}
assert DecodableFeature.decode_example_num_calls == 1
ds = ds.decode(False)
assert next(iter(ds)) == {"i": "0"}
assert DecodableFeature.decode_example_num_calls == 1
ds = ds.decode(True)
assert next(iter(ds)) == {"i": "decoded"}
assert DecodableFeature.decode_example_num_calls == 2
ds = ds.decode(num_threads=1)
assert next(iter(ds)) == {"i": "decoded"}
assert DecodableFeature.decode_example_num_calls == 4
############################
#
# IterableColumn tests
#
############################
class TestIterableColumn:
def test_simple_getitem(self):
def gen():
yield {"text": "Good", "label": 0}
yield {"text": "Bad", "label": 1}
ds = IterableDataset.from_generator(gen)
texts = ds["text"]
assert isinstance(texts, IterableColumn)
first_pass = list(texts)
assert first_pass == ["Good", "Bad"]
second_pass = list(texts)
assert second_pass == ["Good", "Bad"]
def test_chained_getitem(self):
def gen():
yield {"sample": {"text": "Good", "label": 0}}
yield {"sample": {"text": "Bad", "label": 1}}
ds = IterableDataset.from_generator(gen)
texts = ds["sample"]["text"]
assert isinstance(texts, IterableColumn)
first_pass = list(texts)
assert first_pass == ["Good", "Bad"]
second_pass = list(texts)
assert second_pass == ["Good", "Bad"]
def test_getitem_for_batched_dataset(self):
data = [
{"text": "Good", "label": 0},
{"text": "Bad", "label": 1},
{"text": "Good again", "label": 0},
{"text": "Bad again", "label": 1},
]
def gen():
yield from data
ds = IterableDataset.from_generator(gen).batch(batch_size=2)
texts = ds["text"]
assert isinstance(texts, IterableColumn)
assert list(texts) == [["Good", "Bad"], ["Good again", "Bad again"]]