CLIP-VIT-L + Qwen 多模态源码阅读 - 语言模型篇(3)

多模态学习笔记 - 语言模型篇(3)

参考repo:WatchTower-Liu/VLM-learning; url: VLLM-BASE

吐槽

今天接着昨天的源码继续看,黑神话:悟空正好今天发售,希望广大coder能玩的开心~

学习心得

前情提要

详情请看多模态源码阅读 - 2
上次我们讲到利用view()函数对token_type_ids、position_ids进行重新塑形,确保这些张量的最后一个维度和input_shape(输入序列数据)的最后一个维度相等。重构的代码中默认启用缓存键值对(显然use_cache的bool值有点可有可无了QAQ),如果past_key_values的值为空,代表处于推理或者训练的第一步,此时我们初始化past_length为0,初始化past_key_values为长度为Qwen模型层数量的元组,self.h是Qwen模型的成员变量,我们无需太过关心(因为我们只是继承Qwen模型的成员变量,并重构了forward方法)。
如果我们当前不处于训练或推理的第一步,past_key_values显然就不为空(因为我们默认启用缓存键值对,ps:科研级代码是这样的),不管缓存量化(use_cache_quantization)启用与否,我们将past_length更新为第一个注意力头键张量的第二个或倒数第二个维度。这里唯一的区别只是元组的维数和维度不太一样。
如果position_ids为None,我们需要初始化一个position_ids,起始位置为past_length,终止位置为psst_lenght + input_shape[=1],确保我们的position_ids长度与input_shape的最后一个维度相等,随后重新塑形,同样是为了确保position_ids为二维张量,且最后一个维度与input_shape对齐,代码如下:

        if token_type_ids is not None:token_type_ids = token_type_ids.view(-1, input_shape[-1])if position_ids is not None:position_ids = position_ids.view(-1, input_shape[-1])if past_key_values is None:past_length = 0past_key_values = tuple([None] * len(self.h))else:if self.use_cache_quantization:past_length = past_key_values[0][0][0].size(2)else:past_length = past_key_values[0][0].size(-2)if position_ids is None:position_ids = torch.arange(past_length,input_shape[-1] + past_length,dtype=torch.long,device=device,)position_ids = position_ids.unsqueeze(0).view(-1, input_shape[-1])

新的记忆

代码块1

接着上面的代码,继续看MQwen.py中MQwenModel中重构的forward方法,代码如下:

		if attention_mask is not None:# image_feaute_length = self.otherConfig["image_context_length"]*self.otherConfig["image_feature_hidden_size"]# attention_mask_length = attention_mask.shape[-1] - image_feaute_length + self.otherConfig["image_context_length"]# attention_mask = torch.ones((batch_size, attention_mask_length), dtype=torch.long, device=device)if batch_size <= 0:raise ValueError("batch_size has to be defined and > 0")attention_mask = attention_mask.view(batch_size, -1)attention_mask = attention_mask[:, None, None, :]attention_mask = attention_mask.to(dtype=self.dtype)attention_mask = (1.0 - attention_mask) * torch.finfo(self.dtype).min

如果没有传入attention_mask,我们需要根据batch_size重塑一个注意力掩码,注意力掩码用于告诉模型应该关注和忽略序列数据中的哪些部分,并且防止信息泄露,在处理序列到序列任务时利用未来信息生成当前输出。
首先检测传入的batch_size是否小于等于0,这很显然,对于空数据,是无法初始化一个合法的注意力掩码的。
如果batch_size合法,我们重塑attention_mask的第一个维度为batch_size。并且将attention_mask扩展为一个四维张量,其中第二第三维度为1,attention_mask的维度大致为(batch_size,1,1,未知),扩展为四维是为了适用于多头机制,对每一个头的输出进行操作。而后将attention_mask的数据类型变更为self.dtype这一题继承而来的成员变量。对于attention_mask的值进行翻转,将原先的1变为0,0变为1,然后让attention_mask乘以一个极大的负数。这样做的目的是让应该被忽略的地方变为一个极大的负数,而被注意的地方仍为0,考虑到softmax函数如下:
S o f t m a x ( x 1 ) = e x i ∑ j e x j Softmax(x_1) = \frac{e^{x_i}}{\sum_{j}e^{x_j}} Softmax(x1)=jexjexi
其中 x i x_i xi是输入序列中当前元素的掩码值, x j x_j xj代表任意元素的掩码值。如果掩码值为0, e x i e^{x_i} exi的值为1,如果掩码只为极大负数,值趋近于0而不为0。
这样做的目的是为了让模型完全忽略本不应该关注的部分。如果按照原先的mask,我们将应当被忽略的地方置0,在softmax操作时,幂0的e值为1,仍然会对输出有贡献,如果将其变为一个极大的负数,那么它就能真正的趋于0,被完全忽略。

