GQA

GQA

MQA

In [12]:
import torch
from torch import nn
from torch.nn import attention
import time

# 设置随机种子
torch.manual_seed(42)


MAX_TOKEN_LENGTH = 2048
class MultiQueryAttention(nn.Module):
    def __init__(self, embed_dim, num_heads):
        super(MultiQueryAttention, self).__init__()
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.head_dim = embed_dim // num_heads

        assert (
            self.head_dim * num_heads == self.embed_dim
        ), "embed_dim must be divisible by num_heads"

        self.q_proj = nn.Linear(embed_dim, embed_dim)
        self.k_proj = nn.Linear(embed_dim, self.head_dim)
        self.v_proj = nn.Linear(embed_dim, self.head_dim)
        self.out_proj = nn.Linear(embed_dim, embed_dim)
        
        self.register_buffer('attention_mask', torch.tril(torch.ones(MAX_TOKEN_LENGTH, MAX_TOKEN_LENGTH)))
        
        self.o_proj = nn.Linear(embed_dim, embed_dim)
        
    def forward(self, x, mask=None):
        batch_size, seq_len , _ = x.size()
        q = self.q_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
        
        k = self.k_proj(x).view(batch_size,  seq_len,  1, self.head_dim)
        k = k.transpose(1, 2)
        v = self.v_proj(x).view(batch_size,   seq_len, 1, self.head_dim)
        v = v.transpose(1, 2)
        
        attention_weight = torch.matmul(q, k.transpose(-2, -1)) / (self.head_dim ** 0.5)
        # print('attention_weight shape:', attention_weight)
        
        if mask is not None:
            attention_mask_temp = self.attention_mask[:seq_len, :seq_len]
            attention_weight = attention_weight.masked_fill(attention_mask_temp == 0, float('-inf'))
            
        attention_probs = nn.Softmax(dim=-1)(attention_weight)
        attention_output = torch.matmul(attention_probs, v)
        attention_output = attention_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.embed_dim)
        
        return self.out_proj(attention_output)
    
    
class MultiQueryAttentionEinsum(nn.Module):
    def __init__(self, embed_dim, num_heads):
        super(MultiQueryAttentionEinsum, self).__init__()
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.head_dim = embed_dim // num_heads

        assert (
            self.head_dim * num_heads == self.embed_dim
        ), "embed_dim must be divisible by num_heads"

        self.q_proj = nn.Linear(embed_dim, embed_dim)
        self.k_proj = nn.Linear(embed_dim, self.head_dim)
        self.v_proj = nn.Linear(embed_dim, self.head_dim)
        self.out_proj = nn.Linear(embed_dim, embed_dim)
        
        self.register_buffer('attention_mask', torch.tril(torch.ones(MAX_TOKEN_LENGTH, MAX_TOKEN_LENGTH)))
        
        self.o_proj = nn.Linear(embed_dim, embed_dim)
    def forward_einsum(self, x, mask=None):
        batch_size, seq_len , _ = x.size()
        q = self.q_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim)
        
        k = self.k_proj(x)
        # k = k.transpose(1, 2)
        v = self.v_proj(x)
        
        attention_weight = torch.einsum('bihj,bmj->bhim',q,k) / (self.head_dim ** 0.5)
        attention_weight = attention_weight[:, 0, :, :]
        # print('attention_weight shape (einsum):', attention_weight)
        if mask is not None:
            attention_mask_temp = self.attention_mask[:seq_len, :seq_len]
            attention_weight = attention_weight.masked_fill(attention_mask_temp == 0, float('-inf'))
            
        attention_probs = nn.Softmax(dim=-1)(attention_weight)
        # attention_output = torch.matmul(attention_probs, v)
        attention_output = torch.einsum('bkj,bjn->bkn',attention_probs , v)
        
        attention_output = attention_output.view(batch_size, seq_len, 1, self.head_dim)
        
        # Expand to num_heads
        attention_output = attention_output.expand(-1, -1, self.num_heads, -1)
        
        attention_output = attention_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.embed_dim)
        
        
        return self.out_proj(attention_output)
    
    
    # 测试代码
batch_size = 64
seq_len = 512
d_model = 128
num_heads = 8  # 8个Query头,但只有1个KV头

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
model = MultiQueryAttention(d_model, num_heads).to(device)
model_einsum = MultiQueryAttentionEinsum(d_model, num_heads).to(device)
x = torch.randn(batch_size, seq_len, d_model).to(device)

print(f"Input shape: {x.shape}")

