{'fraction_non_alphanumeric': 0.028105167724388033, 'fraction_numerical': 0.0076414972153866076, 'mean_word_length': 4.9926821155338725, 'pattern_counts': {'":': 0, '<': 2, '<?xml version=': 0, '>': 2, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
Colmena: Scalable Machine-Learning-Based Steering of Ensemble Simulations for High Performance Computing

Scientific applications that involve simulation ensembles can be accelerated greatly by using experiment design methods to select the best simulations to perform. Methods that use machine learning (ML) to create proxy models of simulations show particular promise for guiding ensembles but are challenging to deploy because of the need to coordinate dynamic mixes of simulation and learning tasks. We present Colmena, an open-source Python framework that allows users to steer campaigns by providing just the implementations of individual tasks plus the logic used to choose which tasks to execute when. Colmena handles task dispatch, results collation, ML model invocation, and ML model (re)training, using Parsl to execute tasks on HPC systems. We describe the design of Colmena and illustrate its capabilities by applying it to electrolyte design, where it both scales to 65536 CPUs and accelerates the discovery rate for high-performance molecules by a factor of 100 over unguided searches.


I. INTRODUCTION
High performance computing (HPC) campaigns involving repeated runs of simulations codes are being applied widely in science and engineering, for example to simulate molecules or proteins in different configurations to build models of how properties change with temperatures (model fitting) [1] or to evaluate many engine designs to find those with optimal efficiency (optimization) [2]. While experiments themselves are independent and can be run in parallel, fixed computing budgets and ever larger search spaces make it increasingly important to use results from completed experiments to steer such campaigns, i.e., to inform decisions about which experiments to perform next. Machine learning (ML) is an emerging tool for writing steering applications that can learn from new data faster than can human experts.
The core of algorithms for steering ensemble simulations, in broad terms, is a process that selects inputs to simulations and determines allocations of resources to tasks. Steering policies vary significantly in complexity. For example, genetic algorithms select new experiments by combining characteristics of previous simulations, typically dedicating resources equally among experiments [3], while Optimal Experimental Design (OED) methods determine new experiments based on the predictions and uncertainty of an internal statistical model, with resources allocated to tasks using methods such as all-toone task, batches of experiments [4], and streaming [5]. Some methods deploy only a single type of task, and others can select among different task types or levels of accuracy [6].
Steering algorithms that employ ML are particularly problematic to deploy on HPC due to the need to manage the execution of not only simulations but also steering (e.g., model update, experimental design) tasks. Achieving good results requires balancing two often conflicting needs: performing steering tasks often enough, and fast enough, to enable good choices of simulation tasks, and maintaining high HPC utilization. At a simple level, both challenges can be solved by reallocating resources-a common capability of many workflow systems. On a deeper level, the challenges involve more subtle questions, such as how many resources to allocate to steering and when to retrain ML model(s). For example, one may want to enforce a limited budget for steering tasks or decide when to retrain models by comparing simulation outputs to ML predictions. The problem of expressing such policies and deploying them to HPC requires much innovation.
Existing methods for steering HPC simulations are purposebuilt tools that combine a specific class of steering algorithm with methods for deploying computations across resources. An early example, Nimrod/O [7], used built-in optimization algorithms to choose which simulations to schedule over distributed clusters. More recently, the CANDLE Supervisor library [3] uses the Swift/T workflow engine [8] to distribute tasks selected by an optimization algorithm that determines new tasks after results are placed in an output queue [3]. Rocketsled works by adding a special "steering" task at the end of a Fireworks workflow definition that submits new work after completion (e.g., to launch a Bayesian optimization task) [9]. Variations of these patterns exist in other steering tools, including LibEnsemble [10], CAMD [11], DeepHyper [12], SuperLearner [2], and DeepDriveMD [13]. Each steering system uses a different way to express planning policies and are integrated differently with workflow engine, all of which provide informative initial steps towards learning how to best steer ensembles with AI. Further advancement in ensemble simulations will require systems that provide greater flexibility in policies for orchestrating machine learning and simulation tasks together.
We present Colmena, a general-purpose library for steering ensembles of experiments on HPC computing systems.
Colmena is an open-source Python code that permits writing complex agents for steering ensembles of simulations and executing them across diverse computational resources, with a particular focus on applications that use ML to design computational campaigns. In this paper, we formalize the experiment steering process and describe the design principles for the Colmena library. We then demonstrate the ability to use Colmena to steer simulations on 1024 nodes (65 536 cores) and illustrate its use on a molecular design challenge. We intend that Colmena will facilitate experimentation with advanced algorithms for steering experiments across diverse computing resources and, eventually, automated laboratories.
The main contributions of this paper are: • An abstract formulation of the computational campaign steering problem. • The introduction of Colmena, a Python library for steering computational campaigns on HPC. • A demonstration of using Colmena to design molecular materials using quantum chemistry and ML. • An analysis of the scaling performance of Colmena on a Cray XC40.

II. THE PROBLEM
We first provide an abstract definition of the problem that we seek to solve and then describe the example application used in our experiments.

A. Abstract Formulation
Let us assume that we have a (typically large) set of entities, e ∈ E, each with properties p ∈ P; a set of assays (e.g., simulations or laboratory experiments) a ∈ A, each of which can be used to estimate a property P (a) ∈ P of an entity; and a scoring function S, that when applied to available data on an entity's properties returns either a numeric score or φ if data are inadequate to assign a score. Note that multiple assays may exist for the same property, each with different cost and accuracy characteristics.
Given E, P, and A, we can determine entity properties by performing a series of tasks, each involving the application of an assay a ∈ A to an entity e ∈ E to obtain an estimated value v for property p = P (a). Given a record D of such tests, with each d ∈ D defined by a tuple (e, a, p, v), we can assign a score to D. For example, we might define the score as that of the single highest-scoring entity: We can also determine the cost incurred to produce D by summing the costs incurred to obtain each value Experiment design problem. When the number of entities, |E|, is large and/or assays are expensive, it becomes impractical to evaluate every possible entity-property combination. The quality of the answers obtained then depends on which assays have been performed. Thus we have an experiment design problem. If D is every possible combination of tests, and B is a resource bound, then we want to identify a set of tests D such that: max The order in which tests are performed then matters. For example, do we focus on lower-cost assays that may identify promising entities, or on higher-cost assays that may confirm (or eliminate) promising entities?
Static vs. learned assays. We distinguish between static assays, which have fixed behavior over the course of an experiment (e.g., a simulation code) and learned assays, which can be improved as more data are added to the record. An example of the latter is an ML model that approximates results obtained from an expensive simulation.
Training. This additional kind of task is used to generate a new version of a learned assay given the current record: a = retrain(a, D). As ML model accuracy generally increases with training data quantity and diversity, we have another dimension to our experiment design problem-whether to perform assays designed to increase training data diversity or to characterize promising entities.
Generating candidates. If the number of entities is large or even innumerable (e.g., all possible polymers), we may introduce a generator G that when called produces one or more new candidate entities based on the record.
Decision problem. If actions are taken one at a time, then system state at each step is captured by the sets of known entities E, associated data D, and assays (including learned assays) A. Initially, each may be empty or alternatively may be prepopulated to provide some initial knowledge of the problem. At each step, the next action is one of: • generate one or more new entities, e = G(D); • run a task a(e) for some a and e; or • (re)train a learned assay a to generate a new a .

B. Our Example Application
As an illustrative example, we apply Colmena to a problem in electrolyte design for next-generation batteries. In this application, entities are molecules; properties of interest include atomization energy, ionization potential, toxicity, stability, and synthesizability; assays include a variety of computational methods with varying costs and accuracies; and a scoring function might impose toxicity and stability thresholds and then sum the other properties.
More specifically, we present results for a version of this problem that involves a fixed search space of molecules and a single property to optimize: specifically, 10 5 molecules (represented as SMILES strings) from the QM9 dataset [14], [15]; a single property, ionization potential; and two assays, namely a quantum chemistry (QC) simulation and an ML model; and ionization potential as the quantity to maximize. While simple, this configuration allows us to explore many relevant tradeoffs.
We implement these two assays as follows. For the QC assay, we use the NWChem simulation code [16]. We first parse the SMILES string and generate an approximate geometry using RDKit [17]. We then use NWChem through the QCEngine Python interface [18] to compute the equilibrium geometry for the neutral and oxidized molecule and then compute the vibrational modes for each molecule. All computations are performed at the B3LYP/3-21G level of accuracy and typically require six node-hours per molecule on four nodes of the Theta system at the Argonne Leadership Computing Facility, as described in Section IV-B.
The ML assay uses an ensemble of message-passing neural networks (MPNNs) [19] implemented in Tensorflow, each trained using a different subset of the training data. We employ an ensemble of models to produce both a mean and an estimate of model uncertainty for each prediction. The initial ensemble of 16 MPNN models were trained using 2563 oxidation potentials computed with the QC assay. We use this additional dataset and any new data in subsequent retraining tasks, which we limit to 15 minutes on a single node. To apply the ML model, we first use RDKit to parse the SMILES string, featurize the data in a form used by a Tensorflow ML model, and then evaluate the MPNN ensemble. It requires 3 × 10 −6 node-hours to evaluate a single molecule, which equates to 100 molecules per node-second.

III. OUR APPROACH
We formulated the decision problem abstractly as a sequential process, where planning and simulation tasks are performed serially. However, in order to use highly parallel computers to accelerate the exploration process, we want to allow simultaneous execution both of multiple instances of the same activity (for example, applying an assay to multiple entities) and of different activities (e.g., running a simulationbased assay, retraining an ML model with simulation results, running an ML assay, deciding which entities to explore next). Running multiple instances of the same activity at once is important because, at least in the applications that we consider here, no single action can scale efficiently to use all of a large parallel computer. Running different activities at the same time is important because different actions vary greatly in their computational demands; thus, strict sequencing would reduce parallel efficiency. However, achieving high parallel efficiency is difficult in practice due to competing needs for efficient execution (demanding high parallelism, modest communication), resource management (dynamically reallocating resources), and timeliness of information (making results from one computation available rapidly to computations deciding on next actions). We have implemented Colmena to solve these challenges.

A. Colmena Architecture
Applications built using Colmena are formed of three types of independent processes: a Thinker, a Task Server, and one or more Workers (as shown in Figure 1).
The user-supplied Thinker implements the decision-making policy used to generate new tasks, record assay results (D), and update ML models (E). As described in Section II-A, tasks can include performing a new assay on a specified entity, updating a learned assay, and generating new entities. The Thinker communicates task requests to the Task Server; the results of those tasks, once available, are returned from the Task Server to the Thinker. The Thinker makes decisions in response to results being communicated or other events (e.g., availability of compute resources).
The Task Server matches each task request to the corresponding task definition (e.g., assay definition, ML retraining action) and dispatches the resulting task to an appropriate Worker. The Task Server itself holds the assay definitions (A), information about available computational resources, and details of which assays can run on what resources. Task requests are received from the Thinker and can be executed in any order.
Each Worker receives a sequence of tasks ({task input, task definition} pairs) from the Task Server. It executes each task that it receives and provides results back to the Task Server.
From a decision perspective, the challenge is to allow these different activities to run with maximum concurrency and performance.

B. Colmena Implementation
Our implementation of Colmena uses Redis [20] for asynchronous communication between the Thinker and Task Server, and the Parsl [21] parallel programming library to manage execution of tasks on the Workers. 1) Thinker: The Thinker decides which tasks are run and how to allocate resources among them. It communicates with the Task Server by writing task requests to, or reading results from, Redis queues. Requests and results are communicated as JSON objects and contain the inputs to a task, the outputs of the task, and a variety of profiling data (e.g., task runtime, time inputs received by Task Server). We provide a Python API for the message format, with utility operations such as accessing the positional or keyword arguments for a task and serializing inputs and results.
A Thinker is composed of multiple Agents, implemented as threads. As we demonstrate in Section IV, separating the decision process into multiple cooperative agents simplifies expressing resource allocation and task selection strategies. Each agent can communicate with the Task Server via Redis and with other agents via Python's threading library. Each agent typically responds to a different kind of event (e.g., resources becoming available, completion of a certain type of assay). For example, one agent might submits task requests to the Task Server from a task queue as resources become available, and a second respond to results arriving from the Task Server by adding new tasks to the task queue.
The BaseThinker class in Colmena simplifies writing Thinkers with multiple agents. Agent processes run as separate Python threads that communicate with the Task Server via the Redis queue and with each other via Python threading tools. Agents are implemented as methods of a BaseThinker subclass and then marked with the @agent decorator, as illustrated in Listing 1. There are also special-purpose types of agents, such as a "result processor," that run after certain conditions are met, such as a result completing. The BaseThinker class provides a function that launches all of the decorated functions to run concurrently.
Colmena also provides a class for monitoring, controlling, and allocating resources between different assays. The resource allocation object, ResourceTracker, stores a fixed count of resources that are available to a Thinker class and how they are assigned into different pools. Agent threads may query the availability of resources in each pool, acquire or release them, and change the levels of allocation between different pools. The class is created using Python's Lock and Semaphore objects to ensure resource requests can occur and be fulfilled concurrently.
2) Task Server: The Task Server is a stateful entity that performs high-throughput task processing. It receives task requests from an inputs queue and posts results asynchronously to an outputs queue when a task is complete. The Task Server requires a robust and performant backend for managing the execution of a diverse set of potential assays-from shortrunning, single-core inference tasks to long-running, multinode MPI simulations. Further, the Task Server should provide an intuitive way to represent diverse assays, transparently serialize and transfer inputs/outputs to/from Workers, dynamically provision resources in heterogeneous environments (e.g., clusters and clouds, CPUs and GPUs), scale and make efficient use of large-scale systems, elastically adapt resource configurations in response to workload, and provide fault tolerance to reliably execute assays with performance monitoring, error capture, and checkpoint/retry.
While there are several potential parallel and distributed 1 from colmena import thinker self.queues.send_task(self.next_task, 33 task='simulate') 34 35 thinker = Thinker(queues) 36 thinker.run() Listing 1: An example Thinker that implements the simple policy, "run 10 tasks in total, three at a time, generating a new task based on results obtained so far as each task completes." Its implementation comprises two agents, planner and consumer. The planner first sends three initial task requests to the Task Server, and then continually computes the best-possible next task to perform, given the current state. The second consumer, invoked whenever the Task Server sends notification that a task has completed, stores the result and submits the next task. The decorator @agent causes the planner to be launched as a thread when thinker.run() is called; the decorator @results_processor indicates that consumer should be run, also as a thread, each time that a task completes.
computing toolkits and workflow systems (e.g., funcX [22], Ray [23], Swift/T [8]) that could be used for this purpose, we implement the Task Server using Parsl [21]. Parsl is a parallel programming library for Python that extends Python's native concurrent.futures interface to enable high-performance, distributed computation and dataflow-based workflows. Parsl provides the runtime infrastructure to execute Python tasks asynchronously on various compute resources. It is able to serialize Python functions and input arguments, transfer those functions and arguments to a remote system, execute the function in the configured Worker environment, and retrieve results and errors. Parsl's modular design and standard interfaces enable workloads to be executed using various executors, such as via pilot jobs or a distributed MPI job, and by interacting with various job schedulers and cloud APIs, such as Slurm, PBS, and Amazon Web Services (AWS). The ability for Parsl to interface with job schedulers and cloud APIs, in particular, opens the possibility for writing application adjust the amount of resources devoted to a problem during the course of an application (e.g., reducing the simulation resources while ML models are retraining).
Users create a Task Server by providing a list of tasks and specifying, using Parsl's Python-based notation, the target computational resources. The tasks are defined as Python functions, which we wrap using Parsl's PythonApp to allow the functions to be executed remotely. Each assay can be mapped to different computational resources, making it possible to run assays on different resources (e.g., specialized hardware) or use different types of Workers (e.g., single-node vs multi-node) on the same resource.
The Task Server itself is written as a multi-threaded Python process. An intake thread reads task requests from the inputs queue and uses them to launch Parsl task executions. Output threads write completed tasks to the result queue.
3) Communication: The Thinker and Task Server communicate via Redis queues, with distinct request/result queue pairs for different task types (e.g., different assays, ML training). The Thinker writes requests to the appropriate request queue, to be received by the Task Server; when task execution completes, the Task Server writes the result to the corresponding result queue, from which it is read by the Thinker. This use of different queues for different task types simplifies implementation of Thinkers with multiple sub-agents.
Upon receiving a task request, the Task Server creates and launches a corresponding Parsl task. Parsl uses a hierarchical communication model, with ZeroMQ channels to efficiently distribute tasks to its Workers. As the Task Server receives results from Workers over these channels, it posts each to the appropriate result queue.
For tasks with large input or result values, Colmena uses a Value Server to pass values directly from the Thinker to the Worker-bypassing the Task Server. The Value Server uses Redis as the backend key-value store and exposes a lazy object proxy interface. Lazy object proxies simplify interaction with the Value Server because (1) the proxies behave as the wrapped object so users do not need to modify code to accommodate the proxies, (2) the proxies automatically handle retrieving data from the value store once the data are first needed, and (3) the lazy aspect of the proxies can amortize communication costs with the Value Server.
Any arbitrary object v can be wrapped in a proxy. The process of wrapping v involves placing v into the Value Server and returning a proxy p that stores the key associated with v in the Value Server and some additional metadata. Proxies behave like the underlying object, e.g., isinstance(p, type(v))==True. The proxy p is lazy in that it acts as a reference to v until p is accessed. Thus, p is cheap (in terms of serialization and communication costs) to include as a task input in the {task input, task definition} pair. When first used, p is resolved, meaning v is retrieved from the Value Server and stored inside p such that p can be used as v would be.
Colmena will automatically proxy input and result values larger than a user-defined threshold, and/or users can manually proxy large objects. The Value Server has a Worker-level cache to speed up tasks that reuse the same inputs (e.g., the model for ML inference tasks). Proxies can be asynchronously resolved, allowing for the overlap of Value Server access and computation. Colmena starts asynchronously resolving all proxies in a task's input prior to the task being executed on a Worker. Thus, the communication with the Value Server is overlapped with the task's execution. The start of a task often involves some initialization or importing of libraries, such that by the time a value is needed by the task, the corresponding proxy has already been resolved in the background.

