记录学习记录学习《手动学习深度学习》这本书的笔记(九)

马不停蹄地来到了第十二章:计算性能……

感觉应该是讲并行计算方面的,比如GPU、CPU、CUDA那些。

第十二章:计算性能

12.1 编译器和解释器

这里先提出了命令式编程和符号式编程的概念。

命令式编程VS符号式编程

目前为止,本书讲的主要是命令式编程,通过直接的方式改变程序的状态,比如"+"、"print"这些,而符号式编程主要通过一些接口,主要关注任务的目的。

问了下ai:命令式编程主要关注每一步要做什么,改变程序状态一步步实现具体功能,达到预期结果;而符号式式编程关注数学符号和逻辑表达式的操作,主要用于逻辑推理。

命令式编程效率不高,因为编译器一步步执行这些操作,却不关注程序整体架构,比如一个函数可能连续调用两次,如果在一个或多个GPU上执行,则开销可能会非常大,并且每走一步都要保留以后可能不会用到的值……总之走一步看一步总是很麻烦的。

考虑另一种符号式编程,它不会马上计算每一步,只在完全定义了整个过程后才执行计算,深度学习的TensorFlow框架就使用了这种编程。

符号式编程流程:

①定义计算流程

②将流程编译成可执行程序

③给定输入,调用编译好的程序执行

而非命令式编程的一步步执行,所以这允许了大量优化,因为编译器在执行之前就可以看到完整的代码,在发现之后不需要某个变量后编译器就可以释放它的内存。

但之前我们一般都是用更好使用更好调试的命令式编程,因为无论是打印中间变量还是使用调试工具命令式编程都更简单。

符号式编程优点在效率高,程序容易移植,甚至可以将python程序移植到与python无关的格式中,使其在非python环境下运行。


混合编程

深度学习编程框架们有使用符号式也有使用命令式的,目前主流的Pytorch(命令式)和Tensorflow(混合式)都有向对方靠拢的趋势。

作者这里拿Pytorch举例(我的书是Pytorch版本的,电子版有Tensorflow版本)。

先构建一个普通MLP:

import torch
from torch import nn
from d2l import torch as d2l# 生产网络的工厂模式
def get_net():net = nn.Sequential(nn.Linear(512, 256),nn.ReLU(),nn.Linear(256, 128),nn.ReLU(),nn.Linear(128, 2))return netx = torch.randn(size=(1, 512)

命令式编程版本:

net = get_net()
net(x)

符号式编程:

net = torch.jit.script(net)
net(x)

测试两种编程方法的性能:

#@save
class Benchmark:"""用于测量运行时间"""def __init__(self, description='Done'):self.description = descriptiondef __enter__(self):self.timer = d2l.Timer()return selfdef __exit__(self, *args):print(f'{self.description}: {self.timer.stop():.4f} sec')net = get_net()
with Benchmark('无torchscript'):for i in range(1000): net(x)net = torch.jit.script(net)
with Benchmark('有torchscript'):for i in range(1000): net(x)

结果

并且符号式编程要求先定义并编译程序,编译程序的好处之一是可以将模型及其参数序列化保存到磁盘,这样保存的训练好的模型可以迁移到其他设备,与其他前端编程语言结合。

(看到这里想到前几天做的情感计算实验,文件就是有前端有后端,后端训练模型并保存,前端只需调用模型就行,这样提高了计算效率)

12.2 异步计算

Python是单线程的,它不擅长处理并行和异步代码。

(怪不得并行计算课的代码要用C语言实现……)

在深度学习框架中,Tensorflow采用异步编程模式提高性能,Pytorch则采用Python自有的调度器实现不同性能的权衡,GPU操作默认情况下是异步的,调用一个使用GPU的函数时,操作会在特定设备上排队,但之后控制权会立刻返还给使用者,不需要等待GPU完成这个任务再执行后续代码。这允许我们并行执行更多运算。

异步编程通过主动减少计算需求和相互依赖,开发更高效的程序。

作者再次做了个实验比较Pytorch(GPU上)和Numpy中矩阵相乘花费的时间:

with d2l.Benchmark('numpy'):for _ in range(10):a = numpy.random.normal(size=(1000, 1000))b = numpy.dot(a, a)with d2l.Benchmark('torch'):for _ in range(10):a = torch.randn(size=(1000, 1000), device=device)b = torch.mm(a, a)

结果:

因为Numpy的矩阵乘法是在CPU上执行,而Pytorch在GPU上,默认情况是异步的。

其实,Pytorch可以看作分为前端和后端,用户通过Python调用Pytorch,这是前端,而执行计算的后端主要由C++实现。用户调用Pytorch后,操作被传到后端执行,后端有自己的多线程,所以Pytorch支持异步计算。

注意:如果要按上述方式工作,后端必须跟踪整个计算图中各步骤直接依赖关系,因此不可以并行化相互依赖的工作。

这就是为什么编程时要主动减少相互依赖的操作。

作者举了一个很直观的例子:

比如这样一段代码:

x = torch.ones((1, 2), device=device)
y = torch.ones((1, 2), device=device)
z = x * y + 2
z

内部是这样运行的:

两个构造矩阵的操作可以并行实现,意思是前端只需要将任务返回后端队列,Python前端等待C++后端线程完成计算结果,而不需要实际计算,这样任务就可以并行计算。(前端Python的性能对计算任务没有什么影响)

前后端交互过程

总而言之,异步产生了一个相当灵活的前端。

电子书上还有一个直观的例子:

12.3 自动并行

 深度学习框架会在后端自动构建计算图。

比如上面那个例子中,初始化两个张量这个步骤,系统就可以选择并发运行它们。

模拟并行计算

在有多个计算设备的情况下,选择并发运行就可以大大提高效率,接下来作者用代码模拟了系统内部并发和不并发执行任务:

def run(x):return [x.mm(x) for _ in range(50)]

假设这个是我们的任务,也就是让 x 自乘50次。

然后设置两个 x ,分别放在两个GPU设备上。

devices = d2l.try_all_gpus()
x_gpu1 = torch.rand(size=(4000, 4000), device=devices[0])
x_gpu2 = torch.rand(size=(4000, 4000), device=devices[1])

torch.cuda.synchronize函数会等待当前设备计算执行结束才往后执行。

于是可以写出串行代码:

with d2l.Benchmark('GPU1 time'):run(x_gpu1)torch.cuda.synchronize(devices[0])with d2l.Benchmark('GPU2 time'):run(x_gpu2)torch.cuda.synchronize(devices[1])

删除俩任务之间的torch.cuda.synchronize,就可以实现俩GPU并行。

并行代码:

with d2l.Benchmark('GPU1 & GPU2'):run(x_gpu1)run(x_gpu2)torch.cuda.synchronize()

可以看出,总执行时间小于两部分执行时间的总和。

所以可以看出,深度学习框架中会默认让两个任务并行执行,提高计算效率。

模拟设备间通信

然后作者又用代码模拟了一个设备之间的通信。

之前我们可以将一个数据迁移到另一个设备上,那么数据可不可以边计算边迁移呢?

def copy_to_cpu(x, non_blocking=False):return [y.to('cpu', non_blocking=non_blocking) for y in x]

(利用Pytorch中的to函数将某个数据迁移设备)

按照之前的样子,写一个串行执行俩步骤的代码:

with d2l.Benchmark('在GPU1上运行'):y = run(x_gpu1)torch.cuda.synchronize()with d2l.Benchmark('复制到CPU'):y_cpu = copy_to_cpu(y)torch.cuda.synchronize()

这样的话效率不高,但是想想其实可以边运算边迁移,将运算完的部分先迁移过去。

这时候就要to函数中的non_blocking参数了,当这个参数为true时,就可以在不需要同步时调用同步,从而实现一边运算一边迁移。

并行代码:

with d2l.Benchmark('在GPU1上运行并复制到CPU'):y = run(x_gpu1)y_cpu = copy_to_cpu(y, True)torch.cuda.synchronize()

这样做就可以让系统先将运算完的部分先迁移,减少运行时间。


这两个任务看着简单,但在实际应用中要通过Python实现还是非常复杂的,比如一个简单的两层感知机在两个GPU和一个CPU下运行的例子:

手动调度其实是非常复杂的,这就体现了基于图的计算后端优化的优势了。

12.4 硬件

这一节主要是讲计算机组成方面的内容,理解计算机内部的组成对于实现高性能的算法有很大帮助。

首先,计算机是由以下关键部分组成的:

  • 中央处理器(CPU):运行计算机的大部分功能,如操作系统,也能够执行给定的程序。
  • 主存(RAM,也叫内存):用于存储计算结果,使CPU可以较快访问到数据。
  • 以太网连接:网络。
  • 高速扩展总线:用于连接GPU,通常用更高级的方式拓扑连接。
  • 持久性存储设备(辅存):固态硬盘(SSD)、硬盘驱动器(HDD),用高速扩展总线连接,提高传输速率。

接下来我们一一讲解这些组成。

1. 高速扩展总线

其中高速扩展总线由多个直连到CPU的通道组成,将CPU与网络、GPU、存储连接到一起。

在计算机上执行代码时,需要将数据移到处理器(CPU或GPU)上计算,然后将结果移到主存或辅存上,为了保证无缝衔接,就要拥有一个较快的高速扩展总线;如果是在多台设备上运行,就要有一个较快的以太网连接。

2. 内存

在读取内存方面有两种方式,一种是随机读取,直接跳到指定位置,只读取需要的部分数据;一种是突发读取,以连续的快速读取完成更大片的数据的访问。在数据连续存储的情况下,突发读取效率快得多。

GPU对内存存取速率的要求更高,内存也比CPU小。

3. 存储器

对于存储器,关键特性是带宽延迟

硬盘驱动器(HDD)比较古早,最大的优点是便宜,众多缺点之一是灾难性故障和高读取延迟。

主要原因是磁盘转速就那么快,如果太快会因为施加在盘片的离心力过大而破碎,性能很难有较大提升,对于较大数据集很难存储。

固态驱动器(SDD)就可以持久并且更快存储信息,它的设计方式使它必须满足一些条件:

  • 以块的方式存储信息。而块只能作为一个整体写入,需要耗费大量时间,按位写入时性能会非常差。
  • 存储单元磨损较快,所以不适合用于交换分区文件和大型日志文件。
  • 带宽大幅增加,必须与高速扩展总线相连。

还有一种存储器是云存储,虚拟机的存储在数量和速度上可以与用户需求相匹配。

4. CPU

中央处理器是计算机的核心。

它的关键组成部分有处理器核心(用于执行机器代码)、总线(用于连接不同组件)、缓存cache(缓解内存到核心间的传输速率)、向量处理单元(用于辅助高性能线性代数和卷积计算)。


每个处理器都由复杂的组件构成,前端加载指令并尝试预测用哪条路径,然后指令从汇编代码解码为微指令(更低级别的操作),最后才由实际核心处理。

通常执行指令的核心可以同时执行多个操作,所以高效的程序可以在每个时间周期执行多条独立指令。


在这里我们可以知道为什么对任务进行向量化,而不是单个单个求解效率会高很多。

为了满足需求,CPU需要在同一个时钟周期内执行许多操作,这种执行方式是通过向量处理单元实现的,处理单元可以执行单指令多数据(SIMD)操作。

比如八个任务要求将两个整数相加,就可以在一个时钟周期完成:

(这张图的意思是八对整数加法,先将八个整数排成向量,将俩向量相加)


然后书里讲了高速缓存Cache的内容,但这部分内容在计组和操作系统课里讲过,就简略带过。

总之Cache主要是缓解CPU核心处理速度过快,而主存到CPU数据传输相对过慢的情况,在两者中间加入速度介于两者之间的缓存,就可以大大缓解这种速度差。

CPU要从主存中读取数据时,先将主存中的一片数据读入Cache,如果将来CPU又要用到这些数据,或者要用到这些数据旁边的数据,就可以直接从Cache中查找。

5. GPU和其他加速卡

GPU对于深度学习非常非常重要。

虽然对于训练(需要反向传播,要求高精度)来说可能没什么,但对于推断(只需前向传播,不需要存储中间数据),我们需要更大的内存和处理能力。

在之前说过向量化能够提高运算效率,当然矩阵化就更好了。利用多个张量核,可以优化矩阵运算的数值精确度。

GPU不太擅长的主要在于稀疏数据和中断。

6. 网络和总线

单个设备不足时就要用到多个设备运算。

平常人们最常用的网络应该是wifi,但是在深度学习中wifi提供的带宽和延迟相对一般,下面介绍几种深度学习中更好用的互连方式。

  • PCIe(高速扩展总线):一种专用总线,适合大批量数据传输。
  • 以太网:连接计算机时最常用的方式,比上面那位慢,但优点是按照成本低,覆盖距离长。
  • 交换机:一种连接多个设备的方式,每一对设备都能同时进行点对点连接。
  • NVLink:PCIe的替代品,适用于带宽非常高的互连,纯粹的强大。

(ps:看完这些仿佛又回到了计网……)

12.5 多GPU训练

这一节主要讲如何用多个GPU并行进行神经网络的训练。

有三种思路,第一种是将不同层分配给不同GPU,第二种是将每一层任务拆解到不同GPU,第三种是跨多个GPU对数据进行拆分。

  • 第一种:网络并行。可以处理更大的网络,并且每个GPU的内存占用都能得到很好的控制。然而,GPU接口之间的密集同步很难实现,每个GPU间可能需要大量数据传输,总之不太好办。
  • 第二种,按层并行。是我们并行计算课实验的内容,比如将矩阵拆分到不同处理单元进行计算,或者按通道划分给不同GPU,这样就可以处理不断变大的网络了。但是,这需要很多同步操作,因为每一层都依赖上一层输出结果,需要传输的数据量不比第一种方法小,总之也不太好办。
  • 第三种,数据并行。对数据进行拆分,每个GPU处理小批量数据的部分训练,最后汇总梯度,这种方法最简单并且可以用于任何情况,只需要在每个小批量处理后同步。只不过添加更多GPU并对训练更大的模型没什么帮助。

三种方法的比较图如下:

另外,GPU的内存对训练很重要,内存大会很方便,在早期是个很棘手的问题,不过现在已经解决。


因为数据并行最好用且最实用,所以我们将按照数据并行的方法实现并行计算。

数据并行时系统内部大致如下(2个GPU的情况):

如图,随机小批量数据被分为2个部分,在不同GPU上计算梯度,最后汇总在一起。

k个GPU并行训练过程:

  1. 小批量数据被均匀分成k个部分,每个部分都被分给不同GPU。
  2. 每个GPU计算各自部分的损失和梯度。
  3. 将所有GPU上的梯度汇总,获得当前小批量的梯度。
  4. 聚合梯度再一次被分成k个部分,分给每个GPU更新参数。

实际中需要将小批量扩大成k的倍数,以便均匀分配,如果会显著增大数据量大小,那么相应可能还要提高学习率(关于为什么可以提高学习率,问了ai但是没有结果,我猜测是因为批量增大,方差更小,数据更稳定,学习率就可以提高),批量规范化也要调整,每个GPU独自进行批量归一化。

然后就是如何用代码实现,这里介绍了几种关键的技术:

1. 数据同步

这里需要构建一个函数,模拟两个GPU同步的过程,也就是两个设备计算出的梯度相加。

def allreduce(data):for i in range(1, len(data)):data[0][:] += data[i].to(data[0].device)for i in range(1, len(data)):data[i][:] = data[0].to(data[i].device)

运行结果示例:

2. 数据分发

这里直接借用了Pytorch框架中的函数,将数据分发给每个设备:

split = nn.parallel.scatter(data, devices)

其中data是需要分配的数据,devices是设备列表,返回的split是分发结果。

构建一个数据分发的函数:

#@save
def split_batch(X, y, devices):"""将X和y拆分到多个设备上"""assert X.shape[0] == y.shape[0]return (nn.parallel.scatter(X, devices),nn.parallel.scatter(y, devices))

然后我们就可以利用构建的这两个辅助函数构造多GPU的数据并行训练函数。

之前说过只要没有互相依赖的关系,系统就会自动并行计算,所以不需要添加什么并行计算的代码,直接用循环让各个设备计算自己的部分就行了。

单个批量训练:

def train_batch(X, y, device_params, devices, lr):X_shards, y_shards = split_batch(X, y, devices)# 在每个GPU上分别计算损失ls = [loss(lenet(X_shard, device_W), y_shard).sum()for X_shard, y_shard, device_W in zip(X_shards, y_shards, device_params)]for l in ls:  # 反向传播在每个GPU上分别执行l.backward()# 将每个GPU的所有梯度相加,并将其广播到所有GPUwith torch.no_grad():for i in range(len(device_params[0])):allreduce([device_params[c][i].grad for c in range(len(devices))])# 在每个GPU上分别更新模型参数for param in device_params:d2l.sgd(param, lr, X.shape[0]) # 在这里,我们使用全尺寸的小批量

随后的train函数和之前没什么区别,都是对每个批量执行上面的批量训练函数。

12.6 多GPU的简洁实现

也可以使用深度学习框架中的API实现,内部原理和上面差不多。

比起之前的训练函数,主要添加的步骤只有利用API在所有设备上设置模型。

def train(net, num_gpus, batch_size, lr):train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)devices = [d2l.try_gpu(i) for i in range(num_gpus)]def init_weights(m):if type(m) in [nn.Linear, nn.Conv2d]:nn.init.normal_(m.weight, std=0.01)net.apply(init_weights)# 在多个GPU上设置模型net = nn.DataParallel(net, device_ids=devices)trainer = torch.optim.SGD(net.parameters(), lr)loss = nn.CrossEntropyLoss()timer, num_epochs = d2l.Timer(), 10animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs])for epoch in range(num_epochs):net.train()timer.start()for X, y in train_iter:trainer.zero_grad()X, y = X.to(devices[0]), y.to(devices[0])l = loss(net(X), y)l.backward()trainer.step()timer.stop()animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu(net, test_iter),))print(f'测试精度:{animator.Y[0][-1]:.2f},{timer.avg():.1f}秒/轮,'f'在{str(devices)}')

