High Performance Linpack (HPL) prakticky: kompilujeme a testujeme

7. 11. 2023
Doba čtení: 21 minut

Sdílet

 Autor: Depositphotos
Vysoké venkovní teploty letos definitivně pominuly, je na čase pořádně ohřát procesory. Od toho tu máme test a benchmark v jednom HPL. Dnes se do toho dáme prakticky a budeme kompilovat a testovat.

minulém díle jsme si řekli, co je to HPL a k čemu se používá. Nyní si jej konečně sami zkompilujeme. Pokud jste ještě nekompilovali, tak budete potřebovat balíček build-essential (Debian, Ubuntu), nebo @development-tools (Fedora, Red Hat)

$ sudo apt install build-essential

nebo

$ sudo dnf install @development-tools

Dále bude návod jen pro Debian a Ubuntu. Pro Fedoru a Red Hat to však bude podobné, jen se mohou lišit názvy balíčků.

Nainstalujeme jednu ze dvou možných MPI knihoven (OpenMPI, nebo MPICH) ve vývojářské variantě. Výběr je na vás a nemá velký vliv na výsledný výkon.

$ sudo apt install libopenmpi-dev

nebo

$ sudo apt install libmpich-dev

Nyní už budeme kompilovat knihovnu BLAS, kterou si vybereme. Jak už jsme říkali, tak právě na knihovně BLAS nejvíce záleží výkon naměřený HPL. Můžete samozřejmě zkusit více knihoven a potom výkon porovnat. Pokud nevíte, nebo chcete prostě něčím začít, doporučuji OpenBLAS.

OpenBLAS

Vezmeme zdrojové kódy aktuální verze 0.3.24 z GitHubu.

wget https://github.com/xianyi/OpenBLAS/archive/refs/tags/v0.3.24.tar.gz
tar xvf v0.3.24.tar.gz
cd OpenBLAS-0.3.24

Nyní je potřeba správně nakonfigurovat procesor pomocí proměnné TARGET=. Možné hodnoty naleznete v souboru TargetList.txt. Pro arm64 se nám mohou hodit ARMV8, CORTEXA53, CORTEXA57, CORTEXA72, CORTEXA73 a CORTEXX1 (domnívám se, že by to mohlo být optimální pro CORTEX-A76, 77 a 78). Ve výsledku se ukáže, že výsledná binárka HPL pro CORTEXA53 a CORTEXA55 je stejná (pro OpenBLAS-0.3.25 již ne). To také platí pro CORTEXA57 a CORTEXA72. A to i když samotné soubory knihovny libopenblas_*.a stejné nejsou. Je to tím, že HPL použije jen pár funkcí z knihovny (nejvíc záleží na dgemm (double precision general matrix multiply) a pak ještě daxpy, dcopy, dgemv, dger, dscal, dswap, dtrsm, dtrsv a idamax) a ty jsou stejné.

Pro amd64 jsou tu volby pro CPU Intel od P2 do SAPPHIRERAPIDS, ale opět se ukáže, že výsledné binárky HPL jsou pro některé TARGETy stejné. Můžeme je rozdělit podle instrukcí SSE4.2 ( NEHALEM), AVX ( SANDYBRIDGE), AVX2 ( HASWELL) a AVX512 ( SKYLAKEX).

Mimochodem, v x86 binárkách se můžeme přímo podívat, jaké tam jsou instrukce. Hodí se k tomu například nástroj elfx86exts napsaný v Rustu. Pokud jej nemáte v repozitářích, stačí cargo install elf86exts. V případě dynamické architektury se vám objeví všechny možné instrukce, ale za běhu se samozřejmě nepoužijí. Pro ARM takový nástroj pokud vím bohužel neexistuje. Jde použít ruční disassembler objdump -d a pak ve výstupu hledat instrukce NEON. V případě HPL půjde patrně o instrukci fmla (ASIMD FP multiply accumulate). Například se můžeme přesvědčit, jaké instrukce jsou v binárce pro SandyBridge (AVX)

$ elfx86exts xhpl-openblas0.3.22.sandybridge
File format and CPU architecture: Elf, X86_64
MODE64 (call)
SSE2 (movsd)
CMOV (cmove)
SSE1 (movaps)
AVX (vmovsd)
NOVLX (vxorps)
SSE3 (movddup)
Instruction set extensions used: AVX, CMOV, MODE64, NOVLX, SSE1, SSE2, SSE3
CPU Generation: Unknown

V univerzální binárce pak najdeme AVX, AVX2, AVX512 i například zastaralé 3DNow.

