Transformers 杂碎知识点

  • 训练过程中更新的步骤

    • 首先以某种方式计算出epoch,每个epoch包含steps_in_epoch
      • epoch是整个数据集被遍历的次数,有多少个epoch,数据集中的每个样本就会被遍历多少次
    • 然后计算出total_updates,表示参数更新次数
    • 在每一次参数更新的时候,算出来此次参数更新所涉及的大batch的数量num_batches
    • 根据num_batches取出batch_samples(一个列表,每个元素是一个小batch),并计算出所有进程的大batch中有效位置的数量num_items_in_batch的和(也就是不为-100的id的数量)
    • 接着取出batch_size个样本(一个小batch)进行损失计算
      • 传递给training_step的是一个小batch数据inputsnum_items_in_batch
        • 将同样的参数传递给compute_loss
          • 模型利用前向传播计算出outputs,其中的outputs["loss"]代表的是inputs中所有有效位置的交叉熵的损失之和除以num_items_in_batch,大约等于一个位置的交叉熵大小(个位数)除以self.current_gradient_accumulation_steps(也就是小batch的数量)再除以进程数
          • 函数最后返回的loss等于outputs["loss"]乘以进程数,大约等于一个位置的交叉熵大小(个位数)除以self.current_gradient_accumulation_steps
        • 然后利用loss执行反向传播累计梯度
          • 单次累积的梯度就相当于一个位置的交叉熵大小(个位数)除以self.current_gradient_accumulation_steps的损失产生的梯度
          • DDP框架知道会累积多少次,然后会自动在最后一次累积的时候对所有进程进行平均。比如进程0算的梯度是1,进程1算的梯度是2,进程2算的梯度是1.5,那么三个进程的梯度都会被统一为1.5,从而三个进程上的模型的权重更新一模一样
          • 在准备进行参数更新之前,每个进程上累积的梯度就相当于一个token的交叉熵所产生的梯度了
    • (不严谨地)每gradient_accumulation_steps步进行一次更新参数并打日志
      • 参数更新使用self.optimizer.step()
      • 打日志调用_maybe_log_save_evaluate
        • tr_loss是当前进程计算出的损失,大约等于一个token产生的交叉熵
        • tr_loss_scalar对所有进程计算的损失求平均,大约等于一个token产生的交叉熵
        • 日志显示的损失是一个位置的平均损失(也就是logs["loss"]
          • self.state.global_step - self._globalstep_last_logged一般为1
          • 最后日志以及画图是按照self.state.log_history的每个元素为单位的
      • 之所以说“不严谨地”,是因为最后不足gradient_accumulation_steps步的时候,会在最后一步进行特殊判断更新;此时current_gradient_accumulation_steps就不等于gradient_accumulation_steps,而是等于最后两次更新之间进行计算的批次数目(应该也就是self.current_gradient_accumulation_steps

    image

  • 任何时候都不要直接去修改Transformers库(除了打印调试语句)

    • 如果想要在某个函数里面实现新功能,最好能够写一个子类
  • Transformers的trainer.py中,有一个参数叫做``

  • 输出文件

    • adapter_config.json:PEFT 适配器(例如 LoRA 或类似方法)的配置文件
    • adapter_model.safetensors:PEFT 适配器的权重
    • added_tokens.json:列出了在训练或预处理期间添加到分词器原始词汇表之外的任何令牌
    • all_results.json:合并了所有阶段(训练、评估以及可能的预测)的指标。它聚合了来自 compute_metrics 的结果,如损失、准确率或自定义指标
      • 这里面的train_loss不是最后时刻的训练损失,而是整个过程训练损失的平均,所以肯定比最后时刻的损失更高
    • chat_template.jinja:用于在分词器中格式化聊天或多轮对话的 Jinja2 模板文件。它定义了用户/助手消息的结构
    • eval_results.json:包含来自验证数据集的评估指标
    • merges.txt:包含字节对编码(BPE)或类似子词分词器合并规则的文本文件
    • README.md:用作模型卡片或文档。它可能包含关于模型、训练设置、使用说明或限制的详细信息
    • special_tokens_map.json:映射特殊令牌(例如,[PAD], [EOS], [BOS])到它们的令牌 ID 或字符串
    • tokenizer_config.json:供 Hugging Face 的 Transformers 库在使用 AutoTokenizer.from_pretrained() 或 PreTrainedTokenizer.from_pretrained() 等方法加载分词器时,用于正确实例化和配置分词器
    • train_results.json:包含训练指标
    • trainer_log.jsonl:记录训练事件,例如每步的损失、学习率或警告
    • trainer_state.json:捕获 Trainer 的内部状态,包括全局步数、当前周期、日志历史(例如,每步的损失)、最佳指标(如果使用早停)以及恢复信息
    • training_args.bin:序列化训练所用 TrainingArguments 对象的二进制文件。它存储所有超参数(例如,学习率、批大小、周期数、DeepSpeed 配置)
    • training_loss.png:绘制损失曲线
    • vocab.json:包含分词器的词汇表,将单词/子词映射到索引
    • 检查点中的文件
      • 如果有文件与上面提到的文件同名,那么就是代表当前这个检查点下的对应的文件
      • latest:文本文件,指示 DeepSpeed 检查点中的最新或当前状态
      • rng_state_x.pth:PyTorch .pth 文件,保存每个进程(例如,CPU/GPU)的随机数生成器(RNG)状态。确保分布式训练中的可复现性
      • scheduler.pt:PyTorch .pt 文件,保存学习率调度器的状态(例如,上一个周期、基础学习率)。允许从精确点恢复调度器
      • zero_to_fp32.py:由 DeepSpeed 提供的 Python 脚本,用于将 ZeRO 优化的检查点(该检查点将优化器状态分片存储在多个 GPU 上以提高内存效率)转换回完整的 FP32(32位浮点)模型状态字典。在训练后用于将分片文件合并为单个可用的模型文件。从检查点目录运行它
      • bf16_zero_pp_rank_x_mp_rank_xx_optim_states:存储了模型和优化器的分片(分区)状态,以节省内存并支持跨多个 GPU 的高效恢复训练
      • zero_pp_rank_x_mp_rank_xx_model_states.pt:包含模型权重的本地分片(不包括优化器)。此处没有 bf16_ 前缀,表明模型状态以默认精度(可能为 FP32 或按配置)保存,而优化器使用 BF16
  • Transformers的Trainer类包含了从检查点恢复和保存模型权重的逻辑

    • 是先加载模型并初始化权重,再在trainer.train(resume_from_checkpoint=training_args.resume_from_checkpoint)中对模型进行包装和断点续传
      • 断点续传会覆盖掉初始化权重
    • 如果有新增加的组件,只要是model = load_model(tokenizer, model_args, finetuning_args, training_args.do_train)model的属性,那么就会自动被注册为参数训练,并且保存和加载的逻辑也是自动的,不用进行额外修改
    • 拓展了词汇表的话,Trainer类是可以自动保存的,但是新词的嵌入向量会保存到adapter_model.safetensors里面
  • 新添加token的时候:

    • 首先调用tokenizer.add_tokens将新token添加进词表里面
    • 然后调用resize_embedding_layer将嵌入层和语言头进行扩展
      • 这个函数不一定有作用
        • 比如说Qwen的tokenizer的长度是151669,而vocab_size的长度是151972
        • 此时条件if len(tokenizer) > current_embedding_size:就不会满足,于是直接返回
        • 多出来的长度是为了加快并行计算,因为151972被128整除
        • 于是新添加进入的token就会直接继承多出来的位置的权重
        • 这些权重就是最开始训练Qwen的时候的权重,因为这些位置是多出来的,在训练过程中不会有梯度,所以这些位置的权重不会变化
          • 没有找到最开始的Qwen是如何初始化权重的
  • 学习率的变化过程:

    • 预热
      • 预热一定是线性的
      • 一定从0开始预热到learning_rate
    • 然后利用调度器衰减
      • 调度器的类型见/transformers/trainer_utils.py
      • 每个类型可以传递的参数见官方文档
        • 在yaml文件中传递的时候使用字典,比如lr_scheduler_kwargs: "{\"min_lr\": 2.0e-4}"
      • 最常用的是cosine,也就是学习率余弦衰减到0
  • 训练中,prompt的位置都是不会计算损失的,只有label的位置会计算损失

    • 对于多模态模型的训练,虽然多模态数据只会在prompt中,但是由于这是自回归训练,所以label中位置的损失的计算仍然会与多模态数据相关,也就是说反向传播的梯度可以传播到多模态相关组件上
  • 可以设置HF_HOME来设置整个 Hugging Face 生态系统的根目录

    • 默认~/.cache/huggingface
    • hub目录存放模型权重,分词器什么的
    • datasets存放数据集的缓存什么的

评估使用的监督和非监督那个不同,然后predict_with_generate控制;准确率类和相似度类;监督数据处理类和非监督数据处理类

posted @ 2025-10-30 09:43  最爱丁珰  阅读(27)  评论(0)    收藏  举报