From 5d4ead378867f097939c02a290bbe2498114dfed Mon Sep 17 00:00:00 2001 From: jdcpni Date: Fri, 25 Nov 2022 10:43:42 -0500 Subject: [PATCH] Refactor/execution mode compiled (#2550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * • composition.py - learn(): add error for use of ExecutionMode.LLVM or ExecutionMode.PyTorch • autodiffcomposition.py - learn(): add warning for use of ExecutionMode.Python • test_learning.py: - add test_execution_mode_pytorch_and_LLVM_errors • test_autodiffcomposition.py: - add test_execution_mode_python_warning * [skip ci] * [skip ci] * • llvm/__init__.py - add ExecutionMode.COMPILED (~ Python | PyTorch). • compositon.py, autodiffcomposition.py, compositionrunner.py: - modifed to sue ExecutionMode.COMPILED for condionals • test_autodiffcomposition.py: - test_execution_mode_python_error() replaces test_execution_mode_python_warning() * - * • Modification per Jan's suggestions * • test_autodiffcomposition.py - test_execution_mode_python_error(): fix bug --- conftest.py | 4 ++-- psyneulink/core/compositions/composition.py | 23 +++++++++++-------- psyneulink/core/llvm/__init__.py | 1 + .../compositions/autodiffcomposition.py | 7 +++--- .../library/compositions/compositionrunner.py | 5 ++-- tests/composition/test_autodiffcomposition.py | 14 +++++------ 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/conftest.py b/conftest.py index 393cb53e3c6..cae7d36ce73 100644 --- a/conftest.py +++ b/conftest.py @@ -75,8 +75,8 @@ def pytest_generate_tests(metafunc): if "autodiff_mode" in metafunc.fixturenames: auto_modes = [ - pnlvm.ExecutionMode.Python, - # pnlvm.ExecutionMode.PyTorch, + # pnlvm.ExecutionMode.Python, + pnlvm.ExecutionMode.PyTorch, pytest.param(pnlvm.ExecutionMode.LLVMRun, marks=pytest.mark.llvm) ] metafunc.parametrize("autodiff_mode", auto_modes) diff --git a/psyneulink/core/compositions/composition.py b/psyneulink/core/compositions/composition.py index d65f8c4e1e6..a83be7b2531 100644 --- a/psyneulink/core/compositions/composition.py +++ b/psyneulink/core/compositions/composition.py @@ -10395,7 +10395,7 @@ def learn( from psyneulink.library.compositions import AutodiffComposition runner = CompositionRunner(self) - if (execution_mode in {pnlvm.ExecutionMode.PyTorch, pnlvm.ExecutionMode.LLVM} + if ((execution_mode is not pnlvm.ExecutionMode.Python) and not isinstance(self, AutodiffComposition)): raise CompositionError(f"ExecutionMode.{execution_mode.name} cannot be used in the learn() method of " f"'{self.name}' because it is not an {AutodiffComposition.componentCategory}") @@ -10477,11 +10477,14 @@ def _execute_controller(self, context=context, node=self.controller) - if self.controller and not execution_mode: + if self.controller and not execution_mode & pnlvm.ExecutionMode.COMPILED: context.execution_phase = ContextFlags.PROCESSING self.controller.execute(context=context) - if execution_mode: + else: + assert (execution_mode == pnlvm.ExecutionMode.LLVM + or execution_mode & pnlvm.ExecutionMode._Fallback),\ + f"PROGRAM ERROR: Unrecognized compiled execution_mode: '{execution_mode}'." _comp_ex.execute_node(self.controller, context=context) context.remove_flag(ContextFlags.PROCESSING) @@ -10705,7 +10708,7 @@ def execute( # Run compiled execution (if compiled execution was requested # NOTE: This should be as high up as possible, # but still after the context has been initialized - if execution_mode: + if execution_mode & pnlvm.ExecutionMode.COMPILED: is_simulation = (context is not None and ContextFlags.SIMULATION_MODE in context.runmode) # Try running in Exec mode first @@ -10819,7 +10822,7 @@ def execute( inputs = self._validate_execution_inputs(inputs) build_CIM_input = self._build_variable_for_input_CIM(inputs) - if execution_mode: + if execution_mode & pnlvm.ExecutionMode.COMPILED: _comp_ex.execute_node(self.input_CIM, inputs, context) # FIXME: parameter_CIM should be executed here as well, # but node execution of nested compositions with @@ -11024,7 +11027,7 @@ def execute( # This ensures that the order in which nodes execute does not affect the results of this timestep frozen_values = {} new_values = {} - if execution_mode: + if execution_mode & pnlvm.ExecutionMode.COMPILED: _comp_ex.freeze_values() # PURGE LEARNING IF NOT ENABLED ---------------------------------------------------------------- @@ -11106,7 +11109,7 @@ def execute( context.replace_flag(ContextFlags.PROCESSING, ContextFlags.LEARNING) # Execute Mechanism - if execution_mode: + if execution_mode & pnlvm.ExecutionMode.COMPILED: _comp_ex.execute_node(node, context=context) else: if node is not self.controller: @@ -11129,7 +11132,7 @@ def execute( elif isinstance(node, Composition): - if execution_mode: + if execution_mode & pnlvm.ExecutionMode.COMPILED: # Invoking nested composition passes data via Python # structures. Make sure all sources get their latest values srcs = (proj.sender.owner for proj in node.input_CIM.afferents) @@ -11168,7 +11171,7 @@ def execute( execution_mode=nested_execution_mode) # Get output info from nested execution - if execution_mode: + if execution_mode & pnlvm.ExecutionMode.COMPILED: # Update result in binary data structure _comp_ex.insert_node_output(node, ret) @@ -11310,7 +11313,7 @@ def execute( context=context) # Extract result here - if execution_mode: + if execution_mode & pnlvm.ExecutionMode.COMPILED: _comp_ex.freeze_values() _comp_ex.execute_node(self.output_CIM, context=context) report(self, diff --git a/psyneulink/core/llvm/__init__.py b/psyneulink/core/llvm/__init__.py index 7c5c951d75f..651225c2a1f 100644 --- a/psyneulink/core/llvm/__init__.py +++ b/psyneulink/core/llvm/__init__.py @@ -85,6 +85,7 @@ class ExecutionMode(enum.Flag): LLVMExec = LLVM | _Exec PTXRun = PTX | _Run PTXExec = PTX | _Exec + COMPILED = ~ (Python | PyTorch) _binary_generation = 0 diff --git a/psyneulink/library/compositions/autodiffcomposition.py b/psyneulink/library/compositions/autodiffcomposition.py index f7efc82f8e4..ee5f37ecb31 100644 --- a/psyneulink/library/compositions/autodiffcomposition.py +++ b/psyneulink/library/compositions/autodiffcomposition.py @@ -634,10 +634,9 @@ def learn(self, *args, **kwargs): if 'execution_mode' in kwargs: execution_mode = kwargs['execution_mode'] if execution_mode == pnlvm.ExecutionMode.Python: - warnings.warn(f"{self.name}.learn() called with ExecutionMode.Python; " - f"learning will be executed using PyTorch; " - f"should use ExecutionMode.PyTorch for clarity, " - f"or a standard Composition for Python execution.)") + raise AutodiffCompositionError(f"{self.name} is an AutodiffComposition so its learn() " + f"cannot be called with execution_mode = ExecutionMode.Python; " + f"use ExecutionMode.PyTorch or ExecutionMode.LLVMRun.") # OK, now that the user has been advised to use ExecutionMode.PyTorch and warned *not* to ExecutionMdoe.Python, # convert ExecutionMode.PyTorch specification to ExecutionMode.Python for internal use (nice, eh?) if execution_mode == pnlvm.ExecutionMode.PyTorch: diff --git a/psyneulink/library/compositions/compositionrunner.py b/psyneulink/library/compositions/compositionrunner.py index 8e7a757a353..601bb6b6484 100644 --- a/psyneulink/library/compositions/compositionrunner.py +++ b/psyneulink/library/compositions/compositionrunner.py @@ -146,7 +146,8 @@ def run_learning(self, --------- Outputs from the final execution """ - if not execution_mode: + + if not (execution_mode & pnlvm.ExecutionMode.COMPILED): self._is_llvm_mode = False else: self._is_llvm_mode = True @@ -195,7 +196,7 @@ def run_learning(self, raise Exception("The minibatch size cannot be greater than the number of trials.") early_stopper = None - if patience is not None and not execution_mode: + if patience is not None and not self._is_llvm_mode: early_stopper = EarlyStopping(min_delta=min_delta, patience=patience) if callable(stim_input) and not isgeneratorfunction(stim_input): diff --git a/tests/composition/test_autodiffcomposition.py b/tests/composition/test_autodiffcomposition.py index 6cf6f56b28f..a6bbdd6ca26 100644 --- a/tests/composition/test_autodiffcomposition.py +++ b/tests/composition/test_autodiffcomposition.py @@ -14,7 +14,7 @@ from psyneulink.core.globals.keywords import TRAINING_SET, Loss from psyneulink.core.components.mechanisms.processing.transfermechanism import TransferMechanism from psyneulink.core.components.projections.pathway.mappingprojection import MappingProjection -from psyneulink.library.compositions.autodiffcomposition import AutodiffComposition +from psyneulink.library.compositions.autodiffcomposition import AutodiffComposition, AutodiffCompositionError from psyneulink.core.compositions.report import ReportOutput logger = logging.getLogger(__name__) @@ -1770,22 +1770,20 @@ def test_params_stay_separate(self, autodiff_mode): assert not np.allclose(pt_weights_hid, hid_map.parameters.matrix.get(None)) assert not np.allclose(pt_weights_out, out_map.parameters.matrix.get(None)) - def test_execution_mode_python_warning(self): + def test_execution_mode_python_error(self): A = TransferMechanism(name="learning-process-mech-A") B = TransferMechanism(name="learning-process-mech-B") adc = AutodiffComposition(name='AUTODIFFCOMP') pway = adc.add_backpropagation_learning_pathway(pathway=[A,B]) # Call learn with default_variable specified for target (for comparison with missing target) - with pytest.warns(UserWarning) as warning: + with pytest.raises(AutodiffCompositionError) as error: adc.learn(inputs={A: 1.0, pway.target: 0.0}, execution_mode=pnl.ExecutionMode.Python, num_trials=2) - assert repr(warning[1].message.args[0]) == '\'AUTODIFFCOMP.learn() called with ExecutionMode.Python; ' \ - 'learning will be executed using PyTorch; should use ' \ - 'ExecutionMode.PyTorch for clarity, or a standard Composition ' \ - 'for Python execution.)\'' - + assert error.value.error_value == 'AUTODIFFCOMP is an AutodiffComposition so its learn() ' \ + 'cannot be called with execution_mode = ExecutionMode.Python; ' \ + 'use ExecutionMode.PyTorch or ExecutionMode.LLVMRun.' @pytest.mark.pytorch @pytest.mark.actime