Skip to content

03. Task scheduling on MIG enabled A100

HongBeenKim edited this page Jul 19, 2022 · 1 revision

App 단위 스케줄링 vs Kernel 단위 스케줄링

App의 kernel launch api를 hooking해서 kernel별로 instance를 할당할 것인지,
아니면 app별로 instance를 할당할 것인지 고민해봤습니다.
Kernel별로 할당하는 편이 좀 더 섬세한 스케줄링이 가능하지 않겠나 생각이 듭니다.

Dynamic Profiling vs Static Profiling

아래 두 가지 가정 하에 dynamic profiling이 낫다고 판단됩니다.

  1. Kernel 단위 스케줄링
  2. CUDA application들은 대체로 kernel을 여러 번 반복 실행함

Dynamic profiling이 상대적으로 정확하지만, overhead가 크다고 알려져 있습니다.
그러나 2번 가정이 충족된다면 kernel이 최초 실행될 때 한 번만 profiling해서 스케줄러에 캐싱해놓으면 됩니다.

뒤늦게 발견한 CUDA api의 한계점

의외로, kernel을 어떤 MIG instance에서 launch할지 선택할 수가 없다고 합니다.
cudaSetDevice() api가 있길래 당연히 이걸로 될 줄 알았는데 아직 MIG instance는 지원을 안 한다고 합니다.
추후 api 업데이트에서 지원할 가능성은 있다고 합니다.
(https://forums.developer.nvidia.com/t/how-to-use-cuda-visible-devices-for-mig-instances/195069)

그래서 일단은 앞서 언급한 두 고민이 의미가 없어진 것 같습니다.
선택의 여지 없이 app 단위 스케줄링으로 결정해야겠네요.
그러면 dynamic profiling을 적용하기도 어려워 보입니다.

Rodinia 벤치마크로 간단한 스케줄링 해보기

우선, 여러 개의 GPU task를 여러 개의 GPU에 분배하는 방법에 관한 논문을 발견해서 읽어봤습니다.
(https://s-space.snu.ac.kr/bitstream/10371/122704/1/000000142512.pdf)
이 논문에서는 Host-GPU간 메모리 복사 시간이 많은 비중을 차지하는 app을 memory-intensive app으로, kernel 실행 시간이 많은 비중을 차지하는 app을 compute-intensive app으로 정의합니다. 메모리 복사는 한 순간에 한 프로세스만 할 수 있고, 메모리 복사와 kernel 실행은 서로 다른 프로세스라도 동시에 진행될 수 있기 때문에 memory-intensive app과 compute-intensive app을 섞어서 분배하면 GPU utilization이 향상된다는 내용입니다. 즉 동시에 실행될 수 있는 부분들을 잘 겹치게 해서 성능 향상을 이끌어낸 것입니다 (논문에서는 이 효과를 latency-hiding이라고 표현합니다).

Latency-hiding뿐만 아니라, 여러 프로세스의 kernel들이 MPS 위에서 동시에 실행될 때 발생하는 자원(SM) 경쟁을 조정하는 것도 전체적인 throughput 향상에 도움이 될 것 같습니다. SM을 많이 쓰는 app과 그렇지 않은 app을 짝짓는 방식으로 load balance를 맞추면 모든 task가 끝나기까지의 시간이 더 적게 걸릴 것이라는 가설입니다.

가설을 검증해보기 위해서 위 논문에서 사용한 rodinia 벤치마크를 가지고 실험을 해봤습니다.

실험 설계

  1. 1g.5gb instance 2개와 2g.5gb instance 1개를 만들고, 각각에서 MPS daemon을 실행해 둡니다. 각 instance를 GI1, GI2, GI3이라고 하겠습니다.
  2. 4개의 서로 다른 app을 프로파일링해서 SM 사용률을 측정합니다. SM 사용률이 낮은 순서대로 app1, app2, app3, app4라고 하겠습니다.
  3. GI1에서 app1과 app2를, GI2에서 app3과 app4를 동시에 launch하고 모든 app이 끝나기까지의 시간(t_unbalanced)을 측정합니다.
  4. GI1에서 app1과 app4를, GI2에서 app2와 app3을 동시에 launch하고 모든 app이 끝나기까지의 시간(t_balanced)을 측정합니다.
  5. GI3에서 4개의 app을 동시에 launch하고 모든 app이 끝나기까지의 시간(t_baseline)을 측정합니다.

t_unbalanced를 측정하는 데 사용한 shell script

#!/bin/bash
GI1=MIG-6e5ecf1c-980b-53b4-b79e-df70177fd284
GI2=MIG-3234bc3b-83f3-5e3a-940e-d1c72da74e00

ts=$(date +%s%N)
export CUDA_VISIBLE_DEVICES=$GI1
export CUDA_MPS_PIPE_DIRECTORY=/tmp/$GI1
/home/dtb05045/gpu-rodinia/cuda/gaussian/gaussian -s 4096 &
/home/dtb05045/gpu-rodinia/cuda/srad/srad_v1/srad 100000 0.5 502 458 &

export CUDA_VISIBLE_DEVICES=$GI2
export CUDA_MPS_PIPE_DIRECTORY=/tmp/$GI2
/home/dtb05045/gpu-rodinia/cuda/lavaMD/lavaMD -boxes1d 60 &
/home/dtb05045/gpu-rodinia/cuda/hotspot/hotspot 512 2 500000 \
    /home/dtb05045/gpu-rodinia/data/hotspot/temp_512 \
    /home/dtb05045/gpu-rodinia/data/hotspot/power_512 output.out &

wait
echo $((($(date +%s%N) - $ts)/1000000))

t_balanced를 측정하는 데 사용한 shell script

#!/bin/bash
GI1=MIG-6e5ecf1c-980b-53b4-b79e-df70177fd284
GI2=MIG-3234bc3b-83f3-5e3a-940e-d1c72da74e00

ts=$(date +%s%N)
export CUDA_VISIBLE_DEVICES=$GI1
export CUDA_MPS_PIPE_DIRECTORY=/tmp/$GI1
/home/dtb05045/gpu-rodinia/cuda/gaussian/gaussian -s 4096 &
/home/dtb05045/gpu-rodinia/cuda/lavaMD/lavaMD -boxes1d 60 &

export CUDA_VISIBLE_DEVICES=$GI2
export CUDA_MPS_PIPE_DIRECTORY=/tmp/$GI2
/home/dtb05045/gpu-rodinia/cuda/hotspot/hotspot 512 2 500000 \
    /home/dtb05045/gpu-rodinia/data/hotspot/temp_512 \
    /home/dtb05045/gpu-rodinia/data/hotspot/power_512 output.out &
/home/dtb05045/gpu-rodinia/cuda/srad/srad_v1/srad 100000 0.5 502 458 &

wait
echo $((($(date +%s%N) - $ts)/1000000))

t_baseline를 측정하는 데 사용한 shell script

#!/bin/bash
GI1=MIG-6e5ecf1c-980b-53b4-b79e-df70177fd284
GI2=MIG-3234bc3b-83f3-5e3a-940e-d1c72da74e00
GI3=MIG-7dd84563-1863-50f4-ad6d-1fcc76ecf77a

ts=$(date +%s%N)

export CUDA_VISIBLE_DEVICES=$GI3
export CUDA_MPS_PIPE_DIRECTORY=/tmp/$GI3
/home/dtb05045/gpu-rodinia/cuda/gaussian/gaussian -s 4096 > ga.log &
/home/dtb05045/gpu-rodinia/cuda/srad/srad_v1/srad 100000 0.5 502 458 > srad.log &
/home/dtb05045/gpu-rodinia/cuda/hotspot/hotspot 512 2 500000 \
    /home/dtb05045/gpu-rodinia/data/hotspot/temp_512 \
    /home/dtb05045/gpu-rodinia/data/hotspot/power_512 output.out > hotspot.log &
/home/dtb05045/gpu-rodinia/cuda/lavaMD/lavaMD -boxes1d 60 > lavaMD.log &

wait
echo $((($(date +%s%N) - $ts)/1000000))

SM 사용률 측정 결과

app1 (gaussian)

app2 (srad)

app3 (hotspot)

app4 (lavaMD)

실행 시간 측정 결과

t_baseline: 24792 ms
각 app별 실행 시간 (ms)

app1(gaussian) app2(srad) app3(hotspot) app4(lavaMD)
12045 24698 20251 2972

t_unbalanced: 33333 ms
각 app별 실행 시간 (ms)

app1(gaussian) app2(srad) app3(hotspot) app4(lavaMD)
20365 33258 16405 4835

t_balanced: 24721 ms
각 app별 실행 시간 (ms)

app1(gaussian) app2(srad) app3(hotspot) app4(lavaMD)
23602 24544 18048 4891

Load balance를 고려한 경우 그렇지 않았을 때에 비해 모든 task가 끝나기까지의 시간이 약 24%정도 적게 측정되었습니다. 그러나 2g.10gb instance에서 네 개의 app을 동시에 실행했을 때(즉 자원 격리를 안 했을 때)와 거의 차이가 보이지 않습니다. app별 실행 시간은 자원 격리를 안 했을 때 오히려 더 짧아지는 경향을 보입니다.