Skip to content

Commit

Permalink
llvm, MemoryFunctions: Implement, use or drop unused parameters (#2824)
Browse files Browse the repository at this point in the history
Implement compiled support for NOISE and RATE parameters.
Drop DUPLICATE_KEYS and PREVIOUS_VALUE. Handling duplicate keys is complex and not supported in compiled mode.
Update tests.
  • Loading branch information
jvesely authored Nov 3, 2023
2 parents 9c827ff + bc409f2 commit 87fd1a9
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 87 deletions.
6 changes: 5 additions & 1 deletion psyneulink/core/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,10 @@ def _get_compilation_state(self):
if getattr(self.parameters, 'has_recurrent_input_port', False):
blacklist.update(['combination_function'])

# Drop previous_value from MemoryFunctions
if hasattr(self.parameters, 'duplicate_keys'):
blacklist.add("previous_value")

def _is_compilation_state(p):
# FIXME: This should use defaults instead of 'p.get'
return p.name not in blacklist and \
Expand Down Expand Up @@ -1400,7 +1404,7 @@ def _get_compilation_params(self):
"random_variables", "smoothing_factor", "per_item",
"key_size", "val_size", "max_entries", "random_draw",
"randomization_dimension", "save_values", "save_samples",
"max_iterations",
"max_iterations", "duplicate_keys",
# not used in compiled learning
"learning_results", "learning_signal", "learning_signals",
"error_matrix", "error_signal", "activation_input",
Expand Down
47 changes: 35 additions & 12 deletions psyneulink/core/components/functions/stateful/memoryfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2427,8 +2427,8 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out,
store_prob = pnlvm.helpers.load_extract_scalar_array_one(builder, store_prob_ptr)
store_rand = builder.fcmp_ordered('<', store_prob, store_prob.type(1.0))

# The call to random function needs to be behind jump to match python
# code
# The call to random function needs to be behind the check of 'store_rand'
# to match python code semantics
with builder.if_then(store_rand):
rand_ptr = builder.alloca(ctx.float_ty)
builder.call(uniform_f, [rand_struct, rand_ptr])
Expand All @@ -2439,6 +2439,27 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out,
# Store
store = builder.load(store_ptr)
with builder.if_then(store, likely=True):
modified_key_ptr = builder.alloca(var_key_ptr.type.pointee)

# Apply noise to key.
# There are 3 types of noise: scalar, vector1, and vector matching variable
noise_ptr = pnlvm.helpers.get_param_ptr(builder, self, params, "noise")
rate_ptr = pnlvm.helpers.get_param_ptr(builder, self, params, "rate")
with pnlvm.helpers.array_ptr_loop(b, var_key_ptr, "key_apply_rate_noise") as (b, idx):
if pnlvm.helpers.is_2d_matrix(noise_ptr):
noise_elem_ptr = b.gep(noise_ptr, [ctx.int32_ty(0), ctx.int32_ty(0), idx])
noise_val = b.load(noise_elem_ptr)
else:
noise_val = pnlvm.helpers.load_extract_scalar_array_one(b, noise_ptr)

rate_val = pnlvm.helpers.load_extract_scalar_array_one(b, rate_ptr)

modified_key_elem_ptr = b.gep(modified_key_ptr, [ctx.int32_ty(0), idx])
key_elem_ptr = b.gep(var_key_ptr, [ctx.int32_ty(0), idx])
key_elem = b.load(key_elem_ptr)
key_elem = b.fmul(key_elem, rate_val)
key_elem = b.fadd(key_elem, noise_val)
b.store(key_elem, modified_key_elem_ptr)

# Check if such key already exists
is_new_key_ptr = builder.alloca(ctx.bool_ty)
Expand All @@ -2451,7 +2472,7 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out,
key_differs_ptr = b.alloca(ctx.bool_ty)
b.store(key_differs_ptr.type.pointee(0), key_differs_ptr)
with pnlvm.helpers.array_ptr_loop(b, cmp_key_ptr, "key_compare") as (b2, idx2):
var_key_element = b2.gep(var_key_ptr, [ctx.int32_ty(0), idx2])
var_key_element = b2.gep(modified_key_ptr, [ctx.int32_ty(0), idx2])
cmp_key_element = b2.gep(cmp_key_ptr, [ctx.int32_ty(0), idx2])
element_differs = b.fcmp_unordered('!=',
b.load(var_key_element),
Expand All @@ -2473,7 +2494,7 @@ def _gen_llvm_function_body(self, ctx, builder, params, state, arg_in, arg_out,
store_key_ptr = builder.gep(keys_ptr, [ctx.int32_ty(0), write_idx])
store_val_ptr = builder.gep(vals_ptr, [ctx.int32_ty(0), write_idx])

builder.store(builder.load(var_key_ptr), store_key_ptr)
builder.store(builder.load(modified_key_ptr), store_key_ptr)
builder.store(builder.load(var_val_ptr), store_val_ptr)

# Update counters
Expand Down Expand Up @@ -2674,18 +2695,20 @@ def _function(self,
# CURRENT PROBLEM WITH LATTER IS THAT IT CAUSES CRASH ON INIT, SINCE NOT OUTPUT_PORT
# SO, WOULD HAVE TO RETURN ZEROS ON INIT AND THEN SUPPRESS AFTERWARDS, AS MOCKED UP BELOW
memory = [[0]* self.parameters.key_size._get(context), [0]* self.parameters.val_size._get(context)]

# Store variable to dict:
rate = self._get_current_parameter_value(RATE, context)
if rate is not None:
key = np.asfarray(key) * np.asfarray(rate)
assert len(key) == len(variable[KEYS]), "{} vs. {}".format(key, variable[KEYS])

if noise is not None:
key = np.asarray(key, dtype=float)
if isinstance(noise, numbers.Number):
key += noise
else:
# assume array with same shape as variable
# TODO: does val need noise?
key += noise[KEYS]
# TODO: does val need noise?
key = np.asfarray(key) + np.asfarray(noise)[KEYS]
assert len(key) == len(variable[KEYS]), "{} vs. {}".format(key, variable[KEYS])

if storage_prob == 1.0 or (storage_prob > 0.0 and storage_prob > random_state.uniform()):
self._store_memory(variable, context)
self._store_memory([key, val], context)

# Return 3d array with keys and vals as lists
# IMPLEMENTATION NOTE: if try to create np.ndarray directly, and keys and vals have same length
Expand Down
Loading

0 comments on commit 87fd1a9

Please sign in to comment.