This is an automated email from the ASF dual-hosted git repository.

jrmccluskey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new d50c0a8235c [RunInference] Reject use_model_manager with a multi-model 
KeyedModelHandler (#40574)
d50c0a8235c is described below

commit d50c0a8235c138f9653405e06efebf5fca0cc545
Author: Divyansh Yadav <[email protected]>
AuthorDate: Wed Oct 21 01:42:43 2026 +0530

    [RunInference] Reject use_model_manager with a multi-model 
KeyedModelHandler (#40574)
---
 sdks/python/apache_beam/ml/inference/base.py      | 33 +++++++++++++++++++++++
 sdks/python/apache_beam/ml/inference/base_test.py | 23 ++++++++++++++++
 2 files changed, 56 insertions(+)

diff --git a/sdks/python/apache_beam/ml/inference/base.py 
b/sdks/python/apache_beam/ml/inference/base.py
index ce014e4b04f..c4c3f146ed4 100644
--- a/sdks/python/apache_beam/ml/inference/base.py
+++ b/sdks/python/apache_beam/ml/inference/base.py
@@ -767,6 +767,10 @@ class KeyedModelHandler(Generic[KeyT, ExampleT, 
PredictionT, ModelT],
     at the same time. For more information about memory management, see
     `Use a keyed `ModelHandler 
<https://beam.apache.org/documentation/ml/about-ml/#use-a-keyed-modelhandler-object>_`.
  # pylint: disable=line-too-long
 
+    A KeyedModelHandler with many models defined per key cannot be used with
+    `RunInference(..., use_model_manager=True)`, since it manages its per-key
+    models itself.
+
 
     Args:
       unkeyed: Either (a) an implementation of ModelHandler that does not
@@ -1459,6 +1463,19 @@ class OOMProtectedFn:
       raise e
 
 
+def _is_multi_model_keyed_handler(model_handler: ModelHandler) -> bool:
+  """Returns whether model_handler is a KeyedModelHandler with many models,
+  looking through any batching or pre/postprocessing wrappers."""
+  while isinstance(model_handler,
+                   (_PrebatchedModelHandler,
+                    _PreProcessingModelHandler,
+                    _PostProcessingModelHandler)):
+    model_handler = model_handler._base
+  return (
+      isinstance(model_handler, KeyedModelHandler) and
+      not model_handler._single_model)
+
+
 class RunInference(beam.PTransform[beam.PCollection[Union[ExampleT,
                                                           Iterable[ExampleT]]],
                                    beam.PCollection[PredictionT]]):
@@ -1505,10 +1522,26 @@ class 
RunInference(beam.PTransform[beam.PCollection[Union[ExampleT,
           the same tag for different models will lead to non-deterministic
           results, so exercise caution when using this parameter. This only
           impacts models which are already being shared across processes.
+        use_model_manager: Whether to load and manage models with a
+          ModelManager, which tracks memory usage and evicts models as needed.
+          This is not supported with a KeyedModelHandler that maps keys to
+          multiple model handlers, since that handler manages its own per-key
+          models (see `max_models_per_worker_hint`); a ValueError is raised
+          in that case.
+        model_manager_args: Keyword arguments used to construct the
+          ModelManager when use_model_manager is True.
         monitoring_transform: A PTransform that receives a copy of the
           un-postprocessed PCollection of PredictionResult objects produced
           directly by inference.
     """
+    if use_model_manager and _is_multi_model_keyed_handler(model_handler):
+      raise ValueError(
+          'use_model_manager=True is not supported with a KeyedModelHandler '
+          'that maps keys to multiple model handlers. That handler loads and '
+          'caches its per-key models itself, so they would not be tracked by '
+          'the ModelManager. Use max_models_per_worker_hint on the '
+          'KeyedModelHandler to limit how many models are loaded at once, or '
+          'set use_model_manager=False.')
     self._model_handler = model_handler
     self._inference_args = inference_args
     self._clock = clock
diff --git a/sdks/python/apache_beam/ml/inference/base_test.py 
b/sdks/python/apache_beam/ml/inference/base_test.py
index 64f5c3b1525..5890dd087d9 100644
--- a/sdks/python/apache_beam/ml/inference/base_test.py
+++ b/sdks/python/apache_beam/ml/inference/base_test.py
@@ -791,6 +791,29 @@ class RunInferenceBaseTest(unittest.TestCase):
     self.assertFalse(mh.should_skip_batching())
     self.assertTrue(any('skips batching' in line for line in logs.output))
 
+  def test_keyed_many_model_handlers_with_model_manager_raises(self):
+    mhs = [
+        base.KeyModelMapping([0],
+                             FakeModelHandler(
+                                 state=200, multi_process_shared=True)),
+        base.KeyModelMapping([1, 2, 3],
+                             FakeModelHandler(multi_process_shared=True))
+    ]
+    keyed_mh = base.KeyedModelHandler(mhs)
+    with self.assertRaisesRegex(ValueError, r'use_model_manager=True'):
+      base.RunInference(keyed_mh, use_model_manager=True)
+
+    # Wrapped handlers should be rejected as well.
+    with self.assertRaisesRegex(ValueError, r'use_model_manager=True'):
+      base.RunInference(
+          keyed_mh.with_preprocess_fn(lambda x: x).with_no_batching(),
+          use_model_manager=True)
+
+    # Without the model manager, or with a single keyed model, it is allowed.
+    base.RunInference(keyed_mh)
+    base.RunInference(
+        base.KeyedModelHandler(FakeModelHandler()), use_model_manager=True)
+
   def test_keyed_model_handler_get_num_bytes(self):
     mh = base.KeyedModelHandler(FakeModelHandler(num_bytes_per_element=10))
     batch = [('key1', 1), ('key2', 2), ('key1', 3)]

Reply via email to