首页 > 其他分享 >Pytorch DDP分布式训练介绍

Pytorch DDP分布式训练介绍

时间:2024-08-25 16:16:30浏览次数:11  
标签:torch tensor DDP rank Pytorch 进程 local 分布式

近期一直在用torch的分布式训练,本文调研了目前Pytorch的分布式并行训练常使用DDP模式(Distributed DataParallell ),从基本概念,初始化启动,以及第三方的分布式训练框架展开介绍。最后以一个Bert情感分类给出完整的代码例子:torch-ddp-examples

基本概念

DistributedDataParallel(DDP)是依靠多进程来实现数据并行的分布式训练方法(简单说,能够扩大batch_size,每个进程负责一部分数据)。在使用DDP分布式训练前,有几个概念或者变量,需要弄清楚,这样后面出了bug大概知道从哪里入手,包括:

  • group: 进程组,一般就需要一个默认的
  • world size: 所有的进程数量
  • rank: 全局的进程id
  • local rank:某个节点上的进程id
  • local_word_size: 某个节点上的进程数 (相对比较少见)

这里需要注意的是,目前为止所有的概念的基本单元都是进程,与GPU没有关系,一个进程可以对应若干个GPU。 所以world_size 并不是等于所有的GPU数量,而人为设定的,这一点网上的很多描述并不准确。只不过平时用的最多的情况是一个进程使用一块GPU,这种情况下 world_size 可以等于所有节点的GPU数量。

假设所有进程数即 world_size为W,每个节点上的进程数即local_world_size为L,则每个进程上的两个ID:

  • rank的取值范围:[0, W-1],rank=0的进程为主进程,会负责一些同步分发的工作
  • local_rank的取值:[0, L-1]

官方的示意图的非常形象,如下,

 

假定有2个机器或者节点,每个机器上有4块GPU。图中一共有4个进程,即world_size=4,那这样每个进程占用两块GPU,其中rank就是[0,1,2,3],每个节点的local_rank就是[0,1]了,其中local_world_size 也就是2。 这里需要注意的是,local_rank是隐式参数,即torch自动分配的。比如local_rank 可以通过自动注入命令行参数或者环境变量来获得) 。

从torch1.10开始,官方建议使用环境变量的方式来获取local_rank, 在后期版本中,会移除命令行的方式。

一些简单的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch.distributed as dist
import argparse, os

parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", type=ine, default=0)
args = parser.parse_args()

dist.init_process_group("nccl")
rank = dist.get_rank()
local_rank_arg = args.local_rank # 命令行形式ARGS形式
local_rank_env = int(os.environ['LOCAL_RANK']) # 在利用env初始ENV环境变量形式
local_world_size = int(os.environ['LOCAL_WORLD_SIZE'])

print(f"{rank=}; {local_rank_arg=}; {local_rank_env=}; {local_world_size=}")

使用python3 -m torch.distributed.launch --nproc_per_node=4 test.py 在一台4卡机器上执行, 样例输出:

1
2
3
4
rank=2; local_rank_arg=2; local_rank_env=2, local_world_size=4
rank=0; local_rank_arg=0; local_rank_env=0, local_world_size=4
rank=3; local_rank_arg=3; local_rank_env=3, local_world_size=4
rank=1; local_rank_arg=1; local_rank_env=1, local_world_size=4

一般的分布式训练都是为每个进程赋予一块GPU,这样比较简单而且容易调试。 这种情况下,可以通过local_rank作为当前进程GPU的id。

分布式训练的场景很多,单机多卡,多机多卡,模型并行,数据并行等等。接下来就以常见的单机多卡的情况进行记录。

DDP启动

初始化

torch的distributed分布式训练首先需要对进程组进行初始化,这是核心的一个步骤,其关键参数如下:

1
torch.distributed.init_process_group(backend, init_method=None, world_size=-1, rank=-1, store=None,...)