C. Measuring Communication Overheads
There are several places for communication overhead between a task selected by a Thinker and the result being received by the Thinker again. As illustrated in Figure 1, each task request has two pairs of serialization/deserialization steps and four data transfer steps. The time required for serialization and deserialization steps are recorded automatically and transmitted along with the task data. The communication times are also measured indirectly by the difference in time between when an object is written to and read from a Redis queue. These times can be compared to the time spent executing the assay, which is also transmitted along with the task request and result objects. The data are available both to aid scaling applications, and can also be used proactively by Thinker applications when planning tasks.

IV. APPLICATION EXPERIMENTS
We conducted experiments to evaluate the performance of our molecular design application and then further studied the performance of components that were bottlenecks in the molecular design application.

A. Application Description
As introduced in Section II-B, our example application involves an ML-guided search of 10 5 molecules for those that match one of our design criteria for molecular electrolytes, namely high ionization potential (IP). At a high level, our application is an adaption of Bayesian Optimization to HPC. We determine a score of each task using the Upper Confidence Bound (UCB), a value based on the mean and confidence interval of the predictions from an ensemble of MPNNs trained to predict the IP from the bonding network of a molecule. UCB defines the highest scoring molecules as those with large means and large confidence intervals [24], which are those likely to both have high performance and provide data that will improve the models (i.e., because they are in regions where the model is least certain). Molecules are evaluated in order of descending scores. The data from completed simulations are used to update the MPNNs and, through the updated models, produce better estimates of molecular properties. Many simulations are performed in parallel as NWChem scales poorly for the small molecule sizes considered in this study.
More formally, the application uses two assays: a more expensive and accurate QC assay and an inexpensive but less accurate ML assay trained on QC results. As we are using a pre-defined search space of molecules, no generator is needed to expand the set of molecules considered progressively during execution. Instead, the application's Thinker maintains two data structures-a molecule queue of {molecule, ML-score} pairs, ordered by ML-score, and a results record of {molecule, QC-score} pairs-that are manipulated by the following three pairs of agents (see Figure 2): • The Trainer periodically sends to the Task Server a retrain task to retrain the ML model based on data in the results record; the companion Updater, upon receiving results for such a task, updates the weights of the ML model. • The ML-Scorer, whenever the ML assay is updated, sends ML-assay tasks to the Task Server to re-score every entity in the molecule list; the companion ML-Recorder, upon receiving results of these tasks, computes the Upper Confidence Bound (UCB) for each and reorders the molecule list with the new information. • The QC-Scorer repeatedly removes a molecule from the front of the molecule list and sends a QC-assay task to the Task Server to determine its QC score; the companion QC-Recorder, upon receiving results for these tasks, stores them in the results list if they pass validation.
The application's precise behavior thus depends (in addition to the total number of available resources) on the allocation of available resources to retrain, ML-assay, and QC-assay tasks: increasing the fraction allocated to QC-assay tasks leads to relatively more QC assays being performed, while increasing the fraction allocated to retrain and QC-assay tasks increases the timeliness of the ML-based scores, and thus in principle leads to more relevant QC assays being performed.
The strategy for controlling the resource allocations is implemented as an Allocator agent. The application's Allocator balances competing demands for resources with a policy that, after an initial set of ML assay results are obtained with a pretrained ML model, (a) retrains the ML model each time that the results record increases in size by an amount n retrain, (b) reruns the ML assay on all entries in the molecule list whenever the ML model is updated, and (c) reallocates resources between retrain, ML-assay, and QC-assay tasks as needed to ensure that retrain and ML-assay tasks are run as fast as possible when generated, with resources otherwise being used for QC-assay tasks. Resource reallocations are performed by controlling the amount of requests sent to the Task Server, which generates requests to the Parsl backend to stop or start Workers. Resources allocated to different purposes thus change in increments of the largest number of nodes needed for a single task, which in this case is four nodes for the QC-assay task.
In addition to the molecule list and results record already mentioned, these processes also share a resource counter (used to track resource availability) and a library of ML models.

