Running Julia in batch mode

Questions

  • What is a batch job?

  • How to make a batch job?

Objectives

  • Short introduction to SLURM scheduler

  • Show structure of a batch script

  • Try example

Compute allocations in this workshop

  • Rackham: naiss2024-22-1202

  • Kebnekaise: hpc2n2024-114

  • Cosmos: lu2024-7-80

Storage space for this workshop

  • Rackham: /proj/r-py-jl-m-rackham

  • Kebnekaise: /proj/nobackup/r-py-jl-m

  • Cosmos: <your own good place>

Warning

  • Any longer, resource-intensive, or parallel jobs must be run through a batch script.

The batch system used at LUNARC, UPPMAX and HPC2N is called SLURM.

SLURM is an Open Source job scheduler, which provides three key functions

  • Keeps track of available system resources

  • Enforces local system resource usage and job scheduling policies

  • Manages a job queue, distributing work across resources according to policies

In order to run a batch job, you need to create and submit a SLURM submit file (also called a batch submit file, a batch script, or a job script). Guides and documentation at: HPC2N, UPPMAX, and LUNARC.

Workflow

  • Write a batch script

    • Inside the batch script you need to load the modules you need, for instance Julia

    • Possibly activate an isolated/virtual environment to access own-installed packages

    • Ask for resources depending on if it is a parallel job or a serial job, if you need GPUs or not, etc.

    • Give the command(s) to your Julia script

  • Submit batch script with sbatch <my-julia-script.sh>

Common file extensions for batch scripts are .sh or .batch, but they are not necessary. You can choose any name that makes sense to you.

Useful commands to the batch system

  • Submit job: sbatch <jobscript.sh>

  • Get list of your jobs: squeue -u <username>

  • Check on a specific job: scontrol show job <job-id>

  • Delete a specific job: scancel <job-id>

  • Useful info about a job: sacct -l -j <job-id> | less -S

  • Url to a page with info about the job (Kebnekaise only): job-usage <job-id>

Examples of batch scripts for Julia

Serial code

Short serial example script for running on Rackham with Julia v. 1.8.5

#!/bin/bash -l                 # -l cleans the environment in the batch job, recommended at UPPMAX
#SBATCH -A naiss2024-22-1202    # your project_ID
#SBATCH --time=00:10:00        # Asking for 10 minutes
#SBATCH -n 1                   # Asking for 1 core
#SBATCH --error=job.%J.err     # error file
#SBATCH --output=job.%J.out    # output file
ml julia/1.8.5 # Julia module

julia serial.jl              # run the serial script

Serial code + self-installed package in virt. env.

Short serial example for running on Rackham. Loading Julia v. 1.8.5 and using any Julia packages you have installed with virtual environment.

#!/bin/bash -l               # -l cleans the environment in the batch job, recommended at UPPMAX
#SBATCH -A naiss2024-22-1202   # Change to your own after the course
#SBATCH --time=00:10:00       # Asking for 10 minutes
#SBATCH -n 1                  # Asking for 1 core
#SBATCH --error=job.%J.err    # error file
#SBATCH --output=job.%J.out   # output file

ml julia/1.8.5                # Julia module

# Move to the directory where the ".toml" files for the environment are located
julia --project=. serial-env.jl  # run the script

If this works, you will see the installed packages in the output file. In the present case because I installed the DFTK package only in my-third-env environment, I can see the following output:

Status `path/Julia-Test/my-third-env/Project.toml`
[acf6eb54] DFTK v0.6.2

Parallel code

The Threaded and Distributed packages are included in the Base installation. However, in order to use MPI with Julia you will need to follow the next steps (only the first time):

# Load the tool chain which contains a MPI library
$ ml gcc/11.3.0 openmpi/4.1.3
# Load Julia
$ ml Julia/1.8.5
# Start Julia on the command line
$ julia
# Change to ``package mode`` and add the ``MPI`` package
(v1.8) pkg> add MPI
  • In the julian mode run these commands:

julia> using MPI
julia> MPI.install_mpiexecjl()
     [ Info: Installing `mpiexecjl` to `/home/u/username/.julia/bin`...
     [ Info: Done!
# Add the installed ``mpiexecjl`` wrapper to your path on the Linux command line
$ export PATH=~/.julia/bin:$PATH
# Now the wrapper should be available on the command line
# nr. of grid points
n = 100000

function integration2d_julia(n)
# interval size
h = π/n
# cummulative variable
mysum = 0.0
# regular integration in the X axis
for i in 0:n-1
    x = h*(i+0.5)
#   regular integration in the Y axis
    for j in 0:n-1
    y = h*(j + 0.5)
    mysum = mysum + sin(x+y)
    end
end
return mysum*h*h
end

res = integration2d_julia(n)
println(res)

The corresponding batch scripts for these examples are given here:

#!/bin/bash -l
#SBATCH -A naiss2024-22-1202
#SBATCH -J job
#SBATCH -n 1
#SBATCH --time=00:10:00
#SBATCH --error=job.%J.err
#SBATCH --output=job.%J.out

ml julia/1.8.5

# "time" command is optional
time julia serial.jl

GPU code

In order to use the NVIDIA GPUs with Julia, you will need to load a CUDA toolkit module on the cluster and install the CUDA package in Julia as in the next sequence of commands:

  • This can only be done on Snowy or Bianca.

  • Then either create an interactive session or make a batch job

  • CUDA is installed at system level so they do not need to be loaded.

  • On snowy

$ interactive -A <proj> -n 1 -M snowy --gres=gpu:1  -t 3:00:00

$ ml Julia/1.8.5   # Julia version
$ julia
(v1.8) pkg> add CUDA
    Updating registry at `~/.julia/registries/General.toml`
    Resolving package versions...
    Installed CEnum ───────── v0.4.2
    ...

Once this initial setting is completed, you will be able to use the GPUs available on the cluster. Here, there is a simple example for computing a matrix-matrix multiplication. As a reference point, we show the simulation on CPUs as well.

Short GPU example for running on Snowy.

#!/bin/bash -l
#SBATCH -A naiss2024-22-1202    # your project_ID
#SBATCH -M snowy
#SBATCH -p node
#SBATCH --gres=gpu:1
#SBATCH -N 1
#SBATCH --job-name=juliaGPU         # create a short name for your job
#SBATCH --time=00:15:00          # total run time limit (HH:MM:SS)
#SBATCH --qos=short              # if test run t<15 min
#SBATCH --mail-type=begin        # send email when job begins
#SBATCH --mail-type=end          # send email when job ends

module load julia/1.8.5      # system CUDA works as of today
julia script-gpu.jl

Cluster Managers

The package ClusterManagers.jl allows you to submit expensive parts of your simulation to the batch queue in a more interactive manner than by using batch scripts. This can useful, for instance if you are developing some code where just specific parts are computationally heavy while the rest is related to data analysis or visualization. In order to use this package, you should add it in a Julia session.

using Distributed, ClusterManagers
# Adapted from: https://github.com/JuliaParallel/ClusterManagers.jl
# Arguments to the Slurm srun(1) command can be given as keyword
# arguments to addprocs.  The argument name and value is translated to
# a srun(1) command line argument as follows:
# 1) If the length of the argument is 1 => "-arg value",
#    e.g. t="0:1:0" => "-t 0:1:0"
# 2) If the length of the argument is > 1 => "--arg=value"
#    e.g. time="0:1:0" => "--time=0:1:0"
# 3) If the value is the empty string, it becomes a flag value,
#    e.g. exclusive="" => "--exclusive"
# 4) If the argument contains "_", they are replaced with "-",
#    e.g. mem_per_cpu=100 => "--mem-per-cpu=100"
# Example: add 2 processes, with your project ID, allocated 5 min, and 2 cores
addprocs(SlurmManager(2), A="project_ID", t="00:5:00", c="2")

# Define a function that computes the square of a number
@everywhere function square(x)
    return x^2
end

hosts = []
result = []
for i in workers()
        println(i)
    host = fetch(@spawnat i gethostname())
    push!(hosts, host)
    result_partial = fetch(@spawnat i square(i))
    push!(result, result_partial)
end

println(hosts)
println(result)

# The Slurm resource allocation is released when all the workers have
# exited
for i in workers()
    rmprocs(i)
end

Exercises

1. Run a serial script

Run the serial script serial-sum.jl:

x = parse( Int32, ARGS[1] )
y = parse( Int32, ARGS[2] )
summ = x + y
println("The sum of the two numbers is ", summ)

This scripts accepts two integers as command line arguments.

2. Run the GPU script

Run the following script script-gpu.jl. Why are we running the simulations twice? Note that at UPPMAX you will need a project will access to Snowy

using CUDA

CUDA.versioninfo()

N = 2^8
x = rand(N, N)
y = rand(N, N)

A = CuArray(x)
B = CuArray(y)

# Calculation on CPU
@time x*y
# Calculation on GPU
@time A*B

# Calculation on CPU
@time x*y
# Calculation on GPU
@time A*B

3. Machine Learning job on GPUs

Julia has already several packages for ML, one of them is Flux (https://fluxml.ai/). We will work with one of the test cases provided by Flux which deals with a data set of tiny images (CIFAR10). Follow this steps:

julia> using MLDatasets: CIFAR10
julia> x, y = CIFAR10(:train)[:]
  • Change the number of epochs in the vgg_cifar10.jl script from 50 to something shorter like 5.

  • Submit the job with the script:

#!/bin/bash
#SBATCH -A hpc2n2024-114        # your project_ID
#SBATCH -J job-serial        # name of the job
#SBATCH -n 1                 # nr. tasks        #remove this line for UPPMAX
#SBATCH --time=00:15:00      # requested time
#SBATCH --error=job.%J.err   # error file
#SBATCH --output=job.%J.out  # output file
#SBATCH --gres=gpu:v100:1     # 1 GPU v100 card   #remove this line for UPPMAX
# On Rackham use the follwing lines instead (rm one #) by subsituting the related HPC2N lines, se above
##SBATCH -M snowy
##SBATCH -p node
##SBATCH --gres=gpu:1
##SBATCH -N 1
##SBATCH --qos=short

ml purge  > /dev/null 2>&1
ml Julia/1.8.5-linux-x86_64
ml CUDA/11.4.1

julia <fix-activate-environment> <fix-name-script>.jl

Keypoints

  • The SLURM scheduler handles allocations to the calculation nodes

  • Batch jobs runs without interaction with user

  • A batch script consists of a part with SLURM parameters describing the allocation and a second part describing the actual work within the job, for instance one or several Julia scripts.