代码块2

        encoder_attention_mask = Nonehead_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers)

我们将encoder_attention_mask置为None,在多模态场景中,Qwen作为解码器使用,不需要encoder_attention_mask,后续也没有给它赋值。head_mask则使用继承成员方法self.get_mask获取,传入两个参数,一个是head_mask,默认为None,一个是隐藏层层数,头掩码用来选择性地忽略部分头的输出,效果与attention_mask类似

代码块3

        if inputs_embeds is None:inputs_embeds = self.wte(input_ids)hidden_states = inputs_embedsif images is not None and first_step:new_hidden_states = []for b_idx, img_idx in enumerate(image_index):new_hidden_states.append(torch.cat([hidden_states[b_idx][:img_idx], images[b_idx], hidden_states[b_idx][img_idx:]], dim = 0))   #############  concat image and texthidden_states = torch.stack(new_hidden_states, dim = 0).to(hidden_states)

如果没有传入input_embeds,将传入的input_ids利用成员方法生成inputs_embeds,并将其作为初始的隐藏状态。
如果我们当前处于推理或者训练的第一步,并且传入了图像数据,就对图像数据和文本数据进行融合,具体来说,我们先新初始化一个列表new_hidden_states用于存储每个批次的合并数据。image_index在多模态大模型学习笔记 - 1中说明过,用来判断每个输入序列数据中图像信息的起始位置。利用torch.cat方法,将每一批次的图像信息插入到word_embeds中,最后再用torch,stack堆叠为一个新的批次,至此,图像数据和文本数据的融合完毕。

代码块4

        kv_seq_len = hidden_states.size()[1]if past_key_values[0] is not None:# past key values[0][0] shape: bs * seq_len * head_num * dimif self.use_cache_quantization:kv_seq_len += past_key_values[0][0][0].shape[2]else:kv_seq_len += past_key_values[0][0].shape[1]

hidden_states的size大致为(batch_size, new_seq_len, 未知),new_seq_len是原始的文本数据序列长度加上图上数据序列长度,kv_seq_len获取不同模态数据合并后的序列长度
如果发现有过去缓存的键值对信息,我们就对kv_seq_len进行累加,这里的shape有点抽象,我们只用知道这些都是以缓存键值对的序列长度即可~

代码块5(ntk,选看)

        if self.training or not self.use_dynamic_ntk:ntk_alpha_list = [1.0]elif kv_seq_len != hidden_states.size()[1]:ntk_alpha_list = self.rotary_emb._ntk_alpha_cached_listelse:ntk_alpha_list = []if attention_mask is not None and kv_seq_len > self.seq_length:true_seq_lens = attention_mask.squeeze(1).squeeze(1).eq(0).sum(dim=-1, dtype=torch.int32)for i in range(hidden_states.size()[0]):true_seq_len = true_seq_lens[i].item()ntk_alpha = self.get_ntk_alpha(true_seq_len)ntk_alpha_list.append(ntk_alpha)else:ntk_alpha = self.get_ntk_alpha(kv_seq_len)ntk_alpha_list.append(ntk_alpha)

NTK比较复杂,作用也很多,这里不展开说,它的主要目的是加速收敛,线性化训练动态,提高模型解释性等(ps:我也不知道干啥用的,但感觉是用来分析模型的训练和决策过程,增强可解释性的)。
首先我们检查当前是否处于训练状态,并且不使用动态NTK,如果是,我们初始化NTK系数为1.0。
反之,我们进一步判断kv_seq_len是否和hidden_states的seq_len长度相等,假如我们先前更新了kv_seq_len的长度,即我们有以缓存的键值对,那么这里必然是不相等的,我们初始化一个ntk_alpha_list,这里调用的是继承的成员变量。
其他情况,我们初始化一个空的ntk_alpha_list,如果存在attention_mask且kv_seq_len大于继承的成员变量self.seq_len,我们用attenrion_mask计算序列的实际长度,这里去除掉四维张量attenrion_mask的中间两个维度,计算seq_len维度中指为0的元素数量(由于之前翻转了attention_mask,所以值为0代表我们需要关注的元素)。我们获取每个批次的true_seq_len,并利用成员方法获取ntk_alpha值,添加到之前初始化的ntk_alpha_list中。
如果没有提供注意力掩码或键值序列长度不大于设定的序列长度,直接为整个键值序列长度计算一个NTK缩放因子,并添加到列表中。
ps:最一头雾水的代码块。

