神经网络——Tensor、计算图、训练方法

神经网络系列---计算图基本原理 符号微分提供了一种精确、直接的方式来计算导数。在深度学习和其他科学计算应用中,通过结合计算图,符号微分使得自动求导和梯度下降优化变得可行和高效。不过,对于非常复杂的表达式,符号微分可能导致表达式膨胀,从而增加了计算复杂性。因此,有时可能会结合使用符号微分和数值微分方法。数值微分是一种有用的工具,特别是当解析解不可用或难以获得时。它提供了一种灵活而实用的方法来近似导数,但必须谨慎选择参数并注意可能的数值问题。在深度学习和其他领域,它通常与符号微分或自动微分结合使用。变量:xxxyyy运算:log⁡。 阅读详情

Tensor、Variable 和 Parameter

经过 Pytorch 0.4.0 的更新后,前两个都是一个 torch.Tensor 对象,可以理解为两者等价;后者是 Parameter 对象。

Tensor 包含如下属性:

  • data,该 tensor 的值。
  • required_grad,该 tensor 是否连接在计算图(computational graph)上。
  • grad,如果 required_grad 是 True,则这个属性存储了反向传播时该 tensor 积累的梯度(这个梯度也是一个 tensor)。
  • grad_fn,该 tensor 计算梯度的函数。
  • is_leaf,在计算图中两种情况下 is_leaf 是 True:模型需要更新的参数 W 和模型的输入 x。is_leaf 和 required_grad 都是 True,该 tensor 才会将计算图中的梯度积累到 grad 属性中。

Parameter 与 Tensor 相比,多了两个特性:

  • 与 model 融为一体,当使用 model.cuda() 时 model 中所有的 Parameter 都会装载到 cuda 上。
  • 可以被 model.parameters 枚举出来,这样结合 optimizer 可以实现参数的更新。

计算图

前向计算时构建计算图,梯度是反向传播时计算,为了节省显存反向传播完计算图被释放。以 c = a*b 为例:

b 的 requires_grad 是 False,所以梯度计算时 b 是常数3,因此只计算梯度到 a 的 grad。

反向传播时 c.backward() 调用 MulBackward 来计算 a 和 b 的梯度:

前向计算和反向传播代码以 Swish 激活函数为例:

​class Swish(torch.autograd.Function):

    @staticmethod
    def forward(ctx, i):
        result = i * torch.sigmoid(i)
        ctx.save_for_backward(i)
        return result
    @staticmethod
    def backward(ctx, grad_output):
        i = ctx.saved_variables[0]
        sigmoid_i = torch.sigmoid(i)
        return grad_output * (sigmoid_i * (1 + i * (1 - sigmoid_i)))

class Swish_module(nn.Module):
    def forward(self, x):
        return Swish.apply(x)

训练方法

神经网络训练时有两部分会占用 GPU 的显存,第一部分是模型的参数权重;第二部分是输入模型的数据进行 forward 时,经过每一个神经元时算出的存储在计算图中的 activation(上面 Swish 代码中的 ctx.save_for_backward(i)?)。

常见的节省显存的训练方法有 Gradient Checkpointing 和混合精度训练。

Gradient Checkpointing

出自2016年的论文《Training Deep Nets With Sublinear Memory Cost》,论文证明这种方法能将模型的空间复杂度从 O(n) 降低到 O(sqrt(n))。简单来说,gradient checkpointing 会忽略一些 activation 来减少计算图占用的显存,在反向传播时重新计算 activation。当然这样做会减慢训练速度,这是一种 compute time 和 memory 的 trade-off。

前向计算时不用存储 activation,之后到每一个模块的反向传播中重新计算 activation,反向传完即释放显存。这时占用显总共需要的显存是每个模块需要的显存的最大值,而不是这些值的加和,这样就达到了节省显存的目的。

During the forward pass, PyTorch saves the input tuple to each function in the model. During backpropagation, the combination of input tuple and function is recalculated for each function in a just-in-time manner, plugged into the gradient formula for each function that needs it, and then discarded.

Pytorch 中提供了 torch.utils.checkpoint.checkpoint 和 torch.utils.checkpoint.checkpoint_sequential 来实现 gradient checkpointing。

torch.utils.checkpoint.checkpoint

class CIFAR10Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.cnn_block_1 = nn.Sequential(*[nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=2), nn.Dropout(0.25)])
        self.cnn_block_2 = nn.Sequential(*[nn.Conv2d(64, 64, 3, padding=1), nn.ReLU(), nn.Conv2d(64, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=2), nn.Dropout(0.25)])
        self.flatten = lambda inp: torch.flatten(inp, 1)
        self.head = nn.Sequential(*[nn.Linear(64 * 8 * 8, 512), nn.ReLU(), nn.Dropout(0.5), nn.Linear(512, 10)])
    
    def forward(self, X):
        X = self.cnn_block_1(X)
        X = self.cnn_block_2(X)
        X = self.flatten(X)
        X = self.head(X)
        return X

