模型结构-qwen原理

1. 背景

本文将以Qwen2系列大模型为基础,讲解Qwen2模型技术架构及模型原理。

2. 编码

词表的设计可以影响训练的效率和下游任务的表现。Qwen系列模型采用的是tiktoken分词器,这是一种快速分词方法,该方法被使用在OpenAI系列模型中,tiktoen的核心逻辑同样是基于BPE算法,下面介绍下这两类算法。

2.1 BPE

把输入字符串分割为单词或子词(单词的部分)是自然语言处理过程中一项最基本的工作,这个过程是分词,存在较多算法,其中最为经典的是BPE(Byte Pair Encoding)算法。

  • 核心代码
    • bpe_train
函数定义:
def bpe_train(data: str, vocab_size: int, pat_str: str) -> dict[bytes, int]句子:data = "你好,qwen大模型"词表大小:vocab_size=275词切分正则:pat_str = r"'s|'t|'re|'ve|'m|'ll|'d| ?[\p{L}]+| ?[\p{N}]+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"
执行步骤:
step1:判断vocab_size < 2**8=256?是->报错,否继续。原因见notes1
step2:0-256,先填充到rank词表,此部分词表固定,下面截取部分内容
{b'\x00': 0, b'\x01': 1,...b'A': 65, b'B': 66,..., b'y': 121, b'z': 122,...,b'\xfe': 254, b'\xff': 255}
step3:把data分割为字节列表,即数据变成如下格式为[["你好"],[","],["qwen大模型"]]:
data = [[b'\xe4', b'\xbd', b'\xa0', b'\xe5', b'\xa5', b'\xbd'], [b'\xef', b'\xbc', b'\x8c'], [b'q', b'w', b'e', b'n', b'\xe5', b'\xa4', b'\xa7', b'\xe6', b'\xa8', b'\xa1', b'\xe5', b'\x9e', b'\x8b']]
step4:计算共同出现的字节对,然后把它从255开始往后计数增加到词表中,直到得到的词表rank等于vocab_size
共现字节对:
{
(b'\xe4', b'\xbd'): 1, 
(b'\xbd', b'\xa0'): 1,
(b'q', b'w') :1,
(b'w', b'e') :1,
(b'e', b'n') :1,
...
}
更新后的rank词表:
{
b'\x00': 0, 
b'\x01': 1...,
b'\xe4\xbd':256,
b'\xbd\xa0':257,
...
}
反复迭代,更新最终的rank词表:
{
b'\x00': 0, 
b'\x01': 1...
b'\xe4\xbd': 256
b'\xe4\xbd\xa0': 257
b'\xe4\xbd\xa0\xe5': 258
b'\xe4\xbd\xa0\xe5\xa5': 259
b'\xe4\xbd\xa0\xe5\xa5\xbd': 260
b'\xef\xbc': 261
b'\xef\xbc\x8c': 262
b'qw': 263
b'qwe': 264
b'qwen': 265
b'qwen\xe5': 266
b'qwen\xe5\xa4': 267
b'qwen\xe5\xa4\xa7': 268
b'qwen\xe5\xa4\xa7\xe6': 269
b'qwen\xe5\xa4\xa7\xe6\xa8': 270
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1': 271
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1\xe5': 272
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1\xe5\x9e': 273
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1\xe5\x9e\x8b': 274
}step4:END
  • 核心代码
    • bpe_encode