代码块6

        self.rotary_emb._ntk_alpha_cached_list = ntk_alpha_listrotary_pos_emb_list = [self.rotary_emb(kv_seq_len, ntk_alpha=ntk_alpha) for ntk_alpha in ntk_alpha_list]hidden_states = self.drop(hidden_states)

将初始化好的ntk_alpha_list缓存到_ntk_alpha_cached_list中,以便重复利用,调用self.rotary_emb方法生成旋转嵌入,传递参数皆在之前初始化完成,生成的旋转嵌入都存储于旋转嵌入列表中。
最后启用dropout随即将一些激活值置为0,提高泛化能力,防止过拟合。

代码块7

        output_shape = input_shape + (hidden_states.size(-1),)if self.gradient_checkpointing and self.training:if use_cache:logger.warning_once("`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`...")use_cache = False

回顾一下inpui_shape的size为(batch_size,text_seq_len + image_seq_len)是一个二维张量,这里再加上hidden_stete的最后一个维度,结合为三维张量,其中hidden_state的最后一个维度就是多模态数据融合后的embed_size(参考之前代码块3中的融合过程)。
如果启用了梯度累积,并且当前处于训练状态,我们检查是否启用了缓存,由于梯度累积和缓存冲突,将use_cache置为False。梯度累积是一个内存优化技术,可以模拟大batch_size的训练,多次小批量训练后将梯度累积,并一次性用于优化器更新权重,这样能够让小批量训练类似于使用大批量训练,提高训练的稳定性和性能。

代码块8

        presents = () if use_cache else Noneall_self_attentions = () if output_attentions else Noneall_hidden_states = () if output_hidden_states else Nonefor i, (block, layer_past) in enumerate(zip(self.h, past_key_values)):if output_hidden_states:all_hidden_states = all_hidden_states + (hidden_states,)if self.gradient_checkpointing and self.training:def create_custom_forward(module):def custom_forward(*inputs):# None for past_key_valuereturn module(*inputs, use_cache, output_attentions)return custom_forwardoutputs = torch.utils.checkpoint.checkpoint(create_custom_forward(block),hidden_states,rotary_pos_emb_list,None,attention_mask,head_mask[i],encoder_hidden_states,encoder_attention_mask,)else:outputs = block(hidden_states,layer_past=layer_past,rotary_pos_emb_list=rotary_pos_emb_list,attention_mask=attention_mask,head_mask=head_mask[i],encoder_hidden_states=encoder_hidden_states,encoder_attention_mask=encoder_attention_mask,use_cache=use_cache,output_attentions=output_attentions,)hidden_states = outputs[0]if use_cache is True:presents = presents + (outputs[1],)if output_attentions:all_self_attentions = all_self_attentions + (outputs[2 if use_cache else 1],)

presents用于缓存键值对信息,如果启用了use_cache。
all_self_attentions用于输出每一层的注意力分数
all_hidden_states则存储每一层的隐藏层状态。
遍历模型的每一层,如果我们要输出每一层的隐藏状态,就添加当前层的隐藏状态进入元祖all_hidden_states。
如果启用了梯度累积技术,并且当前处于训练状态,我们就新建一个工厂函数,这个函数接受一个模块,并且返回一个新的函数customer_forward,这个函数可以在调用原始函数前向传播的同时,传递入新的参数。
使用pytorch的checkpoint函数执行前向传播,传递参数含义如下:
create_custom_forward(block):当前层的自定义前向传播函数,
hidden_states:当前层的隐藏状态
rotary_pos_emb_list:旋转位置嵌入列表,在之前初始化完成
各种mask:用于控制模型注意和忽略的部分
encoder_hidden_states:编码器的隐藏状态
反之,直接调用当前层的网络块进行前向传播计算,参数含义前文中都有说明,不再赘述。
从outputs中提取到当前层的隐藏状态。
判断是否启动缓存,如果启用,将当前层计算得到的键值对存储到prensents元组中。
如果output_attentions为True,将自注意力力权重存入all_self_attentions,这里根据是否启动缓存,索引有所不同。

代码块9

        hidden_states = self.ln_f(hidden_states)hidden_states = hidden_states.view(output_shape)# Add last hidden stateif output_hidden_states:all_hidden_states = all_hidden_states + (hidden_states,)

