5.Pytorch模型单机多GPU训练原理与实现

文章目录

  • Pytorch的单机多GPU训练
    • 1)多GPU训练介绍
    • 2)pytorch中使用单机多`GPU`训练
      • `DistributedDataParallel`(DDP)相关变量及含义
      • a)初始化
      • b)数据准备
      • c)模型准备
      • d)清理
      • e)运行
    • 3)使用`DistributedDataParallel`训练模型的一个简单实例


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


Pytorch的单机多GPU训练

1)多GPU训练介绍

当我们使用的模型过大,训练数据比较多的时候往往需要在多个GPU上训练。使用多GPU训练时有两种方式,一种叫ModelParallelism,一种是DataParallelism

ModelParallelism方式,是在模型比较大导致一张显卡放不下的时候,将模型拆分然后分别放到不同的显卡上,将同一份数据分别输入进行模型训练。这种对模型结构各模块之间有联系时很不友好,有可能都不支持拆分。因此,应用更广泛的是DataParallelism的方式。

DataParallelism方式,是将相同的模型拷贝到不同的显卡上,然后将数据平均划分后输入到相应显卡上进行计算,然后根据计算结果更新模型的参数。

DataParallelism方式更新模型参数时,因为每个显卡上都有一个完整的模型,其可以单独根据一个显卡的运算结果更新参数,即异步更新,也可以将各个显卡的运算结果汇总后再根据总的运算结果一次性更新模型参数,即同步更新。因此,使用DataParallelism模型参数的更新有两种选择方式,不过值得注意的是不同显卡上的模型参数是共享的,也就是虽然不同显卡上都有完整的模型,但模型参数用的是同一份,都是相同的。 所以在模型初始化的时候就要给不同显卡上的模型初始化相同的权重值。根据两种权重更新策略的区别,可以发现,对于单个显卡上batch_size本身就比较大的情况,可以使用异步更新,这样不需要显卡之间运算同步,可以提升训练速度;而对于batch_size比较小的情况,根据mini_batch随机梯度下降算法的原理,最好选用同步更新的方式,保证学习效果。

图片引用自【分布式训练】单机多卡的正确打开方式(一):理论基础


参数同步更新
参数异步更新

使用多GPU训练时,还需要注意的是使用BatchNormalization的情况,对于BN层归一化时,是在单个显卡上计算,还是在不同的显卡之间做同步再计算,同样,对于batch_size比较大时建议使用异步运算,小时使用同步计算以保证模型学习的效果。

2)pytorch中使用单机多GPU训练

相对于tensorflow来说,pytorch中设置模型进行多GPU训练的方式就显的简单多了。在这里只介绍现在pytorch中使用最多的多GPU训练方式即使用DistributedDataParallel类。

DistributedDataParallel(DDP)相关变量及含义

DDP支持在多个机器中进行模型训练,其中每个机器被称之为节点Node,每个机器上有可能有多个GPU,为了不受GIL的限制,DDP会针对每个GPU启动一个进程进行训练,每个进程在对应机器上的编号使用环境变量LOCAL_RANK进行标识。

一次训练,在所有Node上启动的训练进程总和使用WORLD_SIZE来统计。而在分布在所有Node的上某个进程在全局所有进程中的序号使用环境变量RANK进行记录。

介绍到这DDP的整体原理和使用的变量就很清楚了,


DDP

参考上图,是假设有3台机器,每台机器上有2GPU的情况。值的注意的是master_addressmaster_port上的参数,这两个参数是告诉其他进程主进程(RANK=0的进程)的端口号和IP地址,以便于其与主进程之间进行通信,包括数据交换,同步等。

下面几部分,就分别对pytorch模型实现单机多GPU训练要进行哪些设置分别进行介绍。

a)初始化

在编写多GPU训练的代码时,需要先对环境进行初始化,需要调用init_process_group来初始化默认的分布式进程组(default distributed process group)和分布式包(distributed package)。使用的是pytorchtorch.distributed.init_process_group方法。

该方法原型:

torch.distributed.init_process_group(backend=None, \init_method=None, \timeout=datetime.timedelta(seconds=1800), \world_size=-1, \rank=-1, \store=None, \group_name='', \pg_options=None)