首先需要指定分布式的后端,torch提供了NCCL, GLOO,MPI三种可用的后端,这三类支持的分布式操作有所不同,因此选择的时候,需要考虑具体的场景,按照官网说明,CPU的分布式训练选择GLOO, GPU的分布式训练就用NCCL即可。

接下来是初始化方法,有两种方法:

  • 显式指定init_method,可以是TCP连接、File共享文件系统、ENV环境变量三种方式,后面具体介绍。
  • 显式指定store,同时指定world_size 和 rank参数。这里的store是一种分布式中核心的key-value存储,用于不同的进程间共享信息。

这两种方法是互斥的,其实本质上第一种方式是对第二种的一个更高的封装,最后都要落到store上进行实现。如果这两种方法都没有使用,默认使用init_method='env'的方式来初始化。

对于三种init_method:

  • init_method='tcp://ip:port': 通过指定rank 0(即:MASTER进程)的IP和端口,各个进程进行信息交换。 需指定 rank 和 world_size 这两个参数。
  • init_method='file://path':通过所有进程都可以访问共享文件系统来进行信息共享。需要指定rank和world_size参数。
  • init_method=env://:从环境变量中读取分布式的信息(os.environ),主要包括 MASTER_ADDR, MASTER_PORT, RANK, WORLD_SIZE。 其中,rank和world_size可以选择手动指定,否则从环境变量读取。

可以发现,tcp和env两种方式比较类似(其实env就是对tcp的一层封装),都是通过网络地址的方式进行通信,也是最常用的初始化方法。

接下来看具体TCP/ENV初始化的的一个小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os, argparse
import torch
import torch.distributed as dist

parse = argparse.ArgumentParser()
parse.add_argument('--init_method', type=str)
parse.add_argument('--rank', type=int)
parse.add_argument('--ws', type=int)
args = parse.parse_args()

if args.init_method == 'TCP':
dist.init_process_group('nccl', init_method='tcp://127.0.0.1:28765', rank=args.rank, world_size=args.ws)
elif args.init_method == 'ENV':
dist.init_process_group('nccl', init_method='env://')

rank = dist.get_rank()
print(f"rank = {rank} is initialized")
# 单机多卡情况下,localrank = rank. 严谨应该是local_rank来设置device
torch.cuda.set_device(rank)
tensor = torch.tensor([1, 2, 3, 4]).cuda()
print(tensor)

假设单机双卡的机器上运行,则开两个终端,同时运行下面的命令,

1
2
3
4
5
6
# TCP方法
python3 test_ddp.py --init_method=TCP --rank=0 --ws=2
python3 test_ddp.py --init_method=TCP --rank=1 --ws=2
# ENV方法
MASTER_ADDR='localhost' MASTER_PORT=28765 RANK=0 WORLD_SIZE=2 python3 test_gpu.py --init_method=ENV
MASTER_ADDR='localhost' MASTER_PORT=28765 RANK=1 WORLD_SIZE=2 python3 test_gpu.py --init_method=ENV

如果开启的进程未达到 word_size 的数量,则所有进程会一直等待,直到都开始运行,可以得到输出如下:

1
2
3
4
5
6
# rank0 的终端:
rank 0 is initialized
tensor([1, 2, 3, 4], device='cuda:0')
# rank1的终端
rank 1 is initialized
tensor([1, 2, 3, 4], device='cuda:1')

可以看出,在初始化DDP的时候,能够给后端提供主进程的地址端口、本身的RANK,以及进程数量即可。初始化完成后,就可以执行很多分布式的函数了,比如dist.get_rank, dist.all_gather等等。

上面的例子是最基本的使用方法,需要手动运行多个程序,相对繁琐。实际上本身DDP就是一个python 的多进程,因此完全可以直接通过多进程的方式来启动分布式程序。 torch提供了以下两种启动工具来更加方便的运行torch的DDP程序。

mp.spawn

第一种方法便是使用torch.multiprocessing(python的multiprocessing的封装类) 来自动生成多个进程,使用方法也很简单,先看看基本的调用函数spawn:

1
mp.spawn(fn, args=(), nprocs=1, join=True, daemon=False)