首先对hidden_states执行层归一化操作,提高训练过程的稳定性。然后将hidden_states塑形为out_put_shape,在之前有提及,就是Input_shape + 图像和文本embed合并后的最后一个维度,如果out_put_hidden_states为True,将当前层归一化后的hidden_states添加入all_hidden_states。

代码块10

        if not return_dict:return tuple(v for v in [hidden_states, presents, all_hidden_states] if v is not None)return BaseModelOutputWithPast(last_hidden_state=hidden_states,past_key_values=presents,hidden_states=all_hidden_states,attentions=all_self_attentions,)

这段代码主要处理的是返回值类型。如果不要求返回值为字典类型,则返回一个元祖,对于hidden_states等元组,依次遍历里面不为None的元素并返回。
如果返回字典,我们创建一个自定义类BaseModelOutputWithPast的实例,将各种元组传递进去,最后的返回值应该是一个字典类型的数据。
至此,MQwenModel类的forward源码看完,下面要看的就是MQwenLMHeadModel的源码。
QwenModel是基座模型,包含了Qwen的主要架构,QwenLMHeadModel 是在 QwenModel 的基础上增加了一个或多个特定的下游任务头,可以用于特定的下游任务。

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

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

相关文章

设计模式笔记01(java版)

文章目录 设计模式概述学习设计模式的必要性设计模式分类创建型模式结构型模式行为型模式 UML类图概述类图的作用类图表示法类的表示方式类与类之间关系的表示方式1&#xff0c;单向关联2&#xff0c;双向关联3&#xff0c;自关联聚合关系组合关系依赖关系继承关系实现关系 软件…

Pytorch 张量运算函数(补充)

mean() mean()函数是进行张量均值计算的函数,常用参数可以设置参数dim来进行对应维度的均值计算 以下是使用一个二维张量进行演示的例子 import numpy as np import torch device torch.device(mps if torch.backends.mps.is_available() else cpu) print(device ) data1 …

C++ | Leetcode C++题解之第367题有效的完全平方数

题目&#xff1a; 题解&#xff1a; class Solution { public:bool isPerfectSquare(int num) {double x0 num;while (true) {double x1 (x0 num / x0) / 2;if (x0 - x1 < 1e-6) {break;}x0 x1;}int x (int) x0;return x * x num;} };

linux系统,ubuntu安装英伟达NVIDIA4090显卡驱动

文章目录 前言下载英伟达NVIDIA官方驱动安装NVIDIA驱动远程安装关闭交互界面设置权限&#xff08;自己确认版本号5&#xff09;安装&#xff08;自己确认版本号5&#xff09;打开交互界面&#xff0c;并重启系统验证是否安装成功 异常处理问题1问题2问题3&#xff08;可能没解决…

Linux 支持程序在运行时动态加载和卸载共享库,动态链接库

动态链接库 Linux支持动态链接库&#xff08;共享库&#xff09;的概念&#xff0c;允许程序在运行时动态加载和卸载共享库。这有助于减小可执行文件的大小&#xff0c;共享代码&#xff0c;提高代码的可重用性。 #include <dlfcn.h> #include <iostream>int mai…

python小游戏——躲避球(可当课设)

游戏简介&#xff1a; 没有美术&#xff0c;画面简洁&#xff08;懒得做&#xff09;。玩家控制小球躲避敌人&#xff08;上下左右&#xff0c;闪避&#xff09;&#xff0c;敌人体积越大速度越慢&#xff0c;随机生成道具球&#xff08;目前只有生命球&#xff09;&#xff0…

day_49

42. 接雨水 class Solution:def trap(self, height: List[int]) -> int:stack []res 0for i in range(len(height)):while(stack and height[i] > height[stack[-1]]):mid stack.pop()if stack:h min(height[i], height[stack[-1]]) - height[mid]w i - stack[-1] …

​14:00面试,14:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到5月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

Redis内存淘汰

Redis内存淘汰 Redis可以存储多少数据 maxmemory配置&#xff0c;默认是注释掉的。 #maxmemory <bytes>我们可以主动配置maxmemory&#xff0c;maxmemory支持各种单位&#xff0c;默认是字节 maxmemory 1024 maxmemory 1024KB maxmemory 1024MB maxmemory 1024GB当Re…

跟着GPT学习 Kubernetes ,简称 K8s -- Kind(三)