即net = nn.DataParallel(net, device_ids=devices)这一步。

这一个API的具体作用有:

  • 将训练数据均匀分给每一个设备。
  • 将模型复制到每一个设备。
  • 每个GPU独立进行向前向后传播。
  • 向前传播时将所有结果汇总到默认第一个GPU上计算损失。
  • 向后传播时将所有梯度同步到默认第一个GPU上更新模型参数。

另外,代码中的X, y = X.to(devices[0]), y.to(devices[0])这步,可能是DataParallel会自动将所有在第一个GPU设备上的数据分给其他GPU。

总之,DataParallel会解决一切的(合十)。

12.7 参数服务器

在硬件的那一节我们讲到了设备间不同互连方式,它们的带宽差距可能很大,因此速率差距也很大。

下面会介绍一些提高计算效率的其他方法。

1. 数据并行训练

之前说道,三种并行方法中数据并行是最简单直接有效的,我们可以对它进行改良衍生变体。

比如最后先将所有梯度聚合在第一个GPU上,进行参数更新后又将参数重新广播给GPU们:

实际上不一定要在第一个GPU上聚合,可以在CPU上聚合,甚至可以依旧分配给不同GPU,在不同设备上聚合。

虽然听起来很梦幻,但它确实是可以实现的。

作者在这里举了一个具体的例子,比较了三种做法(在GPU上聚合、在CPU上聚合,分配给不同设备进行聚合)。