加上 checkpointing:

class CIFAR10Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.cnn_block_1 = nn.Sequential(*[nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=2)], nn.Dropout(0.25))
        self.cnn_block_2 = nn.Sequential(*[nn.Conv2d(64, 64, 3, padding=1), nn.ReLU(), nn.Conv2d(64, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=2)])
        self.dropout_2 = nn.Dropout(0.25)
        self.flatten = lambda inp: torch.flatten(inp, 1)
        self.linearize = nn.Sequential(*[ nn.Linear(64 * 8 * 8, 512), nn.ReLU()])
        self.dropout_3 = nn.Dropout(0.5)
        self.out = nn.Linear(512, 10)
    
    def forward(self, X):
        X = self.cnn_block_1(X)
        X = torch.utils.checkpoint.checkpoint(self.cnn_block_2, X)
        X = self.dropout_2(X)
        X = self.flatten(X)
        X = self.linearize(X)
        X = self.dropout_3(X)
        X = self.out(X)
        return X

需要注意 Dropout 和 BN 层。

因为需要进行两次前向计算,每一个 Dropout 激活的神经元位置是不同的,这样就会导致错误。应该将 gradient checkpointing 放在 Dropout 层之前或之后;

而 BN 层则会在两次前向计算中和 momentum 参数一起更新两次均值和方差 ,可以通过将 momentum 设置为 sqrt(momentum) 来修正,均值不变的话方差就不会变:

original_rm, original_nm = running_mean, new_mean

# Originally, we have a running_mean to start from
momemtum = 0.9
# running mean after the first forward pass. We want to match this 
# when we use checkpointing
running_mean1 = original_nm * 0.1 + original_rm * 0.9

# With checkpointing, we use momentum = sqrt(0.9) and make two passes
momentum = sqrt(0.9)
runing_mean = original_nm * (1 - sqrt(0.9)) + original_rm * sqrt(0.9)      # Eq (1) 
# Note that in the second forward pass, since the inputs are same, the 
# new_mean value doesn't change
running_mean2 = original_nm * (1 - sqrt(0.9)) + running_mean * sqrt(0.9)   # Eq (2) 

#Substitue value of running_mean from Eq(1) to Eq(2)
assert running_mean2 == running_mean1

同时需要注意的是只将第二个 cnn_block 放入了 checkpoint,因为 checkpoint 会根据输入数据是否需要更新来决定 checkpoint 中的参数是否进行更新。而最开始的 X 是原始数据,显然不需要更新,所以如果将原始数据也放入 checkpoint 的话,会导致之后的 checkpoint 里参数都不更新,也就是模型没有从数据中学习。

checkpoint 的实现如下,可以看到与上面 Swish 在 forward 内的区别是 outputs 之前加上了 with torch.no_grad:

class CheckpointFunction(torch.autograd.Function):

    @staticmethod
    def forward(ctx, run_function, preserve_rng_state, *args):
        check_backward_validity(args)
        ctx.run_function = run_function
        ctx.preserve_rng_state = preserve_rng_state
        if preserve_rng_state:
            ctx.fwd_cpu_state = torch.get_rng_state()
            # Don't eagerly initialize the cuda context by accident.
            # (If the user intends that the context is initialized later, within their run_function, we SHOULD actually stash the cuda state here. 
            # Unfortunately, we have no way to anticipate this will happen before we run the function.)
            ctx.had_cuda_in_fwd = False
            if torch.cuda._initialized:
                ctx.had_cuda_in_fwd = True
                ctx.fwd_gpu_devices, ctx.fwd_gpu_states = get_device_states(*args)
        ctx.save_for_backward(*args)
        with torch.no_grad():
            outputs = run_function(*args)
        return outputs

    @staticmethod
    def backward(ctx, *args):
        if not torch.autograd._is_checkpoint_valid():
            raise RuntimeError("Checkpointing is not compatible with .grad(), please use .backward() if possible")
        inputs = ctx.saved_tensors
        # Stash the surrounding rng state, and mimic the state that was present at this time during forward.  
        # Restore the surrouding state when we're done.
        rng_devices = []
        if ctx.preserve_rng_state and ctx.had_cuda_in_fwd:
            rng_devices = ctx.fwd_gpu_devices
        with torch.random.fork_rng(devices=rng_devices, enabled=ctx.preserve_rng_state):
            if ctx.preserve_rng_state:
                torch.set_rng_state(ctx.fwd_cpu_state)
                if ctx.had_cuda_in_fwd:
                    set_device_states(ctx.fwd_gpu_devices, ctx.fwd_gpu_states)
            detached_inputs = detach_variable(inputs)
            with torch.enable_grad():
                outputs = ctx.run_function(*detached_inputs)

        if isinstance(outputs, torch.Tensor):
            outputs = (outputs,)
        torch.autograd.backward(outputs, args)
        grads = tuple(inp.grad if isinstance(inp, torch.Tensor) else inp for inp in detached_inputs)
        return (None, None) + grads

