warp.shuffle_down() μΌλŒ€μΌ 톡신

μ›Œν”„ 레벨 이웃 ν†΅μ‹ μ—μ„œλŠ” shuffle_down()을 μ‚¬μš©ν•˜μ—¬ μ›Œν”„ λ‚΄ 인접 레인의 데이터에 μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 κ°•λ ₯ν•œ κΈ°λ³Έ μš”μ†Œλ₯Ό 톡해 곡유 λ©”λͺ¨λ¦¬λ‚˜ λͺ…μ‹œμ  동기화 없이 μœ ν•œ μ°¨λΆ„, 이동 평균, 이웃 기반 계산을 효율적으둜 μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

핡심 톡찰: shuffle_down() 연산은 SIMT 싀행을 ν™œμš©ν•˜μ—¬ 각 레인이 같은 μ›Œν”„ λ‚΄ μ΄μ›ƒμ˜ 데이터에 μ ‘κ·Όν•  수 있게 ν•˜λ©°, 효율적인 μŠ€ν…μ‹€ νŒ¨ν„΄κ³Ό μŠ¬λΌμ΄λ”© μœˆλ„μš° 연산을 κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

μŠ€ν…μ‹€ μ—°μ‚°μ΄λž€? μŠ€ν…μ‹€ 연산은 각 좜λ ₯ μš”μ†Œκ°€ 이웃 μž…λ ₯ μš”μ†Œμ˜ κ³ μ •λœ νŒ¨ν„΄μ— μ˜μ‘΄ν•˜λŠ” κ³„μ‚°μž…λ‹ˆλ‹€. λŒ€ν‘œμ μΈ 예둜 μœ ν•œ μ°¨λΆ„(λ„ν•¨μˆ˜), ν•©μ„±κ³±, 이동 평균이 μžˆμŠ΅λ‹ˆλ‹€. β€œμŠ€ν…μ‹€β€œμ€ 이웃 μ ‘κ·Ό νŒ¨ν„΄μ„ κ°€λ¦¬ν‚΅λ‹ˆλ‹€ - 예λ₯Ό λ“€μ–΄ [i-1, i, i+1]을 μ½λŠ” 3점 μŠ€ν…μ‹€μ΄λ‚˜ [i-2, i-1, i, i+1, i+2]λ₯Ό μ½λŠ” 5점 μŠ€ν…μ‹€μ΄ μžˆμŠ΅λ‹ˆλ‹€.

핡심 κ°œλ…

이 νΌμ¦μ—μ„œ 배울 λ‚΄μš©:

  • shuffle_down()을 ν™œμš©ν•œ μ›Œν”„ 레벨 데이터 μ…”ν”Œ
  • μŠ€ν…μ‹€ 계산을 μœ„ν•œ 이웃 μ ‘κ·Ό νŒ¨ν„΄
  • μ›Œν”„ κ°€μž₯μžλ¦¬μ—μ„œμ˜ 경계 처리
  • ν™•μž₯된 이웃 접근을 μœ„ν•œ 닀쀑 μ˜€ν”„μ…‹ μ…”ν”Œ
  • λ©€ν‹° 블둝 μ‹œλ‚˜λ¦¬μ˜€μ—μ„œμ˜ μ›Œν”„ κ°„ μ‘°μ •

shuffle_down 연산은 각 레인이 더 높은 인덱슀의 λ ˆμΈμ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜¬ 수 있게 ν•©λ‹ˆλ‹€: \[\Large \text{shuffle_down}(\text{value}, \text{offset}) = \text{value_from_lane}(\text{lane_id} + \text{offset})\]

이λ₯Ό 톡해 λ³΅μž‘ν•œ 이웃 μ ‘κ·Ό νŒ¨ν„΄μ΄ κ°„λ‹¨ν•œ μ›Œν”„ 레벨 μ—°μ‚°μœΌλ‘œ λ³€ν™˜λ˜μ–΄, λͺ…μ‹œμ  λ©”λͺ¨λ¦¬ 인덱싱 없이 효율적인 μŠ€ν…μ‹€ 계산이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

1. κΈ°λ³Έ 이웃 μ°¨λΆ„

ꡬ성

  • 벑터 크기: SIZE = WARP_SIZE (GPU에 따라 32 λ˜λŠ” 64)
  • κ·Έλ¦¬λ“œ ꡬ성: (1, 1) κ·Έλ¦¬λ“œλ‹Ή 블둝 수
  • 블둝 ꡬ성: (WARP_SIZE, 1) 블둝당 μŠ€λ ˆλ“œ 수
  • 데이터 νƒ€μž…: DType.float32
  • λ ˆμ΄μ•„μ›ƒ: Layout.row_major(SIZE) (1D row-major)

shuffle_down κ°œλ…

κΈ°μ‘΄ 이웃 μ ‘κ·Ό 방식은 λ³΅μž‘ν•œ 인덱싱과 경계 검사가 ν•„μš”ν•©λ‹ˆλ‹€:

