高效 CUDA 调试:将 NVIDIA Compute Sanitizer 与 NVIDIA 工具扩展结合使用并创建自定义工具

高效 CUDA 调试:将 NVIDIA Compute Sanitizer 与 NVIDIA 工具扩展结合使用并创建自定义工具

NVIDIA Compute Sanitizer 是一款功能强大的工具,可以节省您的时间和精力,同时提高 CUDA 应用程序的可靠性和性能。 在 CUDA 环境中调试代码既具有挑战性又耗时,尤其是在处理数千个线程时。 计算消毒剂可以提供帮助!

在本系列的第一篇文章《高效 CUDA 调试:如何使用 NVIDIA Compute Sanitizer 寻找错误》中,我们讨论了如何开始使用一些 Compute Sanitizer 工具来在调试代码时检查内存泄漏和竞争条件。

在第二篇文章《高效 CUDA 调试:使用 NVIDIA Compute Sanitizer 进行内存初始化和线程同步》中,我们探索了用于检查内存初始化和线程同步的工具。

在这篇文章中,我们重点介绍了 Compute Sanitizer 的一些其他功能,即它与 NVIDIA 工具扩展 (NVTX) 的集成,用于标记代码以方便更直接地使用 Compute Sanitizer。 我们还讨论了 Compute Sanitizer 本身的 API,以便创建更多用于调试 CUDA 应用程序的工具。

在这篇文章中,我们重点介绍了 Compute Sanitizer 的一些其他功能,即它与 NVIDIA 工具扩展 (NVTX) 的集成,用于标记代码以方便更直接地使用 Compute Sanitizer。 我们还讨论了 Compute Sanitizer 本身的 API,以便创建更多用于调试 CUDA 应用程序的工具。

NVIDIA Compute Sanitizer

Compute Sanitizer 是一套工具,可以对代码的功能正确性执行不同类型的检查。 主要有四种工具:

  • memcheck:内存访问错误和泄漏检测。
  • racecheck:共享内存数据访问危险检测工具。
  • initcheck:未初始化设备全局内存访问检测工具。
  • synccheck:线程同步危险检测。

除了这些工具之外,NVIDIA Compute Sanitizer 还具有更多功能:

  • 一个 API,用于创建针对 CUDA 应用程序的清理和跟踪工具
  • 与 NVIDIA 工具扩展 (NVTX) 集成
  • Coredump 支持与 CUDA-GDB 一起使用
  • 用于管理工具输出的抑制功能

将 Compute Sanitizer 与 NVTX 结合使用

NVTX 是一个基于 C 的 API,用于注释程序中的代码范围、事件和资源。 此注释允许在应用程序运行时收集更多信息,这可用于在分析和分析代码时改进数据表示。 Compute Sanitizer 和 NVTX 之间的集成使您能够使用 NVTX 来注释代码,以帮助 Compute Sanitizer 捕获错误。

有关 NVTX 注释的更多信息,请参阅以下帖子:

  • C/C++ 和 NVTX:CUDA 专业提示:使用 NVTX 生成自定义应用程序配置文件时间线
    Python 和 NVTX:NVIDIA 工具扩展 API:用于分析 Python 和 C/C++ 代码的注释工具
    Fortran 和 NVTX:使用 NVTX 自定义 CUDA Fortran 分析

NVTX 内存 API 使 CUDA 程序能够向 Compute Sanitizer 通知内存限制,例如内存池管理或权限限制以及内存标签。

内存池管理

NVTX 与 Compute Sanitizer 集成的第一个示例来自 NVTX 内存 API 的子分配部分。

使用 API,您可以将内存分配注释为内存池。 Compute Sanitizer 知道这些池,并且可以检测实际使用特定分配的哪些部分。 然后,如果在代码执行期间访问内存池的任何未注册部分,则通过 Compute Sanitizer memcheck 工具检测到这些访问。

这是基本内存池的示例 mempool_example.cu。

