【CUDA】 矩阵乘法 matMatMul

矩阵乘法 matMatMul

矩阵乘法是基本线性代数子程序(BLAS)的重要组成部分,而且线性代数中许多其他操作以此为基础。

图1是两个矩阵的乘法。

基础方法,正方形tile和长方形tile

基础方法

执行矩阵乘法的基础方法是使用单个线程执行输出矩阵的单个元素乘法。这意味着所需的线程数等于输出矩阵中的元素数。线程排列在二维网格中,每个线程分配一个唯一的索引。线程的索引用于访问输入矩阵的相应行和列。执行行和列的乘法,结果存储在输出矩阵的相应元素中。

这种方法的主要缺点是多个线程加载相同的行和列来计算它们的输出。图2是一个示例。

图2

正如上图所示,为了计算P0,0和P0,1,两个线程都需要加载整个M0。对于P0,0和P1,0也是如此。两个线程都需要加载整个N1列。这意味着线程将多次访问相同的内存位置。

正方形tile

为了避免这个问题,我们可以使用tile。tile是将输入矩阵的一个小部分加载到共享内存中。线程将把tile加载到共享内存中,然后执行乘法运算。图3描述了这种技术。

图3

kernel将计算分为几个阶段。每个阶段,线程将将M和N矩阵中的tile加载到共享内存中。然后,线程将执行tile的乘法,并将结果累积到输出矩阵的相应元素中。

通过这种技术,我们可以看到全局内存访问减少了TILE_WIDTH(图示)倍。

长方形tile

为了进一步减少全局内存访问,我们可以使用图4所示的矩形块。

图4

通过这种技术,我们增加了每个线程的工作量,以进一步减少全局内存访问的次数。kernel再次将计算分成多个阶段。在每个阶段中,线程将从中加载一个tile M 和两个tile N 到共享内存中。然后线程将对这些tile进行乘法运算,并将结果累加到输出矩阵的相应元素中。


Code

host代码使用随机值初始化输入矩阵,并调用kernel执行乘法运算。输入矩阵以线性格式存储。


#include <iostream>

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/transform.h>


#include "mat_mat_mul_kernels.cuh"

