26S21a

QK·2026년 9월 21일

Stage1 스크립트는 총 11개 블록(0~10)으로 구성되어 있습니다. 순서대로 자세히 설명드리고, 지금 겪고 계신 gpu_burn DIED 이슈와 어떻게 연결되는지도 같이 짚어드리겠습니다.

0. Persistence Mode / Fabric Manager 점검

sudo nvidia-smi -pm 1
systemctl status nvidia-fabricmanager
  • 무엇을: GPU가 유휴 시 저전력 모드로 안 빠지게 고정(-pm 1)하고, NVSwitch 8-GPU 구성이면 GPU 간 라우팅을 관리하는 nv-fabricmanager 서비스가 살아있는지 확인.
  • 왜: Fabric Manager가 안 떠 있으면 이후 NVLink(3번)나 NCCL(7번)이 실패하거나 토폴로지가 깨진 것처럼 보입니다. 뒷 단계 실패의 "숨은 원인"인 경우가 많아 제일 먼저 봅니다.

1. 기본 장치 인식 및 Driver/CUDA 확인

nvidia-smi --query-gpu=index,name,uuid,driver_version,pstate,...
  • 무엇을: 8장이 다 잡히는지, 드라이버 버전이 뭔지, 각 GPU의 온도/전력이 정상 범위인지 한 줄씩 출력.
  • 왜: 여기서 8장이 아니거나 드라이버 버전이 이상하면 뒤 단계는 볼 필요도 없이 여기서 멈춰야 합니다.
  • 지금 이슈와 연결: DIED가 전 GPU에서 동시에 뜬다면, 이 단계에서 드라이버 버전을 먼저 적어두세요. B300급이면 R570 이상 최신 드라이버가 필요한데, 구버전이면 gpu-burn 커널 자체가 못 돌아갑니다.

2. PCIe 링크 세대/폭 실측

lspci -vvv | grep -E "NVIDIA|LnkCap|LnkSta"
nvidia-smi --query-gpu=...pcie.link.gen.max,pcie.link.gen.current...
  • 무엇을: 각 GPU 슬롯이 협상한 PCIe 세대/레인 수(Max vs Current)를 실측.
  • 왜: Current가 Max보다 낮으면 슬롯 안착 불량이나 BIOS 설정 문제 신호. B300 정식 스펙은 Gen6이므로, 여기서 나온 실측치를 서버 스펙시트와 대조해서 "기대값"을 먼저 확정해야 판정 오류가 없습니다.