这个例子中的连接设备大概是,一个GPU可以同时为所有其他GPU发送不同数据,但不能两个GPU同时发送数据。

这时候第三种做法就很有优势了,每个GPU轮流将梯度分块分给各个GPU,在每个GPU上完成各自的聚合后再返还给各个GPU,可以实现高效的数据传递。

三种方法比较如下:

2. 环同步

有一些定制网络连接方式。

比如一些连接,每个GPU通过PCIe连接到CPU,每个GPU还有6个NVLink连接。

大概长这样:

我们可以将这个结构分成环,下面是两种分法:

(不管看成哪样的环,总之可以看成一个环)

这样梯度就可以按环传播,从第一个设备开始,它将它的梯度传给 2 设备,然后 2 设备计算自己的梯度和传来的梯度,相加之和给第 3 个设备……最后所有梯度相加的结果返回给第 1 个设备。

但这样很低效,因为计算实际是按照设备数量线性递增的。

然后我们就想到可以用之前那种方法,将梯度分块,每次每个设备传递部分梯度块。

流程如下:

虽然传递数据的时间还是随着节点数线性增加,但是计算可以同步完成,大大提高了效率。

3. 多机训练

在多台机器上训练。其实和之前差不多,只不过每个机器中又有多个GPU,每次计算完这个批量的梯度之和都要将梯度整合后返回中心的参数服务器。