B. Experimental Setup
We used the Theta supercomputer at the Argonne Leadership Computing Facility (ALCF) [25], a 11.69-petaflop system based on the second-generation Intel Xeon Phi "Knights Landing" (KNL) processor. Its 4392 nodes each have a 64core processor with 16 GB MCDRAM and 192 GB of DDR4 RAM, for a total of 281 088 cores; nodes are interconnected with a high speed Cray Aries network.
We performed 256-node and 1024-node runs of the electrolyte design application on Theta. The application was configured so that the Thinker process ran on the machineoriented miniserver (MOM) node, each NWChem task was allocated four nodes, and each Tensorflow task ran on one node each. Tensorflow inference tasks were grouped into batches of 4096 for efficient multithreaded execution on each node.

C. Application Evaluation
We evaluate the performance of our molecular design application from the two perspectives of computational performance (specifically, computational efficiency) and the quality of the results obtained.
1) Performance Evaluation: We evaluate the performance of the application by measuring the fraction of the time worker processes spend performing the computational tasks requested by the thinker (e.g., simulation, ML inference) and not communicating work to/from the Task Server; 100% indicates perfect performance.
As shown in Figure 3, we maintain near 100% utilization for most of a 1024-node run of our application. We note two major sources of under-utilization. The first is the start-up time for inference Workers, which is a median of 3 minutes. The startup cost can be reduced by unpacking Python libraries to node-local memory before launching Parsl Workers [26]. The second source of under-utilization is simulation tasks that do not complete within the timescale of the job, which leads to the associated resources being counted as unutilized even though the calculations are running (as verified via Parsl logs). This source of under-utilization can be mitigated by periodically checkpointing simulation tasks or by splitting simulation tasks

