深度学习运算:CUDA 编程简介

一、说明

        如今,当我们谈论深度学习时,通常会将其实现与利用 GPU 来提高性能联系起来。GPU(图形处理单元)最初设计用于加速图像、2D 和 3D 图形的渲染。然而,由于它们能够执行许多并行操作,因此它们的实用性超出了深度学习等应用程序。

二、GPU上启动深度学习

        GPU 在深度学习模型中的使用始于 2000 年代中后期,并在 2012 年左右随着 AlexNet 的出现而变得非常流行。 AlexNet 是由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 设计的卷积神经网络,于 2012 年赢得了 ImageNet 大规模视觉识别挑战赛 (ILSVRC)。这一胜利标志着一个里程碑,因为它证明了深度神经网络在图像分类和识别方面的有效性。使用 GPU 训练大型模型。

        这一突破之后,使用 GPU 进行深度学习模型变得越来越流行,这促成了 PyTorch 和 TensorFlow 等框架的创建。

        现在,我们只是.to("cuda")在 PyTorch 中编写将数据发送到 GPU 并期望加速训练。但深度学习算法在实践中如何利用 GPU 的计算性能呢?让我们来看看吧!

        神经网络、CNN、RNN 和 Transformer 等深度学习架构基本上是使用矩阵加法、矩阵乘法和将函数应用于矩阵等数学运算来构建的。因此,如果我们找到一种方法来优化这些操作,我们就可以提高深度学习模型的性能。

        那么,让我们从简单的开始吧。假设您想要将两个向量C = A + B相加。

        在 C 中的一个简单实现是:

void AddTwoVectors(flaot A[], float B[], float C[]) {for (int i = 0; i < N; i++) {C[i] = A[i] + B[i];}
}

        正如您所注意到的,计算机必须迭代向量,在每次迭代中按顺序添加每对元素。但这些操作是相互独立的。第 i对元素的添加不依赖于任何其他对。那么,如果我们可以同时执行这些操作,并行添加所有元素对呢?

        一种简单的方法是使用 CPU 多线程来并行运行所有计算。然而,当涉及深度学习模型时,我们正在处理包含数百万个元素的大量向量。一个普通的CPU只能同时处理大约十几个线程。这就是 GPU 发挥作用的时候!现代 GPU 可以同时运行数百万个线程,从而增强了海量向量上的数学运算的性能。

三、GPU 与 CPU 比较

        尽管对于单个操作,CPU 计算可能比 GPU 更快,但 GPU 的优势依赖于其并行化能力。其原因是它们的设计目标不同。 CPU 的设计目的是尽可能快地执行一系列操作(线程)(并且只能同时执行数十个操作),而 GPU 的设计目的是并行执行数百万个操作(同时牺牲单个线程的速度)。

        为了说明这一点,可以将 CPU 想象成一辆法拉利,将 GPU 想象成总线。如果您的任务是运送一个人,那么法拉利(CPU)是更好的选择。然而,如果您要运送几个人,即使法拉利(CPU)每次行程更快,公共汽车(GPU)也可以一次性运送所有人,比法拉利多次运送路线更快。因此,CPU 更适合处理顺序操作,GPU 更适合处理并行操作。

        为了提供更高的并行能力,GPU 设计分配更多的晶体管用于数据处理,而不是数据缓存和流量控制,这与 CPU 分配大量晶体管用于此目的不同,以优化单线程性能和复杂指令执行。

        下图展示了CPU vs GPU的芯片资源分布。

图片由作者提供,灵感来自CUDA C++ 编程指南

        CPU 具有强大的内核和更复杂的高速缓存架构(为此分配大量晶体管)。这种设计可以更快地处理顺序操作。另一方面,GPU 优先考虑拥有大量核心以实现更高水平的并行性。

        现在我们已经了解了这些基本概念,那么我们如何在实践中利用这种并行计算能力呢?

