在使用 AMD GPU 的 PyTorch 中实现自动混合精度

Automatic mixed precision in PyTorch using AMD GPUs — ROCm Blogs

随着模型规模的增加,训练它们所需的时间和内存——以及因此而产生的成本——也在增加。因此,采取任何措施来减少训练时间和内存使用都是非常有益的。这就是自动混合精度(AMP)派上用场的地方。

在这篇博文中,我们将讨论 AMP 的基础知识、其工作原理以及它如何提高 AMD GPU 上的训练效率。

前提条件

要运行本博客中使用的代码,您需要以下内容:

  • 硬件

    • AMD GPU - 请参阅兼容 GPU 列表

    操作系统

    • Linux - 请参阅支持的 Linux 发行版

  • 软件

    • ROCm - 请参阅安装说明

运行本博客中的代码

有两种方式运行本博客中的代码:

  1. Docker (推荐)

  2. 构建您自己的 Python 环境。

在 Docker 中运行

使用 Docker 是构建所需环境最简单且最可靠的方式。

  • 确保你已安装 Docker。参见安装说明

  • 确保你在主机上已安装 amdgpu-dkms(与 ROCm 一同安装),这允许从 Docker 容器中访问 GPU。参见此处提供的 ROCm Docker 说明。

  • 克隆以下仓库,并进入博客目录

    git clone https://github.com/ROCm/rocm-blogs.git
    cd rocm-blogs/blogs/artificial-intelligence/automatic-mixed-precision
    
  • 构建并启动容器。有关构建过程的详细信息,请参阅 docker 目录中的 dockerfile。这将启动一个 Jupyter lab 服务器。

    cd docker
    docker compose build
    docker compose up
    
  • 在浏览器中,导航到http://localhost:8888并以 notebook 格式打开文件 amp_blog.py (要执行此操作,请右键单击该文件,然后选择“Open with…”和“Notebook”)

    注意

    这个笔记本是一个Jupytext 配对笔记本,采用 py:percent 格式。

构建你自己的 Python 环境

或者,如果您不希望使用 Docker 容器,请参阅附录中关于在主机上运行的说明,这些说明详细介绍了如何构建您自己的 Python 环境。请访问 在主机上运行。

太好了!现在我们可以深入了解自动混合精度了!

什么是混合精度?

默认情况下,大多数机器学习框架(包括 PyTorch)使用 32 位浮点(通常称为全精度或单精度)。然而,使用较低精度的格式,例如 float16(半精度),有潜力提高训练速度并减少内存使用。

在训练过程中,半精度操作提供了许多好处:

  • 更低的内存占用:这可能允许更大的模型适应内存,增加批处理大小,或两者兼顾。

  • 更快的数据传输:由于在系统中不同组件之间传输的数据量有效减少了 2 倍,这允许提高训练速度,特别是在多 GPU 或多节点训练中的梯度广播。

  • 更快的数学运算:通过减少操作中的位数,这降低了复杂性并允许增加训练和推理速度。

然而,完全以半精度训练模型可能会有一些挑战。这些挑战包括计算精度的丧失风险,以及梯度消失或爆炸等问题,这可能会降低模型的性能。这就是 AMP(自动混合精度)的作用所在。

ROCm 支持各种数据类型和精度 - 有关详细信息,请参阅 ROCm 精度支持。 

自动混合精度(AMP)

AMP 允许我们通过使用以下三个关键概念,在几乎不需要对现有代码进行修改的情况下,克服这些困难:

  • 在训练期间保持模型权重的全精度副本。

  • 在可能的情况下使用半精度操作,但在精度重要的情况下(例如在累积操作中)回退到全精度操作。

  • 使用梯度缩放来应对梯度下溢。

接下来,我们将看到如何使用 torch.autocast 通过自动混合精度来训练一个网络。

首先,我们导入以下包:

import gc
import time
import numpy as np
import torch
import matplotlib.pyplot as plt

Torch 自动混合精度