$ elfx86exts xhpl-openblas0.3.22.universal-from-sandybrydge
File format and CPU architecture: Elf, X86_64
MODE64 (call)
SSE2 (movsd)
CMOV (cmove)
SSE1 (movaps)
SSE3 (haddpd)
3DNow (prefetch)
MMX (emms)
AVX (vbroadcastsd)
NOVLX (vmovups)
FMA4 (vfmaddpd)
AVX2 (vpand)
AVX512 (vmovapd)
BWI (kmovq)
BMI2 (sarx)
VLX (vbroadcastsd)
DQI (kmovb)
Instruction set extensions used: 3DNow, AVX, AVX2, AVX512, BMI2, BWI, CMOV, DQI, FMA4, MMX, MODE64, NOVLX, SSE1, SSE2, SSE3, VLX
CPU Generation: Unknown

V případě ARM můžeme třeba spočítat počet instrukcí  fmla.

$ objdump -d xhpl-openblas0.3.21.a53 | grep fmla | wc
    357    2142   16895

Když si budete kompilovat knihovnu pro procesor s instrukcemi, které váš nemá, tak dostanete při testu chybu. Ale knihovna jde nainstalovat a slinkovat s HPL. Samozřejmě že program pak pustíte jen na procesoru, co už tyto instrukce umí. Můžete si tedy vše zkompilovat na jednom počítači a pak testovat počítačů více.

Já si tedy vyberu pro Raspberry Pi 4 TARGET=CORTEXA72  a pustím kompilaci. Pokud TARGET nespecifikujete, dojde k autodetekci.

export NO_SHARED=1
export TARGET=CORTEXA72
NO_FORTRAN=1 NUM_THREADS=8 USE_OPENMP=1 make -j$(nproc)
make PREFIX=${HOME}/openblas install

Proměnná NO_SHARED nám zajistí statickou knihovnu. Výsledná binárka HPL bude tak staticky slinkovaná a nebude k ní potřeba složitě přikládat dynamický so soubor knihovny. To je zase pro případ, že kompilujeme na jednom počítači a spouštíme na jiném. NO_FORTRAN je jasné, NUM_THREADS nastavte na maximum jader, co chcete pomocí OpenMP obsloužit. Pokud nenastavíte, samo se detekuje počet jader na stroji, kde kompilujete.

Pokud použijete místo toho MPI, nebo vás zajímá výkon jen jednoho jádra, tak je možné OpenMP vypnout a výsledek bude rychlejší ( USE_OPENMP=0 USE_THREAD=0). Pro testování stability však pro jednoduchost OpenMP použijeme.

Na Raspberry Pi 4 se OpenBLAS kompiluje asi 16 minut, na SandyBridge 4×2,5 GHz asi 7 minut. Takto si můžete nakompilovat statické knihovny OpenBLAS pro více TARGETů i více verzí. Výsledek bude vypadat například takto

$ ls -l ${HOME}/openblas/lib/
total 695296
drwxrwxr-x 3 rock rock     4096 Oct  5 12:48 cmake
-rw-r--r-- 1 rock rock 44943532 Oct  5 13:51 libopenblas-r0.3.24.a
lrwxrwxrwx 1 rock rock       27 Oct  7 13:21 libopenblas.a -> libopenblas_armv8-r0.3.24.a
-rw-r--r-- 1 rock rock 24790982 Oct  6 22:13 libopenblas_armv8-r0.3.24.a
-rw-r--r-- 1 rock rock 26828348 Oct  6 21:56 libopenblas_armv8p-r0.3.24.a
-rw-r--r-- 1 rock rock 24720786 Oct  5 15:33 libopenblas_cortexa53-r0.3.24.a
-rw-r--r-- 1 rock rock 26752584 Oct  5 15:40 libopenblas_cortexa53p-r0.3.24.a
-rw-r--r-- 1 rock rock 24720786 Oct  5 13:59 libopenblas_cortexa55-r0.3.24.a
-rw-r--r-- 1 rock rock 26750384 Oct  6 16:10 libopenblas_cortexa55p-r0.3.24.a
-rw-r--r-- 1 rock rock 24794166 Oct  5 15:27 libopenblas_cortexa57-r0.3.24.a
-rw-r--r-- 1 rock rock 26835276 Oct  5 15:22 libopenblas_cortexa57p-r0.3.24.a
-rw-r--r-- 1 rock rock 24794214 Oct  5 15:02 libopenblas_cortexa72-r0.3.24.a
-rw-r--r-- 1 rock rock 26835324 Oct  5 14:57 libopenblas_cortexa72p-r0.3.24.a
-rw-r--r-- 1 rock rock 24793222 Oct  5 15:09 libopenblas_cortexa73-r0.3.24.a
-rw-r--r-- 1 rock rock 26834076 Oct  5 15:16 libopenblas_cortexa73p-r0.3.24.a
-rw-r--r-- 1 rock rock 24801798 Oct  6 16:38 libopenblas_cortexx1-r0.3.24.a
-rw-r--r-- 1 rock rock 26838212 Oct  6 16:57 libopenblas_cortexx1p-r0.3.24.a
-rw-r--r-- 1 rock rock 24818390 Oct  5 15:55 libopenblas_neoversen1-r0.3.24.a
-rw-r--r-- 1 rock rock 26890652 Oct  5 15:48 libopenblas_neoversen1p-r0.3.24.a
-rw-r--r-- 1 rock rock 24818558 Oct  6 17:14 libopenblas_neoversen2-r0.3.24.a
-rw-r--r-- 1 rock rock 26890612 Oct  6 17:20 libopenblas_neoversen2p-r0.3.24.a
-rw-r--r-- 1 rock rock 24532944 Oct  5 16:05 libopenblas_neoversev1-r0.3.24.a
-rw-r--r-- 1 rock rock 26605590 Oct  5 16:17 libopenblas_neoversev1p-r0.3.24.a
-rw-r--r-- 1 rock rock 24311744 Oct  6 17:09 libopenblas_thunderx-r0.3.24.a
-rw-r--r-- 1 rock rock 26356294 Oct  6 17:04 libopenblas_thunderxp-r0.3.24.a
-rw-r--r-- 1 rock rock 47222354 Oct  5 12:49 libopenblasp-r0.3.24.a
drwxrwxr-x 2 rock rock     4096 Oct  5 12:48 pkgconfig

