【Megatron-DeepSpeed】张量并行工具代码mpu详解(四):张量并行版Embedding层及交叉熵的实现及测试

相关博客
【Megatron-DeepSpeed】张量并行工具代码mpu详解(四):张量并行版Embedding层及交叉熵的实现及测试
【Megatron-DeepSpeed】张量并行工具代码mpu详解(三):张量并行层的实现及测试
【Megatron-DeepSpeed】张量并行工具代码mpu详解(一):并行环境初始化
【Megatron-DeepSpeed】张量并行工具代码mpu详解(二):Collective通信操作的封装mappings
【深度学习】【分布式训练】DeepSpeed:AllReduce与ZeRO-DP
【深度学习】混合精度训练与显存分析
【深度学习】【分布式训练】Collective通信操作及Pytorch示例
【自然语言处理】【大模型】大语言模型BLOOM推理工具测试
【自然语言处理】【大模型】GLM-130B:一个开源双语预训练语言模型
【自然语言处理】【大模型】用于大型Transformer的8-bit矩阵乘法介绍

张量并行版Embedding层及交叉熵的实现及测试

​ Megatron-DeepSpeed是DeepSpeed版本的NVIDIA Megatron-LM。像BLOOM、GLM-130B等主流大模型都是基于Megatron-DeepSpeed开发的。这里以BLOOM版本的Megetron-DeepSpeed为例,介绍其模型并行代码mpu的细节(位于megatron/mpu下)。

​ 理解该部分的代码需要对模型并行的原理以及集合通信有一定的理解,可以看文章:

  • 【深度学习】【分布式训练】Collective通信操作及Pytorch示例
  • 【深度学习】【分布式训练】一文捋顺千亿模型训练技术:流水线并行、张量并行和3D并行
  • 【深度学习】【分布式训练】DeepSpeed:AllReduce与ZeRO-DP

强烈建议阅读,不然会影响本文的理解:

  • 【Megatron-DeepSpeed】张量并行工具代码mpu详解(一):并行环境初始化
  • 【Megatron-DeepSpeed】张量并行工具代码mpu详解(二):Collective通信操作的封装mappings
  • 【Megatron-DeepSpeed】张量并行工具代码mpu详解(三):张量并行层的实现及测试

阅读建议:

  1. 本文仅会解析核心代码,并会不介绍所有代码;
  2. 本文会提供一些测试脚本来展现各部分代码的功能;
  3. 建议实际动手实操来加深理解;
  4. 建议对Collective通信以及分布式模型训练有一定理解,再阅读本文;

一、总览

​ mpu目录下核心文件有:

  • initialize.py:负责数据并行组、张量并行组和流水线并行组的初始化,以及获取与各类并行组相关的信息;
  • data.py:实现张量并行中的数据广播功能;
  • cross_entropy.py:张量并行版本的交叉熵;
  • layers.py:并行版本的Embedding层,以及列并行线性层和行并行线性层;
  • mappings.py:用于张量并行的通信操作;

二、张量并行版Embedding层

在这里插入图片描述

​ Embedding层本质就是一个查找表。如上图所示,张量并行版embedding层就是将完整的embedding层,在vocab的维度切分。张量并行组中的每个进程仅持有部分embedding层。

1. 实现代码

​ 这里直接在原始的文件(megatron/mpu/layers.py)中,添加一个自定义的并行版Embedding层。其与原始版完全相同,仅添加了一些输出来展示整个过程。

