/ Blog

MPI Rehberi: Message Passing Interface ile Paralel Programlama

MPI programlama temelleri, OpenMPI kurulumu, temel komutlar ve SLURM ile MPI iş gönderimi.

Yüksek performanslı hesaplama (HPC) dünyasında paralel programlama, büyük ölçekli bilimsel problemleri çözmenin temel yoludur. Bu problemlerin büyük çoğunluğu tek bir işlemcinin kapasitesini aştığından, hesabı birden fazla işlemci veya düğüm arasında dağıtmak gerekir. Message Passing Interface (MPI), bu dağıtık hesaplamayı mümkün kılan, endüstri standardı haline gelmiş bir iletişim kütüphanesidir.

Bu rehberde MPI’ın ne olduğunu, nasıl kurulacağını, temel kavramlarını ve SLURM iş zamanlayıcısıyla birlikte nasıl kullanılacağını pratik örneklerle ele alacağız.

MPI Nedir ve Neden Kullanılır?

MPI, birden fazla işlem arasında mesaj alışverişi yaparak paralel çalışmayı sağlayan bir API standardıdır. 1994 yılında tanımlanan bu standart, farklı üreticilerin birbirleriyle uyumlu uygulamalar geliştirmesine zemin hazırlamıştır. Bugün OpenMPI ve MPICH en yaygın açık kaynak uygulamalarıdır.

MPI’ın tercih edilmesinin başlıca nedenleri şunlardır:

  • Taşınabilirlik: Tek düğümlü sistemlerden binlerce çekirdekli süperbilgisayarlara kadar aynı kod çalışır.
  • Ölçeklenebilirlik: Problemin boyutu ve kaynak sayısı birlikte artırılabilir.
  • Standart olmayan donanımlar arası iletişim: InfiniBand gibi yüksek hızlı ağlar üzerinden düşük gecikmeyle çalışır.
  • Olgun ekosistem: Onlarca yıllık birikim, geniş kütüphane ve araç desteği anlamına gelir.

MPI ve OpenMP Farkı

MPI ile sıklıkla karıştırılan OpenMP, aynı düğüm içindeki paylaşımlı bellek ortamında çalışan bir paralel programlama modelidir. MPI ise farklı düğümler arasında dağıtık bellek iletişimi sağlar. Birçok HPC uygulaması her iki modeli birden kullanan hibrit MPI+OpenMP yaklaşımını benimser: düğümler arası iletişimde MPI, düğüm içi paralelleştirmede OpenMP.

OpenMPI Kurulumu

Linux (Ubuntu/Debian)

sudo apt-get update
sudo apt-get install -y openmpi-bin openmpi-common libopenmpi-dev

Linux (RHEL/CentOS/Rocky)

sudo dnf install -y openmpi openmpi-devel
module load mpi/openmpi-x86_64

Kurulumu Doğrulama

mpicc --version
mpirun --version

Çıktı şuna benzer bir şey göstermelidir:

mpicc (Open MPI) 4.1.6
mpirun (Open MPI) 4.1.6

Temel MPI Kavramları

MPI programlarını anlamak için birkaç temel kavrama hakim olmak gerekir.

Rank ve Communicator

Her MPI süreci, rank adı verilen benzersiz bir tam sayı kimliğiyle tanımlanır. Süreci gruplayıp yönetmek için kullanılan yapıya ise communicator denir. Varsayılan communicator MPI_COMM_WORLD tüm çalışan süreçleri kapsar.

Temel MPI Fonksiyonları

FonksiyonAçıklama
MPI_InitMPI ortamını başlatır
MPI_FinalizeMPI ortamını sonlandırır
MPI_Comm_rankÇalışan sürecin rank numarasını döndürür
MPI_Comm_sizeToplam süreç sayısını döndürür
MPI_SendBelirtilen sürece mesaj gönderir (bloklu)
MPI_RecvBelirtilen süreçten mesaj alır (bloklu)
MPI_BcastBir süreçten tüm süreçlere veri yayınlar
MPI_ReduceTüm süreçlerin verilerini tek bir süreçte toplar
MPI_ScatterVeriyi süreçlere dağıtır
MPI_GatherSüreçlerden veriyi toplar

