beam-boot / beamboot /two_layer_cpu.py
StarAtNyte1's picture
Publish Beam Boot / Super Beam source
324ecd2 verified
Raw History Blame Contribute Delete
6.22 kB
"""Two stock Beam decoder layers executing a compiled CPU transition.
Layer 0's SwiGLU MLP decodes the current PC into a memory address. Layer 1's
attention reads RAM, and its SwiGLU MLP computes the next CPU state. The host
serializes state/write events but does not look up ROM instructions during a
step. Weights and event format are constructed; this is not pretrained Beam.
"""
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, from_bits
from .hf_decoder_cpu import DecoderCPU
from .large_cpu import EventResult
class TwoLayerCPU(DecoderCPU):
def __init__(self, program, max_writes=128):
super().__init__(program, max_writes)
config = BeamConfig(hidden_size=128, intermediate_size=256, num_hidden_layers=2,
num_attention_heads=6, num_key_value_heads=1, head_dim=128,
layer_types=['full_attention', 'full_attention'],
mlp_layer_types=['dense', 'dense'], attention_dropout=0.)
config._attn_implementation = 'eager'
self.first = BeamDecoderLayer(config, 0).eval().requires_grad_(False)
self.layer.self_attn.layer_idx = 1
self.layer.attn_residual_add_idx = 4
self.layer.mlp_residual_add_idx = 5
self.layer.self_attn.config = config
# Layer 0 converts eight bipolar PC bits into four bipolar address bits.
ids = torch.arange(256)
patterns = (((ids[:, None] >> torch.arange(8)) & 1)*2-1).float()
addresses = []
for pc in range(256):
op, arg = program[pc] if pc < len(program) else ('HALT', 0)
addr = arg if op in {'LOAD', 'STORE', 'ADDM', 'SUBM'} else 0
addresses.append([2*b-1 for b in bits(addr, 4)])
with torch.no_grad():
for proj in (self.first.self_attn.q_proj, self.first.self_attn.k_proj,
self.first.self_attn.v_proj, self.first.self_attn.o_proj,
self.first.self_attn.gate_proj, self.first.mlp.gate_proj,
self.first.mlp.up_proj, self.first.mlp.down_proj):
proj.weight.zero_()
self.first.self_attn.gate_bias.zero_()
self.first.mlp.gate_proj.weight[:, 48:56] = 32 * patterns
self.first.mlp.gate_proj.weight[:, 5] = -7.5 * 32
self.first.mlp.up_proj.weight[:, 5] = 1
self.first.mlp.down_proj.weight[:4, :] = torch.tensor(addresses, dtype=torch.float32).T
# The first layer emits query-address bits with a different scale
# from its unchanged fields; align them before layer-1 Q RMSNorm.
for head in range(6):
for i in range(4): self.layer.self_attn.q_proj.weight[head*128+i, i] *= 1/(4*math.sqrt(2))
# Recalibrate the second layer MLP to its actual layer-1 residual
# and normalization magnitudes: state=1/sqrt(3), read=sqrt(128/3).
old_alpha, new_alpha = 1/math.sqrt(2), 1/math.sqrt(3)
old_read, new_read = 8., math.sqrt(128/3)
self.layer.mlp.gate_proj.weight[:, 48:60] *= old_alpha/new_alpha
self.layer.mlp.gate_proj.weight[:, 5] *= old_alpha/new_alpha
self.layer.mlp.gate_proj.weight[:, 32:48] *= old_read/new_read
self.layer.mlp.up_proj.weight[:, 5] *= old_alpha/new_alpha
self.cache = DynamicCache(config=config)
self.pc = self.acc = self.writes = 0
self.halted = False
rows = torch.stack([self._event(a, 0, 0, True, 0, 0) for a in range(16)]).unsqueeze(0)
with torch.inference_mode(): self._forward(rows)
def _event(self, address, value, position, valid, pc, acc):
# Query-address channels are zero. Layer 0 fills them from PC.
x = torch.zeros(128)
x[4:6] = 1
x[6:10] = torch.tensor([2*b-1 for b in bits(address, 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
x[48:60] = torch.tensor([2*b-1 for b in bits(pc, 8)+bits(acc, 4)])
x[-1] = math.sqrt(128-float(x.square().sum()))
return x
def _forward(self, rows):
if not hasattr(self, 'first'):
return super()._forward(rows)
old = self.cache.get_seq_length(layer_idx=1)
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)
kwargs = dict(position_embeddings=None, attention_mask=mask,
attention_scaling=compute_attention_scaling(positions),
past_key_values=self.cache, use_cache=True)
return self.layer(self.first(rows, **kwargs), **kwargs)
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._event(address, value, self.writes, True, self.pc, self.acc).reshape(1, 1, -1))
def step(self):
if self.halted: raise RuntimeError('machine is halted')
old_acc = self.acc
with torch.inference_mode():
output = self._forward(self._event(0, 0, self.writes, False, self.pc, self.acc).reshape(1, 1, -1))[0, 0]
if not torch.isfinite(output).all(): raise RuntimeError('non-finite output')
address = from_bits((output[:4] > 0).int().tolist())
decoded = output[60:90].reshape(15, 2).argmax(dim=-1).tolist()
self.pc, self.acc = from_bits(decoded[:8]), from_bits(decoded[8:12])
write, out, self.halted = map(bool, decoded[12:])
if write: self.write(address, old_acc)
return EventResult(self.pc, self.acc, write, out, self.halted, address, old_acc)