优化注意力层提升 Transformer 模型效率:通过改进注意力机制降低机器学习成本

Transformer 架构由 Vaswani 等人在 2017 年发表的里程碑式论文《Attention Is All You Need》中首次提出,如今已被广泛认为是过去十年间最具开创性的科学突破之一。注意力机制是 Transformer 的核心创新,它为人工智能模型提供了一种全新的方法,使模型能够根据具体任务的需求,灵活地聚焦输入序列的不同部分,从而更深入地理解复杂的语言和结构。

最初在自然语言处理领域崭露头角,Transformer 架构的卓越性能很快吸引了跨学科的关注,其应用迅速扩展到语音识别、计算机视觉、强化学习、生物信息学等多个前沿领域,展现出令人瞩目的学科交叉潜力。然而与其革命性突破同时,注意力层的高计算复杂度也逐渐成为制约其进一步发展的瓶颈。随着模型规模的持续增长,注意力层的计算资源需求呈指数级上升,训练和部署成本也随之攀高。

寻找降低注意力层计算开销的有效策略,在提高基于 Transformer 的人工智能模型效率和可扩展性方面至关重要。本文将深入探讨在 PyTorch 生态系统中优化注意力层的多种技术路径,并将重点聚焦于那些在降低计算成本的同时能够保持注意力层精度的创新方法。这些方法包括 PyTorch SDPA、FlashAttention、TransformerEngine Attention、FlexAttention 以及 xFormer attention。

本文将排除通过近似注意力计算来减少计算成本的其他方法(如 DeepSpeed 的 Sparse Attention、Longformer、Linformer 等),同时也不会详细讨论通用的优化技术,尽管这些技术对注意力性能亦有积极影响,但它们并不专门针对注意力计算本身。

值得强调的是,注意力优化是一个极其活跃且快速发展的研究领域,新的方法和突破不断涌现。本文的目标并非提供一个详尽无遗的技术指南,而是希望通过梳理当前主流的优化路径,为读者提供一个清晰的技术概述,并为后续的深入探索和实践铺平道路。

实验模型

为了便于讨论,我们使用流行的 Python 包 timm(版本 0.9.7)构建一个基于 Vision Transformer(ViT)的分类模型,以演示各种注意力内核对性能的影响。

首先,定义一个简化的 Transformer 块,允许通过将注意力函数传递给其构造函数来参数化。由于不同的注意力实现可能假设特定的输入张量格式,还包括一个选项来控制格式,以确保与所选的注意力内核兼容。

 # 通用导入  importos, time, functools  # torch 导入  importtorch  fromtorch.utils.dataimportDataset, DataLoader  importtorch.nnasnn  # timm 导入  importtimm  fromtimm.models.vision_transformerimportVisionTransformer  fromtimm.layersimportMlp  IMG_SIZE=224  BATCH_SIZE=128  # 定义 ViT 设置  NUM_HEADS=16  HEAD_DIM=64  DEPTH=24  PATCH_SIZE=16  SEQ_LEN= (IMG_SIZE//PATCH_SIZE)**2# 196  classMyAttentionBlock(nn.Module):  def__init__(  self,  attn_fn,  format=None,  dim: int=768,  num_heads: int=12,  **kwargs  ) ->None:  super().__init__()  self.attn_fn=attn_fn  self.num_heads=num_heads  self.head_dim=dim//num_heads  self.norm1=nn.LayerNorm(dim)  self.norm2=nn.LayerNorm(dim)  self.qkv=nn.Linear(dim, dim*3, bias=False)  self.proj=nn.Linear(dim, dim)  self.mlp=Mlp(  in_features=dim,  hidden_features=dim*4,  )  permute= (2, 0, 3, 1, 4)  self.permute_attn=functools.partial(torch.transpose,dim0=1,dim1=2)  ifformat=='bshd':  permute= (2, 0, 1, 3, 4)  self.permute_attn=nn.Identity()  self.permute_qkv=functools.partial(torch.permute,dims=permute)  defforward(self, x_in: torch.Tensor) ->torch.Tensor:  x=self.norm1(x_in)  B, N, C=x.shape  qkv=self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim)  # 根据指定格式置换张量  qkv=self.permute_qkv(qkv)  q, k, v=qkv.unbind(0)  # 使用用户指定的注意力函数  x=self.attn_fn(q, k, v)  # 根据指定格式置换输出  x=self.permute_attn(x).reshape(B, N, C)  x=self.proj(x)  x=x+x_in  x=x+self.mlp(self.norm2(x))  returnx

