Resolve #25605 cyclic reference in _LRScheduler#25776
Resolve #25605 cyclic reference in _LRScheduler#25776huzecong wants to merge 7 commits intopytorch:masterfrom huzecong:fix-scheduler
Conversation
Cyclic reference was introduced in a previous version due to runtime overwriting of the bound method `optimizer.step`. This is now avoided by keeping a weak reference to the optimizer instance.
|
Thank you! Do you think there is a way you can conveniently test this (e.g., by setting |
like this? |
|
@ezyang I can add a unit test similar to the snippet I included in the issue. Would that do? |
|
Yes that would be great, ty |
When multiple schedulers are constructed for the same optimizer, `optim.step` is only wrapped once.
| def wrapper(*args, **kwargs): | ||
| opt._step_count += 1 | ||
| return func(*args, **kwargs) | ||
| instance = instance_ref() |
There was a problem hiding this comment.
Is it provable that the optimizer instance is always live at this point? If not maybe we should check the return result of instance...
There was a problem hiding this comment.
I think it should be, since this function will only be accessible through optimizer.step().
|
#17630 could be due to having cyclic reference, which prevents optimizer and parameters from garbage collected when For #9942 store a partial function to a bound method would probably also create a self-reference. TBH I don't really see the need for a partial function here; it's perfectly fine to check Should I also fix this in the same PR? |
Good point: might as well remove
I'm leaning for a separate one, but I'm ok either way that is convenient for you, thanks! |
|
The test fails, btw |
Python `gc` module behavior changed in version 3.7. Prior to 3.7, when calling `gc.get_referrers` on a local variable in a function, the current frame would be included in the returned list.
ReduceLROnPlateau would previously store a partial function over a instance method, which creates a cyclic reference. Refactored the code to directly use attributes instead of creating a partial function.
|
Sorry for the delay, been busy during the week. The test error was due to a change in Python version 3.7. Prior to 3.7, I've also addressed #9942 (fix |
Didn't realize that `ReduceLROnPlateau` inherited from `object` rather than `_LRScheduler`... Might leave that change to the future.
|
@pytorchbot rebase this please |
|
Just waiting on CI |
|
Cool! Glad to help! |
facebook-github-bot
left a comment
There was a problem hiding this comment.
@ezyang is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
|
@huzecong This stuff is really cool and highly instructive to a chronic Cuda programmer/beginner Pythonista. Is there a reason you chose to grab a manual weakref to the optimizer instance instead of using types.MethodType? In your original issue you suggested Does types.MethodType actually resolve the circular reference issue? I'm very interested because I'm a frequent (ab)user of it. I realize that generally, functions are descriptors that become MethodTypes instances whenever they're retrieved and called (as in would in fact create a non-weak reference cycle for |
|
@mcarilli Sorry if the previous issue confused you, but yes you're right, obj = Foo()
obj.func = obj.funcsaves the But anyway, this isn't a big problem because Python has mechanisms (albeit more expensive) to deal with circular references. For simple personal scripts, it should be fine as long as the object in question is not holding too much memory. |
|
Makes sense, thanks very much! My only remaining point of confusion is then: why did you suggest
in the original issue? Did you originally assume (as I did) that MethodType was smart enough to store a weakref to self rather than an ordinary non-weak ref? |
|
Yeah, that's pretty much what I thought at the beginning :D |
Summary: Cyclic reference was introduced in a previous version due to runtime overwriting of the bound method `optimizer.step`. This is now avoided by keeping a weak reference to the optimizer instance. Credit: https://stackoverflow.com/questions/26157952/why-set-a-bound-method-to-python-object-create-a-circular-reference Pull Request resolved: pytorch#25776 Differential Revision: D17420770 Pulled By: ezyang fbshipit-source-id: 546ec94cf725ebfddb310b24e6a2e146ddecd1f6
Cyclic reference was introduced in a previous version due to runtime overwriting of the bound method
optimizer.step. This is now avoided by keeping a weak reference to the optimizer instance.Credit: https://stackoverflow.com/questions/26157952/why-set-a-bound-method-to-python-object-create-a-circular-reference