Voltline's picture
Release VimeML V4.2 epoch20 FP32 and Core ML INT8 (GPL-2.0)
c83c617 verified
Raw History Blame Contribute Delete
2.86 kB
"""V4 functional single-token KV graph and local TorchScript validation."""
import math
import torch
from torch import nn
from vimeml.deployment.graph import trace_graph
CACHE_SHAPE = (7, 1, 6, 128, 64)
class KVStepGraphV4(nn.Module):
"""Explicit functional FP32 buffers; caller owns snapshots and rollback."""
def __init__(self, model):
super().__init__()
self.model = model.eval()
self.register_buffer("slots", torch.arange(128))
def forward(self, input_ids, position, key_cache, value_cache):
position = position.to(torch.int64)
hidden = self.model.token_embedding(input_ids) + self.model.position_embedding(position).unsqueeze(0)
insert = (self.slots == position[0]).reshape(1, 1, 128, 1)
valid = (self.slots <= position[0]).reshape(1, 1, 1, 128)
keys, values = [], []
for layer, block in enumerate(self.model.blocks):
qkv = block.attention.qkv(block.attention_norm(hidden)).reshape(1, 1, 3, 6, 64)
query, key, value = qkv.permute(2, 0, 3, 1, 4).unbind(0)
updated_key = torch.where(insert, key, key_cache[layer])
updated_value = torch.where(insert, value, value_cache[layer])
scores = torch.matmul(query, updated_key.transpose(-2, -1)) / math.sqrt(64)
probabilities = torch.softmax(scores.masked_fill(~valid, -torch.inf), dim=-1)
attended = torch.matmul(probabilities, updated_value).transpose(1, 2).reshape(1, 1, 384)
hidden = hidden + block.attention.projection(attended)
hidden = hidden + block.ffn(block.ffn_norm(hidden))
keys.append(updated_key)
values.append(updated_value)
return self.model.lm_head(self.model.final_norm(hidden)), torch.stack(keys), torch.stack(values)
@torch.inference_mode()
def trace_kv_graph(model):
graph = KVStepGraphV4(model).eval()
args = (torch.tensor([[2]], dtype=torch.int32), torch.tensor([0], dtype=torch.int32),
torch.zeros(CACHE_SHAPE), torch.zeros(CACHE_SHAPE))
traced = torch.jit.trace(graph, args)
# Different positions from the tracing example, including last cache slot.
ids = torch.randint(7, 8192, (1, 128), generator=torch.Generator().manual_seed(940))
cache = model.new_cache()
for position in (0, 7, 127):
if position > cache.length:
model.forward_cached(ids[:, cache.length:position], cache)
key, value = cache.key.clone(), cache.value.clone()
token = ids[:, position:position + 1].int()
reference = model.forward_cached(token, cache)
actual = traced(token, torch.tensor([position], dtype=torch.int32), key, value)
for a, b in zip(actual, (reference, cache.key, cache.value), strict=True):
torch.testing.assert_close(a, b, atol=3e-4, rtol=3e-4)
return traced