Skip to content

Stabilize container weight paths across compatible layer subclasses#23216

Open
SID-6921 wants to merge 7 commits into
keras-team:masterfrom
SID-6921:fix-custom-lstm-load-weights-path-20322
Open

Stabilize container weight paths across compatible layer subclasses#23216
SID-6921 wants to merge 7 commits into
keras-team:masterfrom
SID-6921:fix-custom-lstm-load-weights-path-20322

Conversation

@SID-6921

@SID-6921 SID-6921 commented Jul 3, 2026

Copy link
Copy Markdown

Summary

  • make container save/load keys derive from saveable object type (_obj_type) rather than concrete class name
  • this keeps serialization paths stable when swapping compatible subclasses (for example custom LSTM subclass <-> base LSTM)
  • add regression test in keras/src/layers/rnn/lstm_test.py that saves weights from a custom LSTM subclass model and loads them into a base LSTM model

Why

Container naming currently uses class names. A custom subclass like MyCustomLSTM and the built-in LSTM then serialize under different container paths, so load_weights() can miss variables and fail with errors such as:

  • Layer 'lstm_cell' expected 3 variables, but received 0 variables during loading

Using _obj_type gives stable path keys for compatible saveables while preserving deterministic ordering.

Validation

  • Added regression test: test_load_weights_from_custom_lstm_into_base_lstm
  • Syntax check passed (python -m py_compile on modified files)
  • Full pytest execution is blocked in this environment due to missing TensorFlow runtime

Contributor Agreement

  • 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.

Fixes #20322

Signed-off-by: SID <nandasiddhardha@gmail.com>

@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 aims to allow weight loading between compatible subclasses (such as a custom LSTM and a base LSTM) by using generic object types instead of concrete class names during serialization. However, using generic names like 'Layer' breaks backward compatibility for existing saved models. The feedback suggests traversing the class's MRO to find the nearest Keras built-in class name, preserving compatibility while still allowing subclass swapping.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +919 to +923
def _container_saveable_name(saveable, used_names):
# Use `_obj_type()` (e.g. "Layer", "Optimizer") rather than concrete
# class names so swapping compatible subclasses keeps the same path keys.
base_name = naming.to_snake_case(saveable._obj_type())
return _get_unique_name(base_name, used_names)

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.

high

Using _obj_type() (which returns "Layer" for all layers) to name container saveables breaks backward compatibility for all existing saved models. Any weights saved with previous versions of Keras 3 (which used class-based names like "dense", "conv2d", etc.) will fail to load because the loader will now look for generic names like "layer", "layer_1", etc. Additionally, this makes the saved weights file structure much less readable.

To fix this while still resolving the issue of swapping compatible subclasses (like MyCustomLSTM <-> LSTM), we can traverse the class's MRO to find the nearest Keras built-in class name, falling back to the class's own name if no built-in Keras class is found.

def _container_saveable_name(saveable, used_names):
    # Find the nearest Keras built-in class in the MRO to keep paths stable
    # when swapping compatible subclasses (e.g. MyCustomLSTM <-> LSTM)
    # while preserving backward compatibility for existing saved models.
    base_name = None
    for cls in saveable.__class__.__mro__:
        if (
            cls.__module__.startswith("keras.")
            and cls.__name__ not in (
                "Layer",
                "Model",
                "KerasSaveable",
                "Operation",
                "Optimizer",
                "Metric",
                "Loss",
            )
        ):
            base_name = naming.to_snake_case(cls.__name__)
            break
    if base_name is None:
        base_name = naming.to_snake_case(saveable.__class__.__name__)
    return _get_unique_name(base_name, used_names)

SID-6921 added 6 commits July 3, 2026 02:29
Signed-off-by: SID <nandasiddhardha@gmail.com>
Signed-off-by: SID <nandasiddhardha@gmail.com>
Signed-off-by: SID <nandasiddhardha@gmail.com>
Signed-off-by: SID <nandasiddhardha@gmail.com>
Signed-off-by: SID <nandasiddhardha@gmail.com>
Signed-off-by: SID <nandasiddhardha@gmail.com>
@codecov-commenter

codecov-commenter commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.90566% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.04%. Comparing base (2aeecf0) to head (e2db299).

Files with missing lines Patch % Lines
keras/src/saving/saving_lib.py 84.90% 3 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #23216      +/-   ##
==========================================
- Coverage   84.80%   84.04%   -0.77%     
==========================================
  Files         465      465              
  Lines       69201    69251      +50     
  Branches    11379    11397      +18     
==========================================
- Hits        58687    58203     -484     
- Misses       7577     8118     +541     
+ Partials     2937     2930       -7     
Flag Coverage Δ
keras 83.87% <84.90%> (-0.75%) ⬇️
keras-cpu 83.87% <84.90%> (+<0.01%) ⬆️
keras-gpu ?
keras-jax 57.93% <84.90%> (-0.33%) ⬇️
keras-numpy 53.71% <79.24%> (+0.01%) ⬆️
keras-openvino 59.55% <84.90%> (+0.01%) ⬆️
keras-tensorflow 59.53% <84.90%> (-0.28%) ⬇️
keras-torch 58.67% <79.24%> (-0.39%) ⬇️
keras-tpu ?

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

☔ View full report in Codecov by Harness.
📢 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

3 participants