# κΈ°μ‘΄ 방식 - λ³΅μž‘ν•˜κ³  였λ₯˜κ°€ λ°œμƒν•˜κΈ° 쉬움
if global_i < size - 1:
    next_value = input[global_i + 1]  # λ²”μœ„ 초과 κ°€λŠ₯μ„±
    result = next_value - current_value

κΈ°μ‘΄ λ°©μ‹μ˜ 문제점:

  • 경계 검사: λ°°μ—΄ 경계λ₯Ό μˆ˜λ™μœΌλ‘œ 확인해야 함
  • λ©”λͺ¨λ¦¬ μ ‘κ·Ό: λ³„λ„μ˜ λ©”λͺ¨λ¦¬ λ‘œλ“œκ°€ ν•„μš”
  • 동기화: 곡유 λ©”λͺ¨λ¦¬ νŒ¨ν„΄μ—μ„œ 배리어가 ν•„μš”ν•  수 있음
  • λ³΅μž‘ν•œ 둜직: κ²½κ³„μ˜ μ˜ˆμ™Έ 상황 μ²˜λ¦¬κ°€ μž₯황해짐

shuffle_down()을 μ‚¬μš©ν•˜λ©΄ 이웃 접근이 κ°„κ²°ν•΄μ§‘λ‹ˆλ‹€:

# μ›Œν”„ μ…”ν”Œ 방식 - κ°„λ‹¨ν•˜κ³  μ•ˆμ „
current_val = input[global_i]
next_val = shuffle_down(current_val, 1)  # lane+1μ—μ„œ κ°’ κ°€μ Έμ˜€κΈ°
if lane < WARP_SIZE - 1:
    result = next_val - current_val

shuffle_down의 μž₯점:

  • λ©”λͺ¨λ¦¬ μ˜€λ²„ν—€λ“œ 제둜: μΆ”κ°€ λ©”λͺ¨λ¦¬ μ ‘κ·Ό λΆˆν•„μš”
  • μžλ™ 경계 처리: ν•˜λ“œμ›¨μ–΄κ°€ μ›Œν”„ 경계λ₯Ό 관리
  • 동기화 λΆˆν•„μš”: SIMT 싀행이 정확성을 보μž₯
  • μ‘°ν•© κ°€λŠ₯: λ‹€λ₯Έ μ›Œν”„ μ—°μ‚°κ³Ό μ‰½κ²Œ κ²°ν•©

μ™„μ„±ν•  μ½”λ“œ

shuffle_down()으둜 λ‹€μŒ μš”μ†Œμ— μ ‘κ·Όν•˜μ—¬ μœ ν•œ 차뢄을 κ΅¬ν˜„ν•©λ‹ˆλ‹€.

μˆ˜ν•™μ  μ—°μ‚°: 각 μš”μ†Œμ˜ 이산 λ„ν•¨μˆ˜(μœ ν•œ μ°¨λΆ„)λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€: \[\Large \text{output}[i] = \text{input}[i+1] - \text{input}[i]\]

μž…λ ₯ 데이터 [0, 1, 4, 9, 16, 25, ...] (제곱수: i * i)λ₯Ό μ°¨λΆ„κ°’ [1, 3, 5, 7, 9, ...] (ν™€μˆ˜)둜 λ³€ν™˜ν•˜μ—¬, 이차 ν•¨μˆ˜μ˜ 이산 λ„ν•¨μˆ˜λ₯Ό 효과적으둜 κ³„μ‚°ν•©λ‹ˆλ‹€.

comptime SIZE = WARP_SIZE
comptime BLOCKS_PER_GRID = (1, 1)
comptime THREADS_PER_BLOCK = (WARP_SIZE, 1)
comptime dtype = DType.float32
comptime layout = Layout.row_major(SIZE)


fn neighbor_difference[
    layout: Layout, size: Int
](
    output: LayoutTensor[dtype, layout, MutAnyOrigin],
    input: LayoutTensor[dtype, layout, ImmutAnyOrigin],
):
    """
    Compute finite differences: output[i] = input[i+1] - input[i]
    Uses shuffle_down(val, 1) to get the next neighbor's value.
    Works across multiple blocks, each processing one warp worth of data.
    """
    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)
    lane = Int(lane_id())

    # FILL IN (roughly 7 lines)


전체 파일 보기: problems/p25/p25.mojo

팁

1. shuffle_down μ΄ν•΄ν•˜κΈ°

shuffle_down(value, offset) 연산은 각 레인이 더 높은 인덱슀의 λ ˆμΈμ—μ„œ 데이터λ₯Ό 받을 수 있게 ν•©λ‹ˆλ‹€. λͺ…μ‹œμ  λ©”λͺ¨λ¦¬ λ‘œλ“œ 없이 이웃 μš”μ†Œμ— μ ‘κ·Όν•˜λŠ” 방법을 μ‚΄νŽ΄λ³΄μ„Έμš”.