int main(int argc, char *argv[])
{

    int rows1, cols1rows2, cols2, t_x;

    if(argc != 5){
        std::cout << "Usage: " << argv[0] << " <rows1> <cols1rows2> <cols2> <t_x>" << std::endl;
        return 1;
    }

    rows1 = atoi(argv[1]);
    cols1rows2 = atoi(argv[2]);
    cols2 = atoi(argv[3]);
    t_x = atoi(argv[4]);

    thrust::host_vector<float> h_in_mat1(rows1 * cols1rows2);
    thrust::host_vector<float> h_in_mat2(cols1rows2 * cols2);
    thrust::host_vector<float> h_out_host(rows1 * cols2);

    srand(time(NULL));
    for(int i = 0; i < rows1 * cols1rows2; i++)
        h_in_mat1[i] = rand() / (float)RAND_MAX;

    for(int i = 0; i < cols1rows2 * cols2; i++)
        h_in_mat2[i] = rand() / (float)RAND_MAX;

    for(int i = 0; i < rows1; ++i)
        for(int j = 0; j < cols2; ++j)
            for(int k = 0; k < cols1rows2; ++k)
                h_out_host[i * cols2 + j] += h_in_mat1[i * cols1rows2 + k] * h_in_mat2[k * cols2 + j];

    thrust::device_vector<float> d_in_mat1 = h_in_mat1;
    thrust::device_vector<float> d_in_mat2 = h_in_mat2;

    thrust::device_vector<float> d_out(rows1 * cols2);

    dim3 blockDim(t_x, t_x);
    dim3 gridDim((cols2 + blockDim.x - 1) / blockDim.x, (rows1 + blockDim.y - 1) / blockDim.y);
    mat_mat_mul<float><<<gridDim, blockDim>>>(
        thrust::raw_pointer_cast(d_in_mat1.data()),
        thrust::raw_pointer_cast(d_in_mat2.data()),
        thrust::raw_pointer_cast(d_out.data()),
        rows1, cols1rows2, cols1rows2, cols2);

    bool success = true;
    for(int i = 0; i < rows1 * cols2; ++i)
        if(abs(h_out_host[i] - d_out[i]) >= 0.001){
            std::cout << "Error at " << i << ": " << h_out_host[i] << " != " << d_out[i] << " (Mat Mat Mul)" << std::endl;
            success = false;
            break;
        }
    if(success)
        std::cout << "Success (Mat Mat Mul)" << std::endl;


    blockDim = dim3(t_x, t_x);
    gridDim = dim3((cols2 + blockDim.x - 1) / blockDim.x, (rows1 + blockDim.y - 1) / blockDim.y);
    mat_mat_mul_tiles<float><<<gridDim, blockDim, 2 * blockDim.x * blockDim.x * sizeof(float)>>>(
        thrust::raw_pointer_cast(d_in_mat1.data()),
        thrust::raw_pointer_cast(d_in_mat2.data()),
        thrust::raw_pointer_cast(d_out.data()),
        rows1, cols1rows2, cols1rows2, cols2);

    success = true;
    for(int i = 0; i < rows1 * cols2; ++i)
        if(abs(h_out_host[i] - d_out[i]) >= 0.001){
            std::cout << "Error at " << i << ": " << h_out_host[i] << " != " << d_out[i] << " (Mat Mat Mul Tiles)" << std::endl;
            success = false;
            break;
        }
    if(success)
        std::cout << "Success (Mat Mat Mul Tiles)" << std::endl;

    
    blockDim = dim3(t_x, t_x);
    gridDim = dim3((cols2 + blockDim.x - 1) / blockDim.x / 2, (rows1 + blockDim.y - 1) / blockDim.y);
    mat_mat_mul_rec_tiles<float><<<gridDim, blockDim, 3 * blockDim.x * blockDim.x * sizeof(float)>>>(
        thrust::raw_pointer_cast(d_in_mat1.data()),
        thrust::raw_pointer_cast(d_in_mat2.data()),
        thrust::raw_pointer_cast(d_out.data()),
        rows1, cols1rows2, cols1rows2, cols2);

    success = true;
    for(int i = 0; i < rows1 * cols2; ++i)
        if(abs(h_out_host[i] - d_out[i]) >= 0.001){
            std::cout << "Error at " << i << ": " << h_out_host[i] << " != " << d_out[i] << " (Mat Mat Mul Rec Tiles)" << std::endl;
            success = false;
            break;
        }
    if(success)
        std::cout << "Success (Mat Mat Mul Rec Tiles)" << std::endl;

    return 0;

}

以下是kernel的展示。

基础方法

template<typename T> __global__
void mat_mat_mul(T *in_mat1, T *in_mat2, T *out_mat, 
                 int mat1_rows, int mat1_cols, 
                 int mat2_rows, int mat2_cols) {

    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;

    if (row >= mat1_rows || col >= mat2_cols) return;

    T sum = 0;
    for (int k = 0; k < mat1_cols; ++k)
        sum += in_mat1[row * mat1_cols + k] * in_mat2[k * mat2_cols + col];

    out_mat[row * mat2_cols + col] = sum;
}

在这种基本方法中,kernel非常简单。

线程首先计算它们的行和列索引。如果行或列索引大于输入矩阵的行数或列数,则线程返回。

int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;

if (row >= mat1_rows || col >= mat2_cols) return;

然后,这些线程会对行和列进行相乘,并将结果存储在输出矩阵的相应元素中。

T sum = 0;
for (int k = 0; k < mat1_cols; ++k)
    sum += in_mat1[row * mat1_cols + k] * in_mat2[k * mat2_cols + col];

