3.9 EXERCISES

news2024/10/5 13:01:37
  1. 矩阵加法需要两个输入矩阵A和B,并产生一个输出矩阵C。输出矩阵C的每个元素都是输入矩阵A和B的相应元素的总和,即C[i][j] = A[i][j] + B[i][j]。为了简单起见,我们将只处理元素为单精度浮点数的平方矩阵。编写一个矩阵加法内核和主机stub函数,可以使用四个参数调用:指针到输出矩阵、指针到第一个输入矩阵、指针到第二个输入矩阵以及每个维度中的元素数量。按照以下说明操作:
#include <stdio.h>
#include <cuda_runtime.h>

// CUDA内核函数定义
__global__ void matrixAdd(float *C, const float *A, const float *B, int N) {
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;

    if (row < N && col < N) {
        int idx = row * N + col;
        C[idx] = A[idx] + B[idx];
    }
}

int main() {
    int N = 256; // 矩阵大小为 N x N
    size_t bytes = N * N * sizeof(float);

    // 分配主机内存
    float *h_A = (float*)malloc(bytes);
    float *h_B = (float*)malloc(bytes);
    float *h_C = (float*)malloc(bytes);

    // 初始化输入矩阵
    for (int i = 0; i < N * N; i++) {
        h_A[i] = static_cast<float>(i);
        h_B[i] = static_cast<float>(i);
    }

    // 分配设备内存
    float *d_A, *d_B, *d_C;
    cudaMalloc(&d_A, bytes);
    cudaMalloc(&d_B, bytes);
    cudaMalloc(&d_C, bytes);

    // 复制数据从主机到设备
    cudaMemcpy(d_A, h_A, bytes, cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, h_B, bytes, cudaMemcpyHostToDevice);

    // 设置线程块和网格大小
    dim3 threadsPerBlock(16, 16);
    dim3 blocksPerGrid((N + threadsPerBlock.x - 1) / threadsPerBlock.x,
                       (N + threadsPerBlock.y - 1) / threadsPerBlock.y);

    // 执行内核函数
    matrixAdd<<<blocksPerGrid, threadsPerBlock>>>(d_C, d_A, d_B, N);

    // 等待CUDA完成并检查错误
    cudaDeviceSynchronize();
    cudaError_t error = cudaGetLastError();
    if (error != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s\n", cudaGetErrorString(error));
        // 清理
        cudaFree(d_A);
        cudaFree(d_B);
        cudaFree(d_C);
        free(h_A);
        free(h_B);
        free(h_C);
        return -1;
    }

    // 将结果复制回主机
    cudaMemcpy(h_C, d_C, bytes, cudaMemcpyDeviceToHost);

    // 验证结果
    for (int i = 0; i < N * N; i++) {
        if (h_C[i] != h_A[i] + h_B[i]) {
            fprintf(stderr, "Result verification failed at element %d!\n", i);
            return -1;
        }
    }
    printf("Test PASSED\n");

    // 清理
    cudaFree(d_A);
    cudaFree(d_B);
    cudaFree(d_C);
    free(h_A);
    free(h_B);
    free(h_C);

    return 0;
}
这段代码首先定义了一个matrixAdd内核,它计算两个矩阵的和,并将结果存储在输出矩阵中。然后在main函数中,它创建了两个输入矩阵和一个输出矩阵,将它们复制到设备内存中,调用内核函数,并将结果复制回主机内存。最后,它验证结果并清理分配的内存。

  1. 矩阵-向量乘法接受输入矩阵B和向量C,并产生一个输出向量A。输出向量A的每个元素都是输入矩阵B和C的一行的点积,即 A[i] = ∑j B[i][j] + C[j]。为了简单起见,我们将只处理元素为单精度浮点数的平方矩阵。编写一个矩阵向量乘法内核和一个主机存根函数,该函数可以使用四个参数调用:指针到输出矩阵、指针到输入矩阵、指针到输入向量以及每个维度的元素数量。使用一个线程来计算输出矢量元素。