# layers.py
class MyVocabParallelEmbedding(torch.nn.Module):def __init__(self, num_embeddings, embedding_dim,init_method=init.xavier_normal_):super(MyVocabParallelEmbedding, self).__init__()# 初始化一些参数self.num_embeddings = num_embeddings # 词表大小self.embedding_dim = embedding_dimself.padding_idx = Noneself.max_norm = Noneself.norm_type = 2.self.scale_grad_by_freq = Falseself.sparse = Falseself._weight = Noneself.tensor_model_parallel_size = get_tensor_model_parallel_world_size()# 张量并行组中的每个rank仅持有部分vocab embedding# 这里会计算当前rank持有的vocab的起始和结束位置self.vocab_start_index, self.vocab_end_index = \VocabUtility.vocab_range_from_global_vocab_size(self.num_embeddings, get_tensor_model_parallel_rank(),self.tensor_model_parallel_size)# 当前rank持有的部分vocab的大小self.num_embeddings_per_partition = self.vocab_end_index - \self.vocab_start_indexargs = get_args()# embedding层添加LayerNormif mpu.is_pipeline_first_stage() and (args.use_bnb_optimizer or args.embed_layernorm):self.norm = LayerNorm(embedding_dim)# bnb是指bitsandbytes,该库针对8-bit做了一些cuda函数的封装,这里忽略if args.use_bnb_optimizer:# for BNB we ignore the passed init_method and use torch.nn.init.xavier_uniform_# modified to calculate std on the unpartitioned embeddinginit_method = partial(xavier_uniform_tensor_parallel_, tp_degree=self.tensor_model_parallel_size)# 初始化embedding层的权重# 每个rank仅初始化自己所持有的那部分if args.use_cpu_initialization:self.weight = Parameter(torch.empty(self.num_embeddings_per_partition, self.embedding_dim,dtype=args.params_dtype))_initialize_affine_weight_cpu(self.weight, self.num_embeddings, self.embedding_dim,self.num_embeddings_per_partition, 0, init_method)else:self.weight = Parameter(torch.empty(self.num_embeddings_per_partition, self.embedding_dim,device=torch.cuda.current_device(), dtype=args.params_dtype))_initialize_affine_weight_gpu(self.weight, init_method,partition_dim=0, stride=1)# bnb(忽略)if args.use_bnb_optimizer:from bitsandbytes.optim import GlobalOptimManagerGlobalOptimManager.get_instance().override_config(self.weight, 'optim_bits', 32)GlobalOptimManager.get_instance().register_parameters(self.weight)def forward(self, input_):if torch.any(input_ >= self.num_embeddings):raise ValueError(f"There is an input id in the input that is greater than the highest possible input id.\nInput: {input_}\nnum_embeddings: {self.num_embeddings}")# 全局rankglobal_rank = torch.distributed.get_rank()# 张量并行组中的ranktp_rank = get_tensor_model_parallel_rank()info = f"*"*20 + \f"\n> global_rank={global_rank}\n" + \f"> tensor parallel rank={tp_rank}\n" + \f"> full embedding size={(self.num_embeddings, self.embedding_dim)}\n" + \f"> partial embedding size={list(self.weight.size())}\n" \f"> input = {input_}\n" \f"> vocab_start_index={self.vocab_start_index}, vocab_end_index={self.vocab_end_index}\n"if self.tensor_model_parallel_size > 1:# Build the mask.input_mask = (input_ < self.vocab_start_index) | \(input_ >= self.vocab_end_index)# Mask the input.masked_input = input_.clone() - self.vocab_start_indexmasked_input[input_mask] = 0else:# input_ is garanted to be in the range [0:self.vocab_end_index - self.vocab_start_index] thanks to the first checkmasked_input = input_info += f"> input_mask={input_mask} \n"info += f"> masked_input={masked_input} \n"# 获得embeddingoutput_parallel = F.embedding(masked_input, self.weight,self.padding_idx, self.max_norm,self.norm_type, self.scale_grad_by_freq,self.sparse)# 由于在当前rank上,仅能获得部分输入的embedding# 因此,将mask掉的input对应的embedding设置为全0if self.tensor_model_parallel_size > 1:output_parallel[input_mask, :] = 0.0info += f"> output_parallel={output_parallel}\n"# 上一步设置为全0的embedding会在这一步通过allreduce,组装成完整的embeddingoutput = reduce_from_tensor_model_parallel_region(output_parallel)info += f"> output={output}\n"if hasattr(self, 'norm'):output = self.norm(output)print(info, end="")return output

2. 测试脚本

​ 实验设置为:张量并行度为2,流水线并行度也为2。测试脚本比较简单,直接调用上面实现的MyVocabParallelEmbedding

# test_embedding.py
import sys
sys.path.append("..")from megatron.mpu import layers
from commons import set_random_seed
from commons import print_separator
from megatron.initialize import _initialize_distributed
from megatron.global_vars import set_global_variables
import megatron.mpu as mpu
from torch.nn.parameter import Parameter
import torch.nn.init as init
import torch
import randomdef test_parallel_embedding():batch_size = 2seq_length = 4vocab_size = 6hidden_size = 8seed = 123set_random_seed(seed)# (2,4)input_data = torch.LongTensor(size=(batch_size, seq_length)).random_(0, vocab_size).cuda()embedding_vocab_parallel = layers.MyVocabParallelEmbedding(vocab_size, hidden_size, init_method=init.normal_).cuda()output = embedding_vocab_parallel(input_data)def main():set_global_variables(ignore_unknown_args=True)_initialize_distributed()world_size = torch.distributed.get_world_size()print_separator('Test test_parallel_embedding')test_parallel_embedding()if __name__ == '__main__':main()

