首页 / 文章 / 实用笔记:构建大语言模型,第一部分——训练:预测下一个标记

实用笔记:构建大语言模型,第一部分——训练:预测下一个标记

《实用笔记:构建大语言模型》第一部分——训练:预测下一个标记的实操指南,包含适用于采用该模式的团队的相关契约、检查项以及可直接使用的代码模板。

3057 词

可将此内容作为《构建大语言模型,第一部分——训练:预测下一个标记》中理念的面向操作员的简化版本:清晰的阶段划分、有序的代码模块,以及便于交接时参考的恢复说明。 将“概览”阶段视为可量化的界面使用效果最佳。在扩大范围之前,先记录一份理想的测试案例、一个故障实例以及回滚说明。 在功能结果旁同时记录耗时以及标记或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外账单。

为何先选择视觉变换器?

对于视觉Transformer阶段,应在修改代码之前明确输入内容、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储和功能标志应集中存放于一个位置,以便操作人员无需查看整个流程即可进行审核。 当下一步是代码或工具调用时,优先使用具有模式验证的结构化输出,而非自由形式的文本。

核心理念:预测下一个标记

在进入“单一思路预测阶段”时,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的功能。 当下一步操作为代码编写或工具调用时,应优先使用具有结构化格式且经过模式验证的输出,而非自由形式的文字描述。

logits, _ = model(ids)                     # ids = the tokens so far, shape (1, T)
probs = F.softmax(logits[0, -1], dim=-1)   # one probability per vocabulary token

文本 → 令牌:为何我们必须自行开发分词器

对于文本处理相关的任务,为何要在修改代码之前先确定阶段划分、输入参数、该步骤的负责人以及结束标准?这样操作人员就可以从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 应优先选择小型、易于测试的单元,而非冗长的脚本。当某个步骤失败时,故障应能指向具体的责任方,而非整个复杂的流程。 如果后续步骤是代码执行或工具调用,应优先使用具有结构化格式且经过模式验证的输出,而非自由形式的文字描述。 对于文本处理相关的任务,为何要在修改代码之前先确定阶段划分、输入参数、该步骤的负责人以及结束标准?这样操作人员就可以从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 除了功能结果外,还应记录处理时间以及Token或查询成本。提前了解成本情况,可避免在流程从演示环境转向共享环境时出现意外的费用支出。

import glob
from tokenizers import Tokenizer, models, trainers, pre_tokenizers
from llm_transformer.prep_tales import strip_gutenberg, RAW_DIR

# read every raw .txt book and strip its Project Gutenberg header/footer,
# leaving just the story text - one big string per book
tales_texts = [strip_gutenberg(open(p, encoding="utf-8", errors="ignore").read())
               for p in sorted(glob.glob(f"{RAW_DIR}/*.txt"))]   # 12 books, ~809k words
tok = Tokenizer(models.BPE())
tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)   # start from bytes
trainer = trainers.BpeTrainer(vocab_size=8192, special_tokens=["<|endoftext|>"],
                              initial_alphabet=pre_tokenizers.ByteLevel.alphabet())
tok.train_from_iterator(tales_texts, trainer)   # learn the 7,935 merges
tok.save("tales_bpe.json")
tok = Tokenizer.from_file("tales_bpe.json")   # reload the trained tokenizer
ids = tok.encode("Once upon a time").ids      # -> [412, 987, 15, 733]   (integers)
idx = torch.tensor(ids).unsqueeze(0)          # (1, 4) — a batch of one sequence

vocab_size = tok.get_vocab_size()             # 8192 - sets the NUMBER OF ROWS below

令牌→向量:查找而非计算

在处理令牌向量的查找阶段时,首先明确相关规范:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续代码修改的规范性。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放,以便操作人员无需查看整个系统结构即可进行审计。 缓存稳定的系统指令和工具架构。重复发送相同的开头信息是导致资源浪费的常见原因。

self.token_embed = nn.Embedding(vocab_size, n_embed)   # the 8,192 x 256 table
# .weight IS a learnable (8192, 256) parameter tensor — 2.1M weights, trained by backprop;
# each id's row is nudged only on steps where that token appears in the batch
...
tok_emb = self.token_embed(idx)     # idx (B, T)  ->  vectors (B, T, 256), one row per id