#include <stdio.h>
#include <cuda_runtime.h>

// CUDA内核函数定义
__global__ void matrixVectorMultiply(float *A, const float *B, const float *C, int N) {
    int row = blockIdx.x * blockDim.x + threadIdx.x;

    if (row < N) {
        float sum = 0.0f;
        for (int j = 0; j < N; ++j) {
            sum += B[row * N + j] * C[j];
        }
        A[row] = sum;
    }
}

int main() {
    int N = 256; // 矩阵大小为 N x N
    size_t sizeMatrix = N * N * sizeof(float);
    size_t sizeVector = N * sizeof(float);

    // 分配主机内存
    float *h_B = (float*)malloc(sizeMatrix);
    float *h_C = (float*)malloc(sizeVector);
    float *h_A = (float*)malloc(sizeVector);

    // 初始化输入矩阵B和向量C
    for (int i = 0; i < N * N; i++) {
        h_B[i] = static_cast<float>(i);
    }
    for (int i = 0; i < N; i++) {
        h_C[i] = static_cast<float>(i);
    }

    // 分配设备内存
    float *d_B, *d_C, *d_A;
    cudaMalloc(&d_B, sizeMatrix);
    cudaMalloc(&d_C, sizeVector);
    cudaMalloc(&d_A, sizeVector);

    // 复制数据从主机到设备
    cudaMemcpy(d_B, h_B, sizeMatrix, cudaMemcpyHostToDevice);
    cudaMemcpy(d_C, h_C, sizeVector, cudaMemcpyHostToDevice);

    // 设置线程块和网格大小
    int threadsPerBlock = 256;
    int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;

    // 执行内核函数
    matrixVectorMultiply<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);

    // 等待CUDA完成并检查错误
    cudaDeviceSynchronize();
    cudaError_t error = cudaGetLastError();
    if (error != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s\n", cudaGetErrorString(error));
        // 清理
        cudaFree(d_B);
        cudaFree(d_C);
        cudaFree(d_A);
        free(h_B);
        free(h_C);
        free(h_A);
        return -1;
    }

    // 将结果复制回主机
    cudaMemcpy(h_A, d_A, sizeVector, cudaMemcpyDeviceToHost);

    // 验证结果(这里跳过了验证步骤)

    // 清理
    cudaFree(d_B);
    cudaFree(d_C);
    cudaFree(d_A);
    free(h_B);
    free(h_C);
    free(h_A);

    return 0;
}
  1. 如果CUDA设备的SM最多可以占用1536个线程和最多4个线程块。以下哪个块配置会导致SM中线程数量最多?
    在这里插入图片描述
    答:C 。3*512

  2. 对于向量加法,假设向量长度为2000,每个线程计算一个输出元素,线程块大小为512个线程。网格中将有多少个线程?
    在这里插入图片描述
    答:2048

  3. ***关于上一个问题,由于矢量长度的边界检查,您预计有多少warp发散?(存疑)
    在这里插入图片描述
    答:A.1

因为即便在最后一个线程块中有一些线程不执行实际的向量加法操作,所有的线程都会执行相同的边界检查逻辑,因此没有warp发散。

  1. 您需要编写一个在大小为400 x 900像素的图像上操作的kernel。您想为每个像素分配一个线程。您希望您的线程块是正方形的,并使用设备上每个块的最大线程数(您的设备具有计算能力3.0)。您将如何选择内核的网格尺寸和块尺寸?