其中:

  • fn: 进程的入口函数,该函数的第一个参数会被默认自动加入当前进程的rank, 即实际调用: fn(rank, *args)
  • nprocs: 进程数量,即:world_size
  • args: 函数fn的其他常规参数以tuple的形式传递

具体看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
import torch.distributed as dist
import torch.multiprocessing as mp

def fn(rank, ws, nums):
dist.init_process_group('nccl', init_method='tcp://127.0.0.1:28765',
rank=rank, world_size=ws)
rank = dist.get_rank()
print(f"rank = {rank} is initialized")
torch.cuda.set_device(rank)
tensor = torch.tensor(nums).cuda()
print(tensor)

if __name__ == "__main__":
ws = 2
mp.spawn(fn, nprocs=ws, args=(ws, [1, 2, 3, 4]))

直接执行一次命令 python3 test_ddp.py 即可,输出如下:

1
2
3
4
rank = 0 is initialized
rank = 1 is initialized
tensor([1, 2, 3, 4], device='cuda:1')
tensor([1, 2, 3, 4], device='cuda:0')

这种方式同时适用于TCP和ENV初始化。

launch/run

第二种方法则是torch提供的 torch.distributed.launch工具,可以以模块的形式直接执行:

1
python3 -m torch.distributed.launch --配置 train.py --args参数

常用配置有:

  • --nnodes: 使用的机器数量,单机的话,就默认是1了
  • --nproc_per_node: 单机的进程数,即单机的worldsize
  • --master_addr/port: 使用的主进程rank0的地址和端口
  • --node_rank: 当前的进程rank

在单机情况下, 只有--nproc_per_node 是必须指定的,--master_addr/portnode_rank都是可以由launch通过环境自动配置,举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch
import torch.distributed as dist
import torch.multiprocessing as mp
import os

dist.init_process_group('nccl', init_method='env://')

rank = dist.get_rank()
local_rank = os.environ['LOCAL_RANK']
master_addr = os.environ['MASTER_ADDR']
master_port = os.environ['MASTER_PORT']
print(f"rank = {rank} is initialized in {master_addr}:{master_port}; local_rank = {local_rank}")
torch.cuda.set_device(rank)
tensor = torch.tensor([1, 2, 3, 4]).cuda()
print(tensor)

使用也很方便,通过python3 -m torch.distribued.launch --nproc_per_node=2 test_ddp.py 运行,输出如下:

1
2
3
4
rank = 0 is initialized in 127.0.0.1:29500; local_rank = 0
rank = 1 is initialized in 127.0.0.1:29500; local_rank = 1
tensor([1, 2, 3, 4], device='cuda:1')
tensor([1, 2, 3, 4], device='cuda:0')

torch1.10开始用终端命令torchrun来代替torch.distributed.launch,具体来说,torchrun实现了launch的一个超集,不同的地方在于:

  • 完全使用环境变量配置各类参数,如RANK,LOCAL_RANK, WORLD_SIZE等,尤其是local_rank不再支持用命令行隐式传递的方式
  • 能够更加优雅的处理某个worker失败的情况,重启worker。需要代码中有load_checkpoint(path)save_checkpoint(path) 这样有worker失败的话,可以通过load最新的模型,重启所有的worker接着训练。具体参考 imagenet-torchrun
  • 训练的节点数目可以弹性变化。

同样上面的代码,直接使用 torchrun --nproc_per_node=2 test_gpu.py 运行即可,不用写那么长长的命令了。

需要注意的是, torchrun或者launch对上面ENV的初始化方法支持最完善,TCP初始化方法的可能会出现问题,因此尽量使用env来初始化dist。

DDP模型训练