函数参数:

  • backend: 参数类型为str or Backend,根据pytorch编译时的配置来选择,支持mpi/gloo/nccl/ucc,这个后端指的是多GPU之间进行通信的方式,根据不同类型的GPU进行选择,对于NVIDIAGPU一般选择nccl,对于IntelGPU一般选择ucc
  • init_method: 参数类型为str,指定初始化方法,一般使用env://,表示使用环境变量MASTER_ADDRMASTER_PORT来初始化。和store变量是互斥的。
  • timeout: 参数类型为datetime.timedelta,指定初始化超时时间,如果超时则抛出异常。
  • world_size: 参数类型为int,指定进程组的大小,如果为-1,则使用环境变量WORLD_SIZE来指定,定义store变量时必须指定world_size
  • rank: 参数类型为int,指定当前进程在进程组中的排位,如果为-1,则使用环境变量RANK来指定,定义store变量时,必须指定rank
  • store: 参数类型为Store,指定用于保存分布式训练状态的存储Key/Value对象,用于交换连接/地址信息,所有的进程都能访问,和init_method方法互斥。
  • group_name: 参数类型为str,指定进程组的名字,这个变量已经是deprecated了。
  • pg_options: 参数类型为ProcessGroupOptions,指定进程组的其他选项,如allreduce_post_hook等,目前仅对nccl后端支持ProcessGroupNCCL.Options选项。

使用torch.distributed.init_process_group初始化进程组的两种方式

  • 指定store/rank/world_size
  • 指定init_method,明确给出进程间在哪通过哪种协议发现其他进程并通信,此时rank/world_size是可选的

初始化后,进程组可以通过torch.distributed.get_world_size()torch.distributed.get_rank()来获取进程组大小和当前进程在进程组中的排位

所以最简单的初始化方式,只需要指定后端即可:

torch.distributed.init_process_group(backend='nccl')

每个进程的环境变量RANK是在启动时由torchrun命令行工具自动添加的,WORLD_SIZE是在torchrun启动时根据启动的进程数自动添加的。

b)数据准备

pytorch中,数据的准备是先实例化torch.utils.data.Dataset的数据类,然后再将其放入数据加载器torch.utils.data.DataLoader中,以控制加载数据的进程数num_worker、采样器samplerbatch_size大小等。

在使用DistributedDataParallel实现训练时,在数据加载器中上需要使用两个采样器sampler = DistributedSampler(data)batch_sampler = torch.utils.data.BatchSampler(train_sampler, batch_size, drop_last=True)来指定数据采样器,这样可以保证每个进程每个batch只处理属于自己的数据。

这里一起来看下DistributedSamplerBatchSampler

DDP模式就是将数据均分到多个GPU上来优化算法,对于每个GPU该如何从总的训练数据中采样属于自己用的数据,这就需要一个采样策略,这正是DistributedSampler发挥的作用


DistributedSampler

如上图,假设有11个样本,GPU的数量为2DistributedSampler的作用先是把数据打散,然后均分到每个gpu上,当数据不组时,会采用循环重复的策略来补满。

torch.utils.data.BatchSampler则是指定每个batch的样本数量,以及是否丢弃最后一个可能不足的batch。当设置drop_last=True时,会将最后不足一个batch的数据丢弃。


BatchSampler

上面介绍的过程是对于一轮数据训练时数据加载器的工作过程,对整个训练过程,为了保证学习的效果,需要在每个epoch设置采样器能重新打散数据,因此要在每一轮训练开始前调用DistributedSamplerset_epoch方法。

sampler = DistributedSampler(data)
batch_sampler = torch.utils.data.BatchSampler(sampler, batch_size, drop_last=True)
dataloader = torch.utils.data.Dataloader(data_set, batch_sampler=train_batch_sampler)
for i in range(epoches):sampler.set_epoch(epoch)...

c)模型准备

使用DistributedDataParallel进行模型训练时,需要将模型放在DistributedDataParallel类中,这样模型就可以在多GPU上并行计算。

此外,还有一些需要注意的。

在设置device时,要想指定使用的GPU需要设置环境变量CUDA_VISIBLE_DEVICES=1,2,在代码中对于模型,可以使用model.to(device)来设置deviceLOCAL_RANK中获取,当设置CUDA_VISIBLE_DEVICES时,LOCAL_RANK0时从指定的GPU开始的,而不是硬件上的GPU序号,例如指定CUDA_VISIBLE_DEVICES=1,2时,LOCAL_RANK=0时对应的是GPU1LOCAL_RANK=1时对应的是GPU2

