【LLM】LLaMA架构(RMSNorm+ KV cache+Rotary Positional Encodings+门控FFN+MoE)

【LLM】LLaMA架构(RMSNorm+ KV cache+Rotary Positional Encodings+门控FFN+MoE)

文章目录


一、LLaMA架构

1. 基本介绍

《LLaMA: Open and Efficient Foundation Language Models》论文地址:https://arxiv.org/abs/2302.13971

LLaMA并非一个全新的、从零开始设计的架构。它巧妙整合并验证了多个当时业界公认的最高效的Transformer改进方案。例如:

  • 预归一化:使用RMSNorm层进行归一化,提高训练稳定性。
  • SwiGLU激活函数:替代传统的Relu,提升模型表达能力。
  • 旋转位置编码:使用RoPE,能更好地处理长序列。

2. 技术路线图:对比Transformer

请添加图片描述


从对比图可以看出,LLaMA架构在位置编码和自注意力机制上做了较大的调整。Transformer-Decoder中的位置编码不再是给input embedding做改进,而是给经过QK做编码。为了提升计算效率,LLaMA的自注意机制采用的是KV缓存:加入多头为8个头,此时我们要求将KV分别生成两个矩阵并将这两个矩阵保存,针对不同的Q,都是使用缓存的这两个KV来计算。

3. 思考

3.1 为什么LLaMA使用的是Transformer中的Decoder解码器?

  • 任务需求的匹配:自回归生成。Decoder的在训练时使用因果注意力掩码(Causal Attention Mask) ,也称为前瞻掩码(Look-ahead Mask),在计算注意力权重矩阵时,会做一个时间窗静止当前单词对后续单词进行询问,在预测下一个单词的时候,只利用之前的所有词。这本是就是一个天然、高效的文本生成器结构。Encoder模型(BERT)主要擅长理解型任务(eg. 文本分类、文本匹配、语义相似度),而不是生成类任务。这个

架构效率:在先沟通的计算预算下,一个巨型纯Decoder模型可能比一个同等规模的Encoder-Decoder模型在生成任务上表现更好,因为所有参数都聚焦于同一个目标。

请添加图片描述


Bert是Encoder-only模型,GPT是Decoder-only模型,上图是具体的对比。

3.2 为什么RoPE只给Q和K做位置编码?

  • 注意力分数的计算原理: 注意力机制的核心是“注意力分数”矩阵(Q·K T)计算的是查询与键的匹配度或相关性。这个相关性必须包含位置信息,因为一个词与另一个词的相关性高度依赖于它们之间的相对位置(例如,“apple”在“eat”前面和后面含义完全不同)。因此,位置编码的核心目的是为了让模型在计算“谁应该关注谁”(Q·K^T)时,能够感知到位置关系。
  • 注意力分数的计算原理:RoPE是一种相对位置编码,它通过旋转矩阵将位置信息注入到Q和K中,得到的点积(Q_i· K_ j ^ T)结果只依赖于相对位置 i - j,而不是绝对位置 i 或 j 。如果我们将RoPE也应用到V上,可能会扭曲V本身所携带的语义信息,且是没必要的,因为softmax分数已经包含了位置感知,这个分数决定了V的权重,旋转V并不会改变“该关注哪个token的决策”。

二、重要组成部分

1. Embedding

在PyTorch,nn.Embedding层是用于处理离散数据的关键组件,主要功能是将输入的整数索引映射到连续的高维向量空间中,即将索引转化为嵌入向量。

import torch import torch.nn as nn # 定义Embedding层 embedding = nn.Embedding(10,3)# num_embeddings=10, embedding_dim=3# 输入索引 input_indices = torch.tensor([1,2,3])# 获取嵌入向量 output = embedding(input_indices)print(output)

2. RMSNorm均方根层归一化

2.1 Layer Normalization 和 Batch Normalization 的区别

最重要的区别在于计算均值和方差的方向不同,LN在一次更新迭代中统计同一层内的所有神经元节点的输出分布(同一个样本下);BN是在一个Batch内统计某特定神经元的输出分布(跨样本)。

请添加图片描述


在NLP任务中会经常处理长度不同的句子,使用LN时可以不需要考虑其他样本的长度是否,如果按照Batch维度进行统计的话,会存在一定问题:为了让样本均衡,一般会对样本进行裁剪或者填补,里面一定有大量为0的特征值,因此在计算特征均值和方差肯定会受到影响。

2.2 LN与RMSNorm的区别

在这里插入图片描述


那么问题来了,为什么通过RMSNorm可以起到归一化的作用?

首先,要先回顾下归一化层的作用。归一化层是为了防止梯度爆炸/消失,实现手段是控制尺度,而非严格的中心化。RMS(x) 衡量的是向量的"典型幅度",类似于向量的L2范数(相差一个根号n因子)。经过RMSNorm后,输出向量的RMS值为1:RMS(RMSNorm(x))=1,这就强制输出的尺寸保有一致性。

