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ı
| Fonksiyon | Açıklama |
|---|---|
MPI_Init | MPI ortamını başlatır |
MPI_Finalize | MPI ortamını sonlandırır |
MPI_Comm_rank | Çalışan sürecin rank numarasını döndürür |
MPI_Comm_size | Toplam süreç sayısını döndürür |
MPI_Send | Belirtilen sürece mesaj gönderir (bloklu) |
MPI_Recv | Belirtilen süreçten mesaj alır (bloklu) |
MPI_Bcast | Bir süreçten tüm süreçlere veri yayınlar |
MPI_Reduce | Tüm süreçlerin verilerini tek bir süreçte toplar |
MPI_Scatter | Veriyi süreçlere dağıtır |
MPI_Gather | Sü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şken | Açıklama |
|---|---|
$SLURM_NTASKS | Toplam görev (MPI süreç) sayısı |
$SLURM_NNODES | Ayrılan düğüm sayısı |
$SLURM_NTASKS_PER_NODE | Düğüm başına görev sayısı |
$SLURM_JOB_ID | İşin benzersiz kimliği |
$SLURM_NODELIST | Kullanı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.