Model Library
Thinker Task Server into smaller steps, such as computing the neutral geometry first and then computing the ionization potential. The latter approach has the advantage that the Thinker can then use intermediate results to decide whether the computation should be continued (e.g., by deciding if it is likely to complete before the end of an allocation).

ML-Scorer
The overheads due to communication are minimal for QC-Assay tasks. The median cost for launching a new simulation is 1015 ms: 620 ms for the result to be received by the Thinker, 35 ms for submitting the new task to the Task Server, and 360 ms for the task to be launched on a Worker. The cost here is minimal (0.03%) compared to the median simulation runtime of 3275 s. We note that there is a significant variation between the largest cost, result communication, with that of failed tasks often 10× shorter than successful tasks. The difference can be attributed to the amount of data transferred, with failed tasks typically sending 0.5 KB and successful tasks communicating a median of 4.3 MB.
The communication cost of the ML-Assay tasks is partly hidden by prefetching tasks to Workers, but we note two issues that lower utilization and could inhibit further scaling. One is the startup time for the Workers used for ML assays. We find that it takes a median of 175 s for a Worker to begin work after being sent its first task from the Task Server, due to the startup time noted earlier. The second startup cost is the time it takes to compile a TensorFlow model before execution. The first task run by a Worker requires a median of 100 s, and the second task requires only a median of 80 s. We observe similar startup issues for the training tasks.
A second issue for inference is the time required to communicate results from a Worker to the Task Server. The communication time for an inference task request is 500 ms (0.6% of the median execution time), which can be hidden by prefetching work to the Workers. While this cost has not been a problem in the simulations run to date, it may become so at larger scales or problems. As with the QC-Assay tasks, we associate the long communication times with transferring large data objects. The training tasks require 30 s to communicate the > 50 MB updated ML model, a small (3%) fraction of the median run time of 850 s. In summary, we find the Colmena Task Server fulfills the performance requirements required for our application. We can change the allocation between node-parallel and single-node tasks during the run, with the latency dominated by the time to start a Python interpreter (∼100 s) for single-node tasks. The latency for launching new tasks after a simulation completes is low (<1s), though we note this latency can increase to several seconds for tasks with large inputs (e.g., entire deep learning models). Overall, the system was able to maintain a total utilization of 85% during the run with the largest source of underutilization resulting from the trailing tasks.
One approach to improve scaling in the future may be to have the Thinker application consider Worker start-up costs when allocating resources. For example, it may be better to maintain a smaller number of nodes dedicated to the ML-Assay tasks, so as to leverage the benefits of "warmed" nodes for inference tasks, rather than repeatedly stopping and starting inference tasks over time. A second approach is to optimize the communication of large results and requests, which we describe in the next sections.
2) Molecular Design Performance: We assess the performance of the application at solving our target problem by studying the scores of the molecules in the record over a run. We perform several runs using 256 nodes where we either retrain the ML assay on-demand during the run (as in Figure 3) or only once at the beginning of the run. For context, we also compare both versions of the application to a run where we select tasks randomly. Figure 4 shows QC results as a function of time for Thinkers with different policies. The two Thinkers that use ML models to select molecules for QC simulation perform much better than the one that selects molecules at random, finding over 100× more high-performing molecules with ionization potentials above 10 V. Normalizing by the total number of molecules evaluated during the run, the random agent finds a high-performance molecule with a success rate of 0.5%, whereas the success rates are 78% and 64% for the runs with and without retraining tasks. In short, we find a significant advantage in using ML assays to prioritize the order in which we consider a molecular design space and also an advantage to reprioritizing the list of simulations during a run. The application where we respond to new simulation results finds 10% more molecules with large ionization potentials even though it spends 5% less time on simulations.
The benefits of active learning are clearest at the end of the run, where the application uses data from the largest number of previous simulations to select new inputs. In the last hour of each run, the average ionization potential of the application with retraining was significantly higher than the run without (10.5 V vs. 9.8 V), clearly illustrating that retraining leads to an improved ability to identify high-value simulations. The effect can potentially be traced to improvements in the machine learning models. The initial models have a mean absolute error (MAE) of 0.395 V on 185 molecules selected at random of the search space and the MAE of the models is reduced to 0.389 V by the second re-training event and 0.382 V by the last batch of the run. The performance gains are subtle but the increased search performance illustrates how even minor improvements in a model can lead to an improved ability to select new molecules. The challenge then becomes being able to perform model updates quickly.
Our application provides reasonable response times between when a simulation completes and its data are used to select the next simulations. The time-to-solution for retraining is an average of 57 minutes between when a simulation completes and the task list is updated based on its results, in which time an average of 63 new simulations are submitted-15% of the 537 submitted during the entire run. Such results demonstrate the need to dedicate specific resources to the ML tasks to keep up with the rate data can be produced by the simulation codes. Dedicating fewer nodes to the retraining or inference task would slow how quickly the application responds to new data, to the detriment of the active learning process.
Our work demonstrates clear benefits for online training of The number of high-performing molecules (i.e., with ionization potential greater than 10 V) evaluated over time by the Colmena molecular design application when using three different Thinker strategies: random, which selects molecules for QC simulations at random; no-retrain, which selects molecules for QC simulations based on an ML model that is trained once; and update-8, which selects molecules based on an ML model that is retrained after 8 QC simulations complete successfully. Vertical dashed lines indicate the times at which the molecule list was reordered after model rerunning ML-Assay tasks.
ML models and opens up previously inaccessible opportunities for evaluating adaptive experimental design on HPC systems. For example, our application presents an excellent use case for asynchronous, batched active learning strategies [5]. There is also significant room to devise optimal strategies for partitioning resources between simulation and learning tasks that are aware of the overhead for re-tasking nodes (e.g., start-up costs of machine learning workers). We hope that Colmena and our example application provide a starting point for innovating new algorithms to steer ensemble simulations on HPC.