这其中很重要的是同步,就是参数服务器接受多个机器的结果的过程。

但每个服务器的带宽有限,数据传输的时间开销会随着机器数目的增加而线性增长。

可以增加参数服务器数量解决。

4. 键-值存储

在许多工作节点和GPU中,梯度 i 的计算可定义为:

其中g_{ijk}是梯度 i 在工作节点 k ,GPU j 中拆分的梯度。

这样就可以假设 i 是键,gi 就是值。

定义两个操作push(累计梯度)和pull(取得聚合梯度),i 可以代表层。

  • push(key,value)将特定的梯度值从工作节点发送到公共存储,在那里通过某种方式(相加)来聚合值;

  • pull(key,value)从公共存储中取得某种方式(组合来自所有工作节点的梯度)的聚合值。

通过将所有复杂操作转化成这两个操作,就可以方便解释。

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

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

相关文章

模板引擎语法-过滤器

模板引擎语法-过滤器 文章目录 模板引擎语法-过滤器[toc]1.default过滤器2.default_if_none过滤器3.length过滤器4.addslashes过滤器5.capfirst过滤器6.cut过滤器7.date过滤器8.dictsort过滤器 1.default过滤器 default过滤器用于设置默认值。default过滤器对于变量的作用&…

make学习三:书写规则

