cuda系列详细教程

news2024/12/25 12:31:06

  随着人工智能的发展与人才的内卷,很多企业已将深度学习算法的C++部署能力作为基本技能之一。面对诸多arm相关且资源有限的设备,往往想更好的提速,满足更高时效性,必将更多类似矩阵相关运算交给CUDA处理。同时,面对市场诸多教程与诸多博客岑子不起的教程或高昂教程费用,使读者(特别是小白)容易迷糊,无法快速入手CUDA编程,实现工程化。
因此,我将结合我的工程实战经验,我将在本专栏实现CUDA系列教程,帮助读者(或小白)实现CUDA工程化,掌握CUDA编程能力。学习我的教程专栏,你将绝对能实现CUDA工程化,完全从环境安装到CUDA核函数编程,从核函数到使用相关内存优化,从内存优化到深度学习算子开发(如:nms),从算子优化到模型(以yolo系列为基准)部署。最重要的是,我的教程将简单明了直切主题,CUDA理论与实战实例应用,并附相关代码,可直接上手实战。我的想法是掌握必要CUDA相关理论,去除非必须繁杂理论,实现CUDA算法应用开发,待进一步提高,将进一步理解更高深理论。

链接:https://blog.csdn.net/weixin_38252409/category_12383040.html?spm=1001.2014.3001.5482

 一、核函数index寻找

cuda通过线程执行并行运算,理所当然,我们需要知道如何使用每个线程实现自己的计算逻辑。而线程操作通过索引(index)操作,索引和block与grid挂钩,自然我们需要知晓如何在grid与block中确定索引。为此,我写了索引寻找规律,可以通过公式直接计算,我在此不细说,仅展示以下展示部分代码,其详细内容和附件可点击我的链接。

部分展示代码如下:

3d grid与1d block寻找索引代码:

blockSize = blockDim.x(一维 block 的大小)
blockId = Dx * Dy * z + Dx * y + x (三维 grid 中 block 的 id,用公式)
  = gridDim.x * gridDim.y * blockIdx.z + gridDim.x * blockIdx.y + blockIdx.x
threadId = threadIdx.x (一维 block 中 thread 的 id)
Id = (gridDim.x * gridDim.y * blockIdx.z + gridDim.x * blockIdx.y + blockIdx.x ) * blockDim.x + threadIdx.x

1d grid, 2d block寻找索引代码:

blockSize = blockDim.x * blockDim.y(二维 block 的大小)
blockId = blockIdx.x(一维 grid 中 block id)
threadId = Dx * y + x (二维 block 中 thread 的 id)
  = blockDim.x * threadIdx.y + threadIdx.x
Id = blockIdx.x * (blockDim.x * blockDim.y) + blockDim.x * threadIdx.y + threadIdx.x

二、kernel函数实例

  如上所说,我们知道kernel函数索引寻找方法,我们自然想通过索引实现各种运算,多数为矩阵运算。为此,我写了大量实例cuda代码,并用不同实例说明其cuda编码规律,我将以其中一个实例矩阵加法代码作为展示,此代码使用多种途径求其结果,其详细内容和附件代码可点击我的链接。

__global__ void gpu_matrix_plus_thread(int* a, int* b, int* c)
{   
    //方法一:通过id方式计算
    //grid为2维度,block为2维度,使用公式id=blocksize * blockid + threadid
    int blocksize = blockDim.x*blockDim.y;
    int blockid = gridDim.x*blockIdx.y+blockIdx.x;
    int threadid = blockDim.x*threadIdx.y+threadIdx.x;
    int id = blocksize * blockid + threadid;    
    c[id] = a[id] + b[id];
}
__global__ void gpu_matrix_plus1(int* a, int* b, int* c, int m, int n)
{   //方法二:通过row与col的方式计算-->通过变换列给出id
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    c[row*n  + col] = a[row*n  + col] + b[row*n  + col];
}
__global__ void gpu_matrix_plus2(int* a, int* b, int* c, int m, int n)
{   //方法三:通过row与col的方式计算-->通过变换行给出id
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    c[row  + col*m] = a[row  + col*m] + b[row  + col*m];
}

