显存优化 Trick(gradient_accumulation、gradient_checkpointing、xformers)

目录

    • Out of Memory
    • Gradient Accumulation
    • Gradient Checkpointing
    • Xformers
    • Diffusers的显存优化

Out of Memory

先来说下OOM问题,其实也是日常会遇到的情况。模型申请的显存超过了设备实际显存大小,则会报错Out of Memory。一般情况下,batch size设置过大,不能匹配自己手里的计算设备(GPU、TPU等)显存时,会经常触发这个问题。

一般遇到这个情况,都会选择调小batch size,但是很多模型本身就非常大(尤其是预训练模型当道的今天),记得19年的时候拿一张1080ti做BERT finetune,11G的显存,batch size最大也就就能设置成4。但是batch size又是很影响训练效果的超参,在很多时候只能在原作者调参得到的那个数值下才能训练出较好的结果。此时,有钱就选择加卡,不然就只能另辟蹊径来磨一磨手里这张小显存的计算卡了。

正常的训练流程:每个step送入1个batch_size的数据计算梯度,然后用这个梯度反向传播更新参数,接着送入下一个batch_step的数据重复。

Gradient Accumulation

梯度累加 gradient_accumulation ,顾名思义,就是将多次计算得到的梯度值进行累加,然后一次性反向传播,进行参数更新。

在这里插入图片描述

如图所示,假设我们有batch size = 256global-batch,在单卡训练显存不足时,将其分为多个小的mini-batch=64(如图分为大小为64的4个mini-batch),每个step送入1个mini-batch获得梯度,将多次获得的梯度进行累加后再更新参数,以次达到模拟单次使用global-batch训练的目的。

在这里插入图片描述

自动微分机制和梯度累加实现
由于不同深度学习框架的自动微分机制不同,所以实现梯度累加的方式有显著差异。当前业界框架的自动微分机制分为两类:

  • 以Tensor为核心的自动微分,Tensor可配置requires_grad参数控制是否需要梯度。每个Tensor对象有grad_fn属性用来存储该Tensor参与的反向操作,同时还有grad属性,存储该Tensor对应的梯度。计算梯度时通过标量loss.backward()实现。由于API接口使用方式很符合反向传播的概念,业界多数框架选择此方案,如PytorchPaddleOneflowMegEngine等。
  • 函数式自动微分。将神经网络正向计算视作输入到Loss的计算函数,通过函数变换获得反向计算函数,并调用反向计算函数来获得梯度。业界采用此方案的框架有Jax、MindSpore,此外Tensorflow的GradientTape本质上也可以视作该方案的变种。

自动微分原理都是一致的,这两种方案的核心差异点在于是否暴露了自动微分更底层的接口,如Pytorch等框架更多定位纯深度学习,此时只体现backward更符合目标用户的使用习惯。而Jax、MindSpore则在定位上更加底层,Jax直接明言自身为数值计算框架,MindSpore则定位为AI+科学计算融合计算框架,因此函数式自动微分设计更符合框架定位。

由于两种方案的差异,也造成了梯度累加实现的差异,下面主要以Pytorch为例,讲一下梯度累加的实现:

# batch accumulation parameter
accum_iter = 4  # loop through enumaretad batches
for batch_idx, (inputs, labels) in enumerate(data_loader):# forward pass preds = model(inputs)loss  = criterion(preds, labels)# scale the loss to the mean of the accumulated batch sizeloss = loss / accum_iter # backward passloss.backward()# weights updateif ((batch_idx + 1) % accum_iter == 0) or (batch_idx + 1 == len(data_loader)):optimizer.step()optimizer.zero_grad()

由于Pytorch本身,在求完梯度后会自动挂载到Tensor.grad属性上,而在没有手动清空(即optimizer.zero_grad())的情况下,每个step训练求得的梯度会自动累加,因此只需要控制梯度清零参数更新的间隔步数即可。

这里着重强调一个位置,loss = loss / accum_iter这一行操作的含义及实现。稍微翻看了一下搜索引擎排名靠前的几篇,发现误导不少。