# Test standard forward method
print("model forward",model.forward(x))
print("einsum model forward",model_einsum.forward_einsum(x))


# Run each forward 10 times and measure times
n_runs = 10
model_times = []
for i in range(n_runs):
    if device.type == "cuda":
        torch.cuda.synchronize()
    t0 = time.time()
    output = model(x)
    if device.type == "cuda":
        torch.cuda.synchronize()
    model_times.append(time.time() - t0)

einsum_times = []
for i in range(n_runs):
    if device.type == "cuda":
        torch.cuda.synchronize()
    t0 = time.time()
    einsum_output = model_einsum.forward_einsum(x)
    if device.type == "cuda":
        torch.cuda.synchronize()
    einsum_times.append(time.time() - t0)

mean_model = sum(model_times) / n_runs
mean_einsum = sum(einsum_times) / n_runs

print(f"Output shape: {output.shape}")
print(f"Einsum output shape: {einsum_output.shape}")

print(f"\nModel times (s): {model_times}")
print(f"Einsum times (s): {einsum_times}")

print(f"\nModel mean time: {mean_model:.6f} s")
print(f"Einsum mean time: {mean_einsum:.6f} s")
print(f"Speedup (model / einsum): {mean_model/mean_einsum:.2f}x" if mean_einsum > 0 else "N/A")
Using device: cuda
Input shape: torch.Size([64, 512, 128])
model forward tensor([[[-0.0417, -0.0962, -0.0195,  ..., -0.0249, -0.0060, -0.0187],
         [-0.0471, -0.1022, -0.0412,  ..., -0.0801,  0.0117, -0.0393],
         [-0.0484, -0.0960, -0.0345,  ..., -0.0425, -0.0073, -0.0249],
         ...,
         [-0.0580, -0.0948, -0.0441,  ..., -0.0263,  0.0051, -0.0031],
         [-0.0718, -0.0940, -0.0404,  ..., -0.0493,  0.0116, -0.0260],
         [-0.0312, -0.0954, -0.0391,  ..., -0.0588, -0.0098, -0.0116]],

        [[-0.0562, -0.0910, -0.0749,  ..., -0.0770,  0.0107, -0.0125],
         [-0.0430, -0.0805, -0.0535,  ..., -0.0658,  0.0152,  0.0030],
         [-0.0422, -0.0873, -0.0786,  ..., -0.1111,  0.0145, -0.0119],
         ...,
         [-0.0416, -0.0841, -0.0837,  ..., -0.0761,  0.0052,  0.0024],
         [-0.0264, -0.0930, -0.0561,  ..., -0.0871,  0.0264, -0.0075],
         [-0.0439, -0.0709, -0.0657,  ..., -0.0726,  0.0295, -0.0067]],

        [[-0.0554, -0.1095, -0.0277,  ..., -0.0255,  0.0077, -0.0318],
         [-0.0275, -0.1266, -0.0289,  ..., -0.0417,  0.0115, -0.0168],
         [-0.0436, -0.1247, -0.0599,  ..., -0.0223,  0.0193, -0.0083],
         ...,
         [-0.0270, -0.1196, -0.0538,  ..., -0.0417,  0.0292, -0.0168],
         [-0.0351, -0.1196, -0.0495,  ..., -0.0440,  0.0300, -0.0148],
         [-0.0259, -0.1140, -0.0422,  ..., -0.0397,  0.0154, -0.0197]],

        ...,

        [[-0.0297, -0.0617, -0.0395,  ..., -0.0279,  0.0497, -0.0118],
         [-0.0454, -0.0820, -0.0576,  ..., -0.0592,  0.0440,  0.0046],
         [-0.0351, -0.0853, -0.0594,  ..., -0.0322,  0.0520,  0.0035],
         ...,
         [-0.0285, -0.0894, -0.0543,  ..., -0.0307,  0.0741,  0.0015],
         [-0.0481, -0.0888, -0.0586,  ..., -0.0383,  0.0639, -0.0208],
         [-0.0450, -0.0932, -0.0591,  ..., -0.0363,  0.0454,  0.0020]],

        [[-0.0407, -0.1031, -0.0104,  ..., -0.0181, -0.0083, -0.0064],
         [-0.0602, -0.0932, -0.0337,  ..., -0.0155,  0.0132, -0.0291],
         [-0.0214, -0.1273, -0.0250,  ..., -0.0361, -0.0056,  0.0151],
         ...,
         [-0.0449, -0.1218, -0.0115,  ..., -0.0474,  0.0083, -0.0158],
         [-0.0266, -0.1260, -0.0251,  ..., -0.0339, -0.0089, -0.0033],
         [-0.0343, -0.0926,  0.0147,  ..., -0.0480, -0.0004, -0.0057]],

        [[-0.0321, -0.1097, -0.0702,  ..., -0.0439,  0.0069, -0.0204],
         [-0.0552, -0.0954, -0.0549,  ..., -0.0421,  0.0334, -0.0236],
         [-0.0601, -0.0963, -0.0623,  ..., -0.0521,  0.0027, -0.0343],
         ...,
         [-0.0341, -0.1263, -0.0566,  ..., -0.0462,  0.0235, -0.0303],
         [-0.0684, -0.0850, -0.0236,  ..., -0.0378,  0.0238, -0.0276],
         [-0.0454, -0.1015, -0.0509,  ..., -0.0396, -0.0157, -0.0296]]],
       device='cuda:0', grad_fn=<ViewBackward0>)