torch.autocast是一个上下文管理器,它允许所包裹的代码区域以自动混合精度运行。根据操作类型,被包裹的操作将自动转换为较低的精度,以提高速度和减少内存使用。

首先,让我们通过一个非常简单的例子来看一下 torch.autocast 的作用:

def test_amp():"""测试 torch.autocast 的类型转换"""device = "cuda" if torch.cuda.is_available() else "cpu"# 创建两个大小为N的向量x = torch.rand((1024, 1), device=device)y = torch.rand((1024, 1), device=device)print(f"Input dtypes:\n  x: {x.dtype}\n  y: {y.dtype}")# 在启用autocast的情况下执行操作with torch.autocast(device_type=device):a = x @ y.T  # 可以使用autocastb = x + y  # 不能使用# 打印结果数据类型print(f"Output dtypes:\n  Multiplication: {a.dtype}\n  Addition: {b.dtype}")test_amp()
Input dtypes:x: torch.float32y: torch.float32
Output dtypes:Multiplication: torch.float16Addition: torch.float32

在这个例子中,我们可以看到两个输入张量(`x` 和 y)的类型都是 float32。`torch.autocast`会自动以半精度执行矩阵乘法,同时在加法操作中保持全精度。在这里,输入张量(x, y)类似于模型权重,而输出(a, b)类似于模型的激活值和输出。

注意:

虽然在技术上可以使用较低精度执行加法运算,但 torch.autocast 选择在加法操作中保持全精度,因为这些操作更容易受到精度损失和误差积累的影响。有关哪些操作可以使用自动类型转换的详细信息,请参阅Autocast Op Reference.

在训练循环中添加autocast 

将 torch.autocast 添加到训练循环中很简单:只需将前向传播和损失计算包裹在上述的上下文管理器中即可。

首先,以下是一个典型的 不使用 autocast 的训练循环片段:

# Define model, optimizer, loss_fn, etc
...
for epoch in epochs:for inputs, targets in batches:opt.zero_grad()outputs = model(inputs)loss = loss_fn(outputs,targets)loss.backward()opt.step()

我们可以增强上面的循环以使用 torch.autocast,如下所示。注意,我们  将前向传播和损失计算包裹在 autocast 中。反向传播和优化器步骤必须在 autocast 之外执行:

...
for epoch in epochs:for inputs, targets in batches:opt.zero_grad()with torch.autocast(device_type='cuda'):  # Wrap forward pass and loss calc onlyoutputs = model(inputs)loss = loss_fn(outputs,targets)loss.backward()opt.step()

GradScaler

在混合精度训练中,梯度可能会发生下溢,导致它们的值被清零。为了应对这个问题,可以在训练过程中使用torch.cuda.amp.GradScaler 。这个缩放器通过在每次迭代时根据缩放因子调整梯度来减轻下溢问题。通常,使用的缩放因子很大,大约为2的16次方。然而,为了防止潜在的问题,缩放器通过监控无穷大或NaN(非数字)梯度值动态更新缩放因子。如果在某次迭代中遇到了这种值,优化步骤将被跳过,缩放因子会向下调整。

在训练中使用 GradScaler 非常简单。我们初始化一个缩放器对象,然后在用优化器更新模型参数之前,用它来缩放损失。优化步骤之后,缩放器会被更新,以确保下次训练迭代的正确缩放。

下面的代码示例展示了如何在混合精度训练循环中添加 GradScaler。请注意以下区别:

  • 在训练循环之外实例化一个 GradScaler

  • 将 loss.backward() 改为 scaler.scale(loss).backward():这将缩放损失,然后进行反向传播,创建缩放后的梯度

  • 将 opt.step() 改为 scaler.step(opt):`scaler.step` 会取消缩放梯度然后应用它们。如果遇到 inf 或 NaN,步骤将被跳过

  • 最后添加 scaler.update():如果遇到 inf 或 NaN,更新缩放器的缩放因子

