| 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 |
| | 101 | library(future.apply) |
| | 102 | |
| | 103 | ncores <- as.integer(Sys.getenv("SLURM_CPUS_PER_TASK")) |
| | 104 | |
| | 105 | plan(multisession, workers = ncores) |
| | 106 | cat("Future plan =", class(plan())[1], "\n") |
| | 107 | |
| | 108 | cat("SLURM_CPUS_PER_TASK =", ncores, "\n") |
| | 109 | cat("Workers =", nbrOfWorkers(), "\n") |
| | 110 | |
| | 111 | x <- iris[which(iris[,5] != "setosa"), c(1,5)] |
| | 112 | |
| | 113 | iterations <- 100000 |
| | 114 | |
| | 115 | chunks <- split( |
| | 116 | seq_len(iterations), |
| | 117 | cut(seq_len(iterations), ncores, labels = FALSE) |
| | 118 | ) |
| | 119 | |
| | 120 | part <- 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 | |
| | 144 | print(part) |
| | 145 | plan(sequential) |
| | 146 | }}} |
| | 147 | |
| | 148 | In 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 | |
| | 150 | Whereas 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 | |
| | 154 | Also, 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 |
| 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 | |
| | 169 | module load R/4.4.1 |
| | 170 | |
| | 171 | Rscript 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 |
| | 189 | Submitted batch job 3336943 |
| | 190 | [tulaneID@cypress1 ~]$seff 3336943 |
| | 191 | Job ID: 3336943 |
| | 192 | Cluster: cypress |
| | 193 | User/Group: cbaribault/hpcstaff |
| | 194 | State: RUNNING (exit code 0:0) |
| | 195 | Cores: 16 |
| | 196 | CPU Utilized: 0:00:00 |
| | 197 | CPU Efficiency: 0.0% of 00:00:32 core-walltime |
| | 198 | Memory Utilized: 0.00 GB |
| | 199 | Memory 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 |
| | 211 | JobID: 3336943 |
| | 212 | Node: cypress01-058 |
| | 213 | User: cbaribault |
| | 214 | Allocated CPUs: 16 |
| | 215 | Total %CPU: 1531.7 |
| | 216 | Total %MEM: 0.0 |
| | 217 | Core 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 |
| | 228 | top - 01:19:36 up 5 days, 14:05, 0 users, load average: 10.80, 3.41, 1.45 |
| | 229 | Tasks: 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 |
| | 231 | KiB Mem : 65879704 total, 60164396 free, 2180912 used, 3534396 buff/cache |
| | 232 | KiB 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 | # |
| | 285 | seffRunningPrototype() |
| | 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 | }}} |