【DataWhale】快乐学习大模型 | 202507,Task05笔记

前言

今天是Transformer的编码实战阶段,照着示例代码执行一遍吧

embedding

self.tok_embeddings = nn.Embedding(args.vocab_size, args.dim)

把token向量转为embedding矩阵(一个token一个embedding向量)

位置编码

为了解决“我喜欢你”和“你喜欢我”结果一致的问题,加入了位置的影响。

小技巧

import pdb
pdb.set_trace()

在需要打断点的地方加这个,可以暂停python程序进行调试。

Transformer源码和结构图

来自:happy-llm,代码部分做了第2章代码的合并总结,方便本地直接运行。
在这里插入图片描述

import torch
import torch.nn as nn
import torch.nn.functional as F
import math'''多头自注意力计算模块'''
class MultiHeadAttention(nn.Module):def __init__(self, args, is_causal=False):# 构造函数# args: 配置对象super().__init__()# 隐藏层维度必须是头数的整数倍,因为后面我们会将输入拆成头数个矩阵assert args.dim % args.n_heads == 0# 模型并行处理大小,默认为1。model_parallel_size = 1# 本地计算头数,等于总头数除以模型并行处理大小。self.n_local_heads = args.n_heads // model_parallel_size# 每个头的维度,等于模型维度除以头的总数。self.head_dim = args.dim // args.n_heads# Wq, Wk, Wv 参数矩阵,每个参数矩阵为 n_embd x n_embd# 这里通过三个组合矩阵来代替了n个参数矩阵的组合,其逻辑在于矩阵内积再拼接其实等同于拼接矩阵再内积,# 不理解的读者可以自行模拟一下,每一个线性层其实相当于n个参数矩阵的拼接self.wq = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)self.wk = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)self.wv = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)# 输出权重矩阵,维度为 dim x n_embd(head_dim = n_embeds / n_heads)self.wo = nn.Linear(args.n_heads * self.head_dim, args.dim, bias=False)# 注意力的 dropoutself.attn_dropout = nn.Dropout(args.dropout)# 残差连接的 dropoutself.resid_dropout = nn.Dropout(args.dropout)self.is_causal = is_causal# 创建一个上三角矩阵,用于遮蔽未来信息# 注意,因为是多头注意力,Mask 矩阵比之前我们定义的多一个维度if is_causal:mask = torch.full((1, 1, args.max_seq_len, args.max_seq_len), float("-inf"))mask = torch.triu(mask, diagonal=1)# 注册为模型的缓冲区self.register_buffer("mask", mask)def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor):# 获取批次大小和序列长度,[batch_size, seq_len, dim]bsz, seqlen, _ = q.shape# 计算查询(Q)、键(K)、值(V),输入通过参数矩阵层,维度为 (B, T, n_embed) x (n_embed, n_embed) -> (B, T, n_embed)xq, xk, xv = self.wq(q), self.wk(k), self.wv(v)# 将 Q、K、V 拆分成多头,维度为 (B, T, n_head, C // n_head),然后交换维度,变成 (B, n_head, T, C // n_head)# 因为在注意力计算中我们是取了后两个维度参与计算# 为什么要先按B*T*n_head*C//n_head展开再互换1、2维度而不是直接按注意力输入展开,是因为view的展开方式是直接把输入全部排开,# 然后按要求构造,可以发现只有上述操作能够实现我们将每个头对应部分取出来的目标xq = xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)xk = xk.view(bsz, seqlen, self.n_local_heads, self.head_dim)xv = xv.view(bsz, seqlen, self.n_local_heads, self.head_dim)xq = xq.transpose(1, 2)xk = xk.transpose(1, 2)xv = xv.transpose(1, 2)# 注意力计算# 计算 QK^T / sqrt(d_k),维度为 (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T)scores = torch.matmul(xq, xk.transpose(2, 3)) / math.sqrt(self.head_dim)# 掩码自注意力必须有注意力掩码if self.is_causal:assert hasattr(self, 'mask')# 这里截取到序列长度,因为有些序列可能比 max_seq_len 短scores = scores + self.mask[:, :, :seqlen, :seqlen]# 计算 softmax,维度为 (B, nh, T, T)scores = F.softmax(scores.float(), dim=-1).type_as(xq)# 做 Dropoutscores = self.attn_dropout(scores)# V * Score,维度为(B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)output = torch.matmul(scores, xv)# 恢复时间维度并合并头。# 将多头的结果拼接起来, 先交换维度为 (B, T, n_head, C // n_head),再拼接成 (B, T, n_head * C // n_head)# contiguous 函数用于重新开辟一块新内存存储,因为Pytorch设置先transpose再view会报错,# 因为view直接基于底层存储得到,然而transpose并不会改变底层存储,因此需要额外存储output = output.transpose(1, 2).contiguous().view(bsz, seqlen, -1)# 最终投影回残差流。output = self.wo(output)output = self.resid_dropout(output)return outputclass MLP(nn.Module):'''前馈神经网络'''def __init__(self, dim: int, hidden_dim: int, dropout: float):super().__init__()# 定义第一层线性变换,从输入维度到隐藏维度self.w1 = nn.Linear(dim, hidden_dim, bias=False)# 定义第二层线性变换,从隐藏维度到输入维度self.w2 = nn.Linear(hidden_dim, dim, bias=False)# 定义dropout层,用于防止过拟合self.dropout = nn.Dropout(dropout)def forward(self, x):# 前向传播函数# 首先,输入x通过第一层线性变换和RELU激活函数# 然后,结果乘以输入x通过第三层线性变换的结果# 最后,通过第二层线性变换和dropout层return self.dropout(self.w2(F.relu(self.w1(x))))class LayerNorm(nn.Module):''' Layer Norm 层'''def __init__(self, features, eps=1e-6):super(LayerNorm, self).__init__()# 线性矩阵做映射self.a_2 = nn.Parameter(torch.ones(features))self.b_2 = nn.Parameter(torch.zeros(features))self.eps = epsdef forward(self, x):# 在统计每个样本所有维度的值,求均值和方差mean = x.mean(-1, keepdim=True) # mean: [bsz, max_len, 1]std = x.std(-1, keepdim=True) # std: [bsz, max_len, 1]# 注意这里也在最后一个维度发生了广播return self.a_2 * (x - mean) / (std + self.eps) + self.b_2class EncoderLayer(nn.Module):'''Encoder层'''def __init__(self, args):super().__init__()# 一个 Layer 中有两个 LayerNorm,分别在 Attention 之前和 MLP 之前self.attention_norm = LayerNorm(args.n_embd)# Encoder 不需要掩码,传入 is_causal=Falseself.attention = MultiHeadAttention(args, is_causal=False)self.fnn_norm = LayerNorm(args.n_embd)self.feed_forward = MLP(args.n_embd, args.hidden_dim, args.dropout)def forward(self, x):# Layer Normnorm_x = self.attention_norm(x)# 自注意力h = x + self.attention.forward(norm_x, norm_x, norm_x)# 经过前馈神经网络out = h + self.feed_forward.forward(self.fnn_norm(h))return outclass Encoder(nn.Module):'''Encoder 块'''def __init__(self, args):super(Encoder, self).__init__() # 一个 Encoder 由 N 个 Encoder Layer 组成self.layers = nn.ModuleList([EncoderLayer(args) for _ in range(args.n_layer)])self.norm = LayerNorm(args.n_embd)def forward(self, x):"分别通过 N 层 Encoder Layer"for layer in self.layers:x = layer(x)return self.norm(x)class DecoderLayer(nn.Module):'''解码层'''def __init__(self, args):super().__init__()# 一个 Layer 中有三个 LayerNorm,分别在 Mask Attention 之前、Self Attention 之前和 MLP 之前self.attention_norm_1 = LayerNorm(args.n_embd)# Decoder 的第一个部分是 Mask Attention,传入 is_causal=Trueself.mask_attention = MultiHeadAttention(args, is_causal=True)self.attention_norm_2 = LayerNorm(args.n_embd)# Decoder 的第二个部分是 类似于 Encoder 的 Attention,传入 is_causal=Falseself.attention = MultiHeadAttention(args, is_causal=False)self.ffn_norm = LayerNorm(args.n_embd)# 第三个部分是 MLPself.feed_forward = MLP(args.n_embd, args.hidden_dim, args.dropout)def forward(self, x, enc_out):# Layer Normnorm_x = self.attention_norm_1(x)# 掩码自注意力x = x + self.mask_attention.forward(norm_x, norm_x, norm_x)# 多头注意力norm_x = self.attention_norm_2(x)h = x + self.attention.forward(norm_x, enc_out, enc_out)# 经过前馈神经网络out = h + self.feed_forward.forward(self.ffn_norm(h))return outclass Decoder(nn.Module):'''解码器'''def __init__(self, args):super(Decoder, self).__init__() # 一个 Decoder 由 N 个 Decoder Layer 组成self.layers = nn.ModuleList([DecoderLayer(args) for _ in range(args.n_layer)])self.norm = LayerNorm(args.n_embd)def forward(self, x, enc_out):"Pass the input (and mask) through each layer in turn."for layer in self.layers:x = layer(x, enc_out)return self.norm(x)class PositionalEncoding(nn.Module):'''位置编码模块'''def __init__(self, args):super(PositionalEncoding, self).__init__()# Dropout 层self.dropout = nn.Dropout(p=args.dropout)# block size 是序列的最大长度pe = torch.zeros(args.block_size, args.n_embd)position = torch.arange(0, args.block_size).unsqueeze(1)# 计算 thetadiv_term = torch.exp(torch.arange(0, args.n_embd, 2) * -(math.log(10000.0) / args.n_embd))# 分别计算 sin、cos 结果pe[:, 0::2] = torch.sin(position * div_term)pe[:, 1::2] = torch.cos(position * div_term)pe = pe.unsqueeze(0)self.register_buffer("pe", pe)def forward(self, x):# 将位置编码加到 Embedding 结果上x = x + self.pe[:, : x.size(1)].requires_grad_(False)return self.dropout(x)class Transformer(nn.Module):'''整体模型'''def __init__(self, args):super().__init__()# 必须输入词表大小和 block sizeassert args.vocab_size is not Noneassert args.block_size is not Noneself.args = argsself.transformer = nn.ModuleDict(dict(wte = nn.Embedding(args.vocab_size, args.n_embd),wpe = PositionalEncoding(args),drop = nn.Dropout(args.dropout),encoder = Encoder(args),decoder = Decoder(args),))# 最后的线性层,输入是 n_embd,输出是词表大小self.lm_head = nn.Linear(args.n_embd, args.vocab_size, bias=False)# 初始化所有的权重self.apply(self._init_weights)# 查看所有参数的数量print("number of parameters: %.2fM" % (self.get_num_params()/1e6,))'''统计所有参数的数量'''def get_num_params(self, non_embedding=False):# non_embedding: 是否统计 embedding 的参数n_params = sum(p.numel() for p in self.parameters())# 如果不统计 embedding 的参数,就减去if non_embedding:n_params -= self.transformer.wpe.weight.numel()return n_params'''初始化权重'''def _init_weights(self, module):# 线性层和 Embedding 层初始化为正则分布if isinstance(module, nn.Linear):torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)if module.bias is not None:torch.nn.init.zeros_(module.bias)elif isinstance(module, nn.Embedding):torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)'''前向计算函数'''def forward(self, idx, targets=None):# 输入为 idx,维度为 (batch size, sequence length, 1);targets 为目标序列,用于计算 lossdevice = idx.deviceb, t = idx.size()assert t <= self.args.block_size, f"不能计算该序列,该序列长度为 {t}, 最大序列长度只有 {self.args.block_size}"# 通过 self.transformer# 首先将输入 idx 通过 Embedding 层,得到维度为 (batch size, sequence length, n_embd)print("idx",idx.size())# 通过 Embedding 层tok_emb = self.transformer.wte(idx)print("tok_emb",tok_emb.size())# 然后通过位置编码pos_emb = self.transformer.wpe(tok_emb) # 再进行 Dropoutx = self.transformer.drop(pos_emb)# 然后通过 Encoderprint("x after wpe:",x.size())enc_out = self.transformer.encoder(x)print("enc_out:",enc_out.size())# 再通过 Decoderx = self.transformer.decoder(x, enc_out)print("x after decoder:",x.size())if targets is not None:# 训练阶段,如果我们给了 targets,就计算 loss# 先通过最后的 Linear 层,得到维度为 (batch size, sequence length, vocab size)logits = self.lm_head(x)# 再跟 targets 计算交叉熵loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)else:# 推理阶段,我们只需要 logits,loss 为 None# 取 -1 是只取序列中的最后一个作为输出logits = self.lm_head(x[:, [-1], :]) # note: using list [-1] to preserve the time dimloss = Nonereturn logits, lossif __name__ == '__main__':class Args:vocab_size = 10000block_size = 128n_embd = 256dropout = 0.1n_layer = 6n_heads = 8dim = 256hidden_dim = 512dropout = 0.1max_seq_len = 128args = Args()model = Transformer(args)print(model)# 训练模型是有targetprint("########## Training ##########")target = torch.randint(0, args.vocab_size, (2, args.block_size))logits, loss = model(target, target)print("Training logits shape:", logits.shape)print("logits shape:", logits.shape)print("loss:", loss)# 测试模型的前向计算print("########## Inference ##########")idx = torch.randint(0, args.vocab_size, (2, args.block_size))logits, loss = model(idx)print("Inference logits shape:", logits.shape)print("logits shape:", logits.shape)print("loss:", loss)

