Freigeben über


Verwenden von GPU-Computeknoten

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
  1. Klicken Sie in "Auftragsverwaltung " auf "Neuer Auftrag".

  2. Wählen Sie unter "Auftragsressourcen" unter "Auftragsressourcen" die OPTION "GPU" aus.

  3. 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.

  4. 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
  1. Erstellen Sie in der Konfiguration eine neue Auftragsvorlage mit den richtigen Anfangseinstellungen.

  2. Geben Sie im Auftragsvorlagen-Editor unter Knotengruppen als erforderlich eine oder mehrere Knotengruppen an, die GPU-Knoten enthalten.

  3. 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;  
}