一文搞懂:如何在深度学习中使用GPU和cuda加速

一文搞懂如何在深度学习中使用GPU和cuda加速

一、开篇结论

在使用PyTorch进行深度学习时,通常将模型和相关数据移动到GPU上以加速训练过程。以下是一些通常需要放在GPU上的变量和操作:

  1. 模型(Model):

    • 将深度学习模型移动到GPU上,可以使用 model.to(device),其中 devicetorch.device 类型,表示设备,可以是 ‘cuda’ 或 ‘cuda:0’ 等。
    model = model.to(device)
    
  2. 数据(Data):

    • 将输入数据和标签移动到 GPU 上,这通常是在每个训练迭代之前完成的。可以使用 tensor.to(device)tensor.cuda()
    data = data.to(device)
    labels = labels.to(device)
    
  3. 优化器不能to(cuda)

  4. 损失函数(Loss Function):

    • 损失函数也可以移到 GPU 上。
    loss_function = torch.nn.CrossEntropyLoss()
    loss_function = loss_function.to(device)
    
  5. 中间变量和计算过程:

    • 如果在训练过程中使用了一些中间变量或进行一些计算,确保将它们移到 GPU 上以充分利用硬件加速。
    intermediate_result = intermediate_result.to(device)
    

任何在模型训练过程中需要在前向传播、反向传播和优化步骤中使用的张量和变量都应该被移到 GPU 上。需要注意的是,不是所有的操作都能在 GPU 上执行,有时需要将数据移回 CPU 进行某些操作(例如数据可视化或其他非深度学习任务)。

二、案例-1

以下是一个简单的 Python 脚本,演示如何在 GPU 上训练一个简单的神经网络。这个示例使用 PyTorch 库,并假设已经安装了 PyTorch 和 CUDA。

import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个简单的神经网络
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc = nn.Linear(10, 1)

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

# 创建模型实例
model = SimpleNet()

# 检查是否有可用的 GPU,如果有就使用第一个 GPU,否则使用 CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("Using device:", device)

# 将模型移动到设备
model = model.to(device)

# 创建一些示例数据(这里使用随机生成的数据)
input_data = torch.randn(5, 10).to(device)
target = torch.randn(5, 1).to(device)

# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 训练模型
num_epochs = 100
for epoch in range(num_epochs):
    # 前向传播
    output = model(input_data)
    
    # 计算损失
    loss = criterion(output, target)
    
    # 反向传播和优化
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # 打印每个epoch的损失
    print(f"Epoch {epoch+1}/{num_epochs}, Loss: {loss.item()}")

print("Training finished!")

在这个例子中,模型、输入数据、目标数据以及优化器都被移动到 GPU 上。这样,模型的前向传播、反向传播和优化步骤都会在 GPU 上执行,从而加速训练过程。

  • 注意

对于损失函数和优化器,通常将它们移动到 GPU 上是可选的,因为它们通常只包含一些参数和计算,而不涉及大量的计算。然而,如果模型和数据已经在 GPU 上,将损失函数和优化器也移动到 GPU 上可以确保所有的计算都在同一设备上进行,从而避免在 CPU 和 GPU 之间的数据传输开销。

如果模型和数据已经在 GPU 上,可以选择将损失函数和优化器也移到 GPU 上,如下所示:

# 将损失函数和优化器移动到 GPU
criterion = nn.MSELoss().to(device)
optimizer = optim.SGD(model.parameters(), lr=0.01).to(device)

这样,损失函数和优化器的计算将在 GPU 上进行,与模型和数据一致。然而,这不是强制性的,如果选择在 CPU 上创建它们,PyTorch 会自动处理在 GPU 和 CPU 之间的数据传输(一般情况,需要手动处理)。在实际中,很多人选择将损失函数和优化器保留在 CPU 上,因为它们通常具有较少的参数,不会成为训练过程的性能瓶颈。

三、一些细节

Xdata在GPU上面,len(Xdata)也在GPU上面吗?

不是的,len(Xdata)并不在GPU上。len()函数是Python内置函数,它返回对象(例如列表、元组、字符串等)中元素的数量或长度,而不是将对象本身移到GPU上。所以,即使您的数据Xdata存储在GPU上,len(Xdata)仍然会在CPU上执行,并返回对象中元素的数量。

需要注意的是,如果您想在GPU上使用len()函数计算张量的长度,您可以使用PyTorch提供的size()方法来获取张量的大小,例如:

import torch
# 创建一个张量,将其移动到GPU上
my_tensor = torch.tensor([1, 2, 3]).cuda()
# 使用size()方法获取张量的大小
tensor_size = my_tensor.size()
# 输出结果
print(tensor_size[0])  # 这里输出的是3,在GPU上

在这个例子中,我们首先创建了一个张量my_tensor,然后使用.cuda()方法将其移动到GPU上。接下来,我们使用size()方法获取张量的大小,并从中提取第一个元素作为张量的长度。由于所有操作都在GPU上执行,因此最终结果也在GPU上。

如何将一个列表放在cuda上?

如果想将一个Python列表放在CUDA上,需要先将其转换为PyTorch张量,并使用.cuda()方法将其移动到GPU上。例如:

import torch
# 创建一个Python列表
my_list = [1, 2, 3, 4, 5]
# 将列表转换为PyTorch张量,并将其移动到GPU上
my_tensor = torch.tensor(my_list).cuda()
# 输出结果
print(my_tensor)

在这个例子中,我们首先创建了一个Python列表my_list,然后使用torch.tensor()函数将其转换为PyTorch张量my_tensor。接下来,我们使用.cuda()方法将张量移动到GPU上。

需要注意的是,只有当GPU具有足够的内存来容纳整个列表时,才能将其放在CUDA上。如果列表非常大,或者GPU内存很小,那么可能会出现内存不足的错误。此外,将数据从主机内存移动到GPU内存需要一定的时间,因此如果要频繁地在CPU和GPU之间复制数据,也可能会降低效率。

可以直接将上面的my_list = [1, 2, 3, 4, 5]使用my_list.cuda()的方式吗?

不可以直接使用my_list.cuda()方法将Python列表放在CUDA上。因为PyTorch张量具有GPU和CPU上不同的内部表示形式,只有PyTorch张量才能移动到GPU上。

如果要将Python列表放在CUDA上,需要先将其转换为PyTorch张量,并使用.cuda()方法将其移动到GPU上。因此,正确的做法是首先将Python列表转换为PyTorch张量,如下所示:

import torch
# 创建一个Python列表
my_list = [1, 2, 3, 4, 5]
# 将列表转换为PyTorch张量,并将其移动到GPU上
my_tensor = torch.tensor(my_list).cuda()
# 输出结果
print(my_tensor)

在这个例子中,我们首先创建了一个Python列表my_list,然后使用torch.tensor()函数将其转换为PyTorch张量my_tensor。接下来,我们使用.cuda()方法将张量移动到GPU上。

一个网络在GPU上面,那么网络参数也是在GPU上面吗?可以将GPU上面的网络参数放入result=[]列表吗?

  • 方法1

是的,如果一个神经网络被加载到GPU上训练或推断,那么该网络的所有参数也将存储在GPU上。因此,如果您想将网络参数添加到Python列表中,您需要确保将它们从GPU内存中转移到主机内存中。

这可以通过使用cpu()方法将参数从GPU移回CPU内存,然后使用tolist()方法将其转换为Python列表来实现。例如:

import torch
# 创建一个神经网络,并将其移动到GPU上
net = MyNet().cuda()
# 训练网络...
# 获取网络参数,并将它们添加到 result 中
result = []
for param in net.parameters():
    result.append(param.cpu().detach().numpy().tolist())
# 输出结果
print(result)

在这个例子中,我们首先创建了一个神经网络 net 并将其移动到GPU上。接下来,我们迭代遍历了 net 的所有参数,并使用cpu() 方法将每个参数移回到CPU内存中。然后,我们使用detach()方法和numpy()方法将参数转换为NumPy数组,最后使用tolist()方法将其转换为Python列表,并将其添加到 result 中。
需要注意的是,如果您要频繁地从GPU内存中迁移数据,将会降低效率。因此,应该尽量避免在GPU和CPU之间频繁地复制数据。

  • 方法2

如果将GPU上的变量添加到Python列表中,会导致一个TypeError错误。因为PyTorch张量是一种特殊类型的对象,它们不支持Python列表的操作。

要在GPU上创建一个列表,并将张量对象添加到该列表中,可以使用PyTorch的torch.Tensor.tolist()方法或者torch.Tensor.cpu().numpy()方法先将张量转换为NumPy数组,再将数组添加到列表中。例如:

import torch
# 假设 x 在 GPU 上面
x = torch.randn(2, 3, device='cuda')
# 创建一个空列表
result = []
# 将 x 添加到 result 中 (使用tolist)
result.append(x.tolist())
# 或者将 x 添加到 result 中 (使用cpu + numpy)
result.append(x.cpu().numpy())
# 输出结果
print(result)