einsum model forward tensor([[[-0.0606,  0.0606, -0.0435,  ...,  0.0282, -0.0598,  0.0774],
         [-0.0384,  0.0515, -0.0125,  ...,  0.0301, -0.0773,  0.0499],
         [-0.0380,  0.0602, -0.0298,  ...,  0.0099, -0.0611,  0.0686],
         ...,
         [-0.0572,  0.0625, -0.0295,  ...,  0.0408, -0.0764,  0.0610],
         [-0.0554,  0.0592, -0.0044,  ...,  0.0390, -0.0748,  0.0536],
         [-0.0389,  0.0641, -0.0400,  ...,  0.0327, -0.0571,  0.0554]],

        [[-0.0200,  0.0861, -0.0398,  ...,  0.0378, -0.0788,  0.0536],
         [-0.0116,  0.0666, -0.0109,  ...,  0.0458, -0.0891,  0.0587],
         [-0.0426,  0.0876, -0.0152,  ...,  0.0263, -0.0666,  0.0596],
         ...,
         [-0.0396,  0.0583, -0.0385,  ...,  0.0473, -0.0888,  0.0752],
         [-0.0329,  0.0712, -0.0136,  ...,  0.0291, -0.0859,  0.0494],
         [-0.0575,  0.0592, -0.0247,  ...,  0.0423, -0.0808,  0.0542]],

        [[-0.0106,  0.0787, -0.0196,  ...,  0.0371, -0.0747,  0.0429],
         [-0.0370,  0.0590, -0.0354,  ...,  0.0425, -0.0754,  0.0491],
         [-0.0521,  0.0618, -0.0338,  ...,  0.0503, -0.0808,  0.0462],
         ...,
         [-0.0468,  0.0736, -0.0288,  ...,  0.0407, -0.0857,  0.0282],
         [-0.0233,  0.0747, -0.0418,  ...,  0.0553, -0.0840,  0.0676],
         [-0.0457,  0.0731, -0.0345,  ...,  0.0530, -0.0796,  0.0500]],

        ...,

        [[-0.0518,  0.0606, -0.0227,  ...,  0.0547, -0.0992,  0.0556],
         [-0.0343,  0.0334,  0.0013,  ...,  0.0657, -0.0972,  0.0575],
         [-0.0427,  0.0353, -0.0081,  ...,  0.0525, -0.0801,  0.0619],
         ...,
         [-0.0192,  0.0480, -0.0048,  ...,  0.0746, -0.1112,  0.0480],
         [-0.0429,  0.0479, -0.0173,  ...,  0.0266, -0.0893,  0.0646],
         [-0.0237,  0.0477,  0.0083,  ...,  0.0379, -0.0978,  0.0781]],

        [[-0.0400,  0.0494, -0.0750,  ...,  0.0589, -0.0970,  0.0771],
         [-0.0669,  0.0548, -0.0726,  ...,  0.0529, -0.0872,  0.0773],
         [-0.0466,  0.0433, -0.0520,  ...,  0.0606, -0.1065,  0.0523],
         ...,
         [-0.0666,  0.0439, -0.0675,  ...,  0.0577, -0.0959,  0.0845],
         [-0.0501,  0.0471, -0.0574,  ...,  0.0562, -0.0889,  0.0679],
         [-0.0364,  0.0528, -0.0723,  ...,  0.0416, -0.0863,  0.0725]],

        [[-0.0628,  0.0646, -0.0674,  ...,  0.0628, -0.0718,  0.0450],
         [-0.0728,  0.0650, -0.0699,  ...,  0.0710, -0.0654,  0.0540],
         [-0.0528,  0.0536, -0.0666,  ...,  0.0572, -0.0739,  0.0489],
         ...,
         [-0.0573,  0.0590, -0.0828,  ...,  0.0728, -0.0755,  0.0660],
         [-0.0672,  0.0604, -0.0826,  ...,  0.0566, -0.0688,  0.0472],
         [-0.0628,  0.0606, -0.0767,  ...,  0.0652, -0.0734,  0.0457]]],
       device='cuda:0', grad_fn=<ViewBackward0>)