首先是注释里说明的含义,不知出处但是几乎所有人都备注一句normalize loss to account for batch accumulation,中文译作loss正则化。这个地方显然是越写越偏了。

实际上这里就是做了一次求mean的操作。原因是直接累加的accum_iter次梯度值作为一次参数更新的梯度,是将梯度值放大了accum_iter倍,而Pytorch的参数更新是写在optimizer.step()方法内部,无法手动控制,因此只能根据链式法则,在loss处进行缩放,来达到缩放梯度的目的。与常规理解的正则化没有任何关系。

此外,还有一个谬误的写法:

    loss = criterion(outputs, labels)loss += loss / accumulation_stepsloss.backward()

loss通常不会这样累加,一般会单独维护一个 total_loss, 且累加之后再做loss.backward(),微分结果也是错误的,由左式正确的偏导,变为loss累加和对w求偏导:
在这里插入图片描述

Gradient Checkpointing

当我们采用了分布式训练以及混合精度训练都不能降低显存大小的时候(比如多语言large模型,光词表就有几十万),现有的 GPU 资源无法训练一个设备装不下的模型。下面我们介绍一种改善这个问题的技术:梯度检查点 gradient_checkpointing

简单的说,梯度检查点 gradient_checkpointing 的工作原理是在反向传播时重新计算深度神经网络的中间值(而通常情况是在前向传播时存储的)。 这个策略是用时间(重新计算这些值两次的时间成本)来换空间(提前存储这些值的内存成本)

神经网络使用的总内存基本上是两个部分的总和

  • 第一部分是模型使用的静态内存。尽管 PyTorch 模型中内置了一些固定开销,但总的来说几乎完全由模型权重决定。而如今,在生产中使用的现代深度学习模型的总参数在100万到10亿之间。作为参考,一个带 16GB GPU 内存的 NVIDIA T4 的实际限制大约在1-1.5亿个参数之间。
  • 第二部分是模型的计算图所占用的动态内存。在训练模式下,每次通过神经网络的前向传播都为网络中的每个神经元计算一个激活值,这个值随后被存储在所谓的计算图中。必须为批次中的每个单个训练样本存储一个值,因此数量会迅速的累积起来。总成本取决于模型大小和批处理大小,并设置适用于您的GPU内存的最大批处理大小的限制。

模型的训练需要计算前向传播(forward)来获得预测结果,然后通过反向传播(backward)来计算梯度以进行参数更新。

一开始前向传播forward计算结果时,存储储激活值的原因是,在反向传播backword期间计算梯度时需要用到激活值。

现在,在forward后不存储激活值,在backward需要激活值时重新计算

梯度检查点gradient_checkpointing是如何起作用的

大型模型在静态和动态方面都很耗资源。首先,它们很难适配 GPU,而且哪怕你把它们放到了设备上,也很难训练,因为批处理大小被迫限制的太小而无法收敛。

梯度检查点(gradient checkpointing)的工作原理是从计算图中省略一些激活值(由前向传播产生,其中这里的”一些“是指可以只省略模型中的部分激活值,折中时间和空间,陈天奇在它的论文中Training Deep Nets with Sublinear Memory Cost使用了如下动图的方法,即前向传播的时候存一个节点释放一个节点,空的那个等需要用的时候再backword的时候重新计算)。这减少了计算图使用的内存,降低了总体内存压力(并允许在处理过程中使用更大的批次大小)。

请添加图片描述
PyTorch 通过torch.utils.checkpoint.checkpointtorch.utils.checkpoint.checkpoint_sequential提供梯度检查点,根据官方文档的 notes,它实现了以下功能:在前向传播时,PyTorch 将保存模型中的每个函数的输入元组在反向传播过程中,对于每个函数,输入元组和函数的组合以实时的方式重新计算,插入到每个需要它的函数的梯度公式中,然后丢弃(显存中只保存输入数据和函数)。网络计算开销大致相当于每个样本通过模型前向传播开销的两倍。