函数定义:
def bpe_encode(mergeable_ranks: dict[bytes, int], input: bytes) -> list[int]:
步骤:
较为简单,不赘述。逻辑和训练极为相似,就是吧inputbytes挨个组成对后直接去查词表,如果有就记下来,最终成该字节对应的编码数字
实验:
```python
>data = "你好,qwen大模型"
>pat_str = r"'s|'t|'re|'ve|'m|'ll|'d| ?[\p{L}]+| ?[\p{N}]+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"
>mergeable_ranks = bpe_train(data=data, vocab_size=275, pat_str=pat_str)
>print(bpe_encode(mergeable_ranks, data.encode("utf-8")))
[260, 262, 274]
结果:
上述代码执行后,"你好,qwen大模型"的编码为[260, 262, 274],找到bpe_train中最终的词表
得到对应字节为:
b'\xe4\xbd\xa0\xe5\xa5\xbd': 260
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1\xe5': 272
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1\xe5\x9e\x8b': 274
再次解码:
b'\xe4\xbd\xa0\xe5\xa5\xbd'.decode("utf8")=你好
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1\xe5'.decode("utf8") =error(编码器未经充分训练,错误忽略)
b'qwen\xe5\xa4\xa7\xe6\xa8\xa1\xe5\x9e\x8b'.decode("utf8")=qwen大模型
解释:
前面的训练只做了非常小的词表,并没有进行过滤和特殊处理,实际的编码比这个要复杂,会对特殊字符进行处理,此部分后面简单介绍。		
要点解释
notes1:
- byte=8bit= [0-1][0-1][0-1][0-1][0-1][0-1][0-1][0-1],共计可以表示2**8=256个状态
- BPE编码是以字节为单位,所以,至少要有1个字节,也就是256个状态,每个状态可以认为是词表中某个具体的词或词的编码0-255
- 结论:至少1个字节->词表至少256
- 知识扩充- 众所周知,ASCII表和纯数字存在对应关系,英文属于拼音语言,a-zA-Z共计72个字符即可表示几乎所有文字,1byte就可以描述其基本字符,而中文,是象形文字,其编码较为复杂,往往一个中文汉字,就需要多字节。- UTF-8编码:一个中文汉字需要3个字节```python>zh = "你好">zh.encode("utf8")
b'\xe4\xbd\xa0\xe5\xa5\xbd'>zh[0].encode("utf8")
b'\xe4\xbd\xa0'>zh[1].encode("utf8")
b'\xe5\xa5\xbd'>list(zh.encode("utf8"))
[228, 189, 160, 229, 165, 189]>for v in list(s.encode("utf8")):...     bin(v)
'0b11100100'
'0b10111101' 
'0b10100000'
'0b11100101'
'0b10100101'
'0b10111101'```- gbk编码:一个中文汉字需要2个字节```python>zh = "你好">zh.encode("gbk")
b'\xc4\xe3\xba\xc3'>zh[0].encode("gbk")
b'\xc4\xe3'>zh[1].encode("gbk")
b'\xba\xc3'>list(zh.encode("gbk"))
[196, 227, 186, 195]>for v in list(s.encode("gbk")):...     bin(v)
'0b11000100'
'0b11100011'
'0b10111010'
'0b11000011'```

2.2 tiktoken

前面介绍了BPE算法的核心逻辑,那么tiktoken作为qwen2大模型的编码算法,为什么采用它呢?其实最主要的原因就一个:快。

  • 速度快
    在这里插入图片描述
    如上图,可以看到tiktoken相比较huggingface的同类开源的编码器快3-6倍。
    • Rust编程
      tiktoken核心编码的部分,即train和encode都是通过Rust来实现的,Rust具备一些优势。
      • 性能强,没有编译器,直接就是机器码,其速度和C++差不多,甚至超越C++,比python快2-3倍
      • 并行计算,更好的支持并行计算,而不受python等全局锁限制
    • 算法优化
      BPE算法有多种实现逻辑,前面讲的是最简单的naive算法,其实现有多种,tiktoken就对此部分进行了优化。
      • BPE核心算法优化
        _byte_pair_merge函数实现了BPE的核心逻辑,通过维护一个parts向量来追踪可能合并的字节对。
fn _byte_pair_merge(ranks: &HashMap<Vec<u8>, Rank>, piece: &[u8]) -> Vec<(usize, Rank)> {// This is a vector of (start, rank).// The rank is of the pair starting at position start.let mut parts = Vec::with_capacity(piece.len() + 1);// Note that we hash bytes when indexing into `ranks`, not token pairs. As long as we train BPE// the way we currently do, this is equivalent. An easy way to break this would be to decouple// merge priority from token index or to prevent specific token merges.let mut min_rank: (Rank, usize) = (Rank::MAX, usize::MAX);for i in 0..piece.len() - 1 {let rank = *ranks.get(&piece[i..i + 2]).unwrap_or(&Rank::MAX);if rank < min_rank.0 {min_rank = (rank, i);}parts.push((i, rank));}parts.push((piece.len() - 1, Rank::MAX));parts.push((piece.len(), Rank::MAX));let get_rank = {#[inline(always)]|parts: &Vec<(usize, Rank)>, i: usize| {if (i + 3) < parts.len() {// Similar to `piece[i..i + 2]` above. The +3 is because we haven't yet deleted// parts[i + 1], see comment in the main loop.*ranks.get(&piece[parts[i].0..parts[i + 3].0]).unwrap_or(&Rank::MAX)} else {Rank::MAX}}};// If you have n parts and m merges, this does O(mn) work.// We could do something with a heap and do O(m log n) work.// n is often very small so considerations like cache-locality outweigh the algorithmic// complexity downsides of the `parts` vector.while min_rank.0 != Rank::MAX {let i = min_rank.1;// Update parts[i] and parts[i - 1] before removing parts[i + 1], since// `parts.remove(i + 1)` will thrash the cache.if i > 0 {parts[i - 1].1 = get_rank(&parts, i - 1);}parts[i].1 = get_rank(&parts, i);parts.remove(i + 1);min_rank = (Rank::MAX, usize::MAX);for (i, &(_, rank)) in parts[..parts.len() - 1].iter().enumerate() {if rank < min_rank.0 {min_rank = (rank, i);}}}parts
}
  • 特殊字符处理
    在_encode_native和_encode_unstable_native函数中,代码区分了普通字符和特殊字符的处理,允许在编码过程中包含用户定义的特殊字符。
函数定义:
_encode_native(&self, text: &str, allowed_special: &HashSet<&str>) -> (Vec<Rank>, usize) 
核心代码:
step1:通过正则从开始索引匹配特殊字符
next_special = special_regex.find_from_pos(text, start_find).unwrap();
step2:挨个遍历text,找到符合定义的特殊字符跳出,不符合继续找;一旦找到next_special在allowed_special先是跳出,然后直接进行编码
```rust
let mut next_special;
let mut start_find = start;
loop {// Find the next allowed special token, if anynext_special = special_regex.find_from_pos(text, start_find).unwrap();match next_special {Some(m) => {if allowed_special.contains(&text[m.start()..m.end()]) {break;}start_find = m.start() + 1;}None => break,}
}
...
match next_special {Some(m) => {let piece = m.as_str();let token = self.special_tokens_encoder[piece];ret.push(token);start = m.end();last_piece_token_len = 0;}None => break,
}
  • 缓存策略