nvidia-smi topo -m
nvidia-smi nvlink -s
nvidia-smi nvlink -e
  • 무엇을: topo -m은 GPU 간 연결이 NVLink(NV#)인지 PCIe 경유(PHB/SYS)인지 매트릭스로 보여주고, nvlink -s는 각 링크의 Active 상태, nvlink -e는 CRC/Replay 에러 카운터를 봅니다.
  • 왜: 8장 전부가 NV#로 붙어야 정상이고, SYS/PHB로 나오면 Fabric Manager나 물리 연결 문제입니다. 에러 카운터가 0이 아니면 NVLink 케이블/커넥터 불량 의심.

4. ECC 메모리 에러 확인

nvidia-smi --query-gpu=...ecc.errors.corrected...ecc.errors.uncorrected...
  • 무엇을: HBM의 정정 가능(Corrected)/불가능(Uncorrected) 에러 누적치.
  • 왜: Uncorrected가 0이 아니면 그 GPU의 HBM 다이 결함 가능성이 높아 즉시 벤더 문의 대상입니다.

5. XID / Row-Remapping / Pending Page Retirement (신규 추가분)

dmesg -T | grep -i xid
nvidia-smi -q -d ROW_REMAPPER
nvidia-smi -q | grep -A5 "Pending Page Retirement"
  • 무엇을: 커널 로그에 찍히는 GPU 하드웨어 예외 코드(Xid), 불량 메모리 row를 자동으로 우회시킨 이력, 재부팅 시 은퇴 예정인 불량 페이지 여부.
  • 왜: ECC 카운터만으로는 안 잡히는 다이 결함 신호를 잡는 용도입니다.
  • 지금 이슈와 직결: 앞서 말씀드린 대로, gpu_burn 실행 시점에 dmesg -T를 다시 떠서 여기(5번)와 비교하면 Xid가 새로 찍혔는지(하드웨어) vs Xid 없이 그냥 DIED만 뜨는지(소프트웨어/커널 미지원) 를 구분할 수 있는 핵심 체크포인트입니다.

6. DCGM 진단 (Level 3 / 가능하면 Level 4)

dcgmi diag -r 3
dcgmi diag -r 4   # 지원되면
  • 무엇을: NVIDIA 공식 종합 진단 도구로 PCIe, 메모리 대역폭, NVLink, 연산 유닛을 한 번에 스윕.
  • 왜: gpu-burn보다 훨씬 체계적으로 컴포넌트별 Pass/Fail을 내주는 "공식 판정 도구"입니다. Level 4는 라이선스/지원 여부에 따라 안 될 수 있어 --help 체크 후 폴백합니다.
all_reduce_perf -b 8M -e 4G -f 2 -g 8
  • 무엇을: 8장이 동시에 협업하는 실제 통신 패턴으로 NVLink 실효 대역폭(BusBw)을 측정.
  • 왜: nvidia-smi nvlink -s는 "링크가 떠 있다"만 보여주지 "실제로 빠른지"는 안 보여줍니다. 이 단계가 진짜 성능 검증입니다.

8. FP8/FP4 텐서코어 스트레스 (신규 추가분, PyTorch 기반)

python3 fp8_fp4_stress.py 180
  • 무엇을: torch._scaled_mm(FP8 GEMM)을 각 GPU에서 반복 실행해 텐서코어 자체를 부하.
  • 왜: gpu-burn은 FP32/FP64 위주라 Blackwell의 핵심인 FP8/FP4 텐서코어 경로는 검증 못 합니다.
  • 지금 이슈와 직결: 이건 gpu-burn과 완전히 독립된 CUDA 부하 경로라서, 지난번에 말씀드린 진단용으로 딱 맞습니다. 이 8번 단계만 따로 떼서 먼저 돌려보시면 "gpu-burn만의 문제(예: gencode 미지원)"인지 "CUDA 실행 환경 자체의 문제"인지 즉시 구분됩니다. 이게 실패하면 문제는 gpu-burn이 아니라 드라이버/CUDA 쪽입니다.

9. 전력/열화 스트레스 (gpu_burn 본체)

nvidia-smi --query-gpu=...temperature.gpu,power.draw,clocks...,clocks_throttle_reasons.active --format=csv -l 1 &  # 백그라운드 모니터링
gpu_burn ${SOAK_SECONDS}
  • 무엇을: 온도/전력/클럭/쓰로틀 플래그를 1초 주기로 기록하면서 gpu-burn을 15분(기본)~장기간 구동.
  • 왜: 지금 막히신 지점이 바로 여기입니다. 8번(FP8 스트레스)이 정상 동작했는데 여기만 DIED가 난다면, 앞서 안내드린 gpu-burn Makefile의 -gencode(compute capability) 문제일 가능성이 매우 높아집니다.

10. 스트레스 후 재확인

grep -v "0x0000000000000000" 09_thermal_power_trace.csv   # 쓰로틀 플래그 감지
dmesg -T | grep -i xid                                       # XID 신규 발생 여부
nvidia-smi -q -d ROW_REMAPPER                                 # Row-remap 신규 여부
diff 05_xid_before.txt 10_xid_after.txt                       # 테스트 전후 비교
  • 무엇을: 9번 부하 전(5번)과 후(10번)의 XID/Row-Remap을 diff로 비교해서 "이 부하 테스트 때문에 새로 생긴 문제"인지 자동 판정.
  • 왜: 부하 테스트 자체가 하드웨어를 손상시켰는지, 원래 있던 문제인지 시점을 특정하기 위함입니다.

정리하면: 지금 막히신 문제를 좁히려면 8번(FP8 스트레스)을 gpu_burn(9번)보다 먼저 단독 실행해보시는 게 가장 빠른 진단 경로입니다. 8번이 정상 동작하면 문제는 gpu-burn 빌드(gencode)로 좁혀지고, 8번도 실패하면 CUDA/드라이버 환경 문제로 좁혀집니다.

==

Stage2는 크게 4개 섹션(A: 네트워크, B: MinIO S3, C: 로컬 NVMe, D: 요약)으로 나뉩니다. 순서대로 설명드립니다.

SECTION A — 네트워크

A0. 로컬 NVMe 구성 확인

lsblk -d -o NAME,SIZE,MODEL
nvme list
  • 무엇을: 물리 디스크 목록과 모델명, 개수를 확인.
  • 왜: 뒤에 나오는 C섹션(fio 테스트)이 단일 드라이브가 아니라 RAID 어레이를 대상으로 해야 할 수도 있어서, LOCAL_CACHE_DIR이 실제로 몇 장짜리 어레이를 가리키는지 먼저 확인하는 단계입니다.

A1. ConnectX-6 NIC 물리 링크 및 NUMA 매핑

ip route get ${STORAGE_NODE_IP}   # 스토리지 노드로 가는 인터페이스 자동 탐지
ethtool <NIC>                      # Speed/Duplex/Link 상태
cat /sys/class/net/<NIC>/device/numa_node
lspci -tvnn | grep -E "Mellanox|NVIDIA"
  • 무엇을: 실제로 스토리지 노드와 통신에 쓰이는 인터페이스가 뭔지 라우팅 테이블로 찾아낸 뒤, 링크 속도가 100Gbps로 협상됐는지, 그리고 그 NIC가 어느 CPU 소켓(NUMA 노드)에 물려있는지 확인.
  • 왜: NIC가 CPU 소켓0에, 데이터를 쓸 GPU가 소켓1에 있으면 UPI/QPI를 건너야 해서 미세한 지연이 생깁니다. 원인 모르고 네트워크 튜닝만 하다 시간 낭비하는 걸 막는 사전 체크입니다.

A2. 단일 vs 멀티 스트림 iperf3

iperf3 -c <IP> -p 5201 -t 10          # 단일 스트림
iperf3 -c <IP> -p 5201 -P 8 -t 15     # 8개 동시 스트림
  • 무엇을: 스트리밍 하나로 붙었을 때와 8개 커넥션을 동시에 열었을 때의 실측 대역폭을 비교.
  • 왜: 이 구성(50G LACP, Layer3+4 해시)에서는 커넥션 하나로는 물리적으로 25Gbps 이상 못 나갑니다(LACP 해시가 하나의 연결을 한 링크에만 고정 배치하기 때문). 8개로 나누면 두 물리 링크에 분산되어 50Gbps 근처까지 올라가는 걸 숫자로 증명하는 단계입니다. 여기서 단일 스트림 속도를 "느리다"고 오판하면 안 됩니다 — 구조적으로 원래 그렇습니다.

A3. 양방향(Bi-directional) 테스트

iperf3 -c <IP> -p 5201 --bidir -P 8 -t 15
  • 무엇을: 송신과 수신을 동시에 밀어서, 정말로 양방향 합쳐 100G(50G+50G) 근처까지 나오는지 확인.
  • 왜: 단방향만 재면 실제 학습/추론 중 업/다운로드가 동시에 일어나는 상황(체크포인트 업로드 중 다음 배치 다운로드 등)의 실제 한계를 놓칩니다.

A4. 광모듈 DDM / FEC / PHY 에러

ethtool -m <NIC>   # 광모듈 수신광 파워, 온도 (DDM)
ethtool -S <NIC> | grep rx_crc_errors, fec_corrected/uncorrectable_blocks, rx_discards_phy 등
  • 무엇을: TCP 스택 위의 iperf3 결과와 달리, 물리 계층(광신호 자체)의 건강 상태를 봅니다.
  • 왜: CRC 에러나 FEC uncorrectable 블록이 쌓이고 있으면 iperf3 대역폭 숫자가 정상으로 나와도 언젠가 패킷 재전송/드롭으로 터질 잠재 불량입니다. DAC(구리) 케이블이면 DDM 항목이 원래 조회 안 되니 그건 정상입니다.

SECTION B — MinIO S3 처리량

B1. Warp 벤치마크

warp mixed --obj.size=64MiB --concurrent=16 --duration=30s --get-distrib 80 --stat-distrib 10 --put-distrib 10
  • 무엇을: MinIO 공식 벤치마크 툴로, 64MB 오브젝트 기준 Get 80%/Stat 10%/Put 10% 비율의 현실적인 혼합 부하를 16 동시성으로 30초간.
  • 왜: iperf3는 "순수 네트워크 파이프"만 재지만, 여기엔 MinIO 자체의 디스크 I/O, 오브젝트 메타데이터 처리 오버헤드가 얹힙니다. 실제 모델 가중치 다운로드 시 체감 속도에 가장 가까운 숫자입니다.

SECTION C — 로컬 NVMe 심층

C1. SMART 상태 (테스트 전)

NVME_DEV=$(df ${LOCAL_CACHE_DIR} | ... | sed 's/p?[0-9]+$//')  # 마운트 경로 → 실제 nvme 디바이스명 역추적
nvme smart-log <device>
  • 무엇을: 테스트로 디스크를 혹사시키기 전의 미디어 에러/수명 지표를 베이스라인으로 기록.

C2. 200GB 연속 순차 쓰기 (백그라운드 온도 모니터링 포함)

( while true; do nvme smart-log ... | awk '/temperature/...'; sleep 1; done ) &   # 1초 주기 온도 로깅
fio --rw=write --bs=2M --size=200G --numjobs=1 --runtime=180
  • 무엇을: 2MB 블록으로 200GB를 순차로 밀어넣으면서 동시에 온도를 추적.
  • 왜: NVMe는 보통 앞쪽 수십GB는 SLC 캐시 덕에 7,000MB/s 이상 나오다가, 캐시가 소진되면 실제 낸드 속도(1,500MB/s 이하일 수도)로 뚝 떨어집니다. 140GB 모델 가중치 저장처럼 큰 파일을 다룰 때의 진짜 속도를 보려면 최소 200GB는 밀어봐야 합니다. 동시에 온도가 70~80℃를 넘으면 컨트롤러가 자체적으로 쓰로틀링을 걸어 I/O가 멈칫거리는지도 이 구간에서 잡힙니다.

C3. 고부하 순차 읽기

fio --rw=read --bs=2M --size=100G --numjobs=4 --runtime=60
  • 무엇을: 4개 잡을 병렬로 돌려 순차 읽기 피크 대역폭 측정.
  • 왜: 모델을 콜드 로드(처음 로딩)할 때의 최대 읽기 속도를 실측 — 2단계 Cold Load 테스트(5a)와 비교 기준점이 됩니다.

C4. SMART 상태 (테스트 후) + Media Errors 판정

nvme smart-log <device>
awk '/media_errors/ {print $3}'
  • 무엇을: 200GB+100GB 총 300GB를 밀어넣은 직후의 SMART를 다시 찍어서 C1과 비교.
  • 왜: media_errors가 0이 아니게 바뀌었으면, 이 정도 부하만으로도 낸드 불량이 드러난 것 — 신품 디스크치고는 심각한 징후라 즉시 교체 대상입니다.

SECTION D — 결과 요약

모든 수치(NIC 속도/단일·멀티 스트림/S3 처리량/NVMe 쓰기·읽기/Media Errors)를 stage2_summary.txt 하나로 모아 tee — 이게 보고서에 바로 붙여넣을 수 있는 최종 산출물입니다.


참고: Stage2는 Stage1과 달리 gpu-burn 같은 GPU 커널 실행에 의존하지 않으므로, 지금 겪고 계신 gpu-burn 이슈와는 독립적으로 먼저 돌려보셔도 무방합니다. 다만 A2/B1은 스토리지 노드 쪽에 iperf3 -s -D가 미리 떠 있어야 하고, STORAGE_NODE_IP/MINIO_ENDPOINT 등 환경변수를 실제 값으로 바꿔야 동작합니다.

==

Stage3는 4개 스크립트로 구성됩니다 — 3a(vLLM), 3b(Triton+TRT-LLM)+보조러너, 3c(SGLang, 선택), 3d(AIPerf 통합, 신규). 각각 자세히 설명드립니다.


Stage3a — vLLM 추론 서빙 벤치마크

공통 함수 구조

  • start_vllm_server(model_id, quant_flag, prefix_cache): vLLM API 서버를 백그라운드로 띄우고, /v1/models 엔드포인트가 응답할 때까지 5초 간격으로 폴링. 서버 프로세스가 죽으면(kill -0 실패) 즉시 에러로 중단.
  • stop_vllm_server(): SIGTERM으로 정상 종료 후 wait.
  • run_serving_bench(tag, in_len, out_len, concurrency, num_prompts): vLLM 공식 benchmark_serving 모듈로 실제 트래픽을 흉내 낸 요청을 보내고 결과를 JSON으로 저장.

이 구조를 5가지 시나리오에 반복 적용합니다.

시나리오 1) Dense 70B, BF16, Short(1k)/Long(32k) Context

  • TP_SIZE=8로 8장 전체를 텐서 병렬로 묶고, concurrency 1/16/64(단기)·1/8/16(장문)으로 스윕.
  • 왜 concurrency를 다르게: 단기 컨텍스트는 디코딩(토큰 생성) 병목이라 동시성을 높여도 버틸 여력이 있어 64까지, 장문 컨텍스트는 프리필(입력 처리) 자체가 무거워서 16 정도가 현실적 상한입니다.

