当前位置:AIGC资讯 > AIGC > 正文

DataWhale Task02:从零预训练一个tiny-llama 20923

DataWhale Task02:从零预训练一个tiny-llama 20923

原文link:https://github.com/KMnO4-zx/tiny-llm

开源内容:https://github.com/datawhalechina/tiny-universe

写在前面!!:因为没有编译环境,这期主要讲代码的理解

首先,安装python脚本,安装依赖库:

pip install -r requirements.txt

训练步骤:

训练Tokenizer: python train_vocab.py --download True --vocab_size 4096 数据预处理:python preprocess.py 训练模型:python train.py 使用模型生成文本:python sample.py --prompt "One day, Lily met a Shoggoth"

具体步骤:

1.训练Tokenizer:

在自然语言处理(NLP)中,Tokenizer(分词器)是将输入文本分割成更小单元(通常称为token)的工具或方法。Token可以是词、子词、字符、甚至是标点符号,具体取决于所使用的分词方法。Tokenizer 的主要作用是将原始文本转换成适合模型处理的形式。

以下是常见的两种分词方法:

词级别分词(Word-level tokenization):直接按词进行分割,通常基于空格或标点符号。例如:

输入:“I love programming.” 输出:["I", "love", "programming", "."]

子词级别分词(Subword-level tokenization):将单词进一步拆解成更小的子单元,常见于一些基于子词的模型(如BPE或WordPiece)。这对处理未登录词(out-of-vocabulary words)尤其有效。

输入:“programming” 输出:["pro", "gram", "ming"]

Tokenizer 通常是语言模型的前处理步骤,用于将自然语言文本转换为可以输入模型的数值表示。

下载数据集并训练:

python train_vocab.py --download True --vocab_size 4096

tokenizer.py

def download_file(url: str, fname: str, chunk_size=1024):
    """发送HTTP GET请求以流式方式获取文件"""
    ···

def download():
    """执行 download_file 下载数据集"""
    ···

def train_vocab(vocab_size: int=32000, num_shards: int=20):
    """
    vocab_size: int, 词汇表的大小,决定分词器的词汇量。
    num_shards: int, 用于加快词汇表训练的效率,指定要处理的分片数量。
    """
    # 确保词汇表大小为正数
    assert vocab_size > 0, "Vocab size must be positive"

    # SentencePiece 模型的前缀路径,将用于保存分词器
    prefix = os.path.join(DATA_CACHE_DIR, f"tok{vocab_size}")

    # 1) 将多个分片中的文本导出为单个文本文件 tiny.txt
    tiny_file = os.path.join(DATA_CACHE_DIR, "tiny.txt")
    data_dir = os.path.join(DATA_CACHE_DIR, "TinyStories_all_data")
    shard_filenames = sorted(glob.glob(os.path.join(data_dir, "*.json")))

    # 创建 tiny.txt 文件并写入指定数量的分片中的文本
    print(f"Writing temporary file {tiny_file} with {num_shards} shards...")
    with open(tiny_file, "w", encoding="utf-8") as of:
        # 遍历前 num_shards 个分片
        for shard in tqdm(shard_filenames[:num_shards]):
            with open(shard, "r") as f:
                data = json.load(f)  # 读取分片中的JSON数据
            # 遍历每个例子,将其中的故事文本写入 tiny.txt 文件
            for example in data:
                text = example["story"]
                text = text.strip()  # 去除文本首尾的空白字符
                of.write(text + "\n")  # 每个文本写入一行

    # 输出生成的 tiny.txt 文件的大小
    print(f"Size is: {os.path.getsize(tiny_file) / 1024 / 1024:.2f} MB")

    # 2) 使用 SentencePiece 训练分词器
    print("Will now train the vocab...")
    spm.SentencePieceTrainer.train(
        input=tiny_file,         # 输入文件为之前生成的 tiny.txt
        model_prefix=prefix,     # 模型前缀路径
        model_type="bpe",        # 使用 Byte-Pair Encoding (BPE) 训练分词器
        vocab_size=vocab_size,   # 词汇表大小
        self_test_sample_size=0, # 自测样本大小设置为 0
        input_format="text",     # 输入文件格式为纯文本
        character_coverage=1.0,  # 覆盖所有字符(包括非常见字符)
        num_threads=os.cpu_count(),  # 使用 CPU 的线程数
        split_digits=True,       # 拆分数字
        allow_whitespace_only_pieces=True,  # 允许仅由空格组成的词元
        byte_fallback=True,      # 启用字节级回退
        unk_surface=r" \342\201\207 ",  # UNK token 表示未知字符的方式
        normalization_rule_name="identity"  # 使用“identity”归一化规则
    )

    # 3) 可选的清理操作,询问用户是否删除临时文件 tiny.txt
    dec = input(f"Delete the temporary file {tiny_file}? [y/N] ")
    if dec.lower() == "y":
        os.remove(tiny_file)  # 删除临时文件
        print(f"Deleted {tiny_file}")

    # 输出模型保存的路径
    print(f"Trained tokenizer is in {prefix}.model")
    print("Done.")