启动命令:

options=" \--tensor-model-parallel-size 2 \--pipeline-model-parallel-size 2 \--num-layers 10 \--hidden-size 768 \--micro-batch-size 2 \--num-attention-heads 32 \--seq-length 512 \--max-position-embeddings 512\--use_cpu_initialization True"cmd="deepspeed test_embedding.py $@ ${options}"eval ${cmd}

3. 测试结果

在这里插入图片描述

  • 全局rank为2,在张量并行组中的rank为0;
  • 完整的embedding层大小应为(6, 8),当前设备持有的embedding层大小为(3, 8),符合张量并行度为2的假设;
  • 当前设备持有的词表id范围介于0到3,输入中超出该词表范围都会被mask;
  • 当前设备的输出(output_parallel),会有部分embedding为全0,而完整的输出(output)则将张量并行组中所有的embedding输出都聚合在一起;

三、张量并行版交叉熵

​ 我们以自然语言模型为例,展示交叉熵的计算原理。

​ 若模型针对单个token预测的logit表示为 l ⃗ = [ l 1 , … , l k ] \vec{l}=[l_1,\dots,l_k] l =[l1,,lk],经过softmax转换后的概率分布为 p ⃗ = [ p 1 , … , p k ] \vec{p}=[p_1,\dots,p_k] p =[p1,,pk],其中:
p i = e l i ∑ j k e l j p_i=\frac{e^{l_i}}{\sum_{j}^k e^{l_j}} pi=jkeljeli
该token的真实标签表示为 y ⃗ = [ y 1 , … , y k ] \vec{y}=[y_1,\dots,y_k] y =[y1,,yk],由于其是one-hot编码,所以 y ⃗ \vec{y} y 中仅有一个值为1,其余均为0。那么该token上的交叉熵损失函数为
loss = − ∑ i = 1 k y i log ⁡ ( p i ) = − ∑ i = 1 k y i log ⁡ ( e l i ∑ j k e l j ) = ∑ i = 1 k y i [ log ⁡ ( ∑ j k e l j ) − log ⁡ ( e l i ) ] = log ⁡ ( ∑ j k e l j ) − ∑ i = 1 k y i log ⁡ ( e l i ) = log ⁡ ( ∑ j k e l j ) − ∑ i = 1 k y i l i \begin{align} \text{loss}&=-\sum_{i=1}^k y_i\log(p_i) \\ &=-\sum_{i=1}^k y_i\log(\frac{e^{l_i}}{\sum_{j}^k e^{l_j}}) \\ &=\sum_{i=1}^k y_i[\log(\sum_{j}^k e^{l_j})-\log(e^{l_i})] \\ &=\log(\sum_{j}^k e^{l_j})-\sum_{i=1}^k y_i \log(e^{l_i}) \\ &=\log(\sum_{j}^k e^{l_j})-\sum_{i=1}^k y_i {l_i} \end{align} loss=i=1kyilog(pi)=i=1kyilog(jkeljeli)=i=1kyi[log(jkelj)log(eli)]=log(jkelj)i=1kyilog(eli)=log(jkelj)i=1kyili
由于模型输出的 l ⃗ \vec{l} l 是已知的,那么上式第一项 log ⁡ ( ∑ j k e l j ) \log(\sum_{j}^k e^{l_j}) log(jkelj)是一个固定的常数;由于所有的 y i y_i yi中仅有一个是1,那么第二项 ∑ i = 1 k y i l i \sum_{i=1}^k y_i {l_i} i=1kyili本质上就是正确token对应的logit值。

mpu代码中的交叉熵实现基本上遵循上面的分析,仅是添加了batch size和seq_length维度,但核心思想不变

1. 实现代码

​ 同样,也是在原始文件(megatron/mpu/cross_entropy.py)中,添加一个自定义的并行版交叉熵。该实现与原版完全相同,仅添加了一些输出来展示整个过程。