Output shape: torch.Size([64, 512, 128])
Einsum output shape: torch.Size([64, 512, 128])

Model times (s): [0.018999814987182617, 0.021999120712280273, 0.020002126693725586, 0.019997835159301758, 0.019999980926513672, 0.01900005340576172, 0.02200007438659668, 0.018999814987182617, 0.019001483917236328, 0.020000219345092773]
Einsum times (s): [0.01000070571899414, 0.010999917984008789, 0.012997865676879883, 0.011000394821166992, 0.009999990463256836, 0.0110015869140625, 0.011557817459106445, 0.009998798370361328, 0.01100015640258789, 0.012001276016235352]

Model mean time: 0.020000 s
Einsum mean time: 0.011056 s
Speedup (model / einsum): 1.81x

GQA

In [26]:
from networkx.algorithms.centrality import group
from openai.types import batch
from regex import P


MAX_TOKEN_LENGTH = 2048
class GroupQueryAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, group_size):
        super(GroupQueryAttention, self).__init__()
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.head_dim = embed_dim // num_heads
        self.group_size = group_size
        
        
        self.num_kv_heads = group_size
        # 计算每组有多少个 Query 头 (Group Size)
        self.num_heads_per_group = num_heads // self.num_kv_heads 
        
        assert (
            self.head_dim * num_heads == self.embed_dim
        ), "embed_dim must be divisible by num_heads"

        self.q_proj = nn.Linear(embed_dim, embed_dim)
        self.k_proj = nn.Linear(embed_dim, self.head_dim*group_size)
        self.v_proj = nn.Linear(embed_dim, self.head_dim*group_size)
        self.out_proj = nn.Linear(embed_dim, embed_dim)
        
        self.register_buffer('attention_mask', torch.tril(torch.ones(MAX_TOKEN_LENGTH, MAX_TOKEN_LENGTH)))
        
        self.o_proj = nn.Linear(embed_dim, embed_dim)
        
    def forward(self, x, mask=None):
        batch_size, seq_len , _ = x.size()
        num_heads_every_group = self.num_heads // self.group_size
        q = self.q_proj(x).view(batch_size, seq_len, self.group_size, num_heads_every_group, self.head_dim)
        
        k = self.k_proj(x).view(batch_size,  seq_len,  self.group_size, self.head_dim)
        v = self.v_proj(x).view(batch_size,  seq_len, self.group_size, self.head_dim)
        
        attention_weight = torch.einsum('bignd,bjgd->bgnij', q, k) / (self.head_dim ** 0.5)
        # print('attention_weight shape:', attention_weight)
        
        if mask is not None:
            attention_mask_temp = self.attention_mask[:seq_len, :seq_len]
            attention_weight = attention_weight.masked_fill(attention_mask_temp == 0, float('-inf'))
            
        attention_probs = nn.Softmax(dim=-1)(attention_weight)
        attention_output = torch.einsum('bgnij,bjgd->bgnid',attention_probs , v)
        # print("attention_output: ",attention_output)
        attention_output = attention_output.view(batch_size, self.num_heads,  seq_len, self.head_dim).transpose(1,2)
        attention_output = attention_output.contiguous().view(batch_size, seq_len, self.num_heads*self.head_dim)
        
        # attention_output = attention_output.reshape(batch_size, seq_len, self.embed_dim)
        
        return self.out_proj(attention_output)
    
    def forward_gemini(self, x, mask=None):
        batch_size, seq_len, _ = x.size()
        
        # 1. 投影并重塑维度
        # Q: [Batch, Seq, Num_KV_Heads, Group_Size, Head_Dim]
        # g: num_kv_heads (组数)
        # r: num_heads_per_group (每组内的重复次数)
        q = self.q_proj(x).view(batch_size, seq_len, self.num_kv_heads, self.num_heads_per_group, self.head_dim)
        
        # K, V: [Batch, Seq, Num_KV_Heads, Head_Dim]
        # 相比 MQA,这里多了 'g' 维度,但没有 'r' 维度
        k = self.k_proj(x).view(batch_size, seq_len, self.num_kv_heads, self.head_dim)
        v = self.v_proj(x).view(batch_size, seq_len, self.num_kv_heads, self.head_dim)
        
        # 2. 计算 Attention Scores
        # Einsum 逻辑:
        # b: batch
        # l: query seq len
        # s: key seq len
        # g: num_kv_heads (groups) -> Q 和 K 在此维度必须对齐
        # r: num_heads_per_group  -> K 在此维度广播 (K 没有 r 维度)
        # d: head_dim
        # Q(b,l,g,r,d) @ K(b,s,g,d) -> Scores(b,g,r,l,s)
        attention_weight = torch.einsum('blgrd, bsgd -> bgrls', q, k) / (self.head_dim ** 0.5)
        # print('attention_weight shape:', attention_weight)
        
        # 3. Masking
        if mask is not None:
            # Mask 形状通常是 [Seq, Seq]
            attention_mask_temp = self.attention_mask[:seq_len, :seq_len]
            # 自动广播 mask 到 [Batch, Groups, Group_Size, Seq, Seq]
            attention_weight = attention_weight.masked_fill(attention_mask_temp == 0, float('-inf'))
            
        attention_probs = nn.Softmax(dim=-1)(attention_weight)
        
        # 4. 计算加权和
        # Scores(b,g,r,l,s) @ V(b,s,g,d) -> Output(b,l,g,r,d)
        # 同样,V 在 r 维度上被广播
        attention_output = torch.einsum('bgrls, bsgd -> blgrd', attention_probs, v)
        # print("attention_output: ",attention_output)
        # 5. 合并维度
        # [Batch, Seq, Groups, Group_Size, Head_Dim] -> [Batch, Seq, Num_Heads, Head_Dim] -> [Batch, Seq, Embed_Dim]
        attention_output = attention_output.reshape(batch_size, seq_len, self.embed_dim)
        
        return self.out_proj(attention_output)
    
    # 测试代码