shuffle_down(val, 1)이 ν•˜λŠ” 일:

  • 레인 0이 레인 1의 값을 λ°›μŒ
  • 레인 1이 레인 2의 값을 λ°›μŒ
  • …
  • 레인 30이 레인 31의 값을 λ°›μŒ
  • 레인 31은 λ―Έμ •μ˜ 값을 λ°›μŒ (경계 κ²€μ‚¬λ‘œ 처리)

2. μ›Œν”„ 경계 고렀사항

μ›Œν”„μ˜ κ°€μž₯μžλ¦¬μ—μ„œ μ–΄λ–€ 일이 μΌμ–΄λ‚˜λŠ”μ§€ 생각해 λ³΄μ„Έμš”. 일뢀 λ ˆμΈμ€ μ…”ν”Œ μ—°μ‚°μœΌλ‘œ μ ‘κ·Όν•  μœ νš¨ν•œ 이웃이 없을 수 μžˆμŠ΅λ‹ˆλ‹€.

과제: μ›Œν”„ κ²½κ³„μ—μ„œ μ…”ν”Œ 연산이 λ―Έμ •μ˜ 데이터λ₯Ό λ°˜ν™˜ν•  수 μžˆλŠ” 경우λ₯Ό μ²˜λ¦¬ν•˜λ„λ‘ μ•Œκ³ λ¦¬μ¦˜μ„ μ„€κ³„ν•˜μ„Έμš”.

WARP_SIZE = 32μ—μ„œμ˜ 이웃 μ°¨λΆ„:

  • μœ νš¨ν•œ μ°¨λΆ„ (lane < WARP_SIZE - 1): 레인 0-30 (31개 레인)

    • 쑰건: \(\text{lane_id}() \in {0, 1, \cdots, 30}\)
    • 이유: shuffle_down(current_val, 1)이 λ‹€μŒ μ΄μ›ƒμ˜ 값을 μ„±κ³΅μ μœΌλ‘œ κ°€μ Έμ˜΄
    • κ²°κ³Ό: output[i] = input[i+1] - input[i] (μœ ν•œ μ°¨λΆ„)
  • 경계 μΌ€μ΄μŠ€ (else): 레인 31 (1개 레인)

    • 쑰건: \(\text{lane_id}() = 31\)
    • 이유: shuffle_down(current_val, 1)이 λ―Έμ •μ˜ 데이터λ₯Ό λ°˜ν™˜ (레인 32κ°€ μ—†μŒ)
    • κ²°κ³Ό: output[i] = 0 (μ°¨λΆ„ 계산 λΆˆκ°€)

3. 레인 식별

lane = lane_id()  # 0λΆ€ν„° WARP_SIZE-1κΉŒμ§€ λ°˜ν™˜

레인 번호 λ§€κΈ°κΈ°: 각 μ›Œν”„ λ‚΄μ—μ„œ λ ˆμΈμ€ 0, 1, 2, …, WARP_SIZE-1둜 λ²ˆν˜Έκ°€ λ§€κ²¨μ§‘λ‹ˆλ‹€

이웃 μ°¨λΆ„ ν…ŒμŠ€νŠΈ:

pixi run p25 --neighbor
pixi run -e amd p25 --neighbor
pixi run -e apple p25 --neighbor
uv run poe p25 --neighbor

ν’€μ—ˆμ„ λ•Œμ˜ μ˜ˆμƒ 좜λ ₯:

WARP_SIZE:  32
SIZE:  32
output: [1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0, 33.0, 35.0, 37.0, 39.0, 41.0, 43.0, 45.0, 47.0, 49.0, 51.0, 53.0, 55.0, 57.0, 59.0, 61.0, 0.0]
expected: [1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0, 33.0, 35.0, 37.0, 39.0, 41.0, 43.0, 45.0, 47.0, 49.0, 51.0, 53.0, 55.0, 57.0, 59.0, 61.0, 0.0]
βœ… Basic neighbor difference test passed!

μ†”λ£¨μ…˜

fn neighbor_difference[
    layout: Layout, size: Int
](
    output: LayoutTensor[dtype, layout, MutAnyOrigin],
    input: LayoutTensor[dtype, layout, ImmutAnyOrigin],
):
    """
    Compute finite differences: output[i] = input[i+1] - input[i]
    Uses shuffle_down(val, 1) to get the next neighbor's value.
    Works across multiple blocks, each processing one warp worth of data.
    """
    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)
    lane = Int(lane_id())

    if global_i < size:
        # Get current value
        current_val = input[global_i]

        # Get next neighbor's value using shuffle_down
        next_val = shuffle_down(current_val, 1)

        # Compute difference - valid within warp boundaries
        # Last lane of each warp has no valid neighbor within the warp
        # Note there's only one warp in this test, so we don't need to check global_i < size - 1
        # We'll see how this works with multiple blocks in the next tests
        if lane < WARP_SIZE - 1:
            output[global_i] = next_val - current_val
        else:
            # Last thread in warp or last thread overall, set to 0
            output[global_i] = 0