# cross_entropy.py
class _MyVocabParallelCrossEntropy(torch.autograd.Function):@staticmethoddef forward(ctx, vocab_parallel_logits, target):# vocab_parallel_logits: (batch_size, seq_length, vocab_size)# target: (batch_size, seq_length)global_rank = torch.distributed.get_rank()tp_rank = get_tensor_model_parallel_rank()# 在vocab维度取最大值,也就是每个token对于logits的最大值logits_max = torch.max(vocab_parallel_logits, dim=-1)[0]torch.distributed.all_reduce(logits_max,op=torch.distributed.ReduceOp.MAX,group=get_tensor_model_parallel_group())vocab_parallel_logits.sub_(logits_max.unsqueeze(dim=-1))info = f"*"*20 + \f"\n> global_rank={global_rank}\n" + \f"> tp_rank={tp_rank}\n" + \f"> size of vocab_parallel_logits={list(vocab_parallel_logits.size())}\n" + \f"> size of target={list(target.size())}\n"# 依据当前进程持有的部分词表大小partition_vocab_size,以及张量并行组中rank和world size,# 确定出当前进程持有词表的起始索引vocab_start_index和结束索引vocab_end_indexget_vocab_range = VocabUtility.vocab_range_from_per_partition_vocab_sizepartition_vocab_size = vocab_parallel_logits.size()[-1]rank = get_tensor_model_parallel_rank()world_size = get_tensor_model_parallel_world_size()vocab_start_index, vocab_end_index = get_vocab_range(partition_vocab_size, rank, world_size)# 将不在词表中的target遮蔽掉target_mask = (target < vocab_start_index) | (target >= vocab_end_index)masked_target = target.clone() - vocab_start_indexmasked_target[target_mask] = 0# ligits_2d: (batch_size*seq_length, vocab_size)logits_2d = vocab_parallel_logits.view(-1, partition_vocab_size)# masked_target_1d: (batch_size*seq_length)masked_target_1d = masked_target.view(-1)arange_1d = torch.arange(start=0, end=logits_2d.size()[0],device=logits_2d.device)# predicted_logits_1d 表示正确token对应的logitpredicted_logits_1d = logits_2d[arange_1d, masked_target_1d]predicted_logits_1d = predicted_logits_1d.clone().contiguous()predicted_logits = predicted_logits_1d.view_as(target)# 将当前进程无法获得的logits设置为0,用于后续allreduce组成完成logitspredicted_logits[target_mask] = 0.0info += f"> size of logits_2d={list(logits_2d.size())}\n" + \f"> size of masked_target_1d={list(masked_target_1d.size())}\n" + \f"> size of predicted_logits={list(predicted_logits_1d.size())}\n"# 各个进程持有的predicted_logits的大小是完全相同的# 但是,当前进程持有的predicted_logits仅在当前词表上才有取值,其余值为0# 通过allreduce即可得到完整的predicted_logitstorch.distributed.all_reduce(predicted_logits,op=torch.distributed.ReduceOp.SUM,group=get_tensor_model_parallel_group())# 求softmax分母的部分exp_logits = vocab_parallel_logitstorch.exp(vocab_parallel_logits, out=exp_logits)sum_exp_logits = exp_logits.sum(dim=-1)torch.distributed.all_reduce(sum_exp_logits,op=torch.distributed.ReduceOp.SUM,group=get_tensor_model_parallel_group())# 对应上面公式推导的最终结果# loss: (batch_size, seq_length)。# loss是一个矩阵,矩阵的值对应单个token的交叉熵loss = torch.log(sum_exp_logits) - predicted_logitsinfo += f"> size of sum_exp_logits={list(sum_exp_logits.size())}\n" + \f"> size of loss={list(loss.size())}\n"print(info, end="")exp_logits.div_(sum_exp_logits.unsqueeze(dim=-1))ctx.save_for_backward(exp_logits, target_mask, masked_target_1d)return loss@staticmethoddef backward(ctx, grad_output):# Retreive tensors from the forward path.softmax, target_mask, masked_target_1d = ctx.saved_tensors# All the inputs have softmax as thier gradient.grad_input = softmax# For simplicity, work with the 2D gradient.partition_vocab_size = softmax.size()[-1]grad_2d = grad_input.view(-1, partition_vocab_size)# Add the gradient from matching classes.arange_1d = torch.arange(start=0, end=grad_2d.size()[0],device=grad_2d.device)grad_2d[arange_1d, masked_target_1d] -= (1.0 - target_mask.view(-1).float())# Finally elementwise multiplication with the output gradients.grad_input.mul_(grad_output.unsqueeze(dim=-1))return grad_input, None