我们定义一个随机生成的数据集,我们将在训练期间用它来训练

 # 使用随机数据  classFakeDataset(Dataset):  def__len__(self):  return1000000  def__getitem__(self, index):  rand_image=torch.randn([3, IMG_SIZE, IMG_SIZE],  dtype=torch.float32)  label=torch.tensor(data=index%1000, dtype=torch.int64)  returnrand_image, label

接下来定义 ViT 训练函数。虽然我们的示例侧重于演示训练工作负载,但必须强调的是,在模型推理期间优化注意力层同样重要,甚至更为重要。

定义的训练函数接受自定义的 Transformer 块和一个控制使用 torch.compile 的标志。

 deftrain_fn(block_fn, compile):  torch.random.manual_seed(0)  device=torch.device("cuda:0")  torch.set_float32_matmul_precision("high")  # 创建数据集和数据加载器  train_set=FakeDataset()  train_loader=DataLoader(  train_set, batch_size=BATCH_SIZE,  num_workers=12, pin_memory=True, drop_last=True)  model=VisionTransformer(  img_size=IMG_SIZE,  patch_size=PATCH_SIZE,  embed_dim=NUM_HEADS*HEAD_DIM,  depth=DEPTH,  num_heads=NUM_HEADS,  class_token=False,  global_pool="avg",  block_fn=block_fn  ).to(device)  ifcompile:  model=torch.compile(model)  # 定义损失和优化器  criterion=torch.nn.CrossEntropyLoss()  optimizer=torch.optim.SGD(model.parameters())  model.train()  t0=time.perf_counter()  summ=0  count=0  forstep, datainenumerate(train_loader):  # 将数据复制到 GPU  inputs=data[0].to(device=device, non_blocking=True)  label=data[1].to(device=device, non_blocking=True)  withtorch.amp.autocast('cuda', enabled=True, dtype=torch.bfloat16):  outputs=model(inputs)  loss=criterion(outputs, label)  optimizer.zero_grad(set_to_none=True)  loss.backward()  optimizer.step()  # 捕获步骤时间  batch_time=time.perf_counter() -t0  ifstep>20:  # 跳过前几步  summ+=batch_time  count+=1  t0=time.perf_counter()  ifstep>100:  break  print(f'average step time: {summ/count}')  # 定义编译和未编译的训练函数变体  train=functools.partial(train_fn, compile=False)  train_compile=functools.partial(train_fn, compile=True)

下面的代码块中定义了一个 PyTorch 原生的注意力函数,并使用它来训练我们的 ViT 模型:

 defattn_fn(q, k, v):  scale=HEAD_DIM**-0.5  q=q*scale  attn=q@k.transpose(-2, -1)  attn=attn.softmax(dim=-1)  x=attn@v  returnx  block_fn=functools.partial(MyAttentionBlock, attn_fn=attn_fn)  print('Default Attention')  train(block_fn)  print('Compiled Default Attention')  train_compile(block_fn)

在 NVIDIA H100 上运行了这个模型,使用 CUDA 12.4 和 PyTorch 2.5.1。未编译的变体平均步时间为 370 毫秒(ms),而编译的变体改进为 242 ms。我们将使用这些结果作为基准,比较其他执行注意力计算的解决方案。

PyTorch SDPA

在 PyTorch 中提升注意力层性能的最简单方法之一是使用 scaled_dot_product_attention (SDPA) 函数。目前处于测试阶段的 PyTorch SDPA 整合了多个内核级优化,并根据输入的属性动态选择最有效的一个。支持的后端(截至目前)包括:FlashAttention-2、Memory-Efficient Attention、基于 C++ 的 Math Attention 和 CuDNN。这些后端将高级操作融合在一起,同时采用 GPU 级优化以提高计算效率和内存利用率。

SDPA 不断发展,定期引入新的和改进的后端实现。例如PyTorch 2.5 引入了一个更新的 CuDNN 后端,具有专门为 NVIDIA Hopper 架构 GPU 训练量身定制的 SDPA 原语。

