본문으로 건너뛰기

[vllm] [vLLM 성능 최적화] MoE 레이어의 불필요한 메모리 복사를 제거하는 Humming 최적화 분석

PR 링크: vllm-project/vllm#59340 상태: Merged | 변경: +10 / -2

들어가며: vLLM과 MoE의 효율성\n\nvLLM은 대규모 언어 모델(LLM)의 추론 속도를 극대화하기 위한 오픈소스 라이브러리로, 특히 Mixture of Experts(MoE) 모델 지원에 있어 탁월한 성능을 보여줍니다. MoE는 모델의 전체 파라미터 중 일부만을 활성화하여 연산 효율을 높이는 구조인데, 이때 각 전문가(Expert)에게 데이터를 배분하고 연산하는 과정에서 발생하는 오버헤드를 줄이는 것이 성능 최적화의 핵심입니다. 이번 글에서는 vLLM의 Humming 전문가 레이어에서 발견된 불필요한 메모리 복사 문제를 해결한 최적화 PR([Perf][Humming] Skip the redundant MoE input copy before the w13 GEMM)을 분석해 보겠습니다.\n\n## 문제 상황: 왜 불필요한 복사가 발생했는가?\n\nvLLM의 HummingExpertsBase 클래스는 전문가 레이어의 연산을 처리할 때 may_process_input이라는 함수를 거칩니다. 이 함수는 입력 데이터에 대해 양자화(Quantization), Hadamard 변환, 활성화 함수 적용, 또는 토큰 스캐터링(Scatter)과 같은 전처리를 수행합니다.\n\n기존의 구현에서는 전처리가 필요한지 여부와 상관없이 항상 quanted_gate_up_input이라는 워크스페이스 버퍼를 전달했습니다. 이로 인해 양자화나 변환이 전혀 필요 없는 상황에서도 입력 데이터를 이 버퍼로 복사하는 과정이 강제되었습니다. 이후 수행되는 w13 GEMM(Gate & Up projection) 연산은 원본 데이터를 직접 읽을 수 있음에도 불구하고, 굳이 복사된 데이터를 읽게 되어 메모리 대역폭을 낭비하게 되었습니다.\n\n## 코드 분석: 조건부 입력 처리 로직 도입\n\n이번 PR의 핵심은 전처리가 실제로 필요한 경우에만 출력 버퍼를 할당하고, 그렇지 않으면 None을 전달하여 복사를 생략하는 것입니다.\n\n### vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py 변경 사항\n\nBefore:\npython\n# 기존에는 조건 없이 quanted_input(워크스페이스)을 전달함\ninputs, group_scales, token_scales = may_process_input(\n config,\n inputs=inputs,\n outputs=quanted_input,\n token_scales=scale2 if mode.has_secondary_scale else scale,\n hadamard_block_size=getattr(quant_config, f\"{prefix}_hadamard_block_size\"),\n layout=layout,\n # ... 생략 ...\n)\n\n\nAfter:\npython\n# 1. 전처리가 필요한지 확인하는 플래그 생성\nhadamard_block_size = getattr(quant_config, f\"{prefix}_hadamard_block_size\")\nhas_input_processing = (\n mode.should_quantize\n or (hadamard_block_size is not None and hadamard_block_size > 1)\n or activation is not None\n or scatter_idx is not None\n or num_valid_tokens is not None\n)\n\n# 2. 전처리가 필요할 때만 outputs에 버퍼를 전달, 아니면 None 전달\ninputs, group_scales, token_scales = may_process_input(\n config,\n inputs=inputs,\n outputs=quanted_input if has_input_processing else None,\n token_scales=scale2 if mode.has_secondary_scale else scale,\n hadamard_block_size=hadamard_block_size,\n layout=layout,\n expert_tokens=expert_tokens if layout == \"grouped_mask\" else None,\n scatter_idx=scatter_idx,\n # ... 생략 ...\n)\n\n\n위 코드에서 볼 수 있듯이, has_input_processing이라는 변수를 통해 다섯 가지 조건(양자화 여부, Hadamard 블록 크기, 활성화 함수 유무, 스캐터 인덱스 유무, 유효 토큰 수 제한 유무)을 체크합니다. 만약 이 중 하나라도 해당된다면 전처리가 필요하므로 기존처럼 quanted_input 버퍼를 사용합니다. 하지만 모든 조건이 False라면 outputs에 None이 전달됩니다.\n\nmay_process_input 내부 로직은 outputs가 None일 경우 별도의 복사나 변환 작업을 수행하지 않고 입력받은 inputs를 그대로 반환하도록 설계되어 있습니다. 결과적으로 w13 GEMM 연산은 원본 inputs 텐서를 직접 참조하게 됩니다.\n\n## 왜 이게 좋은 최적화인가?\n\n이 최적화는 전형적인 "불필요한 작업 제거(Eliminating Redundant Work)" 사례입니다.\n\n1. 메모리 대역폭(Memory Bandwidth) 절약: GPU는 연산 능력(FLOPS)에 비해 메모리에서 데이터를 읽고 쓰는 속도가 상대적으로 느립니다. 특히 LLM 추론은 메모리 대역폭에 의해 성능이 결정되는 경우가 많습니다. 수 기가바이트에 달하는 텐서를 한 곳에서 다른 곳으로 복사하는 것은 그 자체로 대역폭을 점유하며, 이는 실제 행렬 곱셈(GEMM) 연산에 가야 할 자원을 뺏는 결과가 됩니다.\n2. 지연 시간(Latency) 감소: 복사 작업은 아주 짧은 시간이 걸리더라도 커널 실행 오버헤드를 발생시킵니다. 수천 번의 반복이 일어나는 추론 과정에서 이러한 미세한 지연 시간이 쌓이면 전체 처리량(Throughput)에 영향을 줍니다.\n3. Zero-copy 지향: 데이터 전처리가 필요 없는 'Clean path'에서는 원본 데이터를 그대로 사용하는 Zero-copy 방식을 취함으로써 하드웨어 자원을 가장 효율적으로 활용할 수 있습니다.\n\n## 리뷰어 피드백 분석\n\nPR 리뷰 과정에서 mgoin은 이 로직을 Humming 내부에서 처리할지 고민했지만, 현재로서는 이 방식이 적절하다고 판단하여 승인했습니다. 이는 특정 하드웨어나 커널의 특수성을 고려하면서도 코드의 명확성을 유지하려는 고민을 보여줍니다. CI 결과에서도 SM80(A100 등) 아키텍처에서 모든 테스트가 통과되었음을 확인할 수 있습니다.\n\n## 마치며: 시니어 엔지니어의 관점\n\n성능 최적화는 거창한 알고리즘의 변경에서만 오는 것이 아닙니다. 이번 사례처럼 "당연히 해야 한다고 생각했던 복사"가 정말 필요한지 의문을 던지는 것에서 시작됩니다. 특히 고성능 컴퓨팅(HPC)이나 딥러닝 가속기 라이브러리를 다룰 때는 데이터의 흐름(Data Flow)을 면밀히 추적하여 불필요한 메모리 이동을 최소화하는 습관이 중요합니다. vLLM과 같은 프로젝트가 세계 최고의 성능을 유지하는 비결은 바로 이런 디테일한 최적화들이 모여있기 때문입니다.

참고 자료

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글