TensorFlowAgentParallelRunner¶
- class pams.runners.TensorFlowAgentParallelRunner(settings, prng=None, logger=None, simulator_class=<class 'pams.simulator.Simulator'>)[source]¶
TensorFlow Agent Parallel runner class. This is experimental.
This runner is
pams.runners.MultiProcessAgentParallelRunnerwhose worker processes are prepared for agents that use TensorFlow inpams.agents.Agent.submit_orders(), e.g., Keras models. The simulation results are identical to those ofpams.runners.SequentialRunnerwith the same settings and the same seed as long as the agents are deterministic and TensorFlow uses the same number of threads (see below).TensorFlow is an optional dependency of PAMS:
import pamsdoes not import it, and this runner imports it when it is created. If TensorFlow is not installed, an ImportError is raised. Install it bypip install tensorflow(orpip install tensorflow-cpufor the CPU-only build).The worker processes are started by
spawnby default, i.e.,default_start_methodis"spawn", because TensorFlow is not fork-safe. Once TensorFlow runs an operation, the process has thread pools (and CUDA contexts for GPUs), but a process started byforkhas only a copy of the thread calling fork. In the forked process, operations that use the thread pools of TensorFlow, e.g., a large matrix multiplication, hang, GPUs cannot be used, and TensorFlow cannot be configured anymore. Such a fork happens easily, e.g., when agents build their models on the main process or apams.runners.SequentialRunneris run before on the same process. A spawned process starts a new interpreter and initializes TensorFlow by itself.simulation.startMethodin the config still takes precedence:forkserveris also safe if TensorFlow is not used at the top level of the main module, andforkworks only if TensorFlow is not used on the main process before the simulation.Each worker process configures TensorFlow once before it runs any task, by the following keys of
simulationin the config:tensorflowIntraOpThreads(int ≥ 1): number of threads that TensorFlow uses to run one operation, e.g., a matrix multiplication, in each worker process. The default is the number of CPUs divided by the number of worker processes running tasks at the same time, i.e., the smaller ofnumParalleland the largestmaxNormalOrdersof the sessions (at least 1), so that the worker processes do not use more threads than the CPUs in total. TensorFlow uses all the CPUs in each process by default. The results of TensorFlow can depend on this number (see below).tensorflowInterOpThreads(int ≥ 1): number of threads that TensorFlow uses to run independent operations at the same time in each worker process. The default is 1.tensorflowGpuMemoryGrowth(bool): whether to enable memory growth for all the visible GPUs in each worker process. The default is true. Without memory growth, TensorFlow allocates almost all the memory of a GPU in the first process using it, so the other worker processes fail with out of memory errors when they share the GPU. Set it to false to keep the default behavior of TensorFlow, e.g., when each worker process uses its own GPU.
Then, the initializer given by
_get_worker_initializeris called as inpams.runners.MultiProcessAgentParallelRunner, e.g., to load a read-only model once per worker process. These keys do not configure TensorFlow on the main process. IftensorflowGpuMemoryGrowthis true, TensorFlow lists the GPUs before the initializer is called, so the initializer cannot select the GPUs of a worker process by the environment variableCUDA_VISIBLE_DEVICES; usetf.config.set_visible_devicesinstead.Note
As
pams.runners.MultiProcessAgentParallelRunner, this runner pickles the agents and the markets, i.e., the whole simulation, for each task.tf.Tensorandtf.Variableheld by agents are pickled as copies of their values, and Keras 3 models are pickled by saving them in the.kerasformat, which takes tens of milliseconds even for a small model, in every task and even in the tasks of other agents. Objects made bytf.functioncannot be pickled even if they are defined at the top level of a module. Therefore, agents should not hold models as their attributes. Instead, an agent should hold a key of its model, such as the path of a saved model, and get the model from a cache at the top level of a module, e.g., a function decorated byfunctools.lru_cache()that loads the model. Then, each worker process loads the model once, at its first task or in the worker initializer, and reuses it in the later tasks, andpams.runners.SequentialRunnercan run the same agents.Note
Only the orders and the state of the agent’s pseudo random number generator (
prng) are returned from the worker processes. The random states of TensorFlow, e.g., the global seed andtf.random.Generator, are not returned, and which worker process runs an agent changes from run to run. For the same results aspams.runners.SequentialRunner, agents should draw random numbers fromprng, e.g., the seeds of stateless random operations such astf.random.stateless_normal.Models must also give the same outputs on every process. On CPUs, the outputs of the operations that TensorFlow splits among threads, e.g., reductions and matrix multiplications over long axes, depend on the number of intra-op threads, even with
tf.config.experimental.enable_op_determinism. The default number of this runner depends on the number of CPUs,numParallelandmaxNormalOrders, and it can differ from that of the main process, where TensorFlow uses all the CPUs. Therefore, for results identical topams.runners.SequentialRunner, or across differentnumParallel, settensorflowIntraOpThreadsexplicitly, and set the same number on the process runningpams.runners.SequentialRunnerbytf.config.threading.set_intra_op_parallelism_threadsbefore TensorFlow is initialized there. On GPUs, some operations are not deterministic unlesstf.config.experimental.enable_op_determinismis called on the main process and in the worker processes.Warning
This runner makes the simulation faster only if the TensorFlow computations in
pams.agents.Agent.submit_orders()are much heavier than the cost of pickling the simulation in every task. Moreover, each worker process imports and initializes TensorFlow at its start, which takes seconds and hundreds of megabytes of memory per process.-
default_start_method:
Optional[str] = 'spawn'¶ start method of the worker processes used when
simulation.startMethodis not set in the config. TensorFlow is not fork-safe, so it is"spawn".- Type:
Optional[str]