<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>qtscott 님의 블로그</title>
    <link>https://qtscott.tistory.com/</link>
    <description>qtscott 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 25 Jun 2026 18:17:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>qtscott</managingEditor>
    <item>
      <title>AI Datacenter Network 스터디 1주차 - AI를 위한 네트워크 들여다보기</title>
      <link>https://qtscott.tistory.com/1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;AI Datacenter Network Study를 시작했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;첫번째 주에는 GPU 사용에 있어서 네트워크가 어떻게 발전했는지와 왜 병목이 되는지 그리고 그걸 해결하기 위한 기술들을 알아봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1. 병목은 연산이 아니라 메모리와 데이터를 나르는 통로인 네트워크&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kVjGm/dJMcabdvLAo/kRCU4KEvWfelUKNfdWGvik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kVjGm/dJMcabdvLAo/kRCU4KEvWfelUKNfdWGvik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kVjGm/dJMcabdvLAo/kRCU4KEvWfelUKNfdWGvik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkVjGm%2FdJMcabdvLAo%2FkRCU4KEvWfelUKNfdWGvik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;400&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 10여 년간 GPU의 연산 성능 증가폭은 메모리 대역폭 증가폭을 크게 앞질렀다. 코어는 빨라졌는데 메모리가 데이터를 그 속도로 못 따라가서, 코어가 데이터를 기다리는 상태가 된다. 흔히 말하는 &lt;b&gt;'Memory Wall'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;9:1-9:159;332-490&quot; data-ke-size=&quot;size16&quot;&gt;AI 연산의 실제 병목은 GPU 코어의 FLOPS가 아니라, 모델 파라미터와 &lt;b&gt;KV cache&lt;/b&gt;를 &lt;b&gt;GPU 메모리&lt;/b&gt;에서 얼마나 빠르게 읽고 쓰느냐로 결정되는 경우가 많다. GPU가 아무리 많은 FLOPS를 내도 메모리 대역폭이 못 받쳐주면 전체 성능은 Memory Bound 상태에 묶인다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;11:1-11:176;492-667&quot; data-ke-size=&quot;size16&quot;&gt;이게 단일 GPU 안의 문제라면, &lt;b&gt;GPU를 여러 장 묶는 순간&lt;/b&gt; 똑같은 문제가 이번엔 &lt;b&gt;네트워크&lt;/b&gt;에서 터진다. 큰 모델은 애초에 한 장에 안 들어간다. 2조 파라미터급이면 가중치 + gradient + optimizer 상태로 수십 TB라, 수백~수천 장에 쪼개야 한다. 그리고 쪼개는 방식이 곧 통신 패턴을 결정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;13:1-13:105;669-773&quot;&gt;&lt;b&gt;Data Parallelism&lt;/b&gt; &amp;mdash; 모델을 모든 GPU에 복제하고 배치를 나눈다. 매 스텝 끝에 GPU마다 다른 gradient를 평균 내야 하는데, 이게 All-Reduce다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;14:1-14:85;774-858&quot;&gt;&lt;b&gt;Tensor Parallelism&lt;/b&gt; &amp;mdash; 행렬 곱 하나를 여러 GPU에 쪼갠다. 단계마다 서로를 기다려야 해서 가장 빠른 링크에서만 효율적이다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;15:1-15:48;859-906&quot;&gt;&lt;b&gt;Pipeline Parallelism&lt;/b&gt; &amp;mdash; 레이어를 GPU들에 나눠 맡긴다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;16:1-16:83;907-989&quot;&gt;&lt;b&gt;Expert Parallelism (MoE)&lt;/b&gt; &amp;mdash; expert를 여러 GPU에 흩뿌린다. all-to-all 통신이 잦아 지연에 민감하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 방식을 쓰든 결론은 같다. 쪼개는 순간 GPU들은 끊임없이 서로 통신하고, 그 통신량이 곧 네트워크 부하가 된다. 게다가 GPU 한 장이 느려지면 나머지 수천 장이 같이 멈춰 기다린다. 그래서 분산 학습에서는 &quot;가장 느린 통신 경로&quot;가 전체 속도를 좌우한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. 그래서 RDMA, 그리고 InfiniBand&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 분산 학습에서는 gradient 동기화, All-Reduce, parameter exchange 같은 GPU 간 통신이 끊임없이 일어난다. 그래서 GPU와 네트워크 카드 사이에서 데이터를 얼마나 효율적으로 옮기느냐가 전체 처리 속도를 크게 좌우한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;24:1-24:166;1317-1482&quot; data-ke-size=&quot;size16&quot;&gt;일반 TCP/IP로 두 GPU 서버가 통신하면, 데이터가 GPU &amp;rarr; Host RAM &amp;rarr; user buffer &amp;rarr; kernel socket buffer &amp;rarr; NIC를 거치고, 받는 쪽에서 같은 과정을 거꾸로 되밟는다. 매 단계가 메모리 복사이거나 커널 처리라, 대역폭을 키워도 CPU가 못 따라온다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;24:1-24:166;1317-1482&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;26:1-26:246;1484-1729&quot; data-ke-size=&quot;size16&quot;&gt;RDMA(Remote Direct Memory Access)는 이 경로에서 커널과 CPU 복사를 들어낸다. 한 서버의 NIC가 상대 서버의 미리 등록된 메모리에 직접 읽고 쓴다. 그래서 전송 전에 메모리를 등록(페이지 고정 + 접근 key 발급)해두고, Queue Pair와 Work Request로 통신을 굴린다. CPU는 초기 설정&amp;middot;연결 관리(control path)만 맡고, 반복되는 대용량 전송(data path)에서는 빠진다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;26:1-26:246;1484-1729&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;28:1-28:138;1731-1868&quot; data-ke-size=&quot;size16&quot;&gt;GPU 데이터라면 한 단계 더 나아간다. GPUDirect RDMA는 NIC가 GPU의 VRAM에 직접 DMA를 걸어 호스트 RAM 경유까지 없앤다. 그래서 GPU VRAM &amp;harr; NIC &amp;harr; 네트워크 &amp;harr; NIC &amp;harr; GPU VRAM으로 한 번에 간다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;30:1-30:50;1870-1919&quot; data-ke-size=&quot;size16&quot;&gt;RDMA를 네트워크에 실어 나르는 방식은 여러 가지가 있었지만, 지금은 크게 두 갈래다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;32:1-33:153;1921-2236&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;32:1-32:163;1921-2083&quot;&gt;&lt;b&gt;InfiniBand&lt;/b&gt; &amp;mdash; RDMA를 위해 처음부터 설계된 전용 패브릭. 링크 레벨에서 credit 기반 흐름 제어로 무손실이고 저지연이며, SHARP(스위치가 All-Reduce 같은 reduction을 네트워크 안에서 대신 수행) 같은 기능을 받친다. 대신 전용 장비가 필요하다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;33:1-33:153;2084-2236&quot;&gt;&lt;b&gt;RoCEv2&lt;/b&gt; &amp;mdash; 이더넷 + IP + UDP 위에 InfiniBand의 RDMA transport를 그대로 얹은 방식. 범용 이더넷 장비를 쓸 수 있지만, 손실 가능한 이더넷을 무손실처럼 만들기 위해 PFC / ECN / DCQCN 같은 혼잡 제어를 얹어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;35:1-35:267;2238-2504&quot; data-ke-size=&quot;size16&quot;&gt;사내 인프라는 트레이닝에 무게를 두고 더 큰 대역폭과 RDMA에 특화된 InfiniBand로 구성했다. 그래서 이번 스터디도 IB에 조금 더 집중했다. DGX B300은 각 GPU에 ConnectX-8 SuperNIC가 800Gbps(XDR)로 붙고, 이 NIC가 PCIe Gen6로 GPU에 직결된다 &amp;mdash; 앞서 말한 GPUDirect RDMA의 물리적 토대다. 그리고 이 NIC들을 Quantum-X800 스위치(포트당 800Gb/s, Quantum-3 ASIC)가 받아낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;3. 실제로 IB 패브릭 들여다보기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;(1) &lt;/b&gt;&lt;b&gt;ibstat&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;mdash;&amp;nbsp;&lt;/b&gt;&lt;b&gt;링크들이 살아있는지 확인&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782091284204&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ibstat mlx5_0
