-
Notifications
You must be signed in to change notification settings - Fork 0
03. Task scheduling on MIG enabled A100
App의 kernel launch api를 hooking해서 kernel별로 instance를 할당할 것인지,
아니면 app별로 instance를 할당할 것인지 고민해봤습니다.
Kernel별로 할당하는 편이 좀 더 섬세한 스케줄링이 가능하지 않겠나 생각이 듭니다.
아래 두 가지 가정 하에 dynamic profiling이 낫다고 판단됩니다.
- Kernel 단위 스케줄링
- CUDA application들은 대체로 kernel을 여러 번 반복 실행함
Dynamic profiling이 상대적으로 정확하지만, overhead가 크다고 알려져 있습니다.
그러나 2번 가정이 충족된다면 kernel이 최초 실행될 때 한 번만 profiling해서 스케줄러에 캐싱해놓으면 됩니다.
의외로, 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을 적용하기도 어려워 보입니다.
우선, 여러 개의 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 벤치마크를 가지고 실험을 해봤습니다.
- 1g.5gb instance 2개와 2g.5gb instance 1개를 만들고, 각각에서 MPS daemon을 실행해 둡니다. 각 instance를 GI1, GI2, GI3이라고 하겠습니다.
- 4개의 서로 다른 app을 프로파일링해서 SM 사용률을 측정합니다. SM 사용률이 낮은 순서대로 app1, app2, app3, app4라고 하겠습니다.
- GI1에서 app1과 app2를, GI2에서 app3과 app4를 동시에 launch하고 모든 app이 끝나기까지의 시간(t_unbalanced)을 측정합니다.
- GI1에서 app1과 app4를, GI2에서 app2와 app3을 동시에 launch하고 모든 app이 끝나기까지의 시간(t_balanced)을 측정합니다.
- 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))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별 실행 시간은 자원 격리를 안 했을 때 오히려 더 짧아지는 경향을 보입니다.