Parameters that had no entry yet were all assigned the same dict object:
self.pid2config[id(p)] = key_value_dict
so overriding one parameter afterwards silently changed the others registered in
the same call:
mng.override_config([p1, p2], "optim_bits", 32)
mng.pid2config[id(p1)] is mng.pid2config[id(p2)] -> True
mng.override_config(p1, "lr", 0.01)
p1: {'optim_bits': 32, 'lr': 0.01}
p2: {'optim_bits': 32, 'lr': 0.01} <- never asked for
The docstring's own example is the single-parameter override, so this is the
documented usage rather than an edge case, and nothing raises: the parameter
just trains with settings the caller did not choose.
The same reference is also handed back out when the caller supplies
`key_value_dict` themselves, so their dict grows behind their back:
d = {"optim_bits": 8}
mng.override_config(p3, key_value_dict=d)
mng.override_config(p3, "lr", 0.5)
d -> {'optim_bits': 8, 'lr': 0.5}
Copy on insert. After:
p1: {'optim_bits': 32, 'lr': 0.01}
p2: {'optim_bits': 32}
d : {'optim_bits': 8}
override_config stores the same dict object for every parameter that does not already have an entry:
So a later single-parameter override takes the update branch and writes into the config every parameter from the earlier call is pointing at:
The docstring's own example is exactly this shape — register several parameters, then override one of them — so it is the documented usage rather than an edge case. Nothing raises; the affected parameters just train with hyperparameters the caller did not choose.
The same reference is handed back out when the caller passes key_value_dict themselves, so their dictionary grows behind their back:
The change
Copy on insert (dict(key_value_dict)). After:
p1: {'optim_bits': 32, 'lr': 0.01} p2: {'optim_bits': 32} d : {'optim_bits': 8}The update branch is unchanged, so accumulating several overrides onto one parameter still works the way it does today.
Tests
Two added next to test_override_config_after_register in tests/test_optim.py, both CPU-only:
Reverting only optimizer.py:
and with the change, 2 passed. ruff check and ruff format --check clean.
Separate from #2028, which is in utils.py; no overlapping hunks.