#include <stdio.h>__global__ void populateMemory(int* chunk) {int i = threadIdx.x + blockDim.x * blockIdx.x;chunk[i] = i;
}int main(int argc, char **argv) {int poolSize   = 4096 * sizeof(int);int numThreads = 63;// int bucketSize = numThreads * sizeof(int); // You need this later ...void *pool;cudaMallocManaged(&pool, poolSize); // Create your memory pool// Assign part of the memory pool to the bucketauto bucket = (int *)pool + 16; // Address of bucket is 16 bytes into the pool// Set values in bucketpopulateMemory<<<1, numThreads>>>(bucket);cudaDeviceSynchronize();printf("After populateMemory 1: bucket 0, 1 ..  62: %d %d .. %d\n", bucket[0], bucket[1], bucket[numThreads-1]);// Set some more values in bucketpopulateMemory<<<1, numThreads + 1>>>(bucket);cudaDeviceSynchronize();printf("After populateMemory 2: bucket 0, 1 ..  63: %d %d .. %d\n", bucket[0], bucket[1], bucket[numThreads]);cudaFree(pool);exit(0);
}

在代码示例中,您创建了一个大小为 4096 个整数的内存池(称为池!)。 然后,您分配该池的一部分(由变量存储桶标记),从距池开头 16 字节的地址开始。

您打算将存储桶设置为具有 numThreads 个元素,在本例中为 63,由变量bucketSize 确定。 然后,您可以使用 GPU 内核 populateMemory 用一些值填充存储桶。 块数设置为 1,线程数设置为 numThreads,这意味着 populateMemory 执行 1×63 次,按预期设置存储桶中的每个值。

但是,您随后尝试通过 populateMemory 内核再次填充存储桶。 这次,您将线程数设置为 numThreads+1 (64)。 您的意图是让存储桶具有 63 个值,但现在您尝试分配 64 个值。但是,这不会导致错误。 例如,您没有访问任何越界内存,因为存储桶所属的实际内存池足够大,可以容纳额外的元素。

编译它并通过 memcheck 运行它,以确认这个潜在的错误没有被发现。 我们在 NVIDIA V100 GPU 上运行,因此我们将 GPU 架构设置为 sm_70。 您可能需要更改此设置,具体取决于您运行的内容。

$ nvcc -o mempool.exe mempool_example.cu -arch=sm_70
$ ./mempool.exe
After populateMemory 1: bucket 0, 1 ..  62: 0 1 .. 62
After populateMemory 2: bucket 0, 1 ..  63: 0 1 .. 63$compute-sanitizer --tool memcheck ./mempool.exe
========= COMPUTE-SANITIZER
After populateMemory 1: bucket 0, 1 ..  62: 0 1 .. 62
After populateMemory 2: bucket 0, 1 ..  63: 0 1 .. 63
========= ERROR SUMMARY: 0 errors

这就是 NVTX API 可以提供帮助的地方。 您可以使用 NVTX 的内存堆寄存器函数 nvtxMemHeapRegister 注册任何 cudaMalloc 内存分配。 这将内存注册为一个堆,代表可以进一步细分为多个区域的内存范围。 您可以通过以下过程在此代码中执行此操作。

首先,完成将 NVTX 与 Compute Sanitizer 结合使用所需的四个步骤。

对于 C 和 C++,NVTX 是一个仅包含头文件的库,没有依赖项,因此您必须获取 NVTX 头文件才能包含。 通常,这些会随您首选的 CUDA 下载一起提供,例如工具包或 HPC SDK。 但是,NVTX 内存 API 相对较新,因此现在可以从 /NVIDIA/NVTX GitHub 存储库获取它。 将来,它将作为工具包的一部分包含在内。

特别是,nvToolsExtMem.h 标头尚无法通过其他方法获得,因此在克隆 NVTX GitHub 分支 dev-mem-api 后检查它是否存在:

$ git clone --branch dev-mem-api https://github.com/NVIDIA/NVTX.git
…
$ ls NVTX/c/include/nvtx3/
nvToolsExtCuda.h    nvToolsExt.h           nvToolsExtMem.h     nvToolsExtSync.h  nvtxDetail
nvToolsExtCudaRt.h  nvToolsExtMemCudaRt.h  nvToolsExtOpenCL.h  nvtx3.hpp         nvtxExtDetail