CA 'mlx5_0'
	CA type: MT4131
	Number of ports: 1
	Firmware version: 40.47.2526
	Hardware version: 0
	Node GUID: 0x7425540300c73865
	System image GUID: 0x7425540300c73865
	Port 1:
		State: Active 
		Physical state: LinkUp  
		Rate: 800 
		Base lid: 190 
		LMC: 0
		SM lid: 151
		Capability mask: 0xa751ec48
		Port GUID: 0x7425540300c73865
		Link layer: InfiniBand&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;CA type: MT4131&lt;/b&gt;은 ConnectX-8 NIC가 제대로 잡혔다는 뜻이고, &lt;b&gt;State: Active&lt;/b&gt;는 포트가 살아서 Subnet Manager에 등록돼 통신 가능한 상태라는 의미다(&lt;b&gt;Base lid&lt;/b&gt;가 찍혀 있으면 SM이 LID를 할당해 패브릭에 정상 편입됐다는 증거다). &lt;b&gt;Rate: 800&lt;/b&gt;은 방향당 800Gb/s, 즉 XDR로 협상됐다는 가장 중요한 한 줄이다(NDR로 떨어졌으면 400이 떴을 것이다). 마지막으로 &lt;b&gt;Link layer: InfiniBand&lt;/b&gt;는 이 포트가 이더넷이 아니라 IB 모드로 동작 중이라는 확인이다. 정리하면 이 한 화면이 &quot;스펙시트의 800G가 내 케이블에서 실제로 나오고, IB 모드로 패브릭에 붙었다&quot;를 한 번에 보여준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;(2) 800G가 실제로 나오는가 &amp;mdash; &lt;/b&gt;&lt;b&gt;ib_write_bw&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크가 살아있는 건 확인했으니, 이제 두 노드 사이 RDMA Write 대역폭을 측정한다. --use_cuda=0을 붙이면 GPU 메모리를 직접 소스로 써서 GPUDirect RDMA 경로까지 함께 측정된다.&lt;/p&gt;
