How it Works
No magic tricks, no mojo 😇
Imagine if you could run Python code everywhere. Python is an incredibly simple and mature language. But because it requires an interpreter, it either cannot run natively cross-platform; or it incurs a significant performance cost compared to languages that are “closer to hardware”.
We are on a mission to build a world where to get the performance benefits thereof.
Function works by lowering your Python code to native code. The benefit is that developers can think and write code in a high-level language, but still get the raw performance of a low-level language.
Symbolic Tracing
Our compiler begins by building an intermediate representation (IR) of your Python function using a combination of static analysis and symbolic tracing. We use PEP 523 to hook into a sandboxed Python interpreter before executing your function. We can then build a trace of every operation that happened within your function.
PEP 523 also forms the foundation of torch.compile
in PyTorch 2.0.
In fact, this is what spurred initial development of Function. Read the paper.
For example, consider the following function which classifies an image:
The symbolic tracer generates a graph that looks like the following:
We inject more metadata with static analysis to build a full IR for your Python code. After this, we then lower the IR to native code.
Lowering to Native Code
We lower an IR graph to several different platform-specific implementations in native code. We do so by walking through the
graph’s nodes and finding one or more operators that implement the node. Take the resize
node in the
graph above:
Type | Name | Target | Args |
---|---|---|---|
call_function | resize | <function resize at 0x11d3ae950> | (image, [232]) |
We search through our library of native operators that perform a resize operation on an image. Here’s an example targeting Apple Silicon with Accelerate.framework:
This approach gives us two main benefits:
If you are a hardware vendor interested in enabling developers to leverage your custom accelerators, reach out to us! You’ll bring the hardware; we’ll bring the software.
Compiling Binaries
Finally, we compile the lowered native code for each of our supported targets.
When an application uses the fxn.predictions.create
method to create a prediction, our client SDK will download
a compiled binary, load it into the process, and invoke it.
You can inspect the native source code generated by Function using the Function CLI. Take an example Python function:
We can compile this function using the Function CLI:
Once compiled, use the Function CLI to inspect the generated native source code:
Function can generate hundreds of implementations for a given compiled function. As such, prefer
the --prediction <prediction id>
option instead of --predictor <tag>
.
The result is a reference source file including the relevant native methods:
Function currently generates native code in C++, supported by Rust.
If you would like to compile Python functons to a custom platform, reach out to us.