batch_size = 64
seq_len = 512
d_model = 128
num_heads = 8  # 8个Query头,但只有1个KV头
group_size = 2

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
model = GroupQueryAttention(d_model, num_heads, group_size).to(device)

model_output = model(x)
print(f"Group Query Attention output shape: {model_output[0,0,0]}")

forward_gemini_output = model.forward_gemini(x)
print(f"Group Query Attention Gemini output shape: {forward_gemini_output[0,0,0]}")
Using device: cuda
Group Query Attention output shape: -0.058482810854911804
Group Query Attention Gemini output shape: -0.058482810854911804
In [ ]:
import torch
import torch.nn as nn

MAX_TOKEN_LENGTH = 2048

class GroupedQueryAttentionEinsum():
    def __init__(self, embed_dim, num_heads, num_kv_heads):
        """
        Args:
            embed_dim: 模型嵌入维度
            num_heads: Query 的总头数
            num_kv_heads: Key/Value 的头数 (GQA 分组数)
        """
        super(GroupedQueryAttentionEinsum, self).__init__()
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.num_kv_heads = num_kv_heads
        self.head_dim = embed_dim // num_heads
        
        # 计算每组有多少个 Query 头 (Group Size)
        self.num_heads_per_group = num_heads // num_kv_heads

        assert self.head_dim * num_heads == self.embed_dim, "embed_dim must be divisible by num_heads"
        assert num_heads % num_kv_heads == 0, "num_heads must be divisible by num_kv_heads"

        # Q 投影: 保持原样,覆盖所有 Query 头
        self.q_proj = nn.Linear(embed_dim, embed_dim)
        
        # K, V 投影: 维度由 num_kv_heads 决定
        self.k_proj = nn.Linear(embed_dim, self.num_kv_heads * self.head_dim)
        self.v_proj = nn.Linear(embed_dim, self.num_kv_heads * self.head_dim)
        
        self.out_proj = nn.Linear(embed_dim, embed_dim)
        
        self.register_buffer('attention_mask', torch.tril(torch.ones(MAX_TOKEN_LENGTH, MAX_TOKEN_LENGTH)))
        

    
    
# 测试代码
batch_size = 64
seq_len = 512
d_model = 128
num_heads = 8  # 8个Query头,但只有1个KV头
group_size = 2

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
model = GroupedQueryAttentionEinsum(d_model, num_heads, group_size).to(device)

