Hugging Face系列2:详细剖析Hugging Face网站资源——实战六类开源库

news2024/11/28 0:50:29

Hugging Face系列2:详细剖析Hugging Face网站资源——实战六类开源库

  • 前言
  • 本篇摘要
  • 2. Hugging Face开源库
    • 2.1 transformers
      • 2.1.1 简介
      • 2.1.2 实战
        • 1. 文本分类
        • 2. 图像识别
        • 3. 在Pytorch和TensorFlow中使用pipeline
    • 2.2 diffusers
      • 2.2.1 简介
      • 2.2.2 实战
        • 1. 管线
        • 2. 模型和调度器
    • 2.3 datasets
      • 2.3.1 简介
      • 2.3.2 实战
    • 2.4 PEFT
      • 2.4.1 简介
      • 2.4.2 实战
        • 1. peft模型
        • 2. 新旧模型结构对比
        • 3. PEFT模型推理
    • 2.5 accelerate
      • 2.5.1 简介
      • 2.5.2 实战
        • 1. 与pytorch融合
        • 2. 命令行accelerate config&launch
        • 3. DeepSpeed
        • 4. notebook_launcher
    • 2.6 optimum
      • 2.6.1 简介
      • 2.6.2 实战
        • 1. ONNX&ONNX Runtime
        • 2. 加速推理
        • 3. 加速训练
  • 参考文献

前言

本系列文章旨在全面系统的介绍Hugging Face,让小白也能熟练使用Hugging Face上的各种开源资源,并上手创建自己的第一个Space App,在本地加载Hugging Face管线训练自己的第一个模型,并使用模型生成采样数据,同时详细解决部署中出现的各种问题。后续文章会分别介绍采样器及其加速、显示分类器引导扩散模型、CLIP多模态图像引导生成、DDMI反转及控制类大模型ControlNet等,根据反馈情况可能再增加最底层的逻辑公式和从零开始训练LLM等,让您从原理到实践彻底搞懂扩散模型和大语言模型。欢迎点赞评论、收藏和关注。

本系列文章如下:

  1. 《详细剖析Hugging Face网站资源——models/datasets/spaces》:全面系统的介绍Hugging Face资源;
  2. 《详细剖析Hugging Face网站资源——实战六类开源库》
  3. 《从0到1:使用Hugging Face管线加载Diffusion模型生成第一张图像》:在本地加载Hugging Face管线训练自己的第一个模型,并使用模型生成采样数据,同时详细解决部署中出现的各种问题。

本篇摘要

本篇主要介绍Hugging Face。Hugging Face是一个人工智能的开源社区,是相关从业者协作和交流的平台。它的核心产品是Hugging Face Hub,这是一个基于Git进行版本管理的存储库,截至2024年5月,已托管了65万个模型、14.5万个数据集以及超过17万个Space应用。另外,Hugging Face还开源了一系列的机器学习库如Transformers、Datasets和Diffusers等,以及界面演示工具Gradio。此外,Hugging Face设计开发了很多学习资源,比如与NLP(大语言模型)、扩散模型及深度强化学习等相关课程。最后介绍一些供大家交流学习的平台。为了更有趣,本篇介绍了大量有趣的Spaces应用,比如换装IDM-VTON、灯光特效IC-Light、LLM性能排行Artificial Analysis LLM Performance Leaderboard和自己部署的文生图模型stable-diffusion-xl-base-1.0、对图片精细化的stable-diffusion-xl-refiner-1.0等。只要读者认真按着文章操作,上述操作都可自己实现。下面对以上内容逐一介绍。

2. Hugging Face开源库

除了Hugging Face Hub提供的models、datasets和spaces,Hugging Face还在GitHub上开源了一系列的机器学习库和工具。在GitHub的Hugging Face组织页面,其置顶了一些开源代码仓库,包括transformers、diffusers、datasets、peft、accelerate以及optimum,如下图所示。本篇逐一详细介绍并给出对应的实战用例,方便读者更直观的理解和应用。

在这里插入图片描述

2.1 transformers

2.1.1 简介

transformers 提供了数以千计的可执行不同任务的SOTA1预训练模型,这些任务包括(1)支持 100 多种语言的文本分类、信息抽取、问答、摘要、翻译、文本生成的文本任务;(2)用于图像分类,目标检测和分割等图像任务;(3)用于语音识别和音频分类等音频任务。Transformer模型还可以在几种模式的组合上执行任务,例如表格问答、光学字符识别、从扫描文档中提取信息、视频分类和视觉问答等。

Transformers 提供了便于快速下载和使用的API,让你可以将预训练模型在给定文本、数据集上微调后,通过 model hub 与社区共享;同时,每个定义的 Python 模块均完全独立,方便修改和快速研究实验。Transformers 支持三个最热门的深度学习库: Jax, PyTorch 以及 TensorFlow——并与之无缝整合。你可以直接使用一个框架训练你的模型然后用另一个加载和推理,以支持框架之间的互操作。模型还可以导出成ONNX和TorchScript等格式,以方便在生产环境中部署。

用户可以通过transformers直接测试和使用model hub中的大部分模型,以节省训练时间和资源,降低成本。

2.1.2 实战

Hugging Face提供pipeline API以便在给定的输入(文本、图像、音频等)上使用模型。模型训练期间,管道用预训练模型实现预处理。

在使用任何库之前,需要先安装:

# upgrade参数保证安装最新版
pip install --upgrade XXX

下面看三个例子。

1. 文本分类

使用pipeline实现文本分类:

>>> from transformers import pipeline

# Allocate a pipeline for sentiment-analysis. The second line of code downloads and caches the pretrained model used by the pipeline, while the third evaluates it on the given text.
>>> classifier = pipeline('sentiment-analysis')
>>> classifier('We are very happy to introduce pipeline to the transformers repository.')
[{'label': 'POSITIVE', 'score': 0.9996980428695679}]
2. 图像识别

