Accelerarea modelelor Stan pentru dezvoltatorii de pachete R

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

(Acest articol a fost publicat pentru prima dată pe R | Dr Tom Palmerși cu amabilitate a contribuit la R-bloggeri). (Puteți raporta problema legată de conținutul acestei pagini aici)


Doriți să vă distribuiți conținutul pe R-bloggeri? dați clic aici dacă aveți un blog, sau aici dacă nu aveți.

Introducere

În slujba mea anterioară, computerul meu de lucru era un desktop Windows – da, acelea au fost zilele dinaintea laptopurilor și a hotdesking-ului!

Doctorandul meu era interesat de metodele bayesiene și am creat un pachet R care includea câteva modele Stan. Am fost întotdeauna frustrat de cât de încet au fost compilate pe mașinile noastre Windows. Câțiva ani mai târziu, când am primit un MacBook Air, am fost șocat de cât de repede au compilat.

Pe mașina mea Windows, pachetul nostru mrbayes durează 3 minute și 55 de secunde pentru compilare și instalare. Pe MacBook Air meu M4 durează 1 minut și 16 secunde.

Următoarele sfaturi arată cum să îmbunătățiți aceste timpi.

Pentru a genera cronometrarile pe care le-am folosit

time R CMD INSTALL --preclean .

Câștig mare 1: Activați compilațiile paralele cu MAKEFLAGS variabila de mediu

Setați MAKEFLAGS variabilă de mediu în dvs ~/.Renviron fişier. Aceasta controlează câte make joburile rulează concomitent. Alegeți un număr nu mai mare decât numărul de nuclee de procesare pe care le are mașina dvs. Pentru a găsi această cursă

# Windows - in a Git Bash shell
echo $NUMBER_OF_PROCESSORS

# macOS
sysctl -n hw.logicalcpu

# Ubuntu Linux
nproc

Un punct de plecare rezonabil este numărul dvs. de bază, sau câteva mai puține pentru a lăsa spațiu liber pentru orice altceva faceți în timpul unei compilații. De exemplu,

# In ~/.Renviron
MAKEFLAGS=-j6

Închideți și reporniți R/RStudio după efectuarea acestei modificări.

Pe mașina mea Windows, acest lucru a redus construcția de la 3:55 la 1:15. Pentru a vă găsi propriul punct favorabil în mod empiric, vedeți exemplul de la sfârșitul Big win 2.

Câștig mare 2: activați memoria cache a compilatorului C/C++ folosind ccache

Instala
ccachemi se pare cel mai ușor să folosești un manager de pachete, de exemplu,

# macOS
brew install ccache

# Ubuntu/Debian Linux
apt install ccache

# Windows
winget install ccache

Indiferent de metoda de instalare pe care o utilizați, asigurați-vă ccache este pe tine PATH după instalare. Puteți testa cu, să zicem,

ccache --version

Pentru a activa ccachepe macOS și Linux, aceasta intră ~/.R/Makevars; pe Windows este ~/.R/Makevars.win (creați directorul și fișierul dacă nu există), setați

# macOS
CC = ccache clang
CXX = ccache clang++
CXX17 = ccache clang++

# Windows and Linux
# Most Linux users will be on gcc by default
# Change to clang if you're using that
CC = ccache gcc
CXX = ccache g++
CXX17 = ccache g++

După o primă rulare de compilare pentru cache-ul care urmează să fie generat, compilațiile ulterioare sunt mult mai rapide.

  • Windows, a doua compilație: 18 secunde
  • M4 MacBook Air, a doua compilație: 5 secunde

Poate mai important, dacă, să zicem, pachetul tău are 5 modele și modifici codul doar pentru unul dintre ele, ccache știe să folosească memoria cache pentru cele 4 modele neschimbate.

  • Windows, a doua compilație, doar 1 model editat: 1 minut și 10 secunde
  • M4 MacBook Air, a doua compilație, doar 1 model editat: 19 secunde

Puteți verifica ccache funcţionează, observând scăderea timpului şi verificând ieşirea de

ccache -s

De asemenea, este util să zeroi statisticile ccache înainte de o rulare de sincronizare cu

ccache -z

Testarea care dintre modelele dvs. durează cel mai mult pentru compilare