out_mat[row * mat2_cols + col] = sum;

我们可以观察到,线程不仅会多次加载相同的行和列,而且它们还会以非合并的方式加载M的元素。这意味着线程将无法充分利用内存带宽。


正方形tile

template<typename T> __global__
void mat_mat_mul_tiles(T *in_mat1, T *in_mat2, T *out_mat,
                       int mat1_rows, int mat1_cols,
                       int mat2_rows, int mat2_cols) {
    
    // Initialize shared memory
    int TILE_WIDTH = blockDim.x;
    extern __shared__ uint8_t shared_mem[];
    T *ds_mat1 = reinterpret_cast<T*>(shared_mem);
    T *ds_mat2 = reinterpret_cast<T*>(shared_mem + TILE_WIDTH * TILE_WIDTH * sizeof(T));
    
    int bx = blockIdx.x,  by = blockIdx.y;
    int tx = threadIdx.x, ty = threadIdx.y;

    int row = by * TILE_WIDTH + ty;
    int col = bx * TILE_WIDTH + tx;

    T out_value = 0;
    // Loop over the in_mat1 and in_mat2 tiles required to compute the out_mat element
    for (int ph = 0; ph < (mat1_cols + TILE_WIDTH - 1) / TILE_WIDTH; ++ph) {

        // Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
        ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows && ph * TILE_WIDTH + tx < mat1_cols ?
                                        in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;
                                        
        ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows && col < mat2_cols ?
                                        in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

        // Synchronize to make sure the tiles are loaded
        __syncthreads();

        // Compute the out_mat element
        for (int k = 0; k < TILE_WIDTH; ++k)
            out_value += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];
        
        // Synchronize to make sure the out_mat element is computed
        // before other threads load new tiles
        __syncthreads();
    }

    // Store the out_mat element in out_mat
    if (row < mat1_rows && col < mat2_cols)
        out_mat[row * mat2_cols + col] = out_value;
}

在这种方法中,我们使用共享内存来存储输入矩阵的块。线程首先计算它们的行和列索引。如果行或列索引大于输入矩阵的行数或列数,则线程返回。

int row = by * TILE_WIDTH + ty;
int col = bx * TILE_WIDTH + tx;

if (row >= mat1_rows || col >= mat2_cols) return;

在每个阶段:

线程将tiles加载到共享内存中。 这些tiles以合并访存的方式加载。

// Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows && ph * TILE_WIDTH + tx < mat1_cols ?
                                in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;

ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows && col < mat2_cols ?
                                in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

// Synchronize to make sure the tiles are loaded
__syncthreads();

当线程加载完tile后,它们会计算输出矩阵元素。线程通过将tiles的相应行和列相乘,并将结果相加来计算输出矩阵元素。

// Compute the out_mat element
for (int k = 0; k < TILE_WIDTH; ++k)
    out_value += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];

// Synchronize to make sure the out_mat element is computed
// before other threads load new tiles
__syncthreads();

最后,线程将输出矩阵元素存储在输出矩阵中。

// Store the out_mat element in out_mat
out_mat[row * mat2_cols + col] = out_value;

长方形tiles