使用pipeline实现图像识别:

>>> import requests
>>> from PIL import Image
>>> from transformers import pipeline

# Download an image with cute cats
>>> url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/coco_sample.png"
>>> image_data = requests.get(url, stream=True).raw
>>> image = Image.open(image_data)

# Allocate a pipeline for object detection
>>> object_detector = pipeline('object-detection')
>>> object_detector(image)
[{'score': 0.9982201457023621,
  'label': 'remote',
  'box': {'xmin': 40, 'ymin': 70, 'xmax': 175, 'ymax': 117}},
 {'score': 0.9960021376609802,
  'label': 'remote',
  'box': {'xmin': 333, 'ymin': 72, 'xmax': 368, 'ymax': 187}},
 {'score': 0.9954745173454285,
  'label': 'couch',
  'box': {'xmin': 0, 'ymin': 1, 'xmax': 639, 'ymax': 473}},
 {'score': 0.9988006353378296,
  'label': 'cat',
  'box': {'xmin': 13, 'ymin': 52, 'xmax': 314, 'ymax': 470}},
 {'score': 0.9986783862113953,
  'label': 'cat',
  'box': {'xmin': 345, 'ymin': 23, 'xmax': 640, 'ymax': 368}}]
3. 在Pytorch和TensorFlow中使用pipeline

代码如下:

>>> from transformers import AutoTokenizer, AutoModel

>>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased")
>>> model = AutoModel.from_pretrained("google-bert/bert-base-uncased")

# pytorch uses pt and tensorflow uses tf
>>> inputs = tokenizer("Hello world!", return_tensors="pt")
>>> outputs = model(**inputs)

代码解释:tokenizer负责为预训练模型预处理字符串,并且可指定返回张量格式,然后传入model作训练,产生最终输出。示例代码演示了如何在PyTorch或TensorFlow框架中使用模型,并在新数据集上使用训练API快速微调预处理模型。

2.2 diffusers

2.2.1 简介

diffusers是SOTA预训练扩散模型的首选库,可用于生成图像、音频,甚至分子的3D结构。diffusers是一个模块化工具箱,既可支持简单的推理解决方案,也支持训练自己的扩散模型。diffusers库设计注重可用性而非性能,简单而非简易,可定制化而非抽象。

diffusers提供三个核心组件:1)最先进的扩散管道diffusion pipelines,只需几行代码即可进行推理;2)可互换噪声调度器noise schedulers,可用于调节推理过程中模型生成中的扩散速度和输出质量;3)预训练模型pretrained models可用作构建块,并与调度器相结合,以创建自己的端到端扩散系统。

2.2.2 实战

实战包括管线、模型和调度器。

1. 管线

使用diffusers生成输出非常容易,如文生图,可使用from_pretrained方法加载预训练的扩散模型stable-diffusion:

from diffusers import DiffusionPipeline
import torch

pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16)
pipeline.to("cuda")
pipeline("An image of a squirrel in Picasso style").images[0]
2. 模型和调度器

还可以深入研究预训练模型和噪声调度器的工具箱,构建自己的扩散系统:

from diffusers import DDPMScheduler, UNet2DModel
from PIL import Image
import torch

scheduler = DDPMScheduler.from_pretrained("google/ddpm-cat-256")
model = UNet2DModel.from_pretrained("google/ddpm-cat-256").to("cuda")
scheduler.set_timesteps(50)

sample_size = model.config.sample_size
noise = torch.randn((1, 3, sample_size, sample_size), device="cuda")
input = noise

for t in scheduler.timesteps:
    with torch.no_grad():
        noisy_residual = model(input, t).sample
        prev_noisy_sample = scheduler.step(noisy_residual, t, input).prev_sample
        input = prev_noisy_sample

image = (input / 2 + 0.5).clamp(0, 1)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8"))
image

2.3 datasets

2.3.1 简介

datasets是一个轻量级库,旨在让社区轻松添加和共享新的数据集,它提供两个主要功能:

  1. 可用于众多公共数据集的dataloaders:可一行代码下载和预处理HuggingFace Datasets Hub提供的任意数据集,包括图像数据集、音频数据集、467种语言/方言的文本数据集等。示例如squad_dataset=load_dataset(“squad”)。
  2. 高效的数据预处理:可简单、快速和可复制的预处理众多公共数据集及本地的CSV、JSON、text、PNG、JPEG、WAV、MP3、Parquet等格式数据。示例如processed_dataset=dataset.map(process_example)。

此外,datasets还有许多其他有趣的功能:

  1. 适配于大型数据集:datasets将用户从RAM内存限制中解放出来,所有数据集都使用高效的零序列化成本后端(Apache Arrow)进行内存映射。
  2. 智能缓存:重复处理数据无需等待。
  3. 支持轻量快速透明的Python API,Python API还具有多处理器、缓存及内存映射等特性。
  4. 内置支持与NumPy、pandas、PyTorch、TensorFlow 2和JAX进行互操作。
  5. 原生支持音频和图像数据。
  6. 支持启用流模式以节省磁盘空间,可快速开启对数据集进行迭代。

需要指出的是,HuggingFace Datasets源于优秀的TensorFlow数据集的一个分支,两者差异主要是HuggingFace Datasets动态加载python脚本而不是在库中提供、后端序列化基于Apache Arrow而不是TF Records、面向用户的datasets对象(封装了一个内存映射的Arrow表缓存)基于与框架无关的由tf.data的方法激发的数据集类而不是基于tf.data.Dataset。

2.3.2 实战

datasets的API以函数datasets.load_dataset(dataset_name, **kwargs)为中心,该函数用于实例化数据集。
加载文本数据集的示例:

from datasets import load_dataset

# Print all the available datasets
from huggingface_hub import list_datasets
print([dataset.id for dataset in list_datasets()])

