Advertisement

第十二章:huggingface的resume训练的Demo

阅读量:

文章目录

引言
涉及huggingface预定义文件的相关名词
第一部分:huggingface预定义文件相关名词
1. huggingface预定义文件名称
- 1. huggingface预定义文件名称
2. 名称解析
3. 关键文件内容解析

  • 二、权重参数保存策略
    • 1、safetensors权重文件被保存

    • 2、scaler量化模型权重文件被保存

    • 3、优化器与学习率 scheduler参数配置被分别保存

    • 4、自定义状态管理组件的状态信息被持久化存储

    • 5、随机数生成器状态也被存储为独立文件

    • 6、完整的权重存储位置指向Huggingface官方仓库

    • 三、Resume的Demo

      • 1、Demo构建
      • 2、实现Resume方法
    • 总结


前言

该大模型主要依赖HuggingFace开源库进行开发,并基于其官方文档和相关资源进行基础部分的学习与实现。而对于较为基础的内容,则主要参考官方文档和相关博客资料。然而,在进一步利用开源大模型提升行业性能这一目标上却面临诸多挑战。该问题涉及对开源代码进行二次开发以开展实验测试工作。基于此,在本教程中将着重于对相关理论和技术进行深入解析,并详细阐述训练逻辑、权重管理、高效微调技术(如LoRA)、断点续训策略以及推理权重优化等技术要点。这些内容均通过与HuggingFace开源代码及示例项目的深度结合来进行具体实现。

之前的文章都涉及huggingface训练相关的源码解读工作。而本篇文章旨在提供相关权重参数、模型状态等数据存储的具体方法,并构建一个用于演示的Resume(或Project)框架。


一、huggingface预定义文件名词

随后我要解读与之相关的Resume内容,在此之前我会列出HuggingFace自带的文件名称,并说明它们的作用

1、huggingface预定义文件名称

来源huggingface的transformers.utils.init.py文件:

复制代码
    WEIGHTS_NAME = "pytorch_model.bin"
    WEIGHTS_INDEX_NAME = "pytorch_model.bin.index.json"
    ADAPTER_CONFIG_NAME = "adapter_config.json"
    ADAPTER_WEIGHTS_NAME = "adapter_model.bin"
    ADAPTER_SAFE_WEIGHTS_NAME = "adapter_model.safetensors"
    TF2_WEIGHTS_NAME = "tf_model.h5"
    TF2_WEIGHTS_INDEX_NAME = "tf_model.h5.index.json"
    TF_WEIGHTS_NAME = "model.ckpt"
    FLAX_WEIGHTS_NAME = "flax_model.msgpack"
    FLAX_WEIGHTS_INDEX_NAME = "flax_model.msgpack.index.json"
    SAFE_WEIGHTS_NAME = "model.safetensors"
    SAFE_WEIGHTS_INDEX_NAME = "model.safetensors.index.json"
    CONFIG_NAME = "config.json"
    FEATURE_EXTRACTOR_NAME = "preprocessor_config.json"
    IMAGE_PROCESSOR_NAME = FEATURE_EXTRACTOR_NAME
    GENERATION_CONFIG_NAME = "generation_config.json"
    MODEL_CARD_NAME = "modelcard.json"

2、名称解释

