計(jì)算的歷史已經(jīng)證明,用計(jì)算機(jī)硬件實(shí)現(xiàn)的相對簡單的算法所能實(shí)現(xiàn)的是無限的。但計(jì)算機(jī)用有限大小的數(shù)字表示的“真相”基本上是近似的。正如大衛(wèi)·戈德伯格所寫,“ 將無限多個(gè)實(shí)數(shù)壓縮成有限位數(shù)需要一個(gè)近似表示 。”浮點(diǎn)是實(shí)數(shù)最廣泛使用的表示形式,在許多處理器中都有實(shí)現(xiàn),包括 GPU 。它之所以受歡迎,是因?yàn)樗軌虼硪粋€(gè)大的動(dòng)態(tài)范圍的價(jià)值觀,并權(quán)衡范圍和精度。
不幸的是,浮點(diǎn)的靈活性和范圍可能會在某些應(yīng)用程序中造成麻煩,因?yàn)樵诠潭ǚ秶鷥?nèi)的精度更為重要:想想美元和美分。二進(jìn)制浮點(diǎn)數(shù)不能準(zhǔn)確地代表每一個(gè)十進(jìn)制值,它們的近似值和舍入可能會導(dǎo)致累積錯(cuò)誤,例如,在會計(jì)計(jì)算中可能是不可接受的。此外,添加非常大和非常小的浮點(diǎn)數(shù)可能會導(dǎo)致截?cái)噱e(cuò)誤。由于這些原因,許多貨幣和會計(jì)計(jì)算都是使用定點(diǎn)十進(jìn)制算法實(shí)現(xiàn)的,該算法存儲固定數(shù)量的小數(shù)位數(shù)。根據(jù)所需的范圍,定點(diǎn)算法可能需要更多的位。
NVIDIA GPU 不在硬件中實(shí)現(xiàn)定點(diǎn)算法,但 GPU 加速的軟件實(shí)現(xiàn)可能是高效的。事實(shí)上, RAPIDS cuDF 庫已經(jīng)提供了高效的 32 位和 64 位定點(diǎn)十進(jìn)制數(shù)和計(jì)算。但是 RAPIDS cuDF 和 GPU 加速的 Apache Spark 的一些用戶需要 128 位小數(shù)提供的更高范圍和精度,現(xiàn)在 NVIDIA CUDA 11.5 提供了對 128 位整數(shù)類型(int128)的預(yù)覽支持,這是實(shí)現(xiàn) 128 位十進(jìn)制算法所需的。
在本文中,在介紹 CUDA 新的 int128 支持后,我們將詳細(xì)介紹如何在其上實(shí)現(xiàn)十進(jìn)制定點(diǎn)算法。然后,我們將演示 RAPIDS cuDF 中的 128 位定點(diǎn)支持如何使關(guān)鍵的 Apache Spark 工作負(fù)載完全在 GPU 上運(yùn)行。
介紹 CUDA __int128
在 NVIDIA CUDA 11.5 中, NVCC 離線編譯器在主機(jī)編譯器支持的平臺上為有符號和無符號__int128數(shù)據(jù)類型添加了預(yù)覽支持。nvrtc JIT 編譯器還增加了對 128 位整數(shù)的支持,但需要一個(gè)命令行選項(xiàng)--device-int128來啟用這種支持。算術(shù)、邏輯和位運(yùn)算都支持 128 位整數(shù)。請注意, DWARF 調(diào)試對 128 位整數(shù)的支持目前還不可用,并將在后續(xù)的 CUDA 版本中提供。在 11.6 版本中, cuda gdb 和 Nsight Visual Studio Code Edition 增加了對檢查這種新變量類型的支持。
NVIDIA GPU 以 32 位的數(shù)量計(jì)算整數(shù),因此 128 位整數(shù)用四個(gè) 32 位無符號整數(shù)表示。加法、減法和乘法算法非常簡單,使用內(nèi)置的 PTX addc / madc 指令處理多個(gè)精度值。除法和余數(shù)使用簡單的 O ( n ^ 2 )除法算法實(shí)現(xiàn),類似于 Brent 和 Zimmermann 的書 現(xiàn)代計(jì)算機(jī)算法 中的算法 1.6 ,并進(jìn)行了一些優(yōu)化,以改進(jìn)商選擇步驟并最小化校正步驟。 128 位整數(shù)的一個(gè)令人振奮的用例是使用它們實(shí)現(xiàn)十進(jìn)制定點(diǎn)算法。的 21.12 版本中包含 128 位十進(jìn)制定點(diǎn)支持 RAPIDS libcudf .繼續(xù)閱讀,了解更多關(guān)于定點(diǎn)算法的信息,以及__int128如何用于實(shí)現(xiàn)高精度計(jì)算。
不動(dòng)點(diǎn)算法
定點(diǎn)數(shù)字通過存儲小數(shù)部分的固定位數(shù)來表示實(shí)數(shù)。定點(diǎn)數(shù)字也可用于“省略”整數(shù)值的低階數(shù)字(即,如果你想表示 1000 的倍數(shù),你可以使用一個(gè) 10 進(jìn)制的定點(diǎn)數(shù)字,其刻度等于 3 )。記住定點(diǎn)和浮點(diǎn)之間區(qū)別的一個(gè)簡單方法是,對于定點(diǎn)數(shù)字,小數(shù)點(diǎn)是固定的,而在浮點(diǎn)數(shù)字中,小數(shù)點(diǎn)可以浮動(dòng)(移動(dòng))。
定點(diǎn)數(shù)字背后的基本思想是,即使所表示的值可以有小數(shù)位數(shù)(也就是 1.23 中的 0.23 ),實(shí)際上也可以將值存儲為整數(shù)。例如,為了表示 1.23 ,可以用scale = -2和值 123 構(gòu)造一個(gè)fixed_point數(shù)字。通過將數(shù)值乘以小數(shù)位數(shù),可以將這種表示轉(zhuǎn)換為浮點(diǎn)數(shù)。在我們的例子中, 1.23 是通過 123 (值)乘以 0.001 ( 10 (基數(shù))乘以 -2 (刻度)的冪)得到的。當(dāng)構(gòu)造一個(gè)定點(diǎn)數(shù)時(shí),會出現(xiàn)相反的情況,您可以“移位”該值,以便將其存儲為整數(shù)(如果使用的是小數(shù)點(diǎn) -2 ( 0.001 ( 10 (基數(shù))與 -2 (小數(shù)點(diǎn))的冪之比),則浮點(diǎn)數(shù)為 1.23 時(shí),您將除以 0.001 )。
請注意,定點(diǎn)表示不是唯一的,因?yàn)榭梢赃x擇多個(gè)比例。對于 1.23 的示例,可以使用小于 -2 的任何比例,例如 -3 或 -4 。唯一的區(qū)別是存儲在磁盤上的數(shù)字不同; 123 表示刻度 -2 , 1230 表示刻度 -3 , 12300 表示刻度 -4 。當(dāng)您知道您的用例只需要一組小數(shù)位時(shí),您應(yīng)該使用 least precise (又名最大)刻度來最大化可表示值的范圍。使用 -2 刻度時(shí),范圍約為 -2000 萬至+ 2000 萬(小數(shù)點(diǎn)后兩位),而使用 -3 刻度時(shí),范圍約為 -2 萬至+ 200 萬(小數(shù)點(diǎn)后三位)。如果你知道你是在為錢建模,而且不需要小數(shù)點(diǎn)后三位,那么 scale-2 是一個(gè)更好的選擇。
定點(diǎn)類型的另一個(gè)參數(shù)是 base 。在本文的例子中,以及在 RAPIDS cuDF 中,我們使用 10 進(jìn)制,或十進(jìn)制定點(diǎn)。十進(jìn)制定點(diǎn)是最容易考慮的,因?yàn)槲覀儗κM(jìn)制(以 10 為基數(shù))數(shù)字很熟悉。定點(diǎn)數(shù)的另一個(gè)常用基數(shù)是基數(shù) 2 ,也稱為二進(jìn)制定點(diǎn)。這僅僅意味著,不是將數(shù)值按 10 的冪移動(dòng),而是將數(shù)值按 2 的冪移動(dòng)。您可以在后面的“示例”部分中看到一些二進(jìn)制和十進(jìn)制定點(diǎn)值的示例。
定點(diǎn)與浮點(diǎn)