将上面代码保存为 transformer_demo.py 后,直接运行即可

python .\transformer_demo.py

运行结果

在这里插入图片描述
可以看到训练模式下计算了loss(真实GPU训练还会再稍复杂一些)。推理模式下直接推理了下一个的输出,[2,1,10000]中,10000是词表大小,2是batch(如同时2个人在用),1是下一次token。10000中最大的数值最大的位置就是下次概率最大的token。

参考链接

1、happy-llm/docs/chapter2/第二章 Transformer架构.md

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915622.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915622.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用ffmpeg 进行视频的拼接

author: hjjdebug date: 2025年 07月 22日 星期二 17:06:02 CST descrip: 用ffmpeg 进行视频的拼接 文章目录1. 指定协议为concat 方式.1.1 协议为concat 模式,会调用 concat_open 函数1.2 当读数据时,会调用concat_read2. 指定file_format 为 concat 方式2.1 调用concat_read_…

HTTP与HTTPS技术细节及TLS密钥交换与证书校验全流程

HTTP与HTTPS技术细节及TLS密钥交换与证书校验全流程 引言 文档目的与范围 核心技术栈概述 本文档的核心技术栈围绕传输层安全协议&#xff08;TLS&#xff09;展开。TLS协议作为安全套接字层&#xff08;SSL&#xff09;的后继标准&#xff0c;是现代网络安全通信的基础&am…