V názvu hned vidíme TARGET, verzi OpenBLAS a také, jestli je knihovna zkompilovaná s vlákny (p), nebo bez (nic). Dynamická architektura (viz níže) pak prostě nemá žádný TARGET. Knihovna, která se použije při linkování s HPL je pak ta, na kterou ukazuje symbolický link  libopenblas.a.

Kromě toho můžeme využít dynamickou architekturu, tedy zkompilovat víc TARGETů do jedné knihovny. Při běhu se použije ta optimální pro daný procesor. Na první pohled to funguje docela dobře, není tedy důvod to nepoužít. Důvodem proti může být velikost a někdy špatná detekce (obzvlášť u big.LITTLE). I když vlastní knihovna s dynamickou architekturou je asi jen dvakrát větší (jak na amd64 tak i na arm64), tak výsledná binárka HPL je větší asi padesátkrát (11,7 MB místo 0,2 MB). Zřejmě se nešikovně linkuje stále stejný kód knihovny několikrát dokola.

Další nevýhodou autodetekce je, že na big.LITTLE architektuře vybere vždy to horší jádro (možná se kouká jen na jádro 0, což bývá zpravidla to LITTLE?). Ke špatné autodetekci dojde jak při kompilaci bez specifikace TARGETu, tak i při spuštění univerzálního kódu. Mám to vyzkoušené na Rockchip RK3588 (Rock 5B, 4× Cortex-A76 + 4× Cortex-A55), Rockchip 3399 (Orange Pi 4, 2× Cortex-A72 + 4× Cortex-A53) a Amlogic A311D (Khadas VIM3, 4× Cortex-A73 + 2× Cortex-A52). Autodetekce při spuštění u OpenBLAS jde obejít nastavením proměnné například  OPENBLAS_CORETYPE=CORTEXA72.

Pokud byste měli podezření, že HPL nejede optimálně, můžete se vrátit k jednotlivým TARGETům. V případě dynamické architektury kompilujeme takto

export NO_SHARED=1
export TARGET=ARMV8
export DYNAMIC_ARCH=1
NO_FORTRAN=1 NUM_THREADS=8 USE_OPENMP=1 make -j$(nproc)
make PREFIX=${HOME}/openblas install

Zde TARGET je vlastně nejnižší procesor, na kterém by knihovna ještě měla jet. Pokud ho nespecifikujete, tak se opět uplatní autodetekce. Ale tím pádem bychom na Raspberry Pi 4 neměli třeba CORTEX-A53. Kompilace na Raspberry Pi 4 zabere asi 43 minut (asi 2,6× déle, než jeden TARGET) a na SandyBridge 4×2,5 GHz asi 19 minut (asi 2,7× déle, než jeden TARGET).

BLIS

Alternativně můžeme použít knihovnu BLIS, kterou v aktuální verzi 0.9.0 stáhneme také z GitHubu.

wget https://github.com/flame/blis/archive/refs/tags/0.9.0.tar.gz
tar xvf 0.9.0.tar.gz
cd blis-0.9.0

Kompilujeme pomocí

./configure -p ${HOME}/blis --disable-shared -t openmp auto
make -j${nproc}
make -j${nproc} check
make install
ln -s ${HOME}/blis/lib/libblis.a ${HOME}/blis/lib/libopenblas.a

Zde opět použijeme OpenMP. Při použití MPI můžeme vypnout (vynechá se -t openmp, nebo dáte -t no). Parametr auto zajistí autodetekci. Pokud chcete specifikovat CPU, stačí zadat jeho název. Všechny názvy nalezneme pomocí  ls config.