上面部分介绍了一些细节如何启动分布式训练,接下来介绍如何把单机训练模型的代码改成分布式运行。基本流程如下:

  • 分布式训练数据加载。Dataloader需要把所有数据分成N份(N为worldsize), 并能正确的分发到不同的进程中,每个进程可以拿到一个数据的子集,不重叠,不交叉。这部分工作靠 DistributedSampler完成,具体的函数签名如下:

    1
    2
    torch.utils.data.distributed.DistributedSampler(dataset,
    num_replicas=None, rank=None, shuffle=True, seed=0, drop_last=False)
    • dataset: 需要加载的完整数据集
    • num_replicas: 把数据集分成多少份,默认是当前dist的world_size
    • rank: 当前进程的id,默认从dist的rank
    • shuffle:是否打乱
    • drop_last: 如果数据长度不能被world_size整除,可以考虑是否将剩下的扔掉
    • seed:随机数种子。这里需要注意,从源码中可以看出,真正的种子其实是 self.seed+self.epoch 这样的好处是,不同的epoch每个进程拿到的数据是不一样,因此需要在每个epoch开始前设置下:sampler.set_epoch(epoch)

    其实Sampler的实现也很简单,核心代码就一句:

    1
    indices[self.rank: self.total_size: self.num_replicas]

    假设4卡12条数据的话,rank=0,1,2,3, num_replicas=4, 那么每个卡取的数据索引就是:

    1
    rank0: [0 4 8]; rank1: [1 5 9]; rank2: [2 6 10]; rank3: [3 7 11]

    保证不重复不交叉。这样在分布式训练的时候,只需要给Dataloader指定DistributedSampler即可,简单示例如下:

    1
    2
    3
    4
    5
    sampler = DistributedSampler(dataset)
    loader = DataLoader(dataset, sampler=sampler)
    for epoch in range(start_epoch, n_epochs):
    sampler.set_epoch(epoch) # 设置epoch 更新种子
    train(loader)
  • 模型的分布式训练封装。将单机模型使用torch.nn.parallel.DistributedDataParallel 进行封装,如下:

    1
    2
    3
    4
    torch.cuda.set_device(local_rank)
    model = Model().cuda()
    model = DistributedDataParallel(model, device_ids=[local_rank])
    # 要调用model内的函数或者属性. model.module.xxxx

    这样在多卡训练时,每个进程有一个model副本和optimizer,使用自己的数据进行训练,之后反向传播计算完梯度的时候,所有进程的梯度会进行all-reduce操作进行同步,进而保证每个卡上的模型更新梯度是一样的,模型参数也是一致的。

    这里有一个需要注意的地方,在save和load模型时候,为了减小所有进程同时读写磁盘,一般处理方法是以主进程为主,rank0先save模型,在map到其他进程。这样的另外一个好处,在最开始训练时,模型随机初始化之后,保证了所有进程的模型参数保持一致。

    【注:其实在torch的DDP封装的时候,已经做到了这一点,即使开始随机初始化不同,经过DDP封装,所有进程都一样的参数】简洁代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    model = DistributedDataParallel(model, device_ids=[local_rank])
    CHECKPOINT_PATH ="./model.checkpoint"
    if rank == 0:
    torch.save(ddp_model.state_dict(), CHECKPOINT_PATH)
    # barrier()其他保证rank 0保存完成
    dist.barrier()
    map_location = {"cuda:0": f"cuda:{local_rank}"}
    model.load_state_dict(torch.load(CHECKPOINT_PATH, map_location=map_location))
    # 后面正常训练代码
    optimizer = xxx
    for epoch:
    for data in Dataloader:
    model(data)
    xxx
    # 训练完成 只需要保存rank 0上的即可
    # 不需要dist.barrior(), all_reduce 操作保证了同步性
    if rank == 0:
    torch.save(ddp_model.state_dict(), CHECKPOINT_PATH)

总结一下的话,使用DDP分布式训练的话,一共就如下个步骤:

  • 初始化进程组 dist.init_process_group
  • 设置分布式采样器 DistributedSampler
  • 使用DistributedDataParallel封装模型
  • 使用torchrun 或者 mp.spawn 启动分布式训练