三、性能优化(内存)

  既然我们已可自如实现自己计算逻辑,那么我么也需兼顾运行效率,运行速度提升可通过更好pipeline逻辑实现,也可通过内存方式实现,而逻辑架构因人而异,我将不在细说,内存实现可通过对cuda内存理解便可掌握。为此,我也写了内存相关实例代码,介绍其内存使用方法,如纹理内存、共享内存等,我将以纹理内存代码作为展示,其更多详细内容和附件代码可点击我的链接。

//核心代码,在gpu端执行的kernel,  
__global__ void Textureone(unsigned int* listTarget, int size)
{
    unsigned int texvalue = 0;
    int index = blockIdx.x * blockDim.x + threadIdx.x; //通过线程ID得到数组下标 
    if (index < size)
        texvalue= tex1Dfetch(texone, index)*100; //通过索引获得纹理值再乘100 
        listTarget[index] = texvalue;
}

四、原子操作

  在 CUDA 中,原子操作是一种用于确保多个线程同时访问同一内存地址时的同步机制。原子操作可以确保只有一个线程可以访问内存地址,并且可以避免数据竞争和不确定的结果。我将已实例代码展示原子操作,以下为原子操作实例,其更多详细内容和附件代码可点击我的链接。

部分原子操作代码如下:

__global__ void kernel(int* data) {
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    // 对共享内存中的数据执行原子加操作
    atomicAdd(&data[tid], 1);
}

int main() {
    int size = 1024; int* data = new int[size]; int* d_data; cudaMalloc(&d_data, size * sizeof(int)); cudaMemcpy(d_data, data, size * sizeof(int), cudaMemcpyHostToDevice); kernel<<<1, size>>>(d_data); cudaMemcpy(data, d_data, size * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(d_data); delete[] data; return 0; }

五、流stream

六、cuda处理nms编码

大量算法的后处理逻辑均会使用NMS算法去重,然CPU算法较慢,为此我写了cuda的NMS算法处理,以下将部分展示,其详细代码可点击我的链接。

部分代码如下:

// 定义CUDA核函数,用于执行NMS算法
__global__ void nms_kernel(nms_box* boxes, int* indices, int* num_indices, float nms_thr)
{
    /*
    boxes:输入nms信息,为结构体
    indices:输入为列表序列,记录所有box,如[0,1,2,3,4,5,...],后续将不需要会变成-1。
    num_indices:记录有多少个box数量
    float nms_thr:nms的阈值,实际为iou阈值
    */
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= *num_indices) { return; }
    int index = indices[i];
    if (index == -1) { return; }
    nms_box box = boxes[index];
    for (int j = i + 1; j < *num_indices; j++) {
        int other_index = indices[j];
        if (other_index == -1) { continue; }
        nms_box other_box = boxes[other_index];
        float iou_value = iou(box, other_box);
        printf("iou value:%f\n", iou_value);
        if (iou_value > nms_thr) { indices[j] = -1; }
    }
}

输出结果如下:

    

七、cuda处理yolo算法输出编码

在tensorrt部署中,yolo算法输出在gpu设备上且数据较为庞大(如:640输入将有25200*(class_num+5)*batch数据),使用cpu处理,需将值从gpu端复制host端,复制过程会花费很多时间。因此,我为此我写了cuda的yolo输出数据处理,以下将部分展示,其详细代码可点击我的链接。

代码如下:

__global__ void decode_yolo_kernel(float* prob, float* parray, int max_objects, int cls_num, float conf_thr, int* d_count) {
    int idx = blockDim.x * blockIdx.x + threadIdx.x; 
    int tmp_idx = idx * (cls_num + 5); 
    float left = prob[tmp_idx + 0]; 
    float top = prob[tmp_idx + 1];
    float right = prob[tmp_idx + 2];
    float bottom = prob[tmp_idx + 3];
    float conf = prob[tmp_idx + 4]; 
    float class_score = prob[tmp_idx + 5];
    float tmp_conf = conf * class_score;
    int class_id = 0;
    for (int j = 0; j < cls_num; j++) {
        int cls_idx = tmp_idx + 5 + j;
        if (tmp_conf < conf * prob[cls_idx]) {
            class_id = j;
            tmp_conf = conf * prob[cls_idx];
        }
    }
    if (tmp_conf < conf_thr) { return; }
    int index = atomicAdd(d_count, 1);
    if (index >= max_objects) { return; } 
    int out_index = index * 6; 
    parray[out_index + 0] = left;   
    parray[out_index + 1] = top;    
    parray[out_index + 2] = right;  
    parray[out_index + 3] = bottom;         parray[out_index + 4] = tmp_conf;   
    parray[out_index + 5] = class_id;   

}