对于具有计算能力3.0的NVIDIA GPU,每个线程块最多可以有1024个线程。由于您想要使用正方形的线程块,因此您应该选择一个线程块的尺寸,其边长是1024的平方根。1024的平方根是32,所以您可以选择一个线程块的尺寸为32x32,这样每个块就有 32 × 32 = 1024 32 \times 32 = 1024 32×32=1024个线程,这是最大线程数。
对于一个400x900像素的图像,您需要足够的块来覆盖所有的像素。通过将图像的尺寸除以线程块的尺寸,您可以得到网格的尺寸:

  • 对于宽度(900像素),您需要 ⌈ 900 32 ⌉ = 29 \lceil \frac{900}{32} \rceil = 29 32900=29 个块(因为900不能被32整除,所以您需要向上取整)。
  • 对于高度(400像素),您需要 ⌈ 400 32 ⌉ = 13 \lceil \frac{400}{32} \rceil = 13 32400=13 个块。
    因此,您的网格尺寸将是 29x13 个块,这确保了您有足够的线程块来为每个像素分配一个线程。需要注意的是,这将导致一些线程是多余的,因为29x32 = 928和13x32 = 416,而图像只有900x400。这意味着在宽度方向上将有28个线程和在高度方向上将有16个线程不会对应于图像中的任何像素。在kernel代码中,您需要添加适当的边界检查来避免这些线程进行越界操作。
    综上所述,您的内核配置应该如下:
  • 线程块尺寸(blockDim): (32, 32)
  • 网格尺寸(gridDim): (29, 13)
  1. 关于上一个问题,您希望有多少个空闲线程?

为了确定有多少个空闲线程,我们需要计算网格中所有线程的总数并减去图像中像素的总数。

我们已经决定使用一个网格尺寸为29x13的线程块,每个线程块的尺寸为32x32。所以,网格中总共的线程数是:

总线程数 = 网格宽度 × 网格高度 × 块宽度 × 块高度 \text{总线程数} = \text{网格宽度} \times \text{网格高度} \times \text{块宽度} \times \text{块高度} 总线程数=网格宽度×网格高度×块宽度×块高度

总线程数 = 29 × 13 × 32 × 32 \text{总线程数} = 29 \times 13 \times 32 \times 32 总线程数=29×13×32×32

总线程数 = 387 , 072 \text{总线程数} = 387,072 总线程数=387,072

图像中实际的像素总数是:

像素总数 = 图像宽度 × 图像高度 = 400 × 900 = 360 , 000 \text{像素总数} = \text{图像宽度} \times \text{图像高度} = 400 \times 900 = 360,000 像素总数=图像宽度×图像高度=400×900=360,000

因此,空闲线程的总数是:

空闲线程数 = 总线程数 − 像素总数 \text{空闲线程数} = \text{总线程数} - \text{像素总数} 空闲线程数=总线程数像素总数

空闲线程数 = 387 , 072 − 360 , 000 = 27 , 072 \text{空闲线程数} = 387,072 - 360,000 = 27,072 空闲线程数=387,072360,000=27,072

所以,将会有27,072个空闲线程。这些线程应该在kernel代码中通过边界检查来处理,以确保它们不会执行任何对应于图像外部的操作。

  1. 考虑一个假设的块,在到达block之前,有8个线程执行一段代码。线程需要以下时间(以微秒为单位)来执行这些部分:2.0、2.3、3.0、2.8、2.4、1.9、2.6和2.9,其余时间等待障碍。等待屏障的线程总执行时间的百分比是多少?
    答:在CUDA中,一个线程块中的所有线程在继续执行之前必须到达同步点(屏障)。这意味着所有线程必须等待直到块中最慢的线程完成其执行。在这个假设的情况下,最慢的线程需要3.0微秒来执行它的部分。

对于每个线程,等待时间是最慢线程的执行时间减去该线程的执行时间。我们需要计算每个线程的等待时间,然后将它们加起来得到总等待时间,最后计算这个总等待时间占所有线程执行时间总和的百分比。

