在 AMD GPU 上构建解码器 Transformer 模型

Building a decoder transformer model on AMD GPU(s) — ROCm Blogs

2024年3月12日 作者 Phillip Dang.

在这篇博客中,我们展示了如何使用 PyTorch 2.0 和 ROCm 在单个节点上的单个和多个 AMD GPU 上运行Andrej Karpathy’s beautiful PyTorch re-implementation of GPTGPT PyTorch 重新实现Andrej Karpathy’s beautiful PyTorch re-implementation of GPT。

GPT 模型的核心是仅解码器的 transformer 架构。这种架构以自回归的方式一次生成一个输出标记(在我们的情况下是字符),这意味着每个生成的标记都依赖于之前生成的标记。如需深入了解该模型的工作原理,我们强烈推荐查看以下内容:

  • Andrej Karpathy的 Let’s build GPT: 从头开始,用代码详细阐述 视频

  • Attention Is All You Need 论文。

我们首先使用 PyTorch 和 ROCm 在单个 GPU 上训练我们的模型,然后稍微修改代码以使用 to run on two GPUs using PyTorch 的分布式数据并行(Distributed Data Parallel, DDP)在两个 GPU 上运行。

在多 GPU 上进行分布式数据并行性的工作原理是:

  1. 将当前全局训练批次拆分为每个 GPU 上的小局部批次。例如,如果你有 8 个 GPU,且全局批次设置为 32 个样本,则每个 8 个 GPU 将具有 4 个样本的局部批次大小。

  2. 将模型复制到每个设备,以便每个设备可以独立处理其局部批次。

  3. 运行前向传播,然后是反向传播,并输出模型权重相对于局部批次损失的渐变。这会在多个设备上并发发生。

  4. 同步每个设备计算的局部梯度并将它们结合起来以更新模型权重。更新后的权重会重新分发到每个设备。

如需深入了解 PyTorch 中的分布式训练,请参阅 使用 DDP 进行多 GPU 训练。

先决条件

要跟随本博客的内容进行操作,您需要具备以下软件:

  • ROCm

  • PyTorch

  • Linux OS

接下来,通过运行以下代码确保您的系统能够识别两块 AMD GPU:

! rocm-smi --showproductname

输出结果应如下所示:

================= ROCm System Management Interface ================
========================= Product Info ============================
GPU[0] : Card series: Instinct MI210
GPU[0] : Card model: 0x0c34
GPU[0] : Card vendor: Advanced Micro Devices, Inc. [AMD/ATI]
GPU[0] : Card SKU: D67301
GPU[1] : Card series: Instinct MI210
GPU[1] : Card model: 0x0c34
GPU[1] : Card vendor: Advanced Micro Devices, Inc. [AMD/ATI]
GPU[1] : Card SKU: D67301
===================================================================
===================== End of ROCm SMI Log =========================

确保 PyTorch 也能识别这些 GPU:

import torch
import torch.nn as nn
from torch.nn import functional as F
print(f"number of GPUs: {torch.cuda.device_count()}")
print([torch.cuda.get_device_name(i) for i in range(torch.cuda.device_count())])

输出结果应如下所示:

number of GPUs: 2
['AMD Radeon Graphics', 'AMD Radeon Graphics']

加载数据

我们使用 tiny_shakespeare 数据集,该数据集包含来自莎士比亚各种剧作的40,000行文本。让我们加载它并查看前200个字符。

! wget https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt
with open('input.txt', 'r', encoding='utf-8') as f:
    text = f.read()
print(text[:200])

输出:

First Citizen:
Before we proceed any further, hear me speak.

All:
Speak, speak.

First Citizen:
You are all resolved rather to die than to famish?

All:
Resolved. resolved.

First Citizen:
First, you

接下来,我们从文本中的唯一字符创建我们的词汇表,并建立字符与整数之间的映射。

chars = sorted(list(set(text)))
vocab_size = len(chars)
# create a mapping from characters to integers
stoi = { ch:i for i,ch in enumerate(chars) }
itos = { i:ch for i,ch in enumerate(chars) }
encode = lambda s: [stoi[c] for c in s] # encoder: take a string, output a list of integers
decode = lambda l: ''.join([itos[i] for i in l]) # decoder: take a list of integers, output a string