이 μ†”λ£¨μ…˜μ€ shuffle_down()이 κΈ°μ‘΄ λ°°μ—΄ 인덱싱을 효율적인 μ›Œν”„ 레벨 ν†΅μ‹ μœΌλ‘œ μ–΄λ–»κ²Œ λ³€ν™˜ν•˜λŠ”μ§€ λ³΄μ—¬μ€λ‹ˆλ‹€.

μ•Œκ³ λ¦¬μ¦˜ 뢄석:

if global_i < size:
    current_val = input[global_i]           # 각 레인이 μžμ‹ μ˜ μš”μ†Œλ₯Ό 읽음
    next_val = shuffle_down(current_val, 1) # ν•˜λ“œμ›¨μ–΄κ°€ 데이터λ₯Ό 였λ₯Έμͺ½μœΌλ‘œ 이동

    if lane < WARP_SIZE - 1:
        output[global_i] = next_val - current_val  # μ°¨λΆ„ 계산
    else:
        output[global_i] = 0                       # 경계 처리

SIMT μ‹€ν–‰ 상세 뢄석:

사이클 1: λͺ¨λ“  레인이 λ™μ‹œμ— 값을 λ‘œλ“œ
  레인 0: current_val = input[0] = 0
  레인 1: current_val = input[1] = 1
  레인 2: current_val = input[2] = 4
  ...
  레인 31: current_val = input[31] = 961

사이클 2: shuffle_down(current_val, 1)이 λͺ¨λ“  λ ˆμΈμ—μ„œ μ‹€ν–‰
  레인 0: 레인 1μ—μ„œ current_val μˆ˜μ‹  β†’ next_val = 1
  레인 1: 레인 2μ—μ„œ current_val μˆ˜μ‹  β†’ next_val = 4
  레인 2: 레인 3μ—μ„œ current_val μˆ˜μ‹  β†’ next_val = 9
  ...
  레인 30: 레인 31μ—μ„œ current_val μˆ˜μ‹  β†’ next_val = 961
  레인 31: λ―Έμ •μ˜ μˆ˜μ‹  (레인 32 μ—†μŒ) β†’ next_val = ?

사이클 3: μ°¨λΆ„ 계산 (레인 0-30만 ν•΄λ‹Ή)
  레인 0: output[0] = 1 - 0 = 1
  레인 1: output[1] = 4 - 1 = 3
  레인 2: output[2] = 9 - 4 = 5
  ...
  레인 31: output[31] = 0 (경계 쑰건)

μˆ˜ν•™μ  톡찰: 이산 λ„ν•¨μˆ˜ μ—°μ‚°μž \(D\)λ₯Ό κ΅¬ν˜„ν•©λ‹ˆλ‹€: \[\Large D\lbrack f\rbrack(i) = f(i+1) - f(i)\]

이차 μž…λ ₯ \(f(i) = i^2\)에 λŒ€ν•΄: \[\Large D[i^2] = (i+1)^2 - i^2 = i^2 + 2i + 1 - i^2 = 2i + 1\]

shuffle_down이 μš°μ›”ν•œ 이유:

  1. λ©”λͺ¨λ¦¬ νš¨μœ¨μ„±: κΈ°μ‘΄ 방식은 input[global_i + 1] λ‘œλ“œκ°€ ν•„μš”ν•˜μ—¬ μΊμ‹œ 미슀λ₯Ό μœ λ°œν•  수 있음
  2. 경계 μ•ˆμ „μ„±: λ²”μœ„ 초과 μ ‘κ·Ό μœ„ν—˜μ΄ μ—†μŒ - ν•˜λ“œμ›¨μ–΄κ°€ μ›Œν”„ 경계λ₯Ό 처리
  3. SIMT μ΅œμ ν™”: 단일 λͺ…령이 λͺ¨λ“  λ ˆμΈμ„ λ™μ‹œμ— 처리
  4. λ ˆμ§€μŠ€ν„° 톡신: 데이터가 λ©”λͺ¨λ¦¬ 계측 ꡬ쑰가 μ•„λ‹Œ λ ˆμ§€μŠ€ν„° 사이λ₯Ό 이동

μ„±λŠ₯ νŠΉμ„±:

  • μ§€μ—° μ‹œκ°„: 1 사이클 (λ©”λͺ¨λ¦¬ μ ‘κ·Όμ˜ 100+ 사이클 λŒ€λΉ„)
  • λŒ€μ—­ν­: 0 λ°”μ΄νŠΈ (κΈ°μ‘΄ λ°©μ‹μ˜ μŠ€λ ˆλ“œλ‹Ή 4λ°”μ΄νŠΈ λŒ€λΉ„)
  • 병렬성: 32개 레인 λͺ¨λ‘ λ™μ‹œμ— 처리

2. 닀쀑 μ˜€ν”„μ…‹ 이동 평균