在下面的代码块中,遍历支持的后端列表,并评估每个后端的训练运行时性能。我们使用一个辅助函数 set_sdpa_backend 来编程 SDPA 后端:

 fromtorch.nn.functionalimportscaled_dot_product_attentionassdpa  defset_sdpa_backend(backend):  torch.backends.cuda.enable_flash_sdp(False)  torch.backends.cuda.enable_mem_efficient_sdp(False)  torch.backends.cuda.enable_math_sdp(False)  torch.backends.cuda.enable_cudnn_sdp(False)  ifbackendin ['flash_sdp','all']:  torch.backends.cuda.enable_flash_sdp(True)  ifbackendin ['mem_efficient_sdp','all']:  torch.backends.cuda.enable_mem_efficient_sdp(True)  ifbackendin ['math_sdp','all']:  torch.backends.cuda.enable_math_sdp(True)  ifbackendin ['cudnn_sdp','all']:  torch.backends.cuda.enable_cudnn_sdp(True)  forbackendin ['flash_sdp', 'mem_efficient_sdp',  'math_sdp', 'cudnn_sdp']:  set_sdpa_backend(backend)  block_fn=functools.partial(MyAttentionBlock,  attn_fn=sdpa)  print(f'PyTorch SDPA - {backend}')  train(block_fn)  print(f'Compiled PyTorch SDPA - {backend}')  train_compile(block_fn)

结果如下

SDPA 后端的选择对性能有明显影响,而模型编译执行的优化似乎掩盖了注意力内核之间的差异。需要注意的是不要从这些结果中得出任何结论,因为不同注意力函数对性能的影响可能因具体模型和用例而异。

第三方注意力内核

虽然 PyTorch SDPA 是一个很好的起点,但使用第三方注意力内核可以进一步加速机器学习工作负载。这些替代方案通常具有更大的灵活性,提供更广泛的注意力配置选项。有些还包括针对特定硬件加速器或更新 GPU 架构的优化。

我们将探讨一些可用的第三方注意力内核,并评估它们对运行时性能的潜在影响。

FlashAttention-3

虽然 Pytorch SDPA 支持 FlashAttention 后端,但更高级的 FlashAttention 实现可以在 flash-attn 库中找到。这里将探讨 FlashAttention-3 测试版,它的速度比 FlashAttention-2 快多达 2 倍。鉴于其开发的早期阶段,FlashAttention-3 只能直接从 GitHub 仓库安装,并且其使用仅限于某些头部维度。并且它尚不支持模型编译。在下面的代码块中,我们将 Transformer 块配置为使用 flash-attn-3,同时将注意力输入格式设置为“bshd”(批次、序列、头、深度),以满足库的期望。

 # flash attention 3  fromflash_attn_interfaceimportflash_attn_funcasfa3  attn_fn=lambdaq,k,v: fa3(q,k,v)[0]  block_fn=functools.partial(MyAttentionBlock,  attn_fn=attn_fn,  format='bshd')  print(f'Flash Attention 3')  train(block_fn)

结果步时间为 240 ms,比 SDPA flash-attn 快 5%。

Transformer Engine

Transformer Engine (TE) 是一个专门设计用于加速 NVIDIA GPU 上 Transformer 模型的库。TE 定期更新,利用最新的 NVIDIA 硬件和软件产品的功能进行优化,使用户能够在这些优化集成到通用框架(如 PyTorch)之前很长时间内访问专用内核。

在下面的代码块中使用 TE 版本 1.11.0 的 DotProductAttention。与 PyTorch SDPA 类似,TE 支持通过环境变量控制的多个后端。这里我们演示使用 NVTE_FUSED_ATTN 后端。

 defset_te_backend(backend):  # 必须在第一次使用 transformer_engine.pytorch.attention 之前应用  os.environ["NVTE_FLASH_ATTN"] ='0'  os.environ["NVTE_FUSED_ATTN"] ='0'  os.environ["NVTE_UNFUSED_ATTN"] ='0'  ifbackend=='flash':  os.environ["NVTE_FLASH_ATTN"] ='1'  ifbackend=='fused':  os.environ["NVTE_FUSED_ATTN"] ='1'  ifbackend=='unfused':  os.environ["NVTE_UNFUSED_ATTN"] ='1'  fromtransformer_engine.pytorch.attentionimportDotProductAttention  set_te_backend('fused')  attn_fn=DotProductAttention(NUM_HEADS, HEAD_DIM, NUM_HEADS,  qkv_format='bshd',  # 禁用掩码(默认是因果掩码)  attn_mask_type='no_mask')  block_fn=functools.partial(MyAttentionBlock,  attn_fn=attn_fn,  format='bshd')  print(f'Transformer Engine Attention')  train(block_fn)  print(f'Compiled Transformer Engine Attention')  train_compile(block_fn)

