Anole / chameleon /inference /transformer.py
xuefengli
update
7362797
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the Chameleon License found in the
# LICENSE file in the root directory of this source tree.
from dataclasses import dataclass
import torch
from torch import distributed as dist
from torch import nn
from torch.nn import functional as F
from xformers.ops import RMSNorm, fmha, rope_padded
from xformers.ops.fmha.attn_bias import (
BlockDiagonalCausalWithOffsetPaddedKeysMask as AttnBias,
)
@dataclass
class ModelArgs:
model_parallel_size: int = 1
dim: int = 512
n_layers: int = 8
n_heads: int = 8
n_kv_heads: int | None = None
vocab_size: int = -1
ffn_dim_multiplier: float | None = None
multiple_of: int = 256
norm_eps: float = 1e-5
rope_theta: float = 10000.0
qk_normalization: bool = False
swin_norm: bool = False
LayerCache = tuple[torch.Tensor, torch.Tensor]
class Attention(nn.Module):
def __init__(
self,
model_parallel_size: int,
dim: int,
head_dim: int,
n_heads: int,
n_kv_heads: int,
rope_theta: float,
qk_normalization: bool = False,
):
super().__init__()
self.model_parallel_size = model_parallel_size
self.head_dim = head_dim
self.rope_theta = rope_theta
self.n_local_heads = n_heads // model_parallel_size
self.n_local_kv_heads = n_kv_heads // model_parallel_size
self.wqkv = nn.Linear(
dim,
(self.n_local_heads + 2 * self.n_local_kv_heads) * head_dim,
bias=False,
dtype=torch.bfloat16,
)
self.wo = nn.Linear(
self.n_local_heads * head_dim,
dim,
bias=False,
dtype=torch.bfloat16,
)
self.qk_normalization = qk_normalization
if qk_normalization:
self.q_normalization = torch.nn.LayerNorm(head_dim)
self.k_normalization = torch.nn.LayerNorm(head_dim)
self._register_load_state_dict_pre_hook(self.load_hook)
# This adapter makes sure we can load vanilla
# Llama checkpoints where wq, wk, and wv are
# not fused in a single parameter
def load_hook(
self,
state_dict,
prefix,
local_metadata,
strict,
missing_keys,
unexpected_keys,
error_msgs,
):
if prefix + "wq.weight" in state_dict:
wq = state_dict.pop(prefix + "wq.weight")
wk = state_dict.pop(prefix + "wk.weight")
wv = state_dict.pop(prefix + "wv.weight")
state_dict[prefix + "wqkv.weight"] = torch.cat([wq, wk, wv])
def forward(
self,
x: torch.Tensor,
cache: LayerCache,
attn_bias: AttnBias,
group: dist.ProcessGroup | None = None,
) -> torch.Tensor:
# x.shape is (sum(seq_lens), dim)
#
# Since we support heterogenous sequence
# lengths, the hidden states are all
# concatenated together along the usual
# sequence dimension. The attention below
# finds out where sequences start & end
# using the provided attention bias.
xqkv = self.wqkv(x)
xq = xqkv[:, : (self.n_local_heads * self.head_dim)]
xkv = xqkv[:, (self.n_local_heads * self.head_dim) :]
xk, xv = xkv.chunk(2, 1)
if self.qk_normalization:
xq = xq.view(-1, self.n_local_heads, self.head_dim)
xq = self.q_normalization(xq)
xq = xq.view(-1, self.n_local_heads * self.head_dim)
xk = xk.view(-1, self.n_local_kv_heads, self.head_dim)
xk = self.k_normalization(xk)
xk = xk.view(-1, self.n_local_kv_heads * self.head_dim)
output_shape = xq.shape
xq = xq.view(1, xq.shape[0], self.n_local_heads, self.head_dim)
xk = xk.view(1, xk.shape[0], self.n_local_kv_heads, self.head_dim)
xv = xv.view(1, xv.shape[0], self.n_local_kv_heads, self.head_dim)
cache_k, cache_v = cache
xq = rope_padded(
xq=xq,
xk=xk,
xv=xv,
cache_k=cache_k,
cache_v=cache_v,
attn_bias=attn_bias,
theta=self.rope_theta,
)
# Handle GQA
# Q shape: [B, M, Hkv, Hq // Hkv, K]
heads_per_group = self.n_local_heads // self.n_local_kv_heads
cache_k = cache_k.unsqueeze(3).expand(-1, -1, -1, heads_per_group, -1)
cache_v = cache_v.unsqueeze(3).expand(-1, -1, -1, heads_per_group, -1)
xq = xq.reshape(
[*xq.shape[:2], self.n_local_kv_heads, heads_per_group, xq.shape[-1]]
)
# rope_padded() updated the caches, so we
# call attention directly
output = fmha.memory_efficient_attention_forward(
xq, cache_k, cache_v, attn_bias
)
output = self.wo(output.reshape(output_shape))
if self.model_parallel_size > 1:
dist.all_reduce(output, group=group)
return output
class FeedForward(nn.Module):
def __init__(
self,
model_parallel_size: int,
dim: int,
hidden_dim: int,
multiple_of: int,
ffn_dim_multiplier: float | None,
):
super().__init__()
self.model_parallel_size = model_parallel_size
hidden_dim = int(2 * hidden_dim / 3)
if ffn_dim_multiplier is not None:
hidden_dim = int(ffn_dim_multiplier * hidden_dim)
hidden_dim = multiple_of * ((hidden_dim + multiple_of - 1) // multiple_of)
assert hidden_dim % model_parallel_size == 0
self.w13 = nn.Linear(
dim,
2 * hidden_dim // model_parallel_size,
bias=False,
)
self.w2 = nn.Linear(
hidden_dim // model_parallel_size,
dim,
bias=False,
)
self._register_load_state_dict_pre_hook(self.load_hook)
# This adapter makes sure we can load vanilla
# Llama checkpoints where w1 and w3 are not
# fused in a single parameter
def load_hook(
self,
state_dict,
prefix,
local_metadata,
strict,
missing_keys,
unexpected_keys,
error_msgs,
):
if prefix + "w1.weight" in state_dict:
w1 = state_dict.pop(prefix + "w1.weight")
w3 = state_dict.pop(prefix + "w3.weight")
state_dict[prefix + "w13.weight"] = torch.cat([w1, w3])
def forward(
self, x: torch.Tensor, group: dist.ProcessGroup | None = None
) -> torch.Tensor:
x13 = self.w13(x)
x1, x3 = x13.chunk(2, -1)
output = self.w2(F.silu(x1) * x3)
if self.model_parallel_size > 1:
dist.all_reduce(output, group=group)
return output
class TransformerBlock(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
assert args.dim % args.n_heads == 0
head_dim = args.dim // args.n_heads
if args.n_kv_heads is not None:
n_kv_heads = args.n_kv_heads
else:
n_kv_heads = args.n_heads
model_parallel_size = args.model_parallel_size
assert args.n_heads % n_kv_heads == 0
assert args.n_heads % model_parallel_size == 0
assert n_kv_heads % model_parallel_size == 0
self.attention = Attention(
model_parallel_size=model_parallel_size,
dim=args.dim,
head_dim=head_dim,
n_heads=args.n_heads,
n_kv_heads=n_kv_heads,
rope_theta=args.rope_theta,
qk_normalization=args.qk_normalization,
)
self.feed_forward = FeedForward(
model_parallel_size=model_parallel_size,
dim=args.dim,
hidden_dim=4 * args.dim,
multiple_of=args.multiple_of,
ffn_dim_multiplier=args.ffn_dim_multiplier,
)
self.attention_norm = RMSNorm(args.dim, eps=args.norm_eps)
self.ffn_norm = RMSNorm(args.dim, eps=args.norm_eps)
self.swin_norm = args.swin_norm
def forward(
self,
x: torch.Tensor,
cache: LayerCache,
attn_bias: AttnBias,
group: dist.ProcessGroup | None = None,
) -> torch.Tensor:
if self.swin_norm:
h = x + self.attention_norm(
self.attention.forward(
x,
cache,
attn_bias,
group=group,
)
)
out = h + self.ffn_norm(self.feed_forward(h, group=group))
else:
h = x + self.attention.forward(
self.attention_norm(x),
cache,
attn_bias,
group=group,
)
out = h + self.feed_forward(self.ffn_norm(h), group=group)
return out
class Transformer(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.args = args
self.model_parallel_size = args.model_parallel_size
assert args.dim % self.model_parallel_size == 0
assert args.vocab_size > 0
assert args.vocab_size % self.model_parallel_size == 0
self.tok_embeddings = nn.Embedding(
num_embeddings=args.vocab_size,
embedding_dim=args.dim // self.model_parallel_size,
)
self.layers = nn.ModuleList()
for _ in range(args.n_layers):
self.layers.append(TransformerBlock(args))
self.norm = RMSNorm(args.dim, eps=args.norm_eps)
self.output = nn.Linear(
args.dim,
args.vocab_size // self.model_parallel_size,
bias=False,
)
@torch.no_grad()
def forward_with_attn_bias(
self,
token_values: torch.Tensor,
attn_bias: AttnBias,
cache: list[LayerCache],
group: dist.ProcessGroup | None = None,
) -> torch.Tensor:
h = self.tok_embeddings(token_values)
if self.model_parallel_size > 1:
gather = [torch.empty_like(h) for _ in range(self.model_parallel_size)]
dist.all_gather(gather, h, group=group)
h = torch.cat(gather, dim=-1)
for i, layer in enumerate(self.layers):
h = layer(h, cache[i], attn_bias, group=group)
logits = self.output(self.norm(h))
if self.model_parallel_size > 1:
gather = [torch.empty_like(logits) for _ in range(self.model_parallel_size)]
dist.all_gather(gather, logits, group=group)
logits = torch.cat(gather, dim=-1)
return logits.float()
def forward(
self,
token_values: torch.Tensor,
token_lengths: torch.Tensor,
start_pos: torch.Tensor,
cache: list[LayerCache],
kv_padding: int,
group: dist.ProcessGroup | None = None,
) -> torch.Tensor:
attn_bias = AttnBias.from_seqlens(
q_seqlen=token_lengths.tolist(),
kv_seqlen=(start_pos + token_lengths).tolist(),
kv_padding=kv_padding,
)
return self.forward_with_attn_bias(token_values, attn_bias, cache, group=group)
def make_cache(
args: ModelArgs,
length: int,
device: str | torch.device | None = None,
n_layers: int | None = None,
dtype: torch.dtype | None = None,
) -> list[LayerCache]:
"""
Allocate a cache to be used with the Transformer module.
Args:
args (ModelArgs): the model configuration.
length (int): per layer cache size.
It is usually budgeted as ``max_batch * max_seq``
device (torch.device, optional): the device on which
the cache should be allocated.
n_layers (int, optional): the number of layers to
allocate a cache for (defaults to the model
settings).
dtype (torch.dtype, optional): the dtype to use for
cache entries (defaults to the default dtype).
Returns:
The cache object to pass to ``Tranformer.forward``.
"""
head_dim = args.dim // args.n_heads
n_kv_heads = args.n_kv_heads
if n_kv_heads is None:
n_kv_heads = args.n_heads
n_local_kv_heads = n_kv_heads // args.model_parallel_size
if n_layers is None:
n_layers = args.n_layers
shape = (1, length, n_local_kv_heads, head_dim)
return [
(
torch.zeros(shape, device=device, dtype=dtype),
torch.zeros(shape, device=device, dtype=dtype),
)
for _ in range(n_layers)
]
def cache_prefix(cache: list[LayerCache], length: int) -> list[LayerCache]:
"""
Take a prefix view of a larger cache.
The original cache object remains of identical size and valid
after the shrinked alias has been used. This function is useful
when a cache was allocated for a larger batch size than what is
necessary.
Args:
cache: the cache to take a view in.
length (int): the desired length
Returns:
A view in the input cache object.
"""
if len(cache) > 0:
assert cache[0][0].shape[1] >= length
return [(ck[:, :length], cv[:, :length]) for ck, cv in cache]