现在您可以在源代码的开头包含 NVTX 和 NVTX API 头文件:

#include <nvtx3/nvToolsExt.h>
#include <nvtx3/nvToolsExtMem.h>

Compute Sanitizer 要求在任何 NVTX 调用之前初始化 CUDA 运行时。 无论如何,这可能会发生在您的代码中,具体取决于您开始使用 NVTX 的位置,但您可以使用 cudaFree 强制执行它,例如:

// Forces CUDA runtime initialization.
cudaFree(0);

最后,创建一个 NVTX 域。 这些是调用 API 所必需的。 目前,这些域没有特定的功能,但将用于未来的 Compute Sanitizer 版本。

// Create the NVTX domain
auto mynvtxDomain = nvtxDomainCreateA("my-domain");

好的,这就是完成的先决步骤。 现在,使用 NVTX 将池分配注册为内存池或堆:

nvtxMemVirtualRangeDesc_t myPoolRangeDesc = {}; // Descriptor for the// range memory pool
myPoolRangeDesc.size = poolSize; // Size of the range memory pool
myPoolRangeDesc.ptr  = pool;     // Pointer to the pool itselfnvtxMemHeapDesc_t myHeapDesc = {}; // Descriptor for the heapmyHeapDesc.extCompatID = NVTX_EXT_COMPATID_MEM;
myHeapDesc.structSize = sizeof(nvtxMemHeapDesc_t);
myHeapDesc.usage = NVTX_MEM_HEAP_USAGE_TYPE_SUB_ALLOCATOR;
myHeapDesc.type = NVTX_MEM_TYPE_VIRTUAL_ADDRESS;
myHeapDesc.typeSpecificDescSize = sizeof(nvtxMemVirtualRangeDesc_t);
myHeapDesc.typeSpecificDesc = &myPoolRangeDesc;auto mynvtxPool = nvtxMemHeapRegister(mynvtxDomain, &myHeapDesc);

这些步骤已注册该池并将其分配给变量 mynvtxPool。 要在前面的示例中使用它,您现在必须在池中创建一个子分配来代表存储桶。 语法与分配池本身的方式没有什么不同,但这次使用区域描述符而不是堆描述符:

nvtxMemVirtualRangeDesc_t mySubRangeDesc = {}; // Descriptor for the range
mySubRangeDesc.size = bucketSize; // Size of your suballocation (in bytes)
mySubRangeDesc.ptr  = bucket;     // Pointer to the suballocationnvtxMemRegionsRegisterBatch_t myRegionsDesc = {};
myRegionsDesc.extCompatID = NVTX_EXT_COMPATID_MEM;
myRegionsDesc.structSize  = sizeof(nvtxMemRegionsRegisterBatch_t);
myRegionsDesc.regionType  = NVTX_MEM_TYPE_VIRTUAL_ADDRESS;
myRegionsDesc.heap = mynvtxPool; // The heap you registered earlier
myRegionsDesc.regionCount = 1;
myRegionsDesc.regionDescElementSize = sizeof(nvtxMemVirtualRangeDesc_t);
myRegionsDesc.regionDescElements = &mySubRangeDesc;nvtxMemRegionsRegister(mynvtxDomain, &myRegionsDesc);

这既是您的内存池,又是现在已向 NVTX 注册的子分配存储桶。 这意味着 Compute Sanitizer 可以将它们的属性作为其检查的一部分。 现在,看看它是否检测到将存储桶填充到其预期范围之外的错误尝试。

以下是现在具有 NVTX 注册的基本内存池的完整代码示例,包括 NVTX 注册,mempool_nvtx_example.cu

