Runner overview
The main entry point for inference is the Runner. This is the primary manager of inputs, outputs, processing, etc, and runs the end-to-end prediction loop. This page will define the overall architecture of the Runner, including advanced usage.
Runners follow the standard factory pattern of the repository (see
Codebase overview). The standard runner is a forecaster,
called DefaultRunner. Forecasting
functionality is primarily defined in the superclass Runner, which is found in
src/anemoi/inference/runner.py. Further information on runners can be found in the configuration file documentation, see runner.
Using and configuring Runners
As a Runner is used to coordinate the processing, inputs and outputs for all inference runs, it is highly configurable. We will primarily be looking at the forecaster use case, but the other runners follow the same overall pattern.
This is the initialisation of a Runner:
def __init__(self, config: RunConfiguration, *, classes: RunnerClasses | None = None) -> None:
As you can see, it takes in RunConfiguration, which is the base
configuration input.
The other configuration option is RunnerClasses.
Both are discussed in more detail below.
Configuring with RunConfiguration
RunConfiguration is a Pydantic class which mirrors the top level
options in the configuration yaml file –
so, the configuration options such as checkpoint, input,
output are accessible via config.checkpoint, config.input,
or config.output.
These configuration inputs are all used somewhere in Runner to create
the whole pipeline. These are covered in the configuration
documentation, which shows how they are defined
in a yaml file. They can also be defined in code using
RunConfiguration:
config = RunConfiguration(checkpoint="checkpoint_path", input="grib")
All the defaults for each configuration are found in
RunConfiguration. The only required input is checkpoint.
Configuring with RunnerClasses
RunnerClasses provide options for reading in metadata, tensor
handlers, and checkpoints. This class is defined in runner.py:
class RunnerClasses(BaseModel):
"""Configurable class types used by the Runner.
Child runners can override these with different classes.
"""
model_config = ConfigDict(arbitrary_types_allowed=True)
tensor_handler: type[TensorHandler] = TensorHandler
checkpoint: type[Checkpoint] = Checkpoint
metadata: type[Metadata] = Metadata
RunnerClasses allows you to swap out some of the key internal classes used by the runner (also known as a trait). It consists of a set of classes which are used to initialise
the TensorHandler,
Checkpoint, and
Metadata.
This facility is there to support extending the base runner to perform tasks other than forecasting, without having to rewrite or overload large parts of the runner.
By default, when creating metadata, runner passes into the Metadata
class, which defines the kind of “standard” Metadata processing.
The structure of that class is:
class Metadata(LegacyMixin):
"""Base Metadata class."""
def __init__(self, metadata: dict[str, Any], supporting_arrays: dict[str, FloatArray] = {}):
"""Initialize the Metadata object.
Parameters
----------
metadata : dict
The metadata dictionary.
supporting_arrays : dict, optional
The supporting arrays, by default {}.
"""
...
If you needed some kind of custom metadata management in your runner, you could subclass this class:
class MyMetadata(Metadata):
"""My custom metadata class."""
def __init__(self, metadata: dict[str, Any], supporting_arrays: dict[str, FloatArray] = {}):
super().__init__(metadata, supporting_arrays)
# My custom code here
This can then be passed into the Runner class using the
RunnerClasses structure:
@runner_registry.register("custom-runner")
class MyRunner(Runner):
def __init__(self, config: RunConfiguration):
super().__init__(
config,
classes=RunnerClasses(
metadata=MyMetadata,
),
)
This line overrides RunnerClasses to pass in the custom
MyMetadata class. The rest of the defaults from RunnerClasses
are still used.
It is also possible to use an existing Runner, and just override the
RunnerClasses:
my_runner = DefaultRunner(config, classes=RunnerClasses(metadata=MyMetadata))
Creating custom Runners
If the configuration you need is not covered in the configuration or
the RunnerClasses options, you can create custom runners which
override runner behaviour by inheriting from Runner. If creating a
custom runner, ensure that it is registered in the runner registry so
the rest of the code can access it. This can then be selected using the
runner configuration option.
@runner_registry.register("custom-runner")
class MyRunner(Runner):
def __init__(self, config: RunConfiguration):
super().__init__(
config,
classes=RunnerClasses(
metadata=MyMetadata,
),
)