diff --git a/2_psrs_cdist/sheet_2.ipynb b/2_psrs_cdist/sheet_2.ipynb index fe71c6109382e75c78e493358de44138f378bbd4..f357c796befbaa18962afe9bac39f46ed248eb61 100644 --- a/2_psrs_cdist/sheet_2.ipynb +++ b/2_psrs_cdist/sheet_2.ipynb @@ -11,13 +11,13 @@ "Dr. Marie Weiel (marie.weiel@kit.edu) \n", "Dr. Kaleb Phipps (kaleb.phipps@kit.edu) \n", "\n", - "## Übung 2 am 03.12.24: Parallel Sorting by Regular Sampling und Pairwise Distances\n", - "In der zweiten Übung beschäftigen wir uns mit dem \"Parallel Sorting by Regular Sampling\" (PSRS) Algorithmus (siehe Vorlesung vom 07.11.24) und der parallelen Berechnung paarweiser Distanzen (\"pairwise distances\", siehe Vorlesung vom 14.11.24). \n", + "## Übung 2 am 03.12.24: Pairwise Distances und Parallel Sorting by Regular Sampling\n", + "In der zweiten Übung beschäftigen wir uns mit der parallelen Berechnung paarweiser Distanzen (\"pairwise distances\", siehe Vorlesung vom 14.11.24) und dem \"Parallel Sorting by Regular Sampling\" (PSRS) Algorithmus (siehe Vorlesung vom 07.11.24). \n", "\n", "### Aufgabe 1\n", "Untenstehend finden Sie eine parallele Implementierung eines Algorithmus zur Berechnung paarweiser Distanzen in `Python3`. Wir verwenden 50 000 Samples des [SUSY-Datensatzes](https://archive.ics.uci.edu/dataset/279/susy). Diese finden Sie in der HDF5-Datei `/pfs/work7/workspace/scratch/ku4408-VL-ScalableAI/data/SUSY_50k.h5` auf dem bwUniCluster. Der SUSY-Datensatz enthält insgesamt 5 000 000 Samples aus Monte-Carlo-Simulationen hochenergetischer Teilchenkollisionen. Jedes Sample hat 18 Features, bestehend aus kinematischen Eigenschaften, die typischerweise von Teilchendetektoren gemessen werden, sowie aus diesen Messungen abgeleiteten Größen. Führen Sie den Code auf einem, zwei, vier, acht sowie 16 CPU-basierten Knoten in den Partitionen \"single\" bzw. \"multiple\" des bwUniClusters aus. Untersuchen Sie das schwache sowie das starke Skalierungsverhalten des Algorithmus und stellen Sie diese grafisch dar, z.B. mit `matplotlib.pyplot` in `Python3`. \n", "\n", - "**Zur Erinnerung (siehe auch Vorlesung vom 2.11.23):** Bei der starken Skalierung wird die Problemgröße konstant gehalten, während man die Anzahl der Prozesse erhöht, d.h. es wird untersucht, inwieweit sich ein Problem konstanter Größe durch Hinzunahme von mehr Rechenressourcen schneller lösen lässt. Bei der schwachen Skalierung wird die Problemgröße pro Prozess konstant gehalten, während man die Anzahl der Prozesse erhöht, d.h. es wird untersucht, inwieweit sich ein größeres Problem durch Hinzunahme von mehr Rechenressourcen in gleicher Zeit lösen lässt. Das bedeutet, dass Sie die Problemgröße zur Untersuchung des schwachen Skalierungsverhaltens proportional anpassen müssen! \n", + "**Zur Erinnerung (siehe auch Vorlesung vom 31.10.24):** Bei der starken Skalierung wird die Problemgröße konstant gehalten, während man die Anzahl der Prozesse erhöht, d.h. es wird untersucht, inwieweit sich ein Problem konstanter Größe durch Hinzunahme von mehr Rechenressourcen schneller lösen lässt. Bei der schwachen Skalierung wird die Problemgröße pro Prozess konstant gehalten, während man die Anzahl der Prozesse erhöht, d.h. es wird untersucht, inwieweit sich ein größeres Problem durch Hinzunahme von mehr Rechenressourcen in gleicher Zeit lösen lässt. Das bedeutet, dass Sie die Problemgröße zur Untersuchung des schwachen Skalierungsverhaltens proportional anpassen müssen! \n", "\n", "**Vorgehensweise (analog zum ersten Übungsblatt):**\n", "- Laden Sie zunächst die benötigten Module auf dem bwUniCluster.\n", @@ -34,13 +34,13 @@ "source": [ "#!/bin/bash\n", "\n", - "#SBATCH --job-name=cdist # job name\n", - "#SBATCH --partition=multiple # queue for resource allocation\n", - "#SBATCH --nodes=2 # number of nodes to be used\n", - "#SBATCH --time=4:00 # wall-clock time limit\n", - "#SBATCH --mem=90000 # memory per node \n", - "#SBATCH --cpus-per-task=40 # number of CPUs required per MPI task\n", - "#SBATCH --ntasks-per-node=1 # maximum count of tasks per node\n", + "#SBATCH --job-name=cdist # Job name\n", + "#SBATCH --partition=multiple # Queue for resource allocation\n", + "#SBATCH --nodes=2 # Number of nodes to be used\n", + "#SBATCH --time=4:00 # Wall-clock time limit\n", + "#SBATCH --mem=90000 # Memory per node \n", + "#SBATCH --cpus-per-task=40 # Number of CPUs required per MPI task\n", + "#SBATCH --ntasks-per-node=1 # Maximum count of tasks per node\n", "#SBATCH --mail-type=ALL # Notify user by email when certain event types occur.\n", "\n", "export IBV_FORK_SAFE=1\n", @@ -335,35 +335,29 @@ " local_sorted, local_indices = torch.sort(a)\n", " print(f\"Rank {rank}/{size}: Local sorting done...[OK]\")\n", "\n", - " n_local = torch.tensor(\n", - " torch.numel(local_sorted), dtype=torch.int\n", - " ) # Number of elements in local chunk.\n", + " n_local = torch.numel(local_sorted) # Number of elements in local chunk.\n", " print(f\"Rank {rank}/{size}: Number of elements in local chunk is {n_local}.\")\n", - " counts = torch.zeros(\n", - " size, dtype=torch.int\n", - " ) # Initialize array for local element numbers.\n", - " comm.Allgather([n_local, MPI.INT], [counts, MPI.INT])\n", + " n_global = comm.allreduce(n_local, op=MPI.SUM)\n", "\n", " # Each rank chooses p regular samples.\n", " # For this, separate sorted tensor into p+1 equal-length partitions.\n", - " # Regular samples have indices 1, w+1, 2w+1,...,(p−1)w+1\n", - " # where w=n/p^2 (here: `size` = p, `n_local` = overall number of samples/p).\n", - " partitions = [int(x * n_local / size) for x in range(0, size)]\n", - "\n", - " reg_samples_local = local_sorted[partitions]\n", - " assert len(partitions) == size\n", - " print(\n", - " f\"Rank {rank}/{size}: There are {len(partitions)} local regular samples: {reg_samples_local}\"\n", + " # Regular samples have indices 0, w, 2w,...,(p−1)w where w=n/p^2.\n", + " # Here: `size` = p\n", + " w = int(n_global / size ** 2)\n", + " partitions = [idx * w for idx in range(0, size)]\n", + " regular_samples_local = local_sorted[partitions]\n", + " print(\n", + " f\"Rank {rank}/{size}: There are {len(partitions)} local regular samples: {regular_samples_local}\"\n", " )\n", "\n", " # Root gathers regular samples.\n", " num_regs_global = int(\n", - " comm.allreduce(torch.numel(reg_samples_local), op=MPI.SUM)\n", + " comm.allreduce(torch.numel(regular_samples_local), op=MPI.SUM)\n", " ) # Get overall number of regular samples.\n", " if rank == 0:\n", " print(f\"Overall number of regular samples is {num_regs_global}.\")\n", " reg_samples_global = torch.zeros(num_regs_global, dtype=a.dtype)\n", - " comm.Gather(reg_samples_local, reg_samples_global, root=0)\n", + " comm.Gather(regular_samples_local, reg_samples_global, root=0)\n", " if rank == 0:\n", " print(\"On root: Regular samples gathered...[OK]\")\n", "\n",