四、CUDA简介

        当您运行某些深度学习模型时,您可能会选择使用一些流行的 Python 库,例如 PyTorch 或 TensorFlow。然而,众所周知,这些库的核心在底层运行 C/C++ 代码。此外,正如我们之前提到的,您可以使用 GPU 来加快处理速度。这就是 CUDA 发挥作用的地方! CUDA 代表统一计算架构,它是 NVIDIA 开发的用于在 GPU 上进行通用处理的平台。因此,虽然游戏引擎使用 DirectX 来处理图形计算,但 CUDA 使开发人员能够将 NVIDIA 的 GPU 计算能力集成到他们的通用软件应用程序中,而不仅仅是图形渲染。

        为了实现这一点,CUDA 提供了一个简单的基于 C/C++ 的接口 (CUDA C/C++),该接口允许访问 GPU 的虚拟指令集和特定操作(例如在 CPU 和 GPU 之间移动数据)。

在进一步讨论之前,让我们先了解一些基本的 CUDA 编程概念和术语:

  • host:指CPU及其内存;
  • device:指GPU及其内存;
  • kernel:指在设备(GPU)上执行的函数;

        因此,在使用 CUDA 编写的基本代码中,程序在主机( CPU)上运行将数据发送到设备(GPU) 并启动要在设备(GPU)上执行的内核(函数) 这些内核由多个线程并行执行。执行后,结果从设备GPU)传回主机(CPU)。

        那么让我们回到两个向量相加的问题:

#include <stdio.h>void AddTwoVectors(flaot A[], float B[], float C[]) {for (int i = 0; i < N; i++) {C[i] = A[i] + B[i];}
}int main() {...AddTwoVectors(A, B, C);...
}

        在 CUDA C/C++ 中,程序员可以定义称为内核的 C/C++ 函数,这些函数在调用时由 N 个不同的 CUDA 线程并行执行 N 次。

        要定义内核,可以使用__global__声明说明符,并且可以使用符号指定执行该内核的 CUDA 线程数<<<...>>>


#include <stdio.h>// Kernel definition
__global__ void AddTwoVectors(float A[], float B[], float C[]) {int i = threadIdx.x;C[i] = A[i] + B[i];
}int main() {...// Kernel invocation with N threadsAddTwoVectors<<<1, N>>>(A, B, C);...
}

        每个线程执行内核,并被赋予一个唯一的线程 ID,该 IDthreadIdx可通过内置变量在内核中访问。上面的代码将两个大小为 N 的向量 A 和 B 相加,并将结果存储到向量 C 中。您可以注意到,CUDA 允许我们同时执行所有这些操作,而不是按顺序执行每个成对加法的循环,并行使用 N 个线程。

        但在运行这段代码之前,我们需要进行另一次修改。请务必记住,内核函数在设备 (GPU) 内运行。所以它的所有数据都需要存储在设备内存中。您可以使用以下 CUDA 内置函数来完成此操作:

#include <stdio.h>// Kernel definition
__global__ void AddTwoVectors(float A[], float B[], float C[]) {int i = threadIdx.x;C[i] = A[i] + B[i];
}int main() {int N = 1000; // Size of the vectorsfloat A[N], B[N], C[N]; // Arrays for vectors A, B, and C...float *d_A, *d_B, *d_C; // Device pointers for vectors A, B, and C// Allocate memory on the device for vectors A, B, and CcudaMalloc((void **)&d_A, N * sizeof(float));cudaMalloc((void **)&d_B, N * sizeof(float));cudaMalloc((void **)&d_C, N * sizeof(float));// Copy vectors A and B from host to devicecudaMemcpy(d_A, A, N * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_B, B, N * sizeof(float), cudaMemcpyHostToDevice);// Kernel invocation with N threadsAddTwoVectors<<<1, N>>>(d_A, d_B, d_C);// Copy vector C from device to hostcudaMemcpy(C, d_C, N * sizeof(float), cudaMemcpyDeviceToHost);}

        我们需要使用指针,而不是直接将变量 A、B 和 C 传递给内核。在 CUDA 编程中,您不能在内核启动 (<<<...>>>) 中直接使用主机数组(如示例中的 A、B 和 C)。 CUDA 内核在设备内存上操作,因此您需要将设备指针(d_A、d_B 和 d_C)传递给内核以供其操作。

        除此之外,我们需要使用 cudaMalloc 在设备上分配内存,并使用 cudaMemcpy 在主机和设备之间复制数据。

        现在我们可以添加向量A和B的初始化,并在代码末尾刷新cuda内存。

#include <stdio.h>// Kernel definition
__global__ void AddTwoVectors(float A[], float B[], float C[]) {int i = threadIdx.x;C[i] = A[i] + B[i];
}int main() {int N = 1000; // Size of the vectorsfloat A[N], B[N], C[N]; // Arrays for vectors A, B, and C// Initialize vectors A and Bfor (int i = 0; i < N; ++i) {A[i] = 1;B[i] = 3;}float *d_A, *d_B, *d_C; // Device pointers for vectors A, B, and C// Allocate memory on the device for vectors A, B, and CcudaMalloc((void **)&d_A, N * sizeof(float));cudaMalloc((void **)&d_B, N * sizeof(float));cudaMalloc((void **)&d_C, N * sizeof(float));// Copy vectors A and B from host to devicecudaMemcpy(d_A, A, N * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_B, B, N * sizeof(float), cudaMemcpyHostToDevice);// Kernel invocation with N threadsAddTwoVectors<<<1, N>>>(d_A, d_B, d_C);// Copy vector C from device to hostcudaMemcpy(C, d_C, N * sizeof(float), cudaMemcpyDeviceToHost);// Free device memorycudaFree(d_A);cudaFree(d_B);cudaFree(d_C);
}

      另外,我们需要添加 cudaDeviceSynchronize();在我们调用内核之后。这是一个用于将主机线程与设备同步的函数。当调用此函数时,主机线程将等待,直到设备上所有先前发出的 CUDA 命令完成后才继续执行。

        除此之外,添加一些 CUDA 错误检查也很重要,这样我们就可以识别 GPU 上的错误。如果我们不添加此检查,代码将继续执行主机线程(CPU),并且将很难识别与 CUDA 相关的错误。

        两种技术的实现如下:

#include <stdio.h>// Kernel definition
__global__ void AddTwoVectors(float A[], float B[], float C[]) {int i = threadIdx.x;C[i] = A[i] + B[i];
}int main() {int N = 1000; // Size of the vectorsfloat A[N], B[N], C[N]; // Arrays for vectors A, B, and C// Initialize vectors A and Bfor (int i = 0; i < N; ++i) {A[i] = 1;B[i] = 3;}float *d_A, *d_B, *d_C; // Device pointers for vectors A, B, and C// Allocate memory on the device for vectors A, B, and CcudaMalloc((void **)&d_A, N * sizeof(float));cudaMalloc((void **)&d_B, N * sizeof(float));cudaMalloc((void **)&d_C, N * sizeof(float));// Copy vectors A and B from host to devicecudaMemcpy(d_A, A, N * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_B, B, N * sizeof(float), cudaMemcpyHostToDevice);// Kernel invocation with N threadsAddTwoVectors<<<1, N>>>(d_A, d_B, d_C);// Check for errorcudaError_t error = cudaGetLastError();if(error != cudaSuccess) {printf("CUDA error: %s\n", cudaGetErrorString(error));exit(-1);}// Waits untill all CUDA threads are executedcudaDeviceSynchronize();// Copy vector C from device to hostcudaMemcpy(C, d_C, N * sizeof(float), cudaMemcpyDeviceToHost);// Free device memorycudaFree(d_A);cudaFree(d_B);cudaFree(d_C);
}

        要编译并运行 CUDA 代码,您需要确保系统上安装了 CUDA 工具包。然后,您可以使用 NVIDIA CUDA 编译器 nvcc 编译代码。如果您的计算机上没有 GPU,您可以使用 Google Colab。您只需在运行时 → 笔记本设置中选择 GPU,然后将代码保存在 example.cu 文件中并运行:

%%shell
nvcc example.cu -o compiled_example # compile
./compiled_example # run# you can also run the code with bug detection sanitizer
compute-sanitizer --tool memcheck ./compiled_example 

        然而,我们的代码仍然没有完全优化。上面的示例使用了大小为 N = 1000 的向量。但是,这是一个很小的数字,无法完全展示 GPU 的并行化能力。此外,在处理深度学习问题时,我们经常处理具有数百万个参数的大量向量。但是,如果我们尝试设置(例如 N = 500000)并<<<1, 500000>>>使用上面的示例运行内核,则会抛出错误。因此,要改进代码并执行此类操作,我们首先需要了解CUDA编程的一个重要概念:线程层次结构。

五、线程层次结构

        内核函数的调用是使用符号 完成的<<<number_of_blocks, threads_per_block>>>。因此,在上面的示例中,我们使用 N 个 CUDA 线程运行 1 个块。但是,每个块对其可支持的线程数量都有限制。发生这种情况是因为块内的每个线程都需要位于同一流多处理器核心上,并且必须共享该核心的内存资源。

        您可以使用以下代码片段获得此限制:

int device;
cudaDeviceProp props;
cudaGetDevice(&device);
cudaGetDeviceProperties(&props, device);
printf("Maximum threads per block: %d\n", props.maxThreadsPerBlock);

        在当前的 Colab GPU 上,一个线程块最多可以包含 1024 个线程。因此,我们需要更多的块来执行更多的线程,以便处理示例中的大量向量。此外,块被组织成网格,如下图所示:

https://handwiki.org/wiki/index.php?curid=1157670(CC BY -SA 3.0)

        现在,可以使用以下方式访问线程 ID:

int i = blockIdx.x * blockDim.x + threadIdx.x;

        所以,我们的脚本变成:

#include <stdio.h>// Kernel definition
__global__ void AddTwoVectors(float A[], float B[], float C[], int N) {int i = blockIdx.x * blockDim.x + threadIdx.x;if (i < N) // To avoid exceeding array limitC[i] = A[i] + B[i];
}int main() {int N = 500000; // Size of the vectorsint threads_per_block;int device;cudaDeviceProp props;cudaGetDevice(&device);cudaGetDeviceProperties(&props, device);threads_per_block = props.maxThreadsPerBlock;printf("Maximum threads per block: %d\n", threads_per_block); // 1024float A[N], B[N], C[N]; // Arrays for vectors A, B, and C// Initialize vectors A and Bfor (int i = 0; i < N; ++i) {A[i] = 1;B[i] = 3;}float *d_A, *d_B, *d_C; // Device pointers for vectors A, B, and C// Allocate memory on the device for vectors A, B, and CcudaMalloc((void **)&d_A, N * sizeof(float));cudaMalloc((void **)&d_B, N * sizeof(float));cudaMalloc((void **)&d_C, N * sizeof(float));// Copy vectors A and B from host to devicecudaMemcpy(d_A, A, N * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_B, B, N * sizeof(float), cudaMemcpyHostToDevice);// Kernel invocation with multiple blocks and threads_per_block threads per blockint number_of_blocks = (N + threads_per_block - 1) / threads_per_block;AddTwoVectors<<<number_of_blocks, threads_per_block>>>(d_A, d_B, d_C, N);// Check for errorcudaError_t error = cudaGetLastError();if (error != cudaSuccess) {printf("CUDA error: %s\n", cudaGetErrorString(error));exit(-1);}// Wait until all CUDA threads are executedcudaDeviceSynchronize();// Copy vector C from device to hostcudaMemcpy(C, d_C, N * sizeof(float), cudaMemcpyDeviceToHost);// Free device memorycudaFree(d_A);cudaFree(d_B);cudaFree(d_C);}

六、性能比较

        下面对不同向量大小的两个向量相加运算的 CPU 和 GPU 计算进行了比较。

图片由作者提供

        正如我们所看到的,GPU 处理的优势只有在向量大小 N 较大时才变得明显。另外,请记住,这次比较仅考虑内核/函数的执行。它没有考虑在主机设备之间复制数据的时间,尽管在大多数情况下这可能并不重要,但在我们的情况下相对相当可观,因为我们只执行简单的加法操作。因此,重要的是要记住,GPU 计算仅在处理高度计算密集型且高度并行化的计算时才显示出其优势。

七、多维线程

        好的,现在我们知道如何提高简单数组操作的性能。但在处理深度学习模型时,我们需要处理矩阵和张量运算。在前面的示例中,我们仅使用具有 N 个线程的一维块。但是,也可以执行多维线程块(最多 3 维)。因此,为了方便起见,如果需要运行矩阵运算,可以运行 NxM 线程的线程块。在这种情况下,您可以获得矩阵行列索引为row = threadIdx.x, col = threadIdx.y。另外,为了方便起见,您可以使用dim3变量类型来定义number_of_blocksthreads_per_block.

        下面的示例说明了如何添加两个矩阵。

#include <stdio.h>// Kernel definition
__global__ void AddTwoMatrices(float A[N][N], float B[N][N], float C[N][N]) {int i = threadIdx.x;int j = threadIdx.y;C[i][j] = A[i][j] + B[i][j];
}int main() {...// Kernel invocation with 1 block of NxN threadsdim3 threads_per_block(N, N);AddTwoMatrices<<<1, threads_per_block>>>(A, B, C);...
}

        您还可以扩展此示例以处理多个块:

#include <stdio.h>// Kernel definition
__global__ void AddTwoMatrices(float A[N][N], float B[N][N], float C[N][N]) {int i = blockIdx.x * blockDim.x + threadIdx.x;int j = blockIdx.y * blockDim.y + threadIdx.y;if (i < N && j < N) {C[i][j] = A[i][j] + B[i][j];}
}int main() {...// Kernel invocation with 1 block of NxN threadsdim3 threads_per_block(32, 32);dim3 number_of_blocks((N + threads_per_block.x - 1) ∕ threads_per_block.x, (N + threads_per_block.y - 1) ∕ threads_per_block.y);AddTwoMatrices<<<number_of_blocks, threads_per_block>>>(A, B, C);...
}

        您还可以使用相同的想法扩展此示例以处理 3 维操作。

        现在您知道了如何操作多维数据,还有另一个重要且简单的概念需要学习:如何在内核中调用函数。基本上,这只需使用__device__声明说明符即可完成。这定义了设备(GPU)可以直接调用的函数。因此,它们只能从__global__另一个__device__函数调用。下面的示例将 sigmoid 运算应用于向量(深度学习模型上非常常见的运算)。

#include <math.h>// Sigmoid function
__device__ float sigmoid(float x) {return 1 / (1 + expf(-x));
}// Kernel definition for applying sigmoid function to a vector
__global__ void sigmoidActivation(float input[], float output[]) {int i = threadIdx.x;output[i] = sigmoid(input[i]);}

        现在您已经了解了 CUDA 编程的基本重要概念,您可以开始创建 CUDA 内核了。就深度学习模型而言,它们基本上是一堆矩阵和张量运算,例如求和、乘法、卷积、归一化等。例如,一个简单的矩阵乘法算法可以并行化如下:

// GPU version__global__ void matMul(float A[M][N], float B[N][P], float C[M][P]) {int row = blockIdx.x * blockDim.x + threadIdx.x;int col = blockIdx.y * blockDim.y + threadIdx.y;if (row < M && col < P) {float C_value = 0;for (int i = 0; i < N; i++) {C_value += A[row][i] * B[i][col];}C[row][col] = C_value;}
}

        现在将其与下面两个矩阵乘法的普通 CPU 实现进行比较:

// CPU versionvoid matMul(float A[M][N], float B[N][P], float C[M][P]) {for (int row = 0; row < M; row++) {for (int col = 0; col < P; col++) {float C_value = 0;for (int i = 0; i < N; i++) {C_value += A[row][i] * B[i][col];}C[row][col] = C_value;}}
}

        您可以注意到,在 GPU 版本上,我们的循环更少,从而可以更快地处理操作。下面是CPU和GPU在NxN矩阵乘法上的性能比较:图片由作者提供

        正如您所观察到的,随着矩阵大小的增加,矩阵乘法运算的 GPU 处理性能提升甚至更高。

        现在,考虑一个基本的神经网络,它主要涉及y = σ(W x + b ) 操作,如下所示:

        图片由作者提供

        这些操作主要包括矩阵乘法、矩阵加法以及将函数应用于数组,所有这些操作您都已经熟悉了并行化技术。因此,您现在能够从头开始实现在 GPU 上运行的自己的神经网络!

