Changes between Version 10 and Version 11 of Workshops/JobParallelism/WhileYourJobIsRunning


Ignore:
Timestamp:
09/02/2026 01:46:47 AM (43 hours ago)
Author:
Carl Baribault
Comment:

Removed multi-node for now and improved single-node example

Legend:

Unmodified
Added
Removed
Modified
  • Workshops/JobParallelism/WhileYourJobIsRunning

    v10 v11  
    9191=== Example 2: a running batch job using R requesting 1 node ===
    9292
    93 ==== Prepare sample R code ====
    94 
    95  For this and the following example, download and make a copy of the sample R code via the following.
    96 
    97 {{{
    98 [tulaneID@cypress1 ~]$git clone https://hidekiCCS:@bitbucket.org/hidekiCCS/hpc-workshop.git
    99 [tulaneID@cypress1 ~]$cp -r hpc-workshop/R/* .
    100 [tulaneID@cypress1 ~]$ls
    101 bootstrap.R  bootstrap.sh  bootstrapWargs.R  bootstrapWargs.sh  myRscript.R  slurmscript1  slurmscript2
    102 }}}
    103 
    104  For demonstration purposes, the downloaded and copied R script, '''bootstrap.R''' (see [wiki:cypress/R#PassingSLURMEnvironmentVariables here]), has been modified to run 1000000 (1M) samples rather than the original 10000 (10K) samples.
    105 
    106 {{{
    107 [tulaneID@cypress1 ~]$diff bootstrap.R hpc-workshop/R/bootstrap.R
    108 10c10
    109 < iterations <- 1000000# Number of iterations to run
    110 ---
    111 > iterations <- 10000# Number of iterations to run
    112 bootstrap.R  bootstrap.sh  bootstrapWargs.R  bootstrapWargs.sh  myRscript.R  slurmscript1  slurmscript2
    113 }}}
    114 
    115 ==== Submit the test batch job ====
    116 
    117  The job script, '''bootstrap.sh''', is requesting 1 node and 16 cores.
    118 
    119 {{{
    120 [tulaneID@cypress1 ~]$grep cpus-per-task bootstrap.sh
     93==== Prepare sample R script ====
     94
     95 For this example, we'll use the following R script in the file '''bootstrapFutureApply.R'''.
     96
     97 Note that we'll need to install the R package '''future.apply''' ahead of time.
     98
     99{{{
     100[tulaneID@cypress1 ~]$cat bootstrapFutureApply.R
     101library(future.apply)
     102
     103ncores <- as.integer(Sys.getenv("SLURM_CPUS_PER_TASK"))
     104
     105plan(multisession, workers = ncores)
     106cat("Future plan =", class(plan())[1], "\n")
     107
     108cat("SLURM_CPUS_PER_TASK =", ncores, "\n")
     109cat("Workers =", nbrOfWorkers(), "\n")
     110
     111x <- iris[which(iris[,5] != "setosa"), c(1,5)]
     112
     113iterations <- 100000
     114
     115chunks <- split(
     116    seq_len(iterations),
     117    cut(seq_len(iterations), ncores, labels = FALSE)
     118)
     119
     120part <- system.time({
     121    results <- future_lapply(
     122        chunks,
     123        function(idx) {
     124            local_results <- vector("list", length(idx))
     125            for (j in seq_along(idx)) {
     126                ind <- sample(100, 100, replace = TRUE)
     127                result1 <- glm(
     128                    x[ind,2] ~ x[ind,1],
     129                    family = binomial()
     130                )
     131
     132                local_results[[j]] <- coefficients(result1)
     133            }
     134
     135            do.call(cbind, local_results)
     136        },
     137        future.seed = TRUE
     138    )
     139
     140    r <- do.call(cbind, results)
     141
     142})[3]
     143
     144print(part)
     145plan(sequential)
     146}}}
     147
     148In contrast with previous examples in [[cypress/R#RunningaRscriptinBatchmode|Running R with Batch Mode]], '''bootstrapFutureApply.R''' above uses future.apply to execute larger pre-assigned chunks of iterations per worker, reducing scheduling overhead but making workload balance and result aggregation more explicit.
     149
     150Whereas the previous '''bootstrap.R''' uses doParallel/foreach to dynamically distribute individual bootstrap iterations across worker processes with excellent CPU utilization yet with significant overhead spent in the function '''cbind''' for allocating and combining the results of the individual samples.
     151 
     152==== Prepare sample job script ====
     153
     154Also, we'll use the following job script, '''bootstrapFutureApply.sh''', using the latest available version of R, 4.4.1, and is requesting 1 node and 16 cores for 1 hour.
     155
     156{{{
     157[tulaneID@cypress1 ~]$cat bootstrapFutureApply.sh
     158#!/bin/bash
     159#SBATCH --qos=normal            # Quality of Service
     160#SBATCH --partition=centos7     # Partition
     161##SBATCH --qos=workshop         # Quality of Service
     162##SBATCH --partition=workshop   # Partition
     163#SBATCH --job-name=R            # Job Name
     164#SBATCH --time=1:00:00          # WallTime
     165#SBATCH --nodes=1               # Number of Nodes
     166#SBATCH --ntasks-per-node=1     # Number of Tasks per Node
    121167#SBATCH --cpus-per-task=16      # Number of threads per task (OMP threads)
    122 [tulaneID@cypress1 ~]$sbatch bootstrap.sh
    123 Submitted batch job 3289740
    124 [tulaneID@cypress1 ~]$squeue -u $USER
    125              JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
    126            3289740 workshop7        R tulaneID  R       0:00      1 cypress01-009
    127 # note - the following result after many attempts of top with result only %CPU ~= 100.0
    128 [tulaneID@cypress1 ~]$ssh cypress01-009 'top -b -n 1 -u $USER' | \
    129 awk 'NR > 7 { sum_cpu += $9; sum_mem += $10 } \
    130 END { print "Total %CPU:", sum_cpu; print "Total %MEM:", sum_mem }'
    131 Total %CPU: 1556.6
    132 Total %MEM: 3.3
    133 }}}
    134 
    135 ==== Calculate core efficiency ====
    136 
    137  The resulting core efficiency is
    138 
    139 {{{
    140 [tulaneID@cypress1 ~]$bc <<< "scale=2;(1556.3 / 100) / 16"
    141 .97
    142 }}}
    143 
    144  This is quite close to the ideal value, 1 - fairly good usage of the node's 16 requested cores.
    145 
    146 === Example 3: same R code requesting 2 nodes - 1 node unused ===
    147 
    148  The following uses the same the R sampling code as above (see [wiki:cypress/R#PassingSLURMEnvironmentVariables here]) requesting 16 cores and 2 nodes (--nodes - one of which is unused.
    149 
    150 {{{
    151 [tulaneID@cypress1 ~]$diff bootstrap.sh bootstrap2nodes.sh
    152 7c7
    153 < #SBATCH --nodes=1               # Number of Nodes
    154 ---
    155 > #SBATCH --nodes=2               # Number of Nodes
    156 [tulaneID@cypress1 ~]$sbatch bootstrap2nodes.sh
    157 Submitted batch job 3289779
    158 [tulaneID@cypress1 ~]$squeue -u $USER
    159              JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
    160            3289779 workshop7        R tulaneID  R       0:03      2 cypress01-[009-010]
    161 # use the following to list job nodes separately
    162 [tulandID@cypress1 R]$scontrol show hostname cypress01-[009-010]
    163 cypress01-009
    164 cypress01-010
    165 [tulaneID@cypress1 ~]$ssh cypress01-009 'top -b -n 1 -u $USER' | \
    166 awk 'NR > 7 { sum_cpu += $9; sum_mem += $10 } \
    167 END { print "Total %CPU:", sum_cpu; print "Total %MEM:", sum_mem }'
    168 Total %CPU: 1587.6
    169 Total %MEM: 3.3
    170 [tulaneID@cypress1 ~]$ssh cypress01-010 'top -b -n 1 -u $USER' | \
    171 awk 'NR > 7 { sum_cpu += $9; sum_mem += $10 } \
    172 END { print "Total %CPU:", sum_cpu; print "Total %MEM:", sum_mem }'
    173 Total %CPU: 13.3
    174 Total %MEM: 0
    175 }}}
    176 
    177  The resulting core efficiency for each of the two requested nodes is
    178 {{{
    179 [tulaneID@cypress1 ~]$bc <<< "scale=3; (1587.6 / 100) / 16"
    180 .992
    181 [tulaneID@cypress1 ~]$bc <<< "scale=3; (13.3 / 100) / 16"
    182 .008
    183 }}}
    184  Result:
    185  * On the first node, cypress01-009, usage is '''nearly ideal''' (.992 ~= 1.0).
    186  * On the second node, cypress01-010, usage is '''nearly non-existent''' (.008 ~= 0.0).
    187 
    188 === Running R on multiple nodes ===
    189 
    190  For information on how to run R code on multiple nodes on a SLURM cluster, see [wiki:/cypress/R#RunningRonmultiplenodes Running R on multiple nodes].
     168
     169module load R/4.4.1
     170
     171Rscript bootstrapFutureApply.R
     172}}}
     173
     174 '''For workshop''' modify the above using the comment character '''#''' in order to use '''workshop''' and '''workshop7''' for '''qos''' and '''partition''', respectively
     175
     176{{{
     177...
     178##SBATCH --qos=normal            # Quality of Service
     179##SBATCH --partition=centos7     # Partition
     180#SBATCH --qos=workshop           # Quality of Service
     181#SBATCH --partition=workshop7    # Partition
     182...
     183}}}
     184
     185==== Submit the test batch job and analyze usage ====
     186
     187{{{
     188[tulaneID@cypress1 ~]$sbatch bootstrapFutureApply.sh
     189Submitted batch job 3336943
     190[tulaneID@cypress1 ~]$seff 3336943
     191Job ID: 3336943
     192Cluster: cypress
     193User/Group: cbaribault/hpcstaff
     194State: RUNNING (exit code 0:0)
     195Cores: 16
     196CPU Utilized: 0:00:00
     197CPU Efficiency: 0.0% of 00:00:32 core-walltime
     198Memory Utilized: 0.00 GB
     199Memory Efficiency: 0.0% of requested per-CPU memory x 16
     200------------------------------------------------------------
     201}}}
     202
     203 Unfortunately the seff command is reports zero usage of core and memory due to a lack of information available on the running job in the SLURM job database.
     204
     205==== For a running job ====
     206
     207 Until we can enhance the local seff command, we can define and use a shell function, '''seffRunningPrototype''', shown further below as a workaround for now.
     208
     209{{{
     210[tulaneID@cypress1 ~]$seffRunningPrototype 3336943
     211JobID: 3336943
     212Node: cypress01-058
     213User: cbaribault
     214Allocated CPUs: 16
     215Total %CPU: 1531.7
     216Total %MEM: 0.0
     217Core efficiency: 0.96
     218}}}
     219
     220 Here admittedly, the memory usage is not optimal, but the core efficiency is quite good at .96 - near the ideal 1.00.
     221
     222==== Correlate with results from top command ====
     223
     224 Here also is the result of running the top command on the running job's compute node, which correlates fairly well with the result from above.
     225
     226{{{
     227[tulaneID@cypress1 ~]$ssh cypress01-058 top -b -n 1 -u cbaribault
     228top - 01:19:36 up 5 days, 14:05,  0 users,  load average: 10.80, 3.41, 1.45
     229Tasks: 472 total,  17 running, 455 sleeping,   0 stopped,   0 zombie
     230%Cpu(s): 80.7 us,  0.3 sy,  0.0 ni, 19.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
     231KiB Mem : 65879704 total, 60164396 free,  2180912 used,  3534396 buff/cache
     232KiB Swap: 12582908 total, 12582908 free,        0 used. 62270792 avail Mem
     233
     234   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
     235 57357 cbariba+  20   0  241000  85116   7420 R 100.0  0.1   0:54.25 R
     236 57359 cbariba+  20   0  240856  84988   7420 R 100.0  0.1   0:54.41 R
     237 57360 cbariba+  20   0  241020  85076   7420 R 100.0  0.1   0:54.25 R
     238 57361 cbariba+  20   0  240996  85092   7420 R 100.0  0.1   0:54.15 R
     239 57362 cbariba+  20   0  240980  85020   7420 R 100.0  0.1   0:54.29 R
     240 57363 cbariba+  20   0  240860  85008   7420 R 100.0  0.1   0:54.12 R
     241 57364 cbariba+  20   0  240872  84984   7420 R 100.0  0.1   0:54.13 R
     242 57365 cbariba+  20   0  241008  85128   7420 R 100.0  0.1   0:54.12 R
     243 57366 cbariba+  20   0  241000  85172   7420 R 100.0  0.1   0:54.29 R
     244 57367 cbariba+  20   0  240872  85012   7420 R 100.0  0.1   0:54.12 R
     245 57369 cbariba+  20   0  240992  85040   7420 R 100.0  0.1   0:54.21 R
     246 57355 cbariba+  20   0  240988  84988   7420 R  93.8  0.1   0:54.13 R
     247 57356 cbariba+  20   0  241016  85124   7420 R  93.8  0.1   0:54.18 R
     248 57358 cbariba+  20   0  241132  84996   7420 R  93.8  0.1   0:54.18 R
     249 57368 cbariba+  20   0  240872  84976   7420 R  93.8  0.1   0:54.21 R
     250 57370 cbariba+  20   0  240972  85020   7420 R  93.8  0.1   0:54.15 R
     251 57307 cbariba+  20   0  237004  80980   7316 S   6.2  0.1   0:17.80 R
     252 58217 cbariba+  20   0   69372   2416   1508 R   6.2  0.0   0:00.03 top
     253 57278 cbariba+  20   0    9568   1356   1124 S   0.0  0.0   0:00.00 slurm_scr+
     254 58216 cbariba+  20   0  180444   2480   1128 S   0.0  0.0   0:00.00 sshd
     255
     256}}}
     257
     258
     259 Here is the definition of '''seffRunningPrototype''' in the source file seffRunningPrototype.sh.
     260
     261{{{
     262[tulaneID@cypress1 ~]$cat seffRunningPrototype.sh
     263# CAVEAT:
     264# This function measures ALL processes owned by the user on the node.
     265# If the user has multiple Slurm jobs, interactive sessions, Jupyter
     266# notebooks, or other processes on the same node, CPU and memory
     267# utilization may be inflated.
     268#
     269# For true job-specific accounting, process ownership should be
     270# restricted to the Slurm job's process tree (slurmstepd descendants).
     271# Show live CPU and memory utilization for a running Slurm job.
     272#
     273# Notes:
     274# - CPU utilization is gathered from all processes owned by the job's user
     275#   on the allocated node.
     276# - This assumes the user is not running multiple jobs on the same node.
     277# - Core efficiency is computed as:
     278#
     279#       total_cpu_percent / (100 * allocated_cpus)
     280#
     281#   Examples:
     282#       1600% CPU on a 16-core allocation = 1.00 efficiency
     283#        800% CPU on a 16-core allocation = 0.50 efficiency
     284#
     285seffRunningPrototype()
     286{
     287    # Slurm job ID supplied by the caller.
     288    local jobid="$1"
     289
     290    # Query Slurm for:
     291    #   node  = allocated compute node
     292    #   user  = job owner
     293    #   cpus  = CPUs allocated to the job
     294    read node user cpus <<< $(
     295        squeue -j "$jobid" \
     296               --noheader \
     297               --format="%.100N %.30u %.10C"
     298    )
     299
     300    # Ensure the job was found.
     301    if [ -z "$node" ]; then
     302        echo "Job $jobid not found."
     303        return 1
     304    fi
     305
     306    echo "JobID: $jobid"
     307    echo "Node: $node"
     308    echo "User: $user"
     309    echo "Allocated CPUs: $cpus"
     310
     311    # Execute on the compute node:
     312    #
     313    #   ps -u <user>
     314    #
     315    # returns one line per process with:
     316    #   %CPU %MEM
     317    #
     318    # AWK sums CPU and memory across all matching processes.
     319    ssh "$node" "
     320        ps -u $user -o pcpu=,pmem= |
     321        awk -v cpus=$cpus '
     322        {
     323            cpu += \$1
     324            mem += \$2
     325        }
     326        END {
     327            printf \"Total %%CPU: %.1f\n\", cpu
     328            printf \"Total %%MEM: %.1f\n\", mem
     329            printf \"Core efficiency: %.2f\n\", cpu / (100 * cpus)
     330        }'
     331    "
     332}
     333}}}