Skip to content

Document static input signature requirement for LiteRT PyTorch export - #22997

Merged
hertschuh merged 1 commit into
keras-team:masterfrom
pctablet505:doc-litert-pytorch-static-shape
May 27, 2026
Merged

hertschuh merged 1 commit into
keras-team:masterfrom
pctablet505:doc-litert-pytorch-static-shape

Conversation

@pctablet505

@pctablet505 pctablet505 commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

Addressing issue #22996 by documenting that dynamic shapes are not supported in LiteRT with the PyTorch backend and that users must explicitly specify a fixed static input signature.

  • I am a human, and not a bot.
  • I will be responsible for responding to review comments in a timely manner.
  • I will work with the maintainers to push this PR forward until submission.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the documentation in keras/src/export/litert.py and keras/src/models/model.py to clarify limitations regarding dynamic input shapes when exporting to LiteRT (TFLite) using the PyTorch backend. The reviewer pointed out that dynamic shapes are generally unsupported for almost all operations in this configuration, rather than just 'standard layers' or 'standard PyTorch ops'. The feedback suggests generalizing this limitation in the docstrings to prevent misleading users, which aligns with the repository's style guide on keeping documentation clear and accurate.

Comment thread keras/src/export/litert.py Resolved
Comment thread keras/src/models/model.py Resolved
@pctablet505
pctablet505 force-pushed the doc-litert-pytorch-static-shape branch from d4038db to b310b7a Compare May 27, 2026 19:04
@pctablet505

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the documentation for LiteRT (TFLite) export to clarify that dynamic input shapes are not supported when using the PyTorch backend, requiring users to explicitly provide a static input_signature. The review feedback suggests expanding these docstring updates to explicitly warn users that any dynamic dimensions (represented as None) will be automatically replaced with 1 during export if a static signature is not provided. This addition ensures users understand the underlying behavior and can properly pad their inputs to avoid unexpected runtime issues.

Comment thread keras/src/export/litert.py Resolved
Comment thread keras/src/models/model.py Resolved
Comment thread keras/src/models/model.py Resolved
@pctablet505
pctablet505 force-pushed the doc-litert-pytorch-static-shape branch from b310b7a to 173288f Compare May 27, 2026 19:07
@pctablet505
pctablet505 requested a review from hertschuh May 27, 2026 19:08
@pctablet505
pctablet505 marked this pull request as ready for review May 27, 2026 19:08
@codecov-commenter

codecov-commenter commented May 27, 2026 •

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 84.68%. Comparing base (bce1c57) to head (173288f).
⚠️ Report is 14 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #22997      +/-   ##
==========================================
+ Coverage   84.53%   84.68%   +0.14%     
==========================================
  Files         464      464              
  Lines       68515    69066     +551     
  Branches    11254    11363     +109     
==========================================
+ Hits        57919    58488     +569     
+ Misses       7656     7621      -35     
- Partials     2940     2957      +17     
Flag Coverage Ξ”
keras 84.48% <ΓΈ> (+0.14%) ⬆️
keras-cpu 83.71% <ΓΈ> (ΓΈ)
keras-gpu 69.35% <ΓΈ> (+4.39%) ⬆️
keras-jax 58.46% <ΓΈ> (+0.37%) ⬆️
keras-numpy 53.51% <ΓΈ> (ΓΈ)
keras-openvino 59.31% <ΓΈ> (ΓΈ)
keras-tensorflow 59.74% <ΓΈ> (+0.22%) ⬆️
keras-torch 59.13% <ΓΈ> (+0.24%) ⬆️
keras-tpu 57.05% <ΓΈ> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

β˜” View full report in Codecov by Sentry.
πŸ“’ Have feedback on the report? Share it here.

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hertschuh hertschuh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this clarification!

