GPU 프로파일링을 사용하여 AKS(Azure Kubernetes Service) GPU 워크로드 최적화(미리 보기)

AI 유추 서비스와 같은 GPU 기반 워크로드는 메모리를 많이 사용하고 GPU가 실제로 수행하는 작업을 자세히 파악하지 않고도 최적화 및 디버그하기 어려울 수 있습니다. OOM(메모리 부족) 오류, 예기치 않은 대기 시간 급증 또는 GPU 메모리 압력 증가가 표시될 수 있지만 기존 Kubernetes 메트릭은 코드에서 메모리가 할당되는 위치를 알려주지 않습니다. 프로파일링은 GPU 메모리 사용을 담당하는 정확한 함수를 이해하는 데 도움이 됩니다.

Important

AKS 미리 보기 기능은 셀프 서비스에서 사용할 수 있습니다(옵트인 방식). 미리 보기는 "있는 그대로" 및 "사용 가능한 상태로" 제공되며 서비스 수준 계약 및 제한적 보증에서 제외됩니다. AKS 미리 보기의 일부는 고객 지원팀에서 최선을 다해 지원합니다. 따라서 이러한 기능은 프로덕션 용도로 사용할 수 없습니다. 자세한 내용은 다음 지원 문서를 참조하세요.

이 문서에서는 AKS에서 GPU 관찰 기능을 사용하는 방법을 안내합니다.

  1. 실시간 GPU 관찰 에이전트 배포 - eBPF 기반 계측을 사용하여 GPU 메모리 할당을 추적하고 프로파일러합니다.
  2. 불꽃 그래프 읽기 - 프로파일링 출력을 해석하여 가장 많은 GPU 메모리를 사용하는 정확한 함수를 찾는 방법을 알아봅니다.

Important

오픈 소스 소프트웨어는 AKS 설명서와 샘플 전반에서 언급되어 있습니다. 배포하는 소프트웨어는 AKS 서비스 수준 계약, 제한된 보증 및 Azure 지원 제외됩니다. AKS와 함께 오픈 소스 기술을 사용하는 경우 각 커뮤니티 및 프로젝트 유지 관리자에서 사용할 수 있는 지원 옵션을 참조하여 계획을 개발합니다.

Microsoft는 AKS에 배포하는 오픈 소스 패키지를 빌드하는 역할을 담당합니다. 해당 책임에는 컨테이너 이미지의 이진 파일에 대한 제어와 함께 빌드, 스캔, 서명, 유효성 검사 및 핫픽스 프로세스의 완전한 소유권이 포함됩니다. 자세한 내용은 AKS의 취약성 관리AKS 지원 범위를 참조하세요.

GPU 관찰성 배포 - AKS에서 GPU 메모리 프로파일링

사전 요구 사항

  • GPU 사용 노드 풀이 하나 이상 있는 AKS 클러스터입니다.
  • Azure CLI 버전 2.72.0 이상이 설치되었습니다. 실행 az --version 하여 확인합니다.
  • k8s-extension Azure CLI 추가 기능이 설치되었습니다. 설치하려면 az extension add --name k8s-extension을 실행하세요.
  • Helm 버전 3.x 이상이 설치되었습니다. 실행 helm version 하여 확인합니다.
  • Azure Monitor(선택 사항으로, 원하는 경우 사용자 고유의 모니터링 설정을 사용할 수 있습니다).
  • Azure Managed Grafana(시각화의 경우 선택 사항).

1단계: Inspektor 가젯 확장을 통해 GPU 프로파일링 사용

Inspektor 가젯은 Kubernetes에 대한 오픈 소스 eBPF 기반의 관찰성 프레임워크입니다. GPU 프로파일링의 경우 코드 변경, 사이드카 또는 Pod 다시 시작 없이 CUDA(Compute Unified Device Architecture) 메모리 할당 호출을 추적합니다. 다음 확장 명령을 실행하여 AKS 클러스터에서 GPU 프로파일링을 사용하도록 설정합니다.

az k8s-extension create \
  --extension-type microsoft.inspektorgadget \
  --subscription <your-subscription-id> \
  -g <your-resource-group> \
  -c <your-cluster-name> \
  -t managedClusters \
  --release-train preview \
  -n inspektor-gadget \
  --configuration-settings gpuObservability.enabled=true \
  --configuration-settings azureMonitor.enabled=true \

메모

이 단계에서는 AKS 클러스터에서 Azure Monitor 이미 사용하도록 설정했다고 가정합니다. 자체 Prometheus 설정을 사용할 계획이라면 --configuration-settings azureMonitor.enabled=true를 제거하세요.

파드가 실행 중인지 확인하세요:

kubectl get pods -n gadget -l k8s-app=gadget

Tip

GPU 메모리 프로파일링은 발생하는 메모리 할당 이벤트를 캡처합니다. GPU 프로파일링을 사용하도록 설정하기 전에 워크로드가 GPU 메모리를 할당하는 경우 프로파일러가 이러한 할당 이벤트를 캡처하지 않습니다. 시작 중에 GPU 메모리를 미리 할당하는 vLLM과 같은 워크로드의 경우 워크로드를 배포하기 전에 GPU 프로파일링을 사용하도록 설정하거나 워크로드를 다시 시작하여 초기 메모리 할당 경로를 캡처합니다.

2단계: 파이로스코프를 사용하여 프로필 시각화 사용

메모

클러스터에 기존 Grafana/Pyroscope 스택이 있는 경우 이 단계를 건너뛸 수 있습니다.

파이로스코프는 메모리 최적화 및 문제 해결에 필요한 성능 프로필을 시각화하고 저장할 수 있는 오픈 소스 프로젝트입니다. 다음 명령을 실행하여 클러스터에 단일 파이로스코프 인스턴스를 배포합니다.

helm install pyroscope -n gadget \
  oci://ghcr.io/grafana/helm-charts/pyroscope \
  --version 1.15.0 \
  --set pyroscope.image.repository=grafana/pyroscope \
  --set-string pyroscope.image.tag=1.15.0 \
  --set pyroscope.replicaCount=1 \
  --set pyroscope.structuredConfig.self_profiling.disable_push=true \
  --set pyroscope.structuredConfig.storage.backend=filesystem \
  --set pyroscope.service.type=LoadBalancer \
  --set pyroscope.service.port=4040 \
  --set-string pyroscope.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-internal"=true \
  --set-string pyroscope.service.annotations."service\.beta\.kubernetes\.io/azure-pls-create"=true \
  --set-string pyroscope.service.annotations."service\.beta\.kubernetes\.io/azure-pls-name"=pyroscope-pls \
  --set-string pyroscope.service.annotations."service\.beta\.kubernetes\.io/azure-pls-proxy-protocol"=false \
  --set-string pyroscope.service.annotations."service\.beta\.kubernetes\.io/azure-pls-visibility"='*' \
  --set alloy.enabled=false \
  --set minio.enabled=false

파드가 실행 중인지 확인하세요:

kubectl get pods -n gadget pyroscope-0

메모

고가용성 파이로스코프 설정을 배포하려면 구성 옵션에 대한 파이로스코프 마이크로 서비스 설명서를 참조하세요.

3단계: 파이로스코프를 Azure Managed Grafana 연결

Tip

kubectl port-forward -n gadget pyroscope-0 4040:4040를 사용해 Pyroscope UI에 연결하여 워크로드 프로필을 직접 볼 수 있습니다.

파이로스코프를 Azure Managed Grafana 연결하면 Grafana 대시보드에서 GPU 프로필을 시각화할 수 있습니다. AMG가 Kubernetes Pod로 실행되는 파이로스코프에 연결할 수 있는 안전한 방법이 필요하므로 Azure Private Link 사용하여 연결을 설정합니다. 먼저 클러스터 관련 환경 변수를 설정합니다.

export RESOURCE_GROUP="<your-resource-group>"
export AKS_CLUSTER="<your-aks-cluster-name>"
export LOCATION="<your-aks-cluster-location>"
export GRAFANA_NAME="<your-azure-managed-grafana-name>"
export AKS_NODE_RG=$(az aks show -g "$RESOURCE_GROUP" -n "$AKS_CLUSTER" --query 'nodeResourceGroup' -o tsv)

Tip

기존 Azure Managed Grafana 인스턴스가 없는 경우 실행 az grafana create -n "$GRAFANA_NAME" -g "$RESOURCE_GROUP" --location "$LOCATION" -o none 하여 만듭니다.

파이로스코프를 Azure Managed Grafana 연결하는 프라이빗 엔드포인트를 만듭니다. 프라이빗 엔드포인트에 대한 변수 내보내기:

export PYROSCOPE_PLS="pyroscope-pls"
export PYROSCOPE_MPE="pyroscope-mpe"
export PYROSCOPE_PORT="4040"

프라이빗 링크를 만듭니다.

메모

프라이빗 링크를 만드는 데 몇 분 정도 걸릴 수 있습니다.

# Check amg extension version
ver=$(az extension show --name amg --query version -o tsv)
[[ "${ver%%.*}" -ge 3 ]] && MPE="managed-private-endpoint" || MPE="mpe"

# Ensure Pyroscope PLS is present
until az network private-link-service show -n "$PYROSCOPE_PLS" -g "$AKS_NODE_RG" -o none 2>/dev/null; do
  sleep 10
done

# Get the PLS resource ID
PYRO_PLS_ID=$(az network private-link-service show \
  -n "$PYROSCOPE_PLS" -g "$AKS_NODE_RG" --query 'id' -o tsv)

# Create the MPE in Grafana
az grafana $MPE create \
  --workspace-name "$GRAFANA_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --name "$PYROSCOPE_MPE" \
  --private-link-resource-id "$PYRO_PLS_ID" \
  --location "$LOCATION" -o none

# Wait for MPE to be ready
sleep 30

# Find the pending connection created by Grafana
PYRO_CONN=$(az network private-link-service show \
  -n "$PYROSCOPE_PLS" -g "$AKS_NODE_RG" \
  --query "privateEndpointConnections[?privateLinkServiceConnectionState.status=='Pending' && starts_with(name, 'grafana-${GRAFANA_NAME}')].name | [0]" -o tsv)

# Approve it
az network private-link-service connection update \
  --name "$PYRO_CONN" \
  --service-name "$PYROSCOPE_PLS" \
  --resource-group "$AKS_NODE_RG" \
  --connection-status Approved -o none

# Refresh Grafana so it sees the approval
az grafana $MPE refresh \
  --workspace-name "$GRAFANA_NAME" \
  --resource-group "$RESOURCE_GROUP" -o none

echo "Successfully created private-link"

Azure Managed Grafana 데이터 원본을 만듭니다.

# Check amg extension version
ver=$(az extension show --name amg --query version -o tsv)
[[ "${ver%%.*}" -ge 3 ]] && MPE="managed-private-endpoint" || MPE="mpe"

# Grab the private IP
PYRO_IP=$(az grafana $MPE show \
  --workspace-name "$GRAFANA_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --name "$PYROSCOPE_MPE" \
  --query 'privateLinkServicePrivateIP' -o tsv)

# Prepare Pyroscope URL
export PYROSCOPE_URL="http://${PYRO_IP}:${PYROSCOPE_PORT}"

# Create Pyroscope data source in Grafana
az grafana data-source create -n "$GRAFANA_NAME" -g "$RESOURCE_GROUP" --definition "{
  \"name\": \"local-pyroscope\",
  \"uid\": \"local-pyroscope\",
  \"type\": \"grafana-pyroscope-datasource\",
  \"access\": \"proxy\",
  \"url\": \"${PYROSCOPE_URL}\",
  \"jsonData\": { \"keepCookies\": [\"pyroscope_git_session\"] }
}" -o none

echo "Successfully created local-pyroscope data-source"

메모

클러스터에 기존 Grafana/Pyroscope 스택이 있는 경우 이 단계를 건너뛸 수 있습니다.

데이터 원본에 유효한 URL이 있는지 확인합니다.

az grafana data-source show -n $GRAFANA_NAME --data-source local-pyroscope

4단계: Grafana를 Prometheus용 Azure Monitor 관리형 서비스에 연결

메모

이러한 단계는 Prometheus to Grafana에 대한 Connect Azure Monitor 관리 서비스를 기반으로 합니다. 특히 다른 리소스 그룹 또는 구독에 있는 경우 Grafana의 관리 ID가 Azure Monitor 작업 영역에서 모니터링 데이터 판독기 역할을 가지고 있는지 확인합니다.

필요한 변수를 내보냅니다.

export AMP_NAME="<your-amp-workspace-name>"
export AMP_RESOURCE_GROUP="<your-amp-resource-group>"

