From 3c271f39974fbbd277619b4dec18556925588ee7 Mon Sep 17 00:00:00 2001 From: amandarichardsonn <30413257+amandarichardsonn@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:23:31 -0700 Subject: [PATCH] Smartsim Documentation Refactor (#463) This PR provides a full refactor to the SmartSim documentation. This branch merges in sections: Experiment, Orchestrator, Model, Ensemble, RunSettings, BatchSettings, and SmartSim Logging. [ reviewed by @ashao @ankona @al-rigazzi @mellis13 @juliaputko ] [ committed by @amandarichardsonn ] --- doc/api/smartsim_api.rst | 45 +- doc/batch_settings.rst | 127 + doc/changelog.rst | 4 + doc/conf.py | 30 +- doc/ensemble.rst | 1214 +++++++++ doc/experiment.rst | 610 +++-- doc/images/Experiment.png | Bin 0 -> 104122 bytes doc/images/clustered_orchestrator-1.png | Bin 0 -> 110736 bytes doc/images/colocated_orchestrator-1.png | Bin 0 -> 70915 bytes doc/index.rst | 6 +- doc/installation_instructions/basic.rst | 6 +- .../platform/nonroot-linux.rst | 2 +- .../platform/olcf-summit.rst | 2 +- doc/launchers.rst | 248 -- doc/ml_features.rst | 4 +- doc/model.rst | 2343 +++++++++++++++++ doc/orchestrator.rst | 1051 ++++++-- doc/requirements-doc.txt | 2 + doc/run_settings.rst | 311 +++ doc/sr_advanced_topics.rst | 2 +- doc/ss_logger.rst | 221 ++ .../application_consumer_script.py | 17 + .../application_producer_script.py | 10 + .../ensemble_ml_model_file.py | 40 + .../ensemble_ml_model_mem.py | 40 + .../ensemble_torchscript_file.py | 13 + .../ensemble_torchscript_mem.py | 16 + .../ensemble_torchscript_string.py | 16 + .../experiment_driver.py | 42 + .../ensemble_doc_examples/file_attach.py | 20 + .../manual_append_ensemble.py | 25 + .../param_expansion_1.py | 16 + .../param_expansion_2.py | 21 + .../ensemble_doc_examples/replicas_1.py | 10 + .../ensemble_doc_examples/replicas_2.py | 15 + .../experiment_doc_examples/exp.py | 26 + .../model_doc_examples/from_file_ml_model.py | 40 + .../model_doc_examples/from_file_script.py | 14 + .../model_doc_examples/in_mem_ml_model.py | 40 + .../model_doc_examples/in_mem_script.py | 16 + .../model_doc_examples/model_file.py | 19 + .../model_doc_examples/model_init.py | 16 + .../model_doc_examples/prefix_data.py | 12 + .../model_doc_examples/string_script.py | 16 + .../doc_examples/orch_examples/colo_app.py | 15 + .../doc_examples/orch_examples/colo_driver.py | 29 + .../doc_examples/orch_examples/std_app.py | 15 + .../doc_examples/orch_examples/std_driver.py | 46 + .../tutorials}/getting_started/consumer.py | 0 .../getting_started/getting_started.ipynb | 0 .../multi_db_example/application_script.py | 0 .../multi_db_example/multidb_driver.py | 0 .../getting_started/output_my_parameter.py | 0 .../output_my_parameter_new_tag.py | 0 .../tutorials}/getting_started/producer.py | 0 .../ml_inference/Inference-in-SmartSim.ipynb | 0 .../ml_inference/colo-db-torch-example.py | 0 .../tutorials}/ml_training/surrogate/LICENSE | 0 .../ml_training/surrogate/README.md | 0 .../ml_training/surrogate/fd_sim.py | 0 .../ml_training/surrogate/steady_state.py | 0 .../ml_training/surrogate/tf_model.py | 0 .../ml_training/surrogate/tf_training.py | 0 .../surrogate/train_surrogate.ipynb | 0 .../ml_training/surrogate/vishelpers.py | 0 .../online_analysis/lattice/LICENSE | 0 .../online_analysis/lattice/README.md | 0 .../online_analysis/lattice/driver.py | 0 .../online_analysis/lattice/fv_sim.py | 0 .../lattice/online_analysis.ipynb | 0 .../online_analysis/lattice/probe.script | 0 .../online_analysis/lattice/vishelpers.py | 0 docker/docs/dev/Dockerfile | 5 - smartsim/database/orchestrator.py | 4 +- smartsim/experiment.py | 4 +- 75 files changed, 6004 insertions(+), 842 deletions(-) create mode 100644 doc/batch_settings.rst create mode 100644 doc/ensemble.rst create mode 100644 doc/images/Experiment.png create mode 100644 doc/images/clustered_orchestrator-1.png create mode 100644 doc/images/colocated_orchestrator-1.png delete mode 100644 doc/launchers.rst create mode 100644 doc/model.rst create mode 100644 doc/run_settings.rst create mode 100644 doc/ss_logger.rst create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/application_producer_script.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_file.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_mem.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_file.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_string.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/file_attach.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/replicas_1.py create mode 100644 doc/tutorials/doc_examples/ensemble_doc_examples/replicas_2.py create mode 100644 doc/tutorials/doc_examples/experiment_doc_examples/exp.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/from_file_ml_model.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/from_file_script.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/in_mem_ml_model.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/in_mem_script.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/model_file.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/model_init.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/prefix_data.py create mode 100644 doc/tutorials/doc_examples/model_doc_examples/string_script.py create mode 100644 doc/tutorials/doc_examples/orch_examples/colo_app.py create mode 100644 doc/tutorials/doc_examples/orch_examples/colo_driver.py create mode 100644 doc/tutorials/doc_examples/orch_examples/std_app.py create mode 100644 doc/tutorials/doc_examples/orch_examples/std_driver.py rename {tutorials => doc/tutorials}/getting_started/consumer.py (100%) rename {tutorials => doc/tutorials}/getting_started/getting_started.ipynb (100%) rename {tutorials => doc/tutorials}/getting_started/multi_db_example/application_script.py (100%) rename {tutorials => doc/tutorials}/getting_started/multi_db_example/multidb_driver.py (100%) rename {tutorials => doc/tutorials}/getting_started/output_my_parameter.py (100%) rename {tutorials => doc/tutorials}/getting_started/output_my_parameter_new_tag.py (100%) rename {tutorials => doc/tutorials}/getting_started/producer.py (100%) rename {tutorials => doc/tutorials}/ml_inference/Inference-in-SmartSim.ipynb (100%) rename {tutorials => doc/tutorials}/ml_inference/colo-db-torch-example.py (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/LICENSE (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/README.md (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/fd_sim.py (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/steady_state.py (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/tf_model.py (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/tf_training.py (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/train_surrogate.ipynb (100%) rename {tutorials => doc/tutorials}/ml_training/surrogate/vishelpers.py (100%) rename {tutorials => doc/tutorials}/online_analysis/lattice/LICENSE (100%) rename {tutorials => doc/tutorials}/online_analysis/lattice/README.md (100%) rename {tutorials => doc/tutorials}/online_analysis/lattice/driver.py (100%) rename {tutorials => doc/tutorials}/online_analysis/lattice/fv_sim.py (100%) rename {tutorials => doc/tutorials}/online_analysis/lattice/online_analysis.ipynb (100%) rename {tutorials => doc/tutorials}/online_analysis/lattice/probe.script (100%) rename {tutorials => doc/tutorials}/online_analysis/lattice/vishelpers.py (100%) diff --git a/doc/api/smartsim_api.rst b/doc/api/smartsim_api.rst index adf7081ec..787ef182e 100644 --- a/doc/api/smartsim_api.rst +++ b/doc/api/smartsim_api.rst @@ -1,17 +1,15 @@ - ************* SmartSim API ************* - .. _experiment_api: Experiment ========== - .. currentmodule:: smartsim.experiment +.. _exp_init: .. autosummary:: Experiment.__init__ @@ -34,6 +32,8 @@ Experiment :members: +.. _settings-info: + Settings ======== @@ -377,23 +377,47 @@ container. :undoc-members: :members: +.. _orc_api: Orchestrator ============ .. currentmodule:: smartsim.database -.. _orc_api: +.. autosummary:: + + Orchestrator.__init__ + Orchestrator.db_identifier + Orchestrator.num_shards + Orchestrator.db_nodes + Orchestrator.hosts + Orchestrator.reset_hosts + Orchestrator.remove_stale_files + Orchestrator.get_address + Orchestrator.is_active + Orchestrator.set_cpus + Orchestrator.set_walltime + Orchestrator.set_hosts + Orchestrator.set_batch_arg + Orchestrator.set_run_arg + Orchestrator.enable_checkpoints + Orchestrator.set_max_memory + Orchestrator.set_eviction_strategy + Orchestrator.set_max_clients + Orchestrator.set_max_message_size + Orchestrator.set_db_conf Orchestrator ------------ +.. _orchestrator_api: .. autoclass:: Orchestrator :members: :inherited-members: :undoc-members: +.. _model_api: Model ===== @@ -417,17 +441,17 @@ Model Model.disable_key_prefixing Model.query_key_prefixing +Model +----- + .. autoclass:: Model :members: :show-inheritance: :inherited-members: -.. _ensemble_api: - Ensemble ======== - .. currentmodule:: smartsim.entity.ensemble .. autosummary:: @@ -443,6 +467,11 @@ Ensemble Ensemble.query_key_prefixing Ensemble.register_incoming_entity +Ensemble +-------- + +.. _ensemble_api: + .. autoclass:: Ensemble :members: :show-inheritance: @@ -461,7 +490,6 @@ SmartSim includes built-in utilities for supporting TensorFlow, Keras, and Pytor TensorFlow ---------- - SmartSim includes built-in utilities for supporting TensorFlow and Keras in training and inference. .. currentmodule:: smartsim.ml.tf.utils @@ -510,7 +538,6 @@ SmartSim includes built-in utilities for supporting PyTorch in training and infe Slurm ===== - .. currentmodule:: smartsim.slurm .. autosummary:: diff --git a/doc/batch_settings.rst b/doc/batch_settings.rst new file mode 100644 index 000000000..07cef4c95 --- /dev/null +++ b/doc/batch_settings.rst @@ -0,0 +1,127 @@ +.. _batch_settings_doc: + +************** +Batch Settings +************** +======== +Overview +======== +SmartSim provides functionality to launch entities (``Model`` or ``Ensemble``) +as batch jobs supported by the ``BatchSettings`` base class. While the ``BatchSettings`` base +class is not intended for direct use by users, its derived child classes offer batch +launching capabilities tailored for specific workload managers (WLMs). Each SmartSim +`launcher` interfaces with a ``BatchSettings`` subclass specific to a system's WLM: + +- The Slurm `launcher` supports: + - :ref:`SbatchSettings` +- The PBS Pro `launcher` supports: + - :ref:`QsubBatchSettings` +- The LSF `launcher` supports: + - :ref:`BsubBatchSettings` + +.. note:: + The local `launcher` does not support batch jobs. + +After creating a ``BatchSettings`` instance, users gain access to the methods +of the associated child class, providing them with the ability to further configure the batch +settings for jobs. + +In the following :ref:`Examples` subsection, we demonstrate the initialization +and configuration of a batch settings object. + +.. _batch_settings_ex: + +======== +Examples +======== +A ``BatchSettings`` child class is created using the ``Experiment.create_batch_settings`` +factory method. When the user initializes the ``Experiment`` at the beginning of the Python driver script, +they may specify a `launcher` argument. SmartSim will then register or detect the `launcher` and return the +corresponding supported child class when ``Experiment.create_batch_settings`` is called. This +design allows SmartSim driver scripts utilizing ``BatchSettings`` to be portable between systems, +requiring only a change in the specified `launcher` during ``Experiment`` initialization. + +Below are examples of how to initialize a ``BatchSettings`` object per `launcher`. + +.. tabs:: + + .. group-tab:: Slurm + To instantiate the ``SbatchSettings`` object, which interfaces with the Slurm job scheduler, specify + `launcher="slurm"` when initializing the ``Experiment``. Upon calling ``create_batch_settings``, + SmartSim will detect the job scheduler and return the appropriate batch settings object. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher Slurm + exp = Experiment("name-of-experiment", launcher="slurm") + + # Initialize a SbatchSettings object + sbatch_settings = exp.create_batch_settings(nodes=1, time="10:00:00") + # Set the account for the slurm batch job + sbatch_settings.set_account("12345-Cray") + # Set the partition for the slurm batch job + sbatch_settings.set_queue("default") + + The initialized ``SbatchSettings`` instance can now be passed to a SmartSim entity + (``Model`` or ``Ensemble``) via the `batch_settings` argument in ``create_batch_settings``. + + .. note:: + If `launcher="auto"`, SmartSim will detect that the ``Experiment`` is running on a Slurm based + machine and set the launcher to `"slurm"`. + + .. group-tab:: PBS Pro + To instantiate the ``QsubBatchSettings`` object, which interfaces with the PBS Pro job scheduler, specify + `launcher="pbs"` when initializing the ``Experiment``. Upon calling ``create_batch_settings``, + SmartSim will detect the job scheduler and return the appropriate batch settings object. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher PBS Pro + exp = Experiment("name-of-experiment", launcher="pbs") + + # Initialize a QsubBatchSettings object + qsub_batch_settings = exp.create_batch_settings(nodes=1, time="10:00:00") + # Set the account for the PBS Pro batch job + qsub_batch_settings.set_account("12345-Cray") + # Set the partition for the PBS Pro batch job + qsub_batch_settings.set_queue("default") + + The initialized ``QsubBatchSettings`` instance can now be passed to a SmartSim entity + (``Model`` or ``Ensemble``) via the `batch_settings` argument in ``create_batch_settings``. + + .. note:: + If `launcher="auto"`, SmartSim will detect that the ``Experiment`` is running on a PBS Pro based + machine and set the launcher to `"pbs"`. + + .. group-tab:: LSF + To instantiate the ``BsubBatchSettings`` object, which interfaces with the LSF job scheduler, specify + `launcher="lsf"` when initializing the ``Experiment``. Upon calling ``create_batch_settings``, + SmartSim will detect the job scheduler and return the appropriate batch settings object. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher LSF + exp = Experiment("name-of-experiment", launcher="lsf") + + # Initialize a BsubBatchSettings object + bsub_batch_settings = exp.create_batch_settings(nodes=1, time="10:00:00", batch_args={"ntasks": 1}) + # Set the account for the lsf batch job + bsub_batch_settings.set_account("12345-Cray") + # Set the partition for the lsf batch job + bsub_batch_settings.set_queue("default") + + The initialized ``BsubBatchSettings`` instance can now be passed to a SmartSim entity + (``Model`` or ``Ensemble``) via the `batch_settings` argument in ``create_batch_settings``. + + .. note:: + If `launcher="auto"`, SmartSim will detect that the ``Experiment`` is running on a LSF based + machine and set the launcher to `"lsf"`. + +.. warning:: + Note that initialization values provided (e.g., `nodes`, `time`, etc) will overwrite the same arguments in `batch_args` if present. \ No newline at end of file diff --git a/doc/changelog.rst b/doc/changelog.rst index 11c4a6da7..a45a30850 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -18,12 +18,15 @@ To be released at some future point in time Description +- SmartSim Documentation refactor - Update the version of Redis from `7.0.4` to `7.2.4` - Update Experiment API typing - Fix publishing of development docs Detailed Notes +- Implemented new structure of SmartSim documentation. Added examples + images and further detail of SmartSim components. - Update Redis version to `7.2.4`. This change fixes an issue in the Redis build scripts causing failures on Apple Silicon hosts. (SmartSim-PR507_) - The container which builds the documentation for every merge to develop @@ -33,6 +36,7 @@ Detailed Notes (SmartSim-PR-PR504_) - Update the generic `t.Any` typehints in Experiment API. (SmartSim-PR501_) +.. _SmartSim-PR463: https://github.com/CrayLabs/SmartSim/pull/463 .. _SmartSim-PR507: https://github.com/CrayLabs/SmartSim/pull/507 .. _SmartSim-PR504: https://github.com/CrayLabs/SmartSim/pull/504 .. _SmartSim-PR501: https://github.com/CrayLabs/SmartSim/pull/501 diff --git a/doc/conf.py b/doc/conf.py index e489fd797..0739047fc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -52,9 +52,11 @@ 'breathe', 'nbsphinx', 'sphinx_copybutton', - 'sphinx_tabs.tabs' + 'sphinx_tabs.tabs', + 'sphinx_design', ] +autodoc_mock_imports = ["smartredis.smartredisPy"] suppress_warnings = ['autosectionlabel'] # Add any paths that contain templates here, relative to this directory. @@ -82,7 +84,6 @@ # a list of builtin themes. html_theme = "sphinx_book_theme" - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". @@ -104,8 +105,31 @@ # white background with dark themes. If sphinx-tabs updates its # static/tabs.css, this may need to be updated. html_css_files = ['custom_tab_style.css'] - autoclass_content = 'both' add_module_names = False nbsphinx_execute = 'never' + +from inspect import getsourcefile + +# Get path to directory containing this file, conf.py. +DOCS_DIRECTORY = os.path.dirname(os.path.abspath(getsourcefile(lambda: 0))) + +def ensure_pandoc_installed(_): + import pypandoc + + # Download pandoc if necessary. If pandoc is already installed and on + # the PATH, the installed version will be used. Otherwise, we will + # download a copy of pandoc into docs/bin/ and add that to our PATH. + pandoc_dir = os.path.join(DOCS_DIRECTORY, "bin") + # Add dir containing pandoc binary to the PATH environment variable + if pandoc_dir not in os.environ["PATH"].split(os.pathsep): + os.environ["PATH"] += os.pathsep + pandoc_dir + pypandoc.ensure_pandoc_installed( + targetfolder=pandoc_dir, + delete_installer=True, + ) + + +def setup(app): + app.connect("builder-inited", ensure_pandoc_installed) \ No newline at end of file diff --git a/doc/ensemble.rst b/doc/ensemble.rst new file mode 100644 index 000000000..93019d18d --- /dev/null +++ b/doc/ensemble.rst @@ -0,0 +1,1214 @@ +.. _ensemble_doc: + +******** +Ensemble +******** +======== +Overview +======== +A SmartSim ``Ensemble`` enables users to run a **group** of computational tasks together in an +``Experiment`` workflow. An ``Ensemble`` is comprised of multiple ``Model`` objects, +where each ``Ensemble`` member (SmartSim ``Model``) represents an individual application. +An ``Ensemble`` can be managed as a single entity and +launched with other :ref:`Model's` and :ref:`Orchestrators` to construct AI-enabled workflows. + +The :ref:`Ensemble API` offers key features, including methods to: + +- :ref:`Attach Configuration Files` for use at ``Ensemble`` runtime. +- :ref:`Load AI Models` (TF, TF-lite, PT, or ONNX) into the ``Orchestrator`` at ``Ensemble`` runtime. +- :ref:`Load TorchScripts` into the ``Orchestrator`` at ``Ensemble`` runtime. +- :ref:`Prevent Data Collisions` within the ``Ensemble``, which allows for reuse of application code. + +To create a SmartSim ``Ensemble``, use the ``Experiment.create_ensemble`` API function. When +initializing an ``Ensemble``, consider one of the **three** creation strategies explained +in the :ref:`Initialization` section. + +SmartSim manages ``Ensemble`` instances through the :ref:`Experiment API` by providing functions to +launch, monitor, and stop applications. + +.. _init_ensemble_strategies: + +============== +Initialization +============== +Overview +======== +The :ref:`Experiment API` is responsible for initializing all workflow entities. +An ``Ensemble`` is created using the ``Experiment.create_ensemble`` factory method, and users can customize the +``Ensemble`` creation via the factory method parameters. + +The factory method arguments for ``Ensemble`` creation can be found in the :ref:`Experiment API` +under the ``create_ensemble`` docstring. + +By using specific combinations of the factory method arguments, users can tailor +the creation of an ``Ensemble`` to align with one of the following creation strategies: + +1. :ref:`Parameter Expansion`: Generate a variable-sized set of unique simulation instances + configured with user-defined input parameters. +2. :ref:`Replica Creation`: Generate a specified number of ``Model`` replicas. +3. :ref:`Manually`: Attach pre-configured ``Model``'s to an ``Ensemble`` to manage as a single unit. + +.. _param_expansion_init: + +Parameter Expansion +=================== +Parameter expansion is a technique that allows users to set parameter values per ``Ensemble`` member. +This is done by specifying input to the `params` and `perm_strategy` factory method arguments during +``Ensemble`` creation (``Experiment.create_ensemble``). Users may control how the `params` values +are applied to the ``Ensemble`` through the `perm_strategy` argument. The `perm_strategy` argument +accepts three values listed below. + +**Parameter Expansion Strategy Options:** + +- `"all_perm"`: Generate all possible parameter permutations for an exhaustive exploration. This + means that every possible combination of parameters will be used in the ``Ensemble``. +- `"step"`: Create parameter sets by collecting identically indexed values across parameter lists. + This allows for discrete combinations of parameters for ``Model``'s. +- `"random"`: Enable random selection from predefined parameter spaces, offering a stochastic approach. + This means that the parameters will be chosen randomly for each ``Model``, which can be useful + for exploring a wide range of possibilities. + +-------- +Examples +-------- +This subsection contains two examples of ``Ensemble`` parameter expansion. The +:ref:`first example` illustrates parameter expansion using two parameters +while the :ref:`second example` demonstrates parameter expansion with two +parameters along with the launch of the ``Ensemble`` as a batch workload. + +.. _param_first_ex: + +Example 1 : Parameter Expansion Using `all_perm` Strategy + + In this example an ``Ensemble`` of four ``Model`` entities is created by expanding two parameters + using the `all_perm` strategy. All of the ``Model``'s in the ``Ensemble`` share the same ``RunSettings`` + and only differ in the value of the `params` assigned to each member. The source code example + is available in the dropdown below for convenient execution and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py + + Begin by initializing a ``RunSettings`` object to apply to + all ``Ensemble`` members: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py + :language: python + :linenos: + :lines: 6-7 + + Next, define the parameters that will be applied to the ``Ensemble``: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py + :language: python + :linenos: + :lines: 9-13 + + Finally, initialize an ``Ensemble`` by specifying the ``RunSettings``, `params` and `perm_strategy="all_perm"`: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py + :language: python + :linenos: + :lines: 15-16 + + By specifying `perm_strategy="all_perm"`, all permutations of the `params` will + be calculated and distributed across ``Ensemble`` members. Here there are four permutations of the `params` values: + + .. code-block:: bash + + ensemble member 1: ["Ellie", 2] + ensemble member 2: ["Ellie", 11] + ensemble member 3: ["John", 2] + ensemble member 4: ["John", 11] + +.. _param_second_ex: + +Example 2 : Parameter Expansion Using `step` Strategy with the ``Ensemble`` Configured For Batch Launching + + In this example an ``Ensemble`` of two ``Model`` entities is created by expanding two parameters + using the `step` strategy. All of the ``Model``'s in the ``Ensemble`` share the same ``RunSettings`` + and only differ in the value of the `params` assigned to each member. Lastly, the ``Ensemble`` is + submitted as a batch workload. The source code example is available in the dropdown below for + convenient execution and customization. + + .. dropdown:: Example Driver Script source code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py + + Begin by initializing and configuring a ``BatchSettings`` object to + run the ``Ensemble`` instance: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py + :language: python + :linenos: + :lines: 6-8 + + The above ``BatchSettings`` object will instruct SmartSim to run the ``Ensemble`` on two + nodes with a timeout of `10 hours`. + + Next initialize a ``RunSettings`` object to apply to all ``Ensemble`` members: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py + :language: python + :linenos: + :lines: 10-12 + + Next, define the parameters to include in ``Ensemble``: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py + :language: python + :linenos: + :lines: 14-18 + + Finally, initialize an ``Ensemble`` by passing in the ``RunSettings``, `params` and `perm_strategy="step"`: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py + :language: python + :linenos: + :lines: 20-21 + + When specifying `perm_strategy="step"`, the `params` sets are created by collecting identically + indexed values across the `param` value lists. + + .. code-block:: bash + + ensemble member 1: ["Ellie", 2] + ensemble member 2: ["John", 11] + +.. _replicas_init: + +Replicas +======== +A replica strategy involves the creation of identical ``Model``'s within an ``Ensemble``. +This strategy is particularly useful for applications that have some inherent randomness. +Users may use the `replicas` factory method argument to create a specified number of identical +``Model`` members during ``Ensemble`` creation (``Experiment.create_ensemble``). + +-------- +Examples +-------- +This subsection contains two examples of using the replicas creation strategy. The +:ref:`first example` illustrates creating four ``Ensemble`` member clones +while the :ref:`second example` demonstrates creating four ``Ensemble`` +member clones along with the launch of the ``Ensemble`` as a batch workload. + +.. _replicas_first_ex: + +Example 1 : ``Ensemble`` creation with replicas strategy + + In this example an ``Ensemble`` of four identical ``Model`` members is created by + specifying the number of clones to create via the `replicas` argument. + All of the ``Model``'s in the ``Ensemble`` share the same ``RunSettings``. + The source code example is available in the dropdown below for convenient execution + and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/replicas_1.py + + To create an ``Ensemble`` of identical ``Model``'s, begin by initializing a ``RunSettings`` + object: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/replicas_1.py + :language: python + :linenos: + :lines: 6-7 + + Initialize the ``Ensemble`` by specifying the ``RunSettings`` object and number of clones to `replicas`: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/replicas_1.py + :language: python + :linenos: + :lines: 9-10 + + By passing in `replicas=4`, four identical ``Ensemble`` members will be initialized. + +.. _replicas_second_ex: + +Example 2 : ``Ensemble`` Creation with Replicas Strategy and ``Ensemble`` Batch Launching + + In this example an ``Ensemble`` of four ``Model`` entities is created by specifying + the number of clones to create via the `replicas` argument. All of the ``Model``'s in + the ``Ensemble`` share the same ``RunSettings`` and the ``Ensemble`` is + submitted as a batch workload. The source code example is available in the dropdown below for + convenient execution and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/replicas_2.py + + To launch the ``Ensemble`` of identical ``Model``'s as a batch job, begin by initializing a ``BatchSettings`` + object: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/replicas_2.py + :language: python + :linenos: + :lines: 6-9 + + The above ``BatchSettings`` object will instruct SmartSim to run the ``Ensemble`` on four + nodes with a timeout of `10 hours`. + + Next, create a ``RunSettings`` object to apply to all ``Model`` replicas: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/replicas_2.py + :language: python + :linenos: + :lines: 10-12 + + Initialize the ``Ensemble`` by specifying the ``RunSettings`` object, ``BatchSettings`` object + and number of clones to `replicas`: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/replicas_2.py + :language: python + :linenos: + :lines: 14-15 + + By passing in `replicas=4`, four identical ``Ensemble`` members will be initialized. + +.. _append_init: + +Manually Append +=============== +Manually appending ``Model``'s to an ``Ensemble`` offers an in-depth level of customization in ``Ensemble`` design. +This approach is favorable when users have distinct requirements for individual ``Model``'s, such as variations +in parameters, run settings, or different types of simulations. + +-------- +Examples +-------- +This subsection contains an example of creating an ``Ensemble`` by manually appending ``Model``'s. +The example illustrates attaching two SmartSim ``Model``'s to the ``Ensemble``. +The ``Ensemble`` is submitted as a batch workload. + +Example 1 : Append ``Model``'s to an ``Ensemble`` and Launch as a Batch Job + + In this example, we append ``Model``'s to an ``Ensemble`` for batch job execution. To do + this, we first initialize an Ensemble with a ``BatchSettings`` object. Then, manually + create ``Model``'s and add each to the ``Ensemble`` using the ``Ensemble.add_model`` function. + The source code example is available in the dropdown below for convenient execution and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py + + To create an empty ``Ensemble`` to append ``Model``'s, initialize the ``Ensemble`` with + a batch settings object: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py + :language: python + :linenos: + :lines: 6-11 + + Next, create the ``Model``'s to append to the ``Ensemble``: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py + :language: python + :linenos: + :lines: 13-20 + + Finally, append the ``Model`` objects to the ``Ensemble``: + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py + :language: python + :linenos: + :lines: 22-25 + + The new ``Ensemble`` is comprised of two appended ``Model`` members. + +.. _attach_files_ensemble: + +===== +Files +===== +Overview +======== +``Ensemble`` members often depend on external files (e.g. training datasets, evaluation datasets, etc) +to operate as intended. Users can instruct SmartSim to copy, symlink, or manipulate external files +prior to an ``Ensemble`` launch via the ``Ensemble.attach_generator_files`` function. Attached files +will be applied to all ``Ensemble`` members. + +.. note:: + Multiple calls to ``Ensemble.attach_generator_files`` will overwrite previous file configurations + on the ``Ensemble``. + +To attach a file to an ``Ensemble`` for use at runtime, provide one of the following arguments to the +``Ensemble.attach_generator_files`` function: + +* `to_copy` (t.Optional[t.List[str]] = None): Files that are copied into the path of the ``Ensemble`` members. +* `to_symlink` (t.Optional[t.List[str]] = None): Files that are symlinked into the path of the ``Ensemble`` members. + A symlink, or symbolic link, is a file that points to another file or directory, allowing you to access that file + as if it were located in the same directory as the symlink. + +To specify a template file in order to programmatically replace specified parameters during generation +of ``Ensemble`` member directories, pass the following value to the ``Ensemble.attach_generator_files`` function: + +* `to_configure` (t.Optional[t.List[str]] = None): This parameter is designed for text-based ``Ensemble`` + member input files. During directory generation for ``Ensemble`` members, the linked files are parsed and replaced with + the `params` values applied to each ``Ensemble`` member. To further explain, the ``Ensemble`` + creation strategy is considered when replacing the tagged parameters in the input files. + These tagged parameters are placeholders in the text that are replaced with the actual + parameter values during the directory generation process. The default tag is a semicolon + (e.g., THERMO = ;THERMO;). + +In the :ref:`Example` subsection, we provide an example using the value `to_configure` +within ``Ensemble.attach_generator_files``. + +.. seealso:: + To add a file to a single ``Model`` that will be appended to an ``Ensemble``, refer to the :ref:`Files` + section of the ``Model`` documentation. + +.. _files_example_doc_ensem: + +Example +======= +This example demonstrates how to attach a text file to an ``Ensemble`` for parameter replacement. +This is accomplished using the `params` function parameter in +the ``Experiment.create_ensemble`` factory function and the `to_configure` function parameter +in ``Ensemble.attach_generator_files``. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/file_attach.py + +In this example, we have a text file named `params_inputs.txt`. Within the text, is the parameter `THERMO` +that is required by each ``Ensemble`` member at runtime: + +.. code-block:: bash + + THERMO = ;THERMO; + +In order to have the tagged parameter `;THERMO;` replaced with a usable value at runtime, two steps are required: + +1. The `THERMO` variable must be included in ``Experiment.create_ensemble`` factory method as + part of the `params` parameter. +2. The file containing the tagged parameter `;THERMO;`, `params_inputs.txt`, must be attached to the ``Ensemble`` + via the ``Ensemble.attach_generator_files`` method as part of the `to_configure` parameter. + +To encapsulate our application within an ``Ensemble``, we must create an ``Experiment`` instance +to gain access to the ``Experiment`` factory method that creates the ``Ensemble``. +Begin by importing the ``Experiment`` module and initializing an ``Experiment``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/file_attach.py + :language: python + :linenos: + :lines: 1-4 + +To create our ``Ensemble``, we are using the `replicas` initialization strategy. +Begin by creating a simple ``RunSettings`` object to specify the path to +the executable simulation as an executable: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/file_attach.py + :language: python + :linenos: + :lines: 6-7 + +Next, initialize an ``Ensemble`` object with ``Experiment.create_ensemble`` +by passing in `ensemble_settings`, `params={"THERMO":1}` and `replicas=2`: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/file_attach.py + :language: python + :linenos: + :lines: 9-10 + +We now have an ``Ensemble`` instance named `example_ensemble`. Attach the above text file +to the ``Ensemble`` for use at entity runtime. To do so, we use the +``Ensemble.attach_generator_files`` function and specify the `to_configure` +parameter with the path to the text file, `params_inputs.txt`: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/file_attach.py + :language: python + :linenos: + :lines: 12-13 + +To create an isolated directory for the ``Ensemble`` member outputs and configuration files, invoke ``Experiment.generate`` via the +``Experiment`` instance `exp` with `example_ensemble` as an input parameter: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/file_attach.py + :language: python + :linenos: + :lines: 15-16 + +After invoking ``Experiment.generate``, the attached generator files will be available for the +application when ``exp.start(example_ensemble)`` is called. + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/file_attach.py + :language: python + :linenos: + :lines: 18-19 + +The contents of `params_inputs.txt` after ``Ensemble`` completion are: + +.. code-block:: bash + + THERMO = 1 + +.. _ensemble_ml_model_script: + +===================== +ML Models and Scripts +===================== +Overview +======== +SmartSim users have the capability to load ML models and TorchScripts into an ``Orchestrator`` +within the ``Experiment`` script for use within ``Ensemble`` members. Functions +accessible through an ``Ensemble`` object support loading ML models (TensorFlow, TensorFlow-lite, +PyTorch, and ONNX) and TorchScripts into standalone or colocated ``Orchestrators`` before +application runtime. + +.. seealso:: + To add an ML model or TorchScript to a single ``Model`` that will be appended to an + ``Ensemble``, refer to the :ref:`ML Models and Scripts` + section of the ``Model`` documentation. + +Depending on the planned storage method of the **ML model**, there are **two** distinct +approaches to load it into the ``Orchestrator``: + +- :ref:`From Memory` +- :ref:`From File` + +.. warning:: + Uploading an ML model :ref:`from memory` is solely supported for + standalone ``Orchestrators``. To upload an ML model to a colocated ``Orchestrator``, users + must save the ML model to disk and upload :ref:`from file`. + +Depending on the planned storage method of the **TorchScript**, there are **three** distinct +approaches to load it into the ``Orchestrator``: + +- :ref:`From Memory` +- :ref:`From File` +- :ref:`From String` + +.. warning:: + Uploading a TorchScript :ref:`from memory` is solely supported for + standalone ``Orchestrators``. To upload a TorchScript to a colocated ``Orchestrator``, users + upload :ref:`from file` or :ref:`from string`. + +Once a ML model or TorchScript is loaded into the ``Orchestrator``, ``Ensemble`` members can +leverage ML capabilities by utilizing the SmartSim client (:ref:`SmartRedis`) +to execute the stored ML models or TorchScripts. + +.. _ai_model_ensemble_doc: + +AI Models +========= +When configuring an ``Ensemble``, users can instruct SmartSim to load +Machine Learning (ML) models dynamically to the ``Orchestrator`` (colocated or standalone). ML models added +are loaded into the ``Orchestrator`` prior to the execution of the ``Ensemble``. To load an ML model +to the ``Orchestrator``, SmartSim users can serialize and provide the ML model **in-memory** or specify the **file path** +via the ``Ensemble.add_ml_model`` function. The supported ML frameworks are TensorFlow, +TensorFlow-lite, PyTorch, and ONNX. + +Users must **serialize TensorFlow ML models** before sending to an ``Orchestrator`` from memory +or from file. To save a TensorFlow model to memory, SmartSim offers the ``serialize_model`` +function. This function returns the TF model as a byte string with the names of the +input and output layers, which will be required upon uploading. To save a TF model to disk, +SmartSim offers the ``freeze_model`` function which returns the path to the serialized +TF model file with the names of the input and output layers. Additional TF model serialization +information and examples can be found in the :ref:`ML Features` section of SmartSim. + +.. note:: + Uploading an ML model from memory is only supported for standalone ``Orchestrators``. + +When attaching an ML model using ``Ensemble.add_ml_model``, the +following arguments are offered to customize storage and execution: + +- `name` (str): name to reference the ML model in the ``Orchestrator``. +- `backend` (str): name of the backend (TORCH, TF, TFLITE, ONNX). +- `model` (t.Optional[str] = None): An ML model in memory (only supported for non-colocated ``Orchestrators``). +- `model_path` (t.Optional[str] = None): serialized ML model. +- `device` (t.Literal["CPU", "GPU"] = "CPU"): name of device for execution, defaults to “CPU”. +- `devices_per_node` (int = 1): The number of GPU devices available on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. +- `first_device` (int = 0): The first GPU device to use on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. +- `batch_size` (int = 0): batch size for execution, defaults to 0. +- `min_batch_size` (int = 0): minimum batch size for ML model execution, defaults to 0. +- `min_batch_timeout` (int = 0): time to wait for minimum batch size, defaults to 0. +- `tag` (str = ""): additional tag for ML model information, defaults to “”. +- `inputs` (t.Optional[t.List[str]] = None): ML model inputs (TF only), defaults to None. +- `outputs` (t.Optional[t.List[str]] = None): ML model outputs (TF only), defaults to None. + +.. seealso:: + To add an ML model to a single ``Model`` that will be appended to an + ``Ensemble``, refer to the :ref:`AI Models` + section of the ``Model`` documentation. + +.. _in_mem_ML_model_ensemble_ex: + +------------------------------------- +Example: Attach an In-Memory ML Model +------------------------------------- +This example demonstrates how to attach an in-memory ML model to a SmartSim ``Ensemble`` +to load into an ``Orchestrator`` at ``Ensemble`` runtime. The source code example is +available in the dropdown below for convenient execution and customization. + +.. dropdown:: Experiment Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_mem.py + +.. note:: + This example assumes: + + - an ``Orchestrator`` is launched prior to the ``Ensemble`` execution + - an initialized ``Ensemble`` named `ensemble_instance` exists within the ``Experiment`` workflow + - a Tensorflow-based ML model was serialized using ``serialize_model`` which returns the + ML model as a byte string with the names of the input and output layers + +**Attach the ML Model to a SmartSim Ensemble** + +In this example, we have a serialized Tensorflow-based ML model that was saved to a byte string stored under `model`. +Additionally, the ``serialize_model`` function returned the names of the input and output layers stored under +`inputs` and `outputs`. Assuming an initialized ``Ensemble`` named `ensemble_instance` exists, we add the byte string TensorFlow model using +``Ensemble.add_ml_model``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_mem.py + :language: python + :linenos: + :lines: 39-40 + +In the above ``ensemble_instance.add_ml_model`` code snippet, we offer the following arguments: + +- `name` ("cnn"): A name to reference the ML model in the ``Orchestrator``. +- `backend` ("TF"): Indicating that the ML model is a TensorFlow model. +- `model` (model): The in-memory representation of the TensorFlow model. +- `device` ("GPU"): Specifying the device for ML model execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. +- `inputs` (inputs): The name of the ML model input nodes (TensorFlow only). +- `outputs` (outputs): The name of the ML model output nodes (TensorFlow only). + +.. warning:: + Calling `exp.start(ensemble_instance)` prior to the launch of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent standalone ``Orchestrator``. + +When the ``Ensemble`` is started via ``Experiment.start``, the ML model will be loaded to the +launched standalone ``Orchestrator``. The ML model can then be executed on the ``Orchestrator`` via a SmartSim +client (:ref:`SmartRedis`) within the application code. + +.. _from_file_ML_model_ensemble_ex: + +------------------------------------- +Example: Attach an ML Model From File +------------------------------------- +This example demonstrates how to attach a ML model from file to a SmartSim ``Ensemble`` +to load into an ``Orchestrator`` at ``Ensemble`` runtime. The source code example is +available in the dropdown below for convenient execution and customization. + +.. dropdown:: Experiment Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_file.py + +.. note:: + This example assumes: + + - a standalone ``Orchestrator`` is launched prior to ``Ensemble`` execution + - an initialized ``Ensemble`` named `ensemble_instance` exists within the ``Experiment`` workflow + - a Tensorflow-based ML model was serialized using ``freeze_model`` which returns the + the path to the serialized model file and the names of the input and output layers + +**Attach the ML Model to a SmartSim Ensemble** + +In this example, we have a serialized Tensorflow-based ML model that was saved to disk and stored under `model`. +Additionally, the ``freeze_model`` function returned the names of the input and output layers stored under +`inputs` and `outputs`. Assuming an initialized ``Ensemble`` named `ensemble_instance` exists, we add a TensorFlow model using +the ``Ensemble.add_ml_model`` function and specify the ML model path to the parameter `model_path`: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_file.py + :language: python + :linenos: + :lines: 39-40 + +In the above ``ensemble_instance.add_ml_model`` code snippet, we offer the following arguments: + +- `name` ("cnn"): A name to reference the ML model in the ``Orchestrator``. +- `backend` ("TF"): Indicating that the ML model is a TensorFlow model. +- `model_path` (model_file): The path to the ML model script. +- `device` ("GPU"): Specifying the device for ML model execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. +- `inputs` (inputs): The name of the ML model input nodes (TensorFlow only). +- `outputs` (outputs): The name of the ML model output nodes (TensorFlow only). + +.. warning:: + Calling `exp.start(ensemble_instance)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent ``Orchestrator``. + +When the ``Ensemble`` is started via ``Experiment.start``, the ML model will be loaded to the +launched ``Orchestrator``. The ML model can then be executed on the ``Orchestrator`` via a SmartSim +client (:ref:`SmartRedis`) within the application executable. + +.. _TS_ensemble_doc: + +TorchScripts +============ +When configuring an ``Ensemble``, users can instruct SmartSim to load TorchScripts dynamically +to the ``Orchestrator``. The TorchScripts become available for each ``Ensemble`` member upon being loaded +into the ``Orchestrator`` prior to the execution of the ``Ensemble``. SmartSim users may upload +a single TorchScript function via ``Ensemble.add_function`` or alternatively upload a script +containing multiple functions via ``Ensemble.add_script``. To load a TorchScript to the +``Orchestrator``, SmartSim users can follow one of the following processes: + +- :ref:`Define a TorchScript Function In-Memory` + Use the ``Ensemble.add_function`` to instruct SmartSim to load an in-memory TorchScript to the ``Orchestrator``. +- :ref:`Define Multiple TorchScript Functions From File` + Provide file path to ``Ensemble.add_script`` to instruct SmartSim to load the TorchScript from file to the ``Orchestrator``. +- :ref:`Define a TorchScript Function as String` + Provide function string to ``Ensemble.add_script`` to instruct SmartSim to load a raw string as a TorchScript function to the ``Orchestrator``. + +.. note:: + Uploading a TorchScript :ref:`from memory` using ``Ensemble.add_function`` + is only supported for standalone ``Orchestrators``. Users uploading + TorchScripts to colocated ``Orchestrators`` should instead use the function ``Ensemble.add_script`` + to upload :ref:`from file` or as a :ref:`string`. + +Each function also provides flexible device selection, allowing users to choose between which device the TorchScript is executed on, `"GPU"` or `"CPU"`. +In environments with multiple devices, specific device numbers can be specified using the +`devices_per_node` parameter. + +.. note:: + If `device=GPU` is specified when attaching a TorchScript function to an ``Ensemble``, this instructs + SmartSim to execute the TorchScript on GPU nodes. However, TorchScripts loaded to an ``Orchestrator`` are + executed on the ``Orchestrator`` compute resources. Therefore, users must make sure that the device + specified is included in the ``Orchestrator`` compute resources. To further explain, if a user + specifies `device=GPU`, however, initializes ``Orchestrator`` on only CPU nodes, + the TorchScript will not run on GPU nodes as advised. + +Continue or select the respective process link to learn more on how each function (``Ensemble.add_script`` and ``Ensemble.add_function``) +dynamically loads TorchScripts to the ``Orchestrator``. + +.. seealso:: + To add a TorchScript to a single ``Model`` that will be appended to an + ``Ensemble``, refer to the :ref:`TorchScripts` + section of the ``Model`` documentation. + +.. _in_mem_TF_ensemble_doc: + +------------------------------- +Attach an In-Memory TorchScript +------------------------------- +Users can define TorchScript functions within the ``Experiment`` driver script +to attach to an ``Ensemble``. This feature is supported by ``Ensemble.add_function``. + +.. warning:: + ``Ensemble.add_function`` does **not** support loading in-memory TorchScript functions to a colocated ``Orchestrator``. + If you would like to load a TorchScript function to a colocated ``Orchestrator``, define the function + as a :ref:`raw string` or :ref:`load from file`. + +When specifying an in-memory TF function using ``Ensemble.add_function``, the +following arguments are offered: + +- `name` (str): reference name for the script inside of the ``Orchestrator``. +- `function` (t.Optional[str] = None): TorchScript function code. +- `device` (t.Literal["CPU", "GPU"] = "CPU"): device for script execution, defaults to “CPU”. +- `devices_per_node` (int = 1): The number of GPU devices available on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. +- `first_device` (int = 0): The first GPU device to use on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. + +.. _in_mem_TF_ex: + +Example: Load a In-Memory TorchScript Function +---------------------------------------------- +This example walks through the steps of instructing SmartSim to load an in-memory TorchScript function +to a standalone ``Orchestrator``. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Experiment Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py + +.. note:: + The example assumes: + + - a standalone ``Orchestrator`` is launched prior to ``Ensemble`` execution + - an initialized ``Ensemble`` named `ensemble_instance` exists within the ``Experiment`` workflow + +**Define an In-Memory TF Function** + +To begin, define an in-memory TorchScript function within the Python driver script. +For the purpose of the example, we add a simple TorchScript function, `timestwo`: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py + :language: python + :linenos: + :lines: 3-4 + +**Attach the In-Memory TorchScript Function to a SmartSim Ensemble** + +We use the ``Ensemble.add_function`` function to instruct SmartSim to load the TorchScript function `timestwo` +onto the launched standalone ``Orchestrator``. Specify the function `timestwo` to the `function` +parameter: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py + :language: python + :linenos: + :lines: 15-16 + +In the above ``ensemble_instance.add_function`` code snippet, we offer the following arguments: + +- `name` ("example_func"): A name to uniquely identify the TorchScript within the ``Orchestrator``. +- `function` (timestwo): Name of the TorchScript function defined in the Python driver script. +- `device` ("GPU"): Specifying the device for TorchScript execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. + +.. warning:: + Calling `exp.start(ensemble_instance)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the TorchScript to a non-existent ``Orchestrator``. + +When the ``Ensemble`` is started via ``Experiment.start``, the TF function will be loaded to the +standalone ``Orchestrator``. The function can then be executed on the ``Orchestrator`` via a SmartSim +client (:ref:`SmartRedis`) within the application code. + +.. _TS_from_file_ensemble: + +------------------------------ +Attach a TorchScript From File +------------------------------ +Users can attach TorchScript functions from a file to an ``Ensemble`` and upload them to a +colocated or standalone ``Orchestrator``. This functionality is supported by the ``Ensemble.add_script`` +function's `script_path` parameter. + +When specifying a TorchScript using ``Ensemble.add_script``, the +following arguments are offered: + +- `name` (str): Reference name for the script inside of the ``Orchestrator``. +- `script` (t.Optional[str] = None): TorchScript code (only supported for non-colocated ``Orchestrators``). +- `script_path` (t.Optional[str] = None): path to TorchScript code. +- `device` (t.Literal["CPU", "GPU"] = "CPU"): device for script execution, defaults to “CPU”. +- `devices_per_node` (int = 1): The number of GPU devices available on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. +- `first_device` (int = 0): The first GPU device to use on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. + +Example: Loading a TorchScript From File +---------------------------------------- +This example walks through the steps of instructing SmartSim to load a TorchScript from file +to an ``Orchestrator``. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Experiment Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_file.py + +.. note:: + This example assumes: + + - an ``Orchestrator`` is launched prior to ``Ensemble`` execution + - an initialized ``Ensemble`` named `ensemble_instance` exists within the ``Experiment`` workflow + +**Define a TorchScript Script** + +For the example, we create the Python script `torchscript.py`. The file contains multiple +simple torch function shown below: + +.. code-block:: python + + def negate(x): + return torch.neg(x) + + def random(x, y): + return torch.randn(x, y) + + def pos(z): + return torch.positive(z) + +**Attach the TorchScript Script to a SmartSim Ensemble** + +Assuming an initialized ``Ensemble`` named `ensemble_instance` exists, we add a TorchScript script using +the ``Ensemble.add_script`` function and specify the script path to the parameter `script_path`: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py + :language: python + :linenos: + :lines: 12-13 + +In the above ``smartsim_model.add_script`` code snippet, we offer the following arguments: + +- `name` ("example_script"): Reference name for the script inside of the ``Orchestrator``. +- `script_path` ("path/to/torchscript.py"): Path to the script file. +- `device` ("GPU"): device for script execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. + +.. warning:: + Calling `exp.start(ensemble_instance)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent ``Orchestrator``. + +When `ensemble_instance` is started via ``Experiment.start``, the TorchScript will be loaded from file to the +``Orchestrator`` that is launched prior to the start of `ensemble_instance`. + +.. _TS_raw_string_ensemble: + +--------------------------------- +Define TorchScripts as Raw String +--------------------------------- +Users can upload TorchScript functions from string to send to a colocated or +standalone ``Orchestrator``. This feature is supported by the +``Ensemble.add_script`` function's `script` parameter. + +When specifying a TorchScript using ``Ensemble.add_script``, the +following arguments are offered: + +- `name` (str): Reference name for the script inside of the ``Orchestrator``. +- `script` (t.Optional[str] = None): String of function code (e.g. TorchScript code string). +- `script_path` (t.Optional[str] = None): path to TorchScript code. +- `device` (t.Literal["CPU", "GPU"] = "CPU"): device for script execution, defaults to “CPU”. +- `devices_per_node` (int = 1): The number of GPU devices available on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. +- `first_device` (int = 0): The first GPU device to use on the host. This parameter only applies to GPU devices and will be ignored if device is specified as CPU. + +Example: Load a TorchScript From String +--------------------------------------- +This example walks through the steps of instructing SmartSim to load a TorchScript function +from string to an ``Orchestrator`` before the execution of the associated ``Ensemble``. +The source code example is available in the dropdown below for convenient execution and customization. + +.. dropdown:: Experiment Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_string.py + +.. note:: + This example assumes: + + - an ``Orchestrator`` is launched prior to ``Ensemble`` execution + - an initialized ``Ensemble`` named `ensemble_instance` exists within the ``Experiment`` workflow + +**Define a String TorchScript** + +Define the TorchScript code as a variable in the Python driver script: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_string.py + :language: python + :linenos: + :lines: 12-13 + +**Attach the TorchScript Function to a SmartSim Ensemble** + +Assuming an initialized ``Ensemble`` named `ensemble_instance` exists, we add a TorchScript using +the ``Ensemble.add_script`` function and specify the variable `torch_script_str` to the parameter +`script`: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_string.py + :language: python + :linenos: + :lines: 15-16 + +In the above ``ensemble_instance.add_script`` code snippet, we offer the following arguments: + +- `name` ("example_script"): key to store script under. +- `script` (torch_script_str): TorchScript code. +- `device` ("GPU"): device for script execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. + +.. warning:: + Calling `exp.start(ensemble_instance)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent ``Orchestrator``. + +When the ``Ensemble`` is started via ``Experiment.start``, the TorchScript will be loaded to the +``Orchestrator`` that is launched prior to the start of the ``Ensemble``. + +.. _prefix_ensemble: + +========================= +Data Collision Prevention +========================= +Overview +======== +When multiple ``Ensemble`` members use the same code to send and access their respective data +in the ``Orchestrator``, key overlapping can occur, leading to inadvertent data access +between ``Ensemble`` members. To address this, SmartSim supports key prefixing +through ``Ensemble.enable_key_prefixing`` which enables key prefixing for all +``Ensemble`` members. For example, during an ``Ensemble`` simulation with prefixing enabled, SmartSim will add +the ``Ensemble`` member `name` as a prefix to the keys sent to the ``Orchestrator``. +Enabling key prefixing eliminates issues related to key overlapping, allowing ``Ensemble`` +members to use the same code without issue. + +The key components of SmartSim ``Ensemble`` prefixing functionality include: + +1. **Sending Data to the Orchestrator**: Users can send data to an ``Orchestrator`` + with the ``Ensemble`` member name prepended to the data name by utilizing SmartSim :ref:`Ensemble functions`. +2. **Retrieving Data From the Orchestrator**: Users can instruct a ``Client`` to prepend a + ``Ensemble`` member name to a key during data retrieval, polling, or check for existence on the ``Orchestrator`` + through SmartRedis :ref:`Client functions`. However, entity interaction + must be registered using :ref:`Ensemble` or :ref:`Model` functions. + +.. seealso:: + For information on prefixing ``Client`` functions, visit the :ref:`Client functions` page of the ``Model`` + documentation. + +For example, assume you have an ``Ensemble`` that was initialized using the :ref:`replicas` creation strategy. +Two identical ``Model`` were created named `ensemble_0` and `ensemble_1` that use the same executable application +within an ``Ensemble`` named `ensemble`. In the application code you use the function ``Client.put_tensor("tensor_0", data)``. +Without key prefixing enabled, the slower member will overwrite the data from the faster simulation. +With ``Ensemble`` key prefixing turned on, `ensemble_0` and `ensemble_1` can access +their tensor `"tensor_0"` by name without overwriting or accessing the other ``Model``'s `"tensor_0"` tensor. +In this scenario, the two tensors placed in the ``Orchestrator`` are named `ensemble_0.tensor_0` and `ensemble_1.tensor_0`. + +.. _model_prefix_func_ensemble: + +------------------ +Ensemble Functions +------------------ +An ``Ensemble`` object supports two prefixing functions: ``Ensemble.enable_key_prefixing`` and +``Ensemble.register_incoming_entity``. For more information on each function, reference the +:ref:`Ensemble API docs`. + +To enable prefixing on a ``Ensemble``, users must use the ``Ensemble.enable_key_prefixing`` +function in the ``Experiment`` driver script. This function activates prefixing for tensors, +``Datasets``, and lists sent to an ``Orchestrator`` for all ``Ensemble`` members. This function +also enables access to prefixing ``Client`` functions within the ``Ensemble`` members. This excludes +the ``Client.set_data_source`` function, where ``enable_key_prefixing`` is not require for access. + +.. note:: + ML model and script prefixing is not automatically enabled through ``Ensemble.enable_key_prefixing``. + Prefixing must be enabled within the ``Ensemble`` by calling the ``use_model_ensemble_prefix`` method + on the ``Client`` embedded within the member application. + +Users can enable the SmartRedis ``Client`` to interact with prefixed data, ML models and TorchScripts +using the ``Client.set_data_source``. However, for SmartSim to recognize the producer entity name +passed to the function within an application, the producer entity must be registered on the consumer +entity using ``Ensemble.register_incoming_entity``. + +If a consumer ``Ensemble`` member requests data sent to the ``Orchestrator`` by other ``Ensemble`` members, the producer members must be +registered on consumer member. To access ``Ensemble`` members, SmartSim offers the attribute ``Ensemble.models`` that returns +a list of ``Ensemble`` members. Below we demonstrate registering producer members on a consumer member: + +.. code-block:: python + + # list of producer Ensemble members + list_of_ensemble_names = ["producer_0", "producer_1", "producer_2"] + + # Grab the consumer Ensemble member + ensemble_member = ensemble.models.get("producer_3") + # Register the producer members on the consumer member + for name in list_of_ensemble_names: + ensemble_member.register_incoming_entity(ensemble.models.get(name)) + +For examples demonstrating how to retrieve data within the entity application that produced +the data, visit the ``Model`` :ref:`Copy/Rename/Delete Operations` subsection. + +Example: Ensemble Key Prefixing +=============================== +In this example, we create an ``Ensemble`` comprised of two ``Model``'s that use identical code +to send data to a standalone ``Orchestrator``. To prevent key collisions and ensure data +integrity, we enable key prefixing on the ``Ensemble`` which automatically +appends the ``Ensemble`` member `name` to the data sent to the ``Orchestrator``. After the +``Ensemble`` completes, we launch a consumer ``Model`` within the ``Experiment`` driver script +to demonstrate accessing prefixed data sent to the ``Orchestrator`` by ``Ensemble`` members. + +This example consists of **three** Python scripts: + +1. :ref:`Application Producer Script`: This script is encapsulated + in a SmartSim ``Ensemble`` within the ``Experiment`` driver script. Prefixing is enabled + on the ``Ensemble``. The producer script puts NumPy tensors on an ``Orchestrator`` + launched in the ``Experiment`` driver script. The ``Ensemble`` creates two + identical ``Ensemble`` members. The producer script is executed + in both ``Ensemble`` members to send two prefixed tensors to the ``Orchestrator``. + The source code example is available in the dropdown below for convenient customization. + +.. dropdown:: Application Producer Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/application_producer_script.py + +1. :ref:`Application Consumer Script`: This script is encapsulated + within a SmartSim ``Model`` in the ``Experiment`` driver script. The script requests the + prefixed tensors placed by the producer script. The source code example is available in + the dropdown below for convenient customization. + +.. dropdown:: Application Consumer Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py + +1. :ref:`Experiment Driver Script`: The driver script launches the + ``Orchestrator``, the ``Ensemble`` (which sends prefixed keys to the ``Orchestrator``), + and the ``Model`` (which requests prefixed keys from the ``Orchestrator``). The + ``Experiment`` driver script is the centralized spot that controls the workflow. + The source code example is available in the dropdown below for convenient execution and + customization. + +.. dropdown:: Experiment Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + +.. _app_prod_prefix_ensemble: + +------------------------------- +The Application Producer Script +------------------------------- +In the ``Experiment`` driver script, we instruct SmartSim to create an ``Ensemble`` comprised of +two duplicate members that execute this producer script. In the producer script, a SmartRedis ``Client`` sends a +tensor to the ``Orchestrator``. Since the ``Ensemble`` members are identical and therefore use the same +application code, two tensors are sent to the ``Orchestrator``. Without prefixing enabled on the ``Ensemble`` +the keys can be overwritten. To prevent this, we enable key prefixing on the ``Ensemble`` in the driver script +via ``Ensemble.enable_key_prefixing``. When the producer script is executed by each ``Ensemble`` member, a +tensor is sent to the ``Orchestrator`` with the ``Ensemble`` member `name` prepended to the tensor `name`. + +Here we provide the producer script that is applied to the ``Ensemble`` members: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/application_producer_script.py + :language: python + :linenos: + +After the completion of ``Ensemble`` members `producer_0` and `producer_1`, the contents of the ``Orchestrator`` are: + +.. code-block:: bash + + 1) "producer_0.tensor" + 2) "producer_1.tensor" + +.. _app_con_prefix_ensemble: + +------------------------------- +The Application Consumer Script +------------------------------- +In the ``Experiment`` driver script, we initialize a consumer ``Model`` that encapsulates +the consumer application to request the tensors produced from the ``Ensemble``. To do +so, we use SmartRedis key prefixing functionality to instruct the SmartRedis ``Client`` +to append the name of an ``Ensemble`` member to the key `name`. + +.. seealso:: + For more information on ``Client`` prefixing functions, visit the :ref:`Client functions` + subsection of the ``Model`` documentation. + +To begin, specify the imports and initialize a SmartRedis ``Client``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py + :language: python + :linenos: + :lines: 1-4 + +To retrieve the tensor from the first ``Ensemble`` member named `producer_0`, use +``Client.set_data_source``. Specify the name of the first ``Ensemble`` member +as an argument to the function. This instructs SmartSim to append the ``Ensemble`` member name to the data +search on the ``Orchestrator``. When ``Client.poll_tensor`` is executed, +the SmartRedis `client` will poll for key, `producer_0.tensor`: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py + :language: python + :linenos: + :lines: 6-9 + +Follow the same steps above, however, change the data source `name` to the `name` +of the second ``Ensemble`` member (`producer_1`): + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py + :language: python + :linenos: + :lines: 11-14 + +We print the boolean return to verify that the tensors were found: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py + :language: python + :linenos: + :lines: 16-17 + +When the ``Experiment`` driver script is executed, the following output will appear in `consumer.out`: + +.. code-block:: bash + + Default@11-46-05:producer_0.tensor was found: True + Default@11-46-05:producer_1.tensor was found: True + +.. warning:: + For SmartSim to recognize the ``Ensemble`` member names as a valid data source + to ``Client.set_data_source``, you must register each ``Ensemble`` member + on the consumer ``Model`` in the driver script via ``Model.register_incoming_entity``. + We demonstrate this in the ``Experiment`` driver script section of the example. + +.. _exp_prefix_ensemble: + +--------------------- +The Experiment Script +--------------------- +The ``Experiment`` driver script manages all workflow components and utilizes the producer and consumer +application scripts. In the example, the ``Experiment``: + +- launches standalone ``Orchestrator`` +- launches an ``Ensemble`` via the replicas initialization strategy +- launches a consumer ``Model`` +- clobbers the ``Orchestrator`` + +To begin, add the necessary imports, initialize an ``Experiment`` instance and initialize the +standalone ``Orchestrator``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 1-9 + +We are now setup to discuss key prefixing within the ``Experiment`` driver script. +To create an ``Ensemble`` using the replicas strategy, begin by initializing a ``RunSettings`` +object to apply to all ``Ensemble`` members. Specify the path to the application +producer script: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 11-12 + +Next, initialize an ``Ensemble`` by specifying `ensemble_settings` and the number of ``Model`` `replicas` to create: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 14-15 + +Instruct SmartSim to prefix all tensors sent to the ``Orchestrator`` from the ``Ensemble`` via ``Ensemble.enable_key_prefixing``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 17-18 + +Next, initialize the consumer ``Model``. The consumer ``Model`` application requests +the prefixed tensors produced by the ``Ensemble``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 20-23 + +Next, organize the SmartSim entity output files into a single ``Experiment`` folder: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 25-26 + +Launch the ``Orchestrator``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 28-29 + +Launch the ``Ensemble``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 31-32 + +Set `block=True` so that ``Experiment.start`` waits until the last ``Ensemble`` member has finished before continuing. + +The consumer ``Model`` application script uses ``Client.set_data_source`` which +accepts the ``Ensemble`` member names when searching for prefixed +keys in the ``Orchestrator``. In order for SmartSim to recognize the ``Ensemble`` +member names as a valid data source in the consumer ``Model``, we must register +the entity interaction: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 34-36 + +Launch the consumer ``Model``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 38-39 + +To finish, tear down the standalone ``Orchestrator``: + +.. literalinclude:: tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py + :language: python + :linenos: + :lines: 41-42 \ No newline at end of file diff --git a/doc/experiment.rst b/doc/experiment.rst index 986db4cad..9936f49a9 100644 --- a/doc/experiment.rst +++ b/doc/experiment.rst @@ -1,326 +1,356 @@ - *********** Experiments *********** +======== +Overview +======== +SmartSim helps automate the deployment of AI-enabled workflows on HPC systems. With SmartSim, users +can describe and launch combinations of applications and AI/ML infrastructure to produce novel and +scalable workflows. SmartSim supports launching these workflows on a diverse set of systems, including +local environments such as Mac or Linux, as well as HPC job schedulers (e.g. Slurm, PBS Pro, and LSF). -The Experiment acts as both a factory class for constructing the stages of an -experiment (``Model``, ``Ensemble``, ``Orchestrator``, etc.) as well as an -interface to interact with the entities created by the experiment. - -Users can initialize an :ref:`Experiment ` at the beginning of a -Jupyter notebook, interactive python session, or Python file and use the -``Experiment`` to iteratively create, configure and launch computational kernels -on the system through the specified launcher. - -.. |SmartSim Architecture| image:: images/ss-arch-overview.png - :width: 700 - :alt: Alternative text - -|SmartSim Architecture| - - -The interface was designed to be simple, with as little complexity as possible, -and agnostic to the backend launching mechanism (local, Slurm, PBSPro, etc.). - -Model -===== - -``Model(s)`` are subclasses of ``SmartSimEntity(s)`` and are created through the -Experiment API. Models represent any computational kernel. Models are flexible -enough to support many different applications, however, to be used with our -clients (SmartRedis) the application will have to be written in Python, C, C++, -or Fortran. +The ``Experiment`` API is SmartSim's top level API that provides users with methods for creating, combining, +configuring, launching and monitoring :ref:`entities` in an AI-enabled workflow. More specifically, the +``Experiment`` API offers three customizable workflow components that are created and initialized via factory +methods: -Models are given :ref:`RunSettings ` objects that specify how a kernel -should be executed with regard to the workload manager (e.g. Slurm) and the -available compute resources on the system. +* :ref:`Orchestrator` +* :ref:`Model` +* :ref:`Ensemble` -Each launcher supports specific types of ``RunSettings``. +Settings are given to ``Model`` and ``Ensemble`` objects to provide parameters for how the job should be executed. The +:ref:`Experiment API` offers two customizable Settings objects that are created via the factory methods: - - :ref:`SrunSettings ` for Slurm - - :ref:`AprunSettings ` for PBSPro - - :ref:`MpirunSettings ` for OpenMPI with `mpirun` on PBSPro, LSF, and Slurm - - :ref:`JsrunSettings ` for LSF +* :ref:`RunSettings` +* :ref:`BatchSettings` -These settings can be manually specified by the user, or auto-detected by the -SmartSim Experiment through the ``Experiment.create_run_settings`` method. +Once a workflow component is initialized (e.g. ``Orchestrator``, ``Model`` or ``Ensemble``), a user has access +to the associated entity API which supports configuring and retrieving the entities' information: -A simple example of using the Experiment API to create a model and run it -locally: +* :ref:`Orchestrator API` +* :ref:`Model API` +* :ref:`Ensemble API` -.. code-block:: Python +There is no limit to the number of SmartSim entities a user can +initialize within an ``Experiment``. - from smartsim import Experiment +.. figure:: images/Experiment.png - exp = Experiment("simple", launcher="local") + Sample ``Experiment`` showing a user application leveraging + machine learning infrastructure launched by SmartSim and connected + to online analysis and visualization via the in-memory ``Orchestrator``. - settings = exp.create_run_settings("echo", exe_args="Hello World") - model = exp.create_model("hello_world", settings) +Find an example of the ``Experiment`` class and factory methods used within a +workflow in the :ref:`Example` section of this page. - exp.start(model, block=True) - print(exp.get_status(model)) +.. _launcher_exp_docs: -If the launcher has been specified, or auto-detected through setting -``launcher=auto`` in the Experiment initialization, the ``create_run_settings`` -method will automatically create the appropriate ``RunSettings`` object and -return it. +========= +Launchers +========= +SmartSim supports launching AI-enabled workflows on a wide variety of systems, including locally on a Mac or +Linux machine or on HPC machines with a job scheduler (e.g. Slurm, PBS Pro, and LSF). When creating a SmartSim +``Experiment``, the user has the opportunity to specify the `launcher` type or defer to automatic `launcher` selection. +`Launcher` selection determines how SmartSim translates entity configurations into system calls to launch, +manage, and monitor. Currently, SmartSim supports 5 `launchers`: -For example with Slurm +1. ``local`` **[default]**: for single-node, workstation, or laptop +2. ``slurm``: for systems using the Slurm scheduler +3. ``pbs``: for systems using the PBS Pro scheduler +4. ``pals``: for systems using the PALS scheduler +5. ``lsf``: for systems using the LSF scheduler +6. ``auto``: have SmartSim auto-detect the launcher to use -.. code-block:: Python +If the systems `launcher` cannot be found or no `launcher` argument is provided, the default value of +`"local"` will be assigned which will start all ``Experiment`` launched entities on the +localhost. - from smartsim import Experiment +For examples specifying a `launcher` during ``Experiment`` initialization, navigate to the +``Experiment`` :ref:`__init__ special method` in the ``Experiment`` API docstring. - exp = Experiment("hello_world_exp", launcher="slurm") - srun = exp.create_run_settings(exe="echo", exe_args="Hello World!") +.. _entities_exp_docs: - # helper methods for configuring run settings are available in - # each of the implementations of RunSettings - srun.set_nodes(1) - srun.set_tasks(32) +======== +Entities +======== +Entities are SmartSim API objects that can be launched and +managed on the compute system through the ``Experiment`` API. +The SmartSim entities include: + +* ``Orchestrator`` +* ``Model`` +* ``Ensemble`` + +While the ``Experiment`` object is intended to be instantiated once in the +Python driver script, there is no limit to the number of SmartSim entities +within the ``Experiment``. In the following subsections, we define the +general purpose of the three entities that can be created through the +``Experiment``. + +To create a reference to a newly instantiated entity object, use the +associated ``Experiment.create_...`` factory method shown below. + +.. list-table:: Experiment API Entity Creation + :widths: 20 65 25 + :header-rows: 1 + + * - Factory Method + - Example + - Return Type + * - ``create_database`` + - ``orch = exp.create_database([port, db_nodes, ...])`` + - :ref:`Orchestrator ` + * - ``create_model`` + - ``model = exp.create_model(name, run_settings)`` + - :ref:`Model ` + * - ``create_ensemble`` + - ``ensemble = exp.create_ensemble(name[, params, ...])`` + - :ref:`Ensemble ` + +After initialization, each entity can be started, monitored, and stopped using +the ``Experiment`` post-creation methods. + +.. list-table:: Interact with Entities During the Experiment + :widths: 25 55 25 + :header-rows: 1 + + * - Factory Method + - Example + - Desc + * - ``start`` + - ``exp.start(*args[, block, summary, ...])`` + - Launch an Entity + * - ``stop`` + - ``exp.stop(*args)`` + - Stop an Entity + * - ``get_status`` + - ``exp.get_status(*args)`` + - Retrieve Entity Status + +.. _orchestrator_exp_docs: + +Orchestrator +============ +The :ref:`Orchestrator` is an in-memory database built for +a wide variety of AI-enabled workflows. The ``Orchestrator`` can be thought of as a general +feature store for numerical data, ML models, and scripts. The ``Orchestrator`` is capable +of performing inference and script evaluation using data in the feature store. +Any SmartSim ``Model`` or ``Ensemble`` member can connect to the +``Orchestrator`` via the :ref:`SmartRedis` +``Client`` library to transmit data, execute ML models, and execute scripts. + +**SmartSim Offers Two Types of Orchestrator Deployments:** + +* :ref:`Standalone Orchestrator Deployment` +* :ref:`Colocated Orchestrator Deployment` + +To create a standalone ``Orchestrator`` that does not share compute resources with other +SmartSim entities, use the ``Experiment.create_database`` factory method which +returns an ``Orchestrator`` object. To create a colocated ``Orchestrator`` that +shares compute resources with a ``Model``, use the ``Model.colocate_db_tcp`` +or ``Model.colocate_db_uds`` member functions accessible after a +``Model`` object has been initialized. The functions instruct +SmartSim to launch an ``Orchestrator`` on the application compute nodes. An ``Orchestrator`` object is not +returned from a ``Model.colocate_db`` instruction, and subsequent interactions with the +colocated ``Orchestrator`` are handled through the :ref:`Model API`. + +SmartSim supports :ref:`multi-database` functionality, enabling an ``Experiment`` to have +several concurrently launched ``Orchestrator(s)``. If there is a need to launch more than +one ``Orchestrator``, the ``Experiment.create_database`` and ``Model.colocate..`` +functions mandate the specification of a unique ``Orchestrator`` identifier, denoted +by the `db_identifier` argument for each ``Orchestrator``. The `db_identifier` is used +in an application script by a SmartRedis ``Client`` to connect to a specific ``Orchestrator``. + +.. _model_exp_docs: - model = exp.create_model("hello_world", srun) - exp.start(model, block=True, summary=True) +Model +===== +:ref:`Model(s)` represent a simulation model or any computational kernel, +including applications, scripts, or generally, a program. They can +interact with other SmartSim entities via data transmitted to/from +SmartSim ``Orchestrator(s)`` using a SmartRedis ``Client``. - print(exp.get_status(model)) +A ``Model`` is created through the factory method: ``Experiment.create_model``. +``Model(s)`` are initialized with ``RunSettings`` objects that specify +how a ``Model`` should be launched by a workload manager +(e.g., Slurm) and the compute resources required. +Optionally, the user may also specify a ``BatchSettings`` object if +the ``Model`` should be launched as a batch job on the WLM system. +The ``create_model`` factory method returns an initialized ``Model`` object that +gives you access to functions associated with the :ref:`Model API`. -The above will run ``srun -n 32 -N 1 echo Hello World!``, monitor its -execution, and inform the user when it is completed. This driver script can be -executed in an interactive allocation, or placed into a batch script as follows: +A ``Model`` supports key features, including methods to: -.. code-block:: bash +- :ref:`Attach configuration files` for use at ``Model`` runtime. +- :ref:`Colocate an Orchestrator` to a SmartSim ``Model``. +- :ref:`Load an ML model` into the ``Orchestrator`` at ``Model`` runtime. +- :ref:`Load a TorchScript function` into the ``Orchestrator`` at ``Model`` runtime. +- :ref:`Enable data collision prevention` which allows + for reuse of key names in different ``Model`` applications. - #!/bin/bash - #SBATCH --exclusive - #SBATCH --nodes=1 - #SBATCH --ntasks-per-node=32 - #SBATCH --time=00:10:00 +Visit the respective links for more information on each topic. - python /path/to/script.py +.. _ensemble_exp_docs: Ensemble ======== +In addition to a single ``Model``, SmartSim allows users to create, +configure, and launch an :ref:`Ensemble` of ``Model`` objects. +``Ensemble(s)`` can be given parameters and a permutation strategy that define how the +``Ensemble`` will create the underlying ``Model`` objects. Users may also +manually create and append ``Model(s)`` to an ``Ensemble``. For information +and examples on ``Ensemble`` creation strategies, visit the :ref:`Initialization` +section within the ``Ensemble`` documentation. + +An ``Ensemble`` supports key features, including methods to: + +- :ref:`Attach configuration files` for use at ``Ensemble`` runtime. +- :ref:`Load an ML model` (TF, TF-lite, PT, or ONNX) into the ``Orchestrator`` at ``Ensemble`` runtime. +- :ref:`Load a TorchScript function` into the ``Orchestrator`` at ``Ensemble`` runtime. +- :ref:`Prevent data collisions` within the ``Ensemble``, which allows for reuse of application code. + +Visit the respective links for more information on each topic. + +.. _exp_example: + +======= +Example +======= +.. compound:: + In the following section, we provide an example of using SmartSim to automate the + deployment of an HPC workflow consisting of a ``Model`` and standalone ``Orchestrator``. + The example demonstrates: + + *Initializing* + - a workflow (``Experiment``) + - an in-memory database (standalone ``Orchestrator``) + - an application (``Model``) + *Generating* + - the ``Orchestrator`` output directory + - the ``Model`` output directory + *Starting* + - an in-memory database (standalone ``Orchestrator``) + - an application (``Model``) + *Stopping* + - an in-memory database (standalone ``Orchestrator``) + + The example source code is available in the dropdown below for convenient execution + and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + +Initializing +============ +.. compound:: + To create a workflow, *initialize* an ``Experiment`` object + at the start of the Python driver script. This involves specifying + a name and the system launcher that will execute all entities. + Set the `launcher` argument to `auto` to instruct SmartSim to attempt + to find the machines WLM. + + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + :language: python + :linenos: + :lines: 1-7 + + We also initialize a SmartSim :ref:`logger`. We will use the logger to log the ``Experiment`` + summary. + +.. compound:: + Next, launch an in-memory database, referred to as an ``Orchestrator``. + To *initialize* an ``Orchestrator`` object, use the ``Experiment.create_database`` + factory method. Create a multi-sharded ``Orchestrator`` by setting the argument `db_nodes` to three. + SmartSim will assign a `port` to the ``Orchestrator`` and attempt to detect your machine's + network interface if not provided. + + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + :language: python + :linenos: + :lines: 9-10 + +.. compound:: + Before invoking the factory method to create a ``Model``, + first create a ``RunSettings`` object. ``RunSettings`` hold the + information needed to execute the ``Model`` on the machine. The ``RunSettings`` + object is initialized using the ``Experiment.create_run_settings`` method. + Specify the executable to run and arguments to pass to the executable. + + The example ``Model`` is a simple `Hello World` program + that echos `Hello World` to stdout. + + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + :language: python + :linenos: + :lines: 12-13 + + After creating the ``RunSettings`` object, initialize the ``Model`` object by passing the `name` + and `settings` to ``create_model``. + + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + :language: python + :linenos: + :lines: 14-15 + +Generating +========== +.. compound:: + Next we generate the file structure for the ``Experiment``. A call to ``Experiment.generate`` + instructs SmartSim to create directories within the ``Experiment`` folder for each instance passed in. + We organize the ``Orchestrator`` and ``Model`` output files within the ``Experiment`` folder by + passing the ``Orchestrator`` and ``Model`` instances to ``exp.generate``: + + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + :language: python + :linenos: + :lines: 17-18 + + `Overwrite=True` instructs SmartSim to overwrite entity contents if files and subdirectories + already exist within the ``Experiment`` directory. + + .. note:: + If files or folders are attached to a ``Model`` or ``Ensemble`` members through ``Model.attach_generator_files`` + or ``Ensemble.attach_generator_files``, the attached files or directories will be symlinked, copied, or configured and + written into the created directory for that instance. + + The ``Experiment.generate`` call places the `.err` and `.out` log files in the entity + subdirectories within the main ``Experiment`` directory. + +Starting +======== +.. compound:: + Next launch the components of the ``Experiment`` (``Orchestrator`` and ``Model``). + To do so, use the ``Experiment.start`` factory method and pass in the previous + ``Orchestrator`` and ``Model`` instances. -In addition to a single model, SmartSim has the ability to launch an -``Ensemble`` of ``Model`` applications simultaneously. - -An ``Ensemble`` can be constructed in three ways: - 1. Parameter expansion (by specifying ``params`` and ``perm_strat`` argument) - 2. Replica creation (by specifying ``replicas`` argument) - 3. Manually (by adding created ``Model`` objects) if launching as a batch job - -Ensembles can be given parameters and permutation strategies that define how the -``Ensemble`` will create the underlying model objects. - -Three strategies are built in: - 1. ``all_perm``: for generating all permutations of model parameters - 2. ``step``: for creating one set of parameters for each element in `n` arrays - 3. ``random``: for random selection from predefined parameter spaces - -Here is an example that uses the ``random`` strategy to intialize four models -with random parameters within a set range. We use the ``params_as_args`` field -to specify that the randomly selected learning rate parameter should be passed -to the created models as a executable argument. - -.. code-block:: bash - - import numpy as np - from smartsim import Experiment - - exp = Experiment("Training-Run", launcher="auto") - - # setup ensemble parameter space - learning_rate = list(np.linspace(.01, .5)) - train_params = {"LR": learning_rate} - - # define how each member should run - run = exp.create_run_settings(exe="python", - exe_args="./train-model.py") - - ensemble = exp.create_ensemble("Training-Ensemble", - params=train_params, - params_as_args=["LR"], - run_settings=run, - perm_strategy="random", - n_models=4) - exp.start(ensemble, summary=True) - - -A callable function can also be supplied for custom permutation strategies. The -function should take two arguments: a list of parameter names, and a list of -lists of potential parameter values. The function should return a list of -dictionaries that will be supplied as model parameters. The length of the list -returned will determine how many ``Model`` instances are created. - -For example, the following is the built-in strategy ``all_perm``: - -.. code-block:: python - - from itertools import product - - def create_all_permutations(param_names, param_values): - perms = list(product(*param_values)) - all_permutations = [] - for p in perms: - temp_model = dict(zip(param_names, p)) - all_permutations.append(temp_model) - return all_permutations - - -After ``Ensemble`` initialization, ``Ensemble`` instances can be -passed as arguments to ``Experiment.generate()`` to write assigned -parameter values into attached and tagged configuration files. - -Launching Ensembles -------------------- - -Ensembles can be launched in previously obtained interactive allocations -and as a batch. Similar to ``RunSettings``, ``BatchSettings`` specify how -an application(s) in a batch job should be executed with regards to the system -workload manager and available compute resources. - - - :ref:`SbatchSettings ` for Slurm - - :ref:`QsubBatchSettings ` for PBSPro - - :ref:`BsubBatchSettings ` for LSF - -If it only passed ``RunSettings``, ``Ensemble``, objects will require either -a ``replicas`` argument or a ``params`` argument to expand parameters -into ``Model`` instances. At launch, the ``Ensemble`` will look for -interactive allocations to launch models in. - -If it passed ``BatchSettings`` without other arguments, an empty ``Ensemble`` -will be created that ``Model`` objects can be added to manually. All ``Model`` -objects added to the ``Ensemble`` will be launched in a single batch. - -If it passed ``BatchSettings`` and ``RunSettings``, the ``BatchSettings`` will -determine the allocation settings for the entire batch, and the ``RunSettings`` -will determine how each individual ``Model`` instance is executed within -that batch. - -This is the same example as above, but tailored towards a running as a batch job -on a slurm system: - -.. code-block:: bash - - import numpy as np - from smartsim import Experiment - - exp = Experiment("Training-Run", launcher="slurm") - - # setup ensemble parameter space - learning_rate = list(np.linspace(.01, .5)) - train_params = {"LR": learning_rate} - - # define resources for all ensemble members - sbatch = exp.create_batch_settings(nodes=4, - time="01:00:00", - account="12345-Cray", - queue="gpu") - - # define how each member should run - srun = exp.create_run_settings(exe="python", - exe_args="./train-model.py") - srun.set_nodes(1) - srun.set_tasks(24) - - ensemble = exp.create_ensemble("Training-Ensemble", - params=train_params, - params_as_args=["LR"], - batch_settings=sbatch, - run_settings=srun, - perm_strategy="random", - n_models=4) - exp.start(ensemble, summary=True) - - -This will generate and execute a batch script that looks something like -the following: - -.. code-block:: bash - - # GENERATED - - #!/bin/bash - - #SBATCH --output=/lus/smartsim/Training-Ensemble.out - #SBATCH --error=/lus/smartsim/Training-Ensemble.err - #SBATCH --job-name=Training-Ensemble-CHTN0UI2DORX - #SBATCH --nodes=4 - #SBATCH --time=01:00:00 - #SBATCH --partition=gpu - #SBATCH --account=12345-Cray - - cd /scratch/smartsim/Training-Run ; /usr/bin/srun --output /scratch/smartsim/Training-Run/Training-Ensemble_0.out --error /scratch/smartsim/Training-Ensemble_0.err --job-name Training-Ensemble_0-CHTN0UI2E5DX --nodes=1 --ntasks=24 /scratch/pyenvs/smartsim/bin/python ./train-model.py --LR=0.17 & - - cd /scratch/smartsim/Training-Run ; /usr/bin/srun --output /scratch/smartsim/Training-Run/Training-Ensemble_1.out --error /scratch/smartsim/Training-Ensemble_1.err --job-name Training-Ensemble_1-CHTN0UI2JQR5 --nodes=1 --ntasks=24 /scratch/pyenvs/smartsim/bin/python ./train-model.py --LR=0.32 & - - cd /scratch/smartsim/Training-Run ; /usr/bin/srun --output /scratch/smartsim/Training-Run/Training-Ensemble_2.out --error /scratch/smartsim/Training-Ensemble_2.err --job-name Training-Ensemble_2-CHTN0UI2P2AR --nodes=1 --ntasks=24 /scratch/pyenvs/smartsim/bin/python ./train-model.py --LR=0.060000000000000005 & - - cd /scratch/smartsim/Training-Run ; /usr/bin/srun --output /scratch/smartsim/Training-Run/Training-Ensemble_3.out --error /scratch/smartsim/Training-Ensemble_3.err --job-name Training-Ensemble_3-CHTN0UI2TRE7 --nodes=1 --ntasks=24 /scratch/pyenvs/smartsim/bin/python ./train-model.py --LR=0.35000000000000003 & - - wait - -Prefixing Keys in the Orchestrator ----------------------------------- - -If each of multiple ensemble members attempt to use the same code to access their respective models -in the Orchestrator, the keys by which they do this will overlap and they can end up accessing each -others' data inadvertently. To prevent this situation, the SmartSim Entity object supports key -prefixing, which automatically prepends the name of the model to the keys by which it is accessed. -With this enabled, key overlapping is no longer an issue and ensemble members can use the same code. - -Under the hood, calling ensemble.enable_key_prefixing() causes the SSKEYOUT environment variable to -be set, which in turn causes all keys generated by an ensemble member to be prefixed with its model -name. Similarly, if the model for the ensemble member has incoming entities (such as those set via -model.register_incoming_entity() or ensemble.register_incoming_entity()), the SSKEYIN environment -variable will be set and the keys associated with those inputs will be automatically prefixed. Note -that entities must register themselves as this is not done by default. - -Finally, please note that while prefixing is enabled by default for tensors, datasets, and aggregated -lists of datasets, a SmartRedis client must manually call Client.use_model_ensemble_prefix() to -ensure that prefixes are used with models and scripts. - -We modify the example above to enable key prefixing as follows: - -.. code-block:: bash - - import numpy as np - from smartsim import Experiment - - exp = Experiment("Training-Run", launcher="slurm") - - # setup ensemble parameter space - learning_rate = list(np.linspace(.01, .5)) - train_params = {"LR": learning_rate} - - # define resources for all ensemble members - sbatch = exp.create_batch_settings(nodes=4, - time="01:00:00", - account="12345-Cray", - queue="gpu") - - # define how each member should run - srun = exp.create_run_settings(exe="python", - exe_args="./train-model.py") - srun.set_nodes(1) - srun.set_tasks(24) + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + :language: python + :linenos: + :lines: 20-21 - ensemble = exp.create_ensemble("Training-Ensemble", - params=train_params, - params_as_args=["LR"], - batch_settings=sbatch, - run_settings=srun, - perm_strategy="random", - n_models=4) +Stopping +======== +.. compound:: + Lastly, to clean up the ``Experiment``, tear down the launched ``Orchestrator`` + using the ``Experiment.stop`` factory method. - # Enable key prefixing -- note that this should be done - # before starting the experiment - ensemble.enable_key_prefixing() + .. literalinclude:: tutorials/doc_examples/experiment_doc_examples/exp.py + :language: python + :linenos: + :lines: 23-26 - exp.start(ensemble, summary=True) + Notice that we use the ``Experiment.summary`` function to print + the summary of the workflow. +When you run the experiment, the following output will appear:: -Further Information -------------------- + | | Name | Entity-Type | JobID | RunID | Time | Status | Returncode | + |----|----------------|---------------|-------------|---------|---------|-----------|--------------| + | 0 | hello_world | Model | 1778304.4 | 0 | 10.0657 | Completed | 0 | + | 1 | orchestrator_0 | DBNode | 1778304.3+2 | 0 | 43.4797 | Cancelled | 0 | -For more informtion about Ensembles, please refer to the :ref:`Ensemble API documentation `. \ No newline at end of file +.. note:: + Failure to tear down the ``Orchestrator`` at the end of an ``Experiment`` + may lead to ``Orchestrator`` launch failures if another ``Experiment`` is + started on the same node. \ No newline at end of file diff --git a/doc/images/Experiment.png b/doc/images/Experiment.png new file mode 100644 index 0000000000000000000000000000000000000000..a103dd6ddd5a3993ffc021a6b662bfd128a86827 GIT binary patch literal 104122 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

+O-t6(t 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þ`cSjDO$kJ~UO2q40~SVIHE4bMxu? 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 literal 0 HcmV?d00001 diff --git a/doc/images/clustered_orchestrator-1.png b/doc/images/clustered_orchestrator-1.png new file mode 100644 index 0000000000000000000000000000000000000000..996d55e85e7ef5ae88e3996ef7f1b8c960c26108 GIT binary patch literal 110736 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�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 literal 0 HcmV?d00001 diff --git a/doc/images/colocated_orchestrator-1.png b/doc/images/colocated_orchestrator-1.png new file mode 100644 index 0000000000000000000000000000000000000000..0da5d0609d714d13b5f1de401af755bfb06c523e GIT binary patch literal 70915 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 a*^?vaV9Y+)A8S7V45X)z2LloD+kXM5Tiuob literal 0 HcmV?d00001 diff --git a/doc/index.rst b/doc/index.rst index 91a7ee1ba..7e7d9c2d6 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -30,8 +30,12 @@ :caption: SmartSim experiment + run_settings + batch_settings + model + ensemble orchestrator - launchers + ss_logger ml_features api/smartsim_api diff --git a/doc/installation_instructions/basic.rst b/doc/installation_instructions/basic.rst index 2f43db50f..75b099ad5 100644 --- a/doc/installation_instructions/basic.rst +++ b/doc/installation_instructions/basic.rst @@ -1,3 +1,5 @@ +.. _basic_install_SS: + ****************** Basic Installation ****************** @@ -27,7 +29,7 @@ The base prerequisites to install SmartSim and SmartRedis are: - git - `git-lfs`_ -.. _git-lfs: https://github.com/git-lfs/git-lfs?utm_source=gitlfs_site&utm_medium=installation_link&utm_campaign=gitlfs#installing +.. _git-lfs: https://github.com/git-lfs/git-lfs?utm_source=gitlfs_site&utm_medium=installation_link&utm_campaign=gitlfs .. note:: @@ -48,7 +50,7 @@ The machine-learning backends have additional requirements in order to use GPUs for inference - `CUDA Toolkit 11 (tested with 11.8) `_ - - `cuDNN 8 (tested with 8.9.1) `_ + - `cuDNN 8 (tested with 8.9.1) `_ - OS: Linux - GPU: Nvidia diff --git a/doc/installation_instructions/platform/nonroot-linux.rst b/doc/installation_instructions/platform/nonroot-linux.rst index 2c8f7933a..3070a871a 100644 --- a/doc/installation_instructions/platform/nonroot-linux.rst +++ b/doc/installation_instructions/platform/nonroot-linux.rst @@ -13,6 +13,6 @@ a user is possible. ./cuda_11.4.4_470.82.01_linux.run --toolkit --silent --toolkitpath=/path/to/install/location/ For cuDNN, follow `Nvidia's instructions -`_, +`_, and copy the cuDNN libraries to the `lib64` directory at the CUDA Toolkit location specified above. \ No newline at end of file diff --git a/doc/installation_instructions/platform/olcf-summit.rst b/doc/installation_instructions/platform/olcf-summit.rst index 5727ae8fe..6268584cc 100644 --- a/doc/installation_instructions/platform/olcf-summit.rst +++ b/doc/installation_instructions/platform/olcf-summit.rst @@ -6,7 +6,7 @@ Since SmartSim does not have a built PowerPC build, the build steps for an IBM system are slightly different than other systems. Luckily for us, a conda channel with all relevant packages is maintained as part -of the `OpenCE `_ initiative. Users can follow these +of the `OpenCE `_ initiative. Users can follow these instructions to get a working SmartSim build with PyTorch and TensorFlow for GPU on Summit. Note that SmartSim and SmartRedis will be downloaded to the working directory from which these instructions are executed. diff --git a/doc/launchers.rst b/doc/launchers.rst deleted file mode 100644 index 22425071e..000000000 --- a/doc/launchers.rst +++ /dev/null @@ -1,248 +0,0 @@ - -********* -Launchers -********* - -SmartSim interfaces with a number of backends called `launchers` that -are responsible for constructing jobs based on run parameters and -launching them onto a system. - -The `launchers` allow SmartSim users to interact with their system -programmatically through a python interface. -Because of this, SmartSim users do not have to leave the Jupyter Notebook, -Python REPL, or Python script to launch, query, and interact with their jobs. - -SmartSim currently supports 5 `launchers`: - 1. ``local``: for single-node, workstation, or laptop - 2. ``slurm``: for systems using the Slurm scheduler - 3. ``pbs``: for systems using the PBSpro scheduler - 4. ``lsf``: for systems using the LSF scheduler - 5. ``auto``: have SmartSim auto-detect the launcher to use. - -To specify a specific launcher, one argument needs to be provided -to the ``Experiment`` initialization. - -.. code-block:: python - - from smartsim import Experiment - - exp = Experiment("name-of-experiment", launcher="local") # local launcher - exp = Experiment("name-of-experiment", launcher="slurm") # Slurm launcher - exp = Experiment("name-of-experiment", launcher="pbs") # PBSpro launcher - exp = Experiment("name-of-experiment", launcher="lsf") # LSF launcher - exp = Experiment("name-of-experiment", launcher="auto") # auto-detect launcher - -------------------------------------------------------------------------- - -Local -===== - - -The local launcher can be used on laptops, workstations and single -nodes of supercomputer and cluster systems. Through -launching locally, users can prototype workflows and quickly scale -them to larger systems with minimal changes. - -As with all launchers in SmartSim, the local launcher supports -asynchronous execution meaning once entities have been launched -the main thread of execution is not blocked. Daemon threads -that manage currently running jobs will be created when active -jobs are present within SmartSim. - -.. _psutil: https://github.com/giampaolo/psutil - -The local launcher uses the `psutil`_ library to execute and monitor -user-created jobs. - - -Running Locally ---------------- - -The local launcher supports the base :ref:`RunSettings API ` -which can be used to run executables as well as run executables -with arbitrary launch binaries like `mpiexec`. - -The local launcher is the default launcher for all ``Experiment`` -instances. - -The local launcher does not support batch launching. Ensembles -are always executed in parallel but launched sequentially. - ----------------------------------------------------------------------- - -Slurm -===== - -The Slurm launcher works directly with the Slurm scheduler to launch, query, -monitor and stop applications. During the course of an ``Experiment``, -launched entities can be queried for status, completion, and errors. - -The amount of communication between SmartSim and Slurm can be tuned -for specific guidelines of different sites by setting the -value for ``jm_interval`` in the SmartSim configuration file. - -To use the Slurm launcher, specify at ``Experiment`` initialization: - -.. code-block:: python - - from smartsim import Experiment - - exp = Experiment("NAMD-worklfow", launcher="slurm") - - -Running on Slurm ----------------- - -The Slurm launcher supports three types of ``RunSettings``: - 1. :ref:`SrunSettings ` - 2. :ref:`MpirunSettings ` - 3. :ref:`MpiexecSettings ` - -As well as batch settings for ``sbatch`` through: - 1. :ref:`SbatchSettings ` - - -Both supported ``RunSettings`` types above can be added -to a ``SbatchSettings`` batch workload through ``Ensemble`` -creation. - - -Getting Allocations -------------------- - -Slurm supports a number of user facing features that other schedulers -do not. For this reason, an extra module :ref:`smartsim.slurm ` can be -used to obtain allocations to launch on and release them after -``Experiment`` completion. - -.. code-block:: python - - from smartsim.wlm import slurm - alloc = slurm.get_allocation(nodes=1) - -The ID of the allocation is returned as a string to the user so that -they can specify what entities should run on which allocations -obtained by SmartSim. - -Additional arguments that would have been passed to the ``salloc`` -command can be passed through the ``options`` argument in a dictionary. - -Anything passed to the options will be processed as a Slurm -argument and appended to the salloc command with the appropriate -prefix (e.g. `-` or `--`). - -For arguments without a value, pass None as the value: - - `exclusive=None` - -.. code-block:: python - - from smartsim.wlm import slurm - salloc_options = { - "C": "haswell", - "partition": "debug", - "exclusive": None - } - alloc_id = slurm.get_slurm_allocation(nodes=128, - time="10:00:00", - options=salloc_options) - -The above code would generate a ``salloc`` command like: - -.. code-block:: bash - - salloc -N 5 -C haswell --partition debug --time 10:00:00 --exclusive - - - -Releasing Allocations ---------------------- - -The :ref:`smartsim.slurm ` interface -also supports releasing allocations obtained in an experiment. - -The example below releases the allocation in the example above. - -.. code-block:: python - - from smartsim.wlm import slurm - salloc_options = { - "C": "haswell", - "partition": "debug", - "exclusive": None - } - alloc_id = slurm.get_slurm_allocation(nodes=128, - time="10:00:00", - options=salloc_options) - - # - - slurm.release_slurm_allocation(alloc_id) - -------------------------------------------------------------------- - -PBSPro -====== - -Like the Slurm launcher, the PBSPro launcher works directly with the PBSPro -scheduler to launch, query, monitor and stop applications. - -The amount of communication between SmartSim and PBSPro can be tuned -for specific guidelines of different sites by setting the -value for ``jm_interval`` in the SmartSim configuration file. - -To use the PBSpro launcher, specify at ``Experiment`` initialization: - -.. code-block:: python - - from smartsim import Experiment - - exp = Experiment("LAMMPS-melt", launcher="pbs") - - - -Running on PBSpro ------------------ - -The PBSpro launcher supports three types of ``RunSettings``: - 1. :ref:`AprunSettings ` - 2. :ref:`MpirunSettings ` - 3. :ref:`MpiexecSettings ` - -As well as batch settings for ``qsub`` through: - 1. :ref:`QsubBatchSettings ` - -Both supported ``RunSettings`` types above can be added -to a ``QsubBatchSettings`` batch workload through ``Ensemble`` -creation. - ---------------------------------------------------------------------- - -LSF -=== - -The LSF Launcher works like the PBSPro launcher and -is compatible with LSF and OpenMPI workloads. - -To use the LSF launcher, specify at ``Experiment`` initialization: - -.. code-block:: python - - from smartsim import Experiment - - exp = Experiment("MOM6-double-gyre", launcher="lsf") - - -Running on LSF --------------- - -The LSF launcher supports three types of ``RunSettings``: - 1. :ref:`JsrunSettings ` - 2. :ref:`MpirunSettings ` - 3. :ref:`MpiexecSettings ` - -As well as batch settings for ``bsub`` through: - 1. :ref:`BsubBatchSettings ` - -Both supported ``RunSettings`` types above can be added -to a ``BsubBatchSettings`` batch workload through ``Ensemble`` -creation. diff --git a/doc/ml_features.rst b/doc/ml_features.rst index 6096f005e..4e0919a08 100644 --- a/doc/ml_features.rst +++ b/doc/ml_features.rst @@ -1,3 +1,5 @@ +.. _ml_features_docs: + ########### ML Features ########### @@ -303,7 +305,7 @@ with TensorFlow or PyTorch backends. .. code-block:: python - client.run_model(model_key, inputs=["mnist_imagse"], outputs=["mnist_output"]) + client.run_model(model_key, inputs=["mnist_images"], outputs=["mnist_output"]) output = client.get_tensor("mnist_output") diff --git a/doc/model.rst b/doc/model.rst new file mode 100644 index 000000000..52e1ce1c0 --- /dev/null +++ b/doc/model.rst @@ -0,0 +1,2343 @@ +.. _model_object_doc: + +***** +Model +***** +======== +Overview +======== +SmartSim ``Model`` objects enable users to execute computational tasks in an +``Experiment`` workflow, such as launching compiled applications, +running scripts, or performing general computational operations. A ``Model`` can be launched with +other SmartSim ``Model(s)`` and ``Orchestrator(s)`` to build AI-enabled workflows. +With the SmartSim ``Client`` (:ref:`SmartRedis`), data can be transferred from a ``Model`` +to the ``Orchestrator`` for use in an ML model (TF, TF-lite, PyTorch, or ONNX), online +training process, or additional ``Model`` applications. SmartSim ``Clients`` (SmartRedis) are available in +Python, C, C++, or Fortran. + +To initialize a SmartSim ``Model``, use the ``Experiment.create_model`` factory method. +When creating a ``Model``, a :ref:`RunSettings` object must be provided. A ``RunSettings`` +object specifies the ``Model`` executable (e.g. the full path to a compiled binary) as well as +executable arguments and launch parameters. These specifications include launch commands (e.g. `srun`, `aprun`, `mpiexec`, etc), +compute resource requirements, and application command-line arguments. + +Once a ``Model`` instance has been initialized, users have access to +the :ref:`Model API` functions to further configure the ``Model``. +The Model API functions provide users with the following capabilities: + +- :ref:`Attach Files to a SmartSim Model` +- :ref:`Colocate an Orchestrator to a SmartSim Model` +- :ref:`Attach a ML Model to the SmartSim Model` +- :ref:`Attach a TorchScript Function to the SmartSim Model` +- :ref:`Enable SmartSim Model Data Collision Prevention` + +Once the ``Model`` has been configured and launched, a user can leverage an ``Orchestrator`` within a ``Model`` +through **two** strategies: + +- :ref:`Connect to a Standalone Orchestrator` + When a ``Model`` is launched, it does not use or share compute + resources on the same host (computer/server) where a SmartSim ``Orchestrator`` is running. + Instead, it is launched on its own compute resources specified by the ``RunSettings`` object. + The ``Model`` can connect via a SmartRedis ``Client`` to a launched standalone ``Orchestrator``. + +- :ref:`Connect to a Colocated Orchestrator` + When the colocated ``Model`` is started, SmartSim launches an ``Orchestrator`` on the ``Model`` compute + nodes prior to the ``Model`` execution. The ``Model`` can then connect to the colocated ``Orchestrator`` + via a SmartRedis ``Client``. + +.. note:: + For the ``Client`` connection to be successful from within the ``Model`` application, + the SmartSim ``Orchestrator`` must be launched prior to the start of the ``Model``. + +.. note:: + A ``Model`` can be launched without an ``Orchestrator`` if data transfer and ML capabilities are not + required. + +SmartSim manages ``Model`` instances through the :ref:`Experiment API` by providing functions to +launch, monitor, and stop applications. Additionally, a ``Model`` can be launched individually +or as a group via an :ref:`Ensemble`. + +============== +Initialization +============== +Overview +======== +The ``Experiment`` is responsible for initializing all SmartSim entities. +A ``Model`` is created using the ``Experiment.create_model`` factory method, and users can customize the +``Model`` via the factory method parameters. + +The key initializer arguments for ``Model`` creation can be found in the :ref:`Experiment API` +under the ``create_model`` docstring. + +A `name` and :ref:`RunSettings` reference are required to initialize a ``Model``. +Optionally, include a :ref:`BatchSettings` object to specify workload manager batch launching. + +.. note:: + ``BatchSettings`` attached to a ``Model`` are ignored when the ``Model`` is executed as part of an ``Ensemble``. + +The `params` factory method parameter for ``Model`` creation allows a user to define simulation parameters and +values through a dictionary. Using ``Model`` :ref:`file functions`, users can write these parameters to +a file in the ``Model`` working directory. + +When a ``Model`` instance is passed to ``Experiment.generate``, a +directory within the Experiment directory +is created to store input and output files from the ``Model``. + +.. note:: + It is strongly recommended to invoke ``Experiment.generate`` on the ``Model`` + instance before launching the ``Model``. If a path is not specified during + ``Experiment.create_model``, calling ``Experiment.generate`` with the ``Model`` + instance will result in SmartSim generating a ``Model`` directory within the + ``Experiment`` directory. This directory will be used to store the ``Model`` outputs + and attached files. + +.. _std_model_doc: + +Example +======= +In this example, we provide a demonstration of how to initialize and launch a ``Model`` +within an ``Experiment`` workflow. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/model_init.py + +All workflow entities are initialized through the :ref:`Experiment API`. +Consequently, initializing a SmartSim ``Experiment`` is a prerequisite for ``Model`` +initialization. + +To initialize an instance of the ``Experiment`` class, import the SmartSim +``Experiment`` module and invoke the ``Experiment`` constructor +with a `name` and `launcher`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_init.py + :language: python + :linenos: + :lines: 1-4 + +A ``Model`` requires ``RunSettings`` objects to specify how the ``Model`` should be +executed within the workflow. We use the ``Experiment`` instance `exp` to +call the factory method ``Experiment.create_run_settings`` to initialize a ``RunSettings`` +object. Finally, we specify the executable `"echo"` to run the executable argument `"Hello World"`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_init.py + :language: python + :linenos: + :lines: 6-7 + +.. seealso:: + For more information on ``RunSettings`` objects, reference the :ref:`RunSettings` documentation. + +We now have a ``RunSettings`` instance named `model_settings` that contains all of the +information required to launch our application. Pass a `name` and the run settings instance +to the ``create_model`` factory method: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_init.py + :language: python + :linenos: + :lines: 9-10 + +To create an isolated output directory for the ``Model``, invoke ``Experiment.generate`` on the +``Model`` `model_instance`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_init.py + :language: python + :linenos: + :lines: 12-13 + +.. note:: + The ``Experiment.generate`` step is optional; however, this step organizes the ``Experiment`` + entity output files into individual entity folders within the ``Experiment`` folder. Continue + in the example for information on ``Model`` output generation or visit the + :ref:`Output and Error Files` section. + +All entities are launched, monitored and stopped by the ``Experiment`` instance. +To start the ``Model``, invoke ``Experiment.start`` on `model_instance`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_init.py + :language: python + :linenos: + :lines: 15-16 + +When the ``Experiment`` driver script is executed, two files from the `model_instance` will be created +in the generated ``Model`` subdirectory: + +1. `model_instance.out` : this file will hold outputs produced by the `model_instance` workload. +2. `model_instance.err` : this file will hold any errors that occurred during `model_instance` execution. + +.. _colo_model_doc: + +====================== +Colocated Orchestrator +====================== +A SmartSim ``Model`` has the capability to share compute node(s) with a SmartSim ``Orchestrator`` in +a deployment known as a colocated ``Orchestrator``. In this scenario, the ``Orchestrator`` and ``Model`` share +compute resources. To achieve this, users need to initialize a ``Model`` instance using the +``Experiment.create_model`` function and then utilize one of the three functions listed below to +colocate an ``Orchestrator`` with the ``Model``. This instructs SmartSim to launch an ``Orchestrator`` +on the application compute node(s) before the ``Model`` execution. + +There are **three** different Model API functions to colocate a ``Model``: + +- ``Model.colocate_db_tcp``: Colocate an ``Orchestrator`` instance and establish client communication using TCP/IP. +- ``Model.colocate_db_uds``: Colocate an ``Orchestrator`` instance and establish client communication using Unix domain sockets (UDS). +- ``Model.colocate_db``: (deprecated) An alias for `Model.colocate_db_tcp`. + +Each function initializes an unsharded ``Orchestrator`` accessible only to the ``Model`` processes on the same compute node. When the ``Model`` +is started, the ``Orchestrator`` will be launched on the same compute resource as the ``Model``. Only the colocated ``Model`` +may communicate with the ``Orchestrator`` via a SmartRedis ``Client`` by using the loopback TCP interface or +Unix Domain sockets. Extra parameters for the ``Orchestrator`` can be passed into the colocate functions above +via `kwargs`. + +.. code-block:: python + + example_kwargs = { + "maxclients": 100000, + "threads_per_queue": 1, + "inter_op_threads": 1, + "intra_op_threads": 1 + } + +For a walkthrough of how to colocate a ``Model``, navigate to the +:ref:`Colocated Orchestrator` for instructions. + +For users aiming to **optimize performance**, SmartSim offers the flexibility to specify +processor IDs to which the colocated ``Orchestrator`` should be pinned. This can be achieved using +the `custom_pinning` argument, which is recognized by both ``Model.colocate_db_uds`` and +``Model.colocate_db_tcp``. In systems where specific processors support ML model and +TorchScript execution, users can employ the `custom_pinning` argument to designate +these processor IDs. This ensures that the specified processors are available +when executing ML models or TorchScripts on the colocated ``Orchestrator``. +Additionally, users may use the `custom_pinning` argument to avoid reserved processors +by specifying a available processor ID or a list of available processor IDs. + +.. _files_doc: + +===== +Files +===== +Overview +======== +Applications often depend on external files (e.g. training datasets, evaluation datasets, etc) +to operate as intended. Users can instruct SmartSim to copy, symlink, or manipulate external files +prior to a ``Model`` launch via the ``Model.attach_generator_files`` function. + +.. note:: + Multiple calls to ``Model.attach_generator_files`` will overwrite previous file configurations + in the ``Model``. + +To setup the run directory for the ``Model``, users should pass the list of files to +``Model.attach_generator_files`` using the following arguments: + +* `to_copy` (t.Optional[t.List[str]] = None): Files that are copied into the path of the ``Model``. +* `to_symlink` (t.Optional[t.List[str]] = None): Files that are symlinked into the path of the ``Model``. + +User-formatted files can be attached using the `to_configure` argument. These files will be modified +during ``Model`` generation to replace tagged sections in the user-formatted files with +values from the `params` initializer argument used during ``Model`` creation: + +* `to_configure` (t.Optional[t.List[str]] = None): Designed for text-based ``Model`` input files, + `"to_configure"` is exclusive to the ``Model``. During ``Model`` directory generation, the attached + files are parsed and specified tagged parameters are replaced with the `params` values that were + specified in the ``Experiment.create_model`` factory method of the ``Model``. The default tag is a semicolon + (e.g., THERMO = ;THERMO;). + +In the :ref:`Example` subsection, we provide an example using the value `to_configure` +within ``attach_generator_files``. + +.. _files_example_doc: + +Example +======= +This example demonstrates how to attach a file to a ``Model`` for parameter replacement at the time +of ``Model`` directory generation. This is accomplished using the `params` function parameter in +``Experiment.create_model`` and the `to_configure` function parameter +in ``Model.attach_generator_files``. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/model_file.py + +In this example, we have a text file named `params_inputs.txt`. Within the text file, is the parameter `THERMO` +that is required by the ``Model`` application at runtime: + +.. code-block:: bash + + THERMO = ;THERMO; + +In order to have the tagged parameter `;THERMO;` replaced with a usable value at runtime, two steps are required: + +1. The `THERMO` variable must be included in ``Experiment.create_model`` factory method as + part of the `params` initializer argument. +2. The file containing the tagged parameter `;THERMO;`, `params_inputs.txt`, must be attached to the ``Model`` + via the ``Model.attach_generator_files`` method as part of the `to_configure` function parameter. + +To encapsulate our application within a ``Model``, we must first create an ``Experiment`` instance. +Begin by importing the ``Experiment`` module and initializing an ``Experiment``: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_file.py + :language: python + :linenos: + :lines: 1-4 + +A SmartSim ``Model`` requires a ``RunSettings`` object to +specify the ``Model`` executable (e.g. the full path to a compiled binary) as well as +executable arguments and launch parameters. Create a simple ``RunSettings`` object +and specify the path to the executable script as an executable argument (`exe_args`): + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_file.py + :language: python + :linenos: + :lines: 6-7 + +.. seealso:: + To read more on SmartSim ``RunSettings`` objects, reference the :ref:`RunSettings` documentation. + +Next, initialize a ``Model`` object via ``Experiment.create_model``. Pass in the `model_settings` instance +and the `params` value: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_file.py + :language: python + :linenos: + :lines: 9-10 + +We now have a ``Model`` instance named `model_instance`. Attach the text file, `params_inputs.txt`, +to the ``Model`` for use at entity runtime. To do so, use the +``Model.attach_generator_files`` function and specify the `to_configure` +parameter with the path to the text file, `params_inputs.txt`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_file.py + :language: python + :linenos: + :lines: 12-13 + +To created an isolated directory for the ``Model`` outputs and configuration files, invoke ``Experiment.generate`` +on `model_instance` as an input parameter: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/model_file.py + :language: python + :linenos: + :lines: 15-16 + +The contents of `getting-started/model_name/params_inputs.txt` at runtime are: + +.. code-block:: bash + + THERMO = 1 + +.. _model_output_files: + +====================== +Output and Error Files +====================== +By default, SmartSim stores the standard output and error of the ``Model`` in two files: + +* `.out` +* `.err` + +The files are created in the working directory of the ``Model``, and the filenames directly match the +``Model`` name. The `.out` file logs standard outputs and the +`.err` logs errors for debugging. + +.. note:: + Invoking ``Experiment.generate(model)`` will create a directory `model_name/` and will store + the two files within that directory. You can also specify a path for these files using the + `path` parameter when invoking ``Experiment.create_model``. + +.. _ml_script_model_doc: + +===================== +ML Models and Scripts +===================== +Overview +======== +SmartSim users have the capability to load ML models and TorchScripts into an ``Orchestrator`` +within the ``Experiment`` script for use within a ``Model``. Functions accessible through +a ``Model`` object support loading ML models (TensorFlow, TensorFlow-lite, PyTorch, and ONNX) and +TorchScripts into standalone or colocated ``Orchestrator(s)`` before application runtime. + +Users can follow **two** processes to load an ML model to the ``Orchestrator``: + +- :ref:`From Memory` +- :ref:`From File` + +.. warning:: + Uploading an ML model :ref:`from memory` is solely supported for + standalone ``Orchestrator(s)``. To upload an ML model to a colocated ``Orchestrator``, users + must save the ML model to disk and upload :ref:`from file`. + +Users can follow **three** processes to load a TorchScript to the ``Orchestrator``: + +- :ref:`From Memory` +- :ref:`From File` +- :ref:`From String` + +.. warning:: + Uploading a TorchScript :ref:`from memory` is solely supported for + standalone ``Orchestrator(s)``. To upload a TorchScript to a colocated ``Orchestrator``, users + upload :ref:`from file` or :ref:`from string`. + +Once an ML model or TorchScript is loaded into the ``Orchestrator``, ``Model`` objects can +leverage ML capabilities by utilizing the SmartSim ``Client`` (:ref:`SmartRedis`) +to execute the stored ML models and TorchScripts. + +.. _ai_model_doc: + +AI Models +========= +When configuring a ``Model``, users can instruct SmartSim to load +Machine Learning (ML) models to the ``Orchestrator``. ML models added +are loaded into the ``Orchestrator`` prior to the execution of the ``Model``. To load an ML model +to the ``Orchestrator``, SmartSim users can provide the ML model **in-memory** or specify the **file path** +when using the ``Model.add_ml_model`` function. SmartSim solely supports loading an ML model from file +for use within standalone ``Orchestrator(s)``. The supported ML frameworks are TensorFlow, +TensorFlow-lite, PyTorch, and ONNX. + +The arguments that customize the storage and execution of an ML model can be found in the +:ref:`Model API` under the ``add_ml_model`` docstring. + +.. _in_mem_ML_model_ex: + +------------------------------------- +Example: Attach an In-Memory ML Model +------------------------------------- +This example demonstrates how to attach an in-memory ML model to a SmartSim ``Model`` +to load into an ``Orchestrator`` at ``Model`` runtime. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/in_mem_ml_model.py + +.. note:: + This example assumes: + + - an ``Orchestrator`` is launched prior to the ``Model`` execution (colocated or standalone) + - an initialized ``Model`` named `smartsim_model` exists within the ``Experiment`` workflow + - a Tensorflow-based ML model was serialized using ``serialize_model`` which returns the + the ML model as a byte string with the names of the input and output layers + +**Attach the ML Model to a SmartSim Model** + +In this example, we have a serialized Tensorflow-based ML model that was saved to a byte string stored under `model`. +Additionally, the ``serialize_model`` function returned the names of the input and output layers stored under +`inputs` and `outputs`. Assuming an initialized ``Model`` named `smartsim_model` exists, we add the in-memory TensorFlow model using +the ``Model.add_ml_model`` function and specify the in-memory ML model to the function parameter `model`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/in_mem_ml_model.py + :language: python + :linenos: + :lines: 39-40 + +In the above ``smartsim_model.add_ml_model`` code snippet, we pass in the following arguments: + +- `name` ("cnn"): A name to reference the ML model in the ``Orchestrator``. +- `backend` ("TF"): Indicating that the ML model is a TensorFlow model. +- `model` (model): The in-memory representation of the TensorFlow model. +- `device` ("GPU"): Specifying the device for ML model execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. +- `inputs` (inputs): The name of the ML model input nodes (TensorFlow only). +- `outputs` (outputs): The name of the ML model output nodes (TensorFlow only). + +.. warning:: + Calling `exp.start(smartsim_model)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent ``Orchestrator``. + +When the ``Model`` is started via ``Experiment.start``, the ML model will be loaded to the +launched ``Orchestrator``. The ML model can then be executed on the ``Orchestrator`` via a SmartSim +``Client`` (:ref:`SmartRedis`) within the application code. + +.. _from_file_ML_model_ex: + +------------------------------------- +Example: Attach an ML Model From File +------------------------------------- +This example demonstrates how to attach a ML model from file to a SmartSim ``Model`` +to load into an ``Orchestrator`` at ``Model`` runtime. +The source code example is available in the dropdown below for +convenient execution and customization. + +.. note:: + SmartSim supports loading ML models from file to standalone ``Orchestrator(s)``. + This feature is **not** supported for colocated ``Orchestrator(s)``. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/from_file_ml_model.py + +.. note:: + This example assumes: + + - a standalone ``Orchestrator`` is launched prior to the ``Model`` execution + - an initialized ``Model`` named `smartsim_model` exists within the ``Experiment`` workflow + - a Tensorflow-based ML model was serialized using ``freeze_model`` which returns the + the path to the serialized model file and the names of the input and output layers + +**Attach the ML Model to a SmartSim Model** + +In this example, we have a serialized Tensorflow-based ML model that was saved to disk and stored under `model`. +Additionally, the ``freeze_model`` function returned the names of the input and output layers stored under +`inputs` and `outputs`. Assuming an initialized ``Model`` named `smartsim_model` exists, we add the TensorFlow model using +the ``Model.add_ml_model`` function and specify the TensorFlow model path to the parameter `model_path`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/from_file_ml_model.py + :language: python + :linenos: + :lines: 39-40 + +In the above ``smartsim_model.add_ml_model`` code snippet, we pass in the following arguments: + +- `name` ("cnn"): A name to reference the ML model in the ``Orchestrator``. +- `backend` ("TF"): Indicating that the ML model is a TensorFlow model. +- `model_path` (model_file): The path to the ML model script. +- `device` ("GPU"): Specifying the device for ML model execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. +- `inputs` (inputs): The name of the ML model input nodes (TensorFlow only). +- `outputs` (outputs): The name of the ML model output nodes (TensorFlow only). + +.. warning:: + Calling `exp.start(smartsim_model)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent ``Orchestrator``. + +When the ``Model`` is started via ``Experiment.start``, the ML model will be loaded to the +launched standalone ``Orchestrator``. The ML model can then be executed on the ``Orchestrator`` +via a SmartRedis ``Client`` (:ref:`SmartRedis`) within the application code. + +.. _TS_doc: + +TorchScripts +============ +When configuring a ``Model``, users can instruct SmartSim to load TorchScripts +to the ``Orchestrator``. TorchScripts added are loaded into the ``Orchestrator`` prior to +the execution of the ``Model``. To load a TorchScript to the ``Orchestrator``, SmartSim users +can follow one of the processes: + +- :ref:`Define a TorchScript Function In-Memory` + Use the ``Model.add_function`` to instruct SmartSim to load an in-memory TorchScript to the ``Orchestrator``. +- :ref:`Define a TorchScript Function From File` + Provide file path to ``Model.add_script`` to instruct SmartSim to load the TorchScript from file to the ``Orchestrator``. +- :ref:`Define a TorchScript Function as String` + Provide function string to ``Model.add_script`` to instruct SmartSim to load a raw string as a TorchScript function to the ``Orchestrator``. + +.. note:: + SmartSim does **not** support loading in-memory TorchScript functions to colocated ``Orchestrator(s)``. + Users should instead load TorchScripts to a colocated ``Orchestrator`` from file or as a raw string. + +Continue or select a process link to learn more on how each function (``Model.add_script`` and ``Model.add_function``) +load TorchScripts to launched ``Orchestrator(s)``. + +.. _in_mem_TF_doc: + +------------------------------- +Attach an In-Memory TorchScript +------------------------------- +Users can define TorchScript functions within the Python driver script +to attach to a ``Model``. This feature is supported by ``Model.add_function`` which provides flexible +device selection, allowing users to choose between which device the TorchScript is executed on, `"GPU"` or `"CPU"`. +In environments with multiple devices, specific device numbers can be specified using the +`devices_per_node` function parameter. + +.. warning:: + ``Model.add_function`` does **not** support loading in-memory TorchScript functions to a colocated ``Orchestrator``. + If you would like to load a TorchScript function to a colocated ``Orchestrator``, define the function + as a :ref:`raw string` or :ref:`load from file`. + +The arguments that customize the execution of an in-memory TorchScript function can be found in the +:ref:`Model API` under the ``add_function`` docstring. + +Example: Load a In-Memory TorchScript Function +---------------------------------------------- +This example walks through the steps of instructing SmartSim to load an in-memory TorchScript function +to a standalone ``Orchestrator``. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/in_mem_script.py + +.. note:: + The example assumes: + + - a standalone ``Orchestrator`` is launched prior to the ``Model`` execution + - an initialized ``Model`` named `smartsim_model` exists within the ``Experiment`` workflow + +**Define an In-Memory TF Function** + +To begin, define an in-memory TorchScript function within the ``Experiment`` driver script. +For the purpose of the example, we add a simple TorchScript function named `timestwo`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/in_mem_script.py + :language: python + :linenos: + :lines: 3-4 + +**Attach the In-Memory TorchScript Function to a SmartSim Model** + +We use the ``Model.add_function`` function to instruct SmartSim to load the TorchScript function `timestwo` +onto the launched standalone ``Orchestrator``. Specify the function `timestwo` to the `function` +parameter: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/in_mem_script.py + :language: python + :linenos: + :lines: 15-16 + +In the above ``smartsim_model.add_function`` code snippet, we input the following arguments: + +- `name` ("example_func"): A name to uniquely identify the TorchScript within the ``Orchestrator``. +- `function` (timestwo): Name of the TorchScript function defined in the Python driver script. +- `device` ("CPU"): Specifying the device for TorchScript execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. + +.. warning:: + Calling `exp.start(smartsim_model)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the TorchScript to a non-existent ``Orchestrator``. + +When the ``Model`` is started via ``Experiment.start``, the TF function will be loaded to the +standalone ``Orchestrator``. The function can then be executed on the ``Orchestrator`` via a SmartRedis +``Client`` (:ref:`SmartRedis`) within the application code. + +.. _TS_from_file: + +------------------------------ +Attach a TorchScript From File +------------------------------ +Users can attach TorchScript functions from a file to a ``Model`` and upload them to a +colocated or standalone ``Orchestrator``. This functionality is supported by the ``Model.add_script`` +function's `script_path` parameter. The function supports +flexible device selection, allowing users to choose between `"GPU"` or `"CPU"` via the `device` parameter. +In environments with multiple devices, specific device numbers can be specified using the +`devices_per_node` parameter. + +The arguments that customize the storage and execution of a TorchScript script can be found in the +:ref:`Model API` under the ``add_script`` docstring. + +Example: Load a TorchScript From File +------------------------------------- +This example walks through the steps of instructing SmartSim to load a TorchScript from file +to a launched ``Orchestrator``. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/from_file_script.py + +.. note:: + This example assumes: + + - a ``Orchestrator`` is launched prior to the ``Model`` execution (Colocated or standalone) + - an initialized ``Model`` named `smartsim_model` exists within the ``Experiment`` workflow + +**Define a TorchScript Script** + +For the example, we create the Python script `torchscript.py`. The file contains a +simple torch function shown below: + +.. code-block:: python + + def negate(x): + return torch.neg(x) + +**Attach the TorchScript Script to a SmartSim Model** + +Assuming an initialized ``Model`` named `smartsim_model` exists, we add the TorchScript script using +``Model.add_script`` by specifying the script path to the `script_path` parameter: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/from_file_script.py + :language: python + :linenos: + :lines: 13-14 + +In the above ``smartsim_model.add_script`` code snippet, we include the following arguments: + +- `name` ("example_script"): Reference name for the script inside of the ``Orchestrator``. +- `script_path` ("path/to/torchscript.py"): Path to the script file. +- `device` ("CPU"): device for script execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. + +.. warning:: + Calling `exp.start(smartsim_model)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent ``Orchestrator``. + +When `smartsim_model` is started via ``Experiment.start``, the TorchScript will be loaded from file to the +``Orchestrator`` that is launched prior to the start of `smartsim_model`. The function can then be executed +on the ``Orchestrator`` via a SmartRedis ``Client`` (:ref:`SmartRedis`) within the application code. + +.. _TS_raw_string: + +--------------------------------- +Define TorchScripts as Raw String +--------------------------------- +Users can upload TorchScript functions from string to colocated or +standalone ``Orchestrator(s)``. This feature is supported by the +``Model.add_script`` function's `script` parameter. The function supports +flexible device selection, allowing users to choose between `"GPU"` or `"CPU"` via the `device` parameter. +In environments with multiple devices, specific device numbers can be specified using the +`devices_per_node` parameter. + +The arguments that customize the storage and execution of a TorchScript script can be found in the +:ref:`Model API` under the ``add_script`` docstring. + +Example: Load a TorchScript From String +--------------------------------------- +This example walks through the steps of instructing SmartSim to load a TorchScript +from string to a ``Orchestrator``. The source code example is available in the dropdown below for +convenient execution and customization. + +.. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/string_script.py + +.. note:: + This example assumes: + + - a ``Orchestrator`` is launched prior to the ``Model`` execution (standalone or colocated) + - an initialized ``Model`` named `smartsim_model` exists within the ``Experiment`` workflow + +**Define a String TorchScript** + +Define the TorchScript code as a variable in the ``Experiment`` driver script: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/string_script.py + :language: python + :linenos: + :lines: 12-13 + +**Attach the TorchScript Function to a SmartSim Model** + +Assuming an initialized ``Model`` named `smartsim_model` exists, we add a TensorFlow model using +the ``Model.add_script`` function and specify the variable `torch_script_str` to the parameter +`script`: + +.. literalinclude:: tutorials/doc_examples/model_doc_examples/string_script.py + :language: python + :linenos: + :lines: 15-16 + +In the above ``smartsim_model.add_script`` code snippet, we offer the following arguments: + +- `name` ("example_script"): key to store script under. +- `script` (torch_script_str): TorchScript code. +- `device` ("CPU"): device for script execution. +- `devices_per_node` (2): Use two GPUs per node. +- `first_device` (0): Start with 0 index GPU. + +.. warning:: + Calling `exp.start(smartsim_model)` prior to instantiation of an ``Orchestrator`` will result in + a failed attempt to load the ML model to a non-existent ``Orchestrator``. + +When the ``Model`` is started via ``Experiment.start``, the TorchScript will be loaded to the +``Orchestrator`` that is launched prior to the start of the ``Model``. + +.. _model_key_collision: + +========================= +Data Collision Prevention +========================= +Overview +======== +If an ``Experiment`` consists of multiple ``Model(s)`` that use the same key names to reference +information in the ``Orchestrator``, the names used to reference data, ML models, and scripts will be +identical, and without the use of SmartSim and SmartRedis prefix methods, ``Model(s)`` +will end up inadvertently accessing or overwriting each other’s data. To prevent this +situation, the SmartSim ``Model`` object supports key prefixing, which prepends +the name of the ``Model`` to the keys sent to the ``Orchestrator`` to create unique key names. +With this enabled, collision is avoided and ``Model(s)`` can use the same key names within their applications. + +The key components of SmartSim ``Model`` prefixing functionality include: + +1. **Sending Data to the Orchestrator**: Users can send data to an ``Orchestrator`` + with the ``Model`` name prepended to the data name through SmartSim :ref:`Model functions` and + SmartRedis :ref:`Client functions`. +2. **Retrieving Data from the Orchestrator**: Users can instruct a ``Client`` to prepend a + ``Model`` name to a key during data retrieval, polling, or check for existence on the ``Orchestrator`` + through SmartRedis :ref:`Client functions`. + +For example, assume you have two ``Model(s)`` in an ``Experiment``, named `model_0` and `model_1`. In each +application code you use the function ``Client.put_tensor("tensor_0", data)`` to send a tensor named `"tensor_0"` +to the same ``Orchestrator``. With ``Model`` key prefixing turned on, the `model_0` and `model_1` +applications can access their respective tensor `"tensor_0"` by name without overwriting or accessing +the other ``Model(s)`` `"tensor_0"` tensor. In this scenario, the two tensors placed in the +``Orchestrator`` are `model_0.tensor_0` and `model_1.tensor_0`. + +Enabling and Disabling +====================== +SmartSim provides support for toggling prefixing on a ``Model`` for tensors, ``Datasets``, +lists, ML models, and scripts. Prefixing functions from the SmartSim :ref:`Model API` and SmartRedis :ref:`Client API` rely on +each other to fully support SmartSim key prefixing. For example, to use the ``Client`` prefixing +functions, a user must enable prefixing on the ``Model`` through ``Model.enable_key_prefixing``. +This function enables and activates prefixing for tensors, ``Datasets`` and lists placed in an ``Orchestrator`` +by the ``Model``. This configuration can be toggled within the ``Model`` application through +``Client`` functions, such as disabling tensor prefixing via ``Client.use_tensor_ensemble_prefix(False)``. + +The interaction between the prefix SmartSim `Model Functions` and SmartRedis +`Client Functions` are documentation below. + +.. _model_prefix_func: + +--------------- +Model Functions +--------------- +A ``Model`` object supports two prefixing functions: ``Model.enable_key_prefixing`` and +``Model.register_incoming_entity``. + +To enable prefixing on a ``Model``, users must use the ``Model.enable_key_prefixing`` +function in the ``Experiment`` driver script. The key components of this function include: + +- Activates prefixing for tensors, ``Datasets``, and lists sent to a ``Orchestrator`` from within + the ``Model`` application. +- Enables access to prefixing ``Client`` functions within the ``Model`` application. This excludes + the ``Client.set_data_source`` function, where ``enable_key_prefixing`` is not require for access. + +.. note:: + ML model and script prefixing is not automatically enabled through ``Model.enable_key_prefixing`` + and rather must be enabled within the ``Model`` application using ``Client.use_model_ensemble_prefix``. + +Users can enable a SmartRedis ``Client`` to interact with prefixed data, ML models and TorchScripts +within a ``Model`` application by specifying the producer entity name to ``Client.set_data_source``. +However, for SmartSim to recognize the entity name within the application, the producer +entity must be registered on the consumer entity using ``Ensemble.register_incoming_entity``. +This also applies to scenarios where the ``Model`` attempts to access data placed by self. +For more information on ``Client.set_data_source``, visit the +:ref:`Client functions` section. + +.. _client_prefix_func: + +---------------- +Client Functions +---------------- +A ``Client`` object supports five prefixing functions: ``Client.use_tensor_ensemble_prefix``, +``Client.use_dataset_ensemble_prefix``, ``Client.use_list_ensemble_prefix``, +``Client.use_model_ensemble_prefix`` and ``Client.set_data_source``. + +To enable or disable SmartRedis data structure prefixing for tensors, ``Datasets``, aggregation lists, ML models +and scripts, SmartRedis ``Client`` offers functions per data structure: + +- Tensor: ``Client.use_tensor_ensemble_prefix`` +- ``Dataset``: ``Client.use_dataset_ensemble_prefix`` +- Aggregation lists: ``Client.use_list_ensemble_prefix`` +- ML models/scripts: ``Client.use_model_ensemble_prefix`` + +.. warning:: + To access the ``Client`` prefixing functions, prefixing must be enabled on the + ``Model`` through ``Model.enable_key_prefixing``. This function activates prefixing + for tensors, ``Datasets`` and lists. + +Examples are provided below that show the use of these ``Client`` methods in conjunction +with the SmartSim key prefixing ``Model`` API functions. + +Users can enable the SmartSim ``Client`` to interact with prefixed data, ML models and TorchScripts +using the ``Client.set_data_source`` function. To leverage this capability: + +1. Use ``Model.register_incoming_entity`` on the ``Model`` intending to interact with prefixed data in the ``Orchestrator`` + placed by a separate ``Model``. +2. Pass the SmartSim entity (e.g., another ``Model``) to ``Model.register_incoming_entity`` in order to + reference the ``Model`` prefix in the application code. +3. In the ``Model`` application, instruct the ``Client`` to prepend the specified ``Model`` name during key searches + using ``Client.set_data_source("model_name")``. + +Examples are provided below that show the use of these ``Client`` methods in conjunction +with the SmartSim key prefixing ``Model`` API functions. + +.. _put_set_prefix: + +Put/Set Operations +================== +In the following tabs we provide snippets of driver script and application code to demonstrate +activating and deactivating prefixing for tensors, ``Datasets``, lists, ML models and scripts using +SmartRedis put/get semantics. + +.. tabs:: + + .. group-tab:: Tensor + **Activate Tensor Prefixing in the Driver Script** + + To activate prefixing on a ``Model`` in the driver script, a user must use the function + ``Model.enable_key_prefixing``. This functionality ensures that the ``Model`` name + is prepended to each tensor name sent to the ``Orchestrator`` from within the ``Model`` + executable code. The source code example is available in the dropdown below for + convenient execution and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/prefix_data.py + + In the driver script snippet below, we take an initialized ``Model`` and activate tensor + prefixing through the ``enable_key_prefixing`` function: + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/prefix_data.py + :language: python + :linenos: + :lines: 6-12 + + In the `model` application, two tensors named `tensor_1` and `tensor_2` are sent to a launched ``Orchestrator``. + The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "model_name.tensor_1" + 2) "model_name.tensor_2" + + You will notice that the ``Model`` name `model_name` has been prepended to each tensor name + and stored in the ``Orchestrator``. + + **Activate Tensor Prefixing in the Application** + + Users can further configure tensor prefixing in the application by using + the ``Client`` function ``use_tensor_ensemble_prefix``. By specifying a boolean + value to the function, users can turn prefixing on and off. + + .. note:: + To have access to ``Client.use_tensor_ensemble_prefix``, prefixing must be enabled + on the ``Model`` in the driver script via ``Model.enable_key_prefixing``. + + In the application snippet below, we demonstrate enabling and disabling tensor prefixing: + + .. code-block:: python + + # Disable key prefixing + client.use_tensor_ensemble_prefix(False) + # Place a tensor in the Orchestrator + client.put_tensor("tensor_1", np.array([1, 2, 3, 4])) + # Enable key prefixing + client.use_tensor_ensemble_prefix(True) + # Place a tensor in the Orchestrator + client.put_tensor("tensor_2", np.array([5, 6, 7, 8])) + + In the application, two tensors named `tensor_1` and `tensor_2` are sent to a launched ``Orchestrator``. + The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "tensor_1" + 2) "model_name.tensor_2" + + You will notice that the ``Model`` name `model_name` is **not** prefixed to `tensor_1` since + we disabled tensor prefixing before sending the tensor to the ``Orchestrator``. However, + when we enabled tensor prefixing and sent the second tensor, the ``Model`` name was prefixed + to `tensor_2`. + + .. group-tab:: Dataset + **Activate Dataset Prefixing in the Driver Script** + + To activate prefixing on a ``Model`` in the driver script, a user must use the function + ``Model.enable_key_prefixing``. This functionality ensures that the ``Model`` name + is prepended to each ``Dataset`` name sent to the ``Orchestrator`` from within the ``Model``. + The source code example is available in the dropdown below for + convenient execution and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/prefix_data.py + + In the driver script snippet below, we take an initialized ``Model`` and activate ``Dataset`` + prefixing through the ``enable_key_prefixing`` function: + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/prefix_data.py + :language: python + :linenos: + :lines: 6-12 + + In the `model` application, two Datasets named `dataset_1` and `dataset_2` are sent to a launched ``Orchestrator``. + The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "model_name.{dataset_1}.dataset_tensor_1" + 2) "model_name.{dataset_1}.meta" + 3) "model_name.{dataset_2}.dataset_tensor_2" + 4) "model_name.{dataset_2}.meta" + + You will notice that the ``Model`` name `model_name` has been prefixed to each ``Dataset`` name + and stored in the ``Orchestrator``. + + **Activate Dataset Prefixing in the Application** + + Users can further configure ``Dataset`` prefixing in the application by using + the ``Client`` function ``use_dataset_ensemble_prefix``. By specifying a boolean + value to the function, users can turn prefixing on and off. + + .. note:: + To have access to ``Client.use_dataset_ensemble_prefix``, prefixing must be enabled + on the ``Model`` in the driver script via ``Model.enable_key_prefixing``. + + In the application snippet below, we demonstrate enabling and disabling ``Dataset`` prefixing: + + .. code-block:: python + + # Disable key prefixing + client.use_dataset_ensemble_prefix(False) + # Place a Dataset in the Orchestrator + client.put_dataset(dataset_1) + # Enable key prefixing + client.use_dataset_ensemble_prefix(True) + # Place a Dataset in the Orchestrator + client.put_dataset(dataset_2) + + In the application, we have two ``Datasets`` named `dataset_1` and `dataset_2`. + We then send them to a launched ``Orchestrator``. The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "{dataset_1}.dataset_tensor_1" + 2) "{dataset_1}.meta" + 3) "model_name.{dataset_2}.dataset_tensor_1" + 4) "model_name.{dataset_2}.meta" + + You will notice that the ``Model`` name `model_name` is **not** prefixed to `dataset_1` since + we disabled ``Dataset`` prefixing before sending the ``Dataset`` to the ``Orchestrator``. However, + when we enabled ``Dataset`` prefixing and sent the second ``Dataset``, the ``Model`` name was prefixed + to `dataset_2`. + + .. group-tab:: Aggregation List + **Activate Aggregation List Prefixing in the Driver Script** + + To activate prefixing on a ``Model`` in the driver script, a user must use the function + ``Model.enable_key_prefixing``. This functionality ensures that the ``Model`` name + is prepended to each list name sent to the ``Orchestrator`` from within the ``Model``. + The source code example is available in the dropdown below for + convenient execution and customization. + + .. dropdown:: Example Driver Script Source Code + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/prefix_data.py + + In the driver script snippet below, we take an initialized ``Model`` and activate list + prefixing through the ``enable_key_prefixing`` function: + + .. literalinclude:: tutorials/doc_examples/model_doc_examples/prefix_data.py + :language: python + :linenos: + :lines: 6-12 + + In the `model` application, a list named `dataset_list` is sent to a launched ``Orchestrator``. + The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "model_name.dataset_list" + + You will notice that the ``Model`` name `model_name` has been prefixed to the list name + and stored in the ``Orchestrator``. + + **Activate Aggregation List Prefixing in the Application** + + Users can further configure list prefixing in the application by using + the ``Client`` function ``use_list_ensemble_prefix``. By specifying a boolean + value to the function, users can turn prefixing on and off. + + .. note:: + To have access to ``Client.use_list_ensemble_prefix``, prefixing must be enabled + on the ``Model`` in the driver script via ``Model.enable_key_prefixing``. + + In the application snippet below, we demonstrate enabling and disabling list prefixing: + + .. code-block:: python + + # Disable key prefixing + client.use_list_ensemble_prefix(False) + # Place a Dataset in the Orchestrator + client.put_dataset(dataset_1) + # Place a list in the Orchestrator + client.append_to_list("list_1", dataset_1) + # Enable key prefixing + client.use_dataset_ensemble_prefix(True) + # Place a Dataset in the Orchestrator + client.put_dataset(dataset_2) + # Append Dataset to list in the Orchestrator + client.append_to_list("list_2", dataset_2) + + In the application, two lists named `list_1` and `list_2` are sent to the ``Orchestrator``. + The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "list_1" + 2) "model_name.{dataset_1}.meta" + 3) "model_name.{dataset_1}.dataset_tensor_1" + 4) "model_name.list_2" + 5) "model_name.{dataset_2}.meta" + 6) "model_name.{dataset_2}.dataset_tensor_2" + + You will notice that the ``Model`` name `model_name` is **not** prefixed to `list_1` since + we disabled list prefixing before sending the list to the ``Orchestrator``. However, + when we enabled list prefixing and sent the second list, the ``Model`` name was prefixed + to `list_2` as well as the list ``Dataset`` members. + + .. note:: + The ``Datasets`` sent to the ``Orchestrator`` are all prefixed. This is because + ``Model.enable_key_prefixing`` turns on prefixing for tensors, ``Datasets`` and lists. + + .. group-tab:: ML Model + **Activate ML Model Prefixing in the Application** + + Users can configure ML model prefixing in the application by using + the ``Client`` function ``use_model_ensemble_prefix``. By specifying a boolean + value to the function, users can turn prefixing on and off. + + .. note:: + To have access to ``Client.use_model_ensemble_prefix``, prefixing must be enabled + on the ``Model`` in the driver script via ``Model.enable_key_prefixing``. + + In the application snippet below, we demonstrate enabling and disabling ML model prefixing: + + .. code-block:: python + + # Disable ML model prefixing + client.use_model_ensemble_prefix(False) + # Send ML model to the Orchestrator + client.set_model( + "ml_model_1", serialized_model_1, "TF", device="CPU", inputs=inputs, outputs=outputs + ) + # Enable ML model prefixing + client.use_model_ensemble_prefix(True) + # Send prefixed ML model to the Orchestrator + client.set_model( + "ml_model_2", serialized_model_2, "TF", device="CPU", inputs=inputs, outputs=outputs + ) + + In the application, two ML models named `ml_model_1` and `ml_model_2` are sent + to a launched ``Orchestrator``. The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "ml_model_1" + 2) "model_name.ml_model_2" + + You will notice that the ``Model`` name `model_name` is **not** prefixed to `ml_model_1` since + we disabled ML model prefixing before sending the ML model to the ``Orchestrator``. However, + when we enabled ML model prefixing and sent the second ML model, the ``Model`` name was prefixed + to `ml_model_2`. + + .. group-tab:: Script + **Activate Script Prefixing in the Application** + + Users can configure script prefixing in the application by using + the ``Client`` function ``use_model_ensemble_prefix``. By specifying a boolean + value to the function, users can turn prefixing on and off. + + .. note:: + To have access to ``Client.use_model_ensemble_prefix``, prefixing must be enabled + on the ``Model`` in the driver script via ``Model.enable_key_prefixing``. + + In the application snippet below, we demonstrate enabling and disabling script prefixing: + + .. code-block:: python + + # Disable script prefixing + client.use_model_ensemble_prefix(False) + # Store a script in the Orchestrator + client.set_function("script_1", script_1) + # Enable script prefixing + client.use_model_ensemble_prefix(True) + # Store a prefixed script in the Orchestrator + client.set_function("script_2", script_2) + + In the application, two ML models named `script_1` and `script_2` are sent + to a launched ``Orchestrator``. The contents of the ``Orchestrator`` after ``Model`` completion are: + + .. code-block:: bash + + 1) "script_1" + 2) "model_name.script_2" + + You will notice that the ``Model`` name `model_name` is **not** prefixed to `script_1` since + we disabled script prefixing before sending the script to the ``Orchestrator``. However, + when we enabled script prefixing and sent the second script, the ``Model`` name was prefixed + to `script_2`. + +.. _get_prefix: + +Get Operations +============== +In the following sections, we walk through snippets of application code to demonstrate the retrieval +of prefixed tensors, ``Datasets``, lists, ML models, and scripts using SmartRedis put/get +semantics. The examples demonstrate retrieval within the same application where the data +structures were placed, as well as scenarios where data structures are placed by separate +applications. + +.. tabs:: + + .. group-tab:: Tensor + **Retrieve a Tensor Placed by the Same Application** + + SmartSim supports retrieving prefixed tensors sent to the ``Orchestrator`` from within the + same application where the tensor was placed. To achieve this, users must + provide the ``Model`` name that stored the tensor to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key searches. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name + in the driver script. + + As an example, we placed a prefixed tensor on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.tensor_name" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate retrieving the tensor: + + .. code-block:: python + + # Set the name to prepend to key searches + client.set_data_source("model_1") + # Retrieve the prefixed tensor + tensor_data = client.get_tensor("tensor_name") + # Log the tensor data + client.log_data(LLInfo, f"The tensor value is: {tensor_data}") + + In the `model.out` file, the ``Client`` will log the message:: + Default@00-00-00:The tensor value is: [1 2 3 4] + + **Retrieve a Tensor Placed by an External Application** + + SmartSim supports retrieving prefixed tensors sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the tensor + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data in the + driver script. + + In the example, a ``Model`` named `model_1` has placed a tensor in a standalone + ``Orchestrator`` with prefixing enabled on the ``Model``. The contents of the ``Orchestrator`` + are as follows: + + .. code-block:: bash + + 1) "model_1.tensor_name" + + We create a separate ``Model``, named `model_2`, with the executable application code below. + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + Here we retrieve the stored tensor named `tensor_name`: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Retrieve the prefixed tensor + tensor_data = client.get_tensor("tensor_name") + # Log the tensor data + client.log_data(LLInfo, f"The tensor value is: {tensor_data}") + + In the `model.out` file, the ``Client`` will log the message:: + Default@00-00-00:The tensor value is: [1 2 3 4] + + .. group-tab:: Dataset + **Retrieve a Dataset Placed by the Same Application** + + SmartSim supports retrieving prefixed ``Datasets`` sent to the ``Orchestrator`` from within the + same application where the ``Dataset`` was placed. To achieve this, users must + provide the ``Model`` name that stored the ``Dataset`` to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key searches. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed ``Dataset`` on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.{dataset_name}.dataset_tensor" + 2) "model_1.{dataset_name}.meta" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate retrieving the ``Dataset``: + + .. code-block:: python + + # Set the name to prepend to key searches + client.set_data_source("model_1") + # Retrieve the prefixed Dataset + dataset_data = client.get_dataset("dataset_name") + # Log the Dataset data + client.log_data(LLInfo, f"The Dataset value is: {dataset_data}") + + In the `model.out` file, the ``Client`` will log the message: + + .. code-block:: bash + + Default@00-00-00:Default@00-00-00:The dataset value is: + + DataSet (dataset_name): + Tensors: + dataset_tensor: + type: 16 bit unsigned integer + dimensions: [4] + elements: 4 + Metadata: + none + + **Retrieve a Dataset Placed by an External Application** + + SmartSim supports retrieving prefixed ``Datasets`` sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the ``Dataset`` + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a ``Dataset`` in a standalone + ``Orchestrator`` with prefixing enabled on the ``Model``. The contents of the ``Orchestrator`` + are as follows: + + .. code-block:: bash + + 1) "model_1.{dataset_name}.dataset_tensor" + 2) "model_1.{dataset_name}.meta" + + We create a separate ``Model``, named `model_2`, with the executable application code below. + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + Here we retrieve the stored ``Dataset`` named `dataset_name`: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Retrieve the prefixed Dataset + dataset_data = client.get_dataset("dataset_name") + # Log the Dataset data + client.log_data(LLInfo, f"The Dataset value is: {dataset_data}") + + In the `model.out` file, the ``Client`` will log the message: + + .. code-block:: bash + + Default@00-00-00:Default@00-00-00:The Dataset value is: + + DataSet (dataset_name): + Tensors: + dataset_tensor: + type: 16 bit unsigned integer + dimensions: [4] + elements: 4 + Metadata: + none + + .. group-tab:: Aggregation List + **Retrieve a Aggregation List Placed by the Same Application** + + SmartSim supports retrieving prefixed lists sent to the ``Orchestrator`` from within the + same application where the list was placed. To achieve this, users must + provide the ``Model`` name that stored the list to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key searches. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed list on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.dataset_list" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate checking the length of the list: + + .. code-block:: python + + # Set the name to prepend to key searches + client.set_data_source("model_1") + # Retrieve the prefixed list + list_data = client.get_datasets_from_list("dataset_list") + # Log the list data + client.log_data(LLInfo, f"The length of the list is: {len(list_data)}") + + In the `model.out` file, the ``Client`` will log the message:: + The length of the list is: 1 + + **Retrieve a Aggregation List Placed by an External Application** + + SmartSim supports retrieving prefixed lists sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the list + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a list in a standalone + ``Orchestrator`` with prefixing enabled on the ``Model``. The contents of the ``Orchestrator`` + are as follows: + + .. code-block:: bash + + 1) "model_name.dataset_list" + + We create a separate ``Model``, named `model_2`, with the executable application code below. + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + Here we check the length of the list named `dataset_list`: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Retrieve the prefixed list + list_data = client.get_datasets_from_list("dataset_list") + # Log the list data + client.log_data(LLInfo, f"The length of the list is: {len(list_data)}") + + In the `model.out` file, the ``Client`` will log the message:: + The length of the list is: 1 + + .. group-tab:: ML Model + **Retrieve a ML Model Placed by the Same Application** + + SmartSim supports retrieving prefixed ML models sent to the ``Orchestrator`` from within the + same application where the ML model was placed. To achieve this, users must + provide the ``Model`` name that stored the ML model to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key searches. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed ML model on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.mnist_cnn" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate retrieving the ML model: + + .. code-block:: python + + # Set the name to prepend to key searches + client.set_data_source("model_1") + # Retrieve the prefixed ML model + model_data = client.get_model("mnist_cnn") + + **Retrieve a ML Model Placed by an External Application** + + SmartSim supports retrieving prefixed ML model sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the ML model + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a ML model in a standalone + ``Orchestrator`` with prefixing enabled on the ``Model``. The contents of the ``Orchestrator`` + are as follows: + + .. code-block:: bash + + 1) "model_1.mnist_cnn" + + We create a separate ``Model``, named `model_2`, with the executable application code below. + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + Here we retrieve the stored ML model named `mnist_cnn`: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Retrieve the prefixed model + model_data = client.get_model("mnist_cnn") + + .. group-tab:: Script + **Retrieve a Script Placed by the Same Application** + + SmartSim supports retrieving prefixed scripts sent to the ``Orchestrator`` from within the + same application where the script was placed. To achieve this, users must + provide the ``Model`` name that stored the script to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key searches. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed script on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.normalizer" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate retrieving the script: + + .. code-block:: python + + # Set the name to prepend to key searches + client.set_data_source("model_1") + # Retrieve the prefixed script + script_data = client.get_script("normalizer") + # Log the script data + client.log_data(LLInfo, f"The script data is: {script_data}") + + In the `model.out` file, the ``Client`` will log the message: + + .. code-block:: bash + + The script data is: def normalize(X): + """Simple function to normalize a tensor""" + mean = X.mean + std = X.std + + return (X-mean)/std + + **Retrieve a Script Placed by an External Application** + + SmartSim supports retrieving prefixed scripts sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the script + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a script in a standalone + ``Orchestrator`` with prefixing enabled on the ``Model``. The contents of the ``Orchestrator`` + are as follows: + + .. code-block:: bash + + 1) "model_1.normalizer" + + We create a separate ``Model``, named `model_2`, with the executable application code below. + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + Here we retrieve the stored script named `normalizer`: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Retrieve the prefixed script + script_data = client.get_script("model_1.normalizer") + # Log the script data + client.log_data(LLInfo, f"The script data is: {script_data}") + + In the `model.out` file, the ``Client`` will log the message: + + .. code-block:: bash + + The script data is: def normalize(X): + """Simple function to normalize a tensor""" + mean = X.mean + std = X.std + + return (X-mean)/std + +.. _run_prefix: + +Run Operations +============== +In the following sections, we walk through snippets of application code to demonstrate executing +prefixed ML models and scripts using SmartRedis run semantics. The examples demonstrate +executing within the same application where the ML Model and Script were placed, as well as scenarios +where ML Model and Script are placed by separate applications. + +.. tabs:: + + .. group-tab:: ML Model + **Access ML Models From within the Application** + + SmartSim supports executing prefixed ML models with prefixed tensors sent to the ``Orchestrator`` from within + the same application that the ML model was placed. To achieve this, users must + provide the ``Model`` name that stored the ML model and input tensors to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key names. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed ML model and tensor on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.mnist_cnn" + 2) "model_1.mnist_images" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate running the ML model: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Run the ML model + client.run_model(name="mnist_cnn", inputs=["mnist_images"], outputs=["Identity"]) + + The ``Orchestrator`` now contains prefixed output tensors: + + .. code-block:: bash + + 1) "model_1.Identity" + 2) "model_1.mnist_cnn" + 3) "model_1.mnist_images" + + .. note:: + The output tensors are prefixed because we executed ``model_1.enable_key_prefixing`` + in the driver script which enables and activates prefixing for tensors, ``Datasets`` + and lists. + + **Access ML Models Loaded From an External Application** + + SmartSim supports executing prefixed ML models with prefixed tensors sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the ML model and tensor + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a ML model and tensor in a standalone + ``Orchestrator`` with prefixing enabled on the ``Model``. The contents of the ``Orchestrator`` + are as follows: + + .. code-block:: bash + + 1) "model_1.mnist_cnn" + 2) "model_1.mnist_images" + + We create a separate ``Model``, named `model_2`, with the executable application code below. + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate running the ML model: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Run the ML model + client.run_model(name="mnist_cnn", inputs=["mnist_images"], outputs=["Identity"]) + + The ``Orchestrator`` now contains prefixed output tensors: + + .. code-block:: bash + + 1) "model_2.Identity" + 2) "model_1.mnist_cnn" + 3) "model_1.mnist_images" + + .. note:: + The output tensors are prefixed because we executed ``model_2.enable_key_prefixing`` + in the driver script which enables and activates prefixing for tensors, ``Datasets`` + and lists. + + .. group-tab:: Script + + **Access Scripts From within the Application** + + SmartSim supports executing prefixed scripts with prefixed tensors sent to the ``Orchestrator`` from within + the same application that the script was placed. To achieve this, users must + provide the ``Model`` name that stored the script and input tensors to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key names. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed script and tensor on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.normalizer" + 2) "model_1.X_rand" + + To run the script, the prefixed script name `"model_name.normalizer"` and prefixed + input tensors `"model_name.X_rand"` must be provided, as demonstrated below: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Run the script + client.run_script("normalizer", "normalize", inputs=["X_rand"], outputs=["X_norm"]) + + The ``Orchestrator`` now contains prefixed output tensors: + + .. code-block:: bash + + 1) "model_1.normalizer" + 2) "model_1.X_rand" + 3) "model_1.X_norm" + + .. note:: + The output tensors are prefixed because we executed ``model_1.enable_key_prefixing`` + in the driver script which enables and activates prefixing for tensors, ``Datasets`` + and lists. + + **Access Scripts Loaded From an External Application** + + SmartSim supports executing prefixed scripts with prefixed tensors sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the script and tensor + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a script and tensor in a standalone + ``Orchestrator`` with prefixing enabled on the ``Model``. The contents of the ``Orchestrator`` + are as follows: + + .. code-block:: bash + + 1) "model_1.normalizer" + 2) "model_1.X_rand" + + We create a separate ``Model``, named `model_2`, with the executable application code below. + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for use in ``Client.set_data_source``. + + In the application snippet below, we demonstrate running the script: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Run the script + client.run_script("normalizer", "normalize", inputs=["X_rand"], outputs=["X_norm"]) + + The ``Orchestrator`` now contains prefixed output tensors: + + .. code-block:: bash + + 1) "model_1.normalizer" + 2) "model_1.X_rand" + 3) "model_2.X_norm" + + .. note:: + The output tensors are prefixed because we executed ``model_2.enable_key_prefixing`` + in the driver script which enables and activates prefixing for tensors, ``Datasets`` + and lists. + +.. _copy_rename_del_prefix: + +Copy/Rename/Delete Operations +============================= +In the following sections, we walk through snippets of application code to demonstrate the copy, rename and delete +operations on prefixed tensors, ``Datasets``, lists, ML models, and scripts. The examples +demonstrate these operations within the same script where the data +structures were placed, as well as scenarios where data structures are placed by separate +scripts. + +.. tabs:: + + .. group-tab:: Tensor + **Copy/Rename/Delete Operations on Tensors in The Same Application** + + SmartSim supports copy/rename/delete operations on prefixed tensors sent to the ``Orchestrator`` from within + the same application that the tensor was placed. To achieve this, users must + provide the ``Model`` name that stored the tensor to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key names. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed tensor on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.tensor" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + To rename the tensor in the ``Orchestrator``, we provide self ``Model`` name + to ``Client.set_data_source`` then execute the function ``rename_tensor``: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Rename the tensor + client.rename_tensor("tensor", "renamed_tensor") + + Because prefixing is enabled on the ``Model`` via ``enable_key_prefixing`` in the driver script, + SmartSim will keep the prefix on the tensor but replace the tensor name as shown in the ``Orchestrator``: + + .. code-block:: bash + + 1) "model_1.renamed_tensor" + + Next, we copy the prefixed tensor to a new destination: + + .. code-block:: python + + client.copy_tensor("renamed_tensor", "copied_tensor") + + Since tensor prefixing is enabled on the ``Client``, the `copied_tensor` is prefixed: + + .. code-block:: bash + + 1) "model_1.renamed_tensor" + 2) "model_1.copied_tensor" + + Next, delete `renamed_tensor`: + + .. code-block:: python + + client.delete_tensor("renamed_tensor") + + The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_1.copied_tensor" + + **Copy/Rename/Delete Operations on Tensors Placed by an External Application** + + SmartSim supports copy/rename/delete operations on prefixed tensors sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the tensor + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a tensor in a standalone ``Orchestrator`` with prefixing enabled + on the ``Client``. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.tensor" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + From within a separate ``Model`` named `model_2`, we perform basic copy/rename/delete operations. + To instruct the ``Client`` to prepend a ``Model`` name to all key searches, use the + ``Client.set_data_source`` function. Specify the ``Model`` name `model_1` + that placed the tensor in the ``Orchestrator``: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + + To rename the tensor in the ``Orchestrator``, we provide the tensor name: + + .. code-block:: python + + client.rename_tensor("tensor", "renamed_tensor") + + SmartSim will replace the prefix with the current ``Model`` name since prefixing is enabled + on the current ``Model``. The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_2.renamed_tensor" + + .. note:: + In the driver script, we also register `model_2` as an entity on itself via ``model_2.register_incoming_entity(model_2)``. + This way we can use ``Client.set_data_source`` to interact with prefixed data placed by `model_2`. + + Next, we copy the prefixed tensor to a new destination: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_2") + # Copy the tensor data + client.copy_tensor("renamed_tensor", "copied_tensor") + + The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_2.renamed_tensor" + 2) "model_2.copied_tensor" + + Next, delete `copied_tensor` by specifying the name: + + .. code-block:: python + + client.delete_tensor("copied_tensor") + + The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_2.renamed_tensor" + + .. group-tab:: Dataset + **Copy/Rename/Delete Operations on A Dataset in The Same Application** + + SmartSim supports copy/rename/delete operations on prefixed ``Datasets`` sent to the ``Orchestrator`` from within + the same application that the ``Dataset`` was placed. To achieve this, users must + provide the ``Model`` name that stored the ``Dataset`` to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key names. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed ``Dataset`` on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.{dataset}.dataset_tensor" + 2) "model_1.{dataset}.meta" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + To rename the ``Dataset`` in the ``Orchestrator``, we provide self ``Model`` name + to ``Client.set_data_source`` then execute the function ``rename_tensor``: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Rename the Dataset + client.rename_dataset("dataset", "renamed_dataset") + + Because prefixing is enabled on the ``Model`` via ``enable_key_prefixing`` in the driver script, + SmartSim will keep the prefix on the ``Dataset`` but replace the ``Dataset`` name as shown in the ``Orchestrator``: + + .. code-block:: bash + + 1) "model_1.{renamed_dataset}.dataset_tensor" + 2) "model_1.{renamed_dataset}.meta" + + Next, we copy the prefixed ``Dataset`` to a new destination: + + .. code-block:: python + + client.copy_dataset("renamed_dataset", "copied_dataset") + + Since ``Dataset`` prefixing is enabled on the ``Client``, the `copied_dataset` is prefixed: + + .. code-block:: bash + + 1) "model_1.{renamed_dataset}.dataset_tensor" + 2) "model_1.{renamed_dataset}.meta" + 3) "model_1.{copied_dataset}.dataset_tensor" + 4) "model_1.{copied_dataset}.meta" + + Next, delete `copied_dataset`: + + .. code-block:: python + + client.delete_dataset("model_name.copied_dataset") + + The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_1.{renamed_dataset}.dataset_tensor" + 2) "model_1.{renamed_dataset}.meta" + + **Copy/Rename/Delete Operations on Datasets Placed by an External Application** + + SmartSim supports copy/rename/delete operations on prefixed ``Datasets`` sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the ``Dataset`` + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a ``Dataset`` in a standalone ``Orchestrator`` with prefixing enabled + on the ``Client``. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.{dataset}.dataset_tensor" + 2) "model_1.{dataset}.meta" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + From within a separate ``Model`` named `model_2`, we perform basic copy/rename/delete operations. + To instruct the ``Client`` to prepend a ``Model`` name to all key searches, use the + ``Client.set_data_source`` function. Specify the ``Model`` name `model_1` + that placed the ``Dataset`` in the ``Orchestrator``: + + .. code-block:: python + + client.set_data_source("model_1") + + To rename the ``Dataset`` in the ``Orchestrator``, we provide the ``Dataset`` `name`: + + .. code-block:: python + + client.rename_tensor("dataset", "renamed_dataset") + + SmartSim will replace the prefix with the current ``Model`` name since prefixing is enabled + on the current ``Model`` via ``Model.enable_key_prefixing`` in the driver script. + The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_2.{renamed_dataset}.dataset_tensor" + 2) "model_2.{renamed_dataset}.meta" + + .. note:: + In the driver script, we also register `model_2` as an entity on itself via ``model_2.register_incoming_entity(model_2)``. + This way we can use ``Client.set_data_source`` to interact with prefixed data placed by `model_2`. + + Next, we copy the prefixed ``Dataset`` to a new destination: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_2") + # Copy the tensor data + client.copy_dataset("renamed_dataset", "copied_dataset") + + The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_2.{renamed_dataset}.dataset_tensor" + 2) "model_2.{renamed_dataset}.meta" + 3) "model_2.{copied_dataset}.dataset_tensor" + 4) "model_2.{copied_dataset}.meta" + + Next, delete `copied_dataset` by specifying the name: + + .. code-block:: python + + client.delete_dataset("copied_tensor") + + The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_2.{renamed_dataset}.dataset_tensor" + 2) "model_2.{renamed_dataset}.meta" + + .. group-tab:: Aggregation List + **Copy/Rename/Delete Operations on a Aggregation List in The Same Application** + + SmartSim supports copy/rename/delete operations on prefixed lists sent to the ``Orchestrator`` from within + the same application that the list was placed. To achieve this, users must + provide the ``Model`` name that stored the list to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key names. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed list on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.list_of_datasets" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + To rename the list in the ``Orchestrator``, we provide self ``Model`` name + to ``Client.set_data_source`` then execute the function ``rename_list``: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Rename the list + client.rename_list("list_of_datasets", "renamed_list") + + Because prefixing is enabled on the ``Model`` via ``enable_key_prefixing`` in the driver script, + SmartSim will keep the prefix on the list but replace the list name as shown in the ``Orchestrator``: + + .. code-block:: bash + + 1) "model_1.renamed_list" + + Next, we copy the prefixed list to a new destination: + + .. code-block:: python + + client.copy_list("renamed_list", "copied_list") + + Since list prefixing is enabled on the ``Client``, the `copied_list` is prefixed: + + .. code-block:: bash + + 1) "model_1.renamed_list" + 2) "model_1.copied_list" + + Next, delete `copied_list`: + + .. code-block:: python + + client.delete_list("copied_list") + + The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_1.renamed_list" + + **Copy/Rename/Delete Operations on Aggregation Lists Placed by an External Application** + + SmartSim supports copy/rename/delete operations on prefixed lists sent to the ``Orchestrator`` by separate + ``Model(s)``. To achieve this, users need to provide the ``Model`` name that stored the list + to ``Client.set_data_source``. This action instructs the ``Client`` to prepend the ``Model`` + name to all key searches. For SmartSim to recognize the ``Model`` name as a data source, + users must execute the ``Model.register_incoming_entity`` function on the ``Model`` + responsible for the search and pass the ``Model`` instance that stored the data. + + In the example, a ``Model`` named `model_1` has placed a list in a standalone ``Orchestrator`` with prefixing enabled + on the ``Client``. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.list_of_datasets" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_2`, + we execute ``model_2.register_incoming_entity(model_1)``. By passing the producer ``Model`` + instance to the consumer ``Model``, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + From within a separate ``Model`` named `model_2`, we perform basic copy/rename/delete operations. + To instruct the ``Client`` to prepend a ``Model`` name to all key searches, use the + ``Client.set_data_source`` function. Specify the ``Model`` name `model_1` + that placed the list in the ``Orchestrator``: + + .. code-block:: python + + client.set_data_source("model_1") + + To rename the list in the ``Orchestrator``, we provide the list name: + + .. code-block:: python + + client.rename_list("list_of_datasets", "renamed_list") + + SmartSim will replace the prefix with the current ``Model`` name since prefixing is enabled + on the current ``Model``. The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_2.renamed_list" + + .. note:: + In the driver script, we also register `model_2` as an entity on itself via ``model_2.register_incoming_entity(model_2)``. + This way we can use ``Client.set_data_source`` to interact with prefixed data placed by `model_2`. + + Next, we copy the prefixed list to a new destination: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_2") + # Copy the tensor data + client.copy_dataset("renamed_list", "copied_list") + + The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_2.renamed_list" + 2) "model_2.copied_list" + + Next, delete `copied_list` by specifying the name: + + .. code-block:: python + + client.delete_list("copied_list") + + The contents of the ``Orchestrator`` are: + + .. code-block:: bash + + 1) "model_2.renamed_list" + + .. group-tab:: ML Model + **Delete ML Models From within the Application** + + SmartSim supports delete operations on prefixed ML models sent to the ``Orchestrator`` from within + the same application that the ML model was placed. To achieve this, users must + provide the ``Model`` name that stored the ML model to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key names. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed ML model on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + .. code-block:: bash + + 1) "model_1.ml_model" + + To delete the ML model in the ``Orchestrator``, we provide self ``Model`` name + to ``Client.set_data_source`` then execute the function ``delete_model``: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Delete the ML model + client.delete_model("ml_model") + + **Delete a ML Model Placed by an External Application** + + SmartSim supports delete operations on prefixed ML models sent to the ``Orchestrator`` by separate ``Model(s)``. + To do so, users must provide the ``Model`` name that stored the ML model to ``Client.set_data_source``. + This will instruct the ``Client`` to prepend the ``Model`` name input to all key searches. + + In the example, a ``Model`` named `model_1` has placed a ML model in a standalone ``Orchestrator`` with prefixing enabled + on the ``Client``. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.ml_model" + + From within a separate ``Model`` named `model_2`, we perform a basic delete operation. + To instruct the ``Client`` to prepend a ``Model`` name to all key searches, use the + ``Client.set_data_source`` function. Specify the ``Model`` name `model_1` + that placed the list in the ``Orchestrator``: + + .. code-block:: python + + client.set_data_source("model_1") + + To delete the ML model in the ``Orchestrator``, we provide the ML model name: + + .. code-block:: python + + client.delete_model("ml_model") + + .. group-tab:: Script + + **Delete Scripts From within the Application** + + SmartSim supports delete operations on prefixed scripts sent to the ``Orchestrator`` from within + the same application that the script was placed. To achieve this, users must + provide the ``Model`` name that stored the script to ``Client.set_data_source``. This action + instructs the ``Client`` to prepend the ``Model`` name to all key names. For SmartSim to + recognize the ``Model`` name as a data source, users must execute the + ``Model.register_incoming_entity`` function on the ``Model`` and pass the self ``Model`` name. + + As an example, we placed a prefixed script on the ``Orchestrator`` within a ``Model`` named + `model_1`. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.script" + + .. note:: + In the driver script, after initializing the ``Model`` instance named `model_1`, + we execute ``model_1.register_incoming_entity(model_1)``. By passing the ``Model`` + instance to itself, we instruct SmartSim to recognize the name of `model_1` as a valid data + source for subsequent use in ``Client.set_data_source``. + + To delete the script in the ``Orchestrator``, we provide the full list name: + + .. code-block:: python + + # Set the Model source name + client.set_data_source("model_1") + # Rename the script + client.delete_script("script") + + **Delete a Script Placed by an External Application** + + SmartSim supports delete operations on prefixed scripts sent to the ``Orchestrator`` by separate ``Model(s)``. + To do so, users must provide the ``Model`` name that stored the script to ``Client.set_data_source``. + This will instruct the ``Client`` to prepend the ``Model`` name input to all key searches. + + In the example, a ``Model`` named `model_1` has placed a ML model in a standalone ``Orchestrator`` with prefixing enabled + on the ``Client``. The ``Orchestrator`` contents are: + + .. code-block:: bash + + 1) "model_1.script" + + From within a separate ``Model`` named `model_2`, we perform a basic delete operation. + To instruct the ``Client`` to prepend a ``Model`` name to all key searches, use the + ``Client.set_data_source`` function. Specify the ``Model`` name `model_1` + that placed the list in the ``Orchestrator``: + + .. code-block:: python + + client.set_data_source("model_1") + + To delete the script in the ``Orchestrator``, we provide the script name: + + .. code-block:: python + + client.delete_model("script") \ No newline at end of file diff --git a/doc/orchestrator.rst b/doc/orchestrator.rst index 456d9a814..6ccc7c1e1 100644 --- a/doc/orchestrator.rst +++ b/doc/orchestrator.rst @@ -1,208 +1,688 @@ +.. _orch_docs: + ************ Orchestrator ************ +======== +Overview +======== +The ``Orchestrator`` is an in-memory database with features built for +AI-enabled workflows including online training, low-latency inference, cross-application data +exchange, online interactive visualization, online data analysis, computational steering, and more. + +An ``Orchestrator`` can be thought of as a general feature store +capable of storing numerical data (tensors and ``Datasets``), AI models (TF, TF-lite, PyTorch, or ONNX), +and scripts (TorchScripts). In addition to storing data, the ``Orchestrator`` is capable of +executing AI models and TorchScripts on the stored data using CPUs or GPUs. + +.. figure:: images/smartsim-arch.png + + Sample ``Experiment`` showing a user application leveraging + machine learning infrastructure launched by SmartSim and connected + to an online analysis and visualization simulation via the ``Orchestrator``. + +Users can establish a connection to the ``Orchestrator`` from within ``Model`` executable code, ``Ensemble`` +member executable code, or ``Experiment`` driver scripts by using the +:ref:`SmartRedis` ``Client`` library. + +SmartSim offers **two** types of ``Orchestrator`` deployments: + +- :ref:`Standalone Deployment` + A standalone ``Orchestrator`` is ideal for systems that have heterogeneous node types + (i.e. a mix of CPU-only and GPU-enabled compute nodes) where + ML model and TorchScript evaluation is more efficiently performed off-node. This + deployment is also ideal for workflows relying on data exchange between multiple + applications (e.g. online analysis, visualization, computational steering, or + producer/consumer application couplings). Standalone deployment is also optimal for + high data throughput scenarios where ``Orchestrators`` require large amounts of compute resources. + +- :ref:`Colocated Deployment` + A colocated ``Orchestrator`` is ideal when the data and hardware accelerator are located on the same compute node. + This setup helps reduce latency in ML inference and TorchScript evaluation by eliminating off-node communication. +.. warning:: + Colocated ``Orchestrators`` cannot share data across compute nodes. + Communication is only supported between a ``Model`` and colocated ``Orchestrator`` pair. + +SmartSim allows users to launch :ref:`multiple Orchestrators` of either type during +the course of an ``Experiment``. If a workflow requires a multiple ``Orchestrator`` environment, a +`db_identifier` argument must be specified during ``Orchestrator`` initialization. Users can connect to +``Orchestrators`` in a multiple ``Orchestrator`` workflow by specifying the respective `db_identifier` argument +within a :ref:`ConfigOptions` object that is passed into the SmartRedis ``Client`` constructor. + +.. _standalone_orch_doc: + +===================== +Standalone Deployment +===================== +-------- +Overview +-------- +During standalone ``Orchestrator`` deployment, a SmartSim ``Orchestrator`` (the database) runs on separate +compute node(s) from the SmartSim ``Model`` node(s). A standalone ``Orchestrator`` can be deployed on a single +node (single-sharded) or distributed (sharded) over multiple nodes. With a multi-node ``Orchestrator``, users can +scale the number of database nodes for inference and script evaluation, enabling +increased in-memory capacity for data storage in large-scale workflows. Single-node +``Orchestrators`` are effective for small-scale workflows and offer lower latency for ``Client`` API calls +that involve data appending or processing (e.g. ``Client.append_to_list``, ``Client.run_model``, etc). + +When connecting to a standalone ``Orchestrator`` from within a ``Model`` application, the user has +several options to connect a SmartRedis ``Client``: + +- In an ``Experiment`` with a single deployed ``Orchestrator``, users can rely on SmartRedis + to detect the ``Orchestrator`` address through runtime configuration of the SmartSim ``Model`` environment. + A default ``Client`` constructor, with no user-specified parameters, is sufficient to + connect to the ``Orchestrator``. The only exception is for the Python ``Client``, which requires + the `cluster` constructor parameter to differentiate between standalone deployment and colocated + deployment. +- In an ``Experiment`` with multiple ``Orchestrators``, users can connect to a specific ``Orchestrator`` by + first specifying the `db_identifier` in the ``ConfigOptions`` constructor within the executable application. + Subsequently, users should pass the ``ConfigOptions`` instance to the ``Client`` constructor. +- Users can specify or override automatically configured connection options by providing the + ``Orchestrator`` address in the ``ConfigOptions`` object. Subsequently, users should pass the ``ConfigOptions`` + instance to the ``Client`` constructor. + +If connecting to a standalone ``Orchestrator`` from a ``Experiment`` driver script, the user must specify +the address of the ``Orchestrator`` to the ``Client`` constructor. SmartSim does not automatically +configure the environment of the ``Experiment`` driver script to connect to an ``Orchestrator``. Users +can access an ``Orchestrators`` address through ``Orchestrator.get_address``. -The ``Orchestrator`` is an in-memory database that is launched prior to all other -entities within an ``Experiment``. The ``Orchestrator`` can be used to store and retrieve -data during the course of an experiment and across multiple entities. In order to -stream data into or receive data from the ``Orchestrator``, one of the SmartSim clients -(SmartRedis) has to be used within a Model. +.. note:: + In SmartSim ``Model`` applications, it is advisable to **avoid** specifying addresses directly to the ``Client`` constructor. + Utilizing the SmartSim environment configuration for SmartRedis ``Client`` connections + allows the SmartSim ``Model`` application code to remain unchanged even as ``Orchestrator`` deployment + options vary. -.. |orchestrator| image:: images/Orchestrator.png - :width: 700 - :alt: Alternative text +The following image illustrates +communication between a standalone ``Orchestrator`` and a +SmartSim ``Model``. In the diagram, the application is running on multiple compute nodes, +separate from the ``Orchestrator`` compute nodes. Communication is established between the +``Model`` application and the sharded ``Orchestrator`` using the :ref:`SmartRedis client`. -|orchestrator| +.. figure:: images/clustered_orchestrator-1.png -Combined with the SmartRedis clients, the ``Orchestrator`` is capable of hosting and executing -AI models written in Python on CPU or GPU. The ``Orchestrator`` supports models written with -TensorFlow, Pytorch, TensorFlow-Lite, or models saved in an ONNX format (e.g. sci-kit learn). + Sample Standalone ``Orchestrator`` Deployment +.. note:: + Users do not need to know how the data is stored in a standalone configuration and + can address the cluster with the SmartRedis ``Client`` like a single block of memory + using simple put/get semantics in SmartRedis. + +In scenarios where data needs to be shared amongst ``Experiment`` entities, +such as online analysis, training, and processing, a standalone ``Orchestrator`` +is optimal. The data produced by multiple processes in a ``Model`` is stored in the standalone +``Orchestrator`` and is available for consumption by other ``Model``'s. + +If a workflow requires an application to leverage multiple standalone deployments, +multiple ``Clients`` can be instantiated within an application, +with each ``Client`` connected to a unique ``Orchestrator``. This is accomplished through the use of the +`db-identifier` and :ref:`ConfigOptions` object specified at ``Orchestrator`` initialization time. +For more information on a multiple database ``Experiment``, visit the :ref:`Multiple Orchestrators` section on +this page. + +------- +Example +------- +In the following example, we demonstrate deploying a standalone ``Orchestrator`` on an HPC system. +Once the standalone ``Orchestrator`` is launched from the ``Experiment`` driver script, we walk through +connecting a SmartRedis ``Client`` to the ``Orchestrator`` from within the ``Model`` +application to transmit and poll for data. -Cluster Orchestrator -==================== +The example is comprised of two script files: + +- :ref:`Application Script` + The application script is a Python file that contains instructions to create a SmartRedis + ``Client`` connection to the standalone ``Orchestrator``. To demonstrate the ability of + workflow components to access data from other entities, we retrieve the tensors set by + the driver script using a SmartRedis ``Client`` in the application script. We then instruct + the ``Client`` to send and retrieve data from within the application script. The example source + code is available in the dropdown below for convenient execution and customization. + + .. dropdown:: Example Application Script source code + + .. literalinclude:: tutorials/doc_examples/orch_examples/std_app.py -The ``Orchestrator`` supports single node and distributed memory settings. This means -that a single compute host can be used for the database or multiple by specifying -``db_nodes`` to be greater than 1. +- :ref:`Experiment Driver Script` + The ``Experiment`` driver script is responsible for launching and managing SmartSim entities. Within this script, + we use the ``Experiment`` API to create and launch a standalone ``Orchestrator``. To demonstrate the capability of + a ``Model`` application to access ``Orchestrator`` data sent from other sources, we employ the SmartRedis ``Client`` in + the driver script to store a tensor in the ``Orchestrator``, which is later retrieved by the ``Model`` application. + To employ the application script, we initialize a ``Model`` object with the application script as the executable, + launch the ``Orchestrator``, and then launch the ``Model``. -.. |cluster-orc| image:: images/clustered-orc-diagram.png - :width: 700 - :alt: Alternative text + To further demonstrate the ability of workflow components to access data from + other entities, we retrieve the tensors stored by the completed ``Model`` using a SmartRedis ``Client`` in + the driver script. Lastly, we tear down the ``Orchestrator``. The example source code is available in the dropdown below for + convenient execution and customization. -|cluster-orc| + .. dropdown:: Example Experiment Driver Script Source Code + .. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py -With a clustered ``Orchestrator``, multiple compute hosts memory can be used together -to store data. As well, the CPU or GPU(s) where the ``Orchestrator`` is running can -be used to execute the AI models, and Torchscript code on data stored within it. +.. _standalone_orch_app_script: -Users do not need to know how the data is stored in a clustered configuration and -can address the cluster with the SmartRedis clients like a single block of memory -using simple put/get semantics in SmartRedis. SmartRedis will ensure that data -is evenly distributed amongst all nodes in the cluster. +Application Script +================== +To begin writing the application script, import the necessary SmartRedis packages: -The cluster deployment is optimal for high data throughput scenarios such as -online analysis, training and processing. +.. literalinclude:: tutorials/doc_examples/orch_examples/std_app.py + :language: python + :linenos: + :lines: 1-2 +Client Initialization +--------------------- +To establish a connection with the ``Orchestrator``, we need to initialize a new SmartRedis ``Client``. +Because the ``Orchestrator`` launched in the driver script is sharded, we specify the +constructor argument `cluster` as `True`. -Colocated Orchestrator -======================= +.. literalinclude:: tutorials/doc_examples/orch_examples/std_app.py + :language: python + :linenos: + :lines: 4-5 -A colocated Orchestrator is a special type of Orchestrator that is deployed on -the same compute hosts an a ``Model`` instance defined by the user. In this -deployment, the database is *not* connected together in a cluster and each -shard of the database is addressed individually by the processes running -on that compute host. +.. note:: + Note that the C/C++/Fortran SmartRedis ``Clients`` are capable of reading cluster configurations + from the SmartSim ``Model`` environment and the `cluster` constructor argument does not need to be specified + in those ``Client`` languages. -.. |colo-orc| image:: images/co-located-orc-diagram.png - :width: 700 - :alt: Alternative text +Since there is only one ``Orchestrator`` launched in the ``Experiment`` +(the standalone ``Orchestrator``), specifying an ``Orchestrator`` `db_identifier` +is **not** required when initializing the SmartRedis ``Client``. +SmartRedis will handle the connection configuration. +.. note:: + To create a SmartRedis ``Client`` connection to the standalone ``Orchestrator``, the ``Orchestrator`` must be launched + from within the driver script prior to the start of the ``Model``. -|colo-orc| +Data Retrieval +-------------- +To confirm a successful connection to the ``Orchestrator``, we retrieve the tensor set from the ``Experiment`` script. +Use the ``Client.get_tensor`` method to retrieve the tensor named `tensor_1` placed by the driver script: -This deployment is designed for highly performant online inference scenarios where -a distributed process (likely MPI processes) are performing inference with -data local to each process. +.. literalinclude:: tutorials/doc_examples/orch_examples/std_app.py + :language: python + :linenos: + :lines: 7-10 -This method is deemed ``locality based inference`` since data is local to each -process and the ``Orchestrator`` is deployed locally on each compute host where -the distributed application is running. +After the ``Model`` is launched by the driver script, the following output will appear in +`getting-started/model/model.out`:: + Default@17-11-48:The multi-sharded db tensor is: [1 2 3 4] -To create a colocated model, first, create a ``Model`` instance and then call -the ``Model.colocate_db_tcp`` or ``Model.colocate_db_uds`` function. +Data Storage +------------ +Next, create a NumPy tensor to send to the standalone ``Orchestrator`` using +``Client.put_tensor(name, data)``: -.. currentmodule:: smartsim.entity.model +.. literalinclude:: tutorials/doc_examples/orch_examples/std_app.py + :language: python + :linenos: + :lines: 12-15 -.. automethod:: Model.colocate_db_tcp - :noindex: +We retrieve `"tensor_2"` in the ``Experiment`` driver script. -.. automethod:: Model.colocate_db_uds - :noindex: +.. _standalone_orch_driver_script: -Here is an example of creating a simple model that is colocated with an -``Orchestrator`` deployment using Unix Domain Sockets +Experiment Driver Script +======================== +To run the previous application script, we define a ``Model`` and ``Orchestrator`` within the +``Experiment`` driver script. Configuring and launching workflow entities (``Model`` and ``Orchestrator``) requires the utilization of +``Experiment`` class methods. The ``Experiment`` object is intended to be instantiated +once and utilized throughout the workflow runtime. -.. code-block:: python +In this example, we instantiate an ``Experiment`` object with the name `getting-started` +and the `launcher` set to `auto`. When using `launcher=auto`, SmartSim attempts to find a launcher on the machine. +For example, if this script were run on a Slurm-based system, SmartSim will automatically set the launcher to `slurm`. +We also setup the SmartSim `logger` to output information from the ``Experiment`` at runtime: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 1-9 + +Orchestrator Initialization +--------------------------- +In the next stage of the ``Experiment``, we create a standalone ``Orchestrator``. + +To create a standalone ``Orchestrator``, utilize the ``Experiment.create_database`` function: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 11-12 + +Client Initialization +--------------------- +The SmartRedis ``Client`` object contains functions that manipulate, send, and retrieve +data on the ``Orchestrator``. Begin by initializing a SmartRedis ``Client`` object for the standalone ``Orchestrator``. + +SmartRedis ``Clients`` in driver scripts do not have the ability to use a `db-identifier` or +rely on automatic configurations to connect to ``Orchestrators``. Therefore, when creating a SmartRedis ``Client`` +connection from within a driver script, specify the address of the ``Orchestrator`` you would like to connect to. +You can easily retrieve the ``Orchestrator`` address using the ``Orchestrator.get_address`` function: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 14-15 + +Data Storage +------------ +In the application script, we retrieved a NumPy tensor stored from within the driver script. +To support the application functionality, we create a +NumPy array in the ``Experiment`` driver script to send to the ``Orchestrator``. To +send a tensor to the ``Orchestrator``, use the function ``Client.put_tensor(name, data)``: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 17-20 + +Model Initialization +-------------------- +In the next stage of the ``Experiment``, we configure and create +a SmartSim ``Model`` and specify the executable path during ``Model`` creation: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 22-27 + +File Generation +--------------- +To create an isolated output directory for the ``Orchestrator`` and ``Model``, invoke ``Experiment.generate`` on the +``Experiment`` instance `exp` with `standalone_orchestrator` and `model` as input parameters: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 29-30 + +Invoking ``Experiment.generate(standalone_orchestrator, model)`` will create two directories: +`standalone_orchestrator/` and `model/`. Each of these directories will store +two output files: a `.out` file and a `.err` file. - from smartsim import Experiment - exp = Experiment("colo-test", launcher="auto") +.. note:: + It is important to invoke ``Experiment.generate`` with all ``Experiment`` entity instances + before launching. This will ensure that the output files are organized in the main ``experiment-name/`` + folder. In this example, the ``Experiment`` folder is named `getting-started/`. - colo_settings = exp.create_run_settings(exe="./some_mpi_app") +Entity Deployment +----------------- +In the next stage of the ``Experiment``, we launch the ``Orchestrator``, then launch the ``Model``. - colo_model = exp.create_model("colocated_model", colo_settings) - colo_model.colocate_db_uds( - db_cpus=1, # cpus given to the database on each node - debug=False # include debug information (will be slower) - ifname=network_interface # specify network interface(s) to use (i.e. "ib0" or ["ib0", "lo"]) - ) - exp.start(colo_model) +Step 1: Start Orchestrator +'''''''''''''''''''''''''' +In the context of this ``Experiment``, it's essential to create and launch +the ``Orchestrator`` as a preliminary step before any other workflow entities. This is important +because the application requests and sends tensors to a launched ``Orchestrator``. +To launch the ``Orchestrator``, pass the ``Orchestrator`` instance to ``Experiment.start``. -By default, SmartSim will pin the database to the first _N_ CPUs according to ``db_cpus``. By -specifying the optional argument ``custom_pinning``, an alternative pinning can be specified -by sending in a list of CPU ids (e.g [0,2,range(5,8)]). For optimal performance, most users -will want to also modify the RunSettings for the model to pin their application to cores not -occupied by the database. +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 32-33 -.. warning:: +The ``Experiment.start`` function launches the ``Orchestrator`` for use within the workflow. +In other words, the function deploys the ``Orchestrator`` on the allocated compute resources. + +Step 2: Start Model +''''''''''''''''''' +Next, launch the `model` instance using the ``Experiment.start`` function: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 35-36 + +In the next subsection, we request tensors placed by the ``Model`` application. +We specify `block=True` to ``exp.start`` to require the ``Model`` to finish before +the ``Experiment`` continues. + +Data Polling +------------ +Next, check if the tensor exists in the standalone ``Orchestrator`` using ``Client.poll_tensor``. +This function queries for data in the ``Orchestrator``. The function requires the tensor name (`name`), +how many milliseconds to wait in between queries (`poll_frequency_ms`), +and the total number of times to query (`num_tries`). Check if the data exists in the ``Orchestrator`` by +polling every 100 milliseconds until 10 attempts have completed: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 38-41 + +When you execute the driver script, the output will be as follows:: + + 23:45:46 system.host.com SmartSim[87400] INFO The tensor exists: True + +Cleanup +------- +Finally, use the ``Experiment.stop`` function to stop the ``Orchestrator`` instance. Print the +workflow summary with ``Experiment.summary``: + +.. literalinclude:: tutorials/doc_examples/orch_examples/std_driver.py + :language: python + :linenos: + :lines: 43-46 + +When you run the ``Experiment``, the following output will appear:: + + | | Name | Entity-Type | JobID | RunID | Time | Status | Returncode | + |----|----------------|---------------|-------------|---------|---------|-----------|--------------| + | 0 | model | Model | 1658679.3 | 0 | 1.3342 | Completed | 0 | + | 1 | orchestrator_0 | DBNode | 1658679.2+2 | 0 | 42.8742 | Cancelled | 0 | + +.. _colocated_orch_doc: + +==================== +Colocated Deployment +==================== +-------- +Overview +-------- +During colocated ``Orchestrator`` deployment, a SmartSim ``Orchestrator`` (the database) runs on +the ``Model``'s compute node(s). Colocated ``Orchestrators`` can only be deployed as isolated instances +on each compute node and cannot be clustered over multiple nodes. The ``Orchestrator`` on each application node is +utilized by SmartRedis ``Clients`` on the same node. With a colocated ``Orchestrator``, all interactions +with the database occur on the same node, thus resulting in lower latency compared to the standard ``Orchestrator``. +A colocated ``Orchestrator`` is ideal when the data and hardware accelerator are located on the +same compute node. + +Communication between a colocated ``Orchestrator`` and ``Model`` is initiated in the application through a +SmartRedis ``Client``. Since a colocated ``Orchestrator`` is launched when the ``Model`` +is started by the ``Experiment``, connecting a SmartRedis ``Client`` to a colocated ``Orchestrator`` is only possible from within +the associated ``Model`` application. + +There are **three** methods for connecting the SmartRedis ``Client`` to the colocated ``Orchestrator``: + +- In an ``Experiment`` with a single deployed ``Orchestrator``, users can rely on SmartRedis + to detect the ``Orchestrator`` address through runtime configuration of the SmartSim ``Model`` environment. + A default ``Client`` constructor, with no user-specified parameters, is sufficient to + connect to the ``Orchestrator``. The only exception is for the Python ``Client``, which requires + the `cluster=False` constructor parameter for the colocated ``Orchestrator``. +- In an ``Experiment`` with multiple ``Orchestrators``, users can connect to a specific ``Orchestrator`` by + first specifying the `db_identifier` in the ``ConfigOptions`` constructor. Subsequently, users should pass the + ``ConfigOptions`` instance to the ``Client`` constructor. +- Users can specify or override automatically configured connection options by providing the + ``Orchestrator`` address in the ``ConfigOptions`` object. Subsequently, users should pass the ``ConfigOptions`` + instance to the ``Client`` constructor. + +Below is an image illustrating communication within a colocated ``Model`` spanning multiple compute nodes. +As demonstrated in the diagram, each process of the application creates its own SmartRedis ``Client`` +connection to the ``Orchestrator`` running on the same host. + +.. figure:: images/colocated_orchestrator-1.png + + Sample Colocated ``Orchestrator`` Deployment + +Colocated deployment is ideal for highly performant online inference scenarios where +a distributed application (likely an MPI application) is performing inference with +data local to each process. With colocated deployment, data does not need to travel +off-node to be used to evaluate a ML model, and the results of the ML model evaluation +are stored on-node. + +If a workflow requires an application to both leverage colocated +deployment and standalone deployment, multiple ``Clients`` can be instantiated within an application, +with each ``Client`` connected to a unique deployment. This is accomplished through the use of the +`db-identifier` specified at ``Orchestrator`` initialization time. + +------- +Example +------- +In the following example, we demonstrate deploying a colocated ``Orchestrator`` on an HPC system. +Once the ``Orchestrator`` is launched, we walk through connecting a SmartRedis ``Client`` +from within the application script to transmit and poll for data on the ``Orchestrator``. + +The example is comprised of two script files: + +- :ref:`Application Script` + The application script is a Python script that connects a SmartRedis + ``Client`` to the colocated ``Orchestrator``. From within the application script, + the ``Client`` is utilized to both send and retrieve data. The source code example + is available in the dropdown below for convenient execution and customization. + + .. dropdown:: Example Application Script Source Code + + .. literalinclude:: tutorials/doc_examples/orch_examples/colo_app.py - Pinning is not supported on MacOS X. Setting ``custom_pinning`` to anything - other than ``None`` will raise a warning and the input will be ignored. +- :ref:`Experiment Driver Script` + The ``Experiment`` driver script launches and manages + the example entities through the ``Experiment`` API. + In the driver script, we use the ``Experiment`` API + to create and launch a colocated ``Model``. The source code example is available + in the dropdown below for convenient execution and customization. + + .. dropdown:: Example Experiment Driver source code + + .. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + +.. _colocated_orch_app_script: + +Application Script +================== +To begin writing the application script, import the necessary SmartRedis packages: + +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_app.py + :language: python + :linenos: + :lines: 1-2 + +Client Initialization +--------------------- +To establish a connection with the colocated ``Orchestrator``, we need to initialize a +new SmartRedis ``Client`` and specify `cluster=False` since colocated deployments are never +clustered but only single-sharded. + +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_app.py + :language: python + :linenos: + :lines: 4-5 + +.. note:: + Note that the C/C++/Fortran SmartRedis ``Clients`` are capable of reading cluster configurations + from the ``Model`` environment and the `cluster` constructor argument does not need to be specified + in those ``Client`` languages. .. note:: + Since there is only one ``Orchestrator`` launched in the ``Experiment`` + (the colocated ``Orchestrator``), specifying a ``Orchestrator`` `db_identifier` + is not required when initializing the ``Client``. SmartRedis will handle the + connection configuration. - Pinning _only_ affects the co-located deployment because both the application and the database - are sharing the same compute node. For the clustered deployment, a shard occupies the entirety - of the node. +.. note:: + To create a ``Client`` connection to the colocated ``Orchestrator``, the colocated ``Model`` must be launched + from within the driver script. You must execute the Python driver script, otherwise, there will + be no ``Orchestrator`` to connect the ``Client`` to. + +Data Storage +------------ +Next, using the SmartRedis ``Client`` instance, we create and store a NumPy tensor through +``Client.put_tensor(name, data)``: + +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_app.py + :language: python + :linenos: + :lines: 7-10 + +We will retrieve `“tensor_1”` in the following section. + +Data Retrieval +-------------- +To confirm a successful connection to the ``Orchestrator``, we retrieve the tensor we stored. +Use the ``Client.get_tensor`` method to retrieve the tensor by specifying the name +`“tensor_1”`: + +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_app.py + :language: python + :linenos: + :lines: 12-15 + +When the ``Experiment`` completes, you can find the following log message in `colo_model.out`:: + + Default@21-48-01:The colocated db tensor is: [1 2 3 4] + +.. _colocated_orch_driver_script: + +Experiment Driver Script +======================== +To run the previous application script, a ``Model`` object must be configured and launched within the +``Experiment`` driver script. Configuring and launching workflow entities (``Model``) +requires the utilization of ``Experiment`` class methods. The ``Experiment`` object is intended to +be instantiated once and utilized throughout the workflow runtime. + +In this example, we instantiate an ``Experiment`` object with the name `getting-started` +and the `launcher` set to `auto`. When using `launcher=auto`, SmartSim attempts to find a launcher on the machine. +In this case, since we are running the example on a Slurm-based machine, +SmartSim will automatically set the launcher to `slurm`. We set up the SmartSim `logger` +to output information from the ``Experiment`` at runtime: + +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + :language: python + :linenos: + :lines: 1-9 + +Colocated Model Initialization +------------------------------ +In the next stage of the ``Experiment``, we create and launch a colocated ``Model`` that +runs the application script with a ``Orchestrator`` on the same compute node. -Redis -===== +Step 1: Configure +''''''''''''''''' +In this example ``Experiment``, the ``Model`` application is a Python script as defined in section: +:ref:`Application Script`. Before initializing the ``Model`` object, we must use +``Experiment.create_run_settings`` to create a ``RunSettings`` object that defines how to execute +the ``Model``. To launch the Python script in this example workflow, we specify the path to the application +file `application_script.py` as the `exe_args` parameter and the executable `exe_ex` (the Python +executable on this system) as `exe` parameter. The ``Experiment.create_run_settings`` function +will return a ``RunSettings`` object that can then be used to initialize the ``Model`` object. -.. _Redis: https://github.com/redis/redis -.. _RedisAI: https://github.com/RedisAI/RedisAI +.. note:: + Change the `exe_args` argument to the path of the application script + on your file system to run the example. -The ``Orchestrator`` is built on `Redis`_. Largely, the job of the ``Orchestrator`` is to -create a Python reference to a Redis deployment so that users can launch, monitor -and stop a Redis deployment on workstations and HPC systems. +Use the ``RunSettings`` helper functions to +configure the the distribution of computational tasks (``RunSettings.set_nodes``). In this +example, we specify to SmartSim that we intend the ``Model`` to run on a single compute node. -Redis was chosen for the Orchestrator because it resides in-memory, can be distributed on-node -as well as across nodes, and provides low latency data access to many clients in parallel. The -Redis ecosystem was a primary driver as the Redis module system provides APIs for languages, -libraries, and techniques used in Data Science. In particular, the ``Orchestrator`` -relies on `RedisAI`_ to provide access to Machine Learning runtimes. +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + :language: python + :linenos: + :lines: 11-14 -At its core, Redis is a key-value store. This means that put/get semantics are used to send -messages to and from the database. SmartRedis clients use a specific hashing algorithm, CRC16, to ensure -that data is evenly distributed amongst all database nodes. Notably, a user is not required to -know where (which database node) data or Datasets (see Dataset API) are stored as the -SmartRedis clients will infer their location for the user. +Step 2: Initialize +'''''''''''''''''' +Next, create a ``Model`` instance using the ``Experiment.create_model`` factory method. +Pass the ``model_settings`` object as input to the method and +assign the returned ``Model`` instance to the variable `model`: + +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + :language: python + :linenos: + :lines: 16-17 + +Step 3: Colocate +'''''''''''''''' +To colocate an ``Orchestrator`` with a ``Model``, use the ``Model.colocate_db_uds`` function. +This function will colocate an ``Orchestrator`` instance with this ``Model`` over +a Unix domain socket connection. +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + :language: python + :linenos: + :lines: 19-20 -KeyDB -===== +Step 4: Generate Files +'''''''''''''''''''''' +Next, generate the ``Experiment`` entity directories by passing the ``Model`` instance to +``Experiment.generate``: -.. _KeyDB: https://github.com/EQ-Alpha/KeyDB +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + :language: python + :linenos: + :lines: 22-23 -`KeyDB`_ is a multi-threaded fork of Redis that can be swapped in as the database for -the ``Orchestrator`` in SmartSim. KeyDB can be swapped in for Redis by setting the -``REDIS_PATH`` environment variable to point to the ``keydb-server`` binary. +Step 5: Start +''''''''''''' +Next, launch the colocated ``Model`` instance using the ``Experiment.start`` function. -A full example of configuring KeyDB to run in SmartSim is shown below +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + :language: python + :linenos: + :lines: 25-26 -.. code-block:: bash +Cleanup +------- +.. note:: + Since the colocated ``Orchestrator`` is automatically torn down by SmartSim once the colocated ``Model`` + has finished, we do not need to `stop` the ``Orchestrator``. + +.. literalinclude:: tutorials/doc_examples/orch_examples/colo_driver.py + :language: python + :linenos: + :lines: 28-29 + +When you run the experiment, the following output will appear:: - # build KeyDB - # see https://github.com/EQ-Alpha/KeyDB + | | Name | Entity-Type | JobID | RunID | Time | Status | Returncode | + |----|--------|---------------|-----------|---------|---------|-----------|--------------| + | 0 | model | Model | 1592652.0 | 0 | 10.1039 | Completed | 0 | - # get KeyDB configuration file - wget https://github.com/CrayLabs/SmartSim/blob/d3d252b611c9ce9d9429ba6eeb71c15471a78f08/smartsim/_core/config/keydb.conf +.. _mutli_orch_doc: - export REDIS_PATH=/path/to/keydb-server - export REDIS_CONF=/path/to/keydb.conf +====================== +Multiple Orchestrators +====================== +SmartSim supports automating the deployment of multiple ``Orchestrators`` +from within an ``Experiment``. Communication with the ``Orchestrator`` via a SmartRedis ``Client`` is possible with the +`db_identifier` argument that is required when initializing an ``Orchestrator`` or +colocated ``Model`` during a multiple ``Orchestrator`` ``Experiment``. When initializing a SmartRedis +``Client`` during the ``Experiment``, create a ``ConfigOptions`` object to specify the `db_identifier` +argument used when creating the ``Orchestrator``. Pass the ``ConfigOptions`` object to +the ``Client`` init call. - # run smartsim workload +.. _mutli_orch: +----------------------------- Multiple Orchestrator Example -============================= +----------------------------- SmartSim offers functionality to automate the deployment of multiple databases, supporting workloads that require multiple ``Orchestrators`` for a ``Experiment``. For instance, a workload may consist of a simulation with high inference performance demands (necessitating a co-located deployment), -along with an analysis and -visualization workflow connected to the simulation (requiring a standard orchestrator). -In the following example, we simulate a simple version of this use case. +along with an analysis and visualization workflow connected to the simulation +(requiring a standalone ``Orchestrator``). In the following example, we simulate a +simple version of this use case. The example is comprised of two script files: -* The :ref:`Application Script` -* The :ref:`Experiment Driver Script` +* The Application Script +* The ``Experiment`` Driver Script **The Application Script Overview:** In this example, the application script is a python file that contains instructions to complete computational tasks. Applications are not limited to Python and can also be written in C, C++ and Fortran. -This script specifies creating a Python SmartRedis client for each -standard orchestrator and a colocated orchestrator. We use the -clients to request data from both standard databases, then -transfer the data to the colocated database. The application -file is launched by the experiment driver script +This script specifies creating a Python SmartRedis ``Client`` for each +standalone ``Orchestrator`` and a colocated ``Orchestrator``. We use the +``Clients`` to request data from both standalone ``Orchestrators``, then +transfer the data to the colocated ``Orchestrator``. The application +file is launched by the ``Experiment`` driver script through a ``Model`` stage. **The Application Script Contents:** -1. Connecting SmartRedis clients within the application to retrieve tensors - from the standard databases to store in a colocated database. Details in section: - :ref:`Initialize the Clients`. +1. Connecting SmartRedis ``Clients`` within the application to retrieve tensors + from the standalone ``Orchestrators`` to store in a colocated ``Orchestrator``. Details in section: + :ref:`Initialize the Clients`. **The Experiment Driver Script Overview:** -The experiment driver script holds the stages of the workflow +The ``Experiment`` driver script holds the stages of the workflow and manages their execution through the ``Experiment`` API. -We initialize an Experiment +We initialize an ``Experiment`` at the beginning of the Python file and use the ``Experiment`` to iteratively create, configure and launch computational kernels on the system through the `slurm` launcher. @@ -211,143 +691,146 @@ runs the application. **The Experiment Driver Script Contents:** -1. Launching two standard Orchestrators with unique identifiers. Details in section: - :ref:`Launch Multiple Orchestrators`. -2. Launching the application script with a co-located database. Details in section: - :ref:`Initialize a Colocated Model`. -3. Connecting SmartRedis clients within the driver script to send tensors to standard Orchestrators +1. Launching two standalone ``Orchestrators`` with unique identifiers. Details in section: + :ref:`Launch Multiple Orchestrators`. +2. Launching the application script with a colocated ``Orchestrator``. Details in section: + :ref:`Initialize a Colocated Model`. +3. Connecting SmartRedis ``Clients`` within the driver script to send tensors to standalone ``Orchestrators`` for retrieval within the application. Details in section: - :ref:`Create Client Connections to Orchestrators`. + :ref:`Create Client Connections to Orchestrators`. -Setup and run instructions can be found :ref:`here` +Setup and run instructions can be found :ref:`here` + +.. _app_script_multi_db: The Application Script ----------------------- -Applications interact with the databases -through a SmartRedis client. +====================== +Applications interact with the ``Orchestrators`` +through a SmartRedis ``Client``. In this section, we write an application script to demonstrate how to connect SmartRedis -clients in the context of multiple -launched databases. Using the clients, we retrieve tensors -from two databases launched in the driver script, then store -the tensors in the colocated database. +``Clients`` in the context of multiple +launched ``Orchestrators``. Using the ``Clients``, we retrieve tensors +from two ``Orchestrators`` launched in the driver script, then store +the tensors in the colocated ``Orchestrators``. .. note:: - The Experiment must be started to use the Orchestrators within the + The ``Experiment`` must be started to use the ``Orchestrators`` within the application script. Otherwise, it will fail to connect. - Find the instructions on how to launch :ref:`here` + Find the instructions on how to launch :ref:`here` To begin, import the necessary packages: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 1-3 +.. _init_model_client: + Initialize the Clients -^^^^^^^^^^^^^^^^^^^^^^ -To establish a connection with each database, -we need to initialize a new SmartRedis client for each -``Orchestrator``. +---------------------- +To establish a connection with each ``Orchestrators``, +we need to initialize a new SmartRedis ``Client`` for each. Step 1: Initialize ConfigOptions -"""""""""""""""""""""""""""""""" -Since we are launching multiple databases within the experiment, +'''''''''''''''''''''''''''''''' +Since we are launching multiple ``Orchestrators`` within the ``Experiment``, the SmartRedis ``ConfigOptions`` object is required when initializing -a client in the application. -We use the ``ConfigOptions.create_from_environment()`` +a ``Client`` in the application. +We use the ``ConfigOptions.create_from_environment`` function to create three instances of ``ConfigOptions``, with one instance associated with each launched ``Orchestrator``. -Most importantly, to associate each launched Orchestrator to a ConfigOptions object, -the ``create_from_environment()`` function requires specifying the unique database identifier +Most importantly, to associate each launched ``Orchestrator`` to a ``ConfigOptions`` object, +the ``create_from_environment`` function requires specifying the unique ``Orchestrator`` identifier argument named `db_identifier`. -For the single-sharded database: +For the single-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 5-6 -For the multi-sharded database: +For the multi-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 10-11 -For the colocated database: +For the colocated ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 15-16 Step 2: Initialize the Client Connections -""""""""""""""""""""""""""""""""""""""""" +''''''''''''''''''''''''''''''''''''''''' Now that we have three ``ConfigOptions`` objects, we have the -tools necessary to initialize three SmartRedis clients and -establish a connection with the three databases. -We use the SmartRedis ``Client`` API to create the client instances by passing in +tools necessary to initialize three SmartRedis ``Clients`` and +establish a connection with the three ``Orchestrators``. +We use the SmartRedis ``Client`` API to create the ``Client`` instances by passing in the ``ConfigOptions`` objects and assigning a `logger_name` argument. -Single-sharded database: +Single-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 7-8 -Multi-sharded database: +Multi-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 12-13 -Colocated database: +Colocated ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 17-18 Retrieve Data and Store Using SmartRedis Client Objects -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To confirm a successful connection to each database, we will retrieve the tensors +------------------------------------------------------- +To confirm a successful connection to each ``Orchestrator``, we will retrieve the tensors that we plan to store in the python driver script. After retrieving, we -store both tensors in the colocated database. -The ``Client.get_tensor()`` method allows +store both tensors in the colocated ``Orchestrator``. +The ``Client.get_tensor`` method allows retrieval of a tensor. It requires the `name` of the tensor assigned -when sent to the database via ``Client.put_tensor()``. +when sent to the ``Orchestrator`` via ``Client.put_tensor``. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 20-26 -Later, when you run the experiment driver script the following output will appear in ``tutorial_model.out`` +Later, when you run the ``Experiment`` driver script the following output will appear in ``tutorial_model.out`` located in ``getting-started-multidb/tutorial_model/``:: Model: single shard logger@00-00-00:The single sharded db tensor is: [1 2 3 4] Model: multi shard logger@00-00-00:The multi sharded db tensor is: [5 6 7 8] -This output showcases that we have established a connection with multiple Orchestrators. +This output showcases that we have established a connection with multiple ``Orchestrators``. -Next, take the tensors retrieved from the standard deployment databases and -store them in the colocated database using ``Client.put_tensor(name, data)``. +Next, take the tensors retrieved from the standalone deployment ``Orchestrators`` and +store them in the colocated ``Orchestrator`` using ``Client.put_tensor(name, data)``. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 28-30 -Next, check if the tensors exist in the colocated database using ``Client.poll_tensor()``. -This function queries for data in the database. The function requires the tensor name (`name`), +Next, check if the tensors exist in the colocated ``Orchestrator`` using ``Client.poll_tensor``. +This function queries for data in the ``Orchestrator``. The function requires the tensor name (`name`), how many milliseconds to wait in between queries (`poll_frequency_ms`), and the total number of times to query (`num_tries`): -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: :lines: 32-37 @@ -358,156 +841,162 @@ The output will be as follows:: Model: colo logger@00-00-00:The colocated db has tensor_2: True The Experiment Driver Script ----------------------------- +============================ To run the previous application, we must define workflow stages within a workload. Defining workflow stages requires the utilization of functions associated -with the ``Experiment`` object. The Experiment object is intended to be instantiated +with the ``Experiment`` object. The ``Experiment`` object is intended to be instantiated once and utilized throughout the workflow runtime. In this example, we instantiate an ``Experiment`` object with the name ``getting-started-multidb``. We setup the SmartSim ``logger`` to output information from the Experiment. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 1-10 +.. _launch_multiple_orch: + Launch Multiple Orchestrators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +----------------------------- In the context of this ``Experiment``, it's essential to create and launch -the databases as a preliminary step before any other components since -the application script requests tensors from the launched databases. +the ``Orchestrators`` as a preliminary step before any other components since +the application script requests tensors from the launched ``Orchestrators``. -We aim to showcase the multi-database automation capabilities of SmartSim, so we -create two databases in the workflow: a single-sharded database and a -multi-sharded database. +We aim to showcase the multi-Orchestrator automation capabilities of SmartSim, so we +create two ``Orchestrators`` in the workflow: a single-sharded ``Orchestrator`` and a +multi-sharded ``Orchestrator``. Step 1: Initialize Orchestrators -"""""""""""""""""""""""""""""""" -To create an database, utilize the ``Experiment.create_database()`` function. +'''''''''''''''''''''''''''''''' +To create an ``Orchestrator``, utilize the ``Experiment.create_database`` function. The function requires specifying a unique -database identifier argument named `db_identifier` to launch multiple databases. -This step is necessary to connect to databases outside of the driver script. +``Orchestrator`` identifier argument named `db_identifier` to launch multiple ``Orchestrators``. +This step is necessary to connect to ``Orchestrators`` outside of the driver script. We will use the `db_identifier` names we specified in the application script. -For the single-sharded database: +For the single-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 12-14 -For the multi-sharded database: +For the multi-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 16-18 .. note:: - Calling ``exp.generate()`` will create two subfolders - (one for each Orchestrator created in the previous step) - whose names are based on the db_identifier of that Orchestrator. + Calling ``exp.generate`` will create two subfolders + (one for each ``Orchestrator`` created in the previous step) + whose names are based on the `db_identifier` of that ``Orchestrator``. In this example, the Experiment folder is - named ``getting-started-multidb/``. Within this folder, two Orchestrator subfolders will + named ``getting-started-multidb/``. Within this folder, two ``Orchestrator`` subfolders will be created, namely ``single_shard_db_identifier/`` and ``multi_shard_db_identifier/``. -Step 2: Start Databases -""""""""""""""""""""""" -Next, to launch the databases, -pass the database instances to ``Experiment.start()``. +Step 2: Start +''''''''''''' +Next, to launch the ``Orchestrators``, +pass the ``Orchestrator`` instances to ``Experiment.start``. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 20-21 -The ``Experiment.start()`` function launches the ``Orchestrators`` for use within the workflow. In other words, the function -deploys the databases on the allocated compute resources. +The ``Experiment.start`` function launches the ``Orchestrators`` for use within the workflow. In other words, the function +deploys the ``Orchestrators`` on the allocated compute resources. .. note:: By setting `summary=True`, SmartSim will print a summary of the - experiment before it is launched. After printing the experiment summary, - the experiment is paused for 10 seconds giving the user time to - briefly scan the summary contents. If we set `summary=False`, then the experiment + ``Experiment`` before it is launched. After printing the ``Experiment`` summary, + the ``Experiment`` is paused for 10 seconds giving the user time to + briefly scan the summary contents. If we set `summary=False`, then the ``Experiment`` would be launched immediately with no summary. +.. _client_connect_orch: + Create Client Connections to Orchestrators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +------------------------------------------ The SmartRedis ``Client`` object contains functions that manipulate, send, and receive -data within the database. Each database has a single, dedicated SmartRedis ``Client``. -Begin by initializing a SmartRedis ``Client`` object per launched database. +data within the ``Orchestrator``. Each ``Orchestrator`` has a single, dedicated SmartRedis ``Client``. +Begin by initializing a SmartRedis ``Client`` object per launched ``Orchestrator``. To create a designated SmartRedis ``Client``, you need to specify the address of the target -running database. You can easily retrieve this address using the ``Orchestrator.get_address()`` function. +running ``Orchestrator``. You can easily retrieve this address using the ``Orchestrator.get_address`` function. -For the single-sharded database: +For the single-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 23-24 -For the multi-sharded database: +For the multi-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 25-26 Store Data Using Clients -^^^^^^^^^^^^^^^^^^^^^^^^ +------------------------ In the application script, we retrieved two NumPy tensors. To support the apps functionality, we will create two -NumPy arrays in the python driver script and send them to the a database. To -accomplish this, we use the ``Client.put_tensor()`` function with the respective -database client instances. +NumPy arrays in the python driver script and send them to the a ``Orchestrator``. To +accomplish this, we use the ``Client.put_tensor`` function with the respective +``Orchestrator`` `client` instances. -For the single-sharded database: +For the single-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 28-31 -For the multi-sharded database: +For the multi-sharded ``Orchestrator``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 33-36 -Lets check to make sure the database tensors do not exist in the incorrect databases: +Lets check to make sure the ``Orchestrator`` tensors do not exist in the incorrect ``Orchestrators``: -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 38-42 -When you run the experiment, the following output will appear:: +When you run the ``Experiment``, the following output will appear:: 00:00:00 system.host.com SmartSim[#####] INFO The multi shard array key exists in the incorrect database: False 00:00:00 system.host.com SmartSim[#####] INFO The single shard array key exists in the incorrect database: False +.. _init_colocated_model: + Initialize a Colocated Model -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In the next stage of the experiment, we -launch the application script with a co-located database +---------------------------- +In the next stage of the ``Experiment``, we +launch the application script with a co-located ``Orchestrator`` by configuring and creating a SmartSim colocated ``Model``. Step 1: Configure -""""""""""""""""" -You can specify the run settings of a model. -In this experiment, we invoke the Python interpreter to run -the python script defined in section: :ref:`The Application Script`. -To configure this into a ``Model``, we use the ``Experiment.create_run_settings()`` function. +''''''''''''''''' +You can specify the run settings of a ``Model``. +In this ``Experiment``, we invoke the Python interpreter to run +the python script defined in section: :ref:`The Application Script`. +To configure this into a SmartSim ``Model``, we use the ``Experiment.create_run_settings`` function. The function returns a ``RunSettings`` object. When initializing the RunSettings object, we specify the path to the application file, `application_script.py`, for ``exe_args``, and the run command for ``exe``. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 44-45 @@ -517,62 +1006,66 @@ we specify the path to the application file, on your machine to run the example. With the ``RunSettings`` instance, -configure the the distribution of computational tasks (``RunSettings.set_nodes()``) and the number of instances -the script is execute on each node (``RunSettings.set_tasks_per_node()``). In this +configure the the distribution of computational tasks (``RunSettings.set_nodes``) and the number of instances +the script is execute on each node (``RunSettings.set_tasks_per_node``). In this example, we specify to SmartSim that we intend to execute the script once on a single node. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 46-48 Step 2: Initialize -"""""""""""""""""" -Next, create a ``Model`` instance using the ``Experiment.create_model()``. +'''''''''''''''''' +Next, create a ``Model`` instance using the ``Experiment.create_model``. Pass the ``model_settings`` object as an argument -to the ``create_model()`` function and assign to the variable ``model``. +to the ``create_model`` function and assign to the variable ``model``. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 49-50 Step 2: Colocate -"""""""""""""""" -To colocate the model, use the ``Model.colocate_db_uds()`` function to -Colocate an Orchestrator instance with this Model over +'''''''''''''''' +To colocate the ``Model``, use the ``Model.colocate_db_uds`` function to +Colocate an ``Orchestrator`` instance with this ``Model`` over a Unix domain socket connection. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 51-52 This method will initialize settings which add an unsharded -database to this Model instance. Only this Model will be able -to communicate with this colocated database by using the loopback TCP interface. +``Orchestrator`` to this ``Model`` instance. Only this ``Model`` will be able +to communicate with this colocated ``Orchestrator`` by using the loopback TCP interface. Step 3: Start -""""""""""""" -Next, launch the colocated model instance using the ``Experiment.start()`` function. +''''''''''''' +Next, launch the colocated ``Model`` instance using the ``Experiment.start`` function. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 53-54 .. note:: We set `block=True`, - so that ``Experiment.start()`` waits until the last Model has finished + so that ``Experiment.start`` waits until the last ``Model`` has finished before returning: it will act like a job monitor, letting us know if processes run, complete, or fail. Cleanup Experiment -^^^^^^^^^^^^^^^^^^ -Finally, use the ``Experiment.stop()`` function to stop the database instances. Print the -workflow summary with ``Experiment.summary()``. +------------------ +Finally, use the ``Experiment.stop`` function to stop the standard ``Orchestrator`` instances. + +.. note:: + Co-located ``Orchestrator``s are stopped when their associated ``Model``'s are stopped. -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +Print the workflow summary with ``Experiment.summary``. + +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: :lines: 56-59 @@ -586,16 +1079,18 @@ When you run the experiment, the following output will appear:: | 1 | single_shard_db_identifier_0 | DBNode | 1556529.3 | 0 | 68.8732 | Cancelled | 0 | | 2 | multi_shard_db_identifier_0 | DBNode | 1556529.4+2 | 0 | 45.5139 | Cancelled | 0 | +.. _run_ex_instruct: + How to Run the Example ----------------------- -Below are the steps to run the experiment. Find the -:ref:`experiment source code` -and :ref:`application source code` +====================== +Below are the steps to run the ``Experiment``. Find the +:ref:`experiment source code` +and :ref:`application source code` below in the respective subsections. .. note:: The example assumes that you have already installed and built - SmartSim and SmartRedis. Please refer to Section :ref:`Basic Installation` + SmartSim and SmartRedis. Please refer to Section :ref:`Basic Installation` for further details. For simplicity, we assume that you are running on a SLURM-based HPC-platform. Refer to the steps below for more details. @@ -609,7 +1104,7 @@ Step 1 : Setup your directory tree application_script.py experiment_script.py - You can find the application and experiment source code in subsections below. + You can find the application and ``Experiment`` source code in subsections below. Step 2 : Install and Build SmartSim This example assumes you have installed SmartSim and SmartRedis in your @@ -619,21 +1114,25 @@ Step 2 : Install and Build SmartSim Step 3 : Change the `exe_args` file path When configuring the colocated model in `experiment_script.py`, we pass the file path of `application_script.py` to the `exe_args` argument - on line 33 in :ref:`experiment_script.py`. + on line 33 in :ref:`experiment_script.py`. Edit this argument to the file path of your `application_script.py` -Step 4 : Run the Experiment - Finally, run the experiment with ``python experiment_script.py``. +Step 4 : Run the ``Experiment`` + Finally, run the ``Experiment`` with ``python experiment_script.py``. + +.. _multi_app_source_code: Application Source Code -^^^^^^^^^^^^^^^^^^^^^^^ -.. literalinclude:: ../tutorials/getting_started/multi_db_example/application_script.py +----------------------- +.. literalinclude:: tutorials/getting_started/multi_db_example/application_script.py :language: python :linenos: +.. _multi_exp_source_code: + Experiment Source Code -^^^^^^^^^^^^^^^^^^^^^^ -.. literalinclude:: ../tutorials/getting_started/multi_db_example/multidb_driver.py +---------------------- +.. literalinclude:: tutorials/getting_started/multi_db_example/multidb_driver.py :language: python :linenos: \ No newline at end of file diff --git a/doc/requirements-doc.txt b/doc/requirements-doc.txt index e883a2805..8b6d46bb9 100644 --- a/doc/requirements-doc.txt +++ b/doc/requirements-doc.txt @@ -12,3 +12,5 @@ ipython jinja2==3.1.2 protobuf numpy +sphinx-design +pypandoc diff --git a/doc/run_settings.rst b/doc/run_settings.rst new file mode 100644 index 000000000..449b61ea4 --- /dev/null +++ b/doc/run_settings.rst @@ -0,0 +1,311 @@ +.. _run_settings_doc: + +************ +Run Settings +************ +======== +Overview +======== +``RunSettings`` are used in the SmartSim API to define how ``Model`` and ``Ensemble`` jobs +should be executed. + +In general, ``RunSettings`` define: + +- the executable +- the arguments to pass to the executable +- necessary environment variables at runtime +- the required compute resources + +The base ``RunSettings`` class is utilized for local task launches, +while its derived child classes offer specialized functionality for HPC workload managers (WLMs). +Each SmartSim `launcher` interfaces with a specific ``RunSettings`` subclass tailored to an HPC job scheduler. + +- Navigate to the :ref:`Local` section to configure run settings locally +- Navigate to the :ref:`HPC Systems` section to configure run settings for HPC + +A ``RunSettings`` object is initialized through the ``Experiment.create_run_settings`` function. +This function accepts a `run_command` argument: the command to run the executable. + +If `run_command` is set to `"auto"`, SmartSim will attempt to match a run command on the +system with a ``RunSettings`` class. If found, the class corresponding to +that `run_command` will be created and returned. + +If the `run_command` is passed a recognized run command (e.g. `"srun"`) the ``RunSettings`` +instance will be a child class such as ``SrunSettings``. You may also specify `"mpirun"`, +`"mpiexec"`, `"aprun"`, `"jsrun"` or `"orterun"` to the `run_command` argument. +This will return the associated child class. + +If the run command is not supported by SmartSim, the base ``RunSettings`` class will be created and returned +with the specified `run_command` and `run_args` evaluated literally. + +After creating a ``RunSettings`` instance, users gain access to the attributes and methods +of the associated child class, providing them with the ability to further configure the run +settings for jobs. + +======== +Examples +======== +.. _run_settings_local_ex: + +Local +===== +When running SmartSim on laptops and single node workstations via the `"local"` +`launcher`, job execution is configured with the base ``RunSettings`` object. +For local launches, ``RunSettings`` accepts a `run_command` parameter to allow +the use of parallel launch binaries like `"mpirun"`, `"mpiexec"`, and others. + +If no `run_command` is specified and the ``Experiment`` `launcher` is set to `"local"`, +the executable is launched locally. When utilizing the `"local"` launcher and configuring +the `run_command` parameter to `"auto"` in the ``Experiment.create_run_settings`` factory +method, SmartSim defaults to omitting any run command prefix before the executable. + +Once the ``RunSettings`` object is initialized using the ``Experiment.create_run_settings`` factory +method, the :ref:`RunSettings API` can be used to further configure the +``RunSettings`` object prior to execution. + +.. note:: + The local `launcher` is the default `launcher` for all ``Experiment`` instances. + +When the user initializes the ``Experiment`` at the beginning of the Python driver script, +a `launcher` argument may be specified. SmartSim will register or detect the +`launcher` and return the supported class upon a call to ``Experiment.create_run_settings``. +Below we demonstrate creating and configuring the base ``RunSettings`` +object for local launches by specifying the `"local"` launcher during ``Experiment`` creation. +We also demonstrate specifying `run_command="mpirun"` locally. + +**Initialize and Configure a RunSettings Object with No Run Command Specified:** + +.. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher local + exp = Experiment("name-of-experiment", launcher="local") + + + # Initialize a RunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command=None) + +**Initialize and Configure a RunSettings Object with the `mpirun` Run Command Specified:** + +.. note:: + Please note that to run this example you need to have an MPI implementation + (e.g. OpenMPI or MPICH) installed. + +.. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher local + exp = Experiment("name-of-experiment", launcher="local") + + # Initialize a RunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="mpirun") + +Users may replace `mpirun` with `mpiexec`. + +.. _run_settings_hpc_ex: + +HPC System +========== +To configure an entity for launch on an HPC system, SmartSim offers ``RunSettings`` child classes. +Each WLM `launcher` supports different ``RunSettings`` child classes. +When the user initializes the ``Experiment`` at the beginning of the Python driver script, +a `launcher` argument may be specified. The specified `launcher` will be used by SmartSim to +return the correct ``RunSettings`` child class that matches with the specified (or auto-detected) +`run_command` upon a call to ``Experiment.create_run_settings``. Below we demonstrate +creating and configuring the base ``RunSettings`` object for HPC launches +by specifying the launcher during ``Experiment`` creation. We show examples +for each job scheduler. + +.. tabs:: + + .. group-tab:: Slurm + + The Slurm `launcher` supports the :ref:`SrunSettings API ` as well as the :ref:`MpirunSettings API `, + :ref:`MpiexecSettings API ` and :ref:`OrterunSettings API ` that each can be used to run executables + with launch binaries like `"srun"`, `"mpirun"`, `"mpiexec"` and `"orterun"`. Below we step through initializing a ``SrunSettings`` and ``MpirunSettings`` + instance on a Slurm based machine using the associated `run_command`. + + **SrunSettings** + + Run a job with the `srun` command on a Slurm based system. Any arguments passed in + the `run_args` dict will be converted into `srun` arguments and prefixed with `"--"`. + Values of `None` can be provided for arguments that do not have values. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the Experiment and provide launcher Slurm + exp = Experiment("name-of-experiment", launcher="slurm") + + # Initialize a SrunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="srun") + # Set the number of nodes + run_settings.set_nodes(4) + # Set the number of cpus to use per task + run_settings.set_cpus_per_task(2) + # Set the number of tasks for this job + run_settings.set_tasks(100) + # Set the number of tasks for this job + run_settings.set_tasks_per_node(25) + + **MpirunSettings** + + Run a job with the `mpirun` command (MPI-standard) on a Slurm based system. Any + arguments passed in the `run_args` dict will be converted into `mpirun` arguments + and prefixed with `"--"`. Values of `None` can be provided for arguments that do + not have values. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the Experiment and provide launcher Slurm + exp = Experiment("name-of-experiment", launcher="slurm") + + # Initialize a MpirunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="mpirun") + # Set the number of cpus to use per task + run_settings.set_cpus_per_task(2) + # Set the number of tasks for this job + run_settings.set_tasks(100) + # Set the number of tasks for this job + run_settings.set_tasks_per_node(25) + + Users may replace `mpirun` with `mpiexec` or `orterun`. + + .. group-tab:: PBS Pro + The PBS Pro `launcher` supports the :ref:`AprunSettings API ` as well as the :ref:`MpirunSettings API `, + :ref:`MpiexecSettings API ` and :ref:`OrterunSettings API ` that each can be used to run executables + with launch binaries like `"aprun"`, `"mpirun"`, `"mpiexec"` and `"orterun"`. Below we step through initializing a ``AprunSettings`` and ``MpirunSettings`` + instance on a PBS Pro based machine using the associated `run_command`. + + **AprunSettings** + + Run a job with `aprun` command on a PBS Pro based system. Any arguments passed in + the `run_args` dict will be converted into `aprun` arguments and prefixed with `--`. + Values of `None` can be provided for arguments that do not have values. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher PBS Pro + exp = Experiment("name-of-experiment", launcher="pbs") + + # Initialize a AprunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="aprun") + # Set the number of cpus to use per task + run_settings.set_cpus_per_task(2) + # Set the number of tasks for this job + run_settings.set_tasks(100) + # Set the number of tasks for this job + run_settings.set_tasks_per_node(25) + + **MpirunSettings** + + Run a job with `mpirun` command on a PBS Pro based system. Any arguments passed + in the `run_args` dict will be converted into `mpirun` arguments and prefixed with `--`. + Values of `None` can be provided for arguments that do not have values. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher PBS Pro + exp = Experiment("name-of-experiment", launcher="pbs") + + # Initialize a MpirunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="mpirun") + # Set the number of cpus to use per task + run_settings.set_cpus_per_task(2) + # Set the number of tasks for this job + run_settings.set_tasks(100) + # Set the number of tasks for this job + run_settings.set_tasks_per_node(25) + + Users may replace `mpirun` with `mpiexec` or `orterun`. + + .. group-tab:: PALS + The PALS `launcher` supports the :ref:`MpiexecSettings API ` that can be used to run executables + with the `mpiexec` launch binary. Below we step through initializing a ``MpiexecSettings`` instance on a PALS + based machine using the associated `run_command`. + + **MpiexecSettings** + + Run a job with `mpiexec` command on a PALS based system. Any arguments passed in the `run_args` dict will be converted into `mpiexec` arguments and prefixed with `--`. + Values of `None` can be provided for arguments that do not have values. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher PALS + exp = Experiment("name-of-experiment", launcher="pals") + + # Initialize a MpiexecSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="mpiexec") + # Set the number of tasks for this job + run_settings.set_tasks(100) + # Set the number of tasks for this job + run_settings.set_tasks_per_node(25) + + .. group-tab:: LSF + The LSF `launcher` supports the :ref:`JsrunSettings API ` as well as the :ref:`MpirunSettings API `, + :ref:`MpiexecSettings API ` and :ref:`OrterunSettings API ` that each can be used to run executables + with launch binaries like `"jsrun"`, `"mpirun"`, `"mpiexec"` and `"orterun"`. Below we step through initializing a ``JsrunSettings`` and ``MpirunSettings`` + instance on a LSF based machine using the associated `run_command`. + + **JsrunSettings** + + Run a job with `jsrun` command on a LSF based system. Any arguments passed in the + `run_args` dict will be converted into `jsrun` arguments and prefixed with `--`. + Values of `None` can be provided for arguments that do not have values. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher LSF + exp = Experiment("name-of-experiment", launcher="lsf") + + # Initialize a JsrunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="jsrun") + # Set the number of cpus to use per task + run_settings.set_cpus_per_task(2) + # Set the number of tasks for this job + run_settings.set_tasks(100) + # Set the number of tasks for this job + run_settings.set_tasks_per_node(25) + + **MpirunSettings** + + Run a job with `mpirun` command on a LSF based system. Any arguments passed in the + `run_args` dict will be converted into `mpirun` arguments and prefixed with `--`. + Values of `None` can be provided for arguments that do not have values. + + .. code-block:: python + + from smartsim import Experiment + + # Initialize the experiment and provide launcher LSF + exp = Experiment("name-of-experiment", launcher="lsf") + + # Initialize a MpirunSettings object + run_settings = exp.create_run_settings(exe="echo", exe_args="Hello World", run_command="mpirun") + # Set the number of cpus to use per task + run_settings.set_cpus_per_task(2) + # Set the number of tasks for this job + run_settings.set_tasks(100) + # Set the number of tasks for this job + run_settings.set_tasks_per_node(25) + + Users may replace `mpirun` with `mpiexec` or `orterun`. + +.. note:: + SmartSim will look for an allocation by accessing the associated WLM job ID environment variable. If an allocation + is present, the entity will be launched on the reserved compute resources. A user may also specify the allocation ID + when initializing a run settings object via the `alloc` argument. If an allocation is specified, the entity receiving + these run parameters will launch on that allocation. \ No newline at end of file diff --git a/doc/sr_advanced_topics.rst b/doc/sr_advanced_topics.rst index 30da2c578..763a7fbe7 100644 --- a/doc/sr_advanced_topics.rst +++ b/doc/sr_advanced_topics.rst @@ -1,2 +1,2 @@ - +.. _config_options_explain: .. include:: ../smartredis/doc/advanced_topics.rst \ No newline at end of file diff --git a/doc/ss_logger.rst b/doc/ss_logger.rst new file mode 100644 index 000000000..186e28a89 --- /dev/null +++ b/doc/ss_logger.rst @@ -0,0 +1,221 @@ +****** +Logger +****** + +.. _ss_logger: + +======== +Overview +======== +SmartSim supports logging experiment activity through a logging API accessible via +the SmartSim `log` module. The SmartSim logger, backed by Python logging, enables +real-time logging of experiment activity **to stdout** and/or **to file**, with +multiple verbosity levels for categorizing log messages. + +Users may instruct SmartSim to log certain verbosity level log messages +and omit others through the `SMARTSIM_LOG_LEVEL` environment variable. The `SMARTSIM_LOG_LEVEL` +environment variable may be overridden when logging to file by specifying a log level to +the ``log_to_file`` function. Examples walking through logging :ref:`to stdout` +and :ref:`to file` are provided below. + +SmartSim offers **four** log functions to use within the Python driver script. The +below functions accept string messages: + +- ``logger.error`` +- ``logger.warning`` +- ``logger.info`` +- ``logger.debug`` + +The `SMARTSIM_LOG_LEVEL` environment variable accepts **four** log levels: `quiet`, +`info`, `debug` and `developer`. Setting the log level in the environment (or via the override function) +controls the log messages that are output at runtime. The log levels are listed below from +least verbose to most verbose: + +- level: `quiet` + - The `quiet` log level instructs SmartSim to print ``error`` and ``warning`` messages. +- level: `info` + - The `info` log level instructs SmartSim to print ``info``, ``error`` and ``warning`` messages. +- level: `debug` + - The `debug` log level instructs SmartSim to print ``debug``, ``info``, ``error`` and ``warning`` messages. +- level: `developer` + - The `developer` log level instructs SmartSim to print ``debug``, ``info``, ``error`` and ``warning`` messages. + +.. note:: + Levels `developer` and `debug` print the same log messages. The `developer` log level is intended for use + during code development and signifies highly detailed and verbose logging. + +.. note:: + `SMARTSIM_LOG_LEVEL` defaults to log level `info`. For SmartSim log API examples, continue to the :ref:`Examples` section. + +.. _log_ex: + +======== +Examples +======== +.. _log_to_stdout: + +------------- +Log to stdout +------------- +The ``get_logger`` function in SmartSim enables users to initialize a logger instance. +Once initialized, a user may use the instance to log a message using one of the four +logging functions. + +To use the SmartSim logger within a Python script, import the required `get_logger` +function from the `log` module: + +.. code-block:: python + + from smartsim.log import get_logger + +Next, initialize an instance of the logger and provide a logger `name`: + +.. code-block:: python + + logger = get_logger("SmartSim") + +To demonstrate full functionality of the SmartSim logger, we include all log +functions in the Python driver script with log messages: + +.. code-block:: python + + logger.info("This is a message") + logger.debug("This is a debug message") + logger.error("This is an error message") + logger.warning("This is a warning message") + +Execute the script *without* setting the `SMARTSIM_LOG_LEVEL`. Remember that `SMARTSIM_LOG_LEVEL` +defaults to `info`. When we execute the script, the following messages will print to stdout: + +.. code-block:: bash + + 11:15:00 system.host.com SmartSim[130033] INFO This is a message + 11:15:00 system.host.com SmartSim[130033] ERROR This is an error message + 11:15:00 system.host.com SmartSim[130033] WARNING This is a warning message + +Notice that the `debug` function message was filtered. This is because by using +a lower verbosity level (`info`), we instruct SmartSim to omit the higher verbosity level messages (`debug` and `developer`). + +Next, set `SMARTSIM_LOG_LEVEL` to `debug`: + +.. code-block:: bash + + export SMARTSIM_LOG_LEVEL=debug + +When we execute the script again, +the following messages will print to stdout: + +.. code-block:: bash + + 11:15:00 system.host.com SmartSim[65385] INFO This is a message + 11:15:00 system.host.com SmartSim[65385] DEBUG This is a debug message + 11:15:00 system.host.com SmartSim[65385] ERROR This is an error message + 11:15:00 system.host.com SmartSim[65385] WARNING This is a warning message + +Notice that all log messages print to stdout. By using a higher verbosity level (`debug`), +we instruct SmartSim to print all log functions at and above the level. + +Next, set `SMARTSIM_LOG_LEVEL` to `quiet` in terminal: + +.. code-block:: bash + + export SMARTSIM_LOG_LEVEL=quiet + +When we run the program once again, the following output is printed +to stdout: + +.. code-block:: bash + + 11:15:00 system.host.com SmartSim[65385] ERROR This is an error message + 11:15:00 system.host.com SmartSim[65385] WARNING This is a warning message + +Notice that the `info` and `debug` log functions were filtered. This is because by using +the least verbose level (`quiet`), we instruct SmartSim to omit messages at higher verbosity levels +(`info`, `debug` and `developer`). + +To finish the example, set `SMARTSIM_LOG_LEVEL` to `info` in terminal: + +.. code-block:: bash + + export SMARTSIM_LOG_LEVEL=info + +When we execute the script, the following messages will print +to stdout: + +.. code-block:: bash + + 11:15:00 system.host.com SmartSim[130033] INFO This is a message + 11:15:00 system.host.com SmartSim[130033] ERROR This is an error message + 11:15:00 system.host.com SmartSim[130033] WARNING This is a warning message + +Notice that the same messages were logged to stdout as when we ran the script with the default value `info`. +SmartSim omits messages at higher verbosity levels (`debug` and `developer`). + +.. _log_to_file: + +--------------- +Logging to File +--------------- +The ``log_to_file`` function in SmartSim allows users to log messages +to a specified file by providing a file name or relative file path. If the file name +passed in does not exist, SmartSim will create the file. If the program is re-executed with the same +file name, the file contents will be overwritten. + +To demonstrate, begin by importing the functions `get_logger` and `log_to_file` from the `log` module: + +.. code-block:: python + + from smartsim.log import get_logger, log_to_file + +Initialize a logger for use within the Python driver script: + +.. code-block:: python + + logger = get_logger("SmartSim") + +Invoke the ``log_to_file`` function to instruct SmartSim to create a file named `logger.out` +to write log messages to: + +.. code-block:: python + + log_to_file("logger.out") + +For the example, we add all log functions to the script: + +.. code-block:: python + + logger.info("This is a message") + logger.debug("This is a debug message") + logger.error("This is an error message") + logger.warning("This is a warning message") + +Remember that the default value for the `SMARTSIM_LOG_LEVEL` variable is `info`. +Therefore, we will not set the environment variable and instead rely on the +default. + +When we execute the Python script, a file named `logger.out` is created in our working +directory with the listed contents: + +.. code-block:: bash + + 11:15:00 system.host.com SmartSim[10950] INFO This is a message + 11:15:00 system.host.com SmartSim[10950] ERROR This is an error message + 11:15:00 system.host.com SmartSim[10950] WARNING This is a warning message + +Notice that the `debug` function message was filtered. This is because by using +a lower verbosity level (`info`), we instruct SmartSim to omit higher verbosity messages (`debug` and `developer`). + +In the same Python script, add a log level to the ``log_to_file`` as a input argument: + +.. code-block:: python + + log_to_file("logger.out", "quiet") + +When we execute the Python script once again, SmartSim will override the `SMARTSIM_LOG_LEVEL` +variable to output messages of log level `quiet`. SmartSim will overwrite the contents +of `logger.out` with: + +.. code-block:: bash + + 11:15:00 system.host.com SmartSim[10950] ERROR This is an error message + 11:15:00 system.host.com SmartSim[10950] WARNING This is a warning message \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py b/doc/tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py new file mode 100644 index 000000000..57d720163 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/application_consumer_script.py @@ -0,0 +1,17 @@ +from smartredis import Client, LLInfo + +# Initialize a Client +client = Client(cluster=False) + +# Set the data source +client.set_data_source("producer_0") +# Check if the tensor exists +tensor_1 = client.poll_tensor("tensor", 100, 100) + +# Set the data source +client.set_data_source("producer_1") +# Check if the tensor exists +tensor_2 = client.poll_tensor("tensor", 100, 100) + +client.log_data(LLInfo, f"producer_0.tensor was found: {tensor_1}") +client.log_data(LLInfo, f"producer_1.tensor was found: {tensor_2}") \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/application_producer_script.py b/doc/tutorials/doc_examples/ensemble_doc_examples/application_producer_script.py new file mode 100644 index 000000000..619a56e05 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/application_producer_script.py @@ -0,0 +1,10 @@ +from smartredis import Client +import numpy as np + +# Initialize a Client +client = Client(cluster=False) + +# Create NumPy array +array = np.array([1, 2, 3, 4]) +# Use SmartRedis Client to place tensor in standalone Orchestrator +client.put_tensor("tensor", array) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_file.py b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_file.py new file mode 100644 index 000000000..a2fa206f5 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_file.py @@ -0,0 +1,40 @@ +from smartsim import Experiment +from tensorflow import keras +from tensorflow.keras.layers import Conv2D, Input + +class Net(keras.Model): + def __init__(self): + super(Net, self).__init__(name="cnn") + self.conv = Conv2D(1, 3, 1) + + def call(self, x): + y = self.conv(x) + return y + +def save_tf_cnn(path, file_name): + """Create a Keras CNN and save to file for example purposes""" + from smartsim.ml.tf import freeze_model + + n = Net() + input_shape = (3, 3, 1) + n.build(input_shape=(None, *input_shape)) + inputs = Input(input_shape) + outputs = n(inputs) + model = keras.Model(inputs=inputs, outputs=outputs, name=n.name) + + return freeze_model(model, path, file_name) + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +ensemble_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Model object +ensemble_instance = exp.create_ensemble("ensemble_name", ensemble_settings) + +# Serialize and save TF model to file +model_file, inputs, outputs = save_tf_cnn(ensemble_instance.path, "model.pb") + +# Attach ML model file to Ensemble +ensemble_instance.add_ml_model(name="cnn", backend="TF", model_path=model_file, device="GPU", devices_per_node=2, first_device=0, inputs=inputs, outputs=outputs) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_mem.py b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_mem.py new file mode 100644 index 000000000..98974fdc2 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_ml_model_mem.py @@ -0,0 +1,40 @@ +from smartsim import Experiment +from tensorflow import keras +from tensorflow.keras.layers import Conv2D, Input + +class Net(keras.Model): + def __init__(self): + super(Net, self).__init__(name="cnn") + self.conv = Conv2D(1, 3, 1) + + def call(self, x): + y = self.conv(x) + return y + +def create_tf_cnn(): + """Create an in-memory Keras CNN for example purposes + + """ + from smartsim.ml.tf import serialize_model + n = Net() + input_shape = (3,3,1) + inputs = Input(input_shape) + outputs = n(inputs) + model = keras.Model(inputs=inputs, outputs=outputs, name=n.name) + + return serialize_model(model) + +# Serialize and save TF model +model, inputs, outputs = create_tf_cnn() + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +ensemble_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Model object +ensemble_instance = exp.create_ensemble("ensemble_name", ensemble_settings) + +# Attach the in-memory ML model to the SmartSim Ensemble +ensemble_instance.add_ml_model(name="cnn", backend="TF", model=model, device="GPU", devices_per_node=2, first_device=0, inputs=inputs, outputs=outputs) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_file.py b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_file.py new file mode 100644 index 000000000..819ed814f --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_file.py @@ -0,0 +1,13 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +ensemble_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Model object +ensemble_instance = exp.create_ensemble("ensemble_name", ensemble_settings) + +# Attach TorchScript to Ensemble +ensemble_instance.add_script(name="example_script", script_path="path/to/torchscript.py", device="GPU", devices_per_node=2, first_device=0) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py new file mode 100644 index 000000000..3e68bfd5a --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_mem.py @@ -0,0 +1,16 @@ +from smartsim import Experiment + +def timestwo(x): + return 2*x + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +ensemble_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Ensemble object +ensemble_instance = exp.create_ensemble("ensemble_name", ensemble_settings) + +# Attach TorchScript to Ensemble +ensemble_instance.add_function(name="example_func", function=timestwo, device="GPU", devices_per_node=2, first_device=0) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_string.py b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_string.py new file mode 100644 index 000000000..b8f907e9a --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/ensemble_torchscript_string.py @@ -0,0 +1,16 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +ensemble_settings = exp.create_run_settings(exe="path/to/executable/simulation") + +# Initialize a Model object +ensemble_instance = exp.create_ensemble("ensemble_name", ensemble_settings) + +# TorchScript string +torch_script_str = "def negate(x):\n\treturn torch.neg(x)\n" + +# Attach TorchScript to Ensemble +ensemble_instance.add_script(name="example_script", script=torch_script_str, device="GPU", devices_per_node=2, first_device=0) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py b/doc/tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py new file mode 100644 index 000000000..1a1db58e4 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/experiment_driver.py @@ -0,0 +1,42 @@ +from smartsim import Experiment +from smartsim.log import get_logger + +logger = get_logger("Experiment Log") +# Initialize the Experiment +exp = Experiment("getting-started", launcher="auto") + +# Initialize a standalone Orchestrator +standalone_orch = exp.create_database(db_nodes=1) + +# Initialize a RunSettings object for Ensemble +ensemble_settings = exp.create_run_settings(exe="/path/to/executable_producer_simulation") + +# Initialize Ensemble +producer_ensemble = exp.create_ensemble("producer", run_settings=ensemble_settings, replicas=2) + +# Enable key prefixing for Ensemble members +producer_ensemble.enable_key_prefixing() + +# Initialize a RunSettings object for Model +model_settings = exp.create_run_settings(exe="/path/to/executable_consumer_simulation") +# Initialize Model +consumer_model = exp.create_model("consumer", model_settings) + +# Generate SmartSim entity folder tree +exp.generate(standalone_orch, producer_ensemble, consumer_model, overwrite=True) + +# Launch Orchestrator +exp.start(standalone_orch, summary=True) + +# Launch Ensemble +exp.start(producer_ensemble, block=True, summary=True) + +# Register Ensemble members on consumer Model +for model in producer_ensemble: + consumer_model.register_incoming_entity(model) + +# Launch consumer Model +exp.start(consumer_model, block=True, summary=True) + +# Clobber Orchestrator +exp.stop(standalone_orch) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/file_attach.py b/doc/tutorials/doc_examples/ensemble_doc_examples/file_attach.py new file mode 100644 index 000000000..68f233342 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/file_attach.py @@ -0,0 +1,20 @@ +from smartsim import Experiment + +# Initialize the Experiment +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +ensemble_settings = exp.create_run_settings(exe="python", exe_args="/path/to/application.py") + +# Initialize an Ensemble object via replicas strategy +example_ensemble = exp.create_ensemble("ensemble", ensemble_settings, replicas=2, params={"THERMO":1}) + +# Attach the file to the Ensemble instance +example_ensemble.attach_generator_files(to_configure="path/to/params_inputs.txt") + +# Generate the Ensemble directory +exp.generate(example_ensemble) + +# Launch the Ensemble +exp.start(example_ensemble) + diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py b/doc/tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py new file mode 100644 index 000000000..89c9ea27e --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/manual_append_ensemble.py @@ -0,0 +1,25 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize BatchSettings +bs = exp.create_batch_settings(nodes=10, + time="01:00:00") + +# Initialize Ensemble +ensemble = exp.create_ensemble("ensemble-append", batch_settings=bs) + +# Initialize RunSettings for Model 1 +srun_settings_1 = exp.create_run_settings(exe=exe, exe_args="path/to/application_script_1.py") +# Initialize RunSettings for Model 2 +srun_settings_2 = exp.create_run_settings(exe=exe, exe_args="path/to/application_script_2.py") +# Initialize Model 1 with RunSettings 1 +model_1 = exp.create_model(name="model_1", run_settings=srun_settings_1) +# Initialize Model 2 with RunSettings 2 +model_2 = exp.create_model(name="model_2", run_settings=srun_settings_2) + +# Add Model member to Ensemble +ensemble.add_model(model_1) +# Add Model member to Ensemble +ensemble.add_model(model_2) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py b/doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py new file mode 100644 index 000000000..6ccbce397 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_1.py @@ -0,0 +1,16 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings +rs = exp.create_run_settings(exe="path/to/example_simulation_program") + +#Create the parameters to expand to the Ensemble members +params = { + "name": ["Ellie", "John"], + "parameter": [2, 11] + } + +# Initialize the Ensemble by specifying RunSettings, the params and "all_perm" +ensemble = exp.create_ensemble("model_member", run_settings=rs, params=params, perm_strategy="all_perm") diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py b/doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py new file mode 100644 index 000000000..f6fb30967 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/param_expansion_2.py @@ -0,0 +1,21 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a BatchSettings +bs = exp.create_batch_settings(nodes=2, + time="10:00:00") + +# Initialize and configure RunSettings +rs = exp.create_run_settings(exe="python", exe_args="path/to/application_script.py") +rs.set_nodes(1) + +#Create the parameters to expand to the Ensemble members +params = { + "name": ["Ellie", "John"], + "parameter": [2, 11] + } + +# Initialize the Ensemble by specifying RunSettings, BatchSettings, the params and "step" +ensemble = exp.create_ensemble("ensemble", run_settings=rs, batch_settings=bs, params=params, perm_strategy="step") \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/replicas_1.py b/doc/tutorials/doc_examples/ensemble_doc_examples/replicas_1.py new file mode 100644 index 000000000..0dd5d16f5 --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/replicas_1.py @@ -0,0 +1,10 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +rs = exp.create_run_settings(exe="python", exe_args="path/to/application_script.py") + +# Initialize the Ensemble by specifying the number of replicas and RunSettings +ensemble = exp.create_ensemble("ensemble-replica", replicas=4, run_settings=rs) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/ensemble_doc_examples/replicas_2.py b/doc/tutorials/doc_examples/ensemble_doc_examples/replicas_2.py new file mode 100644 index 000000000..e2363a5be --- /dev/null +++ b/doc/tutorials/doc_examples/ensemble_doc_examples/replicas_2.py @@ -0,0 +1,15 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a BatchSettings object +bs = exp.create_batch_settings(nodes=4, + time="10:00:00") + +# Initialize and configure a RunSettings object +rs = exp.create_run_settings(exe="python", exe_args="path/to/application_script.py") +rs.set_nodes(4) + +# Initialize an Ensemble +ensemble = exp.create_ensemble("ensemble-replica", replicas=4, run_settings=rs, batch_settings=bs) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/experiment_doc_examples/exp.py b/doc/tutorials/doc_examples/experiment_doc_examples/exp.py new file mode 100644 index 000000000..7a36262be --- /dev/null +++ b/doc/tutorials/doc_examples/experiment_doc_examples/exp.py @@ -0,0 +1,26 @@ +from smartsim import Experiment +from smartsim.log import get_logger + +# Initialize an Experiment +exp = Experiment("example-experiment", launcher="auto") +# Initialize a SmartSim logger +smartsim_logger = get_logger("logger") + +# Initialize an Orchestrator +standalone_database = exp.create_database(db_nodes=3, port=6379, interface="ib0") + +# Initialize the Model RunSettings +settings = exp.create_run_settings("echo", exe_args="Hello World") +# Initialize the Model +model = exp.create_model("hello_world", settings) + +# Generate the output directory +exp.generate(standalone_database, model, overwrite=True) + +# Launch the Orchestrator then Model instance +exp.start(standalone_database, model) + +# Clobber the Orchestrator +exp.stop(standalone_database) +# Log the summary of the Experiment +smartsim_logger.info(exp.summary()) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/from_file_ml_model.py b/doc/tutorials/doc_examples/model_doc_examples/from_file_ml_model.py new file mode 100644 index 000000000..329d08edc --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/from_file_ml_model.py @@ -0,0 +1,40 @@ +from smartsim import Experiment +from tensorflow import keras +from tensorflow.keras.layers import Conv2D, Input + +class Net(keras.Model): + def __init__(self): + super(Net, self).__init__(name="cnn") + self.conv = Conv2D(1, 3, 1) + + def call(self, x): + y = self.conv(x) + return y + +def save_tf_cnn(path, file_name): + """Create a Keras CNN and save to file for example purposes""" + from smartsim.ml.tf import freeze_model + + n = Net() + input_shape = (3, 3, 1) + n.build(input_shape=(None, *input_shape)) + inputs = Input(input_shape) + outputs = n(inputs) + model = keras.Model(inputs=inputs, outputs=outputs, name=n.name) + + return freeze_model(model, path, file_name) + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Model object +model_instance = exp.create_model("model_name", model_settings) + +# Get and save TF model +model_file, inputs, outputs = save_tf_cnn(model_instance.path, "model.pb") + +# Attach the from file ML model to the SmartSim Model +model_instance.add_ml_model(name="cnn", backend="TF", model_path=model_file, device="GPU", devices_per_node=2, first_device=0, inputs=inputs, outputs=outputs) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/from_file_script.py b/doc/tutorials/doc_examples/model_doc_examples/from_file_script.py new file mode 100644 index 000000000..ca6dcaea1 --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/from_file_script.py @@ -0,0 +1,14 @@ + +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Model object +model_instance = exp.create_model("model_name", model_settings) + +# Attach TorchScript to Model +model_instance.add_script(name="example_script", script_path="path/to/torchscript.py", device="GPU", devices_per_node=2, first_device=0) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/in_mem_ml_model.py b/doc/tutorials/doc_examples/model_doc_examples/in_mem_ml_model.py new file mode 100644 index 000000000..a34cceb4a --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/in_mem_ml_model.py @@ -0,0 +1,40 @@ +from smartsim import Experiment +from tensorflow import keras +from tensorflow.keras.layers import Conv2D, Input + +class Net(keras.Model): + def __init__(self): + super(Net, self).__init__(name="cnn") + self.conv = Conv2D(1, 3, 1) + + def call(self, x): + y = self.conv(x) + return y + +def create_tf_cnn(): + """Create an in-memory Keras CNN for example purposes + + """ + from smartsim.ml.tf import serialize_model + n = Net() + input_shape = (3,3,1) + inputs = Input(input_shape) + outputs = n(inputs) + model = keras.Model(inputs=inputs, outputs=outputs, name=n.name) + + return serialize_model(model) + +# Serialize and save TF model +model, inputs, outputs = create_tf_cnn() + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Model object +model_instance = exp.create_model("model_name", model_settings) + +# Attach the in-memory ML model to the SmartSim Model +model_instance.add_ml_model(name="cnn", backend="TF", model=model, device="GPU", devices_per_node=2, first_device=0, inputs=inputs, outputs=outputs) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/in_mem_script.py b/doc/tutorials/doc_examples/model_doc_examples/in_mem_script.py new file mode 100644 index 000000000..634746085 --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/in_mem_script.py @@ -0,0 +1,16 @@ +from smartsim import Experiment + +def timestwo(x): + return 2*x + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="path/to/example_simulation_program") + +# Initialize a Model object +model_instance = exp.create_model("model_name", model_settings) + +# Append TorchScript function to Model +model_instance.add_function(name="example_func", function=timestwo, device="GPU", devices_per_node=2, first_device=0) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/model_file.py b/doc/tutorials/doc_examples/model_doc_examples/model_file.py new file mode 100644 index 000000000..8961d50a8 --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/model_file.py @@ -0,0 +1,19 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="path/to/executable/simulation") + +# Initialize a Model object +model_instance = exp.create_model("model_name", model_settings, params={"THERMO":1}) + +# Attach the file to the Model instance +model_instance.attach_generator_files(to_configure="path/to/params_inputs.txt") + +# Store model_instance outputs within the Experiment directory named getting-started +exp.generate(model_instance) + +# Launch the Model +exp.start(model_instance) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/model_init.py b/doc/tutorials/doc_examples/model_doc_examples/model_init.py new file mode 100644 index 000000000..b1bb090f4 --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/model_init.py @@ -0,0 +1,16 @@ +from smartsim import Experiment + +# Init Experiment and specify to launch locally in this example +exp = Experiment(name="getting-started", launcher="local") + +# Initialize RunSettings +model_settings = exp.create_run_settings(exe="echo", exe_args="Hello World") + +# Initialize Model instance +model_instance = exp.create_model(name="example-model", run_settings=model_settings) + +# Generate Model directory +exp.generate(model_instance) + +# Launch Model +exp.start(model_instance) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/prefix_data.py b/doc/tutorials/doc_examples/model_doc_examples/prefix_data.py new file mode 100644 index 000000000..da4034d82 --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/prefix_data.py @@ -0,0 +1,12 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Create the run settings for the Model +model_settings = exp.create_run_settings(exe="path/to/executable/simulation") + +# Create a Model instance named 'model' +model = exp.create_model("model_name", model_settings) +# Enable tensor, Dataset and list prefixing on the 'model' instance +model.enable_key_prefixing() \ No newline at end of file diff --git a/doc/tutorials/doc_examples/model_doc_examples/string_script.py b/doc/tutorials/doc_examples/model_doc_examples/string_script.py new file mode 100644 index 000000000..52495ab47 --- /dev/null +++ b/doc/tutorials/doc_examples/model_doc_examples/string_script.py @@ -0,0 +1,16 @@ +from smartsim import Experiment + +# Initialize the Experiment and set the launcher to auto +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="path/to/executable/simulation") + +# Initialize a Model object +model_instance = exp.create_model("model_name", model_settings) + +# TorchScript string +torch_script_str = "def negate(x):\n\treturn torch.neg(x)\n" + +# Attach TorchScript to Model +model_instance.add_script(name="example_script", script=torch_script_str, device="GPU", devices_per_node=2, first_device=0) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/orch_examples/colo_app.py b/doc/tutorials/doc_examples/orch_examples/colo_app.py new file mode 100644 index 000000000..930789fab --- /dev/null +++ b/doc/tutorials/doc_examples/orch_examples/colo_app.py @@ -0,0 +1,15 @@ +from smartredis import Client, LLInfo +import numpy as np + +# Initialize a Client +colo_client = Client(cluster=False) + +# Create NumPy array +local_array = np.array([1, 2, 3, 4]) +# Store the NumPy tensor +colo_client.put_tensor("tensor_1", local_array) + +# Retrieve tensor from driver script +local_tensor = colo_client.get_tensor("tensor_1") +# Log tensor +colo_client.log_data(LLInfo, f"The colocated db tensor is: {local_tensor}") \ No newline at end of file diff --git a/doc/tutorials/doc_examples/orch_examples/colo_driver.py b/doc/tutorials/doc_examples/orch_examples/colo_driver.py new file mode 100644 index 000000000..fde06e9b7 --- /dev/null +++ b/doc/tutorials/doc_examples/orch_examples/colo_driver.py @@ -0,0 +1,29 @@ +import numpy as np +from smartredis import Client +from smartsim import Experiment +from smartsim.log import get_logger + +# Initialize a logger object +logger = get_logger("Example Experiment Log") +# Initialize the Experiment +exp = Experiment("getting-started", launcher="auto") + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="path/to/executable_simulation") +# Configure RunSettings object +model_settings.set_nodes(1) + +# Initialize a SmartSim Model +model = exp.create_model("colo_model", model_settings) + +# Colocate the Model +model.colocate_db_uds() + +# Generate output files +exp.generate(model) + +# Launch the colocated Model +exp.start(model, block=True, summary=True) + +# Log the Experiment summary +logger.info(exp.summary()) \ No newline at end of file diff --git a/doc/tutorials/doc_examples/orch_examples/std_app.py b/doc/tutorials/doc_examples/orch_examples/std_app.py new file mode 100644 index 000000000..67129fbf4 --- /dev/null +++ b/doc/tutorials/doc_examples/orch_examples/std_app.py @@ -0,0 +1,15 @@ +from smartredis import Client, LLInfo +import numpy as np + +# Initialize a SmartRedis Client +application_client = Client(cluster=True) + +# Retrieve the driver script tensor from Orchestrator +driver_script_tensor = application_client.get_tensor("tensor_1") +# Log the tensor +application_client.log_data(LLInfo, f"The multi-sharded db tensor is: {driver_script_tensor}") + +# Create a NumPy array +local_array = np.array([5, 6, 7, 8]) +# Use SmartRedis client to place tensor in multi-sharded db +application_client.put_tensor("tensor_2", local_array) diff --git a/doc/tutorials/doc_examples/orch_examples/std_driver.py b/doc/tutorials/doc_examples/orch_examples/std_driver.py new file mode 100644 index 000000000..cf425125b --- /dev/null +++ b/doc/tutorials/doc_examples/orch_examples/std_driver.py @@ -0,0 +1,46 @@ +import numpy as np +from smartredis import Client +from smartsim import Experiment +from smartsim.log import get_logger + +# Initialize the logger +logger = get_logger("Example Experiment Log") +# Initialize the Experiment +exp = Experiment("getting-started", launcher="auto") + +# Initialize a multi-sharded Orchestrator +standalone_orchestrator = exp.create_database(db_nodes=3) + +# Initialize a SmartRedis client for multi-sharded Orchestrator +driver_client = Client(cluster=True, address=standalone_orchestrator.get_address()[0]) + +# Create NumPy array +local_array = np.array([1, 2, 3, 4]) +# Use the SmartRedis client to place tensor in the standalone Orchestrator +driver_client.put_tensor("tensor_1", local_array) + +# Initialize a RunSettings object +model_settings = exp.create_run_settings(exe="/path/to/executable_simulation") +model_settings.set_nodes(1) + +# Initialize the Model +model = exp.create_model("model", model_settings) + +# Create the output directory +exp.generate(standalone_orchestrator, model) + +# Launch the multi-sharded Orchestrator +exp.start(standalone_orchestrator) + +# Launch the Model +exp.start(model, block=True, summary=True) + +# Poll the tensors placed by the Model +app_tensor = driver_client.poll_key("tensor_2", 100, 10) +# Validate that the tensor exists +logger.info(f"The tensor exists: {app_tensor}") + +# Cleanup the Orchestrator +exp.stop(standalone_orchestrator) +# Print the Experiment summary +logger.info(exp.summary()) \ No newline at end of file diff --git a/tutorials/getting_started/consumer.py b/doc/tutorials/getting_started/consumer.py similarity index 100% rename from tutorials/getting_started/consumer.py rename to doc/tutorials/getting_started/consumer.py diff --git a/tutorials/getting_started/getting_started.ipynb b/doc/tutorials/getting_started/getting_started.ipynb similarity index 100% rename from tutorials/getting_started/getting_started.ipynb rename to doc/tutorials/getting_started/getting_started.ipynb diff --git a/tutorials/getting_started/multi_db_example/application_script.py b/doc/tutorials/getting_started/multi_db_example/application_script.py similarity index 100% rename from tutorials/getting_started/multi_db_example/application_script.py rename to doc/tutorials/getting_started/multi_db_example/application_script.py diff --git a/tutorials/getting_started/multi_db_example/multidb_driver.py b/doc/tutorials/getting_started/multi_db_example/multidb_driver.py similarity index 100% rename from tutorials/getting_started/multi_db_example/multidb_driver.py rename to doc/tutorials/getting_started/multi_db_example/multidb_driver.py diff --git a/tutorials/getting_started/output_my_parameter.py b/doc/tutorials/getting_started/output_my_parameter.py similarity index 100% rename from tutorials/getting_started/output_my_parameter.py rename to doc/tutorials/getting_started/output_my_parameter.py diff --git a/tutorials/getting_started/output_my_parameter_new_tag.py b/doc/tutorials/getting_started/output_my_parameter_new_tag.py similarity index 100% rename from tutorials/getting_started/output_my_parameter_new_tag.py rename to doc/tutorials/getting_started/output_my_parameter_new_tag.py diff --git a/tutorials/getting_started/producer.py b/doc/tutorials/getting_started/producer.py similarity index 100% rename from tutorials/getting_started/producer.py rename to doc/tutorials/getting_started/producer.py diff --git a/tutorials/ml_inference/Inference-in-SmartSim.ipynb b/doc/tutorials/ml_inference/Inference-in-SmartSim.ipynb similarity index 100% rename from tutorials/ml_inference/Inference-in-SmartSim.ipynb rename to doc/tutorials/ml_inference/Inference-in-SmartSim.ipynb diff --git a/tutorials/ml_inference/colo-db-torch-example.py b/doc/tutorials/ml_inference/colo-db-torch-example.py similarity index 100% rename from tutorials/ml_inference/colo-db-torch-example.py rename to doc/tutorials/ml_inference/colo-db-torch-example.py diff --git a/tutorials/ml_training/surrogate/LICENSE b/doc/tutorials/ml_training/surrogate/LICENSE similarity index 100% rename from tutorials/ml_training/surrogate/LICENSE rename to doc/tutorials/ml_training/surrogate/LICENSE diff --git a/tutorials/ml_training/surrogate/README.md b/doc/tutorials/ml_training/surrogate/README.md similarity index 100% rename from tutorials/ml_training/surrogate/README.md rename to doc/tutorials/ml_training/surrogate/README.md diff --git a/tutorials/ml_training/surrogate/fd_sim.py b/doc/tutorials/ml_training/surrogate/fd_sim.py similarity index 100% rename from tutorials/ml_training/surrogate/fd_sim.py rename to doc/tutorials/ml_training/surrogate/fd_sim.py diff --git a/tutorials/ml_training/surrogate/steady_state.py b/doc/tutorials/ml_training/surrogate/steady_state.py similarity index 100% rename from tutorials/ml_training/surrogate/steady_state.py rename to doc/tutorials/ml_training/surrogate/steady_state.py diff --git a/tutorials/ml_training/surrogate/tf_model.py b/doc/tutorials/ml_training/surrogate/tf_model.py similarity index 100% rename from tutorials/ml_training/surrogate/tf_model.py rename to doc/tutorials/ml_training/surrogate/tf_model.py diff --git a/tutorials/ml_training/surrogate/tf_training.py b/doc/tutorials/ml_training/surrogate/tf_training.py similarity index 100% rename from tutorials/ml_training/surrogate/tf_training.py rename to doc/tutorials/ml_training/surrogate/tf_training.py diff --git a/tutorials/ml_training/surrogate/train_surrogate.ipynb b/doc/tutorials/ml_training/surrogate/train_surrogate.ipynb similarity index 100% rename from tutorials/ml_training/surrogate/train_surrogate.ipynb rename to doc/tutorials/ml_training/surrogate/train_surrogate.ipynb diff --git a/tutorials/ml_training/surrogate/vishelpers.py b/doc/tutorials/ml_training/surrogate/vishelpers.py similarity index 100% rename from tutorials/ml_training/surrogate/vishelpers.py rename to doc/tutorials/ml_training/surrogate/vishelpers.py diff --git a/tutorials/online_analysis/lattice/LICENSE b/doc/tutorials/online_analysis/lattice/LICENSE similarity index 100% rename from tutorials/online_analysis/lattice/LICENSE rename to doc/tutorials/online_analysis/lattice/LICENSE diff --git a/tutorials/online_analysis/lattice/README.md b/doc/tutorials/online_analysis/lattice/README.md similarity index 100% rename from tutorials/online_analysis/lattice/README.md rename to doc/tutorials/online_analysis/lattice/README.md diff --git a/tutorials/online_analysis/lattice/driver.py b/doc/tutorials/online_analysis/lattice/driver.py similarity index 100% rename from tutorials/online_analysis/lattice/driver.py rename to doc/tutorials/online_analysis/lattice/driver.py diff --git a/tutorials/online_analysis/lattice/fv_sim.py b/doc/tutorials/online_analysis/lattice/fv_sim.py similarity index 100% rename from tutorials/online_analysis/lattice/fv_sim.py rename to doc/tutorials/online_analysis/lattice/fv_sim.py diff --git a/tutorials/online_analysis/lattice/online_analysis.ipynb b/doc/tutorials/online_analysis/lattice/online_analysis.ipynb similarity index 100% rename from tutorials/online_analysis/lattice/online_analysis.ipynb rename to doc/tutorials/online_analysis/lattice/online_analysis.ipynb diff --git a/tutorials/online_analysis/lattice/probe.script b/doc/tutorials/online_analysis/lattice/probe.script similarity index 100% rename from tutorials/online_analysis/lattice/probe.script rename to doc/tutorials/online_analysis/lattice/probe.script diff --git a/tutorials/online_analysis/lattice/vishelpers.py b/doc/tutorials/online_analysis/lattice/vishelpers.py similarity index 100% rename from tutorials/online_analysis/lattice/vishelpers.py rename to doc/tutorials/online_analysis/lattice/vishelpers.py diff --git a/docker/docs/dev/Dockerfile b/docker/docs/dev/Dockerfile index eff99de36..48a9f4027 100644 --- a/docker/docs/dev/Dockerfile +++ b/docker/docs/dev/Dockerfile @@ -58,9 +58,4 @@ RUN git clone https://github.com/CrayLabs/SmartDashboard.git --branch develop -- RUN python -m pip install -r doc/requirements-doc.txt \ && NO_CHECKS=1 SMARTSIM_SUFFIX=dev python -m pip install . -RUN mkdir -p doc/tutorials/ \ - && cd doc/tutorials/ \ - && rm -rf * \ - && ln -s ../../tutorials/* . - RUN make docs diff --git a/smartsim/database/orchestrator.py b/smartsim/database/orchestrator.py index 431cb43c5..9cd436e55 100644 --- a/smartsim/database/orchestrator.py +++ b/smartsim/database/orchestrator.py @@ -174,11 +174,11 @@ def __init__( Extra configurations for RedisAI - See https://oss.redislabs.com/redisai/configuration/ + See https://oss.redis.com/redisai/configuration/ :param threads_per_queue: threads per GPU device :type threads_per_queue: int, optional - :param inter_op_threads: threads accross CPU operations + :param inter_op_threads: threads across CPU operations :type inter_op_threads: int, optional :param intra_op_threads: threads per CPU operation :type intra_op_threads: int, optional diff --git a/smartsim/experiment.py b/smartsim/experiment.py index 279128282..175997c96 100644 --- a/smartsim/experiment.py +++ b/smartsim/experiment.py @@ -835,8 +835,8 @@ def summary(self, style: str = "github") -> str: launched and completed in this ``Experiment`` :param style: the style in which the summary table is formatted, - for a full list of styles see: - https://github.com/astanin/python-tabulate#table-format, + for a full list of styles see the table-format section of: + https://github.com/astanin/python-tabulate, defaults to "github" :type style: str, optional :return: tabulate string of ``Experiment`` history