시나리오 2) Dense 70B, FP8 양자화

  • --quantization fp8 플래그만 추가해 동일 시나리오 재실행.
  • 왜: B300(Blackwell)의 핵심 가치는 FP8 텐서코어인데, 이 비교가 없으면 "B300이라서 좋다"는 근거가 안 나옵니다. 시나리오1(BF16) 대비 TTFT/처리량 개선폭이 리포트의 핵심 수치가 됩니다.

시나리오 3) Prefix Caching 검증

  • --enable-prefix-caching 켠 상태에서 --random-prefix-len 32000(32k 프리픽스 고정) + 짧은 출력(256) 요청을 8동시성으로 30개 반복.
  • 왜: 멀티턴 챗봇이나 RAG처럼 "같은 시스템 프롬프트/문서를 계속 재사용"하는 워크로드에서, 캐시 히트로 TTFT가 얼마나 단축되는지를 수치로 증명하는 단계입니다.

시나리오 4) DeepSeek-R1 (대형 MoE, FP8)

  • 체크포인트 자체가 이미 FP8이라 별도 양자화 플래그 없이 그대로 로딩.
  • 왜: 지금까지의 70B dense 테스트로는 B300의 HBM 288GB×8=2.3TB 용량을 전혀 못 채웁니다. 671B급 MoE를 실제로 올려서 서빙 가능함을 증명하는 게 이 시나리오의 목적입니다. 모델 크기가 커서(~700GB) 로딩 자체가 오래 걸릴 수 있다는 경고를 로그에 남깁니다.