表 1 :浮點(diǎn)和定點(diǎn)的比較。
絕對誤差是實(shí)際值與其計(jì)算機(jī)表示(以定點(diǎn)或浮點(diǎn)表示)之間的差值。相對誤差是絕對誤差與代表值之比。
為了演示定點(diǎn)可以解決的浮點(diǎn)表示問題,讓我們看看浮點(diǎn)是如何表示的。浮點(diǎn)數(shù)不能完全代表所有值。例如,與值 1.1 最接近的 32 位浮點(diǎn)數(shù)為 1.10000002384185791016 ( 看浮子。讓我們想象一下 )。在執(zhí)行算術(shù)運(yùn)算時(shí),尾隨的“不精確”可能會導(dǎo)致錯(cuò)誤。例如, 1.1 + 1.1 的收益率為 2.2000000476837158231 。

圖 1 :浮點(diǎn) 1.1 的可視化。
相比之下,使用定點(diǎn)表示時(shí),使用整數(shù)存儲 exact 值。為了使用比例等于 -1 的定點(diǎn)數(shù)字表示 1.1 ,存儲值 11 。算術(shù)運(yùn)算是在基礎(chǔ)整數(shù)上執(zhí)行的,因此將 1.1 + 1.1 作為定點(diǎn)數(shù)相加,只需將 11 + 11 相加,就可以得到 22 ,正好代表值 2.2
為什么定點(diǎn)運(yùn)算很重要?
如前一個(gè)例子所示,定點(diǎn)算法避免了浮點(diǎn)數(shù)固有的精度和舍入誤差,同時(shí)還提供了表示小數(shù)的能力。通過保持相對誤差恒定,浮點(diǎn)提供了更大的值范圍。然而,這意味著,當(dāng)添加非常大和非常小的數(shù)字時(shí),它可能會出現(xiàn)較大的絕對(截?cái)啵╁e(cuò)誤,并會遇到舍入錯(cuò)誤。固定點(diǎn)表示法總是有相同的 absolute 錯(cuò)誤,代價(jià)是能夠表示縮小的值范圍。如果您知道小數(shù)點(diǎn)/二進(jìn)制點(diǎn)后需要特定精度,則定點(diǎn)允許您在不截?cái)噙@些數(shù)字的情況下保持其精度,即使值增長到范圍的極限。如果你需要更多的范圍,你必須添加更多的位。因此,小數(shù) 128 對一些用戶來說變得很重要。

