class_docstr: Coordinator is responsible for running user code on a cluster.
  
  Has one main method, `launch_clients`, which runs the user's code
  on every node of the cluster. Since all we pass to each worker are
  their worker id and the distribution strategy, this means that each
  worker will do its own full graph transformation based on the strategy.
  Then the workers will sync up when they run the graphs.
  
  `join` is called by `atexit` so that we can try to make sure the
  remote processes are killed with the chief's process is ended.
docstr: Launch the user's code on each worker.
  
  Sets environment variables so that we run the correct AutoDist code paths on workers.
  (i.e., the non-chief code-paths).
  
  Store each new process created into the class so they can be monitored with `join`.
code: def launch_clients(self):
      """
      Launch the user's code on each worker.
  
      Sets environment variables so that we run the correct AutoDist code paths on workers.
      (i.e., the non-chief code-paths).
  
      Store each new process created into the class so they can be monitored with `join`.
      """
      atexit.register(self.join)
  
      replica_devices = [
          DeviceSpec.from_string(device_string)
          for device_string in self._strategy.graph_config.replicas
      ]
      replica_hosts = {d.host_address for d in replica_devices}
  
      # Assumption: Master node must run one replica.
      # assert any([is_local_address(h) for h in replica_hosts])
  
      for replica_host in replica_hosts:
          # Run the process
          if not self.cluster.is_chief(replica_host):
              # Build the command
              env = {
                  ENV.AUTODIST_WORKER.name: replica_host,
                  ENV.AUTODIST_STRATEGY_ID.name: self._strategy.id,
                  ENV.AUTODIST_MIN_LOG_LEVEL.name: ENV.AUTODIST_MIN_LOG_LEVEL.val,
                  ENV.AUTODIST_IS_TESTING.name: ENV.AUTODIST_IS_TESTING.val,
                  ENV.AUTODIST_PATCH_TF.name: ENV.AUTODIST_PATCH_TF.val,
                  ENV.AUTODIST_INTERNAL_TF.name: ENV.AUTODIST_INTERNAL_TF.val,
                  ENV.SYS_DATA_PATH.name: ENV.SYS_DATA_PATH.val,
                  ENV.SYS_RESOURCE_PATH.name: ENV.SYS_RESOURCE_PATH.val,
              }
              cmd_env = ["{}={}".format(k, v) for k, v in env.items()]
              cmd_main = ["python"] + sys.argv
              cmd = cmd_env + cmd_main
  
              self.cluster.remote_copy(
                  local_path=self._strategy.path,
                  remote_path=DEFAULT_SERIALIZATION_DIR,
                  hostname=replica_host,
              )
              proc = self.cluster.remote_exec(
                  cmd, hostname=replica_host
              )
              self.threads.append(self._proc_wait_async(proc))
  
file_path: autodist/autodist/coordinator.py
imports: import sys
  import threading
  import atexit
  import os
  from autodist.const import ENV, DEFAULT_SERIALIZATION_DIR
  from autodist.resource_spec import DeviceSpec
  from autodist.utils import logging
label_desc: Use image node_id 2 for calling the Coordinator obj's underlying member method code with example usage: obj.launch_clients() without return types