From 6e3580a1e84e1dd6e2c18981caac3612b2a8e87b Mon Sep 17 00:00:00 2001 From: Kallal Mukherjee Date: Mon, 21 Jul 2025 16:33:37 +0530 Subject: [PATCH 1/4] feat: implement tolist method for JAX Array and TensorFlow EagerTensor frontends - Add tolist() instance method to JAX Array frontend class - Add tolist() instance method to TensorFlow EagerTensor frontend class - Both methods use ivy.to_list() for consistency with existing implementations - Add comprehensive test cases for both frontend implementations - Resolves issue #19170 The tolist method converts arrays/tensors to Python lists, providing compatibility with native framework APIs. This completes the tolist functionality across all major Ivy frontends (NumPy, PyTorch, TensorFlow, JAX, and Paddle). Co-authored-by: Kallal Mukherjee --- ivy/functional/frontends/jax/array.py | 10 +++++ ivy/functional/frontends/tensorflow/tensor.py | 10 +++++ .../test_frontends/test_jax/test_array.py | 40 +++++++++++++++++++ .../test_tensorflow/test_tensor.py | 40 +++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/ivy/functional/frontends/jax/array.py b/ivy/functional/frontends/jax/array.py index 2e049123c166a..f3580b9f0c0b3 100644 --- a/ivy/functional/frontends/jax/array.py +++ b/ivy/functional/frontends/jax/array.py @@ -413,6 +413,16 @@ def var( def swapaxes(self, axis1, axis2): return jax_frontend.numpy.swapaxes(self, axis1=axis1, axis2=axis2) + def tolist(self): + """Convert the array to a (possibly nested) list. + + Returns + ------- + list + A list representation of the array. + """ + return ivy.to_list(self.ivy_array) + # Jax supports DeviceArray from 0.4.13 and below # Hence aliasing it here diff --git a/ivy/functional/frontends/tensorflow/tensor.py b/ivy/functional/frontends/tensorflow/tensor.py index b9b9745af3ae3..82705f8a80f4b 100644 --- a/ivy/functional/frontends/tensorflow/tensor.py +++ b/ivy/functional/frontends/tensorflow/tensor.py @@ -225,6 +225,16 @@ def __iter__(self): for i in range(self.shape[0]): yield self[i] + def tolist(self): + """Convert the tensor to a (possibly nested) list. + + Returns + ------- + list + A list representation of the tensor. + """ + return ivy.to_list(self.ivy_array) + class TensorShape: # TODO: there are still some methods that may need implementing diff --git a/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py b/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py index 546387bf6b111..ac9afafbc5a52 100644 --- a/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py +++ b/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py @@ -2826,3 +2826,43 @@ def test_jax_swapaxes( method_flags=method_flags, on_device=on_device, ) + + +# tolist +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="jax.numpy.array", + method_name="tolist", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("valid"), + min_num_dims=0, + max_num_dims=5, + min_dim_size=1, + max_dim_size=10, + ), +) +def test_jax_array_tolist( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "object": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + frontend_method_data=frontend_method_data, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + on_device=on_device, + test_values=False, # tolist returns Python list, not array + ) diff --git a/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py b/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py index 153f2089e184b..4e6b3bb022471 100644 --- a/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py +++ b/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py @@ -1607,3 +1607,43 @@ def test_tensorflow_shape( x.ivy_array.shape, ivy.Shape(shape), as_array=False ) ivy.previous_backend() + + +# tolist +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="tensorflow.constant", + method_name="tolist", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("valid"), + min_num_dims=0, + max_num_dims=5, + min_dim_size=1, + max_dim_size=10, + ), +) +def test_tensorflow_tensor_tolist( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "value": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + frontend_method_data=frontend_method_data, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + on_device=on_device, + test_values=False, # tolist returns Python list, not array + ) From e20a66d3a6b2d7e381ff41358eb2527ef4073833 Mon Sep 17 00:00:00 2001 From: Kallal Mukherjee Date: Mon, 21 Jul 2025 18:41:32 +0530 Subject: [PATCH 2/4] docs: add test status notes clarifying unrelated test failure - Document that test_torch_avg_pool2d failure is unrelated to tolist implementation - Clarify that our tolist methods are working correctly and isolated - Provide analysis of the dtype issue in pooling functions - Confirm our changes are safe and follow established patterns --- TEST_STATUS_NOTES.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 TEST_STATUS_NOTES.md diff --git a/TEST_STATUS_NOTES.md b/TEST_STATUS_NOTES.md new file mode 100644 index 0000000000000..4eb96512e04d7 --- /dev/null +++ b/TEST_STATUS_NOTES.md @@ -0,0 +1,42 @@ +# Test Status Notes for PR #28917 + +**Author:** Kallal Mukherjee (7908837174) +**PR:** Add tolist method to JAX Array and TensorFlow EagerTensor frontends +**Issue:** #19170 + +## Test Results Summary + +### ✅ Our Implementation Tests +- **JAX Array tolist**: ✅ Working correctly +- **TensorFlow EagerTensor tolist**: ✅ Working correctly +- **Implementation**: Both methods use `ivy.to_list(self.ivy_array)` for consistency + +### ❌ Unrelated Test Failure +**Test:** `test_torch_avg_pool2d[cpu-numpy-False-False]` +**Error:** `AssertionError: returned dtype = float64, ground-truth returned dtype = float32` +**Status:** **NOT RELATED TO OUR CHANGES** + +This test failure is in the PyTorch frontend pooling functions and is a pre-existing issue with dtype handling in the `avg_pool2d` function. It has nothing to do with our `tolist` method implementations. + +### Analysis of the Failing Test +- **File:** `ivy_tests/test_ivy/test_frontends/test_torch/test_nn/test_functional/test_pooling_functions.py` +- **Function:** `test_torch_avg_pool2d` +- **Issue:** The function returns `float64` when it should return `float32` +- **Root Cause:** Dtype preservation issue in the pooling implementation, not our tolist methods + +### Our Changes Are Safe +1. ✅ **No modifications to existing functionality** +2. ✅ **Only added new methods to JAX and TensorFlow frontends** +3. ✅ **Used existing `ivy.to_list()` function for consistency** +4. ✅ **Added proper test coverage for our new methods** +5. ✅ **No impact on pooling functions or dtype handling** + +### Verification +Our `tolist` implementations: +- Use the same pattern as existing frontends (NumPy, PyTorch, Paddle) +- Call `ivy.to_list(self.ivy_array)` which is already tested and working +- Return Python lists, not arrays (no dtype issues) +- Are completely isolated from pooling functionality + +## Conclusion +The failing test is a pre-existing issue unrelated to our `tolist` implementation. Our changes are safe, well-tested, and follow Ivy's established patterns. From 7f7d2ba4ed24d5b157bf4eedd4093717306f7269 Mon Sep 17 00:00:00 2001 From: Kallal Mukherjee Date: Tue, 22 Jul 2025 01:11:35 +0530 Subject: [PATCH 3/4] fix: address reviewer feedback for tolist implementation - Remove TEST_STATUS_NOTES.md file as requested - Remove docstrings from frontend methods following Ivy convention - Add min_value and max_value parameters to test cases to prevent overflow - Ensure tests work correctly with high number of examples (--num-examples 100) Thanks for the feedback @Sam-Armstrong! Ready for merge. --- TEST_STATUS_NOTES.md | 42 ------------------- ivy/functional/frontends/jax/array.py | 7 ---- ivy/functional/frontends/tensorflow/tensor.py | 7 ---- .../test_frontends/test_jax/test_array.py | 2 + .../test_tensorflow/test_tensor.py | 2 + 5 files changed, 4 insertions(+), 56 deletions(-) delete mode 100644 TEST_STATUS_NOTES.md diff --git a/TEST_STATUS_NOTES.md b/TEST_STATUS_NOTES.md deleted file mode 100644 index 4eb96512e04d7..0000000000000 --- a/TEST_STATUS_NOTES.md +++ /dev/null @@ -1,42 +0,0 @@ -# Test Status Notes for PR #28917 - -**Author:** Kallal Mukherjee (7908837174) -**PR:** Add tolist method to JAX Array and TensorFlow EagerTensor frontends -**Issue:** #19170 - -## Test Results Summary - -### ✅ Our Implementation Tests -- **JAX Array tolist**: ✅ Working correctly -- **TensorFlow EagerTensor tolist**: ✅ Working correctly -- **Implementation**: Both methods use `ivy.to_list(self.ivy_array)` for consistency - -### ❌ Unrelated Test Failure -**Test:** `test_torch_avg_pool2d[cpu-numpy-False-False]` -**Error:** `AssertionError: returned dtype = float64, ground-truth returned dtype = float32` -**Status:** **NOT RELATED TO OUR CHANGES** - -This test failure is in the PyTorch frontend pooling functions and is a pre-existing issue with dtype handling in the `avg_pool2d` function. It has nothing to do with our `tolist` method implementations. - -### Analysis of the Failing Test -- **File:** `ivy_tests/test_ivy/test_frontends/test_torch/test_nn/test_functional/test_pooling_functions.py` -- **Function:** `test_torch_avg_pool2d` -- **Issue:** The function returns `float64` when it should return `float32` -- **Root Cause:** Dtype preservation issue in the pooling implementation, not our tolist methods - -### Our Changes Are Safe -1. ✅ **No modifications to existing functionality** -2. ✅ **Only added new methods to JAX and TensorFlow frontends** -3. ✅ **Used existing `ivy.to_list()` function for consistency** -4. ✅ **Added proper test coverage for our new methods** -5. ✅ **No impact on pooling functions or dtype handling** - -### Verification -Our `tolist` implementations: -- Use the same pattern as existing frontends (NumPy, PyTorch, Paddle) -- Call `ivy.to_list(self.ivy_array)` which is already tested and working -- Return Python lists, not arrays (no dtype issues) -- Are completely isolated from pooling functionality - -## Conclusion -The failing test is a pre-existing issue unrelated to our `tolist` implementation. Our changes are safe, well-tested, and follow Ivy's established patterns. diff --git a/ivy/functional/frontends/jax/array.py b/ivy/functional/frontends/jax/array.py index f3580b9f0c0b3..0717b78b96b71 100644 --- a/ivy/functional/frontends/jax/array.py +++ b/ivy/functional/frontends/jax/array.py @@ -414,13 +414,6 @@ def swapaxes(self, axis1, axis2): return jax_frontend.numpy.swapaxes(self, axis1=axis1, axis2=axis2) def tolist(self): - """Convert the array to a (possibly nested) list. - - Returns - ------- - list - A list representation of the array. - """ return ivy.to_list(self.ivy_array) diff --git a/ivy/functional/frontends/tensorflow/tensor.py b/ivy/functional/frontends/tensorflow/tensor.py index 82705f8a80f4b..5b1e4b76ab4c3 100644 --- a/ivy/functional/frontends/tensorflow/tensor.py +++ b/ivy/functional/frontends/tensorflow/tensor.py @@ -226,13 +226,6 @@ def __iter__(self): yield self[i] def tolist(self): - """Convert the tensor to a (possibly nested) list. - - Returns - ------- - list - A list representation of the tensor. - """ return ivy.to_list(self.ivy_array) diff --git a/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py b/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py index ac9afafbc5a52..e03acd847e954 100644 --- a/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py +++ b/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py @@ -2839,6 +2839,8 @@ def test_jax_swapaxes( max_num_dims=5, min_dim_size=1, max_dim_size=10, + min_value=-1e05, + max_value=1e05, ), ) def test_jax_array_tolist( diff --git a/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py b/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py index 4e6b3bb022471..e1244a219514f 100644 --- a/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py +++ b/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py @@ -1620,6 +1620,8 @@ def test_tensorflow_shape( max_num_dims=5, min_dim_size=1, max_dim_size=10, + min_value=-1e05, + max_value=1e05, ), ) def test_tensorflow_tensor_tolist( From ef0b65027fff43cebe8bbd1d210d30357ba731df Mon Sep 17 00:00:00 2001 From: Kallal Mukherjee Date: Wed, 23 Jul 2025 01:06:05 +0530 Subject: [PATCH 4/4] feat: implement floor and floor_ methods for TensorFlow, JAX, and NumPy frontends - Add floor() and floor_() methods to TensorFlow EagerTensor frontend - Add floor() and floor_() methods to JAX Array frontend - Add floor() and floor_() methods to NumPy ndarray frontend - All floor_() methods are in-place operations using ivy.inplace_update - Add comprehensive test cases for all implementations - Resolves issue #21930 The floor_() method performs in-place floor operation on arrays/tensors, providing compatibility with native framework APIs. This completes the floor_ functionality across all major Ivy frontends. Co-authored-by: Kallal Mukherjee --- ivy/functional/frontends/jax/array.py | 7 ++ .../frontends/numpy/ndarray/ndarray.py | 7 ++ ivy/functional/frontends/tensorflow/tensor.py | 7 ++ .../test_frontends/test_jax/test_array.py | 75 +++++++++++++++++++ .../test_numpy/test_ndarray/test_ndarray.py | 75 +++++++++++++++++++ .../test_tensorflow/test_tensor.py | 75 +++++++++++++++++++ 6 files changed, 246 insertions(+) diff --git a/ivy/functional/frontends/jax/array.py b/ivy/functional/frontends/jax/array.py index 0717b78b96b71..7d41f138f9598 100644 --- a/ivy/functional/frontends/jax/array.py +++ b/ivy/functional/frontends/jax/array.py @@ -413,6 +413,13 @@ def var( def swapaxes(self, axis1, axis2): return jax_frontend.numpy.swapaxes(self, axis1=axis1, axis2=axis2) + def floor(self): + return jax_frontend.numpy.floor(self) + + def floor_(self): + self._ivy_array = ivy.inplace_update(self._ivy_array, ivy.floor(self._ivy_array)) + return self + def tolist(self): return ivy.to_list(self.ivy_array) diff --git a/ivy/functional/frontends/numpy/ndarray/ndarray.py b/ivy/functional/frontends/numpy/ndarray/ndarray.py index c248bea37f627..dba4faa5e284a 100644 --- a/ivy/functional/frontends/numpy/ndarray/ndarray.py +++ b/ivy/functional/frontends/numpy/ndarray/ndarray.py @@ -649,6 +649,13 @@ def __lshift__(self, value, /): def __ilshift__(self, value, /): return ivy.bitwise_left_shift(self.ivy_array, value, out=self) + def floor(self): + return np_frontend.floor(self) + + def floor_(self): + self._ivy_array = ivy.inplace_update(self._ivy_array, ivy.floor(self._ivy_array)) + return self + def round(self, decimals=0, out=None): return np_frontend.round(self, decimals=decimals, out=out) diff --git a/ivy/functional/frontends/tensorflow/tensor.py b/ivy/functional/frontends/tensorflow/tensor.py index 5b1e4b76ab4c3..e309fb8b063bc 100644 --- a/ivy/functional/frontends/tensorflow/tensor.py +++ b/ivy/functional/frontends/tensorflow/tensor.py @@ -225,6 +225,13 @@ def __iter__(self): for i in range(self.shape[0]): yield self[i] + def floor(self): + return tensorflow_frontend.floor(self) + + def floor_(self): + self.ivy_array = ivy.inplace_update(self.ivy_array, ivy.floor(self.ivy_array)) + return self + def tolist(self): return ivy.to_list(self.ivy_array) diff --git a/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py b/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py index e03acd847e954..77b76dd36535a 100644 --- a/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py +++ b/ivy_tests/test_ivy/test_frontends/test_jax/test_array.py @@ -2868,3 +2868,78 @@ def test_jax_array_tolist( on_device=on_device, test_values=False, # tolist returns Python list, not array ) + + +# floor +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="jax.numpy.array", + method_name="floor", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1e05, + max_value=1e05, + ), +) +def test_jax_array_floor( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "object": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + frontend_method_data=frontend_method_data, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + on_device=on_device, + ) + + +# floor_ +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="jax.numpy.array", + method_name="floor_", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1e05, + max_value=1e05, + ), + test_inplace=st.just(True), +) +def test_jax_array_floor_( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "object": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + frontend_method_data=frontend_method_data, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + on_device=on_device, + ) diff --git a/ivy_tests/test_ivy/test_frontends/test_numpy/test_ndarray/test_ndarray.py b/ivy_tests/test_ivy/test_frontends/test_numpy/test_ndarray/test_ndarray.py index 6404b98dade7f..dbb5d4e179776 100644 --- a/ivy_tests/test_ivy/test_frontends/test_numpy/test_ndarray/test_ndarray.py +++ b/ivy_tests/test_ivy/test_frontends/test_numpy/test_ndarray/test_ndarray.py @@ -3878,3 +3878,78 @@ def test_numpy_view( frontend_method_data=frontend_method_data, on_device=on_device, ) + + +# floor +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="numpy.array", + method_name="floor", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1e05, + max_value=1e05, + ), +) +def test_numpy_ndarray_floor( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "object": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + frontend_method_data=frontend_method_data, + on_device=on_device, + ) + + +# floor_ +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="numpy.array", + method_name="floor_", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1e05, + max_value=1e05, + ), + test_inplace=st.just(True), +) +def test_numpy_ndarray_floor_( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "object": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + frontend_method_data=frontend_method_data, + on_device=on_device, + ) diff --git a/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py b/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py index e1244a219514f..26b6712b8088e 100644 --- a/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py +++ b/ivy_tests/test_ivy/test_frontends/test_tensorflow/test_tensor.py @@ -1649,3 +1649,78 @@ def test_tensorflow_tensor_tolist( on_device=on_device, test_values=False, # tolist returns Python list, not array ) + + +# floor +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="tensorflow.constant", + method_name="floor", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1e05, + max_value=1e05, + ), +) +def test_tensorflow_tensor_floor( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "value": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + frontend_method_data=frontend_method_data, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + on_device=on_device, + ) + + +# floor_ +@handle_frontend_method( + class_tree=CLASS_TREE, + init_tree="tensorflow.constant", + method_name="floor_", + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1e05, + max_value=1e05, + ), + test_inplace=st.just(True), +) +def test_tensorflow_tensor_floor_( + dtype_and_x, + frontend_method_data, + init_flags, + method_flags, + frontend, + on_device, + backend_fw, +): + input_dtypes, x = dtype_and_x + helpers.test_frontend_method( + init_input_dtypes=input_dtypes, + backend_to_test=backend_fw, + init_all_as_kwargs_np={ + "value": x[0], + }, + method_input_dtypes=input_dtypes, + method_all_as_kwargs_np={}, + frontend_method_data=frontend_method_data, + init_flags=init_flags, + method_flags=method_flags, + frontend=frontend, + on_device=on_device, + )