Skip to content

Commit e3b4d8c

Browse files
authored
Fix flaky ML RunInference tests by disabling reshuffle on beam.Create (#39118)
1 parent 5a2e98b commit e3b4d8c

6 files changed

Lines changed: 64 additions & 11 deletions

File tree

sdks/python/apache_beam/ml/inference/base_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,7 @@ def test_timing_metrics(self):
10471047
def test_forwards_batch_args(self):
10481048
examples = list(range(100))
10491049
with TestPipeline('FnApiRunner') as pipeline:
1050-
pcoll = pipeline | 'start' >> beam.Create(examples)
1050+
pcoll = pipeline | 'start' >> beam.Create(examples, reshuffle=False)
10511051
actual = pcoll | base.RunInference(FakeModelHandlerNeedsBigBatch())
10521052
assert_that(actual, equal_to(examples), label='assert:inferences')
10531053

sdks/python/apache_beam/ml/inference/pytorch_inference_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,8 @@ def batch_validator_keyed_tensor_inference_fn(
635635
min_batch_size=2,
636636
max_batch_size=2)
637637

638-
pcoll = pipeline | 'start' >> beam.Create(KEYED_TORCH_EXAMPLES)
638+
pcoll = pipeline | 'start' >> beam.Create(
639+
KEYED_TORCH_EXAMPLES, reshuffle=False)
639640
inference_args_side_input = (
640641
pipeline | 'create side' >> beam.Create(inference_args))
641642
predictions = pcoll | RunInference(
@@ -709,7 +710,7 @@ def batch_validator_tensor_inference_fn(
709710
min_batch_size=2,
710711
max_batch_size=2)
711712

712-
pcoll = pipeline | 'start' >> beam.Create(examples)
713+
pcoll = pipeline | 'start' >> beam.Create(examples, reshuffle=False)
713714
predictions = pcoll | RunInference(model_handler)
714715
assert_that(
715716
predictions,

sdks/python/apache_beam/ml/inference/sklearn_inference_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ def batch_validator_numpy_inference_fn(
299299
with TestPipeline() as pipeline:
300300
examples = [numpy.array([0, 0]), numpy.array([1, 1])]
301301

302-
pcoll = pipeline | 'start' >> beam.Create(examples)
302+
pcoll = pipeline | 'start' >> beam.Create(examples, reshuffle=False)
303303
actual = pcoll | RunInference(
304304
SklearnModelHandlerNumpy(
305305
model_uri=temp_file_name,
@@ -457,7 +457,7 @@ def batch_validator_pandas_inference_fn(
457457
with TestPipeline() as pipeline:
458458
dataframe = pandas_dataframe()
459459
splits = [dataframe.loc[[i]] for i in dataframe.index]
460-
pcoll = pipeline | 'start' >> beam.Create(splits)
460+
pcoll = pipeline | 'start' >> beam.Create(splits, reshuffle=False)
461461
actual = pcoll | RunInference(
462462
SklearnModelHandlerPandas(
463463
model_uri=temp_file_name,

sdks/python/apache_beam/ml/inference/tensorflow_inference_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def fake_batching_inference_fn(
165165
examples, [tf.math.multiply(n, 2) for n in examples])
166166
]
167167

168-
pcoll = pipeline | 'start' >> beam.Create(examples)
168+
pcoll = pipeline | 'start' >> beam.Create(examples, reshuffle=False)
169169
predictions = pcoll | RunInference(model_handler)
170170
assert_that(
171171
predictions,
@@ -258,7 +258,7 @@ def fake_batching_inference_fn(
258258
examples, [numpy.multiply(n, 2) for n in examples])
259259
]
260260

261-
pcoll = pipeline | 'start' >> beam.Create(examples)
261+
pcoll = pipeline | 'start' >> beam.Create(examples, reshuffle=False)
262262
predictions = pcoll | RunInference(model_handler)
263263
assert_that(
264264
predictions,

sdks/python/apache_beam/ml/inference/tensorrt_inference_test.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,8 @@ def test_pipeline_single_tensor_feature_built_engine(self):
362362
max_batch_size=4,
363363
engine_path=
364364
'gs://apache-beam-ml/models/single_tensor_features_engine.trt')
365-
pcoll = pipeline | 'start' >> beam.Create(SINGLE_FEATURE_EXAMPLES)
365+
pcoll = pipeline | 'start' >> beam.Create(
366+
SINGLE_FEATURE_EXAMPLES, reshuffle=False)
366367
predictions = pcoll | RunInference(engine_handler)
367368
assert_that(
368369
predictions,
@@ -423,7 +424,8 @@ def fake_inference_fn(batch, engine, inference_args=None):
423424
'gs://apache-beam-ml/models/single_tensor_features_engine.trt',
424425
inference_fn=fake_inference_fn,
425426
large_model=True)
426-
pcoll = pipeline | 'start' >> beam.Create(SINGLE_FEATURE_EXAMPLES)
427+
pcoll = pipeline | 'start' >> beam.Create(
428+
SINGLE_FEATURE_EXAMPLES, reshuffle=False)
427429
predictions = pcoll | RunInference(engine_handler)
428430
assert_that(
429431
predictions,
@@ -443,7 +445,7 @@ def test_pipeline_sets_env_vars_correctly(self):
443445
self.assertFalse('FOO' in os.environ)
444446
_ = (
445447
pipeline
446-
| 'start' >> beam.Create(SINGLE_FEATURE_EXAMPLES)
448+
| 'start' >> beam.Create(SINGLE_FEATURE_EXAMPLES, reshuffle=False)
447449
| RunInference(engine_handler))
448450
pipeline.run()
449451
self.assertTrue('FOO' in os.environ)
@@ -457,7 +459,8 @@ def test_pipeline_multiple_tensor_feature_built_engine(self):
457459
max_batch_size=4,
458460
engine_path=
459461
'gs://apache-beam-ml/models/multiple_tensor_features_engine.trt')
460-
pcoll = pipeline | 'start' >> beam.Create(TWO_FEATURES_EXAMPLES)
462+
pcoll = pipeline | 'start' >> beam.Create(
463+
TWO_FEATURES_EXAMPLES, reshuffle=False)
461464
predictions = pcoll | RunInference(engine_handler)
462465
assert_that(
463466
predictions,

sdks/python/apache_beam/transforms/util_test.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
from apache_beam.transforms.util import GcpHsmGeneratedSecret
7676
from apache_beam.transforms.util import GcpSecret
7777
from apache_beam.transforms.util import Secret
78+
from apache_beam.transforms.util import _BatchSizeEstimator
79+
from apache_beam.transforms.util import _GlobalWindowsBatchingDoFn
7880
from apache_beam.transforms.window import FixedWindows
7981
from apache_beam.transforms.window import GlobalWindow
8082
from apache_beam.transforms.window import GlobalWindows
@@ -1258,6 +1260,53 @@ def check_batch_homogeneity(batch):
12581260
checks = batches | beam.Map(check_batch_homogeneity)
12591261
assert_that(checks, is_not_empty())
12601262

1263+
def test_global_batching_dofn_single_vs_multiple_bundles(self):
1264+
# This test directly verifies how bundling affects the batch sizes produced by
1265+
# the internal _GlobalWindowsBatchingDoFn of BatchElements.
1266+
1267+
# 1. Single Bundle Scenario:
1268+
# Four elements processed within the same start_bundle / finish_bundle lifecycle.
1269+
# min_batch_size = 2, max_batch_size = 2.
1270+
estimator = _BatchSizeEstimator(min_batch_size=2, max_batch_size=2)
1271+
dofn = _GlobalWindowsBatchingDoFn(estimator, element_size_fn=lambda x: 1)
1272+
1273+
dofn.start_bundle()
1274+
outputs = []
1275+
for elem in [1, 2, 3, 4]:
1276+
outputs.extend(dofn.process(elem))
1277+
outputs.extend(dofn.finish_bundle() or [])
1278+
1279+
# We should get exactly two batches of size 2.
1280+
batch_sizes = [len(wv.value) for wv in outputs]
1281+
self.assertEqual(batch_sizes, [2, 2])
1282+
1283+
# 2. Multiple Bundles Scenario (simulating elements split due to Reshuffle/GroupByKey):
1284+
# The runner splits elements into multiple bundles:
1285+
# Bundle 1 gets elements 1, 2, 3.
1286+
# Bundle 2 gets element 4.
1287+
estimator = _BatchSizeEstimator(min_batch_size=2, max_batch_size=2)
1288+
dofn = _GlobalWindowsBatchingDoFn(estimator, element_size_fn=lambda x: 1)
1289+
1290+
outputs = []
1291+
# Bundle 1
1292+
dofn.start_bundle()
1293+
for elem in [1, 2, 3]:
1294+
outputs.extend(dofn.process(elem))
1295+
outputs.extend(dofn.finish_bundle() or [])
1296+
1297+
# Bundle 2
1298+
dofn.start_bundle()
1299+
for elem in [4]:
1300+
outputs.extend(dofn.process(elem))
1301+
outputs.extend(dofn.finish_bundle() or [])
1302+
1303+
# The batch sizes will be [2, 1, 1] instead of [2, 2] because of bundle flushes.
1304+
# Specifically:
1305+
# - Bundle 1 emits a batch of 2, and then the remaining 1 element is flushed at finish_bundle (batch size 1).
1306+
# - Bundle 2 emits its 1 element at finish_bundle (batch size 1).
1307+
batch_sizes = [len(wv.value) for wv in outputs]
1308+
self.assertEqual(batch_sizes, [2, 1, 1])
1309+
12611310

12621311
class SortAndBatchElementsTest(unittest.TestCase):
12631312
"""Tests for SortAndBatchElements transform."""

0 commit comments

Comments
 (0)