广播分发中心-广播注册流程

广播是怎么注册的呢&#xff1f;阶段组件/数据结构作用描述存储位置/关联关系App进程阶段BroadcastReceiver开发者自定义的广播接收器&#xff0c;实现onReceive方法处理事件。App进程&#xff08;Activity/Service等组件内&#xff09;ReceiverDispatcher将BroadcastReceiver封…

OpenCV计算机视觉实战(16)——图像分割技术

OpenCV计算机视觉实战&#xff08;16&#xff09;——图像分割技术0. 前言1. 分水岭算法1.1 应用场景1.2 实现过程2. GrabCut 交互式分割2.1 应用场景2.2 实现过程3. FloodFill3.1 应用场景3.2 实现过程小结系列链接0. 前言 图像分割是计算机视觉中将像素划分为具有特定语义或…

Coturn打洞服务器

* 概念理解&#xff1a;1. SDP协议&#xff1a;会话描述协议&#xff0c;视频通话的双方通过交换SDP信息进行媒体协商&#xff0c;从而选择使用某一相同的媒体协议进行通信&#xff1b;TLS协议&#xff1a;基于TCP的安全层传输协议DTLS协议&#xff1a;基于UDP的安全层传输协议…

python flusk 监控

# 创建虚拟环境目录 python3 -m venv /sda1/xunjian/venv # 激活虚拟环境 source /sda1/xunjian/venv/bin/activate # 激活后终端会显示 (venv)创建虚拟环境&#xff08;在当前目录&#xff09;&#xff1a;bashpython3 -m venv venv激活虚拟环境&#xff1a;bashsource venv/b…