让我们先计算总等待时间:

  • 线程1的等待时间: 3.0 − 2.0 = 1.0 3.0 - 2.0 = 1.0 3.02.0=1.0 微秒
  • 线程2的等待时间: 3.0 − 2.3 = 0.7 3.0 - 2.3 = 0.7 3.02.3=0.7 微秒
  • 线程3的等待时间: 3.0 − 3.0 = 0.0 3.0 - 3.0 = 0.0 3.03.0=0.0 微秒(最慢的线程,没有等待时间)
  • 线程4的等待时间: 3.0 − 2.8 = 0.2 3.0 - 2.8 = 0.2 3.02.8=0.2 微秒
  • 线程5的等待时间: 3.0 − 2.4 = 0.6 3.0 - 2.4 = 0.6 3.02.4=0.6 微秒
  • 线程6的等待时间: 3.0 − 1.9 = 1.1 3.0 - 1.9 = 1.1 3.01.9=1.1 微秒
  • 线程7的等待时间: 3.0 − 2.6 = 0.4 3.0 - 2.6 = 0.4 3.02.6=0.4 微秒
  • 线程8的等待时间: 3.0 − 2.9 = 0.1 3.0 - 2.9 = 0.1 3.02.9=0.1 微秒

总等待时间是所有线程等待时间的总和:

总等待时间 = 1.0 + 0.7 + 0.0 + 0.2 + 0.6 + 1.1 + 0.4 + 0.1 = 4.1  微秒 \text{总等待时间} = 1.0 + 0.7 + 0.0 + 0.2 + 0.6 + 1.1 + 0.4 + 0.1 = 4.1 \text{ 微秒} 总等待时间=1.0+0.7+0.0+0.2+0.6+1.1+0.4+0.1=4.1 微秒

总执行时间是所有线程执行时间的总和:

总执行时间 = 2.0 + 2.3 + 3.0 + 2.8 + 2.4 + 1.9 + 2.6 + 2.9 = 19.9  微秒 \text{总执行时间} = 2.0 + 2.3 + 3.0 + 2.8 + 2.4 + 1.9 + 2.6 + 2.9 = 19.9 \text{ 微秒} 总执行时间=2.0+2.3+3.0+2.8+2.4+1.9+2.6+2.9=19.9 微秒

等待时间占总执行时间的百分比是:

百分比 = ( 总等待时间 总执行时间 ) × 100 \text{百分比} = \left( \frac{\text{总等待时间}}{\text{总执行时间}} \right) \times 100 百分比=(总执行时间总等待时间)×100

百分比 = ( 4.1 19.9 ) × 100 ≈ 20.6 % \text{百分比} = \left( \frac{4.1}{19.9} \right) \times 100 \approx 20.6\% 百分比=(19.94.1)×10020.6%

因此,等待屏障的线程总执行时间的百分比大约是20.6%。

  1. 指出每个多处理器可以进行以下哪些分配。在不可能的情况下,请指出限制因素(s)。
    在这里插入图片描述
    好的,我们来看看每个多处理器可以进行的分配情况:

A. 在具有计算能力1.0的设备上,8个块,每个块128个线程

  • 计算能力1.0的设备每个多处理器的最大线程数为768,8个块乘以128个线程等于1024个线程,超出了最大线程数的限制。因此,这种分配是不可能的,限制因素是多处理器的最大线程数。

B. 在具有计算能力1.2的设备上,8个块,每个块128个线程

  • 计算能力1.2的设备每个多处理器的最大线程数为1024,8个块乘以128个线程等于1024个线程,没有超出限制。因此,这种分配是可能的。

C. 在具有计算能力3.0的设备上,8个块,每个块128个线程

  • 计算能力3.0的设备每个多处理器的最大线程数为2048,8个块乘以128个线程等于1024个线程,没有超出限制。因此,这种分配是可能的。

D. 在具有计算能力1.0的设备上,16个块,每个块64个线程

  • 计算能力1.0的设备每个多处理器的最大线程数为768,而16个块乘以64个线程等于1024个线程,超出了最大线程数的限制。此外,计算能力1.0的设备每个多处理器的最大块数为8,这里的16个块也超出了限制。因此,这种分配是不可能的,限制因素是多处理器的最大线程数和最大块数。

