AIGC实战:LLaMA2打造中文写作利器——数据准备与模型训练全攻略

news2024/11/26 9:49:27
目录
  • 一、下载并加载中文数据集
  • 二、中文数据集处理
    • 1、数据格式
      • 2、数据集处理之tokenizer训练格式
        • 1)先将一篇篇文本拼凑到一起(只是简单的拼凑一起,用于训练tokenizer)
          • 2)将数据集进行合并
      • 3、数据集处理之模型(llama2)训练(train.py)格式
  • 三、训练一个tokenizer
  • 四、使用训练的tokenizer预编码输入数据
  • 五、训练llama2模型
    • 1、修改参数
        • 1)vocab_size
          • 2)max_seq_len与batch size
          • 3)token
      • 2、模型训练
      • 3、模型读取与转换
        • 1) python 读取bin模型
          • 2)python读取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来教大家如何训练一个内容生成工具,即训练属于自己的AIGC(Artificial 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

如果没有显卡,可使用kaggle,kaggle的P100 gpu 足矣

可直接运行的kaggle:
llama2-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 Ben's 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日为止共非法牟利80.6万多元。目前,广东省人民政府、中共广东省纪委正在严肃处理这一事件。
这个厂无视国家统一定价,随意自定水泥价格。他们的主要手法是:一、用计划内的熟料,加工成计划外水泥,然后高价出售。今年,他们用这种手段共多收货款64,900多元。二、以超产自销为名,擅自将五二五号水泥出厂价从每吨67元提高到113元3角,共多收货款128,200多元。三、擅自加收纸袋差价、装车费、转仓费、铁路专用线费等。这个厂有一段长6公里的铁路专用线,早由铁路部门统一管理和收费。但工厂从去年3月起,还收铁路专用线费,仅今年上半年就多收了54,300多元。

代码如下:

"""
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_completed

import numpy as np
import requests
import torch
import torch.distributed as dist
from tqdm import tqdm

# 将数据json转为txt文件,然后通过程序meragedatas.py合并数据集

DATA_CACHE_DIR = "data"

import json

def process_shard(filename):
    tokenize_data_filename = filename.replace(".json", ".txt")
        # 判断文件是否存在

    # 文件存在,以追加模式打开文件
    with open(tokenize_data_filename, "w",encoding='utf-8') as f:
        # 写入内容
        f.close()
 
    fd = open(tokenize_data_filename, "a",encoding='utf-8')

    with open(filename,encoding='utf-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\n"
            for 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_workers=8) 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 = "data"

DATA_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",encoding='utf-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_completed

import numpy as np
import requests
import torch
import torch.distributed as dist
from tqdm import tqdm

# 将数据json转为txt文件,然后通过程序meragedatas.py合并数据集

DATA_CACHE_DIR = "data"

import json


def process_shard(filename):
    tokenize_data_filename = filename.replace(".json", ".txt")
        # 判断文件是否存在

    # 文件存在,以追加模式打开文件
    with open(tokenize_data_filename, "w",encoding='utf-8') as f:
        # 写入内容
        f.close()
 
    fd = open(tokenize_data_filename, "a",encoding='utf-8')

    fd.write("[")
    
    ifstart = True
    with open(filename,encoding='utf-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_content

            if not ifstart:

                fd.write(",")
                fd.write("\n")
            ifstart = False


            jsonContent = {
                "story":one_txt_content
            }

            json.dump(jsonContent, fd, ensure_ascii=False)

    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_workers=8) 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米深度内查明有“地下海”,地下水资源总量每日为1,471万吨\n《陕西日报》△平利县农民积极发展香菇生产,全县有1,100多户和外贸公司签订合同\n《解放日报》△上海造船工业今年上半年创历史最好水平,已完成船舶20艘,计17.7万多吨位,其中出口船11艘,共15.6万多吨位,总产值4亿多\n《北京日报》北京市政府通知:严格控制企业职工加班加点,制止滥发加班加点工资\n”}]

总之:这里我们准备了2种数据,一种用于训练tokenizer,一种用于训练llama2模型,并分别简单介绍了数据结构

三、训练一个tokenizer

训练操作可以参考博文:
[玩转AIGC]sentencepiece训练一个Tokenizer(标记器)

spm_train --input=data/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、修改参数

1)vocab_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

2)max_seq_len与batch size

max_seq_len:推理生成的句子长度,会直接影响生成的故事长度,默认为256,能人为在run.c里面去修改长度(但是长度最好不超过训练时的max_seq_len,否则运行run.c时运行到越界了会报错),在run.c里面的变量为steps,训练时max_seq_len不能太大,要不然会报显存不足,训练时候会看到提示:

代码里面默认为64
256,也就是batch size为64,max_seq_len为256,这边我为了增长推理输出的句子长度(max_seq_len),把训练时的batch_size减少了,要不然内存要不足了,也就是改为16
1024

3)token

run.c中int token = 1,表示从头开始生成,设置为0会不知道从哪开始,随便生成的,也就是开头不知道从哪开始,所以建议token采用默认值,也就是token=1

2、模型训练

训练之前需修改数据集加载的路径

先来看看训练时数据集是怎么加载的:

先来看看训练时数据集是怎么加载的:

from tinystories import Task

iter_batches = partial(
    Task.iter_batches,
    batch_size=batch_size,
    max_seq_len=max_seq_len,
    device=device,
    num_workers=0,
)

train_batch_iter = iter_batches("train")

可以看到调用了Task.iter_batches,Task是在tinystories.py里面定义的,来看看tinystories.py里面的Task:

class Task:

    @staticmethod
    def iter_batches(split, batch_size, max_seq_len, device, num_workers=0):
        ds = PretokDataset(split, max_seq_len)
        dl = torch.utils.data.DataLoader(
            ds, batch_size=batch_size, pin_memory=True, num_workers=num_workers
        )
        for x, y in dl:
            x = x.to(device, non_blocking=True)
            y = y.to(device, non_blocking=True)
            yield x, y

可看到调用了PretokDataset,仔细看PretokDataset,发现了数据集路径,修改即可,也就是修改PretokDataset下的"news_peoples_daily"

data_dir = os.path.join(DATA_CACHE_DIR, "news_peoples_daily")

修改好之后训练模型:

python3 train.py

1)直接可跑的代码:

下载数据集之后放在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 np

def checkpoint_init_weights(p, f, shared_weights):
    ptr = 0
    w = {}

    # Read token_embedding_table
    w["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_weight
    w["rms_att_weight"] = f[ptr:ptr + p["n_layers"] * p["dim"]].reshape((p["n_layers"], p["dim"]))
    ptr += p["n_layers"] * p["dim"]

    # Read wq
    w["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 wk
    w["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 wv
    w["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 wo
    w["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_weight
    w["rms_ffn_weight"] = f[ptr:ptr + p["n_layers"] * p["dim"]].reshape((p["n_layers"], p["dim"]))
    ptr += p["n_layers"] * p["dim"]

    # Read w1
    w["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 w2
    w["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 w3
    w["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_weight
    w["rms_final_weight"] = f[ptr:ptr + p["dim"]]
    ptr += p["dim"]

    # Read freq_cis_real
    head_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_imag
    w["freq_cis_imag"] = f[ptr:ptr + p["seq_len"] * head_size // 2]
    ptr += p["seq_len"] * head_size // 2

    # Set wcls
    w["wcls"] = w["token_embedding_table"] if shared_weights else f[ptr:]

    return w

model_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(), dtype=np.float32)
    
    
    weights = checkpoint_init_weights(config, f_data, shared_weights=True)
    
    
    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("模型加载完成")

2)python读取pt模型并转为bin

1>逐层手写转换(占用内存少)

loadModelPt.py

import torch
import numpy as np
import struct

from model import precompute_freqs_cis

filepath = "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) # 转为byte
   
    f.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
的转换(占用内存大一些)

github:https://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.py

4、模型推理

1)代码与模型

代码与模型在run.zip里面

可以看到主要为上图框中的4个文件,其中.bin文件均为模型文件,一个是文本编码模型,一个是llama模型

2)编译运行

进行编译

make run

运行推理

./run model.bin

int token = 0时生成的内容,开头随便生成

int token = 1,=从头生成,且max_seq_len=1024

五、拓展

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 tokens
    char* str_buffer = malloc((max_token_length*2+1) * sizeof(char)); // *2 for concat, +1 for null terminator

    // first encode every individual character in the input string
    *n_tokens = 0; // the number of tokens
    int text_length = strlen(text);
    int i = 0;
    while (i < text_length) {
        unsigned char byte1 = text[i];
        unsigned char byte2 = text[i+1];
        unsigned char byte3 = text[i+2];

        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_scores
    while (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[i+1])
            sprintf(str_buffer, "%s%s", vocab[tokens[i]], vocab[tokens[i+1]]);
            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
                best_score = vocab_scores[id];
                best_id = id;
                best_idx = i;
            }
        }

        if (best_idx == -1) {
            break; // we couldn't find any more pairs to merge, so we're done
        }

        // merge the consecutive pair (best_idx, best_idx+1) into new token best_id
        tokens[best_idx] = best_id;
        // delete token at position best_idx+1, shift the entire sequence back 1
        for (int i = best_idx+1; i < (*n_tokens-1); i++) {
            tokens[i] = tokens[i+1];
        }
        (*n_tokens)--; // token length decreased
    }

    free(str_buffer);
}

运行:

make run 

./run model.bin -i "### 新世界"

./run model.bin -i "### 新世界" -n 8000

2、可自定义参数运行(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 postprocessing
            t = self.sp_model.id_to_piece(i)
            if i == self.bos_id:
                t = '\n<s>\n'
            elif i == self.eos_id:
                t = '\n</s>\n'
            elif len(t) == 6 and t.startswith('<0x') and t.endswith('>'):
                t = chr(int(t[3:5], 16)) # e.g. make '<0x01>' into '\x01'
            t = t.replace('▁', ' ') # sentencepiece uses this as the whitespace
            
            print(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 bytes
                f.write(bytes)  # write token bytes

feature/avx2的export

def export(self):

        # get all the tokens (postprocessed) and their scores as floats
        tokens, scores = [], []
        for i in range(self.n_words): # 遍历所有字

            # decode the token and light postprocessing
            t = self.sp_model.id_to_piece(i) # 文本
            s = self.sp_model.get_score(i)  # 分数
            # 上面相当于遍历了tokenizer.vocab
            
            if i == self.bos_id:
                # 原来为<s>,只是为了添加换行符,容易看
                t = '\n<s>\n'
            elif i == self.eos_id:
                # 原来为</s>,只是为了添加换行符,容易看
                t = '\n</s>\n'
            elif len(t) == 6 and t.startswith('<0x') and t.endswith('>'):
                t = chr(int(t[3:5], 16)) # e.g. make '<0x01>' into '\x01'
            t = t.replace('▁', ' ') # sentencepiece uses this character as whitespace
            b = t.encode('utf-8') # bytes of this token, utf-8 encoded

            tokens.append(b)
            scores.append(s)
            if len(b) == 33:
                print(t)
            
            #print(t,s)
        print(self.n_words)
        
        # record the max token length
        max_token_length = max(len(t) for t in tokens)

        # 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.vocab,tokens是通过b = t.encode('utf-8')编码为二进制的
                #f.write(struct.pack("fI", score, len(bytes)))
                f.write(struct.pack("I", len(bytes)))
                f.write(bytes)

把写入文件那里摘出来:

#master
	
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 bytes
	      f.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.vocab,tokens是通过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, "couldn't 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;} // 读取scores
            if (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 tokens
    char* str_buffer = malloc((max_token_length*2+1) * sizeof(char)); // *2 for concat, +1 for null terminator

    // first encode every individual character in the input string
    *n_tokens = 0; // the number of tokens
    int 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[i+1];
        unsigned char byte3 = text[i+2];
        
        //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); // 去tokens(tokenizer.vocab)里面去找,存在就获取其index
        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; //找到了就保存对应的索引id
        (*n_tokens)++; // 记录token的总个数
    }

    // merge the best consecutive pair each iteration, according to the scores in vocab_scores
    while (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[i+1])
            sprintf(str_buffer, "%s%s", vocab[tokens[i]], vocab[tokens[i+1]]); // 两个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*2+1) * sizeof(char));
      
        ///sprintf(str_buffer1, "%s", vocab[best_id]);
        //printf("str_buffer1 = %s\n",str_buffer1);

        if (best_idx == -1) {
            break; // we couldn't find any more pairs to merge, so we're done // 直到找不到匹配的,退出死循环
        }

        // merge the consecutive pair (best_idx, best_idx+1) into new token best_id 保存分数最高的那个组成词
        tokens[best_idx] = best_id;
        
        //char* str_buffer2 = malloc((max_token_length*2+1) * 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_idx+1, shift the entire sequence back 1
        //删除两个被组合的token,保留组合后的token,tokens[best_idx]与tokens[best_idx+1]组合的新词保存到tokens[best_idx],因此剩下没组合的词汇要往前挪
        for (int i = best_idx+1; i < (*n_tokens-1); i++) { 
            tokens[i] = tokens[i+1];
            
        }
        (*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的逻辑回归(logits)
        transformer(token, pos, &config, &state, &weights);
        
        // pos从零开始循环
        

        // advance the state state machine
        if(pos < num_prompt_tokens) {
            // if we are still processing the input prompt, force the next prompt token
            next = prompt_tokens[pos];
        } else {
            // sample the next token
            if (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 q=0; q<config.vocab_size; q++) { state.logits[q] /= temperature; }
                // apply softmax to the logits to get the probabilities for next token 
                // 在逻辑回归中使用softmax,用于获取下个可能得token
                softmax(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)抽样方法,并将最不可能的标记概率设为零
                    // 可以控制生成结果的多样性,并避免生成概率非常低的token
                    next = sample_topp(state.logits, config.vocab_size, topp, state.probindex);
                }
            }
        }
        pos++;

        // data-dependent terminating condition: the BOS (1) token delimits sequences
        if (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 slower
        if (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 = token
    new_token.score = 0
    m.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)

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

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

相关文章

新书速览|解密AI绘画与修图: Stable Diffusion+Photoshop

《解密AI绘画与修图&#xff1a; Stable DiffusionPhotoshop》 本书内容 《解密AI绘画与修图&#xff1a;Stable DiffusionPhotoshop》全面介绍了Photoshop和Stable Diffusion的交互方式&#xff0c;以及各自的AI功能和具体使用方法。除了讲解功能&#xff0c;还通过实际案例加…

浪潮信息存储的灵魂:平台化+场景化 全面释放数据价值

在数字化浪潮的席卷下&#xff0c;浪潮信息存储平台凭借卓越的性能和稳定性&#xff0c;正日益成为企业释放数据价值的重要力量。近日&#xff0c;浪潮信息出席了“2024数据基础设施技术峰会”&#xff0c;相关代表聚焦当前数据价值的释放话题&#xff0c;围绕先进存储基础设施…

Mongodb安装与配置

Mongodb的下载 这里下载的是MongoDB 7.0.11版本的 首先进入官网&#xff1a;https://www.mongodb.com/ 点击完上面两步后&#xff0c;加载来到该页面&#xff0c;选择自己的版本、系统&#xff0c;是压缩包(zip)还是安装包(msi)。 下载好之后能&#xff0c;来到安装包哪里&a…

爱奇艺 Opal 机器学习平台:特征中心建设实践

01 综述 Opal 是爱奇艺大数据团队研发的一站式机器学习平台&#xff0c;旨在提升特征迭代、模型训练效率&#xff0c;帮助业务提高收益。整个平台覆盖了机器学习生命周期中特征生产、样本构建、模型探索、模型训练、模型部署等在内的多个关键环节。其中特征作为模型训练的基石…

maven安装jar和pom到本地仓库

举例子我们要将 elastic-job-spring-boot-starter安装到本地的maven仓库&#xff0c;如下&#xff1a; <dependency><groupId>com.github.yinjihuan</groupId><artifactId>elastic-job-spring-boot-starter</artifactId><version>1.0.5&l…

基于SpringBoot网吧管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; Java精品实战案例《600套》 2025-2026年最值得选择的Java毕业设计选题大全&#xff1…

TCP/IP模型原理(理论)

TCP/IP模型 1. 网络模型简介2. 应用层2.1 URL2.1.1 urlencode和urldecode 2.2 HTTP协议2.2.1 HTTP协议格式2.2.2 HTTP问题2.2.3 HTTPS 3 传输层3.1 端口号3.2 udp3.2.1 udp协议帧格式3.2.2 udp特点3.2.3 udp缓冲区3.2.4 注意 3.3 tcp协议3.3.1 tcp协议段格式3.3.2 确认应答机制…

计算机组成原理:海明校验

在上图中&#xff0c;对绿色的7比特数据进行海明校验&#xff0c;需要添加紫色的4比特校验位&#xff0c;总共是蓝色的11比特。紫色的校验位pi分布于蓝色的hi的1, 2, 4, 8, 16, 32, 64位&#xff0c;是2i-1位。绿色的数据位bi分布于剩下的位。 在下图中&#xff0c;b1位于h3&a…

如何借助ai(文心一言)获取tushare的数据

1. 准备工作 确保已安装python &#xff0c;安装Tushare库 和文心一言的地址&#xff08;文心一言&#xff09;&#xff1a; 注册Tushare账号并获取Token&#xff1a;在Tushare官方网站注册账号&#xff0c;并获取个人Token。如下 tushare地址&#xff1a;&#xff08;点击即…

音视频开发32 FFmpeg 编码- 视频编码 h264 参数相关

1. ffmpeg -h 这个命令总不会忘记&#xff0c;用这个先将ffmpeg所有的help信息都list出来 C:\Users\Administrator>ffmpeg -h ffmpeg version 6.0-full_build-www.gyan.dev Copyright (c) 2000-2023 the FFmpeg developersbuilt with gcc 12.2.0 (Rev10, Built by MSYS2 pro…

如何恢复已删除的 JPG/JPEG 图片文件

您是否意外丢失或删除了 JPG 或 JPEG 照片&#xff1f;幸运的是&#xff0c;您可以使用照片恢复工具找回它们。立即获取 JPEG 恢复工具 - PC 版照片恢复&#xff1a; 照片是捕捉和重温生活中特殊时刻的最佳方式。因此&#xff0c;当我们因硬盘崩溃、意外格式化磁盘或错误删除图…

Element UI搭建使用过程

本章内容基于上一篇---Vue-cli搭建项目基础版 Vue-cli搭建项目----基础版-CSDN博客 官网地址:Element - The worlds most popular Vue UI framework 介绍:完全基于Vue.js ,用于快速搭建用户界面. 第一步:安装ElementUI 在终端输入 npm i element-ui -S 在main.js输入 …

SpringMVC启动流程

文章目录 引文HandlerHandlerMapperHandlerAdapterRequestMapping方法参数解析RequestMapping方法返回值解析文件上传流程拦截器解析 SpringMVC启动流程如下 引文 我们在使用SpringMVC时&#xff0c;传统的方式是在webapp目录下定义一个web.xml文件&#xff0c;比如&#xff1a…

无忧易售新功能:一键白底转换,升级产品图片质感

在电商领域不断追求卓越与效率的今天&#xff0c;无忧易售ERP推出一键白底转换功能&#xff0c;为卖家们提供前所未有的便捷与高效&#xff0c;改变了商品图片处理的传统模式&#xff0c;革新了卖家们的图片处理体验&#xff0c;让商品展示焕然一新&#xff0c;助力商家在激烈的…

点云入门知识

点云的处理任务 场景语义分割 物体的三维表达方法&#xff08;3D representations&#xff09;&#xff1a; 点云&#xff1a;是由物体表面上许多点数据来表征这个物体。最接近原始传感器数据&#xff0c;且具有丰富的几何信息。 Mesh&#xff1a;用三角形面片和正方形面片拼…

计算机人说学校-北京理工大学-计算机方向

1. 专长、特点、特色 北京理工大学&#xff08;北理工&#xff09;的计算机专业同样具有显著的优势和特点&#xff1a; 学术水平高&#xff1a;作为一所985高校&#xff0c;北理工在计算机科学与技术以及人工智能领域都有着较高的学术水平和教学资源。研究方向广泛&#xff1…

阿里云服务器数据库迁云: 数据从传统到云端的安全之旅(WordPress个人博客实战教学)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 一、 开始实战1.2创建实验资源1.3重置云服务器ECS的登录密码&#xff08;请记住密码&#xff09;1.4 设置安全组端口1…

akamai指纹自由

最近项目的部分业务设计到了akamai的技术。也是第一次接触&#xff0c;于是网上搜了一下&#xff0c;相关的帖子和文章很多&#xff0c;在前人共享的资料的基础上&#xff0c;自己这几天终于完全弄通了。 先上结果 akmai的针对策略就2个&#xff0c;第一个是tls验证&#xff0…

【ElementPlus源码】Container 布局容器

文章目录 index.tsContainerheaderutilswithInstallwithNoopInstall hooksuseNamespace 单元测试 看源码时候做的笔记。如有错误请指出&#xff01; 关于路径的省略&#xff0c;详见button&#xff1a;【ElementPlus源码】Button按钮-CSDN博客 index.ts 导入一堆组件&#xff…

(南京观海微电子)——三极管原理及应用区别

PNP与NPN三极管的原理与使用方法 三极管主要的功能是电流放大和开关作用。 三极管按材料分有两种&#xff1a;锗管和硅管。而每一种又有NPN和PNP两种结构形式&#xff0c;但使用最多的是硅NPN和PNP两种三极管&#xff0c;两者除了电源极性不同外&#xff0c;其工作原理都是相同…