八、结论

        在这篇文章中,我们介绍了有关 GPU 处理以增强深度学习模型性能的介绍性概念。然而,还需要指出的是,您所看到的概念只是基础知识,还有很多东西需要学习。 PyTorch 和 Tensorflow 等库实现的优化技术涉及其他更复杂的概念,例如优化内存访问、批量操作等(它们利用构建在 CUDA 之上的库,例如 cuBLAS 和 cuDNN)。不过,我希望这篇文章能够帮助您了解.to("cuda")在 GPU 上编写和执行深度学习模型时幕后发生的事情。

        在以后的文章中,我将尝试引入有关 CUDA 编程的更复杂的概念。请在评论中告诉我您的想法或您希望我接下来写什么!非常感谢您的阅读!

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

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

相关文章

kafka启动报错(kafka.common.InconsistentClusterIdException)

文章目录 前言kafka启动报错(kafka.common.InconsistentClusterIdException)1. 查找日志2. 定位问题/解决 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不…

SpringCloud系列(17)--将服务消费者Consumer注册进Zookeeper

前言&#xff1a;在上一章节中我们把服务提供者Provider注册进了Zookeeper&#xff0c;而本章节则是关于如何将服务消费者Consumer注册进Zookeeper 1、再次创建一个服务提供者模块&#xff0c;命名为consumerzk-order80 (1)在父工程下新建模块 (2)选择模块的项目类型为Maven并…

