Skip to content

Fix #20598 — Custom models/layers with sublayers in nested lists fail to save/load weights#22362

Draft
pctablet505 wants to merge 2 commits intokeras-team:masterfrom
pctablet505:fix/20598-nested-list-save-load
Draft

Fix #20598 — Custom models/layers with sublayers in nested lists fail to save/load weights#22362
pctablet505 wants to merge 2 commits intokeras-team:masterfrom
pctablet505:fix/20598-nested-list-save-load

Conversation

@pctablet505
Copy link
Collaborator

Fixes: #20598

Problem

When a custom Model stores sublayers in a nested list — e.g. self.blocks = [[Dense(8), Dense(8)], [Dense(8), Dense(8)]] — those inner lists are tracked by the layer tracker, but the saving logic only handled KerasSaveable objects inside a container. Plain list / dict / tuple / set elements were silently skipped, meaning all weights inside nested list containers were not saved or restored. After model.save() + load_model(), the sublayers would be reinitialized with fresh random weights.

Root Cause

_save_container_state and _load_container_state iterated over a container and branched only on isinstance(saveable, KerasSaveable), with no handling for when the item itself is another nested container.

Fix

Add an elif isinstance(saveable, (list, dict, tuple, set)) branch in both _save_container_state and _load_container_state that recurses into the nested container with a _container path segment (deduplicated with the same used-names counter).

Files Changed

  • keras/src/saving/saving_lib.py — recursive nested-container handling in _save_container_state and _load_container_state
  • keras/src/saving/saving_lib_test.py — regression test: model with 2×2 nested Dense block list, save → load → assert outputs match

When a layer stores sub-layers in nested containers (e.g.
self.blocks = [[Dense, Dense], [Dense, Dense]]), the save/load
functions silently skipped inner containers because
_save_container_state and _load_container_state only handled
KerasSaveable items, not nested list/dict/tuple/set.

Add recursive handling of nested containers with index-based
namespacing (_container, _container_1, etc.) to avoid HDF5 path
collisions.

Fixes keras-team#20598
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical bug where Keras models containing sublayers within nested Python data structures (like lists of lists) would not properly save and restore the weights of those deeply embedded layers. The fix involves enhancing the saving and loading mechanisms to recursively traverse these nested containers, ensuring that all KerasSaveable objects, regardless of their nesting depth within standard Python collections, are correctly serialized and deserialized.

Highlights

  • Fix for nested sublayer weight saving: Previously, custom Keras models with sublayers stored in nested Python containers (lists, dicts, tuples, sets) failed to save and load their weights correctly, leading to reinitialization with random weights. This PR introduces recursive handling for these nested containers during the saving and loading process.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • keras/src/saving/saving_lib.py
    • Implemented recursive logic in _save_container_state to handle nested lists, dictionaries, tuples, and sets, ensuring that KerasSaveable objects within them are properly saved.
    • Implemented recursive logic in _load_container_state to handle nested lists, dictionaries, tuples, and sets, ensuring that KerasSaveable objects within them are properly loaded.
  • keras/src/saving/saving_lib_test.py
    • Added test_nested_list_layer_saving to validate that models with sublayers stored in nested lists correctly save and load their weights, asserting output consistency.
Activity
  • The pull request was created by pctablet505.
  • No human activity (comments, reviews) has occurred yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

The pull request successfully addresses the issue where custom models/layers with sublayers stored in nested lists failed to save and load their weights. The changes in saving_lib.py correctly implement recursive handling for nested list, dict, tuple, and set containers during both saving and loading processes. The addition of test_nested_list_layer_saving in keras/src/saving/saving_lib_test.py provides a good regression test, confirming the fix.

Comment on lines +897 to +902
name = "_container"
if name in used_names:
used_names[name] += 1
name = f"{name}_{used_names[name]}"
else:
used_names[name] = 0
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic for generating a unique name for the container (_container, _container_1, etc.) is duplicated here and in _load_container_state. Consider extracting this logic into a small helper function to improve maintainability and reduce redundancy. This aligns with the principle of keeping APIs modular and automating repetitive tasks (Repository Style Guide, lines 51, 124).

            name = "_container"
            name = _get_unique_container_name(name, used_names)

Comment on lines +947 to +952
name = "_container"
if name in used_names:
used_names[name] += 1
name = f"{name}_{used_names[name]}"
else:
used_names[name] = 0
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This block duplicates the name generation logic found in _save_container_state. Extracting this into a helper function would make the code cleaner and easier to maintain. This aligns with the principle of keeping APIs modular and automating repetitive tasks (Repository Style Guide, lines 51, 124).

            name = "_container"
            name = _get_unique_container_name(name, used_names)

@codecov-commenter
Copy link

codecov-commenter commented Mar 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.96%. Comparing base (95e74a9) to head (92c8aa5).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #22362   +/-   ##
=======================================
  Coverage   82.95%   82.96%           
=======================================
  Files         595      595           
  Lines       66040    66054   +14     
  Branches    10305    10309    +4     
=======================================
+ Hits        54785    54799   +14     
  Misses       8639     8639           
  Partials     2616     2616           
Flag Coverage Δ
keras 82.78% <100.00%> (+<0.01%) ⬆️
keras-jax 60.84% <100.00%> (+<0.01%) ⬆️
keras-numpy 55.03% <100.00%> (+<0.01%) ⬆️
keras-openvino 49.11% <100.00%> (+0.01%) ⬆️
keras-tensorflow 62.07% <100.00%> (+<0.01%) ⬆️
keras-torch 60.88% <100.00%> (+<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.

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.

3 participants