template<typename T> __global__
void mat_mat_mul_rec_tiles(T* in_mat1, T* in_mat2, T* out_mat,
    int mat1_rows, int mat1_cols,
    int mat2_rows, int mat2_cols) {

    // Initialize shared memory
    int TILE_WIDTH = blockDim.x;
    extern __shared__ uint8_t shared_mem[];
    T* ds_mat1 = reinterpret_cast<T*>(shared_mem);
    T* ds_mat2 = reinterpret_cast<T*>(shared_mem + TILE_WIDTH * TILE_WIDTH * sizeof(T));
    T* ds_mat3 = reinterpret_cast<T*>(shared_mem + 2 * TILE_WIDTH * TILE_WIDTH * sizeof(T));

    int bx = blockIdx.x, by = blockIdx.y;
    int tx = threadIdx.x, ty = threadIdx.y;

    int row = by * TILE_WIDTH + ty;
    int col = bx * TILE_WIDTH * 2 + tx;

    T out_value1 = 0;
    T out_value2 = 0;
    // Loop over the in_mat1 and in_mat2 tiles required to compute the out_mat element
    for (int ph = 0; ph < (mat1_cols + TILE_WIDTH - 1) / TILE_WIDTH; ++ph) {

        // Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
        ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows&& ph* TILE_WIDTH + tx < mat1_cols ?
            in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;

        ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

        ds_mat3[ty * TILE_WIDTH + TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& TILE_WIDTH + col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + TILE_WIDTH + col] : 0;

        // Synchronize to make sure the tiles are loaded
        __syncthreads();

        // Compute the out_mat element
        for (int k = 0; k < TILE_WIDTH; k++) {
            out_value1 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];
            out_value2 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat3[k * TILE_WIDTH + TILE_WIDTH + tx];
        }

        // Synchronize to make sure the Pvalues are computed
        // before other threads load new tiles
        __syncthreads();
    }

    // Store the Pvalues in out_mat
    if (row < mat1_rows && col < mat2_cols)
        //out_mat[row][col];
        out_mat[row * mat2_cols + col] = out_value1;

    if (row < mat1_rows && TILE_WIDTH + col < mat2_cols)
        //out_mat[row][TILE_WIDTH + col];
        out_mat[row * mat2_cols + TILE_WIDTH + col] = out_value2;
}

这个kernel函数几乎与正方形tile函数相同。

首先,线程计算出他们将计算的输出矩阵元素的行和列。

int row = by * TILE_WIDTH     + ty;
int col = bx * TILE_WIDTH * 2 + tx;

然后线程将tile加载到共享内存中。唯一的区别是N加载2个tile。

// Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
        ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows&& ph* TILE_WIDTH + tx < mat1_cols ?
            in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;

        ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

        ds_mat3[ty * TILE_WIDTH + TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& TILE_WIDTH + col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + TILE_WIDTH + col] : 0;

然后线程计算两个输出矩阵元素。

// Compute the out_mat element
        for (int k = 0; k < TILE_WIDTH; k++) {
            out_value1 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];
            out_value2 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat3[k * TILE_WIDTH + TILE_WIDTH + tx];
        }

最后,存储两个输出矩阵元素。

// Store the Pvalues in out_mat
if (row < mat1_rows && col < mat2_cols)
  //out_mat[row][col];
    out_mat[row * mat2_cols + col] = out_value1;

if (row < mat1_rows && TILE_WIDTH + col < mat2_cols)
  //out_mat[row][TILE_WIDTH + col];
    out_mat[row * mat2_cols + TILE_WIDTH + col] = out_value2;

性能分析

运行时间:

矩阵维度:1024 × 1024

线程块维度:32 × 32

可见使用共享内存可以有效降低运行速度。但长方形tile反而耗时更久。因为L1缓存与共享内存公用硬件空间。可能共享内存占据大部分空间,而L1缓存所剩无几,从而导致长方形tile耗时更久。具体情况需要做性能分析,之后再补充。也许是长方形tile的复杂性增加。kernel引入了许多分支和同步点。这可能导致耗时更久。

笔者采用设备:RTX3060 6GB

PMPP项目提供的分析

kernel的性能是使用NvBench项目在多个gpu中测量的。研究的性能测量方法有:

内存带宽:每秒传输的数据量。

内存带宽利用率:占用内存带宽的百分比。

基础方法

正方形tile

长方形tile

参考文献:

1、大规模并行处理器编程实战(第2版)

2、PPMP

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

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

相关文章

如何学习和提升SQL

资料来源于腾讯技术直播&#xff0c;只作为学习记录&#xff0c;如有侵权&#xff0c;请联系作者进行删除

