拨开算力的迷雾:聊聊不同 GPU 计算能力的上限

文 | 卜居@知乎

编 | 兔子酱


通过深入了解自己手头 GPU 的计算能力上限,能够在买新卡时做出更理性判断。本文深入GPU架构,重点介绍了其中的ampere架构。另外,作者还对比了不同GPU之间的峰值计算能力,增加读者对硬件资源的了解。

前言

2020年5月14日,在全球疫情肆虐,无数仁人志士前赴后继攻关新冠疫苗之际,NVIDIA 创始人兼首席执行官黄仁勋在自家厨房直播带货,哦不对应该是 NVIDIA GTC 2020 主题演讲中热情洋溢地介绍了新鲜出炉的基于最新 Ampere 架构的 NVIDIA A100 GPU,号称史上最豪华的烧烤。

NVIDIA A100 Tensor Core GPU 基于最新的 Ampere 架构,其核心为基于台积电 7nm 工艺制造的 GA100,内有 542 亿晶体管,裸片尺寸为 826mm²,而前代 GV100 裸片尺寸 815mm²,内有 211 亿晶体管,短短 3 年时间,得益于新工艺,芯片集成度翻了不止一倍!

从 NVIDIA 发布会内容以及白皮书中能看到一些夺目的数字,今天我们来解密这些数字是怎么得出来的。为此我们需要深入 GPU 架构一探究竟。

GPU架构演变

图形处理器(GPU, Graphics Processing Unit),用来加速计算机图形实时绘制,俗称显卡,经常用于打游戏。自 NVIDIA 于 1999 年发明第一款 GPU GeForce 256,尔来二十有一年矣。

▲GeForce 256, 1999

从图片看到 GeForce 256 衣着相当简朴,完全看不到 RTX 3090 的贵族气质,显示输出口仅支持 VGA,显存 32 MB,另外和主机的接口是早已不见踪影的 AGP,支持的图形 API 为 DirectX 7.0、OpenGL 1.2,目前主流游戏都跑不动,放到现在只能当摆设。

那时显卡还只是纯粹的显卡,硬件架构还是固定的渲染流水线,如下图所示。

渲染流水线中可被程序员控制的部分有两处:Geometry Processing 和 Pixel Processing,前者处理几何坐标变换,涉及矩阵乘计算;后者处理图像像素,涉及插值计算。有一些对科学有执着追求的人们试图用渲染流水线做一些除了打游戏之外更为正经的工作。于是,他们把计算输入数据伪造成顶点坐标或纹理素材,把计算机程序模拟为渲染过程,发挥异于常人的聪明才智,使用 OpenGL/DirectX/Cg 实现各类数值算法,将显卡这个为游戏做出突出贡献的可造之材打造为通用并行计算的利器,此时的 GPU 被赋能了更多工作内容,称作 GPGPU(General Purpose GPU)。

从事 GPGPU 编程的程序员十分苦逼,既要懂图形 API、GPU 架构,还要把各个领域算法摸清楚翻译为顶点坐标、纹理、渲染器这些底层实现,十分难以维护,今天一气呵成的代码,明天就形同陌路。程序如有 bug,调试工具奇缺,只能靠运气和瞪眼法。

为了彻底解放生产力,提高编程效率,NVIDIA 在 2006 年引入统一图形和计算架构以及 CUDA 工具,从此 GPU 就可以直接用高级语言编程,由程序员控制众多 CUDA 核心完成海量数值计算,GPGPU 也已成为历史。

GeForce 8800 是第一款支持 CUDA 计算的 GPU,核心为 G80,首次将渲染流水线中分离的顶点处理器与像素处理器替换为统一的计算单元,可用于执行顶点/几何/像素/通用计算等程序。G80 首次引入 SIMT(Single-Instruction Multiple-Thread) 执行模型,多个线程在不同计算单元上并发执行同一条指令,引入 barrier 和 shared memory实现线程间同步与通信。G80 架构图如下:

▲G80/G92 架构图,G92 相比 G80 仅为工艺升级(90nm - 65nm),架构没有变化