表 2 :小數(shù)點(diǎn) 32 的范圍,小數(shù)點(diǎn)= 2 。
fixed_point編號有許多應(yīng)用程序和用例。您可以找到使用fixed_point數(shù)字 on Wikipedia 的實(shí)際應(yīng)用程序列表
fixed_point inRAPIDSlibcudf
概述
RAPIDS lib cuDF ` fixed _ point `類型的核心是一個(gè)簡單的類模板。
templateclass fixed_point { Rep _value; scale_type _scale; }
fixed_point類的模板是:
-
Rep:表示fixed_point數(shù)字(例如,使用的整數(shù)類型) -
Rad:數(shù)字的Radix(例如,基數(shù) 2 或基數(shù) 10 )
decimal32和decimal64類型分別使用int32_t和int64_t作為 Rep ,并且都有Radix::BASE_10.。scale是一個(gè)強(qiáng)類型的運(yùn)行時(shí)變量(請參見下面的運(yùn)行時(shí)刻度和強(qiáng)類型小節(jié)等)。
fixed_point類型有幾個(gè)構(gòu)造函數(shù)(請參見下面的“構(gòu)造方法”小節(jié))、可轉(zhuǎn)換為整型和浮點(diǎn)型的顯式轉(zhuǎn)換運(yùn)算符,以及完整的運(yùn)算符(加法、減法等)。
規(guī)模的跡象
在大多數(shù) C ++定點(diǎn)實(shí)現(xiàn)(包括 RAPIDS LIbcUDF )中,negative scale指示小數(shù)位數(shù)。positive scale表示可表示的倍數(shù)(例如,如果scale = 2表示decimal32,則可以表示 100 的倍數(shù))。
auto const number_with_pos_scale = decimal32{1.2345, scale_type{-2}}; // 1.23
auto const number_with_neg_scale = decimal32{12345, scale_type{2}}; // 12300
建設(shè)者
libcudf中提供了以下(簡化的)構(gòu)造函數(shù):
fixed_point(T const& value, scale_type const& scale) fixed_point(scaled_integers) // already "shifted" value fixed_point(T const& value) // scale = 0 fixed_point() // scale = 0, value = 0
其中,Rep是有符號整數(shù)類型,T可以是整數(shù)類型或浮點(diǎn)數(shù)。
設(shè)計(jì)與動(dòng)機(jī)
libcudf 的fixed_point類型有許多設(shè)計(jì)目標(biāo)。這些措施包括:
- 需要運(yùn)行時(shí)量表
- 與潛在標(biāo)準(zhǔn) C ++固定點(diǎn)類型的一致性
- 強(qiáng)類型
下面詳細(xì)介紹了這些設(shè)計(jì)動(dòng)機(jī)。
運(yùn)行時(shí)規(guī)模和第三方定點(diǎn)庫
在設(shè)計(jì)階段,我們研究了八個(gè)現(xiàn)有定點(diǎn) C ++庫。不使用第三方庫的主要原因是,所有現(xiàn)有的定點(diǎn)類型/庫都是以scale作為編譯時(shí)參數(shù)設(shè)計(jì)的。這不適用于 RAPIDSlibcudf,因?yàn)樗枰?scale 作為運(yùn)行時(shí)參數(shù)。
雖然RAPIDS?libcudf是一個(gè) C ++庫,可以在 C ++應(yīng)用程序中使用,它也是后端RAPIDS cuDF,這是一個(gè) Python 庫。 Python 是一種解釋(而非編譯,如 C ++)語言。此外, cuDF 必須能夠從其他數(shù)據(jù)源讀取或接收定點(diǎn)數(shù)據(jù)。這意味著我們不知道編譯時(shí)定點(diǎn)值的scale。因此,我們需要在fixed_pointRAPIDS 中輸入fixed_point類型,該類型具有運(yùn)行時(shí)刻度參數(shù)。
我們引用的主要庫是CNL,John McFarlane的組成數(shù)字庫,目前是[VZX97 ]向C++標(biāo)準(zhǔn)中添加定點(diǎn)類型的參考。我們的目標(biāo)是使 RAPIDSlibcudf fixed_point類型盡可能與潛在的標(biāo)準(zhǔn)化類型相似。下面是 RAPIDSlibcudf和CNL之間的一個(gè)簡單比較。
CNL (Godbolt Link)
using namespace cnl; auto x = fixed_point{1.23};
RAPIDS libcudf
using namespace numeric; auto x = fixed_point{1.23, scale_type{-2}};
或者:
using namespace numeric;
auto x = decimal32{1.23, scale_type{-2}};
這里需要注意的最重要的區(qū)別是,在 CNL 示例中,-2作為模板(又稱編譯時(shí)參數(shù)),而在 cuDF lib cuDF 示例中,scale_type{-2}作為運(yùn)行時(shí)參數(shù)。
強(qiáng)類型
fixed_point類型的設(shè)計(jì)中融入了強(qiáng)類型。這方面的兩個(gè)例子是:
-
將scoped enumeration與
direct-list-initialization對于刻度盤類型 -
對
Radix使用作用域枚舉而不是整數(shù)類型
RAPIDS lib cuDF 堅(jiān)持強(qiáng)類型最佳實(shí)踐和強(qiáng)類型API,因?yàn)閺?qiáng)類型提供了保護(hù)和表達(dá)能力。與強(qiáng)大的打字相比,我不會進(jìn)入較弱的兔子洞,但是如果你想了解更多關(guān)于它的信息,那么有很多很棒的資源,包括Jonathon Bocarra的VC++上的流暢的C++帖子。
添加對decimal128的支持
RAPIDSlibcudf21.12 將 CUDA 添加為受支持的fixed_point類型。這需要進(jìn)行一些更改,第一個(gè)是添加依賴于decimal12811.5 提供的__int128類型的decimal128類型別名。
using decimal32 = fixed_point; using decimal64 = fixed_point ; using decimal128 = fixed_point<__int128_t, Radix::BASE_10>;
這需要進(jìn)行一些內(nèi)部更改,包括更新類型特征函數(shù)__int128_t對某些函數(shù)的專門化,以及添加支持,以便cudf::column_view和朋友使用decimal128。以下簡單示例演示了 lib cuDF API 與decimal128的配合使用(注意,所有這些示例對decimal32和decimal64的作用相同)。
例子
簡單貨幣
一個(gè)簡單的貨幣示例使用libcudf提供的decimal32類型,其刻度為 -2 ,正好代表 17.29 美元:
auto const money = decimal32{17.29, scale_type{-2}};
大數(shù)和小數(shù)之和
當(dāng)求大值和小值之和時(shí),定點(diǎn)非常有用。作為一個(gè)簡單的玩具示例,下面的代碼將指數(shù) -2 到 9 的 10 的冪相加。
templateauto sum_powers_of_10() { auto iota = std::views::iota(-2, 10); return std::transform_reduce( iota.begin(), iota.end(), T{}, std::plus{}, [](auto e) -> T { return std::pow(10, e); }); }
比較 32 位浮點(diǎn)和十進(jìn)制定點(diǎn)可以得出以下結(jié)果:
sum_powers_of_10(); // 1111111168.000000 sum_powers_of_10 (); // 1111111111.11
其中decimal_type是 32 位的 10 進(jìn)制定點(diǎn)類型。使用 Godbolthere上的 CNL 庫可以看到一個(gè)例子。
避免浮點(diǎn)舍入問題
下面是一段代碼(見Godbolt中),舉例說明浮點(diǎn)值遇到問題(在 C ++中):
std::cout << std::roundf(256.49999) << ' '; // prints 257
RAPIDS lib cuDF 中的等效代碼不會有相同的問題(請參見Github上的內(nèi)容):
auto col = // decimal32 column with scale -5 and value 256.49999 auto result = cudf::round(input); // result is 256
256.4999 的值不能用 32 位二進(jìn)制浮點(diǎn)表示,因此在調(diào)用 std :: roundf 函數(shù)之前,將其舍入到 256.5 。使用定點(diǎn)表示法可以避免這個(gè)問題,因?yàn)?256.4999 可以用任何具有五個(gè)或五個(gè)以上精度分?jǐn)?shù)值的 10 進(jìn)制(十進(jìn)制)類型來表示。
二進(jìn)制與十進(jìn)制定點(diǎn)
// Decimal Fixed Point using decimal32 = fixed_point; auto const a = decimal32{17.29, scale_type{-2}}; // 17.29 auto const b = decimal32{4.2, scale_type{ 0}}; // 4 auto const c = decimal32{1729, scale_type{ 2}}; // 1700 // Binary Fixed Point using binary32 = fixed_point ; auto const x = binary{17.29, scale_type{-2}}; // 17.25 auto const y = binary{4.2, scale_type{ 0}}; // 4 auto const z = binary{1729, scale_type{ 2}}; // 1728
小數(shù) 128
// Multiplying two decimal128 numbers
auto const x = decimal128{1.1, scale_type{-1});
auto const y = decimal128{2.2, scale_type{-1}};
auto const z = x * y; // 2.42 with scale equal to -2 // Adding two decimal128 numbers
auto const x = decimal128{1.1, scale_type{-1});
auto const y = decimal128{2.2, scale_type{-1}};
auto const z = x * y; // 3.3 with scale equal to -1
DecimalType和 RAPIDS Spark
Apache Spark SQL 中的 DecimalType 是一種可以表示 Java BigDecimal 值的數(shù)據(jù)類型。對財(cái)務(wù)數(shù)據(jù)進(jìn)行操作的 SQL 查詢大量使用十進(jìn)制類型。與定點(diǎn)十進(jìn)制數(shù)的 RAPIDS lib cuDF 實(shí)現(xiàn)不同, Spark 中的 DecimalType 可能的最大精度限制為 38 位。小數(shù)點(diǎn)后的位數(shù)也被限制在 38 。這個(gè)定義是 C ++刻度的否定。例如,像 0.12345 這樣的十進(jìn)制值在 Spark 中的刻度為 5 ,但在 libcudf 中的刻度為 -5 。
Spark 嚴(yán)格遵循 Apache Hive 操作精度計(jì)算規(guī)范,并為用戶提供配置十進(jìn)制操作精度損失的選項(xiàng)。 Spark SQL 在執(zhí)行聚合、窗口、強(qiáng)制轉(zhuǎn)換等操作時(shí)積極提高結(jié)果列的精度。這種行為本身就是使小數(shù) 128 與現(xiàn)實(shí)世界的查詢極其相關(guān)的原因,它回答了一個(gè)問題:“為什么我們需要支持高精度的小數(shù)列?”??紤]下面的例子,特別是哈希骨灰,它有一個(gè)乘法表達(dá)式,涉及一個(gè)十進(jìn)制 64 列,價(jià)格,和一個(gè)非十進(jìn)制列,數(shù)量。 Spark 首先將非十進(jìn)制列強(qiáng)制轉(zhuǎn)換為適當(dāng)?shù)氖M(jìn)制列。然后確定結(jié)果精度,該精度大于輸入精度。因此,即使涉及小數(shù) 64 個(gè)輸入,結(jié)果精度也是小數(shù) 128 的情況也很常見。
scala> val queryDfGpu = readPar.agg(sum('price*'quantity))
queryDfGpu1: org.apache.spark.sql.DataFrame = [sum((price * quantity)): decimal(32,2)] scala> queryDfGpu.explain
== Physical Plan ==
*(2) HashAggregate(keys=[], functions=[sum(CheckOverflow((promote_precision(cast(price#19 as decimal(12,2))) * promote_precision(cast(cast(quantity#20 as decimal(10,0)) as decimal(12,2)))), DecimalType(22,2), true))])
+- Exchange SinglePartition, true, [id=#429] +- *(1) HashAggregate(keys=[], functions=[partial_sum(CheckOverflow((promote_precision(cast(price#19 as decimal(12,2))) * promote_precision(cast(cast(quantity#20 as decimal(10,0)) as decimal(12,2)))), DecimalType(22,2), true))]) +- *(1) ColumnarToRow +- FileScan parquet [price#19,quantity#20] Batched: true,DataFilters: [], Format: Parquet, Location: InMemoryFileIndex[file:/tmp/dec_walmart.parquet], PartitionFilters: [], PushedFilters: [], ReadSchema: struct
隨著在 lib cuDF 中引入了新的小數(shù) 128 數(shù)據(jù)類型, Spark 的 RAPIDS 插件能夠利用更高的精度,并將計(jì)算保持在 GPU 上,而之前需要返回到 CPU 上。
作為一個(gè)例子,讓我們來看一個(gè)在以下模式下運(yùn)行的簡單查詢。
{ id : IntegerType // Unique ID prodName : StringType // Product name will be used to aggregate / partition price : DecimalType(11,2) // Decimal64 quantity : IntegerType // Quantity of product }
此查詢計(jì)算 totalCost 上的無界窗口,即總和(價(jià)格*數(shù)量)。然后,它會在排序后由 prodName 對結(jié)果進(jìn)行分組,并返回最小總成本。
// Run window operation
val byProdName = Window.partitionBy('prodName)
val queryDfGpu = readPar.withColumn( "totalCost", sum('price*'quantity) over byProdName).sort( "prodName").groupBy( "prodName").min( "totalCost")
RAPIDS Spark 插件設(shè)置為僅當(dāng)所有表達(dá)式都可以在 GPU 上計(jì)算時(shí),才在 GPU 上運(yùn)行運(yùn)算符。讓我們先看看下面這個(gè)查詢的物理計(jì)劃,不支持小數(shù) 128 。)
如果不支持十進(jìn)制 128 ,每個(gè)運(yùn)算符都會返回 CPU ,因?yàn)闊o法支持包含十進(jìn)制 128 類型的子表達(dá)式。因此,包含 exec 或父表達(dá)式的表達(dá)式也不會在 GPU 上執(zhí)行,以避免低效的行到列和列到行轉(zhuǎn)換。
== Physical Plan == *(3) HashAggregate(keys=[prodName#18], functions=[min(totalCost#66)]) +- *(3) HashAggregate(keys=[prodName#18], functions=[partial_min(totalCost#66)]) +- *(3) Project [prodName#18, totalCost#66] +- Window [sum(_w0#67) windowspecdefinition(prodName#18, specifiedwindowframe(RowFrame, unboundedpreceding$(), unboundedfollowing$())) AS totalCost#66], [prodName#18] +- *(2) Sort [prodName#18 ASC NULLS FIRST], false, 0 +- Exchange hashpartitioning(prodName#18, 1), true, [id=#169] +- *(1) Project [prodName#18, CheckOverflow((promote_precision(cast(price#19 as decimal(12,2))) * promote_precision(cast(cast(quantity#20 as decimal(10,0)) as decimal(12,2)))), DecimalType(22,2), true) AS _w0#67] +- *(1) ColumnarToRow +- FileScan parquet [prodName#18,price#19,quantity#20] Batched: true, DataFilters: [], Format: Parquet, Location: InMemoryFileIndex[file:/tmp/dec_walmart.parquet], PartitionFilters: [], PushedFilters: [], ReadSchema: struct
啟用小數(shù) 128 支持后的查詢計(jì)劃顯示,所有操作現(xiàn)在都可以在 GPU 上運(yùn)行。由于沒有 ColumnarToRow 和 RowToColumnar 轉(zhuǎn)換(在查詢中顯示為 collect 操作),因此通過在 GPU 上運(yùn)行整個(gè)查詢,可以獲得更好的性能。
== Physical Plan == GpuColumnarToRow false +- GpuHashAggregate(keys=[prodName#18], functions=[gpumin(totalCost#31)]), filters=ArrayBuffer(None)) +- GpuHashAggregate(keys=[prodName#18], functions=[partial_gpumin(totalCost#31)]), filters=ArrayBuffer(None)) +- GpuProject [prodName#18, totalCost#31] +- GpuWindow [prodName#18, _w0#32, gpusum(_w0#32, DecimalType(32,2)) gpuwindowspecdefinition(prodName#18, gpuspecifiedwindowframe(RowFrame, gpuspecialframeboundary(unboundedpreceding$()), gpuspecialframeboundary(unboundedfollowing$()))) AS totalCost#31], [prodName#18] +- GpuCoalesceBatches batchedbykey(prodName#18 ASC NULLS FIRST) +- GpuSort [prodName#18 ASC NULLS FIRST], false, com.nvidia.spark.rapids.OutOfCoreSort$@3204b591 +- GpuShuffleCoalesce 2147483647 +- GpuColumnarExchange gpuhashpartitioning(prodName#18, 1), true, [id=#57] +- GpuProject [prodName#18, gpucheckoverflow((gpupromoteprecision(cast(price#19 as decimal(12,2))) * gpupromoteprecision(cast(cast(quantity#20 as decimal(10,0)) as decimal(12,2)))), DecimalType(22,2), true) AS _w0#32] +- GpuFileGpuScan parquet [prodName#18,price#19,quantity#20] Batched: true, DataFilters: [], Format: Parquet, Location: InMemoryFileIndex[file:/tmp/dec_walmart.parquet], PartitionFilters: [], PushedFilters: [], ReadSchema: struct
對于乘法運(yùn)算,數(shù)量列轉(zhuǎn)換為小數(shù) 64 (精度= 10 ),價(jià)格列(已為小數(shù) 64 類型)轉(zhuǎn)換為精度 12 ,使兩列的類型相同。結(jié)果列的大小調(diào)整為精度 22 ,這是小數(shù) 128 類型,因?yàn)榫却笥?18 。這顯示在上面計(jì)劃的 GpuProject 節(jié)點(diǎn)中。
對 sum ()的窗口操作也將精度進(jìn)一步提升到 32 。
我們使用 NVIDIA 決策支持( NDS )來衡量加速比, NDS 是 Spark 客戶和提供商經(jīng)常使用的 TPC-DS 數(shù)據(jù)科學(xué)基準(zhǔn)的一種改編。 NDS 包含與行業(yè)標(biāo)準(zhǔn)基準(zhǔn)相同的 100 多個(gè) SQL 查詢,但修改了數(shù)據(jù)集生成和執(zhí)行腳本的部分。? NDS 的結(jié)果與 TPC-DS 不可比。
NDS 查詢子集的初步運(yùn)行表明,由于支持小數(shù) 128 ,性能顯著提高,如下圖所示。它們在八個(gè)節(jié)點(diǎn)組成的集群上運(yùn)行,每個(gè)節(jié)點(diǎn)有一個(gè) A100 GPU 和 1024 CPU 內(nèi)核,在 Spark 3.1.1 上運(yùn)行 16 個(gè)內(nèi)核的執(zhí)行器。每個(gè)執(zhí)行器在內(nèi)存中使用 240GiB 。這些查詢顯示了接近 8 倍的出色加速,這可以歸因于以前的操作回到了現(xiàn)在在 GPU 上運(yùn)行的 CPU ,從而避免了行到列和列到行的轉(zhuǎn)換以及其他相關(guān)的開銷。所有 NDS 查詢的端到端運(yùn)行時(shí)間平均提高了 2 倍。這(希望)只是一個(gè)開始!

圖 2 :NDS 查詢子集的性能評估.隨著 Spark Spark 插件的 21.12 版本發(fā)布,大多數(shù)操作員都可以使用十進(jìn)制 128 支持。需要對溢出條件進(jìn)行一些特殊處理,以保持 CPU 和 GPU 之間的結(jié)果兼容性。這項(xiàng)工作的最終目標(biāo)是通過 RAPIDS for Spark 插件,讓零售和金融查詢充分受益于 GPU 的加速。
總結(jié)
RAPIDS libcudf 中的固定點(diǎn)類型、 DecimalType 的添加以及 Spark 的 RAPIDS 插件的 decimal128 支持,使得以前只有在 CPU 上才可能在 GPU 上運(yùn)行的激動(dòng)人心的用例成為可能。
關(guān)于作者
Conor Hoekstra 是 NVIDIA 的高級圖書館軟件工程師,在 RAPIDS 團(tuán)隊(duì)工作。他對編程語言、算法和漂亮的代碼非常熱衷。他是編程語言虛擬 Meetup 的創(chuàng)始人和組織者,他有一個(gè) YouTube 頻道,他是兩個(gè)播客的主持人:算法+數(shù)據(jù)結(jié)構(gòu)=程序和 ArrayCast 。康納還是 CPPNNorth 會議的項(xiàng)目主席,也是一位熱心的會議發(fā)言人。
Kuhu Shukla 是 NVIDIA Spark- GPU 團(tuán)隊(duì)的高級軟件工程師。在此之前,她是雅虎 Hadoop 核心團(tuán)隊(duì)的成員!在伊利諾伊州香檳市的 Apache Tez 、 Thread 和 HDFS 等大數(shù)據(jù)平臺上工作。她是 Apache Tez 項(xiàng)目的 PMC 。她在北卡羅來納州立大學(xué)獲得了計(jì)算機(jī)科學(xué)碩士學(xué)位。
Mark Harris 是 NVIDIA 杰出的工程師,致力于 RAPIDS 。 Mark 擁有超過 20 年的 GPUs 軟件開發(fā)經(jīng)驗(yàn),從圖形和游戲到基于物理的模擬,到并行算法和高性能計(jì)算。當(dāng)他還是北卡羅來納大學(xué)的博士生時(shí),他意識到了一種新生的趨勢,并為此創(chuàng)造了一個(gè)名字: GPGPU (圖形處理單元上的通用計(jì)算)。
審核編輯:郭婷
-
NVIDIA
+關(guān)注
關(guān)注
14文章
5465瀏覽量
108797 -
gpu
+關(guān)注
關(guān)注
28文章
5068瀏覽量
134153 -
編譯器
+關(guān)注
關(guān)注
1文章
1666瀏覽量
51010
發(fā)布評論請先 登錄
SM4算法實(shí)現(xiàn)分享(一)算法原理
NVIDIA攜手Ansys和DCAI推進(jìn)流體動(dòng)力學(xué)量子算法發(fā)展
借助NVIDIA技術(shù)加速半導(dǎo)體芯片制造
使用NVIDIA CUDA-X庫加速科學(xué)和工程發(fā)展
原創(chuàng):labview 讀取補(bǔ)碼表示的有符號16位數(shù)時(shí),如何解析成負(fù)數(shù)
74HC390;74HCT390雙十進(jìn)制波紋計(jì)數(shù)器規(guī)格書
74HC42 BCD至十進(jìn)制解碼器規(guī)格書
NVIDIA宣布NVIDIA Isaac重要更新
用long int型采集了ad輸出的24位有符號二進(jìn)制補(bǔ)碼數(shù)據(jù),該如何觀察數(shù)據(jù)?
bcd編碼的優(yōu)缺點(diǎn) bcd編碼的常見錯(cuò)誤
bcd與十進(jìn)制之間的關(guān)系
bcd編碼的應(yīng)用 bcd與二進(jìn)制的區(qū)別
NVIDIA加速全球大多數(shù)超級計(jì)算機(jī)推動(dòng)科技進(jìn)步

利用NVIDIA CUDA 11.5實(shí)現(xiàn)128十進(jìn)制算法
評論