代码理解:

主要使用了 SentencePiece 库来生成基于 BPE(Byte-Pair Encoding)的方法。训练过程由多个步骤构成,涉及从数据集中读取文本、生成临时文件、训练词汇表并最终保存模型。

train_vocab概述:这个函数的目标是:

从多个 JSON 文件中提取文本数据。 将这些数据写入一个临时文件(tiny.txt)。 使用 SentencePiece 对提取的文本进行分词器的训练。 最终根据训练设置生成一个词汇表大小为 vocab_size 的分词器模型。

vocab_size: 定义训练出的分词器的词汇表大小(即模型中包含多少个词元)。

num_shards: 指定从数据集中读取的分片数,用于加速处理多个数据分片

代码详解:

assert vocab_size > 0, "Vocab size must be positive"

确保词汇表大小为整数,用来确保 vocab_size 是大于 0 的有效值,如果不满足条件则抛出错误

prefix = os.path.join(DATA_CACHE_DIR, f"tok{vocab_size}")

设置模型的路径前缀,将词汇表训练的模型路径设置为 DATA_CACHE_DIR 目录下的 tok{vocab_size}。训练后的模型将保存为 {prefix}.model 文件。

tiny_file = os.path.join(DATA_CACHE_DIR, "tiny.txt") data_dir = os.path.join(DATA_CACHE_DIR, "TinyStories_all_data") shard_filenames = sorted(glob.glob(os.path.join(data_dir, "*.json")))

将数据集的多个 JSON 文件合并成一个 tiny.txt 文件,

data_dir 是数据集所在的目录,代码会读取该目录下的所有 JSON 文件。 shard_filenames 是读取到的 JSON 文件路径列表,按文件名排序。
with open(tiny_file, "w", encoding="utf-8") as of:
    for shard in tqdm(shard_filenames[:num_shards]):
        with open(shard, "r") as f:
            data = json.load(f)
        for example in data:
            text = example["story"]
            text = text.strip()
            of.write(text + "\n")

打开 tiny.txt 作为写入的目标文件。

遍历前 num_shards 个 JSON 文件,每个文件包含许多 JSON 格式的“故事”。

对每个 JSON 文件中的每个“故事”进行处理,将其中的 story 字段文本提取出来并写入 tiny.txt

print(f"Size is: {os.path.getsize(tiny_file) / 1024 / 1024:.2f} MB")

输入生成文件(tiny.txt)的大小

spm.SentencePieceTrainer.train( input=tiny_file, model_prefix=prefix, model_type="bpe", vocab_size=vocab_size, ... )

使用 SentencePiece 训练分词器

input=tiny_file: 使用之前生成的 tiny.txt 文件作为训练输入。

model_prefix=prefix: 模型文件将以 prefix 为前缀保存。