在 G80 中有 8 个 TPC(纹理处理簇,Texture Processing Clusters),每个 TPC 有 2 个 SM(流多处理器,Stream Multiprocessors),共计 16 个 SM。每个 SM 内部架构如下图:

▲G80/G92 架构图

每个 SM 内部有 8 个 SP(流处理器,Streaming Processor,后改称 CUDA Core),这是真正干活的单元,可以完成基本数学计算。8 个 SP 需要听口号统一行动,互相之间通过 shared memory 传递信息。

G80 架构比较简单,奠定了通用计算 GPU 的基础。接下来的 14 年,NVIDIA GPU 以大约每两年一代的速度逐步升级硬件架构,配套软件和库也不断丰富起来,CUDA Toolkit 最新已到 11.0,生态系统已颇为健壮,涵盖石油探测、气象预报、医疗成像、智能安防等各行各业, GPU 现已成为世界顶级超算中心的标配计算器件。

下表展示了从 2006 年至今支持 CUDA 计算的 GPU。有没有看到你手中的那一款?

架构起名是有讲究的,都是科学史上著名的物理学家、数学家(同时也是理工科同学的梦魇,多少次因为写错了计量单位被扣分):特斯拉、费米、开普勒、麦克斯韦、帕斯卡、伏打、图灵、安培。(那么接下来是?)

限于篇幅,我们不再深入探讨每种架构细节,直接跳跃到最新 Ampere 架构,看看世界顶级计算能力是如何炼成的。对历史感兴趣的读者可以继续研读扩展材料[6]。

Ampere 架构详解

从 Ampere 白皮书[1]看到 GA100 的总体架构图如下:

▲GA100 总体架构图

总体布局比较中正,八个 GPC 与 L2 Cache 坐落于核心地段,左右为外部存储接口,12 道显存控制器负责与 6 块 HBM2 存储器数据交互,顶部为 PCIe 4.0 控制器负责与主机通信,底部又有 12 条高速 NVLink 通道与其他 GPU 连为一体。

GA100 以及基于 GA100 GPU 实现的 A100 Tensor Core GPU 内部资源如下表所示:

名词解释:

  • GPC —— 图形处理簇,Graphics Processing Clusters

  • TPC —— 纹理处理簇,Texture Processing Clusters

  • SM —— 流多处理器,Stream Multiprocessors

  • HBM2 —— 高带宽存储器二代,High Bandwidth Memory Gen 2

实际上到手的 A100 GPU 是阉割版,相比完整版 GA100 少了一组 GPC 和一组 HBM2。至于为什么,要考虑这个芯片巨大的面积和工艺水平,以及整板功耗。由于少了这一组 GPC,导致后面一些奇奇怪怪的数字出现,等到了合适的时机再解释。

A100 SM 的架构细节如下图所示:

▲GA100 SM 架构图

GA100 的 SM 架构相比 G80 复杂了很多,占地面积也更大。每个 SM 包括 4 个区块,每个区块有独立的 L0 指令缓存、Warp 调度器、分发单元,以及 16384 个 32 位寄存器,这使得每个 SM 可以并行执行 4 组不同指令序列。4 个区块共享 L1 指令缓存和数据缓存、shared memory、纹理单元。
图中能看出 INT32 计算单元数量与 FP32 一致,而 FP64 计算单元数量是 FP32 的一半,这在后面峰值计算能力中会有体现。

每个 SM 除了 INT32、FP32、FP64 计算单元之外,还有额外 4 个身宽体胖的 Tensor Core,这是加速 Deep Learning 计算的重磅武器,已发展到第三代,每个时钟周期可做 1024 次 FP16 乘加运算,与 Volta 和 Turing 相比,每个 SM 的吞吐翻倍,支持的数据类型也更为丰富,包括 FP64、TF32、FP16、BF16、INT8、INT4、INT1(另外还有 BF16),不同类型指令吞吐见下表[2]所示:

▲Volta/Turing/Ampere 单个 SM 不同数值类型指令吞吐

利用这张表我们可以计算出 GPU 峰值计算能力,公式如下:

其中  为 GPU 核心的运行频率,  为 GPU SM 数量,  为特定数据类型的指令吞吐,后面乘 2 是因为乘加视作两次浮点运算。