系列文章目录 Make学习一:make初探 Make学习二:makefile组成要素 文章目录 系列文章目录前言默认目标规则语法order-only prerequisites文件名中的通配符伪目标 Phony Targets没有 Prerequisites 和 recipe内建特殊目标名一个目标多条规则或多个目标共…

网络安全技能大赛B模块赛题解析Server12环境

已知靶机存在⽹站系统,使⽤Nmap⼯具扫描靶机端⼝,并将⽹站服务的端⼝号作为Flag (形式:Flag字符串)值提交 使用nmap扫描目标靶机网站服务的端口号为8089 Falg:8089 访问⽹站/admin/pinglun.asp⻚⾯&#…

1、Linux操作系统下,ubuntu22.04版本切换中英文界面

切换中英文界面的方法很多,我也是按照一个能用的方法弄过来并且记录, 1.如果刚开始使用Ubuntu环境,桌面的语言环境为英文,需要安装中文简体的字体包 打开桌面终端,输入 sudo apt install language-pack-zh-hans lan…

SmolVLM2: The Smollest Video Model Ever(六)

继续微调 微调视频的代码如下: # 此Python文件用于对SmolVLM2进行视频字幕任务的微调 # 导入所需的库 import os os.environ["CUDA_VISIBLE_DEVICES"] "1" import torch from peft import LoraConfig, prepare_model_for_kbit_training, get…

Spring Boot安装指南

🔖 Spring Boot安装指南 🌱 Spring Boot支持两种使用方式: 1️⃣ 可作为常规Java开发工具使用 2️⃣ 可作为命令行工具安装 ⚠️ 安装前提: 📌 系统需安装 Java SDK 17 或更高版本 🔍 建议先运行检查命令…

数据结构(七)---链式栈

#### 链式栈实现 ##### linkstack.h #ifndef _LINKSTACK_H #define _LINKSTACK_H // 引入相关的库文件 #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义元素类型的别名 typedef int DATA; //定义链式栈节点 typedef struct node { …

【Spring Boot】Maven中引入 springboot 相关依赖的方式

文章目录 Maven中引入 springboot 相关依赖的方式1. 不使用版本管理&#xff08;不推荐&#xff09;2、使用版本管理&#xff08;推荐&#xff09;2.1 继承 spring-boot-starter-parent2.2 使用 spring-boot-dependencies 自定义父工程2.3引入 spring-framework-bom Maven中引…

DataStreamAPI实践原理——快速上手

引入 通过编程模型&#xff0c;我们知道Flink的编程模型提供了多层级的抽象&#xff0c;越上层的API&#xff0c;其描述性和可阅读性越强&#xff0c;越下层API&#xff0c;其灵活度高、表达力越强&#xff0c;多数时候上层API能做到的事情&#xff0c;下层API也能做到&#x…