# 定义模型、优化器、损失函数
...
# 实例化一个缩放器用于整个训练过程
scaler = torch.cuda.amp.GradScaler()for epoch in epochs:for inputs, targets in batches:opt.zero_grad()with torch.autocast(device_type='cuda'):outputs = model(inputs)loss = loss_fn(outputs,targets)scaler.scale(loss).backward() # 缩放损失并执行反向传播scaler.step(opt)              # 执行一步优化scaler.update()               # 更新缩放器的缩放因子

使用自动混合精度进行训练 

将所有内容结合起来,演示使用自动混合精度和梯度缩放进行训练。

首先,让我们创建一个上下文管理器,帮助我们测量运行时间和最大内存使用量:

class TorchMemProfile:"""Context manager to measure run time and max memory allocation"""def __enter__(self):gc.collect()torch.cuda.empty_cache()torch.cuda.reset_peak_memory_stats()torch.cuda.synchronize()self.start_time = time.time()return selfdef __exit__(self, exc_type, exc_value, exc_tb):self.end_time = time.time()torch.cuda.synchronize()self.duration = self.end_time - self.start_timeself.max_memory_allocated = torch.cuda.max_memory_allocated()

示例问题 

接下来,让我们构建一个简单的神经网络模型并定义一个示例问题。AMP(自动混合精度)在具有以下特点的网络中尤其有用:(a)较高的计算负荷,(b)相对于参数较大的激活量(如卷积或注意力机制)。因此,我们将创建一个简单的卷积网络,该网络旨在预测一个量化的二维正弦波。

模型的输入是一个形状为 (2,512,512) 的张量,其中 (512,512) 是图像大小,前两个维度(通道)是每个网格点的 X 和 Y 坐标(例如,“像素” (0,0) 的值是 (0,0),“像素” (511,0) 的值是 (1,0) 等)。输出是一个形状为 (1,512,512) 的张量,其中每个像素处的值是该坐标处二维正弦波的预测值。

这里我们生成一个量化的二维正弦波的方法是:

  • 生成指定大小的二维网格。

  •  计算每个点的余弦值。

  • 将输出离散化为四个级别,从而增加不连续性以使问题更难。

def generate_grid(shape):"""生成给定形状的网格。返回一个形状为 (2, shape) 的 numpy 数组,包含网格上每个位置的 x, y 坐标。坐标标准化到区间 [0,1]。"""x = np.arange(shape[0]) / (shape[0] - 1)y = np.arange(shape[1]) / (shape[1] - 1)xx, yy = np.meshgrid(x, y)grid = np.stack([xx, yy])return griddef generate_quantized_sin(grid, frequency, n_bins=4):"""给定一个二维网格和频率,计算网格上每个点的正弦波。然后将输出量化为所需的级别数量。"""out = np.cos(grid[0, :] * frequency[0] * 2 * np.pi) + np.cos(grid[1, :] * frequency[1] * 2 * np.pi)bins = np.linspace(out.min(), out.max(), n_bins + 1)[:n_bins]out = np.digitize(out, bins)# 标准化到 [0,1]out = (out - out.min()) / (out.max() - out.min())return out# 生成我们的输入和目标。
# 输入是二维坐标网格,目标是量化的正弦波
shape = (512, 512)
frequency = (4, 2)
inputs = generate_grid(shape)
targets = generate_quantized_sin(inputs, frequency)# 扩展用于训练的维度,并转换为 torch 张量
inputs = np.expand_dims(inputs, axis=0)
targets = np.expand_dims(targets, axis=(0, 1))
inputs = torch.tensor(inputs, dtype=torch.float32).cuda()
targets = torch.tensor(targets, dtype=torch.float32).cuda()print(f"Input shape:  {inputs.shape}")
print(f"Target shape: {targets.shape}")# 绘制目标
fig, ax = plt.subplots()
ax.imshow(targets.cpu().squeeze())
plt.show()

Input shape:  torch.Size([1, 2, 512, 512])
Target shape: torch.Size([1, 1, 512, 512])

png