稳态视觉诱发电位 (SSVEP) 分类学习系列 (4) :Temporal-Spatial Transformer

稳态视觉诱发电位分类学习系列:Temporal-Spatial Transformer 0. 引言1. 主要贡献2. 提出的方法2.1 解码的主要步骤2.2 网络的主要结构 3. 结果和讨论3.1 在两个数据集下的分类效果3.2 与基线模型的比较3.3 消融实验3.4 t-SNE 可视化 4. 总结欢迎来稿 论文地址&#xff1a;http…

【进阶六】Python实现SDVRPTW(需求拆分)常见求解算法——禁忌搜索+模拟退火算法(TS+SA)

基于python语言&#xff0c;采用经典禁忌搜索&#xff08;TS&#xff09;模拟退火&#xff08;SA&#xff09;对 带硬时间窗的需求拆分车辆路径规划问题&#xff08;SDVRPTW&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时…

EureKa技术解析:科技行业的革新风暴(ai写作)

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

如何驱动消费者自我裂变,助力平台引流与卖货双重提升

大家好&#xff0c;我是微三云周丽 在浩瀚的商业海洋中&#xff0c;电商行业一直以其独特的魅力和无限的可能性吸引着众多创业者和投资者的目光。近年来&#xff0c;一种被誉为电商模式中的“神盘”——众筹卖货模式&#xff0c;正悄然崭露头角&#xff0c;以其独特的运作方式…