# in train.py
import os
device = f'cuda:{os.getenv(LOCAL_RANK)}'

执行,

CUDA_VISIBLE_DEVICES=1,2 torchrun --nnodes 1 --nproc_group_size 2 train.py

加载模型后对于使用多GPU时还需注意的是参数初始化,要使用同一份权重值对模型进行初始化,否则在模型训练时,每个GPU上的模型参数就会不一样,从而导致训练效果不佳。一种方案是将主进程上的权重先保存下来,然后再加载到其他进程的模型上:

model = Model()
if not os.path.exists(weights_path):checkpoint_path = os.path.join(tempfile.gettempdir(), "initial_weights.pt")if rank == 0:torch.save(model.state_dict(), checkpoint_path)dist.barrier()model.load_state_dict(torch.load(checkpoint_path, map_location=device))

上面的代码的功能很明了,值得注意的是dist.barrier()语句,它表示等待所有进程都到达这个语句处,然后才进行下一步操作,确保所有进程都执行到这一步,然后才开始进行权重加载。

当模型使用BatchNormalization时,除了需要将模型放入DistributedDataParallel类中,还需要使用torch.nn.SyncBatchNorm.convert_sync_batchnorm方法对模型上的BN层进行转化,这样模型训练时,每个GPU上的BatchNormalization层就会与其他GPU上的BN层进行同步更新。

model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[int(os.environ['LOCAL_RANK'])])

到这里,能够在多GPU上训练的模型就准备好了。下面再来看下模型训练时需要留意的地方。

  • 训练过程中平均损失值的计算。在单个进程中loss是在单个进程数据上计算的,为了记录训练过程,打印平均损失值时,要将所有进程上的loss值累加后除以进程组的大小,以得到平均损失值。
def reduce_value(value, average=True):world_size = get_world_size()if world_size < 2:  # 单GPU的情况return valuewith torch.no_grad():dist.all_reduce(value)if average:value /= world_sizereturn valuereduace_value(loss)

注意上面代码中使用的dist.all_reduce函数,它用于进行数据同步,将数据从所有进程收集到主进程上,并将主进程上的数据广播到所有进程上,这样所有进程上的数据就相同了。

  • 训练完一个epoch时,在每个进程中要使用torch.cuda.synchronize(device),以确保使用当前设备的所有进程都计算完成。

  • 在训练过程中使用DDP模型,进程验证时,也需要使用dist.all_reduce来统计所有的运算结果:

@torch.no_grad()
def evaluate(model, data_loader, device):model.eval()sum_num = torch.zeros(1).to(device)# 在进程0中打印验证进度if os.getenv("RANK")==0:data_loader = tqdm(data_loader, file=sys.stdout)for step, data in enumerate(data_loader):images, labels = datapred = model(images.to(device))pred = torch.max(pred, dim=1)[1]sum_num += torch.eq(pred, labels.to(device)).sum()# 等待所有进程计算完毕if device != torch.device("cpu"):torch.cuda.synchronize(device)sum_num = dist.all_reduce(sum_num)return sum_num.item()

d)清理

在训练代码的最后,定义完训练逻辑后,需要调用torch.distributed.destroy_process_group来关闭进程组,结束进程之间的通信。

torch.distributed.destroy_process_group()

e)运行