&lt;pre id=&quot;code_1782091427651&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 서버 노드

$ ib_write_bw -d mlx5_0 -F --report_gbits --use_cuda=0



# 클라이언트 노드 (서버 IP 지정)

$ ib_write_bw -d mlx5_0 -F --report_gbits --use_cuda=0 &amp;lt;server_ip&amp;gt;



initializing CUDA

Listing all CUDA devices in system:

CUDA device 0: PCIe address is 1A:00

CUDA device 1: PCIe address is 3C:00

CUDA device 2: PCIe address is 62:00

CUDA device 3: PCIe address is 73:00

CUDA device 4: PCIe address is 9A:00

CUDA device 5: PCIe address is BC:00

CUDA device 6: PCIe address is DF:00

CUDA device 7: PCIe address is F0:00



Picking device No. 0

[pid = 2072037, dev = 0] device name = [NVIDIA B300 SXM6 AC]

creating CUDA Ctx

making it the current CUDA Ctx

CUDA device integrated: 0

allocated GPU buffer of a 131072 address at 0x55673145afd0 for type CUDA_MEM_DEVICE

---------------------------------------------------------------------------------------

                    RDMA_Write BW Test

 Dual-port       : OFF Device         : mlx5_0

 Number of qps   : 1 Transport type : IB

 Connection type : RC Using SRQ      : OFF

 PCIe relax order: ON Lock-free      : OFF

 ibv_wr* API     : ON Using DDP      : ON

 TX depth        : 128

 CQ Moderation   : 1

 CQE Poll Batch  : 16

 Mtu             : 4096[B]

 Link type       : IB

 Max inline data : 0[B]

 rdma_cm QPs : OFF

 Data ex. method : Ethernet

---------------------------------------------------------------------------------------

 local address: LID 0xaa QPN 0x0057 PSN 0x49ce33 RKey 0x1fffbf VAddr 0x0074ea67010000

 remote address: LID 0xbe QPN 0x0046 PSN 0xfc006f RKey 0x1fffbe VAddr 0x0071f13b010000

---------------------------------------------------------------------------------------

 #bytes     #iterations    BW peak[Gb/sec]    BW average[Gb/sec]   MsgRate[Mpps]

 65536      5000             786.06             784.74      1.496775