Docker 入门篇(二)-- Linux 环境离线安装

引言 docker 系列文章&#xff1a; Docker 入门篇&#xff08;一&#xff09;-- 简介与安装教程&#xff08;Windows和Linux&#xff09; 一、安装环境准备 centos &#xff1a;CentOS Linux release 7.6.1810 (Core)docker 版本&#xff1a;docker-26.1.0.tgz 官网下载地址…

【RAG 论文】Chain-of-Note:为 RAG 引入 CoT 让模型生成阅读笔记来提高面对噪音文档和未知场景的鲁棒性

论文&#xff1a;Chain-of-Note: Enhancing Robustness in Retrieval-Augmented Language Models ⭐⭐⭐ Tencent AI Lab, arXiv:2311.09210 文章目录 一、论文速读二、实现的细节2.1 Note Design2.2 Data Collection2.3 Model Training 三、实验结果3.1 QA Performance3.2 对 …

虚拟机VMware下ROS Neotic(Ubuntu 20.04)下安装OpenCV

一、ROS安装 ROS的官方安装步骤&#xff1a; 1、noetic / Ubuntu 20.04 &#xff1a; http://wiki.ros.org/noetic/Installation/Ubuntu 2、melodic / Ubuntu 18.04&#xff1a; http://wiki.ros.org/melodic/Installation/Ubuntu 3、kinetic / Ubuntu 16.04&#xff1a; http:…