代码解释:

目的:简化模拟yolo输出结果于cuda核函数中处理

假设:置信度阀值为0.45,类别为2,最大目标数为3,

下图数据说明:左边为核函数实现代码展示;右边每一含为一个目标预测结果,分别表示box值[x,y,w,h]、置信度conf、类别预测值[c1_score,c2_score];右下角为cuda核函数选择目标结果,其中conf为类别score*conf;

实现方法:利用核函数与原子操作完成目标筛选。

运行结果如下:

八、cuda处理yolo算法整个过程

在tensorrt部署中,yolo算法输出使用gpu处理已在上面涉及,然如何去重box与yolo整套后处理呢?为此,我也写了基于cuda处理yolo的整个过程,以下将部分代码展示,其详细代码可点击我的链接。

代码如下:

void imitate_yolo_postprocess_convert() {
   
    const int block = 32;
    /*************************************************开始cuda计算***********************************************/
    cudaStream_t stream;
    cudaStreamCreate(&stream);

    h_count = 0;
    cudaMemcpy(d_count, &h_count, sizeof(int), cudaMemcpyHostToDevice);  //初始化记录有效变量d_count与h_count
    int grid = (anchor_output_num + block - 1) / block;
    decode_yolo_kernel << < grid, block, 0, stream >> > (gpu_input, gpu_output, max_object, cls_num, conf_thr, d_count);
    cudaMemcpy(&h_count, d_count, sizeof(int), cudaMemcpyDeviceToHost);
    if (h_count > max_object) { h_count = max_object; };


    /****************************************打印模型输出输出数据结果--》通过置信度已过滤不满足要求和给出类别**********************************/

    float* host_decode = nullptr; // 保存gpu处理的变量 
    cudaMallocHost((void**)&host_decode, sizeof(float) * max_object * 6);
    cudaMemcpy(host_decode, gpu_output, sizeof(float) * max_object * 6, cudaMemcpyDeviceToHost);
    std::cout << "\n\n打印输出结果-gpu_output\n" << endl;
    if (h_count == 0) { std::cout << "\n无检测结果" << endl; }
    for (int i = 0; i < h_count; i++) {
        int idx = i * 6;
        std::cout << "x1:" << host_decode[idx] << "\ty1:" << host_decode[idx + 1] << "\tx2:" << host_decode[idx + 2]
            << "\ty2:" << host_decode[idx + 3] << "\tconf:" << host_decode[idx + 4] << "\tclass_id:" << host_decode[idx + 5] << endl;

    }
    /******************************************************************************************************************************/

    int grid_max = (max_object + block - 1) / block;
    data_format_convert << < grid_max, block, 0, stream >> > (d_boxes, gpu_output, h_count); // gpu_output格式为[x1,y1,conf,cls_id]

    /****************************************将数据转换为带有nms_box格式数据******************************************************/
    nms_box* h_boxes_format = nullptr;
    cudaMallocHost(&h_boxes_format, anchor_output_num * sizeof(nms_box));
    cudaMemcpy(h_boxes_format, d_boxes, anchor_output_num * sizeof(nms_box), cudaMemcpyDeviceToHost);
    std::cout << "\n\n打印格式转换输出-h_boxes_format\n" << endl;
    if (h_count == 0) { std::cout << "\n无检测结果" << endl; }
    for (int i = 0; i < h_count; i++) {
        nms_box bb = h_boxes_format[i];
        std::cout << "x1:" << bb.x1 << "\ty1:" << bb.y1 << "\tx2:" << bb.x2 << "\ty2:" << bb.y2 << "\tconf:" << bb.score << "\tclass_id:" << bb.cls_id << endl;
    }
    /******************************************************************************************************************************/


    cudaMemcpy(d_nms_indices, h_nms_indices_init, max_object * sizeof(int), cudaMemcpyHostToDevice); //初始化nms处理的索引-->很重要

    /****************************************查看d_nms_indices数据******************************************************/
    int* d_nms_indices_visual = nullptr;
    cudaMallocHost(&d_nms_indices_visual, max_object * sizeof(int));
    cudaMemcpy(d_nms_indices_visual, d_nms_indices, max_object * sizeof(int), cudaMemcpyDeviceToHost);
    std::cout << "\n\nd_nms_indices:\n" << endl;
    for (int i = 0; i < max_object; i++) { std::cout << "\t" << d_nms_indices_visual[i] << endl; }

    /******************************************************************************************************************************/

    nms_yolo_kernel << <grid_max, block >> > (d_boxes, d_nms_indices, h_count, nms_thr);

    /*******将yolo的gpu上结果转host端,然后保存结果处理-->最终结果保存在keep_boxes中**********/
    cudaMemcpy(h_boxes, d_boxes, anchor_output_num * sizeof(nms_box), cudaMemcpyDeviceToHost);
    cudaMemcpy(h_nms_indices, d_nms_indices, max_object * sizeof(int), cudaMemcpyDeviceToHost);  //保存处理后的indice

    vector<nms_box> keep_boxes(h_count);
    for (int i = 0; i < h_count; i++) {
        if (h_nms_indices[i] > -1) {
            keep_boxes[i] = h_boxes[i];
        }
    }


    /****************************************查看nms处理后的-d_nms_indices******************************************************/
    std::cout << "nms处理后,保留box索引,-1表示排除obj,>-1表示保存obj" << endl;
    for (int i = 0; i < max_object; i++) { std::cout << h_nms_indices[i] << "\t"; }
    /**********************************************************************************************/

    /****************************************随便一张图为背景-显示结果于图上******************************************************/
    cv::Mat image = cv::imread("image.jpg");

    for (nms_box box : keep_boxes) {

        cv::Point p1(box.x1, box.y1);
        cv::Point p2(box.x2, box.y2);
        cv::rectangle(image, p1, p2, cv::Scalar(0, 255, 0), 4, 1, 0);//矩形的两个顶点,两个顶点都包括在矩形内部
    }


    cv::resize(image, image, cv::Size(600, 400), 0, 0, cv::INTER_NEAREST);
    cv::imshow("www", image);
    cv::waitKey(100000);
    cv::destroyAllWindows();
    /**********************************************************************************************/


}