ꡬ성

  • 벑터 크기: SIZE_2 = 64 (λ©€ν‹° 블둝 μ‹œλ‚˜λ¦¬μ˜€)
  • κ·Έλ¦¬λ“œ ꡬ성: BLOCKS_PER_GRID = (2, 1) κ·Έλ¦¬λ“œλ‹Ή 블둝 수
  • 블둝 ꡬ성: THREADS_PER_BLOCK = (WARP_SIZE, 1) 블둝당 μŠ€λ ˆλ“œ 수

μ™„μ„±ν•  μ½”λ“œ

μ—¬λŸ¬ shuffle_down 연산을 μ‚¬μš©ν•˜μ—¬ 3점 이동 평균을 κ΅¬ν˜„ν•©λ‹ˆλ‹€.

μˆ˜ν•™μ  μ—°μ‚°: μ„Έ 개의 연속 μš”μ†Œλ₯Ό μ‚¬μš©ν•˜μ—¬ μŠ¬λΌμ΄λ”© μœˆλ„μš° 평균을 κ³„μ‚°ν•©λ‹ˆλ‹€: \[\Large \text{output}[i] = \frac{1}{3}\left(\text{input}[i] + \text{input}[i+1] + \text{input}[i+2]\right)\]

경계 처리: μ›Œν”„ κ²½κ³„μ—μ„œ μ•Œκ³ λ¦¬μ¦˜μ΄ μš°μ•„ν•˜κ²Œ μ μ‘ν•©λ‹ˆλ‹€:

  • 3점 전체 μœˆλ„μš°: \(\text{output}[i] = \frac{1}{3}\sum_{k=0}^{2} \text{input}[i+k]\) - λͺ¨λ“  이웃이 μ‚¬μš© κ°€λŠ₯ν•  λ•Œ
  • 2점 μœˆλ„μš°: \(\text{output}[i] = \frac{1}{2}\sum_{k=0}^{1} \text{input}[i+k]\) - λ‹€μŒ μ΄μ›ƒλ§Œ μ‚¬μš© κ°€λŠ₯ν•  λ•Œ
  • 1점 μœˆλ„μš°: \(\text{output}[i] = \text{input}[i]\) - 이웃이 μ‚¬μš© λΆˆκ°€ν•  λ•Œ

μ΄λŠ” shuffle_down()이 μ›Œν”„ λ²”μœ„ λ‚΄μ—μ„œ μžλ™ 경계 μ²˜λ¦¬μ™€ ν•¨κ»˜ 효율적인 μŠ€ν…μ‹€ 연산을 κ°€λŠ₯ν•˜κ²Œ ν•˜λŠ” 방법을 λ³΄μ—¬μ€λ‹ˆλ‹€.

comptime SIZE_2 = 64
comptime BLOCKS_PER_GRID_2 = (2, 1)
comptime THREADS_PER_BLOCK_2 = (WARP_SIZE, 1)
comptime layout_2 = Layout.row_major(SIZE_2)


fn moving_average_3[
    layout: Layout, size: Int
](
    output: LayoutTensor[dtype, layout, MutAnyOrigin],
    input: LayoutTensor[dtype, layout, ImmutAnyOrigin],
):
    """
    Compute 3-point moving average: output[i] = (input[i] + input[i+1] + input[i+2]) / 3
    Uses shuffle_down with offsets 1 and 2 to access neighbors.
    Works within warp boundaries across multiple blocks.
    """
    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)
    lane = Int(lane_id())

    # FILL IN (roughly 10 lines)


팁

1. 닀쀑 μ˜€ν”„μ…‹ μ…”ν”Œ νŒ¨ν„΄

이 퍼즐은 μ—¬λŸ¬ 이웃에 λ™μ‹œμ— μ ‘κ·Όν•΄μ•Ό ν•©λ‹ˆλ‹€. μ„œλ‘œ λ‹€λ₯Έ μ˜€ν”„μ…‹μœΌλ‘œ μ…”ν”Œ 연산을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

핡심 질문:

  • input[i+1]κ³Ό input[i+2]λ₯Ό μ…”ν”Œ μ—°μ‚°μœΌλ‘œ μ–΄λ–»κ²Œ κ°€μ Έμ˜¬ 수 μžˆμ„κΉŒμš”?
  • μ…”ν”Œ μ˜€ν”„μ…‹κ³Ό 이웃 거리의 κ΄€κ³„λŠ” λ¬΄μ—‡μΌκΉŒμš”?
  • 같은 μ†ŒμŠ€ 값에 λŒ€ν•΄ μ—¬λŸ¬ 번 μ…”ν”Œμ„ μˆ˜ν–‰ν•  수 μžˆμ„κΉŒμš”?

μ‹œκ°ν™” κ°œλ…:

ν˜„μž¬ 레인이 ν•„μš”ν•œ κ°’: current_val, next_val, next_next_val
μ…”ν”Œ μ˜€ν”„μ…‹:        0 (직접),    1,        2