@google-ml-butler google-ml-butler Bot added kokoro:force-run ready to pull Ready to be merged into the codebase labels May 27, 2026
@hertschuh
hertschuh merged commit 12766e2 into keras-team:master May 27, 2026
13 checks passed
@google-ml-butler google-ml-butler Bot removed awaiting review ready to pull Ready to be merged into the codebase kokoro:force-run labels May 27, 2026
pull Bot pushed a commit to haifeng-jin/keras that referenced this pull request Aug 6, 2026
…ectory. (keras-team#23109)

* Add sliding_window to MultiHeadAttention and GroupedQueryAttention (#22814)

* Add sliding_window parameter to MultiHeadAttention and GroupedQueryAttention

* Address Gemini review: clarify docstring, use arange for sliding window mask

* Add LiteRT export support for PyTorch backend (#22758)

* Add LiteRT export support via PyTorch backend

This PR adds LiteRT export capability for the PyTorch backend via
 when .

Changes:
- Expand  to dispatch to
  when the backend is torch, converting through .
- Add device-to-CPU / restore logic for torch backend export.
- Add JAX x64 state preservation to isolate 's side effect
  of enabling  globally.
- Add  to validate and translate kwargs
  (including TFLite-style  β†’  quant_config).
- Add  with 22 tests covering sequential,
  functional, subclass, multi-input/output, numeric parity,
  kwargs validation, and quantization.
- Expand  litert backend check to allow  backend.
- Add  to requirements.txt and
  requirements-torch-cuda.txt.

Depends on #22797 (PyTorch ExportedProgram export), which is now
included in this branch's history.

* Address review comments for PR #22758

- litert.py: move import to top, remove redundancy, wrap litert_torch import
  inside _preserve_jax_x64_state, add documented model.eval() call
- litert_torch_test.py: use _preserve_jax_x64_state from litert, remove
  _to_numpy helper in favor of backend.convert_to_numpy, move imports to
  top, remove sys.modules test, add inference checks, improve docstrings
- torch_test.py: clarify skip-connection test naming and docstring
- torch/core.py: document why slice_update uses index_put_ instead of narrow
- torch/numpy.py: extend repeat fast-path to list/tuple of ints

* Fix torch backend convert_to_tensor creating float64 constants in torch.export graphs

When a Python scalar float was passed to convert_to_tensor with an
explicit float32 dtype, the function internally converted it to
np.array(float) which defaults to float64. During torch.export, the
original float64 numpy array got lifted as a constant before the cast
to float32 occurred in the graph. This caused MLIR verification errors
in strict downstream pipelines like litert_torch.convert.

The fix skips np.array() conversion for scalar Python values (bool,
int, float) so they go directly to torch.as_tensor() with the
requested dtype.

Fixes #22815

* Address review comments: remove redundant tf imports and divider comment

* Skip LiteRT export test if ai-edge-litert is missing (#22831)

* Fix Keras LiteRT export for Keras 3 + TF 2.20 + Python 3.13

This is a complete rewrite of the LiteRT conversion path to eliminate
the circular dependency that crashes on Keras 3 with TF 2.20.

Root causes fixed:
1. Circular dependency: Keras litert.py -> TF from_keras_model() ->
   Keras ExportArchive -> TF saved_model.save() -> Python 3.13
   wrapt.ObjectProxy __dict__ descriptor incompatibility -> TypeError
2. keras_deps bridge: TF fallback _trace_model_call() calls
   keras_deps.get_call_context_function()() which returns None on
   Keras 3 -> TypeError

Solution:
- Replace from_keras_model() with ExportArchive -> SavedModel ->
  from_saved_model() single path (TF official Keras 3 path)
- Remove nested inputs adapter model; dict inputs handled natively
- Add try/except TypeError guard around SavedModelTrackableView.descendants()
  for Python 3.13 + wrapt.ObjectProxy incompatibility

Test changes:
- Remove tf.lite.Interpreter fallback; require ai_edge_litert only
- Dict-input tests feed dicts natively to interpreter
- Custom InputSpec tests include name= for TFLite input name preservation

Fixes tensorflow/tensorflow#117453

* Skip litert tests when ai_edge_litert is missing

* Move basic litert tests to separate class and rename old class

* Fix wrapping in PR template. (#22840)

The AI-Assisted Contribution Policy line was unintentionally wrapped.

* Parallelize CPU tests to have them run faster. (#22658)

We have CPU runners with 16 cores now.

* Update Gemini CLI action version in workflow (#22841)

* Update Gemini CLI action version in workflow

* Update Gemini CLI action version to v0.39.1

* Update Gemini CLI action version in workflow (#22843)

* Implement segment_min function in keras.ops (#22829)

* add segment_min for backends

* add segment_min for ops

* add math_test for segment_min

* update code for openvino side

* Add segment_min

* Update the code by gemini review

---------

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* [Fix] Dense layer GPU OOM with rank-3 input due to BatchMatMulV2 gradient materialization (#22832)

* Unique ops (#22827)

* aded return-index parameter in ops unique

* added another test case

* fixed torch backend implementation

* added 2 new test and canonicalized axis in torch and numpy backend

* fix: added canonicalized axis for torch, numpy and openvino

* fix: replaced pytorch api from scatter_ to scatter_reduce_ due to gpu error

* Fix torch convert_to_tensor: honor explicit dtype for Python scalars (#22821)

* Improve sub path resolution in `file_utils`. (#22839)

- Replaced `is_path_in_dir` with `resolve_sub_path` which resolves a relative path and makes sure it stays within the `base_dir`.
- Fixed `is_link_in_dir` to correctly resolve the paths depending on whether it is a hard link or a symlink.
- Use `resolve_sub_path` in `saving_lib._write_nested_dict_to_dir` which is used by the Orbax reloading.

* Fix workflow naming by using matrix-aware job names instead of unsupported top-level expressions. (#22781)

* Fix workflow naming by using matrix-aware job names instead of unsupported top-level expressions.
Also removed invalid reference to matrix.multi_device which was not defined in the strategy. added some files to repo

* undo the keras gpu test name

* Undo job name change for "Run tests on GPU"

---------

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* fix: setting a fix seed in update kernel initializer (#22845)

* skip one-batch-ahead prefetch iterator on CPU (#22847)

* Replace cast+multiply masking with ops.where to reduce memory (#22392)

* fix(layers): replace cast+multiply masking with ops.where to reduce memory

* fix(layers): ensure boolean mask in ops.where for OpenVINO compatibility

* fix(pooling): remove unused backend import

* style: apply ruff formatting to batch_normalization and iou_metrics

* fix: address review feedback on masking PR

- metrics_utils: avoid redundant cast and recomputation of logical_and
- constraints: use ops.maximum(w, 0) instead of ops.where
- batch_normalization: add comment explaining del statements

* Add keras.ops.cdist for pairwise Euclidean distances (#22095)

* Add keras.ops.cdist for pairwise Euclidean distances

* Add keras.ops.cdist for pairwise Euclidean distances

* Add keras.ops.cdist with symbolic support and tests

* Add keras.ops.cdist with symbolic support and tests

* rebase completed

* Use convert_to_tensor instead of tf.convert_to_tensor

* Update excluded concrete tests list

---------

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Fix: Mask out-of-bounds class IDs (#22835) (#22844)

* Fix: Mask out-of-bounds class IDs and fix absent-class logic to staay within range in IoU metrics (#22835)

* Chore: format API files with Linux line endings

* Chore: re-trigger CI for contributor agreement

* fix(ops): validate input channels in conv/depthwise_conv/separable_conv symbolic path (#22853)

* fix(ops): validate newshape in reshape for consistent backend behavior (#22868)

* [OpenVINO] Implement cdist op (#22870)

* Implement segment_prod function in keras.ops (#22854)

* Add segment_prod

* Add segment_prod

* Update the code by gemini review

* Update test case

* Update test case

* Remove duplication of qr in linalg and math modules for all backends (#22871)

* Fix: keras.ops.argpartition failed when axis==None (#22560)

* fix:argpartition different tensor error

* fix:coding style

* fix:format

* fix:imports

* fix: argpartition backends and ops layer to handle axis=None correctly
Update tests to pass for both symbolic and eager tensors

* fix: handle axis=None correctly in argpartition backends and ops

* handle argpartition across backends

* handle argpartition across backends to avoid flattening & consistent behaviour

* fix:backend tests by solving incorrect axis swapping

* fix: argpartition implementation to ensure correct partition semantics while maintaining compatibility with numPy behavior across axes

* fix: argpartition axis=None to correctly flatten input and align backend execution with symbolic shape inference

* fix:add early axis=None flatten + axis=0 handling in openvino argpartition

* fix:add early axis=None flatten + axis=0 handling in openvino argpartition

* fix:format

* fix:format

* fix:openvino argpartition to handle axis=None by flattening input before axis normalization

* fix: wrap output with get_ov_output to reuse as Keras tensor

* fix: output for openvino ops

* fix: sorting with flatten +set axis=0

* update the test file

* fix: test functions

* fix: test functions

* fix: test functions

* Move back argpartition test in the DynamicShape class

---------

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Fix #20429 β€” `Attention` layer hardcodes 3-D shape assumptions, breaking N-D inputs (#22361)

* Fix Attention layer failing with N-dimensional inputs

The Attention layer hardcoded axes=[0, 2, 1] in the transpose for dot
score calculation, which only works for 3D tensors. When 4D inputs
(e.g. from Conv2D/MaxPooling2D) are passed, it raises a dimension
mismatch error.

Replace ops.transpose(key, axes=[0, 2, 1]) with ops.swapaxes(key,
-2, -1) which generalizes to any number of dimensions. Also fix
compute_output_shape and compute_output_spec to use relative indices
instead of hardcoded positional indices.

Fixes https://github.com/keras-team/keras/issues/20429

* style: run pre-commit hooks (ruff formatting)

* Fix flaky Equalization test by using fixed random seed

The test_input_dtypes_int64 test was flaky because it used
np.random.uniform without a fixed seed and asserted that all
256 integer values appeared in the equalized output. Adding a
fixed seed makes the test deterministic and prevents CI failures
unrelated to the PR changes.

* Apply ruff formatting to equalization_test.py

* Validate input channels in conv_transpose for symbolic and eager paths (#22861)

* fix(ops): validate input channels in conv_transpose for symbolic and eager paths

* Remove unused import from nn.py

Removed unused import for check_depthwise_conv_input_channels.

* Remove unused import for depthwise conv check

* Remove unused import in nn.py

Removed unused import for check_depthwise_conv_input_channels.

* Remove unused import for depthwise conv check

---------

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Fix argpartition unit test with Torch on GPU. (#22882)

* Fix torch Conv2DTranspose same padding (#22876)

* Fix validate_reshape_shape crash under torch.compile with dynamic shapes (#22894)

* Fix validate_reshape_shape crash under torch.compile with dynamic shapes

* Address review: accept np.integer dims and use elif for -1 count

* Fix misleading error messages in ReLU and add missing tests for ReLU, constraints, and regularizers (#22444)

* Fix misleading error messages in ReLU and add missing tests for ReLU, constraints, and regularizers

* Fix code formatting

* Fix formatting for regularizers_test.py

* Address review: remove verbose comment and boolean test

* Fix ruff formatting and import sorting

* Add tmp_build_dir to gitignore

* Address hertschuh review: revert gitignore, remove comment blocks, merge roundtrip tests

* Address hertschuh review: revert gitignore, remove comment blocks, merge roundtrip tests

* Revert .gitignore to original

* Trigger CI checks

* Fixes backward-compatiblity loading of legacy  files after #22362 (#22891)

* Fixes backward-compatiblity loading of legacy  files

* improve modularity

* re-remove set support

* address reviews

* Fix torch BiLSTM dispatch and engage cuDNN (#22874)

* Fix torch BiLSTM dispatch and engage cuDNN

* Address review: docstring and remove redundant tensor conversions

* Skip cuDNN LSTM during ONNX trace

* Loosen ONNX LSTM export test tolerance to 1e-5

* feat: Add a predict_proba method on SKLearnClassifier (#21556)

* feat: Add predict_proba on SKLearnClassifier

* feat: Add _estimator_has check

* feat: Use available_if for classifier predict_proba

* test: Add test checks for predict_proba

* test: Avoid numpy+openvino known errors

* use proper sklearn available_if import path

Co-authored-by: Adrin Jalali <adrin.jalali@gmail.com>

* fix

Co-authored-by: Adrin Jalali <adrin.jalali@gmail.com>

* update predict_proba check

* add decision_function

* fix decision_function tests

* fix tests tmp

* fix ruff ci: remove unused import

* remove assert usage

* Fix formatting

---------

Co-authored-by: Adrin Jalali <adrin.jalali@gmail.com>
Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* MultiOptimizer support for training sub-networks. (#22897)

* multioptimizer support for keras torch, tensorflow and jax backend

* cleaned class_map

* add example usage and comprehensive test

* ruff formatted

* update: added serialization and test for lambda and keras variable

* grading unscaling test

* feat: complete multi-optimizer implementation and public API exports

* new optimizer map structure: dunder methods

* added multioptimizer support in keras

* fixed: loss scaling

* resloved comments

* fixed optimizer tests

* Replaces saving_lib_test.py filesystem calls with gfile (#22901)

* Fixing resize bug (#22903)

* Supports symbolic and dynamic tensor shapes in `reshape` (#22927)

* do not convert or validate tensor  as tuple in

* Update keras/src/ops/numpy.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* format

* Address reviews

* Address reviews

* format

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* [OpenVINO]  Fix dynamic batch shape propagation through ops.slice (#22915)

* Revert "[OpenVino] Change `_parameterize_data` for KerasHub. (#22681)"

This reverts commit 5648142b79d272bb996c53524c7be1da8b3b2117.

* [OpenVINO] Handle symbolic slice lengths in core.slice

* [OpenVINO] Implement segment_min and segment_prod (#22917)

* fix(ops): raise on zero-size pooling outputs when pool_size > input spatial dim (#22919)

* Auto-enable tf2onnx large_model for models >1.5GB (#22825)

* Fix TPU test workflow job name rendering raw template expression

The job name used `format()` with a boolean expression inside, which
caused GitHub Actions to render the raw template in the checks UI.

Replace the dynamic expression with fully static `matrix.include` names
so the job name evaluates reliably to:
- 'Run tests on TPU' for single-device
- 'Run tests on multi-TPU' for multi-device

Fixes #22776

* Auto-enable tf2onnx large_model for models >1.5GB

TensorFlow GraphDef has a 2GB protobuf limit. When exporting large
models (e.g. Llama 1B, Gemma3 1B) to ONNX via tf2onnx, freezing all
variables into the graph causes import_graph_def to fail with:

  RuntimeError: size too big: ... string length exceeds max size

This change estimates total weight size and passes large_model=True
to tf2onnx when the model exceeds 1.5GB, triggering external weight
storage and avoiding the protobuf limit.

Weight size is computed from model.weights metadata (shape + dtype)
without materializing tensor values, so load_weights=False is safe.

Fixes keras-team/keras-hub#2340

* Fix multi-axis reduction bugs in all, any, and prod for Torch backend (#22907)

* Revert "Auto-enable tf2onnx large_model for models >1.5GB (#22825)" (#22954)

This reverts commit 8f09b27fe0f07da3c3950e362d3bfe64a3566840.

* Add keras.ops.erf test coverage and update NumPy backend dtype logic (#22912)

* Fix: KerasFileEditor eject HDF5 ExternalLink/SoftLink groups (#22899)

* KerasFileEditor: reject HDF5 ExternalLink/SoftLink groups

`_extract_weights_from_store` walked the HDF5 tree with raw subscripts
(`data[key]`, `value["vars"]`). When the input file is attacker-supplied,
either of those subscripts transparently follows an `h5py.ExternalLink`
or `h5py.SoftLink` group into another HDF5 file on the host. The
attacker-controlled payload can be as small as ~944 bytes and contains
no Python code, so it bypasses any static-analysis-based defense.

After the link is followed, `editor.weights_dict` is populated from the
linked file. A subsequent `editor.save(...)` β€” part of the documented
edit workflow β€” writes those tensors back out as regular datasets,
turning the editor into a full cross-file disclosure / exfiltration
channel for any HDF5 file (e.g. another user's weights checkpoint on a
shared training cluster, an `~/.cache/huggingface/...` cached model,
etc.) that the victim process can read.

PR #22057 (the original CVE-2026-1669 fix) already added a
dataset-level `value.external` check to this same method, but only
covered `Dataset` external storage. The matching `ExternalLink` /
`SoftLink` guard on `Group` children β€” which is what `safe_get_h5_group`
in `saving_lib` was written to provide β€” was not applied here. Apply
it now:

  * Before subscripting `data[key]`, fetch the link class via
    `data.get(..., getclass=True, getlink=True)` and raise `ValueError`
    on `ExternalLink` / `SoftLink`. This mirrors `safe_get_h5_group`
    but handles the case where the child can be a `Dataset` rather
    than a `Group`.
  * Resolve `value["vars"]` through `saving_lib.safe_get_h5_group` so
    a nested link named `vars` cannot be used to redirect the editor.

Keras's own `save_model` / `save_weights` never emits `ExternalLink` or
`SoftLink`, so legitimate weight files are unaffected β€” only attacker-
crafted payloads are rejected.

Two regression tests cover both link kinds.

Reported via huntr:
https://huntr.com/bounties/d5688f27-73af-4853-8855-0c3a45c0f290

* KerasFileEditor: trim review comments

Shorten the ExternalLink/SoftLink guard comment, restore the original
"vars" recursion comment, and drop the added test docstrings/comments
per review. No behavior change.

* Fix: load_weights reject HDF5 ExternalLink/SoftLink on legacy .h5 dispatcher (#22900)

* load_weights: route legacy `.h5` `model_weights` through safe_get_h5_group

`saving_api.py` was the only remaining raw `f["model_weights"]`
subscript on the legacy HDF5 weight-loading path. h5py transparently
follows `h5py.ExternalLink` / `h5py.SoftLink` on plain subscripts, so
a ~1 KB attacker-supplied `.h5` whose `model_weights` group is an
`ExternalLink` redirects `keras.saving.load_weights(model, "x.h5")`
(equivalently `model.load_weights("x.h5")`) into any HDF5 file the
victim process can read. The contents land in the victim's
in-process model and become trivially exfiltrable via
`model.get_weights()`, `model.save(...)`, or inference.

This is the same vulnerability class as CVE-2026-1669 and is a
missed spot relative to PR #22057 and #22801, which already routed
the equivalent accesses in `saving_lib.py`, `file_editor.py`, and
`legacy/saving/legacy_h5_format.py` through `safe_get_h5_group`
(introduced specifically for this purpose). `safe_mode` does not
gate this path β€” it only guards Python-object deserialization.

The fix is a one-line change here: use `saving_lib.safe_get_h5_group`
at this dispatcher access, matching what `legacy_h5_format` already
does for its own `f["model_weights"]` read.

Keras's own `save_model` / `save_weights` never emits
`ExternalLink` / `SoftLink`, so legitimate `.h5` weight files are
unaffected; only attacker-crafted payloads are rejected.

Reported via huntr:
https://huntr.com/bounties/d5688f27-73af-4853-8855-0c3a45c0f290

* load_weights: trim review comments

Remove the safe_get_h5_group dispatcher comment and the added test
docstrings/comments per review. No behavior change.

* Fix flaky linalg.qr test by relaxing tolerance (#22946)

* relax numerical tolerance

* changing rtol value

* Validate axes is a valid permutation in transpose (#22922)

* fix(ops): validate axes is a valid permutation in transpose

* review: move eager validation out of ops.transpose into TF backend

* LossScaleOptimizer and Callback Support for MultiOptimizers. (#22959)

* added callback support

* modularized repeating code

* multioptim check

* added LossScaleOptimizer Compatibility

* fixed tensorboard callback

* fixed naming convention

* fixed verbose test

* fix(ops): validate axis in softmax/normalize/swapaxes/moveaxis (#22963)

* fix(layers): validate scalar args in GroupNormalization and MultiHeadAttention (#22966)

* Relax validation threshold for int8 quantization tests. (#22970)

The latest release of JAX (0.10.1) causes this tests to fail on GPU.

* Add fused bidirectional GRU for torch and harden gru() dispatch (#22913)

* Fix torch GRU cuDNN dispatch and add fused bidirectional GRU

* Add cuDNN GRU dispatch tests and hoist eligibility checks

* Implement fabs function across multiple backends and add tests (#22904)

* Implement fabs function across multiple backends and add tests

* Fix type handling in fabs

* revert: remove unnecessary type casting in JAX fabs

---------

Co-authored-by: Shipra <Shi-pra-19@users.noreply.github.com>

* fix(callbacks): reset `self.best` in EarlyStopping/ReduceLROnPlateau between fit calls (#22967)

* Give clearer error when dict-input Functional model receives non-dict (#22968)

* fix(functional): give clearer error when dict-input model receives non-dict

* chore: retrigger CI (numpy job hit self-hosted runner infra error)

* review: move dict-input error message into assert_input_compatibility

* fix(ops): validate axis in symbolic sort/argsort/cumsum/cumprod/take (#22924)

* Fix bug introduced by Orbax's recent change from using the name "pytree" as default to "state" (#22985)

* Fix bug introduced by Orbax's recent change from using the name "pytree" as default to "state"

* Update orbax_util.py

* Fix line wrapping

---------

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Optimize tf.tensordot by removing redundant float casts (#22932)

* avoid float

* format fix

* relaxing tolerance

* Fix pytree related JaxLayer tests. (#22991)

JAX is updating the error messages related to pytree utils.

Fix unit tests by not verifying error messages coming from outside of Keras.

* Update assignee list (#22995)

* Fix int8 quantization test with MultiHeadAttention on JAX on GPU. (#22992)

https://github.com/keras-team/keras/pull/22970 was not enough to fix the test for JAX on GPU.

* Engage flash SDPA in MHA/GQA causal-only path (#22910)

* Route MHA/GQA causal-only path through is_causal SDPA

* Skip causal-only fast path when _compute_attention is overridden

* Strengthen extra_mask coverage in causal-only fallback tests

* feat: Add dsplit function across multiple backends (#22964)

* feat: Add dsplit function across multiple backends

* fix: Correct axis in dsplit function documentation

---------

Co-authored-by: Shipra <Shi-pra-19@users.noreply.github.com>

* Implement correctness assertions for test_ema in Adam optimizer (#22979)

- Updated test_ema in keras/src/optimizers/adam_test.py with assertions for initialization, manual updates, and model.fit() integration.
- Added test_ema_overwrite_frequency to verify periodic model weight overwriting with EMA values.
- Verified changes with ruff and py_compile.

Co-authored-by: andersendsa <199610634+andersendsa@users.noreply.github.com>

* Fix: KerasFileEditor reject HDF5 virtual datasets (#22976)

* Fix: KerasFileEditor reject HDF5 virtual datasets

KerasFileEditor._extract_weights_from_store reads each leaf dataset with a
raw value[()]. It already rejects group-level ExternalLink/SoftLink (#22899)
and Dataset external storage (#22057, the CVE-2026-1669 fix), but never
checks value.is_virtual. An HDF5 Virtual Dataset is a HardLink with
external=None, so it passes both guards, yet h5py resolves its source mapping
on read and returns bytes from another HDF5 file the victim process can read.
The leaked tensors surface via editor.weights_dict / weights_summary() and
are re-emitted as plain datasets by editor.save(), reproducing the
CVE-2026-1669 cross-file disclosure channel via virtual storage instead of
links.

saving_lib.safe_get_h5_dataset already rejects virtual datasets; this path
did not route through it. Add the matching is_virtual check inline.
Regression test covers the VDS case.

* Address review comments

- file_editor.py: drop the explanatory comment and include
  current_inner_path in the virtual-dataset rejection message so the
  user sees which dataset triggered it (Rule 140 / contextual error).
- file_editor_test.py: reuse a single self.get_temp_dir() for both
  fixture files; rename victim_private.weights.h5 -> other.weights.h5
  and vds_payload.weights.h5 -> virtual.weights.h5 (and the matching
  *_fpath locals) to use neutral names.

* Respect axis when squeezing singleton class dim in sparse_categorical_crossentropy (#22969)

* fix(losses): respect axis when squeezing singleton class dim in sparse_categorical_crossentropy

* review: use canonicalize_axis, drop verbose comments, use skipif marker

* Document static input signature requirement for LiteRT PyTorch export (#22997)

* Validate axis in symbolic stack/concatenate/split/diff/take_along_axis/unstack (#22981)

* fix(ops): validate axis in symbolic stack/concatenate/split/diff/take_along_axis/unstack

* chore: retrigger CI (numpy job hit self-hosted runner infra error)

* Validate positive integer size in RepeatVector and UpSampling1D (#22987)

* fix(layers): validate positive integer size in RepeatVector and UpSampling1D

* review: separate TypeError from ValueError in UpSampling1D size validation

* chore: retrigger CI (numpy job hit self-hosted runner infra error)

* chore: retrigger CI (numpy job hit self-hosted runner infra error)

* Correct symbolic output shape of ops.inner for multi-dimensional inputs (#22989)

* fix(ops): correct symbolic output shape of ops.inner for multi-dim inputs

* test: cover scalar and dynamic-dim paths in ops.inner; handle scalar operands

* fix(ops): drop temp dim from matmul output shape for 1-D operands (#22994)

* Add more best practices to Gemini review style guide (#22961)

* Add more best practices to Gemini review style guide

I crawled the last year of PRs and mined the comments for actionable
review style guide points.

* Address review

* Address more feedback, clean up style

* Implement erfc function in keras.ops (#22977)

* Add erfc for backends

* Add math test

* update erfc on openvino

* Add erfc math test

* update excluded_concrete_tests.txt

* update the code by gemini review

* update the code by review

* Add tensor converting for each backends

* fix(backend/torch): handle SymInt/SymFloat in convert_to_tensor and slice (#22998)

- convert_to_tensor: handle scalar torch.SymInt/SymFloat directly
  via torch.as_tensor, avoiding the numpy path that fails with
  symbolic types during torch.export tracing.

- convert_to_tensor: handle list/tuple containing SymInt/SymFloat
  by using torch.as_tensor directly.

- slice: allow torch.SymInt in start_indices and shape for the
  Python slicing fast path, enabling proper dynamic shape tracing
  in torch.export.

- Add core_test.py with focused unit tests for all SymInt branches.

* Keras subclass optimizers default name to None (#22972)

* changed optimizer subclass name to none

* added test for multiple uppercase letter optimizer

* fixed tensorflow backend

* fixed torch backend errors

* fix: added type validation for inner_config in deserialize_keras_object (#22974)

* fix: added type validation for inner_config in deserialize_keras_object

Includes type validation for standard classes, __typespec__ and functions as well as an exception for Sequential models. Also added corresponding unit tests to serialization_lib_test.

* Update keras/src/saving/serialization_lib.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* "refactor: remove redundant check for inner_config, as suggested"

* Update keras/src/saving/serialization_lib.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update keras/src/saving/serialization_lib.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Fix indentation and formatting in serialization_lib.py

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Fix data distribution for model training in Jax Training (#23003)

* fix

* Addressed automatic comments

* Added tst

* Fixed test

* Make EinsumDense.compute_output_shape work before build (#23000)

* fix(layers): make EinsumDense.compute_output_shape work before build

* review: remove unused self.full_output_shape and PR-specific comments

* Implement Hashing.compute_output_shape (#23002)

* fix(layers): implement Hashing.compute_output_shape

* review: parameterize Hashing compute_output_shape test

* Updates to nightly github workflow. (#23008)

Some updates for debuggability and to make it more consistent with the tests run in `actions.yml`.

- Added backend information in tests, right now it's impossible to tell which test is which.
- Removed uninstall of `keras-nightly`, this was workaround added a long time ago which is no longer needed.
- Removed `install -e "."`, this is no longer needed as we added local import support.

* Fix for flaky BatchNormalization renorm test. (#23007)

* Unskip more NumPy masking related tests. (#23009)

Follow-up to https://github.com/keras-team/keras/pull/22491

Simply, `backend.set_keras_mask` needs to be used to support NumPy.

* Fix: reject hard-link tar members whose name escapes the extraction directory (#22973)

* Reject hard links whose name escapes the extraction directory

filter_safe_tarinfos / is_link_in_dir validated only the target
(info.linkname) of a hard-link tar member, never the link's own
destination (info.name). A crafted tar with a hard-link member whose
name is an absolute path or contains "../" therefore passed the filter
and was materialized outside the extraction directory by
tarfile.extractall, allowing arbitrary file write/overwrite via
keras.utils.get_file(extract=True) / extract_archive on Python < 3.12
(where tarfile's data filter is not applied).

Validate info.name for hard links the same way regular files and
symbolic links are already validated, completing the tar-extraction
path-traversal hardening for the hard-link case.

* Address review: shorten hard-link comment and drop redundant test

Condense the is_link_in_dir hard-link comment to a single line per
reviewer request; context lives in the PR description and blame.

Remove FilterSafePathsTest.test_hardlink_name_traversal_skipped: the
hardlink_name_out parameterized case in IsLinkInDirTest already covers
rejection of a hard link whose name escapes base_dir.

* Fix: reject HDF5 shape-bomb datasets in the main load_model/load_weights path (#22975)

* Reject HDF5 datasets that declare far more data than is stored

safe_get_h5_dataset materialized an h5py dataset sized to its declared
shape without checking how much data is actually stored on disk. A
crafted .keras/.weights.h5 with a chunked, compressed, fill-value-only
dataset can declare an enormous shape (e.g. petabytes) while occupying a
few kilobytes, forcing load_model/load_weights to attempt a huge
allocation and OOM-kill the process (CWE-789 / CWE-409).

Reject datasets whose declared in-memory size both exceeds a floor and
exceeds the on-disk storage size by a large factor. Genuine arrays (even
compressed) stay well within this bound; shape/decompression bombs do
not. This extends the KerasFileEditor-only size guard to the main
load_model/load_weights path.

* Address review: 4 GiB floor, readable sizes, drop redundant test

- Raise _H5_DATASET_BOMB_FLOOR_BYTES from 1 GiB to 4 GiB for consistency
  with the KerasFileEditor fix (PR #21880).
- Format the declared/stored byte counts in the error with
  readable_memory_size so PiB-scale values are legible.
- Remove SafeGetH5DatasetTest.test_allows_regular_dataset; normal dataset
  loading is already covered by the existing round-trip tests.

* Use parameterization in test_rank_adjustment (#22990)

* Use parameterization in test_rank_adjustment

Refactored test_rank_adjustment in keras/src/losses/loss_test.py to use
pytest.mark.parametrize instead of a manual for loop.
Removed associated TODO and commented-out code.

* fixing syntax

* format fix

* skipping numpy tests

* fix numeric mismatch

* removing numpy

---------

Co-authored-by: andersendsa <199610634+andersendsa@users.noreply.github.com>
Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Validate Pipeline.from_config layers and avoid mutating input config (#23004)

`Pipeline.from_config` accessed `config["layers"]` directly, raising an
opaque `KeyError` (when the key is missing) or `TypeError` (when
`layers` is `None`) on a malformed config. Validate that `layers` is a
list/tuple and raise a clear `ValueError` otherwise, and build a shallow
copy of `config` so the caller's dict is not deserialized in place.

Fixes #22450

* feat: add CITATION.cff file for GitHub repository citation (#23005)

* feat: add CITATION.cff file for GitHub repository citation

* fix: replace year with date-released in preferred-citation for CFF compliance

* Reject ZIP archive members that declare far more data than is stored (#23010)

`_load_model_from_fileobj` reads `config.json`, `model.weights.h5` and the
shard map from the `.keras` archive without bounding the decompressed size.
The `config.json` read is the first thing load_model does, before any other
validation and regardless of `safe_mode`. A crafted `.keras` can store a few
KB on disk yet declare a multi-gigabyte member, so loading it allocates memory
(or extracts to disk) for the declared size and the process is OOM-killed or
fills the disk (CWE-409).

Add `_reject_zip_bomb` (rejects a member whose declared size both exceeds a
4 GiB floor -- matching the HDF5 dataset guard for CVE-2026-0897 -- and exceeds
its on-disk size by more than 100x) and a `_safe_zip_read` that checks then
reads. Apply it to:

- `config.json` and the shard map, via `_safe_zip_read`;
- `model.weights.h5`, via `_reject_zip_bomb` placed *before* the
  in-memory/extract-to-disk/on-the-fly `try/except` -- the bare `except` falls
  back to reading the weights on the fly, so a check inside it would be
  swallowed and the bomb still read; checking up front covers all three paths.

Keras writes archive members uncompressed (ratio ~1:1) and DEFLATE cannot
exceed ~1032:1, so genuine artifacts -- including arbitrarily large legitimate
models -- are unaffected.

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Fix torch median to average the two middle values for even-length inputs (#23013)

* Resolve paths with realpath to prevent symlink traversal in extraction (#23015)

`resolve_path` wrapped `realpath` with `abspath`, which collapses `..`
lexically before symlinks are resolved. For an archive member named
`link/../x`, the `..` cancels the `link` component as text, so `realpath`
never follows the symlink; `resolve_sub_path` then judges the member to be
inside the extraction directory while `tarfile.extractall` writes it through
the real symlink to a location outside it.

A TAR with an in-bounds symlink `link -> .` plus a file `link/../<name>`
therefore writes `<name>` outside the extraction directory (chainable to
deeper paths) via `get_file(extract=True)` / `extract_archive`.

Use `os.path.realpath(path)` directly: it returns an absolute path and
follows symlinks component by component, so `..` is applied after the symlink
is resolved and the containment check is correct.

* Build(deps): Bump the github-actions group with 2 updates (#23020)

Bumps the github-actions group with 2 updates: [codecov/codecov-action](https://github.com/codecov/codecov-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `codecov/codecov-action` from 6.0.0 to 6.0.1
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/57e3a136b779b570ffcdbf80b3bdc90e7fab3de2...e79a6962e0d4c0c17b229090214935d2e33f8354)

Updates `github/codeql-action` from 4.35.2 to 4.36.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/95e58e9a2cdfd71adc6e0353d5c52f41a045d225...7211b7c8077ea37d8641b6271f6a365a22a5fbfa)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Validate DiskIOStore asset paths stay within the working directory (#23017)

`DiskIOStore.make`/`get` joined a caller-supplied asset path onto the working
directory (and `make` created it) without verifying that it stays inside that
directory. This is the one asset I/O path in the save/load pipeline that
skips the containment already applied to archive extraction
(`filter_safe_tarinfos`/`filter_safe_zipinfos`) and Orbax asset writes
(`_write_nested_dict_to_dir`), both of which use `resolve_sub_path`.

Apply the same `resolve_sub_path` check in `make`/`get`/`has_path` so an asset
path that escapes the working directory is rejected. Normal nested asset paths
are unaffected.

* Save output_padding in Conv1D/2D/3DTranspose get_config (#23014)

* Save output_padding in Conv1D/2D/3DTranspose get_config

* Parameterize output_padding serialization test

* Reject npz weight members that declare far more data than is stored (#23016)

`NpzIOStore.get` returns `self.contents[path].tolist()`. Accessing
`self.contents[path]` makes NumPy allocate an array sized to the `.npy`
header's declared shape before any stored data is validated, so a tiny npz
member can declare an enormous shape and drive an unbounded allocation.

`keras.saving.load_model` selects the npz reader purely from the archive file
list (`model.weights.npz` present and `model.weights.h5` absent), so an
attacker who supplies the `.keras` file controls this path: a ~2 KB file can
force an arbitrarily large allocation (clean `MemoryError`, or an OOM kill for
a shape tuned near available RAM whose zero-filled member compresses heavily).
`safe_mode` does not gate weight I/O.

Peek each member's `.npy` header and reject it when the declared array size
exceeds both a 4 GiB floor and 100x the bytes actually stored, before the
array is materialized.

Co-authored-by: hertschuh <1091026+hertschuh@users.noreply.github.com>

* Build(deps): Update litert-torch requirement in the python group (#23019)

Updates the requirements on [litert-torch](https://github.com/google-ai-edge/litert-torch) to permit the latest version.

Updates `litert-torch` to 0.9.1
- [Release notes](https://github.com/google-ai-edge/litert-torch/releases)
- [Commits](https://github.com/google-ai-edge/litert-torch/compare/v0.9.0...v0.9.1)

---
updated-dependencies:
- dependency-name: litert-torch
  dependency-version: 0.9.1
  dependency-type: direct:production
  dependency-group: python
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Save data_format in image preprocessing layers' get_config (#23023)

* Fix bin boundaries calculation in Discretization layer (#22892)

* Fix bin boundaries calculation in Discretization layer

* Fix bin boundaries calculation in summarize function of Discretization layer

* Remove redundancy in summarize function of Discretization layer

* Trigger CI

* Refactor slicing step calculation for clarity in Discretization layer

* πŸ›‘οΈ Sentinel: [HIGH] Fix insecure deserialization in dataset utilities (#23026)

* security: set allow_pickle=False for np.load in dataset utilities

np.load defaults to allow_pickle=False for security reasons, to prevent
insecure deserialization vulnerabilities that could lead to arbitrary
code execution if a maliciously crafted `.npz` file is loaded.

Several datasets in `keras/src/datasets` used `allow_pickle=True` unnecessarily
for numeric arrays (e.g. `mnist`, `boston_housing`, `california_housing`).
This commit removes the explicit `allow_pickle=True` argument for those datasets,
enhancing the security of the application.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* security: set allow_pickle=False for np.load in dataset utilities

np.load defaults to allow_pickle=False for security reasons, to prevent
insecure deserialization vulnerabilities that could lead to arbitrary
code execution if a maliciously crafted `.npz` file is loaded.

Several datasets in `keras/src/datasets` used `allow_pickle=True` unnecessarily
for numeric arrays (e.g. `mnist`, `boston_housing`, `california_housing`).
This commit removes the explicit `allow_pickle=True` argument for those datasets,
enhancing the security of the application.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* security: set allow_pickle=False for np.load in dataset utilities

np.load defaults to allow_pickle=False for security reasons, to prevent
insecure deserialization vulnerabilities that could lead to arbitrary
code execution if a maliciously crafted `.npz` file is loaded.

Several datasets in `keras/src/datasets` used `allow_pickle=True` unnecessarily
for numeric arrays (e.g. `mnist`, `boston_housing`, `california_housing`).
This commit removes the explicit `allow_pickle=True` argument for those datasets,
enhancing the security of the application.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* security: set allow_pickle=False for np.load in dataset utilities

np.load defaults to allow_pickle=False for security reasons, to prevent
insecure deserialization vulnerabilities that could lead to arbitrary
code execution if a maliciously crafted `.npz` file is loaded.

Several datasets in `keras/src/datasets` used `allow_pickle=True` unnecessarily
for numeric arrays (e.g. `mnist`, `boston_housing`, `california_housing`).
This commit removes the explicit `allow_pickle=True` argument for those datasets,
enhancing the security of the application.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* security: set allow_pickle=False for np.load in dataset utilities

np.load defaults to allow_pickle=False for security reasons, to prevent
insecure deserialization vulnerabilities that could lead to arbitrary
code execution if a maliciously crafted `.npz` file is loaded.

Several datasets in `keras/src/datasets` used `allow_pickle=True` unnecessarily
for numeric arrays (e.g. `mnist`, `boston_housing`, `california_housing`).
This commit removes the explicit `allow_pickle=True` argument for those datasets,
enhancing the security of the application.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* security: set allow_pickle=False for np.load in dataset utilities

np.load defaults to allow_pickle=False for security reasons, to prevent
insecure deserialization vulnerabilities that could lead to arbitrary
code execution if a maliciously crafted `.npz` file is loaded.

Several datasets in `keras/src/datasets` used `allow_pickle=True` unnecessarily
for numeric arrays (e.g. `mnist`, `boston_housing`, `california_housing`).
This commit removes the explicit `allow_pickle=True` argument for those datasets,
enhancing the security of the application.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* security: set allow_pickle=False for np.load in dataset utilities

np.load defaults to allow_pickle=False for security reasons, to prevent
insecure deserialization vulnerabilities that could lead to arbitrary
code execution if a maliciously crafted `.npz` file is loaded.

Several datasets in `keras/src/datasets` used `allow_pickle=True` unnecessarily
for numeric arrays (e.g. `mnist`, `boston_housing`, `california_housing`).
This commit removes the explicit `allow_pickle=True` argument for those datasets,
enhancing the security of the application.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>

* Add fmax function for element-wise maximum, handling NaNs across backends (#23024)

Co-authored-by: Shipra <Shi-pra-19@users.noreply.github.com>

* Save seed in Attention layer get_config (#23029)

* Remove progress bar double averaging of metrics. (#23030)

* Remove progress bar double averaging of metrics.

All metrics emitted to the progress bar in `model.fit` and `mode.evaluate` are stateful. For instance, average metrics maintain the running sum and denominator and output the average.

Therefore, the progress bar needs to consider all of the metrics stateless. To make this easier without having to constantly update the list of stateful metrics (like Keras 2 does), added a backwards compatible parameter `stateless_metrics` to list stateless metrics instead.

Fixes https://github.com/keras-team/keras/issues/21301

* Simplify if statements

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Fix test

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Fix error message

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Fix TF backend depthwise and separable conv with stride and dilation (#23027)

* Fix TF backend depthwise and separable conv with stride and dilation

tf.nn.depthwise_conv2d and tf.nn.separable_conv2d reject strides > 1
combined with dilation_rate > 1, so these ops returned a wrong, smaller
output on the TensorFlow backend while the JAX and Torch backends were
correct. When both are greater than 1, run the dilated conv at stride 1
and subsample the result, with explicit SAME padding. The fallback is
gated to only that combination, so the common fused paths are unchanged.
It covers depthwise and separable, 1D and 2D, channels_first and
channels_last.

Remove the two TF skips in the depthwise and separable value tests; they
already cover this case against a numpy reference and now pass on TF.

* Simplify SAME padding and strided slice in the conv decomposition

Strides and dilation are static Python ints, so drop the tensor
conversions in the SAME padding helper and use plain integer math.
Inline the strided subsample with idiomatic slicing and remove the
_spatial_strided_slice_channels_last helper.

* Add channels_first and 1D regression tests for the TF conv stride+dilation decomposition

Cover the channels_first transpose and 1D (lift-to-2D) branches of the
depthwise/separable stride+dilation decomposition added for #22515, for
both depthwise and separable convs. Validated against the existing numpy
conv references on the tensorflow, jax, and numpy backends.

* Re-run CI (numpy job hit a self-hosted runner container failure)

* Address review: fold data_format into existing conv tests, skip unsupported TF-CPU channels_first cases

Per maintainer review on #23027: parameterize the existing
test_depthwise_conv_2d / test_separable_conv_2d with data_format instead
of separate methods, rename the 1D tests to test_depthwise_conv_1d /
test_separable_conv_1d over strides=(1,2) and dilation_rate=(1,2), and
skip the channels_first cases that TF CPU does not support outside the
stride+dilation decomposition path (a separate pre-existing gap).

* Add fmin function for element-wise minimum, handling NaNs across backends (#23035)

Co-authored-by: Shipra <Shi-pra-19@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load` (#23034)

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

* πŸ›‘οΈ Sentinel: [ENHANCEMENT] Explicitly disable pickle in `np.load`

🚨 Severity: LOW / ENHANCEMENT
πŸ’‘ Vulnerability: `np.load` was called without explicitly specifying `allow_pickle=False`. While modern NumPy versions default to `False`, relying on defaults can lead to insecure deserialization if the environment uses an older version or the default changes.
🎯 Impact: Insecure deserialization can lead to arbitrary code execution if a maliciously crafted `.npz` file is loaded.
πŸ”§ Fix: Explicitly pass `allow_pickle=False` to `np.load` in `keras/src/saving/saving_lib.py`.
βœ… Verification: Ran `pytest keras/src/saving/saving_lib_test.py` to ensure no regressions.

Co-authored-by: rni418 <70032655+rni418@users.noreply.github.com>

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>

* [OpenVINO] Implement erfc op (#23037)

* [OpenVINO] Implement erfc op

* dtype promotion not needed

* add dtype promotion

* Add keras.config.enable_jit_cache for JAX persistent compilation cache (#22911)

* Add keras.config.enable_jit_cache for JAX persistent compilation cache

* Update keras/src/backend/config.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update keras/src/backend/config_test.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Add docstring examples; fix default-path test under KERAS_HOME

* Tighten JIT cache config API and tests

* Regenerate api/ to match alphabetical ordering

* Drop config API, document JAX compilation cache in compile()

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* On Tensorflow, allow `unique` argument `size` to be dynamic. (#23042)

`size` as a scalar tensor is now supported.

* Add foundational distribution metadata (num_processes, num_model_replicas, data_shard_id) (#23025)

* Add foundational distribution metadata (num_processes, num_model_replicas, data_shard_id)

* Add tests for data_shard_id in Distribution class

* Optimize vectorized_map using list comprehension (#23012)

* optimize `vectorized_map` using list comprehension

- Replaced the `for` loop and `.append()` call in `vectorized_map` with a list comprehension.
- This avoids attribute lookup overhead and leverages C-level list construction execution, improving performance.

* relaxing the  threshold

* remove random seed

---------

Co-authored-by: andersendsa <199610634+andersendsa@users.noreply.github.com>

* Respect compiled trainable state in TensorFlowTrainer (#22787)

* respect_compiled

* add warning

* fix indentation

* fix pytorch backend

* format fix

* relaxing tolerances

* checking new layers in current state

* [OpenVINO] Upgrade to opset16 (#23039)

* [OpenVINO] Upgrade to opset16

* remove bitwise ops turnaround

* add dilations to avg_pool

* retrigger CI

* [Distribution] Refactor TF dataset distribution and implement centralized sharding routing (#23043)

* Add regression test for dict input name preservation in LiteRT torch export (#23053)

Adds test_dict_input_names_preserved to litert_torch_test.py with a
version-gated skip so it only runs when litert-torch > 0.9.1 is installed.

This covers the upstream fix for:
https://github.com/google-ai-edge/litert-torch/issues/1022

Co-authored-by: pctablet505 <pctablet505@users.noreply.github.com>

* fixes `LambdaCallback` docs (#23057)

unescaped `\n` was causing bad formatting

* [Bug-Fix] Sparse TensorFlow wrapper drops trailing all-zero rows (#23060)

* [Bug-fix] Audio padded batching assumes scalar labels (#23062)

* Fix TensorBoard callback step counter never updating (#22357)

* Fix TensorBoard callback step counter never updating

The _push_writer method captured the step argument by value (always 0)
from on_train_begin/on_test_begin. This caused two issues:
1. should_record() always returned True (0 % N == 0), defeating the
   update_freq gating mechanism
2. writer.as_default(step=0) set the default step for custom user
   summaries to 0 permanently, so all custom summaries overwrote
   each other at step 0

Use backend.Variable for the step counter instead of a plain integer.
The variable is updated on each batch begin, and since writer.as_default()
reads the variable's current value, both the recording gate and custom
summary default step now reflect the actual batch number.

Fixes keras-team#20143

* Fix TensorBoard callback step counter never updating

The _push_writer method captured the step argument by value (always 0)
from on_train_begin/on_test_begin. This caused two issues:
1. should_record() always returned True (0 % N == 0), defeating the
   update_freq gating mechanism
2. writer.as_default(step=0) set the default step for custom user
   summaries to 0 permanently, so all custom summaries overwrote
   each other at step 0

Use backend.Variable for the step counter instead of a plain integer.
The variable is updated on each batch begin, and since writer.as_default()
reads the variable's current value, both the recording gate and custom
summary default step now reflect the actual batch number.

Fix GPU backend compatibility by converting the tensor result of
should_record() to a Python bool before returning. Torch GPU tensors
cannot be directly converted to numpy without CPU transfer, which
TensorFlow's record_if() tries to do internally.

* Fix TensorBoard callback step counter never updating

Use backend.Variable for the step counter so the closure in
_push_writer sees the current batch number instead of the
initial value (0).

Convert the tensor result of should_record() to a Python bool
before returning. This ensures TF record_if() receives a plain
bool across all backends. PyTorch GPU tensors are handled via
.item() (no CPU transfer needed); TF eager tensors use .numpy();
TF graph mode tensors fall through unchanged.

Fixes keras-team#20143

* Fix TensorBoard step counter by re-pushing writer each batch

Instead of using tf.Variable or backend.Variable (which breaks
backend agnosticism or fails with torch integer params), re-push
the summary writer context on every batch begin with the updated
step. This ensures:

- The default step seen by custom tf.summary calls is current
- The should_record condition uses the actual batch number
- No backend-specific variables needed

Fixes #20143

* Replace `np.prod` and `np.sum` with `math.prod` and `sum` when handling shapes. (#23065)

- `np.prod` and `np.sum` are subject to overflows, `math.prod` and `sum` are not
- `math.prod` and `sum` will support custom objects, in particular symbolic dimensions, whereas `np.prod` and `np.sum` require numbers

* Apply causal mask in MHA/GQA manual attention path (#23040)

* Apply causal mask in MHA/GQA manual attention path

* Combine causal mask with explicit attention_mask in manual path

* Honor is_causal with an explicit mask in the Torch SDPA path

* Honor is_causal with an explicit mask in the OpenVINO SDPA path

* [Bug-Fix] Torch divide_no_nan() produces NaN gradients (#23064)

* Security: Fix prompt injection in issue triage workflow (#23066)

* Make Lambda and TorchModuleWrapper from_config fail closed when safe_mode is unset (#23048)

* Make Lambda and TorchModuleWrapper from_config fail closed when safe_mode is unset

`Lambda.from_config` computed `safe_mode = safe_mode or in_safe_mode()`, which
is `None` (falsy) when the layer is deserialized outside of a `SafeModeScope`
and without an explicit `safe_mode` -- for example a direct
`keras.Model.from_config(...)` / `keras.Sequential.from_config(...)` call, or a
layer config that omits the `"module"` key and is routed through the legacy
deserializer. In that case the guard did not fire and the marshalled lambda
bytecode was loaded via `func_load`, allowing arbitrary code execution.
`TorchModuleWrapper.from_config` had the same problem with its `torch.load()`
pickle (`if in_safe_mode():`).

Treat an unset safe mode as safe (fail closed), matching the handling already
used by `TFSMLayer.from_config`: deserialization is refused unless safe mode is
explicitly disabled via `safe_mode=False`, `SafeModeScope(False)`, or
`keras.config.enable_unsafe_deserialization()`. `load_model` and
`model_from_json` are unaffected -- they already establish a `SafeModeScope`.

Adds regression tests for both layers and updates the `TorchModuleWrapper`
round-trip test to disable safe mode explicitly.

* Add `safe_mode` argument to `TorchModuleWrapper.from_config`

Follow the `Lambda.from_config` / `TFSMLayer.from_config` pattern: accept `custom_objects` and `safe_mode` keyword arguments. When `safe_mode` is unset, fall back to the ambient `SafeModeScope`, treating an unset scope as safe (fail closed). Update the test to exercise the explicit `safe_mode=False` argument alongside the ambient-scope path.

* Transpose channels_first to NHWC on CPU for depthwise/separable conv (#23041)

* Transpose channels_first to NHWC on CPU for depthwise/separable conv

Fixes #23031. On the TF backend, channels_first depthwise_conv (2D) and
separable_conv (1D and 2D) passed NCHW straight to the tf.nn ops, which TF
CPU rejects ("Depthwise convolution on CPU is only supported for NHWC
format"). Apply the same CPU transpose-to-NHWC pattern already used by the
1D depthwise path and the stride+dilation decomposition: transpose
channels_first inputs to channels_last on CPU, compute, and transpose the
output back; keep NCHW on GPU where it is supported natively.

Removes the TF-CPU channels_first skip guards added in #23027, since those
cases now pass.

* Cache the CPU-only device check used by the conv transpose

Move the per-call tf.config.list_logical_devices() check into a cached
_cpu_only() helper and use it at all four channels_first transpose sites,
including the pre-existing 1D depthwise path. Addresses review feedback that
the device query ran on every conv call.

* Disable autocast in GroupNormalization for mixed precision stability (#23070)

* Disable autocast in GroupNormalization for mixed precision stability

GroupNormalization did not disable autocast the way LayerNormalization
does. Under a mixed_float16 policy its inputs and its gamma/beta reads
were downcast to float16 before the normalization moments were computed.
Inputs above the float16 max overflowed to inf and produced NaN outputs,
and large affi…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants