Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Ab HPC Pack 2012 R2 Update 3 können Sie die GPU-Ressourcen verwalten und überwachen und GPGPU-Aufträge auf den Computeknoten planen, um die GPU-Ressourcen vollständig zu nutzen.
In diesem Artikel:
Einsatz
Es gibt keine zusätzlichen Schritte, die Sie ausführen müssen, um die Computeknoten mit installierten unterstützten GPU-Karten manuell bereitzustellen. Sie können einer der vorhandenen Methoden folgen, um Ihrem HPC Pack-Cluster Computeknoten hinzuzufügen.
Hinweis
Derzeit unterstützt HPC Pack nur die CUDA-kompatiblen Produkte, z. B. die NVidia Tesla-Serie. Eine detailliertere Liste der unterstützten Produkte finden Sie auf der Nvidia-Website .
Verwaltung und Überwachung
GPU-Knotengruppe
Nachdem die GPU-Computeknoten erfolgreich bereitgestellt wurden, wird automatisch eine neue Standardknotengruppe (GPUNodes) erstellt. Alle Knoten, die mit unterstützten GPU-Ressourcen erkannt werden, werden dieser Gruppe hinzugefügt.
Es ist auch möglich, neue benutzerdefinierte Knotengruppen zu erstellen, die GPU-Knoten enthalten, basierend darauf, wie Sie die Computeressourcen im Cluster organisieren möchten. Dies ähnelt dem Erstellen benutzerdefinierter Gruppen mit anderen Knotentypen.
Knoteneigenschaften
Wenn Sie einen GPU-Computeknoten im HPC Cluster Manager auswählen, können Sie einige grundlegende Informationen auf der Registerkarte "GPU-Informationen " in den Knoteneigenschaftendetails anzeigen. Die grundlegenden GPU-Informationen umfassen:
GPU-Karte PIC BUS-ID
GPU-Modulname
Gesamtspeicher der GPU
GPU SM-Takt (Streaming Multiprozessoruhr in MHz)
GPU-Wärmekarte
Die folgenden GPU-Metriken werden den Wärmekartenansichten des Clusterknotens hinzugefügt:
GPU-Zeit (%)
GPU-Stromverbrauch (Watt)
GPU-Speicherauslastung (%)
GPU-Speicherauslastung(MB)
GPU-Lüftergeschwindigkeit (%)
GPU-Temperaturen (Grad C)
GPU SM-Takt (MHz)
Sie können Ihre eigene Ansicht der Wärmekarten anpassen, um die GPU-Nutzung auf die gleiche Weise wie bei anderen vorhandenen Wärmekartenvorgängen zu überwachen.
Wenn mehrere GPUs in einem Computeknoten vorhanden sind, werden mehrere Metrikwerte angezeigt.
GPU-Auftragsplanung
Wenn Sie die bewährten Methoden für CUDA-Codierung für HPC Pack (siehe weiter unten in diesem Artikel) ausführen, können GPU-Aufträge von HPC Pack geplant werden, um die GPU-Ressourcen im Cluster vollständig zu nutzen. Sie können dies in HPC Cluster Manager, HPC PowerShell oder der HPC Pack-Auftragsplanungs-API tun.
HPC Cluster Manager
So übermitteln Sie einen GPU-Auftrag im HPC Cluster Manager
Klicken Sie in "Auftragsverwaltung " auf "Neuer Auftrag".
Wählen Sie unter "Auftragsressourcen" unter "Auftragsressourcen" die OPTION "GPU" aus.
Wählen Sie unter "Ressourcenauswahl" eine Knotengruppe aus, die GPU-Knoten enthält. Andernfalls schlägt der auftrag, der für GPU-Ressourcen geplant ist, fehl.
Sie können auch angeben, auf welchen Knoten Sie die Aufträge ausführen möchten. Diese Knoten müssen auch GPU-Knoten sein.
Schließen Sie die verbleibenden Einstellungen ab, und übermitteln Sie den Auftrag.
HPC PowerShell
Um einen GPU-Auftrag über HPC PowerShell zu übermitteln, geben Sie den NumGpus-Parameter wie in den folgenden Beispielbefehlen an:
$job = New-HpcJob -NumGpus 1
Add-HpcTask -Job $job -CommandLine "<CommandLine>"
Submit-HpcJob -Job $job
HPC Pack-Auftragsplanungs-API
Wenn Sie einen GPU-Auftrag über die Auftragsplanungs-API übermitteln möchten, legen Sie dies UnitType
wie im folgenden Codeausschnitt gezeigt auf :JobUnitType.Gpu
Scheduler sc = new Scheduler();
sc.Connect(<ClusterName>);
ISchedulerJob job = sc.CreateJob();
sc.AddJob(job);
job.UnitType = JobUnitType.Gpu;
ISchedulerTask task = job.CreateTask();
task.CommandLine = "<CommandLine>";
job.AddTask(task);
sc.SubmitJob(job, <username>, <password>);
Auftragsvorlage
Um die Planung von GPU-Auftrags zu vereinfachen, erstellen Sie eine Auftragsvorlage, die Ihre GPU-bezogenen Einstellungen im Voraus vorbereitet.
So erstellen Sie eine Auftragsvorlage mit GPU-Einstellungen
Erstellen Sie in der Konfiguration eine neue Auftragsvorlage mit den richtigen Anfangseinstellungen.
Geben Sie im Auftragsvorlagen-Editor unter Knotengruppen als erforderlich eine oder mehrere Knotengruppen an, die GPU-Knoten enthalten.
Fügen Sie unter den Eigenschaften der Auftragsvorlage eine neue Einstellung für den Typ "Einheit " hinzu. Machen Sie GPU zum Standardwert, und wählen Sie ihn als gültigen Wert aus. Dadurch wird sichergestellt, dass GPU-Aufträge auf Basis von GPUs problemlos geplant werden können.
CUDA-Codebeispiel
Die GPU-Planung in HPC Pack (ab HPC Pack 2012 R2 Update 3) ist einfach. Der HPC Pack-Auftragsplaner weist die Aufträge und Aufgaben nur den Computeknoten zu, die über verfügbare GPU-Ressourcen verfügen.
Wenn einem Knoten ein Auftrag zugewiesen wird, müssen Sie zunächst sicherstellen, dass die richtige GPU-Einheit auf diesem Computer verwendet wird. Verwenden Sie die HPC Pack-Umgebungsvariable CCP_GPUIDS, um diese Informationen direkt abzurufen. Hier ist ein Codeausschnitt:
/* Host main routine */
int main(void)
{
// get the available free GPU ID and use it in this thread.
cudaSetDevice(atoi(getenv("CCP_GPUIDS")));
// other CUDA operations
}
// once job finishes, GPU will be freed up and recognized by job scheduler as available to assign to other job/tasks.
Der Rückgabewert des Aufrufs getenv("CCP_GPUIDS")
ist eine Zeichenfolge, die den Index der verfügbaren GPUs enthält. In den häufigsten Fällen benötigt jede Aufgabe eine GPU, und der Rückgabewert ist eine Zeichenfolge mit einer Indexziffer, z. B. 0, 1 usw.
Das folgende Beispiel enthält weitere Details:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <vector>
#include <sstream>
#include <iostream>
#include <ctime>
#include <algorithm>
__global__ void spinKernel(int count)
{
for (int i = 0; i < count; i++)
{
for (int j = 0; j < 1024; j++)
{
int hello = 0;
int world = 1;
int helloWorld = hello + world;
}
}
}
bool sortFunc(std::pair<int, std::string> *p1, std::pair<int, std::string> *p2)
{
return p1->second.compare(p2->second) < 0;
}
void getSortedGpuMap(std::vector<std::pair<int, std::string> *> &map)
{
int count = 0;
cudaGetDeviceCount(&count);
for (int i = 0; i < count; i++)
{
char buffer[64];
cudaDeviceGetPCIBusId(buffer, 64, i);
// Build a mapping between cuda Device Id and Pci Bus Id
map.push_back(new std::pair<int, std::string>(i, std::string(buffer)));
}
// Sort by Pci Bus Id
std::sort(map.begin(), map.end(), sortFunc);
}
int main()
{
// In case different processes get different cuda Device Ids, sort them by Pci Bus Id
// Not necessary if the devices always give the same Ids in a machine
std::vector<std::pair<int, std::string> *> deviceMap;
getSortedGpuMap(deviceMap);
cudaError_t cudaStatus = cudaSuccess;
std::vector<int> myGpuIds;
// Get the process-wide environment variable set by HPC
std::istringstream iss(getenv("CCP_GPUIDS"));
clock_t startTime = clock();
clock_t endTime;
do
{
std::string idStr;
iss >> idStr;
// Drop the ending chars
if (idStr.length() == 0)
{
break;
}
int gpuId = atoi(idStr.c_str());
std::cout << "GPU ID parsed: " << gpuId << std::endl;
// Set the device ID
cudaStatus = cudaSetDevice(deviceMap[gpuId]->first);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaSetDevice failed! Current GPU ID: %d", deviceMap[gpuId]->first);
return 1;
}
std::cout << "GPU with HPC ID " << gpuId << " has been set" << std::endl;
std::cout << "Cuda Device ID: " << deviceMap[gpuId]->first << std::endl << "Pci Bus Id: " << deviceMap[gpuId]->second << std::endl;
// Launch a kernel to spin GPU, async by default
spinKernel <<<1024, 1024>>> (1024);
endTime = clock();
double elapsed = (double)(endTime - startTime) / CLOCKS_PER_SEC;
startTime = endTime;
printf("Spin kernel launched after %.6f seconds\n\n", elapsed);
// Record the device IDs for further usage
myGpuIds.push_back(deviceMap[gpuId]->first);
} while (iss);
for (int i = 0; i < deviceMap.size(); i++)
{
// Clean up the map since device IDs have been saved
delete deviceMap[i];
}
std::cout << "Waiting for all the kernels finish..." << std::endl << std::endl;
for (std::vector<int>::iterator it = myGpuIds.begin(); it != myGpuIds.end(); it++)
{
// Set the device ID
cudaStatus = cudaSetDevice(*it);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaSetDevice failed! Current GPU ID: %d", *it);
return 1;
}
cudaDeviceSynchronize();
}
endTime = clock();
double elapsed = (double)(endTime - startTime) / CLOCKS_PER_SEC;
startTime = endTime;
printf("Spin kernels finished after %.6f seconds\n\n", elapsed);
for (std::vector<int>::iterator it = myGpuIds.begin(); it != myGpuIds.end(); it++)
{
// Set the device ID
cudaStatus = cudaSetDevice(*it);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaSetDevice failed! Current GPU ID: %d", *it);
return 1;
}
std::cout << "GPU with Device ID " << *it << " has been set" << std::endl;
// Do cleanup
cudaStatus = cudaDeviceReset();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaDeviceReset failed! Current GPU ID: %d", *it);
return 1;
}
std::cout << "Reset device done" << std::endl << std::endl;
}
return 0;
}