torch.utils.checkpoint.checkpoint_sequential

比 checkpoint 要麻烦一些,需要自己设定将 model 分成几个 segment。checkpoint_sequential 会将 model 分成不同的 segment,不在前向传播时计算这些 segment 的 activation(对应 Swish 代码中的 result = i * torch.sigmoid(i)?)。在反向传播时,这些 segment 的 activation 才被计算出来,这时总共需要的显存是每个 segment 需要占用的显存中最大的那一个,而不是这些的加和。

import torch
import torch.nn as nn
from torch.utils.checkpoint import checkpoint_sequential

# a trivial model
model = nn.Sequential(nn.Linear(100, 50), nn.ReLU(), nn.Linear(50, 20), nn.ReLU(), nn.Linear(20, 5), nn.ReLU())
# model input
input_var = torch.randn(1, 100, requires_grad=True)
# the number of segments to divide the model into
segments = 2
# get the modules in teh model, should be in order
modules = [module for k, module in model.modulles.items()]
# finally, apply checkpointing to the model, note the code that this replaces:
# out = model(input_var)
out = checkpoint_sequential(modules, segments, input_var)
# backpropagate
out.sum().backwards()

pytorch_memonger/Checkpointing_for_PyTorch_models.ipynb at master · prigoyal/pytorch_memonger · GitHub
Training larger-than-memory PyTorch models using gradient checkpointing
Explore Gradient-Checkpointing in PyTorch

混合精度训练

Apex 和 amp

Apex:全称 A Pytorch Extension,一种 Pytorch 的扩展插件,目的是为了用户能够快速实现 amp。

amp(auto mixed precision):自动半精度,是单精度 float 和半精度 float16 的混合,出自论文《MIXED PRECISION TRAINING》。

优点

与单精度 float(32 bit,4个字节)相比,半精度 float16 仅有 16bit,2个字节组成,需要的存储空间是 float 的一半。显存占用更少,训练的时候可以使用更大的 batchsize;

训练速度更快,因为在 NVIDIA 比较新版本的 GPU 上都搭载了 tensor core。tensor core 是专门为这种操作设计:将两个 fp16 的矩阵相乘,再将结果加在一个 fp16 或 fp32 的矩阵上。模型进行前向计算、反向传播以及参数更新时,都会用到乘法和加法的运算。

tensor core 和 CUDA core 的区别可以简单理解为:前者每个 GPU 时钟执行一次矩阵乘法,后者每个 GPU 时钟执行一次值乘法。

缺点

溢出错误。float16 最大范围是 [-65504,-66504],float16 能表示的精度范围是2的-24次方,超过这个数值的数字会被直接置0。对于深度学习而言,最大的问题在于 Underflow(下溢出)。在训练后期,例如激活函数的梯度会非常小, 甚至在梯度乘以学习率后,值会更加小。

舍入误差。当梯度过小,小于当前区间内的最小间隔时,该次梯度更新可能会失败。

解决办法

对于这两个缺点的解决办法:混合精度训练+动态损失放大。

混合精度训练。对 weights、activations、gradients 等在前向计算和反向传播时适合采用 fp16 的操作,其存储和计算都使用 fp16,其余的用 fp32 进行计算,计算之后依然采用 fp16 进行存储,同时拷贝一份 fp32 的 weights 用于参数更新。

在论文中还提到一个『计算精度』的问题:在某些模型中,fp16 矩阵乘法的过程中,需要利用 fp32 来进行矩阵乘法中间的累加(accumulated),然后再将 fp32 的值转化为 fp16 进行存储。 换句不太严谨的话来说,也就是利用 fp16 进行乘法和存储,利用 fp32 来进行加法计算。 这么做的原因主要是为了减少加法过程中的舍入误差,保证精度不损失。

pytorch混合精度训练_cdknight_happy的博客-优快云博客_scaler.unscale_​​​

(受不了 优快云 的水印🤬🤬🤬)

在参数更新时,其公式为:权重 = 旧权重 + lr * 梯度,而在深度模型中 lr * 梯度 这个值往往是非常小的。如果也使用 fp16 来进行相加的话, 则很可能会出现上面所说的 舍入误差 这个问题,导致更新无效,所以需要拷贝一份 fp32 的 weights。

适合采用 fp16 的一些操作:

适合采用 fp32 的一些操作:

动态损失放大(Loss Scale)。是为了解决 溢出错误,训练到了后期梯度(特别是激活函数平滑段的梯度)会特别小,fp16 表示容易产生下溢现象。为了解决梯度过小的问题,对计算出来的 loss 值进行 scale。

由于链式法则的存在,loss 上的 scale 也会作用在梯度上,这样比起对每个梯度进行 scale 更加划算。

scale 过后的梯度,就会平移到 fp16 有效的展示范围内。这样,scaled-gradient 就可以一直使用 fp16 进行存储了。只有在进行更新的时候,才会将 scaled-gradient 转化为 fp32,同时将 scale 抹去。