上图展示了我们的网络将要学习的目标。 

网络

接下来,让我们定义一个简单的卷积神经网络。输入是一个二维“图像”,其中通道是给定位置的 (x, y) 坐标,输出是每个点上的正弦波的值。模型由一系列卷积层、批归一化和激活函数组成。

class ConvModel(torch.nn.Module):def __init__(self, hidden_size=256, n_hidden_layers=2):super().__init__()layers = [torch.nn.Conv2d(2, hidden_size, 1),torch.nn.BatchNorm2d(hidden_size),torch.nn.ReLU(),]for i in range(n_hidden_layers):layers += [torch.nn.Conv2d(hidden_size, hidden_size, 1),torch.nn.BatchNorm2d(hidden_size),torch.nn.ReLU(),]layers += [torch.nn.Conv2d(hidden_size, 1, 1)]self.model = torch.nn.Sequential(*layers)def forward(self, x):return self.model(x)

现在,让我们比较使用标准训练和启用 AMP(自动混合精度)训练时的训练时间和内存使用情况。 

标准训练循环

首先,我们定义一个“标准”训练循环。我们初始化我们的模型、损失函数和优化器,然后执行一个标准的循环 n_epochs(为简化起见,epoch 只是一个单一示例)。

def test_standard_training(n_epochs, inputs, targets, hidden_size, n_hidden_layers):"""测试一个标准的训练循环"""model = ConvModel(hidden_size, n_hidden_layers).cuda()loss_fn = torch.nn.MSELoss()opt = torch.optim.Adam(model.parameters(), 0.001)with TorchMemProfile() as profile:for i in range(n_epochs):opt.zero_grad()outputs = model(inputs)loss = loss_fn(outputs, targets)loss.backward()opt.step()return profile, loss, outputs

AMP训练循环 

接下来,让我们定义一个包含自动混合精度(AMP)的训练循环。我们可以看到一些关键的区别(代码中有注释):

  • 我们实例化了一个 torch.cuda.amp.GradScaler,该工具将用于缩放梯度以防止溢出或下溢

  • 我们将前向传播和损失计算包裹在 torch.autocast 中

注意:

我们*不会*将反向传播或优化器步操作包裹在 torch.autocast 中。这些步骤仍将在可能的情况下以较低精度运行,但在必要时会进行升级,例如更新权重时。

def test_amp_training(n_epochs, inputs, targets, hidden_size, n_hidden_layers):"""包含自动混合精度的训练循环"""model = ConvModel(hidden_size, n_hidden_layers).cuda()loss_fn = torch.nn.MSELoss()opt = torch.optim.Adam(model.parameters(), 0.001)scaler = torch.cuda.amp.GradScaler()             # 创建梯度缩放器with TorchMemProfile() as profile:for i in range(n_epochs):opt.zero_grad()with torch.autocast(device_type="cuda"):  # 仅将前向传播和损失计算包裹在 autocast 中outputs = model(inputs)loss = loss_fn(outputs, targets)scaler.scale(loss).backward()             # 对损失进行缩放,然后调用 backwardscaler.step(opt)                          # 使用缩放器进行优化器的一步scaler.update()                           # 额外调用更新缩放器return profile, loss, outputs

标准训练与 AMP 性能比较

最后,让我们测试标准训练循环 vs AMP训练循环的相对性能。