现在,我们将数据在编码字符之后分成训练集和测试集(即将每个字符转换为一个整数)。

data = torch.tensor(encode(text), dtype=torch.long)
n = int(0.9*len(data)) # first 90% will be train, rest val
train_data = data[:n]
val_data = data[n:]

让我们来看一个输入和目标的例子。

block_size = 8
x = train_data[:block_size]
y = train_data[1:block_size+1]
for t in range(block_size):
    context = x[:t+1]
    target = y[t]
    print(f"when input is {context} the target: {target}")

输出:

when input is tensor([18]) the target: 47
when input is tensor([18, 47]) the target: 56
when input is tensor([18, 47, 56]) the target: 57
when input is tensor([18, 47, 56, 57]) the target: 58
when input is tensor([18, 47, 56, 57, 58]) the target: 1
when input is tensor([18, 47, 56, 57, 58,  1]) the target: 15
when input is tensor([18, 47, 56, 57, 58,  1, 15]) the target: 47
when input is tensor([18, 47, 56, 57, 58,  1, 15, 47]) the target: 58

创建解码器transformer模型

让我们设置一些超参数,用于构建和训练模型。因为我们能够使用强大的AMD GPU,可以扩展我们的网络,并设置与Andrej在其视频教程中使用的相同的超参数。这包括增加我们的批量大小、块大小、层数和头数以及嵌入大小。我们希望这将使我们的损失值更低。

# 超参数
batch_size = 64 # 我们将并行处理多少个独立序列?
block_size = 256 # 预测的最大上下文长度是多少?
max_iters = 5000
eval_interval = 100
learning_rate = 3e-4
device = 'cuda' if torch.cuda.is_available() else 'cpu'
eval_iters = 200
n_embd = 384
n_head = 6
n_layer = 6
dropout = 0.2
# ------------

torch.manual_seed(1337) # 设置手动种子以保证可重复性

解码器Transformer架构

以下是我们的主要模型类,它创建了一个解码器Transformer架构。其组件包括:

  • 自注意力机制:允许解码器在生成输出时权衡输入序列的不同部分。

  • 掩码自注意力:通过掩盖后续位置,防止解码器在训练期间看到未来的令牌。

  • 解码器层:由多层子层组成,如多头注意力和前馈神经网络,促进信息处理和生成。

Transformer decoder process

class Head(nn.Module):
    """ 一个自注意力头  """

    def __init__(self, head_size):
        super().__init__()
        self.key = nn.Linear(n_embd, head_size, bias=False)
        self.query = nn.Linear(n_embd, head_size, bias=False)
        self.value = nn.Linear(n_embd, head_size, bias=False)
        self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))

        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        # 输入大小 (batch, time-step, channels)
        # 输出大小 (batch, time-step, head size)
        B,T,C = x.shape
        k = self.key(x)   # (B,T,hs)
        q = self.query(x) # (B,T,hs)
        # 计算注意力分数 ("affinities")
        wei = q @ k.transpose(-2,-1) * k.shape[-1]**-0.5 # (B, T, hs) @ (B, hs, T) -> (B, T, T)
        # 使用上三角矩阵技巧创建掩码用于自注意力机制
        # 该掩码确保在训练期间,解码器只能关注到当前生成的令牌之前的位置,防止看到未来的令牌,即仅从左到右注意
        wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # (B, T, T)
        wei = F.softmax(wei, dim=-1) # (B, T, T)
        wei = self.dropout(wei)
        #  执行值的加权聚合
        v = self.value(x) # (B,T,hs)
        out = wei @ v # (B, T, T) @ (B, T, hs) -> (B, T, hs)
        return out

class MultiHeadAttention(nn.Module):
    """ 并行的多头自注意力机制 """

    def __init__(self, num_heads, head_size):
        super().__init__()
        self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
        self.proj = nn.Linear(head_size * num_heads, n_embd)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        out = torch.cat([h(x) for h in self.heads], dim=-1)
        out = self.dropout(self.proj(out))
        return out

class FeedFoward(nn.Module):
    """ 一个简单的线性层及其非线性激活 """

    def __init__(self, n_embd):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(n_embd, 4 * n_embd),
            nn.ReLU(),
            nn.Linear(4 * n_embd, n_embd),
            nn.Dropout(dropout),
        )

    def forward(self, x):
        return self.net(x)