混合精度训练的两种方式

NVIDIA apex

from apex import amp
model, optimizer = amp.initialize(model, optimizer, opt_level="O1") # 这里是“欧一”,不是“零一”
with amp.scale_loss(loss, optimizer) as scaled_loss:
    scaled_loss.backward()#梯度自动缩放
optimizer.step()

其中只有一个 opt_level 需要用户自行配置:

  • o0:纯FP32训练,可以作为 accuracy 的 baseline;
  • o1:混合精度训练(推荐使用),根据黑白名单自动决定使用 fp16(GEMM,卷积)还是 fp32(Softmax)进行计算。
  • o2:几乎都是 fp16混合精度训练,不存在黑白名单,除了 Batch norm,几乎都是用 fp16 计算。
  • o3:纯 fp16 训练,很不稳定,但是可以作为 speed 的 baseline;

torch 原生支持的amp

from torch.cuda.amp import autocast as autocast, GradScaler

scaler = GradScaler()

        ...
        # 前向过程为 model 和 loss 开启 autocast
        with autocast():
            output = model(input)
            loss = loss_fn(output, target)
        # Scales loss,这是因为半精度的数值范围有限,因此需要用它放大
        scaler.scale(loss).backward()
        # unscale之前放大后的梯度,但是scale太多可能出现inf或NaN,故其会判断是否出现了inf/NaN
        # 如果梯度的值不是 infs 或者 NaNs, 那么调用optimizer.step()来更新权重,
        # 如果检测到出现了inf或者NaN,就跳过这次梯度更新,同时动态调整scaler的大小
        scaler.step(optimizer)
        # 查看是否要更新scaler,这个要注意不能丢
        scaler.update()

每个模型 scale 的程度是不确定的,同时训练前期的梯度比训练后期的梯度要大得多。如果 scale 不合适的话,又可能导致 Overflow。

PyTorch 使用 exponential backoff 来解决这个问题,scale 在刚开始的时候为2的16次方(下面代码中的 init_scale=65536.0),之后每隔一段时间加倍,直到梯度上溢出现 inf(scaler.scale(loss).backward() 检查是否上溢)。此时这个 batch 不更新参数(scaler.step(optimizer) 抛弃这个 batch,不更新参数),将 scale 减半。这里的 scale 用 GradScaler() 实现,这些参数都是默认的:

torch.cuda.amp.GradScaler(
    init_scale=65536.0, growth_factor=2.0, backoff_factor=0.5,
    growth_interval=2000, enabled=True
)

pytorch原生支持的apex混合精度和nvidia apex混合精度AMP技术加速模型训练效果对比_colourmind的博客-优快云博客_apex.amp
【PyTorch】唯快不破:基于Apex的混合精度加速 - 知乎
A developer-friendly guide to mixed precision training with PyTorch
浅谈混合精度训练 - 知乎 

Dsitributed training

分为 Data parallelization 和 Model parallelization。前者的做法是为每个 GPU 分配当前 model 的副本和当前 batch data 的一个 slice(DP),每个 GPU 独立完成前向计算和反向传播,计算出梯度后将各 GPU 的梯度相加求平均,用平均的梯度更新参数(DP)。后者是为每个 GPU 分配当前 model 的一个 slice,也就是其中的一些层。

前者更容易实现,因为不需要解剖模型结构,这里主要关注前者,API 为 DistributedDataParalled(DDP) 和 DataParallel(DP)。

先说区别:

  • DataParallel 采用单进程多线程的方法实现(所以由于 GIL 锁的限制,性能不如 DistributedDataParalled),参数更新算法采用 Parameter Server,只能在单机上用,不支持混合精度训练;
  • DistributedDataParalled 采用多进程的方法实现,参数更新算法采用 Ring AllReduce,可以在多机上用,支持混合精度训练,需要注意 BN 层的数据同步。

参数更新算法

常用的两种参数同步的算法:Parameter Server 和 Ring AllReduce。假设有5张 GPU:

  • Parameter Server

需要将一个 GPU 设置为 Reducer,Reducer 将 data batch 分成五份分到各个卡上,每张卡负责自己的那一份 slice 的训练(有效 batch size = batch size)。反向传播得到梯度返回给 Reducer 上做累积,更新完权重后再分发给各个卡。

缺点是有很严重的木桶效应;负载不均衡,Reducer 需要额外的显存空间来存储各个 GPU 的梯度。

  • Ring AllReduce

5张卡以环形相连,每张卡都有一份模型的副本和整个 batch size(有效 batch size = batch size*5)。每张卡都有左手卡和右手卡,一个负责接收,一个负责发送,循环4次完成梯度累积(Scatter Reduce),再循环4次做参数同步(All Gather)。

Scatter Reduce 步骤中,GPU 将交换数据,使每个 GPU 都得到最终结果的一个块。这个步骤与梯度的反向传播一同进行,来缓解通信时的 bottleneck。