VUE2 项目学习笔记 ? 语法 v-if/v-show

?语法页面渲染的时候&#xff0c;需要服务器传过来的对象中的一个属性&#xff0c;然后根据这个属性用v-for渲染标签&#xff0c;这里写的v-for".... in dataList.goodsList"但是当解析到这行语法的时候&#xff0c;dataList还没返回&#xff0c;因此控制台会报错找…

使用qemu命令启动虚拟机

1. 安装相关软件 yum install qemu edk2* libvirt -y 启动libvirt服务 systemctl start libvirtd systemctl status libvirtd2. 创建虚拟机 2.1. qemu启动命令示例 /usr/bin/qemu-system-loongarch64 \-machine virt,accelkvm \-nodefaults \-m 2048 \-smp 2,maxcpus4,co…

大模型系统化学习路线

人工智能大模型系统化学习路线一、基础理论筑基&#xff08;1-2个月) 目标&#xff1a;建立大模型核心认知框架 核心内容&#xff1a; 深度学习基础&#xff1a;神经网络原理、CNN/RNN结构、梯度下降算法大模型本质&#xff1a;Transformer架构&#xff08;重点掌握注意力机制、…

LLaMA-Factory 微调可配置的模型基本参数

LLaMA-Factory 微调可配置的模型基本参数 flyfish 基本参数 一、模型加载与路径配置参数名类型描述默认值model_name_or_pathOptional[str]模型路径&#xff08;本地路径或 Huggingface/ModelScope 路径&#xff09;。Noneadapter_name_or_pathOptional[str]适配器路径&#xf…

