天津营销型网站建设费用,来宾绍兴seo网站托管方案,设计美观网站有哪些,免费移动版wordpress目录 一、下载并加载中文数据集二、中文数据集处理1、数据格式2、数据集处理之tokenizer训练格式1#xff09;先将一篇篇文本拼凑到一起#xff08;只是简单的拼凑一起#xff0c;用于训练tokenizer#xff09;2#xff09;将数据集进行合并 3、数据集处理之模型#xff… 目录 一、下载并加载中文数据集二、中文数据集处理1、数据格式2、数据集处理之tokenizer训练格式1先将一篇篇文本拼凑到一起只是简单的拼凑一起用于训练tokenizer2将数据集进行合并 3、数据集处理之模型llama2训练train.py格式 三、训练一个tokenizer四、使用训练的tokenizer预编码输入数据五、训练llama2模型1、修改参数1vocab_size2max_seq_len与batch size3token 2、模型训练3、模型读取与转换1) python 读取bin模型2python读取pt模型并转为bin 4、模型推理1代码与模型2编译运行 五、拓展1、可自定义参数运行(master分支下的旧tokenizer.bin模型)2、可自定义参数运行(feature/avx2分支下的tokenizer.bin模型)3、上述两种自定义参数运行的差异4、C读取tokenizer注释5、run.c中的bpe_encode即tokenizer的具体流程6、模型推理while (pos steps) 循环7、tokenizer拓展词汇 好久没更新这个专栏的文章了今天抽空写了一篇。————2023.12.28 摘要文体包括新闻法律文书公告广告等每种文体的书写风格不一样如果拥有自己的数据集想针对特定文体来训练一个内容生成的工具来帮助自己写点文章如果没接触过AIGC可能一开始会觉得无所入手那么希望本文能够帮助到你。本文将基于llama2来教大家如何训练一个内容生成工具即训练属于自己的AIGCArtificial Intelligence Generated Content。 这里需要训练两个模型一个是tokenizer一个是llama2模型我们一个一个来。 看这篇文章之前可以看下以下两篇文章
[玩转AIGC]sentencepiece训练一个Tokenizer(标记器)[玩转AIGC]如何训练LLaMA2模型训练、推理、代码讲解并附可直接运行的kaggle连接 第一篇是关于如何训练llama2的Tokenizer模型 第二篇是关于如何训练llama2的content generation模型里面包括了对llama2的代码解析 相关github tokenizer: GitHub - google/sentencepiece: Unsupervised text tokenizer for Neural Network-based text generation. llama2.c: GitHub - karpathy/llama2.c: Inference Llama 2 in one file of pure C 如果没有显卡可使用kagglekaggle的P100 gpu 足矣
可直接运行的kagglellama2-c-chinese
一、下载并加载中文数据集
加载中文数据集 数据来源 https://github.com/esbatmop/MNBVC https://huggingface.co/datasets/liwu/MNBVC 简单的加载方式
from datasets import load_dataset
dataset load_dataset(liwu/MNBVC, law_judgement,cache_dir./dataset)# print(next(iter(dataset))) # get the first line)dataset.save_to_disk(./datasets)
由于law_judgement数据集太大了要下载很久所以可以下载小一点的数据集比如news_peoples_daily 改为news_peoples_daily数据集
from datasets import load_dataset
dataset load_dataset(liwu/MNBVC, news_peoples_daily,cache_dir./dataset)# print(next(iter(dataset))) # get the first line)dataset.save_to_disk(./datasets)二、中文数据集处理
需要把下载的数据集进行处理才能用来训练。
1、数据格式
下载后的数据集如下 获取到的中文数据集需要转换成对应的格式
首先我们借用训练英文时的数据集TinyStories_all_data来看下训练llama2时的数据格式如下我们取一条数据集出来看看
{story: \n\nLily and Ben are friends. They like to play in the park. One day, they see a big tree with a swing. Lily wants to try the swing. She runs to the tree and climbs on the swing.\n\Push me, Ben!\ she says. Ben pushes her gently. Lily feels happy. She swings higher and higher. She laughs and shouts.\nBen watches Lily. He thinks she is cute. He wants to swing too. He waits for Lily to stop. But Lily does not stop. She swings faster and faster. She is having too much fun.\n\Can I swing too, Lily?\ Ben asks. Lily does not hear him. She is too busy swinging. Ben feels sad. He walks away.\nLily swings so high that she loses her grip. She falls off the swing. She lands on the ground. She hurts her foot. She cries.\n\Ow, ow, ow!\ she says. She looks for Ben. She wants him to help her. But Ben is not there. He is gone.\nLily feels sorry. She wishes she had shared the swing with Ben. She wishes he was there to hug her. She limps to the tree. She sees something hanging from a branch. It is Bens hat. He left it for her.\nLily smiles. She thinks Ben is nice. She puts on his hat. She hopes he will come back. She wants to say sorry. She wants to be friends again.,instruction: {prompt:: Write a short story (3-5 paragraphs) which only uses very simple words that a 3 year old child would understand. The story should use the verb \hang\, the noun \foot\ and the adjective \cute\. The story has the following features: the story should contain at least one dialogue. Remember to only use simple words!\n\nPossible story:,words: [hang,foot,cute],features: [Dialogue]},summary: Lily and Ben play in the park and Lily gets too caught up in swinging, causing Ben to leave. Lily falls off the swing and hurts herself, but Ben leaves his hat for her as a kind gesture.,source: GPT-4
}训练时读取了story里面的内容进行训练因此我们需要将news_peoples_daily的格式进行转换news_peoples_daily的json格式如下
有用的部分也就是[”段落”][”内容”]但可以看到文章是被分为多段了所以要把这些段落整合到一起作为一篇新闻然后再把它放到”story”的字段下
{文件名: /Users/liuhui/Downloads/rmrb/7z/1983年07月/1983-07-07_对外友协举行酒会_庆祝蒙古人民革命六十二周年.txt,是否待查文件: false,是否重复文件: false,文件大小: 556,simhash: 8677582667933606471,最长段落长度: 42,段落数: 9,去重段落数: 9,低质量段落数: 0,段落: [{行号: 0,是否重复: false,是否跨文件重复: false,md5: 17018587826f99a0ac2ccd3d5973b2f3,内容: ### 对外友协举行酒会 庆祝蒙古人民革命六十二周年},{行号: 2,是否重复: false,是否跨文件重复: false,md5: a45e5def32d55da952dfbfb1a20c6283,内容: 1983-07-07},{行号: 3,是否重复: false,是否跨文件重复: false,md5: 39cfb2c05e0d07765c687366fe84c5ff,内容: 第4版()},{行号: 4,是否重复: false,是否跨文件重复: false,md5: 2405e967330cfcb4305cb674ef749c0d,内容: 专栏},{行号: 6,是否重复: false,是否跨文件重复: false,md5: f080683bf18389cdcb28f05b6d42ac44,内容: 对外友协举行酒会},{行号: 7,是否重复: false,是否跨文件重复: false,md5: 4fec90badc3733037cf2b36572f8c347,内容: 庆祝蒙古人民革命六十二周年},{行号: 8,是否重复: false,是否跨文件重复: false,md5: 9032d9de9b25275e7ae700dde105e3f6,内容: 新华社北京7月5日电 为庆祝蒙古人民革命六十二周年对外友协今天下午在这里举行酒会。},{行号: 9,是否重复: false,是否跨文件重复: false,md5: b331b6d7e9d420e6aa99c790ee6e356c,内容: 应邀出席酒会的有蒙古人民共和国驻中国大使彭茨克·沙格达尔苏伦以及大使馆外交官员。},{行号: 10,是否重复: false,是否跨文件重复: false,md5: 921500d7924acf82842f4704a5cf5211,内容: 对外友协副会长陆璀主持了酒会。酒会结束后放映了中国彩色故事片《快乐的单身汉》。}]
}
文章每一段的内容也就是用下面的结构体表示 {文件名: string,是否待查文件: bool,是否重复文件: bool,文件大小: int32,simhash: uint64,最长段落长度: int32,段落数: int32,去重段落数: int32,低质量段落数: int32,段落: [{行号: int32,是否重复: bool,是否跨文件重复: bool,md5: string,内容: string,},......]
}2、数据集处理之tokenizer训练格式
1先将一篇篇文本拼凑到一起只是简单的拼凑一起用于训练tokenizer
### 对外友协举行酒会 庆祝蒙古人民革命六十二周年
1983-07-07
第4版()
专栏
对外友协举行酒会
庆祝蒙古人民革命六十二周年
新华社北京7月5日电 为庆祝蒙古人民革命六十二周年对外友协今天下午在这里举行酒会。
应邀出席酒会的有蒙古人民共和国驻中国大使彭茨克·沙格达尔苏伦以及大使馆外交官员。
对外友协副会长陆璀主持了酒会。酒会结束后放映了中国彩色故事片《快乐的单身汉》。### 英德水泥厂不择手段乱涨价 广东省府省纪委正严肃处理
唐炜
1983-07-19
第1版()
专栏
英德水泥厂不择手段乱涨价 广东省府省纪委正严肃处理
据新华社广州7月18日电 记者唐炜广东省英德水泥厂用多种手段擅自提高水泥价格经初步查明今年到7月9日为止共非法牟利806万多元。目前广东省人民政府、中共广东省纪委正在严肃处理这一事件。
这个厂无视国家统一定价随意自定水泥价格。他们的主要手法是一、用计划内的熟料加工成计划外水泥然后高价出售。今年他们用这种手段共多收货款64900多元。二、以超产自销为名擅自将五二五号水泥出厂价从每吨67元提高到113元3角共多收货款128200多元。三、擅自加收纸袋差价、装车费、转仓费、铁路专用线费等。这个厂有一段长6公里的铁路专用线早由铁路部门统一管理和收费。但工厂从去年3月起还收铁路专用线费仅今年上半年就多收了54300多元。代码如下 Download, preprocess and serve the TinyStories dataset as a DataLoader.
import argparse
import glob
import json
import os
import random
from typing import List
from concurrent.futures import ThreadPoolExecutor, as_completedimport numpy as np
import requests
import torch
import torch.distributed as dist
from tqdm import tqdm# 将数据json转为txt文件然后通过程序meragedatas.py合并数据集DATA_CACHE_DIR dataimport jsondef process_shard(filename):tokenize_data_filename filename.replace(.json, .txt)# 判断文件是否存在# 文件存在以追加模式打开文件with open(tokenize_data_filename, w,encodingutf-8) as f:# 写入内容f.close()fd open(tokenize_data_filename, a,encodingutf-8)with open(filename,encodingutf-8) as f:file_content f.read()json_objs file_content.split(\n)# all_content for obj in tqdm(json_objs):if obj.strip():data json.loads(obj)one_txt_content \n\nfor para in data[段落]:one_txt_content one_txt_content para[内容] \n# 写入内容fd.write(one_txt_content)# # iterate the shards and tokenize all of them one by one
data_dir os.path.join(DATA_CACHE_DIR, news_peoples_daily)
shard_filenames sorted(glob.glob(os.path.join(data_dir, *.json)))# # # process all the shards in a threadpool
with ThreadPoolExecutor(max_workers8) as executor:executor.map(process_shard, shard_filenames)print(Done.)2将数据集进行合并
由于有多个json文本文件然后也保存了多个txt文件所以将这些txt文件合并为一个文件保存为data/mergedatas.txt”
import os# 将news_peoples_daily.py生成的txt文件合并到一个文件用于训练tokenizer.model# 目标文件夹路径和输出文件路径
folder_path dataDATA_CACHE_DIR data
data_dir os.path.join(DATA_CACHE_DIR, news_peoples_daily)output_path data/mergedatas.txt# 遍历目标文件夹下的所有文件
with open(output_path, w,encoding utf-8) as output_file:for filename in os.listdir(data_dir):# 检查文件是否以.txt结尾if filename.endswith(.txt):# 是txt文件打开文件并将内容写入输出文件中file_path os.path.join(data_dir, filename)with open(file_path, r,encodingutf-8) as input_file:output_file.write(input_file.read())合并后的数据集data/mergedatas.txt”就可以用来训练tokenizer了训练过程参考下面的文章
[玩转AIGC]sentencepiece训练一个Tokenizer(标记器)
3、数据集处理之模型llama2训练train.py格式
我们还需要对数据集进行处理使得其符合train.py的输入数据格式也就是转为带key为story的json数据保存为txt文件 Download, preprocess and serve the TinyStories dataset as a DataLoader.
import argparse
import glob
import json
import os
import random
from typing import List
from concurrent.futures import ThreadPoolExecutor, as_completedimport numpy as np
import requests
import torch
import torch.distributed as dist
from tqdm import tqdm# 将数据json转为txt文件然后通过程序meragedatas.py合并数据集DATA_CACHE_DIR dataimport jsondef process_shard(filename):tokenize_data_filename filename.replace(.json, .txt)# 判断文件是否存在# 文件存在以追加模式打开文件with open(tokenize_data_filename, w,encodingutf-8) as f:# 写入内容f.close()fd open(tokenize_data_filename, a,encodingutf-8)fd.write([)ifstart Truewith open(filename,encodingutf-8) as f:file_content f.read()json_objs file_content.split(\n)# all_content for obj in tqdm(json_objs):if obj.strip():data json.loads(obj)one_txt_content one_article for para in data[段落]: # 一段内容one_txt_content one_txt_content para[内容] \n# 写入内容#fd.write(one_txt_content)# all_content all_content one_txt_contentif not ifstart:fd.write(,)fd.write(\n)ifstart FalsejsonContent {story:one_txt_content}json.dump(jsonContent, fd, ensure_asciiFalse)fd.write(])fd.close()# # iterate the shards and tokenize all of them one by one
data_dir os.path.join(DATA_CACHE_DIR, news_peoples_daily)
shard_filenames sorted(glob.glob(os.path.join(data_dir, *.json)))# # # process all the shards in a threadpool
with ThreadPoolExecutor(max_workers8) as executor:executor.map(process_shard, shard_filenames)print(Done.)
转换后如下 [{“story”: “### 对外友协举行酒会 庆祝蒙古人民革命六十二周年\n1983-07-07\n第4版()\n专栏\n对外友协举行酒会\n庆祝蒙古人民革命六十二周年\n新华社北京7月5日电\u3000为庆祝蒙古人民革命六十二周年对外友协今天下午在这里举行酒会。\n应邀出席酒会的有蒙古人民共和国驻中国大使彭茨克·沙格达尔苏伦以及大使馆外交官员。\n对外友协副会长陆璀主持了酒会。酒会结束后放映了中国彩色故事片《快乐的单身汉》。\n”}, {“story”: “### 今日兄弟报纸要目\n1983-07-08\n第4版()\n专栏今日兄弟报纸要目\n今日兄弟报纸要目\n《天津日报》△国务院委托水电部在天津召开的引滦工程管理工作会议提出不但要把引滦工程建设成为第一流的工程而且要努力创造第一流的管理水平\n《经济日报》△一些地区和单位措施不力心存观望关停计划外烟厂进展迟缓\n△社论执行国务院决定不能打折扣\n《四川日报》△四川省政府决定计划外烟厂一律关停\n《湖北日报》△武汉钢铁公司主动清查乱涨价问题从7月1日起停止加收计划外协作钢材“管理费”\n《文汇报》△进一步加强关于统一祖国方针政策的宣传教育《上海市对台宣传展览》昨日开幕\n《解放军报》△北京部队某炮团坚持原则退回12名不符合规定的汽车驾驶员\n《人民铁道》△特约评论员文章杜绝野蛮装卸的根本措施在于加强基础工作\n《南方日报》△广东省地质局水文一队在雷州半岛地表以下500米深度内查明有“地下海”地下水资源总量每日为1471万吨\n《陕西日报》△平利县农民积极发展香菇生产全县有1100多户和外贸公司签订合同\n《解放日报》△上海造船工业今年上半年创历史最好水平已完成船舶20艘计177万多吨位其中出口船11艘共156万多吨位总产值4亿多\n《北京日报》北京市政府通知严格控制企业职工加班加点制止滥发加班加点工资\n”}] 总之这里我们准备了2种数据一种用于训练tokenizer一种用于训练llama2模型并分别简单介绍了数据结构
三、训练一个tokenizer
训练操作可以参考博文[玩转AIGC]sentencepiece训练一个Tokenizer(标记器)
spm_train --inputdata/mergedatas.txt -model_prefix./tokenizer训练完成后可以查看相关词汇
查看词汇个数
打开训练好的文件tokenizer.vocab就可以看到个数可看到一共是8000 将tokenizer转为C可读的bin运行
# 以下代码是在llama2.c根目录运行
python3 tokenizer.py可看到
tokenizer.bin
四、使用训练的tokenizer预编码输入数据
在进行train之前先对训练集进行处理即使用训练好的tokenizer进行编码:
先修改tinystories.py的pretokenize()方法里面的数据集路径
data_dir os.path.join(DATA_CACHE_DIR, news_peoples_daily)必要时修改主路径
DATA_CACHE_DIR data然后运行
python3 tinystories.py pretokenize五、训练llama2模型
1、修改参数
1vocab_size
训练之前需要对一些参数进行修改这一步很重要
首先要改词汇量大小前面我们查到词汇量是8000
vocab_size设置为总文字数的个数可以看到原代码为32000所以这里将32000改为8000否则在运行./run model.bin的时候会在下面画框那句return了因为数组越界。
注意run.c里面的config是从train出来的model.bin读取的也就是里面的checkpoint 如果训练时忘记改了那就直接在run.c里面直接把config.vocab_size改过来即可上面划线部分就是直接把32000改为8000
2max_seq_len与batch size
max_seq_len推理生成的句子长度会直接影响生成的故事长度默认为256能人为在run.c里面去修改长度但是长度最好不超过训练时的max_seq_len否则运行run.c时运行到越界了会报错在run.c里面的变量为steps训练时max_seq_len不能太大要不然会报显存不足训练时候会看到提示 代码里面默认为64256也就是batch size为64max_seq_len为256这边我为了增长推理输出的句子长度max_seq_len把训练时的batch_size减少了要不然内存要不足了也就是改为161024
3token
run.c中int token 1表示从头开始生成设置为0会不知道从哪开始随便生成的也就是开头不知道从哪开始所以建议token采用默认值也就是token1
2、模型训练
训练之前需修改数据集加载的路径
先来看看训练时数据集是怎么加载的 先来看看训练时数据集是怎么加载的
from tinystories import Taskiter_batches partial(Task.iter_batches,batch_sizebatch_size,max_seq_lenmax_seq_len,devicedevice,num_workers0,
)train_batch_iter iter_batches(train)可以看到调用了Task.iter_batchesTask是在tinystories.py里面定义的来看看tinystories.py里面的Task
class Task:staticmethoddef iter_batches(split, batch_size, max_seq_len, device, num_workers0):ds PretokDataset(split, max_seq_len)dl torch.utils.data.DataLoader(ds, batch_sizebatch_size, pin_memoryTrue, num_workersnum_workers)for x, y in dl:x x.to(device, non_blockingTrue)y y.to(device, non_blockingTrue)yield x, y可看到调用了PretokDataset仔细看PretokDataset发现了数据集路径修改即可也就是修改PretokDataset下的news_peoples_daily
data_dir os.path.join(DATA_CACHE_DIR, news_peoples_daily)修改好之后训练模型
python3 train.py1直接可跑的代码
下载数据集之后放在data目录依次运行 1、python3 news_peoples_daily.py 2、python3 mergedatas.py 3、python3 processTrainDataSets.py 4、python3 tinystories.py pretokenize 5、python3 train.py 2只保留训练的代码
放在kaggle里面的代码需要创建data/news_peoples_daily文件然后把编码好的数据集.bin文件放到里面直接训练训练即可 3、模型读取与转换
训练之后在out里我们可以得到两个模型 model.bin模型是可以用来进行C代码推理的
1) python 读取bin模型
import torch
import struct
import numpy as npdef checkpoint_init_weights(p, f, shared_weights):ptr 0w {}# Read token_embedding_tablew[token_embedding_table] f[ptr:ptr p[vocab_size] * p[dim]].reshape((p[vocab_size], p[dim]))ptr p[vocab_size] * p[dim]# Read rms_att_weightw[rms_att_weight] f[ptr:ptr p[n_layers] * p[dim]].reshape((p[n_layers], p[dim]))ptr p[n_layers] * p[dim]# Read wqw[wq] f[ptr:ptr p[n_layers] * p[dim] * p[dim]].reshape((p[n_layers], p[dim], p[dim]))ptr p[n_layers] * p[dim] * p[dim]# Read wkw[wk] f[ptr:ptr p[n_layers] * p[dim] * p[dim]].reshape((p[n_layers], p[dim], p[dim]))ptr p[n_layers] * p[dim] * p[dim]# Read wvw[wv] f[ptr:ptr p[n_layers] * p[dim] * p[dim]].reshape((p[n_layers], p[dim], p[dim]))ptr p[n_layers] * p[dim] * p[dim]# Read wow[wo] f[ptr:ptr p[n_layers] * p[dim] * p[dim]].reshape((p[n_layers], p[dim], p[dim]))ptr p[n_layers] * p[dim] * p[dim]# Read rms_ffn_weightw[rms_ffn_weight] f[ptr:ptr p[n_layers] * p[dim]].reshape((p[n_layers], p[dim]))ptr p[n_layers] * p[dim]# Read w1w[w1] f[ptr:ptr p[n_layers] * p[dim] * p[hidden_dim]].reshape((p[n_layers], p[dim], p[hidden_dim]))ptr p[n_layers] * p[dim] * p[hidden_dim]# Read w2w[w2] f[ptr:ptr p[n_layers] * p[hidden_dim] * p[dim]].reshape((p[n_layers], p[hidden_dim], p[dim]))ptr p[n_layers] * p[hidden_dim] * p[dim]# Read w3w[w3] f[ptr:ptr p[n_layers] * p[dim] * p[hidden_dim]].reshape((p[n_layers], p[dim], p[hidden_dim]))ptr p[n_layers] * p[dim] * p[hidden_dim]# Read rms_final_weightw[rms_final_weight] f[ptr:ptr p[dim]]ptr p[dim]# Read freq_cis_realhead_size p[dim] // p[n_heads]w[freq_cis_real] f[ptr:ptr p[seq_len] * head_size // 2]ptr p[seq_len] * head_size // 2# Read freq_cis_imagw[freq_cis_imag] f[ptr:ptr p[seq_len] * head_size // 2]ptr p[seq_len] * head_size // 2# Set wclsw[wcls] w[token_embedding_table] if shared_weights else f[ptr:]return wmodel_path model4.bin
#model_path stories15M.bin
# 打开二进制模型文件
with open(model_path, rb) as f:# 读取模型文件头部信息#data f.read()#print(data)header f.read(struct.calcsize(iiiiiii))header struct.unpack(iiiiiii, header)print(header)dim, hidden_dim, n_layers, n_heads, n_kv_heads, vocab_size, max_seq_len header#config f.read(struct.calcsize(config))#config struct.unpack(config, config)with open(model_path, rb) as f:config {dim: dim,hidden_dim: hidden_dim,n_layers: n_layers,n_heads: n_heads,n_kv_heads: n_kv_heads,vocab_size: vocab_size,seq_len:max_seq_len}f_data np.frombuffer(f.read(), dtypenp.float32)weights checkpoint_init_weights(config, f_data, shared_weightsTrue)print(weights[token_embedding_table].shape)print(weights[rms_att_weight].shape)print(weights[wq].shape)print(weights[wk].shape)print(weights[wv].shape)print(weights[wo].shape)print(weights[rms_ffn_weight].shape)print(weights[w1].shape)print(weights[w2].shape)print(weights[w3].shape)print(weights[rms_final_weight].shape)print(weights[freq_cis_real].shape)print(weights[freq_cis_imag].shape)print(weights[wcls].shape)print(weights.keys())print(weights[freq_cis_real])print(weights[freq_cis_imag])np.save(freq_cis_real.npy, weights[freq_cis_real])np.save(freq_cis_imag.npy, weights[freq_cis_imag])print(模型加载完成)2python读取pt模型并转为bin
1逐层手写转换占用内存少
loadModelPt.py
import torch
import numpy as np
import structfrom model import precompute_freqs_cisfilepath model66.bin
f open(filepath, wb)def serialize(t):# 将张量转换为浮点数数组并写入文件d t.detach().cpu().view(-1).numpy().astype(np.float32) # 多维转为1维b struct.pack(f{len(d)}f, *d) # 转为bytef.write(b) # 写入文件# 指定模型文件路径
model_path out/ckpt3.pt# 加载模型
model torch.load(model_path)print(model.keys())print(model_args,model[model_args])
print(----------)
print(config,model[config])
print(----------)
print(optimizer.keys(),model[optimizer].keys())
print(optimizer[state].keys(),model[optimizer][state].keys())
print(----------)
print(model[model].keys(),model[model].keys())print(model[model][tok_embeddings.weight])dim model[model_args][dim]
hidden_dim model[model][layers.0.feed_forward.w1.weight].shape[0]
n_layers model[model_args][n_layers]
n_heads model[model_args][n_heads]
n_kv_heads model[model_args][n_kv_heads]
vocab_size model[model_args][vocab_size]
max_seq_len model[model_args][max_seq_len]print(hidden_dim,hidden_dim)header struct.pack(iiiiiii, dim, hidden_dim, n_layers, n_heads, n_kv_heads, vocab_size, max_seq_len)f.write(header)serialize(model[model][tok_embeddings.weight])print(model[model][layers.0.attention.wq.weight])serialize(model[model][layers.0.attention_norm.weight])
serialize(model[model][layers.1.attention_norm.weight])
serialize(model[model][layers.2.attention_norm.weight])
serialize(model[model][layers.3.attention_norm.weight])
serialize(model[model][layers.4.attention_norm.weight])
serialize(model[model][layers.5.attention_norm.weight])serialize(model[model][layers.0.attention.wq.weight])
serialize(model[model][layers.1.attention.wq.weight])
serialize(model[model][layers.2.attention.wq.weight])
serialize(model[model][layers.3.attention.wq.weight])
serialize(model[model][layers.4.attention.wq.weight])
serialize(model[model][layers.5.attention.wq.weight])serialize(model[model][layers.0.attention.wk.weight])
serialize(model[model][layers.1.attention.wk.weight])
serialize(model[model][layers.2.attention.wk.weight])
serialize(model[model][layers.3.attention.wk.weight])
serialize(model[model][layers.4.attention.wk.weight])
serialize(model[model][layers.5.attention.wk.weight])serialize(model[model][layers.0.attention.wv.weight])
serialize(model[model][layers.1.attention.wv.weight])
serialize(model[model][layers.2.attention.wv.weight])
serialize(model[model][layers.3.attention.wv.weight])
serialize(model[model][layers.4.attention.wv.weight])
serialize(model[model][layers.5.attention.wv.weight])serialize(model[model][layers.0.attention.wo.weight])
serialize(model[model][layers.1.attention.wo.weight])
serialize(model[model][layers.2.attention.wo.weight])
serialize(model[model][layers.3.attention.wo.weight])
serialize(model[model][layers.4.attention.wo.weight])
serialize(model[model][layers.5.attention.wo.weight])serialize(model[model][layers.0.ffn_norm.weight])
serialize(model[model][layers.1.ffn_norm.weight])
serialize(model[model][layers.2.ffn_norm.weight])
serialize(model[model][layers.3.ffn_norm.weight])
serialize(model[model][layers.4.ffn_norm.weight])
serialize(model[model][layers.5.ffn_norm.weight])serialize(model[model][layers.0.feed_forward.w1.weight])
serialize(model[model][layers.1.feed_forward.w1.weight])
serialize(model[model][layers.2.feed_forward.w1.weight])
serialize(model[model][layers.3.feed_forward.w1.weight])
serialize(model[model][layers.4.feed_forward.w1.weight])
serialize(model[model][layers.5.feed_forward.w1.weight])serialize(model[model][layers.0.feed_forward.w2.weight])
serialize(model[model][layers.1.feed_forward.w2.weight])
serialize(model[model][layers.2.feed_forward.w2.weight])
serialize(model[model][layers.3.feed_forward.w2.weight])
serialize(model[model][layers.4.feed_forward.w2.weight])
serialize(model[model][layers.5.feed_forward.w2.weight])serialize(model[model][layers.0.feed_forward.w3.weight])
serialize(model[model][layers.1.feed_forward.w3.weight])
serialize(model[model][layers.2.feed_forward.w3.weight])
serialize(model[model][layers.3.feed_forward.w3.weight])
serialize(model[model][layers.4.feed_forward.w3.weight])
serialize(model[model][layers.5.feed_forward.w3.weight])serialize(model[model][norm.weight])freqs precompute_freqs_cis(model[model_args][dim] // model[model_args][n_heads], model[model_args][max_seq_len] * 2)serialize(freqs.real[:model[model_args][max_seq_len]])serialize(freqs.imag[:model[model_args][max_seq_len]])print(--------------)f.close()2参考llama2.py的转换占用内存大一些
githubhttps://github.com/tairov/llama2.py/tree/master
需对原来的代码做小修改改为从.pt读取参数然后也要修改输入
修改后的代码为
export_meta_llama_bin.py
如果你想改输入输出的路径那么要修改代码export_meta_llama_bin.py里面的
model_path out/ckpt.pt
output_path model.bin然后直接运行
python3 export_meta_llama_bin.py4、模型推理
1代码与模型
代码与模型在run.zip里面 可以看到主要为上图框中的4个文件其中.bin文件均为模型文件一个是文本编码模型一个是llama模型
2编译运行
进行编译
make run运行推理
./run model.binint token 0时生成的内容开头随便生成 int token 1从头生成且max_seq_len1024 五、拓展
1、可自定义参数运行(master分支下的旧tokenizer.bin模型)
git checkout feature/avx2修改
去掉下面两句(读取tokenizer.bin时)
if (fread(max_token_length, sizeof(int), 1, file) ! 1) { fprintf(stderr, failed read\n); return 1; }if (fread(vocab_scores i, sizeof(float), 1, file) ! 1) { fprintf(stderr, failed read\n); return 1;}bpe_encode也需要做修改添加中文支持
参考https://github.com/chenyangMl/llama2.c-zh/blob/main/run.c
void bpe_encode(char *text, char **vocab, float *vocab_scores, int vocab_size, unsigned int max_token_length, int *tokens, int *n_tokens) {// a temporary buffer to merge two consecutive tokenschar* str_buffer malloc((max_token_length*21) * sizeof(char)); // *2 for concat, 1 for null terminator// first encode every individual character in the input string*n_tokens 0; // the number of tokensint text_length strlen(text);int i 0;while (i text_length) {unsigned char byte1 text[i];unsigned char byte2 text[i1];unsigned char byte3 text[i2];if ((byte1 0xE0) 0xE0) {// 3-byte character (Chinese character, with utf8 encoding)sprintf(str_buffer, %c%c%c, byte1, byte2, byte3);i 3;} else {// 1-byte character (English character)sprintf(str_buffer, %c, byte1);i 1;}int id str_lookup(str_buffer, vocab, vocab_size);if (id -1) { fprintf(stderr, not good\n); exit(EXIT_FAILURE); }// printf(c%s, vocab_size%d, id%d\n, str_buffer, vocab_size,id);tokens[*n_tokens] id;(*n_tokens);}// merge the best consecutive pair each iteration, according to the scores in vocab_scoreswhile (1) {float best_score -1e10;int best_id -1;int best_idx -1;for (int i 0; i (*n_tokens-1); i) {// check if we can merge the pair (tokens[i], tokens[i1])sprintf(str_buffer, %s%s, vocab[tokens[i]], vocab[tokens[i1]]);int id str_lookup(str_buffer, vocab, vocab_size);if (id ! -1 vocab_scores[id] best_score) {// this merge pair exists in vocab! record its score and positionbest_score vocab_scores[id];best_id id;best_idx i;}}if (best_idx -1) {break; // we couldnt find any more pairs to merge, so were done}// merge the consecutive pair (best_idx, best_idx1) into new token best_idtokens[best_idx] best_id;// delete token at position best_idx1, shift the entire sequence back 1for (int i best_idx1; i (*n_tokens-1); i) {tokens[i] tokens[i1];}(*n_tokens)--; // token length decreased}free(str_buffer);
}运行
make run ./run model.bin -i ### 新世界./run model.bin -i ### 新世界 -n 80002、可自定义参数运行(feature/avx2分支下的tokenizer.bin模型)
AVX2指的是使用 AVX2 指令集的内嵌函数intrinsics来执行矩阵乘法matmul操作当然也包含了原始的矩阵乘法方法
将tokenizer.model拷贝到代码根目录下运行
python3 tokenizer.py导出的模型tokenizer.bin
可见比master分支下的模型还要大一些内容更丰富
跟1、可自定义参数运行(运行master旧tokenizer.bin模型) 一样但是只需要修改bpe_encode 使得代码能够兼容中文不一样的地方是不需要修改tokenizer.bin模型的读取也就是不需要去掉
if (fread(max_token_length, sizeof(int), 1, file) ! 1) { fprintf(stderr, failed read\n); return 1; }if (fread(vocab_scores i, sizeof(float), 1, file) ! 1) { fprintf(stderr, failed read\n); return 1;}3、上述两种自定义参数运行的差异
不同的地方在export
master的export
def export(self):tokens []for i in range(self.n_words):# decode the token and light postprocessingt self.sp_model.id_to_piece(i)if i self.bos_id:t \ns\nelif i self.eos_id:t \n/s\nelif len(t) 6 and t.startswith(0x) and t.endswith():t chr(int(t[3:5], 16)) # e.g. make 0x01 into \x01t t.replace(▁, ) # sentencepiece uses this as the whitespaceprint(t)tokens.append(t)with open(TOKENIZER_BIN, wb) as f:for token in tokens:bytes token.encode(utf-8)f.write((len(bytes)).to_bytes(4, little)) # write length of bytesf.write(bytes) # write token bytesfeature/avx2的export
def export(self):# get all the tokens (postprocessed) and their scores as floatstokens, scores [], []for i in range(self.n_words): # 遍历所有字# decode the token and light postprocessingt self.sp_model.id_to_piece(i) # 文本s self.sp_model.get_score(i) # 分数# 上面相当于遍历了tokenizer.vocabif i self.bos_id:# 原来为s只是为了添加换行符容易看t \ns\nelif i self.eos_id:# 原来为/s只是为了添加换行符容易看t \n/s\nelif len(t) 6 and t.startswith(0x) and t.endswith():t chr(int(t[3:5], 16)) # e.g. make 0x01 into \x01t t.replace(▁, ) # sentencepiece uses this character as whitespaceb t.encode(utf-8) # bytes of this token, utf-8 encodedtokens.append(b)scores.append(s)if len(b) 33:print(t)#print(t,s)print(self.n_words)# record the max token lengthmax_token_length max(len(t) for t in tokens)# write to a binary filewith open(TOKENIZER_BIN, wb) as f:f.write(struct.pack(I, max_token_length)) # 保存tokenizer.vocab里面的中文词汇编为二进制的最大长度print(max_token_length)for bytes, score in zip(tokens, scores): # 遍历tokenizer.vocabtokens是通过b t.encode(utf-8)编码为二进制的#f.write(struct.pack(fI, score, len(bytes)))f.write(struct.pack(I, len(bytes)))f.write(bytes)把写入文件那里摘出来
#masterwith open(TOKENIZER_BIN, wb) as f:for token in tokens:bytes token.encode(utf-8)f.write((len(bytes)).to_bytes(4, little)) # write length of bytesf.write(bytes) # write token bytes#feature/avx2# write to a binary file
with open(TOKENIZER_BIN, wb) as f:f.write(struct.pack(I, max_token_length)) # 保存tokenizer.vocab里面的中文词汇编为二进制的最大长度print(max_token_length)for bytes, score in zip(tokens, scores): # 遍历tokenizer.vocabtokens是通过b t.encode(utf-8)编码为二进制的f.write(struct.pack(fI, score, len(bytes)))#f.write(struct.pack(I, len(bytes)))f.write(bytes)多写了max_token_length与score
把f.write(struct.pack(“fI”, score, len(bytes)))改为f.write(struct.pack(“I”, len(bytes)))
把**f.write(struct.pack(“I”, max_token_length))**去掉两者就一样了
4、C读取tokenizer注释
{FILE *file fopen(tokenizer.bin, rb);if (!file) { fprintf(stderr, couldnt load tokenizer.bin\n); return 1; }if (fread(max_token_length, sizeof(int), 1, file) ! 1) { fprintf(stderr, failed read1\n); return 1; } // 读取max_token_length用于内存分配int len;for (int i 0; i config.vocab_size; i) {if (fread(vocab_scores i, sizeof(float), 1, file) ! 1) { fprintf(stderr, failed read2\n); return 1;} // 读取scoresif (fread(len, sizeof(int), 1, file) ! 1) { fprintf(stderr, failed read3\n); return 1; }//读取二进制token的长度vocab[i] (char *)malloc(len 1);if (fread(vocab[i], len, 1, file) ! 1) { fprintf(stderr, failed read4\n); return 1; } //读取二进制token数据vocab[i][len] \0; // add the string terminating token}fclose(file);}5、run.c中的bpe_encode即tokenizer的具体流程
void bpe_encode(char *text, char **vocab, float *vocab_scores, int vocab_size, unsigned int max_token_length, int *tokens, int *n_tokens) {printf(%s\n, text); // text为输入的文字提示词prompt// a temporary buffer to merge two consecutive tokenschar* str_buffer malloc((max_token_length*21) * sizeof(char)); // *2 for concat, 1 for null terminator// first encode every individual character in the input string*n_tokens 0; // the number of tokensint text_length strlen(text);//printf(text_length %d\n,text_length);int i 0;while (i text_length) {unsigned char byte1 text[i];unsigned char byte2 text[i1];unsigned char byte3 text[i2];//UTF-8 编码中文通常使用 3 个字节来表示所以一次性先取3个字节if ((byte1 0xE0) 0xE0) { // 判断是否为中文// 3-byte character (Chinese character, with utf8 encoding)sprintf(str_buffer, %c%c%c, byte1, byte2, byte3); // 将字节编码转为字符串也就是单个中文文字i 3;} else {// 1-byte character (English character)sprintf(str_buffer, %c, byte1);// 将字节编码转为字符串也就是单个英文字母i 1;}int id str_lookup(str_buffer, vocab, vocab_size); // 去tokenstokenizer.vocab里面去找存在就获取其indexif (id -1) { fprintf(stderr, not good\n); exit(EXIT_FAILURE); } // 找不到说明输入的字符不支持// printf(c%s, vocab_size%d, id%d\n, str_buffer, vocab_size,id);tokens[*n_tokens] id; //找到了就保存对应的索引id(*n_tokens); // 记录token的总个数}// merge the best consecutive pair each iteration, according to the scores in vocab_scoreswhile (1) {float best_score -1e10; // -1 乘以 10 的 10 次方-10B。这是一个非常大的负数约等于负一百亿int best_id -1;int best_idx -1;for (int i 0; i (*n_tokens-1); i) {// 判断前后两个token能否组成一个词// check if we can merge the pair (tokens[i], tokens[i1])sprintf(str_buffer, %s%s, vocab[tokens[i]], vocab[tokens[i1]]); // 两个token组为一个//printf( %s\n,str_buffer);int id str_lookup(str_buffer, vocab, vocab_size);if (id ! -1 vocab_scores[id] best_score) {// this merge pair exists in vocab! record its score and position//组合的词在tokenizer.vocab里面那么记录其分数与位置index id,最后获取组合后分数最高的那个词汇best_score vocab_scores[id]; // 记录分数best_id id; // 记录在词汇表中的位置best_idx i; // 记录在tokens中的位置}}//char* str_buffer1 malloc((max_token_length*21) * sizeof(char));///sprintf(str_buffer1, %s, vocab[best_id]);//printf(str_buffer1 %s\n,str_buffer1);if (best_idx -1) {break; // we couldnt find any more pairs to merge, so were done // 直到找不到匹配的退出死循环}// merge the consecutive pair (best_idx, best_idx1) into new token best_id 保存分数最高的那个组成词tokens[best_idx] best_id;//char* str_buffer2 malloc((max_token_length*21) * sizeof(char));//printf(best_idx:%d\n,best_id);//sprintf(str_buffer2, %s%s, vocab[best_id]);//printf(best[i] %s\n, str_buffer2); // delete token at position best_idx1, shift the entire sequence back 1//删除两个被组合的token保留组合后的tokentokens[best_idx]与tokens[best_idx1]组合的新词保存到tokens[best_idx]因此剩下没组合的词汇要往前挪for (int i best_idx1; i (*n_tokens-1); i) { tokens[i] tokens[i1];}(*n_tokens)--; // token length decreased组合后tokens长度减小1个//for (int i 0; i (*n_tokens) ; i) {// sprintf(str_buffer2, %s%s, vocab[tokens[i]]);// printf(tokens[i] %s\n, str_buffer2); //}}free(str_buffer);
}对于大部分常见的中文字符UTF-8 编码使用 3 个字节来表示。每个字节都有 8 位因此一个中文字符在 UTF-8 编码中所占用的总位数是 3 × 8 24 位。
比如用下面的输入带了prompt
./run model66.bin -n 10 -i 中国特色社会主义输入的prompt为“中国特色社会主义”会通过bpe_encode这个函数进行处理结合分数来处理
训练的tokenizer的词汇表我们可以看到 两个组合就是
在tokenizer词汇表里面能找到的就是以下的词汇 可见中国是分数得分最高的因此第一轮的
best_score -6.41448; best_id 48; best_idx 0;
把“中”“国”组合为“中国”因此tokens变为以下的 接着再进行组合那么就是
中国特
特色
色社
社会
会主
主义再来查看tokenizer词汇表 得分最高的是社会将“社”“会”两个词组合到一起因此输入的tokens变为 将tokens两两前后合并得到
中国特
特色
色社会
社会主
主义查看tokenizer词汇表
得到主义得分最低因此tokens就变为 然后再进行两两前后组合
中国特
特色
色社会
社会主义查看tokenizer词汇表 因此tokens变为 最后再进行组合 中国特色
特色社会主义在tokenizer词汇表里面已经找不到相应词汇了此时就结束while(1)的死循环
上面可以看到最后的tokens就变成了
tokens[0] 中国 tokens[1] 特色 tokens[2] 社会主义
也就是说原本为“中”“国”“特”“色”“社”“会”“主”“义”经过bpe_encode的处理就变成了“中国”“特色”“社会主义”原本看起来没关系的独个词汇变成有关联
最终得到的tokens就赋值给了prompt_tokens即变为[48, 2953, 274]然后再补一些padding使得输入shape一致。
6、模型推理while (pos steps) 循环
**steps**不是表示词汇个数而是生成的token个数有个token包含了多个词汇有的token是标点符号比如“社会主义”“”
注很多没细看以后有空再补充
while (pos steps) {// forward the transformer to get logits for the next token// 将输入数据通过 Transformer 模型进行前向传递以获取下一个token的逻辑回归logitstransformer(token, pos, config, state, weights);// pos从零开始循环// advance the state state machineif(pos num_prompt_tokens) {// if we are still processing the input prompt, force the next prompt tokennext prompt_tokens[pos];} else {// sample the next tokenif (temperature 0.0f) {// greedy argmax sampling: take the token with the highest probability //采用贪婪获取最高的分数结果只有一个next argmax(state.logits, config.vocab_size);} else {// apply the temperature to the logits //引入随机性到逻辑回归增加结果多样性for (int q0; qconfig.vocab_size; q) { state.logits[q] / temperature; }// apply softmax to the logits to get the probabilities for next token // 在逻辑回归中使用softmax用于获取下个可能得tokensoftmax(state.logits, config.vocab_size);// we sample from this distribution to get the next token//从这个分布中随机取样随机取一个token作为下一个生成的结果if (topp 0) {// simply sample from the predicted probability distribution// 直接从预测的概率分布中进行抽样next sample(state.logits, config.vocab_size);} else {// top-p (nucleus) sampling, clamping the least likely tokens to zero// 使用 top-p或称为 nucleus抽样方法并将最不可能的标记概率设为零// 可以控制生成结果的多样性并避免生成概率非常低的tokennext sample_topp(state.logits, config.vocab_size, topp, state.probindex);}}}pos;// data-dependent terminating condition: the BOS (1) token delimits sequencesif (next 1) { break; }// following BOS (1) token, sentencepiece decoder strips any leading whitespace (see PR #89)char *token_str (token 1 vocab[next][0] ) ? vocab[next]1 : vocab[next];printf(%s, token_str);fflush(stdout);token next;//printf(next:%d\n,next);// init the timer here because the first iteration can be slowerif (start 0) { start time_in_ms(); }}7、tokenizer拓展词汇
https://github.com/google/sentencepiece/blob/9cf136582d9cce492ba5a0cfb775f9e777fe07ea/python/add_new_vocab.ipynb
import sentencepiece.sentencepiece_model_pb2 as model
m model.ModelProto()
m.ParseFromString(open(tokenizer1.model, rb).read())special_tokens open(special_tokens.txt, r).read().split(\n)special_tokens [token for token in special_tokens if token ! ]print(special_tokens)for token in special_tokens:new_token model.ModelProto().SentencePiece()new_token.piece tokennew_token.score 0m.pieces.append(new_token)with open(new.model, wb) as f:f.write(m.SerializeToString())new_token model.ModelProto().SentencePiece()
new_token.piece token
new_token.score -18.60770034790039打印new_token会得到下面的内容
piece: \350\257\275
score: -18.60770034790039采用UTF-8编码的可恢复为
utf8_bytes b\350\257\275
text utf8_bytes.decode(utf-8)
print(text)打出来是“诽”
输出所加载模型的所有token
print(m.pieces)