例如 A100 FP32 CUDA Core 指令吞吐  ,核心运行频率为  ,总共 SM 数量  ,那么

对照 NVIDIA Ampere 白皮书[1] 中有关 FP32 峰值计算能力的数字 19.5 TFLOPS,基本一致。

将剩下的指令吞吐数字代入公式中,可以得到 A100 其他数据类型的峰值计算能力,包括令人震惊的 TF32 和令人迷惑的 FP16 性能。

理论峰值计算能力只是一个上限,我们还关心 GPU 计算能力实测值,可以利用如下公式:

其中  为某个任务所需的全部乘、加运算数量,例如矩阵乘

其中 A、B、C、D 均为矩阵,各自尺寸以下标作为标识。完成上述公式计算所需总乘加次数为:

则 GEMM 实测计算能力为:

从前面两张图看到 Volta/Turing 架构 CUDA Core FP16 计算吞吐为 FP32 的 2 倍,而到了 Ampere 架构发生了阶跃,直接变 4 倍(256 vs 64,78 TFLOPS vs 19.5 TFLOPS),我们拿到物理卡后第一时间进行了不同精度 GEMM 评测,发现 FP16 性能相比 FP32 并非 4 倍,而是和 Turing 一样 2 倍左右,感觉更像是文档出现了谬误。

▲CUTLASS 实测性能

等待后续 NV 的软件或文档更新来释疑。

GPU 峰值计算能力对比

我们可以通过翻阅 GPU 数据手册、白皮书获得不同型号 GPU 峰值计算能力,但这仅停留在纸面,对于管控系统而言需要借助工具来获取这些数值记录在设备数据库,之后调度器可根据计算需求以及库存情况进行计算能力分配。本节将提供这样一个工具来自动计算 GPU 峰值计算能力,基于 CUDA Runtime API 编写,对具体 CUDA 版本没有特殊要求。A100 上运行输出如下:

由此得到的 A100 理论峰值计算能力与上节 CUTLASS 实测结果能对号入座。

利用该工具,你可以更深入了解自己手头 GPU 的计算能力上限,买新卡时会做出更理性判断。下面展示 2016-2020 主流 GPU 型号及其理论峰值计算力:

P4, 2016, Pascal

▲Tesla P4 峰值计算能力, P4 实际可以超频到 1.531 GHz,官方并未对超频性能做出承诺,用户需根据业务特点进行合理设置

P40, 2016, Pascal

▲Tesla P40 峰值计算能力

P100, 2016, Pascal

▲Tesla P100(PCIe 版) 峰值计算能力, NVLink 版比这个结果要高一点

GTX 1080, 2016, Pascal

▲GTX 1080 峰值计算能力

Tesla V100, 2017, Volta

▲Tesla V100 峰值计算能力,忽略最后一行(系早期工具 bug)

T4, 2018, Turing

▲Tesla T4 峰值计算能力, 实测 T4 正常工作频率约为峰值的 70%

RTX 2080 Ti, 2018, Turing

▲RTX 2080 Ti 峰值计算能力

Jetson Nano, 2019, Maxwell

▲Jetson Nano 峰值计算能力

Jetson TX2, 2016, Pascal

▲Jetson TX2 峰值计算能力

Jetson Xavier, 2018, Volta

▲Jetson Xavier 峰值计算能力

如果上面结果中没有发现你的 GPU 装备,欢迎运行下面代码并将结果发在评论区。

本文代码