시나리오 5) 소형 8B 모델

  • concurrency를 128까지 더 높여서 테스트.
  • 왜: 70B/MoE 테스트는 메모리 대역폭이 병목이라 "하드웨어의 순수 디코딩 속도 한계"를 못 봅니다. 8B처럼 메모리 압박이 거의 없는 모델로 돌리면, ITL(토큰 간 지연)의 이론적 하한선 — 즉 이 GPU가 낼 수 있는 최고 디코딩 속도 — 를 잴 수 있습니다.

결과 집계

  • 마지막 파이썬 블록이 ${LOG_DIR} 안의 모든 결과 JSON을 훑어서 TTFT/ITL/Req·s/Tok·s를 표로 정리한 vllm_report.md를 생성합니다.

Stage3b — Triton + TensorRT-LLM (+ 보조 러너)

이 스크립트는 엔진 빌드 전용이고, 실제 부하 측정은 별도 파일(stage3b_genai_perf_runner.sh)에서 합니다.

1) BF16 엔진 빌드

convert_checkpoint.py --dtype bfloat16 --tp_size 8   # HF 가중치 → TRT-LLM 체크포인트 변환
trtllm-build ... --use_paged_context_fmha enable      # 실제 실행 가능한 엔진 바이너리로 컴파일
  • 왜 2단계인가: TensorRT-LLM은 vLLM과 달리 "가중치를 그대로 읽어서 즉석 로딩"하는 방식이 아니라, 모델 그래프를 GPU에 최적화된 커널로 사전 컴파일해야 합니다. 그래서 최초 1회 20~40분짜리 빌드 과정이 필요합니다(대신 그만큼 런타임 속도가 빠름).