TE 注意力在模型变体中的平均步时间分别为 243 ms 和 204 ms。

XFormer Attention

PyTorch SDPA 的内存高效后端的底层是由 xFormers 库提供的注意力内核。我们可以直接使用源代码,在下面的代码块中,使用 xFormers 版本 0.0.28 的 memory_efficient_attention 操作符。

 # xformer memory efficient attention  fromxformers.opsimportmemory_efficient_attentionasmea  block_fn=functools.partial(MyAttentionBlock,  attn_fn=mea,  format='bshd')  print(f'xFormer Attention ')  train(block_fn)  print(f'Compiled xFormer Attention ')  train_compile(block_fn)

平均时间为 246 ms,比 SDPA 内存高效内核快 10.5%

结果

eager 模型的赢家是 flash-attn-3,平均步时间比基线模型快 54%。这相当于训练时间减少了 54%。在编译模式下,优化内核的性能大致相同,最快的实现达到了 202 ms,比基线提高了 20%。

为了进行更广泛的评估,我们增加注意力序列长度到 3136 个标记重新运行实验。

 IMG_SIZE=224  BATCH_SIZE=8  # 定义 ViT 设置  NUM_HEADS=12  HEAD_DIM=64  DEPTH=6  PATCH_SIZE=4  SEQ_LEN= (IMG_SIZE//PATCH_SIZE)**2# 3136

结果如下:

当序列长度更大时,注意力内核的性能影响更加明显。flash-attn-3 依旧领先——这次性能提高了约 5 倍,超过了 PyTorch 原生函数。对于编译模型 TE 内核脱颖而出,总体最佳步时间为 53 ms。

使用 FlexAttention 自定义注意力

在探讨标准注意力函数的优化过程中,我们常常遇到需要对注意力计算进行定制化修改的场景。这些修改可能涉及屏蔽中间张量的特定值或对其执行特定操作。这类定制需求可能会与我们此前讨论的优化注意力块的实现方案产生冲突。针对这一问题,我们将探讨几种可行的解决策略:

利用高级内核 API 在着手开发自定义解决方案之前,建议首先全面评估现有优化注意力内核提供的 API。许多先进的注意力内核已经提供了丰富且灵活的接口,支持高度定制化的注意力计算。通过仔细研究这些 API很可能找到已经满足需求的现成功能,从而避免重复造轮子。

实现自定义内核 若现有 API 无法完全满足特定需求,创建自定义注意力实现可能是唯一的解决方案。但这是一条充满挑战的技术路径。正如我们在之前的讨论中阐明的,自定义内核开发存在显著的技术复杂性和性能优化难题。对于追求极致性能的开发者而言,最有效的策略是在现有(最优)内核的基础上进行微小而精准的改动,而非全盘重构。

引入 FlexAttention PyTorch 最新引入的 FlexAttention 为解决注意力计算的定制化需求提供了一个突破性的解决方案。这一创新性特性使开发者能够在不牺牲性能的前提下,灵活实现各种注意力变体。

通过将查询和键标记的点积结果抽象为 score,FlexAttention 提供了两个关键的定制机制:

  1. score_mod 函数:允许对注意力分数进行编程级别的精细调整
  2. block_mask 掩码:可以自动应用于 score 张量,实现更复杂的注意力模式

FlexAttention 的技术创新体现在:

  • score_mod 操作符直接编译到注意力操作符中,形成单一的融合内核
  • 利用 block_masks 的稀疏性特性,智能地避免不必要的计算开销

根据 FlexAttention 官方文档报告的基准测试,该方案在各类使用场景中均展现出显著的性能提升。下面我们将通过具体实例,深入探讨 score_modblock_mask 的实际应用。