在这个例子中,我们首先在GPU上创建一个张量 x,然后创建一个空列表 result。接下来,我们使用 x.tolist() 方法将 x 转换为Python列表并将其添加到 result 中,或者使用 x.cpu().numpy() 方法将 x 转换为CPU上的NumPy数组,然后将该数组添加到 result 中。

需要注意的是,在向列表中添加大量的GPU张量时,将所有张量都复制到CPU和/或内存中可能会导致性能问题。因此,如果您需要频繁地向列表中添加GPU张量,最好在GPU上直接操作数据,而不是通过将数据复制到CPU和/或内存中来进行操作。

假设Xdata放在GPU上面,索引它的元素,假设索引结果为x, 那么x也在GPU上面吗?

是的,如果在GPU上索引一个PyTorch张量 Xdata 的元素,那么返回的元素也会在 GPU 上。

当从GPU上的张量中索引一个元素时,返回的元素是作为新的张量对象返回的。这个张量对象与原始张量共享数据,并且默认情况下位于相同的设备上。因此,在索引操作之后,返回的新张量也将在GPU上。例如:

import torch
# 假设 Xdata 在 GPU 上
Xdata = torch.randn(2, 3, device='cuda')
# 索引 Xdata 的第一个元素
x = Xdata[0]
# x 在 GPU 上 
print(x.device)

在这个例子中,我们首先在GPU上创建了一个2x3的张量Xdata,然后使用方括号运算符索引它的第一个元素并将其赋值给变量x。由于返回的x是一个新的张量对象,它将保持在原始张量所在的设备上,也就是在GPU上。

需要注意的是,如果在CPU上索引GPU上的张量,将会导致数据从GPU复制到CPU。这可能会影响代码的性能,因此建议尽可能在GPU上避免不必要的数据传输。

网络模型可以在定义时就放在cuda上面,还是在实例化时放在cuda上面?

网络模型可以在定义时就放在CUDA上,也可以在实例化时放在CUDA上。

如果模型中包含可学习参数(例如神经网络中的权重和偏差),则通常最好在模型定义时将其放在CUDA上。这样可以确保所有参数都在同一个设备上,并在训练期间自动处理设备移动。例如:

import torch
class MyModel(torch.nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.linear = torch.nn.Linear(2, 1)
        
    def forward(self, x):
        return self.linear(x)
model = MyModel().cuda()  # 将模型定义在 CUDA 上

在这个示例中,我们定义了一个简单的MyModel类,其中包含一个线性层作为其唯一子模块。我们在构造函数中调用基类构造函数super()并初始化线性层。然后,我们将整个模型放在CUDA上,方法是使用.cuda()方法。现在,我们可以像这样使用模型:

x = torch.tensor([[1.0, 2.0], [3.0, 4.0]]).cuda()
y = model(x)
print(y)

在这里,我们首先将输入张量x移动到CUDA上,然后使用模型对其进行前向传递,产生输出张量y。由于模型在CUDA上定义,因此它将自动处理设备移动,并将输出张量y移回CUDA上。

如果模型不包含可学习参数,或者想要在实例化时动态选择设备,则可以在实例化时将模型放在CUDA上。例如:

import torch
class MyModel(torch.nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.relu = torch.nn.ReLU()
        
    def forward(self, x):
        return self.relu(x)
model = MyModel()  # 模型定义不包含可学习参数
device = 'cuda' if torch.cuda.is_available() else 'cpu'  # 动态选择设备
model.to(device)  # 将模型放在指定设备上

在这个示例中,我们定义了一个简单的MyModel类,其中只包含一个ReLU激活函数,没有可学习参数。我们在构造函数中调用基类构造函数super()并初始化ReLU激活函数。然后,我们根据系统是否支持CUDA动态选择设备,并使用.to()方法将模型放在指定设备上。
现在,我们可以像这样使用模型:

x = torch.tensor([-1.0, 2.0, -3.0])
y = model(x.to(device))
print(y)

在这里,我们首先将输入张量x移动到指定设备上,然后使用模型对其进行前向传递,产生输出张量y。由于模型在实例化时放置在指定设备上,因此它将自动处理设备移动,并将输出张量y移回指定设备上。

如何将cuda上的变量转到cpu上面?

要将CUDA上的变量移动到CPU上,请使用.cpu()方法。例如:

import torch
# 假设 x 在 GPU 上
x = torch.tensor(2.5, device='cuda')
# 将 x 从 GPU 移动到 CPU
x_cpu = x.cpu()
# 现在可以在 CPU 上使用 x_cpu 变量
print(x_cpu)

在这个示例中,我们首先将x张量对象创建在GPU上。然后,我们使用.cpu()方法将其移动到CPU上,并将其分配给一个新的变量x_cpu。现在,我们可以在CPU上使用x_cpu变量并打印它。

请注意,将张量移动到不同的设备(如从GPU到CPU)可能会涉及到数据的复制,因此需要确保不会频繁地在不同的设备之间移动数据以避免性能下降。

可以直接将GPU上面的变量打印出来吗,还是说需要先放在cpu上面才能打印

在Python中,不能直接打印GPU上的变量。需要将其从GPU设备移动到CPU,并将其转换为NumPy数组或Python标量,然后才能打印它们。下面是一个示例:

import torch
# 假设 x 在 GPU 上
x = torch.tensor(2.5, device='cuda')
# 将 x 从 GPU 移动到 CPU 并转换成 numpy 数组
x_cpu = x.cpu().detach().numpy()
print(x_cpu)

在这个示例中,我们首先定义了一个张量对象x,并将其放在GPU设备上。然后,我们使用.cpu()方法将其从GPU移动到CPU,并使用.detach()方法分离出其计算图依赖关系,并将其转换为NumPy数组。最后,我们使用Python内置的print()函数将其打印出来。

但是,torch1.8_cuda111在运行下面代码时,并没有遇到错误

import torch

# 假设 x 在 GPU 上
x = torch.tensor(2.5, device='cuda')

print(x)

输出

tensor(2.5000, device='cuda:0')

如何将自己定义的函数,也传给cuda进行处理?

要将自己定义的函数传递到CUDA进行处理,需要使用PyTorch提供的CUDA扩展功能。具体来说,可以使用torch.cuda.jit模块中的@torch.jit.script装饰器将Python函数转换为Torch脚本,并使用.cuda()方法将其移动到GPU上。
下面是一个示例:

import torch
# 定义一个简单的自定义函数
def add(a, b):
    return a + b
# 将 Python 函数转换为 Torch 脚本 
add_script = torch.jit.script(add)
# 将 Torch 脚本移动到 GPU 上
add_cuda = add_script.cuda()
# 在 GPU 上调用自定义函数
x = torch.ones(5).cuda()
y = torch.ones(5).cuda()
z = add_cuda(x, y)
print(z)

在这个例子中,我们首先定义了一个简单的自定义函数 add ,该函数返回两个输入张量的和。接下来,我们将 Python 函数转换为 Torch 脚本,方法是使用 @torch.jit.script 装饰器。
然后,我们使用 .cuda() 方法将 Torch 脚本移动到 GPU 上。最后,在 GPU 上调用自定义函数时,我们使用 add_cuda 代替原始函数 add。

torch中 x数据已经使用x.to(device), 再使用x.to(device)会报错吗?

在 PyTorch 中,如果已经将一个张量 (tensor) 移到了指定的设备上,再次调用 to 方法将不会产生任何影响,也不会报错。这是因为 to 方法内部会检查当前张量所在的设备和目标设备是否一致,如果一致,则直接返回原始张量。
以下是一个简单的示例代码,演示了当我们尝试将已经被移动到 GPU 上的张量再次移动到相同的 GPU 设备时,不会引发错误:

import torch
# 创建张量并将其移动到 GPU 上
x = torch.randn(4, 4).cuda()
# 再次将 x 移动到相同的 GPU 设备上
x = x.to("cuda")
# 不会报错

在这个例子中,我们创建了一个张量,然后通过调用 .cuda() 方法将其移动到默认的 CUDA 设备上。然后,我们使用 .to(“cuda”) 方法再次将其移动到相同的设备上。由于设备相同,因此这不会抛出任何异常或错误。

四、案例-2

下面是一个稍微复杂一点的例子,涉及一个更深的卷积神经网络 (CNN) 模型和 CIFAR-10 数据集。这个例子演示了如何在 GPU 上训练一个 CNN 模型。

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

# 定义卷积神经网络
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1)
        self.relu1 = nn.ReLU()
        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.relu2 = nn.ReLU()
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(128 * 8 * 8, 512)
        self.relu3 = nn.ReLU()
        self.fc2 = nn.Linear(512, 10)

    def forward(self, x):
        x = self.relu1(self.conv1(x))
        x = self.pool(self.relu2(self.conv2(x)))
        x = x.view(-1, 128 * 8 * 8)
        x = self.relu3(self.fc1(x))
        x = self.fc2(x)
        return x

# 加载CIFAR-10数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)

# 创建模型实例
model = SimpleCNN()

# 检查是否有可用的 GPU,如果有就使用第一个 GPU,否则使用 CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("Using device:", device)

# 将模型移动到设备
model = model.to(device)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

# 训练模型
num_epochs = 5
for epoch in range(num_epochs):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()

        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if i % 2000 == 1999:
            print(f"[{epoch + 1}, {i + 1}] loss: {running_loss / 2000:.3f}")
            running_loss = 0.0

print("Training finished!")

在这个例子中,我们定义了一个简单的 CNN 模型(SimpleCNN),加载了 CIFAR-10 数据集,将模型和数据移动到 GPU 上,并使用交叉熵损失和随机梯度下降优化器进行训练。这个例子中的模型和数据集相对较小,但可以在更大的模型和数据集上使用相同的技术。

要求优化器和损失函数也在cuda上

当将模型和数据移动到 GPU 上时,为了保持一致,最好也将优化器和损失函数移到 GPU 上。以下是修改后的代码:

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

# 定义卷积神经网络
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1)
        self.relu1 = nn.ReLU()
        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.relu2 = nn.ReLU()
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(128 * 8 * 8, 512)
        self.relu3 = nn.ReLU()
        self.fc2 = nn.Linear(512, 10)

    def forward(self, x):
        x = self.relu1(self.conv1(x))
        x = self.pool(self.relu2(self.conv2(x)))
        x = x.view(-1, 128 * 8 * 8)
        x = self.relu3(self.fc1(x))
        x = self.fc2(x)
        return x

