GLM-4v-9B-源码解析-五-
GLM-4v-9B 源码解析(五)
license: other
license_name: glm-4
license_link: https://huggingface.co/THUDM/glm-4v-9b/blob/main/LICENSE
language:
- zh
- en
tags: - glm
- chatglm
- thudm
inference: false
GLM-4V-9B 源码解析
Read this in English
2024/08/12, 本仓库代码已更新并使用 transforemrs>=4.44.0
, 请及时更新依赖。
GLM-4V-9B 是智谱 AI 推出的最新一代预训练模型 GLM-4 系列中的开源多模态版本。
GLM-4V-9B 具备 1120 * 1120 高分辨率下的中英双语多轮对话能力,在中英文综合能力、感知推理、文字识别、图表理解等多方面多模态评测中,GLM-4V-9B 表现出超越 GPT-4-turbo-2024-04-09、Gemini
1.0 Pro、Qwen-VL-Max 和 Claude 3 Opus 的卓越性能。
多模态能力
GLM-4V-9B 是一个多模态语言模型,具备视觉理解能力,其相关经典任务的评测结果如下:
MMBench-EN-Test | MMBench-CN-Test | SEEDBench_IMG | MMStar | MMMU | MME | HallusionBench | AI2D | OCRBench | |
---|---|---|---|---|---|---|---|---|---|
英文综合 | 中文综合 | 综合能力 | 综合能力 | 学科综合 | 感知推理 | 幻觉性 | 图表理解 | 文字识别 | |
GPT-4o, 20240513 | 83.4 | 82.1 | 77.1 | 63.9 | 69.2 | 2310.3 | 55 | 84.6 | 736 |
GPT-4v, 20240409 | 81 | 80.2 | 73 | 56 | 61.7 | 2070.2 | 43.9 | 78.6 | 656 |
GPT-4v, 20231106 | 77 | 74.4 | 72.3 | 49.7 | 53.8 | 1771.5 | 46.5 | 75.9 | 516 |
InternVL-Chat-V1.5 | 82.3 | 80.7 | 75.2 | 57.1 | 46.8 | 2189.6 | 47.4 | 80.6 | 720 |
LlaVA-Next-Yi-34B | 81.1 | 79 | 75.7 | 51.6 | 48.8 | 2050.2 | 34.8 | 78.9 | 574 |
Step-1V | 80.7 | 79.9 | 70.3 | 50 | 49.9 | 2206.4 | 48.4 | 79.2 | 625 |
MiniCPM-Llama3-V2.5 | 77.6 | 73.8 | 72.3 | 51.8 | 45.8 | 2024.6 | 42.4 | 78.4 | 725 |
Qwen-VL-Max | 77.6 | 75.7 | 72.7 | 49.5 | 52 | 2281.7 | 41.2 | 75.7 | 684 |
GeminiProVision | 73.6 | 74.3 | 70.7 | 38.6 | 49 | 2148.9 | 45.7 | 72.9 | 680 |
Claude-3V Opus | 63.3 | 59.2 | 64 | 45.7 | 54.9 | 1586.8 | 37.8 | 70.6 | 694 |
GLM-4v-9B | 81.1 | 79.4 | 76.8 | 58.7 | 47.2 | 2163.8 | 46.6 | 81.1 | 786 |
本仓库是 GLM-4V-9B 的模型仓库,支持8K
上下文长度。
运行模型
更多推理代码和依赖信息,请访问我们的 github。
请严格按照依赖安装,否则无法正常运行。
。
import torch
from PIL import Image
from transformers import AutoModelForCausalLM, AutoTokenizer
device = "cuda"
tokenizer = AutoTokenizer.from_pretrained("THUDM/glm-4v-9b", trust_remote_code=True)
query = '描述这张图片'
image = Image.open("your image").convert('RGB')
inputs = tokenizer.apply_chat_template([{"role": "user", "image": image, "content": query}],
add_generation_prompt=True, tokenize=True, return_tensors="pt",
return_dict=True) # chat mode
inputs = inputs.to(device)
model = AutoModelForCausalLM.from_pretrained(
"THUDM/glm-4v-9b",
torch_dtype=torch.bfloat16,
low_cpu_mem_usage=True,
trust_remote_code=True
).to(device).eval()
gen_kwargs = {"max_length": 2500, "do_sample": True, "top_k": 1}
with torch.no_grad():
outputs = model.generate(**inputs, **gen_kwargs)
outputs = outputs[:, inputs['input_ids'].shape[1]:]
print(tokenizer.decode(outputs[0]))
协议
GLM-4 模型的权重的使用则需要遵循 LICENSE。
引用
如果你觉得我们的工作有帮助的话,请考虑引用下列论文。
@misc{glm2024chatglm,
title={ChatGLM: A Family of Large Language Models from GLM-130B to GLM-4 All Tools},
author={Team GLM and Aohan Zeng and Bin Xu and Bowen Wang and Chenhui Zhang and Da Yin and Diego Rojas and Guanyu Feng and Hanlin Zhao and Hanyu Lai and Hao Yu and Hongning Wang and Jiadai Sun and Jiajie Zhang and Jiale Cheng and Jiayi Gui and Jie Tang and Jing Zhang and Juanzi Li and Lei Zhao and Lindong Wu and Lucen Zhong and Mingdao Liu and Minlie Huang and Peng Zhang and Qinkai Zheng and Rui Lu and Shuaiqi Duan and Shudan Zhang and Shulin Cao and Shuxun Yang and Weng Lam Tam and Wenyi Zhao and Xiao Liu and Xiao Xia and Xiaohan Zhang and Xiaotao Gu and Xin Lv and Xinghan Liu and Xinyi Liu and Xinyue Yang and Xixuan Song and Xunkai Zhang and Yifan An and Yifan Xu and Yilin Niu and Yuantao Yang and Yueyan Li and Yushi Bai and Yuxiao Dong and Zehan Qi and Zhaoyu Wang and Zhen Yang and Zhengxiao Du and Zhenyu Hou and Zihan Wang},
year={2024},
eprint={2406.12793},
archivePrefix={arXiv},
primaryClass={id='cs.CL' full_name='Computation and Language' is_active=True alt_name='cmp-lg' in_archive='cs' is_general=False description='Covers natural language processing. Roughly includes material in ACM Subject Class I.2.7. Note that work on artificial languages (programming languages, logics, formal systems) that does not explicitly address natural-language issues broadly construed (natural-language processing, computational linguistics, speech, text retrieval, etc.) is not appropriate for this area.'}
}
@misc{wang2023cogvlm,
title={CogVLM: Visual Expert for Pretrained Language Models},
author={Weihan Wang and Qingsong Lv and Wenmeng Yu and Wenyi Hong and Ji Qi and Yan Wang and Junhui Ji and Zhuoyi Yang and Lei Zhao and Xixuan Song and Jiazheng Xu and Bin Xu and Juanzi Li and Yuxiao Dong and Ming Ding and Jie Tang},
year={2023},
eprint={2311.03079},
archivePrefix={arXiv},
primaryClass={cs.CV}
}
GLM-4V-9B
2024/08/12, The repository code has been updated and now requires transformers>=4.44.0
. Please update your dependencies accordingly.
GLM-4V-9B is an open source multimodal version of the latest generation of pre-trained models in the GLM-4 series launched by Zhipu AI.
GLM-4V-9B has the ability to conduct multi-round conversations in Chinese and English at a high resolution of 1120 * 1120. In multimodal evaluations of comprehensive Chinese and English abilities, perceptual reasoning, text recognition, and chart understanding, GLM-4V-9B has shown superior performance over GPT-4-turbo-2024-04-09, Gemini
1.0 Pro, Qwen-VL-Max, and Claude 3 Opus.
Multimodal
GLM-4V-9B is a multimodal language model with visual understanding capabilities. The evaluation results of its related classic tasks are as follows:
MMBench-EN-Test | MMBench-CN-Test | SEEDBench_IMG | MMStar | MMMU | MME | HallusionBench | AI2D | OCRBench | |
---|---|---|---|---|---|---|---|---|---|
英文综合 | 中文综合 | 综合能力 | 综合能力 | 学科综合 | 感知推理 | 幻觉性 | 图表理解 | 文字识别 | |
GPT-4o, 20240513 | 83.4 | 82.1 | 77.1 | 63.9 | 69.2 | 2310.3 | 55 | 84.6 | 736 |
GPT-4v, 20240409 | 81 | 80.2 | 73 | 56 | 61.7 | 2070.2 | 43.9 | 78.6 | 656 |
GPT-4v, 20231106 | 77 | 74.4 | 72.3 | 49.7 | 53.8 | 1771.5 | 46.5 | 75.9 | 516 |
InternVL-Chat-V1.5 | 82.3 | 80.7 | 75.2 | 57.1 | 46.8 | 2189.6 | 47.4 | 80.6 | 720 |
LlaVA-Next-Yi-34B | 81.1 | 79 | 75.7 | 51.6 | 48.8 | 2050.2 | 34.8 | 78.9 | 574 |
Step-1V | 80.7 | 79.9 | 70.3 | 50 | 49.9 | 2206.4 | 48.4 | 79.2 | 625 |
MiniCPM-Llama3-V2.5 | 77.6 | 73.8 | 72.3 | 51.8 | 45.8 | 2024.6 | 42.4 | 78.4 | 725 |
Qwen-VL-Max | 77.6 | 75.7 | 72.7 | 49.5 | 52 | 2281.7 | 41.2 | 75.7 | 684 |
GeminiProVision | 73.6 | 74.3 | 70.7 | 38.6 | 49 | 2148.9 | 45.7 | 72.9 | 680 |
Claude-3V Opus | 63.3 | 59.2 | 64 | 45.7 | 54.9 | 1586.8 | 37.8 | 70.6 | 694 |
GLM-4v-9B | 81.1 | 79.4 | 76.8 | 58.7 | 47.2 | 2163.8 | 46.6 | 81.1 | 786 |
This repository is the model repository of GLM-4V-9B, supporting 8K
context length.
Quick Start
For more inference code and requirements, please visit our github page.
Please strictly follow the dependencies to install, otherwise it will not run properly
import torch
from PIL import Image
from transformers import AutoModelForCausalLM, AutoTokenizer
device = "cuda"
tokenizer = AutoTokenizer.from_pretrained("THUDM/glm-4v-9b", trust_remote_code=True)
query = 'discribe this image'
image = Image.open("your image").convert('RGB')
inputs = tokenizer.apply_chat_template([{"role": "user", "image": image, "content": query}],
add_generation_prompt=True, tokenize=True, return_tensors="pt",
return_dict=True) # chat mode
inputs = inputs.to(device)
model = AutoModelForCausalLM.from_pretrained(
"THUDM/glm-4v-9b",
torch_dtype=torch.bfloat16,
low_cpu_mem_usage=True,
trust_remote_code=True
).to(device).eval()
gen_kwargs = {"max_length": 2500, "do_sample": True, "top_k": 1}
with torch.no_grad():
outputs = model.generate(**inputs, **gen_kwargs)
outputs = outputs[:, inputs['input_ids'].shape[1]:]
print(tokenizer.decode(outputs[0]))
License
The use of the GLM-4 model weights needs to comply with the LICENSE.
Citation
If you find our work helpful, please consider citing the following papers.
@misc{glm2024chatglm,
title={ChatGLM: A Family of Large Language Models from GLM-130B to GLM-4 All Tools},
author={Team GLM and Aohan Zeng and Bin Xu and Bowen Wang and Chenhui Zhang and Da Yin and Diego Rojas and Guanyu Feng and Hanlin Zhao and Hanyu Lai and Hao Yu and Hongning Wang and Jiadai Sun and Jiajie Zhang and Jiale Cheng and Jiayi Gui and Jie Tang and Jing Zhang and Juanzi Li and Lei Zhao and Lindong Wu and Lucen Zhong and Mingdao Liu and Minlie Huang and Peng Zhang and Qinkai Zheng and Rui Lu and Shuaiqi Duan and Shudan Zhang and Shulin Cao and Shuxun Yang and Weng Lam Tam and Wenyi Zhao and Xiao Liu and Xiao Xia and Xiaohan Zhang and Xiaotao Gu and Xin Lv and Xinghan Liu and Xinyi Liu and Xinyue Yang and Xixuan Song and Xunkai Zhang and Yifan An and Yifan Xu and Yilin Niu and Yuantao Yang and Yueyan Li and Yushi Bai and Yuxiao Dong and Zehan Qi and Zhaoyu Wang and Zhen Yang and Zhengxiao Du and Zhenyu Hou and Zihan Wang},
year={2024},
eprint={2406.12793},
archivePrefix={arXiv},
primaryClass={id='cs.CL' full_name='Computation and Language' is_active=True alt_name='cmp-lg' in_archive='cs' is_general=False description='Covers natural language processing. Roughly includes material in ACM Subject Class I.2.7. Note that work on artificial languages (programming languages, logics, formal systems) that does not explicitly address natural-language issues broadly construed (natural-language processing, computational linguistics, speech, text retrieval, etc.) is not appropriate for this area.'}
}
@misc{wang2023cogvlm,
title={CogVLM: Visual Expert for Pretrained Language Models},
author={Weihan Wang and Qingsong Lv and Wenmeng Yu and Wenyi Hong and Ji Qi and Yan Wang and Junhui Ji and Zhuoyi Yang and Lei Zhao and Xixuan Song and Jiazheng Xu and Bin Xu and Juanzi Li and Yuxiao Dong and Ming Ding and Jie Tang},
year={2023},
eprint={2311.03079},
archivePrefix={arXiv},
primaryClass={cs.CV}
}
- GLM-4V-9B 源码解析
.\chatglm4v-9b\configuration_chatglm.py
.\chatglm4v-9b\modeling_chatglm.py
- GLM-4V-9B
.\chatglm4v-9b\tokenization_chatglm.py
.\chatglm4v-9b\visual.py
.\chatglm4v-9b\tokenization_chatglm.py
# 导入所需的库和模块
import regex as re # 正则表达式库
import base64 # 用于处理 Base64 编码
import os # 提供与操作系统交互的功能
import json # 处理 JSON 数据
import tiktoken # 处理文本编码
import torch # 深度学习框架
from torch import TensorType # 从 torch 导入 TensorType 类型
from typing import List, Optional, Union, Dict, Any # 类型提示
from torchvision import transforms # 计算机视觉的转换工具
from transformers import PreTrainedTokenizer # 导入预训练的分词器基类
from transformers.utils import logging, PaddingStrategy # 导入日志和填充策略
from transformers.tokenization_utils_base import EncodedInput, BatchEncoding # 导入编码输入和批处理编码类
# 定义 ChatGLM4Tokenizer 类,继承自 PreTrainedTokenizer
class ChatGLM4Tokenizer(PreTrainedTokenizer):
# 词汇文件名称
vocab_files_names = {"vocab_file": "tokenizer.model"}
# 模型输入名称列表
model_input_names = ["input_ids", "attention_mask", "position_ids"]
# 初始化方法
def __init__(
self,
vocab_file, # 词汇文件路径
padding_side="left", # 填充方向
clean_up_tokenization_spaces=False, # 是否清理标记化空格
encode_special_tokens=False, # 是否编码特殊标记
image_size=None, # 图像大小
**kwargs # 其他关键字参数
):
self.name = "GLM4Tokenizer" # 设置分词器名称
self.vocab_file = vocab_file # 设置词汇文件
# 定义正则表达式模式字符串
pat_str = "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+"
self.pat_str = re.compile(pat_str) # 编译正则表达式
self.encode_special_tokens = encode_special_tokens # 设置特殊标记编码标志
self.image_size = image_size # 设置图像大小
mergeable_ranks = {} # 初始化可合并的排名字典
with open(vocab_file) as f: # 打开词汇文件
for line in f: # 遍历文件每一行
token, rank = line.strip().split() # 拆分标记和排名
rank = int(rank) # 将排名转换为整数
token = base64.b64decode(token) # 解码 Base64 标记
mergeable_ranks[token] = rank # 将标记和排名添加到字典中
self.mergeable_ranks = mergeable_ranks # 保存可合并的排名字典
# 初始化 tiktoken 编码器
self.tokenizer = tiktoken.Encoding(
name="my_tokenizer", # 设置编码器名称
pat_str=pat_str, # 设置正则表达式模式
mergeable_ranks=mergeable_ranks, # 设置可合并的排名
special_tokens={} # 初始化特殊标记为空字典
)
self.decoder = {rank: token for token, rank in mergeable_ranks.items()} # 反转可合并排名字典为解码器
self.n_words = len(self.decoder) # 计算单词数量
# 调用父类初始化方法
super().__init__(
padding_side=padding_side, # 设置填充方向
clean_up_tokenization_spaces=clean_up_tokenization_spaces, # 设置空格清理标志
**kwargs # 传递其他参数
)
# 词汇大小属性
@property
def vocab_size(self):
return self.n_words # 返回单词数量
# 获取词汇的方法
def get_vocab(self):
""" Returns vocab as a dict """
# 创建一个从标记 ID 到标记的字典
vocab = {self._convert_id_to_token(i): i for i in range(self.vocab_size)}
vocab.update(self.added_tokens_encoder) # 更新词汇字典,添加额外的标记
return vocab # 返回词汇字典
# 将标记转换为字符串的方法
def convert_tokens_to_string(self, tokens: List[Union[bytes, str, int]]) -> str:
"""
Converts a sequence of tokens in a single string.
"""
text = "" # 初始化文本字符串
temp = b"" # 初始化临时字节串
for t in tokens: # 遍历标记列表
if isinstance(t, int): # 如果标记是整数
t = chr(t) # 将其转换为字符
if isinstance(t, str): # 如果标记是字符串
if temp: # 如果临时字节串不为空
text += temp.decode("utf-8", errors="replace") # 解码并添加到文本中
elif isinstance(t, bytes): # 如果标记是字节
temp += t # 将字节添加到临时字节串
else: # 如果标记类型不匹配
raise TypeError("token should only be of type int, bytes or str") # 抛出类型错误
if temp: # 如果临时字节串不为空
text += temp.decode("utf-8", errors="replace") # 解码并添加到文本中
return text # 返回最终文本字符串
# 定义一个私有方法,用于对输入文本进行分词
def _tokenize(self, text, **kwargs):
# 初始化一个空列表用于存放分词结果
tokens = []
# 使用分词器将输入文本编码为 ID 列表
ids = self.tokenizer.encode(text)
# 遍历 ID 列表,将每个 ID 转换为对应的词汇
for t in ids:
tokens.append(self.decoder[t])
# 返回分词结果
return tokens
# 定义一个私有方法,将给定的词转换为其对应的 ID
def _convert_token_to_id(self, token):
""" 将词(字符串)转换为词汇表中的 ID。 """
# 返回词的可合并排名作为其 ID
return self.mergeable_ranks[token]
# 定义一个私有方法,将给定的索引转换为对应的词
def _convert_id_to_token(self, index):
""" 将索引(整数)转换为词(字符串),使用词汇表。 """
# 返回对应索引的词,若不存在则返回空字符串
return self.decoder.get(index, "")
# 定义一个方法,将词汇表和特殊标记文件保存到指定目录
def save_vocabulary(self, save_directory, filename_prefix=None):
"""
保存词汇表和特殊标记文件到一个目录。
参数:
save_directory (`str`):
要保存词汇表的目录。
filename_prefix (`str`, *可选*):
要添加到保存文件名称的可选前缀。
返回:
`Tuple(str)`: 保存文件的路径。
"""
# 检查指定目录是否存在
if os.path.isdir(save_directory):
# 如果存在,构造词汇文件的完整路径
vocab_file = os.path.join(
save_directory, self.vocab_files_names["vocab_file"]
)
else:
# 如果不存在,使用指定的保存目录作为文件路径
vocab_file = save_directory
# 以二进制模式打开词汇文件进行读取
with open(self.vocab_file, 'rb') as fin:
# 读取词汇文件内容
proto_str = fin.read()
# 以二进制模式打开目标文件进行写入
with open(vocab_file, "wb") as writer:
# 将读取的内容写入目标文件
writer.write(proto_str)
# 返回保存的文件路径元组
return (vocab_file,)
# 定义一个方法获取前缀标记
def get_prefix_tokens(self):
# 返回特定的前缀标记 ID 列表
prefix_tokens = [self.convert_tokens_to_ids("[gMASK]"), self.convert_tokens_to_ids("<sop>")]
return prefix_tokens
# 定义一个方法构建单条消息
def build_single_message(self, role, metadata, message, tokenize=True, message_prefix=None):
# 断言角色在预定义的角色列表中
assert role in ["system", "user", "assistant", "observation"], role
# 如果需要分词
if tokenize:
# 将角色转换为 ID,并编码元数据为 ID 列表
role_tokens = [self.convert_tokens_to_ids(f"<|{role}|>")] + self.tokenizer.encode(f"{metadata}\n",
disallowed_special=())
# 编码消息为 ID 列表
message_tokens = self.tokenizer.encode(message, disallowed_special=())
# 如果提供了消息前缀,将其添加到消息 ID 列表中
if message_prefix is not None:
message_tokens = message_prefix + message_tokens
# 返回角色 ID 和消息 ID 的组合
tokens = role_tokens + message_tokens
return tokens
else:
# 如果不需要分词,返回格式化的消息字符串
return str(f"<|{role}|>{metadata}\n{message}")
# 定义一个方法应用聊天模板
def apply_chat_template(
self,
conversation: Union[List[Dict[str, str]], List[List[Dict[str, str]]], "Conversation"],
add_generation_prompt: bool = False,
tokenize: bool = True,
padding: bool = False,
truncation: bool = False,
max_length: Optional[int] = None,
return_tensors: Optional[Union[str, TensorType]] = None,
return_dict: bool = False,
tokenizer_kwargs: Optional[Dict[str, Any]] = None,
add_special_tokens: bool = True,
**kwargs,
# 构建包含特殊标记的模型输入,用于序列分类任务
def build_inputs_with_special_tokens(
self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None
) -> List[int]:
"""
从单个或成对序列构建模型输入,通过连接并添加特殊标记。BERT 序列格式如下:
- 单序列: `[CLS] X [SEP]`
- 序列对: `[CLS] A [SEP] B [SEP]`
参数:
token_ids_0 (`List[int]`):
将添加特殊标记的 ID 列表。
token_ids_1 (`List[int]`, *可选*):
可选的第二个 ID 列表,用于序列对。
返回:
`List[int]`: 包含适当特殊标记的 [输入 ID](../glossary#input-ids) 列表。
"""
# 获取前缀标记
prefix_tokens = self.get_prefix_tokens()
# 在 token_ids_0 前添加前缀标记
token_ids_0 = prefix_tokens + token_ids_0
# 如果存在 token_ids_1,则进行连接并添加结束标记
if token_ids_1 is not None:
token_ids_0 = token_ids_0 + token_ids_1 + [self.convert_tokens_to_ids("<eos>")]
# 返回构建好的 token_ids_0
return token_ids_0
# 填充输入以匹配最大长度
def _pad(
self,
encoded_inputs: Union[Dict[str, EncodedInput], BatchEncoding],
max_length: Optional[int] = None,
padding_strategy: PaddingStrategy = PaddingStrategy.DO_NOT_PAD,
pad_to_multiple_of: Optional[int] = None,
return_attention_mask: Optional[bool] = None,
) -> dict:
"""
对编码后的输入进行填充(左侧/右侧填充,并达到预定义长度或批次中的最大长度)
Args:
encoded_inputs:
词元化输入的字典(`List[int]`)或词元化输入的批次(`List[List[int]]`)。
max_length: 返回列表的最大长度,选项上也为填充长度(见下文)。
将根据特殊标记进行截断。
padding_strategy: 用于填充的 PaddingStrategy。
- PaddingStrategy.LONGEST: 填充到批次中最长的序列
- PaddingStrategy.MAX_LENGTH: 填充到最大长度(默认)
- PaddingStrategy.DO_NOT_PAD: 不进行填充
填充的方向由 self.padding_side 定义:
- 'left': 在序列的左侧进行填充
- 'right': 在序列的右侧进行填充
pad_to_multiple_of: (可选)如果设置,将序列填充到所提供值的倍数。
这在启用计算能力 `>= 7.5`(Volta)上的 NVIDIA 硬件的 Tensor Core 时尤其有用。
return_attention_mask:
(可选)设置为 False 以避免返回注意力掩码(默认:根据模型具体情况设置)
"""
# 从模型默认值加载
assert self.padding_side == "left" # 确保填充方向为左侧
required_input = encoded_inputs[self.model_input_names[0]] # 获取必需的输入
seq_length = len(required_input) # 计算输入序列的长度
if padding_strategy == PaddingStrategy.LONGEST: # 如果填充策略为最长填充
max_length = len(required_input) # 设置最大长度为输入序列的长度
# 如果最大长度和倍数填充值都不为 None,且最大长度不是倍数填充值的倍数
if max_length is not None and pad_to_multiple_of is not None and (max_length % pad_to_multiple_of != 0):
# 调整最大长度为下一个倍数填充值
max_length = ((max_length // pad_to_multiple_of) + 1) * pad_to_multiple_of
# 检查是否需要填充
needs_to_be_padded = padding_strategy != PaddingStrategy.DO_NOT_PAD and len(required_input) != max_length
# 如果没有注意力掩码,初始化它
if "attention_mask" not in encoded_inputs:
encoded_inputs["attention_mask"] = [1] * seq_length # 初始化注意力掩码为全1
if "position_ids" not in encoded_inputs:
encoded_inputs["position_ids"] = list(range(seq_length)) # 初始化位置ID为0到序列长度-1
if needs_to_be_padded: # 如果需要填充
difference = max_length - len(required_input) # 计算需要填充的数量
if "attention_mask" in encoded_inputs: # 如果存在注意力掩码
# 在注意力掩码的左侧添加0以匹配最大长度
encoded_inputs["attention_mask"] = [0] * difference + encoded_inputs["attention_mask"]
if "position_ids" in encoded_inputs: # 如果存在位置ID
# 在位置ID的左侧添加0以匹配最大长度
encoded_inputs["position_ids"] = [0] * difference + encoded_inputs["position_ids"]
# 在必需输入的左侧添加填充标记以匹配最大长度
encoded_inputs[self.model_input_names[0]] = [self.pad_token_id] * difference + required_input
return encoded_inputs # 返回处理后的编码输入
.\chatglm4v-9b\visual.py
# 导入必要的库
import torch # 导入 PyTorch 库
from torch import nn # 从 PyTorch 导入神经网络模块
from argparse import Namespace # 导入命名空间用于解析命令行参数
import torch.nn.functional as F # 导入 PyTorch 的函数性模块
from transformers.activations import ACT2FN # 从 Transformers 导入激活函数
import math # 导入数学库
from torch.nn import LayerNorm # 从 PyTorch 导入层归一化模块
# 定义标准注意力机制函数
def standard_attention(query_layer, key_layer, value_layer, scaling_attention_score=True):
# 如果需要缩放注意力得分,执行缩放
if scaling_attention_score:
query_layer = query_layer / math.sqrt(query_layer.shape[-1]) # 对查询层进行缩放
# 计算注意力得分,通过矩阵乘法得到
attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) # 计算查询和键的点积
# 应用 softmax 函数计算注意力概率
attention_probs = F.softmax(attention_scores, dim=-1) # 对注意力得分进行归一化处理
# 通过注意力概率与值层计算上下文层
context_layer = torch.matmul(attention_probs, value_layer) # 计算加权后的值
return context_layer # 返回上下文层
# 定义默认注意力函数
def attention_fn_default(query_layer, key_layer, value_layer, scaling_attention_score=True):
# 检查 PyTorch 版本并进行处理
if int(torch.__version__.split('.')[0]) >= 2 and scaling_attention_score:
# Pytorch 2.0 的注意力机制在注意力掩码为 float 时消耗大量内存,并在注意力掩码为 None 时有 NaN bug。
attn_output = torch.nn.functional.scaled_dot_product_attention(
query_layer, key_layer, value_layer, # 执行缩放点积注意力
attn_mask=None, # 不使用注意力掩码
dropout_p=0., # 不使用 dropout
is_causal=False # 非因果注意力
)
return attn_output # 返回注意力输出
else:
# 使用标准注意力函数
return standard_attention(
query_layer, key_layer, value_layer, scaling_attention_score=scaling_attention_score # 调用标准注意力
)
# 定义 PatchEmbedding 类
class PatchEmbedding(nn.Module):
def __init__(self, config):
super().__init__() # 初始化父类
# 创建卷积层,用于图像切片嵌入
self.proj = nn.Conv2d(config.in_channels, config.hidden_size, kernel_size=config.patch_size,
stride=config.patch_size) # 定义卷积层
# 定义 CLS token 嵌入
self.cls_embedding = nn.Parameter(torch.zeros(1, config.hidden_size)) # 初始化 CLS token
# 定义位置嵌入
self.position_embedding = nn.Embedding(config.num_positions, config.hidden_size) # 初始化位置嵌入
# 定义前向传播方法
def forward(self, images: "tensor(B, C, H, W)") -> "tensor(B, L, D)":
# 对输入图像进行卷积
x = self.proj(images) # 应用卷积层
x = x.flatten(2).transpose(1, 2) # 将输出展平并转置维度
cls_token = self.cls_embedding.expand(x.shape[0], -1, -1) # 扩展 CLS token
x = torch.cat((cls_token, x), dim=1) # 将 CLS token 与其他嵌入连接
x += self.position_embedding.weight.unsqueeze(0) # 添加位置嵌入
return x # 返回最终的嵌入
# 定义 Attention 类
class Attention(nn.Module):
def __init__(self, config):
super().__init__() # 初始化父类
self.num_heads = config.num_heads # 设置头数
head_dim = config.hidden_size // config.num_heads # 计算每个头的维度
self.scale = head_dim ** -0.5 # 计算缩放因子
self.query_key_value = nn.Linear(config.hidden_size, config.hidden_size * 3) # 定义 QKV 线性变换
self.dense = nn.Linear(config.hidden_size, config.hidden_size) # 定义输出线性层
self.output_dropout = torch.nn.Dropout(config.dropout_prob) # 定义 dropout 层
# 定义前向传播方法
def forward(self, x: "tensor(B, L, D)") -> "tensor(B, L, D)":
B, L, _ = x.shape # 获取批量大小和序列长度
qkv = self.query_key_value(x) # 计算 QKV
qkv = qkv.reshape(B, L, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4) # 重新形状并排列维度
q, k, v = qkv[0], qkv[1], qkv[2] # 拆分 QKV
out = attention_fn_default( # 调用注意力函数
q, k, v # 传入 Q、K 和 V
)
output = self.dense(out.transpose(1, 2).reshape(B, L, -1)) # 应用输出线性层
output = self.output_dropout(output) # 应用 dropout
return output # 返回最终输出
# 定义注意力机制函数,接受查询(q)、键(k)和值(v)作为输入
def attention(self, q, k, v):
# 计算注意力权重,将查询与键的转置相乘,并进行缩放
attn_weights = torch.matmul(q * self.scale, k.transpose(-2, -1))
# 对注意力权重进行归一化处理,使用 softmax 函数
attn_weights = attn_weights.softmax(dim=-1)
# 将归一化后的注意力权重与值相乘以获得输出
output = torch.matmul(attn_weights, v)
# 返回最终的输出
return output
# 定义一个多层感知机(MLP)类,继承自 nn.Module
class MLP(nn.Module):
# 初始化方法,接受配置参数
def __init__(self, config):
# 调用父类构造方法
super().__init__()
# 保存配置参数
self.config = config
# 根据配置选择激活函数
self.activation_fn = ACT2FN[config.hidden_act]
# 定义第一层全连接层,输入维度为 hidden_size,输出维度为 intermediate_size
self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
# 定义第二层全连接层,输入维度为 intermediate_size,输出维度为 hidden_size
self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
# 前向传播方法,接受输入张量 x
def forward(self, x: torch.Tensor) -> torch.Tensor:
# 通过第一层全连接层
x = self.fc1(x)
# 应用激活函数
x = self.activation_fn(x)
# 通过第二层全连接层
x = self.fc2(x)
# 返回输出张量
return x
# 定义一个变压器层(TransformerLayer)类,继承自 nn.Module
class TransformerLayer(nn.Module):
# 初始化方法,接受配置参数
def __init__(self, config):
# 调用父类构造方法
super().__init__()
# 定义输入层归一化层,维度为 hidden_size
self.input_layernorm = LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
# 定义注意力机制层
self.attention = Attention(config)
# 定义多层感知机
self.mlp = MLP(config)
# 定义后注意力层归一化层
self.post_attention_layernorm = LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
# 前向传播方法,接受隐藏状态
def forward(self, hidden_states):
# 将隐藏状态赋值给注意力输入
attention_input = hidden_states
# 经过注意力层和输入归一化层
attention_output = self.input_layernorm(self.attention(attention_input))
# 将注意力输出加回原输入(残差连接)
hidden_states = attention_input + attention_output
# 将当前状态赋值给 MLP 输入
mlp_input = hidden_states
# https://github.com/THUDM/GLM-4/issues/350
# 经过后注意力层归一化和 MLP,保持设备一致
mlp_output = self.post_attention_layernorm(self.mlp(mlp_input)).to(mlp_input.device)
# 将 MLP 输出加回输入(残差连接)
output = mlp_input + mlp_output
# 返回最终输出
return output
# 定义变压器模型(Transformer)类,继承自 nn.Module
class Transformer(nn.Module):
# 初始化方法,接受配置参数
def __init__(self, config):
# 调用父类构造方法
super().__init__()
# 创建包含多个变压器层的模块列表,根据配置中的层数
self.layers = nn.ModuleList([TransformerLayer(config) for _ in range(config.num_hidden_layers)])
# 前向传播方法,接受隐藏状态
def forward(self, hidden_states):
# 遍历每一层变压器,更新隐藏状态
for layer_module in self.layers:
hidden_states = layer_module(hidden_states)
# 返回最终的隐藏状态
return hidden_states
# 定义 GLU 模型类,继承自 nn.Module
class GLU(nn.Module):
# 初始化方法,接受配置和输入特征数量
def __init__(self, config, in_features):
# 调用父类构造方法
super().__init__()
# 定义线性投影层,无偏置
self.linear_proj = nn.Linear(in_features, config.hidden_size, bias=False)
# 定义第一层归一化层
self.norm1 = nn.LayerNorm(config.hidden_size)
# 定义第一激活函数为 GELU
self.act1 = nn.GELU()
# 定义第二激活函数为 SiLU
self.act2 = nn.functional.silu
# 定义从 hidden_size 到 ffn_hidden_size 的线性层,无偏置
self.dense_h_to_4h = nn.Linear(config.hidden_size, config.ffn_hidden_size, bias=False)
# 定义门控线性层,从 hidden_size 到 ffn_hidden_size,无偏置
self.gate_proj = nn.Linear(config.hidden_size, config.ffn_hidden_size, bias=False)
# 定义从 ffn_hidden_size 回到 hidden_size 的线性层,无偏置
self.dense_4h_to_h = nn.Linear(config.ffn_hidden_size, config.hidden_size, bias=False)
# 前向传播方法,接受输入 x
def forward(self, x):
# 通过线性投影层
x = self.linear_proj(x)
# 归一化后应用第一激活函数
x = self.act1(self.norm1(x))
# 计算门控乘积并通过 dense 层
x = self.act2(self.gate_proj(x)) * self.dense_h_to_4h(x)
# 通过最后的线性层
x = self.dense_4h_to_h(x)
# 返回输出 x
return x
# 定义 EVA2CLIP 模型类,继承自 nn.Module
class EVA2CLIPModel(nn.Module):
# 初始化方法,接收配置参数
def __init__(self, config):
# 调用父类的初始化方法
super().__init__()
# 将视觉配置转换为命名空间对象,方便访问其属性
vision_config = Namespace(**config.vision_config)
# 创建补丁嵌入层,使用视觉配置
self.patch_embedding = PatchEmbedding(vision_config)
# 创建变换器,使用视觉配置
self.transformer = Transformer(vision_config)
# 创建线性投影层,输入特征为隐藏层大小
self.linear_proj = GLU(config, in_features=config.hidden_size)
# 创建卷积层,输入通道为隐藏层大小,输出通道为配置的隐藏层大小
self.conv = nn.Conv2d(in_channels=vision_config.hidden_size, out_channels=config.hidden_size, kernel_size=2,
stride=2)
# 创建一个可学习的参数,初始化为零,表示开始位置
self.boi = nn.Parameter(torch.zeros(1, 1, config.hidden_size))
# 创建一个可学习的参数,初始化为零,表示结束位置
self.eoi = nn.Parameter(torch.zeros(1, 1, config.hidden_size))
# 获取缩放因子,来自视觉配置
self.scaling_factor = vision_config.scaling_factor
# 前向传播方法,处理输入图像并返回结果
def forward(self, images: "tensor(B, C, H, W)") -> "tensor(B, L, D)":
# 将输入图像经过补丁嵌入层处理
x = self.patch_embedding(images)
# 将嵌入结果传入变换器
x = self.transformer(x)
# 去掉第一个标记,通常是用于序列处理
x = x[:, 1:]
# 获取当前张量的批量、序列长度和隐藏层维度
b, s, h = x.shape
# 计算网格大小,通常用于将序列重塑为二维形式
grid_size = int(s ** 0.5)
# 重塑张量形状,调整为适合卷积操作的格式
x = x.view(b, grid_size, grid_size, h).permute(0, 3, 1, 2)
# 将重塑后的张量经过卷积层处理
x = self.conv(x)
# 展平张量的特征维度,并转置以适配后续操作
x = x.flatten(2).transpose(1, 2)
# 将张量经过线性投影层处理
x = self.linear_proj(x)
# 扩展开始位置参数到当前批量大小,保持维度一致
boi = self.boi.expand(x.shape[0], -1, -1).to(x.device)
# 扩展结束位置参数到当前批量大小,保持维度一致
eoi = self.eoi.expand(x.shape[0], -1, -1).to(x.device)
# 将开始标记、处理后的张量和结束标记在序列维度拼接
x = torch.cat((boi, x, eoi), dim=1)
# 对结果进行缩放
x = x / self.scaling_factor
# 返回最终处理后的张量
return x