Salt, spikes, and three thermostats โ€” the neuromodules of a numpy organism
DEV Community

Salt, spikes, and three thermostats - the neuromodules of a numpy organism

The follow-up to the no-LLM experiment: every neuromodule under the loops. How a spike is born, where homeostasis lives, what the stem multiplies, and why frozen encoding exists. With code. From the map to the tissue The first post made a bet: a body that thinks with weights you own, remembers outside the prompt, wants on a scale of hours, sleeps - no rented LLM in the head. It stayed at the altitude of loops: what calls what, which ring closes, which ring is a name. This post goes down to the tissue. Every neuromodule under the loops, one at a time: the rule it implements, the constants it actually uses, and - the part that matters - where its output goes. An organ whose output nobody reads is a class name with an anatomy prefix. We have grown a few of those, and they are marked as such. Everything below is real code from vi/brain/neural/ , cut for length but not altered. Numpy is the tissue. No framework, no hidden API. One moment, wire by wire A single cognitive pass, in the order the code runs it: | # | What happens | Who does it | |---|---|---| | 1 | Arousal integrates; cortical gain computed | Brainstem.update , gain() | | 2 | Channel energies gated by TRN competition | ThalamicRelay.gate | | 3 | Cortex output ร— gain ร— gate | text_cortex , in _run_encode | | 4 | Grid code added; EC-II โ†’ DG โ†’ CA3 โ†’ CA1 | HippocampalFormation.process | | 5 | Completed memory blended with now | blend_with_recall | | 6 | Hippocampus GRU โ†’ attention over memories โ†’ PFC GRU | rate path | | 7 | Lateral competition, then the same three regions again, in spikes | LateralCompetition , SpikingHybridCore | | 8 | Spike/rate blend by surprise โ†’ workspace concat โ†’ dense | cognitive_pass | | 9 | Glia hears the workspace and answers | GlialNetwork.modulate | | 10 | Workspace pushed to a memory slot | memory_bank.push_slot | That is one moment. ThoughtEncoder and Broca run after this function; credit after speech runs after the utterance. Both were covered last time. The stem sets the gain, and a gain is not a multiplier The brainstem holds one scalar with inertia: arousal. It is a learned function of four live inputs - drive, surprise, firing-rate discord, and interoception from the body - integrated over turns, because waking does not jump: # vi/brain/neural/subcortex.py - Brainstem.update features = [drive, surprise, discord, self._interoception] target = _sigmoid(self.core.forward(features)[0]) nxt = (1.0 - AROUSAL_TAU) * self.arousal + AROUSAL_TAU * target # ฯ„ = 0.25 self.arousal = _clamp01(nxt) What arousal buys is not "more". An inverted U (Yerkes-Dodson): both drowsiness and overexcitement lose. # vi/brain/neural/subcortex.py def gain(self) -> float: d = (self.arousal - AROUSAL_PEAK) / AROUSAL_WIDTH # peak 0.55, width 0.30 shape = math.exp(-0.5 * d * d) return GAIN_MIN + (GAIN_MAX - GAIN_MIN) * shape # 0.35 .. 1.0 def relay_open(self) -> float: if self.arousal float: # 0.80 .. 1.25 return EXCITABILITY_MIN + (EXCITABILITY_MAX - EXCITABILITY_MIN) * self.arousal Three outputs, three different destinations: gain multiplies cortex output, relay_open multiplies the thalamic gates, excitability_scale goes down the stem and sets LIF excitability - the neuromodulation the spiking core receives each pass. One scalar, three readers. The thalamus is a relay with gates, not a multiplexer. Channels compete: each modality's energy is real RMS, not a boolean, because a spotlight needs a difference between a whisper and a shout. # vi/brain/neural/subcortex.py - ThalamicRelay.gate (core lines) modulation = self.core.forward(e + list(self.top_down)) drive = [TOPOGRAPHIC_GAIN * (e[i] - 0.5) + modulation[i] for i in range(n)] # 4.0 inhibited = [ drive[i] - TRN_INHIBITION * (total - drive[i]) / max(1, n - 1) # 0.45 for i in range(n) ] gates = [_sigmoid(inhibited[i]) * open_frac for i in range(n)] gates = [g if mask[i] else 0.0 for i, g in enumerate(gates)] Two details worth stealing. First: an absent modality is forced to 0 - the gate never invents a signal that did not arrive. Second: the cortico-thalamic loop (what passed now biases what passes next) closes only on waking passes. On frozen encoding it stays open, otherwise the neighboring phrase would pre-open the gates for yours and the same phrase would encode differently depending on company. Before memory needs space, it needs a metric Raw similarity is a bad place to build memory: the cosine between two nearby utterances is almost always close to one. The entorhinal cortex adds a metric - grid cells, three plane waves at 60ยฐ per module, modules at scales 0.55 ยท 1.42โฑ: # vi/brain/neural/entorhinal.py - GridModule.activate (core) k = 2.0 * math.pi / max(1e-6, self.scale) for ox, oy in self.phases: # phases tile the unit cell x, y = px - ox, py - oy acc = sum(math.cos(k * (ux * x + uy * y)) for ux, uy in self._axes) # 3 axes, 60ยฐ out.append(max(0.0, min(1.0, (acc + 1.5) / 4.5))) One module is ambiguous - it repeats every period. Modules at geometrically spaced scales together are a residue-class system: position is unambiguous over a range that exceeds each period. Exponential capacity of code from a linear number of cells; that is the whole reason grids are not "another Dense layer". The projection into the 2-D map is learnable but slow-trained on purpose - it is a coordinate system, and jerking it with every gradient would let the metric drift. The slow path: separate, complete, compare The dentate gyrus is pattern separation: an expanding projection (4ร—) into a sparse code (k-winners at 5% sparsity), where similar inputs must get dissimilar codes. Three slow mechanisms sit on top of the k-winners, and each is a small essay: # vi/brain/neural/hippocampal_formation.py - DentateGyrus drive = w @ x - np.asarray(self.threshold) drive = drive * np.asarray(self._excitability()) # young cells shout winners = np.argpartition(drive, -k)[-k:] - Threshold homeostasis. A cell winning more than 1.6ร— its expected share raises its own threshold by 0.02; starved cells lower theirs. Without it, a few cells capture every input and separation collapses into noise. - Age structure. A cell younger than 400 wins is 2.2ร— more excitable - new experience lands on the young instead of overwriting the mature. Cells age by winning, not by wall-clock. - Neurogenesis. Every 120 calls, the two rarest-winning mature cells are reborn with fresh weights. Capacity is freed where it provably did nothing. CA3 is not a weight matrix. It is a modern continuous Hopfield over an explicit trace buffer: capacity 256, retrieval by softmax at ฮฒ=8, a repeated episode merges into the old trace (similarity โ‰ฅ 0.92) instead of taking a cell, eviction takes the weakest-and-oldest. # vi/brain/neural/hippocampal_formation.py - CA3Recurrent.complete (core) logits = beta * (P @ q) + np.log(np.maximum(strength, 1e-6)) wts = np.exp(logits); wts /= wts.sum() retrieved = wts @ P v = np.tanh(rw * retrieved + (1.0 - rw) * v) # the cue is never fully released Explicit traces are what make the rest possible: you cannot replay or release an individual memory out of an outer-product weight matrix. CA1 is a comparator, and the space it compares in matters: completed memory vs the direct EC-III path, both projected into CA3 space. Comparing CA1 output against raw sensory gave 0.5 forever - a random projection against an original. Empty hippocampus returns novelty 1.0: an attractor over no traces returns the cue untouched, and "everything is familiar" is a lie a newborn must not tell. Then the theta clock, which keeps the two directions from eating each other: # vi/brain/neural/hippocampal_formation.py - ThetaRhythm encode_weight = 0.5 + 0.5 * math.cos(2.0 * math.pi * phase) # period 8 passes retrieve_weight = 1.0 - encode_weight At the encode phase the recurrent net barely runs - which is why familiarity is measured by a separate probe at full recurrence, not by the phase-suppressed completion. And the write is gated twice: store_strength = encode_w * novelty * self._emotional_gain self.ca3.store(mossy_drive, strength=store_strength) Phase (don't glue the new onto the just-completed old), novelty (don't spend finite capacity re-storing the known), emotion (the amygdala gain, clamped 0.25-3.0 - fear and boredom at the same novelty do not write the same). Recall has a price in the other direction: retrieval makes a trace labile, and it re-encodes mixed with the current context at rate 0.12. Memories drift by being remembered. A trace never recalled stays untouched. How the spikes go Now the part that gets romanticized everywhere else. Spikes here are not decoration and not a second brain; they are a control layer inside the pass. The same three region representations (hippocampus, attended memory, PFC) that the rate path competes with are re-encoded as spike trains, and what the spikes decide is who the workspace listens to. One population, twelve timesteps, in the code that actually runs (numpy path): # vi/brain/neural/spiking_hybrid.py - LIFPopulation._simulate_numpy (core) drive = np.abs(stim) * stim_scale * (1.0 + phase) * gamma drive = (drive + recurrent_drive) * exc # exc โ† stem neuromod can_fire = refractory = thr) & can_fire # ฯ„=0.82, refractory=2 membrane = np.where(spikes, v_reset, membrane) refractory = np.where(spikes, ref_n, np.maximum(0, refractory - 1)) gamma is a 40 Hz sinusoid with a floor - a coarse gamma-band modulation, not a claim. stim_scale normalizes any input vector into a spiking regime (4..24). Deterministic: no RNG anywhere in the membrane dynamics. The query population and one population per memory candidate each produce a train; the spike score of a memory is the inner product of firing rates with the query's. Then the surprise gate converts two measurements into one control number: # vi/brain/neural/spiking_hybrid.py - SpikingHybridCore.surprise_gate (core) mismatch = abs(q_mean - base) / max(0.15, base + 0.15) # rate vs its own EMA baseline ent = entropy(energies / total) # uniform regions โ†’ explore surprise = clamp01(0.55 * min(1.

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.