현대 딥 러닝 개발에서는 일반적으로 다른 모듈을 사용하여 빌딩 블록과 같은 복잡한 소프트웨어 시스템을 구축합니다. 이 프로세스는 종종 빠르고 효과적입니다. 그러나 문제가 발생했을 때 문제를 신속하게 찾아 해결하는 방법은 시스템의 복잡성과 결합으로 인해 딥 러닝 시스템 설계자와 유지관리자에게 항상 문제가 되었습니다.
iQiyi의 백엔드 기술팀의 일원으로서 우리는 어려운 문제를 해결하기 위해 열심히 노력하는 동료들에게 약간의 영감을 주기를 바라며 딥 러닝 훈련 메모리 관련 문제를 해결하는 과정을 자세히 기록했습니다.
배경
지난 분기 동안 우리는 A100 클러스터에서 랜덤 CPU 메모리 OOM 현상을 관찰해 왔습니다. 대규모 모델 훈련이 도입되면서 oom은 더욱 견딜 수 없게 되었고, 이로 인해 우리는 이 문제를 해결하기로 결심하게 되었습니다.
내가 어디에서 왔는지 되돌아보면서 나는 문득 깨달음을 느꼈다. 사실 우리는 한때 문제의 진실에 매우 가까웠지만 상상력이 부족하여 놓쳤습니다.
프로세스
처음에는 역사적 기록에 대한 귀납적 분석을 수행했습니다. 최종 솔루션에 매우 중요한 지침이 되는 몇 가지 규칙이 발견되었습니다.
-
이는 A100 클러스터에서 발생한 새로운 문제이며 다른 클러스터에서는 발생한 적이 없습니다.
-
문제는 pytorch의 ddp 분산 교육과 관련되어 있습니다. pytorch를 사용하는 다른 교육 모드는 발생하지 않았습니다.
-
이 OOM 문제는 매우 무작위적이며 일부는 3시간 이내에 발생하고 일부는 일주일 이상 후에 발생합니다.
-
OOM 중에 메모리 서지가 발생하며 기본적으로 아래 그림과 같이 1분 30초 이내에 10%에서 90%로 증가가 완료됩니다.

위의 정보를 사용할 수 있지만 문제를 안정적으로 재현할 수 없기 때문에 처음에는 전적으로 다양한 상상에 의존했습니다. 예를 들면 다음과 같습니다.
-
객체가 재활용되지 않아 지속적인 메모리 누수가 발생하므로 코드 문제일 수 있습니까?
-
glibc의 PTMALLOC 할당자에 너무 많은 조각이 있어 특정 순간에 갑작스러운 메모리 요청이 지속적인 메모리 할당으로 이어진다는 사실과 유사하게 기본 메모리 할당자에 문제가 있을 수 있습니까?
-
-
아래에서는 처음 두 가지 가정을 자세히 소개합니다.
코드 문제인지 확인하기 위해 문제가 발생한 장면에 디버깅 코드를 추가하고 주기적으로 호출했습니다. 다음 코드는 현재 Python gc 모듈에서 재활용할 수 없는 모든 개체를 인쇄합니다.
그러나 이 코드를 추가한 후 얻은 로그 분석을 보면 OOM 중에 메모리를 많이 차지하는 도달 불가능한 개체가 없으며 연속 gc로는 OOM 자체를 완화할 수 없는 것으로 나타났습니다. 따라서 이 시점에서 우리의 첫 번째 추측은 파산했으며 문제는 코드(메모리 누수)로 인한 것이 아닙니다.
이 단계에서 우리는 glibc의 기본 PTMALLOC과 비교하여 jemalloc 메모리 할당자를 도입했는데, 이는 더 효율적인 메모리 할당과 메모리 할당 자체 디버깅에 대한 더 나은 지원을 제공할 수 있다는 것입니다.
-
기본 메모리 할당자에 문제가 있을 수 있나요?
-
토치 코드 자체를 수정하지 않고 Python에서 jemalloc의 현재 상태를 직접 보기 위해 ctypes를 사용하여 Python에서 jemalloc 인터페이스를 직접 노출했습니다.