model_type="bpe": 采用 BPE(Byte-Pair Encoding)算法训练分词器。

vocab_size: 指定词汇表的大小。

其他参数用于控制分词器训练时的一些细节,例如字符覆盖率、线程数、是否拆分数字等。

可选的清理步骤,训练完成后,询问用户是否删除临时文件 tiny.txt,如果用户输入 y 则删除该文件。

print(f"Trained tokenizer is in {prefix}.model")

输出模型保存路径,模型文件会保存为 {prefix}.model,这行代码输出模型保存的路径。

展示如何使用该类来处理 TinyStory 数据集中的故事文本

class Tokenizer:
    def __init__(self, tokenizer_model=None):
        """
        初始化分词器。加载预训练的SentencePiece模型,并设置一些特殊的token ID。
   class Tokenizer:
    def __init__(self, tokenizer_model=None):
        """
        初始化分词器。加载预训练的SentencePiece模型,并设置一些特殊的token ID。

        参数:
        tokenizer_model: str, 可选,分词器模型的路径,如果不指定则使用默认路径 TOKENIZER_MODEL。
        """
        # 如果提供了分词器模型路径,使用该路径;否则使用默认模型路径
        model_path = tokenizer_model if tokenizer_model else TOKENIZER_MODEL
        # 确保模型文件存在
        assert os.path.isfile(model_path), model_path

        # 加载 SentencePiece 模型
        self.sp_model = SentencePieceProcessor(model_file=model_path)
        self.model_path = model_path

        # 获取分词器的特殊token和词汇表大小
        self.n_words: int = self.sp_model.vocab_size()  # 词汇表大小
        self.bos_id: int = self.sp_model.bos_id()       # 句子开头 (BOS) 的ID
        self.eos_id: int = self.sp_model.eos_id()       # 句子结尾 (EOS) 的ID
        self.pad_id: int = self.sp_model.pad_id()       # 填充 (PAD) 的ID

        # 验证分词器词汇表大小是否正确
        assert self.sp_model.vocab_size() == self.sp_model.get_piece_size()

    def encode(self, s: str, bos: bool, eos: bool) -> List[int]:
        """
        将字符串编码为词元ID列表。可以选择是否添加句子开头 (BOS) 和句子结尾 (EOS) 标记。

        参数:
        s: str, 要编码的字符串。
        bos: bool, 是否在编码的词元列表前添加 BOS 标记。
        eos: bool, 是否在编码的词元列表末尾添加 EOS 标记。

        返回:
        List[int]: 编码后的词元ID列表。
        """
        # 确保输入是字符串类型
        assert type(s) is str
        # 使用SentencePiece将字符串编码为词元ID
        t = self.sp_model.encode(s)
        # 如果需要BOS标记,将其添加到词元列表开头
        if bos:
            t = [self.bos_id] + t
        # 如果需要EOS标记,将其添加到词元列表末尾
        if eos:
            t = t + [self.eos_id]
        return t

    def decode(self, t: List[int]) -> str:
        """
        将词元ID列表解码为字符串。

        参数:
        t: List[int], 词元ID列表。

        返回:
        str: 解码后的字符串。s
        """
        return self.sp_model.decode(t)     
    
    
    
    

其中:

1.__init__方法

tokenizer_model: 这是一个可选参数,用于指定分词器模型的路径。如果未提供,则使用默认路径 TOKENIZER_MODEL

model_path = tokenizer_model if tokenizer_model else TOKENIZER_MODEL
assert os.path.isfile(model_path), model_path

首先,检查是否提供了自定义的分词器模型路径。如果没有,就使用默认路径 TOKENIZER_MODEL

使用 assert 语句确保指定路径的模型文件存在。如果文件不存在,抛出错误并输出文件路径。

self.sp_model = SentencePieceProcessor(model_file=model_path)
self.model_path = model_path

首先,检查是否提供了自定义的分词器模型路径。如果没有,就使用默认路径 TOKENIZER_MODEL

使用 assert 语句确保指定路径的模型文件存在。如果文件不存在,抛出错误并输出文件路径。

self.sp_model = SentencePieceProcessor(model_file=model_path)
self.model_path = model_path

SentencePieceProcessor 是 SentencePiece 的核心处理器,负责加载模型。model_file=model_path 使得处理器加载指定路径下的分词器模型。

self.n_words: int = self.sp_model.vocab_size()
self.bos_id: int = self.sp_model.bos_id()
self.eos_id: int = self.sp_model.eos_id()
self.pad_id: int = self.sp_model.pad_id()

n_words: 模型的词汇表大小(即分词器中包含多少个词元)。 bos_id: 句子开头的特殊标记 BOS 的 ID。 eos_id: 句子结尾的特殊标记 EOS 的 ID。 pad_id: 填充标记 PAD 的 ID。
assert self.sp_model.vocab_size() == self.sp_model.get_piece_size()

通过断言确保 vocab_size()get_piece_size() 的结果一致,验证分词器的词汇表大小是正确的。

2.encode方法

encode 方法用于将输入字符串编码为词元ID列表,同时可以选择是否在结果中添加 BOSEOS 标记。

s: 要编码的字符串。

bos: 是否在词元列表开头添加 BOS 标记。

eos: 是否在词元列表末尾添加 EOS 标记。

assert type(s) is str
t = self.sp_model.encode(s)

首先,检查 s 是否为字符串类型。

使用 SentencePieceencode 方法将字符串编码为词元ID列表。

if bos:

t = [self.bos_id] + t

if eos:

t = t + [self.eos_id]

如果 bos 参数为真,则在词元列表开头插入 BOS 标记。 如果 eos 参数为真,则在词元列表末尾添加 EOS 标记。

最后,返回处理后的词元ID列表。

3.decode方法

decode 方法用于将词元ID列表解码回原始字符串。

t: 词元ID的列表。

return self.sp_model.decode(t)

直接使用 SentencePiecedecode 方法将 ID 列表解码回对应的文本字符串。

2.数据预处理

详细代码的分析,具体代码见github。

将文本数据转换为模型能够理解的数字序列

该代码实现了分词数据的预处理、加载和批处理,适用于大规模语言模型的训练任务。它主要分为以下几部分:

1. 分片处理函数 process_shard 和预处理函数 pretokenize

这两个函数负责将文本数据进行分词处理并保存为二进制文件。尽管函数体未展示出来,但从函数签名可以推测:

process_shard 负责处理单个数据分片,将其中的文本数据分词并存储为 .bin 文件。 pretokenize 负责对所有分片进行批量处理,调用 process_shard 对每个分片进行分词预处理。

2. PretokDataset

该类继承自 torch.utils.data.IterableDataset,用于从磁盘加载预处理好的分词数据并将其提供给 PyTorch 模型训练。

核心功能:

初始化数据集:通过 __init__ 方法,指定数据集的划分方式(训练集或测试集)、最大序列长度、词汇表大小以及词汇表来源(如 Llama2 或自定义词汇表)。

加载分片文件:在 __iter__ 方法中:

根据 vocab_source 决定要加载的分片文件路径。 对于训练集,加载所有分片;对于测试集,加载第一个分片。

批量生成:通过 memmap 方式读取二进制文件中的数据,确保大文件可以从磁盘加载而不完全占用内存。每个分片文件被分割为多个批次,max_seq_len 决定了每个批次的长度。

模型输入 x 是当前批次的前 max_seq_len 个词元。 模型输出 y 是对应下一个词元,用于构建语言模型的监督学习任务。
其他功能:

随机性:通过 worker_inforank 实现数据的并行加载和分布式训练。在数据加载过程中,基于 worker_idrank 创建随机数种子,确保不同进程、线程之间的数据处理是唯一的,不会重复。

数据生成:__iter__ 方法生成 (x, y) 对,供 PyTorch 模型训练。

3. Task

Task 类封装了数据集的批处理流程,并为外部调用提供了一个静态方法 iter_batches,用于迭代生成批次数据。

核心功能:
批量加载数据:使用 torch.utils.data.DataLoader 创建批次迭代器,将 PretokDataset 的输出打包为固定大小的批次。 数据迁移到设备:在每个批次中,将数据移动到指定的计算设备(如 GPU),并使用 non_blocking=True 以加快数据拷贝的速度。
其他功能:
支持并行加载:可以通过 num_workers 参数指定数据加载器的并行线程数,以加速数据加载过程。

3: 训练模型

这个 generate 方法实现了一个基于语言模型的文本生成过程,逐步生成新 token 并将其附加到现有序列 idx 中。该方法可以通过多次前向传播和采样来生成新序列,使用了温度和 top_k 策略来控制生成过程。

关键流程解释:

初始输入 idx

idx 是形状为 (bz, seq_len) 的长整型张量,表示输入序列。 bz 是 batch size,seq_len 是序列的长度。

序列长度控制:

当输入序列长度超过模型的最大序列长度 self.args.max_seq_len 时,序列会被截断,只保留最后的 max_seq_len 个 token(即上下文)。 这保证了模型的输入不会超过它的上下文窗口大小。

前向传播:

对于当前输入序列 idx_cond,模型进行前向传播,输出预测 logits。logits[:, -1, :] 表示我们只取最后一个 token 的输出,因为这是当前生成 token 的预测概率分布。

生成新 token:

确定性采样(当 temperature == 0.0 时):直接选择概率最高的下一个 token(torch.topk(logits, k=1))。 随机采样(当 temperature > 0 时):先将 logits 按 temperature 缩放,然后进行采样。temperature 值越高,生成的文本越随机;值越低,生成的文本越确定。 top_k 采样:如果指定了 top_k,只从 top k 个最可能的 token 中进行采样,这可以减少生成的随机性,避免模型生成一些不合理的 token。 Softmax 和 Multinomial:F.softmax 将 logits 转换为概率分布,torch.multinomial 根据该分布采样出下一个 token。

更新序列:

将新生成的 token(idx_next)附加到现有序列 idx 后面,并继续迭代直到生成所需的 token 数量。

返回生成的序列:

最终返回更新后的 idx,它包含初始输入序列和新生成的 token。

参数:

idx: 输入的 token 序列,形状为 (batch_size, seq_len)max_new_tokens: 要生成的 token 数量。 temperature: 控制采样随机性的参数,值越低生成越确定,越高生成越随机。 top_k: 限制从 top k 个 token 中进行采样,值较低时可提高生成质量。

代码示例的逻辑流:

每次生成时,输入序列 idx 截取为最大允许的长度。 通过模型获取序列最后一个位置的 logits。 根据温度和 top_k 参数选择下一个 token。 将新生成的 token 加入到序列,重复生成直到达到 max_new_tokens

这个方法主要用于简单的文本生成任务,适合在推理模式下运行。

4: 使用模型生成文本

在模型训练完成后,会在output目录下生成一个ckpt.pt文件,这个文件就是我们训练好的模型。我们可以使用以下命令生成文本。

python sample.py --prompt "One day, Lily met a Shoggoth"

sample.py代码详解:

    class TextGenerator:
    def __init__(self, 
                 checkpoint='output/ckpt.pt',  # 模型检查点路径
                 tokenizer_model_path='tok4096.model',  # 分词器模型路径
                 seed=1337,  # 随机种子,确保可重复性
                 device=None,  # 设备,优先使用 CUDA,如果没有可用的 CUDA,则使用 CPU
                 dtype="float32"):  # 数据类型,默认为 float32,可以选择 float16 或 bfloat16
        """
        初始化 TextGenerator 类,加载模型、设置设备和分词器等。
        """

	#模型加载配置

​        self.checkpoint = checkpoint  # 保存的模型检查点路径
​        self.tokenizer_model_path = tokenizer_model_path  # 分词器模型文件路径
​        self.seed = seed  # 随机数种子,用于生成的可重复性
​        self.device = device or ('cuda' if torch.cuda.is_available() else 'cpu')  # 根据硬件条件选择设备
​        self.dtype = dtype  # 模型的浮点数类型
​        self.device_type = 'cuda' if 'cuda' in self.device else 'cpu'  # 判断当前设备是否为 CUDA
    # 设置随机种子,确保生成的可重复性
    torch.manual_seed(seed)  # 设置 CPU 随机种子
    torch.cuda.manual_seed(seed)  # 设置 CUDA 随机种子
    torch.backends.cuda.matmul.allow_tf32 = True  # 允许 CUDA 使用 TF32 精度进行矩阵乘法运算
    torch.backends.cudnn.allow_tf32 = True  # 允许 cuDNN 使用 TF32 精度加速
    
    # 根据 dtype 选择适当的自动混合精度上下文
    ptdtype = {'float32': torch.float32, 'bfloat16': torch.bfloat16, 'float16': torch.float16}[self.dtype]
    self.ctx = nullcontext() if self.device_type == 'cpu' else torch.amp.autocast(device_type=self.device_type, dtype=ptdtype)
    
    # 加载模型检查点文件
    checkpoint_dict = torch.load(self.checkpoint, map_location=self.device)  # 加载模型参数
    gptconf = ModelArgs(**checkpoint_dict['model_args'])  # 初始化模型参数
    self.model = Transformer(gptconf)  # 实例化 Transformer 模型
    state_dict = checkpoint_dict['model']  # 获取模型状态字典
    
    # 去除状态字典中的不必要前缀
    unwanted_prefix = '_orig_mod.'  # 这个前缀在保存时可能被添加,现在要去除它
    for k, v in list(state_dict.items()):
        if k.startswith(unwanted_prefix):
            state_dict[k[len(unwanted_prefix):]] = state_dict.pop(k)  # 去除不必要的前缀
    
    # 加载模型参数到模型中
    self.model.load_state_dict(state_dict, strict=False)
    # 计算模型参数量
    num_params = sum(p.numel() for p in self.model.parameters() if p.requires_grad)
    print(f"Model has {num_params} parameters.")
    # 设置模型为评估模式(evaluation mode),防止训练模式下的 dropout 等操作影响结果
    self.model.eval()
    # 将模型放置到正确的设备上(GPU 或 CPU)
    self.model.to(self.device)
    # 初始化分词器
    self.tokenizer = Tokenizer(tokenizer_model=self.tokenizer_model_path)  # 根据指定的路径加载分词器

def sample(self, 
           start="Hello!",  # 生成文本的起始提示词,可以是任意字符串
           num_samples=3,  # 生成样本的数量,默认生成 3 个样本
           max_new_tokens=256,  # 每个样本生成的最大 token 数,默认最多生成 256 个 token
           temperature=1.0,  # 控制生成的随机性,1.0 为标准,值越大越随机
           top_k=300):  # 保留概率最高的 top_k 个 token,限制生成时的选择范围
    """
    根据给定的起始文本生成样本。
    
    :param start: 生成文本的起始提示词
    :param num_samples: 要生成的文本样本数
    :param max_new_tokens: 每个样本生成的最大 token 数
    :param temperature: 控制生成的随机性,值越小生成越确定,值越大生成越随机
    :param top_k: 限制生成时选择的 token 范围
    :return: 生成的文本样本列表
    """
    # 如果 start 是以 'FILE:' 开头,表示从文件中读取起始文本
    if start.startswith('FILE:'):
        with open(start[5:], 'r', encoding='utf-8') as f:
            start = f.read()  # 读取文件内容作为起始文本
    
    # 将起始文本编码为 token id 序列
    start_ids = self.tokenizer.encode(start, bos=True, eos=False)  # bos=True 表示加上句首标记,eos=False 表示不加句尾标记
    x = (torch.tensor(start_ids, dtype=torch.long, device=self.device)[None, ...])  # 将编码后的 token id 转为 PyTorch 张量
    
    generated_texts = []  # 用于保存生成的文本样本
    with torch.no_grad():  # 禁用梯度计算,提升效率
        with self.ctx:  # 进入自动混合精度的上下文(如果是 GPU 并使用 float16 时)
            for k in range(num_samples):  # 循环生成指定数量的样本
                y = self.model.generate(x, max_new_tokens, temperature=temperature, top_k=top_k)  # 生成文本
                generated_texts.append(self.tokenizer.decode(y[0].tolist()))  # 解码生成的 token 序列为可读文本
    
    return generated_texts  # 返回生成的文本样本

实现了一个 TextGenerator 类,用于加载预训练语言模型,并根据输入提示生成文本样本。下面是对各个部分的详细解释:

1. 初始化 (__init__ 方法)

__init__ 方法用于加载模型、设置设备(如 CPU 或 CUDA)、配置分词器和初始化随机种子,以确保生成的文本可重复。

参数:
checkpoint: 模型的检查点文件路径,包含模型的权重和配置。 tokenizer_model_path: 分词器模型的路径,负责将文本转化为 token 序列。 seed: 随机种子,确保每次生成的文本可重复。 device: 选择使用的计算设备,优先选择 CUDA,如果没有可用的 CUDA,则使用 CPU。 dtype: 数据类型,默认为 float32,也可以选择 float16bfloat16 来加速计算。
主要步骤:
设置随机种子:通过 torch.manual_seedtorch.cuda.manual_seed 确保 CPU 和 CUDA 设备上的随机性是可控的。 设备配置:如果可用,则使用 CUDA,否则使用 CPU;设置自动混合精度(torch.amp.autocast)来提高生成效率。 加载模型检查点:从指定的 checkpoint 路径加载预训练模型,去除状态字典中的不必要前缀(如 _orig_mod.),然后将模型权重加载到模型中。 评估模式:设置模型为评估模式(self.model.eval()),禁用掉训练过程中使用的 dropout 等机制,以确保生成结果的稳定性。 分词器初始化:加载分词器,用于将文本转化为 token,并将生成的 token 序列解码回可读文本。

2. 生成文本 (sample 方法)

sample 方法负责根据给定的起始文本生成指定数量的样本,返回的是生成的文本列表。

参数:
start: 生成文本的起始提示词,可以是任意字符串,也可以是文件路径(以 FILE: 开头)。 num_samples: 要生成的文本样本数量,默认生成 3 个样本。 max_new_tokens: 每个样本生成的最大 token 数量,默认值为 256。 temperature: 控制生成文本的随机性,值越高越随机;值越低生成的文本越接近模型的确定性预测。 top_k: 限制生成时的选择范围,只保留概率最高的 top k 个 token,这样可以避免生成低概率但可能不合理的 token。
主要步骤:
文件输入支持:如果 start 参数以 FILE: 开头,表示从指定的文件中读取起始文本作为生成提示词。 分词器编码:将起始文本编码为 token id 序列,添加句首标记(bos=True),生成的序列作为模型的初始输入。 生成样本:在禁用梯度计算(torch.no_grad())的上下文中,使用模型的 generate 方法生成指定数量的文本样本。 解码:将生成的 token 序列通过分词器解码成可读的文本,并保存到列表中。

3. 模型生成逻辑

sample 方法中,调用 self.model.generate 执行生成逻辑。该方法是之前定义的,用于根据给定的输入序列逐步生成新 token,并支持多种采样策略(如 temperature 和 top_k 采样)。生成的 token 序列通过分词器解码回文本后返回。

总结

更新时间 2024-09-28