import torch import torch.nn as nn classRMSNorm(nn.Module):def__init__(self, dim:int, eps:float=1e-8):super().__init__() self.eps = eps self.weight = nn.Parameter(torch.ones(dim))def_norm(self, x):# 计算 RMS 归一化因子 rms = torch.rsqrt(x.pow(2).mean(-1, keepdim=True)+ self.eps)return x * rms defforward(self, x):# 保持计算精度为float,然后转换回输入类型 output = self._norm(x.float()).type_as(x)return output * self.weight # 正确使用方式 x = torch.randn(2,3,768) rmsnorm = RMSNorm(dim=768, eps=1e-6)# 创建实例 norm = rmsnorm(x)# 通过实例调用print("输出形状:", norm.shape)#输出: torch.Size([2, 3, 768])

3. 旋转位置编码Rotary Positional Encodings

请添加图片描述


这段代码不用记了,记不住的···,知道旋转位置编码的原理即可。

#定义频率计算defprecompute_pos_cis(dim:int, max_position:int, theta:float=10000.0):#频率 freqs =1.0/(theta **(torch.arange(0, dim,2)[:(dim //2)].float()/ dim))#位置编码m m = torch.arange(max_position, device=freqs.device)#频率乘以位置编码、外积 freqs = torch.outer(m, freqs).float()# pos_cis = torch.polar(torch.ones_like(freqs), freqs)return pos_cis #将频率用于q、k矩阵defapply_rotary_emb(xq, xk, pos_cis):defunite_shape(pos_cis, x): ndim = x.ndim assert0<=1< ndim print(pos_cis.shape)print(x.shape[1])print(x.shape[-1])assert pos_cis.shape ==(x.shape[1], x.shape[-1]) shape =[d if i ==1or i == ndim -1else1for i, d inenumerate(x.shape)]return pos_cis.view(*shape) xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1],-1,2)) xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1],-1,2)) pos_cis = unite_shape(pos_cis, xq_) xq_out = torch.view_as_real(xq_ * pos_cis).flatten(3) xk_out = torch.view_as_real(xk_ * pos_cis).flatten(3)return xq_out.type_as(xq), xk_out.type_as(xk)

4. Self-Attention(Grouped Multi-Query Attetion with KV cache)

4.1 基本概念

KV缓存(Key-Value Cache)是Transformer模型自回归生成任务中的一个重要加速技术。在文本生成任务中,每一步生成一个新的token,这个新的token跟之前所有的tokens一起重新输入到模型中,预测下一个token。对于每一步的生成,模型会重新计算所有tokens的注意力分数,这个过程是非常耗时的,因此,为了避免重复计算注意力层中的K和V,在生成后续token时,模型只需要计算token的Query,直接调用缓存中的Key和Vaule。

4.2 工作原理

KV缓存大部分时候适用于推理过程中。

  1. 初始化:
    当模型开始时,模型计算输入序列的Key和Value,并将这些计算结果缓存起来,保存在内存中。大部分时候,每个注意力层都会有一对Key-Value缓存,这个缓存是自回归的每次循环中共用的。还有一种做法是:在多头注意力机制中,只保留一个头或者两个头以上的KV值,并共享给所有头使用。
  2. 生成过程:
    当生成下一个token时,模型不需要重新计算前面已经生成的token的Key和Value始终保持更新,包含所有已生成的tokens。最终会包含所有生成序列的Key和Value。
  3. 更新缓存:
    对于每一个生成步骤,模型还会将当前生成的token的Key和Value加入缓存,确保缓存中的Key和Value始终保持更新,能够包含所有已经生成的tokens。
  4. 加速效果:
    由于每个生成步骤只需要计算当前token的Query,而不需要重新计算整个序列的Key和Value,这大大减少了计算量。随着序列长度增加,缓存的使用能够显著减少时间复杂度,使生成过程更快。

4.3 模型代码

4.3.1 分组查询注意力

多个查询头共享一组K、V头。
场景:假设有

  • 查询头数(n_q_heads):8
  • KV缓存头数量(n_kv_heads):2
  • 每个头的维度(head_dim):64

那么我们需要让:

  • 这里每个KV需要被8/2=4个查询头共享
defrepeat_kv(x:torch.Tensor, n_rep:int):''' :param x: tensor , shape (bs, slen, n_kv_heads, head_dim) :param n_rep: 重复次数 :return: ''' bs, slen, n_kv_heads, head_dim = x.shape #bs: 批次大小 (batch size)# slen: 序列长度 (sequence length)# n_kv_heads: KV 头的数量 (number of key-value heads)# head_dim: 每个头的维度大小 (dimension size of each head)if n_rep ==1:return x return( x[:,:,:,None,:].expand(bs, slen, n_kv_heads, n_rep, head_dim).reshape(bs, slen, n_kv_heads * n_rep, head_dim))
4.3.2 注意力机制
import torch import torch.nn as nn from Config import LMConfig import torch.nn.functional as F import math from typing import Optional, Tuple from dataclasses import dataclass @dataclassclassLMConfig: dim:int=4096 n_heads:int=32 n_kv_heads: Optional[int]=None max_seq_len:int=2048 dropout:float=0.1 flash_attn:bool=Truedef__post_init__(self):if self.n_kv_heads isNone: self.n_kv_heads = self.n_heads #旋转位置编码defapply_rotary_emb( xq: torch.Tensor, xk: torch.Tensor, freqs_cis: torch.Tensor )-> Tuple[torch.Tensor, torch.Tensor]:""" 应用旋转位置编码到查询和键上 """# 将复数转换为实数和虚数部分 xq_complex = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1],-1,2)) xk_complex = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1],-1,2))# 应用旋转 freqs_cis = freqs_cis.unsqueeze(0).unsqueeze(2)# 添加batch和head维度 xq_out = torch.view_as_real(xq_complex * freqs_cis).flatten(3) xk_out = torch.view_as_real(xk_complex 