Score Mod 示例——使用 Tanh 进行softcap

softcap是一种常用的技术,以下代码块扩展了我们的 PyTorch 原生注意力内核,添加了softcap:

 defsoftcap_attn(q, k, v):  scale=HEAD_DIM**-0.5  q=q*scale  attn=q@k.transpose(-2, -1)  # 应用软封顶  attn=30*torch.tanh(attn/30)  attn=attn.softmax(dim=-1)  x=attn@v  returnx

首先使用 PyTorch 原生内核训练模型,然后使用优化的 Flex Attention API 进行训练。这些实验是在 3136 长度序列设置下运行的。

 # flex attention 导入  fromtorch.nn.attention.flex_attentionimport (  create_block_mask,  create_mask,  flex_attention  )  compiled_flex=torch.compile(flex_attention)  # score_mod 定义  deftanh_softcap(score, b, h, q_idx, kv_idx):return30*torch.tanh(score/30)block_fn=functools.partial(MyAttentionBlock, attn_fn=softcap_attn)print(f'Attention with Softcap')train(block_fn)print(f'Compiled Attention with Softcap')train_compile(block_fn)flex_fn=functools.partial(flex_attention, score_mod=tanh_softcap)compiled_flex_fn=functools.partial(compiled_flex, score_mod=tanh_softcap)block_fn=functools.partial(MyAttentionBlock,attn_fn=flex_fn)compiled_block_fn=functools.partial(MyAttentionBlock,attn_fn=compiled_flex_fn) print(f'Flex Attention with Softcap')train(compiled_block_fn)print(f'Compiled Flex Attention with Softcap')train_compile(block_fn)

结果如下

Flash Attention 内核的影响提供了约 3.5 倍的性能提升。

Mask Mod 示例——邻域掩码

还可以通过将稀疏掩码应用于注意力 score 来评估 mask_mod 。我们序列中的每个标记代表 2D 输入图像中的一个补丁。可以修改内核,使每个标记仅关注 2D 标记数组中相应位置的 5x5 窗口内的其他标记。

 # 将标记 ID 转换为 2D 索引  defseq_indx_to_2d(idx):  n_row_patches=IMG_SIZE//PATCH_SIZE  r_ind=idx//n_row_patches  c_ind=idx%n_row_patches  returnr_ind, c_ind  # 仅关注 2D 标记数组中 5x5 窗口内的标记  defmask_mod(b, h, q_idx, kv_idx):  q_r, q_c=seq_indx_to_2d(q_idx)  kv_r, kv_c=seq_indx_to_2d(kv_idx)  returntorch.logical_and(torch.abs(q_r-kv_r)<5, torch.abs(q_c-kv_c)<5)

我们使用支持传递注意力掩码的 PyTorch SDPA。以下代码块包括带掩码的 SDPA 实验,随后是 Flex Attention 实现:

 # 物化掩码以用于 SDPA  mask=create_mask(mask_mod, 1, 1, SEQ_LEN, SEQ_LEN, device='cuda')  set_sdpa_backend('all')  masked_sdpa=functools.partial(sdpa, attn_mask=mask)  block_fn=functools.partial(MyAttentionBlock,  attn_fn=masked_sdpa)  print(f'Masked SDPA Attention')  train(block_fn)  print(f'Compiled Masked SDPA Attention')  train_compile(block_fn)  block_mask=create_block_mask(mask_mod, None, None, SEQ_LEN, SEQ_LEN)  flex_fn=functools.partial(flex_attention, block_mask=block_mask)  compiled_flex_fn=functools.partial(compiled_flex, block_mask=block_mask)  block_fn=functools.partial(MyAttentionBlock,  attn_fn=flex_fn)  compiled_block_fn=functools.partial(MyAttentionBlock,  attn_fn=compiled_flex_fn)  print(f'Masked Flex Attention')  train(compiled_block_fn)  print(f'Compiled Masked Flex Attention')  train_compile(block_fn)

Flex Attention 提供了显著的性能提升,提升了 2.19 倍和 2.59 倍。

Flex Attention 限制

尽管我们成功地展示了 Flex Attention 的强大和潜力,但仍有一些限制需要注意:

使用 Flex Attention,(在撰写本文时)只能修改注意力分数(查询和键标记的点积结果)。它不支持在注意力计算的其他阶段进行更改。