class Block(nn.Module):
    """ Transformer块: 通信后是计算  """

    def __init__(self, n_embd, n_head):
        # n_embd: 嵌入维度,n_head: 多头数量
        super().__init__()
        head_size = n_embd // n_head
        self.sa = MultiHeadAttention(n_head, head_size)
        self.ffwd = FeedFoward(n_embd)
        self.ln1 = nn.LayerNorm(n_embd)
        self.ln2 = nn.LayerNorm(n_embd)

    def forward(self, x):
        x = x + self.sa(self.ln1(x))
        x = x + self.ffwd(self.ln2(x))
        return x

class GPTLanguageModel(nn.Module):

    def __init__(self):
        super().__init__()
        # 每个令牌直接从查找表中读取下一个令牌的logits
        self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
        self.position_embedding_table = nn.Embedding(block_size, n_embd)
        self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
        self.ln_f = nn.LayerNorm(n_embd) # final layer norm
        self.lm_head = nn.Linear(n_embd, vocab_size)

        # 更好的权重初始化,原始GPT视频中未涵盖,但很重要,将在后续视频中介绍
        self.apply(self._init_weights)

    def _init_weights(self, module):
        if isinstance(module, nn.Linear):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)

    def forward(self, idx, targets=None):
        B, T = idx.shape

        # idx 和 targets 都是 (B, T) 的整数张量
        tok_emb = self.token_embedding_table(idx) # (B,T,C)
        pos_emb = self.position_embedding_table(torch.arange(T, device=device)) # (T,C)
        x = tok_emb + pos_emb # (B,T,C)
        x = self.blocks(x) # (B,T,C)
        x = self.ln_f(x) # (B,T,C)
        logits = self.lm_head(x) # (B,T,vocab_size)

        if targets is None:
            loss = None
        else:
            B, T, C = logits.shape
            logits = logits.view(B*T, C)
            targets = targets.view(B*T)
            loss = F.cross_entropy(logits, targets)

        return logits, loss

    def generate(self, idx, max_new_tokens):
        # idx 是 (B, T) 当前上下文的索引数组
        for _ in range(max_new_tokens):
            # 将 idx 裁剪为最后 block_size 个令牌
            idx_cond = idx[:, -block_size:]
            # 获取预测结果
            logits, loss = self(idx_cond)
            # 只关注最后一个时间步
            logits = logits[:, -1, :] # becomes (B, C)
            # 应用 softmax 获得概率
            probs = F.softmax(logits, dim=-1) # (B, C)
            # 从概率分布中采样
            idx_next = torch.multinomial(probs, num_samples=1) # (B, 1)
            # 将采样得到的索引追加到运行的序列中
            idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)
        return idx

实用函数

在训练我们的模型之前,我们需要两个实用函数:

  • 在训练我们的模型之前,我们需要两个实用函数:

  • 一个是获取随机批量数据的函数

为了估计损失:

@torch.no_grad()
def estimate_loss(model):
    out = {}
    model.eval()
    for split in ['train', 'val']:
        losses = torch.zeros(eval_iters)
        for k in range(eval_iters):
            X, Y = get_batch(split)
            logits, loss = model(X, Y)
            losses[k] = loss.item()
        out[split] = losses.mean()
    model.train()
    return out

为了获取一个小批量的数据:

# 数据加载
def get_batch(split):
    # 数据加载
    data = train_data if split == 'train' else val_data
    ix = torch.randint(len(data) - block_size, (batch_size,))
    x = torch.stack([data[i:i+block_size] for i in ix])
    y = torch.stack([data[i+1:i+block_size+1] for i in ix])
    x, y = x.to(device), y.to(device)
    return x, y

训练和推理 

现在我们已经准备好所有的部分,让我们实例化我们的模型,进行训练,并运行一些推理来生成我们(希望)类似莎士比亚风格的文本。

这是我们的主函数:

