Modulus Configuration

Modulus employs an extension of the Hydra configuration framework to offer a highly customizable but user friendly method for configuring the majority of Modulus’ features. This is achieved by using easy to understand YAML files which contain essential hyper-parameters for any physics-informed deep learning model. While you can still achieve the same level of customization in Modulus as any deep learning library, our built in configuration framework allows many of the internal features to be much more accessible. This section provides an overview of the built in configurable API Modulus provides.

Minimal Example

To understand configuration within Modulus, start with a simple minimum working example. Generally speaking, Modulus follows the same work flow as Hydra with just some minor differences. For each Modulus program you should create a YAML configuration file that is then loaded into a Python ModulusConfig object which is used by Modulus. Consider the following example:

Listing 1 conf/config.yaml
defaults:
 - modulus_default
 - arch:
    - fully_connected
 - optimizer: adam
 - scheduler: exponential_lr
 - loss: sum
Listing 2 main.py
import modulus
from modulus.hydra import to_yaml
from modulus.hydra.config import ModulusConfig

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    print(to_yaml(cfg))

if __name__ == "__main__":
   run()

Here, we have defined a minimal configuration (config) YAML file. A defaults list in config.yaml is used to load predefined configurations that are supported by Modulus. This config file is then loaded into python using the @modulus.main() decorator, in which you specify the location and name of your custom config. The config object, cfg, is then ingested into Modulus and used to set-up all sorts of internals all of which can be individually customized as discussed in the following sections.

For this example, Modulus has been configured to load a fully connected neural network, ADAM optimizer, exponential decay LR scheduler and a summation loss aggregation. Each of the included examples present in this user guide has its own config file which can be referenced.

Config Structure

Configs in Modulus are required to follow a common structure to ensure that all necessary parameters are provided independent of the user explicitly providing them. This is done by specifying the modulus_default schema at the top of the defaults list in every configuration file which will create the following config structure:

config
 |
 | <General parameters>
 |
 |- arch
     | <Architecture parameters>
 |- training
     | <Training parameters>
 |- loss
     | <Loss parameters>
 |- optimizer
     | <Optimizer parameters>
 |- scheduler
     | <Scheduler parameters>
 |- profiler
     | <Profiler parameters>

This config object has multiple configuration groups that each contain separate parameters pertaining to various features needed inside of Modulus. As seen in the example above, these groups can be quickly populated in the defaults list (e.g. optimizer: adam will populate the optimizer configuration group with parameters needed for ADAM). The next section takes a look at each of these groups in greater detail.

Warning

- modulus_default should always be placed at the top of your defaults list in Modulus config files. Without this, essential parameters will not be initialized and Modulus will not run!

Note

Need some details on different config groups or get documentation links? Use the --help flag with your Modulus program to bring up some useful information!

Configuration Groups

Global Parameters

Some essential parameters that you will find in a Modulus configuration include:

  • jit: Turn on TorchScript

  • save_filetypes: Types of file outputs from constraints, validators and inferencers

  • debug: Turn on debug logging

  • initialization_network_dir: Custom location to load pre-trained models from

Architecture

The architecture config group holds a list of model configurations that can be used to create different built in neural networks present within Modulus. While not required by the Modulus solver, this parameter group allows you to tune model architectures through the YAML config file or even the command line.

To initialize an architecture using the config, Modulus provides an instantiate_arch() method that allows different architectures to be initialized easily. The following two examples initialize the same neural network.

Listing 3 Config model intialization
# config/config.yaml
defaults:
    - modulus_default
    - arch:
        - fully_connected

# Python code
import modulus
from modulus.hydra import instantiate_arch
from modulus.hydra.config import ModulusConfig

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    model = instantiate_arch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v"), Key("p")],
        cfg=cfg.arch.fully_connected,
    )


if __name__ == "__main__":
    run()
Listing 4 Explicit model intialization
# Python code
import modulus
from modulus.hydra.config import ModulusConfig
from modulus.architecture.fully_connected import FullyConnectedArch

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    model = FullyConnectedArch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v"), Key("p")],
        layer_size: int = 512,
        nr_layers: int = 6,
        ...
    )

