Voltline's picture
Release vimeml-tiny-zh-v1 (step 844730): FP32 bundle, INT8 Core ML packages, source snapshot (part 2)
99297d7 verified
Raw History Blame Contribute Delete
4.4 kB
"""vimeml-tiny-zh-v1 decoder: the V4 block family with two registered shapes.
d384, 6 heads, SwiGLU 960, learned positions 128, tied embeddings; only the
vocabulary and layer count vary, fixed to the 8K/L10 and 12K/L9 pairs of
docs/zh-CN/training-zh-v1-plan.md.
"""
import math
from dataclasses import dataclass
import torch
from vimeml.training.model_v2 import GPTV2Config, TinyGPTV2
LAYER_PARAMETERS = 1_696_512
# vocab_size -> (n_layers, total parameters)
SHAPES = {8192: (10, 20_160_384), 12288: (9, 20_036_736)}
@dataclass(frozen=True)
class GPTZh13Config(GPTV2Config):
vocab_size: int = 12288
context_length: int = 128
d_model: int = 384
n_heads: int = 6
n_layers: int = 9
d_ff: int = 960
dropout: float = 0.0
def __post_init__(self):
super().__post_init__()
if self.vocab_size not in SHAPES or self.n_layers != SHAPES[self.vocab_size][0]:
raise ValueError("vimeml-tiny-zh-v1 shapes are 8192/L10 and 12288/L9")
fixed = {"context_length": 128, "d_model": 384, "n_heads": 6, "d_ff": 960, "dropout": 0.0}
for name, value in fixed.items():
if type(getattr(self, name)) is not type(value) or getattr(self, name) != value:
raise ValueError(f"vimeml-tiny-zh-v1 {name} is fixed to {value}")
@dataclass
class KVCacheZh13:
key: torch.Tensor
value: torch.Tensor
length: int = 0
def clone(self):
return KVCacheZh13(self.key.clone(), self.value.clone(), self.length)
class TinyGPTZh13(TinyGPTV2):
def __init__(self, config=GPTZh13Config()):
if type(config) is not GPTZh13Config:
raise ValueError("vimeml-tiny-zh-v1 requires GPTZh13Config")
super().__init__(config)
if self.parameter_count() != SHAPES[config.vocab_size][1] or any(
sum(p.numel() for p in block.parameters()) != LAYER_PARAMETERS for block in self.blocks):
raise ValueError("vimeml-tiny-zh-v1 parameter budget does not match its contract")
def new_cache(self, batch_size=1):
if type(batch_size) is not int or batch_size < 1:
raise ValueError("Positive integer cache batch required")
shape = (self.config.n_layers, batch_size, 6, 128, 64)
device = self.token_embedding.weight.device
return KVCacheZh13(torch.zeros(shape, device=device), torch.zeros(shape, device=device))
@torch.inference_mode()
def forward_cached(self, input_ids, cache):
"""FP32 research KV path; matches forward() on the same prefix."""
if self.training or self.token_embedding.weight.dtype != torch.float32:
raise ValueError("KV path requires eval mode and FP32 weights")
if input_ids.ndim != 2 or input_ids.dtype not in (torch.int32, torch.int64):
raise ValueError("Expected integer [batch,time] tokens")
batch, size = input_ids.shape
if (cache.key.shape != (self.config.n_layers, batch, 6, 128, 64) or size < 1
or cache.length + size > 128):
raise ValueError("Invalid KV cache or context overflow")
if int(input_ids.min()) < 0 or int(input_ids.max()) >= self.config.vocab_size:
raise ValueError("Token outside vocabulary")
start, end = cache.length, cache.length + size
positions = torch.arange(start, end, device=input_ids.device)
hidden = self.token_embedding(input_ids) + self.position_embedding(positions)
causal = positions[:, None] >= torch.arange(end, device=input_ids.device)[None, :]
for layer, block in enumerate(self.blocks):
qkv = block.attention.qkv(block.attention_norm(hidden)).reshape(batch, size, 3, 6, 64)
query, key, value = qkv.permute(2, 0, 3, 1, 4).unbind(0)
cache.key[layer, :, :, start:end].copy_(key)
cache.value[layer, :, :, start:end].copy_(value)
scores = torch.matmul(query, cache.key[layer, :, :, :end].transpose(-2, -1)) / math.sqrt(64)
attended = torch.matmul(torch.softmax(scores.masked_fill(~causal, -torch.inf), dim=-1),
cache.value[layer, :, :, :end])
hidden = hidden + block.attention.projection(attended.transpose(1, 2).reshape(batch, size, 384))
hidden = hidden + block.ffn(block.ffn_norm(hidden))
cache.length = end
return self.lm_head(self.final_norm(hidden))