İlk MPI Programı: “Merhaba Dünya”

Aşağıdaki C örneği, her sürecin kendi rank numarasını ve toplam süreç sayısını ekrana yazdırdığı klasik başlangıç programıdır:

#include <mpi.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int rank, size;

    /* MPI ortamını başlat */
    MPI_Init(&argc, &argv);

    /* Bu sürecin rank numarasını al */
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    /* Toplam süreç sayısını al */
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    printf("Merhaba! Ben rank %d, toplam %d süreç var.\n", rank, size);

    /* MPI ortamını kapat */
    MPI_Finalize();
    return 0;
}

Derleme ve çalıştırma:

# mpicc, MPI için özelleştirilmiş bir C derleyici sarmalayıcısıdır
mpicc -o merhaba merhaba.c

# 4 süreçle çalıştır
mpirun -np 4 ./merhaba

Beklenen çıktı (sıra garanti değildir):

Merhaba! Ben rank 2, toplam 4 süreç var.
Merhaba! Ben rank 0, toplam 4 süreç var.
Merhaba! Ben rank 3, toplam 4 süreç var.
Merhaba! Ben rank 1, toplam 4 süreç var.

Noktadan Noktaya İletişim (Point-to-Point)

MPI’ın en temel iletişim biçimi, iki süreç arasındaki doğrudan mesaj alışverişidir. Aşağıdaki örnekte rank 0 bir mesaj gönderir, rank 1 ise onu alır:

#include <mpi.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    int rank;
    char mesaj[50];
    MPI_Status durum;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (rank == 0) {
        strcpy(mesaj, "Rank 1'e selam!");
        MPI_Send(mesaj, strlen(mesaj) + 1, MPI_CHAR, 1, 0, MPI_COMM_WORLD);
        printf("Rank 0 mesajı gönderdi.\n");
    } else if (rank == 1) {
        MPI_Recv(mesaj, 50, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &durum);
        printf("Rank 1 aldı: %s\n", mesaj);
    }

    MPI_Finalize();
    return 0;
}

Kolektif İşlemler: MPI_Reduce ile Pi Hesabı

Kolektif işlemler, tüm süreçlerin katıldığı iletişim kalıplarıdır. Şu örnek, Monte Carlo yöntemiyle Pi sayısını hesaplamak için MPI_Reduce kullanır; her süreç bağımsız bir örnekleme yapar ve sonuçlar rank 0’da toplanır:

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int rank, size;
    long long yerel_toplam = 0, genel_toplam = 0;
    long long N = 10000000; /* toplam örnekleme sayısı */

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    /* Her süreç N/size adet örnekleme yapar */
    long long yerel_N = N / size;
    srand(rank * 12345); /* her süreç farklı tohum kullanır */

    for (long long i = 0; i < yerel_N; i++) {
        double x = (double)rand() / RAND_MAX;
        double y = (double)rand() / RAND_MAX;
        if (x * x + y * y <= 1.0) yerel_toplam++;
    }

    /* Tüm süreçlerdeki toplamları rank 0'da birleştir */
    MPI_Reduce(&yerel_toplam, &genel_toplam, 1,
               MPI_LONG_LONG, MPI_SUM, 0, MPI_COMM_WORLD);

    if (rank == 0) {
        double pi = 4.0 * genel_toplam / N;
        printf("Tahmini Pi = %.6f\n", pi);
    }

    MPI_Finalize();
    return 0;
}

SLURM ile MPI İşi Gönderme

Gerçek bir HPC kümesinde işler doğrudan çalıştırılmaz; SLURM gibi bir iş zamanlayıcısına gönderilir. Aşağıda tipik bir SLURM betik dosyası örneği verilmiştir:

