如何使用Nvidia多进程服务(MPS)运行多个非MPI CUDA应用程序?

如何使用Nvidia多进程服务(MPS)运行多个非MPI CUDA应用程序?,cuda,gpu,gpgpu,nvidia,kepler,Cuda,Gpu,Gpgpu,Nvidia,Kepler,我是否可以使用MPS在NVIDIA开普勒GPU上同时运行非MPI CUDA应用程序?我想这样做是因为我的应用程序不能充分利用GPU,所以我希望它们一起运行。是否有代码示例可以执行此操作?MPS服务的中包含了必要的说明。您会注意到,这些指令实际上并不依赖于或调用MPI,因此它们实际上没有任何MPI特定的内容 下面是一个演练/示例 阅读上述链接文件的第2.3节,了解各种要求和限制。为此,我建议使用CUDA 7、7.5或更高版本。与以前版本的CUDA MPS存在一些配置差异,我将在这里不介绍这些差异。

我是否可以使用MPS在NVIDIA开普勒GPU上同时运行非MPI CUDA应用程序?我想这样做是因为我的应用程序不能充分利用GPU,所以我希望它们一起运行。是否有代码示例可以执行此操作?

MPS服务的中包含了必要的说明。您会注意到,这些指令实际上并不依赖于或调用MPI,因此它们实际上没有任何MPI特定的内容

