I discovered something today which is possibly a very powerful technique. Previously, I wasn’t sure if you could do this in Julia - but now I know you can through the help of George Matheos (if it’s not pretty rough on the compiler, tbd):
https://github.com/femtomc/Jaynes.jl/blob/43fb0c323be4bbd933dadbf9b862a84f5700d924/src/contexts/update.jl#L2-L43
As you know, I’ve been working on this experimental project called Jaynes
which implements the generative function interface of Gen
as closure dynamos (execution contexts). Here is one of the contexts, the one which is responsible for updating choices in a recorded version of a model call and then adjusting the log weights appropriately.
This is context is also a closure, but it’s a generated function closure (see dynamos in IRTools) - so you have access to the lowered code at call time, and you can choose to compile a specialized version of the original method. The core dynamo transform wraps itself around calls, so it recursively calls itself down the stack, and that’s how dynamos work.
But you can also do other things inside the IR (if you like) before you compile the new method body. One of the most powerful techniques of Gen is the ability to specialize the generative function interface methods to a restricted language (called the static DSL in Gen). The specialization is performed by generated functions when given a tuple (static_dsl_function, static_choicemap, arg_diffs)
.
By moving the generative function interface to dynamo closures, you actually have the potential to specialize any call with the same information if you can configure a specialized IR pass through type information. I previously thought you could not pass this sort of information into generated functions from the outside - but I found out that Gen accomplishes this in static specialization through the keys (which is type information!) of NamedTuple
instances.
Now, the code I’ve shown above shows the dynamo update context - the @dynamo
transform itself (when called) can be configured by the key type information which the context type parameters keep. So you can control an IR transformation at runtime (e.g. dynamically) - which means that you should be able to generalize the static DSL specialization to any program call, if the space of IR passes permits it.
This is long-winded - but that being said, the IRTools
IR will make writing these passes complicated. I need to compute the Markov blanket of addresses given the constraint map which you want to use to update your (call) sample. If we constructed a probabilistic IR which facilitated the writing of these passes, it would likely make this sort of technique more feasible, and likely enable more complex passes.
On top of this, this sort of IR representation would also allow the synthesis of custom “address map” types for specific calls to be performed. Right now, that’s one of the main downsides to using the full dynamic language in Gen - the address map is just a hierarchical dictionary structure, without much specialization for performance. That’s not strictly required - because you should always be able to morph a model with runtime randomness (e.g. stochastic control flow) into a set of static fields with fixed type, as well as some fields with heap allocated structures. Pure dictionaries don’t present a very optimized representation.