E. 在具有计算能力1.2的设备上,16个块,每个块64个线程

  • 计算能力1.2的设备每个多处理器的最大线程数为1024,而16个块乘以64个线程等于1024个线程,没有超出线程数限制。但是,计算能力1.2的设备每个多处理器的最大块数为8,这里的16个块超出了限制。因此,这种分配是不可能的,限制因素是多处理器的最大块数。

F. 在具有计算能力3.0的设备上,16个块,每个块64个线程

  • 计算能力3.0的设备每个多处理器的最大线程数为2048,而16个块乘以64个线程等于1024个线程,没有超出限制。计算能力3.0的设备每个多处理器的最大块数也足够容纳16个块。因此,这种分配是可能的。
  1. 一位CUDA程序员说,如果他们启动一个每个块中只有32个线程的内核,他们可以在需要障碍同步的地方省略__syncthreads()指令。你觉得这是个好主意吗?解释。
    答:不,这不是一个好主意。

__syncthreads()是CUDA编程中的一个同步屏障,它确保块内的所有线程在继续执行之前都达到这一点。这是为了避免竞争条件和数据不一致,尤其是当多个线程需要读写共享内存或者依赖于其他线程的计算结果时。

即使每个块中只有32个线程,也不能保证省略__syncthreads()指令不会导致问题。CUDA架构是SIMT(Single Instruction, Multiple Thread)架构,这意味着一组线程(称为一个线程束)将以锁步方式执行相同的指令。对于NVIDIA的CUDA架构,一个线程束通常包含32个线程,这也是所谓的warp的大小。

当一个块恰好等于一个warp的大小时,人们可能会认为每个warp内的线程是同时执行的,因此不需要同步。然而,这种假设是有风险的,因为:

  1. 即使在一个warp内部,线程执行的顺序和时间仍然是不确定的,尤其是当有分支指令时。
  2. 如果内核中的操作依赖于共享内存或全局内存的读写顺序,省略同步仍然可能导致错误的结果。
  3. 在未来的硬件或软件更新中,warp的行为可能会改变,这使得省略__syncthreads()可能会导致代码的可移植性和可维护性问题。

因此,除非代码的逻辑确保了不同线程之间绝对不需要同步(例如,每个线程使用独立的内存位置,且不依赖于其他线程的计算结果),否则省略__syncthreads()是不安全的。通常,最好是遵循标准的同步做法,以确保代码的正确性和健壮性。

  1. 一位学生提到,他能够通过使用32×32线程块的tile矩阵乘法代码来乘以两个1024×1024矩阵。他正在使用CUDA设备,每个块最多允许512个线程,每个SM最多允许8个块。他进一步提到,线程块中的每个线程都计算结果矩阵的一个元素。你会有什么反应,为什么?

答:
如果该学生声称他们使用32×32线程块来执行1024×1024矩阵的乘法,并且每个线程块中的每个线程都计算结果矩阵的一个元素,那么他们的配置与CUDA设备的硬件限制不符。具体来说,他们的线程块设置超过了每个块允许的最大线程数,因为32×32等于1024,而设备的限制是每个块最多512个线程。

这里的主要问题是,CUDA内核在执行时必须遵守硬件的限制。如果超出了这些限制,CUDA程序将无法成功执行,或者会得到错误的结果。由于CUDA内核的这个限制是由硬件决定的,因此程序员必须设计他们的线程块以适应这些限制。

因此,我的反应将是指出学生的配置错误,并建议他们重新设计线程块的尺寸。为了适应每个块最多512个线程的限制,他们可以选择如下配置之一:

  • 使用16×32线程块,每块有512个线程。
  • 使用32×16线程块,每块有512个线程。
  • 使用其他任何乘积不超过512的线程维度配置。

此外,由于每个SM允许最多8个块,这意味着在任何给定时刻,一个SM可以同时处理的块的数量有限。这个信息对于理解内核如何在多个SM上调度是有用的,但它并不影响线程块大小的选择,只要线程块的大小遵守了每个块的最大线程数限制。

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

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

相关文章

用红葡萄酿造的白葡萄酒是怎样的?