2) FP8 엔진 빌드 (신규)

quantize.py --qformat fp8 --kv_cache_dtype fp8   # 가중치뿐 아니라 KV캐시도 FP8로
trtllm-build ... --use_fp8_context_fmha enable
  • 왜: vLLM과 마찬가지로 BF16 대비 FP8의 실질 개선폭을 봐야 하고, 여기선 KV 캐시까지 FP8로 저장해 메모리 절감 효과까지 같이 확인합니다.

보조 스크립트 stage3b_genai_perf_runner.sh

./stage3b_genai_perf_runner.sh ensemble_bf16   # 또는 ensemble_fp8
  • 무엇을: Triton 서버가 실제로 기동된 상태에서 NVIDIA genai-perf 도구로 Short(1k)/Long(32k) Context를 concurrency 1/16/64, 1/8/16으로 스윕.
  • 왜 분리했나: 엔진 빌드(트리톤 컨테이너 내부, GPU 오프라인 상태)와 부하 측정(호스트 OS, 서버가 살아있는 상태)이 서로 다른 환경/타이밍에서 실행되기 때문입니다. BF16 엔진 기동 → 이 러너 실행 → 종료 → FP8 엔진 기동 → 다시 이 러너 실행, 이렇게 두 번 돌려서 비교합니다.