在 All Gather 步骤中,各 GPU 将交换这些块,以便所有 GPU 得到完整的最终结果。

Each GPU does its own forward pass, and then the gradients are all-reduced across the GPUs. Gradients for each layer do not depend on previous layers, so the gradient all-reduce is calculated concurrently with the backwards pass to futher alleviate the networking bottleneck. At the end of the backwards pass, every node has the averaged gradients, ensuring that the model weights stay synchronized.

分布式训练 — 理论基础_love1005lin的博客-优快云博客_分布式训练

Distributed data parallel training using Pytorch on AWS | Telesens

DistributedDataParalled

有三种不同的实现:Open MPI,NVIDIA NCCL 和 Facebook Gloo。

下面介绍的方法只能用在单机多卡的情况下,假设有4块 GPU:

根据官网的介绍,如果使用 CPU 进行分布式计算,建议使用 Gloo,如果使用 GPU 进行分布式计算,建议使用 NCCL,不推荐多 GPU 时使用 MPI。这些都是 dist.init_process_group() 的第一个 backend(后端)参数可以用的方法。

进程初始化。先看代码:

import torch
import torch.distributed as dist
import torch.multiprocessing as mp

def init_process(rank, size, backend='gloo'):
    """ Initialize the distributed environment. """
    os.environ['MASTER_ADDR'] = '127.0.0.1'
    os.environ['MASTER_PORT'] = '29500'
    dist.init_process_group(backend, rank=rank, world_size=size) # init_method 默认为 “env://”

def train(rank, num_epochs, world_size):
    init_process(rank, world_size)
    print(
        f"Rank {rank + 1}/{world_size} process initialized.\n"
    )
    # rest of the training script goes here!

WORLD_SIZE = torch.cuda.device_count()
if __name__=="__main__":
    mp.spawn(
        train, args=(NUM_EPOCHS, WORLD_SIZE),
        nprocs=WORLD_SIZE, join=True
    )

world_size 是进程数,rank 是当前进程的顺序数。torch.cuda.device_count() 会统计出当前所有可用的 GPU 数为4,所以 mp.spawn 会引出4个不同的进程,默认情况下 rank0 是主进程。

dist.init_process_group() 用于初始化进程及 GPU 通信方式(Gloo)和参数的获取方式(“env://” 表示通过环境变量),建立进程间的通信,当完成该函数时进程被销毁。

os.environ['MASTER_PORT'] = '29500'  是进程间通信的端口,os.environ['MASTER_ADDR'] = '127.0.0.1' 是多机间通信的 IP,这两个均是通过环境变量设置的。

多机多卡间的通信要复杂一些,除了设置端口外还需要设置 IP 地址。

数据/ IO 操作。任何数据下载和 IO 的操作都必须隔离在主进程上:

def tarin( ...):
    ...
    if rank == 0:
        downloading_dataset()
        downloading_model_weights()
    dist.barrier()
    print(
        f"Rank {rank + 1}/{world_size} training process passed data download barrier.\n"
    )

在完成下载操作前,dist.barrier() 阻止其他进程继续向下执行。我们需要保证每张 GPU 上分到的都是不同的数据,这里需要用到 DistributedSampler。rank 和 num_replicas 参数也可以不设置,源码中会自动获取。

dataset = PascalVOCSegmentationDataset()
sampler = DistributedSampler(
    dataset, rank=rank, num_replicas=world_size, shuffle=True
)
dataloader = DataLoader(
    dataset, batch_size=8, sampler=sampler
)

之后将 DistributedSampler 送到 DataLoader 里,此时有效的 batch size 是4*8=32。

当需要保存模型的参数时,也需要主进程来完成:

if rank == 0:
    if not os.path.exists('/spell/checkpoints/'):
        os.mkdir('/spell/checkpoints/')
        torch.save(
            model.state_dict(),
            f'/spell/checkpoints/model_{epoch}.pth'
        )

将 tensor 移至 GPU。代码如下:

def train( ...):
    ...
    model.cuda(rank)
    for i, (batch, target) in enumerate(dataloader):
        batch = batch.cuda(rank)
        target = target.cuda(rank)

固定种子。每个进程上的种子应该是相同的,否则会计算出错误的梯度导致不能收敛。

seed = 1234
np.random.seed(seed)
random.seed(seed)
torch.manual_seed(seed)
torch.set_rng_state(torch.manual_seed(seed).get_state())
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

完成上述步骤后,我们就可以调用 DistributedDataParalled 来进行单机多卡训练:

model = DistributedDataParallel(model, device_ids=[rank])

同步 BN。统计多卡数据上的均值和方差,来获得更好的估计。

model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model)

因为用了 mp.spawn(),所以可以在命令行直接使用 python .py 来执行程序。