由于依赖于 torch.compile,必须小心避免过度重新编译,这可能会极大地降低运行时性能。例如虽然对文档掩码的支持非常吸引人,但只有在所有文档的长度总和保持固定时,它才能按预期执行。

目前(在撰写本文时)Flex Attention 不支持包含 可训练 参数的 score_mod 实现。虽然文档中强调支持相对位置编码,但这些通常是用 可训练 参数(而不是固定值)实现的,目前无法使用。

总结

随着 ML 模型中对 Transformer 架构和注意力层的依赖增加,对优化这些组件的工具和技术的需求也在增加。在本文中,我们探讨了许多注意力内核变体,每个都有其独特的属性、功能和限制。重要的是,没有一种方法适用于所有情况——不同的模型和用例将需要使用不同的内核和不同的优化策略。这强调了拥有多种工具和技术来优化注意力层的重要性。

https://avoid.overfit.cn/post/1d4e9a208695482e9187752d41c6a586

作者:Chaim Rand

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

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

相关文章

在Excel中处理不规范的日期格式数据并判断格式是否正确

有一个Excel表&#xff0c;录入的日期格式很混乱&#xff0c;有些看着差不多&#xff0c;但实际多一个空格少一个字符很难发现&#xff0c;希望的理想格式是 1980-01-01&#xff0c;10位&#xff0c;即&#xff1a;“YYYY-mm-dd”&#xff0c;实际上数据表中这样的格式都有 19…

医工交叉入门书籍分享:Transformer模型在机器学习领域的应用|个人观点·24-11-22

小罗碎碎念 今天给大家推荐一本入门书籍。 这本书由Uday Kamath、Kenneth L. Graham和Wael Emara撰写&#xff0c;深入探讨了Transformer模型在机器学习领域的应用&#xff0c;特别是自然语言处理&#xff08;NLP&#xff09;。 原文pdf已经上传至知识星球的【入门书籍】专栏&…

SpringCloud Gateway转发请求到同一个服务的不同端口

SpringCloud Gateway默认不支持将请求路由到一个服务的多个端口 本文将结合Gateway的处理流程&#xff0c;提供一些解决思路 需求背景 公司有一个IM项目&#xff0c;对外暴露了两个端口8081和8082&#xff0c;8081是springboot启动使用的端口&#xff0c;对外提供一些http接口…

Parker派克防爆电机在实际应用中的安全性能如何保证?

Parker防爆电机确保在实际应用中的安全性能主要通过以下几个方面来保证&#xff1a; 1.防爆外壳设计&#xff1a;EX系列电机采用强大的防爆外壳&#xff0c;设计遵循严格的防爆标准&#xff0c;能够承受内部可能发生的爆炸而不破损&#xff0c;利用间隙切断原理&#xff0c;防…

虚拟形象+动作捕捉:解锁品牌N种营销玩法

近年来&#xff0c;随着Z世代年轻人对于二次元文化的热爱&#xff0c;各种二次元内容频频出圈。为了吸引年轻观众的注意&#xff0c;虚拟IP形象成为了品牌营销的“新宠”与“利器”为品牌踏入元宇宙蓝海提供了关键的切入点。在此背景下虚拟形象动作捕捉技术的组合应用方式正成为…

空间计算、物理计算、实时仿真与创造拥有「自主行为」的小狗 | 播客《编码人声》

「编码人声」是由「RTE开发者社区」策划的一档播客节目&#xff0c;关注行业发展变革、开发者职涯发展、技术突破以及创业创新&#xff0c;由开发者来分享开发者眼中的工作与生活。 虚拟世界与现实世界的界限逐渐模糊&#xff0c;已然成为不争的事实。但究竟哪些曾经的幻想已然…

影响电阻可靠性的因素

一、影响电阻可靠性的因素&#xff1a; 影响电阻可靠性的因素有温度系数、额定功率&#xff0c;最大工作电压、固有噪声和电压系数 &#xff08;一&#xff09;温度系数 电阻的温度系数表示当温度改变1摄氏度时&#xff0c;电阻阻值的相对变化&#xff0c;单位为ppm/C.电阻温度…

JAVA后端如何调用百度的身份证识别API