model_output = model(x)
print(f"Group Query Attention output shape: {model_output}")
Using device: cuda
Group Query Attention output shape: tensor([[[ 0.0203,  0.0375, -0.0733,  ...,  0.0358,  0.0279,  0.0588],
         [-0.0069,  0.0553, -0.0708,  ...,  0.0357,  0.0159,  0.0529],
         [ 0.0169,  0.0567, -0.1109,  ...,  0.0337,  0.0183,  0.0560],
         ...,
         [ 0.0157,  0.0376, -0.0950,  ...,  0.0179,  0.0261,  0.0485],
         [ 0.0133,  0.0375, -0.0687,  ...,  0.0432,  0.0115,  0.0774],
         [ 0.0243,  0.0386, -0.0718,  ...,  0.0295,  0.0192,  0.0636]],

        [[ 0.0101,  0.0831, -0.0721,  ..., -0.0135,  0.0138,  0.0552],
         [ 0.0012,  0.0812, -0.0986,  ..., -0.0081,  0.0164,  0.0324],
         [ 0.0181,  0.0838, -0.0757,  ..., -0.0053,  0.0039,  0.0435],
         ...,
         [ 0.0058,  0.0746, -0.0652,  ..., -0.0040,  0.0399,  0.0323],
         [ 0.0355,  0.0818, -0.0769,  ...,  0.0067,  0.0272,  0.0378],
         [ 0.0101,  0.0924, -0.0668,  ..., -0.0166,  0.0368,  0.0388]],

        [[ 0.0172,  0.0431, -0.0544,  ...,  0.0353,  0.0327,  0.0502],
         [ 0.0109,  0.0739, -0.0534,  ...,  0.0112,  0.0282,  0.0577],
         [ 0.0336,  0.0508, -0.0578,  ...,  0.0065,  0.0338,  0.0476],
         ...,
         [ 0.0184,  0.0763, -0.0942,  ...,  0.0201,  0.0124,  0.0449],
         [ 0.0158,  0.0625, -0.0788,  ...,  0.0009,  0.0310,  0.0580],
         [ 0.0155,  0.0397, -0.0722,  ...,  0.0214,  0.0349,  0.0623]],

        ...,

        [[ 0.0246,  0.0195, -0.0581,  ...,  0.0456,  0.0646,  0.0395],
         [ 0.0161,  0.0092, -0.0779,  ...,  0.0237,  0.0500,  0.0308],
         [ 0.0311,  0.0272, -0.0564,  ...,  0.0126,  0.0648,  0.0549],
         ...,
         [ 0.0463,  0.0300, -0.0738,  ...,  0.0322,  0.0346,  0.0382],
         [ 0.0342,  0.0179, -0.0770,  ...,  0.0402,  0.0588,  0.0374],
         [ 0.0372,  0.0020, -0.0626,  ...,  0.0276,  0.0676,  0.0340]],

        [[ 0.0383, -0.0064, -0.0596,  ...,  0.0366,  0.0203,  0.0246],
         [ 0.0531,  0.0136, -0.0536,  ...,  0.0482,  0.0526,  0.0159],
         [ 0.0476, -0.0067, -0.0700,  ...,  0.0433,  0.0553,  0.0041],
         ...,
         [ 0.0343, -0.0194, -0.0592,  ...,  0.0499,  0.0189,  0.0342],
         [ 0.0614,  0.0163, -0.0745,  ...,  0.0440,  0.0301,  0.0154],
         [ 0.0475, -0.0096, -0.0874,  ...,  0.0417,  0.0489, -0.0080]],

        [[-0.0071,  0.0396, -0.0987,  ...,  0.0074,  0.0589,  0.0018],
         [-0.0003,  0.0474, -0.1039,  ..., -0.0026,  0.0564,  0.0312],
         [ 0.0010,  0.0500, -0.1036,  ...,  0.0136,  0.0615,  0.0157],
         ...,
         [-0.0036,  0.0581, -0.1131,  ..., -0.0254,  0.0260,  0.0224],
         [ 0.0097,  0.0672, -0.1035,  ..., -0.0105,  0.0211,  0.0200],
         [ 0.0069,  0.0536, -0.0933,  ..., -0.0045,  0.0299,  0.0357]]],
       device='cuda:0', grad_fn=<ViewBackward0>)
In [ ]:
from IPython.display import HTML, display