Stage3c — SGLang (선택 단계)

vLLM/Triton과 거의 동일한 골격(서버 기동 → 폴링 → 부하 → 종료)이지만 3가지 차이가 있습니다.

시나리오 1-2) Dense 70B + 프리픽스 재사용

  • SGLang의 핵심 기능인 RadixAttention(트리 구조로 프리픽스를 자동 공유하는 캐시)은 기본 활성화 상태라 별도 플래그가 필요 없습니다. vLLM 시나리오3과 동일 조건(32k 프리픽스, concurrency 8)으로 재실행해 엔진 간 프리픽스 캐싱 효율 직접 비교가 가능합니다.

시나리오 3) FP4 MoE (GPT-OSS-120B)

  • --quantization fp4로 기동. Blackwell의 FP4 텐서코어는 SGLang 커널이 vLLM보다 먼저/더 최적화되어 있다고 알려져 있어, 이 시나리오가 비교의 핵심입니다.

주의: 이 단계는 "MoE/에이전트/RAG 서빙 계획이 실제로 있을 때만" 돌리는 선택 사항으로 설계했습니다. 순수 챗봇형 워크로드뿐이면 3a/3b만으로 충분합니다.


Stage3d — AIPerf 통합 벤치마크 (신규)

3a/3b/3c는 엔진마다 서로 다른 측정 도구(vLLM benchmark_serving, Triton genai-perf, SGLang bench_serving)를 써서, 메트릭 계산 방식이 미묘하게 다를 수 있다는 한계가 있습니다. 3d는 이미 기동된 세 엔진에 동일한 도구(NVIDIA AIPerf)로 동일 조건을 재적용해서 apples-to-apples 비교를 만드는 단계입니다.

  • run_aiperf_openai(): vLLM(8000)/SGLang(30000)처럼 OpenAI 호환 API를 쓰는 엔진 대상.
  • run_aiperf_triton(): Triton은 OpenAI 호환이 아니라 네이티브 gRPC 프로토콜을 쓰므로 --service-kind triton --backend tensorrtllm으로 별도 처리.
  • 4번 섹션(Multi-turn/Poisson): --public-dataset sharegpt로 실제 대화 데이터셋을 쓰고, --request-rate 8.0으로 균일 동시성이 아니라 푸아송 분포의 "실제 트래픽처럼 들쭉날쭉한" 요청 패턴을 흉내 냅니다. 3a~3c의 고정 concurrency 테스트가 "최대 성능 한계"를 본다면, 이 단계는 "운영 중 실제로 체감할 지연"에 더 가깝습니다.
  • 5번 섹션: 결과를 Stage5e에서 배포한 Prometheus Pushgateway로 전송 — 여기서 3단계 전체가 5단계 모니터링 스택과 연결됩니다.

실행 순서 요약: 3a → 3b(빌드) → genai_perf_runner(bf16) → genai_perf_runner(fp8) → (선택) 3c → 3d. 3d는 3a/3b/3c 중 기동해둔 엔진이 있어야 의미가 있으므로 항상 가장 마지막에 돌리는 걸 권장합니다.

profile
engineer

0개의 댓글