大家好&#xff0c;我是 程序员码递夫 。 今天给大家分享的是 JAVA后台如何调用百度的身份证识别API。 1、前言 我们做APP开发时常遇到 身份证认证或资质认证的 需求&#xff0c; 通过上传身份证照片是个常用的操作&#xff0c; 后台对上传的身份证照信息进行识别&#xff0…

Go语言进阶依赖管理

1. Go语言进阶 1.1 Goroutine package mainimport ("fmt""time" )func hello(i int) {println("hello goroutine : " fmt.Sprint(i)) }func main() {for i : 0; i < 5; i {go func(j int) { hello(j) }(i) // 启动一个新的 goroutine&…

基于Java Springboot高考志愿填报辅助系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据库&#xff1a;…

autoware(2)运行自己的数据集

上一节完成了autoware.ai的安装和编译跑通了demo数据集&#xff0c;本将自己录制的数据包用于测试 1.修改点云地图 将加载点云地图的my_map.launch文件复制并命名为my_map_test.launch&#xff0c; &#xff08;1&#xff09;point cloud处替代原来的点云地图为自己的&#…

el-select 和el-tree二次封装

前言 本文章是本人在开发过程中&#xff0c;遇到使用树形数据&#xff0c;动态单选或多选的需求&#xff0c;element中没有这种组件&#xff0c;故自己封装一个&#xff0c;欢迎多多指教 开发环境&#xff1a;element-UI、vue2 组件效果 单选 多选 组件引用 <treeselec…

【LeetCode热题100】栈

这道题一共记录了关于栈的5道题目&#xff1a;删除字符串中所有相邻重复项、比较含退格的字符串、基本计算器II、字符串解码、验证栈序列。 class Solution { public:string removeDuplicates(string s) {string ret;for(auto c : s){if(ret.size() 0 || c ! ret.back()) ret …

《Python基础》之pip换国内镜像源

目录 推荐镜像源网址&#xff1a; 方法一&#xff1a;手动换源 方法二&#xff1a;命令提示符指令换源 临时换源 推荐镜像源网址&#xff1a; 阿里云&#xff1a;Simple Indexhttp://mirrors.aliyun.com/pypi/simple/ 华为云&#xff1a;Index of python-local https://m…

全面击破工程级复杂缓存难题

目录 一、走进业务中的缓存 &#xff08;一&#xff09;本地缓存 &#xff08;二&#xff09;分布式缓存 二、缓存更新模式分析 &#xff08;一&#xff09;Cache Aside Pattern&#xff08;旁路缓存模式&#xff09; 读操作流程 写操作流程 流程问题思考 问题1&#…

Kafka 分区分配及再平衡策略深度解析与消费者事务和数据积压的简单介绍

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十集:制作专门渲染HUD的相机HUD Camera和画布HUD Canvas

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作HUD Camera以及让两个相机同时渲染屏幕二、制作HUD Canvas 1.制作法力条Soul Orb引入库2.制作生命条Health读入数据3.制作吉欧统计数Geo Counter4.制作…

30. 并发编程

一、什么是多任务 如果一个操作系统上同时运行了多个程序&#xff0c;那么称这个操作系统就是 多任务的操作系统&#xff0c;例如&#xff1a;Windows、Mac、Android、IOS、Harmony 等。如果是一个程序&#xff0c;它可以同时执行多个事情&#xff0c;那么就称为 多任务的程序。…

ElasticSearch学习篇17_《检索技术核心20讲》最邻近检索-局部敏感哈希、乘积量化PQ思路

目录 场景在搜索引擎和推荐引擎中&#xff0c;对相似文章去重是一个非常重要的环节&#xff0c;另外是拍照识花、摇一摇搜歌等场景都可以使用它快速检索。 基于敏感性哈希的检索更擅长处理字面上的相似而不是语义上的相似。 向量空间模型ANN检索加速思路 局部敏感哈希编码 随…

mongodb多表查询,五个表查询

需求是这样的&#xff0c;而数据是从mysql导入进来的&#xff0c;由于mysql不支持数组类型的数据&#xff0c;所以有很多关联表。药剂里找药物&#xff0c;需要药剂与药物的关联表&#xff0c;然后再找药物表。从药物表里再找药物与成分关联表&#xff0c;最后再找成分表。 这里…