---------------------------------------------------------------------------------------

deallocating GPU buffer 000074ea67000000

destroying current CUDA Ctx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;64KB 메시지 기준으로 BW peak 786.06Gb/s, BW average 784.74Gb/s가 나왔다. 800G 라인레이트의 약 98% 수준이라, NIC &amp;harr; 스위치 &amp;harr; NIC 경로가 정상이라는 뜻이다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;91:1-91:233;4521-4753&quot; data-ke-size=&quot;size16&quot;&gt;흥미로운 건 GPU 메모리 사용 여부에 따른 차이였다. 같은 노드에서 일반 호스트 메모리로 측정했을 때는 평균 &lt;b&gt;462Gb/s&lt;/b&gt;에 그쳤는데, GPU 메모리를 직접 소스로 쓰는 &lt;b&gt;GPUDirect RDMA&lt;/b&gt; 경로로 바꾸자 &lt;b&gt;784Gb/s&lt;/b&gt;까지 올라갔다. 데이터가 호스트 RAM을 경유하지 않고 NIC &amp;harr; GPU VRAM으로 직행하면서 생기는 차이다. 2번에서 말한 &quot;호스트 RAM 경유까지 없앤다&quot;가 실제 숫자로 드러난 셈이다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;91:1-91:233;4521-4753&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;91:1-91:233;4521-4753&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 정리&lt;/b&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;95:1-95:216;4765-4980&quot; data-ke-size=&quot;size16&quot;&gt;1주차는 AI 워크로드가 GPU 위에서 어떻게 도는지 개론을 잡고, InfiniBand의 입구까지 들어가 봤다. &lt;b&gt;ibstat&lt;/b&gt;으로 링크가 XDR로 살아있는지 확인하고, &lt;b&gt;ib_write_bw&lt;/b&gt;로 두 노드 사이 RDMA Write가 800G 라인레이트에 닿는지,&lt;b&gt; --use_cuda&lt;/b&gt;로 GPUDirect RDMA 경로에서 대역폭이 462 &amp;rarr; 784Gb/s로 뛰는 것까지 눈으로 봤다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;97:1-97:372;4982-5353&quot; data-ke-size=&quot;size16&quot;&gt;다음부터는 이 링크 위에서 한 단계씩 더 측정해볼 생각이다. &lt;b&gt;nccl-tests&lt;/b&gt;의 &lt;b&gt;all_reduce_perf&lt;/b&gt;로 노드 수를 늘려가며 busbw가 유지되는지(스케일링),&lt;b&gt; NCCL_DEBUG=INFO&lt;/b&gt;로 어느 경로를 타는지 확인한다. Quantum-X800만의 기능으로는 SHARP(스위치가 reduction을 대신하는 in-network computing)를 켜고 끈 all-reduce를 비교하고, Adaptive Routing이 다대다 트래픽을 링크별로 고르게 분산하는지 포트 카운터로 관찰한다. 마지막으로 정상일 때 &lt;b&gt;ibdiagnet&lt;/b&gt; 결과를 baseline으로 저장해두면, 나중에 성능 저하가 생겼을 때 어느 포트가 범인인지 빠르게 좁힐 수 있다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;99:1-99:144;5355-5498&quot; data-ke-size=&quot;size16&quot;&gt;흐름은 결국 이렇다 &amp;mdash; 링크가 사는가(&lt;b&gt;ibstat&lt;/b&gt;) &amp;rarr; 한 링크가 제 속도를 내는가(&lt;b&gt;ib_write_bw&lt;/b&gt;) &amp;rarr; 여러 노드로 묶어 collective가 스케일하는가(&lt;b&gt;nccl-tests&lt;/b&gt;) &amp;rarr; 스위치 기능으로 더 끌어올릴 수 있는가(SHARP/AR).&lt;/p&gt;</description>
      <author>qtscott</author>
      <guid isPermaLink="true">https://qtscott.tistory.com/1</guid>
      <comments>https://qtscott.tistory.com/1#entry1comment</comments>
      <pubDate>Mon, 22 Jun 2026 10:43:53 +0900</pubDate>
    </item>
  </channel>
</rss>