~/blis-0.9.0 $ ls -l config/
total 140
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 a64fx
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 amd64
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 amd64_legacy
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 arm32
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 arm64
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 armsve
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 bgq
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 bulldozer
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 cortexa15
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 cortexa53
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 cortexa57
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 cortexa9
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 excavator
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 firestorm
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 generic
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 haswell
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 intel64
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 knc
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 knl
drwxr-xr-x 8 pi pi 4096 Apr  1  2022 old
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 penryn
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 piledriver
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 power10
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 power7
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 power9
-rw-r--r-- 1 pi pi  823 Apr  1  2022 README.md
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 sandybridge
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 skx
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 steamroller
drwxr-xr-x 3 pi pi 4096 Apr  1  2022 template
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 thunderx2
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 x86_64
drwxr-xr-x 3 pi pi 4096 Apr  1  2022 zen
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 zen2
drwxr-xr-x 2 pi pi 4096 Apr  1  2022 zen3

Pro arm64 jsou relevantní cortexa53, cortexa57, firestorm (Apple A14, M1), thunderx2 (Neoverse N1), armsve a generic. Dále je možné také použít dynamickou architekturu, čehož jsem si doposud nevšiml. BLIS to nazývá rodina konfigurací (configuration family). V tom případě použijte název  arm64.

Pro x86 je to složitější, protože tu máte amd64, což se může zdát jako název architektury. Ale ve skutečnosti to jsou jen nové procesory AMD (od Zen). Starší procesory AMD jsou v amd64_legacy  a procesory Intel v intel64. Dohromady amd64, intel64 a amd64_legacy jsou v x86_64. Popsáno je to v soubory config_registry.

V případě BLIS je potřeba ještě ručně udělat link třeba na libopenblas.a, protože HPL libblis.a nezná. BLIS na rozdíl od OpenBLAS nedělá sám kontrolu výsledné knihovny, proto přidáme make -j${nproc} check. To je jen pár testů, jestli chcete testů více, je tu ještě make -j${nproc} test. Ale to asi není pro náš účel potřeba, protože samotná aplikace HPL stejně výsledky sama kontroluje.

U BLIS se nám nevytváří hezká jména knihoven podle procesoru, pro který je přeložena, ale udělá se jen soubor libblis.a. Pokud budete kompilovat knihovnu vícekrát, je potřeba si to pojmenovat ručně.

Doba kompilace BLIS je kratší, než OpenBLAS. Na RPi4 to je asi 2 minuty + 1 minuta check v případě kompilace pro jeden procesor a 5 minut + 1 minuta pro celou rodinu procesorů ( arm64). Na SandyBridge 4×2,5 GHz je to asi 30 sekund + 10 sekund check v případě kompilace pro jeden procesor a 2,5 minuty + 10 sekund pro celou rodinu procesorů ( x86_64).

HPL

Máme tedy zkompilovanou alespoň jednu nebo i více knihoven BLAS. Tím je většina práce hotova. Nyní zkompilujeme vlastní HPL, ale to již trvá daleko kratší dobu. HPL vezmeme v aktuální verzi 2.3 ze stránek netlib.org.

wget https://www.netlib.org/benchmark/hpl/hpl-2.3.tar.gz
tar xvf hpl-2.3.tar.gz
cd hpl-2.3

Při použití OpenBLAS kompilujeme takto

LDFLAGS=-L${HOME}/openblas/lib CFLAGS="-pthread -fopenmp" ./configure
make -j$(nproc)

Při použití BLIS jen změníme cestu ke knihovně

LDFLAGS=-L${HOME}/blis/lib CFLAGS="-pthread -fopenmp" ./configure
make -j$(nproc)

Výsledná binárka pak je ~/hpl-2.3/testing/xhpl. Pokud nepotřebujeme debugovat, tak na ní můžete spustit strip, protože ve výchozím stavu se HPL kompiluje s přepínačem  -g.

Výsledné stripované binárky vypadají například takto