def main():
    model = GPTLanguageModel()
    model = model.to(device)

    # 输出模型的参数数量
    print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters')

    # 创建一个 PyTorch 优化器
    optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)

    for iter in range(max_iters):

        # 定期评估训练集和验证集的损失
        if iter % eval_interval == 0 or iter == max_iters - 1:
            losses = estimate_loss(model)
            print(f"step {iter}: train loss {losses['train']:.4f}, val loss {losses['val']:.4f}")

        # 抽取一个数据批次
        xb, yb = get_batch('train')

        # 计算损失
        logits, loss = model(xb, yb)
        optimizer.zero_grad(set_to_none=True)
        loss.backward()
        optimizer.step()

    # 使用模型进行推理以生成文本
    context = torch.zeros((1, 1), dtype=torch.long, device=device)
    print(decode(model.generate(context, max_new_tokens=2000)[0].tolist()))

我们创建了一个名为 gpt_single_gpu.py 的脚本,包含所有必要的命令。要运行它,请使用以下代码:

python3 gpt_single_gpu.py

以下是脚本的预期输出:

10.788929 M parameters
step 0: train loss 4.2221, val loss 4.2306
step 100: train loss 2.4966, val loss 2.5012
step 200: train loss 2.4029, val loss 2.4295
...
step 4900: train loss 0.8676, val loss 1.5644
step 4999: train loss 0.8598, val loss 1.5677

以下是我们生成的文本的前几行:

Thou fellow'dst idst the game of his names;
And yet since was Menenius, one would thrident again
That Anne. But where shall do become me injuries?

JULIET:
O though often thee cortainted matter,--
A better with him he gone hath
A colder-balm equal-deniving,
Of what a peril the people, when he did make me
Disobedition, become him to see
That conceive on earth fitting his finger,

在验证损失为1.5677时,生成的文本看起来几乎像英语,并且模型能够学习莎士比亚输入的对话风格。

每次运行推理时,我们期望的结果会有所不同。这是因为模型从所有可能的标记分布中进行采样,其中每个标记的概率由 softmax 函数给出。

分布式训练在多 GPU 上

为了在 PyTorch 中使用多个 GPU 在单个节点上训练我们的模型,我们将使用 PyTorch 的分布式数据并行 (Distributed Data Parallel)。 为此,我们只需要对当前代码进行一点点修改。

首先,让我们从 PyTorch 导入一些所需的函数:

import torch.multiprocessing as mp
from torch.distributed import init_process_group, destroy_process_group
from torch.nn.parallel import DistributedDataParallel as DDP

接下来,让我们设置分布式数据并行组。 通常,每个 GPU 运行一个进程,因此我们需要设置一个组,以便所有进程和 GPU 之间可以相互通信。 让我们创建一个执行此操作的小函数。

def ddp_setup(rank, world_size):
    """
    world_size: GPU 的数量
    rank: GPU 的 ID,从 0 到 world_size - 1
    """

    os.environ['MASTER_ADDR'] = 'localhost'
    os.environ['MASTER_PORT'] = '12355'

    # 初始化进程组
    backend = "nccl"
    init_process_group(backend=backend, rank=rank, world_size=world_size)
    torch.cuda.set_device(rank)

在实例化模型之前,我们需要调用这个设置函数。 接下来,我们对主函数进行些许修改,使其能够在多个 GPU 上运行。 请注意,主函数现在接收两个参数:rank(GPU 的 ID)和 world_size(GPU 的数量)。

def main(rank:int, world_size:int):
    print(f"Training DDP model on rank/gpu {rank}.")
    ddp_setup(rank, world_size)

    # 每个 gpu/进程获得不同的种子
    torch.manual_seed(1337 + rank)

    model = GPTLanguageModel()
    model.to(rank)
    model = DDP(model, device_ids=[rank])
    ... # 剩下的训练过程与单进程主函数的训练过程相同

    # 运行推理
    if rank == 0: # 仅在主进程上运行推理。没有这个 if 语句,每个进程都会运行自己的预测
        print('generating text')
        context = torch.zeros((1, 1), dtype=torch.long, device=device)
        # 因为模型现在是一个分布式模型,我们需要通过添加 "module" 来解包它
        print(decode(model.module.generate(context, max_new_tokens=500)[0].tolist()))

    #  一旦模型训练完成,销毁进程以干净退出
    destroy_process_group()

完整代码