# Load a dataset and print the first example in the training set
squad_dataset = load_dataset('squad')
print(squad_dataset['train'][0])

# Process the dataset - add a column with the length of the context texts
dataset_with_length = squad_dataset.map(lambda x: {"length": len(x["context"])})

# Process the dataset - tokenize the context texts (using a tokenizer from the 🤗 Transformers library)
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')

tokenized_dataset = squad_dataset.map(lambda x: tokenizer(x['context']), batched=True)

当数据集比磁盘空间大,或者不想下载数据时,可以使用流媒体,示例如下:

# If you want to use the dataset immediately and efficiently stream the data as you iterate over the dataset
image_dataset = load_dataset('cifar100', streaming=True)
for example in image_dataset["train"]:
    break

2.4 PEFT

2.4.1 简介

PEFT(Parameter-Efficient Fine-Tuning)即SOTA高效参数微调方法,由于大型模型的参数规模,完全微调模型成本极高,而PEFT仅通过微调少量参数就可以达到完全微调的性能,使型预训练模型有效地适应各种下游应用,显著降低计算和存储成本。

PEFT与Transformers集成,可轻松进行模型训练和推理;与Diffuser集成,可方便地管理不同的适配器;与Accelerate集成,可用于真正大型模型的分布式训练和推理。

提示:(1)PEFT方法如何用于下游各类任务可参考PEFT 组织部分jupyter notebook的演示;(2)查看PEFT API Reference的 Adapters部分,可得到PEFT支持的方法列表。阅读CONCEPTUAL GUIDES部分学习如何使用。如下图:
在这里插入图片描述

2.4.2 实战

实战包括示例、新旧模型对比和模型推理三部分。

1. peft模型

在使用PEFT中方法(如LoRA)训练模型之前,需要通过get_peft_model()将基础模型和PEFT配置包装在一起。

比如下例的bigscience/mt0-large,只需训练完全模型0.19%的参数即可:

from transformers import AutoModelForSeq2SeqLM
from peft import get_peft_config, get_peft_model, LoraConfig, TaskType
model_name_or_path = "bigscience/mt0-large"
tokenizer_name_or_path = "bigscience/mt0-large"

peft_config = LoraConfig(
    task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1
)

model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
model_peft = get_peft_model(model, peft_config)
model.print_trainable_parameters()
"trainable params: 2359296 || all params: 1231940608 || trainable%: 0.19151053100118282"
2. 新旧模型结构对比

现在打印出新旧模型作对比,看PEFT到底做了什么。也可参考Spaces空间中使用PEFT的示例,如下:

# 查看peft配置
model.peft_config

PrefixTuningConfig(peft_type=<PeftType.PREFIX_TUNING: 'PREFIX_TUNING'>, base_model_name_or_path='bigscience/mt0-large', 
task_type=<TaskType.SEQ_2_SEQ_LM: 'SEQ_2_SEQ_LM'>, inference_mode=False, num_virtual_tokens=30, token_dim=1024, num_transformer_submodules=1, 
num_attention_heads=16, num_layers=24, encoder_hidden_size=1024, prefix_projection=False)