#include <nvtx3/nvToolsExt.h>
#include <nvtx3/nvToolsExtMem.h>#include <stdio.h>__global__ void populateMemory(int* chunk) {int i = threadIdx.x + blockDim.x * blockIdx.x;chunk[i] = i;
}int main(int argc, char **argv) {int poolSize   = 4096 * sizeof(int);int numThreads = 63;int bucketSize = numThreads * sizeof(int);// Forces CUDA runtime initialization.cudaFree(0);// Create the NVTX domainauto mynvtxDomain = nvtxDomainCreateA("my-domain");void *pool;cudaMallocManaged(&pool, poolSize); // Create your memory pool// Register the pool with NVTXnvtxMemVirtualRangeDesc_t myPoolRangeDesc = {}; // Descriptor for the// range memory poolmyPoolRangeDesc.size = poolSize; // Size of the range memory poolmyPoolRangeDesc.ptr  = pool;     // Pointer to the pool itselfnvtxMemHeapDesc_t myHeapDesc = {}; // Descriptor for the heapmyHeapDesc.extCompatID = NVTX_EXT_COMPATID_MEM;myHeapDesc.structSize = sizeof(nvtxMemHeapDesc_t);myHeapDesc.usage = NVTX_MEM_HEAP_USAGE_TYPE_SUB_ALLOCATOR;myHeapDesc.type = NVTX_MEM_TYPE_VIRTUAL_ADDRESS;myHeapDesc.typeSpecificDescSize = sizeof(nvtxMemVirtualRangeDesc_t);myHeapDesc.typeSpecificDesc = &myPoolRangeDesc;auto mynvtxPool = nvtxMemHeapRegister(mynvtxDomain, &myHeapDesc);// Assign part of the memory pool to the bucketauto bucket = (int *)pool + 16; // Address of bucket is 16 bytes into the pool// Register bucket as a suballocated region in NVTXnvtxMemVirtualRangeDesc_t mySubRangeDesc = {}; // Descriptor for the rangemySubRangeDesc.size = bucketSize; // Size of your suballocation (in bytes)mySubRangeDesc.ptr  = bucket;     // Pointer to the suballocationnvtxMemRegionsRegisterBatch_t myRegionsDesc = {};myRegionsDesc.extCompatID = NVTX_EXT_COMPATID_MEM;myRegionsDesc.structSize  = sizeof(nvtxMemRegionsRegisterBatch_t);myRegionsDesc.regionType  = NVTX_MEM_TYPE_VIRTUAL_ADDRESS;myRegionsDesc.heap = mynvtxPool; // The heap you registered earliermyRegionsDesc.regionCount = 1;myRegionsDesc.regionDescElementSize = sizeof(nvtxMemVirtualRangeDesc_t);myRegionsDesc.regionDescElements = &mySubRangeDesc;nvtxMemRegionsRegister(mynvtxDomain, &myRegionsDesc);// Set values in bucketpopulateMemory<<<1, numThreads>>>(bucket);cudaDeviceSynchronize();printf("After populateMemory 1: bucket 0, 1 ..  62: %d %d .. %d\n", bucket[0], bucket[1], bucket[numThreads-1]);// Set some more values in bucketpopulateMemory<<<1, numThreads + 1>>>(bucket);cudaDeviceSynchronize();printf("After populateMemory 2: bucket 0, 1 ..  63: %d %d .. %d\n", bucket[0], bucket[1], bucket[numThreads]);cudaFree(pool);exit(0);
}

编译它并再次通过 Compute Sanitizer 运行它。 编译步骤中的 include 语句应指向您安装 NVTX 标头的位置。

$ nvcc -I ./NVTX/c/include -o mempool_nvtx.exe mempool_nvtx_example.cu -arch=sm_70
$ compute-sanitizer --tool memcheck --destroy-on-device-error=kernel ./mempool_nvtx.exe
========= COMPUTE-SANITIZER
After populateMemory 1: bucket 0, 1 ..  62: 0 1 .. 62
========= Invalid __global__ write of size 4 bytes
=========     at populateMemory(int *)+0x70
=========     by thread (63,0,0) in block (0,0,0)
=========     Address 0x7f2a9800013c is out of bounds
=========     and is 1 bytes after the nearest allocation at 0x7f2a98000040 of size 252 bytes
=========     Saved host backtrace up to driver entry point at kernel launch time
. . .
=========
After populateMemory 2: bucket 0, 1 ..  63: 0 1 .. 0
========= ERROR SUMMARY: 1 error