我们创建了一个脚本 gpt_multiple_gpus.py ,其中包含所有必需的命令。要运行该脚本,请使用以下代码:

python3 gpt_multiple_gpus.py

以下是该脚本的预期输出。

We have 2 GPUs! Using 2 GPUs
Training DDP model on rank/gpu 1.
Training DDP model on rank/gpu 0.
10.788929 M parameters
10.788929 M parameters
GPU/rank 0 step 0: train loss 4.2221, val loss 4.2306
GPU/rank 1 step 0: train loss 4.2228, val loss 4.2304
GPU/rank 0 step 500: train loss 1.6010, val loss 1.7904
GPU/rank 1 step 500: train loss 1.5984, val loss 1.7871
...
GPU/rank 1 step 4999: train loss 0.5810, val loss 1.7733
GPU/rank 0 step 4999: train loss 0.5807, val loss 1.7723

以下是生成的文本:

HENRY BOLINGBROKE:
Warwick, It say; and he is safe, and whose
With unmorable slaves, they have stafd too:
So say the tidings you for Richmond, with ride?

BUSHY:
Marry, my Lord Clarence to my noble lady.

GLOUCESTER:
Go, to thee to thy daughter as I may break;
And what do now thy will I, I do me say
My name; it is the king.

BUCKINGHAM:
'Twas every stranger:--
Nay, my good son.

输出来自两个不同的GPU(rank 0和1),每个训练/验证损失是不同的。这是因为每个进程有不同的种子,以确保它们不会在相同的数据批次上进行训练。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW Modbus通讯稳定性提升

在LabVIEW开发Modbus通讯程序时,通讯不稳定是一个常见问题,可能导致数据丢失、延迟或错误。为了确保通讯的可靠性,可以从多个角度进行优化,以下是一些有效的解决方案,结合实际案例进行分析。 1. 优化通讯参数设置 通讯…

rtp协议:rtcp包发送和接收规则和报告!

RTCP Packet Send and Receive Rules: 发送和接收 RTCP 包的规则在此列出。允许在多播环境或多点单播环境中运行的实现必须满足第 6.2 节中的要求。这样的实现可以使用本节定义的算法来满足这些要求,或者可以使用其他算法,只要其性能等同或更…

泄密?不可能!谨记10个确保公司数据不泄密的措施,你必须了解!(企业防泄密的最佳选择)

泄密?不可能!这10个确保公司数据不泄密的措施,你必须谨记! 在数据为王的时代,企业信息的保密性直接关系到其核心竞争力与市场地位。 然而,数据泄露事件却屡见不鲜,给企业的声誉和利益带来巨大…

Nacos异地备份方案

Nacos sync的实现样例 项目地址 软件下载:https://github.com/nacos-group/nacos-sync/releases 官方文档:https://nacos.io/docs/v2/ecology/use-nacos-sync/#_top 介绍 NacosSync是一个支持多种注册中心的同步组件,基于Spring boot开发…

STL-常用容器-list

1list基本概念 **功能:**将数据进行链式存储 链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 链表的组成:链表由一系列结点组成 结点的组成:一个是存储…

qt配置https请求

qt应用版本 windows 32位 先说下心理路程,你能遇到的我都遇到了,你能想到的我都想到了,怎么解决看这一篇就够了,从上午12点到晚上12点几乎没离开电脑(除了吃饭),对于openssl这种用的时候无感&am…

Outlook域名邮箱设置教程:配置烽火邮箱?

Outlook域名邮箱同步技巧?如何注册outlook域名邮箱? Outlook域名邮箱不仅提供了高效的邮件管理功能,还能与企业的域名相结合,提升品牌形象。烽火将详细介绍如何在Outlook中配置烽火邮箱,确保您能够顺利使用Outlook域名…

RabbitMQ替换默认端口