# 打印旧模型
print(model)
MT5ForConditionalGeneration(
  (shared): Embedding(250112, 1024)
  (encoder): MT5Stack(
    (embed_tokens): Embedding(250112, 1024)
    (block): ModuleList(
      (0): MT5Block(
        (layer): ModuleList(
          (0): MT5LayerSelfAttention(
            (SelfAttention): MT5Attention(
              (q): Linear(in_features=1024, out_features=1024, bias=False)
              (k): Linear(in_features=1024, out_features=1024, bias=False)
              (v): Linear(in_features=1024, out_features=1024, bias=False)
              (o): Linear(in_features=1024, out_features=1024, bias=False)
              (relative_attention_bias): Embedding(32, 16)
            )
            (layer_norm): MT5LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): MT5LayerFF(
            (DenseReluDense): MT5DenseGatedActDense(
              (wi_0): Linear(in_features=1024, out_features=2816, bias=False)
              (wi_1): Linear(in_features=1024, out_features=2816, bias=False)
              (wo): Linear(in_features=2816, out_features=1024, bias=False)
              (dropout): Dropout(p=0.1, inplace=False)
              (act): NewGELUActivation()
            )
            (layer_norm): MT5LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (1-23): 23 x MT5Block(...)
    (final_layer_norm): MT5LayerNorm()
    (dropout): Dropout(p=0.1, inplace=False)
  )
  (decoder): MT5Stack(
    (embed_tokens): Embedding(250112, 1024)
    (block): ModuleList(
      (0): MT5Block(
        (layer): ModuleList(
          (0): MT5LayerSelfAttention(
            (SelfAttention): MT5Attention(
              (q): Linear(in_features=1024, out_features=1024, bias=False)
              (k): Linear(in_features=1024, out_features=1024, bias=False)
              (v): Linear(in_features=1024, out_features=1024, bias=False)
              (o): Linear(in_features=1024, out_features=1024, bias=False)
              (relative_attention_bias): Embedding(32, 16)
            )
            (layer_norm): MT5LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): MT5LayerCrossAttention(
            (EncDecAttention): MT5Attention(
              (q): Linear(in_features=1024, out_features=1024, bias=False)
              (k): Linear(in_features=1024, out_features=1024, bias=False)
              (v): Linear(in_features=1024, out_features=1024, bias=False)
              (o): Linear(in_features=1024, out_features=1024, bias=False)
            )
            (layer_norm): MT5LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (2): MT5LayerFF(
            (DenseReluDense): MT5DenseGatedActDense(
              (wi_0): Linear(in_features=1024, out_features=2816, bias=False)
              (wi_1): Linear(in_features=1024, out_features=2816, bias=False)
              (wo): Linear(in_features=2816, out_features=1024, bias=False)
              (dropout): Dropout(p=0.1, inplace=False)
              (act): NewGELUActivation()
            )
            (layer_norm): MT5LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (1-23): 23 x MT5Block(
        (layer): ModuleList(...)
    (final_layer_norm): MT5LayerNorm()
    (dropout): Dropout(p=0.1, inplace=False)
  )
  (lm_head): Linear(in_features=1024, out_features=250112, bias=False)
)

# 打印peft模型
print(model_peft)
PeftModelForSeq2SeqLM(
  (base_model): IA3Model(
    (model): MT5ForConditionalGeneration(
      (shared): Embedding(250112, 1024)
      (encoder): MT5Stack(
        (embed_tokens): Embedding(250112, 1024)
        (block): ModuleList(
          (0): MT5Block(
            (layer): ModuleList(
              (0): MT5LayerSelfAttention(
                (SelfAttention): MT5Attention(
                  (q): Linear(in_features=1024, out_features=1024, bias=False)
                  (k): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (v): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (o): Linear(in_features=1024, out_features=1024, bias=False)
                  (relative_attention_bias): Embedding(32, 16)
                )
                (layer_norm): MT5LayerNorm()
                (dropout): Dropout(p=0.1, inplace=False)
              )
              (1): MT5LayerFF(
                (DenseReluDense): MT5DenseGatedActDense(
                  (wi_0): Linear(in_features=1024, out_features=2816, bias=False)
                  (wi_1): Linear(
                    in_features=1024, out_features=2816, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 2816x1])
                  )
                  (wo): Linear(in_features=2816, out_features=1024, bias=False)
                  (dropout): Dropout(p=0.1, inplace=False)
                  (act): NewGELUActivation()
                )
                (layer_norm): MT5LayerNorm()
                (dropout): Dropout(p=0.1, inplace=False)
              )
            )
          )
          (1-23): 23 x MT5Block(...)
        (final_layer_norm): MT5LayerNorm()
        (dropout): Dropout(p=0.1, inplace=False)
      )
      (decoder): MT5Stack(
        (embed_tokens): Embedding(250112, 1024)
        (block): ModuleList(
          (0): MT5Block(
            (layer): ModuleList(
              (0): MT5LayerSelfAttention(
                (SelfAttention): MT5Attention(
                  (q): Linear(in_features=1024, out_features=1024, bias=False)
                  (k): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (v): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (o): Linear(in_features=1024, out_features=1024, bias=False)
                  (relative_attention_bias): Embedding(32, 16)
                )
                (layer_norm): MT5LayerNorm()
                (dropout): Dropout(p=0.1, inplace=False)
              )
              (1): MT5LayerCrossAttention(
                (EncDecAttention): MT5Attention(
                  (q): Linear(in_features=1024, out_features=1024, bias=False)
                  (k): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (v): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (o): Linear(in_features=1024, out_features=1024, bias=False)
                )
                (layer_norm): MT5LayerNorm()
                (dropout): Dropout(p=0.1, inplace=False)
              )
              (2): MT5LayerFF(
                (DenseReluDense): MT5DenseGatedActDense(
                  (wi_0): Linear(in_features=1024, out_features=2816, bias=False)
                  (wi_1): Linear(
                    in_features=1024, out_features=2816, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 2816x1])
                  )
                  (wo): Linear(in_features=2816, out_features=1024, bias=False)
                  (dropout): Dropout(p=0.1, inplace=False)
                  (act): NewGELUActivation()
                )
                (layer_norm): MT5LayerNorm()
                (dropout): Dropout(p=0.1, inplace=False)
              )
            )
          )
          (1-23): 23 x MT5Block(...)
        (final_layer_norm): MT5LayerNorm()
        (dropout): Dropout(p=0.1, inplace=False)
      )
      (lm_head): Linear(in_features=1024, out_features=250112, bias=False)
    )
  )
)

对比以上输出,新旧模型主要的区别在于下面三点,可以看出peft主要通过减少输出特征参数量来缩小训练规模:

model:
              (k): Linear(in_features=1024, out_features=1024, bias=False)
              (v): Linear(in_features=1024, out_features=1024, bias=False)
              (wi_1): Linear(in_features=1024, out_features=2816, bias=False)
model_peft:
                  (k): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (v): Linear(
                    in_features=1024, out_features=1024, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1024x1])
                  )
                  (wi_1): Linear(
                    in_features=1024, out_features=2816, bias=False
                    (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 2816x1])
                  )
3. PEFT模型推理

下面使用PEFT中模型AutoPeftModelForCausalLM进行推理:

from peft import AutoPeftModelForCausalLM
from transformers import AutoTokenizer
import torch

model = AutoPeftModelForCausalLM.from_pretrained("ybelkada/opt-350m-lora").to("cuda")
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")

model.eval()
inputs = tokenizer("Preheat the oven to 350 degrees and place the cookie dough", return_tensors="pt")

outputs = model.generate(input_ids=inputs["input_ids"].to("cuda"), max_new_tokens=50)
print(tokenizer.batch_decode(outputs, skip_special_tokens=True)[0])

"Preheat the oven to 350 degrees and place the cookie dough in the center of the oven. In a large bowl, combine the flour, baking powder, baking soda, salt, and cinnamon. In a separate bowl, combine the egg yolks, sugar, and vanilla."

2.5 accelerate

2.5.1 简介

accelerate可以在任何类型的设备上运行raw格式的PyTorch训练脚本。accelerate就是为PyTorch用户创建的,这些用户喜欢编写PyTorch模型的训练循环,但不愿意编写和维护使用多GPU/TPU/fp16所需的样板代码。accelerate替用户做了这部分工作,它可以准确地抽出与多GPU/TPU/fp16相关的样板代码,并保持代码的其余部分不变。

2.5.2 实战

accelerate除了支持pytorch,还支持多种其他应用方式,下面逐一详细介绍。

1. 与pytorch融合

下面来看一个用accelerate如何改造使用pytorch框架的训练脚本:

  import torch
  import torch.nn.functional as F
  from datasets import load_dataset
+ from accelerate import Accelerator

+ accelerator = Accelerator()
- device = 'cpu'
+ device = accelerator.device

  model = torch.nn.Transformer().to(device)
  optimizer = torch.optim.Adam(model.parameters())

  dataset = load_dataset('my_dataset')
  data = torch.utils.data.DataLoader(dataset, shuffle=True)

+ model, optimizer, data = accelerator.prepare(model, optimizer, data)

  model.train()
  for epoch in range(10):
      for source, targets in data:
          source = source.to(device)
          targets = targets.to(device)

          optimizer.zero_grad()

          output = model(source)
          loss = F.cross_entropy(output, targets)

-         loss.backward()
+         accelerator.backward(loss)

          optimizer.step()

在上例中,通过向任何标准PyTorch训练脚本添加5行,就可以在任何类型的单个或分布式节点(单CPU、单GPU、多GPU和TPU)、以及混合精度的情况下运行(fp8、fp16、bf16)。特别是,在本地机器上调试和训练环境中运行相同的代码而无需进行任何修改。

2. 命令行accelerate config&launch

accelerate提供了一个可选的CLI工具,可在启动脚本之前快速配置和测试培训环境。无需记住如何使用torch.distributed.run或编写TPU训练的特定启动器,只需运行命令:

(pytorch) shoe@shoe-tp14:~$ accelerate config
In which compute environment are you running?
This machine                                                                                                                          
Which type of machine are you using?                                                                                                  
multi-GPU                                                                                                                             
How many different machines will you use (use more than 1 for multi-node training)? [1]: 1                                            
Should distributed operations be checked while running for errors? This can avoid timeout issues but will be slower. [yes/NO]: yes    
Do you wish to optimize your script with torch dynamo?[yes/NO]:yes                                                                    
Which dynamo backend would you like to use?                                                                                           
tensorrt                                                                                                                              
Do you want to customize the defaults sent to torch.compile? [yes/NO]: yes                                                            
Which mode do you want to use?                                                                                                        
default                                                                                                                               
Do you want the fullgraph mode or it is ok to break model into several subgraphs? [yes/NO]: yes                                       
Do you want to enable dynamic shape tracing? [yes/NO]: yes                                                                            
Do you want to use DeepSpeed? [yes/NO]: NO                                                                                            
Do you want to use FullyShardedDataParallel? [yes/NO]: NO                                                                             
Do you want to use Megatron-LM ? [yes/NO]: yes                                                                                        
What is the Tensor Parallelism degree/size? [1]:1                                                                                     
What is the Pipeline Parallelism degree/size? [1]:1                                                                                   
Do you want to enable selective activation recomputation? [YES/no]: 1                                                                 
Please enter yes or no.
Do you want to enable selective activation recomputation? [YES/no]: yes
Do you want to use distributed optimizer which shards optimizer state and gradients across data parallel ranks? [YES/no]: yes
What is the gradient clipping value based on global L2 Norm (0 to disable)? [1.0]: 1
How many GPU(s) should be used for distributed training? [1]:1

Do you wish to use FP16 or BF16 (mixed precision)?
bf16                                                                                                                                  
accelerate configuration saved at /home/shoe/.cache/huggingface/accelerate/default_config.yaml      

以下是您提供的配置的简要概述以及每个选项的含义:

  1. 计算环境:您正在使用本地机器,这可能意味着您将在单台物理服务器或工作站上使用多个GPU。
  2. 机器类型:您正在使用多GPU机器。
  3. 多机器训练:您只计划使用一台机器进行训练,这意味着您将在单节点上进行训练。
  4. 分布式操作检查:您希望在运行时检查分布式操作是否有错误,这样可以避免超时问题,但可能会使训练变慢。
  5. 使用torch dynamo优化:您希望使用torch dynamo来优化您的PyTorch代码,这可以提高性能。
  6. dynamo后端:您选择使用tensorrt作为后端,这通常用于生产环境,可以提供优化的代码。
  7. DeepSpeed:您不打算使用DeepSpeed,这是一个用于深度学习训练的优化库。
  8. FullyShardedDataParallel:您不打算使用FullyShardedDataParallel,这是一个用于数据并行的PyTorch分布式训练的库。
  9. Megatron-LM:您打算使用Megatron-LM,这是一个用于大规模语言模型训练的PyTorch扩展。
  10. Tensor并行度:您设置为1,这意味着您不会使用Tensor并行。
  11. 流水线并行度:您设置为1,这意味着您不会使用流水线并行。
  12. 选择性激活重计算:您启用了选择性激活重计算,这可以提高效率。
  13. 分布式优化器:您启用了分布式优化器,这意味着优化器状态和梯度将在数据并行等级上分片。
  14. 梯度裁剪:您设置了一个基于全局L2范数的梯度裁剪值。
  15. 用于分布式训练的GPU数量:您指定了使用3个GPU进行分布式训练。
  16. FP16或BF16(混合精度):您选择了BF16,这是英伟达的混合精度之一,可以提高训练性能。

逐一根据您的具体需求和配置回答所问,就会生成配置文件default_config.yaml,当使用命令accelerate launch时,它将被自动设置到配置中,示例如下:

!accelerate launch  --multi_gpu --num_processes 2 train_unconditional.py \
  --dataset_name="huggan/smithsonian_butterflies_subset" \
  --resolution=64 \
  --output_dir={model_name} \
  --train_batch_size=32 \
  --num_epochs=50 \
  --gradient_accumulation_steps=1 \
  --learning_rate=1e-4 \
  --lr_warmup_steps=500 \
  --mixed_precision="no"

可以在accelerate config中配置多CPU,也可以直接使用命令mpirun启用多CPU,命令示例如下:

# np参数设置CPU数量
mpirun -np 2 python examples/nlp_example.py
3. DeepSpeed

accelerate支持在单/多GPU上使用DeepSpeed。你即可以通过上面accelerate config中配置DeepSpeed,也可以将DeepSpeed相关参数通过DeepSpeedPlugin集成到python脚本中。其中DeepSpeedPlugin 是一个用于 PyTorch 模型的插件,可以帮助用户更快地训练深度学习模型。具体来说,DeepSpeedPlugin 可以实现以下功能:

  1. 自动混合精度训练:DeepSpeedPlugin 可以自动将模型参数和梯度转换为半精度浮点数,从而减少 GPU 存储器的使用量,加快训练速度。
  2. 分布式训练:DeepSpeedPlugin 可以自动启动多个进程,将训练任务分配到多个 GPU 或多台机器上进行分布式训练。
  3. 梯度累积:DeepSpeedPlugin 可以实现梯度累积,从而减少 GPU 存储器的使用量,允许使用更大的批量大小进行训练。

DeepSpeedPlugin 中的参数解释:

  1. zero_stage:指定使用哪个阶段的 ZeRO 内存优化技术。ZeRO 是一种内存优化技术,可以将模型参数和梯度分成多个分片,从而减少 GPU 存储器的使用量。zero_stage 的取值范围为 0、1、2,分别表示不使用 ZeRO、使用 ZeRO 1 和使用 ZeRO 2。
  2. gradient_accumulation_steps:指定梯度累积的步数,取值为正整数。梯度累积是一种训练技巧,可以将多个小批量数据的梯度累积起来,从而实现大批量数据的训练。

示例如下:

from accelerate import Accelerator, DeepSpeedPlugin

# deepspeed needs to know your gradient accumulation steps beforehand, so don't forget to pass it
# Remember you still need to do gradient accumulation by yourself, just like you would have done without deepspeed
deepspeed_plugin = DeepSpeedPlugin(zero_stage=2, gradient_accumulation_steps=2)
accelerator = Accelerator(mixed_precision='fp16', deepspeed_plugin=deepspeed_plugin)

# How to save your 🤗 Transformer?
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(save_dir, save_function=accelerator.save, state_dict=accelerator.get_state_dict(model))

注意:目前DeepSpeed仍处于试验阶段,不支持手动编写的Config,将来可能支持,同时底层API可能也会有变化。

4. notebook_launcher

在Accelerate 0.3.0版本中引入了notebook_launcher(),它支持在jupyter notebook中启动分布式培训。这对于使用TPU后端训练的Colab或Kaggle型notebook尤其有用。只需在training_function中定义训练循环,然后在最后一个单元格中添加下列代码即可:

from accelerate import notebook_launcher

notebook_launcher(training_function)

2.6 optimum

2.6.1 简介

optimum是Transformers和Diffusers的扩展,它提供了一组性能优化工具,保持易用性的同时,可以在特定的目标硬件上以最高效率训练和运行模型。optimum支持多种类型的加速器,比如ONNX Runtime、Intel Neural Compressor、OpenVINO、NVIDIA TensorRT-LLM、AMD Instinct GPUs and Ryzen AI NPU、AWS Trainum & Inferentia、Habana Gaudi Processor (HPU)、FuriosaAI等,具体可参照optimum官方文档。

2.6.2 实战

在使用optimum特定加速器的功能之前,需要安装对应依赖包,如下表所示:

AcceleratorInstallation
ONNX Runtimepip install --upgrade --upgrade-strategy eager optimum[onnxruntime]
Intel Neural Compressorpip install --upgrade --upgrade-strategy eager optimum[neural-compressor]
OpenVINOpip install --upgrade --upgrade-strategy eager optimum[openvino]
NVIDIA TensorRT-LLMdocker run -it --gpus all --ipc host huggingface/optimum-nvidia
AMD Instinct GPUs and Ryzen AI NPUpip install --upgrade --upgrade-strategy eager optimum[amd]
AWS Trainum & Inferentiapip install --upgrade --upgrade-strategy eager optimum[neuronx]
Habana Gaudi Processor (HPU)pip install --upgrade --upgrade-strategy eager optimum[habana]
FuriosaAIpip install --upgrade --upgrade-strategy eager optimum[furiosa]

optimum提供不同工具在不同框架中导出和运行优化后模型,导出和优化操作即可通过编程的方式,也可由命令行完成。可操作框架包括:

  1. ONNX / ONNX Runtime
  2. TensorFlow Lite
  3. OpenVINO
  4. Habana first-gen Gaudi / Gaudi2
  5. AWS Inferentia 2 / Inferentia 1
  6. NVIDIA TensorRT-LLM

其中ONNX Runtime、Neural Compressor、OpenVINO、TensorFlow Lite的特点总结如下:

FeaturesONNX RuntimeNeural CompressorOpenVINOTensorFlow Lite
Graph optimization✔️N/A✔️N/A
Post-training dynamic quantization✔️✔️N/A✔️
Post-training static quantization✔️✔️✔️✔️
Quantization Aware Training (QAT)N/A✔️✔️N/A
FP16 (half precision)✔️N/A✔️✔️
PruningN/A✔️✔️N/A
Knowledge DistillationN/A✔️✔️N/A

下面我们选择ONNX/ONNX Runtime作为实战对象。

1. ONNX&ONNX Runtime

ONNX(Open Neural Network Exchange)是一个由微软提出的一种开放的神经网络交换格式,用于在不同的深度学习框架之间进行模型的转换和交流。它的设计目标是提供一个中间表示格式,使得用户可以在不同的深度学习训练和推理框架之间无缝地转换模型。

使用ONNX,你可以使用不同的深度学习框架(如PyTorch、TensorFlow、Paddle等)进行模型的训练和定义,然后将模型导出为ONNX格式。导出后的ONNX模型包含了模型的结构和权重参数等信息,但不包含模型的硬件信息。

一旦模型导出为ONNX格式,你可以使用ONNX Runtime或其他支持ONNX的框架将模型转换为目标设备所支持的模型格式,如TensorRT、Core ML、OpenVINO等。这样,你可以在不同的设备上部署和运行模型,无需重新训练或重新实现模型。同时它还支持修改模型的Graph、Node和Tensor。

ONNX的优势在于它提供了跨框架和跨平台的灵活性,使得深度学习模型在不同的环境中更易于部署和集成。它还支持许多常见的深度学习操作和网络架构,使得模型转换过程更加方便和高效。

ONNX Runtime:ONNX Runtime是一个跨平台的高性能推理引擎,支持将ONNX模型转换为多种设备和框架所支持的格式,如TensorFlow、TensorRT、Core ML等。它提供了一套API和工具,使得在不同平台上部署和运行ONNX模型更加便捷。

2. 加速推理

transformers和diffusers格式的模型可导出为ONNX格式,并易于执行图优化和量化操作。先执行格式转换:

optimum-cli export onnx -m deepset/roberta-base-squad2 --optimize O2 roberta_base_qa_onnx

然后使用onnxruntime进行量化:

optimum-cli onnxruntime quantize \
  --avx512 \
  --onnx_model roberta_base_qa_onnx \
  -o quantized_roberta_base_qa_onnx

这些命令将deepset/roberta-base-squad2导出为roberta_base_qa_onnx,并对导出的模型进行O2图优化,最后使用avx512配置进行量化。

完成量化后,就可以在后端使用Python类ONNX Runtime以无缝衔接地运行ONNX模型,示例代码如下:

- from transformers import AutoModelForQuestionAnswering
+ from optimum.onnxruntime import ORTModelForQuestionAnswering
  from transformers import AutoTokenizer, pipeline

  model_id = "deepset/roberta-base-squad2"
  tokenizer = AutoTokenizer.from_pretrained(model_id)
- model = AutoModelForQuestionAnswering.from_pretrained(model_id)
+ model = ORTModelForQuestionAnswering.from_pretrained("roberta_base_qa_onnx")
  qa_pipe = pipeline("question-answering", model=model, tokenizer=tokenizer)
  question = "What's Optimum?"
  context = "Optimum is an awesome library everyone should use!"
  results = qa_pipe(question=question, context=context)

更多使用ORTModelForXXX类运行ONNX模型的细节可参考 Optimum Inference with ONNX Runtime

3. 加速训练

optimum为原生Transformers Trainer提供类型包装器,以便在强大硬件上进行训练。支持如下加速训练方式:

  1. Habana’s Gaudi processors
  2. AWS Trainium instances
  3. ONNX Runtime (optimized for GPUs)
    以ONNX Runtime作为加速训练包装器为例:
- from transformers import Trainer, TrainingArguments
+ from optimum.onnxruntime import ORTTrainer, ORTTrainingArguments

  # Download a pretrained model from the Hub
  model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")

  # Define the training arguments
- training_args = TrainingArguments(
+ training_args = ORTTrainingArguments(
      output_dir="path/to/save/folder/",
      optim="adamw_ort_fused",
      ...
  )

  # Create a ONNX Runtime Trainer
- trainer = Trainer(
+ trainer = ORTTrainer(
      model=model,
      args=training_args,
      train_dataset=train_dataset,
      ...
  )

  # Use ONNX Runtime for training!
  trainer.train()

除了上面列举的开源库之外,其他常用的开源库还有timm、text-generation-inference、candle、trl、tokenizers、evaluate等。其中timm(PyTorch Image Models)是一个深度学习库,其中包含图像模型、优化器、调度器以及训练/验证脚本等内容。text-generation-inference(TGI)文本生成推理是一个用于部署和服务大型语言模型(LLM)的工具包,它为当前流行的开源LLM实现高性能文本生成,包括Llama、Falcon、StarCoder、BLOOM、GPT-NeoX等。candle是Rust的一个极简ML框架,专注于性能(支持GPU)和易用性。trl库是一个全栈工具,使用包括监督微调步骤(SFT)、奖励建模(RM)和近端策略优化(PPO)以及直接偏好优化(DPO)等方法对transformer结构和diffusion模型进行微调和对齐。Tokenizers是适用于研究和生产环境的高性能分词器,使用Rust语言实现,在20秒内就能使用CPU完成对1GB文本数据的分词。Evaluate则使用数十种流行的指标对数据集和模型进行评估。详细介绍可在GitHub Hugging Face组织页面查看。

参考文献

llama factory学习笔记
DeepSpeedPlugin的作用
ONNX 模型格式分析与使用


  1. State of the arts ↩︎

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

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

相关文章

【AI大模型】Prompt Engineering

目录 什么是提示工程&#xff08;Prompt Engineering&#xff09; Prompt 调优 Prompt 的典型构成 「定义角色」为什么有效&#xff1f; 防止 Prompt 攻击 攻击方式 1&#xff1a;著名的「奶奶漏洞」 攻击方式 2&#xff1a;Prompt 注入 防范措施 1&#xff1a;Prompt 注…

如何从清空的回收站恢复照片

担心如何从清空的回收站中恢复已删除的照片&#xff1f;您删除的文件和文件夹暂时存储在 Windows 回收站中。它使 Windows 用户能够快速恢复意外或故意丢失的项目。但是&#xff0c;为了释放计算机驱动器上的某些空间&#xff0c;有时可能需要清理回收站。 然而&#xff0c;有…

车用柴油氧化安定性检测 GB 19147-2009全项检测

柴油分为轻柴油&#xff08;沸点范围约180-370℃&#xff09;和重柴油&#xff08;沸点范围约350-410℃&#xff09;两大类。柴油使用性能中最重要的是着火性和流动性&#xff0c;其技术指标分别为十六烷值和凝点&#xff0c;我国柴油现行规格中要求含硫量控制在0.5%-1.5%。 检…

增值税发票OCR识别功能介绍

OCR增值税发票识别功能介绍如下&#xff1a; 一、技术原理 OCR增值税发票识别系统基于光学字符识别&#xff08;OCR&#xff09;技术和人工智能的支持&#xff0c;将传统纸质发票的信息自动转换为计算机可以读取的数字信息。具体技术流程包括&#xff1a; 图像预处理&#x…

SAP ABAP 往数据库表里加数据

目录 方法一&#xff1a;SE16N SE11 方法二&#xff1a;创建维护VIEW&#xff1a;SE11 SM30 Error补充说明&#xff1a; 方法一&#xff1a;SE16N SE11 首先SE16N 进来。 进来之后在テーブル的位置输入表名&#xff0c;然后点击执行&#xff08;F8&#xff09; 如果第一次…

【复现】含能量路由器的交直流混合配电网潮流计算

目录 1 主要内容 2 理论及模型 3 程序结果 4 下载链接 1 主要内容 程序复现《含能量路由器的交直流混合配电网潮流计算》&#xff0c;主要是对算例4.1进行建模分析&#xff0c;理论和方法按照文献所述。能量路由器&#xff08;ER&#xff09;作为新兴的电力元器件&#xff…

八、【源码】细化XML语句构建器,完善静态SQL解析

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/08-optimize-xml-parse 细化XML语句构建器&#xff0c;完善静态SQL解析 这一节主要是优化XML解析SQL部分&#xff0c;流程大概为&#xff…

SpringBoot+Vue企业客户管理系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 角色对应功能 员工管理员 功能截图

饥荒云服务器卡顿情况如何解决

随着网络游戏的普及&#xff0c;云服务器逐渐成为游戏开发者和玩家们的首选。然而&#xff0c;在使用饥荒云服务器时&#xff0c;有时会遇到卡顿的问题&#xff0c;这给玩家带来了困扰。本文将探讨饥荒云服务器卡顿的原因&#xff0c;并提供一些可能的解决方法。 卡顿产生的原因…

Vyper重入漏洞解析

什么是重入攻击 Reentrancy攻击是以太坊智能合约中最具破坏性的攻击之一。当一个函数对另一个不可信合约进行外部调用时&#xff0c;就会发生重入攻击。然后&#xff0c;不可信合约会递归调用原始函数&#xff0c;试图耗尽资金。 当合约在发送资金之前未能更新其状态时&#…

分布式事务大揭秘:使用MQ实现最终一致性

本文作者:小米,一个热爱技术分享的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 大家好,我是小米,一个热爱分享技术的29岁程序员,今天我们来聊聊分布式事务中的一种经典实现方式——MQ最终一致性。这是一个在互联网公司中广…

第一篇红队笔记-百靶精讲之W1R3S-john

https://download.vulnhub.com/w1r3s/w1r3s.v1.0.1.zip 主机发现 nmap端口扫描及思路 扫描某个网段 扫描单个ip所有端口 重复扫描单个ip具体端口 udp协议再来一次 漏洞扫描 FTP渗透 尝试匿名登陆 防止文件损坏 识别加密方式-hash-identifier base64 Web目录爆破…

AI如何创造情绪价值

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面。从智能家居到自动驾驶&#xff0c;从医疗辅助到金融服务&#xff0c;AI技术的身影无处不在。而如今&#xff0c;AI更是涉足了一个全新的领域——创造情绪价值。 AI已经能够处…

Leetcode 剑指 Offer II 081.组合总和

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个无重复元素的正整数数组 candidates 和一个正整数 targe…

Transformer论文精读

Transformer&#xff1a;Attention is all you need Abstract&#xff1a; 在主流的序列转录模型&#xff08;sequence transduction models&#xff1a;给一个序列&#xff0c;生成另一个序列&#xff09;&#xff0c;主要依赖循环或者卷积神经网络&#xff0c;一般是用enco…

【遗传算法】【机器学习】【Python】常见交叉方法(二)、多点交叉和均匀交叉

往期遗传算法文章见&#xff1a; 【遗传算法】【机器学习】【Python】常见交叉方法&#xff08;一&#xff09;、单点交叉和两点交叉 一、遗传算法流程图 交叉过程即存在于上图的”交叉“&#xff08;crossover&#xff09;步骤中。 二、多点交叉 多点交叉的原理就是&#x…

RISCV中CLINT和PLIC解析

中断这个东西理论上属于CPU核心的东西。一般来说并不需要重新设计。实际的实现中是比较繁琐的&#xff0c;此处只介绍原理。ARM基本上会用NVIC(Nested Vectored Interrupt Controller) 的东西&#xff0c;RISC-V目前实现了一个比较简单的东西&#xff08;有人称之为简洁高效&am…

算法 | hbut期末复习笔记

贪心选择策略&#xff1a;所求问题的整体最优解可以通过一系列局部最优的选择&#xff08;贪心选择&#xff09;得到 最优子结构&#xff1a;问题的最优解包括了其子问题的最优解 回溯法&#xff1a;具有限界函数的深度优先搜索法 回溯法的解空间&#xff1a;子集树&排列…

【计网复习】应用层总结(不含HTTP和错题重点解析)

应用层总结&#xff08;不含HTTP和错题重点解析&#xff09; 应用层简介 应用层的主要功能常见的应用层协议小林对于应用层通常的解释 网络应用模型 客户端-服务器模型&#xff08;Client-Server Model, C/S&#xff09; 特点优点缺点应用场景 对等网络模型&#xff08;Peer-to…

【QT5.14.2】编译MQTT库example的时候报No such file or directory

【QT5.14.2】编译MQTT库example的时候报No such file or directory 前几天导师让跑一下MQTT库&#xff0c;用的5.14.2版本的QT&#xff0c;于是就上网搜了一个教程&#xff1a;https://www.bilibili.com/video/BV1dH4y1e7hG/?spm_id_from333.337.search-card.all.click&v…