下面是一个演练/示例

  • 阅读上述链接文件的第2.3节,了解各种要求和限制。为此,我建议使用CUDA 7、7.5或更高版本。与以前版本的CUDA MPS存在一些配置差异,我将在这里不介绍这些差异。另外,我将演示如何使用单服务器/单GPU。我用于测试的机器是一个CentOS 6.2节点,使用K40c(cc3.5/Kepler)GPU和CUDA 7.0。节点中还有其他GPU。在我的例子中,CUDA枚举顺序将我的K40c放置在设备0上,但nvidia smi枚举顺序恰好将其作为id 2放置在该顺序中。所有这些细节在具有多个GPU的系统中都很重要,会影响下面给出的脚本

  • 我将创建几个helperbash脚本和一个测试应用程序。对于测试应用程序,我们希望内核能够与应用程序其他实例中的内核同时运行,并且我们还希望这些内核(来自不同的应用程序/进程)是否同时运行时能够变得明显。为了满足这些演示需求,让我们制作一个应用程序,它的内核只在单个SM上的单个线程中运行,只需等待一段时间(我们将使用~5秒)即可退出并打印消息。下面是一个测试应用程序,它可以做到这一点:

    $ cat t1034.cu
    #include <stdio.h>
    #include <stdlib.h>
    
    #define MAX_DELAY 30
    
    #define cudaCheckErrors(msg) \
      do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
      } while (0)
    
    
    #include <time.h>
    #include <sys/time.h>
    #define USECPSEC 1000000ULL
    
    unsigned long long dtime_usec(unsigned long long start){
    
      timeval tv;
      gettimeofday(&tv, 0);
      return ((tv.tv_sec*USECPSEC)+tv.tv_usec)-start;
    }
    
    #define APPRX_CLKS_PER_SEC 1000000000ULL
    __global__ void delay_kernel(unsigned seconds){
    
      unsigned long long dt = clock64();
      while (clock64() < (dt + (seconds*APPRX_CLKS_PER_SEC)));
    }
    
    int main(int argc, char *argv[]){
    
      unsigned delay_t = 5; // seconds, approximately
      unsigned delay_t_r;
      if (argc > 1) delay_t_r = atoi(argv[1]);
      if ((delay_t_r > 0) && (delay_t_r < MAX_DELAY)) delay_t = delay_t_r;
      unsigned long long difft = dtime_usec(0);
      delay_kernel<<<1,1>>>(delay_t);
      cudaDeviceSynchronize();
      cudaCheckErrors("kernel fail");
      difft = dtime_usec(difft);
      printf("kernel duration: %fs\n", difft/(float)USECPSEC);
      return 0;
    }
    
    
    $ nvcc -arch=sm_35 -o t1034 t1034.cu
    $ ./t1034
    kernel duration: 6.528574s
    $
    
  • 还有一个bash脚本,用于“同时”启动我们的测试应用程序的2个副本:

  • 我们还可以使用bash脚本关闭服务器,尽管本演练不需要它:

    $ cat stop_as_root.bash
    #!/bin/bash
    echo quit | nvidia-cuda-mps-control
    nvidia-smi -i 2 -c DEFAULT
    $
    
  • 现在,当我们使用上面的
    mps\u run
    脚本启动我们的测试应用程序时,在没有实际启用mps服务器的情况下,我们得到了预期的行为,即应用程序的一个实例需要大约5秒的时间,而另一个实例需要大约两倍的时间(~10秒),因为,由于它不会与来自另一进程的应用程序同时运行,因此在另一个应用程序/内核运行时,它会等待5秒,然后花5秒运行自己的内核,总共约10秒:

    $ ./mps_run
    kernel duration: 6.409399s
    kernel duration: 12.078304s
    $
    
  • 另一方面,如果我们先启动MPS服务器,然后重复测试:

    $ su
    Password:
    # ./start_as_root.bash
    Set compute mode to EXCLUSIVE_PROCESS for GPU 0000:82:00.0.
    All done.
    # exit
    exit
    $ ./mps_run
    kernel duration: 6.167079s
    kernel duration: 6.263062s
    $
    
    我们发现这两个应用程序运行的时间相同,因为由于MPS,内核同时运行

  • 欢迎你来做你认为合适的实验。如果此序列似乎对您正常工作,但运行您自己的应用程序似乎无法获得预期的结果,一个可能的原因可能是由于内核的构造,您的应用程序/内核无法与其他应用程序/内核实例同时运行,而与MPS无关。您可能需要验证和/或研究

  • 这里的大部分信息都是从测试/工作中回收的,尽管这里使用单独的应用程序进行的演示与这里展示的MPI案例不同

  • 更新:当从多个进程运行内核时,非MPS情况下的调度程序行为似乎已随Pascal和更新的GPU而改变。上述测试结果对于在上测试的GPU(例如开普勒)仍然是正确的,但当在Pascal或更新的GPU上运行上述测试用例时,在非MPS情况下将观察到不同的结果。调度器在中被描述为“时间切片”调度器,似乎发生的事情是,根据一些未发布的规则,调度器可以选择抢占正在运行的内核,以便它可以从另一个进程切换到另一个内核,而不是等待一个进程中的内核完成。这仍然不意味着来自不同进程的内核在CUDA文档中的传统用法中“并发”运行,但是上面的代码被时间片调度程序(在Pascal和更新版本上)所“欺骗”,因为它依赖于使用SM时钟来设置内核持续时间。时间片调度程序加上SM时钟的使用,使得这个测试用例看起来“并发”运行。但是,如MPS文档中所述,当A和B在非MPS情况下来自不同进程时,内核A的代码与内核B的代码在相同的时钟周期内执行


    使用上述通用方法证明这一点的另一种方法可能是使用由多个循环设置的内核持续时间,而不是通过读取SM时钟设置的内核持续时间,如前所述。在这种情况下,必须小心避免编译器“优化”循环。

    CUDA是否可以从头开始实现MPS?我不相信。@RobertCrovella您能分享一下您对这个问题的想法吗?你为什么认为这是不可能的?MPS似乎使用了一些未记录的驱动程序API或类似的东西吗?MPS接受从不同进程发出的工作(例如CUDA内核启动),并在设备上运行它们,就像它们从单个进程发出一样。就像它们在单个上下文中运行一样。我不知道如何使用我熟悉的当前公开的API做到这一点。除此之外,我没有任何进一步的想法,也不太可能回答关于这个话题的进一步问题。
    $ ./mps_run
    kernel duration: 6.409399s
    kernel duration: 12.078304s
    $
    
    $ su
    Password:
    # ./start_as_root.bash
    Set compute mode to EXCLUSIVE_PROCESS for GPU 0000:82:00.0.
    All done.
    # exit
    exit
    $ ./mps_run
    kernel duration: 6.167079s
    kernel duration: 6.263062s
    $