The backend framework can either be set by calling
ivy.set_backend(backend_name) or it can inferred from the arguments.
For the latter, a global variable implicit_backend is located in the file which is initialized as numpy, and is always used to infer the backend in cases where: (a) no backend has been set using the
set_backend function and (b) the backend cannot be inferred from the inputs.
If the framework can be inferred from the inputs, then this is always used, and the implicit_backend is overwritten with the framework inferred.
numpy will always be the default backend unless it is explicitly set or is inferred.
When calling this function for setting the backend, the following steps are performed:
store a global copy of the original
ivy_original_dict, if this is not already stored.
import the backend module, for example
ivy.functional.backends.torch, if the backend has been passed in as a string. All functions in this unmodified backend module are primary functions, because only primary functions are stored in
ivy.functional.backends.backend_name. This backend module does not include any compositional functions.
loop through the original
ivy_original_dict(which has all functions, including compositional), and (a) add the primary function from the backend if it exists, (b) else add the compositional function from
wrap the functions where necessary, extending them with shared repeated functionality and writing the function to
ivy.__dict__. Wrapping is used in order to avoid excessive code duplication in every backend function implementation. This is explained in more detail in the next section: Function Wrapping.
It’s helpful to look at an example:
x = ivy.array([[2., 3.]]) ivy.get_backend() <module 'ivy.functional.backends.numpy' from '/opt/project/ivy/functional/backends/numpy/__init__.py'>
y = ivy.multiply(torch.Tensor([3.]), torch.Tensor([4.])) ivy.get_backend() <module 'ivy.functional.backends.torch' from '/opt/project/ivy/functional/backends/torch/__init__.py'>
ivy.set_backend('jax') z = ivy.matmul(jax.numpy.array([[2.,3.]]), jax.numpy.array([[5.],[6.]])) ivy.get_backend() <module 'ivy.functional.backends.jax' from '/opt/project/ivy/functional/backends/jax/__init__.py'> ivy.previous_backend() ivy.get_backend() <module 'ivy.functional.backends.torch' from '/opt/project/ivy/functional/backends/torch/__init__.py'>
In the last example above, the moment any backend is set, it will be used over the implicit_backend.
However when the current backend is set to the previous using the
ivy.previous_backend(), the implicit_backend will be used as a fallback, which will assume the backend from the last run.
While the implicit_backend functionality gives more freedom to the user, the recommended way of doing things would be set the backend explicitly.
In addition, all the previously set backends can be cleared by calling
Backend and Frontend Version Support#
Each time a new ivy backend is set, the backend_handler modifies the
ivy.__dict__ to support the multiple versions of functions that are not forward compatible.
torch.ones_like() in the latest stable version
1.12 has many new arguments
dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format compared to the same function at version
None of these new arguments will cause any forward compatibility issues: they weren’t used in old code, and they can now just be used in new code if desired.
However, the removal of the
out argument does break forward compatibility.
Old torch code will raise an
Argument Not Found error if being run with new torch versions.
However, such forward-breaking changes are in the vast minority.
We currently use a naming convention for such functions and name them as
fn_name_v_1p12_and_above which means that this particular implementation of the function is valid for versions
1.12 and above.
fn_name_v_1p01_to_1p1 means that the function is valid for versions between
1.1 both inclusive.
Each time a backend is set, we go through the
backend.__dict__ and for all functions for which multiple versions are detected, we simply import and assign the original
fn_name to the version specific one.
We do so by detecting the version of the backend framework installed on the user’s end.
We follow the same workflow for providing version support to the frontend functions. Again the version is inferred by importing the corresponding framework on the user’s system. If the user’s system doesn’t have the backend framework installed, we default to the latest version.
This should have hopefully given you a good feel for how the backend framework is set.
If you have any questions, please feel free to reach out on discord in the backend setting channel or in the backend setting forum!