注:以上代码有删减,完整代码可点击链接

结果显示如下:

九、yolo的tensorrt部署(前后处理的cpu版与gpu版)

cuda教程目录
第一章 指针篇
第二章 CUDA原理篇
第三章 CUDA编译器环境配置篇
第四章 kernel函数基础篇
第五章 kernel索引(index)篇
第六章 kenel矩阵计算实战篇
第七章 kenel实战强化篇
第八章 CUDA内存应用与性能优化篇
第九章 CUDA原子(atomic)实战篇
第十章 CUDA流(stream)实战篇
第十一章 CUDA的NMS算子实战篇
第十二章 YOLO的部署实战篇
第十三章 基于CUDA的YOLO部署实战篇

cuda教程内容
第一章到第三章探索指针在cuda函数中的作用与cuda相关原理及环境配置;

第四章初步探索cuda相关函数编写(global、device、__host__等),实现简单入门;

第五章探索不同grid与block配置,如何计算kernel函数的index,以便后续通过index实现各种运算;

第六、七章由浅入深探索核函数矩阵计算,深入探索grid、block与thread索引对kernel函数编写作用与影响,并实战多个应用列子(如:kernel函数实现图像颜色空间转换);

第八章探索cuda内存纹理内存、常量内存、全局内存等分配机制与内存实战应用(附代码),通过不同内存的使用来优化cuda计算性能;

第九章探索cuda原子(atomic)相关操作,并实战应用(如:获得某些自加索引等);

第十章探索cuda流stream相关应用,并给出相关实战列子(如:多流操作等);

