Spaces:
Paused
Paused
Download src/acceleration/cuda/plasticity/reference.py from ThomasHeisig/MHRN-Space: direct link, hf CLI and curl.
- Browser
- Download file 6.27 kB
-
https://huggingface.co/spaces/ThomasHeisig/MHRN-Space/resolve/main/src/acceleration/cuda/plasticity/reference.py
- Command line
-
hf download hf://spaces/ThomasHeisig/MHRN-Space/src/acceleration/cuda/plasticity/reference.py
-
curl -L -o reference.py https://huggingface.co/spaces/ThomasHeisig/MHRN-Space/resolve/main/src/acceleration/cuda/plasticity/reference.py
6.27 kB
| """Ordered scalar synapse oracle for the canonical bounded CUDA reference.""" | |
| from __future__ import annotations | |
| import math | |
| from dataclasses import dataclass | |
| from typing import TYPE_CHECKING | |
| from src.runtime.determinism import ( | |
| release_uniform as release_uniform, | |
| ) | |
| from src.runtime.determinism import ( | |
| ring_slot as ring_slot, | |
| ) | |
| if TYPE_CHECKING: | |
| from ..recurrent.backend import RecurrentInputs | |
| class SynapseConfig: | |
| stdp: bool = True | |
| stp: bool = True | |
| reward_modulated: bool = True | |
| seed: int = 12345 | |
| learning_rate: float = 0.2 | |
| weight_decay: float = 0.001 | |
| weight_max: float = 20.0 | |
| eligibility_tau: float = 100.0 | |
| credit_window: int = 64 | |
| td_lambda: float = 0.9 | |
| gamma: float = 0.99 | |
| def parameters(self) -> list[float]: | |
| return [ | |
| float(self.stdp), | |
| float(self.stp), | |
| float(self.reward_modulated), | |
| float(self.seed), | |
| self.learning_rate, | |
| self.weight_decay, | |
| self.weight_max, | |
| self.eligibility_tau, | |
| float(self.credit_window), | |
| self.td_lambda, | |
| self.gamma, | |
| ] | |
| def validate(self) -> None: | |
| if any( | |
| type(value) is not bool | |
| for value in (self.stdp, self.stp, self.reward_modulated) | |
| ): | |
| raise ValueError("synapse mode flags must be boolean") | |
| if type(self.seed) is not int or not 0 <= self.seed <= 0xFFFFFFFF: | |
| raise ValueError("synapse seed must be uint32") | |
| if type(self.credit_window) is not int or not 0 <= self.credit_window <= 2000: | |
| raise ValueError("credit_window must be in [0,2000]") | |
| for name, value, low, high in ( | |
| ("learning_rate", self.learning_rate, 0.0, 1.0), | |
| ("weight_decay", self.weight_decay, 0.0, 1.0), | |
| ("weight_max", self.weight_max, 0.001, 100.0), | |
| ("eligibility_tau", self.eligibility_tau, 0.001, 10000.0), | |
| ("td_lambda", self.td_lambda, 0.0, 1.0), | |
| ("gamma", self.gamma, 0.0, 1.0), | |
| ): | |
| if ( | |
| isinstance(value, bool) | |
| or not math.isfinite(value) | |
| or not low <= value <= high | |
| ): | |
| raise ValueError(f"invalid synapse {name}") | |
| class SynapseState: | |
| def __init__(self, inputs: RecurrentInputs, config: SynapseConfig) -> None: | |
| self.inputs = inputs | |
| self.config = config | |
| self.weights = list(inputs.weights) | |
| self.eligibility = [0.0] * len(inputs.sources) | |
| self.available = [1.0] * len(inputs.sources) | |
| self.last_event = [-10_000_000] * len(inputs.sources) | |
| self.last_spike = [-10_000_000] * inputs.n_neurons | |
| self.ring_size = max(inputs.delays, default=1) + 1 | |
| self.emitted = [[0.0] * len(inputs.sources) for _ in range(self.ring_size)] | |
| def incoming(self, tick: int, neuron: int) -> float: | |
| # Kept for external callers; the membrane oracle sums in edge order. | |
| return sum( | |
| self.edge_current(tick, edge) | |
| for edge in range( | |
| self.inputs.offsets[neuron], self.inputs.offsets[neuron + 1] | |
| ) | |
| ) | |
| def edge_current(self, tick: int, edge: int) -> float: | |
| previous = tick - self.inputs.delays[edge] | |
| return ( | |
| self.emitted[ring_slot(previous, self.ring_size)][edge] | |
| if previous >= 0 | |
| else 0.0 | |
| ) | |
| def update(self, tick: int, spikes: list[int], reward: float) -> None: | |
| c, inputs = self.config, self.inputs | |
| for target in range(inputs.n_neurons): | |
| for edge in range(inputs.offsets[target], inputs.offsets[target + 1]): | |
| source = inputs.sources[edge] | |
| pre, post = spikes[source], spikes[target] | |
| weight = self.weights[edge] | |
| eligibility = self.eligibility[edge] * math.exp( | |
| -inputs.dt_ms / c.eligibility_tau | |
| ) | |
| # Match ascending-neuron pair-rule ordering, including self edges. | |
| operations = ("pre", "post") if source <= target else ("post", "pre") | |
| for operation in operations: | |
| if operation == "pre" and pre: | |
| if c.stdp and 0 < tick - self.last_spike[target] <= 20: | |
| weight = max(0.0, weight - 0.08) | |
| eligibility -= 0.5 | |
| if operation == "post" and post: | |
| if c.stdp and 0 < tick - self.last_spike[source] <= 20: | |
| weight = min(100.0, weight + 0.1) | |
| eligibility += 1.0 | |
| if pre or post: | |
| self.last_event[edge] = tick | |
| if ( | |
| c.reward_modulated | |
| and tick - self.last_event[edge] <= c.credit_window | |
| ): | |
| weight = min( | |
| 100.0, | |
| max( | |
| 0.0, | |
| weight | |
| + c.learning_rate | |
| * c.td_lambda | |
| * c.gamma | |
| * eligibility | |
| * reward, | |
| ), | |
| ) | |
| amplitude = weight if pre else 0.0 | |
| available = self.available[edge] | |
| if c.stp: | |
| if pre: | |
| amplitude *= float( | |
| release_uniform(c.seed, tick, edge) | |
| < min(0.95, 0.25 + 0.7 * available) | |
| ) | |
| available = max(0.1, available * 0.72) | |
| available = min(1.0, available + 0.025) | |
| self.available[edge] = available | |
| self.emitted[ring_slot(tick, self.ring_size)][edge] = amplitude | |
| self.weights[edge] = min( | |
| c.weight_max, max(0.0, weight * (1.0 - c.weight_decay)) | |
| ) | |
| self.eligibility[edge] = eligibility | |
| for neuron, spike in enumerate(spikes): | |
| if spike: | |
| self.last_spike[neuron] = tick | |