D. Component Evaluation
As discussed in Section IV-C1, we observed that transferring large requests or result objects is a major source of communication costs, and the ML-Assay tasks are the most susceptible to the effect of this bottleneck. Consequently, we first evaluate a system that optimizes large (> 10 MB) result transfers and then evaluate its effect on ML-Assay tasks. This is the done through inclusion of the Value Server, which reduces the requirement of serialization and deserialization of task data. We discuss the performance improvement through the inclusion of a Value Server using a synthetic problem. We then discuss the improvement that can be achieved in realworld production runs for the electrolyte design problem.
1) Synthetic Application: We built a synthetic application, SynApp, to permit the systematic evaluation of Colmena communication overheads. This application uses a Thinker plus N workers, one per node; the Thinker generates T identical tasks, each with duration D, unique (and thus non-cacheable) input of size I, and producing a result of size O. This Thinker first submits one task per worker and then continues to submit a new task each time that it receives a result, until T tasks have To evaluate the impact of the Value Server, we first run SynApp for 200 zero-length tasks with 1 MB inputs on eight nodes (i.e., {T =200, D=0, I=1 MB, O=0, N =8}), both with and without the Value Server, while measuring task overheads. We see in Figure 5 that the use of the Value Server reduces, in particular, task communication times between the Thinker client and Task Server, and serialization times. The cost of transferring input data from the Value Server to the Worker is reduced by the use of the asynchronous data retrieval explained in Section III-B3. (Note that if input values were all identical, this cost would be largely eliminated due to caching.) To further study how the benefit of the Value Server varies with input size I, we repeat the experiments of Figure 5 but while varying I from 1 KB to 10 MB. The results, shown in Figure 6 as percentage improvement in communication overhead time with Value Server relative to the time without Value Server, show that for small inputs (<10 KB), the additional cost of communicating with the Value Server is larger than the cost of passing the input data through the Task Server-but that as the input size increases, the cost of passing input data through the Task Server increases rapidly and the Value Server yields large improvements.
2) Scaling ML Assays: As discussed in Section IV-D1, there is a clear benefit to using the Value Server for tasks with large inputs or large results. We now study a particular sub-problem within the electrolyte design application: running machine learning inference tasks. The input task size was varied by changing the total number of molecules evaluated as a function of the number of nodes allocated for ML inference. The resulting evaluation rate, in molecules per second, is presented in Figure 7.
As noted in Section IV-C1, individual ML inference tasks do not take long to run. Nevertheless, the Value Server can deliver significant benefits even in this case when inference   results must be transferred from many nodes. In Figure 8, we examine the mean time taken to transfer results from Worker to Thinker, with and without the Value Server. We see that, without the Value Server, it takes up to 100 s to transfer ML inference results from more than 100 nodes. With the Value Server, however, the mean transfer time as a function of increasing node count remains constant. We observe significant improvements in the evaluation rate at 1024 nodes with the Value Server. The time to communicate results from a completed job remains ∼100 ms at 1024 nodes when using the Value Server, in contrast to the ∼100 s transfer time without (Figure 8), indicating that the workflow engine in the Task Server is not getting overloaded. Consequently, we maintain ideal scaling up to at least 1024 nodes and reasonable performance at 2048 nodes.
3) Performance Envelopes: To explore how Colmena performance varies with task duration, result size, and number of workers, we performed additional SynApp experiments while varying those parameters. Figure 9 illustrates three performance envelopes computed on Theta. For example, we see that 100 s tasks that consume and produce 1 MB of data run with 90% utilization on 512 workers. We observe that either reducing the amount of data being transmitted or the degree of parallelism can lead to better utilization for shorter tasks. We make this evaluation application available for assessing whether Colmena is an appropriate tool for different use cases or supercomputing systems [28].
V. RELATED WORK Colmena requires methods for creating tasks and for monitoring and managing their execution. It needs to support a wide range of types and scales, from multi-hour, many-node QC tasks to minute-duration, single-node ML tasks. Accordingly, our work with Colmena builds upon much previous work.
General Steering Frameworks. Nimrod/O [7] is an early example of a system for automated (rather than manual [29]) steering of simulation ensembles. Several general-purpose toolkits for automated steering of ensembles have been proposed in recent years, each with different models for coupling steering and simulation. Here, we describe their key features and how they inspired our development of Colmena.
The DeepHyper [12] hyperparameter optimization system uses a centralized planning process to select tasks and distributes a single type of assay across multiple nodes using a workflow engine. The planning process submits tasks to the workflow engine (DeepHyper supports Balsam [30] and Ray [23]) and queries the engine for completed results. Supervisor [3] has a similar centralized architecture to Deep-Hyper, with a single node for communicating with the highperformance Swift/T [8] workflow engine via a queue. Proxima [31] uses ML methods to dynamically tune a surrogatemodeling configuration in response to real-time feedback from the ongoing simulation, but does not optimize for use of HPC. Colmena uses a similar centralized model for task planning; is designed, like DeepHyper, to support multiple workflow engines; and provides easier support for multiple task types.
LibEnsemble [10] expresses ensembles using a model where tasks produced by task generation (akin to steering) workers are executed by simulation workers, and simulation results are fed back to inform the generation tasks. The steering policy can be either centralized (single task generator) or decentralized (multiple task generators). A manager service deploys the task generation and simulation workers onto multiple nodes and routes data across workers. Colmena shares LibEnsemble's ability to dedicate more than one node to steering-related tasks and provides the ability to break policies into an unlimited number of worker types.
Ray [23] uses a similar distributed model to LibEnsemble but permits ensembles to use many types of interacting agents (beyond generator and simulator) with complex coordination policies. Colmena has a similar agent-oriented programming model but centralizes all agents to a single node so that they can communicate with shared memory.
Rocketsled [9] expresses the steering task as a step within a Fireworks [32] workflow that can add new nodes to the workflow graph. Colmena uses a different programming model where planning tasks need not be triggered by workflow events and was designed to separate planning logic from the task execution engine. Cray SmartSim [33] allows multiple planning scripts to independently launch jobs on a cluster and coordinate among each other using Redis. The Colmena and SmartSim programming models are similar, in that multiple planning scripts can submit and receive work concurrently, and possible tasks can be enumerated as a list of assays. Colmena has utilities that simplify building planning strategies for ensemble steering (e.g., pulling from result queues, eventtriggered resource reallocation), whereas SmartSim is capable of building other types of AI+simulation applications (e.g., online analysis, inference from simulation codes).
In short, the Colmena toolkit is purpose-built for expressing the complex policies needed to make efficient use of highly parallel supercomputers for computational campaigns. We designed Colmena to provide many of the features of current frameworks for steering computational campaigns, including the centralized programming model of codes such as DeepHyper or Supervisor, the ability to deploy planning tasks across multiple nodes illustrated by libEnsemble, and simple routes to building planning policies as cooperative agents in the style of Ray and SmartSim. Colmena presents a single package able to recreate the parallelization strategies of all current steering frameworks and provides the flexibility needed to explore even more sophisticated approaches.
Active Learning on HPC. Active learning methods obtain new training data via online querying of an information source, including (as here) computational simulations. The approaches just reviewed may be viewed as active learning methods.
Reinforcement Learning on HPC. Algorithms for training reinforcement learning models have much in common with those for steering ensemble simulations. Reinforcement learning agents gather data by using a policy to guide the evolution of different environments (e.g., simulations for physical systems) and periodically retrain this policy to better steer the environments towards desirable states. Computations that simulate environments, make policy decisions, and retrain policies can all occur asynchronously across distributed resources, akin to Colmena's Thinker/Task Server model for ensemble simulations. Consequently, the design of steering algorithms is related to approaches for distributed training of reinforcement learning (e.g., IMPALA [34]) and to toolkits for deploying reinforcement learning at scale (e.g., RLLib [35], ExaRL [36]). Such algorithms for training reinforcement learning policies are a class of approaches that can be expressed with Colmena.
Specialized Steering Frameworks. Specialized applications that dynamically create or reorder tasks are also prevalent in the literature. Various domain-specific tools (e.g., XtalOpt [37], Kombine [38], DeepDriveMD [13], CAMD [11]) engage different patterns for generating tasks in solving different classes of problems (e.g., optimization, parameter estimation). Tools that perform hyperparameter searches for neural network design, such as DeepHyper [12] and Tune [39], illustrate how to handle ML tasks at scale. Colmena adapts concepts from these tools; each of the algorithms in these tools can be implemented using Colmena.
Workflow Systems. Colmena also relies heavily on workflow systems to distribute computations across many nodes. Applicable workflow systems include Ray [23], Balsam [30], RADICAL Cyber Toolkit [40], Parsl [21], Fireworks [32], and many others [41]. Such systems provide unique approaches to specifying tasks and runtime systems for executing them across distributed resources. Colmena is designed to make use of workflow tools and not to make any new contributions in the design of workflow systems.
Process Management Systems. Methods for deploying many concurrent, short-duration tasks is another area of active research. The challenges are well explained in a recent work that studied many-task performance on Summit [42]. The Process Management Interface (PMIx) [43] defines an API for such capabilities that is available on some supercomputers (e.g., Summit). There are also efforts, such as MPI Comm launch [44], to incorporate process management methods into the Message Passing Interface. Systems for launching, monitoring, and managing large numbers of tasks on HPC are going to be critical as we scale Colmena to larger computational resources.