# 加载CIFAR-10数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)

# 创建模型实例
model = SimpleCNN()

# 检查是否有可用的 GPU,如果有就使用第一个 GPU,否则使用 CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("Using device:", device)

# 将模型移动到设备
model = model.to(device)

# 定义损失函数和优化器,并将它们移动到 GPU
criterion = nn.CrossEntropyLoss().to(device)
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9).to(device)

# 训练模型
num_epochs = 5
for epoch in range(num_epochs):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()

        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if i % 2000 == 1999:
            print(f"[{epoch + 1}, {i + 1}] loss: {running_loss / 2000:.3f}")
            running_loss = 0.0

print("Training finished!")

将损失函数和优化器的实例化和移动到 GPU 的步骤合并在一起,确保它们也在 GPU 上执行。这样,整个训练过程都在 GPU 上进行,充分利用硬件加速。

在Ubuntu 20.04上搭建深度学习环境,可以按照以下步骤进行操作: 1. 首先,安装Ubuntu系统。可以通过制作U盘镜像并进行安装来完成这一步。 2. 安装必要的软件包和工具。使用以下命令安装make、g、cmake和文输入法: ``` sudo apt-get install build-essential cmake fcitx ``` 3. 安装VS Code。可以从官网下载安装包,并使用以下命令进行安装: ``` sudo dpkg -i visual_code_1.69.2-1658162013_amd64.deb ``` 4. 切换Python版本。根据个人需求,可以选择不同的Python版本。可以使用以下命令来切换Python版本: ``` sudo update-alternatives --config python ``` 5. 安装英伟达显卡驱动。根据自己的显卡型号和需求,选择合适的英伟达显卡驱动进行安装。 6. 明确CUDA版本需求。根据自己的需求,确定所需的CUDA版本。可以从英伟达官网下载相应的CUDA安装包。 7. 安装CUDA。根据所需的CUDA版本,选择下载并安装相应的CUDA安装包。 8. 安装CUDNN。根据所需的CUDNN版本,从英伟达官网下载相应的CUDNN安装包,并按照文档进行安装。 9. 安装NCCL。根据需求,选择下载并安装相应版本的NCCL。 10. 安装PaddlePaddle。可以使用pip命令安装PaddlePaddle,如下所示: ``` pip install paddlepaddle ``` 11. 安装PyTorch。可以使用conda命令或pip命令安装PyTorch,具体安装方法可以参考PyTorch官方文档。 通过以上步骤,您就可以在Ubuntu 20.04上成功搭建深度学习环境了。请根据实际需求和具体情况,按照步骤进行操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高山莫衣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值