Tip

실행 az resource list --resource-type Microsoft.Monitor/accounts -g $AMP_RESOURCE_GROUP -o table 하여 Azure Monitor 작업 영역 정보를 나열합니다. Azure Monitor 작업 영역은 AKS 클러스터 및 Azure Managed Grafana 인스턴스와는 다른 리소스 그룹에 있는 경우가 많습니다. 예를 들어 Managed Prometheus가 지역 MA_<region>_<…> 리소스 그룹에 작업 영역을 자동으로 만들거나 중앙 플랫폼 팀이 공유 작업 영역을 소유하는 경우와 같습니다. 대신 구독 전체에서 검색하려면 -g을 생략하고 az resource list --resource-type Microsoft.Monitor/accounts -o table을 실행합니다.

# Get AMW endpoint
AMP_ENDPOINT=$(az resource show --resource-type Microsoft.Monitor/accounts \
  -n "$AMP_NAME" -g "$AMP_RESOURCE_GROUP" \
  --query properties.metrics.prometheusQueryEndpoint -o tsv)

# Create Prometheus data source with MSI auth
az grafana data-source create -n "$GRAFANA_NAME" -g "$RESOURCE_GROUP" \
  --definition "{
    \"name\": \"$AMP_NAME\",
    \"type\": \"prometheus\",
    \"access\": \"proxy\",
    \"url\": \"$AMP_ENDPOINT\",
    \"jsonData\": {
      \"httpMethod\": \"POST\",
      \"azureCredentials\": { \"authType\": \"msi\" }
    }
  }"

데이터 원본을 확인합니다.

az grafana data-source show -n $GRAFANA_NAME --data-source $AMP_NAME

5단계: Grafana에서 대시보드 설정

az grafana dashboard create \
  -n "$GRAFANA_NAME" \
  -g "$RESOURCE_GROUP" \
  --definition "$(curl -sSL https://raw.githubusercontent.com/inspektor-gadget/grafana-dashboards/refs/heads/main/dashboards/gpu-observability/AdvancedGPUObservability.json)"

다음 위치에서 대시보드에 액세스합니다.

GRAFANA_URL=$(az grafana show -n "$GRAFANA_NAME" -g "$RESOURCE_GROUP" --query properties.endpoint -o tsv)

echo "${GRAFANA_URL}/d/AdvancedGPUObservability"

Grafana에 표시된 불꽃 그래프를 읽는 방법에 대한 자세한 내용은 불꽃 그래프 읽기를 참조하세요.

자원을 정리하세요

클러스터 내 GPU 관찰성 스택을 제거하려면 다음을 수행합니다.

helm uninstall inspektor-gadget -n gadget
helm uninstall pyroscope -n gadget
kubectl delete namespace gadget

화염 그래프 읽기

프로파일링 데이터가 파이로스코프 및 Grafana로 전달되면 GPU 메모리를 가장 많이 사용하는 함수를 보여 주는 불꽃 그래프가 표시됩니다. 다음 섹션에서는 이러한 시각화를 읽는 방법을 설명합니다.

불꽃 그래프란?

화염 그래프는 프로파일된 호출 스택의 시각화입니다. 각 막대는 함수를 나타내며, 막대를 쌓아 호출 체인, 즉 누가 누구를 호출했는지를 보여 줍니다. 각 막대의 너비는 해당 함수를 통해 흐르는 측정된 리소스(CPU 시간, 할당된 GPU 메모리 등)의 양을 나타냅니다.

키 규칙: 막대가 넓을수록 측정된 리소스가 해당 함수를 통해 더 많이 흐릅니다.

다음 샘플 화염 그래프는 vLLM 유추 워크로드에서 캡처됩니다.

GPU 메모리 할당 호출 스택을 나타내는 누적 막대를 보여 주는 vLLM 워크로드에 대한 Grafana의 초기 축소된 불꽃 그래프 보기 스크린샷

Tip

Grafana의 플레임 그래프 패널에서 모든 그룹 펼치기를 사용하면 전체 호출 스택을 접지 않고 볼 수 있습니다. 검색 상자를 사용하여 특정 함수 또는 키워드를 찾습니다. 패널이 다시 축소되지 않도록 하려면 확장된 불꽃 그래프를 검사하는 동안 대시보드 자동 새로 고침을 일시 중지합니다(Grafana 대시보드의 오른쪽 위에서 새로 고침 간격을 Off 로 설정).