VI. CONCLUSIONS
We introduced Colmena, an open-source Python library for machine-learning-based steering of ensemble computations on HPC systems. We first formalized the steering process as a design problem where one must decide which computations to perform on what inputs to produce a record of simulations with maximal value at minimal cost. We then described how Colmena facilitates building such steering applications by permitting the construction and composition of a Thinker that implements the decision making processes used to define computational tasks and a Task Server that distributes execution across HPC resources. We illustrated the use of the Colmena library with a molecular design application that finds molecules with high resistance to oxidation at rates 100× faster than naive searches by interleaving simulation and ML tasks. We demonstrated effective scaling on up to 1024 nodes (65 536 cores) and illustrate how to improve the scaling of the application further by using a separate subsystem for transferring large result objects.
We intend that Colmena provides a toolkit for exploring methods for steering ensemble simulations. The flexible, multi-threaded Thinker class permits implementing varied, complex policies for interleaving different types of computation. Our primary goal for creating Colmena is to support the expression of steering policies that use ML to augment human intelligence in designing and managing computational campaigns. Interfaced with a Task Server built using Parsl, users can execute these policies at large scales and across heterogeneous computing resources. As we learn more, we will build templates for common classes of decision problems (e.g., model-based optimization, reinforcement learning) that allow users to quickly deploy state-of-the-art steering policies. Through this work, we hope to enable computational campaigns that take fuller advantage of current and next-generation supercomputers.

DATA AND SOFTWARE AVAILABILITY
The source code used in the this manuscript, logging information for each of the runs described in the paper, and Jupyter notebooks used to analyze logs and produce figures are all published via the Materials Data Facility. [45], [46] The source code and Jupyter notebooks associated with this manuscript is also available on GitHub at https://github.com/ exalearn/electrolyte-design/, which will be updated as our work proceeds.