Compute Sanitizer 确实捕获了写入指定数组末尾的元素的尝试:

Invalid __global__ write of size 4 bytes

现在,如果您有部分内存池需要调整大小甚至销毁怎么办? NVTX 内存 API 还提供了类似的方法来对其 NVTX 注册执行此操作。

要调整大小,请返回之前的池和存储桶示例。 如果您想要将存储桶的大小从 63 个元素调整为 64 个元素,请使用以下代码修改前面的代码示例,也会调整您的 NVTX 注册存储桶的大小以反映这一点:

// Resizing the sub-allocation within the memory pool// You reuse mySubRangeDesc from earliermySubRangeDesc.size = bucketSize + 4; // You want one extra int (4B) elementmySubRangeDesc.ptr  = bucket;nvtxMemRegionsResizeBatch_t myNewRegionsDesc = {};myNewRegionsDesc.extCompatID = NVTX_EXT_COMPATID_MEM;myNewRegionsDesc.structSize = sizeof(mySubRangeDesc);myNewRegionsDesc.regionType = NVTX_MEM_TYPE_VIRTUAL_ADDRESS;myNewRegionsDesc.regionDescCount = 1;myNewRegionsDesc.regionDescElementSize = sizeof(mySubRangeDesc);myNewRegionsDesc.regionDescElements = &mySubRangeDesc;nvtxMemRegionsResize(mynvtxDomain, &myNewRegionsDesc);

正如您所看到的,它与子分配的初始声明类似,但最后使用了函数 nvtxMemRegionsResize。

仔细检查 Compute Sanitizer 是否对现在为调整大小的存储桶分配值的合法尝试感到满意。 在示例代码中两次调用populateMemory之间添加调整大小注册代码,然后编译运行。

$ nvcc -I./NVTX/c/include -o mempool_resize.exe mempool_resize_example.cu -arch=sm_70
$ compute-sanitizer --tool memcheck --destroy-on-device-error=kernel ./mempool_resize.exe
========= COMPUTE-SANITIZER
After populateMemory 1: bucket 0, 1 ..  62: 0 1 .. 62
After populateMemory 2: bucket 0, 1 ..  63: 0 1 .. 63
========= ERROR SUMMARY: 0 errors

希望您能看到类似此示例的内容。 当您调整了子分配注册的大小后,现在不再有关于尝试访问添加到末尾的新元素的投诉。

需要相当多的代码行来注册和调整池和子分配的大小,以便将它们与 NVTX 和 Compute Sanitizer 一起使用。 对于更复杂的代码来说,这可能会变得很麻烦,因此将这些步骤封装到一个单独的类中可能会很有用。 很方便,/NVIDIA/compute-sanitizer-samples GitHub 存储库中提供了这种方法的一个很好的示例,因此这是您自己的代码的一个很好的起点。

还有两个 NVTX API 需要提及:

  • 命名 API:使区域或子分配具有与其关联的 ASCII 名称。 然后,它可以用于在错误报告中通过名称来引用分配,目前支持泄漏和未使用的内存报告。
  • 权限 API:允许将分配访问权限限制为只读或原子等。

Compute Sanitizer API 用于创建您自己的工具

Compute Sanitizer 附带 API,使您能够创建自己的清理和跟踪工具来定位 CUDA 应用程序。 它是一组函数,可用于与 Compute Sanitizer 交互以进行控制和配置、启用或禁用其功能以及访问其结果。

该 API 还为您提供了一种将 Compute Sanitizer 集成到您的开发工作流程中的便捷方法,因为它可以轻松集成到现有的 CUDA 应用程序中。 借助 Compute Sanitizer API,您可以直接利用强大的调试功能并提高 CUDA 应用程序的可靠性和性能。