“Expand all groups”를 선택한 후 Grafana에서 vLLM 워크로드의 불꽃 그래프를 캡처한 스크린샷으로, GPU 메모리 할당을 위한 개별 함수 프레임이 포함된 전체 호출 스택을 보여 줍니다.

Tip

포커스 블록을 사용하여 특정 할당 경로에 집중합니다.

기호 읽기

화염 그래프 레이블은 다음 규칙을 따릅니다.

기호 형식 Meaning
Foobar class Foo: method def bar()
Foo__init__ 클래스 생성자 Foo
bar (단독으로) 독립 실행형 def bar() 함수
<interpreter trampoline> CPython 오버헤드—무시
<raw-address> 예: 0x7f151 네이티브 C/CUDA 코드 - 사용할 수 있는 Python 기호 없음

예제:

  • GPUModelRunner / _allocate_kv_cache_tensors— 클래스의 메서드입니다. class GPUModelRunner: def _allocate_kv_cache_tensors(self)라고 읽습니다.
  • LlamaMLP / __init__— 생성자입니다. 개체를 LlamaMLP(...) 만들 때 호출됩니다.
  • _compile_fx_inner- 클래스 내에 없는 독립 실행형 모듈 수준 함수입니다.

자체 및 합계 이해

이 이해는 화염 그래프를 분석할 때 가장 중요한 개념입니다.

  • Total - 함수에서 사용하는 리소스 와 함수가 호출하는 모든 리소스입니다. 함수는 총합을 크게 가질 수 있지만 그 자체로는 할당하지 않습니다. 이는 단지 호출 체인일 뿐입니다.
  • 자체 - 하위 항목을 제외한 함수에서 직접 사용하는 리소스입니다. 높은 자기 값은 이 함수가 리소스가 실제로 사용되는 위치임을 의미합니다.

예: GPUModelRunner._allocate_kv_cache_tensors의 자체 메모리 사용량은 55.1 GB입니다. 즉, 이 함수는 실제로 torch.empty()를 호출하여 KV 캐시 텐서를 생성합니다.

탐색 팁:

  • 리프 노드(위에 다른 막대가 없는 막대) — 전체 너비가 self에 해당합니다. 할당 핫스팟을 찾으려면 여기에서 시작합니다.
  • 0 자체의 와이드 막대- 다른 사람을 호출하는 오케스트레이터 함수입니다. 할당을 헌팅할 때는 건너뛰어도 됩니다.
  • self 값이 높은 폭이 넓은 막대—최적화 대상입니다.

가장 큰 리소스 소비자 찾기

다음 단계를 사용하여 핫스팟을 식별합니다.

  • 그래프의 위쪽에서 가장 넓은 막대를 살펴봅니다. 메모리가 실제로 할당되는 리프 함수이며 막대가 더 많이 사용할수록 더 넓습니다.
  • 자체와 합계를 확인합니다. 아래쪽 self: 0 에 있는 넓은 막대는 호출 체인일 뿐입니다. 자체 할당량이 높은 막대를 찾을 때까지 위로 따라 올라가세요.
  • 호출 스택을 맨 위에서 위로 읽습니다. 순서 지정은 함수가 호출된 이유를 알려줍니다. 예를 들어:
      <raw-address> e.g 0x7f151               → native code entry
      <interpreter trampoline>                → CPython dispatch
      <module>                                → script top-level
      EngineCoreProc.run_engine_core          → vLLM engine startup
      EngineCore.__init__                     → engine initialization
      EngineCore._initialize_kv_caches        → KV cache setup
      Worker.initialize_from_config           → worker setup
      GPUModelRunner.initialize_kv_cache      → model runner
      GPUModelRunner._allocate_kv_cache_tensors → 💥 actual allocation
    
목표 살펴봐야 할 것
가장 많이 할당하는 항목 가장 넓은 리프 바(스택의 맨 위)
무엇이 가장 큰 원인인가 가장 넓은 막대(스택 맨 아래)
최적화 대상 자체 폭이 넓은 막대—리소스가 소비되는 곳입니다
무시할 함수 0 자체인 폭이 넓은 막대 — 다른 것들만 호출합니다

다음 단계