M3 (Massive 3)
Notes from Monash University's M3 supercomputer. Slurm-scheduled, partition-diverse, and the cluster I run most day-to-day experiments on. This is everything I had to work out the hard way.
The cluster at a glance
- Login host:
m3.massive.org.au - Scheduler: Slurm (
sbatch,squeue,scancel) - Scratch:
$HOME/vf38_scratch/<username>, the only sensible place for big files and caches - GPU partitions: mixed, see table below
- Persistent sessions:
smux(Monash's tmux wrapper over Slurm)
Always do CUDA-sensitive work (pip installs, sgl-kernel builds, model loads) inside a GPU allocation. Login nodes have no GPU, and anything that probes for CUDA devices during install will segfault.
Partitions
| Partition | GPU Types | Per-node | Example srun |
|---|---|---|---|
gpu | A40 / A100 / T4 / L40S | varies | srun --pty -p gpu -t 00:30:00 --gres=gpu:L40S:1 --cpus-per-task=16 --mem=64G bash |
m3h | H100 | 4× H100 | srun --pty --partition=m3h --qos=m3h -t 00:30:00 --gres=gpu:4 --cpus-per-task=72 --mem=985G bash |
m3g | V100 | varies | srun --pty --partition=m3g -t 00:30:00 --gres=gpu:V100:1 --cpus-per-task=18 --mem=64G bash |
To request more than 4 GPUs, add --qos=shortq, without it your job hangs in the queue behind the default QoS limits.
Scratch and caches
Home quota is small. Scratch is big and fast. Redirect every cache that matters before anything Python-ish runs:
export SCRATCH="$HOME/vf38_scratch/<username>"
mkdir -p "$SCRATCH"/{huggingface,uv,pycache,vllm,sglang}
export HF_HOME="$SCRATCH/.caches/huggingface"
export UV_CACHE_DIR="$SCRATCH/.caches/uv"
export PYTHONPYCACHEPREFIX="$SCRATCH/.caches/pycache"
export VLLM_CACHE_DIR="$SCRATCH/.caches/vllm"
export SGLANG_CACHE_DIR="$SCRATCH/.caches/sglang"
Drop that block into ~/.bashrc or the top of every job script. If you don't, your home quota dies the first time you pull a 600 GB checkpoint.
Persistent sessions with smux
Slurm's srun dies when the ssh connection dies. For longer interactive work, smux keeps a Slurm allocation alive across disconnects:
smux new-session --partition=m3h --qos=m3h --time=00:15:00 \
--gres=gpu:4 --cpus=72 --mem=985G
smux attach-session # reconnect later
smux list-sessions # list your active allocations
Use this for anything interactive that might last longer than a coffee break.
CUDA toolchain
M3 ships many CUDA versions as lmod modules. List them once:
ls -a /apps/cuda
module avail cuda
Load the version that matches your PyTorch wheel:
module load cuda/12.6
For bleeding-edge kernels (DeepGEMM on Hopper, for example) you may need 12.9, if it's not in /apps/cuda, install it per-user inside a GPU session; see the Aspire notes for the runfile recipe.
Python environment
Miniconda + uv is the combination that causes the fewest headaches:
conda create -p "$SCRATCH/py312" python=3.12 -y
conda activate "$SCRATCH/py312"
pip install uv
uv cache dir # confirm it points at $SCRATCH
Or, if you want the project-local venv pattern:
uv venv
source .venv/bin/activate
Match PyTorch to the loaded CUDA:
uv pip install torch torchvision --index-url https://download.pytorch.org/whl/cu126
SGLang install
Inside a GPU session so the build-time CUDA probes succeed:
export TORCH_CUDA_ARCH_LIST="9.0" # H100 on m3h
uv pip install "sglang[all]>=0.5.0"
uv pip install --force-reinstall "sgl-kernel==0.2.4" sentencepiece
Quick sanity check:
python -m sglang.bench_offline_throughput --help
Sanity checks on a GPU node
Run these as the first thing inside every allocation, catches 80% of "why isn't my job working" questions before they happen:
hostname
nvidia-smi -L
nvidia-smi
echo "CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES"
python3 - << 'EOF'
import torch
print("torch:", torch.__version__)
print("cuda:", torch.version.cuda)
print("available:", torch.cuda.is_available())
print("device count:", torch.cuda.device_count())
for i in range(torch.cuda.device_count()):
print(i, torch.cuda.get_device_name(i))
EOF
Slurm batch template
For a 1-node, 4-GPU H100 job on m3h:
#!/bin/bash
#SBATCH --job-name=sglang
#SBATCH --partition=m3h
#SBATCH --qos=m3h
#SBATCH --time=00:30:00
#SBATCH --gres=gpu:4
#SBATCH --cpus-per-task=72
#SBATCH --mem=985G
#SBATCH --output=run/%x.%j.out
source "$SCRATCH/py312/bin/activate"
module load cuda/12.6
nvidia-smi -L
time python3 -m sglang.bench_offline_throughput \
--model-path deepseek-ai/DeepSeek-R1 \
--dataset-path "$SCRATCH/ShareGPT_V3_unfiltered_cleaned_split.json" \
--num-prompts 2000 --load-format dummy --seed 2025 --dtype bfloat16 \
--tp 4 --trust-remote-code \
--warmups compile-deep-gemm
Submit with sbatch, monitor with squeue -u $USER, tail the log once it starts.
Things I wish I'd known
$SCRATCHis not automatic, the variable isn't set for you. Export it yourself in every script.--qos=m3his mandatory onm3h, leave it off and the job sits in the queue forever without a clear error.smuxsessions count against your active-job quota, don't leave five of them running.- Module order matters, load
cuda/12.6before activating your conda env, otherwiseLD_LIBRARY_PATHgets clobbered. /appsis read-only, everything you install goes in$SCRATCH.- H100s on
m3hare contended at end of semester, submit early, especially for multi-GPU jobs.
See also
- Aspire 2A+, NSCC Singapore equivalent, PBS Pro rather than Slurm.
- SGLang & DeepSeek, install and benchmark flags.
- SGLang Optimisations, the flags that actually move throughput.