-rwxrwxr-x 1 rock rock 12530616 Oct  7 10:33 xhpl-r0.3.24
-rwxrwxr-x 1 rock rock   231600 Oct  7 13:22 xhpl_armv8-r0.3.24
-rwxrwxr-x 1 rock rock   207024 Oct  7 13:21 xhpl_cortexa53-r0.3.24
-rwxrwxr-x 1 rock rock   207024 Oct  7 12:58 xhpl_cortexa55-r0.3.24
-rwxrwxr-x 1 rock rock   231600 Oct  7 13:19 xhpl_cortexa57-r0.3.24
-rwxrwxr-x 1 rock rock   231600 Oct  7 13:13 xhpl_cortexa72-r0.3.24
-rwxrwxr-x 1 rock rock   231600 Oct  7 12:07 xhpl_cortexa73-r0.3.24
-rwxrwxr-x 1 rock rock   235696 Oct  7 12:26 xhpl_cortexx1-r0.3.24
-rwxrwxr-x 1 rock rock   235696 Oct  7 12:35 xhpl_neoversen1-r0.3.24
-rwxrwxr-x 1 rock rock   235696 Oct  7 12:44 xhpl_neoversen2-r0.3.24
-rwxrwxr-x 1 rock rock   231600 Oct  7 12:43 xhpl_neoversev1-r0.3.24
-rwxrwxr-x 1 rock rock   190640 Oct  7 12:52 xhpl_thunderx-r0.3.24
-rwxrwxr-x 1 rock rock  3529960 Oct  8 16:31 xhpl-blis0.9.0-arm64-no
-rwxrwxr-x 1 rock rock  1178856 Oct  8 21:28 xhpl-blis0.9.0-cortexa53-no
-rwxrwxr-x 1 rock rock  1174760 Oct  8 16:30 xhpl-blis0.9.0-cortexa57-no
-rwxrwxr-x 1 rock rock  1158376 Oct  8 21:27 xhpl-blis0.9.0-generic-no
-rwxrwxr-x 1 rock rock  1486056 Oct  8 16:29 xhpl-blis0.9.0-thunderx2-no
-rwxr-xr-x 1 rock rock  1154968 Jul 17 20:25 xhpl-blis0.9.0.a53
-rwxr-xr-x 1 rock rock  1142680 Jul 17 20:33 xhpl-blis0.9.0.a57
-rwxr-xr-x 1 rock rock  1142680 Jul 17 20:37 xhpl-blis0.9.0.firestorm
-rwxr-xr-x 1 rock rock  1142680 Jul 17 20:34 xhpl-blis0.9.0.generic
-rwxr-xr-x 1 rock rock  1474448 Jul 17 20:38 xhpl-blis0.9.0.thunderx2
-rwxr-xr-x 1 rock rock  3506088 Jul 13 21:45 xhpl-blis0.9.0.u
-rwxr-xr-x 1 rock rock  3497864 Jul 13 21:41 xhpl-blis0.9.0.u-single

Je vidět, že u knihovny BLIS univerzální binárka není až tak velká, jako u OpenBLAS. Má jen 3,4 MB, zato pro jeden procesor je BLIS o něco větší, má 1,1 MB. Pro OpenBLAS je to 11,7 MB a 0,2 MB. Taky je možné se podívat, které binárky jsou shodné. Jak jsme již říkali, tak OpenBLAS Cortex-A53 a Cortex-A55 jsou stejné (pro OpenBLAS-0.3.25 již ne) a pak Cortex-A57 a Cortex-A72 jsou stejné.

$ sha256sum xhpl* | sort
99bdc4ee089bf50b04f5bbb142e439c4b7bf9dd471fe9b1b2015572616a7adec  xhpl_cortexa53-r0.3.24
99bdc4ee089bf50b04f5bbb142e439c4b7bf9dd471fe9b1b2015572616a7adec  xhpl_cortexa55-r0.3.24
edb6113ff7da97b5c2bcf1c7069b05689b86e4ede85def818b68056dadab995b  xhpl_cortexa57-r0.3.24
edb6113ff7da97b5c2bcf1c7069b05689b86e4ede85def818b68056dadab995b  xhpl_cortexa72-r0.3.24

Předkompilované binárky pro aarch64

Pokud se vám nechce kompilovat knihovny a HPL, tak mám předkompilované binárky HPL pro Linux na aarch64 (arm64) na GitHubu. Jsou tam také výsledky některých ARM SBC včetně spotřeby a teploty.

Schválně jsem to dal až na konec, abych vás nepřipravil o radost z kompilování. Pokud chcete použít předkompilované binárky, stačí vám nainstalovat knihovny OpenMPI a to je všechno

$ sudo apt install openmpi-bin

Pokud budete mít výsledek na ARM64 jiné desky, nebo stejné desky jako mám na GitHubu, ale lepší, tak se o něj prosím podělte. Rád ho tam přidám.

HPL.dat

Ke spuštění HPL potřebujeme binárku (máme) a soubor HPL.dat. Ten definuje, co se bude počítat. Můžeme použít například tento

HPLinpack benchmark input file
Innovative Computing Laboratory, University of Tennessee
HPL.out     output file name (if any)
6           device out (6=stdout,7=stderr,file)
1           # of problems sizes (N)
28000       Ns
20          # of NBs
96 104 112 120 128 136 144 152 160 168 176 184 192 200 208 216 224 232 240 248 NBs
0           PMAP process mapping (0=Row-,1=Column-major)
1           # of process grids (P x Q)
1           Ps
1           Qs
16.0        threshold
1           # of panel fact
1           PFACTs (0=left, 1=Crout, 2=Right)
1           # of recursive stopping criterium
4           NBMINs (>= 1)
1           # of panels in recursion
2           NDIVs
1           # of recursive panel fact.
2           RFACTs (0=left, 1=Crout, 2=Right)
1           # of broadcast
2           BCASTs (0=1rg,1=1rM,2=2rg,3=2rM,4=Lng,5=LnM)
1           # of lookahead depth
0           DEPTHs (>=0)
2           SWAP (0=bin-exch,1=long,2=mix)
8           swapping threshold
0           L1 in (0=transposed,1=no-transposed) form
0           U  in (0=transposed,1=no-transposed) form
1           Equilibration (0=no,1=yes)
8           memory alignment in double