calc\_peak\_gflops.cpp#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <cuda_runtime.h>#define CHECK_CUDA(x, str) \if((x) != cudaSuccess) \{ \fprintf(stderr, str); \exit(EXIT_FAILURE); \}int cc2cores(int major, int minor)
{typedef struct{int SM;int Cores;} sSMtoCores;sSMtoCores nGpuArchCoresPerSM[] ={{0x30, 192},{0x32, 192},{0x35, 192},{0x37, 192},{0x50, 128},{0x52, 128},{0x53, 128},{0x60,  64},{0x61, 128},{0x62, 128},{0x70,  64},{0x72,  64},{0x75,  64},{0x80,  64},{-1, -1}};int index = 0;while (nGpuArchCoresPerSM[index].SM != -1){if (nGpuArchCoresPerSM[index].SM == ((major << 4) + minor)){return nGpuArchCoresPerSM[index].Cores;}index++;}printf("MapSMtoCores for SM %d.%d is undefined."  "  Default to use %d Cores/SM\n",major, minor, nGpuArchCoresPerSM[index - 1].Cores);return nGpuArchCoresPerSM[index - 1].Cores;
}bool has_fp16(int major, int minor)
{int cc = major * 10 + minor;return ((cc == 60) || (cc == 62) || (cc == 70) || (cc == 75) || (cc == 80));
}
bool has_int8(int major, int minor)
{int cc = major * 10 + minor;return ((cc == 61) || (cc == 70) || (cc == 75) || (cc == 80));
}
bool has_tensor_core_v1(int major, int minor)
{int cc = major * 10 + minor;return ((cc == 70) || (cc == 72) );
}
bool has_tensor_core_v2(int major, int minor)
{int cc = major * 10 + minor;return (cc == 75);
}
bool has_tensor_core_v3(int major, int minor)
{int cc = major * 10 + minor;return (cc == 80);
}int main(int argc, char **argv)
{cudaDeviceProp prop;int dc;CHECK_CUDA(cudaGetDeviceCount(&dc), "cudaGetDeviceCount error!");printf("GPU count = %d\n", dc);for(int i = 0; i < dc; i++){printf("=================GPU #%d=================\n", i);CHECK_CUDA(cudaGetDeviceProperties(&prop, i), "cudaGetDeviceProperties error");printf("GPU Name = %s\n", prop.name);printf("Compute Capability = %d.%d\n", prop.major, prop.minor);printf("GPU SMs = %d\n", prop.multiProcessorCount);printf("GPU CUDA cores = %d\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount);printf("GPU SM clock rate = %.3f GHz\n", prop.clockRate/1e6);printf("GPU Mem clock rate = %.3f GHz\n", prop.memoryClockRate/1e6);printf("FP32 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2);if(has_fp16(prop.major, prop.minor)){printf("FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 2);}if(has_int8(prop.major, prop.minor)){printf("INT8 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 4);}if(has_tensor_core_v1(prop.major, prop.minor)){printf("Tensor Core FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 8);}if(has_tensor_core_v2(prop.major, prop.minor)){printf("Tensor Core FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 8);printf("Tensor Core INT8 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 16);}if(has_tensor_core_v3(prop.major, prop.minor)){printf("Tensor Core TF32 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 8);printf("Tensor Core FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 16);printf("Tensor Core INT8 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 32);}}return 0;
}`

▲(向右滑动查看完整代码)

编译:

nvcc -I/usr/local/cuda/include -L/usr/local/cuda/lib64 -lcudart -o calc_peak_gflops calc_peak_gflops.cpp

如果提示 nvcc 命令未找到,请先安装 CUDA 并设置 PATH 环境变量包含 nvcc 所在目录(Linux 默认为 /usr/local/cuda/bin)。

export PATH=/usr/local/cuda/bin:$PATH

运行:

./calc_peak_gflops

后记

通过获取 GPU 峰值计算能力,可以加深对手头的硬件资源了解程度,不被过度宣传的文章洗脑,多快好省地完成工作。


文末福利
后台回复关键词【入群
加入卖萌屋NLP/IR/Rec与求职讨论群
有顶会审稿人、大厂研究员、知乎大V和妹纸
等你来撩哦~

参考文献

[1] https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/nvidia-ampere-architecture-whitepaper.pdfwww.nvidia.com
[2] GPU Performance Background User Guidedocs.nvidia.com
[3] https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/tesla-product-literature/NVIDIA-Kepler-GK110-GK210-Architecture-Whitepaper.pdfwww.nvidia.com
[4] https://images.nvidia.com/content/pdf/tesla
/whitepaper/pascal-architecture-whitepaper.pdfimages.nvidia.com
[5] https://images.nvidia.com/content/volta-architecture/pdf/volta-architecture-whitepaper.pdfimages.nvidia.com
[6] NVIDIA GPU架构的变迁史

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

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

相关文章

LeetCode 20. 有效的括号(栈)

文章目录1. 题目信息2. 解题1. 题目信息 给定一个只包括 ‘(’&#xff0c;’)’&#xff0c;’{’&#xff0c;’}’&#xff0c;’[’&#xff0c;’]’ 的字符串&#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括…

消息中间件系列(八):Kafka、RocketMQ、RabbitMQ等的优劣势比较

在高并发业务场景下&#xff0c;典型的阿里双11秒杀等业务&#xff0c;消息队列中间件在流量削峰、解耦上有不可替代的作用。 之前介绍了MQ消息队列的12点核心原理总结&#xff0c;以及如何从0到1设计一个MQ消息队列&#xff0c;以及RPC远程调用和消息队列MQ的区别 今天我们一…

论文浅尝 | 一种用于多关系问答的可解释推理网络

论文笔记整理&#xff1a;谭亦鸣&#xff0c;东南大学博士生&#xff0c;研究方向为跨语言知识图谱问答。来源&#xff1a;COLING 2018链接&#xff1a;https://www.aclweb.org/anthology/C18-1171问题背景与动机多关系问答&#xff08;multi-relationquestion answering&#…

蚁群优化算法 ACO

群体智能&#xff08;swarm intelligence&#xff09; 定义&#xff1a; 由单个复杂个体完成的任务可由大量简单个体组成的群体合作完成&#xff0c;而后者往往更具有健壮性、灵活性等优势。在没有集中控制&#xff0c;不提供全局模型的前提下&#xff0c;为寻找复杂问题解决…

量化投资交易 vn.py

前言&#xff1a;当初接触到vnpy&#xff0c;一开始当然是按照该项目在GitHub上的指南&#xff0c;开始安装&#xff0c;配置&#xff0c;阅读Wiki&#xff0c;但是作为一个python新手&#xff0c;并不能马上利用vnpy来写策略回测甚至实盘。所以我决定还是从源码看起&#xff0…

掌握神经网络,我应该学习哪些至关重要的知识点?

人工智能作为计算机科学领域的一个分支&#xff0c;在互联网和大数据的时代浪潮中显现出其巨大的潜力和蓬勃的活力&#xff0c;类似电子医生、无人驾驶等新名词纷纷涌现。人工智能凭借着它无与伦比的发展优势&#xff0c;推动了各大产业和技术的革命与创新&#xff0c;使得生产…

LeetCode 32. 最长有效括号(栈DP)

文章目录1. 题目信息2. 栈 解题3. 动态规划 解题1. 题目信息 给定一个只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最长的包含有效括号的子串的长度。 示例 1:输入: "(()" 输出: 2 解释: 最长有效括号子串为 "()" 示例 2:输入: ")()())" 输…

消息中间件系列(四):消息队列MQ的特点、选型、及应用场景详解

前面集中谈了分布式缓存Redis系列&#xff1a; 高并发架构系列&#xff1a;分布式锁的由来、特点、及Redis分布式锁的实现详解 高并发架构系列&#xff1a;Redis并发竞争key的解决方案详解 高并发架构系列&#xff1a;Redis缓存和MySQL数据一致性方案详解 Redis的高可用详解…

基金定投

https://www.zhihu.com/question/19909886 相信我&#xff0c;这篇攻略能让你彻底搞懂基金&#xff0c;每年大概率能赚10%左右的收益&#xff01;基金定投核心要搞懂两个问题&#xff1a;买什么基金&#xff0c;什么时候买。今天我给大家一篇文章讲透这两个问题&#xff01;我说…

卖萌屋学术站发布!通往高效刷论文之路

文 | 夕小瑶编 | 兔子酱学术站诞生好久没有冒泡啦&#xff0c;大家还记得雁栖湖畔的夕小瑶吗&#xff01;&#xff08;划掉(*/ω&#xff3c;*)趁着国庆假期&#xff0c;跟卖萌屋小伙伴们终于把拖延已久的《Arxiv神器》翻新了&#xff0c;零零星星做了几个月&#xff0c;最近终…

消息中间件系列(五):MQ消息队列的12点核心原理总结

消息队列已经逐渐成为分布式应用场景、内部通信、以及秒杀等高并发业务场景的核心手段&#xff0c;它具有低耦合、可靠投递、广播、流量控制、最终一致性 等一系列功能。 无论是 RabbitMQ、RocketMQ、ActiveMQ、Kafka还是其它等&#xff0c;都有的一些基本原理、术语、机制等&…

征稿 | 2019年全国知识图谱与语义计算大会(CCKS2019)投稿时间延长

全国知识图谱与语义计算大会&#xff08;CCKS: China Conference on Knowledge Graph and Semantic Computing&#xff09;由中国中文信息学会语言与知识计算专业委员会组织和承办。全国知识图谱与语义计算大会是两个全国性会议的合并&#xff1a;中文知识图谱研讨会the Chines…

LeetCode 641. 设计循环双端队列

文章目录1. 题目信息2. 解题1. 题目信息 设计实现双端队列。 你的实现需要支持以下操作&#xff1a; MyCircularDeque(k)&#xff1a;构造函数,双端队列的大小为k。 insertFront()&#xff1a;将一个元素添加到双端队列头部。 如果操作成功返回 true。 insertLast()&#xff…

代码逆流成河,深入C++如何又快又有效?

虽然编程语言有很多&#xff0c;但在需要顶级性能的项目上&#xff0c;基本都会使用C。尤其是机器人、自动驾驶、AI等嵌入和实时系统&#xff0c;都是C的主要应用方向&#xff0c;在这种层面&#xff0c;几乎没有竞争者。比如熊厂的搜索引擎、推荐引擎等核心产品&#xff0c;鹅…

论文浅尝 | 混合注意力原型网络的含噪音少样本的关系分类

论文笔记整理&#xff1a;余海阳&#xff0c;浙江大学硕士&#xff0c;研究方向为知识图谱、自然语言信息抽取。链接&#xff1a;https://www.aaai.org/Papers/AAAI/2019/AAAI-GaoTianyu.915.pdf动机现有的关系分类方法主要依赖于远程监控&#xff08;DS&#xff09;&#xff0…

消息中间件系列(七):如何从0到1设计一个消息队列中间件

消息队列作为系统解耦&#xff0c;流量控制的利器&#xff0c;成为分布式系统核心组件之一。 如果你对消息队列背后的实现原理关注不多&#xff0c;其实了解消息队列背后的实现非常重要。 不仅知其然还要知其所以然&#xff0c;这才是一个优秀的工程师需要具备的特征。 今天…

LeetCode 239. 滑动窗口最大值(双端队列+单调栈)

文章目录1. 题目信息2. 解题2.1 暴力法2.2 双端队列法1. 题目信息 给定一个数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回滑动窗口中的最大值。 示例:输入: n…

Airbnb搜索:重排序阶段如何优化搜索结果多样性?

文 | 谷育龙Eric编 | QvQ我是谷育龙Eric&#xff0c;研究方向有深度学习、搜索推荐&#xff0c;喜欢为大家分享深度学习在搜索推荐广告排序应用的文章。本文将基于Airbnb KDD 2020年的论文&#xff0c;介绍Airbnb搜索排序中在重排序阶段如何解决多样性的问题&#xff0c;对工业…

肖仰华 | 做个“有知识”的机器人

本文转载自公众号:知识工场。肖仰华博士&#xff0c;复旦大学计算机科学与技术学院教授&#xff0c;博士生导师&#xff0c;知识工场实验室负责人。本文是肖仰华教授应《中国计算机学会通信》邀请所撰写的特邀文章&#xff0c;全文见 CCCF 2019 年第 5 期。摘要&#xff1a;时下…

消息中间件系列(九):详解RocketMQ的架构设计、关键特性、与应用场景

内容大纲&#xff1a; RocketMQ的简介与演进 RocketMQ的架构设计 RocketMQ的关键特性 RocketMQ的应用场景 RocketMQ的简介 RocketMQ一个纯java、分布式、队列模型的开源消息中间件&#xff0c;前身是MetaQ&#xff0c;是阿里研发的一个队列模型的消息中间件&#xff0c;后开…