These examples show how to use the package future
on the Harvard University FASRC Cannon cluster.
- Example of
future
planssequential
,multisession
, andmulticore
future_slow_square.R
: R source coderun_future.sbatch
: Example batch-job submission scriptfuture_slow_square.Rout
: Example output
- Example nested futures with
batchtools_slurm
andmulticore
future_hybrid.R
: R source codefuture_slurm.tmpl
: SLURM batch-job submit script template used bybatchtools_slurm
run_hybrid.sbatch
: Example batch-job submission scriptfuture_hybrid.Rout
: Example output from R codeRhybrid.log
: Example output frombatchtools_slurm
For more examples, refer to:
The package future
has various plan
to parallelize an R code:
sequential
- synchronous
- no parallelization
- single node
multisession
- asynchronous
- background parallelization (no forking): "futures" are evaluated in independent processes as if you are running multiple R/RStudio sessions
- single node
multicore
- asynchronous
- parallelization with forked R processes
- shared memory
- single node
cluster
- asynchronous
- multi node
- external R sessions on current, local, and/or remote nodes
- uses
ssh
to communicate with remote nodes
batchtools
- asynchronous
- multi node
- uses batch submit scripts to submit jobs to remote nodes; specifically on Cannon, we use
batchtools_slurm
In addition to the plan
listed above, future
also allows nested futures. For example, to achieve a hybrid job with multi-node and shared memory (analogous to the more traditional MPI
+OpenMP
hybrid configuration), we can use plan(list(batchtools_slurm,multicore))
as shown in the example below.
Request an interactive node
salloc -p test --time=0:30:00 --mem=4000
Load required software modules.
# R libraries
module load R/4.4.1-fasrc01
Create directory for customized R packages and set it up as a local R-library location.
mkdir -p $HOME/apps/R/4.4.1
export R_LIBS_USER=$HOME/apps/R/4.4.1
Install future
and other packages inside the R shell
> install.packages("future")
Installing package into ‘/n/home05/username/apps/R/4.4.1’
(as ‘lib’ is unspecified)
... omitted output ...
Selection: 78
also installing the dependencies ‘globals’, ‘listenv’, ‘parallelly’
... omitted output ...
* DONE (future)
The downloaded source packages are in
‘/tmp/RtmpB3XhHu/downloaded_packages’
# Install packages necessary for examples presented here:
> install.packages("data.table")
> install.packages("future.apply")
> install.packages("tictoc")
> install.packages("doFuture")
> install.packages("future.batchtools")
... omitted output ...
# Exit R shell
> q()
Save workspace image? [y/n/c]: n
slow_square
example with function future_lapply
File future_slow_square.R
:
# This example was adapted from https://grantmcdermott.com/ds4e/parallel.html
library("data.table")
library("future")
library("future.apply")
library("tictoc")
# comment out one future plan
#plan(sequential) # synchronous
#plan(multisession) # asynchronous, no forking
plan(multicore) # asynchronous, with forking
slow_square =
function(x = 1) {
x_sq = x^2
d = data.frame(value = x, value_squared = x_sq)
Sys.sleep(2)
return(d)
}
tic()
availableCores()
future_ex = future_lapply(1:12, slow_square)
toc(log = TRUE)
File run_future.sbatch
:
#!/bin/bash
#SBATCH --job-name=Rfuture
#SBATCH --output=test_%j.out
#SBATCH --error=test_%j.err
#SBATCH --partition=test
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=4
#SBATCH --mem-per-cpu=4000
#SBATCH -t 0-00:10 # Runtime in D-HH:MM, minimum of 10 minutes
# load modules
module load R/4.4.1-fasrc01
# set R lirbary path
export R_LIBS_USER=$HOME/apps/R/4.4.1
### slow_square_multisession.R
# run R program and keep output and error messages in slow_square_multisession.Rout
Rscript --vanilla future_slow_square.R > future_slow_square.Rout 2>&1
# run R program and keep output in future_slow_square.Rout
# and error messages in error file
#Rscript --vanilla future_slow_square.R > future_slow_square.Rout
Submit a job to run future_slow_square.R
in a compute node:
sbatch run_future.sbatch
# sequential plan
$ cat future_slow_square.Rout
cgroups.cpuset
4
24.144 sec elapsed
# multisession plan
$ cat future_slow_square.Rout
cgroups.cpuset
4
7.178 sec elapsed
# multicore plan
$ cat future_slow_square.Rout
cgroups.cpuset
4
6.225 sec elapsed
batchtools_slurm
submits jobs that will run future_lapply
using the multicore
File future_hybrid.R
:
library("data.table")
library("future")
library("future.apply")
library("future.batchtools")
library("doFuture")
library("tictoc")
options(parallelly.debug = TRUE)
# simple square fucntion to show how to use future
slow_square =
function(x = 1) {
x_sq = x^2
d = data.frame(value = x, value_squared = x_sq)
Sys.sleep(2)
return(d)
}
# sbatch contains the details of the job submission script that will populate
# the template "future_slurm.tmpl"
sbatch <- tweak(batchtools_slurm,
template = "future_slurm.tmpl", # file that will be populated
resources = list(job_name = "Rhybrid", # name of the job
n_cpu = 8, # number of cores per node
queue = "test", # which partition
walltime = "00:10:00", # walltime <hh:mm:ss>
mem_cpu = 1000, # memory per core
nodes = 1, # number of nodes
log_file = "Rhybrid.log")) # name of log file
# The first level of futures submits jobs to the cluster using batchtools.
# The second level of futures uses multicore, where the number of parallel
# processes is automatically decided based on what the cluster grants to
# each compute node.
plan(list(sbatch,multicore))
# function to allow doFuture to recognize the number available cores
myCores <- registerDoFuture()
# The first level of futures is parallelized using "dopar". future recognizes
# "dopar" as the first level of parallelization and it uses batchtools to
# submit all iterations as jobs to the cluster. In this example, 3 jobs are
# simultaneously submitted
results <- foreach(i=1:3) %dopar% {
tic()
c <- availableCores()
print(c)
# second level of future uses future_apply to run a multicore process
future_results <- future_lapply(1:12, slow_square)
toc(log = TRUE)
}
This is the file future_slurm.tmpl
that is populated and submitted by batchtools_slurm
.
## Default resources can be set in your .batchtools.conf.R by defining the variable
## 'default.resources' as a named list.
#!/bin/sh
#SBATCH --job-name <%= resources$job_name %> ## Name of the job
#SBATCH --ntasks-per-node <%= resources$n_cpu %> ## number of processes per node
#SBATCH --partition <%= resources$queue %> ## Job queue
#SBATCH --time <%= resources$walltime %> ## walltime in hh:mm:ss
#SBATCH --mem-per-cpu <%=resources$mem_cpu %> ## min memory per core
#SBATCH --nodes <%= resources$nodes %> ## if 1 put load on one node
#SBATCH --output <%= resources$log_file %> ## Output is sent to logfile, stdout + stderr by default
## Export value of DEBUGME environment var to slave
export DEBUGME=<%= Sys.getenv("DEBUGME") %>
# Load required software modules
module load R/4.4.1-fasrc01
# Set up R library
export R_LIBS_USER=$HOME/apps/R/4.4.1
# Run R process
Rscript -e 'batchtools::doJobCollection("<%= uri %>")'
This is the file run_hybrid.sbatch
that you submit.
#!/bin/bash
#SBATCH -J multinode
#SBATCH -o test.out
#SBATCH -e test.err
#SBATCH -p test
#SBATCH -N 1
#SBATCH --ntasks-per-node=1
#SBATCH -t 30
#SBATCH --mem-per-cpu=4000
# Load required software modules
module load R/4.4.1-fasrc01
# Set up R library
export R_LIBS_USER=$HOME/apps/R/4.4.1
# Run program
Rscript --vanilla future_hybrid.R > future_hybrid.Rout 2>&1
Submit a job to run future_hybrid.R
:
sbatch run_hybrid.sbatch
The output from the R code future_hybrid.R
:
$ cat future_hybrid.Rout
Loading required package: foreach
cgroups.cpuset
8
4.51 sec elapsed
cgroups.cpuset
8
4.428 sec elapsed
cgroups.cpuset
8
4.432 sec elapsed
And the output from batchtools_slurm
:
$ cat Rhybrid.log
### [bt]: This is batchtools v0.9.15
### [bt]: Starting calculation of 1 jobs
### [bt]: Setting working directory to '/n/home_rc/paulasan/myDocNotes/R/future/multinode'
Loading required package: doFuture
Loading required package: foreach
Loading required package: future
Loading required package: tictoc
### [bt]: Memory measurement disabled
### [bt]: Starting job [batchtools job.id=1]
### [bt]: Setting seed to 10696 ...
### [bt]: Job terminated successfully [batchtools job.id=1]
### [bt]: Calculation finished!
### [bt]: Job terminated successfully [batchtools job.id=1]
### [bt]: Calculation finished!
### [bt]: Job terminated successfully [batchtools job.id=1]
### [bt]: Calculation finished!
Note that the script run_hybrid.sbatch
that you submit may request less --ntasks-per-node
than the script submitted by batchtools_slurm
. In this example, run_hybrid.sbatch
requests --ntasks-per-node=1
, but the template future_slurm.tmpl
is populated with --ntasks-per-node=8
(n_cpu=8
in future_hybrid.R
). We can use sacct
to see how each job requests the appropriate number of cores:
$ sacct
JobID JobName Partition Account AllocCPUS State ExitCode
------------ ---------- ---------- ---------- ---------- ---------- --------
11411804 multinode test rc_admin 1 RUNNING 0:0
11411804.ba+ batch rc_admin 1 RUNNING 0:0
11411804.ex+ extern rc_admin 1 RUNNING 0:0
11411805 Rhybrid test rc_admin 8 RUNNING 0:0
11411805.ba+ batch rc_admin 8 RUNNING 0:0
11411805.ex+ extern rc_admin 8 RUNNING 0:0
11411806 Rhybrid test rc_admin 8 RUNNING 0:0
11411806.ba+ batch rc_admin 8 RUNNING 0:0
11411806.ex+ extern rc_admin 8 RUNNING 0:0
11411807 Rhybrid test rc_admin 8 RUNNING 0:0
11411807.ba+ batch rc_admin 8 RUNNING 0:0
11411807.ex+ extern rc_admin 8 RUNNING 0:0