总结一下,DistributedDataParalled 的步骤为:

  • 固定种子,设置通信端口和 IP,使用 torch.distributed.init_process_group() 初始化进程组。
  • 使用 torch.utils.data.distributed.DistributedSampler() 创建 DataLoader。
  • 使用 torch.nn.parallel.DistributedDataParallel() 创建分布式模型,同步 BN。
  • 使用 torch.distributed.launch /直接 python .py 开始训练。

没有用 mp.spawn() 的话,就需要 python -m torch.distributed.launch 的方法来启动训练:分布式训练 - 单机多卡(DP和DDP)_love1005lin的博客-优快云博客_单机多卡

DataParallel

只需要一行代码:

model = nn.DataParallel(model.to(device), device_ids=gpus, output_device=gpus[0])
  • divice_ids:参与训练的 GPU,不设置将默认采用所有 GPU。
  • output_device:用于汇总梯度的 GPU 是哪个,默认为 GPU0。
import torch
import torch.distributed as dist

gpus = [0, 1, 2, 3]
torch.cuda.set_device('cuda:{}'.format(gpus[0]))

train_dataset = ...

train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=...)

model = ...
model = nn.DataParallel(model.to(device), device_ids=gpus, output_device=gpus[0])

optimizer = optim.SGD(model.parameters())

for epoch in range(100):
   for batch_idx, (data, target) in enumerate(train_loader):
      images = images.cuda(non_blocking=True)
      target = target.cuda(non_blocking=True)
      ...
      output = model(images)
      loss = criterion(output, target)
      ...
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()

Distributed model training in PyTorch using DistributedDataParallel

多机多卡

分布式训练 - 多机多卡 (DDP)_love1005lin的博客-优快云博客_nproc_per_node

Distributed data parallel training in Pytorch

PyTorch底层运行机制全景解析:从tensor内存到autograd计算图 PyTorch不是API集合,而是一套以tensor为基石、autograd为引擎、nn为封装、dataloader为输入管道、distributed为扩展层的动态运行时系统。其核心在于Python原生控制与CUDA底层协同——tensor本质是带元数据的内存地址,autograd通过拓扑排序实现反向传播,而torch.compile则在FX图层面融合算子优化性能。这种设计赋予研究者极致调试自由,但也要求开发者理解显存分配、梯度累加、多进程数据加载等真实工程约束。本文聚焦tensor内存布局、autogra 阅读详情

相关推荐

揭秘 PyTorch 动态计算图与反向传播底层逻辑

你知道 PyTorch 动态计算图与反向传播机制的底层逻辑吗?本文围绕此展开,介绍核心属性与原理,用 NumPy 简化模拟,还有测试案例,助你深入理解,为 AI 学习奠基,想了解吗?

weixin_73350076的博客 1239

全连接神经网络——计算图

计算图是一种有向图,用来表达输入、输出以及中间变量之间的计算关系,图中每一个结点代表一种数学运算。例如求函数的计算图,那么该计算图如下所示:不难发现,该图是一个有向图,其中结点包含运算符,边代表着运算结果。

weixin_73371876的博客 946

深度之眼Pytorch打卡(五):Pytorch计算图(动态图与静态图)与自动求导tensor.backgrad()

grpcio, markdown, pyasn1, rsa, pyasn1-modules, cachetools, google-auth, tensorboard-plugin-wit, urllib3, idna, chardet, requests, oauthlib, requests-oauthlib, google-auth-oauthlib, absl-py, werkzeug,...

意疏的学习笔记 7898

Tensor (张量) - 神经网络中的数据结构

Tensor (张量) - 神经网络中的数据结构

程永强 3166

从RuntimeError到detach():理解PyTorch计算图与Tensor的梯度分离

本文深入解析PyTorch中常见的RuntimeError问题,重点讲解Tensor的梯度分离机制与detach()方法的应用。通过计算图原理、梯度属性分析及实际代码示例,帮助开发者理解如何正确处理需要梯度的Tensor转换问题,提升深度学习项目的开发效率。

weixin_27310985的博客 195

ICT调试技巧:从基础到实战的全面指南

本文提供了一份全面的ICT调试技巧指南,涵盖从基础认知到实战应用的全过程。详细解析了ICT设备硬件结构、程序文件构成,并重点讲解了调试界面的核心参数与高效快捷键。通过跳线、电阻、电容及二极管等常见元器件的实战调试案例,分享了参数设置要点与典型问题解决方案,帮助工程师快速掌握ICT调试核心技能,提升测试效率与准确性。

weixin_29202213的博客 342

神经网络与深度学习——神经网络基础——计算图

2.5导数和2.6更多导数的例子在大一的时候学过,这里不做赘述 神经网络的计算都是按照前向或反向传播来计算的,首先通过前向传播计算出预测值以及成本函数,再通过反向传播,计算出梯度或导数,用计算图表示如下 以简单式子J(a,b,c)=3(a+bc)为例 根据每个值的取值,从前到后计算出J的值 ...

kazuhura的博客 1285

硬件工程师的蜗牛成长路