#!/bin/bash
#SBATCH --job-name=mpi_pi_hesap
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=16
#SBATCH --time=00:30:00
#SBATCH --partition=compute
#SBATCH --output=mpi_cikti_%j.log
#SBATCH --error=mpi_hata_%j.log

# Gerekli modülleri yükle
module load openmpi/4.1.6

# Toplam 32 süreç (2 düğüm × 16 görev) başlat
mpirun -np $SLURM_NTASKS ./pi_hesap

Betiği göndermek için:

sbatch pi_hesap.slurm

İşin durumunu izlemek için:

squeue -u $USER

SLURM Ortam Değişkenleri

SLURM, betik içinde kullanılabilecek çok sayıda ortam değişkeni tanımlar. En sık kullanılanları:

DeğişkenAçıklama
$SLURM_NTASKSToplam görev (MPI süreç) sayısı
$SLURM_NNODESAyrılan düğüm sayısı
$SLURM_NTASKS_PER_NODEDüğüm başına görev sayısı
$SLURM_JOB_IDİşin benzersiz kimliği
$SLURM_NODELISTKullanılan düğümlerin listesi

mpirun -np $SLURM_NTASKS kullanmak, betikteki --ntasks parametresini elle tekrarlamamanızı sağlar ve hata riskini azaltır.

Performans İpuçları

İletişim Maliyetini Azaltın

Mesaj sayısını ve boyutunu minimize etmek, MPI uygulamalarında en kritik optimizasyon adımıdır. Küçük mesajları birleştirip tek seferde göndermek (mesaj paketleme), gecikmeyi önemli ölçüde düşürür.

Bloklu vs. Bloksuz İletişim

MPI_Send ve MPI_Recv blokludur; süreç işlem tamamlanana kadar bekler. MPI_Isend ve MPI_Irecv ise bloksuz alternatiflerdir ve hesaplama ile iletişimin örtüşmesine (overlap) olanak tanır. Bu örtüşme, iletişim darboğazlarını azaltarak gerçek dünya performansını artırır.

Süreç Yerleşimini Optimize Edin

mpirun --bind-to core veya --map-by socket gibi seçeneklerle süreçlerin fiziksel çekirdeklere yerleşimi kontrol edilebilir. NUMA topolojisine duyarlı bir yerleşim, bellek bant genişliğini daha verimli kullanır.

Sık Karşılaşılan Hatalar

Uyumsuz MPI_Send/MPI_Recv çiftleri: Gönderilen ve alınan veri tipi veya sayısı uyuşmadığında program askıya alınabilir ya da tanımsız davranış sergiler. Her zaman MPI_Send ile MPI_Recv‘in parametre uyumuna dikkat edin.

Kilitlenme (Deadlock): İki sürecin birbirini beklemesi durumunda oluşur. Örneğin, her iki süreç de önce MPI_Send çağırıp sonra MPI_Recv bekliyorsa tampon dolduğunda kilitlenme meydana gelir. MPI_Sendrecv veya bloksuz iletişim fonksiyonları bu durumu önler.

MPI_Finalize’dan önce çıkış: MPI_Finalize çağrılmadan program sonlandırılırsa diğer süreçler askıda kalabilir. Her kod yolunun MPI_Finalize‘a ulaştığından emin olun.

Sonuç

MPI, HPC dünyasının omurgasını oluşturan ve onlarca yıldır olgunlaşmaya devam eden bir standarttır. Noktadan noktaya mesajlaşmadan kolektif işlemlere, bloklu iletişimden asenkron kalıplara kadar zengin bir araç seti sunar. SLURM ile entegre kullanıldığında, küçük ölçekli bir çalışmadan binlerce çekirdekli üretim iş yüklerine sorunsuz geçiş yapılabilir.

Mevasis olarak MPI tabanlı paralel uygulama geliştirme ve HPC altyapı kurulumu konusunda size destek olmaktan memnuniyet duyarız. İletişim için formu doldurun.