它由以下子 API 组成:

  • 回调:使您能够在用户代码中注册回调,其中回调可以与相关 CUDA 函数或事件组关联,例如 memcpy 操作或驱动程序函数。 然后订阅者可以使用这些回调,例如用于事件跟踪。
  • 修补:允许加载修补函数并将其插入到 GPU 上执行的设备代码中。 然后,它们可以用作检测点,这意味着每当执行修补事件时都会执行修补函数,例如,设置回调,例如进行内存访问的设备代码。
  • 内存:提供标准 CUDA 内存 API 的替换函数。 可以从 Compute Sanitizer 回调中安全地调用替换,例如使用替换 sanitizerAlloc 而不是 cudaMalloc。

结合起来,这些 API 使您能够将 Compute Sanitizer 功能合并到您自己的工具中。

有关更多信息和一些示例代码,请参阅 NVIDIA Compute Sanitizer API 指南。

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

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

相关文章

Exception in thread “main“ com.fasterxml.jackson.databind.JsonMappingException:

问题&#xff1a;jaskson反序列化超出最大长度 Caused by: com.fasterxml.jackson.core.exc.StreamConstraintsException: String length (5043456) exceeds the maximum length (5000000) 场景&#xff1a;前端传递过大base64 原因&#xff1a; jaskon默认已经限制了最大长…

20个超实用Python魔法方法

大家好&#xff01;今天我们要一起探索Python世界的神秘角落——那些被称为“魔法方法”的特殊成员方法。它们就像是编程中的魔法咒语&#xff0c;赋予你的类各种神奇特性&#xff0c;让你的代码更加简洁、强大且有趣味&#xff01; __init__&#xff1a;这是每个对象出生时都要…

安卓利用CameraX 拍照获这张照片的exif信息

一、首先导入相关权限 <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE" /><uses-featureandroid:name"android.hardware.camera"android:required"true" /><uses-permission android:name"andro…

蓝桥杯练习题总结(三)线性dp题(摆花、数字三角形加强版)

目录 一、摆花 思路一&#xff1a; 确定状态&#xff1a; 初始化&#xff1a; 思路二&#xff1a; 确定状态&#xff1a; 初始化&#xff1a; 循环遍历&#xff1a; 状态转移方程&#xff1a; 二、数字三角形加强版 一、摆花 题目描述 小明的花店新开张&#xff0c;为了吸…

Uni-app/Vue/Js本地模糊查询,匹配所有字段includes和some方法结合使用e

