mlos_bench.environments ======================= .. py:module:: mlos_bench.environments .. autoapi-nested-parse:: Tunable Environments for mlos_bench. .. contents:: Table of Contents :depth: 3 Overview ++++++++ Environments are classes that represent an execution setting (i.e., environment) for running a benchmark or tuning process. For instance, a :py:class:`~.LocalEnv` represents a local execution environment, a :py:class:`~.RemoteEnv` represents a remote execution environment, a :py:class:`~mlos_bench.environments.remote.vm_env.VMEnv` represents a virtual machine, etc. An Environment goes through a series of *phases* (e.g., :py:meth:`~.Environment.setup`, :py:meth:`~.Environment.run`, :py:meth:`~.Environment.teardown`, etc.) that can be used to prepare a VM, workload, etc.; run a benchmark, script, etc.; and clean up afterwards. Often, what these phases do (e.g., what commands to execute) will depend on the specific Environment and the configs that Environment was loaded with. This lets Environments be very flexible in what they can accomplish. Environments can be stacked together with the :py:class:`.CompositeEnv` class to represent complex setups (e.g., an application running on a remote VM with a benchmark running from a local machine). See below for the set of Environments currently available in this package. Note that additional ones can also be created by extending the base :py:class:`~.Environment` class and referencing them in the :py:mod:`json configs <mlos_bench.config>` using the ``class`` key. Environment Parameterization ++++++++++++++++++++++++++++ Each :py:class:`~.Environment` can have a set of parameters that define the environment's configuration. These parameters can be *constant* (i.e., immutable from one trial run to the next) or *tunable* (i.e., suggested by the optimizer or provided by the user). The following clauses in the environment configuration are used to declare these parameters: - ``tunable_params``: A list of :py:mod:`tunable <mlos_bench.tunables>` parameters' (covariant) *groups*. At each trial, the Environment will obtain the new values of these parameters from the outside (e.g., from the :py:mod:`Optimizer <mlos_bench.optimizers>`). Typically, this is set using variable expansion via the special ``tunable_params_map`` key in the `globals config <../config/index.html#globals-and-variable-substitution>`_. - ``const_args``: A dictionary of *constant* parameters along with their values. - ``required_args``: A list of *constant* parameters supplied to the environment externally (i.e., from a parent environment, global config file, or command line). Again, tunable parameters change on every trial, while constant parameters stay fixed for the entire experiment. During the ``setup`` and ``run`` phases, MLOS will combine the constant and tunable parameters and their values into a single dictionary and pass it to the corresponding method. Values of constant parameters defined in the Environment config can be overridden with the values from the command line and/or external config files. That allows MLOS users to have reusable immutable environment configurations and move all experiment-specific or sensitive data outside of the version-controlled files. We discuss the `variable propagation <index.html#variable-propagation>`_ mechanism in the section below. Environment Tunables ++++++++++++++++++++ Each environment can use :py:class:`~mlos_bench.tunables.tunable_groups.TunableGroups` to specify the set of configuration parameters that can be optimized or searched. At each iteration of the optimization process, the optimizer will generate a set of values for the :py:class:`Tunables <mlos_bench.tunables.tunable.Tunable>` that the environment can use to configure itself. At a python level, this happens by passing a :py:meth:`~mlos_bench.tunables.tunable_groups.TunableGroups` object to the ``tunable_groups`` parameter of the :py:class:`~.Environment` constructor, but that is typically handled by the :py:meth:`~mlos_bench.services.config_persistence.ConfigPersistenceService.load_environment` method of the :py:meth:`~mlos_bench.services.config_persistence.ConfigPersistenceService` invoked by the ``mlos_bench`` command line tool's :py:class:`mlos_bench.launcher.Launcher` class. In the typical json user level configs, this is specified in the ``include_tunables`` section of the Environment config to include the :py:class:`~mlos_bench.tunables.tunable_groups.TunableGroups` definitions from other json files when the :py:class:`~mlos_bench.launcher.Launcher` processes the initial set of config files. The ``tunable_params`` setting in the ``config`` section of the Environment config can then be used to limit *which* of the ``TunableGroups`` should be used for the Environment. Tunable Parameters Map ^^^^^^^^^^^^^^^^^^^^^^ Although the full set of tunable parameters (and groups) of each Environment is always known in advance, in practice we often want to limit it to a smaller subset for a given experiment. This can be done by adding an extra level of indirection and specifying the ``tunable_params_map`` in the global config. ``tunable_params_map`` associates a variable name with a list of :py:class:`~mlos_bench.tunables.tunable_groups.TunableGroups` names, e.g., .. code-block:: json // experiment-globals.mlos.jsonc { "tunable_params_map": { "tunables_ref1": ["tunable_group1", "tunable_group2"], "tunables_ref2": [] // Useful to disable all tunables. } } Later, in the Environment config, we can use these variable names to refer to the tunable groups we want to use for that Environment: .. code-block:: json // environment.mlos.jsonc { // ... "config": { "tunable_params": [ "$tunables_ref1", // Will be replaced with "tunable_group1", "tunable_group2" "$tunables_ref2", // A no-op "tunable_group3" // Can still refer to a group directly. ], // ... etc. Note: this references the `dummy-tunables.jsonc <https://github.com/microsoft/MLOS/blob/main/mlos_bench/mlos_bench/config/tunables/dummy-tunables.jsonc>`_ file for simplicity. Using such ``"$tunables_ref"`` variables in the Environment config allows us to dynamically change the set of active ``TunableGroups`` for a given Environment using the global config without modifying the Environment configuration files for each experiment, thus making them more modular and composable. Variable Propagation ++++++++++++++++++++ Parameters declared in the ``const_args`` or ``required_args`` sections of the Environment config can be overridden with values specified in the external config files or the command line. In fact, ``const_args`` or ``required_args`` sections can be viewed as placeholders for the parameters that are being pushed to the environment from the outside. The same parameter can be present in both ``const_args`` and ``required_args`` sections. ``required_args`` is just a way to emphasize the importance of the parameter and create a placeholder for it when no default value can be specified the ``const_args`` section. If a ``required_args`` parameter is not present in the ``const_args`` section, and can't be resolved from the ``globals`` this allows MLOS to fail fast and return an error to the user indicating an incomplete config. Note that the parameter **must** appear in the child Environment ``const_args`` or ``required_args`` section; if a parameter is not present in one of these placeholders of the Environment config, it will not be propagated. This allows MLOS users to have small immutable Environment configurations and combine and parameterize them with external (global) configs. Taking it to the next level outside of the Environment configs, the parameters can be defined in the external key-value JSON config files (usually referred to as `global config files <../config/index.html#globals-and-variable-substitution>`_ in MLOS lingo). See :py:mod:`mlos_bench.config` for more details. We can summarize the parameter propagation rules as follows: 1. An environment will only get the parameters defined in its ``const_args`` or ``required_args`` sections. 2. Values of the parameters defined in the global config files will override the values of the corresponding parameters in all environments. 3. Values of the command line parameters take precedence over values defined in the global or environment configs. .. rubric:: Examples Here's a simple working example of a local environment config (written in Python instead of JSON for testing) to show how variable propagation works: Note: this references the `dummy-tunables.jsonc <https://github.com/microsoft/MLOS/blob/main/mlos_bench/mlos_bench/config/tunables/dummy-tunables.jsonc>`_ file for simplicity. >>> # globals.jsonc >>> globals_json = ''' ... { ... "experiment_id": "test_experiment", ... ... "const_arg_from_globals_1": "Substituted from globals - 1", ... "const_arg_from_globals_2": "Substituted from globals - 2", ... ... "const_arg_from_cli_1": "Will be overridden from CLI", ... ... // Define reference names to represent tunable groups in the Environment configs. ... "tunable_params_map": { ... "tunables_ref1": ["dummy_params_group1", "dummy_params_group2"], ... "tunables_ref2": [], // Useful to disable all tunables for the Environment. ... } ... } ... ''' >>> # environment.jsonc >>> environment_json = ''' ... { ... "class": "mlos_bench.environments.local.local_env.LocalEnv", ... "name": "test_env1", ... "include_tunables": [ ... "tunables/dummy-tunables.jsonc" // For simplicity, include all tunables available. ... ], ... "config": { ... "tunable_params": [ ... "$tunables_ref1", // Includes "dummy_params_group1", "dummy_params_group2" ... "$tunables_ref2", // A no-op ... "dummy_params_group3" // Can still refer to a group directly. ... ], ... "const_args": { ... // Environment-specific non-tunable constant parameters: ... "const_arg_1": "Default value of const_arg_1", ... "const_arg_from_globals_1": "To be replaced from global config", ... "const_arg_from_cli_1": "To be replaced from CLI" ... }, ... "required_args": [ ... // These parameters always come from elsewhere: ... "const_arg_from_globals_2", ... "const_arg_from_cli_2", ... // We already define these parameters in "const_args" section above; ... // mentioning them here is optional, but can be used for clarity: ... "const_arg_from_globals_1", ... "const_arg_from_cli_1" ... ], ... "run": [ ... "echo Hello world" ... ] ... } ... } ... ''' Now that we have our environment and global configurations, we can instantiate the :py:class:`~.Environment` and inspect it. In this example we will simulate the command line execution to demonstrate how CLI parameters propagate to the environment. >>> # Load the globals and environment configs defined above via the Launcher as >>> # if we were calling `mlos_bench` directly on the CLI. >>> from mlos_bench.launcher import Launcher >>> argv = [ ... "--environment", environment_json, ... "--globals", globals_json, ... # Override some values via CLI directly: ... "--const_arg_from_cli_1", "Substituted from CLI - 1", ... "--const_arg_from_cli_2", "Substituted from CLI - 2", ... ] >>> launcher = Launcher("sample_launcher", argv=argv) >>> env = launcher.root_environment >>> env.name 'test_env1' ``env`` is an instance of :py:class:`~.Environment` class that we can use to setup, run, and tear down the environment. It also has a set of properties and methods that we can use to access the object's parameters. This way we can check the actual runtime configuration of the environment. First, let's check the tunable parameters: >>> assert env.tunable_params.get_param_values() == { ... "dummy_param": "dummy", ... "dummy_param_int": 0, ... "dummy_param_float": 0.5, ... "dummy_param3": 0.0 ... } We can see the tunables from ``dummy_params_group1`` and ``dummy_params_group2`` groups specified via ``$tunables_ref1``, as well as the tunables from ``dummy_params_group3`` that we specified directly in the Environment config. All tunables are initialized to their default values. Now let's see how the variable propagation works. >>> env.const_args["const_arg_1"] 'Default value of const_arg_1' ``const_arg_1`` has the value we have assigned in the ``"const_args"`` section of the Environment config. No surprises here. >>> env.const_args["const_arg_from_globals_1"] 'Substituted from globals - 1' >>> env.const_args["const_arg_from_globals_2"] 'Substituted from globals - 2' ``const_arg_from_globals_1`` and ``const_arg_from_globals_2`` were declared in the Environment's ``const_args`` and ``required_args`` sections, respectively. Their values were overridden by the values from the global config. >>> env.const_args["const_arg_from_cli_1"] 'Substituted from CLI - 1' >>> env.const_args["const_arg_from_cli_2"] 'Substituted from CLI - 2' Likewise, ``const_arg_from_cli_1`` and ``const_arg_from_cli_2`` got their values from the command line. Note that for ``const_arg_from_cli_1`` the value from the command line takes precedence over the values specified in the Environment's ``const_args`` section **and** the one in the global config. Now let's set up the environment and see how the constant and tunable parameters get combined. We'll also assign some non-default values to the tunables, as the optimizer would do on each trial. >>> env.tunable_params["dummy_param_int"] = 99 >>> env.tunable_params["dummy_param3"] = 0.999 >>> with env: ... assert env.setup(env.tunable_params) ... assert env.parameters == { ... "const_arg_1": "Default value of const_arg_1", ... "const_arg_from_globals_1": "Substituted from globals - 1", ... "const_arg_from_globals_2": "Substituted from globals - 2", ... "const_arg_from_cli_1": "Substituted from CLI - 1", ... "const_arg_from_cli_2": "Substituted from CLI - 2", ... "trial_id": 1, ... "trial_runner_id": 1, ... "experiment_id": "test_experiment", ... "dummy_param": "dummy", ... "dummy_param_int": 99, ... "dummy_param_float": 0.5, ... "dummy_param3": 0.999 ... } These are the values visible to the implementations of the :py:meth:`~.Environment.setup`, :py:meth:`~.Environment.run`, and :py:meth:`~.Environment.teardown` methods. We can see both the constant and tunable parameters combined into a single dictionary :py:attr:`~.Environment.parameters` with proper values assigned to each of them on each iteration. When implementing a new :py:class:`~.Environment`-derived class, developers can rely on the :py:attr:`~.Environment.parameters` data in their versions of :py:meth:`~.Environment.setup` and other methods. For example, :py:class:`~mlos_bench.environments.remote.vm_env.VMEnv` would then pass the :py:attr:`~.Environment.parameters` into an ARM template when provisioning a new VM, and :py:class:`~mlos_bench.environments.local.local_env.LocalEnv` can dump them into a JSON file specified in the ``dump_params_file`` config property, or/and cherry-pick some of these values and make them shell variables with the ``shell_env_params``. A few `Well Known Parameters <../config/index.html#well-known-variables>`_ parameters like ``trial_id`` and ``trial_runner_id`` are added by the :py:mod:`Scheduler <mlos_bench.schedulers>` and used for trials parallelization and storage of the results. It is sometimes useful to add them, for example, to the paths used by the Environment, as in, e.g., ``"/storage/$experiment_id/$trial_id/data/"``, to prevent conflicts when running multiple Experiments and Trials in parallel. We will discuss passing the parameters to external scripts and using them in referencing files and directories in local and shared storage in the documentation of the concrete :py:class:`~.Environment` implementations, especially :py:class:`~mlos_bench.environments.script_env.ScriptEnv` and :py:class:`~mlos_bench.environments.local.local_env.LocalEnv`. Environment Services ++++++++++++++++++++ Environments can also reference :py:mod:`~mlos_bench.services` that provide the necessary support to perform the actions that environment needs for each of its phases depending upon where its being deployed (e.g., local machine, remote machine, cloud provider VM, etc.) Although this can be done in the Environment config directly with the ``include_services`` key, it is often more useful to do it in the global or :py:mod:`cli config <mlos_bench.config>` to allow for the same Environment to be used in different settings (e.g., local machine, SSH accessible machine, Azure VM, etc.) without having to change the Environment config. Variable propagation rules described in the previous section for the environment configs also apply to the :py:mod:`Service <mlos_bench.services>` configurations. That is, every parameter defined in the Service config can be overridden by a corresponding parameter from the global config or the command line. All global configs, command line parameters, Environment ``const_args`` and ``required_args`` sections, and Service config parameters thus form one flat name space of parameters. This imposes a certain risk of name clashes, but also simplifies the configuration process and allows users to keep all experiment-specific data in a few human-readable files. We will discuss the examples of such global and local configuration parameters in the documentation of the concrete :py:mod:`~mlos_bench.services` and :py:mod:`~mlos_bench.environments`. .. rubric:: Examples While this documentation is generated from the source code and is intended to be a useful reference on the internal details, most users will be more interested in generating json configs to be used with the ``mlos_bench`` command line tool. For a simple working user oriented example please see the `test_local_env_bench.jsonc <https://github.com/microsoft/MLOS/blob/main/mlos_bench/mlos_bench/tests/config/environments/local/test_local_env.jsonc>`_ file or other examples in the source tree linked below. For more developer oriented examples please see the `mlos_bench/tests/environments <https://github.com/microsoft/MLOS/blob/main/mlos_bench/mlos_bench/tests/>`_ directory in the source tree. .. rubric:: Notes - See `mlos_bench/environments/README.md <https://github.com/microsoft/MLOS/tree/main/mlos_bench/mlos_bench/environments/>`_ for additional documentation in the source tree. - See `mlos_bench/config/environments/README.md <https://github.com/microsoft/MLOS/tree/main/mlos_bench/mlos_bench/config/environments/>`_ for additional config examples in the source tree. .. seealso:: :py:mod:`mlos_bench.config` Overview of the configuration system. :py:mod:`mlos_bench.services` Overview of the Services available to the Environments and their configurations. :py:mod:`mlos_bench.tunables` Overview of the Tunables available to the Environments and their configurations. Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/mlos_bench/environments/base_environment/index /autoapi/mlos_bench/environments/composite_env/index /autoapi/mlos_bench/environments/local/index /autoapi/mlos_bench/environments/mock_env/index /autoapi/mlos_bench/environments/remote/index /autoapi/mlos_bench/environments/script_env/index /autoapi/mlos_bench/environments/status/index