补充一点使用分布式做evaluation的时候,一般需要先所有进程的输出结果进行gather,再进行指标的计算,两个常用的函数:

  • dist.all_gather(tensor_list, tensor) : 将所有进程的tensor进行收集并拼接成新的tensorlist返回,比如:
  • dist.all_reduce(tensor, op) 这是对tensor的in-place的操作, 对所有进程的某个tensor进行合并操作,op可以是求和等:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import torch
import torch.distributed as dist

dist.init_process_group('nccl', init_method='env://')
rank = dist.get_rank()
torch.cuda.set_device(rank)

tensor = torch.arange(2) + 1 + 2 * rank
tensor = tensor.cuda()
print(f"rank {rank}: {tensor}")

tensor_list = [torch.zeros_like(tensor).cuda() for _ in range(2)]
dist.all_gather(tensor_list, tensor)
print(f"after gather, rank {rank}: tensor_list: {tensor_list}")

dist.barrier()
dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
print(f"after reduce, rank {rank}: tensor: {tensor}")

通过torchrun --nproc_per_node=2 test_ddp.py 输出结果如下:

1
2
3
4
5
6
rank 1: tensor([3, 4], device='cuda:1')
rank 0: tensor([1, 2], device='cuda:0')
after gather, rank 1: tensor_list: [tensor([1, 2], device='cuda:1'), tensor([3, 4], device='cuda:1')]
after gather, rank 0: tensor_list: [tensor([1, 2], device='cuda:0'), tensor([3, 4], device='cuda:0')]
after reduce, rank 0: tensor: tensor([4, 6], device='cuda:0')
after reduce, rank 1: tensor: tensor([4, 6], device='cuda:1')

在evaluation的时候,可以拿到所有进程中模型的输出,最后统一计算指标,基本流程如下:

1
2
3
4
5
6
7
8
pred_list = []
for data in Dataloader:
pred = model(data)
batch_pred = [torch.zeros_like(label) for _ in range(world_size)]
dist.all_gather(batch_pred, pred)
pred_list.extend(batch_pred)
pred_list = torch.cat(pred_list, 1)
# 所有进程pred_list是一致的,保存所有数据模型预测的值

第三方库

前面一部分基本介绍了Pytorch DDP的基本概念,启动方式和如何将单进程代码 改成分布式训练的步骤,总体来看,其实DDP的使用方法已经足够简洁了。 不过近年来还是产生了不少优秀的封装更加higher,使用起来更简单的分布式训练库,本文主要介绍2个亲测还不错的框架。

其实不管哪个分布式框架,都是把上述的几个流程进行一层封装【初始化、包装模型、优化器、数据加载】。一般从以下几个方面衡量框架的易用性:

  • 支持分布式训练模式丰富,如CPU,单机单卡,单机多卡,多机多卡,FP16等
  • 代码简单,不需要改动大量代码即可进行分布式训练
  • 接口丰富,方便自定义。比如能调用和访问底层分布式的一些变量如rank,worldsize,或实现或封装一些分布式函数,比如dist.gather/reduce等。

Accelerator

第一个是由大名鼎鼎的huggingface发布的Accelerator,专门适用于Pytorch的分布式训练框架:

  • GitHub: https://github.com/huggingface/accelerate
  • 官网教程:https://huggingface.co/docs/accelerate/

将单进程代码改为多进程分布式的非常简单:

1
2
3
4
5
6
7
8
9
import accelerate
accelerator = accelerate.Accelerator()
device = accelerator.device #获取当前进程的设备
...
# 进行封装
model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader)

#训练时 loss.backward() 换为:
accelerator.backward(loss)

运行方法使用CLI命令行的方式,先使用accelerator config 配置一次分布式训练的参数,之后就使用 acceleratoe launch运行。具体的可以看官网例子。