이렇게 이 코드를 함수에 넣으면 현재 jemalloc이 상위 계층에서 받은 요청(할당)과 시스템에서 요청한 실제 물리적 메모리 크기(매핑)를 주기적으로 알 수 있습니다.
실제 재현 과정을 거쳐 OOM이 발생하면 할당 및 매핑된 두 값이 매우 가까운 것으로 최종 확인됐다. 따라서 메모리 조각화에 대한 우리의 가설은 무너졌습니다.
막다른 골목에 이르렀을 때 기존의 OOM 로그를 다시 한 번 정리한 결과 이전에 집중하지 않았던 방향이 있다는 것을 발견했습니다. 즉, 비슷한 시간에 여러 대의 기계가 작동하고 있었습니다(1-2). 분 인접) 여러 번 OOM이 발생합니다.
그렇다면 이 마법같은 동시성에 대해 어떤 논리적인 설명이 있을까요? 일반적인 버그로 인해 이러한 일관성이 다시 발생해서는 안 됩니다. 그래서 그들 사이에는 필연적인 연결이 있을 수 있습니다.
그렇다면 이 상관관계는 어디서 오는 걸까요? 이 문제를 탐구하기 위해 분석적 관점은 분산 훈련의 네트워크 통신으로 전환됩니다.
통신에 대한 초기 의심은 OOM을 경험한 머신에 집중되었습니다. 어떤 이유로든 서로 통신하고 있어 서로 문제가 발생할 수 있다는 의심이 들었습니다. 따라서 네트워크 트래픽을 모니터링하기 위해 일일 트레이닝에 tcpdump를 추가했습니다.
마지막으로 tcpdump에 가입한 후 OOM 중에 가장 의심스러운 통신을 발견했습니다. 즉, OOM 시스템은 문제가 발생하기 몇 분 전에 보안 검색 트래픽을 수신했습니다.
최종 포지셔닝
보안팀이 의심되는 개체를 스캔하는 것을 포착한 후, 보안팀과 협업하여 분석을 진행한 결과, 최종적으로 스캔을 기반으로 OOM 문제를 안정적으로 재현할 수 있다는 사실을 확인하여 촉발 원인이 거의 확실했습니다. 그러나 현 시점에서는 OOM 문제를 피하기 위해 보안 검색 전략을 재현하고 변경할 수만 있으며 코드를 추가로 분석하고 최종적으로 찾아야 합니다.
코드를 분석하고 위치를 파악한 결과, pytorch의 DDP 분산 학습 프로토콜에 문제가 있는 것으로 최종 확인되었습니다. 관련 코드는 다음과 같습니다.

위 그림에 표시된 것처럼 pytorch 분산 훈련은 마스터 포트에서 메시지를 계속 수신합니다.

Nmap 스캔 [nmap -sS -sV]은 위의 tcpdump 그림에 표시된 데이터 부분의 녹색 상자 번호 [03]인 QueryType::ADD 메시지 유형을 트리거하여 pytorch가 recvString을 사용하려고 시도하게 했습니다. 후속 메시지로 간주되는 것을 수신하기 위해 버퍼를 사전 할당하는 기능입니다. 하지만 이 버퍼 길이는 [03] 뒤에 uint64_t[little-endian] 유형을 사용하여 파싱되는데, 이는 빨간색 상자 번호 [e0060b0000]이며, 이 값은 962174058496바이트라는 의미로 이해되며, pytorch입니다. will 메모리 할당자가 해당 메모리를 요청한 후 메모리 할당자는 커널에서 해당 물리적 페이지를 추가로 요청합니다. GPU 훈련 클러스터는 거대한 페이지 테이블로 구성되지 않기 때문에 Linux는 4K 세분성에 따라 페이지 누락 인터럽트에서 메모리 할당자의 1T 메모리 요청을 점진적으로만 충족할 수 있습니다. 즉, 모든 메모리를 할당하는 데 약 1분이 소요됩니다. 앞서 관찰된 OOM은 아마도 약 1분 정도의 급격한 기억 증가에 대한 반응으로 발생했을 것입니다.
해결책
원인과 결과를 알고 나면 해결책은 자연스러워집니다.
1. 단기: 보안 검색 정책을 변경하여 방지
2. 장기적: 커뮤니티와 소통하여 Pytorch DDP 프로토콜의 견고성을 [ 1 ]
요약하다
OOM 문제 조사 프로세스의 역추적을 완료한 후, 우리는 이 프로세스 동안 실제로 메모리 관련 도구 및 디버깅 방법에 대한 효과적인 테스트를 수행했음을 발견했습니다.
이 과정에서 우리는 후속 연구 개발에 참고할 수 있는 몇 가지 공통점을 발견했습니다.
-
Jemalloc은 메모리 문제에 대한 매우 효과적인 정량적 분석을 제공할 수 있으며 Python+C와 같은 하이브리드 프로그래밍 시스템에서 근본적인 메모리 관련 문제를 포착할 수 있습니다.
-
기억하세요. 디버깅 과정에서 이에 대한 기대가 컸지만, 결국 memray가 가장 잘 수행할 수 있는 영역은 여전히 순수 Python 측이고, pytorch DDP와 같은 하이브리드 프로그래밍 시스템을 지원하지 않는다는 것을 알게 되었습니다.
때때로 우리는 더 큰 차원의 문제에 대해 생각해야 합니다. 예를 들어, 관련 없는 외부 서비스와의 통신 과정을 고려 대상에 포함하지 않으면 실제 근본 원인을 찾을 수 없습니다.