html_code = '''
<!DOCTYPE html>
<html>
<head>
    <style>
        .container { max-width: 900px; margin: 0 auto; font-family: sans-serif; padding: 20px; }
        h2 { color: #333; border-bottom: 2px solid #2196f3; padding-bottom: 10px; }
        .box { padding: 10px 15px; margin: 5px; border-radius: 5px; font-family: monospace; font-size: 12px; text-align: center; }
        .input { background: #e3f2fd; border: 2px solid #2196f3; }
        .proj { background: #fff3e0; border: 2px solid #ff9800; }
        .norm { background: #f3e5f5; border: 2px solid #9c27b0; }
        .attn { background: #e8f5e9; border: 2px solid #4caf50; }
        .rope { background: #fce4ec; border: 2px solid #e91e63; }
        .arrow { font-size: 20px; color: #666; }
        .flex { display: flex; align-items: center; flex-wrap: wrap; justify-content: center; }
        .row { flex-direction: row; }
        .code { background: #263238; color: #aed581; padding: 15px; border-radius: 5px; font-family: monospace; margin: 10px 0; }
        .highlight { background: #ffeb3b; padding: 2px 5px; }
        table { border-collapse: collapse; width: 100%; margin: 10px 0; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: center; }
        th { background: #2196f3; color: white; }
    </style>
</head>
<body>
<div class="container">
    <h2>🔮 Gemma3 Attention 完整流程</h2>
    
    <!-- 完整流程图 -->
    <div class="flex row">
        <div class="box input">Hidden States<br>[B, Seq, Hidden]</div>
        <span class="arrow">→</span>
        <div class="box proj">Q/K/V 投影<br>Linear</div>
        <span class="arrow">→</span>
        <div class="box norm">Q_norm, K_norm<br>(RMSNorm)</div>
        <span class="arrow">→</span>
        <div class="box rope">RoPE<br>旋转位置编码</div>
        <span class="arrow">→</span>
        <div class="box attn">Attention<br>(GQA + Sliding)</div>
        <span class="arrow">→</span>
        <div class="box proj">O 投影<br>Linear</div>
    </div>

    <hr>
    
    <h2>🔑 GQA 核心: repeat_kv</h2>
    <table>
        <tr>
            <th>配置</th>
            <th>num_attention_heads</th>
            <th>num_key_value_heads</th>
            <th>n_rep</th>
        </tr>
        <tr>
            <td>Gemma3-3b</td>
            <td>8</td>
            <td>2</td>
            <td>4</td>
        </tr>
        <tr>
            <td>gemma3-2b</td>
            <td>8</td>
            <td>4</td>
            <td>2</td>
        </tr>
        <tr>
            <td>MQA</td>
            <td>8</td>
            <td>1</td>
            <td>8</td>
        </tr>
    </table>
    
    <div class="code">
# repeat_kv 核心逻辑
def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
    batch, num_kv_heads, seqlen, head_dim = hidden_states.shape
    if n_rep == 1:
        return hidden_states
    # 复制KV头n_rep次
    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_kv_heads, n_rep, seqlen, head_dim)
    return hidden_states.reshape(batch, num_kv_heads * n_rep, seqlen, head_dim)
    </div>

    <hr>
    
    <h2>🪟 Sliding Window Attention</h2>
    <p><strong>原理:</strong>每个位置i只能attend到[i-window+1, i]范围内的token,超出窗口的被mask掉</p>
    
    <table>
        <tr>
            <th>Window=5</th>
            <th>K0</th>
            <th>K1</th>
            <th>K2</th>
            <th>K3</th>
            <th>K4</th>
            <th>K5</th>
            <th>K6</th>
            <th>K7</th>
            <th>K8</th>
        </tr>
        <tr>
            <td><strong>Q0</strong></td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
        </tr>
        <tr>
            <td><strong>Q1</strong></td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
        </tr>
        <tr>
            <td><strong>Q2</strong></td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
        </tr>
        <tr>
            <td><strong>Q3</strong></td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
        </tr>
        <tr>
            <td><strong>Q4</strong></td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#4caf50">✓</td>
            <td style="background:#e0e0e0">×</td>
            <td style="background:#e0e0e0">×</td>
        </tr>
    </table>
    
    <p style="color: #666; font-size: 12px;">
    ✓ = 可以attend | × = 被mask (attn_weights = -inf → softmax后≈0)
    </p>

    <hr>
    
    <h2>📊 Gemma3 整体架构</h2>
    <svg width="600" height="320" viewBox="0 0 600 320">
        <!-- 输入框 -->
        <rect x="50" y="20" width="80" height="25" rx="4" fill="#e3f2fd" stroke="#2196f3" stroke-width="2"/>
        <text x="90" y="37" text-anchor="middle" font-size="11">input_ids</text>
        
        <rect x="140" y="20" width="90" height="25" rx="4" fill="#fff3e0" stroke="#ff9800" stroke-width="2"/>
        <text x="185" y="37" text-anchor="middle" font-size="11">pixel_values</text>
        
        <!-- Vision路径 -->
        <line x1="185" y1="45" x2="185" y2="65" stroke="#666" stroke-width="1.5"/>
        <rect x="140" y="65" width="90" height="25" rx="4" fill="#fff3e0" stroke="#ff9800" stroke-width="2"/>
        <text x="185" y="82" text-anchor="middle" font-size="11">Vision Tower</text>
        
        <line x1="185" y1="90" x2="185" y2="105" stroke="#666" stroke-width="1.5"/>
        <rect x="140" y="105" width="90" height="25" rx="4" fill="#fff3e0" stroke="#ff9800" stroke-width="2"/>
        <text x="185" y="122" text-anchor="middle" font-size="11">Projector</text>
        
        <!-- Text Embed -->
        <line x1="90" y1="45" x2="90" y2="65" stroke="#666" stroke-width="1.5"/>
        <rect x="50" y="65" width="80" height="25" rx="4" fill="#e3f2fd" stroke="#2196f3" stroke-width="2"/>
        <text x="90" y="82" text-anchor="middle" font-size="11">Embed</text>
        
        <!-- 合并 -->
        <line x1="90" y1="130" x2="185" y2="130" stroke="#666" stroke-width="2"/>
        <line x1="185" y1="130" x2="90" y2="130" marker-end="url(#arrow)" stroke="#666"/>
        
        <rect x="40" y="140" width="180" height="35" rx="4" fill="#f5f5f5" stroke="#333" stroke-width="2"/>
        <text x="130" y="162" text-anchor="middle" font-size="13"><strong>inputs_embeds</strong></text>
        
        <!-- Decoder Layers -->
        <line x1="130" y1="175" x2="130" y2="195" stroke="#666" stroke-width="2"/>
        
        <rect x="60" y="195" width="140" height="28" rx="4" fill="#e8f5e9" stroke="#4caf50" stroke-width="2"/>
        <text x="130" y="213" text-anchor="middle" font-size="11">Gemma3DecoderLayer × N</text>
        
        <rect x="60" y="230" width="140" height="28" rx="4" fill="#e8f5e9" stroke="#4caf50" stroke-width="2"/>
        <text x="130" y="248" text-anchor="middle" font-size="11">Gemma3DecoderLayer × N</text>
        
        <line x1="130" y1="258" x2="130" y2="275" stroke="#666" stroke-width="2"/>
        
        <!-- RMSNorm + 输出 -->
        <rect x="85" y="275" width="90" height="22" rx="4" fill="#f3e5f5" stroke="#9c27b0" stroke-width="2"/>
        <text x="130" y="290" text-anchor="middle" font-size="10">RMSNorm</text>
        
        <line x1="130" y1="297" x2="130" y2="315" stroke="#666" stroke-width="2"/>
        <rect x="80" y="305" width="100" height="25" rx="4" fill="#e3f2fd" stroke="#2196f3" stroke-width="2"/>
        <text x="130" y="322" text-anchor="middle" font-size="11">last_hidden_state</text>
        
        <defs>
            <marker id="arrow" markerWidth="8" markerHeight="8" refX="0" refY="3" orient="auto">
                <path d="M0,0 L0,6 L8,3 z" fill="#666"/>
            </marker>
        </defs>
        
        <!-- 图例 -->
        <rect x="350" y="60" width="15" height="15" fill="#e3f2fd" stroke="#2196f3"/>
        <text x="375" y="72" font-size="11">Text Input</text>
        
        <rect x="350" y="85" width="15" height="15" fill="#fff3e0" stroke="#ff9800"/>
        <text x="375" y="97" font-size="11">Vision</text>
        
        <rect x="350" y="110" width="15" height="15" fill="#e8f5e9" stroke="#4caf50"/>
        <text x="375" y="122" font-size="11">Decoder Layer</text>
        
        <text x="350" y="150" font-size="14"><strong>Gemma3Model:</strong></text>
        <text x="350" y="170" font-size="11" fill="#666">1. Vision Tower → feature extraction</text>
        <text x="350" y="190" font-size="11" fill="#666">2. Projector → 映射到text空间</text>
        <text x="350" y="210" font-size="11" fill="#666">3. 合并 token 和 image embeds</text>
        <text x="350" y="230" font-size="11" fill="#666">4. N层Decoder处理</text>
        <text x="350" y="250" font-size="11" fill="#666">5. RMSNorm + 输出</text>
    </svg>

</div>
</body>
</html>
'''

display(HTML(html_code))

评论(0)

评论加载中…