생각해 λ³΄μ„Έμš”: λͺ‡ 번의 μ…”ν”Œ 연산이 ν•„μš”ν•˜κ³ , μ–΄λ–€ μ˜€ν”„μ…‹μ„ μ‚¬μš©ν•΄μ•Ό ν• κΉŒμš”?

2. 단계적 경계 처리

λ‹¨μˆœν•œ 이웃 μ°¨λΆ„κ³Ό 달리, 이 퍼즐은 2개의 이웃에 μ ‘κ·Όν•΄μ•Ό ν•˜λ―€λ‘œ μ—¬λŸ¬ 경계 μ‹œλ‚˜λ¦¬μ˜€κ°€ μžˆμŠ΅λ‹ˆλ‹€.

κ³ λ €ν•  경계 μ‹œλ‚˜λ¦¬μ˜€:

  • 전체 μœˆλ„μš°: 레인이 두 이웃 λͺ¨λ‘ μ ‘κ·Ό κ°€λŠ₯ β†’ 3개 κ°’ λͺ¨λ‘ μ‚¬μš©
  • λΆ€λΆ„ μœˆλ„μš°: 레인이 1개 μ΄μ›ƒλ§Œ μ ‘κ·Ό κ°€λŠ₯ β†’ 2개 κ°’ μ‚¬μš©
  • μœˆλ„μš° μ—†μŒ: 레인이 이웃에 μ ‘κ·Ό λΆˆκ°€ β†’ 1개 κ°’ μ‚¬μš©

λΉ„νŒμ  사고:

  • μ–΄λ–€ 레인이 각 μΉ΄ν…Œκ³ λ¦¬μ— ν•΄λ‹Ήν• κΉŒμš”?
  • 값이 적을 λ•Œ ν‰κ· μ˜ κ°€μ€‘μΉ˜λ₯Ό μ–΄λ–»κ²Œ μ‘°μ •ν•΄μ•Ό ν• κΉŒμš”?
  • μ–΄λ–€ 경계 쑰건을 검사해야 ν• κΉŒμš”?

κ³ λ €ν•  νŒ¨ν„΄:

if (두 이웃 λͺ¨λ‘ μ ‘κ·Ό κ°€λŠ₯):
    # 3점 평균
elif (ν•œ μ΄μ›ƒλ§Œ μ ‘κ·Ό κ°€λŠ₯):
    # 2점 평균
else:
    # 1점 (평균 μ—†μŒ)

3. λ©€ν‹° 블둝 μ‘°μ •

이 퍼즐은 μ—¬λŸ¬ 블둝을 μ‚¬μš©ν•˜λ©°, 각 블둝이 λ°μ΄ν„°μ˜ λ‹€λ₯Έ μ˜μ—­μ„ μ²˜λ¦¬ν•©λ‹ˆλ‹€.

μ€‘μš”ν•œ 고렀사항:

  • 각 블둝은 레인 0λΆ€ν„° WARP_SIZE-1κΉŒμ§€μ˜ 자체 μ›Œν”„λ₯Ό 가짐
  • 경계 쑰건은 각 μ›Œν”„ λ‚΄μ—μ„œ λ…λ¦½μ μœΌλ‘œ 적용
  • λΈ”λ‘λ§ˆλ‹€ 레인 λ²ˆν˜Έκ°€ μ΄ˆκΈ°ν™”λ¨

생각해 λ³Ό 질문:

  • 경계 둜직이 블둝 0κ³Ό 블둝 1 λͺ¨λ‘μ—μ„œ μ˜¬λ°”λ₯΄κ²Œ λ™μž‘ν•˜λ‚˜μš”?
  • 레인 경계와 μ „μ—­ λ°°μ—΄ 경계λ₯Ό λͺ¨λ‘ κ²€μ‚¬ν•˜κ³  μžˆλ‚˜μš”?
  • μ„œλ‘œ λ‹€λ₯Έ λΈ”λ‘μ—μ„œ global_i와 lane_id()의 κ΄€κ³„λŠ” μ–΄λ–»κ²Œ λ κΉŒμš”?

디버깅 팁: 각 λΈ”λ‘μ˜ 경계 λ ˆμΈμ—μ„œ μ–΄λ–€ 일이 μΌμ–΄λ‚˜λŠ”μ§€ μΆ”μ ν•˜μ—¬ λ‘œμ§μ„ ν…ŒμŠ€νŠΈν•˜μ„Έμš”.

이동 평균 ν…ŒμŠ€νŠΈ:

pixi run p25 --average
pixi run -e amd p25 --average
uv run poe p25 --average

ν’€μ—ˆμ„ λ•Œμ˜ μ˜ˆμƒ 좜λ ₯:

WARP_SIZE:  32
SIZE_2:  64
output: HostBuffer([3.3333333, 6.3333335, 10.333333, 15.333333, 21.333334, 28.333334, 36.333332, 45.333332, 55.333332, 66.333336, 78.333336, 91.333336, 105.333336, 120.333336, 136.33333, 153.33333, 171.33333, 190.33333, 210.33333, 231.33333, 253.33333, 276.33334, 300.33334, 325.33334, 351.33334, 378.33334, 406.33334, 435.33334, 465.33334, 496.33334, 512.0, 528.0, 595.3333, 630.3333, 666.3333, 703.3333, 741.3333, 780.3333, 820.3333, 861.3333, 903.3333, 946.3333, 990.3333, 1035.3334, 1081.3334, 1128.3334, 1176.3334, 1225.3334, 1275.3334, 1326.3334, 1378.3334, 1431.3334, 1485.3334, 1540.3334, 1596.3334, 1653.3334, 1711.3334, 1770.3334, 1830.3334, 1891.3334, 1953.3334, 2016.3334, 2048.0, 2080.0])
expected: HostBuffer([3.3333333, 6.3333335, 10.333333, 15.333333, 21.333334, 28.333334, 36.333332, 45.333332, 55.333332, 66.333336, 78.333336, 91.333336, 105.333336, 120.333336, 136.33333, 153.33333, 171.33333, 190.33333, 210.33333, 231.33333, 253.33333, 276.33334, 300.33334, 325.33334, 351.33334, 378.33334, 406.33334, 435.33334, 465.33334, 496.33334, 512.0, 528.0, 595.3333, 630.3333, 666.3333, 703.3333, 741.3333, 780.3333, 820.3333, 861.3333, 903.3333, 946.3333, 990.3333, 1035.3334, 1081.3334, 1128.3334, 1176.3334, 1225.3334, 1275.3334, 1326.3334, 1378.3334, 1431.3334, 1485.3334, 1540.3334, 1596.3334, 1653.3334, 1711.3334, 1770.3334, 1830.3334, 1891.3334, 1953.3334, 2016.3334, 2048.0, 2080.0])
βœ… Moving average test passed!

μ†”λ£¨μ…˜

fn moving_average_3[
    layout: Layout, size: Int
](
    output: LayoutTensor[dtype, layout, MutAnyOrigin],
    input: LayoutTensor[dtype, layout, ImmutAnyOrigin],
):
    """
    Compute 3-point moving average: output[i] = (input[i] + input[i+1] + input[i+2]) / 3
    Uses shuffle_down with offsets 1 and 2 to access neighbors.
    Works within warp boundaries across multiple blocks.
    """
    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)
    lane = Int(lane_id())

    if global_i < size:
        # Get current, next, and next+1 values
        current_val = input[global_i]
        next_val = shuffle_down(current_val, 1)
        next_next_val = shuffle_down(current_val, 2)

        # Compute 3-point average - valid within warp boundaries
        if lane < WARP_SIZE - 2 and global_i < size - 2:
            output[global_i] = (current_val + next_val + next_next_val) / 3.0
        elif lane < WARP_SIZE - 1 and global_i < size - 1:
            # Second-to-last in warp: only current + next available
            output[global_i] = (current_val + next_val) / 2.0
        else:
            # Last thread in warp or boundary cases: only current available
            output[global_i] = current_val


이 μ†”λ£¨μ…˜μ€ λ³΅μž‘ν•œ μŠ€ν…μ‹€ 연산을 μœ„ν•œ κ³ κΈ‰ 닀쀑 μ˜€ν”„μ…‹ μ…”ν”Œμ„ λ³΄μ—¬μ€λ‹ˆλ‹€.

전체 μ•Œκ³ λ¦¬μ¦˜ 뢄석:

if global_i < size:
    # 단계 1: μ—¬λŸ¬ μ…”ν”Œλ‘œ ν•„μš”ν•œ 데이터 λͺ¨λ‘ 확보
    current_val = input[global_i]                   # 직접 μ ‘κ·Ό
    next_val = shuffle_down(current_val, 1)         # 였λ₯Έμͺ½ 이웃
    next_next_val = shuffle_down(current_val, 2)    # 였λ₯Έμͺ½+1 이웃

    # 단계 2: μ‚¬μš© κ°€λŠ₯ν•œ 데이터에 λ”°λ₯Έ μ μ‘ν˜• 계산
    if lane < WARP_SIZE - 2 and global_i < size - 2:
        # 3점 μŠ€ν…μ‹€ 전체 μ‚¬μš© κ°€λŠ₯
        output[global_i] = (current_val + next_val + next_next_val) / 3.0
    elif lane < WARP_SIZE - 1 and global_i < size - 1:
        # 2점 μŠ€ν…μ‹€λ§Œ μ‚¬μš© κ°€λŠ₯ (μ›Œν”„ 경계 근처)
        output[global_i] = (current_val + next_val) / 2.0
    else:
        # μŠ€ν…μ‹€ μ‚¬μš© λΆˆκ°€ (μ›Œν”„ 경계)
        output[global_i] = current_val

닀쀑 μ˜€ν”„μ…‹ μ‹€ν–‰ 좔적 (WARP_SIZE = 32):