Většina parametrů může mít více hodnot, v jednom řádku máte například # of problems sizes (N), tedy kolik různých N má očekávat na následujícím řádku. Počet parametrů je omezen na 20. Občas by se hodilo i více, ale to bysme museli zasahovat do zdrojáku HPL. Druhá možnost je udělat více HPL.dat souborů a pustit si je postupně.

Hlavní parametr je Ns, tedy rozměr matice Ns × Ns se kterou se pracuje. Optimální je zvolit co nejvyšší Ns tak, aby se ještě vše vlezlo do RAM. Nezapomeneme přitom na nějakou spotřebu samotného operačního systému. Připomínáme, že paměťovou náročnost můžeme odhadnout jako místo zabrané maticí A, tedy 8 byte × Ns2 a k tomu přidáme rezervu zhruba okolo 20 %. Pozor však na dobu výpočtu, která roste jako 2/3 x Ns3-2Ns2. Pokud tedy budeme ze začátku ladit parametry, tak raději nejdříve s nižšími Ns, aby výpočet dlouho netrval.

Takže pro 4 GB RAM můžeme použít Ns=20 000, pro 8 GB RAM 28 000, pro 16 GB RAM 40 000. Viz tabulka, ve které už je s určitou režií OS počítáno

Optimální Ns
RAM [GB] Ns
0,5 7000
1 10000
2 14000
4 20000
8 28000
16 40000
32 57000
64 80000

Takže nahradíme Ns v našem souboru HPL.dat podle toho, kolik máte volné RAM. A nyní už konečně program pustíme.

Spouštění a výsledky

Pokud máme OpenBLAS s OpenMP, tak je stiuace jednoduchá a spustíme prostě ./xhpl. Automaticky se využije plný počet jader. To však někdy není úplně žádoucí. Příkladem je x86 s HT jádry. Ta je pro větší Flop/s lépe vypnout, nebo nepoužívat. Pokud tedy chcete pustit HPL například na 4 jádrech, uděláme OMP_NUM_THREADS=4 ./xhpl. Je dobré nenastavovat OMP_NUM_THREADS úplně globálně, protože tuto proměnou používají všechny OpenMP programy a člověk může zapomenout, co kde nastavil a pak se nestačí divit (vlastní zkušenost™).

V případě BLIS s OpenMP nejde program spustit bez nastavené proměnné OMP_NUM_THREADS, protože bez ní se spustí jen na jednom jádře. Takže v tomto případě pouštíme OMP_NUM_THREADS=4 ./xhpl. Malá poznámka: ono se to tedy spouští stejně, jako OpenBLAS, ale vývojáři BLIS varují, že se proměnná OMP_NUM_THREADS používat nemá, protože může být v nějakém budoucím vydání odstraněna. Má se místo toho používat  BLIS_NUM_THREADS.

Je dobré zkontrolovat pomocí top či htop, jestli pracuje tolik jader, kolik jsme chtěli. Chceme, aby pracovala všechna jádra, ale bez HT. Malá poznámka: na začátku výpočtu, kdy se plní paměť maticí a na konci výpočtu, kdy se kontroluje správnost používá OpenMP jen jedno vlákno. Tato část výpočtu může být i dost dlouhá, záleží na Ns. Ale ve vlastním výpočtu by měli jet jádra všechna.

V případě big.LITTLE je to složitější stejně jako u nových procesorů Intel, kde máte zároveň rychlá a pomalá jádra. HPL s tím nepočítá a rychlá jádra sice budou mít výpočet dřív, ale budou čekat na pomalá, protože práce je rozdělena mezi jádra rovnoměrně. Jedna z možností je pustit HPL dvakrát s různými parametry, jednou jen na rychlých jádrech a zároveň i na pomalých. Přitom je potřeba správně vybrat parametry tak, aby oba výpočty trvaly zhruba stejnou dobu. Příště si ukážeme, jak na to.

Když už koukáte do top/htop, tak se podívejte, jestli jste nepřehnali Ns a neplní se vám swap. V tom případě bude výpočet řádově pomalejší a nebude tak fungovat ani jako test stability. Možná tak jako test stability zařízení swap.

Samozřejmě na počítači nemáme pokud možno spuštěno nic jiného. Jednak by to zabíralo paměť a také obtěžovalo CPU a vyrovnávací paměť. Pokud to jde, tak nastartujeme i bez grafického rozhraní.

Pokud chcete testovat pouze stabilitu, tak máme v podstatě hotovo. Předpřipravený HPL.dat soubor spustí 20 testů s různou velikostí bloků Nb. Pokud všechny dopadnou dobře, tak váš počítač je z velkou pravděpodobností stabilní.

Výsledek pak vypadá například takto (zkráceno, ukazujeme jen první z 20 výsledků):

