import sys from unittest.mock import patch import numpy as np import pandas as pd import pytest import ray from ray.data.tests.conftest import * # noqa from ray.tests.conftest import * # noqa pytest.importorskip("jax") def test_iter_jax_batches(ray_start_regular_shared): df1 = pd.DataFrame( {"one": [1, 2, 3], "two": [1.0, 2.0, 3.0], "label": [1.0, 2.0, 3.0]} ) df2 = pd.DataFrame( {"one": [4, 5, 6], "two": [4.0, 5.0, 6.0], "label": [4.0, 5.0, 6.0]} ) df3 = pd.DataFrame({"one": [7, 8], "two": [7.0, 8.0], "label": [7.0, 8.0]}) df = pd.concat([df1, df2, df3]) ds = ray.data.from_pandas([df1, df2, df3]) iterations = [] for batch in ds.iter_jax_batches(batch_size=3, paddings=-1): iterations.append( np.stack((batch["one"], batch["two"], batch["label"]), axis=1) ) combined_iterations = np.concatenate(iterations) # 8 rows total, batch_size 3, padding=-1 -> 3 batches of size 3 (9 rows total) assert len(combined_iterations) == 9 expected_values = np.concatenate([df.values, [[-1.0, -1.0, -1.0]]]) np.testing.assert_array_equal( np.sort(combined_iterations, axis=0), np.sort(expected_values, axis=0), ) def test_iter_jax_batches_with_collate_fn(ray_start_regular_shared): from ray.data.collate_fn import NumpyBatchCollateFn class CustomCollateFn(NumpyBatchCollateFn): def __call__(self, batch): # Combine "one" and "two" columns into a single "features" tensor return np.stack((batch["one"], batch["two"]), axis=1) ds = ray.data.from_items([{"one": i, "two": i + 1} for i in range(10)]) iterations = [] for batch in ds.iter_jax_batches(batch_size=2, collate_fn=CustomCollateFn()): # The output of collate_fn is now a single numpy array (sharded as jax.Array) iterations.append(batch) combined_iterations = np.concatenate(iterations) # Expected shape: (10, 2) expected = np.stack((np.arange(10), np.arange(10) + 1), axis=1) np.testing.assert_array_equal( np.sort(expected, axis=0), np.sort(combined_iterations, axis=0), ) def test_iter_jax_batches_with_dtypes(ray_start_regular_shared): import jax.numpy as jnp ds = ray.data.from_items([{"one": i, "two": i + 0.5} for i in range(10)]) # Test single dtype for batch in ds.iter_jax_batches(batch_size=2, dtypes=jnp.float32): assert batch["one"].dtype == jnp.float32 assert batch["two"].dtype == jnp.float32 # Test dict of dtypes dtypes = {"one": jnp.int32, "two": jnp.float16} for batch in ds.iter_jax_batches(batch_size=2, dtypes=dtypes): assert batch["one"].dtype == jnp.int32 assert batch["two"].dtype == jnp.float16 # Test padding with dtypes # ds has 10 rows, batch_size=4 -> 3 batches (4, 4, 2) # Without drop_last, 3rd batch is padded to 4. for batch in ds.iter_jax_batches( batch_size=4, dtypes=jnp.float16, paddings=-1, drop_last=False ): assert batch["one"].dtype == jnp.float16 assert batch["two"].dtype == jnp.float16 def test_iter_jax_batches_with_dict_padding(ray_start_regular_shared): ds = ray.data.from_items([{"one": i, "two": i + 0.5} for i in range(10)]) # ds has 10 rows, batch_size=4 -> 3 batches (4, 4, 2) # Total 12 rows after padding. paddings = {"one": -1, "two": -0.5} batches = list(ds.iter_jax_batches(batch_size=4, paddings=paddings)) assert len(batches) == 3 combined_one = np.concatenate([batch["one"] for batch in batches]) combined_two = np.concatenate([batch["two"] for batch in batches]) assert len(combined_one) == 12 assert len(combined_two) == 12 expected_one = np.concatenate([np.arange(10), [-1, -1]]) expected_two = np.concatenate([np.arange(10) + 0.5, [-0.5, -0.5]]) np.testing.assert_array_equal(np.sort(combined_one), np.sort(expected_one)) np.testing.assert_array_equal(np.sort(combined_two), np.sort(expected_two)) def test_iter_jax_batches_batch_size_divisibility_fail(ray_start_regular_shared): with patch("jax.local_device_count", return_value=2): ds = ray.data.range(10) # batch_size must be divisible by num_local_devices=2 with pytest.raises( ValueError, match="evenly divisible by the number of local JAX devices", ): list(ds.iter_jax_batches(batch_size=3)) if __name__ == "__main__": sys.exit(pytest.main(["-v", __file__]))