Fix captured random state in compile#3828
Conversation
872f124 to
117db32
Compare
| recurse = [&](nb::handle subtree) { | ||
| if (nb::isinstance<nb::list>(subtree) || | ||
| if (is_random_state(subtree)) { | ||
| visitor(nb::cast(random_state_key())); |
There was a problem hiding this comment.
I wonder if we can do this in a less intrusive way by replacing
Lines 560 to 563 in 4367c73
with
if (!captured_inputs.is_none()) {
trace_captures.push_back(random_state_sentinel());
flat_in_captures.push_back(random_state_key());
tree_replace(captured_inputs, trace_captures, flat_in_captures);
}There was a problem hiding this comment.
The problem is the random state can be in any place in the captured inputs. As a result we have two options
- Walk the captured inputs and replace the random state with the thread local one before flattening
- Do it when the random state is encountered while flattening and while writing back
Option 1 has the overhead of 2 extra tree walks which for say 100k nodes (like a big transformer or sth) can be non-negligible.
Option 2 is intrusive and ugly.
There was a problem hiding this comment.
That makes sense, we can probably add a callback to tree_flatten to make it cheap to do replacement but it feels over-engineered and still does not fix the hook in tree_visit_update, I'm good with current approach and I can't come with a better one.
This is an interesting bug. In our attempts at thread-safety, compile now captures the random state of the thread that created the function and not the thread that called the function which can (and often is) different.
There isn't too clean of a solution that I could think of so please be as strict as possible in your judgment. The current approach is twofold
tree_visitandtree_visit_updatenow have a special case where if they encounter a random state they instead operate on the underlying array. This is ugly because it is not necessarily consistent withtree_mapor multi-treetree_visit.This closes ml-explore/mlx-lm#1444 and fixes ml-explore/mlx-lm#1439 .