-
Notifications
You must be signed in to change notification settings - Fork 19.6k
Store defaults of optional arguments in auto-config #21615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The current auto-config implementation only stores the supplied arguments, but not the values of the optional arguments of the constructor. As a consequence, deserializing a model fails when the default value of an optional argument changes, even when the structure of the model remains identical. This is problematic when we want to provide better results for newly trained models, but want to keep old models functional. This commit extends the auto-config functionality to also store default values, while otherwise being compatible with the previous implementation. Additional context: In the BayesFlow library, we provide models for downstream use, and aim to avoid changes that break loading of models. We rely on the auto-config functionality to avoid the somewhat tedious and error-prone hand-written specification of the configuration. To be able to change defaults without breaking model loading, it would be great if defaults were serialized in the auto-config as well. See also bayesflow-org/bayesflow#566.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @vpratz, 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 enhances the auto-configuration mechanism in Keras to include the default values of optional arguments when generating a model's configuration. This change addresses a critical issue where models saved with older default argument values would fail to load if those defaults were subsequently modified in the code, ensuring backward compatibility and more robust model serialization.
Highlights
- Enhanced Auto-Configuration: The auto-config functionality is extended to capture and store the default values of optional arguments during object instantiation.
- Improved Model Loading Robustness: This prevents breakage when loading models that were saved with previous default configurations, even if the code's default values have since changed.
- Leverages inspect.signature: The implementation now uses inspect.signature and bound_parameters.apply_defaults() to accurately determine and include default argument values in the generated configuration.
- Handles Variable Arguments: Specific logic is added to manage *args (varargs) for backward compatibility, storing only the first argument if present, or removing the entry if empty.
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| 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 issue 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
-
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. ↩
There was a problem hiding this comment.
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 improves the auto-config functionality by storing default values for optional arguments, which is a great enhancement for model serialization and backward compatibility. The use of inspect.signature is a good move towards a more robust argument handling.
I've found a critical issue in the handling of variable keyword arguments (**kwargs) that could lead to a crash, and I've also suggested an improvement for handling variable positional arguments (*args) to prevent silent data loss. Please see my detailed comments below.
| # Extract all arguments as a dictionary. | ||
| kwargs = bound_parameters.arguments | ||
| # Expand variable kwargs argument. | ||
| kwargs |= kwargs.pop(argspec.varkw, {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If cls.__init__ does not have a variable keyword argument (**kwargs), argspec.varkw will be None. In this case, kwargs.pop(None, {}) will raise a KeyError, causing a crash. You should guard this operation with a check to ensure argspec.varkw is not None.
| kwargs |= kwargs.pop(argspec.varkw, {}) | |
| if argspec.varkw is not None: | |
| kwargs |= kwargs.pop(argspec.varkw, {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.pop does not raise a KeyError when a default is provided
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@divyashreepathihalli Guarding against None is not strictly necessary here, as all keys in kwargs are strings, so kwargs.pop(None, {}) will just return {}. Would you prefer to have it in there anyway to improve clarity?
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #21615 +/- ##
=======================================
Coverage 82.49% 82.49%
=======================================
Files 572 572
Lines 57451 57470 +19
Branches 8982 8987 +5
=======================================
+ Hits 47395 47411 +16
- Misses 7760 7762 +2
- Partials 2296 2297 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
keras/src/ops/operation.py
Outdated
| kwargs[argspec.varargs] = kwargs[argspec.varargs][0] | ||
| else: | ||
| kwargs.pop(argspec.varargs) | ||
| except TypeError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The try/except is very broad, that seems dangerous?
|
Thanks for the PR.
In general the default |
|
Thanks for the review and your assessment. I agree that this approach would give us the most control and reliability (given that we don't make errors when manually specifying the config). Our concern is that it is harder to maintain (e.g., if someone introduces a new argument, but forgets to update the Independent of how we decide there, would you be in favor of updating the auto-config functionality, or would you prefer to leave it as is for now? The two main things I see are:
Let me know what you think. If you think updating this would be useful, I will adapt the code in the PR. If not, feel free to close the PR. |
fchollet
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, we can add this feature. Please add a unit test, to make sure we don't accidentally break it in the future.
|
/gemini review |
There was a problem hiding this comment.
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 is a great initiative to improve the auto-config functionality by including default values of optional arguments, which will certainly enhance model serialization and backward compatibility. The approach of using inspect.signature is robust and modern. I have identified a few areas for improvement concerning error handling and edge cases in function signatures, which I've detailed in my comments. Addressing these points will make the implementation even more solid.
Variadic arguments (i.e., *args) and positional only arguments cannot be passed via `cls(**config)`, so no automatic config can be created for them without adding additional logic to `from_config`. This commit adds an appropriate error message and tests for this case. It also extends the test to contain an optional argument with a default value. This allows testing the behavior defined in the previous commits.
|
Thanks for the response. After some consideration, I think adding support for variadic positional arguments is not worth the effort, as it breaks up the simple notion of the config being a dictionary that can be passed via Therefore, I have implemented the following changes:
Tests:
I have also tested the changes in the BayesFlow test suite and did not encounter any problems. |
|
Wouldn't loss of support for positional-only arguments be a regression? |
|
Thanks for taking a look. No, positional-only arguments are also not supported with the current implementation, as calling This would be the corresponding failing test on class OpWithCustomConstructorNoNamePositionalOnlyArgs(operation.Operation):
def __init__(self, alpha, /):
super().__init__()
self.alpha = alpha
def call(self, x):
return self.alpha * x
def compute_output_spec(self, x):
return keras_tensor.KerasTensor(x.shape, x.dtype)
class OperationTest(testing.TestCase):
# [...]
def test_serialization_custom_constructor_with_no_name_pos_only_auto_config(
self,
):
# Auto generated name is not serialized.
op = OpWithCustomConstructorNoNamePositionalOnlyArgs(0.2)
config = op.get_config()
self.assertEqual(config, {"alpha": 0.2})
revived = OpWithCustomConstructorNoNamePositionalOnlyArgs.from_config(
config
)
self.assertEqual(revived.get_config(), config)which gives the output (on |
* Store optional arguments in auto-config The current auto-config implementation only stores the supplied arguments, but not the values of the optional arguments of the constructor. As a consequence, deserializing a model fails when the default value of an optional argument changes, even when the structure of the model remains identical. This is problematic when we want to provide better results for newly trained models, but want to keep old models functional. This commit extends the auto-config functionality to also store default values, while otherwise being compatible with the previous implementation. Additional context: In the BayesFlow library, we provide models for downstream use, and aim to avoid changes that break loading of models. We rely on the auto-config functionality to avoid the somewhat tedious and error-prone hand-written specification of the configuration. To be able to change defaults without breaking model loading, it would be great if defaults were serialized in the auto-config as well. See also bayesflow-org/bayesflow#566. * auto-conf: correct behavior to match previous implementation * auto-config: riase for variadic arguments, add tests Variadic arguments (i.e., *args) and positional only arguments cannot be passed via `cls(**config)`, so no automatic config can be created for them without adding additional logic to `from_config`. This commit adds an appropriate error message and tests for this case. It also extends the test to contain an optional argument with a default value. This allows testing the behavior defined in the previous commits.
Dear Keras team,
I'm a co-developer of the BayesFlow library. We are currently facing the challenge that we want to be able to adapt the defaults of our networks when we find better configurations, but do so without breaking model loading for models that were saved to disk with the old defaults. We currently use the auto-config functionality to keep our code simple, and ideally we would like to continue to do so. Our problem is the following:
The current auto-config implementation only stores the supplied arguments, but not the values of the optional arguments of the constructor. As a consequence, loading a model fails when the default value of an optional argument has changed, even when the rest of the code of the model remains unchanged.
This PR extends the auto-config functionality to also store default values, while otherwise being (as far as I can tell) compatible with the previous implementation. This ensures that the "old defaults" are serialized, so changing defaults is no longer a problem. Is this a change you would be willing to include, or do you see any downsides with this approach?
See also bayesflow-org/bayesflow#566.
P.S.: A minor thing I encountered when looking at the current implementation is that variable arguments cannot be handled properly, as they cannot be properly converted to a dictionary. Currently, an argument
*argsis ignored without a warning, which might lead users to thinking it is handled automatically.Edit: correct description of how
*argsis handled by the current implementation.