“由黑变白”这是“黑葡萄”的直译&#xff0c;代表一种由深蓝到黑葡萄制成的白葡萄酒&#xff0c;这种酿酒方式起源于法国&#xff0c;黑皮诺和莫尼尔的红葡萄一直被加工成白葡萄酒&#xff0c;作为香槟的基础。这是可能的&#xff0c;因为红色浆果通常果肉较轻。红色素&#…

TypeScript 从入门到进阶之基础篇(八)函数篇

系列文章目录 TypeScript 从入门到进阶系列 TypeScript 从入门到进阶之基础篇(一) ts基础类型篇TypeScript 从入门到进阶之基础篇(二) ts进阶类型篇TypeScript 从入门到进阶之基础篇(三) 元组类型篇TypeScript 从入门到进阶之基础篇(四) symbol类型篇TypeScript 从入门到进阶…

Java课程设计团队博客 —— 基于网页的时间管理系统

博客目录 1.项目简介2.项目采用的技术3.功能需求分析4.项目亮点5.主要功能截图6.Git地址7.总结 Java团队博客分工 姓名职务负责模块孙岚组长 资源文件路径和tomcat服务器的相关配置。 前端的页面设计与逻辑实现的代码编写。 Servlet前后端数据交互的编写。 用户登录和断开连接…

独立式键盘控制的4级变速流水灯

#include<reg51.h> // 包含51单片机寄存器定义的头文件 unsigned char speed; //储存流水灯的流动速度 sbit S1P1^4; //位定义S1为P1.4 sbit S2P1^5; //位定义S2为P1.5 sbit S3P1^6; //位定义S3为P1.6 sbit S4P1^7; //位…

熟悉HBase常用操作

1. 用Hadoop提供的HBase Shell命令完成以下任务 (1)列出HBase所有表的相关信息,如表名、创建时间等。 启动HBase: cd /usr/local/hbase bin/start-hbase.sh bin/hbase shell列出HBase所有表的信息: hbase(main):001:0> list(2)在终端输出指定表的所有记录数据。 …

鸿蒙原生应用/元服务开发-消息通知整体说明

应用/元服务可以通过通知接口发送通知消息&#xff0c;终端用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用。 通知常见的使用场景&#xff1a;显示接收到的短消息、即时消息等。显示应用的推送消息&#xff0c;如广告、版本更新等。显示当前正在进行的事…

CCF模拟题 202309-1 坐标变换(其一)

问题描述 试题编号&#xff1a; 202309-1 试题名称&#xff1a; 坐标变换&#xff08;其一&#xff09; 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 对于平面直角坐标系上的坐标&#xff08;x,y&#xff09;&#xff0c;小P定义了一个包含…

代码随想Day60 | 84.柱状图中最大的矩形

84.柱状图中最大的矩形 这道题和接雨水遥相呼应&#xff0c;接雨水是求外部凹槽&#xff0c;这道题是求内部面积&#xff0c;这道题的整体思路是某一个元素&#xff0c;找到其左边的第一个小于该数的位置&#xff0c;右边的第一个小于该数的位置&#xff0c;然后以当前索引的高…

CodeWave智能开发平台--03--目标:应用创建--07供应商数据表格02

摘要 本文是网易数帆CodeWave智能开发平台系列的第10篇&#xff0c;主要介绍了基于CodeWave平台文档的新手入门进行学习&#xff0c;实现一个完整的应用&#xff0c;本文主要完成07供应商数据表格下 CodeWave智能开发平台的10次接触 CodeWave参考资源 网易数帆CodeWave开发…

202312 青少年软件编程等级考试Scratch一级真题(电子学会)

2023年12月 青少年软件编程等级考试Scratch一级真题&#xff08;电子学会&#xff09; 试卷总分数&#xff1a;100分 试卷及格分&#xff1a;60 分 考试时长&#xff1a;60 分钟 第 1 题 单选题 观察下列每个圆形中的四个数&#xff0c;找出规律&#xff0c;在括…

