class_docstr: This class is responsible for gathering all user-specified configuration options, parsing them,
  instantiating a GraphManager and then starting that GraphManager with either improve() or evaluate().
  This class is also responsible for launching multiple processes.
  It is structured so that it can be sub-classed to provide alternate mechanisms to configure and launch
  Coach jobs.
  
  The key entry-point for this class is the .launch() method which is expected to be called from __main__
  and handle absolutely everything for a job.
class_docstr_tok: ["This","class","is","responsible","for","gathering","all","user-specified","configuration","options",",","parsing","them",",","instantiating","a","GraphManager","and","then","starting","that","GraphManager","with","either","improve","(",")","or","evaluate","(",")",".","This","class","is","also","responsible","for","launching","multiple","processes",".","It","is","structured","so","that","it","can","be","sub-classed","to","provide","alternate","mechanisms","to","configure","and","launch","Coach","jobs",".","The","key","entry-point","for","this","class","is","the",".launch","(",")","method","which","is","expected","to","be","called","from","__main__","and","handle","absolutely","everything","for","a","job","."]
code: def start_multi_process(
      graph_manager: "GraphManager", args: argparse.Namespace
  ):
      total_tasks = args.num_workers
      if args.evaluation_worker:
          total_tasks += 1
  
      ps_hosts = "localhost:{}".format(get_open_port())
      worker_hosts = ",".join(
          [
              "localhost:{}".format(get_open_port())
              for i in range(total_tasks)
          ]
      )
  
      # Shared memory
      class CommManager(BaseManager):
          pass
  
      CommManager.register(
          "SharedMemoryScratchPad",
          SharedMemoryScratchPad,
          exposed=["add", "get", "internal_call"],
      )
      comm_manager = CommManager()
      comm_manager.start()
      shared_memory_scratchpad = comm_manager.SharedMemoryScratchPad()
  
      if args.checkpoint_restore_file:
          raise ValueError(
              "Multi-Process runs only support restoring checkpoints from a directory, "
              "and not from a file. "
          )
  
      def start_distributed_task(
          job_type,
          task_index,
          evaluation_worker=False,
          shared_memory_scratchpad=shared_memory_scratchpad,
          gpu_id=None,
      ):
          task_parameters = DistributedTaskParameters(
              framework_type=args.framework,
              parameters_server_hosts=ps_hosts,
              worker_hosts=worker_hosts,
              job_type=job_type,
              task_index=task_index,
              evaluate_only=0
              if evaluation_worker
              else None,  # 0 value for evaluation worker as it should run infinitely
              use_cpu=args.use_cpu,
              num_tasks=total_tasks,  # training tasks + 1 evaluation task
              num_training_tasks=args.num_workers,
              experiment_path=args.experiment_path,
              shared_memory_scratchpad=shared_memory_scratchpad,
              seed=args.seed + task_index
              if args.seed is not None
              else None,  # each worker gets a different seed
              checkpoint_save_secs=args.checkpoint_save_secs,
              checkpoint_restore_path=args.checkpoint_restore_dir,  # MonitoredTrainingSession only supports a dir
              checkpoint_save_dir=args.checkpoint_save_dir,
              export_onnx_graph=args.export_onnx_graph,
              apply_stop_condition=args.apply_stop_condition,
          )
          if gpu_id is not None:
              set_gpu(gpu_id)
          # we assume that only the evaluation workers are rendering
          graph_manager.visualization_parameters.render = (
              args.render and evaluation_worker
          )
          p = Process(
              target=start_graph, args=(graph_manager, task_parameters)
          )
          # p.daemon = True
          p.start()
          return p
  
      # parameter server
      parameter_server = start_distributed_task(
          "ps", 0, gpu_id=CoachLauncher.gpus[0]
      )
  
      # training workers
      # wait a bit before spawning the non chief workers in order to make sure the session is already created
      curr_gpu_idx = 0
      workers = []
      workers.append(
          start_distributed_task(
              "worker", 0, gpu_id=CoachLauncher.gpus[curr_gpu_idx]
          )
      )
  
      time.sleep(2)
      for task_index in range(1, args.num_workers):
          curr_gpu_idx = (curr_gpu_idx + 1) % len(CoachLauncher.gpus)
          workers.append(
              start_distributed_task(
                  "worker",
                  task_index,
                  gpu_id=CoachLauncher.gpus[curr_gpu_idx],
              )
          )
  
      # evaluation worker
      if args.evaluation_worker or args.render:
          curr_gpu_idx = (curr_gpu_idx + 1) % len(CoachLauncher.gpus)
          evaluation_worker = start_distributed_task(
              "worker",
              args.num_workers,
              evaluation_worker=True,
              gpu_id=CoachLauncher.gpus[curr_gpu_idx],
          )
  
      # wait for all workers
      [w.join() for w in workers]
      if args.evaluation_worker:
          evaluation_worker.terminate()
      parameter_server.terminate()
  
imports: import sys
  import copy
  from configparser import ConfigParser, Error
  import os
  from rl_coach import logger
  import traceback
  from rl_coach.logger import screen, failed_imports
  import argparse
  import atexit
  import time
  import sys
  import json
  from rl_coach.base_parameters import Frameworks, VisualizationParameters, TaskParameters, DistributedTaskParameters, RunType, DistributedCoachSynchronizationType
  from rl_coach.core_types import TotalStepsCounter, RunPhase, PlayingStepsType, TrainingSteps, EnvironmentEpisodes, EnvironmentSteps, StepMethod, Transition
  from multiprocessing import Process
  from multiprocessing.managers import BaseManager
  import subprocess
  from glob import glob
  from rl_coach.graph_managers.graph_manager import HumanPlayScheduleParameters, GraphManager
  from rl_coach.utils import list_all_presets, short_dynamic_import, get_open_port, SharedMemoryScratchPad, get_base_dir, set_gpu
  from rl_coach.graph_managers.basic_rl_graph_manager import BasicRLGraphManager
  from rl_coach.environments.environment import SingleLevelSelection
  from rl_coach.memories.backend.redis import RedisPubSubMemoryBackendParameters
  from rl_coach.memories.backend.memory_impl import construct_memory_params
  from rl_coach.data_stores.data_store import DataStoreParameters
  from rl_coach.data_stores.s3_data_store import S3DataStoreParameters
  from rl_coach.data_stores.nfs_data_store import NFSDataStoreParameters
  from rl_coach.data_stores.redis_data_store import RedisDataStoreParameters
  from rl_coach.data_stores.data_store_impl import get_data_store, construct_data_store_params
  from rl_coach.training_worker import training_worker
  from rl_coach.rollout_worker import rollout_worker
  from rl_coach.schedules import *
  from rl_coach.exploration_policies.e_greedy import *
label_desc: Use image node_id 10 for calling the CoachLauncher obj's underlying member method code with example usage: obj.start_multi_process(graph_manager, args) and returns: p