核心代码:使用缓存返回一个正则表达式对象,该对象用于匹配传入的特殊token集合中的任意一个token
@functools.lru_cache(maxsize=128)
def _special_token_regex(tokens: frozenset[str]) -> "regex.Pattern[str]":inner = "|".join(regex.escape(token) for token in tokens)return regex.compile(f"({inner})")
  • 哈希表
    见前面_byte_pair_merge算法,使用hashmap提高了检索速度
  • 支持扩展
    tiktoken具备扩展能力

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv5-水印检测

简介&#xff1a; YOLOv5在YOLOv4算法的基础上做了进一步的改进&#xff0c;检测性能得到进一步的提升。虽然YOLOv5算法并没有与YOLOv4算法进行性能比较与分析&#xff0c;但是YOLOv5在COCO数据集上面的测试效果还是挺不错的。 YOLOv5是一种单阶段目标检测算法&#xff0c;该算…

内网私有化聊天软件:哪些企业类型最受益?

在数字化时代&#xff0c;企业内部通讯的效率和安全性成为了企业运营中不可或缺的一环。随着数据泄露事件频发和隐私保护意识的增强&#xff0c;越来越多的企业开始寻求更加安全、可控的通讯解决方案。内网部署的私有化聊天软件&#xff0c;以其高度的安全性、定制化特性和自主…

甩锅笔记:好好的服务端应用突然起不来,经定位是无法访问外网了?测试又说没改网络配置,该如何定位?

在工作中、团队协作时&#xff0c;可能遇到的问题&#xff0c;如集成测试等场景。但是作为偏前端的全栈&#xff0c;锅从天上来&#xff0c;不是你想甩就能甩&#xff0c;尤其面对测试等比较强势的团体&#xff08;bug创造者&#xff09;&#xff0c;你必须有强大的心理承受能力…

C++ STL容器(二) —— list 底层剖析

计划写几篇关于C STL容器底层剖析的文章&#xff0c;主要基于的是MSVC的实现&#xff0c;本篇先从比较简单的 list 入手&#xff0c;个人感觉文章更偏于代码的具体实现&#xff0c;而不是原理的讲解&#xff0c;所以前置需要你了解链表的相关算法&#xff0c;如果有问题欢迎评论…

长方形+ 下三角形的图形 css

<div class"transform">42.48%</div>//转化.transform {position: relative;width: 70px;height: 26px;background-color: #dcdfe6; /* 长方形的颜色 */display: flex;justify-content: center;align-items: center;font-family: PingFangTC-Medium;font…

Keil5 操作

目录 1.Debug&#xff08;软件模拟调试&#xff1a;&#xff09;&#xff1a; 2.代码提示设置&#xff1a; 3.添加. c与.h文件&#xff1a; 常用技巧 安装下载推荐&#xff1a;正点原子 1.Debug&#xff08;软件模拟调试&#xff1a;&#xff09;&#xff1a; 文章讲解 …

Selenium自动化安装教程

目录 提示&#xff1a; 一、安装Python运行环境 1. 找到官方网站 ​编辑 2. 找到下载页面 3. 双击安装包 ​编辑 4. 运行 hello world 二、安装 pycharm 1. 找到官方网站 ​编辑 2. 找到下载页面 3. 双击安装包 4. 运行 hello world 5. 字体设置 三、Python管理…

JavaWeb--小白笔记07:servlet对表单数据的简单处理

这里的servlet对表单数据的处理是指使用IDEA创建web工程&#xff0c;再创建html和class文件进行连接&#xff0c;实现html创建一个表单网页&#xff0c;我们对网页中的表单进行填充&#xff0c;可以通过class文件得到网页我们填充的内容进行打印到控制台。 一登录系统页面---h…

