---
title: "warp shuffle: 다섯 입력에서 합계가 흔들리는 이유"
author: "브레이브브라우저쓰자"
published_at: "2026-11-02T07:27:01.193Z"
updated_at: "2026-10-06T03:18:14.300Z"
tags: ["warp shuffle"]
canonical_url: "https://ariser.io/@writer_a9817d5a8de0/warp-shuffle-%EB%8B%A4%EC%84%AF-%EC%9E%85%EB%A0%A5%EC%97%90%EC%84%9C-%ED%95%A9%EA%B3%84%EA%B0%80-%ED%9D%94%EB%93%A4%EB%A6%AC%EB%8A%94-%EC%9D%B4%EC%9C%A0"
ai_assisted: true
---

# warp shuffle: 다섯 입력에서 합계가 흔들리는 이유

이 글은 AI가 활용됐습니다.

[원문](https://ariser.io/@writer_a9817d5a8de0/warp-shuffle-%EB%8B%A4%EC%84%AF-%EC%9E%85%EB%A0%A5%EC%97%90%EC%84%9C-%ED%95%A9%EA%B3%84%EA%B0%80-%ED%9D%94%EB%93%A4%EB%A6%AC%EB%8A%94-%EC%9D%B4%EC%9C%A0)

warp shuffle로 합계를 구했더니 32개 입력에서는 맞고 다섯 개 입력에서는 결과가 흔들린다. 값을 lane 사이에서 옮긴다는 동작은 간단해 보여도, 참여하는 lane과 읽는 원본 lane이 모두 유효해야 한다. warp shuffle의 정확성은 덧셈식뿐 아니라 매 단계의 참가자에서 결정된다.

## mask는 비참여 lane의 값을 만들어 주지 않는다

CUDA의 shuffle 함수는 같은 warp의 thread가 가진 값을 shared memory 없이 교환한다. 참여 mask와 원본 lane을 지정하며, 호출자와 mask에 포함된 아직 종료하지 않은 thread의 호출 조건을 맞춰야 한다. 읽는 원본 thread도 실제로 참여해야 한다. width는 1, 2, 4, 8, 16, 32 중 하나다. [CUDA Programming Guide, Warp Shuffle Functions와 Warp __sync Intrinsic Constraints](https://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/cpp-language-extensions.html#warp-shuffle-functions)

mask에 다섯 비트를 켠다는 사실만으로 원본이 없는 lane의 값이 0이 되지는 않는다. width=5 역시 유효한 부분 묶음을 지정하는 방법이 아니다. 데이터 개수, 참여 lane 수, width가 서로 어떤 관계인지 따로 정해야 한다.

## 여덟 lane의 합을 단계별로 따라간다

설명용으로 완전한 warp 중 lane 0–7만 참여하는 부분 묶음을 만들자. 모든 여덟 lane이 같은 mask 0xFF를 사용하고, width=8로 `__shfl_down_sync`를 호출한다. 각 lane의 처음 값은 순서대로 1, 2, 3, 4, 5, 6, 7, 8인 작은 정수다.

delta=4에서 모든 참여자가 shuffle을 호출하되 lane 번호가 4보다 작은 경우만 받은 값을 더한다. 앞의 네 값은 6, 8, 10, 12가 된다. 뒤의 네 값은 그대로다. width 경계를 벗어나는 원본에 대해서는 이 down 함수가 자기 값을 돌려주는 규칙이 있으므로, 마지막 네 lane도 유효한 범위의 호출을 한다.

delta=2에서는 다시 여덟 lane 모두 호출하고 앞의 두 lane만 더한다. lane 0과 1은 각각 16과 20이다. delta=1에서도 모두 호출한 뒤 lane 0만 더하여 36을 얻는다. 각 단계의 값은 앞 단계 완료 시점의 값을 교환하는 것으로 추적했다. 덧셈을 하는 lane과 교환에 참여하는 lane을 혼동해 뒤쪽 참여자를 너무 일찍 제거하면 원본 값이 사라진다.

이 계산은 호출 계약과 정수 값 이동의 자체 추적이며 CUDA 컴파일이나 실제 GPU 실행 결과를 제시한 것은 아니다. 큰 정수의 overflow나 부동소수점 반올림까지 이 작은 예가 다루지는 않는다.

## 다섯 입력이라면 빈 데이터를 명시적으로 채울 수 있다

lane 0–4만 존재하는 참여 집단에 width=32, delta=4를 그대로 적용하는 순진한 변경을 생각하자. lane 0은 lane 4를 읽지만 lane 1–4는 lane 5–8을 요구한다. 이 원본들은 참여 집단에 없다. mask를 0x1F로 바꾸는 것만으로 모든 shuffle의 읽기가 안전해지지는 않는다.

한 가지 단순한 해결 모델은 실제로 32개의 살아 있는 thread가 있는 완전한 warp를 사용하고, 유효 입력 다섯 개 외의 lane에는 덧셈의 항등원 0을 넣는 것이다. 모든 32개가 mask 0xFFFFFFFF, width=32로 delta 16, 8, 4, 2, 1의 호출에 참여한다. 각 단계에서 lane<delta일 때만 받은 값을 더하면 lane 0에 1+2+3+4+5=15가 남는다.

여기서 ‘0을 넣는다’는 것은 입력 배열의 범위 밖을 실제로 읽는다는 뜻이 아니다. 유효 lane만 입력을 읽고 나머지 thread는 자신의 값 변수를 0으로 초기화한다. 실제 thread가 다섯 개밖에 없는 마지막 warp에 이 full-mask 방법을 그대로 적용하라는 제안도 아니다. 완전한 참여 집단을 확보하거나 불완전한 집단을 지원하는 다른 방식을 사용해야 한다.

shuffle의 sync 이름을 shared memory 전체의 barrier로 읽어서도 안 된다. 원문은 이 함수가 메모리 순서를 제공하지 않는다고 명시한다. 레지스터 값의 교환과 별도로 공유 메모리 생산·소비가 있다면 그 데이터 경로의 동기화를 검토해야 한다.

warp shuffle을 점검할 때는 각 단계의 호출자, mask와 width, 모든 원본 lane을 표로 추적한다. 합계 공식이 맞는지에 더해 원본이 실제로 참여하는지 확인하면 입력 개수가 달라졌을 때만 발생하는 오류를 설명할 수 있다.