1、《模拟电路课程》R/L/C/继电器/光耦/二极管/三极管/MOS管磁珠/ESD/TVS/肖特基二极管/稳压二极管/保险丝/GDT/TSS/光耦/MOS/运放/BJT比较器/面板开关/按键开关/拨码开关/BUCK/LDO/电源防护器件LM5069单输出PLL/多路输出PLL/单端晶振/差分晶振/晶体(这方面不熟悉,待以后补充)1)速度传感器2)温度传感器3)倾斜角传感器4)火焰传感器5)红外传感器6)呼吸心跳采集chaunganqi1)数字隔离 电平转换芯片/磁隔离/专用隔离芯片。

caozhaokun的博客 1978

神经网络之计算图

计算图是一种有向无环图(DAG, Directed Acyclic Graph),用于表示计算过程。图中的节点表示操作(函数或运算),边表示数据(张量)在操作之间的流动。优点描述高效支持大规模并行计算、自动微分灵活支持复杂网络结构(循环、残差等)可扩展各种深度学习框架都基于它构建自动化免去手动推导导数的烦恼。

无风听海 695

Win10家庭版如何添加本地用户和组

在众多Windows版本中,Windows 10以其丰富的功能和卓越的性能吸引了大量用户。然而,对于使用Win10家庭版的用户来说,他们可能会遇到一些功能上的限制,特别是在用户和组管理方面。虽然家庭版在某些高级功能上有所缩减,但我们仍然可以通过一些技巧来实现对本地用户和组的管理。本文将详细介绍如何在Win10家庭版中添加本地用户和组,以便用户能够更好地管理计算机资源。

nntxthml的博客 4555

PPT高清导出秘籍:如何轻松设置600dpi图片分辨率

本文详细解析了PPT导出图片模糊的根本原因,并提供了通过修改Windows注册表将导出分辨率从默认的96dpi提升至600dpi的完整教程。该方法能显著提升图片清晰度,满足学术出版与高质量印刷需求,文中还包含效果对比、进阶设置及常见问题解答。

a0b1c2d3的专栏 1313

Tensor、计算图与训练方法:神经网络三大核心概念解析

深度学习模型训练中,张量如何组织数据、计算图如何支撑自动求导、损失函数与优化器如何协同更新参数,是决定模型能否收敛的底层机制。从图像分类到推荐系统,几乎所有神经网络项目都围绕这三者展开。理解Tensor的维度语义与形状变换规则,能快速定位维度不匹配、数据打乱等常见报错;掌握计算图的前向传播与反向传播原理,才能真正看懂梯度来源;而损失函数、优化器、学习率与正则化的配合,则决定了训练是否稳定、能否泛化。本文从工程实践角度系统梳理这些基础概念,帮助开发者把调参从碰运气变成有依据的诊断过程,覆盖图像、文本等常见任务

weixin_29061821的博客 212

GEE进行Theil-Sen Median斜率估计和Mann-Kendall检验

Python实现Theil-Sen Median斜率估计和Mann-Kendall检验// 定义时间范围//NDVI图像集合// 定义每年的开始和结束日期// 加载NDVI数据集,按日期过滤,并计算最大值.max()}));// 计算Sen斜率并剪裁// 可视化Sen斜率Map.addLayer(senSlope.select('slope'), vis, "Sen斜率 ");// MK检验// 创建一个列表,包含年度NDVI图像的list。

qq_44894692的博客 965

PyTorch深度解析:Tensor——神经网络的核心构建块

具体来说,神经网络的输入数据被转换为Tensor,然后通过一系列的网络层进行前向传播,得到输出Tensor。在训练过程中,根据输出Tensor与真实标签之间的误差,通过反向传播算法计算梯度,并更新网络参数(这些参数也是以Tensor的形式存在的)。本文将深入探讨PyTorch中的Tensor,从其基本定义、特性、操作到实际应用,全面解析其在神经网络中的核心地位。:PyTorch中的Tensor支持多种数据类型,如float32、float64、int32等,以适应不同的计算需求。

huanfeng_AI的博客 1758

kali2020.2安装出现的问题并附详细解决办法

kali2020.2的安装

weixin_48776804的博客 3047

昇腾910安装驱动出错,降低Centos7.6的内核版本

Centos7.6 启动内核修改,解决昇腾910安装驱动错误的问题。

深蓝 3300

tensorflow入门(三)---tensorflow的计算图和tensor

tensorflow程序通常被组织成一个图的构建和图的执行阶段,例如我们搭建一个神经网络,组织各个层及之间关系的过程称为图的构建,然后通过不断反复的执行图中的训练op来逐渐优化参数。在图的构建阶段,就是各种op的拼接组合,op之间流通的tensor是由最初的一个op产生的,它被称为源op,没有输入tensor,只有输出tensor,比如说常量(Constant)就是一个源op: import t

leibaojiangjun1的博客 9016

开源人形机器人硬件生态:关键技术、产业链与低成本突破 | OpenLoong 社区线下分享会