JUC并发编程基础(包含线程概念,状态等具体实现)

一.JUC并发编程基础 1. 并行与并发 1.1 并发: 是在同一实体上的多个事件是在一台处理器上"同时处理多个任务"同一时刻,其实是只有一个事件在发生. 即多个线程抢占同一个资源. 1.2 并行 是在不同实体上的多个事件是在多台处理器上同时处理多个任务同一时刻,大家…

【码银送书第二十二期】《Python数据分析从入门到精通(第2版)》

&#x1f490;大家好&#xff01;我是码银~&#xff0c;欢迎关注&#x1f490;&#xff1a; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 前言 &#x1f340;丛书说明&#xff1a;“软件开发视频大讲堂‘’丛书第1版于2008年8月出版&#xff0c;因其编写细腻、易学实用…

ython 使用 cx_Freeze 打包,不想要打包文件中能直接看到依赖的代码,如何处理

背景&#xff1a;因为使用 cx_Freeze 打包时&#xff0c;添加需要依赖的文件 cx_Freeze 是一个用于将 Python 程序打包成独立可执行文件的工具&#xff0c;支持多个平台。当你需要打包包含多个 .py 文件的项目时&#xff0c;你可以通过编写一个 setup.py 文件来指定哪些模块应…

免杀笔记 ----> ShellCode Loader !!!

学了那么久的前置知识&#xff0c;终于到了能上线的地方了&#xff01;&#xff01;&#xff01; 不过这里还没到免杀的部分&#xff0c;距离bypass一众的杀毒软件还有很长的路要走&#xff01;&#xff01; 目录 1.ShellCode 2.ShellCode Loader的概念 3.可读可写可…

【邀请函】相约CommunityOverCode Asia 2024,共探Flink、Paimon、Celeborn开源新境界!

CommunityOverCode是由Apache软件基金会&#xff08;ASF&#xff09;主办的一系列全球性会议&#xff0c;旨在促进开源技术的发展和社区参与。自1998年以来&#xff0c;ApacheCon一直是这一系列活动的核心&#xff0c;吸引了不同背景和技术层级的参与者&#xff0c;关注于“明天…

C++11 shared_ptr---面试常考

shared_ptr简介 共享对其所指堆内存空间的所有权&#xff0c;当最后⼀个指涉到该对象的shared_ptr不再指向他时&#xff0c;shared_ptr会⾃动析构所指对象如何判断⾃⼰是否指涉到该资源的最后⼀个&#xff1f;《引⽤计数》 shared_ptr构造函数&#xff0c;使引⽤计数析构函数&…

应用了网络变压器的PC网卡连接转换器后不好连网,有掉线现象,但外接路由器无问题,可能是什么原因?

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;今天分享的是应用了网络变压器的PC网卡连接转换器后不好连网&#xff0c;有掉线现象&#xff0c;但外接路由器无问题&#xff0c;可能是什么原因呢&#xff1f;如何解决呢&#xff1f; 首先&#xff0c;我们要了解传…

Java后端开发(十二)-- 未配置 Spring Boot 配置注解处理器

目录 1. 错误表现形式 2. 解决方式 1. 错误表现形式 2. 解决方式 在 Spring Boot 应用程序中,通常使用 @ConfigurationProperties 注解来将配置文件中的属性绑定到 Java 对象中。如果没有配置 Spring Boot 配置注解处理器,那么这些注解将无法自动处理和加载。 …

清空flowable的表定义的相关表

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

qt 如果把像素点数据变成一个图片

1.概要 图像的本质是什么&#xff0c;就是一个个的像素点&#xff0c;对与显示器来说就是一个二维数组。无论多复杂的图片&#xff0c;对于显示器来说就是一个二维数组。 2.代码 #include "widget.h"#include <QApplication> #include <QImage> #incl…