复制代码
    这些参数是用于指定在使用 Hugging Face 库时保存和加载模型和配置文件时使用的文件名。以下是每个参数的详细解释:
    1. `WEIGHTS_NAME`: 保存模型权重的文件名。通常是一个二进制文件,包含了模型的所有参数。 "pytorch_model.bin"
    2. `WEIGHTS_INDEX_NAME`: 保存模型权重索引的文件名。也提供了有关权重文件的额外信息。"pytorch_model.bin.index.json"
    3. `ADAPTER_CONFIG_NAME`: 适配器模型的配置文件名。适配器是指在预训练模型上添加的额外层,用于进行特定任务的微调或迁移学习。"adapter_config.json"
    4. `ADAPTER_WEIGHTS_NAME`: 保存适配器模型权重的文件名。"adapter_model.bin"
    5. `ADAPTER_SAFE_WEIGHTS_NAME`: 保存适配器模型安全权重的文件名。 "adapter_model.safetensors"
    6. `TF2_WEIGHTS_NAME`: TensorFlow 2.x 格式的模型权重文件名。"tf_model.h5"
    7. `TF2_WEIGHTS_INDEX_NAME`: TensorFlow 2.x 格式的模型权重索引文件名。"tf_model.h5.index.json"
    8. `TF_WEIGHTS_NAME`: TensorFlow 1.x 或 2.x 兼容的模型权重文件名。"model.ckpt"
    9. `FLAX_WEIGHTS_NAME`: 使用 Flax 框架保存的模型权重文件名。Flax 是一个基于 JAX 的深度学习库。"flax_model.msgpack"
    10. `FLAX_WEIGHTS_INDEX_NAME`: Flax 模型权重索引文件名。"flax_model.msgpack.index.json"
    11. `SAFE_WEIGHTS_NAME`: 安全模型权重文件名。类似于 `ADAPTER_SAFE_WEIGHTS_NAME`,用于在某些情况下以安全格式保存模型权重。
    12. `SAFE_WEIGHTS_INDEX_NAME`: 安全模型权重索引文件名。"model.safetensors"
    13. `CONFIG_NAME`: 模型配置文件名。这个文件通常包含了模型的架构、超参数和其他配置信息。"config.json"
    14. `FEATURE_EXTRACTOR_NAME`: 特征提取器的配置文件名。特征提取器通常用于从原始数据中提取特征,例如文本的词嵌入。"preprocessor_config.json"
    15. `IMAGE_PROCESSOR_NAME`: 图像处理器的配置文件名。类似于 `FEATURE_EXTRACTOR_NAME`,用于处理图像数据的特征提取器。
    16. `GENERATION_CONFIG_NAME`: 生成模型的配置文件名。用于生成文本或其他类型的数据的模型的配置信息。"generation_config.json"
    17. `MODEL_CARD_NAME`: 模型卡片的文件名。模型卡片通常包含了模型的描述、使用方式、限制和其他相关信息,用于帮助用户了解和使用模型。"modelcard.json"
    这些参数提供了在 Hugging Face 库中加载和保存模型及其相关配置时所需的文件命名约定。

3、特定文件重点解释

adapter_model.bin文件在Hugging Face平台中常用于存储适配器模型相关的权重参数。这种适配器模型设计是一种高效的小型化模型扩展方案,在无需改动预训练架构的前提下即可实现特定任务的微调目的。该方法通过附加小型线性层(适配器)来学习特定任务所需的表示形式,并有效降低了额外引入参数的数量以及计算开销。其中包含的各种适配器模型实例均被系统地保存下来,并可供在实际应用中选择合适的配置对预训练模型进行针对性优化与微调操作。

在Hugging Face平台中,“adapter_model.safetensors”文件作为常见用途出现,专门用于存储适配器模型的安全张量(safe tensors)。这种安全张量技术是一种关键数据保护方法,在云端或其他不可信环境中处理敏感数据时尤为重要。通过严格的加密和其他安全防护措施,“adapter_model.safetensors”文件确保了所有数据在处理过程中的安全性与保密性。“adapter_model.safetensors”文件中的存储内容是经过严格的安全处理后的适配器模型张量集合,在必要时可以快速加载并加以利用以满足特定的数据处理需求。

二、参数决定权重保存

1、model.safetensors保存

通过TrainingArguments参数设置save_safetensors=True的方式进行模型持久化操作,并利用huggingface官方提供的_save()函数来执行持久化操作。具体而言,在完成训练后调用模型的save_pretrained()方法完成持久化操作。