pytorch DistributedDataParallelGPU训练任务启动的命令通常使用的是python -m torch.distributed.launch,在torch1.9.0版本后引入了torchrun命令,两者功能基本类似,python -m torch.distributed.launch 和 torchrun 在功能上是类似的。它们都是用于启动分布式训练的命令行工具,可以自动设置环境变量并启动训练脚本。torchrunPyTorch 1.9.0 版本引入的新命令,旨在为分布式训练提供更简洁和一致的接口。与python -m torch.distributed.launch相比,torchrun具有一些额外的功能和灵活性,例如支持不同的运行模式和分布式运行时后端。对于使用较新版本PyTorch的情况,建议使用torchrun` 来保持一致性以使用其提供的新功能。

关于torchrunpython -m torch.distributed.launch命令支持的选项可以使用--help来查看。

torchrun --nnodes 1 --nproc_per_node 2 train.py train_argspython -m torch.distributed.launch --nnodes 1 --nproc_per_node train.py train_args

更底层的方法可以使用torch.multiprocessing.spawn函数来启动训练,它需要传递一个训练函数和进程数量作为参数。

3)使用DistributedDataParallel训练模型的一个简单实例


import torch
import torchvision
import os
import math
import tqdm
import sysbatch_size=256
epoches = 100
num_classes = 10torch.distributed.init_process_group(backend='nccl')transform = torchvision.transforms.Compose([torchvision.transforms.Resize(128),torchvision.transforms.ToTensor(),torchvision.transforms.Normalize(mean=[0.5, 0.5, 0.5],std=[1.0, 1.0, 1.0])])
train_dataset = torchvision.datasets.CIFAR10(root="./data/cifar10",train=True,download=True,transform=transform)
val_dataset = torchvision.datasets.CIFAR10(root="./data/cifar10",train=False,download=True,transform=transform)
num_classes = len(val_dataset.classes)train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)
val_sampler = torch.utils.data.distributed.DistributedSampler(val_dataset)
train_batch_sampler = torch.utils.data.BatchSampler(train_sampler, batch_size, drop_last=True)
train_dataloader = torch.utils.data.DataLoader(train_dataset,batch_sampler=train_batch_sampler,pin_memory=True,num_workers=4)
val_dataloader = torch.utils.data.DataLoader(val_dataset,batch_size=batch_size,sampler=val_sampler,pin_memory=True,num_workers=4)device = f'cuda:{os.getenv("LOCAL_RANK")}' if torch.cuda.is_available() else 'cpu'
device = torch.device(device)m = torchvision.models.mobilenet_v3_small(pretrained=False, num_classes=num_classes)
ckpt_path = "/tmp/init_weight.pt"
if int(os.getenv("LOCAL_RANK")) == 0:torch.save(m.state_dict(), ckpt_path)
torch.distributed.barrier()
m.load_state_dict(torch.load(ckpt_path, map_location=device))
m = torch.nn.SyncBatchNorm.convert_sync_batchnorm(m).to(device)
m = torch.nn.parallel.DistributedDataParallel(m, device_ids=[int(os.getenv("LOCAL_RANK"))])params = [ param for param in m.parameters() if param.requires_grad ]
optimizer = torch.optim.SGD(params=params,lr=0.001,momentum=0.9,weight_decay=0.005)
lr_func = lambda x : (1 + math.cos(x * math.pi / epoches)) / 2 * (1 - 0.1) + 0.1
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer=optimizer,lr_lambda=lr_func)
loss_func = torch.nn.CrossEntropyLoss()for epoch in range(epoches):train_sampler.set_epoch(epoch)m.train()optimizer.zero_grad()avg_loss = torch.zeros(1, device=device)right_pred_num = torch.zeros(1, device=device)best_acc = 0.0if int(os.getenv("LOCAL_RANK")) == 0:pbar = tqdm.tqdm(train_dataloader, file=sys.stdout)else:pbar = train_dataloaderfor i, (image, label) in enumerate(pbar):image = image.to(device)label = label.to(device)pred = m(image)loss = loss_func(pred, label)loss.backward()torch.distributed.all_reduce(loss)avg_loss = (avg_loss * i + loss.detach()) / (i + 1)if int(os.getenv("LOCAL_RANK")) == 0:pbar.desc  = f"[epoch: {epoch}] step: {i}, learning_rate: {scheduler.get_last_lr()} average loss: {round(avg_loss.item(), 3)}"assert torch.isfinite(loss), f"Nan Loss, Training End."optimizer.step()optimizer.zero_grad()torch.cuda.synchronize(device=device)m.eval()with torch.no_grad():if int(os.getenv("LOCAL_RANK")) == 0:pbar = tqdm.tqdm(val_dataloader, file=sys.stdout)else:pbar = val_dataloaderfor i, (image, label) in enumerate(pbar):image = image.to(device)label = label.to(device)pred = m(image)            pred = torch.max(pred, dim=1)[1]right_pred_num += torch.eq(pred, label).sum()torch.cuda.synchronize(device=device)torch.distributed.all_reduce(right_pred_num)if int(os.getenv("LOCAL_RANK")) == 0:acc = round(right_pred_num.item() / len(val_dataset), 3)print(f"Val Accuracy: {acc}")if acc > best_acc:best_acc = accprint(f"New Best Accuracy: {acc}, Model Saved: best.pt")torch.save(m.state_dict(), "best.pt")# CUDA_VISIBLE_DEVICES=3,4 torchrun -nnodes 1 --nproc_per_node 2 train.py# [epoch: 31] step: 23, learning_rate: [0.0007911220577405485] average loss: 3.66: 
# 100%|██████████████████████████████████████████████| 5/5 [00:01<00:00,  3.01it/s]
# Val Accuracy: 0.239
# New Best Accuracy: 0.239, Model Saved: best.pt
# [epoch: 32] step: 23, learning_rate: [0.0007790686370876671] average loss: 3.635:
# 100%|██████████████████████████████████████████████| 5/5 [00:01<00:00,  3.18it/s]
# Val Accuracy: 0.247
# New Best Accuracy: 0.247, Model Saved: best.pt

代码也可从‵gitee`仓库中下载https://gitee.com/lx_r/object_detection_task。



