File size: 16,381 Bytes
ebd2ca7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 | #!/usr/bin/env python3
"""Same-52-byte variable StateQuant tiers; immutable v6 baseline delegation.
Derived from the frozen Apache-2.0 state_codec and v6 kernels. Only tier
lengths and masked register/payload geometry change. No dense carry, padded
backing storage, resident codebook or resident layout tensor is allocated.
See reference/statequant/LICENSE and docs/THIRD_PARTY_NOTICES.md.
"""
from __future__ import annotations
from pathlib import Path
import sys
import torch
import triton
import triton.language as tl
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
import state_ppl_codec_v6 as v6
from state_ppl_codec_v6 import _stored_quantize
from mamba2_recall import state_codec as legacy_codec
from mamba2_recall.state_quant import _LayerCache
PROTOCOL_SHA = '565707cf401b2ab472b9b78368a7192afeebe73056efd811b7acba610e3d9567'
LAYOUTS = ('16_64_48', '8_80_40', '24_48_56', '32_32_64')
COUNTS = {'16_64_48': (16,64,48), '8_80_40': (8,80,40),
'24_48_56': (24,48,56), '32_32_64': (32,32,64)}
BASELINE = LAYOUTS[0]
RP = 16
def layout_descriptor(layout=BASELINE):
if layout not in COUNTS:
raise ValueError('Unknown frozen v10 tier layout')
n8,n4,nzero = COUNTS[layout]
return dict(layout=layout,n8=n8,n4=n4,nzero=nzero,payload_bytes=48,
scale_bytes=4,row_bytes=52,resident_layout_metadata_bytes=0,
tensor_widths=dict(lo=n8//2,hi=n8//2,q4=n4//2))
def allocate_state(batch,heads,dim,dstate,device,*,layout=BASELINE):
descriptor = layout_descriptor(layout)
if dstate != 128 or any(type(v) is not int or v <= 0 for v in (batch,heads,dim)):
raise ValueError('Positive integer geometry and128 state coordinates required')
if layout == BASELINE:
return v6.allocate_state(batch,heads,dim,dstate,device)
shape = (batch,heads,dim,128)
tensors = {key: torch.zeros((batch,heads,dim,width),dtype=torch.uint8,device=device)
for key,width in descriptor['tensor_widths'].items()}
tensors.update({key: torch.zeros((batch,heads,dim),dtype=torch.float16,device=device)
for key in ('s8','s4')})
return legacy_codec.PackedState('v10_'+layout,tensors,shape)
def validate_state(state,device,layout):
descriptor = layout_descriptor(layout)
batch,heads,dim,dstate = state.shape
if dstate != 128 or min(batch,heads,dim) <= 0:
raise ValueError('Invalid packed state geometry')
mode = 'sq3p25' if layout == BASELINE else 'v10_'+layout
if state.mode != mode or set(state.tensors) != {'lo','hi','q4','s8','s4'}:
raise ValueError('Packed state layout or tensor inventory differs')
storages = set()
for key,value in state.tensors.items():
shape = (batch,heads,dim)
dtype = torch.float16 if key in ('s8','s4') else torch.uint8
if key in descriptor['tensor_widths']:
shape += (descriptor['tensor_widths'][key],)
if (tuple(value.shape) != shape or value.dtype != dtype or value.device != torch.device(device)
or not value.is_contiguous() or value.storage_offset() != 0
or value.untyped_storage().nbytes() != value.numel()*value.element_size()):
raise ValueError('State must use exact unpadded physical tensor allocations')
storages.add(value.untyped_storage().data_ptr())
if len(storages) != 5 or state.nbytes != batch*heads*dim*52:
raise ValueError('State tensors alias or exceed the52-byte budget')
return True
@triton.jit
def _tier_scan(X,DT,A,B,C,D,DB,Perm,Lo,Hi,Q4,S8,S4,Y,
L:tl.constexpr,H:tl.constexpr,P:tl.constexpr,G:tl.constexpr,
N8:tl.constexpr,N4:tl.constexpr,NZ:tl.constexpr,
R8:tl.constexpr,R4:tl.constexpr,RZ:tl.constexpr,TILE:tl.constexpr):
b,h,tile = tl.program_id(0),tl.program_id(1),tl.program_id(2)
p = tile*TILE+tl.arange(0,TILE)
mp = p < P
row = (b*H+h)*P+p
n8 = tl.arange(0,R8)
n4 = tl.arange(0,R4)
nd = tl.arange(0,RZ)
lo = (tl.load(Lo+row[:,None]*(N8//2)+n8[None,:]//2,
mask=mp[:,None] & (n8[None,:]<N8),other=0).to(tl.int32) >> ((n8[None,:]%2)*4)) & 15
hi = (tl.load(Hi+row[:,None]*(N8//2)+n8[None,:]//2,
mask=mp[:,None] & (n8[None,:]<N8),other=0).to(tl.int32) >> ((n8[None,:]%2)*4)) & 15
ub = (hi << 4) | lo
q8 = ub-256*(ub>127).to(tl.int32)
nib = (tl.load(Q4+row[:,None]*(N4//2)+n4[None,:]//2,
mask=mp[:,None] & (n4[None,:]<N4),other=0).to(tl.int32) >> ((n4[None,:]%2)*4)) & 15
q4 = nib-16*(nib>7).to(tl.int32)
scale8 = tl.load(S8+row,mask=mp,other=0).to(tl.float32)
scale4 = tl.load(S4+row,mask=mp,other=0).to(tl.float32)
state8 = q8.to(tl.float32)*scale8[:,None]
state4 = q4.to(tl.float32)*scale4[:,None]
av = tl.load(A+h).to(tl.float32)
dv = tl.load(D+h).to(tl.float32)
bias = tl.load(DB+h).to(tl.float32)
group = h//(H//G)
pos8 = tl.load(Perm+group*128+n8,mask=n8<N8,other=0).to(tl.int32)
pos4 = tl.load(Perm+group*128+N8+n4,mask=n4<N4,other=0).to(tl.int32)
posd = tl.load(Perm+group*128+N8+N4+nd,mask=nd<NZ,other=0).to(tl.int32)
for t in range(L):
xoff = ((b*L+t)*H+h)*P+p
x = tl.load(X+xoff,mask=mp,other=0).to(tl.float32)
dt = tl.load(DT+(b*L+t)*H+h).to(tl.float32)+bias
dt = tl.where(dt<=20.,tl.math.log(tl.math.exp(dt)+1.),dt)
decay = tl.exp(av*dt)
bbase = ((b*L+t)*G+group)*128
bv8 = tl.load(B+bbase+pos8,mask=n8<N8,other=0).to(tl.float32)
cv8 = tl.load(C+bbase+pos8,mask=n8<N8,other=0).to(tl.float32)
bv4 = tl.load(B+bbase+pos4,mask=n4<N4,other=0).to(tl.float32)
cv4 = tl.load(C+bbase+pos4,mask=n4<N4,other=0).to(tl.float32)
bvd = tl.load(B+bbase+posd,mask=nd<NZ,other=0).to(tl.float32)
cvd = tl.load(C+bbase+posd,mask=nd<NZ,other=0).to(tl.float32)
state8 = state8*decay+(bv8[None,:]*dt)*x[:,None]
state4 = state4*decay+(bv4[None,:]*dt)*x[:,None]
stated = (bvd[None,:]*dt)*x[:,None]
# Make padded register lanes explicit zeros before either reduction.
state8 = tl.where(n8[None,:]<N8,state8,0.)
state4 = tl.where(n4[None,:]<N4,state4,0.)
stated = tl.where(nd[None,:]<NZ,stated,0.)
out = tl.sum(state8*cv8[None,:],1)
out += tl.sum(state4*cv4[None,:],1)
out += tl.sum(stated*cvd[None,:],1)+x*dv
tl.store(Y+xoff,out,mask=mp)
den8 = tl.maximum(tl.div_rn(tl.max(tl.abs(state8),1),127.),1e-8)
den4 = tl.maximum(tl.div_rn(tl.max(tl.abs(state4),1),7.),1e-8)
scale8 = den8.to(tl.float16).to(tl.float32)
scale4 = den4.to(tl.float16).to(tl.float32)
q8 = _stored_quantize(state8,scale8[:,None],127)
q4 = _stored_quantize(state4,scale4[:,None],7)
state8 = q8.to(tl.float32)*scale8[:,None]
state4 = q4.to(tl.float32)*scale4[:,None]
qlo = (q8 & 15).to(tl.uint8)
qhi = ((q8 >> 4) & 15).to(tl.uint8)
lo0,lo1 = tl.split(tl.reshape(qlo,(TILE,R8//2,2)))
hi0,hi1 = tl.split(tl.reshape(qhi,(TILE,R8//2,2)))
pairs8 = tl.arange(0,R8//2)
tl.store(Lo+row[:,None]*(N8//2)+pairs8[None,:],lo0|(lo1<<4),mask=mp[:,None] & (pairs8[None,:]<N8//2))
tl.store(Hi+row[:,None]*(N8//2)+pairs8[None,:],hi0|(hi1<<4),mask=mp[:,None] & (pairs8[None,:]<N8//2))
qn = (q4 & 15).to(tl.uint8)
f0,f1 = tl.split(tl.reshape(qn,(TILE,R4//2,2)))
pairs4 = tl.arange(0,R4//2)
tl.store(Q4+row[:,None]*(N4//2)+pairs4[None,:],f0|(f1<<4),mask=mp[:,None] & (pairs4[None,:]<N4//2))
tl.store(S8+row,scale8,mask=mp)
tl.store(S4+row,scale4,mask=mp)
@torch.no_grad()
def scan(x,dt,A,B,C,D,dt_bias,state,permutation,*,layout=BASELINE):
descriptor = layout_descriptor(layout)
if layout == BASELINE:
# This is the original measured v6 path, not an equivalent new kernel.
return v6.scan(x,dt,A,B,C,D,dt_bias,state,permutation,scale_mode='stored_scale',int4_clip=1.)
if x.ndim != 4 or x.dtype != torch.float16 or not x.is_cuda:
raise ValueError('x must be CUDA FP16[batch,tokens,heads,dim]')
batch,length,heads,dim = x.shape
if length < 1 or state.shape != (batch,heads,dim,128):
raise ValueError('Positive sequence and matching packed geometry required')
validate_state(state,x.device,layout)
if B.ndim != 4:raise ValueError('B must have four axes')
groups = B.shape[2]
if groups < 1 or heads%groups:raise ValueError('Heads must be divisible by groups')
shapes = ((dt,(batch,length,heads)),(A,(heads,)),(B,(batch,length,groups,128)),
(C,(batch,length,groups,128)),(D,(heads,)),(dt_bias,(heads,)))
for tensor,shape in shapes:
if tuple(tensor.shape) != shape or tensor.device != x.device:
raise ValueError('Input device/geometry differs')
if A.dtype != torch.float32 or any(v.dtype != torch.float16 for v in (dt,B,C)):
raise ValueError('A must be FP32; dt/B/C must be FP16')
if any(v.dtype not in (torch.float16,torch.float32) for v in (D,dt_bias)):
raise ValueError('D/dt_bias must be FP16 or FP32')
if (permutation.dtype != torch.uint8 or tuple(permutation.shape) != (groups,128)
or permutation.device != x.device):
raise ValueError('Permutation must be CUDA uint8[groups,128]')
x,dt,A,B,C,D,dt_bias,permutation = (v.contiguous() for v in (x,dt,A,B,C,D,dt_bias,permutation))
output = torch.empty_like(x)
n8,n4,nzero = descriptor['n8'],descriptor['n4'],descriptor['nzero']
with torch.cuda.device(x.device):
_tier_scan[(batch,heads,triton.cdiv(dim,RP))](x,dt,A,B,C,D,dt_bias,permutation,
*(state.tensors[k] for k in ('lo','hi','q4','s8','s4')),output,
length,heads,dim,groups,n8,n4,nzero,triton.next_power_of_2(n8),
triton.next_power_of_2(n4),triton.next_power_of_2(nzero),RP,num_warps=4)
return output,None
@torch.no_grad()
def decode_state(state,permutation=None,*,layout=BASELINE):
if layout == BASELINE:return v6.decode_state(state,permutation)
descriptor = layout_descriptor(layout)
validate_state(state,state.tensors['lo'].device,layout)
n8,n4 = descriptor['n8'],descriptor['n4']
shape = state.shape
values = torch.zeros(shape,dtype=torch.float32,device=state.tensors['lo'].device)
def unpack(tensor):
return torch.stack((tensor.int() & 15,tensor.int() >> 4),dim=-1).flatten(-2)
q8 = (unpack(state.tensors['hi']) << 4) | unpack(state.tensors['lo'])
q8 = torch.where(q8>127,q8-256,q8)
q4 = unpack(state.tensors['q4'])
q4 = torch.where(q4>7,q4-16,q4)
values[...,:n8] = q8.float()*state.tensors['s8'].float()[...,None]
values[...,n8:n8+n4] = q4.float()*state.tensors['s4'].float()[...,None]
if permutation is None:return values
groups = permutation.shape[0]
legacy_codec.validate_permutation(permutation,groups)
batch,heads,dim,_ = shape
if heads%groups:raise ValueError('Invalid permutation grouping')
indices = permutation.long().repeat_interleave(heads//groups,0)[None,:,None,:].expand(shape)
return torch.zeros_like(values).scatter_(-1,indices,values)
validate_finite_result = v6.validate_finite_result
class StatePPLQuantV10(v6.StatePPLQuant):
"""Same native FP16 orchestration and one unchanged static permutation."""
def __init__(self,model,permutations,*,layout=BASELINE):
layout_descriptor(layout)
self.layout = layout
super().__init__(model,permutations,scale_mode='stored_scale',int4_clip=1.,diagnostic=None)
@torch.no_grad()
def reset(self,batch_size=1):
if self.layout == BASELINE:return super().reset(batch_size)
if not self._installed:raise RuntimeError('Install controller before resetting')
if type(batch_size) is not int or batch_size<=0:raise ValueError('Positive integer batch required')
self.clear()
for _ in self._mixers:
conv = torch.zeros(batch_size,4,10240,device=self.device,dtype=torch.float16).transpose(1,2)
state = allocate_state(batch_size,128,64,128,self.device,layout=self.layout)
self._cache.append(_LayerCache(conv=conv,state=state))
self._batch_size = batch_size
return self
def storage_descriptor(self):
# Evidence is computed on demand; no persistent tensor metadata added.
return dict(**layout_descriptor(self.layout),layers=[dict(
state_shape=list(entry.state.shape),state_bytes=entry.state.nbytes,
tensors={key:dict(shape=list(value.shape),dtype=str(value.dtype),
storage_bytes=value.untyped_storage().nbytes()) for key,value in entry.state.tensors.items()},
conv_shape=list(entry.conv.shape),conv_storage_bytes=entry.conv.untyped_storage().nbytes())
for entry in self._cache])
# cache_breakdown is inherited unchanged, including the exact baseline dict.
@torch.no_grad()
def _forward(self, index, mx, u, seqlen, seq_idx, cu_seqlens, inference_params):
if self.layout == BASELINE:
return super()._forward(index,mx,u,seqlen,seq_idx,cu_seqlens,inference_params)
if not self._installed or self._failed:
raise RuntimeError("Controller is inactive or failed; reset before reusing it")
if any(value is not None for value in (seqlen, seq_idx, cu_seqlens, inference_params)):
raise ValueError("Use controller-owned caches; native InferenceParams/varlen are unsupported")
if (not isinstance(u, torch.Tensor) or u.ndim != 3 or u.dtype != torch.float16
or u.device != self.device or u.shape[-1] != 4096 or u.shape[1] == 0):
raise ValueError("Mixer input must be CUDA FP16 [batch,tokens,4096]")
if not self._cache:
if index != 0:
raise RuntimeError("A new request must start with backbone layer zero")
self.reset(u.shape[0])
if u.shape[0] != self._batch_size or index != self._expected_layer:
raise RuntimeError("Batch shape or backbone layer order changed without a reset")
length = u.shape[1]
if index == 0:
if len({entry.tokens for entry in self._cache}) != 1:
raise RuntimeError("Per-layer cache positions disagree; reset the request")
self._call_length = length
elif length != self._call_length:
raise RuntimeError("Backbone layers received different token counts")
entry = self._cache[index]
try:
is_step = entry.tokens > 0 and length == 1
# Native step uses rank-two linear/norm inputs; retain those GEMM
# and adapter-hook shapes as well as its convolution arithmetic.
projected = mx.in_proj(u.squeeze(1) if is_step else u)
zxbcdt = projected.unsqueeze(1) if is_step else projected
z, xbc, dt = torch.split(zxbcdt, (8192, 10240, 128), dim=-1)
xbc = self._convolution(mx, xbc, entry)
x, bm, cm = torch.split(xbc, (8192, 1024, 1024), dim=-1)
batch = u.shape[0]
x = x.reshape(batch, length, 128, 64)
bm = bm.reshape(batch, length, 8, 128)
cm = cm.reshape(batch, length, 8, 128)
aa = -torch.exp(mx.A_log.float())
permutation = None if self.permutations is None else self.permutations[index]
y, stats = scan(x, dt, aa, bm, cm, mx.D, mx.dt_bias, entry.state,
permutation=permutation, layout=self.layout)
if y.dtype != torch.float16 or tuple(y.shape) != (batch, length, 128, 64):
raise RuntimeError("State codec returned unexpected readout dtype/shape")
# Keep this module call intact: the external Resurface norm prehook
# adds its post-D correction here, using the matching mixer input.
y = y.reshape(batch, length, 8192)
if is_step:
y = mx.norm(y[:, 0], z[:, 0])
out = mx.out_proj(y).unsqueeze(1)
else:
y = mx.norm(y, z)
out = mx.out_proj(y)
entry.tokens += length
self._expected_layer = (index + 1) % len(self._mixers)
if self._expected_layer == 0:
self._call_length = None
return out
except Exception:
self._failed = True
raise
|