除此之外,accelerator还提供了一些很便利的接口,基本覆盖了分布式训练中需要用到的方法,比如:

  • accelerator.print:仅仅在主进程输出
  • accelerator.process_index: 当前进程ID,没有使用rank命名,而是用的process_index来表示
  • accelerator.is_local_main_process/is_main_processs:: 是否local_rank 或则rank为0, 主进程
  • accelerator.wait_for_everyone() : 类似 dist.barrier() , 等所有进程到达这一步。
  • accelerator.save: 保存模型
  • kwargs_handlers: 可以定义DDP初始化的一些参数,比如最常用的就是 find_unused_parameters,比如:

    1
    2
    3
    4
    import accelerate
    from accelerate import DistributedDataParallelKwargs as DDPK
    kwargs = DDPK(find_unused_parameters=True)
    accelerator = accelerate.Accelerator(kwargs_handlers=[kwargs])

总体来说,accelerator这个库基本已经满足使用Pytorch进行分布训练的需求。 而且十分的符合huggingface的风格,把某个小项目做到最好用,类似的还有transformers, tokenizers, datasets等等。

不足的话,就是accelerate支持的collective function比较少,目前只有all_gather。

Horovod

第二个常用的分布式库Horovod是一个通用的深度学习分布式训练框架,支持Tensorflow,Pytorch,MXNet,Keras等等,因此比Accelerator要更加重些,但是功能也会更加丰富,这里以Pytorch为例来简单介绍。多说一下,Horovod的安装相对复杂一些,需要针对具体的环境参考readme进行安装。

  • GitHub:https://github.com/horovod/horovod
  • 官网:https://horovod.ai/

Horovod的使用也很简单,基本也是那几个流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import horovod.torch as hvd
# 初始化
hvd.init()
# Samapler
# *此处num_replicas=hvd.size(), rank=hvd.rank()必须*
train_sampler = torch.utils.data.distributed.DistributedSampler(
train_dataset, num_replicas=hvd.size(), rank=hvd.rank())

train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=..., sampler=train_sampler)
# 优化器包装
optimizer = hvd.DistributedOptimizer(optimizer, named_parameters=model.named_parameters())
# 模型分发广播
hvd.broadcast_parameters(model.state_dict(), root_rank=0)
# 模型训练不需要修改

horovod支持的运行方式非常多,最常用的就是horovodrun了,比如单机四卡运行:

1
horovodrun -np 4 -H localhost:4 python3 train.py

horovod相比accelerate来说,功能更加丰富,支持的接口,函数,框架都要多, 比如 hvd.all_reduce, hvd.all_gather等等。

综合看,这两个其实都是非常易用的分布式框架了,选择哪个都可以。 当然除了这两个外, 还有一些其他的,比如pytorch-lightning,deepspeed这里就不多介绍了。

最后,以bert情感分类为例子,介绍了如何使用原生DDP和上面2个框架来进行分布式训练,代码见:https://github.com/ShomyLiu/torch-ddp-examples

总结

总算把这篇鸽了很旧的博客写完了, 内容比较基础。目前网上一些教程要么直接给出一个代码例子,要么翻译下官方的例子或者API,很少有比较系统完整的讲解。 本文算是结合自己在用分布式训练时候遇到的一些问题或者困惑来展开介绍的。 其实分布式训练是非常复杂的,比如需要考虑弹性训练恢复等,本文并未涉及。 文中有不严谨的地方,欢迎指出。

参考

  • https://www.cnblogs.com/rossiXYZ/ 从源码角度全方位解析了分布式训练的一系列内容,如果想深入了解机制和架构的话,建议细读
  • https://zhuanlan.zhihu.com/p/76638962
  • https://github.com/pytorch/examples/blob/master/distributed/ddp/README.md
  • https://pytorch.org/tutorials/beginner/dist_overview.html

标签:torch,tensor,DDP,rank,Pytorch,进程,local,分布式
From: https://www.cnblogs.com/zhangxianrong/p/18379062