第十一到十三章探索基于tensorrt部署yolo算法,我们首先将给出通用tensorrt的yolo算法部署,该部署的前后处理基于C++语言的host端实现,然后给出基于cuda的前后处理的算子核函数编写,最后数据无需在gpu与host间复制操作,实现gpu处理,提升算法性能。

目前,以上为我们的cuda教学全部内容,若后续读者有想了解知识,可留言,我们将根据实际情况,更新相关教学内容。

链接:https://blog.csdn.net/weixin_38252409/category_12383040.html?spm=1001.2014.3001.5482

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

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

相关文章

千帆大模型平台再升级:接入大模型最多、Prompt模板最全面

一、前言 近年来AI领域的发展取得了长足的进步&#xff0c;孵化出的相关产品可谓是如火如荼。尤其是最近爆火的ChatGPT&#xff0c;让非IT领域的其他领域的小伙伴也开始接触并使用它。其实它的爆火不是偶然&#xff0c;是因为ChatGPT实实在在的强大&#xff0c;真真切切的能解…

痞子衡嵌入式:借助i.MXRT10xx系列INIT_VTOR功能可以缩短程序热重启时间

大家好&#xff0c;我是痞子衡&#xff0c;是正经搞技术的痞子。今天痞子衡给大家分享的是借助i.MXRT10xx系列INIT_VTOR功能可以缩短程序热重启时间。 最近痞子衡写了篇文章 《i.MXRT从Serial NAND启动时间测量》&#xff0c;这篇文章详细测试了不同长度的 Non-XIP 程序在不同 …

Angular安全专辑 —— CSP防止XSS攻击

什么是 CSP&#xff08;Content Security Policy&#xff09; CSP&#xff08;Content Security Policy&#xff09;是一种Web安全策略&#xff0c;用于减轻和防止跨站脚本攻击&#xff08;XSS&#xff09;等安全漏洞。它通过允许网站管理员定义哪些资源可以加载到网页中&#…

【动态规划】数字三角形

算法提高课课堂笔记。 文章目录 摘花生题意思路代码 最低通行费题意思路代码 方格取数题意思路代码 摘花生 题目链接 Hello Kitty想摘点花生送给她喜欢的米老鼠。 她来到一片有网格状道路的矩形花生地(如下图)&#xff0c;从西北角进去&#xff0c;东南角出来。 地里每个道…

解决GitHub超时上不去

Github对于开发者开发者开发者来说肯定不陌生&#xff0c;但是Github 经常连接不上显示超时&#xff0c;一般都是节点ip的问题。本文主要介绍一下如何通过修改 Hosts 提升 Github 访问速度。之前在 Hosts 文件有加入过节点&#xff0c;不过容易失效&#xff0c;所以自己得常更新…

A10VSO140EF7、A10VSO45ED71变量柱塞泵比例放大器

A10VSO18EF1、A10VSO28EF2、A10VSO45EF5、A10VSO71EF7、A10VSO100EF2、A10VSO140EF7、A10VSO45ED71、A10VSO71ER72、A10VSO100ED71、A10VSO140ER72、A10VSO180ER71比例变量柱塞泵电流信号实现电比例控制。 高精确度。 发生电源故障时的安全功能&#xff0c;例如制动和转向系统…

Python爬虫(八)_Requests的使用

Requests&#xff1a;让HTTP服务人类 虽然Python的标准库中urllib2模块中已经包含了平常我们使用的大多数功能&#xff0c;但是它的API使用起来让人感觉不太好&#xff0c;而Requests自称"HTTP for Humans"&#xff0c;说明使用更简单方便。 Requests唯一的一个非转…

LeetCode--HOT100题(24)

目录 题目描述&#xff1a;234. 回文链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;234. 回文链表&#xff08;简单&#xff09; 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &…

微信-jssdk使用

需求: h5中使用微信的jsSDK,后续实现微信定位以及多图上传 微信文档 申请测试公众号 1.测试公众号进行配置 其中的域名是本地的ip地址 config接口进行权限配置,动态获取JS-SDK权限验证的签名 获取公众号accessToken以及jsTicket public static String WeChatAppId="wx…

Redis数据库的下载和安装