例外情况:因果掩码

在处理“异常与因果阶段”时,首先列出相关约定:所需输入、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 同时记录正常流程和恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续的优化工作。 缓存稳定的系统指令和工具架构。重复发送相同的报文头是导致资源浪费的常见原因。

# tril = torch.tril(torch.ones(T, T))  — a lower-triangular matrix of 1s, made once
attn = q @ k.transpose(-2, -1) / math.sqrt(head_size)          # scores, (B, T, T)
attn = attn.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # blank out the future
attn = F.softmax(attn, dim=-1)                                  # -inf -> weight 0
out  = attn @ v

一次处理,同时涵盖所有场景

在逐个处理每个上下文阶段时,首先需明确规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 优先选择小型、可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 缓存稳定的系统指令和工具结构。重复发送相同的开头信息是导致资源浪费的常见原因。 在逐个处理每个上下文阶段时,首先需明确规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

单次运行内部:attn @ V实际计算的内容

将单次运行中的各个阶段视为可测量的指标时,哪些阶段的效果最佳?在扩大范围之前,先记录一份理想的运行结果、一个失败案例以及回滚说明。 将配置置于应用程序代码之外。环境文件、密钥存储和功能标志应集中存放,这样操作人员无需查看整个系统结构即可进行审计。 为每轮对话和每次会话设定令牌预算。智能工具往往会过度扩展上下文;设置上限可避免演示过程变成意外的费用账单。

一次处理,收获多多:T预测值与T损失值

将“一次通过、多次学习”阶段视为可度量的指标时效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。同时记录正常流程和恢复流程的文档。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的内容。为每轮对话和每次会话设定token预算。智能工具会大量消耗上下文资源,设置上限可避免演示过程变成意外的费用账单。

logits = self.lm_head(x)                       # (B, T, vocab)
B, T, C = logits.shape
loss = F.cross_entropy(logits.reshape(B*T, C), # every position ...
                       targets.reshape(B*T))   # ... vs the token that actually followed

每一次失败都对应一种词汇分类(类似于ViT,但有8,192个类别)

“Each loss is a stage”这一理念在被视为可度量的对象时效果最佳。在扩大范围之前,先记录一份理想的操作日志、一个故障案例以及回滚说明。 相较于庞大的脚本,应优先选择小型且可测试的单元。当某一步骤出现故障时,故障点应指向单一责任主体,而非复杂的流程链。 需为每轮对话和每次会话设定令牌预算。智能工具往往会大量消耗上下文资源,设置上限可避免演示过程变成意外的费用账单。 “Each loss is a stage”这一理念在被视为可度量的对象时效果最佳。在扩大范围之前,先记录一份理想的操作日志、一个故障案例以及回滚说明。 除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,就能在流程从演示环境转向共享环境时避免意外收费。

批处理的构建方式——及其单一故障的形成过程

在修改代码之前,需先明确批处理的执行阶段、输入参数、各步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能开关都应集中存放,这样操作人员只需查看这些内容即可,无需阅读整个系统架构。 当后续步骤为代码执行或工具调用时,宜使用具有结构化格式且经过模式验证的输出,而非自由形式的文本。

训练:观察损失值下降的过程

在训练过程中的损失监控阶段,修改代码之前需先明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误处理都属于产品功能的一部分,而非后续需要补充的内容。 当下一步操作为编写代码或调用工具时,应优先使用具有结构化格式且经过模式验证的输出,而非自由形式的文字描述。

for step in range(train_steps):
    xb, yb = get_batch(train_data)     # random (context, next-token) windows
    _, loss = model(xb, yb)            # forward: T predictions -> one mean loss
    opt.zero_grad(); loss.backward()   # backprop the loss into all 9M parameters
    opt.step()                         # nudge them downhill

我们已实现的功能——以及未来的规划

在修改代码之前,对于“我们构建了什么”以及相应阶段,需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 相较于冗长的脚本,更应采用小型、可测试的单元。当某个步骤失败时,故障原因应能指向单一责任主体,而非复杂的流程链。 如果后续步骤是代码调用或工具调用,相比自由形式的文字描述,带有架构验证的结构化输出更为合适。 在修改代码之前,对于“我们构建了什么”以及相应阶段,需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在流程从演示环境转向共享环境时出现意外费用。