在 Mac M1 上使用 Kind&#xff08;Kubernetes in Docker&#xff09;学习 Kubernetes 是一个非常合适的选择&#xff0c;因为 Kind 可以在本地轻松地创建一个 Kubernetes 集群&#xff0c;适合进行开发、测试和学习。让我们一步一步地从头开始&#xff0c;设置你的环境并逐步学…

shallowReactive 与 shallowRef

除了之前的 ref与reactive 之外&#xff0c;Vue3 还准备了另外两个API&#xff0c;也是用来对响应式数据做处理&#xff0c;那就是 shallowReactive 与 shallowRef shallowReactive 文档解释&#xff1a;reactive() 的浅层作用形式&#xff0c;只能定义对象类型的数据。和 r…

Spring Boot整合Sentry

Spring Boot整合Sentry Sentry搭建Sentry中新建项目集成SpringBoot1. 添加依赖2. 配置Sentry4. 日志集成&#xff08;可选&#xff09;5. 测试Sentry集成6. 配置实时告警配置Alert Settings配置警报规则 发送消息服务代码参照文档 Sentry 是一个日志平台&#xff0c;分为客户端…

AI 未来两年:史无前例的变革与挑战

2024 年 8 月 20 日&#xff0c;正站在科技变革的风暴中心&#xff0c;见证着人工智能&#xff08;AI&#xff09;以惊人的速度重塑着世界。谷歌前 CEO Eric Schmidt 对 AI 未来两年的预测&#xff0c;引起了广泛关注&#xff0c;如今 YC 的 CEO Garry Tan 也高度赞同这些观点&…

CMakeLists.txt模板

#设置编译该CMakeLists.txt文件所需要的最低cmake版本 CMAKE_MINIMUM_REQUIRED(VERSION 最低版本号)# 该项目名称 PROJECT(项目名称)SET(CMAKE_CXX_STANDARD 11)SET(CMAKE_C_STANDARD 11)# 此行可以理解为将路径下所有的文件装载到自定义集合filelist中 FILE(GLOB filelist &q…

Vite + Vue 3 项目中实现路由自动化完整步骤。

下面是从创建项目到实现路由自动化的完整步骤 在 Vite Vue 3 项目中实现路由自动化可以通过使用文件系统路由生成器来简化路由管理过程。以下是实现路由自动化的完整步骤&#xff1a; 1.创建 Vite Vue 3 项目 如果你还没有一个 Vite Vue 3 项目&#xff0c;可以使用以下命令…

利用MongoDB进行数据治理,防范构建生成式AI应用程序时的潜在安全风险

生成式人工智能&#xff08;生成式AI&#xff09;正在蓬勃发展&#xff0c;许多企业和初创公司正在运用AI工具来解决各自的用例问题。随着企业逐渐适应市场上的新技术范式转移&#xff0c;开发者社区和开源模型也在不断发展壮大。 构建智能生成式AI应用程序需要灵活运用数据。…

基础算法--高精度数据(1)

高精度数据处理一般内容简单&#xff0c;写代码难度较大&#xff0c;可能部分内容涉及基础数学、初等数论等知识。请小心食用。不过本节不会给大家太难的高精度处理&#xff0c;我们第一次接触&#xff0c;不能劝退大家对吧。 高精度算法是指&#xff0c;利用基础或高级的数学…

盘古信息IMS MCM制造协同管理系统:为中小企业数字化转型量身打造的数字化方案

近年来&#xff0c;全球经济的不稳定性&#xff0c;给中小企业的经营和发展带来了巨大的挑战。为提升企业竞争力&#xff0c;中小企业纷纷谋求数字化转型路径&#xff0c;优化生产流程、提高运营效率、降低生产成本&#xff0c;以应对变幻莫测的市场环境。IMS MCM是盘古信息为广…

进阶-4.视图、存储过程、存储函数、触发器

视图、存储过程、存储函数、触发器 1.视图1.1 介绍1.2 语法1.3 视图的检查选项1.4 视图的更新1.5 视图作用1.6 案例 2.存储过程21. 介绍2.2 特点2.3 语法2.4 变量2.4.1 系统变量2.4.2用户自定义变量2.4.3 局部变量 2.5参数2.6条件语句2.6.1 if 语法2.6.2 case 2.7循环结构2.7.1…

【数据库和数据仓库】

数据仓库和数据库主要有以下区别&#xff1a; 一、用途不同 数据库&#xff1a;就像一个日常的储物间&#xff0c;主要用于存储和管理日常业务操作中的各种数据&#xff0c;比如电商网站中用户的下单信息、银行系统中客户的交易记录等。它的目的是支持业务的实时交易处理&…