第三节课,后端登录【1】.2--本人

一、视频链接 网址&#xff1a; 后端用户脱敏和session-CSDN直播 二、代码开始 2.1 新建一个request参数。完成用户登录态键 快捷建&#xff0c; 全局变量 代码&#xff1a; // 3.记录用户的登录态/*** 这段代码是Java Web开发中的一部分&#xff0c;用于在会话&#xff08…

【目标检测】FPN特征金字塔完整流程详解

学习视频&#xff1a;1.1.2 FPN结构详解 对比 可以看到FPN是自上而下、自下而上并且可以进行多尺度特征融合的的层级结构。 具体结构 1x1 conv: 对通道数进行调整&#xff0c;不同大小的特征图通道数不同&#xff0c;越高层次的特征图通道数越大&#xff0c;论文中使用256个1…

ChatGPT/GLM API使用

模型幻觉问题 在自然语言处理领域&#xff0c;幻觉&#xff08;Hallucination&#xff09;被定义为生成的内容与提供的源内容无关或不忠实&#xff0c;具体而言&#xff0c;是一种虚假的感知&#xff0c;但在表面上却似乎是真实的。产生背景 检索增强生成&#xff08;RAG&…

线性神经网络示例

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个线性神经网络模型pytorch程序,最后打印5个条件分别的影响力。 一 在这个场景中&#xff0c;一个线性神经网络&…

代码随想录算法训练营DAY32|C++贪心算法Part.2|122.买卖股票的最佳时机II、55.跳跃游戏、45.跳跃游戏II

文章目录 122.买卖股票的最佳时机II思路CPP代码 55.跳跃游戏思路CPP代码 45.跳跃游戏II思路方法一代码改善 CPP代码 122.买卖股票的最佳时机II 力扣题目链接 文章讲解&#xff1a;122.买卖股票的最佳时机II 视频讲解&#xff1a; 状态&#xff1a;本题可以用动态规划&#xff0…

boa交叉编译(移植到arm)

参考&#xff1a;CentOS7 boa服务器的搭建和配置-CSDN博客 以下操作在宿主机/编译平台操作&#xff1a; 1. 先执行[参考]1到3、 4.2、4.3、4.4、4.5 2. 修改MakeFile # 由以下&#xff1a; CC gcc CPP gcc -E # 改为&#xff1a; CC arm-linux-gnueabihf-gcc CPP arm-l…

【Flask】Flask中HTTP请求与接收

一、接收http请求与返回响应 在Flask中&#xff0c;可以通过app.route装饰器来定义路由函数。 app.route(/BringGoods,methods [POST, GET]) GET请求&#xff1a;使用request.args.get(key)或者request.values.get(key)来获取URL中的参数。 POST请求&#xff1a; 使用req…

SSH新功能揭秘:远程工作提升指南【AI写作】

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

014_用vim复制粘贴_保持双手正位

[oeasy]python0014_用vim复制粘贴_保持双手正位 继续运行 &#x1f94a; 回忆上次内容 程序员 还是 很可爱的 要关心 身边的程序员 啊 毕竟是新时代的 典型新职业 文明 主流职业 血型 渔猎采集文明 猎人 O 游牧文明 牧民 B 农业文明 农民 A 工业文明 工人 商…

红魔9pro/9pro+秒解锁BL+获取root权限+刷国际版+救砖降级刷机教程

红魔8开始&#xff0c;官方对刷机就进行了很多限制&#xff0c;常见的就是阉割了解锁BL指令&#xff0c;让我们不能自 己解锁BL刷机了&#xff0c;而8代旧版并没有严格&#xff0c;自然我们可以使用旧版的abl分区来实现解锁BL 红魔9代发布开始&#xff0c;官方直接阉割了全部Ab…

【运维】Git 分支管理

一般来讲&#xff0c;系统代码需要经过研发、测试、生产三种环境。那么在Git上如何管理分支&#xff0c;才不会乱&#xff1f;在线上生产环境有问题时有条不紊的解决。 经过发展&#xff0c;有一个Git Flow原理可帮助解决。设置以下几种分支。 master——production生产环境。…