examples
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| examples [2025/12/29 21:36] – dimitar | examples [2026/06/13 15:24] (current) – nshegunov | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====Description==== | + | ====== Guide to SLURM Resource Management and Job Submission ====== |
| - | This page provides | + | This guide provides |
| - | ---- | + | ===== 1. Theoretical Overview & Cluster Architecture ===== |
| - | ====Simple C/C++ program==== | + | An HPC cluster |
| - | The following | + | |
| - | <code C> | + | Slurm operates under a master-worker orchestration framework managed by three primary daemons: |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | /* | + | * '' |
| - | * Perform element-wise addition of two vectors | + | * '' |
| - | * | + | * '' |
| - | * Parameters: | + | |
| - | | + | |
| - | * b: Second input vector | + | |
| - | | + | |
| - | | + | |
| - | */ | + | |
| - | void vector_addition(const double *a, const double *b, double *result, size_t size) { | + | |
| - | for (size_t i = 0; i < size; i++) { | + | |
| - | result[i] = a[i] + b[i]; | + | |
| - | } | + | |
| - | } | + | |
| - | int main() { | + | ==== Resource Hierarchies ==== |
| - | const size_t size = 10000000; | + | When submitting a workload to Slurm, it is crucial to understand how resource requests translate into physical hardware allocations: |
| - | printf(" | + | * **Cluster: |
| - | | + | * **Partition: |
| - | | + | * **Node:** A single distinct physical server chassis possessing localized processors, volatile system memory |
| - | | + | * **Core/ |
| + | * **Task:** An independent process instance. In distributed execution | ||
| + | * **CPUs-per-task:** The number of hardware threads or CPU cores dedicated to backing a single task (critical for shared-memory multithreading models like OpenMP). | ||
| - | printf(" | + | ==== Deep Dive: CPU and Memory Architecture |
| - | | + | Modern compute nodes are not monolithic. A single node typically contains two or more CPU **sockets** (physical chips). Memory (RAM) is physically divided and attached directly to specific sockets. This is known as **NUMA (Non-Uniform Memory Access)**. |
| - | double | + | |
| - | double | + | |
| - | if (vector_a == NULL || vector_b == NULL || result == NULL) { | + | * **Local |
| - | fprintf(stderr, | + | * **Remote Memory Access:** If a program running on Socket 0 requests data stored in the RAM attached to Socket |
| - | | + | |
| - | } | + | |
| - | printf(" | + | **Why this matters |
| - | srand(time(NULL)); | + | |
| - | | + | |
| - | vector_a[i] = (double)rand() | + | |
| - | vector_b[i] = (double)rand() / RAND_MAX; | + | |
| - | } | + | |
| - | printf(" | + | --- |
| - | vector_addition(vector_a, | + | |
| - | printf(" | + | ===== 2. Advanced Cluster Topology and Inter-Node Communication ===== |
| - | for (int i = 0; i < 5; i++) { | + | |
| - | printf(" | + | |
| - | } | + | |
| - | free(vector_a); | + | To write optimal code for an HPC cluster, developers must account for how components talk to one another. Communication speeds and delays |
| - | free(vector_b); | + | |
| - | free(result); | + | |
| - | return 0; | + | ==== Memory and Communication Layout ==== |
| - | } | + | ^ Execution Scope ^ Communication Medium ^ Relative Bandwidth ^ Latency Complexity ^ |
| - | </code> | + | | **Intra-Core** (Same CPU) | L1 / L2 / L3 Processor Cache | Ultra-High (Terabytes/ |
| + | | **Intra-Node** (Same Server) | Physical RAM & PCIe Bus / NVLink | Very High (Hundreds of GB/s) | Low | | ||
| + | | **Inter-Node** (Across Servers) | InfiniBand Network Fabric / Switches | High (Hundreds of Gbps) | Microseconds (Fabric Transit) | | ||
| - | The following is the respective batch script for compiling and running | + | ==== Network Topologies: Fat-Tree Architecture ==== |
| + | HPC networks like the one powering | ||
| - | <code bash> | + | * **Non-Blocking Fabric:** In a standard IT tree, links get narrower as you go up toward the core switches, causing traffic jams. A **Fat-Tree topology** does the opposite: the network connections multiply and get thicker (more bandwidth) closer to the top core switches. |
| - | # | + | * **Full Bisection Bandwidth: |
| - | # | + | |
| - | #SBATCH --output=vector_sum_%j.out | + | |
| - | # | + | |
| - | #SBATCH --nodes=1 | + | |
| - | #SBATCH --ntasks=1 | + | |
| - | #SBATCH --cpus-per-task=1 | + | |
| - | #SBATCH --time=00:10:00 | + | |
| - | #SBATCH --partition=unite | + | |
| - | echo "=========================================" | + | ==== Deep Dive: InfiniBand & OS Kernel Bypass |
| - | echo "SLURM Job Information" | + | When your code spans across multiple physical nodes (e.g., an MPI job), it bypasses standard slow Ethernet connections. Ethernet requires the operating system' |
| - | echo " | + | |
| - | echo "Job ID: $SLURM_JOB_ID" | + | |
| - | echo "Node: $SLURM_NODELIST" | + | |
| - | echo " | + | |
| - | echo "" | + | |
| - | # Load necessary modules | + | The UNITE cluster utilizes **InfiniBand Architecture (IBA)** and **RDMA (Remote Direct Memory Access)**: |
| - | module load gcc | + | |
| - | # Compile | + | * **Zero-Copy Networking: |
| - | echo " | + | * **Kernel Bypass:** Neither the CPU nor the operating system on Node A or Node B is involved in the transfer. This drops network latency down to single-digit microseconds (<2us), allowing distributed applications to scale linearly. |
| - | gcc -O3 -march=native -o vector_sum vector_sum.c -lm | + | |
| - | if [ $? -ne 0 ]; then | + | ==== GPU Interconnects (PCIe vs. NVLink) ==== |
| - | echo " | + | On GPU partitions (like the '' |
| - | exit 1 | + | |
| - | fi | + | |
| - | echo " | + | * **PCIe Bus:** If two GPUs must communicate via the standard motherboard PCIe bus, bandwidth is limited (typically 32-64 GB/s) and latency increases because data must pass through the CPU. |
| - | echo "" | + | * **NVLink:** High-end cluster architectures utilize NVIDIA NVLink bridges, allowing GPUs to share memory pools directly at massive speeds (hundreds of GB/s). When writing distributed PyTorch or CuPy scripts across multiple GPUs on the same node, the framework relies on this hardware topology. |
| - | echo " | + | --- |
| - | ./ | + | |
| - | echo "" | + | ===== 3. Scheduler Theory: How Slurm Assigns Resources ===== |
| - | echo "Job finished at: $(date)" | + | |
| - | </ | + | |
| - | ---- | + | |
| - | ====Simple Python program==== | + | Slurm does not simply process jobs sequentially. It uses complex algorithms to maximize cluster utilization and ensure fairness. |
| - | The following is a simple **Python** program which performs element-wise addition of 2 vectors. It does **not** use any dependent libraries: | + | |
| - | <code Python> | + | * **Fair-Share Scheduling: |
| - | # | + | * **Backfilling Algorithm: |
| - | import random | + | * '' |
| - | import | + | |
| - | def vector_addition(a, | + | --- |
| - | """ | + | |
| - | Perform element-wise addition of two vectors | + | |
| - | Parameters: | + | ===== 4. Crucial Cluster Etiquette: The Shared Environment ===== |
| - | a: First input vector (list) | + | |
| - | b: Second input vector (list) | + | |
| - | Returns: | + | ^ WARNING: NEVER RUN COMPUTATIONS ON THE LOGIN NODE! ^ |
| - | result: Output vector (a + b) | + | | When you connect to the UNITE cluster via SSH, you land directly on the **Login Node**. This node's sole purpose is file management, code editing, and checking job queues. Heavy processes like compilation, |
| - | """ | + | |
| - | return [a[i] + b[i] for i in range(len(a))] | + | |
| + | To execute code, you **must** pass your workloads over to a dedicated compute node using either **Interactive Sessions** or **Batch Job Submissions**. | ||
| - | def main(): | + | --- |
| - | size = 10000000 | + | |
| - | print(" | + | ===== 5. Key Slurm Commands Workflow ===== |
| - | print(" | + | |
| - | print(" | + | |
| - | print(f" | + | |
| - | print(" | + | Interact with the scheduler fabric using these primary terminal operations: |
| - | random.seed(time.time()) | + | |
| - | vector_a = [random.random() for _ in range(size)] | + | ^ Command ^ Action Description ^ Typical Use Case ^ |
| - | | + | | '' |
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| - | print(" | + | --- |
| - | result = vector_addition(vector_a, | + | |
| - | print(" | + | ===== 6. Interactive Development vs. Non-Interactive Batch ===== |
| - | for i in range(5): | + | |
| - | print(f" | + | |
| + | ==== Workflow A: Interactive Sessions (Testing & Compilation) ==== | ||
| + | To compile libraries, debug scripts line-by-line, | ||
| - | if __name__ | + | For a standard multi-purpose compute slice: |
| - | main() | + | < |
| + | $ srun --partition=unite --cpus-per-task=4 --time=00:30:00 --pty bash | ||
| </ | </ | ||
| - | The following is the respective batch script for running the program. You can see the output of the program in the generated // | + | For a dedicated deep learning workspace utilizing NVIDIA graphics hardware: |
| - | + | < | |
| - | < | + | $ srun --partition=a40 --gres=gpu:1 --cpus-per-task=4 --time=00:30:00 --pty bash |
| - | # | + | |
| - | # | + | |
| - | # | + | |
| - | #SBATCH --error=vector_sum_%j.err | + | |
| - | #SBATCH --nodes=1 | + | |
| - | #SBATCH --ntasks=1 | + | |
| - | # | + | |
| - | # | + | |
| - | # | + | |
| - | + | ||
| - | echo " | + | |
| - | echo "SLURM Job Information" | + | |
| - | echo " | + | |
| - | echo "Job ID: $SLURM_JOB_ID" | + | |
| - | echo "Node: $SLURM_NODELIST" | + | |
| - | echo " | + | |
| - | echo "" | + | |
| - | + | ||
| - | echo " | + | |
| - | python3 --version | + | |
| - | echo "" | + | |
| - | + | ||
| - | echo " | + | |
| - | which python3 | + | |
| - | echo "" | + | |
| - | + | ||
| - | echo " | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | echo "" | + | |
| - | + | ||
| - | python3 vector_sum.py | + | |
| - | + | ||
| - | echo "" | + | |
| - | echo " | + | |
| - | echo "Job finished at: $(date)" | + | |
| - | echo " | + | |
| - | </ | + | |
| - | + | ||
| - | ---- | + | |
| - | + | ||
| - | ====Python program with dependencies==== | + | |
| - | The following is a simple **Python** program which computes the sum of 2 vectors 3 times using **NumPy**. | + | |
| - | + | ||
| - | <code Python> | + | |
| - | # | + | |
| - | import numpy as np | + | |
| - | import time | + | |
| - | + | ||
| - | def vector_addition(size=10000000): | + | |
| - | print(f" | + | |
| - | + | ||
| - | vector_a = np.random.rand(size) | + | |
| - | vector_b = np.random.rand(size) | + | |
| - | + | ||
| - | print(" | + | |
| - | result = vector_a + vector_b | + | |
| - | + | ||
| - | return result | + | |
| - | + | ||
| - | def main(): | + | |
| - | print(" | + | |
| - | print(" | + | |
| - | print(" | + | |
| - | + | ||
| - | sizes = [1000000, 10000000, 50000000] | + | |
| - | + | ||
| - | for size in sizes: | + | |
| - | result = vector_addition(size) | + | |
| - | + | ||
| - | print(f" | + | |
| - | print(f" | + | |
| - | print(" | + | |
| - | + | ||
| - | if __name__ == " | + | |
| - | main() | + | |
| </ | </ | ||
| + | Once executed, Slurm places your terminal directly onto a secure, isolated compute shell. When your testing is complete, type '' | ||
| - | The following is the respective | + | ==== Workflow B: Batch Processing (Production Runs) ==== |
| + | For heavy workloads that take hours or days, write a batch script and submit it using '' | ||
| - | <code bash> | + | Here is a template structure for a production batch file ('' |
| + | <file bash run_job.sh> | ||
| #!/bin/bash | #!/bin/bash | ||
| - | #SBATCH --job-name=vector_sum_numpy | + | #SBATCH --job-name=unite_production_job |
| - | #SBATCH --output=vector_sum_numpy_%j.out | + | #SBATCH --partition=unite |
| - | #SBATCH --error=vector_sum_numpy_%j.err | + | #SBATCH --output=logs_%j.out |
| - | #SBATCH --nodes=1 | + | #SBATCH --error=logs_%j.err |
| - | #SBATCH --ntasks=1 | + | #SBATCH --nodes=1 |
| - | #SBATCH --cpus-per-task=1 | + | #SBATCH --ntasks=1 |
| - | #SBATCH --time=00:15:00 | + | #SBATCH --cpus-per-task=4 # CPU worker core threads assigned to this task |
| - | #SBATCH --partition=unite | + | #SBATCH --mem=16G # Safe RAM allocation limit request |
| + | #SBATCH --time=02: | ||
| - | ################################################################################ | + | # 1. Clean the inherited terminal |
| - | # CONFIGURATION: | + | module |
| - | ################################################################################ | + | |
| - | # Options: " | + | |
| - | PYTHON_ENV_METHOD=" | + | |
| - | VENV_PATH="$HOME/ | + | # 2. Execute target execution logic or launch binaries |
| - | CONDA_ENV_NAME=" | + | echo "Starting production workload execution on node: $(hostname)" |
| - | CONDA_MODULE=" | + | # Run your commands here... |
| - | PYTHON_MODULE=" | + | </file> |
| - | NUMPY_MODULE=" | + | |
| - | ################################################################################ | + | To place this file into the cluster scheduler pipeline, execute: |
| - | # Setup Instructions (run once on login node before first job submission) | + | < |
| - | ################################################################################ | + | $ sbatch run_job.sh |
| - | # For venv: | + | |
| - | # | + | |
| - | # | + | |
| - | # pip install numpy | + | |
| - | # | + | |
| - | # | + | |
| - | # For conda: | + | |
| - | # | + | |
| - | # conda create -n numpy_env python=3.9 numpy | + | |
| - | # conda deactivate | + | |
| - | # | + | |
| - | # For module: | + | |
| - | # Check available modules: module avail python | + | |
| - | # You need to load both python and numpy modules in the script. The numpy module needs to be compatible with the python module. | + | |
| - | # Then you need to modify PYTHON_MODULE and NUMPY_MODULE variables above accordingly. | + | |
| - | ################################################################################ | + | |
| - | + | ||
| - | echo " | + | |
| - | echo "SLURM Job Information" | + | |
| - | echo " | + | |
| - | echo "Job ID: $SLURM_JOB_ID" | + | |
| - | echo "Node: $SLURM_NODELIST" | + | |
| - | echo " | + | |
| - | echo "" | + | |
| - | + | ||
| - | echo " | + | |
| - | echo "" | + | |
| - | + | ||
| - | if [ " | + | |
| - | echo " | + | |
| - | if [ -f " | + | |
| - | source " | + | |
| - | echo " | + | |
| - | else | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | exit 1 | + | |
| - | fi | + | |
| - | + | ||
| - | elif [ " | + | |
| - | echo " | + | |
| - | module load " | + | |
| - | source activate " | + | |
| - | echo "Conda environment activated: $CONDA_ENV_NAME" | + | |
| - | + | ||
| - | elif [ " | + | |
| - | echo " | + | |
| - | module load " | + | |
| - | module load " | + | |
| - | echo " | + | |
| - | + | ||
| - | else | + | |
| - | echo " | + | |
| - | echo "Valid options: venv, conda, module" | + | |
| - | exit 1 | + | |
| - | fi | + | |
| - | + | ||
| - | # Verify Python and NumPy | + | |
| - | echo "" | + | |
| - | echo " | + | |
| - | python3 --version | + | |
| - | + | ||
| - | echo "" | + | |
| - | echo "NumPy version:" | + | |
| - | python3 -c " | + | |
| - | echo "" | + | |
| - | echo " | + | |
| - | which python3 | + | |
| - | + | ||
| - | echo "" | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | echo "" | + | |
| - | + | ||
| - | python3 vector_sum_numpy.py | + | |
| - | + | ||
| - | echo "" | + | |
| - | echo " | + | |
| - | + | ||
| - | if [ " | + | |
| - | deactivate | + | |
| - | echo " | + | |
| - | elif [ " | + | |
| - | conda deactivate | + | |
| - | echo "Conda environment deactivated" | + | |
| - | elif [ " | + | |
| - | # Modules are automatically unloaded when job ends | + | |
| - | echo " | + | |
| - | fi | + | |
| - | + | ||
| - | echo "" | + | |
| - | echo " | + | |
| - | echo "Job finished at: $(date)" | + | |
| - | echo " | + | |
| </ | </ | ||
| - | ---- | + | --- |
| - | ====C/C++ program with dependencies==== | + | ===== 7. Reference Guides and Real-World Examples ===== |
| - | The following is a simple **C/C++** program which compresses and decompresses a string using **zLib**. | + | |
| - | <code C++> | + | To see how to apply these Slurm parameters across different compiler systems, runtime environments, |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #define CHUNK 16384 | + | * **Native Applications (C++):** |
| + | * [[unite_cpp_gcc|Compiling C++ Applications with the GCC Module]] — Step-by-step instructions for running basic '' | ||
| + | * [[unite_cpp_mpi|Compiling and Running Distributed C++ MPI Applications]] — How to request multi-node processing ('' | ||
| + | * **GPU-Accelerated Data Science & ML (Python): | ||
| + | * [[unite_python_cupy|Accelerated Mathematical Computations with CuPy]] — Leveraging GPU hardware tensors on the **unite** partition utilizing tracking hooks like '' | ||
| + | * [[unite_python_torch|Deep Learning Model Frameworks with PyTorch]] — High-performance neural processing allocations specifically built to execute on the advanced **a40** hardware partition. | ||
| - | int main() { | + | --- |
| - | const char *original = " | + | |
| - | " | + | |
| - | printf(" | + | ===== 8. Troubleshooting Common Queue States ===== |
| - | printf(" | + | |
| - | // Compression | + | If you run '' |
| - | uLong source_len = strlen(original) + 1; | + | |
| - | uLong compressed_len = compressBound(source_len); | + | |
| - | unsigned char *compressed = (unsigned char *)malloc(compressed_len); | + | |
| - | + | ||
| - | if (compress(compressed, | + | |
| - | fprintf(stderr, | + | |
| - | free(compressed); | + | |
| - | return 1; | + | |
| - | } | + | |
| - | + | ||
| - | printf(" | + | |
| - | printf(" | + | |
| - | + | ||
| - | // Decompression | + | |
| - | uLong decompressed_len = source_len; | + | |
| - | unsigned char *decompressed = (unsigned char *)malloc(decompressed_len); | + | |
| - | + | ||
| - | if (uncompress(decompressed, | + | |
| - | fprintf(stderr, | + | |
| - | free(compressed); | + | |
| - | free(decompressed); | + | |
| - | return 1; | + | |
| - | } | + | |
| - | + | ||
| - | printf(" | + | |
| - | printf(" | + | |
| - | + | ||
| - | if (strcmp(original, | + | |
| - | printf(" | + | |
| - | } else { | + | |
| - | printf(" | + | |
| - | } | + | |
| - | + | ||
| - | free(compressed); | + | |
| - | free(decompressed); | + | |
| - | + | ||
| - | return 0; | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | The following is the respective batch script for compiling and running the program. You can see the output of the program in the generated // | + | |
| - | + | ||
| - | <code bash> | + | |
| - | # | + | |
| - | # | + | |
| - | #SBATCH --output=zlib_compress_%j.out | + | |
| - | #SBATCH --error=zlib_compress_%j.err | + | |
| - | #SBATCH --time=00: | + | |
| - | #SBATCH --nodes=1 | + | |
| - | #SBATCH --ntasks=1 | + | |
| - | #SBATCH --cpus-per-task=1 | + | |
| - | #SBATCH --partition=unite | + | |
| - | + | ||
| - | module load gcc/ | + | |
| - | module load zlib/ | + | |
| - | + | ||
| - | echo " | + | |
| - | module list | + | |
| - | + | ||
| - | echo "" | + | |
| - | echo " | + | |
| - | gcc -o zlib_compress zlib_compress.c -lz | + | |
| - | + | ||
| - | if [ $? -eq 0 ]; then | + | |
| - | echo " | + | |
| - | echo "" | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | ./ | + | |
| - | else | + | |
| - | echo " | + | |
| - | exit 1 | + | |
| - | fi | + | |
| - | </ | + | |
| - | + | ||
| - | ---- | + | |
| - | ====C++ program which uses MPI==== | + | |
| - | + | ||
| - | The following is an example **C/C++** application which uses **MPI** to perform element-wise addition of two vectors. Each **MPI** task computes the addition of its local region | + | |
| - | + | ||
| - | <code C++> | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | + | ||
| - | #define VECTOR_SIZE 100000 | + | |
| - | + | ||
| - | int main(int argc, char** argv) { | + | |
| - | int rank, size; | + | |
| - | int i; | + | |
| - | + | ||
| - | MPI_Init(& | + | |
| - | MPI_Comm_rank(MPI_COMM_WORLD, | + | |
| - | MPI_Comm_size(MPI_COMM_WORLD, | + | |
| - | + | ||
| - | int local_size = VECTOR_SIZE / size; | + | |
| - | + | ||
| - | int *local_a = (int*)malloc(local_size * sizeof(int)); | + | |
| - | int *local_b = (int*)malloc(local_size * sizeof(int)); | + | |
| - | int *local_c = (int*)malloc(local_size * sizeof(int)); | + | |
| - | + | ||
| - | int *a = NULL; | + | |
| - | int *b = NULL; | + | |
| - | int *c = NULL; | + | |
| - | + | ||
| - | if (rank == 0) { | + | |
| - | a = (int*)malloc(VECTOR_SIZE * sizeof(int)); | + | |
| - | b = (int*)malloc(VECTOR_SIZE * sizeof(int)); | + | |
| - | c = (int*)malloc(VECTOR_SIZE * sizeof(int)); | + | |
| - | + | ||
| - | for (i = 0; i < VECTOR_SIZE; | + | |
| - | a[i] = i + 1; | + | |
| - | } | + | |
| - | + | ||
| - | for (i = 0; i < VECTOR_SIZE; | + | |
| - | b[i] = (i + 1) * 2; | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | MPI_Scatter(a, | + | |
| - | MPI_Scatter(b, | + | |
| - | + | ||
| - | printf(" | + | |
| - | for (i = 0; i < local_size; i++) { | + | |
| - | local_c[i] = local_a[i] + local_b[i]; | + | |
| - | } | + | |
| - | + | ||
| - | MPI_Gather(local_c, | + | |
| - | + | ||
| - | if (rank == 0) { | + | |
| - | printf(" | + | |
| - | for (i = 0; i < 5; i++) { | + | |
| - | printf(" | + | |
| - | } | + | |
| - | printf(" | + | |
| - | + | ||
| - | free(a); | + | |
| - | free(b); | + | |
| - | free(c); | + | |
| - | } | + | |
| - | + | ||
| - | free(local_a); | + | |
| - | free(local_b); | + | |
| - | free(local_c); | + | |
| - | + | ||
| - | MPI_Finalize(); | + | |
| - | + | ||
| - | return 0; | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | The following is the respective batch script for compiling and running the program. You can see the output of the program in the generated // | + | |
| - | + | ||
| - | <code bash> | + | |
| - | # | + | |
| - | #SBATCH --job-name=vector_sum_mpi | + | |
| - | #SBATCH --output=vector_mpi_%j.out | + | |
| - | #SBATCH --error=vector_mpi_%j.err | + | |
| - | #SBATCH --ntasks=4 | + | |
| - | #SBATCH --time=00: | + | |
| - | #SBATCH --partition=unite | + | |
| - | + | ||
| - | module load mpi/ | + | |
| - | + | ||
| - | echo " | + | |
| - | mpicc -o vector_sum_mpi vector_sum_mpi.c | + | |
| - | + | ||
| - | if [ $? -ne 0 ]; then | + | |
| - | echo " | + | |
| - | exit 1 | + | |
| - | fi | + | |
| - | + | ||
| - | echo " | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | + | ||
| - | mpirun -np $SLURM_NTASKS ./ | + | |
| - | + | ||
| - | echo " | + | |
| - | echo "Job completed!" | + | |
| - | echo " | + | |
| - | </ | + | |
| - | + | ||
| - | ---- | + | |
| - | + | ||
| - | ====C++ program which uses multiple threads==== | + | |
| - | + | ||
| - | The following is a simple **C++** program which computes the sum of 2 vectors. It uses multiple **threads**. Each **thread** computes the sum for its respective region. | + | |
| - | + | ||
| - | <code C++> | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | + | ||
| - | #define VECTOR_SIZE 100000 | + | |
| - | + | ||
| - | void vector_add_worker(int thread_id, int start_idx, int end_idx, | + | |
| - | const int* a, const int* b, int* c) { | + | |
| - | int elements = end_idx - start_idx; | + | |
| - | std::cout << " | + | |
| - | << " elements" | + | |
| - | + | ||
| - | for (int i = start_idx; i < end_idx; i++) { | + | |
| - | c[i] = a[i] + b[i]; | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | int main(int argc, char** argv) { | + | |
| - | if (argc != 2) { | + | |
| - | std::cerr << " | + | |
| - | return 1; | + | |
| - | } | + | |
| - | + | ||
| - | int num_threads = std:: | + | |
| - | if (num_threads <= 0) { | + | |
| - | std::cerr << " | + | |
| - | return 1; | + | |
| - | } | + | |
| - | + | ||
| - | std::cout << "Using " << num_threads << " threads" | + | |
| - | + | ||
| - | std:: | + | |
| - | std:: | + | |
| - | std:: | + | |
| - | + | ||
| - | for (int i = 0; i < VECTOR_SIZE; | + | |
| - | a[i] = i + 1; | + | |
| - | b[i] = (i + 1) * 2; | + | |
| - | } | + | |
| - | + | ||
| - | int elements_per_thread = VECTOR_SIZE / num_threads; | + | |
| - | + | ||
| - | std:: | + | |
| - | for (unsigned int t = 0; t < num_threads; | + | |
| - | int start_idx = t * elements_per_thread; | + | |
| - | int end_idx = (t == num_threads - 1) ? VECTOR_SIZE : (t + 1) * elements_per_thread; | + | |
| - | + | ||
| - | threads.emplace_back(vector_add_worker, | + | |
| - | | + | |
| - | } | + | |
| - | + | ||
| - | for (auto& thread : threads) { | + | |
| - | thread.join(); | + | |
| - | } | + | |
| - | + | ||
| - | std::cout << " | + | |
| - | for (int i = 0; i < 5; i++) { | + | |
| - | std::cout << c[i] << " "; | + | |
| - | } | + | |
| - | std::cout << std:: | + | |
| - | + | ||
| - | return 0; | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | The following is the respective batch script for compiling and running the program. You can see the output of the program in the generated // | + | |
| - | + | ||
| - | <code bash> | + | |
| - | # | + | |
| - | #SBATCH --job-name=vector_sum_threads | + | |
| - | #SBATCH --output=vector_sum_threads_%j.out | + | |
| - | #SBATCH --error=vector_sum_threads_%j.err | + | |
| - | #SBATCH --nodes=1 | + | |
| - | #SBATCH --ntasks=1 | + | |
| - | #SBATCH --cpus-per-task=8 | + | |
| - | #SBATCH --time=00: | + | |
| - | #SBATCH --partition=unite | + | |
| - | + | ||
| - | echo "Job started | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | + | ||
| - | module load gcc | + | |
| - | + | ||
| - | echo " | + | |
| - | g++ -std=c++11 -pthread -O3 vector_sum_threads.cpp -o vector_sum_threads | + | |
| - | + | ||
| - | if [ $? -eq 0 ]; then | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | + | ||
| - | echo " | + | |
| - | ./ | + | |
| - | + | ||
| - | echo " | + | |
| - | echo "Job finished at: $(date)" | + | |
| - | else | + | |
| - | echo " | + | |
| - | exit 1 | + | |
| - | fi | + | |
| - | </ | + | |
| - | + | ||
| - | ---- | + | |
| - | + | ||
| - | ====C++ program which uses GPU==== | + | |
| - | + | ||
| - | The following is an example **Cuda** application which uses **Nvidia GPU** to perform element-wise addition of two vectors. Using **Cuda** with **Python** is similar assuming that you know how to manage **Python** dependencies on the cluster which is described in a previous section. What is important here is to understand how to manage the resources of the system. | + | |
| - | + | ||
| - | <code C++> | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | + | ||
| - | #define CUDA_CHECK(call) \ | + | |
| - | do { \ | + | |
| - | cudaError_t error = call; \ | + | |
| - | if (error != cudaSuccess) { \ | + | |
| - | fprintf(stderr, | + | |
| - | cudaGetErrorString(error)); | + | |
| - | exit(EXIT_FAILURE); | + | |
| - | } \ | + | |
| - | } while(0) | + | |
| - | + | ||
| - | /* | + | |
| - | * CUDA kernel for vector addition | + | |
| - | * Each thread computes one element of the result vector | + | |
| - | * | + | |
| - | * Parameters: | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | */ | + | |
| - | __global__ void vectorAddKernel(const float *a, const float *b, float *c, int n) { | + | |
| - | // Calculate global thread ID | + | |
| - | int idx = blockIdx.x * blockDim.x + threadIdx.x; | + | |
| - | + | ||
| - | // Check if thread is within bounds | + | |
| - | if (idx < n) { | + | |
| - | c[idx] = a[idx] + b[idx]; | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | int main() { | + | |
| - | const int N = 50'000'000; | + | |
| - | const size_t bytes = N * sizeof(float); | + | |
| - | + | ||
| - | printf(" | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | + | ||
| - | int deviceId; | + | |
| - | cudaDeviceProp props; | + | |
| - | CUDA_CHECK(cudaGetDevice(& | + | |
| - | CUDA_CHECK(cudaGetDeviceProperties(& | + | |
| - | + | ||
| - | printf(" | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | + | ||
| - | printf(" | + | |
| - | float *h_a = (float *)malloc(bytes); | + | |
| - | float *h_b = (float *)malloc(bytes); | + | |
| - | float *h_c_gpu = (float *)malloc(bytes); | + | |
| - | float *h_c_cpu = (float *)malloc(bytes); | + | |
| - | + | ||
| - | if (!h_a || !h_b || !h_c_gpu || !h_c_cpu) { | + | |
| - | fprintf(stderr, | + | |
| - | return 1; | + | |
| - | } | + | |
| - | + | ||
| - | for (int i = 0; i < N; i++) { | + | |
| - | h_a[i] = (float)rand() / RAND_MAX; | + | |
| - | h_b[i] = (float)rand() / RAND_MAX; | + | |
| - | } | + | |
| - | + | ||
| - | float *d_a, *d_b, *d_c; | + | |
| - | CUDA_CHECK(cudaMalloc(& | + | |
| - | CUDA_CHECK(cudaMalloc(& | + | |
| - | CUDA_CHECK(cudaMalloc(& | + | |
| - | + | ||
| - | double transfer_start = getTime(); | + | |
| - | CUDA_CHECK(cudaMemcpy(d_a, | + | |
| - | CUDA_CHECK(cudaMemcpy(d_b, | + | |
| - | + | ||
| - | int threadsPerBlock = 256; | + | |
| - | int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock; | + | |
| - | + | ||
| - | printf(" | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | printf(" | + | |
| - | + | ||
| - | vectorAddKernel<<< | + | |
| - | + | ||
| - | CUDA_CHECK(cudaMemcpy(h_c_gpu, | + | |
| - | + | ||
| - | printf(" | + | |
| - | for (int i = 0; i < 5; i++) { | + | |
| - | printf(" | + | |
| - | } | + | |
| - | + | ||
| - | CUDA_CHECK(cudaFree(d_a)); | + | |
| - | CUDA_CHECK(cudaFree(d_b)); | + | |
| - | CUDA_CHECK(cudaFree(d_c)); | + | |
| - | CUDA_CHECK(cudaEventDestroy(start)); | + | |
| - | CUDA_CHECK(cudaEventDestroy(stop)); | + | |
| - | free(h_a); | + | |
| - | free(h_b); | + | |
| - | free(h_c_gpu); | + | |
| - | free(h_c_cpu); | + | |
| - | + | ||
| - | return 0; | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | The following is the respective batch script for compiling and running the program. You can see the output of the program in the generated // | + | |
| - | + | ||
| - | <code bash> | + | |
| - | # | + | |
| - | #SBATCH --job-name=vector_sum_cuda | + | |
| - | #SBATCH --output=vector_sum_cuda_%j.out | + | |
| - | #SBATCH --error=vector_sum_cuda_%j.err | + | |
| - | #SBATCH --nodes=1 | + | |
| - | #SBATCH --ntasks=1 | + | |
| - | #SBATCH --cpus-per-task=1 | + | |
| - | #SBATCH --gres=gpu: | + | |
| - | #SBATCH --time=00: | + | |
| - | #SBATCH --partition=unite | + | |
| - | + | ||
| - | echo " | + | |
| - | echo "SLURM Job Information" | + | |
| - | echo " | + | |
| - | echo "Job ID: $SLURM_JOB_ID" | + | |
| - | echo "Node: $SLURM_NODELIST" | + | |
| - | echo " | + | |
| - | echo " | + | |
| - | echo "" | + | |
| - | + | ||
| - | module load nvidia/ | + | |
| - | + | ||
| - | echo " | + | |
| - | nvcc -O3 -o vector_sum_cuda vector_sum_cuda.cu | + | |
| - | + | ||
| - | if [ $? -ne 0 ]; then | + | |
| - | echo " | + | |
| - | exit 1 | + | |
| - | fi | + | |
| - | + | ||
| - | echo " | + | |
| - | echo "" | + | |
| - | + | ||
| - | echo " | + | |
| - | ./ | + | |
| - | + | ||
| - | echo "" | + | |
| - | echo "Job finished at: $(date)" | + | |
| - | </ | + | |
| - | ---- | + | * **(Resources): |
| + | * **(Priority): | ||
| + | * **(PartitionTimeLimit): | ||
| + | * **(InvalidQOS): | ||
examples.1767036981.txt.gz · Last modified: 2025/12/29 21:36 by dimitar
