惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recorded Future
Recorded Future
爱范儿
爱范儿
Y
Y Combinator Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
腾讯CDC
罗磊的独立博客
阮一峰的网络日志
阮一峰的网络日志
Know Your Adversary
Know Your Adversary
P
Proofpoint News Feed
T
Tailwind CSS Blog
Attack and Defense Labs
Attack and Defense Labs
G
GRAHAM CLULEY
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
T
The Blog of Author Tim Ferriss
T
The Exploit Database - CXSecurity.com
博客园 - 叶小钗
Latest news
Latest news
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Hacker News
The Hacker News
量子位
S
Security @ Cisco Blogs
Microsoft Security Blog
Microsoft Security Blog
Stack Overflow Blog
Stack Overflow Blog
美团技术团队
GbyAI
GbyAI
Google DeepMind News
Google DeepMind News
IT之家
IT之家
U
Unit 42
Project Zero
Project Zero
B
Blog
博客园 - 【当耐特】
D
DataBreaches.Net
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
宝玉的分享
宝玉的分享
The Register - Security
The Register - Security
F
Full Disclosure
Vercel News
Vercel News
NISL@THU
NISL@THU
AWS News Blog
AWS News Blog
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
T
Threatpost
Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
T
Tor Project blog
M
MIT News - Artificial intelligence
V
V2EX

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
Demystify the randomness in CUDA kernels
2021-06-11 · via hsfzxjy 的博客

You might have heard that many CUDA operators contains some kind of non-determinism, and to eliminate the randomness, one must pay for the degradation of performance. The warning occurs many times in blog posts or framework documentation, but few of them give a detailed explanation for the source of randomness. To this end, the post is going to explore the problem.

When talked about GPU computation, one might come up with a notion of some super-fast hardwares. The surprising speed comes from intensive parallelism of the architecture, which allows users to run thousands of routines on parallel (compared to dozens on ordinary CPUs). The routines are called threads, and similar to the concept with the same name in operating systems, they suffer from non-deterministic execution order and data race condition.

Non-deterministic execution order means, if we arrange all instructions of different threads into a sequence, ordered by their occurrence time, the sequence could vary greatly across invocations. If two threads run on parallel, each with a single instruction, we cannot tell which one is executed first. This is the fundamental origin of randomness, and is inevitable.

Data race condition is one of the consequences of non-deterministic execution order. When the threads is manipulating some shared variables, and the manipulation is not atomic, i.e. consists of interruptible instruction sequence, the program might yield undesired results. Programs should be carefully designed to avoid race condition, with the help of locks or atomic operations. To alleviate, CUDA provides atomic arithmetic routines like atomicAdd() or atomicMax() for safe access to shared memory.

By far we have seen that there does exist some kind of randomness inside GPUs, and if not handled properly, our program will give incorrect results when working with shared variables. But one may argue that, we have atomic operations like atomicAdd(). If a program correctly sums up the same collection of numbers, although the order might be messed, it should always returns the same result. Sadly this is wrong, since some arithmetic operations DOES rely on the order of operands! Let’s take the following CUDA program as an example:

#include <stdio.h>

__global__ void sum(float *result) {
float i = (float)blockIdx.x;
atomicAdd(result, i / 10);
}

static const int N = 1000;

int main() {
float *cu_result;
cudaMalloc(&cu_result, sizeof(float));
float result;
int i;
for (i = 0; i < 10; i++) {
cudaMemset(cu_result, 0, sizeof(float));
sum<<<N, 1>>>(cu_result);
cudaMemcpy(&result, cu_result, sizeof(float), cudaMemcpyDeviceToHost);
printf("%f\n", result);
}
cudaFree(cu_result);
return 0;
}

In this simple program, we implement a parallelized summing function with atomicAdd(). We sum up $\{0.0,0.1,0.2,\ldots,99.8,99.9\}$ for 10 times and print out the results. The results would look like:

49949.996094
49950.003906
49950.000000
49950.000000
49949.996094
49950.000000
49949.988281
49949.992188
49949.996094
49950.000000

which is completely non-deterministic with some random small errors (the accurate answer should be 49950).

The key is that, floating-point arithemtics are non-associative. The equation $(a + b) + c = a + (b + c)$ does NOT hold for floating-point numbers $a, b, c$.

To understand this, let’s consider an extreme example – a very simple decimal floating-point representation with no fraction digits, i.e., numbers are represented as $\pm b \times 10^{e}$, where $b \in \{0 \ldots 9\}$. We will calculate $(0.8+1.5)-1.6$ and $0.8+(1.5-1.6)$ under this kind of “specification”. Below are their representation:

$$ \begin{aligned} 0.8 &\rightarrow 8 \times 10^{-1} \\ 1.5 &\rightarrow 2 \times 10^{0} \\ 1.6 &\rightarrow 2 \times 10^{0} \\ \end{aligned} $$

For $(0.8 + 1.5)-1.6$, we first shift the base of $8 \times 10^{-1}$ by 1 digit and get $0.8 \times 10^{0}$. Due to an underflow, it is truncated and becomes $0 \times 10^{0}$. Then sum up $0 \times 10^{0}$ (0.8) and $2 \times 10^{0}$ (1.5) and get $2 \times 10^{0}$. Further $2 \times 10^{0} - 2 \times 10^{0}$ (1.6), results in $0 \times 10^{0}$.

For $0.8 + (1.5-1.6)$, first calculate $2 \times 10^0$ (1.5) $-2 \times 10^0$ (1.6) and get $0 \times 10^{0}$. Then $8 \times 10^{-1}$ (0.8) adding with $0 \times 10^0$, results in $8 \times 10^{-1}$.

The example showcases a situation that floating-point underflow during summation could lead to extra numeric error, and further causes non-associativity in the arithemtic. It is simple, but can be generalized to the current IEEE 754 floating-point numbers. The operands’ order matters in floating-point arithemtics!

So here’s the answer. When running parallelized routines, GPUs have some innate randomness which comes from non-deterministic execution order. If a program consists of order-dependent operations, it will give non-deterministic output. Floating-point arithemtics, used widely in CUDA algorithms, are also such kind of operations, though less known by people. Atomic arithemtics solve the race condition problem, but do not guarantee the order.

If one requires determinism, the program should sequentialize the operations by using single thread or special synchronization, which will lead to a performance degrade. There is always a trade-off between determinism and performance.


Author: hsfzxjy.
Link: .
License: CC BY-NC-ND 4.0.
All rights reserved by the author.
Commercial use of this post in any form is NOT permitted.
Non-commercial use of this post should be attributed with this block of text.