================================================================================
HPLinpack 2.3  --  High-Performance Linpack benchmark  --   December 2, 2018
Written by A. Petitet and R. Clint Whaley,  Innovative Computing Laboratory, UTK
Modified by Piotr Luszczek, Innovative Computing Laboratory, UTK
Modified by Julien Langou, University of Colorado Denver
================================================================================

An explanation of the input/output parameters follows:
T/V    : Wall time / encoded variant.
N      : The order of the coefficient matrix A.
NB     : The partitioning blocking factor.
P      : The number of process rows.
Q      : The number of process columns.
Time   : Time in seconds to solve the linear system.
Gflops : Rate of execution for solving the linear system.

The following parameter values will be used:

N      :   20000
NB     :     104      112      120      128      136      144      152      160
             168      176      184      192      200      208      216      224
             232      240      248      256
PMAP   : Row-major process mapping
P      :       1
Q      :       4
PFACT  :   Crout
NBMIN  :       4
NDIV   :       2
RFACT  :   Right
BCAST  :   1ring
DEPTH  :       0
SWAP   : Mix (threshold = 8)
L1     : transposed form
U      : transposed form
EQUIL  : yes
ALIGN  : 8 double precision words

--------------------------------------------------------------------------------

- The matrix A is randomly generated for each test.
- The following scaled residual check will be computed:
      ||Ax-b||_oo / ( eps * ( || x ||_oo * || A ||_oo + || b ||_oo ) * N )
- The relative machine precision (eps) is taken to be               1.110223e-16
- Computational tests pass if scaled residuals are less than                16.0

================================================================================
T/V                N    NB     P     Q               Time                 Gflops
--------------------------------------------------------------------------------
WR00R2C4       20000   104     1     4              93.31             5.7164e+01
HPL_pdgesv() start time Tue Oct 31 14:56:12 2023

HPL_pdgesv() end time   Tue Oct 31 14:57:45 2023

--------------------------------------------------------------------------------
||Ax-b||_oo/(eps*(||A||_oo*||x||_oo+||b||_oo)*N)=   3.47090776e-03 ...... PASSED
================================================================================

Duležité je PASSED a pak výkon v GFlop/s, zde asi 57,2 GFlop/s. Na samém konci se objeví ještě shrnutí, tedy že všech 20 testů dopadlo dobře.

================================================================================

Finished      20 tests with the following results:
              20 tests completed and passed residual checks,
              0 tests completed and failed residual checks,
              0 tests skipped because of illegal input values.
--------------------------------------------------------------------------------

End of Tests.
================================================================================

Tady máme příklad výsledku, kdy byla detekovaná chyba (FAILED). Pokud počítač zatuhne nebo se restartuje tak to je potřeba také brát jako negativní výsledek:

================================================================================
T/V                N    NB     P     Q               Time                 Gflops
--------------------------------------------------------------------------------
WR00R2C4       20000    64     1     1             367.00             1.4534e+01
HPL_pdgesv() start time Mon Jun 13 06:53:23 2022

HPL_pdgesv() end time   Mon Jun 13 06:59:30 2022

--------------------------------------------------------------------------------
||Ax-b||_oo/(eps*(||A||_oo*||x||_oo+||b||_oo)*N)=   6.20248109e+07 ...... FAILED
||Ax-b||_oo  . . . . . . . . . . . . . . . . . =          14.699328
||A||_oo . . . . . . . . . . . . . . . . . . . =        5077.970709
||A||_1  . . . . . . . . . . . . . . . . . . . =        5073.290150
||x||_oo . . . . . . . . . . . . . . . . . . . =          21.018396
||x||_1  . . . . . . . . . . . . . . . . . . . =       86900.973187
||b||_oo . . . . . . . . . . . . . . . . . . . =           0.499989
================================================================================

Dobré je se podívat, jestli výsledné Flop/s zhruba odpovídají počtu jader a frekvenci. Menší výkon může znamenat thermal-throttling nebo jiný problém. Vezmeme například SandyBridge 4×3,2 GHz (bez HT). Podle tabulky na Wikipedii má jedno jádro SandyBridge ve FP64 na takt 8 operací. Dostáváme tedy teoreticky 8×4×3,2 GFlop/s a to je asi 102 GFlop/s. Ve skutečnosti na 4 jádrech dosáhneme asi 91 GFlops/s, to je dobré. Pokud chybně pustíme xhpl na všech 8 jádrech včetně HT, bude výkon nižší asi 84 GFlop/s.

Na Raspberry Pi 4 s 8 GB RAM (4× Cortex-A72 1,8 GHz) trvá jeden test zhruba 15 minut. Pokud používáte OpenMP, tak před testem se navíc jedním jádrem plní paměť maticí (asi 4 minuty) a po skončení testu se opět jedním jádrem kontroluje správnost (asi 5 minut). Dohromady tedy 20 testů poběží asi 8 hodin. Nejlepší výkon jsem zatím dostal asi 18,4 GFlop/s. To je asi 2,6 FP64 operací na takt a jádro (Wikipedie po opravě pro Cortex-A72 říká 4). Jeff Geerling pro Raspberry Pi 4 s 8 GB RAM naměřil v HPL 11,8 GFlop/s, ale on to pouští automaticky a nijak zvlášť neoptimalizuje.