查找和排序(选择题)

查找 寻找最大/小项 n-1 排序 前三个的时间复杂度都是O(n^2),希尔排序是O(n^1.5). 在以上排序方法中&#xff0c;最坏情况下时间复杂度最小的是堆排序。 每经过一次元素的交换会产生新的逆序的是快速排序。

为什么越来越多的网工运维转行网络安全?_idc运维转网络安全工程师_系统运维转行网安

最近越来越多的网工运维小伙伴都在吐槽&#xff1a;干网工、运维多年&#xff0c;薪资还是5.6K&#xff0c;技术也遇瓶颈上不去&#xff0c;考虑转岗或者转行。其中大部分的网工运维小伙伴们纷纷瞄准了高薪高前景的网络安全工程师岗位 网络安全是怎样的岗位&#xff1f; 网络安…

2024重组胶原蛋白行业白皮书:从美业革新先锋到精准医疗动力源

从来源上看&#xff0c;胶原蛋白主要分为动物源胶原蛋白和重组胶原蛋白两大类。重组胶原蛋白相较于传统动物来源的胶原蛋白在生物活性、生物相容性、低免疫原性、降低漏检病原体风险、水溶性、无细胞毒性等方面表现出诸多优越性。随着胶原蛋白的来源和生产方式不断演变&#xf…

改进的yolov10 deepsort目标跟踪(yolo改进+最新算法+附代码和教程)

YOLOv10_DeepSORT&#xff1a;视频中的对象检测与跟踪 本仓库包含了使用YOLOv10对象检测模型和DeepSORT算法在视频中进行对象检测与跟踪的代码。YOLOv10是目前最先进的对象检测模型之一&#xff0c;而DeepSORT是一种基于深度学习的对象跟踪算法&#xff0c;它结合了外观信息和…

BOE(京东方)携故宫博物院举办2024“照亮成长路”公益项目落地仪式以创新科技赋能教育可持续发展

2024年9月20日&#xff0c;BOE&#xff08;京东方&#xff09;“照亮成长路”智慧教室落成暨百堂故宫传统文化公益课山西活动落地仪式在山西省太原市娄烦县实验小学隆重举行。自“照亮成长路”教育公益项目正式设立以来&#xff0c;BOE&#xff08;京东方&#xff09;持续以创新…

jenkins分布式构建

Jenkins分布式构建是一种将构建任务分散到多个机器上的方法&#xff0c;以提高构建效率和并行处理能力 1. 架构 主节点&#xff08;Master&#xff09;&#xff1a;负责管理构建任务、调度和监控所有从节点。从节点&#xff08;Slave&#xff09;&#xff1a;实际执行构建任务…

文件防泄漏方法有哪些|6个方法有效防止文件泄密

文件防泄漏是企业和组织保护其敏感信息和核心资产的重要手段。 以下是六个有效防止文件泄密的方法&#xff1a; 1. 文件加密 透明加密&#xff1a;使用专业的防泄密软件&#xff0c;如安企神等&#xff0c;对敏感文件进行透明加密处理。 这种加密方式在用户创建、编辑和保存…

DPDK 简易应用开发之路 4:基于Pipeline模型的DNS服务器

本机环境为 Ubuntu20.04 &#xff0c;dpdk-stable-20.11.10 使用scapy和wireshark发包抓包分析结果 完整代码见&#xff1a;github Pipeline模型 DPDK Pipeline模型是基于Data Plane Development Kit&#xff08;DPDK&#xff09;的高性能数据包处理框架。它通过将数据流分为多…

力扣46.全排列

一、题目 二、代码 class Solution {int[] nums;List<List<Integer>> ans new ArrayList<>();List<Integer> path new ArrayList<>();boolean[] onPath;public List<List<Integer>> permute(int[] nums) {this.nums nums;int n …

【GUI设计】基于图像分割的GUI系统(3),matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于Matlab的图像处理GUI系统&#xff08;3&#xff09;&#xff0c;用matlab实现。…

AH2212-12V转4.2V充电芯片

AH2212——12V转4.2V充电芯片&#xff0c;峰值2A输出编程电流&#xff0c;实现精准同步开关降压锂电池充电 随着科技的不断发展&#xff0c;移动电源、智能穿戴、电动工具等设备的应用越来越广泛&#xff0c;对电池充电芯片的需求也日益增大。本文将为您介绍一款高性能的充电芯…

与时间函数相关的那些事

在LuatOS中&#xff0c;获取时间函数用得最多的就是os.time()函数了。 接下来&#xff0c;我会讲一些与这个函数以及其他时间函数相关的知识。 一、时间戳相关 os.time()这个函数&#xff0c;只能获取当前时间戳&#xff1b;如果客户希望获取的是当前时间&#xff0c;即相应…