WPF 图片文本按钮 自定义按钮

效果 上面图片,下面文本 样式 <!-- 图片文本按钮样式 --> <Style x:Key="ImageTextButtonStyle" TargetType="Button"><Setter Property="Background" Value="Transparent"/><Setter Property="BorderTh…

驱动开发硬核特训 · Day 22(上篇): 电源管理体系完整梳理:I2C、Regulator、PMIC与Power-Domain框架

&#x1f4d8; 一、电源子系统总览 在现代Linux内核中&#xff0c;电源管理不仅是系统稳定性的保障&#xff0c;也是实现高效能与低功耗运行的核心机制。 系统中涉及电源管理的关键子系统包括&#xff1a; I2C子系统&#xff1a;硬件通信基础Regulator子系统&#xff1a;电源…

设计模式全解析:23种经典设计模式及其应用

创建型模式 1. 单例模式&#xff08;Singleton Pattern&#xff09; 核心思想&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点。适用场景&#xff1a;需要共享资源的场景&#xff0c;如配置管理、日志记录等。 public class Singleton {// 静态变量保存…

力扣热题100题解(c++)—矩阵

73.矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 int m matrix.size(); // 行数int n matrix[0].size(); // 列数bool firstRowZero false; // 标记第一行是否包含 0bool f…

本地部署DeepSeek-R1(Dify升级最新版本、新增插件功能、过滤推理思考过程)

下载最新版本Dify Dify1.0版本之前不支持插件功能&#xff0c;先升级DIfy 下载最新版本&#xff0c;目前1.0.1 Git地址&#xff1a;https://github.com/langgenius/dify/releases/tag/1.0.1 我这里下载到老版本同一个目录并解压 拷贝老数据 需先停用老版本Dify PS D:\D…

PostSwigger Web 安全学习:CSRF漏洞3

CSRF 漏洞学习网站&#xff1a;What is CSRF (Cross-site request forgery)? Tutorial & Examples | Web Security Academy CSRF Token 基本原理 CSRF Token 是服务端生成的唯一、随机且不可预测的字符串&#xff0c;用于验证客户端合法校验。 作用&#xff1a;防止攻击…

用 Nodemon 解决 npm run serve 频繁重启服务

Nodemon 是一个基于 Node.js 构建的开发工具&#xff0c;专为帮助开发者自动监控项目文件的更改而设计。每当文件发生变更时&#xff0c;Nodemon 会自动重启 Node.js 服务器&#xff0c;无需手动停止并重启。这对于提升开发速度、减少人工操作非常有帮助&#xff0c;尤其适用于…

django admin 中更新表数据 之后再将数据返回管理界面

在Django中&#xff0c;更新数据库中的数据并将其重新显示在Django Admin界面上通常涉及到几个步骤。这里我将详细说明如何在Django Admin中更新表数据&#xff0c;并确保更新后的数据能够立即在管理界面上显示。 定义模型 首先&#xff0c;确保你的模型&#xff08;Model&…

真.从“零”搞 VSCode+STM32CubeMx+C <1>构建

目录 前言 准备工作 创建STM32CubeMx项目 VSCode导入项目&配置 构建错误调试 后记 前言 去年10月开始接触单片机&#xff0c;一直在用树莓派的Pico&#xff0c;之前一直用Micropython&#xff0c;玩的不亦乐乎&#xff0c;试错阶段优势明显&#xff0c;很快就能鼓捣一…

C语言学习之结构体

在C语言中&#xff0c;我们已经学了好几种类型的数据。比如整型int、char、short等&#xff0c;浮点型double、float等。但是这些都是基本数据类型&#xff0c;而这些数据类型应用在实际编程里显然是不够用的。比如我们没有办法用一旦数据类型来定义一个”人“的属性。因此这里…

架构-计算机系统基础

计算机系统基础 一、计算机系统组成 &#xff08;一&#xff09;计算机系统层次结构 硬件组成 主机&#xff1a;包含CPU&#xff08;运算器控制器&#xff09;、主存储器&#xff08;内存&#xff09;。外设&#xff1a;输入设备、输出设备、辅助存储器&#xff08;外存&…