2. 测试脚本

# test_cross_entropy.py
import sys
sys.path.append("..")from commons import set_random_seed
from commons import IdentityLayer
from commons import print_separator
from commons import initialize_distributed
from megatron.mpu.cross_entropy import _MyVocabParallelCrossEntropy
import megatron.mpu as mpu
import torch.nn.functional as F
import torch
import randomdef test_cross_entropy():tensor_model_parallel_size = mpu.get_tensor_model_parallel_world_size()batch_size = 32seq_length = 128vocab_size_per_partition = 500logits_scale = 1000.0vocab_size = vocab_size_per_partition * tensor_model_parallel_sizeseed = 1234set_random_seed(seed)identity = IdentityLayer((batch_size, seq_length, vocab_size),scale=logits_scale).cuda()logits = identity()logits_parallel = mpu.scatter_to_tensor_model_parallel_region(logits)target = torch.cuda.LongTensor(size=(batch_size, seq_length)).random_(0, vocab_size)loss = _MyVocabParallelCrossEntropy.apply(logits_parallel, target).mean()if __name__ == '__main__':initialize_distributed()world_size = torch.distributed.get_world_size()tensor_model_parallel_size = 2pipeline_model_parallel_size = 2mpu.initialize_model_parallel(tensor_model_parallel_size,pipeline_model_parallel_size)test_cross_entropy()

启动命名:

deepspeed test_cross_entropy.py

3. 测试结果

在这里插入图片描述

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

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

相关文章