鸿蒙原生应用/元服务开发-短时任务

概述 应用退至后台一小段时间后&#xff0c;应用进程会被挂起&#xff0c;无法执行对应的任务。如果应用在后台仍需要执行耗时不长的任务&#xff0c;如状态保存等&#xff0c;可以通过本文申请短时任务&#xff0c;扩展应用在后台的运行时间。 约束与限制 申请时机&#xf…

使用Vite创建vue3工程

介绍 使用Vite构建工具&#xff0c;创建Vue3工程 示例 第一步&#xff1a;执行创建项目的命令&#xff0c;study-front-vue3是项目名称 npm init vite-app study-front-vue3第二步&#xff1a;进入项目文件夹&#xff0c;执行命令&#xff0c;安装模块 cd study-front-vue…

如何将ElementUI组件库中的时间控件迁移到帆软报表中

需求:需要将ElementUI组件库中的时间控件迁移到帆软报表中,具体为普通报表的参数面板中,填报报表的组件中,决策报表的组件与参数面板中。 这三个场景中分别需要用到帆软报表二开平台的ParameterWidgetOptionProvider,FormWidgetOptionProvider,CellWidgetOptionProvider开…

文献阅读:Sparse Low-rank Adaptation of Pre-trained Language Models

文献阅读&#xff1a;Sparse Low-rank Adaptation of Pre-trained Language Models 1. 文章简介2. 具体方法介绍 1. SoRA具体结构2. 阈值选取考察 3. 实验 & 结论 1. 基础实验 1. 实验设置2. 结果分析 2. 细节讨论 1. 稀疏度分析2. rank分析3. 参数位置分析4. 效率考察 4.…

什么是检索增强生成 (RAG)

什么是 RAG RAG&#xff0c;即检索增强生成&#xff0c;是一种将预训练的大型语言模型的功能与外部数据源相结合的技术。这种方法将 GPT-3 或 GPT-4 等 LLM 的生成能力与专用数据搜索机制的精确性相结合&#xff0c;从而形成一个可以提供细微响应的系统。 本文更详细地探讨了…

JavaWeb——Spring事务管理

六、Spring事务管理 1. 注解 注解&#xff1a;Transactional 位置&#xff1a;业务&#xff08;service&#xff09;层的方法上、类上、接口上——一般在执行多条增删改方法上加 作用&#xff1a;将当前方法交给spring进行事务管理&#xff0c;方法执行前&#xff0c;开启事…

编程语言的语法糖,你了解多少?

什么是语法糖 语法糖是一种编程语言的特性&#xff0c;通常是一些简单的语法结构或函数调用&#xff0c;它可以通过隐藏底层的复杂性&#xff0c;并提供更高级别的抽象&#xff0c;从而使代码更加简洁、易读和易于理解&#xff0c;但它并不会改变代码的执行方式。 为什么需要语…

(aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器

1. 背景介绍 在先前的博客文章中&#xff0c;我们已经搭建了一个基于SRS的流媒体服务器。现在&#xff0c;我们希望通过Web接口来控制这个服务器的行为&#xff0c;特别是对于正在进行的 RTSP 转码任务的管理。这将使我们能够在不停止整个服务器的情况下&#xff0c;动态地启动…

OPPO Find X7 Ultra 发布,搭载双潜望四主摄摄影技术

2024年1月8日&#xff0c;深圳——OPPO发布旗舰Find X7 Ultra&#xff0c;定义移动影像的终极形态。Find X7 Ultra 首创的双潜望四主摄构成哈苏大师镜头群&#xff0c;以六个光学品质焦段提供目前手机最强大、品质最高的多摄变焦能力。首次搭载专为超光影图像引擎定制的一英寸传…

基于黑猩猩算法优化的Elman神经网络数据预测 - 附代码

基于黑猩猩算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于黑猩猩算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于黑猩猩优化的Elman网络5.测试结果6.参考文献7.Matlab代码 摘要&#x…