相关文章

  • Zookeeper分布式一致性协议ZAB介绍
    目录ZAB协议介绍消息广播崩溃恢复数据同步ZAB写数据源码流程图整个Zookeeper就是一个多节点分布式一致性算法的实现,底层采用的实现协议是ZAB。ZAB协议介绍ZAB协议全称:ZookeeperAtomicBroadcast(Zookeeper原子广播协议)。Zookeeper是一个为分布式应用提供高效且......
  • 基于yolov10的电梯电瓶车、电动车检测系统,支持图像检测,也支持视频和摄像实时检测(pytor
       更多目标检测和图像分类识别项目可看我主页其他文章功能演示:基于yolov10的电梯电瓶车,电动车检测,支持图像、视频和摄像实时检测【pytorch框架、python】_哔哩哔哩_bilibili(一)简介基于yolov10的电梯电瓶车、电动车检测系统是在pytorch框架下实现的,这是一个完整的项目,包......
  • 目标检测实践过程中,遇到“No module named ‘torch._six’”报错的一个快速解决方案(无
    很多人在按照网络、书籍教程中的流程尝试自己实现一个基于Faster-RCNN的目标检测模型时,如果调用了PyTorch官方github上的文件时,coco_eval.py文件中会触发报错。1.报错原因PyTorch在2.0之后的版本中移除了_six,导致在coco_eval.py中调用torch._six失败2.解决方案(1)直接根......
  • 折腾 Quickwit,Rust 编写的分布式搜索引擎(专为从对象存储中实现亚秒级搜索而设计)
    什么是Quickwit?Quickwit是首个能在云端存储上直接执行复杂的搜索与分析查询的引擎,并且具有亚秒级延迟。它借助Rust语言和分离计算与存储的架构设计,旨在实现资源高效利用、易于操作以及能够扩展到PB级数据量。Quickwit非常适合日志管理、分布式追踪以及通常为不可变数据......
  • 计算机毕业设计opencv+pytorch疲劳驾驶检测系统 自动驾驶 面部多信息特征融合的疲劳驾
    创新点:算法模型训练、可视化、深度学习框架、疲劳监测、模拟自动驾驶检测驾驶员疲劳核心算法:基于多信息特征指标融合建立驾驶员疲劳评价体系通过构建一种驾驶员面部多信息特征融合综合评价方法,为了能够将系统检测结果更直观的体现,研究了基于一种改进的粗糙集理论对各项特征结......
  • 004.MinIO-DirectPV分布式存储部署
    MinIO部署介绍部署概述Kuberneteshostpath、local和本地静态配置都存在需要事先在node节点准备好可用的块存储或文件系统,例如对插入的硬盘,或者磁盘阵列做分区格式化,文件系统则需提前创建好Kubernetes即将利用的挂载目录,并且两种方法都会有亲和性限制,无法做到让Kubernetes自身的......
  • 大数据技术之Zookeeper实现分布式锁(5)
    目录分布式锁案例1.创建锁节点2.获取锁3.释放锁4.锁自动释放示例代码分布式锁测试测试环境准备测试步骤示例代码Client1.javaClient2.java运行测试Curator框架实现分布式锁案例 步骤1:添加依赖步骤2:创建CuratorFramework实例步骤3:实现分布式......
  • 零基础学习人工智能—Python—Pytorch学习(八)
    前言本文介绍卷积神经网络的上半部分。其实,学习还是需要老师的,因为我自己写文章的时候,就会想当然,比如下面的滑动窗口,我就会想当然的认为所有人都能理解,而实际上,我们在学习的过程中之所以卡顿的点多,就是因为学习资源中想当然的地方太多了。概念卷积神经网络,简称CNN,即Convoluti......
  • 高效缓存策略——.NET Core 中基于 Redis 的分布式缓存实现
    引言在构建高性能的应用程序时,缓存是不可或缺的技术之一。通过缓存,我们能够显著减少数据库的压力、提升应用的响应速度。而在分布式系统中,分布式缓存则成为了处理高并发和大数据量的理想选择。本文将以Redis为例,介绍如何在.NETCore中实现分布式缓存,帮助开发者打造高效......
  • 大厂面试官:你知道Redis如何实现分布式锁么?
    常见面试题,看完基本也没啥问题了Redis如何实现分布式锁分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用。如下图所示:Redis本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁,而且Redis的读写......