本文将深入探讨人形机器人硬件平台的关键技术、产业链与核心部件以及降低成本的策略。从硬件平台的核心技术,到机器人核心零部件的详细介绍,再到产业链各环节的协同发展以及实现低成本化的创新思路,将一同剖析人形机器人迈向广泛应用所面临的挑战与机遇,展望其未来的发展趋势,为开发者和相关从业者提供一些参考与启示。

OpenLoong_的博客 2419

pytorch计算图Computation_graph是什么

引言:计算图(数据流图)类似于数学中的流程图和数据结构中的图

Stevelu的技术博客 1553

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

神经网络入门:张量、计算图与训练方法全解析

从张量到计算图,再到基于梯度下降的训练方法,这三者是神经网络技术的底层骨架。张量作为数据表示的统一载体,承载着形状、数据类型和设备信息;计算图记录了从输入到输出的全部运算路径,为反向传播提供了物理基础;而训练方法则通过梯度更新参数,决定模型能否真正收敛。在图像分类、自然语言处理等典型应用场景中,无论使用PyTorch还是其他框架,这些概念都是排查维度错误、梯度异常和模型不收敛等问题时的核心线索。通过一个完整的二分类Demo,串联起数据加载、模型构建、损失函数、训练循环和验证评估的完整流程,并给出调参建议和调

weixin_30843605的博客 261

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

PyTorch 神经网络基础:Variable 与 Tensor 的梯度计算

本文介绍了 PyTorch 中 Variable 和 Tensor 的基本使用方法,重点解析了如何在计算中使用它们完成自动求导和梯度计算。文章通过实例展示了从早期的 Variable 到 Tensor 的演进,以及在 PyTorch 中对计算图和自动求导的支持。此外,文中详细解释了张量梯度的计算公式与结果,同时展示了如何从 Variable 和 Tensor 中提取数据,并转换为 NumPy 格式。

敲代码别忘了喝上一杯凉白开。 1419

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Android NDK NN API Basic 示例深度解析:用 C API 构建 (tensor0+tensor1)×(tensor2+tensor3) 计算图

Android Neural Networks API(NN API)是 Android 8.0(API 27)起随 NDK 提供的一套底层推理接口,它允许开发者把训练好的模型以**计算图(graph)**的形式提交给系统,由系统分派到 CPU、GPU 或专用 NPU 等硬件加速器上执行。本篇文章以 ndk-samples 仓库中的 [nn-samples/basic 示例](https://li

gitblog_00249的博客 798

神经网络核心三要素:张量、计算图与训练方法全解析

深度学习模型看似复杂,其核心可以抽象为三件事:数据以张量形式流动,运算由计算图组织,参数通过反向传播与梯度下降更新。张量是对标量、向量、矩阵和高维数组的统一抽象,也是批量并行计算的基础;计算图则显式记录前向运算与梯度路径,让链式法则得以自动执行。理解这些基础概念,不仅能看清网络从输入到损失再到参数收敛的完整过程,也能更有效地诊断梯度消失、过拟合、显存异常等工程问题。无论是图像分类、目标检测还是序列建模,最终都落在张量操作、计算图设计、训练策略三者的协同优化上。掌握这套核心框架,神经网络就不再是黑盒,而是一台

weixin_34267123的博客 369

【深度学习理论】一文搞透pytorch中的tensor、autograd、反向传播和计算图

前言 本文的主要目标: 一遍搞懂反向传播的底层原理,以及其在深度学习框架pytorch中的实现机制。当然一遍搞不定两遍三遍也差不多了~ 这样以后,面试时被问到反向传播、tensor、计算图和autograd的底层原理,你不会慌~当然,我们的主要目的,在于能从底层大致理解整个深度学习框架的设计原理,实现方式,这样在模型训练和优化时,能做到心中有谱!本文主要内容: 1.前向传播、反向传播和计算图 2.pytorch中的Tensor 3.链式求导法则和Jacobian雅克比矩阵 4.pytorch中的A

qimo601的专栏 742

【AI系统】计算图挑战与未来

目前主流的 AI 框架都选择使用来抽象神经网络计算表达,通过通用的数据结构(张量)来理解、表达和执行神经网络模型,通过可以把 AI 系统化的问题形象地表示出来。

ZOMI酱 1222

全连接神经网络之tensor(一)

本文介绍了PyTorch框架的基础知识,重点讲解了Tensor的核心操作。主要内容包括:PyTorch与其他主流框架的对比、Tensor的创建与数据类型、形状操作、广播机制、索引与切片、自动微分原理及梯度控制方法。文章还提供了一个20行代码实现的线性回归实战案例,并总结了常见错误及调试技巧。最后列出了PyTorch官方文档的重要参考链接,适合深度学习初学者快速掌握Tensor的基本使用方法。

Hao_diedie的博客 1027
上一篇: Python中的内置函数与lambda表达式
下一篇: 图像生成——总结
不说话装高手H
博客等级 码龄7年 43粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值