# 定义训练参数
n_epochs = 512
hidden_size = 256
n_hidden_layers = 2# 运行标准训练
profile, loss, outputs = test_standard_training(n_epochs, inputs, targets, hidden_size, n_hidden_layers)# 运行AMP训练
profile_amp, loss_amp, outputs_amp = test_amp_training(n_epochs, inputs, targets, hidden_size, n_hidden_layers)
print("Standard training:")
print(f"  Total time: {profile.duration:0.4f}s")
print(f"  Loss: {loss:0.4g}")
print(f"  Max memory allocated: {profile.max_memory_allocated/1024**2:0.4g} MB")print("\nAMP training:")
print(f"  Total time: {profile_amp.duration:0.4f}s")
print(f"  Loss: {loss_amp:0.4g}")
print(f"  Max memory allocated: {profile_amp.max_memory_allocated/1024**2:0.4g} MB")print(f"\nTraining speedup: {(1-profile_amp.duration/profile.duration)*100:0.2f}%")
print(f"Memory savings: {(1-profile_amp.max_memory_allocated/profile.max_memory_allocated)*100:0.2f}%")
Standard training:
Total time: 13.9817s
Loss: 0.01
Max memory allocated: 2061 KBAMP training:
Total time: 7.4969s
Loss: 0.008296
Max memory allocated: 1035 KBTraining speedup: 46.38%
Memory savings: 49.79%

通过AMP训练可以显著提高训练速度(提高46%)并减少近50%的内存使用! 而且,我们的AMP启用的训练并没有性能损失,反而导致了稍微更低的损失。

我们还可以通过可视化模型的输出来检查它们是否按照预期学习。我们看到标准训练和AMP训练都成功地逼近了我们的离散化2D正弦波。

fig, ax = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(10, 4))
ax[0].imshow(targets.squeeze().cpu())
ax[1].imshow(outputs.squeeze().detach().cpu())
ax[2].imshow(outputs_amp.squeeze().detach().cpu())
ax[0].set_title("Target")
ax[1].set_title("Standard")
ax[2].set_title("AMP")
plt.show()

png

具有自动混合精度的推断

我们还可以通过将推理的前向传播过程包装起来以使用 AMP(自动混合精度)来加速推理。

为了演示这一点,我们将定义标准和 AMP 推理函数。请注意,对于 AMP 推理,我们不需要 GradScaler(因为没有梯度!),我们只需要将前向传播过程包装在 torch.autocast 中:

def test_standard_inference(n_epochs, inputs, hidden_size, n_hidden_layers):"""测试标准训练循环"""model = ConvModel(hidden_size, n_hidden_layers).cuda()model.eval()with TorchMemProfile() as profile:for i in range(n_epochs):outputs = model(inputs)return profiledef test_amp_inference(n_epochs, inputs, hidden_size, n_hidden_layers):"""测试标准训练循环"""model = ConvModel(hidden_size, n_hidden_layers).cuda()model.eval()with TorchMemProfile() as profile:for i in range(n_epochs):with torch.autocast(device_type="cuda"):outputs = model(inputs)return profile

# 运行标准推理
inf_profile = test_standard_inference(n_epochs, inputs, hidden_size, n_hidden_layers)# 运行 AMP 推理
inf_profile_amp = test_amp_inference(n_epochs, inputs, hidden_size, n_hidden_layers)

print("Standard inference:")
print(f"  Total time: {inf_profile.duration:0.4f}s")
print(f"  Max memory allocated: {inf_profile.max_memory_allocated/1024**2:0.4g} MB")print("\nAMP inference:")
print(f"  Total time: {inf_profile_amp.duration:0.4f}s")
print(f"  Max memory allocated: {inf_profile_amp.max_memory_allocated/1024**2:0.4g} MB")print(f"\nSpeedup: {(1-inf_profile_amp.duration/inf_profile.duration)*100:0.2f}%")
print(f"Memory savings: {(1-inf_profile_amp.max_memory_allocated/inf_profile.max_memory_allocated)*100:0.2f}%")

输出结果如下:

Standard inference:Total time: 4.2401sMax memory allocated: 3338 MBAMP inference:Total time: 2.8737sMax memory allocated: 1676 MBSpeedup: 32.23%
Memory savings: 49.79%

我们再次看到最大内存使用量减少了近 50%。然而,在推理过程中加速仅为 32%,相比训练过程中观察到的 46%。这种差异是因为部分加速是在反向传播过程中实现的,而推理时不涉及反向传播。

附录

运行在本地主机