if __name__ == "__main__":
    run()

Note

Both of these approaches yield the same model. The instantiate_arch approach allows the model architecture to be controlled through the YAML file and CLI without loss of control. This can streamline the tuning of architecture hyper-parameters.

Currently the architectures that are shipped internally in Modulus that are have a configuration group include:

Examples

Listing 5 Initialization of fully-connected model with 5 layers of size 128
# config.yaml
defaults:
    - modulus_default
    - arch:
        - fully_connected

arch:
    fully_connected:
        layer_size: 512
        nr_layers: 6


# Python code
import modulus
from modulus.hydra import instantiate_arch
from modulus.hydra.config import ModulusConfig

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    model = instantiate_arch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v")],
        cfg=cfg.arch.fully_connected,
    )

if __name__ == "__main__":
    run()
Listing 6 Initialization of modified fourier model and siren model
# config.yaml
defaults:
    - modulus_default
    - arch:
        - modified_fourier
        - siren


# Python code
import modulus
from modulus.hydra import instantiate_arch
from modulus.hydra.config import ModulusConfig

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    model_1 = instantiate_arch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v")],
        frequencies=("axis,diagonal", [i / 2.0 for i in range(10)]),
        cfg=cfg.arch.modified_fourier,
    )

    model_2 = instantiate_arch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v")],
        cfg=cfg.arch.siren,
    )


if __name__ == "__main__":
    run()

Warning

Not all model parameters are controllable through the configs. Parameters that are not supported can be specified through additional keyword arguments in the instantiate_arch method. Alternatively, the model can be initialized in the standard Pythonic approach.

Training

The training config group contains parameters essential to the training process of the model. This is set by default with modulus_default, but many of the parameters contained in this group are often essential to modify.

  • default_training: Default training parameters (set automatically)

Parameters

Some essential parameters that you will find under the training config group include:

  • max_steps: Number of training iterations.

  • grad_agg_freq: Number of iterations to aggregate gradients over (default is 1). Effectively, setting grad_agg_freq=2 will double the batch size per iteration, compared to a case with no gradient aggregation.

  • rec_results_freq: Frequency to record results. This value will be used as the default frequency for recording constraints, validators, inferencers and monitors. See Results Frequency for more details.

  • save_network_freq: Frequency to save a network checkpoint.

  • amp: Use automatic mixed precision. This will set the precision for GPU operations to improve performance (default is 'float16' set using amp_dtype).

  • ntk.use_ntk: Use neural tangent kernel in training (default set to False)

Loss

The loss config group is used to select different loss aggregations that are supported by Modulus. A loss aggregation is the method used to combine the losses from different constraints. Different methods can yield improved performance for some problems.

Optimizer

The loss optimizer group contains the supported optimizers that can be used in Modulus which includes ones that are built into PyTorch as well as from Torch Optimizer package. Some of the most commonly used optimizers include:

  • adam: ADAM optimizer

  • sgd: Standard stochastic gradient descent

  • rmsprop: The RMSprop algorithm

  • adahessian: Second order stochastic optimization algorithm

  • bfgs: L-BFGS iterative optimization method

as well as these more unique optimizers: a2grad_exp, a2grad_inc, a2grad_uni, accsgd, adabelief, adabound, adadelta, adafactor, adagrad, adamax, adamod, adamp, adamw, aggmo, apollo, asgd, diffgrad, lamb, madgrad, nadam, novograd, pid, qhadam, qhm, radam, ranger, ranger_qh, ranger_va, rmsprop, rprop, sgdp, sgdw, shampoo, sparse_adam, swats, yogi.

Scheduler

The scheduler optimizer group contains the supported learning rate schedulers that can be used in Modulus. By default none is specified for which a constant learning rate will be used.

  • exponential_lr: PyTorch exponential learning rate decay initial_learning_rate * gamma ^ (step)

  • tf_exponential_lr: Tensorflow parameterization of exponential learning rate decay initial_learning_rate * decay_rate ^ (step / decay_steps)

