From 714c38ed2e4ee334327a61908f3b0befd60e8b4d Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 19 Jul 2020 12:51:52 -0400 Subject: [PATCH 01/25] llvm, Mechanism: Consolidate execution of mechanism functions in one method Signed-off-by: Jan Vesely --- .../core/components/mechanisms/mechanism.py | 36 +++++++++++++++---- .../processing/transfermechanism.py | 24 ++----------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/psyneulink/core/components/mechanisms/mechanism.py b/psyneulink/core/components/mechanisms/mechanism.py index d14c7561efb..5ee62f6a6f5 100644 --- a/psyneulink/core/components/mechanisms/mechanism.py +++ b/psyneulink/core/components/mechanisms/mechanism.py @@ -1105,7 +1105,7 @@ class `UserList Date: Sun, 19 Jul 2020 13:10:54 -0400 Subject: [PATCH 02/25] llvm, Mechanism: Drop _gen_llvm_function_input_parse Unused Signed-off-by: Jan Vesely --- psyneulink/core/components/mechanisms/mechanism.py | 6 +----- .../modulatory/control/optimizationcontrolmechanism.py | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/psyneulink/core/components/mechanisms/mechanism.py b/psyneulink/core/components/mechanisms/mechanism.py index 5ee62f6a6f5..4884c610869 100644 --- a/psyneulink/core/components/mechanisms/mechanism.py +++ b/psyneulink/core/components/mechanisms/mechanism.py @@ -2947,10 +2947,9 @@ def _fill_input(b, s_input, i): def _gen_llvm_invoke_function(self, ctx, builder, function, params, state, variable, *, tags:frozenset): fun = ctx.import_llvm_function(function, tags=tags) - fun_in, builder = self._gen_llvm_function_input_parse(builder, ctx, fun, variable) fun_out = builder.alloca(fun.args[3].type.pointee) - builder.call(fun, [params, state, fun_in, fun_out]) + builder.call(fun, [params, state, variable, fun_out]) return fun_out, builder @@ -3002,9 +3001,6 @@ def _gen_llvm_function_internal(self, ctx, builder, params, state, arg_in, state, value) return builder, is_finished_cond - def _gen_llvm_function_input_parse(self, builder, ctx, func, func_in): - return func_in, builder - def _gen_llvm_function_reset(self, ctx, builder, params, state, arg_in, arg_out, *, tags:frozenset): assert "reset" in tags reinit_func = ctx.import_llvm_function(self.function, tags=tags) diff --git a/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py b/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py index 133dce6786e..ffbc12735b4 100644 --- a/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py +++ b/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py @@ -1230,10 +1230,9 @@ def _gen_llvm_function(self, *, ctx:pnlvm.LLVMBuilderContext, tags:frozenset): def _gen_llvm_invoke_function(self, ctx, builder, function, params, context, variable, *, tags:frozenset): fun = ctx.import_llvm_function(function) - fun_in, builder = self._gen_llvm_function_input_parse(builder, ctx, fun, variable) fun_out = builder.alloca(fun.args[3].type.pointee) - args = [params, context, fun_in, fun_out] + args = [params, context, variable, fun_out] # If we're calling compiled version of Composition.evaluate, # we need to pass extra arguments if len(fun.args) > 4: From 9efc1712391ca544e883071d9a92d15dbaf5f011 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 19 Jul 2020 14:38:49 -0400 Subject: [PATCH 03/25] llvm/cuda, tests/TransferMechanism: Use cuda_execute in PTX variant of the test Signed-off-by: Jan Vesely --- tests/mechanisms/test_transfer_mechanism.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mechanisms/test_transfer_mechanism.py b/tests/mechanisms/test_transfer_mechanism.py index f228c67cf51..45b94831513 100644 --- a/tests/mechanisms/test_transfer_mechanism.py +++ b/tests/mechanisms/test_transfer_mechanism.py @@ -1566,7 +1566,7 @@ def test_multiple_output_ports_for_multiple_input_ports(self, benchmark, mode): val = benchmark(e.execute, [[1], [2], [3]]) elif mode == 'PTX': e = pnlvm.execution.MechExecution(T) - val = benchmark(e.execute, [[1], [2], [3]]) + val = benchmark(e.cuda_execute, [[1], [2], [3]]) assert len(T.variable)==3 assert all(a==b for a,b in zip(val, [[ 1.],[ 2.],[ 3.]])) From 0561ea1a27965376e3d073ef6f67daa55d0962f2 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 19 Jul 2020 14:42:20 -0400 Subject: [PATCH 04/25] llvm, mechanisms/TransferMechanism: Convert 'clip' to compiled runtime parameter Add tests. Signed-off-by: Jan Vesely --- .../processing/transfermechanism.py | 12 +++-- tests/mechanisms/test_transfer_mechanism.py | 50 ++++++++++++++++--- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/psyneulink/core/components/mechanisms/processing/transfermechanism.py b/psyneulink/core/components/mechanisms/processing/transfermechanism.py index 36968066ff7..4c15ad159a4 100644 --- a/psyneulink/core/components/mechanisms/processing/transfermechanism.py +++ b/psyneulink/core/components/mechanisms/processing/transfermechanism.py @@ -1652,9 +1652,13 @@ def _gen_llvm_mechanism_functions(self, ctx, builder, params, state, arg_in, mf_out, builder = self._gen_llvm_invoke_function(ctx, builder, self.function, mf_params, mf_state, mf_in, tags=tags) - # FIXME: Convert to runtime instead of compile time - clip = self.parameters.clip.get() - if clip is not None: + clip_ptr = pnlvm.helpers.get_param_ptr(builder, self, params, "clip") + if len(clip_ptr.type.pointee) != 0: + assert len(clip_ptr.type.pointee) == 2 + clip_lo = builder.load(builder.gep(clip_ptr, [ctx.int32_ty(0), + ctx.int32_ty(0)])) + clip_hi = builder.load(builder.gep(clip_ptr, [ctx.int32_ty(0), + ctx.int32_ty(1)])) for i in range(mf_out.type.pointee.count): mf_out_local = builder.gep(mf_out, [ctx.int32_ty(0), ctx.int32_ty(i)]) with pnlvm.helpers.array_ptr_loop(builder, mf_out_local, "clip") as (b1, index): @@ -1662,7 +1666,7 @@ def _gen_llvm_mechanism_functions(self, ctx, builder, params, state, arg_in, ptro = b1.gep(mf_out_local, [ctx.int32_ty(0), index]) val = b1.load(ptri) - val = pnlvm.helpers.fclamp(b1, val, clip[0], clip[1]) + val = pnlvm.helpers.fclamp(b1, val, clip_lo, clip_hi) b1.store(val, ptro) return mf_out, builder diff --git a/tests/mechanisms/test_transfer_mechanism.py b/tests/mechanisms/test_transfer_mechanism.py index 45b94831513..90257532c87 100644 --- a/tests/mechanisms/test_transfer_mechanism.py +++ b/tests/mechanisms/test_transfer_mechanism.py @@ -2009,20 +2009,56 @@ def test_termination_measures(self, bin_execute): class TestClip: - def test_clip_float(self): + @pytest.mark.mechanism + @pytest.mark.transfer_mechanism + @pytest.mark.parametrize('mode', ['Python', + pytest.param('LLVM', marks=pytest.mark.llvm), + pytest.param('PTX', marks=[pytest.mark.llvm, pytest.mark.cuda])]) + def test_clip_float(self, mode): T = TransferMechanism(clip=[-2.0, 2.0]) - assert np.allclose(T.execute(3.0), 2.0) - assert np.allclose(T.execute(-3.0), -2.0) + if mode == 'Python': + EX = T.execute + elif mode == 'LLVM': + EX = pnlvm.execution.MechExecution(T).execute + elif mode == 'PTX': + EX = pnlvm.execution.MechExecution(T).cuda_execute + + assert np.allclose(EX(3.0), 2.0) + assert np.allclose(EX(1.0), 1.0) + assert np.allclose(EX(-3.0), -2.0) - def test_clip_array(self): + @pytest.mark.mechanism + @pytest.mark.transfer_mechanism + @pytest.mark.parametrize('mode', ['Python', + pytest.param('LLVM', marks=pytest.mark.llvm), + pytest.param('PTX', marks=[pytest.mark.llvm, pytest.mark.cuda])]) + def test_clip_array(self, mode): T = TransferMechanism(default_variable=[[0.0, 0.0, 0.0]], clip=[-2.0, 2.0]) - assert np.allclose(T.execute([3.0, 0.0, -3.0]), [2.0, 0.0, -2.0]) + if mode == 'Python': + EX = T.execute + elif mode == 'LLVM': + EX = pnlvm.execution.MechExecution(T).execute + elif mode == 'PTX': + EX = pnlvm.execution.MechExecution(T).cuda_execute + assert np.allclose(EX([3.0, 0.0, -3.0]), [2.0, 0.0, -2.0]) - def test_clip_2d_array(self): + @pytest.mark.mechanism + @pytest.mark.transfer_mechanism + @pytest.mark.parametrize('mode', ['Python', + pytest.param('LLVM', marks=pytest.mark.llvm), + pytest.param('PTX', marks=[pytest.mark.llvm, pytest.mark.cuda])]) + def test_clip_2d_array(self, mode): T = TransferMechanism(default_variable=[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], clip=[-2.0, 2.0]) - assert np.allclose(T.execute([[-5.0, -1.0, 5.0], [5.0, -5.0, 1.0], [1.0, 5.0, 5.0]]), + if mode == 'Python': + EX = T.execute + elif mode == 'LLVM': + EX = pnlvm.execution.MechExecution(T).execute + elif mode == 'PTX': + EX = pnlvm.execution.MechExecution(T).cuda_execute + + assert np.allclose(EX([[-5.0, -1.0, 5.0], [5.0, -5.0, 1.0], [1.0, 5.0, 5.0]]), [[-2.0, -1.0, 2.0], [2.0, -2.0, 1.0], [1.0, 2.0, 2.0]]) From f52693e7995e24f8d5d978dc3760b8750ec7c2b5 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Tue, 21 Jul 2020 16:00:41 -0400 Subject: [PATCH 05/25] mechanisms/TransferMechanism: Make sure value history is long enough for termination_measure value.history_min_length >= _termination_measure_num_items_expected - 1 Signed-off-by: Jan Vesely --- .../mechanisms/processing/transfermechanism.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/psyneulink/core/components/mechanisms/processing/transfermechanism.py b/psyneulink/core/components/mechanisms/processing/transfermechanism.py index 4c15ad159a4..450bad58e22 100644 --- a/psyneulink/core/components/mechanisms/processing/transfermechanism.py +++ b/psyneulink/core/components/mechanisms/processing/transfermechanism.py @@ -1792,6 +1792,8 @@ def _instantiate_attributes_after_function(self, context=None): assert False, f"PROGRAM ERROR: Unable to determine length of input for" \ f" {repr(TERMINATION_MEASURE)} arg of {self.name}" + self.parameters.value.history_min_length = self._termination_measure_num_items_expected - 1 + def _report_mechanism_execution(self, input, params, output, context=None): """Override super to report previous_input rather than input, and selected params """ @@ -1826,11 +1828,9 @@ def is_finished(self, context=None): # return True return self.parameters.is_finished_flag._get(context) + assert self.parameters.value.history_min_length + 1 >= self._termination_measure_num_items_expected, "History of 'value' is not guaranteed enough entries for termination_mesasure" measure = self.termination_measure - # comparator = self.parameters.termination_comparison_op._get(context) - comparator = comparison_operators[self.parameters.termination_comparison_op._get(context)] value = self.parameters.value._get(context) - previous_value = self.parameters.value.get_previous(context) if self._termination_measure_num_items_expected==0: status = self.parameters.num_executions._get(context)._get_by_time_scale(self.termination_measure) @@ -1839,10 +1839,13 @@ def is_finished(self, context=None): # Squeeze to collapse 2d array with single item status = measure(np.squeeze(value)) else: + previous_value = self.parameters.value.get_previous(context) status = measure([value, previous_value]) self.parameters.termination_measure_value._set(status, context=context, override=True) + # comparator = self.parameters.termination_comparison_op._get(context) + comparator = comparison_operators[self.parameters.termination_comparison_op._get(context)] # if any(comparison_operators[comparator](np.atleast_1d(status), threshold)): if comparator(np.atleast_1d(status), threshold).any(): logger.info(f'{type(self).__name__} {self.name} has reached threshold ({threshold})') From 03eb884ca2ab0337d50a5aabd7a439ed0fa5a829 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Tue, 21 Jul 2020 13:20:38 -0400 Subject: [PATCH 06/25] llvm, Component: Add support for min_length history in compiled code Signed-off-by: Jan Vesely --- psyneulink/core/components/component.py | 3 ++- psyneulink/core/llvm/helpers.py | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/psyneulink/core/components/component.py b/psyneulink/core/components/component.py index ef4eb719c5b..a51ade62b2c 100644 --- a/psyneulink/core/components/component.py +++ b/psyneulink/core/components/component.py @@ -1269,7 +1269,8 @@ def _state_values(p): val = p.get(context) if isinstance(val, Component): return val._get_state_values(context) - return val + return [val for i in range(p.history_min_length + 1)] + return tuple(map(_state_values, self._get_compilation_state())) def _get_state_initializer(self, context): diff --git a/psyneulink/core/llvm/helpers.py b/psyneulink/core/llvm/helpers.py index d68fc4c1493..b6f3ee88f16 100644 --- a/psyneulink/core/llvm/helpers.py +++ b/psyneulink/core/llvm/helpers.py @@ -82,11 +82,21 @@ def get_param_ptr(builder, component, params_ptr, param_name): name="ptr_param_{}_{}".format(param_name, component.name)) -def get_state_ptr(builder, component, state_ptr, stateful_name): +def get_state_ptr(builder, component, state_ptr, stateful_name, hist_idx=0): idx = ir.IntType(32)(component.llvm_state_ids.index(stateful_name)) - return builder.gep(state_ptr, [ir.IntType(32)(0), idx], - name="ptr_state_{}_{}".format(stateful_name, - component.name)) + ptr = builder.gep(state_ptr, [ir.IntType(32)(0), idx], + name="ptr_state_{}_{}".format(stateful_name, + component.name)) + # The first dimension of arrays is history + if hist_idx is not None and isinstance(ptr.type.pointee, ir.ArrayType): + assert len(ptr.type.pointee) > hist_idx, \ + "History not available: {} ({})".format(ptr.type.pointee, hist_idx) + ptr = builder.gep(state_ptr, [ir.IntType(32)(0), idx, + ir.IntType(32)(hist_idx)], + name="ptr_state_{}_{}_hist{}".format(stateful_name, + component.name, + hist_idx)) + return ptr def unwrap_2d_array(builder, element): From 2a0939d10c415bcd6fc340cfd210b0dea1bec10c Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Tue, 21 Jul 2020 22:42:36 -0400 Subject: [PATCH 07/25] llvm, Mechanism: Update 'value' history before running 'is_finished' Signed-off-by: Jan Vesely --- psyneulink/core/components/mechanisms/mechanism.py | 7 +++++++ .../mechanisms/processing/transfermechanism.py | 11 ++++------- psyneulink/core/llvm/helpers.py | 11 +++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/psyneulink/core/components/mechanisms/mechanism.py b/psyneulink/core/components/mechanisms/mechanism.py index 4884c610869..26325787a8c 100644 --- a/psyneulink/core/components/mechanisms/mechanism.py +++ b/psyneulink/core/components/mechanisms/mechanism.py @@ -2996,6 +2996,13 @@ def _gen_llvm_function_internal(self, ctx, builder, params, state, arg_in, builder = self._gen_llvm_output_ports(ctx, builder, value, params, state, arg_in, arg_out) + val_ptr = pnlvm.helpers.get_state_ptr(builder, self, state, "value") + if val_ptr.type.pointee == value.type.pointee: + pnlvm.helpers.push_state_val(builder, self, state, "value", value) + else: + # FIXME: Does this need some sort of parsing? + warnings.warn("Shape mismatch: function result does not match mechanism value: {}".format(value.type.pointee, val_ptr.type.pointee)) + # is_finished should be checked after output ports ran is_finished_cond = self._gen_llvm_is_finished_cond(ctx, builder, params, state, value) diff --git a/psyneulink/core/components/mechanisms/processing/transfermechanism.py b/psyneulink/core/components/mechanisms/processing/transfermechanism.py index 450bad58e22..8c8ad39baa9 100644 --- a/psyneulink/core/components/mechanisms/processing/transfermechanism.py +++ b/psyneulink/core/components/mechanisms/processing/transfermechanism.py @@ -1555,13 +1555,6 @@ def _clip_result(self, clip, current_input): return current_input def _gen_llvm_is_finished_cond(self, ctx, builder, params, state, current): - prev_val_ptr = pnlvm.helpers.get_state_ptr(builder, self, state, "value") - - # preserve the old prev value - prev_val = builder.load(prev_val_ptr) - # Update previous value to make sure that repeated executions work - builder.store(builder.load(current), prev_val_ptr) - threshold_ptr = pnlvm.helpers.get_param_ptr(builder, self, params, "termination_threshold") if isinstance(threshold_ptr.type.pointee, pnlvm.ir.LiteralStructType): @@ -1593,7 +1586,11 @@ def _gen_llvm_is_finished_cond(self, ctx, builder, params, state, current): cond = b.fcmp_ordered(">=", test_val, max_val) max_val = b.select(cond, test_val, max_val) b.store(max_val, cmp_val_ptr) + elif isinstance(self.termination_measure, Function): + prev_val_ptr = pnlvm.helpers.get_state_ptr(builder, self, state, "value", 1) + prev_val = builder.load(prev_val_ptr) + expected = np.empty_like([self.defaults.value[0], self.defaults.value[0]]) got = np.empty_like(self.termination_measure.defaults.variable) if expected.shape != got.shape: diff --git a/psyneulink/core/llvm/helpers.py b/psyneulink/core/llvm/helpers.py index b6f3ee88f16..35d2d7898a3 100644 --- a/psyneulink/core/llvm/helpers.py +++ b/psyneulink/core/llvm/helpers.py @@ -99,6 +99,17 @@ def get_state_ptr(builder, component, state_ptr, stateful_name, hist_idx=0): return ptr +def push_state_val(builder, component, state_ptr, name, new_val): + val_ptr = get_state_ptr(builder, component, state_ptr, name, None) + for i in range(len(val_ptr.type.pointee) - 1, 0, -1): + dest_ptr = get_state_ptr(builder, component, state_ptr, name, i) + src_ptr = get_state_ptr(builder, component, state_ptr, name, i - 1) + builder.store(builder.load(src_ptr), dest_ptr) + + dest_ptr = get_state_ptr(builder, component, state_ptr, name) + builder.store(builder.load(new_val), dest_ptr) + + def unwrap_2d_array(builder, element): if isinstance(element.type.pointee, ir.ArrayType) and isinstance(element.type.pointee.element, ir.ArrayType): assert element.type.pointee.count == 1 From 405fb1f0bf5af810f8ed2e8d87fe3a9c7f071e0c Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Tue, 21 Jul 2020 23:08:55 -0400 Subject: [PATCH 08/25] llvm, Mechanism: Use only current params and state to determine 'is_finished' Signed-off-by: Jan Vesely --- psyneulink/core/components/mechanisms/mechanism.py | 4 ++-- .../components/mechanisms/processing/transfermechanism.py | 3 ++- .../components/mechanisms/processing/integrator/ddm.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/psyneulink/core/components/mechanisms/mechanism.py b/psyneulink/core/components/mechanisms/mechanism.py index 26325787a8c..4a0e103e53d 100644 --- a/psyneulink/core/components/mechanisms/mechanism.py +++ b/psyneulink/core/components/mechanisms/mechanism.py @@ -2953,7 +2953,7 @@ def _gen_llvm_invoke_function(self, ctx, builder, function, params, state, varia return fun_out, builder - def _gen_llvm_is_finished_cond(self, ctx, builder, params, state, current): + def _gen_llvm_is_finished_cond(self, ctx, builder, params, state): return pnlvm.ir.IntType(1)(1) def _gen_llvm_mechanism_functions(self, ctx, builder, params, state, arg_in, @@ -3005,7 +3005,7 @@ def _gen_llvm_function_internal(self, ctx, builder, params, state, arg_in, # is_finished should be checked after output ports ran is_finished_cond = self._gen_llvm_is_finished_cond(ctx, builder, params, - state, value) + state) return builder, is_finished_cond def _gen_llvm_function_reset(self, ctx, builder, params, state, arg_in, arg_out, *, tags:frozenset): diff --git a/psyneulink/core/components/mechanisms/processing/transfermechanism.py b/psyneulink/core/components/mechanisms/processing/transfermechanism.py index 8c8ad39baa9..e8477433c4c 100644 --- a/psyneulink/core/components/mechanisms/processing/transfermechanism.py +++ b/psyneulink/core/components/mechanisms/processing/transfermechanism.py @@ -1554,7 +1554,8 @@ def _clip_result(self, clip, current_input): current_input[maxCapIndices] = np.max(clip) return current_input - def _gen_llvm_is_finished_cond(self, ctx, builder, params, state, current): + def _gen_llvm_is_finished_cond(self, ctx, builder, params, state): + current = pnlvm.helpers.get_state_ptr(builder, self, state, "value") threshold_ptr = pnlvm.helpers.get_param_ptr(builder, self, params, "termination_threshold") if isinstance(threshold_ptr.type.pointee, pnlvm.ir.LiteralStructType): diff --git a/psyneulink/library/components/mechanisms/processing/integrator/ddm.py b/psyneulink/library/components/mechanisms/processing/integrator/ddm.py index 5513efb05b5..9c14ab9f8db 100644 --- a/psyneulink/library/components/mechanisms/processing/integrator/ddm.py +++ b/psyneulink/library/components/mechanisms/processing/integrator/ddm.py @@ -1198,7 +1198,7 @@ def is_finished(self, context=None): return True return False - def _gen_llvm_is_finished_cond(self, ctx, builder, params, state, current): + def _gen_llvm_is_finished_cond(self, ctx, builder, params, state): # Setup pointers to internal function func_state_ptr = pnlvm.helpers.get_state_ptr(builder, self, state, 'function') func_param_ptr = pnlvm.helpers.get_state_ptr(builder, self, params, 'function') From 9ccc838ee94dbbbb79e21d0dc5781b318cc57f05 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 00:04:27 -0400 Subject: [PATCH 09/25] llvm, Mechanism: COnvert 'is_finished' to a function flavour instead of generating the code directly Signed-off-by: Jan Vesely --- .../core/components/mechanisms/mechanism.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/psyneulink/core/components/mechanisms/mechanism.py b/psyneulink/core/components/mechanisms/mechanism.py index 4a0e103e53d..afc082badc3 100644 --- a/psyneulink/core/components/mechanisms/mechanism.py +++ b/psyneulink/core/components/mechanisms/mechanism.py @@ -3004,8 +3004,9 @@ def _gen_llvm_function_internal(self, ctx, builder, params, state, arg_in, warnings.warn("Shape mismatch: function result does not match mechanism value: {}".format(value.type.pointee, val_ptr.type.pointee)) # is_finished should be checked after output ports ran - is_finished_cond = self._gen_llvm_is_finished_cond(ctx, builder, params, - state) + is_finished_f = ctx.import_llvm_function(self, tags=tags.union({"is_finished"})) + is_finished_cond = builder.call(is_finished_f, [params, state, arg_in, + arg_out]) return builder, is_finished_cond def _gen_llvm_function_reset(self, ctx, builder, params, state, arg_in, arg_out, *, tags:frozenset): @@ -3020,6 +3021,25 @@ def _gen_llvm_function_reset(self, ctx, builder, params, state, arg_in, arg_out, return builder + def _gen_llvm_function(self, *, extra_args=[], ctx:pnlvm.LLVMBuilderContext, tags:frozenset): + if "is_finished" not in tags: + return super()._gen_llvm_function(extra_args=extra_args, ctx=ctx, + tags=tags) + + # Keep all 4 standard arguments to ease invocation + args = [ctx.get_param_struct_type(self).as_pointer(), + ctx.get_state_struct_type(self).as_pointer(), + ctx.get_input_struct_type(self).as_pointer(), + ctx.get_output_struct_type(self).as_pointer()] + + builder = ctx.create_llvm_function(args, self, + return_type=pnlvm.ir.IntType(1), + tags=tags) + params, state = builder.function.args[:2] + finished = self._gen_llvm_is_finished_cond(ctx, builder, params, state) + builder.ret(finished) + return builder.function + def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out, *, tags:frozenset): assert "reset" not in tags From 1e4d237289afd16cd8cb8787b0efbbdee78e2d6e Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 00:28:36 -0400 Subject: [PATCH 10/25] llvm/codegen: Use wrapped function tags and return type when generating compostion wrappers Signed-off-by: Jan Vesely --- psyneulink/core/llvm/codegen.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/psyneulink/core/llvm/codegen.py b/psyneulink/core/llvm/codegen.py index 9dd47f9fd13..bf4ff2172c1 100644 --- a/psyneulink/core/llvm/codegen.py +++ b/psyneulink/core/llvm/codegen.py @@ -44,7 +44,8 @@ def gen_node_wrapper(ctx, composition, node, *, tags:frozenset): cond_ty = cond_gen.get_condition_struct_type().as_pointer() args.append(cond_ty) - builder = ctx.create_llvm_function(args, node, "comp_wrap_" + node_function.name) + builder = ctx.create_llvm_function(args, node, node_function.name, tags=tags, + return_type=node_function.type.pointee.return_type) llvm_func = builder.function for a in llvm_func.args: a.attributes.add('nonnull') @@ -161,21 +162,24 @@ def gen_node_wrapper(ctx, composition, node, *, tags:frozenset): if len(node_function.args) > 4: assert node is composition.controller call_args += [params, state, data_in] - builder.call(node_function, call_args) + ret = builder.call(node_function, call_args) elif "reset" not in tags: # FIXME: reinitialization of compositions is not supported # Condition and data structures includes parent first nested_idx = ctx.int32_ty(composition._get_node_index(node) + 1) node_data = builder.gep(data_in, [zero, nested_idx]) node_cond = builder.gep(llvm_func.args[5], [zero, nested_idx]) - builder.call(node_function, [node_state, node_params, node_in, - node_data, node_cond]) + ret = builder.call(node_function, [node_state, node_params, node_in, + node_data, node_cond]) # Copy output of the nested composition to its output place output_idx = node._get_node_index(node.output_CIM) result = builder.gep(node_data, [zero, zero, ctx.int32_ty(output_idx)]) builder.store(builder.load(result), node_out) - builder.ret_void() + if isinstance(ret.type, ir.VoidType): + builder.ret_void() + else: + builder.ret(ret) return llvm_func From 31da5da93293e1d0f817a22f82c2508b61c9985c Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 03:12:05 -0400 Subject: [PATCH 11/25] llvm/codegen: Use callbacks rather than just the flag when generating code for WhenFinished Signed-off-by: Jan Vesely --- psyneulink/core/llvm/codegen.py | 24 ++++++++++++------------ psyneulink/core/llvm/helpers.py | 28 ++++++++++++---------------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/psyneulink/core/llvm/codegen.py b/psyneulink/core/llvm/codegen.py index bf4ff2172c1..759fab4dd38 100644 --- a/psyneulink/core/llvm/codegen.py +++ b/psyneulink/core/llvm/codegen.py @@ -76,7 +76,7 @@ def gen_node_wrapper(ctx, composition, node, *, tags:frozenset): node_in = builder.alloca(node_function.args[2].type.pointee) incoming_projections = node.afferents - if "reset" in tags: + if "reset" in tags or "is_finished" in tags: incoming_projections = [] # Execute all incoming projections @@ -175,8 +175,11 @@ def gen_node_wrapper(ctx, composition, node, *, tags:frozenset): output_idx = node._get_node_index(node.output_CIM) result = builder.gep(node_data, [zero, zero, ctx.int32_ty(output_idx)]) builder.store(builder.load(result), node_out) + else: + # composition reset + ret = None - if isinstance(ret.type, ir.VoidType): + if ret is None or isinstance(ret.type, ir.VoidType): builder.ret_void() else: builder.ret(ret) @@ -292,14 +295,11 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): builder.store(ctx.int32_ty(0), iter_ptr) # Generate pointers to 'is_finished_flags' locations - is_finished_flag_locs = {} - for idx, node in enumerate(composition.nodes): - node_state = builder.gep(state, [ctx.int32_ty(0), ctx.int32_ty(0), - ctx.int32_ty(idx)]) - is_finished_flag_ptr = helpers.get_state_ptr(builder, node, - node_state, - "is_finished_flag") - is_finished_flag_locs[node] = is_finished_flag_ptr + is_finished_callbacks = {} + for node in composition.nodes: + args = [state, params, comp_in, data, output_storage] + wrapper = ctx.get_node_wrapper(composition, node) + is_finished_callbacks[node] = (wrapper, args) loop_condition = builder.append_basic_block(name="scheduling_loop_condition") builder.branch(loop_condition) @@ -309,7 +309,7 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): run_cond = cond_gen.generate_sched_condition( builder, composition.termination_processing[TimeScale.TRIAL], - cond, None, is_finished_flag_locs) + cond, None, is_finished_callbacks) run_cond = builder.not_(run_cond, name="not_run_cond") loop_body = builder.append_basic_block(name="scheduling_loop_body") @@ -328,7 +328,7 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): name="run_cond_ptr_" + node.name) node_cond = cond_gen.generate_sched_condition( builder, composition._get_processing_condition_set(node), - cond, node, is_finished_flag_locs) + cond, node, is_finished_callbacks) ran = cond_gen.generate_ran_this_pass(builder, cond, node) node_cond = builder.and_(node_cond, builder.not_(ran), name="run_cond_" + node.name) diff --git a/psyneulink/core/llvm/helpers.py b/psyneulink/core/llvm/helpers.py index 35d2d7898a3..b561154a28f 100644 --- a/psyneulink/core/llvm/helpers.py +++ b/psyneulink/core/llvm/helpers.py @@ -373,7 +373,7 @@ def generate_ran_this_trial(self, builder, cond_ptr, node): return builder.icmp_signed("==", node_run, global_run) - def generate_sched_condition(self, builder, condition, cond_ptr, node, is_finished_flag_locs): + def generate_sched_condition(self, builder, condition, cond_ptr, node, is_finished_callbacks): from psyneulink.core.scheduling.condition import All, AllHaveRun, Always, AtPass, AtTrial, EveryNCalls, BeforeNCalls, AtNCalls, AfterNCalls, Never, Not, WhenFinished, WhenFinishedAny, WhenFinishedAll @@ -385,12 +385,12 @@ def generate_sched_condition(self, builder, condition, cond_ptr, node, is_finish elif isinstance(condition, Not): condition = condition.condition - return builder.not_(self.generate_sched_condition(builder, condition, cond_ptr, node, is_finished_flag_locs)) + return builder.not_(self.generate_sched_condition(builder, condition, cond_ptr, node, is_finished_callbacks)) elif isinstance(condition, All): agg_cond = ir.IntType(1)(1) for cond in condition.args: - cond_res = self.generate_sched_condition(builder, cond, cond_ptr, node, is_finished_flag_locs) + cond_res = self.generate_sched_condition(builder, cond, cond_ptr, node, is_finished_callbacks) agg_cond = builder.and_(agg_cond, cond_res) return agg_cond @@ -508,21 +508,18 @@ def generate_sched_condition(self, builder, condition, cond_ptr, node, is_finish elif isinstance(condition, WhenFinished): # The first argument is the target node assert len(condition.args) == 1 - target_is_finished_ptr = is_finished_flag_locs[condition.args[0]] - target_is_finished = builder.load(target_is_finished_ptr) - - return builder.fcmp_ordered("==", target_is_finished, - target_is_finished.type(1)) + target = is_finished_callbacks[condition.args[0]] + is_finished_f = self.ctx.import_llvm_function(target[0], tags=frozenset({"is_finished", "node_wrapper"})) + return builder.call(is_finished_f, target[1]) elif isinstance(condition, WhenFinishedAny): assert len(condition.args) > 0 run_cond = ir.IntType(1)(0) for node in condition.args: - node_is_finished_ptr = is_finished_flag_locs[node] - node_is_finished = builder.load(node_is_finished_ptr) - node_is_finished = builder.fcmp_ordered("==", node_is_finished, - node_is_finished.type(1)) + target = is_finished_callbacks[node] + is_finished_f = self.ctx.import_llvm_function(target[0], tags=frozenset({"is_finished", "node_wrapper"})) + node_is_finished = builder.call(is_finished_f, target[1]) run_cond = builder.or_(run_cond, node_is_finished) @@ -533,10 +530,9 @@ def generate_sched_condition(self, builder, condition, cond_ptr, node, is_finish run_cond = ir.IntType(1)(1) for node in condition.args: - node_is_finished_ptr = is_finished_flag_locs[node] - node_is_finished = builder.load(node_is_finished_ptr) - node_is_finished = builder.fcmp_ordered("==", node_is_finished, - node_is_finished.type(1)) + target = is_finished_callbacks[node] + is_finished_f = self.ctx.import_llvm_function(target[0], tags=frozenset({"is_finished", "node_wrapper"})) + node_is_finished = builder.call(is_finished_f, target[1]) run_cond = builder.and_(run_cond, node_is_finished) From 5a2c4bdcf38e7f022f8009eaf3e1b96ae0e99ba9 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 03:18:00 -0400 Subject: [PATCH 12/25] llvm, tests: Add compiled variants of InputPort modulation tests Signed-off-by: Jan Vesely --- tests/mechanisms/test_control_mechanism.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/mechanisms/test_control_mechanism.py b/tests/mechanisms/test_control_mechanism.py index fdf99160c36..2dc24bbdeea 100644 --- a/tests/mechanisms/test_control_mechanism.py +++ b/tests/mechanisms/test_control_mechanism.py @@ -206,12 +206,19 @@ def test_identicalness_of_control_and_gating(self): expected_results = [[0.96941429, 0.9837254 , 0.99217549]] assert np.allclose(results, expected_results) - def test_control_of_all_input_ports(self): + @pytest.mark.parametrize('mode', ['Python', + pytest.param('LLVM', marks=pytest.mark.llvm), + pytest.param('LLVMExec', marks=pytest.mark.llvm), + pytest.param('LLVMRun', marks=pytest.mark.llvm), + pytest.param('PTX', marks=[pytest.mark.llvm, pytest.mark.cuda]), + pytest.param('PTXExec', marks=[pytest.mark.llvm, pytest.mark.cuda]), + pytest.param('PTXRun', marks=[pytest.mark.llvm, pytest.mark.cuda])]) + def test_control_of_all_input_ports(self, mode): mech = pnl.ProcessingMechanism(input_ports=['A','B','C']) control_mech = pnl.ControlMechanism(control=mech.input_ports) comp = pnl.Composition() comp.add_nodes([(mech, pnl.NodeRole.INPUT), (control_mech, pnl.NodeRole.INPUT)]) - results = comp.run(inputs={mech:[[2],[2],[2]], control_mech:[2]}, num_trials=2) + results = comp.run(inputs={mech:[[2],[2],[2]], control_mech:[2]}, num_trials=2, bin_execute=mode) np.allclose(results, [[4],[4],[4]]) @pytest.mark.parametrize('mode', ['Python', From dcb71469f29aaf7575b4c2ea3e16c2f710e195dd Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 12:04:03 -0400 Subject: [PATCH 13/25] tests: Add test for time based termination measure Signed-off-by: Jan Vesely --- tests/scheduling/test_scheduler.py | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/scheduling/test_scheduler.py b/tests/scheduling/test_scheduler.py index 1ebbc4cf418..d43e6fefd42 100644 --- a/tests/scheduling/test_scheduler.py +++ b/tests/scheduling/test_scheduler.py @@ -1470,3 +1470,47 @@ def test_inline_control_mechanism_example(self): csiController: set([cueInterval]) } assert comp.scheduler.dependency_dict == expected_dependencies + + @pytest.mark.mechanism + @pytest.mark.transfer_mechanism + @pytest.mark.parametrize('mode', ['Python', + # 'LLVM' mode is not supported + # the comparison values and checks + # are not synced between binary + # and Python structures + pytest.param('LLVMExec', marks=pytest.mark.llvm), + pytest.param('LLVMRun', marks=pytest.mark.llvm), + pytest.param('PTXExec', marks=[pytest.mark.llvm, pytest.mark.cuda]), + pytest.param('PTXRun', marks=[pytest.mark.llvm, pytest.mark.cuda]), + ]) + @pytest.mark.parametrize('timescale, expected', + [(TimeScale.TIME_STEP, [[0.5], [0.4375]]), + (TimeScale.PASS, [[0.5], [0.4375]]), + (TimeScale.TRIAL, [[1.5], [0.4375]]), + (TimeScale.RUN, [[1.5], [0.4375]])], + ids=lambda x: x if isinstance(x, TimeScale) else "") + def test_time_termination_measures(self, mode, timescale, expected): + in_one_pass = timescale in {TimeScale.TIME_STEP, TimeScale.PASS} + attention = pnl.TransferMechanism(name='Attention', + integrator_mode=True, + termination_threshold=3, + termination_measure=timescale, + execute_until_finished=in_one_pass) + counter = pnl.IntegratorMechanism( + function=pnl.AdaptiveIntegrator(rate=0.0, offset=1.0)) + + response = pnl.IntegratorMechanism( + function=pnl.AdaptiveIntegrator(rate=0.5)) + + comp = Composition() + comp.add_linear_processing_pathway([counter, response]) + comp.add_node(attention) + comp.scheduler.add_condition(response, pnl.WhenFinished(attention)) + comp.scheduler.add_condition(counter, pnl.Always()) + inputs = {attention: [[0.5]], counter: [[2.0]]} + result = comp.run(inputs=inputs, bin_execute=mode) + if mode == 'Python': + assert attention.execution_count == 3 + assert counter.execution_count == 1 if in_one_pass else 3 + assert response.execution_count == 1 + assert np.allclose(result, expected) From a3e0d6fc6c7a0477263420b1dd5897d9e43a3ddd Mon Sep 17 00:00:00 2001 From: Katherine Mantel Date: Fri, 17 Jul 2020 23:11:31 -0400 Subject: [PATCH 14/25] Parameter: fix error when attempting to clear log when None after copying, if logging is disabled the log may be None, and this previously caused an error when calling Parameter.clear_log() --- psyneulink/core/globals/parameters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/psyneulink/core/globals/parameters.py b/psyneulink/core/globals/parameters.py index a16e7b2fbfa..21df6928ac9 100644 --- a/psyneulink/core/globals/parameters.py +++ b/psyneulink/core/globals/parameters.py @@ -1274,6 +1274,9 @@ def clear_log(self, contexts=NotImplemented): """ Clears the log of this Parameter for every context in **contexts** """ + if self.log is None: + return + if contexts is NotImplemented: self.log.clear() return From 41e50d6704be4086a8d5586799b4392ab6f2ed2b Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 18:58:36 -0400 Subject: [PATCH 15/25] llvm/codegen: Codestyle Signed-off-by: Jan Vesely --- psyneulink/core/llvm/codegen.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/psyneulink/core/llvm/codegen.py b/psyneulink/core/llvm/codegen.py index 759fab4dd38..1f23bad1262 100644 --- a/psyneulink/core/llvm/codegen.py +++ b/psyneulink/core/llvm/codegen.py @@ -242,9 +242,8 @@ def _gen_composition_exec_context(ctx, composition, *, tags:frozenset, suffix="" def gen_composition_exec(ctx, composition, *, tags:frozenset): simulation = "simulation" in tags node_tags = tags.union({"node_wrapper"}) - extra_args = [] - with _gen_composition_exec_context(ctx, composition, tags=tags, extra_args=extra_args) as (builder, data, params, cond_gen): + with _gen_composition_exec_context(ctx, composition, tags=tags) as (builder, data, params, cond_gen): state, _, comp_in, _, cond = builder.function.args # Reset internal clocks of each node @@ -253,10 +252,13 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): from psyneulink import Composition if isinstance(node, Composition): continue - node_state = builder.gep(state, [ctx.int32_ty(0), ctx.int32_ty(0), ctx.int32_ty(idx)]) - num_executions_ptr = helpers.get_state_ptr(builder, node, node_state, "num_executions") + node_state = builder.gep(state, [ctx.int32_ty(0), ctx.int32_ty(0), + ctx.int32_ty(idx)]) + num_executions_ptr = helpers.get_state_ptr(builder, node, + node_state, + "num_executions") num_exec_time_ptr = builder.gep(num_executions_ptr, [ctx.int32_ty(0), ctx.int32_ty(TimeScale.TRIAL.value)]) - builder.store(ctx.int32_ty(0), num_exec_time_ptr) + builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) # Check if there's anything to reset for node in composition._all_nodes: @@ -294,7 +296,7 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): iter_ptr = builder.alloca(ctx.int32_ty, name="iter_counter") builder.store(ctx.int32_ty(0), iter_ptr) - # Generate pointers to 'is_finished_flags' locations + # Generate pointers to 'is_finished' callbacks is_finished_callbacks = {} for node in composition.nodes: args = [state, params, comp_in, data, output_storage] @@ -343,13 +345,14 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): node_state = builder.gep(state, [ctx.int32_ty(0), ctx.int32_ty(0), ctx.int32_ty(idx)]) num_executions_ptr = helpers.get_state_ptr(builder, node, node_state, "num_executions") num_exec_time_ptr = builder.gep(num_executions_ptr, [ctx.int32_ty(0), ctx.int32_ty(TimeScale.TIME_STEP.value)]) - builder.store(ctx.int32_ty(0), num_exec_time_ptr) + builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) # FIXME: Move pass reset to actual pass count num_exec_time_ptr = builder.gep(num_executions_ptr, [ctx.int32_ty(0), ctx.int32_ty(TimeScale.PASS.value)]) - builder.store(ctx.int32_ty(0), num_exec_time_ptr) + builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) run_set_node_ptr = builder.gep(run_set_ptr, [zero, ctx.int32_ty(idx)]) - node_cond = builder.load(run_set_node_ptr, name="node_" + node.name + "_should_run") + node_cond = builder.load(run_set_node_ptr, + name="node_" + node.name + "_should_run") with builder.if_then(node_cond): node_w = ctx.get_node_wrapper(composition, node) node_f = ctx.import_llvm_function(node_w, tags=node_tags) @@ -369,7 +372,9 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): run_set_node_ptr = builder.gep(run_set_ptr, [zero, ctx.int32_ty(idx)]) node_cond = builder.load(run_set_node_ptr, name="node_" + node.name + "_ran") with builder.if_then(node_cond): - out_ptr = builder.gep(output_storage, [zero, zero, ctx.int32_ty(idx)], name="result_ptr_" + node.name) + out_ptr = builder.gep(output_storage, [zero, zero, + ctx.int32_ty(idx)], + name="result_ptr_" + node.name) data_ptr = builder.gep(data, [zero, zero, ctx.int32_ty(idx)], name="data_result_" + node.name) builder.store(builder.load(out_ptr), data_ptr) From c5e96d4ab14b6ab7e81c9703517bd4190de125df Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 19:49:34 -0400 Subject: [PATCH 16/25] llvm/codegen: Generate num_executions pointers once and outside of the main loop Signed-off-by: Jan Vesely --- psyneulink/core/llvm/codegen.py | 43 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/psyneulink/core/llvm/codegen.py b/psyneulink/core/llvm/codegen.py index 1f23bad1262..f176be74e7d 100644 --- a/psyneulink/core/llvm/codegen.py +++ b/psyneulink/core/llvm/codegen.py @@ -246,18 +246,24 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): with _gen_composition_exec_context(ctx, composition, tags=tags) as (builder, data, params, cond_gen): state, _, comp_in, _, cond = builder.function.args - # Reset internal clocks of each node + nodes_states = helpers.get_param_ptr(builder, composition, state, "nodes") + + num_exec_locs = {} for idx, node in enumerate(composition._all_nodes): - #FIXME: This skips nested nodes + #FIXME: This skips nested compositions from psyneulink import Composition if isinstance(node, Composition): continue - node_state = builder.gep(state, [ctx.int32_ty(0), ctx.int32_ty(0), - ctx.int32_ty(idx)]) - num_executions_ptr = helpers.get_state_ptr(builder, node, - node_state, - "num_executions") - num_exec_time_ptr = builder.gep(num_executions_ptr, [ctx.int32_ty(0), ctx.int32_ty(TimeScale.TRIAL.value)]) + node_state = builder.gep(nodes_states, [ctx.int32_ty(0), + ctx.int32_ty(idx)]) + num_exec_locs[node] = helpers.get_state_ptr(builder, node, + node_state, + "num_executions") + + # Reset internal TRIAL clock for each node + for time_loc in num_exec_locs.values(): + num_exec_time_ptr = builder.gep(time_loc, [ctx.int32_ty(0), + ctx.int32_ty(TimeScale.TRIAL.value)]) builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) # Check if there's anything to reset @@ -337,18 +343,17 @@ def gen_composition_exec(ctx, composition, *, tags:frozenset): any_cond = builder.or_(any_cond, node_cond, name="any_ran_cond") builder.store(node_cond, run_set_node_ptr) + # Reset internal TIME_STEP and PASS clock for each node + for time_loc in num_exec_locs.values(): + num_exec_time_ptr = builder.gep(time_loc, [ctx.int32_ty(0), + ctx.int32_ty(TimeScale.TIME_STEP.value)]) + builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) + # FIXME: Move pass reset to actual pass count + num_exec_time_ptr = builder.gep(time_loc, [ctx.int32_ty(0), + ctx.int32_ty(TimeScale.PASS.value)]) + builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) + for idx, node in enumerate(composition.nodes): - # Reset node internal clock - #FIXME: This skips nested nodes - from psyneulink import Composition - if not isinstance(node, Composition): - node_state = builder.gep(state, [ctx.int32_ty(0), ctx.int32_ty(0), ctx.int32_ty(idx)]) - num_executions_ptr = helpers.get_state_ptr(builder, node, node_state, "num_executions") - num_exec_time_ptr = builder.gep(num_executions_ptr, [ctx.int32_ty(0), ctx.int32_ty(TimeScale.TIME_STEP.value)]) - builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) - # FIXME: Move pass reset to actual pass count - num_exec_time_ptr = builder.gep(num_executions_ptr, [ctx.int32_ty(0), ctx.int32_ty(TimeScale.PASS.value)]) - builder.store(num_exec_time_ptr.type.pointee(0), num_exec_time_ptr) run_set_node_ptr = builder.gep(run_set_ptr, [zero, ctx.int32_ty(idx)]) node_cond = builder.load(run_set_node_ptr, From acf04429303b1c4981ed7b3965d0891ac9cfd465 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Wed, 22 Jul 2020 21:00:09 -0400 Subject: [PATCH 17/25] llvm/jit: Restore O2 optimization level by default Signed-off-by: Jan Vesely --- psyneulink/core/llvm/jit_engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psyneulink/core/llvm/jit_engine.py b/psyneulink/core/llvm/jit_engine.py index a26d277e461..b4a0d12c3d8 100644 --- a/psyneulink/core/llvm/jit_engine.py +++ b/psyneulink/core/llvm/jit_engine.py @@ -68,7 +68,7 @@ def _cpu_jit_constructor(): __pass_manager_builder = binding.PassManagerBuilder() __pass_manager_builder.loop_vectorize = False __pass_manager_builder.slp_vectorize = False - __pass_manager_builder.opt_level = 0 # No optimizations + __pass_manager_builder.opt_level = 2 __cpu_features = binding.get_host_cpu_features().flatten() __cpu_name = binding.get_host_cpu_name() @@ -77,7 +77,7 @@ def _cpu_jit_constructor(): __cpu_target = binding.Target.from_default_triple() # FIXME: reloc='static' is needed to avoid crashes on win64 # see: https://github.com/numba/llvmlite/issues/457 - __cpu_target_machine = __cpu_target.create_target_machine(cpu=__cpu_name, features=__cpu_features, opt=0, reloc='static') + __cpu_target_machine = __cpu_target.create_target_machine(cpu=__cpu_name, features=__cpu_features, opt=2, reloc='static') __cpu_pass_manager = binding.ModulePassManager() __cpu_target_machine.add_analysis_passes(__cpu_pass_manager) From 3b49dd4501a3074ab7adee234c42482cefd3f533 Mon Sep 17 00:00:00 2001 From: Katherine Mantel Date: Thu, 23 Jul 2020 18:37:01 -0400 Subject: [PATCH 18/25] Component: don't overload temp variable in __init__ Can cause mistaken assignment of default variable/_user_specified due to initial_function_parameters comprehension --- psyneulink/core/components/component.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psyneulink/core/components/component.py b/psyneulink/core/components/component.py index fe6cbd8ae2b..6cbe6bfe906 100644 --- a/psyneulink/core/components/component.py +++ b/psyneulink/core/components/component.py @@ -1095,16 +1095,16 @@ def __init__(self, k: v for k, v in parameter_values.items() if k in self.parameters.names() and getattr(self.parameters, k).function_parameter } - v = call_with_pruned_args( + var = call_with_pruned_args( self._handle_default_variable, default_variable=default_variable, size=size, **parameter_values ) - if v is None: + if var is None: default_variable = self.defaults.variable else: - default_variable = v + default_variable = var self.defaults.variable = default_variable self.parameters.variable._user_specified = True From 56ef1cb5a169aa275531c2c116e8a9bae8c294d7 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sat, 25 Jul 2020 23:26:05 -0400 Subject: [PATCH 19/25] llvm/execution: Force 'additional_tags' argument to be keyword only Fixes: #1695 Signed-off-by: Jan Vesely --- psyneulink/core/llvm/execution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psyneulink/core/llvm/execution.py b/psyneulink/core/llvm/execution.py index d3323800d7f..b411cfba67e 100644 --- a/psyneulink/core/llvm/execution.py +++ b/psyneulink/core/llvm/execution.py @@ -241,7 +241,7 @@ def execute(self, variable): class CompExecution(CUDAExecution): - def __init__(self, composition, execution_ids=[None], additional_tags=frozenset()): + def __init__(self, composition, execution_ids=[None], *, additional_tags=frozenset()): super().__init__(buffers=['state_struct', 'param_struct', 'data_struct', 'conditions']) self._composition = composition self._execution_contexts = [ From 47fc65360a35ec012c5b75ac10645c4702249422 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sat, 25 Jul 2020 23:55:33 -0400 Subject: [PATCH 20/25] llvm, functions/GridSearch: Conslidate checks for optimizing composition Signed-off-by: Jan Vesely --- .../functions/optimizationfunctions.py | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/psyneulink/core/components/functions/optimizationfunctions.py b/psyneulink/core/components/functions/optimizationfunctions.py index 1b2ab53834c..8d650231be4 100644 --- a/psyneulink/core/components/functions/optimizationfunctions.py +++ b/psyneulink/core/components/functions/optimizationfunctions.py @@ -1352,10 +1352,10 @@ def reset_grid(self): def _gen_llvm_function(self, *, ctx:pnlvm.LLVMBuilderContext, tags:frozenset): if "select_min" in tags: return self._gen_llvm_select_min_function(ctx=ctx, tags=tags) - if self._is_composition_optimize(): - # self.objective_function may be bound method of - # an OptimizationControlMechanism - ocm = self.objective_function.__self__ + ocm = self._get_optimized_composition() + if ocm is not None: + # self.objective_function may be a bound method of + # OptimizationControlMechanism extra_args = [ctx.get_param_struct_type(ocm.agent_rep).as_pointer(), ctx.get_state_struct_type(ocm.agent_rep).as_pointer(), ctx.get_data_struct_type(ocm.agent_rep).as_pointer()] @@ -1382,14 +1382,14 @@ def _get_input_struct_type(self, ctx): return ctx.convert_python_struct_to_llvm_ir(variable) - def _is_composition_optimize(self): - # self.objective_function may be bound method of - # an OptimizationControlMechanism - return hasattr(self.objective_function, '__self__') + def _get_optimized_composition(self): + # self.objective_function may be a bound method of + # OptimizationControlMechanism + return getattr(self.objective_function, '__self__', None) def _get_param_ids(self): ids = super()._get_param_ids() + ["search_space"] - if self._is_composition_optimize(): + if self._get_optimized_composition() is not None: ids.append("objective_function") return ids @@ -1407,10 +1407,10 @@ def _get_param_struct_type(self, ctx): search_space = (self._get_search_dim_type(ctx, d) for d in self.search_space) search_space_struct = pnlvm.ir.LiteralStructType(search_space) - if self._is_composition_optimize(): + ocm = self._get_optimized_composition() + if ocm is not None: # self.objective_function is a bound method of - # an OptimizationControlMechanism - ocm = self.objective_function.__self__ + # OptimizationControlMechanism obj_func_params = ocm._get_evaluate_param_struct_type(ctx) return pnlvm.ir.LiteralStructType([*param_struct, search_space_struct, @@ -1430,10 +1430,10 @@ def _get_param_initializer(self, context): param_initializer = super()._get_param_initializer(context) grid_init = (self._get_search_dim_init(context, d) for d in self.search_space) - if self._is_composition_optimize(): + ocm = self._get_optimized_composition() + if ocm is not None: # self.objective_function is a bound method of - # an OptimizationControlMechanism - ocm = self.objective_function.__self__ + # OptimizationControlMechanism obj_func_param_init = ocm._get_evaluate_param_initializer(context) return (*param_initializer, tuple(grid_init), obj_func_param_init) @@ -1441,7 +1441,7 @@ def _get_param_initializer(self, context): def _get_state_ids(self): ids = super()._get_state_ids() - if self._is_composition_optimize(): + if self._get_optimized_composition() is not None: ids.append("objective_function") return ids @@ -1449,10 +1449,10 @@ def _get_state_ids(self): def _get_state_struct_type(self, ctx): state_struct = ctx.get_state_struct_type(super()) - if self._is_composition_optimize(): + ocm = self._get_optimized_composition() + if ocm is not None: # self.objective_function is a bound method of - # an OptimizationControlMechanism - ocm = self.objective_function.__self__ + # OptimizationControlMechanism obj_func_state = ocm._get_evaluate_state_struct_type(ctx) state_struct = pnlvm.ir.LiteralStructType([*state_struct, obj_func_state]) @@ -1462,10 +1462,10 @@ def _get_state_struct_type(self, ctx): def _get_state_initializer(self, context): state_initializer = super()._get_state_initializer(context) - if self._is_composition_optimize(): + ocm = self._get_optimized_composition() + if ocm is not None: # self.objective_function is a bound method of - # an OptimizationControlMechanism - ocm = self.objective_function.__self__ + # OptimizationControlMechanism obj_func_state_init = ocm._get_evaluate_state_initializer(context) state_initializer = (*state_initializer, obj_func_state_init) @@ -1792,7 +1792,7 @@ def _function(self, format(repr(DIRECTION), self.name, direction) - ocm = self.objective_function.__self__ if self._is_composition_optimize() else None + ocm = self._get_optimized_composition() if ocm is not None and \ ocm.parameters.comp_execution_mode._get(context).startswith("PTX"): opt_sample, opt_value, all_samples, all_values = self._run_cuda_grid(ocm, variable, context) From ce5e65faa096d7c6c668d78c57356b8d47f41724 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 26 Jul 2020 00:42:01 -0400 Subject: [PATCH 21/25] llvm, Component: Drop dict from blacklisted types. Nothing is using it. Signed-off-by: Jan Vesely --- psyneulink/core/components/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psyneulink/core/components/component.py b/psyneulink/core/components/component.py index a9ba876ee8b..05c38ef71c2 100644 --- a/psyneulink/core/components/component.py +++ b/psyneulink/core/components/component.py @@ -1314,7 +1314,7 @@ def _is_compilation_param(p): #FIXME: this should use defaults val = p.get() # Check if the value type is valid for compilation - return not isinstance(val, (str, dict, ComponentsMeta, + return not isinstance(val, (str, ComponentsMeta, ContentAddressableList, type(max), type(_is_compilation_param), type(self._get_compilation_params))) From c1fac115d7b27c9cccff7398a0b49d1a92466d80 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 26 Jul 2020 02:11:50 -0400 Subject: [PATCH 22/25] llvm, functions/GridSearch: Move SampleIterator handling to generic routines Signed-off-by: Jan Vesely --- psyneulink/core/components/component.py | 8 +++++- .../functions/optimizationfunctions.py | 28 +++---------------- psyneulink/core/llvm/builder_context.py | 6 ++++ 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/psyneulink/core/components/component.py b/psyneulink/core/components/component.py index 05c38ef71c2..8200f3c40bf 100644 --- a/psyneulink/core/components/component.py +++ b/psyneulink/core/components/component.py @@ -506,6 +506,7 @@ RESET_STATEFUL_FUNCTION_WHEN, VALUE, VARIABLE from psyneulink.core.globals.log import LogCondition from psyneulink.core.scheduling.time import Time, TimeScale +from psyneulink.core.globals.sampleiterator import SampleIterator from psyneulink.core.globals.parameters import \ Defaults, Parameter, ParameterAlias, ParameterError, ParametersBase, copy_parameter_value from psyneulink.core.globals.preferences.basepreferenceset import BasePreferenceSet, VERBOSE_PREF @@ -1297,7 +1298,7 @@ def _get_compilation_params(self): "input_port_variables", "results", "simulation_results", "monitor_for_control", "feature_values", "simulation_ids", "input_labels_dict", "output_labels_dict", - "modulated_mechanisms", "search_space", "grid", + "modulated_mechanisms", "grid", "activation_derivative_fct", "input_specification", # Shape mismatch "costs", "auto", "hetero", @@ -1369,6 +1370,11 @@ def _get_param_initializer(self, context): def _convert(x): if isinstance(x, Enum): return x.value + elif isinstance(x, SampleIterator): + if isinstance(x.generator, list): + return (float(v) for v in x.generator) + else: + return (float(x.start), float(x.step), int(x.num)) try: return (_convert(i) for i in x) except TypeError: diff --git a/psyneulink/core/components/functions/optimizationfunctions.py b/psyneulink/core/components/functions/optimizationfunctions.py index 8d650231be4..acffba4faa4 100644 --- a/psyneulink/core/components/functions/optimizationfunctions.py +++ b/psyneulink/core/components/functions/optimizationfunctions.py @@ -1388,24 +1388,14 @@ def _get_optimized_composition(self): return getattr(self.objective_function, '__self__', None) def _get_param_ids(self): - ids = super()._get_param_ids() + ["search_space"] + ids = super()._get_param_ids() if self._get_optimized_composition() is not None: ids.append("objective_function") return ids - def _get_search_dim_type(self, ctx, d): - if isinstance(d.generator, list): - # Make sure we only generate float values - return ctx.convert_python_struct_to_llvm_ir([float(x) for x in d.generator]) - if isinstance(d, SampleIterator): - return pnlvm.ir.LiteralStructType((ctx.float_ty, ctx.float_ty, ctx.int32_ty)) - assert False, "Unsupported dimension type: {}".format(d) - def _get_param_struct_type(self, ctx): param_struct = ctx.get_param_struct_type(super()) - search_space = (self._get_search_dim_type(ctx, d) for d in self.search_space) - search_space_struct = pnlvm.ir.LiteralStructType(search_space) ocm = self._get_optimized_composition() if ocm is not None: @@ -1413,31 +1403,21 @@ def _get_param_struct_type(self, ctx): # OptimizationControlMechanism obj_func_params = ocm._get_evaluate_param_struct_type(ctx) return pnlvm.ir.LiteralStructType([*param_struct, - search_space_struct, obj_func_params]) - return pnlvm.ir.LiteralStructType([*param_struct, search_space_struct]) - - def _get_search_dim_init(self, context, d): - if isinstance(d.generator, list): - return tuple(d.generator) - if isinstance(d, SampleIterator): - return (d.start, d.step, d.num) - - assert False, "Unsupported dimension type: {}".format(d) + return param_struct def _get_param_initializer(self, context): param_initializer = super()._get_param_initializer(context) - grid_init = (self._get_search_dim_init(context, d) for d in self.search_space) ocm = self._get_optimized_composition() if ocm is not None: # self.objective_function is a bound method of # OptimizationControlMechanism obj_func_param_init = ocm._get_evaluate_param_initializer(context) - return (*param_initializer, tuple(grid_init), obj_func_param_init) + return (*param_initializer, obj_func_param_init) - return (*param_initializer, tuple(grid_init)) + return param_initializer def _get_state_ids(self): ids = super()._get_state_ids() diff --git a/psyneulink/core/llvm/builder_context.py b/psyneulink/core/llvm/builder_context.py index 126cef3a27a..a745bfe12a9 100644 --- a/psyneulink/core/llvm/builder_context.py +++ b/psyneulink/core/llvm/builder_context.py @@ -20,6 +20,7 @@ from typing import Set import weakref from psyneulink.core.scheduling.time import Time +from psyneulink.core.globals.sampleiterator import SampleIterator from psyneulink.core import llvm as pnlvm from . import codegen from .debug import debug_env @@ -305,6 +306,11 @@ def convert_python_struct_to_llvm_ir(self, t): return pnlvm.builtins.get_mersenne_twister_state_struct(self) elif isinstance(t, Time): return ir.ArrayType(self.int32_ty, len(Time._time_scale_attr_map)) + elif isinstance(t, SampleIterator): + if isinstance(t.generator, list): + return ir.ArrayType(self.float_ty, len(t.generator)) + # Generic iterator is {start, increment, count} + return ir.LiteralStructType((self.float_ty, self.float_ty, self.int32_ty)) assert False, "Don't know how to convert {}".format(type(t)) From e6277bc30362e34c0f4bc73ef7e45ad70a054f6c Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 26 Jul 2020 02:56:52 -0400 Subject: [PATCH 23/25] functions/ContentAddressableMemory: Drop stateful_attributes These were only used for compilation nd are not needed anymore. Signed-off-by: Jan Vesely --- .../components/functions/statefulfunctions/memoryfunctions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py b/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py index 6f96936e60c..f28a86104c3 100644 --- a/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py +++ b/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py @@ -732,7 +732,6 @@ def __init__(self, self.parameters.val_size._set(len(self.previous_value[VALS][0]), Context()) self.has_initializers = True - self.stateful_attributes = ["random_state", "previous_value"] def _get_state_ids(self): return super()._get_state_ids() + ["ring_memory"] From 3f20ed33868d9a3f6e06cdb85e679cd49a0d3e02 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 26 Jul 2020 13:56:22 -0400 Subject: [PATCH 24/25] llvm, functions/ContentAddressableMemory: Cleanup Reuse existing poitners. Use 'None' instance to zero out output memory location. Signed-off-by: Jan Vesely --- .../statefulfunctions/memoryfunctions.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py b/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py index f28a86104c3..d33d15549bb 100644 --- a/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py +++ b/psyneulink/core/components/functions/statefulfunctions/memoryfunctions.py @@ -771,14 +771,9 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out, var_val_ptr = builder.gep(arg_in, [ctx.int32_ty(0), ctx.int32_ty(1)]) # Zero output + builder.store(arg_out.type.pointee(None), arg_out) out_key_ptr = builder.gep(arg_out, [ctx.int32_ty(0), ctx.int32_ty(0)]) out_val_ptr = builder.gep(arg_out, [ctx.int32_ty(0), ctx.int32_ty(1)]) - with pnlvm.helpers.array_ptr_loop(builder, out_key_ptr, "zero_key") as (b, i): - out_ptr = b.gep(out_key_ptr, [ctx.int32_ty(0), i]) - b.store(out_ptr.type.pointee(0), out_ptr) - with pnlvm.helpers.array_ptr_loop(builder, out_val_ptr, "zero_val") as (b, i): - out_ptr = b.gep(out_val_ptr, [ctx.int32_ty(0), i]) - b.store(out_ptr.type.pointee(0), out_ptr) # Check retrieval probability retr_ptr = builder.alloca(pnlvm.ir.IntType(1)) @@ -812,7 +807,7 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out, builder.gep(distance_arg_in, [ctx.int32_ty(0), ctx.int32_ty(0)])) selection_arg_in = builder.alloca(pnlvm.ir.ArrayType(distance_f.args[3].type.pointee, max_entries)) - with pnlvm.helpers.for_loop_zero_inc(builder, entries, "distance_loop") as (b,idx): + with pnlvm.helpers.for_loop_zero_inc(builder, entries, "distance_loop") as (b, idx): compare_ptr = b.gep(keys_ptr, [ctx.int32_ty(0), idx]) b.store(b.load(compare_ptr), b.gep(distance_arg_in, [ctx.int32_ty(0), ctx.int32_ty(1)])) @@ -832,7 +827,7 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out, builder.store(ctx.int32_ty(0), selected_idx_ptr) with pnlvm.helpers.for_loop_zero_inc(builder, entries, "distance_loop") as (b,idx): selection_val = b.load(b.gep(selection_arg_out, [ctx.int32_ty(0), idx])) - non_zero = b.fcmp_ordered('!=', selection_val, ctx.float_ty(0)) + non_zero = b.fcmp_ordered('!=', selection_val, selection_val.type(0)) with b.if_then(non_zero): b.store(idx, selected_idx_ptr) @@ -841,8 +836,8 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out, selected_idx])) selected_val = builder.load(builder.gep(vals_ptr, [ctx.int32_ty(0), selected_idx])) - builder.store(selected_key, builder.gep(arg_out, [ctx.int32_ty(0), ctx.int32_ty(0)])) - builder.store(selected_val, builder.gep(arg_out, [ctx.int32_ty(0), ctx.int32_ty(1)])) + builder.store(selected_key, out_key_ptr) + builder.store(selected_val, out_val_ptr) # Check storage probability store_ptr = builder.alloca(pnlvm.ir.IntType(1)) From 93bda7218f701347b82eb735162d3ccde40eff41 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 26 Jul 2020 14:27:37 -0400 Subject: [PATCH 25/25] llvm, mechanism/OptimizationControlMechanism: Pass the correct context to get the intensity functions initializer Signed-off-by: Jan Vesely --- .../modulatory/control/optimizationcontrolmechanism.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py b/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py index 4d1bc86a79f..0be581c04a8 100644 --- a/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py +++ b/psyneulink/core/components/mechanisms/modulatory/control/optimizationcontrolmechanism.py @@ -1025,8 +1025,7 @@ def _get_evaluate_param_struct_type(self, ctx): def _get_evaluate_param_initializer(self, context): num_estimates = self.parameters.num_estimates.get(context) or 0 - # FIXME: The intensity cost function is not setup with the right execution id - intensity_cost = tuple(op.intensity_cost_function._get_param_initializer(None) for op in self.output_ports) + intensity_cost = tuple(op.intensity_cost_function._get_param_initializer(context) for op in self.output_ports) return (intensity_cost, num_estimates) def _get_evaluate_state_struct_type(self, ctx):