如果您不想使用 Docker,您也可以直接在本地计算机上运行本文中的代码 - 虽然这样做需要一些额外的工作。

  • 先决条件:

    • 安装ROCm 6.0.x

    • 确保您已安装 Python 3.11

    • 安装 PDM - 用于创建可复现的 Python 环境

  • 在本博客的根目录中创建 Python 虚拟环境,并安装所有依赖项:

    pdm sync
    
  • 启动 notebook

    pdm run jupyter-lab
    

导航到https://localhost:8888并运行本博客.

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

安卓设备adb执行AT指令控制电话卡

文章目录 AT指令起源与发展:基本格式:常见应用领域及功能:不同设备中的应用: 安卓获取modem设备输入符入口安卓设备输入AT指令 AT指令 AT 指令是 Attention 的缩写,是一种用于控制调制解调器等通信设备的指令集。 起…

RSTP的工作过程

RSTP简介: 生成树协议(STP)用于在网络中防止环路产生,但 STP 的收敛速度较慢。 RSTP(Rapid Spanning Tree Protocol )快速生成树协议:RSTP 是对 STP 的改进,它能在网络拓扑发生变化…

Django-生成csv文件

定义: python中生成csv文件: csv文件下载: 事例: 例子: urls: from django.urls import path from . import views urlpatterns [path(test_csv,views.test_csv), ] views: def test_csv(request):response Htt…

使用 RabbitMQ 有什么好处?

大家好,我是锋哥。今天分享关于【使用 RabbitMQ 有什么好处?】面试题。希望对大家有帮助; 使用 RabbitMQ 有什么好处? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 RabbitMQ 是一种流行的开源消息代理,广…

go 聊天系统项目-1

1、登录界面 说明:这一节的内容采用 go mod 管理【GO111MODULE‘’】的模块,从第二节开始使用【GO111MODULE‘off’】GOPATH 管理模块。具体参见 go 包相关知识 1.1登录界面代码目录结构 代码所在目录/Users/zld/Go-project/day8/chatroom/ 1.2登录…

Kimi出考题,考题提示词Prompt附上,培训机构试题、期中考试、人事入职试题全搞定

大家好,我是Shelly,一个专注于输出AI工具和科技前沿内容的AI应用教练,体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具,拥抱AI时代的到来。 AI工具集1:大厂AI工具【共23款…

蓝牙资讯|苹果AirPods Pro 2推出听力测试、助听器和听力保护等功能

苹果推送iOS 18.1 系统版本更新,AirPods Pro 2 用户也在 iOS 18.1 中获得了强大的新功能。 运行固件 7B19 的 AirPods Pro 2 用户,搭配 iOS 18.1 系统的 iPhone,将获得三项强大的听力健康功能:听力测试、助听器和听力保护。 听力…

【unique_str 源码学习】

文章目录 &#xff11;&#xff0e;删除器定义2. operator->() 运算符重载3. add_lvalue_reference<element_type>::type 使用 基本原理这篇博主写的很详细 https://yngzmiao.blog.csdn.net/article/details/105725663 &#xff11;&#xff0e;删除器定义 deleter_…

计算机网络:网络层 —— 多播路由选择协议

文章目录 多播路由选择协议多播转发树构建多播转发树基于源树的多播路由选择建立广播转发树建立多播转发树 组共享树的多播路由选择基于核心的生成树的建立过程 因特网的多播路由选择协议 多播路由选择协议 仅使用 IGMP 并不能在因特网上进行IP多播。连接在局域网上的多播路由…

【力扣打卡系列】删除链表重复节点

坚持按题型打卡&刷&梳理力扣算法题系列&#xff0c;语言为go&#xff0c;Day14 删除链表中的节点 题目描述 解题思路 删除指定节点&#xff08;只知道Node&#xff0c;不知道Node上一个节点的情况下&#xff09;将该节点&#xff08;node&#xff09;的后一个节点的值…

【大模型LLM面试合集】大语言模型架构_tokenize分词