测试老鸟经验总结,Jmeter性能测试-重要指标与性能结果分析(超细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Aggregate Report …

【CTF-web】修改请求头(XFF)

题目链接&#xff1a;https://ctf.bugku.com/challenges/detail/id/79.html 随意输入后可以看到需要本地管理员登录&#xff0c;得知这是一道需要修改XFF头的题。 XFF即X-Forwarded-For&#xff0c;该请求标头是一个事实上的用于标识通过代理服务器连接到 web 服务器的客户端的…

【Apollo】Apollo-ros版本架构学习与源码分析

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Apollo-ros版本架构学习与源码分析。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&a…

途乐证券-新股行情持续火爆,哪些因素影响首日表现?

全面注册制以来&#xff0c;参加打新的投资者数量全体呈现下降。打新收益下降&#xff0c;破发频出的布景下&#xff0c;投资者打新策略从逢新必打逐步向优选个股改变。 经过很多历史数据&#xff0c;从商场定价、参加者热度以及机构重视度维度揭秘了上市后股价体现优秀的个股具…

时序预测 | Python实现LSTM长短期记忆网络时间序列预测(电力负荷预测)

时序预测 | Python实现LSTM长短期记忆网络时间序列预测(电力负荷预测) 目录 时序预测 | Python实现LSTM长短期记忆网络时间序列预测(电力负荷预测)效果一览基本描述模型结构程序设计参考资料效果一览

记录一下关于word存放代码出现的问题

如下图所示&#xff0c;从 Word 中复制代码并粘贴到其他地方&#xff0c;例如文本编辑器或代码编辑器中&#xff0c;有时会出现额外的连字符或破折号。这是因为 Word 使用了特定的字符编码和格式&#xff0c;而这些字符在代码中可能不被支持或解析为特殊字符。   可见有时从…

拓扑布局和建立小型网络

练习 2.6.1&#xff1a;拓扑布局和建立小型网络 地址表 本实验不包括地址表。 拓扑图 学习目标 正确识别网络中使用的电缆物理连接点对点交换网络验证每个网络的基本连通性 简介&#xff1a; 许多网络问题都可以在网络的物理层解决。因此&#xff0c;必须清楚了解网络连接…

13-把矩阵看作是对系统的描述

探索矩阵乘法&#xff1a;更深刻的理解与应用视角 &#x1f9e9;&#x1f50d; 引言 &#x1f4d6; 在我们进一步探讨矩阵乘法之前&#xff0c;让我们从不同的角度来理解什么是矩阵&#xff0c;以及如何将矩阵视为一个系统。我们之前已经介绍了矩阵的基本概念和运算&#xff…

【QT】 QSS样式表设计一文了解

很高兴在雪易的CSDN遇见你 &#xff0c;给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 本文分享QT界面设计中的QSS样式技术&#xff0c;主要从**、**和**方面展开&#xff0c;希望对各位小伙伴有所帮助&#xff01;学会了QSS样式设计&#xff0c;就可以开动你的审美&#…

Zookeeper集群 + Kafka集群

文章目录 Zookeeper集群 Kafka集群一.Zookeeper 概述1.Zookeeper 定义2.Zookeeper 工作机制2.1 Zookeeper 工作机制图示 3.Zookeeper 特点4.Zookeeper 数据结构5.Zookeeper 应用场景5.1 统一命名服务5.2 统一配置管理5.3 统一集群管理5.4 服务器动态上下线5.5 软负载均衡 6.Zo…

JMeter启动时常见的错误

很多小伙伴在学工具这一块时&#xff0c;安装也是很吃力的一个问题&#xff0c;之前记得有说过怎么安装jmeter这个工具。那么你要启动jmeter的时候&#xff0c;一些粉丝就会碰到如下几个问题。 1.解压下载好的jmeter安装&#xff0c;Windows 平台&#xff0c;双击 jmeter/bin …

35岁职业危机?不存在!体能断崖?不担心

概述 90年&#xff0c;硕士毕业&#xff0c;干了快8年的Java开发工作。现年33岁&#xff0c;再过2年就要35岁。 工作这些年&#xff0c;断断续续也看过不少35岁找不到工作&#xff0c;转行&#xff0c;降薪入职的传闻、案例。 35岁&#xff0c;甚至30岁之后&#xff0c;体能…

使用gewe框架进行微信群组管理(一)

友情链接&#xff1a;geweapi.com 点击访问即可。 管理员操作 小提示&#xff1a; 添加、删除、转让多个wxid时仅限于添加/删除管理员&#xff0c;1添加 2删除 3转让 请求URL&#xff1a; http://域名地址/api/group/admin 请求方式&#xff1a; POST 请求头&#xff1a…

Expo项目 使用Native base UI库

装包&#xff1a; yarn add native-base expo install react-native-svg12.1.1 Index.js: import React from react import { View, Text } from react-native import useList from ./useList import { NativeBaseProvider, Button, Box } from native-base import styles f…

Python中搭建IP代理池的妙招

在Python的爬虫世界里&#xff0c;你是否也想搭建一个功能强大的IP代理池&#xff0c;让你的爬虫无忧无虑地畅游各大网站&#xff1f;今天&#xff0c;我就来教你使用Scrapy框架搭建IP代理池&#xff0c;让你的爬虫更加智能、高效&#xff01;跟着我一步一步来&#xff0c;轻松…

NGINX负载均衡及LVS-DR负载均衡集群

目录 LVS-DR原理搭建过程nginx 负载均衡 LVS-DR原理 原理&#xff1a; 1. 当用户向负载均衡调度器&#xff08;Director Server&#xff09;发起请求&#xff0c;调度器将请求发往至内核空间 2. PREROUTING链首先会接收到用户请求&#xff0c;判断目标IP确定是本机IP&#xff…

Android JNI实现锅炉压力显示系统详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 第一步创建GuoLu.c文件 // // Created by DELL on 2023/8/13. // #include <stdio.h…

vue3使用pinia和pinia-plugin-persist做持久化存储

插件和版本 1、安装依赖 npm i pinia // 安装 pinia npm i pinia-plugin-persist // 安装持久化存储插件2、main.js引入 import App from ./App.vue const app createApp(App)//pinia import { createPinia } from pinia import piniaPersist from pinia-plugin-persist //持…

2023深圳杯A题完整代码模型

已更新深圳杯A题全部版本&#xff0c;文末获取&#xff01; 摘要 现代社会&#xff0c;随着生活方式的变化和工作压力的增大&#xff0c;慢性非传染性疾病日益成为威胁公众健康的主要问题。心脑血管疾病、糖尿病、恶性肿瘤及慢性阻塞性肺病等慢性病的发病率呈现出上升趋势。为…

缓存 基础

在项目里面缓存是怎么用的? 项目中缓存是如何使用的&#xff1f;为什么要用缓存&#xff1f;缓存使用不当会造成什么后果? 这个问题&#xff0c;互联网公司必问&#xff0c;要是一个人连缓存都不太清楚&#xff0c;那确实比较尴尬。 只要问到缓存&#xff0c;上来第一个问题…