Iată un script rapid pentru a testa ce model durează cel mai mult pentru compilare. Salvează-l așa cum se spune test.sh la nivelul superior al depozitului dvs. și adăugați ^test.sh$ la dvs .Rbuildignore fișier (pentru a evita un R CMD check NOTĂ despre fișierele necunoscute la nivelul superior).

for model in inst/stan/*.stan; do
  cp "$model" "$model.bak"
  # Insert at the top of the file
  sed -i "1i // benchmark $(date +%s%N)" "$model"
  ccache -z
  SECONDS=0
  R CMD INSTALL --preclean . >/dev/null 2>&1
  echo "$(basename $model): ${SECONDS}s"
  ccache -s | grep -E "Hits|Misses" | head -2
  mv "$model.bak" "$model"
done

Găsindu-vă MAKEFLAGS punct dulce

Cu ccache instalat, acum puteți compara diferite -jN valorile curat (the ccache -C apelurile asigură că fiecare rulare este o compilare rece, astfel încât măsurați costul compilației brute mai degrabă decât accesările cache). Puteți crește secvența de numere până la numărul de nuclee de procesare pe care le are mașina dvs.

for j in 1 2 3 4 6 8 10; do
  ccache -C >/dev/null
  echo "=== -j$j ==="
  SECONDS=0
  MAKEFLAGS=-j$j R CMD INSTALL --preclean . >/dev/null 2>&1
  echo "elapsed: ${SECONDS}s"
done

Timingurile de pe MacBook Air au fost

=== -j1 ===
elapsed: 76s
=== -j2 ===
elapsed: 48s
=== -j3 ===
elapsed: 35s
=== -j4 ===
elapsed: 36s
=== -j6 ===
elapsed: 27s
=== -j8 ===
elapsed: 27s
=== -j10 ===
elapsed: 28s

MacBook Air meu are 10 nuclee, dar doar 4 dintre acestea sunt nuclee de performanță, așa că m-am hotărât -j6 pentru că acolo s-au stabilit calendarele mele – și îmi lasă spațiu liber pentru mine inevitabil să-mi verific e-mailul în timpul unei compilații.

Marele câștig 3: combinarea acestora în fluxurile de lucru GitHub Actions

In mea .github/workflows/R-CMD-check.yaml Am pași pentru aceste accelerații. În primul rând, pentru a seta MAKEFLAGS.

      - name: Set parallel compilation flags (Linux and macOS)
        if: runner.os != 'Windows'
        shell: bash
        run: |
          NCPUS=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu)
          echo "Detected ${NCPUS} processors"
          echo "MAKEFLAGS=-j${NCPUS}" >> ~/.Renviron

      - name: Set parallel compilation flags (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          Write-Output "Detected $env:NUMBER_OF_PROCESSORS processors"
          Add-Content -Path "$HOME.Renviron" -Value "MAKEFLAGS=-j$env:NUMBER_OF_PROCESSORS"
      # ccache speeds up Stan model compilation dramatically on warm cache.
      # Note: Windows support via ccache-action is documented as "probably works"
      # rather than fully stable; if it causes issues, scope this step to non-Windows.
      - name: Setup ccache
        uses: hendrikmuhs/(email protected)
        with:
          # Key invalidates when Stan models or DESCRIPTION change.
          # Older caches partially seed new ones via restore-keys.
          key: ccache-${{ matrix.config.os }}-R-${{ matrix.config.r }}-${{ hashFiles('inst/stan/**/*.stan', 'DESCRIPTION') }}
          restore-keys: |
            ccache-${{ matrix.config.os }}-R-${{ matrix.config.r }}-
            ccache-${{ matrix.config.os }}-R-
          max-size: "2G"

      - name: Configure R to use ccache (Linux and macOS)
        if: runner.os != 'Windows'
        shell: bash
        run: |
          mkdir -p ~/.R
          if ( "$RUNNER_OS" = "macOS" ); then
            cat >> ~/.R/Makevars <<'EOF'
          CC = ccache clang
          CXX = ccache clang++
          CXX14 = ccache clang++
          CXX17 = ccache clang++
          CXX20 = ccache clang++
          EOF
          else
            cat >> ~/.R/Makevars <<'EOF'
          CC = ccache gcc
          CXX = ccache g++
          CXX14 = ccache g++
          CXX17 = ccache g++
          CXX20 = ccache g++
          EOF
          fi
          echo "--- ~/.R/Makevars ---"
          cat ~/.R/Makevars

      - name: Configure R to use ccache (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path "$HOME.R" | Out-Null
          $makevars = @"
          CC = ccache gcc
          CXX = ccache g++
          CXX14 = ccache g++
          CXX17 = ccache g++
          CXX20 = ccache g++
          "@
          Add-Content -Path "$HOME.RMakevars.win" -Value $makevars
          Write-Output "--- ~/.R/Makevars.win ---"
          Get-Content "$HOME.RMakevars.win"

Puteți vedea fișierul complet în depozitul meu.

Acest lucru mi-a redus cea mai recentă rulare a ubuntu pentru lansarea r de la 7 minute și 30 de secunde la 4 minute și 49 de secunde.

Câștig mare 4: Treci la clang

Am descoperit că trecând de la gcc la clang oferă o accelerare vizibilă; timpul de compilare single core a scăzut de la 3 minute și 55 de secunde la 3 minute plat pe computerul meu Windows.

Pentru a face acest lucru, trebuie să instalați clang. Pe Windows instalați clang în RTools45 — mai implicat decât pe Linux, dar fezabil.

# Windows within RTools45 Bash shell
# Launch C:rtools45ucrt64.exe
# You may need to close and reopen the shell after the first command
pacman -Syu
pacman -S mingw-w64-ucrt-x86_64-clang

# Ubuntu/Debian Linux
sudo apt install clang

În acest moment, pe Windows rulează

which clang

ar trebui să se întoarcă /ucrt/bin/clang.

Comutați la clang în ~/.R/Makevars (dacă nu utilizați ccache ștergeți prefixul respectiv)

# On Linux
CC = ccache clang
CXX = ccache clang++
CXX14 = ccache clang++
CXX17 = ccache clang++
CXX20 = ccache clang++

si in ~/.R/Makevars.win pe Windows

# On Windows
CC = ccache C:/rtools45/ucrt64/bin/clang.exe
CXX = ccache C:/rtools45/ucrt64/bin/clang++.exe
CXX17 = ccache C:/rtools45/ucrt64/bin/clang++.exe

Utilizatorii Windows vor trebui să adauge următoarele la PATH

C:rtools45ucrt64bin
C:rtools45usrbin

Puteți verifica că lucrurile funcționează rulând

R CMD config CXX17

Cred că ai nevoie clang versiunea 18 sau mai recentă pentru a vedea accelerațiile.

Câștig mic 1: utilizatorii WSL ar trebui să utilizeze sistemul de fișiere nativ

În cadrul WSL este posibil să accesați fișiere din sistemul său nativ de fișiere Linux, adică din interiorul /home/user/...precum și pe sistemul de fișiere Windows, de exemplu, în /mnt/c/.... Cred că operațiunile cu fișierele sunt considerabil mai rapide în interior /home/user/....

Ghiciri naive care nu au făcut nicio diferență

Mă întrebam dacă rulează o compilație non-debug cu spune

pkgbuild::compile_dll(debug = FALSE)

ar accelera lucrurile. Se pare că nu. Pentru modelele Stan, cea mai mare parte a timpului este cheltuit în instanțierea șablonului C++ de către compilator, nu în trecerile de optimizare – așa că dezactivarea semnalizatoarelor de depanare sau scăderea nivelului de optimizare abia dacă ajută.

De asemenea, m-am întrebat dacă folosirea R pe Windows Subsystem pentru Linux ar accelera lucrurile doar în virtutea faptului că sunt pe Linux. Nu a făcut-o, timpuri folosind gcc pe Windows și WSL Ubuntu erau în esență identice. Avantajul utilizării WSL este că este mai ușor să treceți la utilizare clang pe Linux.

(Bani fără obiect) Câștig mare 5: Treci la un Apple Silicon Mac

Mac-urile Apple silicon au performanțe excelente cu un singur thread, arhitectura lor unificată de memorie are o lățime de bandă foarte mare, au cache-uri L1 și L2 mari și SSD-uri NVMe rapide. Împreună, acestea produc timpi foarte rapidi de compilare a modelelor Stan, chiar și pe cele mai mici Mac-uri Apple Silicon.

Rezumat

Pe scurt, cinci câștiguri mari și un câștig mic pentru accelerarea compilației modelelor Stan în pachetele R.

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.