目录 第一章、Redis数据库的下载和安装1.1&#xff09;nosql数据库和 Redis 介绍1.2&#xff09;Windows中下载安装Redis数据库1.3&#xff09;Linux中安装Redis数据库1.4&#xff09;Linux中启动redis1.5&#xff09;Linux中关闭redis 第二章、三种Redis客户端连接Redis数据库…

0804|IO进程线程day7 【重点】线程的同步互斥(互斥锁、信号量、条件变量的概念及基本操作)

线程的同步互斥中的两个概念&#xff1a;临界资源、临界区&#xff1a; 临界资源&#xff1a;当多个任务同时访问一个资源的时候&#xff0c;我们将该资源称之为临界资源。临界区&#xff1a;访问临界资源的代码&#xff0c;称之为临界区线程之间&#xff0c;如果要进行通信&am…

从少年变成恶龙的平台经济

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦midjourney 产品统筹 / bobo 录音间 / 声湃轩北京站 东方甄选和抖音的“矛盾”再一次暴露出平台经济与入驻者之间微妙的关系。 平台经济&#xff0c;从一个引领时代…

人工智能有可能使下一代智力贫乏?

在ChatGPT推出之前&#xff0c;Maya认识的绝大多数大学生都会通过Google去完善自己的论文帮助。但人工智能之前的互联网其实是做不到真正的“高水平抄袭”&#xff0c;因为你根本无法找到复杂、具体、有创意或个性化的答案。 以上面提到的冲突解决课论文为例&#xff0c;写作要…

Effective Java笔记(30)优先考虑泛型方法

正如类可以从泛型中受益一般 &#xff0c;方法也一样。静态工具方法尤其适合于泛型化 。 Collections 中的所有“算法”方法&#xff08;例如 binarySearch 和 sort &#xff09;都泛型化了 。 编写泛型方法与编写泛型类型相类似 。 例如下面这个方法&#xff0c;它返回两个集合…

实战项目ssm权限系统 3-自定义注解+AOP拦截器记录登录日志

一 登录日志以及操作操作日志的操作 1.1 登录日志配置收集 1.1.1 编写log入库的service层接口 1.接口&#xff1a;在spring-security模块中 2.实现类&#xff1a;在service-system模块中 3.dao层&#xff1a;在service-system模块中 1.1.2 过滤器添加log记录 在过滤器中&…

ArcGISPro中如何使用机器学习脚本

点击工程 打开包管理器&#xff0c;我们可以发现&#xff0c;无法修改ArcGIS自带的默认python环境&#xff0c;所以我们需将默认环境进行克隆 点击设置 设置要克隆的地方&#xff0c;点击确定 激活克隆的环境&#xff0c;然后重写启动ArcGISPro 搜索并点击需要安装的库&#xf…

virtualBox安装openEuler、virtualBox安装欧安镜像、openEuler镜像安装、虚拟安装openEuler

应领导要求,为后面系统改成国产操作系统+国产数据的要求,写一篇openEuler的文章。 第一步:下载openEuler镜像:openEuler下载 | 欧拉系统ISO镜像 | openEuler社区官网 选择对应版本:现在一般是64位,下载64位即可 第二步:下载virtualbox,个人下载的7.0.10:Downloads …

【PCL-7】PCL统计滤波

【PCL-1】RANSAC平面分割_pcl ransac平面_WXG1011的博客-CSDN博客 这篇博客已介绍直通滤波与体素滤波&#xff0c;这里主要记录统计滤波。 统计滤波是遍历计算各测点与其领域点的平均距离&#xff0c;以此为标量&#xff0c;假设为高斯正态分布&#xff0c;按标准差去除离群点…

OpenAI允许网站阻止其网络爬虫;谷歌推出类似Grammarly的语法检查功能

&#x1f989; AI新闻 &#x1f680; OpenAI推出新功能&#xff0c;允许网站阻止其网络爬虫抓取数据训练GPT模型 摘要&#xff1a;OpenAI最近推出了一个新功能&#xff0c;允许网站阻止其网络爬虫从其网站上抓取数据训练GPT模型。该功能通过在网站的Robots.txt文件中禁止GPTB…

使用 Etcher 制作U盘系统盘

Etcher 资料&#xff1a; https://github.com/balena-io/etcher/blob/master/SUPPORT.md