兴业小课堂|什么是法拍房助拍机构?如何挑选靠谱的助拍机构?

随着法拍房市场的不断发展和扩大 使法拍房数量的增加 其交易的复杂性和专业性需求也日益凸显 这促使了专门机构的出现来满足市场需求 法拍房助拍机构存在的原因主要有以下几点&#xff1a; 1.专业知识和经验&#xff1a; 法拍房的交易流程相对复杂&#xff0c;涉及到法律法…

对标 GPT-4o 的开源实时语音多模态模型:Moshi

是由法国的 AI 实验室 Kyutai 推出的实时语音多模态模型&#xff0c;支持听、说、看&#xff0c;最关键的是你现在就可以在浏览器中使用&#xff0c;如果这个链接延迟高&#xff0c;可以试试这个, 无需输入邮箱&#xff0c;点击 Join queue 即可。 简单体验了下&#xff0c;比…

Perplexity: 推出 ProSearch 的新版本

Perplexity 更新了 ProSearch 功能&#xff0c;支持多步推理、高级数学和编程能力&#xff0c;比起快速搜索&#xff0c;提供了更深入、全面的搜索&#xff0c;以及详尽报告和分析。 白嫖用户每 4 个小时可免费使用 5 次&#xff0c;Pro 用户几乎无限制。

ChatGPT:SpringBoot解决跨域问题方法-手动设置请求头

ChatGPT&#xff1a;SpringBoot解决跨域问题方法-手动设置请求头 这里的设置响应头是为了发送请求方还是接收请求方 设置响应头是为了发送请求方。具体来说&#xff0c;添加 Access-Control-Allow-Origin 头部是为了告诉浏览器&#xff0c;哪些域名可以访问资源。当设置为 * 时…

二百四十二、Hive——Hive的动态分区表出现day=__HIVE_DEFAULT_PARTITION__分区

一、目的 Hive的DWD层动态分区表的分区出现day__HIVE_DEFAULT_PARTITION__&#xff0c;有点懵&#xff0c;而且表中数据的day字段也显示__HIVE_DEFAULT_PARTITION__ 1、DWD层动态分区表的分区 __HIVE_DEFAULT_PARTITION__ 2、DWD层分区字段day数据 __HIVE_DEFAULT_PARTITION…

【中项第三版】系统集成项目管理工程师 | 第 4 章 信息系统架构① | 4.1-4.2

前言 第4章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于技术相关的内容&#xff0c;学习要以教材为准。本章分值预计在4-5分。 目录 4.1 架构基础 4.1.1 指导思想 4.1.2 设计原则 4.1.3 建设目标 4.1.4 总体框架 4.2 系统架构 4.2.1 架构定义 4.…

通义灵码入选 2024 世界人工智能大会最高荣誉「镇馆之宝」

7 月 4 日&#xff0c;2024 上海世界人工智能大会正式开幕&#xff0c;并揭晓了今年的「镇馆之宝」名单&#xff0c;通义灵码入选&#xff0c;是首个入围该名单的 AI 编程助手。 镇馆之宝是世界人工智能大会展览的最高荣誉&#xff0c;从科技含量、市场前景、创新性以及社会经济…

pip install包出现哈希错误解决

如图&#xff0c;当遇到此类错误时&#xff0c;多半是连接不稳定导致的校验失败。我们可以在PC端&#xff0c;或Ubuntu通过浏览器下载.whl安装文件&#xff1a;直接复制报错信息中的网址到浏览器即可弹出下载窗口。

Vue3重构案例(使用vue3的语法重构element的button组件)

这篇文章紧接的上一篇文章&#xff0c;上篇文章是对给element的button组件写了一个单元测试&#xff0c;这篇文章是使用vue3的语法进行重构&#xff0c;这里说一下单元测试和重构的联系&#xff0c;当你给组件写了单元测试之后&#xff0c;重构会减少你很多的debug时间&#xf…