Radaxa Rock 5B s 16 GB RAM, který má stejný SoC jako Orange Pi 5 (RK3588, 4× Cortex-A76 2,4 GHz + 4× Cortex-A55 1,8 GHz), mi dává jen na 4 velkých jádrech nejlepší výkon asi 66,0 GFlops/s. To je asi 6,9 FP64 operací na takt a jádro (Wikipedie pro Cortex-A76 říká 8). Zde jeden test trvá podobnou dobu kolem 15 minut. V příštím díle zkusíme zkombinovat velká jádra s malými, která samostatně dávají asi 18,4 GFlop/s. Výsledek očekáváme o něco nižší, než 66,0+18,4, protože některé zdroje počítače (například RAM) budou sdílené.

Jeff pro stejnou desku na všech jádrech, ale s 4 GB RAM naměřil 51,4 GFlop/s. Zajímavé je, že Orange Pi 5 také se 4 GB RAM je podle Jeffa rychlejší s 53,3 GFlop/s. Kromě toho je rychlejší i v Geekbench 5 a 6. Rozdíl je v DMC (Dynamic Memory Interface) v RK3588, který umí snižovat takt RAM z 2112 MHz až na 528 MHz. V jádře Orange Pi 5 DMC není aktivní a RAM jede stále na plný výkon. V Rock 5B se frekvence snižuje, čímž se ušetří v klidu asi 0,5–0,6 W. Ale zvyšování frekvence je příliš konzervativní a testy tím trpí. Řešením je povolit v DMC rychlejší zvyšování frekvence echo 25 >/sys/devices/platform/dmc/devfreq/dmc/upthreshold místo výchozích 40. Vypadá to, že v posledních jádrech od Radaxy to je již takhle opravené.

Kromě toho mají obě desky s RK3588 zvláštní jádro označené například 5.10.110–8, ale to nemá s oficiálním jádrem 5.10 mnoho společného. Jde o jádro z Android BSP (Board Support Package).  Na podpoře v oficiálním jádře se pracuje.

Raspberry Pi 5, na kterou ještě čekám (měla by dorazit příští týden?), má podle Jeffa 30,2 GFlop/s.

Mimochodem, v tabulce na Wikipedii byla chyba u ARM Cortex-A53, A55, A72, A73, A75 chybně uvedeno jen 2 FP64 operace na takt. Opravil jsem to na 4 jako u Cortex-A57.

Chyba na Wikipedii

Autor: Wikipedie

Po opravě

Autor: Wikipedia

Zároveň je potřeba si uvědomit, že Cortex-A53 (třeba Raspberry Pi 3) a Cortex-A55 (třeba Odroid M1, nebo malé jádra v Orange Pi 5) jsou in-order (instrukce se nepřeskupují) a bude tedy težké dosáhnout 4 FP64 operace za takt. Naproti tomu Cortex-A57, A72 (Raspberry Pi 4), A73 a A75 jsou již out-of-order (instrukce se automaticky za běhu přeskupují pro optimální výkon), kde dosažení 4 FP64 bude již snazší.

Na Raspberry Pi je možné se přesvědčit, jestli došlo ke snižování frekvence kvůli teplotě i nezávisle pomocí vcgencmd get_throttled. Výsledek by měl být po testu throttled=0x0. Zároveň hlídá i pokles napájecího napětí. Jinde je možné sledovat frekvenci i teplotu CPU během výpočtu třeba právě v htop.

bitcoin_skoleni

Optimalizace Flop/s

Takže máme ověřeno, že náš počítač či SBC je stabilní, nesnižuje frekvenci, případné přetaktování je stabilní, zdroj stačí dodávat proud i extrémně zatíženému procesoru, paměť je v pořádku (pokud jsme naplnili téměř celou paměť) a CPU počítá správné výsledky. Co dále? Můžeme zkusit dosáhnout co největšího výkonu Flop/s. K tomu bude potřeba experimentovat se souborem HPL.dat a také se způsobem, jak HPL spustit na více procesorech. To si detailně ukážeme v dalším díle.

Dopředu vám prozradím, že největší vliv má parametr Nb v HPL.dat. To je velikost bloků, do kterých se matice rozloží. Optimální hodnota závisí na velikosti vyrovnávací paměti, na počtu jader a bohužel na Ns. Dopředu se nedá moc říci, jaké Nb bude optimální. Je potřeba je vyzkoušet. Tak je ostatně i náš HPL.dat napsán. Ostatní parametry v souboru HPL.dat jsou myslím již docela optimální. Jejich popis najdete na oficiálních stránkách netlib.org a také v oficiálním FAQ.

Autor článku

První linux nainstaloval kolem roku 1994 a u něj zůstal. Později vystudoval fyziku a získal doktorát.