天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/ 1.第一步 需要一个数组数据 {"week": "全部","hOutName": null,"weekendPrice": null,"channel": "门市价","hOutId": 98,"cTime": "…

【Redis】Redis 内存管理,Redis事务,bigkey和hotkey

目录 Redis 内存管理 缓存数据设置过期时间&#xff1f; Redis 是如何判断数据是否过期的呢&#xff1f; 过期删除策略 内存淘汰机制 主从模式下对过期键的处理&#xff1f; LRU和LFU的区别 Redis事务 定义和原理 Redis 事务的注意点&#xff1f; 为什么不支持回滚&a…

SQLite数据库文件损坏的可能几种情况(一)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;十三&#xff09; 下一篇&#xff1a;SQLite使用的临时文件&#xff08;二&#xff09; 概述 SQLite数据库具有很强的抗损坏能力。如果应用程序崩溃&#xff0c…

指针数组的有趣程序【C语言】

文章目录 指针数组的有趣程序指针数组是什么&#xff1f;指针数组的魅力指针数组的应用示例&#xff1a;命令行计算器有趣的颜色打印 结语 指针数组的有趣程序 在C语言的世界里&#xff0c;指针是一种强大的工具&#xff0c;它不仅能够指向变量&#xff0c;还能指向数组&#…

HBase Shell基本操作

一、进入Hbase Shell客户端 先在Linux Shell命令行终端执行start-dfs.sh脚本启动HDFS&#xff0c;再执行start-hbase.sh脚本启动HBase。如果Linux系统已配置HBase环境变量&#xff0c;可直接在任意目录下执行hbase shell脚本命令&#xff0c;就可进入HBase Shell的命令行终端环…

Unity Mobile Notifications推送问题

1.在部分机型点击通知弹窗进不去游戏 把这里改成自己的Activity 2.推送的时候没有横幅跟icon红点 主要是第一句话 注册的时候选项可以选择 defaultNotificationChannel new AndroidNotificationChannel(“default_channel”, “Default Channel”, “For Generic notifica…

LinkedIn 互联网架构扩展简史

LinkedIn成立于 2003 年&#xff0c;其目标是连接到您的网络以获得更好的工作机会。第一周只有 2,700 名会员。时间快进了很多年&#xff0c;LinkedIn 的产品组合、会员基础和服务器负载都取得了巨大的增长。 如今&#xff0c;LinkedIn 在全球运营&#xff0c;拥有超过 3.5 亿会…

今日AI热点:科技前沿新动态

引言&#xff1a; 人工智能领域日新月异&#xff0c;每天都有令人振奋的新进展。从苹果到谷歌&#xff0c;从OpenAI到Meta&#xff0c;各大科技巨头纷纷推出创新产品和技术&#xff0c;不断推动着人工智能的发展。让我们一起来看看今日AI热点&#xff0c;探索这个充满活力和激情…

C++从入门到精通——命名空间

命名空间 前言一、命名空间引例什么是命名空间 二、命名空间定义正常的命名空间定义嵌套的命名空间多个相同名称的命名空间 三、命名空间使用加命名空间名称及作用域限定符使用using将命名空间中某个成员引入使用using namespace 命名空间名称引用引用命名空间和引用头文件有什…

Mac安装minio

Mac安装minio 本文介绍使用 mac 安装 MinIO。 所有软件安装优先参考官网&#xff1a;MinIO Object Storage for MacOS — MinIO Object Storage for MacOS #使用 brew 安装 minio brew install minio/stable/minio#找到 minio tong ~ $ brew list minio /opt/homebrew/Cella…

【ssh连接】奇奇怪怪报错记录

gitlab配置ssh连接&#xff0c;先跟着教程生成密钥&#xff0c;上传公钥&#xff0c;将服务器信息存入config文件&#xff0c;但是ssh连接超时&#xff0c;很急&#xff0c;想用服务器&#xff0c;各种搜索尝试&#xff0c;搞了两三天别的什么都没干&#xff0c;还是没解决&…

深度学习pytorch——激活函数损失函数(持续更新)

论生物神经元与神经网络中的神经元联系——为什么使用激活函数&#xff1f; 我们将生物体中的神经元与神经网络中的神经元共同分析。从下图可以看出神经网络中的神经元与生物体中的神经元有很多相似之处&#xff0c;由于只有刺激达到一定的程度人体才可以感受到刺激&#xff0c…

硬件6、AD设计PcbLib之引脚间如何设置距离及设置PCB元器件丝印

设置引脚间的距离 一个器件有两个引脚&#xff0c;在制作这个器件的pcblib时&#xff0c;需要设置两个引脚之间的距离 1、先选中其中一个引脚 2、然后拖动至两个引脚重叠 3、按下M键&#xff0c;通过X&#xff0c;Y移动选中对象 4、输入两个引脚中心点之间的距离 5、然后两…

【随笔】Git -- 基本概念和使用方式(五)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

排序C++

题目 法1 sort升序排序&#xff0c;再逆序输出 #include<iostream> #include<algorithm> using namespace std;const int N 5e53;//注意const&#xff0c;全局 int a[N]; int main() {//错误int N5e53;//错误const int a[N];int n;cin >> n;for (int i 1;…

水电站生态流量监测解决方案:亲历水电站生态监控改造

​记得那是在2022年夏天,我所在的环保咨询公司接到了一项非常具有挑战性的监测项目。某省的环保部门要求对辖区内所有水电站的生态流量情况进行评估,并给出整改建议。作为项目负责人,我深知这项工作的重要意义。&#xff08;选自&#xff1a;智慧水务数字孪生安全监测解决方案提…