beam-boot / beamboot /hf_decoder_memory.py
StarAtNyte1's picture
Publish Beam Boot / Super Beam source
324ecd2 verified
Raw History Blame Contribute Delete
4.02 kB
"""Attention-backed nibble memory through a stock BeamDecoderLayer.
This probe uses constructed weights, a compact one-group Beam config and
host-encoded event vectors. Only memory reads are implemented in this decoder;
the layer's MLP is zeroed. It does not use pretrained Beam weights.
"""
import math
import torch
from transformers import BeamConfig
from transformers.cache_utils import DynamicCache
from transformers.models.beam.modeling_beam import BeamDecoderLayer, compute_attention_scaling
from .attention_cpu import bits
class DecoderMemory:
def __init__(self, max_writes=128):
if not 1 <= max_writes <= 1024: raise ValueError('max_writes must be 1..1024')
self.maximum = max_writes
self.writes = 0
config = BeamConfig(hidden_size=128, intermediate_size=16, num_hidden_layers=1,
num_attention_heads=6, num_key_value_heads=1, head_dim=128,
layer_types=['full_attention'], mlp_layer_types=['dense'],
attention_dropout=0.)
config._attn_implementation = 'eager'
self.layer = BeamDecoderLayer(config, 0).eval().requires_grad_(False)
attn = self.layer.self_attn
with torch.no_grad():
for proj in (attn.q_proj, attn.k_proj, attn.v_proj, attn.o_proj, attn.gate_proj,
self.layer.mlp.gate_proj, self.layer.mlp.up_proj, self.layer.mlp.down_proj):
proj.weight.zero_()
for head in range(6):
offset = 128*head
for i in range(5): attn.q_proj.weight[offset+i, i] = 1
attn.q_proj.weight[offset+5, 5] = 5
for i in range(7): attn.k_proj.weight[i, 6+i] = 1
for i in range(16):
attn.v_proj.weight[i, 13+i] = 1
attn.o_proj.weight[32+i, i] = 1
attn.gate_bias.zero_()
attn.q_norm.weight.fill_(16*max_writes*math.sqrt(30*6)/math.sqrt(128))
attn.k_norm.weight.fill_(1)
self.cache = DynamicCache(config=config)
# Prefill zero-initialized RAM; earlier events cannot inspect future ones.
rows = torch.stack([self._row(a, 0, 0, True) for a in range(16)]).unsqueeze(0)
with torch.inference_mode(): self._forward(rows)
def _row(self, address, value, position, valid):
x = torch.zeros(128)
x[:4] = torch.tensor([2*b-1 for b in bits(address, 4)])
x[4:6] = 1
x[6:10] = x[:4]
p = position / self.maximum
x[10], x[11], x[12] = p, 1 if valid else -1, math.sqrt(max(0, 1-p*p))
x[13+value] = 1
# The input layer's RMS normalization must not change V magnitudes.
x[-1] = math.sqrt(128-float(x.square().sum()))
return x
def _forward(self, rows):
old = self.cache.get_seq_length()
length = rows.shape[1]
mask = None
if length > 1:
mask = rows.new_zeros((1, 1, length, old+length))
mask[0, 0, :, old:].masked_fill_(torch.ones(length, length, dtype=torch.bool).triu(1), float('-inf'))
positions = torch.arange(old, old+length).unsqueeze(0)
return self.layer(rows, position_embeddings=None, attention_mask=mask,
attention_scaling=compute_attention_scaling(positions),
past_key_values=self.cache, use_cache=True)
def write(self, address, value):
if not 0 <= address < 16 or not 0 <= value < 16: raise ValueError('expected nibbles')
if self.writes >= self.maximum: raise ValueError('write budget exhausted')
self.writes += 1
with torch.inference_mode(): self._forward(self._row(address, value, self.writes, True).reshape(1, 1, -1))
def read(self, address):
if not 0 <= address < 16: raise ValueError('expected nibble')
with torch.inference_mode():
output = self._forward(self._row(address, 0, self.writes, False).reshape(1, 1, -1))
return int(output[0, 0, 32:48].argmax())