diff --git a/src/diffusers/configuration_utils.py b/src/diffusers/configuration_utils.py index 16a100aad47c..194f377d3b37 100644 --- a/src/diffusers/configuration_utils.py +++ b/src/diffusers/configuration_utils.py @@ -683,8 +683,10 @@ def inner_init(self, *args, **kwargs): parameters = { name: p.default for i, (name, p) in enumerate(signature.parameters.items()) if i > 0 and name not in ignore } + positional_arg_names = set() for arg, name in zip(args, parameters.keys()): new_kwargs[name] = arg + positional_arg_names.add(name) # Then add all kwargs new_kwargs.update( @@ -695,9 +697,12 @@ def inner_init(self, *args, **kwargs): } ) - # Take note of the parameters that were not present in the loaded config - if len(set(new_kwargs.keys()) - set(init_kwargs)) > 0: - new_kwargs["_use_default_values"] = list(set(new_kwargs.keys()) - set(init_kwargs)) + # Take note of the parameters that were not present in the loaded config. + # Both keyword args (init_kwargs) and positional args must be considered + # explicitly provided so they survive from_config round trips. + explicitly_provided = set(init_kwargs) | positional_arg_names + if len(set(new_kwargs.keys()) - explicitly_provided) > 0: + new_kwargs["_use_default_values"] = list(set(new_kwargs.keys()) - explicitly_provided) new_kwargs = {**config_init_kwargs, **new_kwargs} getattr(self, "register_to_config")(**new_kwargs) diff --git a/tests/others/test_config.py b/tests/others/test_config.py index 58567f80f550..0e3b99ae6d17 100644 --- a/tests/others/test_config.py +++ b/tests/others/test_config.py @@ -296,6 +296,21 @@ def test_use_default_values(self, tmp_path): # Nevertheless "e" should still be correctly loaded to [1, 3] from SampleObject2 instead of defaulting to [1, 5] assert new_config_2.config.e == [1, 3] + def test_positional_args_survive_from_config_round_trip(self, tmp_path): + """Non-regression test for https://github.com/huggingface/diffusers/issues/14460""" + obj = SampleObject(10, 20) + assert obj.config["a"] == 10 + assert obj.config["b"] == 20 + + obj.save_config(tmp_path) + loaded = SampleObject.from_config(SampleObject.load_config(tmp_path)) + assert loaded.config["a"] == 10 + assert loaded.config["b"] == 20 + + # Also verify positional args are not in _use_default_values + assert "a" not in obj.config._use_default_values + assert "b" not in obj.config._use_default_values + def test_check_path_types(self): # Verify that we get a string returned from a WindowsPath or PosixPath (depending on system) config = SampleObjectPaths()