欢迎访问个人网络日志🌹🌹知行空间🌹🌹


1.https://github.com/WZMIAOMIAO/deep-learning-for-image-processing/tree/master/pytorch_classification/train_multi_GPU
2.pytorch多GPU并行训练教程
3.https://zhuanlan.zhihu.com/p/178402798
4.https://pytorch.org/tutorials/beginner/ddp_series_multigpu.html?highlight=multi
5.https://pytorch.org/tutorials/beginner/ddp_series_theory.html#why-you-should-prefer-ddp-over-dataparallel-dp
6.https://medium.com/red-buffer/getting-started-with-pytorch-distributed-54ae933bb9f0

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

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

相关文章

数学建模day15-时间序列分析

时间序列也称动态序列&#xff0c;是指将某种现象的指标数值按照时间顺序排列而成的数值序列。时间序列分析大致可分成三大部分&#xff0c;分别是描述过去、分析规律和预测未来&#xff0c;本讲将主要介绍时间序列分析中常用的三种模型&#xff1a;季节分解、指数平滑方法和AR…

WEB服务器-Tomcat

3. WEB服务器-Tomcat 3.1 简介 3.1.1 服务器概述 服务器硬件 指的也是计算机&#xff0c;只不过服务器要比我们日常使用的计算机大很多。 服务器&#xff0c;也称伺服器。是提供计算服务的设备。由于服务器需要响应服务请求&#xff0c;并进行处理&#xff0c;因此一般来说…

【AI】人工智能和水下机器视觉

目录 一、初识水下机器视觉 ——不同点 ——难点 二、AI如何助力水下机器视觉 三、应用场景 四、关键技术 水下机器视觉&#xff0c;非常复杂&#xff0c;今天来简单讨论一下。因为目标识别更难。 水下机器视觉是机器视觉技术在水下环境中的应用&#xff0c;它与普通机器…

基于Springboot的网上点餐系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的网上点餐系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&am…

【2024】OAK智能深度相机校准教程

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是Ash…

机器人跟踪性能量化指标

衡量机械臂关节轨迹跟踪控制的性能可以通过以下几个方面来进行&#xff1a; 跟踪精度&#xff1a;这是衡量机械臂关节轨迹跟踪控制性能的最重要的指标。它反映了机械臂实际运动轨迹与期望运动轨迹之间的偏差。跟踪精度越高&#xff0c;说明机械臂的控制性能越好。运动范围&…

抖音小店怎么选品?分享如何培养选爆品的思维,每个人都要学会

选品定店铺生死。 一个店铺能不能出单&#xff0c;能不能赚钱&#xff0c;店铺的商品占主要部分&#xff0c;商品才是电商店铺最核心的内容&#xff0c;一个货真价实&#xff0c;物美价廉的产品才是店铺的核心竞争力&#xff0c;运营和找达人都是让产品卖的更多&#xff0c;更…

三、MySQL实例初始化、设置、服务启动关闭、环境变量配置、客户端登入(一篇足以从白走到黑)

目录 1、选择安装的电脑类型、设置端口号 2、选择mysql账号密码加密规则 3、设置root账户密码 4、设置mysql服务名和服务启动策略 5、执行设置&#xff08;初始化mysql实例&#xff09; 6、完成设置 7、MySQL数据库服务的启动和停止 方式一&#xff1a;图形化方式 方式…

AI智能剪辑,快速剪辑出需要的视频