초기 μƒνƒœ (블둝 0, μš”μ†Œ 0-31):
  레인 0: current_val = input[0] = 1
  레인 1: current_val = input[1] = 2
  레인 2: current_val = input[2] = 4
  ...
  레인 31: current_val = input[31] = X

첫 번째 μ…”ν”Œ: shuffle_down(current_val, 1)
  레인 0: next_val = input[1] = 2
  레인 1: next_val = input[2] = 4
  레인 2: next_val = input[3] = 7
  ...
  레인 30: next_val = input[31] = X
  레인 31: next_val = λ―Έμ •μ˜

두 번째 μ…”ν”Œ: shuffle_down(current_val, 2)
  레인 0: next_next_val = input[2] = 4
  레인 1: next_next_val = input[3] = 7
  레인 2: next_next_val = input[4] = 11
  ...
  레인 29: next_next_val = input[31] = X
  레인 30: next_next_val = λ―Έμ •μ˜
  레인 31: next_next_val = λ―Έμ •μ˜

계산 단계:
  레인 0-29: 3점 전체 평균 β†’ (current + next + next_next) / 3
  레인 30:   2점 평균 β†’ (current + next) / 2
  레인 31:   1점 평균 β†’ current (κ·ΈλŒ€λ‘œ 전달)

μˆ˜ν•™μ  기반: κ°€λ³€ 폭 이산 합성곱을 κ΅¬ν˜„ν•©λ‹ˆλ‹€: \[\Large h[i] = \sum_{k=0}^{K(i)-1} w_k^{(i)} \cdot f[i+k]\]

μœ„μΉ˜μ— 따라 컀널이 μ μ‘ν•©λ‹ˆλ‹€:

  • λ‚΄λΆ€ 점: \(K(i) = 3\), \(\mathbf{w}^{(i)} = [\frac{1}{3}, \frac{1}{3}, \frac{1}{3}]\)
  • 경계 근처: \(K(i) = 2\), \(\mathbf{w}^{(i)} = [\frac{1}{2}, \frac{1}{2}]\)
  • 경계: \(K(i) = 1\), \(\mathbf{w}^{(i)} = [1]\)

λ©€ν‹° 블둝 μ‘°μ •: SIZE_2 = 64와 2개 블둝:

블둝 0 (μ „μ—­ 인덱슀 0-31):
  μ „μ—­ 인덱슀 29, 30, 31에 레인 경계 적용

블둝 1 (μ „μ—­ 인덱슀 32-63):
  μ „μ—­ 인덱슀 61, 62, 63에 레인 경계 적용
  레인 번호 μ΄ˆκΈ°ν™”: global_i=32 β†’ lane=0, global_i=63 β†’ lane=31

μ„±λŠ₯ μ΅œμ ν™”:

  1. 병렬 데이터 확보: 두 μ…”ν”Œ 연산이 λ™μ‹œμ— μ‹€ν–‰
  2. 쑰건뢀 λΆ„κΈ°: GPUκ°€ ν”„λ ˆλ””μΌ€μ΄μ…˜μ„ 톡해 λΆ„κΈ° λ ˆμΈμ„ 효율적으둜 처리
  3. λ©”λͺ¨λ¦¬ 병합: 순차적 μ „μ—­ λ©”λͺ¨λ¦¬ μ ‘κ·Ό νŒ¨ν„΄μ΄ GPU에 졜적
  4. λ ˆμ§€μŠ€ν„° μž¬μ‚¬μš©: λͺ¨λ“  쀑간 값이 λ ˆμ§€μŠ€ν„°μ— μœ μ§€

μ‹ ν˜Έ 처리 관점: 이것은 μž„νŽ„μŠ€ 응닡 \(h[n] = \frac{1}{3}[\delta[n] + \delta[n-1] + \delta[n-2]]\)λ₯Ό κ°€μ§„ 인과 FIR ν•„ν„°λ‘œ, 차단 주파수 \(f_c \approx 0.25f_s\)μ—μ„œ μŠ€λ¬΄λ”©μ„ μ œκ³΅ν•©λ‹ˆλ‹€.

μš”μ•½

이 μ„Ήμ…˜μ˜ 핡심 νŒ¨ν„΄μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€

current_val = input[global_i]
neighbor_val = shuffle_down(current_val, offset)
if lane < WARP_SIZE - offset:
    result = compute(current_val, neighbor_val)

핡심 μž₯점:

  • ν•˜λ“œμ›¨μ–΄ νš¨μœ¨μ„±: λ ˆμ§€μŠ€ν„° κ°„ 직접 톡신
  • 경계 μ•ˆμ „μ„±: μžλ™ μ›Œν”„ λ²”μœ„ 처리
  • SIMT μ΅œμ ν™”: 단일 λͺ…λ Ή, λͺ¨λ“  레인 병렬 처리

ν™œμš© λΆ„μ•Ό: μœ ν•œ μ°¨λΆ„, μŠ€ν…μ‹€ μ—°μ‚°, 이동 평균, ν•©μ„±κ³±.