tokenize分词 0.总览 分词方法特点被提出的时间典型模型BPE采用合并规则&#xff0c;可以适应未知词2016年GPT-2、RoBERTaWordPiece采用逐步拆分的方法&#xff0c;可以适应未知词2016年BERTUnigram LM采用无序语言模型&#xff0c;训练速度快2018年XLMSentencePiece采用汉字、…

opencv - py_imgproc - py_grabcut GrabCut 算法提取前景

文章目录 使用 GrabCut 算法进行交互式前景提取目标理论演示 使用 GrabCut 算法进行交互式前景提取 目标 在本章中 我们将了解 GrabCut 算法如何提取图像中的前景我们将为此创建一个交互式应用程序。 理论 GrabCut 算法由英国剑桥微软研究院的 Carsten Rother、Vladimir K…

内存马浅析

之前在jianshu上写了很多博客&#xff0c;但是安全相关的最近很多都被锁了。所以准备陆陆续续转到csdn来。内存马前几年一直是个很热门的漏洞攻击手段&#xff0c;因为相对于落地的木马&#xff0c;无文件攻击的内存马隐蔽性、持久性更强&#xff0c;适用的漏洞场景也更多。 J…

串口接收,不定长数据接收

###1.CUBE-MX配置串口 2.我采用串口中断接收&#xff0c;打开中断接口 3.时钟同样8倍频&#xff0c;1分频&#xff0c;使用内部时钟 打开串口中断 main() { __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 启用空闲中断__HAL_UART_ENABLE_IT(&huart1, UART_IT_R…

CentOS 7 更换软件仓库

CentOS 7 于2024年6月30日停止维护&#xff0c;官方仓库已经没有软件了&#xff0c;想要继续使用 &#xff0c;需要更换软件仓库&#xff0c;这里更换到阿里云的软件仓库 https://developer.aliyun.com/mirror/ 查看目前可用的软件数量 yum repolist 更换软件仓库&#xff1a…

Gorilla Mk1机器人:CubeMars电机加持,助力高空作业新突破

在澳大利亚输电网络的高空作业领域&#xff0c;一款由Crest Robotics研发的创新机器人正悄然改变着工作方式。这款名为Gorilla Mk1的机器人&#xff0c;凭借先进的技术和精密的动力系统&#xff0c;在高压输电线路的维护和检修作业中提供了前所未有的安全性和高效性。而这背后&…

DDRPHY数字IC后端设计实现系列专题之后端设计导入,IO Ring设计

本章详细分析和论述了 LPDDR3 物理层接口模块的布图和布局规划的设计和实 现过程&#xff0c;包括设计环境的建立&#xff0c;布图规划包括模块尺寸的确定&#xff0c;IO 单元、宏单元以及 特殊单元的摆放。由于布图规划中的电源规划环节较为重要&#xff0c; 影响芯片的布线资…

Pinia-状态管理

Pinia-状态管理 特点&#xff1a; 1. 轻量和模块化 Pinia 是一个轻量级的状态管理库&#xff0c;支持模块化管理&#xff0c;即可以将应用的状态分成多个 store 以实现更好的组织。使用 Pinia&#xff0c;可以定义多个 store&#xff0c;每个 store 都是一个独立的模块&#x…

WPF界面控件Essential Studio for WPF更新至2024 v3,具有更高性能 | 附下载

Essential Studio for WPF界面控件包含了利于分析且高性能的Windows应用程序开发中所需的所有控件&#xff0c;如 grids、charts、gauges、menus、calendars、editors等等。同时&#xff0c;我们的文件格式库还允许您导出资料到Excel、World和PDF文件中&#xff0c;以及对这些格…

相关衍生 pika+mongo

衍生相关 pikamongo 很多平台不提供完整的数据展示, 翻页只能翻几页,不过提供相关推荐等方法可获取更多的数据; 使用 rabbitmq 是因为数据量可能有几十上百万, 且能持久化 mongo对于数据并不实时的更新到查询里 def main():# mongodb# client MongoClient(localhost, 27017)cl…