AI智能剪辑技术&#xff0c;是一种基于人工智能的技术&#xff0c;它能够通过机器学习和深度学习算法&#xff0c;自动识别视频中的内容&#xff0c;并根据用户的需求和喜好&#xff0c;快速地剪辑出需要的视频。 所需工具 &#xff1a; 一个【媒体梦工厂】软件 视频素材 …

软件报错msvcp120.dll丢失怎么办?总共有6个msvcp120.dll丢失的解决方法分享

一、msvcp120.dll是什么文件&#xff1f; msvcp120.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;它是运行许多Windows应用程序所必需的动态链接库文件之一。它包含了许多C函数和类&#xff0c;用于支持各种应用程序的正常运行。因此&#xff0c;当ms…

leetcode206.反转链表

https://leetcode.cn/problems/reverse-linked-list/description/ 题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&am…

中国政企客户,需要什么样的云服务?

0. 前言和目录 我前段时间写了一篇《技术服务工作的呼吁和推演》&#xff0c;文中感叹&#xff0c;几乎没有云厂商重视技术服务工作。很意外也很庆幸&#xff0c;这篇文章起到了抛砖引玉的效果&#xff0c;我收到了一些高价值反馈。我的感叹有些肤浅&#xff0c;国内政企云行业…

ssm基于Vue的健身房会员管理系统+vue论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#x…

【车载开发系列】AutoSar当中的DcmDspSecurity容器

【车载开发系列】AutoSar当中的DcmDspSecurity容器 AutoSar当中的DcmDspSecurity容器 【车载开发系列】AutoSar当中的DcmDspSecurity容器一. DcmDspSecurity容器位置二. 关于对安全等级理解三. 关于安全等级的定义1&#xff09;Extendedsecuritylevel2&#xff09;Programmings…

软件设计不是CRUD(10):低耦合模块设计理论——业务抽象:从需求中提取业务维度

接上文《软件设计不是CRUD(9):低耦合模块设计理论——设计落地所面临的挑战》 2、什么是业务抽象 业务抽象是一种将需求落地成模块功能的设计思想,是对业务需求和技术设计进行转换、隔离的一种分析方法。经过业务抽象后的业务模块一般具有较高的业务屈服度,能更大程度满…

Envoy

一、Envoy简介 Envoy 是一款由Lyft开源的高性能服务代理软件&#xff0c;使用现代C语言&#xff08;C11及C14&#xff09;开发&#xff0c;提供四层和七层网络代理功能。2017年&#xff0c;Envoy 被捐赠给 CNCF 基金会&#xff0c;最终成为继Kubenetes利Prometheus 之后第3个 …

EI级 | Matlab实现VMD-TCN-BiLSTM变分模态分解结合时间卷积双向长短期记忆神经网络多变量光伏功率时间序列预测

EI级 | Matlab实现VMD-TCN-BiLSTM变分模态分解结合时间卷积双向长短期记忆神经网络多变量光伏功率时间序列预测 目录 EI级 | Matlab实现VMD-TCN-BiLSTM变分模态分解结合时间卷积双向长短期记忆神经网络多变量光伏功率时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基…

Vue3-46-Pinia-获取全局状态变量的方式

使用说明 在 Pinia 中&#xff0c;获取状态变量的方式非常的简单 &#xff1a; 就和使用对象一样。 使用思路 &#xff1a; 1、导入Store&#xff1b;2、声明Store对象&#xff1b;3、使用对象。 在逻辑代码中使用 但是 Option Store 和 Setup Store 两种方式定义的全局状态变量…

全新小白菜QQ云端机器人登录系统源码 /去除解密授权学习版源码

源码介绍&#xff1a; 全新小白菜QQ云端机器人登录系统源码&#xff0c;是一款经过全面解密的授权学习版源码。 这款源码已解除了授权版的限制&#xff0c;然而许多人可能对其用途并不了解。实际上&#xff0c;该源码主要面向群机器人爱好者设计。它是一个基于挂机宝机器人框…

RT-DETR算法优化改进:多层次特征融合(SDI)结合PConv、DualConv、GSConv,实现二次创新 | UNet v2最新论文

💡💡💡本文独家改进:多层次特征融合(SDI)高效结合DualConv、PConv、GSConv等实现二次创新 1)替代原始的Concat; RT-DETR魔术师专栏介绍: https://blog.csdn.net/m0_63774211/category_12497375.html ✨✨✨魔改创新RT-DETR 🚀🚀🚀引入前沿顶会创新(CVPR…