Explorando Comandos SLURM¶
Objetivo: Entender como a solicitação de recursos impacta o tempo de execução de uma tarefa e compreender como pedir recursos ao SLURM.
Recursos Necessários: Acesso ao cluster Franky configurado.
Programa C++ para realizar a convolução de uma matriz¶
Crie um arquivo chamado convolucao.cpp
com o seguinte conteúdo:
#include <iostream>
#include <mpi.h>
#include <omp.h>
#include <chrono>
#include <cmath>
#define N 300
#define FILTER_SIZE 11
#define ITERATIONS 10
// Função para alocar dinamicamente uma matriz NxN
int** aloca_matriz(int n) {
int** matriz = new int*[n];
for (int i = 0; i < n; i++) {
matriz[i] = new int[n]();
}
return matriz;
}
// Função para liberar memória da matriz NxN
void libera_matriz(int** matriz, int n) {
if (matriz) {
for (int i = 0; i < n; i++) {
delete[] matriz[i];
}
delete[] matriz;
}
}
// Função de convolução
int apply_filter(int x, int y, int** matrix, int filter[FILTER_SIZE][FILTER_SIZE]) {
int result = 0;
int filter_offset = FILTER_SIZE / 2;
for (int i = -filter_offset; i <= filter_offset; i++) {
for (int j = -filter_offset; j <= filter_offset; j++) {
int xi = x + i;
int yj = y + j;
// Garantindo que os índices estão dentro dos limites da matriz
if (xi >= 0 && xi < N && yj >= 0 && yj < N) {
int value = matrix[xi][yj] * filter[i + filter_offset][j + filter_offset];
result += value;
result += std::sin(value) * std::cos(value);
result += static_cast<int>(std::pow(value, 2)) % 7;
}
}
}
return result;
}
int main(int argc, char *argv[]) {
MPI_Init(&argc, &argv);
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
// Aloca as matrizes dinamicamente
int** matrix = aloca_matriz(N);
int** result = aloca_matriz(N);
int filter[FILTER_SIZE][FILTER_SIZE];
// Inicializa a matriz apenas no processo mestre (rank 0)
if (rank == 0) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
matrix[i][j] = (i + j) % 255; // Mantém valores entre 0-255
}
}
for (int i = 0; i < FILTER_SIZE; i++) {
for (int j = 0; j < FILTER_SIZE; j++) {
filter[i][j] = 1; // Filtro simples
}
}
}
MPI_Barrier(MPI_COMM_WORLD);
auto start = std::chrono::high_resolution_clock::now();
// Enviar matriz para todos os processos (transmitindo linha por linha para evitar erros de buffer)
for (int i = 0; i < N; i++) {
MPI_Bcast(matrix[i], N, MPI_INT, 0, MPI_COMM_WORLD);
}
// Definir quantas linhas cada processo vai processar
int rows_per_process = N / size;
int start_row = rank * rows_per_process;
int end_row = (rank == size - 1) ? N : start_row + rows_per_process;
for (int iter = 0; iter < ITERATIONS; iter++) {
#pragma omp parallel for collapse(2) schedule(dynamic, 1)
for (int i = start_row; i < end_row; i++) {
for (int j = 0; j < N; j++) {
result[i][j] = apply_filter(i, j, matrix, filter);
}
}
int recvcounts[size]; // Número de elementos que cada processo enviará
int displs[size]; // Deslocamento de onde cada processo começa no buffer global
for (int i = 0; i < size; i++) {
recvcounts[i] = (i == size - 1) ? (N - i * rows_per_process) * N : rows_per_process * N;
displs[i] = i * rows_per_process * N;
}
MPI_Gatherv(result[start_row], rows_per_process * N, MPI_INT,
matrix[0], recvcounts, displs, MPI_INT,
0, MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
double exec_time = duration.count();
double max_exec_time;
MPI_Reduce(&exec_time, &max_exec_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
if (rank == 0) {
std::cout << "A tarefa levou " << max_exec_time << " segundos para ser executada" << std::endl;
}
// Libera memória das matrizes
libera_matriz(matrix, N);
libera_matriz(result, N);
MPI_Finalize();
return 0;
}
Compile o programa dentro do Cluster:
mpic++ -fopenmp convolucao.cpp -o convolucao
Criação de Scripts SLURM¶
Crie um arquivo convolucao_limitado.slurm com a seguinte configuração:
#!/bin/bash
#SBATCH --job-name=convolucao_limitado # Nome do job
#SBATCH --output=limitado_%j.txt # Gera um novo arquivo de saída a cada execução
#SBATCH --ntasks=1 # Número de tarefas (apenas 1 processos)
#SBATCH --cpus-per-task=1 # Número de threads por tarefa (apenas 1 thread por processo)
#SBATCH --mem=512MB # Memória total alocada por nó (512 MB)
#SBATCH --time=00:10:00 # Tempo máximo de execução (até 10 minutos)
#SBATCH --partition=espec # Fila do cluster a ser utilizada
# Exporta a variável de ambiente para configurar o número de threads OpenMP
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
# Executa o programa usando MPI e OpenMP
mpirun ./convolucao
Vamos solicitar mais recursos e ver o que acontece, crie um arquivo convolucao_buffado.slurm com a seguinte configuração:
#!/bin/bash
#SBATCH --job-name=convolucao_buffado # Nome do job
#SBATCH --output=buffado_%j.txt # Nome do arquivo de saída
#SBATCH --ntasks=4 # Número de tarefas (4 processos)
#SBATCH --cpus-per-task=4 # Número de threads por tarefa (2 threads por processo)
#SBATCH --mem=2048 # Memória total alocada por nó (2 GBs)
#SBATCH --time=00:10:00 # Tempo máximo de execução (10 minutos)
#SBATCH --partition=espec # fila do cluster a ser utilizada
# Exporta a variável de ambiente para configurar o número de threads OpenMP
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
# Executa o programa usando MPI e OpenMP
mpirun ./convolucao
Exploração dos Comandos SLURM¶
Submeta os jobs usando o comando sbatch
:
sbatch convolucao_limitado.slurm
sbatch convolucao_buffado.slurm
Utilize o comando sinfo
para observar o estado dos nós e das filas:
sinfo
Use o comando squeue
para observar o estado dos jobs em execução no Cluster Franky:
squeue
Use o comando abaixo para ver as especificações de recursos disponíveis nas filas do Cluster Franky:
scontrol show partition
Se quiser ver uma fila especifica, use o comando:
scontrol show partition nome_da_fila
Exemplo -> Interpretação da Partição express
¶
Esta configuração define como os jobs são alocados e executados nos nós computacionais.
PartitionName=express
→ Nome da partição.AllowGroups=ALL
→ Todos os grupos de usuários podem submeter jobs.AllowAccounts=ALL
→ Todos os usuários podem submeter jobs, independentemente da conta.-
AllowQos=ALL
→ Todos os níveis de Qualidade de Serviço (QoS) são permitidos. -
AllocNodes=ALL
→ Todos os nós podem ser alocados para jobs nesta partição. -
Nodes=compute[00-04]
→ Os nós compute00, compute01, compute02, compute03 e compute04 estão disponíveis. -
Default=YES
→ Esta é a partição padrão se nenhuma for especificada. MaxNodes=2
→ Cada job pode usar no máximo 2 nós.MinNodes=0
→ Jobs podem rodar mesmo se não houver nós alocados inicialmente.MaxTime=00:05:00
→ Tempo máximo de execução para um job é 5 minutos.-
DefaultTime=NONE
→ O tempo padrão do job não está definido, então é obrigatório especificá-lo ao submeter. -
MaxCPUsPerNode=UNLIMITED
→ Não há limite de CPUs por nó. MaxCPUsPerSocket=UNLIMITED
→ Não há limite de CPUs por socket (conjunto de núcleos de um processador físico).TotalCPUs=80
→ A partição possui 80 CPUs disponíveis no total.TotalNodes=5
→ Existem 5 nós disponíveis.DefMemPerNode=UNLIMITED
→ A memória disponível por nó não tem um limite padrão.MaxMemPerNode=12000
→ Cada nó pode usar até 12GB de RAM por job.-
TRES=cpu=80,mem=70000M,node=5,billing=80
→ O SLURM controla 80 CPUs, 70GB de RAM e 5 nós nesta partição. -
PriorityJobFactor=1
→ Todos os jobs na partição têm fator de prioridade 1 (sem preferência sobre outras partições). -
PriorityTier=1
→ Prioridade desta partição é 1 (normal). -
ExclusiveUser=NO
→ Os nós podem ser compartilhados entre múltiplos jobs de diferentes usuários. OverSubscribe=YES:4
→ Permite que até 4 jobs compartilhem um mesmo nó.OverTimeLimit=NONE
→ Não há limite para jobs que excedam o tempo solicitado.-
PreemptMode=OFF
→ Jobs nesta partição não podem ser interrompidos (preempted) por outros jobs de maior prioridade. -
DisableRootJobs=NO
→ Usuários root podem submeter jobs. GraceTime=0
→ Sem tempo de espera antes da execução do job.Hidden=NO
→ A partição está visível para todos os usuários.RootOnly=NO
→ Não é necessário ser administrador para rodar jobs.ReqResv=NO
→ Não requer reserva antecipada de recursos.
Utilize o comando scancel
para cancelar um job:
scancel jobid
Use o comando sprio
para visualizar a prioridade dos jobs:
sprio
Tip
Se quiser testar outros exemplos verifique o material disponível aqui
Atividade 02: Explorando comandos SLURM¶
-
Modifique o Número de Iterações da Convolução no Código convolucao.cpp; Aumente o número de iterações de convolução no código para 10, 30, e 60. Isso aumentará a complexidade computacional, observe como o desempenho do código é afetado por essa modificação.
-
Ajuste o arquivo .slurm para garantir que os recursos de hardware (número de tarefas, CPUs por tarefa, e memória) e o tempo de execução sejam adequados para processar o aumento no número de iterações de convolução. Experimente diferentes configurações de recursos para observar como eles impactam o tempo de execução.
-
Gere um gráfico comparando o tempo de execução do algoritmo para cada número de iterações. Analise o impacto da alocação de mais recursos, como o número de CPUs a quantidade de memória e observe também, como o volume de uso do cluster por outros usuários afeta o tempo de execução.