Read more

大学四年,我赌上全部的JS逆向,终究输给了AI的10秒

文章目录 * 春招:一场精心策划的“打脸现场” * 10秒,我的四年青春被AI按在地上摩擦 * 马路牙子上的崩溃:我的青春,是个笑话 * 身边的人都在起飞,只有我被留在原地 * 不甘,但也清醒:与AI“搭伙过日子” 大学四年,我把自己活成了一台“JS逆向专用挖掘机”——能赌的、能拼的、能扔的,全他妈一股脑押在了这玩意儿上。没有Plan B,没有退路,我一个在二本院校里挣扎的普通学生,除了死磕,除了把这门技术嚼碎了咽进肚子里,还能有什么资本,跟那些名校出身的“天之骄子”掰手腕? 大一下学期,我彻底与“正常大学生活”决裂,一头扎进网吧的烟雾缭绕和实验室的寂静里,《JavaScript 逆向与爬虫实战》被我翻得封皮掉渣、内页卷边,活像一本被传了十代的武林秘籍。笔记写了满满四大本,每一页都画满了断点、混淆逻辑和补环境的坑,密密麻麻得像蚂蚁搬家的路线图。 别人的大学,是逃课开黑、约学妹看电影,是周末捧着奶茶吹晚风,

旧电脑秒变 AI 员工:OpenClaw 本地部署教程(含环境配置 + 插件开发 + 常见坑)

旧电脑秒变 AI 员工:OpenClaw 本地部署教程(含环境配置 + 插件开发 + 常见坑)

前言 本文基于最新OpenClaw版本编写,适配电脑低配置场景(最低2vCPU+2GiB内存+40GiB SSD),兼容Windows 10/11(优先WSL2)、Ubuntu 20.04+系统,全程纯操作指令,覆盖环境配置、本地部署、插件开发、高频坑排查。核心解决部署卡顿、国内网络适配、插件开发无思路、报错无法排查四大痛点,全程适配国内网络(国内镜像源)、国内大模型(通义千问、阿里云百炼等),无需海外代理,可稳定运行实现自动化办公(文件处理、IM对接、任务调度等)。 一、前置准备(适配优化) 1.1 硬件要求(最低适配) * CPU:Intel i3 4代+/AMD Ryzen 3 2000+(支持虚拟化,

2026最新 Python+AI 入门指南:0基础也能快速上手,避开90%新手坑

2026最新 Python+AI 入门指南:0基础也能快速上手,避开90%新手坑

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、为什么2026年入门AI,首选Python?(新颖热点解读) * 二、Python+AI入门必备:前提+环境搭建(10分钟搞定) * 2.1 核心前提(不用啃硬骨头) * 2.2 环境搭建(Windows/Mac通用,避版本冲突) * 三、Python+AI入门实战:3个热门案例(附完整代码) * 案例1:数据处理(AI入门必备,80%AI开发第一步) * 案例2:机器学习入门(线性回归,房价预测) * 案例3:2026热门·大模型对接(LangChain快速调用) * 四、

【AI】谷歌TurboQuant算法:内存占用减少至少6倍

谷歌在2026年3月25日发布了一项名为 TurboQuant 的突破性压缩算法,它可以在不损失任何模型精度的前提下,将AI大模型运行时的关键内存占用(KV缓存)减少至少6倍,同时将推理速度提升最高8倍。 这一技术突破引发了硅谷和华尔街的广泛关注,甚至让美光、西部数据等存储芯片巨头的股价应声下跌。下面为你详细拆解这项技术: 🚀 TurboQuant核心技术速览 技术指标具体数据说明内存压缩比至少6倍将KV缓存压缩至3-bit精度,相比传统16/32-bit存储推理加速最高8倍在H100 GPU上4-bit TurboQuant vs 32-bit未量化基线精度影响零损失在"大海捞针"等长上下文测试中保持完美分数部署门槛无需训练无需预训练或微调,即插即用应用范围KV缓存压缩 + 向量搜索解决推理内存瓶颈,同时提升语义搜索引擎效率 🔧 核心技术原理:两步"绝杀" 要理解TurboQuant为什么重要,先要明白它解决的是什么问题。大模型推理时,会把历史信息临时存在 KV缓存 中以便快速调用。当上下文窗口从4K扩展到百万级时,KV缓存会迅速膨胀,成为AI推理最大的内存瓶颈