Ubuntu 22 安装 ZooKeeper 3.9.3 记录

Ubuntu 22 安装 ZooKeeper 3.9.3 记录 本文记录在 Ubuntu 22.04 系统上安装 ZooKeeper 3.9.3 的过程&#xff0c;包含 Java 环境准备、配置文件调整、启动与停机操作、以及如何将 ZooKeeper 注册为系统服务。 一、准备环境 ZooKeeper 3.9.x 要求 Java 11 或更高版本&#xff…

FreeSwitch通过Websocket(流式双向语音)对接AI实时语音大模型技术方案(mod_ppy_aduio_stream)

FreeSwitch通过WebSocket对接AI实时语音大模型插件技术方案1. 方案概述 基于FreeSWITCH的实时通信能力&#xff0c;通过WebSocket协议桥接AI大模型服务&#xff0c;实现低延迟、高并发的智能语音交互系统。支持双向语音流处理、实时ASR/TTS转换和动态业务指令执行。 1753095153…

航班调度优化策略全局概览

在机场关闭场景下的航班恢复工作&#xff0c;是将机场关闭期间所有的航班进行取消然后恢复还是将机场关闭期间航班全部延误而后调整呢&#xff1f;简单来说&#xff0c;在实际操作中&#xff0c;既不是无差别地全部取消&#xff0c;也不是无差别地全部延误。这两种“一刀切”的…

spring boot 异步线程@Async 传递 threadLocal数据

将父类的 threadLocal 的数据 在线程池时&#xff0c;可以转给子线程使用。 Async 的使用。 第一步在启动服务加上 EnableAsync 注解。 EnableAsync public class NetCoreApplication {... ... }第二步&#xff1a;导入阿里 线程工具类<dependency><groupId>com.a…

AI产品经理成长记《零号列车》第一集 邂逅0XAI列车

《零号列车》绝非传统意义上的 AI 产品经理教程 —— 它是我沉淀二十多年跨行业数字化转型与工业 4.0 实战经验后,首创的100集大型小说体培养指南。那些曾在千行百业验证过的知识与经验,不再是枯燥的文字堆砌,而是化作一场沉浸式的学习旅程。​ 这里没有生硬的理论灌输,而…

[C++11]范围for循环/using使用

范围for循环 范围for循环&#xff08;Range-based for loop&#xff09;是 C11 引入的一种简洁的循环语法&#xff0c;用于遍历容器中的元素或者其他支持迭代的数据结构。 范围for循环可以让代码更加简洁和易读&#xff0c;避免了传统for循环中索引的操作。 下面是范围for循环的…

简单了解下npm、yarn 和 pnpm 中 add 与 install(i) 命令的区别(附上两图带你一目明了)

目录 pnpm 中 add 和 i 的区别 npm 中 add 和 i 的区别 yarn 中 add 和 i 的区别 附上两图带你一目明了&#xff1a; npm、yarn和pnpm的三者区别图&#xff1a; i 和 add 的核心区别图&#xff1a; 个人建议&#xff1a;在项目中保持命令使用的一致性&#xff0c;选择一种…

ESP32-S3学习笔记<2>:GPIO的应用

ESP32-S3学习笔记&#xff1c;2&#xff1e;&#xff1a;GPIO的应用1. 头文件包含2. GPIO的配置2.1 pin_bit_mask2.2 mode2.3 pull_up_en和pull_down_en2.4 intr_type3. 设置GPIO输出/获取GPIO输入4. 中断的使用4.1 gpio_install_isr_service4.2 gpio_isr_handler_add4.3 gpio_…

得物视觉算法面试30问全景精解

得物视觉算法面试30问全景精解 ——潮流电商 商品鉴别 视觉智能&#xff1a;得物视觉算法面试核心考点全览 前言 得物App作为中国领先的潮流电商与鉴别平台&#xff0c;持续推动商品识别、真假鉴别、图像搜索、内容审核、智能推荐等视觉AI技术的创新与落地。得物视觉算法岗位…

[Linux入门] Linux 账号和权限管理入门:从基础到实践

一、Linux 用户账号&#xff1a;谁能访问系统&#xff1f; 1️⃣超级用户&#xff08;root&#xff09; 2️⃣普通用户 3️⃣程序用户 二、组账号&#xff1a;让用户管理更高效 1️⃣组的类型 2️⃣特殊组 三、用户与组的 “身份证”&#xff1a;UID 和 GID 四、配置文…