操作检查清单

在处理操作检查清单阶段时,首先写下相关契约:所需的输入参数、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。

将此阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功判定标准,杜绝默许的部分完成情况。

缓存系统中稳定的指令和工具结构。重复发送相同的开头信息是导致资源浪费的常见原因。

尽量降低渲染工作的成本,只有在经过测量后才会考虑通过缓存来处理耗时的计算任务。过早使用缓存可能会掩盖过时属性带来的错误。

在预算允许的情况下,利用测试环境而非真实的付费 API,在持续集成过程中添加能够检测关键路径的冒烟测试。

请同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及死信处理都是产品不可或缺的部分,而非后续需要补充的功能。

在推广该技术栈之前,应先冻结版本,为关键流程保存标准日志记录,并明确回滚步骤。共享环境需要设置速率限制、进行租户身份验证,同时要指定专人负责密钥轮换工作。与其展示花哨的一次性演示,不如追求扎实可靠的性能。

关于44c72473e486的批量处理说明:请勿将提供商密钥放入代码仓库,应为每个会话设置令牌使用上限,并将日志记录与评估用配置文件存放在同一位置,以便后续模型更换时仍能保持数据可比性。

在强化措施的第0阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新执行该步骤,而无需猜测隐藏状态。应将此阶段视为输入与验证后输出之间的契约:为相关成果命名,定义成功判定标准,并拒绝默许部分完成的情况。

强化措施细节0/729:需记录该措施的耗时、错误类型以及令牌消耗情况,然后依据固定的评估标准而非主观经验来决定是否保留该变更。

在处理强化措施的第一阶段时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合规范。 应将配置信息与应用程序代码分开。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。

强化措施细节 1/729:针对该措施记录运行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

将强化措施的第二阶段视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份理想的操作流程、一个失败案例以及回滚说明。 相比复杂的脚本,更应采用小型且可测试的单元。当某个步骤出现故障时,故障原因应能明确指向某个具体的责任模块,而非整个混乱的流程。

强化措施细节2/729:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在强化措施的第3阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录运行时间以及代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

强化措施细节3/729:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在处理强化措施的第4阶段时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合要求。

同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续才添加的完善措施。

强化措施细节4/729:需测量该阶段的执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

将强化措施的第5阶段视为可度量的工作面会更为有效。在扩大范围之前,先记录一份理想的操作日志、一个故障案例以及回滚说明。

应将此阶段视为输入参数与验证后输出结果之间的契约。为相关文档命名,明确成功判定标准,杜绝无声的半完成状态。

强化措施细节5/729:记录该条注解的运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

对于强化措施的第6阶段,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。配置应置于应用程序代码之外,环境文件、密钥存储以及功能标志应集中存放于一个操作人员可以审核的位置,无需查看整个系统结构。

强化措施细节6/729:记录该条注解的运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在处理强化措施的第7阶段时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合预期。 相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障应能指向单一的责任模块,而非复杂的流程链。

强化措施细节7/729:需测量该步骤的运行时间、错误类型以及代币消耗情况,然后依据固定的评估标准而非主观判断来决定是否保留该修改。

将强化措施的第8阶段视为可量化的目标面来处理效果最佳。在扩大范围之前,先记录一份理想的运行示例、一个失败案例以及回滚说明。 在功能结果旁同时记录时间消耗及代币或查询成本。提前明确成本情况,可避免在从演示环境过渡到共享环境时出现意外支出。

强化措施细节 8/729:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个别案例来决定是否保留该变更。

在强化措施的第9阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。需同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理方式都是产品的一部分,而非后续需要补充的内容。

强化措施细节 9/729:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个别案例来决定是否保留该变更。

在处理强化措施的第10阶段时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合约定。 将这一阶段视为输入与验证后输出之间的契约。为相关产物命名,明确成功判定标准,杜绝默许部分完成的情况。

强化措施细节10/729:需记录该阶段的执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

将强化措施的第11阶段视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份标准操作示例、一个失败案例以及回滚说明。 应将配置信息与应用程序代码分开。环境文件、密钥存储和功能开关应集中存放于一处,以便操作人员无需查看全部代码结构即可进行审计。

强化细节 11/729:测量该笔记的处理时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个别案例来决定是否保留该变更。