梯度检查点算法将模型的动态内存开销从 O ( N ) O(N) O(N) (n为模型中的层数)降低到 O ( N ) O(\sqrt{N}) O(N ) ,并通过实验展示了将 ImageNet 的一个变种从 48GB 压缩到了 7GB 内存占用。

import torch 
import torch.nn as nn
from torch.autograd import Variable
from torch.utils.checkpoint import checkpointclass in_conv(nn.Module):def __init__(self, in_ch, out_ch):super(in_conv, self).__init__()self.op = nn.Sequential(nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1),nn.BatchNorm2d(out_ch),nn.ReLU(inplace=True),nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1),nn.BatchNorm2d(out_ch),nn.ReLU(inplace=True))def forward(self, x):x = self.op(x)return xclass conv3x3(nn.Module):def __init__(self, in_ch, out_ch):super(conv3x3, self).__init__()self.op = nn.Sequential(nn.Conv2d(in_ch,out_ch, kernel_size=3, padding=1),nn.BatchNorm2d(out_ch),nn.ReLU(inplace=True),nn.Conv2d(out_ch,out_ch, kernel_size=3, padding=1),nn.BatchNorm2d(out_ch),nn.ReLU(inplace=True))def forward(self, x):x = self.op(x)return xclass down_block(nn.Module):def __init__(self, in_ch, out_ch):super(down_block, self).__init__()self.pool = nn.MaxPool2d(2, stride=2)self.conv = conv3x3(in_ch, out_ch)def forward(self, x):x = self.pool(x)x = self.conv(x)return xclass up_block(nn.Module):def __init__(self, in_ch, out_ch, residual=False):super(up_block, self).__init__()self.up = nn.ConvTranspose2d(in_ch, in_ch // 2, kernel_size=2, stride=2)self.conv = conv3x3(in_ch, out_ch)def forward(self, x1, x2):x1 = self.up(x1)diffY = x2.size()[2] - x1.size()[2]diffX = x2.size()[3] - x1.size()[3]x1 = F.pad(x1, (diffX // 2, diffX - diffX // 2,diffY // 2, diffY - diffY // 2))x = torch.cat([x2, x1], dim=1)x = self.conv(x)return xclass out_conv(nn.Module):def __init__(self, in_ch, out_ch):super(out_conv, self).__init__()self.conv = nn.Conv2d(in_ch, out_ch, kernel_size=1)def forward(self, x):x = self.conv(x)return xclass UNet(nn.Module):def __init__(self, img_channels, n_classes,use_checkpoint=False):super(UNet, self).__init__()self.inc = in_conv(img_channels,64)self.down1 = down_block(64, 128)self.down2 = down_block(128, 256)self.down3 = down_block(256, 512)self.down4 = down_block(512, 1024)self.up1 = up_block(1024, 512)self.up2 = up_block(512, 256)self.up3 = up_block(256, 128)self.up4 = up_block(128, 64)self.outc = out_conv(64, 1)def forward(self, x):def forward(self, x):x = Variable(x,requires_grad=True) if self.use_checkpoint:x1 = checkpoint(self.inc,x)x2 = checkpoint(self.down1,x1)x3 = checkpoint(self.down2,x2)x4 = checkpoint(self.down3,x3)x5 = checkpoint(self.down4,x4)x = checkpoint(self.up1,x5,x4)x = checkpoint(self.up2,x,x3)x = checkpoint(self.up3,x,x2)x = checkpoint(self.up4,x,x1)x = checkpoint(self.outc,x)else:x1 = self.inc(x)x2 = self.down1(x1)x3 = self.down2(x2)x4 = self.down3(x3)x5 = self.down4(x4)x = self.up1(x5, x4)x = self.up2(x, x3)x = self.up3(x, x2)x = self.up4(x, x1)x = self.outc(x)return x

注意第94行,必须确保checkpoint的输入输出都声明为require_grad=True的Variable,否则运行时会报如下的错
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

网络训练高效内存管理——torch.utils.checkpoint的使用

torch.utils.checkpoint 简介 和 简易使用

此外,HuggingFace Transformers 也支持 Gradient Checkpoint。 梯度检查点可以通过 PreTrainedModel 实例的 gradient_checkpointing_enable 方法执行。

from transformers import AutoConfig, AutoModel# https://github.com/huggingface/transformers/issues/9919
from torch.utils.checkpoint import checkpoint# initializing model
model_path = "microsoft/deberta-v3-base"
config = AutoConfig.from_pretrained(model_path)
model = AutoModel.from_pretrained(model_path, config=config)# gradient checkpointing
if model.supports_gradient_checkpointing:model.gradient_checkpointing_enable()print(f"Gradient Checkpointing: {model.is_gradient_checkpointing}")

更进一步,看看HF是如何在Bert模型内部实现梯度检查点的

modeling_bert.py中,定义了BertEncoder,其中forward函数内部判断是否开启了梯度检查点,并调用torch.utils.checkpoint.checkpoint实现梯度检查点

class BertEncoder(nn.Module):def forward(args):...for i, layer_module in enumerate(self.layer):...if self.gradient_checkpointing and self.training:if use_cache:logger.warning("`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`...")use_cache = Falsedef create_custom_forward(module):def custom_forward(*inputs):return module(*inputs, past_key_value, output_attentions)return custom_forwardlayer_outputs = torch.utils.checkpoint.checkpoint(create_custom_forward(layer_module),hidden_states,attention_mask,layer_head_mask,encoder_hidden_states,encoder_attention_mask,)else:layer_outputs = layer_module(hidden_states,attention_mask,layer_head_mask,encoder_hidden_states,encoder_attention_mask,past_key_value,output_attentions,)...

Xformers

xformers仅适用于N卡,用于推理和训练中,在attention block中执行优化,可以提高速度并减少内存消耗。在图像生成领域,特点是加速图片生成并降低显存占用,代价是输出图像不稳定,有可能比不开Xformers略差。

self-attention在Transformer中发挥着重要的作用,但在实际应用中也面临着两个挑战:

(1) complexity:self-attention的时间复杂度是 O ( T 2 ⋅ D ) O(T^2·D) O(T2D),在处理长序列问题上存在较大瓶颈。

(2) structural prior:self-attention没有对输入做输入做任何结构偏差的假设,因此对于小数据集可能存在过拟合现象。

xformers在Vanilla Transformer(普通transformer)的self-attention基础上进行改进:

  • Sparse attention :在计算attention matrix时不会attend 每个token,而是仅对部分sparse connection的位置计算。根据确定sparse connection的方法又可以细分为 position-based 和 content-based 两种。

    • Position-based Sparse Attention
      在这里插入图片描述

      • Global Attention:为了缓解稀疏注意力在中长距离依赖关系上模型能力的退化,这一类模式增加了一些全局节点作为节点间信息传播的枢纽。这些全局节点可以关注序列中的所有节点,并且整个序列也都会关注这些全局节点。
      • Band Attention:这一类attention 也可以成为局部attention或者滑动窗口attention,设计的主要思路在于,由于大部分的数据都有很强的局部关系特性,因此可以限制query只关注附近的一些节点从而做到稀疏化的效果。
      • Dilated Attention:这种attention的方法与dilated CNN的方法十分相似,可以在不增加计算复杂度的情况下扩大感受野的大小,并且通过调整dilation 的间距可以扩展至strided attention.
      • Random Attention:为了提高非局部位置之间的联系,每个query随机的选择一些位置去attend.
      • Block Local Attention:将整个序列划分为几个没有重叠部分的block,每个block内部之间做attention.
    • Content-based sparse attention

      • Reformer:Reformer使用了Locality-sensitive hashing(LSH) 对每个query选择对应的key-value对。
      • Route Transformer:相比于reformer,该方法采用了k-means聚类的方法对query和key进行聚类,每个query 只attend 属于同一类cluster的keys. 聚类中心的向量根据被赋予向量的指数移动平均值来计算,同时除以cluster中数目的移动平均值。
  • Linearized attention
    在这里插入图片描述
    这里可将原始的指数内积的计算形式替换为核函数,从而提出新的计算方式,
    在这里插入图片描述
    对于自回归的attention而言,上式中的累积和项可以通过上一个时间步的结果计算叠加而来,因此对于transformer的decoder来说,整个计算过程类似于RNN的计算过程。
    在Linear Transformer中,linear map采用了一种简单的计算方法,
    在这里插入图片描述
    这种feature map的目的不是为了近似内积attention,但是通过实验证明它和标准的Transformer的结果表象相当。

  • Query Prototyping和memory Compression:除了使用sparse attention或者核函数的linearized attention外,另一个改进的思路就是减少query和key-value对的数量。

    • Query Prototyping:如 Informer,编码器接收大量的长序列输入(绿色序列)。采用ProbSparse attention 来代替常规的self-attention。蓝色梯形是自注意力的蒸馏操作来提取主要的attention,大大减小了网络规模。层叠加复制副本的操作增加了鲁棒性。解码器接收长序列输入,将target中元素填充为零,根据特征图的加权注意力组成,立即以生成方式预测输出元素(橙色系列),从而使得Transformer能够更有效的处理长序列数据。
      在这里插入图片描述
      在这里插入图片描述
    • Attention with Compressed Key-Value Memory:相比于减少query数量的方法,这一类方法的主要特点在于通过减少key-value对的数量来减少复杂度。
      • Memory Compressed Attention(MCA):通过跨步卷积的方法来减少key和value的数量,减少的大小和kernel size k的数值有关,这种方法相比于之前提到局部注意力而言,增加了对全局上下文的捕捉。
      • Linformer:利用线性投影将键和值从长度n投射到一个更小的长度的nk。这也将self attention的复杂性降低到线性。这种方法的缺点是必须假定输入序列的长度,因此不能用于自回归的问题上。
      • PoolingFormer:将原始的全注意力机制修改为一个两级注意力机制:第一级采用滑动窗口注意力机制,限制每个词只关注近距离的邻居;第二级采用池化注意力机制,采用更大的窗口来增加每个token 的感受野,同时利用池化操作来压缩键和值向量,以减少要参加注意力运算的token数量。
        在这里插入图片描述
  • 多头机制的改进:多头注意力的一大优势在于它能够共同关注来自不同子空间的不同位置的信息。然而,目前还没有一种机制可以保证不同的注意头确实捕捉了不同的特征。
    在这里插入图片描述

除此之外还有很多对Transformer的改进都集成进了xformers。xFormers 软件包需要最新版本的 PyTorch。如果需要使用以前版本的 PyTorch,那么我建议 从github souce 安装 xFormers。

xFormers安装后,diffusers的unet可以使用enable_xformers_memory_efficient_attention() 为了更快的推理和减少内存消耗:

if enable_xformers_memory_efficient_attention:if is_xformers_available():unet.enable_xformers_memory_efficient_attention()else:raise ValueError("xformers is not available. Make sure it is installed correctly")

PyTorch 2.0相比1.12改变了很多底层实现,不向后兼容。所以不能在PyTorch 2.0上使用xFormers。但是可以使用这个机制:GitHub - Dao-AILab/flash-attention: Fast and memory-efficient exact attention

更具体的直接使用xformers实现attention:

import torch
from xformers.ops import memory_efficient_attention, LowerTriangularMaskdevice='cuda'
batch = 4
n_head = 8
head_dim = 16
seq_len = 128q = torch.rand(batch, seq_len, n_head, head_dim).to(device)
k = torch.rand(batch, seq_len, n_head, head_dim).to(device)
v = torch.rand(batch, seq_len, n_head, head_dim).to(device)# 使用 causal 偏置掩码
o = memory_efficient_attention(q, k, v, LowerTriangularMask())# 不使用任何偏置掩码
o = memory_efficient_attention(q, k, v)

memory_efficient_attention 的等效pytorch代码实现,用于模型导出。注:LowerTriangularMask 之类的掩码请另外手动生成一个等效的 attn_bias Tensor 再用于本函数

# 使用自定义的偏置掩码 attn_bias,要求 xformers 版本 大于等于 0.17
## 这里的 from_len,to_len 分别代表Decoder的序列长度,Encoder的序列长度
from_len = seq_len
to_len = seq_len
attn_bias = torch.rand(batch, n_head, from_len, to_len).to(device)
o = memory_efficient_attention(q, k, v, attn_bias)
import torch.nn.functional as Fdef memory_efficient_attention_pytorch(query, key, value, attn_bias=None, p=0., scale=None):# query     [batch, seq_len, n_head, head_dim]# key       [batch, seq_len, n_head, head_dim]# value     [batch, seq_len, n_head, head_dim]# attn_bias [batch, n_head, seq_len, seq_len]if scale is None:scale = 1 / query.shape[-1] ** 0.5    # BLHC -> BHLCquery = query.transpose(1, 2)key = key.transpose(1, 2)value = value.transpose(1, 2)# scale queryquery = query * scale# BHLC @ BHCL -> BHLLattn = query @ key.transpose(-2, -1)if attn_bias is not None:attn = attn + attn_biasattn = attn.softmax(-1)attn = F.dropout(attn, p)# BHLL @ BHLC -> BHLCout = attn @ value# BHLC -> BLHCout = out.transpose(1, 2)return out

Diffusers的显存优化

  • vae.enable_vae_slicing():将按顺序对每个prompt对应的隐变量进行解码,而不是同时解码整个batch。可以大大减少GPU内存使用,支持更大的batch size。需要 trade-off 推理时间和显存需求。

  • vae.enable_vae_tiling():在有限VRAM的情况下处理大尺寸图像。例如,在8GB VRAM中生成4K图像。分块VAE解码器将图像分割成重叠的块,对每个块解码,最后将输出混合生成最终图像。输出图像由于块解码器独立,会有一些块间色调变化,但不会出现明显的块间裂缝。512x512或以下大小的图像不会进行分块。分块VAE适用于大尺寸图像的生成,可以突破VRAM限制。需要 trade-off patch的调色差异和显存需求。

  • pipeline.enable_sequential_cpu_offload():为进一步节省内存,底层使用accelerate,可以将权重offload到CPU,仅在执行前向传播时加载到GPU。注意,此方法在子模块级别工作,而不是整个模型级别(如每次卸载Unet的一个Conv层)。这是最小化内存消耗的最佳方式,但由于过程的迭代特性,推理速度会大大降低。管道的UNet组件会运行多次(和num_inference_steps一样多次);每次UNet的不同子模块会按需要顺序地在CPU和GPU之间加载和卸载,所以内存传输的次数非常大。

  • pipeline.enable_model_cpu_offload():顺序CPU卸载可以节省大量内存,但会降低推理速度,因为子模块在需要时移动到GPU,在新模块运行时立即返回CPU。全model卸载是sequential卸载另一种替代方案,它按整个模型,而不是每个模型的组成模块来移动到GPU(如每次卸载整个Unet)。这对推理时间几乎没有影响,在这种场景下,pipeline的主要组件(通常是:文本编码器、UNet和VAE)中只有一个处于GPU,其他组件等在CPU。像UNet这样运行多次迭代的组件会一直留在GPU,直到不再需要为止。

import torch
from diffusers import StableDiffusionPipelinepipe = StableDiffusionPipeline.from_pretrained("/data1/huggingface/StableDiffusion/stable-diffusion-v1-5", torch_dtype=torch.float16
)
pipe = pipe.to("cuda")
pipe.enable_model_cpu_offload()  # new diffusers
pipe.unet.enable_xformers_memory_efficient_attention()
pipe.enable_vae_slicing()
pipe.enable_vae_tiling()  # new diffusers
pipe.enable_attention_slicing()prompt = "a photo of an astronaut riding a horse on mars"
images = pipe([prompt] * 32).images  # sample 32 images

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

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

相关文章

C++-引用和指针区别

文章目录 1.变量的组成2.指针2.1 定义2.2 使用指针操作变量2.3 为什么使用指针 3.引用3.1 定义3.2 引用注意事项 4.引用和指针的区别 1.变量的组成 变量的组成:变量地址,变量名,变量值 例: int i 12;2.指针 2.1 定义 指针用于存…

java--Calendar

1.Calendar ①代表的是系统此刻时间对应的日历 ②通过它可以单独获取、修改时间中的年、月、日、时、分、秒等(月份是从0开始的)。 2.Calender日历类的常见方法 注意:calender是可变对象,一旦修改后其对象本身表示的时间将产生变化。

Vue3-02-ref() 响应式详解

ref() 是什么 ref() 是一个函数; ref() 函数用来声明响应式的状态(就是来声明变量的) ref() 函数声明的变量,是响应式的,变量的值改变之后,页面中会自动重新渲染。ref() 有什么特点 1.ref() 可以声明基础…

Idea 插件开发: Swing Designer设计器创建的组件全部为空问题记录

问题现象 通过Swing 设计器创建的对象, Swing组件全部是空的, 导致ToolWindowFactory工厂的实现类调用时候出现了空指针异常 如下方式创建的 问题分析 问题出现时候, 同时给我生成了一个createUIComponents的私有方法, 由于个人当时理解有误, 把他当成了初始化方法, 在里面…

程序员的养生之道

程序员的养生之道 1 对程序员的初次印象2 我的养生之道2.1 规律作息:2.2 合理饮食:2.3 健康饮食:2. 4 增强锻炼:2. 5 心态平和:2. 6 生活习惯:2.7 定期体检:2.8 特殊注意:2.9 补充能…

2-3、运算符

语雀原文链接 文章目录 1、算术运算符2、关系运算符3、逻辑运算符4、赋值运算符5、移位运算符6、位运算符(二进制位进行运算)7、条件运算符:三目运算符8、运算符的优先级 1、算术运算符 :加法-:减法*:乘法/:除法取商%&#xff1…

“创未来,享非凡“ 昇腾AI开发者创享日广州站圆满成功

在羊城广州的科技新风潮中,一个以创新为核心、以智能为驱动的盛会在这座南国明珠城市如火如荼地展开。这不仅是一场技术的盛宴,更是人工智能产业发展动力的一次集结。 12月9日,在广州市工业和信息化局的倡导下,一场主题为“创未来…

【C++数据结构 | 字符串速通】10分钟秒杀字符串相关操作 | 字符串的增删改查 | 字符串与数组相互转换

字符串 by.Qin3Yu 文中所有代码默认已使用std命名空间且已导入部分头文件&#xff1a; #include <iostream> #include <string> using namespace std;概念速览 字符串是一种非常好理解的数据类型&#xff0c;它用于存储和操作文本数据。字符串可以包含任意字符…

data_loader返回的每个batch的数据大小是怎么计算得到的?

data_loader是一个通用的术语&#xff0c;用于表示数据加载器或数据批次生成器。它是在机器学习和深度学习中常用的一个概念。 一、data loader 数据加载器&#xff08;data loader&#xff09;是一个用于加载和处理数据集的工具&#xff0c;它可以将数据集划分为小批次&#…

六:Day03_Mybatis-Plus

一、介绍 MyBatis-Plus&#xff08;简称 MP&#xff0c;是由baomidou(苞米豆)组织开源的&#xff09;是一个基于 MyBatis 的增强工具&#xff0c;它对 Mybatis 的基础功能进行了增强&#xff0c;但未做任何改变&#xff0c;Mybatis-Plus 其实可以看作是对 Mybatis 的再一次封装…

Java 匿名内部类使用的外部变量,为什么一定要加 final?

问题描述 Effectively final Java 1.8 新特性&#xff0c;对于一个局部变量或方法参数&#xff0c;如果他的值在初始化后就从未更改&#xff0c;那么该变量就是 effectively final&#xff08;事实 final&#xff09;。 这种情况下&#xff0c;可以不用加 final 关键字修饰。 …

求导公式,求导的四则运算,复合函数求导

求导公式 求导的四则运算 复合函数求导