前提:客户通过漏洞扫描,发现rabbitmq中的erlang是默认端口4369,出于安全的考虑,需要将erlang的端口修改为其他的端口。 1.查看默认erlang的默认端口 netstat -plnt | grep 4369 2.关闭rabbitmq rabbitmqctl stop(注…

Elastic Stack - FileBeat 入门浅体验

Filebeat 是 Elastic Stack 中的一个轻量级日志转发器,主要用于收集和转发日志数据。Filebeat 作为代理安装在您的服务器上,可以监控您指定的日志文件或位置,收集日志事件,并将其转发到 Elasticsearch 或 Logstash 进行索引。 一…

HTTP cookie 与 session

一.Cookie 定义: 是服务器发送到用户浏览器并保存在浏览器上的一小块数据, 它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器上。 通常, 它用于告知服务端两个请求是否来自同一浏览器, 如保持用户的登录状态、 …

Windows下将网盘挂载到本地使用(Docker+AList+RaiDrive)

文章目录 安装安装Docker安装Alist安装RaiDrive 安装 安装Docker Windows下安装Docker网上有很多教程,也可以参考我写的博客链接 3.1章节 安装Alist 官网 “切换中文”并找到“使用指南” ”安装“–>"使用Docker” 打开cmd执行如下命令启动容器 do…

怎样消除原唱只留伴奏?一键消音!伴奏版轻松制作

在音乐制作、翻唱或卡拉OK等场合,我们经常需要消除歌曲中的原唱声音,只保留伴奏部分。这样做可以让其他人更容易地在原有伴奏的基础上进行演唱或创作。但是,要怎样才能有效地消除原唱,同时又不损害伴奏的质量呢?本文将…

【vue+Naive UI】自定义tabbar切换,以及使用n-data-table展示对应表格实战

效果图如上 ##### 一、具体代码 <template><div :style="{ width }" class="top-tab-selector"><divclass=

springboot092安康旅游网站的设计与实现(论文+源码)_kaic

毕业设计&#xff08;论文&#xff09; 基于JSP的安康旅游网站的设计与实现 姓  名 学  号 院  系 专  业 指导老师 2021 年 月 教务处制 目 录 目 录 摘 要 Abstract 第一章 绪论 1.1 研究现状 1.2 设…

【棉花种类数据集】棉花种类识别 深度学习 人工智能 含数据集

一、背景意义 棉花是全球重要的经济作物&#xff0c;广泛应用于纺织和医药等多个领域。随着可持续发展和高效农业的需求增加&#xff0c;研究不同棉花种类的特性和产量变得尤为重要。利用深度学习技术对这些种类进行分类和识别&#xff0c;不仅能够提升农业生产效率&#xff0c…

Cesium基础-(Entity)-(Corridor 走廊)

里边包含Vue、React框架代码详细步骤、以及代码详细解释 4、Corridor 走廊 以下是 CorridorGeometry 类的属性、方法和静态方法,以表格形式展示: 属性 属性名类型默认值描述positionsArray.定义走廊中心的坐标点数组。widthnumber走廊

CH569开发前的测试

为了玩转准备Ch569的开发工作 &#xff0c;准备了如下硬件和软件&#xff1a; 硬件 1.官方的 Ch569 开发板&#xff0c;官方买到的是两块插接在一起的&#xff1b;除了HSPI接口那里的电阻&#xff0c;这两块可以说是一样的。也意味着两块板子的开发也需要烧录两次&#xff1b…

深度学习(五):语音处理领域的创新引擎(5/10)

一、深度学习在语音处理中的崛起 在语音处理领域&#xff0c;传统方法如谱减法、维纳滤波等在处理复杂语音信号时存在诸多局限性。这些方法通常假设噪声是平稳的&#xff0c;但实际噪声往往是非平稳的&#xff0c;导致噪声估计不准确。同时&#xff0c;为了去除噪声&#xff0…

如何通过自动化有效地简化 Active Directory 操作?

我们都知道规模稍微大一点的企业为了便于计算机的管理&#xff0c;基本都上了微软的AD域控制器。 那么肯定就会存在这么一个问题&#xff0c; 不断的会有计算机加入或者是退出域控制器&#xff0c;批量的创建、修改、删除AD域用户&#xff0c;如果企业的架构需要改变&#xff…

ctfshow(175->178)--SQL注入--联合注入及其过滤

Web175 进入界面&#xff1a; 审计&#xff1a; 查询语句&#xff1a; $sql "select username,password from ctfshow_user5 where username !flag and id ".$_GET[id]." limit 1;";返回逻辑&#xff1a; if(!preg_match(/[\x00-\x7f]/i, json_enc…