Command Line Interface

As previously mentioned, a particular benefit using Hydra configs to control Modulus is that any of these parameters can be controlled through CLI. This can be particularly useful during hyper-parameter tuning or queuing up multiple runs using Hydra multirun. Here are a couple of examples which may be particularly useful when developing physics-informed models.

Listing 7 Changing optimizer and learning rate
$ python main.py optimizer=sgd optimizer.lr=0.01
Listing 8 Hyper-parameter search over architecture parameters using multirun
$ python main.py -m arch.fully_connected.layer_size=128,256 arch.fully_connected.nr_layers=2,4,6
Listing 9 Training for a different number of iterations
$ python main.py training.max_steps=1000

Note

Every parameter present in the config can be adjusted through CLI. For additional information please see the Hydra documentation.

Common Practices

Results Frequency

Modulus offers several different methods for recording the results of your training including recording validation, inference, batch, and monitor results. Each of these can be individually controlled in the training config group, however, typically it’s preferred for each to have the same frequency. In these instances, the rec_results_freq parameter can be used to control all of these parameters uniformly. The following two config files are equivalent.

# config/config.yaml
defaults:
    - modulus_default

training:
    rec_results_freq : 1000
    rec_constraint_freq: 2000
# config/config.yaml
defaults:
    - modulus_default

training:
    rec_validation_freq: 1000
    rec_inference_freq: 1000
    rec_monitor_freq: 1000
    rec_constraint_freq: 2000

Changing Activation Functions

Activations functions are one of the most important hyper-parameters to test for any deep learning model. While all of Modulus’ networks have default activations functions that have been seen to provide the best performance, specific activations may perform better than others on a case to case basis. Changing a activation function is straight forward using the instantiate_arch method:

Listing 10 Initializing a fully-connect model with Tanh activation functions
# Python code
import modulus
from modulus.hydra import instantiate_arch
from modulus.hydra.config import ModulusConfig
from modulus.architecture.layers import Activation

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    model_1 = instantiate_arch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v")],
        cfg=cfg.arch.fully_connected,
        activation_fn=Activation.TANH,
    )

if __name__ == "__main__":
    run()

Warning

Activation functions are not currently supported in the config files. They must be set in the Python script.

Many of Modulus’ models also include support for Adaptive Activation Functions which can be turned on in the config file or explicitly in the code:

# config/config.yaml
defaults:
    - modulus_default
    - arch:
        - fully_connected

arch:
    fully_connected:
        adaptive_activations: true

Multiple Architectures

For some problems, its better to have multiple models to learn the solution of different state variables. This may require the use of models that are the same architecture with different hyper-parameters. We can have multiple neural network models with the same architecture using config group overrides in Hydra. Here the arch_schema config group is used to access an architecture’s structured config.

Listing 11 Extending configs with customized architectures
# config/config.yaml
defaults:
    - modulus_default
    - /arch_schema/fully_connected@arch.model1
    - /arch_schema/fully_connected@arch.model2

arch:
    model1:
        layer_size: 128
    model2:
        layer_size: 256
Listing 12 Initialization of two custom architectures
# Python code
import modulus
from modulus.hydra import instantiate_arch
from modulus.hydra.config import ModulusConfig

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    model_1 = instantiate_arch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v")],
        cfg=cfg.arch.model1,
    )

    model_2 = instantiate_arch(
        input_keys=[Key("x"), Key("y")],
        output_keys=[Key("u"), Key("v")],
        cfg=cfg.arch.model2,
    )


if __name__ == "__main__":
    run()

Run Modes

Modulus has two different run modes available for training and evaluation:

  • train: Default run mode. Trains the neural network.

  • eval: Evaluates provided inferencers, monitors and validators using the last saved training checkpoint. Useful for post-processing after the training is complete.

Listing 13 Changing run mode to evaluate
 # config/config.yaml
 defaults:
     - modulus_default

 run_mode: 'eval'