llvm: _comp_cached: handle weakref proxy/ref caching#2674
llvm: _comp_cached: handle weakref proxy/ref caching#2674kmantel wants to merge 1 commit intoPrincetonUniversity:develfrom
Conversation
WeakKeyDictionary stores weak references to its keys, but throws a TypeError and does not dereference a weakref proxy/ref even if the proxied object is the same as one of its keys
|
This came up when looking at tests/composition/test_autodiffcomposition.py::TestMiscTrainingFunctionality::test_training_then_processing, where AutodiffComposition._get_state_struct_type gets called many times, though it seems intended that the cached result be used |
|
This PR causes the following changes to the html docs (ubuntu-latest-3.7-x64): See CI logs for the full diff. |
| # only call for ProxyTypes because this won't fail on most | ||
| # objects, but specifically not on 'super()' referenced | ||
| # below, which would return the original object super() was | ||
| # called with, resulting in caching the wrong thing here |
There was a problem hiding this comment.
This is a bit confusing. What is the proxy object observed here? and how is it related to autodiff composition?
There was a problem hiding this comment.
The autodiff relation is incidental - it's only how I happened to notice what seemed like unintentional cache misses.
The wrapper in _comp_cached stores a value for an obj, and later is called weakref.proxy objects that reference obj. This does not locate obj in the cache due to TypeError("cannot create weak reference to 'weakcallableproxy' object") which is caught
PsyNeuLink/psyneulink/core/llvm/builder_context.py
Lines 77 to 81 in 0f3ab10
and bypasses caching.
The example I found creating these proxies is
PsyNeuLink/psyneulink/core/llvm/builder_context.py
Lines 64 to 66 in 0f3ab10
which was added after _comp_cached
There was a problem hiding this comment.
Right. the change to use weakref.proxy (c65e0bd) introduced the bug in pr #2613. I should have checked the total number of generated structures.
I'm still unsure about supporting proxy object caching vs. just reverting c65e0bd.
Does this change fix the high number of generated structures in test_training_then_processing? I'd expect that test (and all autodiff compositions) to run into the 'super()' issue instead.
EDIT: To elaborate. It's interesting that the unproxy_weakproxy function works on super objects as well [0].
We could use it to address the super() codepath as well as the issue introduced in c65e0bd. This would also allow us to remove the entire exception block in comp_cached.
The fewer isinstance checks and exception blocks on fast paths, the better.
Otherwise, I think it'd be better to just revert c65e0bd.
There was a problem hiding this comment.
Does this change fix the high number of generated structures in test_training_then_processing? I'd expect that test (and all autodiff compositions) to run into the 'super()' issue instead.
Could you let me know how to check this?
I'm only aware of repeated calls to
PsyNeuLink/psyneulink/library/compositions/autodiffcomposition.py
Lines 858 to 865 in 06f3006
I missed at first that bctx._cache is a WeakKeyDictionary - in that case, it doesn't seem like a problem to just call unproxy_weakproxy each time to let the super objects be stored as well, but I'm not too sure about the intent or benefits of this cache so I'll defer to you.
There was a problem hiding this comment.
that's a good question. the comment above mentioned "AutodiffComposition._get_state_struct_type gets called many times" so I thought you had some monitoring set up.
Either way, it prodded me to fix the stats collection for code generation which has been fixed/extended in #2687.
you should be able to get some numbers by enabling printouts via PNL_LLVM_DEBUG=stat. running tests might need -n0 or pytest might hide the output.
the _comp_cached wrapper is a generalized caching decorator for binary structure types used by compiled functions. any time there's a call to get_*_struct_type it can be cached. There are many repeated calls to get the same structure because the structure construction is often recursive.
specifically "node wrapper" is a pseudo object that represents node and all afferent projections. it gets compiled into a single function and reuses the same data types as composition execute and run. Thus generating node wrapper IR code call composition get_*_struct_type.
There are a few places that call get_.*_struct_type(super()), so I'm not sure what caching will do with those. It can "just work", but it might need a closer look
WeakKeyDictionary stores weak references to its keys, but throws a TypeError and does not dereference a weakref proxy/ref even if the proxied object is the same as one of its keys