复制代码
    # 定义训练参数
    training_args = TrainingArguments(
    output_dir='./out_dirs',
    	...
    save_safetensors=True,  # 默认为False
    
    )
    
    # state_dict可为None,safe_serialization=self.args.save_safetensors参数为True,output_dir为文件路径
    self.model.save_pretrained( output_dir, state_dict=state_dict, safe_serialization=self.args.save_safetensors)

在当前操作中,默认设置为False时会存储右侧图像;而若设置为True,则会将左侧图像存储起来。若设置save_safetensors为True,则会避免生成pytorch_model.bin文件。以隐藏的方式完成存储过程。

在这里插入图片描述

当然,我们也可以在之后使用如下方法保存。

复制代码
    # 开始训练
    trainer.train()
    trainer.save_pretrained( output_dir, state_dict=None, safe_serialization=True)

2、scaler.pt保存

在’BKPT: scaler.pt’的持久化过程中,必须将self.do_grad_scaling属性设置为True,因为这一属性的变化源于Trainer类的初始化配置.如下的代码段展示了具体的实现方式,通过调整其他相关参数来间接控制这一过程,以确保操作的有效性.

复制代码
    self.do_grad_scaling = False
    if (args.fp16 or args.bf16) and not (self.is_deepspeed_enabled or is_sagemaker_mp_enabled()):
    # deepspeed and SageMaker Model Parallel manage their own half precision
    if self.sharded_ddp is not None:
        if args.half_precision_backend == "cuda_amp":
            self.use_cuda_amp = True
            self.amp_dtype = torch.float16 if args.fp16 else torch.bfloat16
            #  bf16 does not need grad scaling
            self.do_grad_scaling = self.amp_dtype == torch.float16

在模型训练中,在满足以下两个条件时(即:当同时支持半精度计算(即args.fp16args.bf16)并且不支持深度专家引擎(即self.is_deepspeed_enabledis_sagemaker_mp_enabled())的情况下),可以将自定义参数self.do_grad_scaling 设为True。需要注意的是,在这种情况下,默认情况下该参数会被设为False。此外,在设置训练相关参数时(例如:是否使用自动混合精度训练、半精度计算类型等),这些参数如args.fp16args.bf16以及自定义的分布式训练配置(如:sharded_ddp)等信息均可以在下方的TrainingArguments中进行详细配置以实现预期的效果。

复制代码
    # 定义训练参数
    training_args = TrainingArguments(
    output_dir='./out_dirs',
    per_device_train_batch_size=3,
    ...
    learning_rate=2e-4,
    fp16=True
    
    
    )

基于此,在确定self.do_grad_scaling为True之后,在Trainer的def _save_checkpoint(self, model, trial, metrics=None):函数中即可完成scaler.pt文件的保存。如以下所示:

复制代码
    if self.do_grad_scaling:
    torch.save(self.scaler.state_dict(), os.path.join(output_dir, SCALER_NAME))

注:当存在self.scaler时,可以通过以下代码块调用以实现模型参数的持久化存储:

3、optimizer.pt与scheduler.pt保存

改写说明:对原文进行了以下修改:

  1. 将"得到"改为"设置为"
  2. "参数"改为"属性"
  3. "可参考我的博客"改为"参考我的博客"
  4. 调整了部分句子结构
  5. 保持了段落结构和数学公式
  6. 增加了一些描述性语言以使内容更丰富
复制代码
    torch.save(self.optimizer.state_dict(), os.path.join(output_dir, OPTIMIZER_NAME))
    
    torch.save(self.lr_scheduler.state_dict(), os.path.join(output_dir, SCHEDULER_NAME))

4、self.state状态保存(trainer_state.json)

通过self.args.should_save属性获取到的布尔值为True。需要注意的是,该参数位于TrainerControl类内。为了进一步了解相关内容,请参考我的博客此处。即可在Trainer类的该方法中完成模型和日志文件的持久化操作。例如,在以下代码片段中实现这一功能:

复制代码
    # Save the Trainer state
    if self.args.should_save:
    self.state.save_to_json(os.path.join(output_dir, TRAINER_STATE_NAME)) # TRAINER_STATE_NAME=trainer_state.json

此外,在讨论自变量state时(...),也可以参考博客此处

5、rng_state.pth保存

即可在当前环境中,在Trainer的def _save_checkpoint(self, model, trial, metrics=None):函数中完成将rng_state.pth存储为指定路径文件。

复制代码
    # Save RNG state in non-distributed training
     rng_states = {
     "python": random.getstate(),
     "numpy": np.random.get_state(),
     "cpu": torch.random.get_rng_state(),
     }
     if torch.cuda.is_available():
     if self.args.parallel_mode == ParallelMode.DISTRIBUTED:
         # In non distributed, we save the global CUDA RNG state (will take care of DataParallel)
         rng_states["cuda"] = torch.cuda.random.get_rng_state_all()
     else:
         rng_states["cuda"] = torch.cuda.random.get_rng_state()
    
     if is_torch_tpu_available():
     rng_states["xla"] = xm.get_rng_state()
    
     # A process can arrive here before the process 0 has a chance to save the model, in which case output_dir may
     # not yet exist.
     os.makedirs(output_dir, exist_ok=True)
    
     if self.args.world_size <= 1:
     torch.save(rng_states, os.path.join(output_dir, "rng_state.pth"))
     else:
     torch.save(rng_states, os.path.join(output_dir, f"rng_state_{self.args.process_index}.pth"))

6、权重相关保存位置(huggingface)

由Trainer内部的训练过程中的一个阶段...

复制代码
    def _inner_training_loop(
        self, batch_size=None, args=None, resume_from_checkpoint=None, trial=None, ignore_keys_for_eval=None
    ):
    		for epoch in range(epochs_trained, num_train_epochs): 
    			...
    			for step, inputs in enumerate(epoch_iterator):
    				 ...
    				 self._maybe_log_save_evaluate(tr_loss, model, trial, epoch, ignore_keys_for_eval) # 有条件的进入
    		 ...
    		 self._maybe_log_save_evaluate(tr_loss, model, trial, epoch, ignore_keys_for_eval)

三、Resume的Demo

1、Demo构建

我曾编写过基于HuggingFace框架的代码用于加载BERT模型,并在之前的项目中实现了类似的功能。在此基础上,我将详细讲解Resume方法。此外,在百度网盘上提供了BERT权重参数及完整的代码文件供参考。

链接如下:

链接:https://pan.baidu.com/s/1yEAc8Xm8mvsO-JCJDaqMbg
提取码:hgfc

我这里直接给出源码:

复制代码
    import os
    os.environ["CUDA_VISIBLE_DEVICES"] = "0"
    
    from torch.utils.data import Dataset
    
    import torch
    from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
    import random
    
    # 随机种子
    seed = 42
    random.seed(seed)
    torch.manual_seed(seed)
    
    # 示例数据,给更多数据便于常看数据是否继承
    
    train_data = [
    ["Work is my source of happiness", 1], ["I am full of joy in my work", 1], ["Work makes me feel satisfied and happy", 1],
    ["I feel pain in my work", 0], ["I detest work pressure", 0],["I love every workday", 1],  
    ["Work makes me feel utterly exhausted", 0], ["I am not interested in work", 0], ["Work makes me feel tired", 0],
    ["I am full of passion and enthusiasm for my work", 1], ["I like my job", 1], ["I really like my job", 1],
    ["I feel disappointed and discouraged about work", 0], ["I hate the repetitiveness of work", 0],
    ["Work leaves me physically and mentally exhausted", 0], ["Work makes me feel fulfilled and happy", 1], 
    ["I have no passion for work", 0], ["Work is my motivation", 1],["Work makes me feel stressed", 0], 
    ["I feel tired and negative about work", 0], ["I despise the daily repetitive tasks at work", 0], 
    ["I am full of love for my work", 1], ["Work is my life", 1], ["I am passionate about my work", 1],
    ["Work makes me feel depressed and hopeless", 0], ["I have lost interest and motivation in work", 0],
    ["I feel tired and resistant towards work", 0], ["Work makes me feel unbearable and intolerable exhaustion", 0], 
    ["I find work monotonous and dull", 0], ["Work leaves me physically and mentally exhausted and helpless", 0], 
    ["Work makes me feel satisfied", 1], ["I enjoy the work process", 1], ["Work makes me feel excited", 1], 
    ["I feel I have lost enthusiasm for work", 0], ["Work makes me feel utterly tired and bored", 0], 
    ["Work makes me feel valuable", 1], ["I love my job", 1], ["Work makes me feel happy", 1], ["I am immersed in my work", 1],  
    ["I am full of passion for my work", 1], ["Work makes me feel happy", 1], ["I do not like work", 0], 
    ["Work makes me feel proud", 1], ["Work is my passion", 1], ["I am full of enthusiasm for my work", 1],
    ["I am tired of work life", 0], ["I do not like going to work", 0], ["I am full of zeal for my work", 1]]
    
    train_texts = [d[0] for d in train_data]
    train_labels = [d[1] for d in train_data]  # 1代表正面,0代表负面
    
    # 加载预训练的Bert tokenizer
    tokenizer = BertTokenizer.from_pretrained("/project/huggingface_demo/bert-base-uncased")
    # 数据编码
    train_encodings = tokenizer(train_texts, truncation=True, padding=True)
    
    class SentimentDataset(torch.utils.data.Dataset):
    def __init__(self, encodings, labels):
        self.encodings = encodings
        self.labels = labels
    
    def __getitem__(self, idx):
        item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
        item['labels'] = torch.tensor(self.labels[idx])
        return item
    
    def __len__(self):
        return len(self.labels)
    
    # 创建数据集
    train_dataset = SentimentDataset(train_encodings, train_labels)
    
    # 加载预训练的Bert模型
    model = BertForSequenceClassification.from_pretrained("/project/huggingface_demo/bert-base-uncased", num_labels=2)  # 最好给绝对路径
    
    
    # 定义训练参数
    training_args = TrainingArguments(
    output_dir='./out_dirs',
    per_device_train_batch_size=3,
    per_device_eval_batch_size=3,
    num_train_epochs=2,
    logging_dir="./logs",
    report_to="none",
    gradient_accumulation_steps=2,  # 梯度迭代累计
    save_strategy ="steps", # 权重保存策略,默认no,不保存
    save_steps=2,
    save_total_limit = 10,
    learning_rate=2e-4,
    
    )
    
    # 定义Trainer对象
    trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
       
    )
    
    
    
    # 开始训练
    #trainer.train()
    
    trainer.train(resume_from_checkpoint=True)  # resume训练方法
    
    print('ok')
    
    # 以下这种方式也可以重载,但trainer.train(resume_from_checkpoint=True)已有方法
    # trainer.model = trainer.model.from_pretrained("/project/llava_module/huggingface_demo/out_dirs/checkpoint-80")
    # trainer.optimizer.load_state_dict(torch.load("/project/llava_module/huggingface_demo/out_dirs/checkpoint-80/optimizer.pt"))
    # trainer.lr_scheduler.load_state_dict(torch.load("/project/llava_module/huggingface_demo/out_dirs/checkpoint-80/scheduler.pt"))

2、实现Resume方法

通过trainer.train()的方式完成第一个阶段的训练,并在训练完成后中断模型;此外,在完成第一个阶段的训练后重启时可以指定resume_from_checkpoint=True来实现 Resume 功能。此外, resume_from_checkpoint还可以指定具体路径指向权重文件夹

总结

了解相关内容保存方法;
了解resume的Demo,以便后文解读使用;

全部评论 (0)

还没有任何评论哟~