block.sum()의 핡심 - 블둝 레벨 내적

Puzzle 12μ—μ„œ μ‚΄νŽ΄λ³Έ 내적을 블둝 레벨 sum μ—°μ‚°μœΌλ‘œ κ΅¬ν˜„ν•©λ‹ˆλ‹€. λ³΅μž‘ν•œ 곡유 λ©”λͺ¨λ¦¬ νŒ¨ν„΄μ„ κ°„λ‹¨ν•œ ν•¨μˆ˜ 호좜둜 λŒ€μ²΄ν•©λ‹ˆλ‹€. 블둝 λ‚΄ 각 μŠ€λ ˆλ“œκ°€ ν•˜λ‚˜μ˜ μš”μ†Œλ₯Ό μ²˜λ¦¬ν•˜κ³  block.sum()으둜 κ²°κ³Όλ₯Ό μžλ™μœΌλ‘œ ν•©μ‚°ν•˜μ—¬, 블둝 ν”„λ‘œκ·Έλž˜λ°μ΄ 전체 μŠ€λ ˆλ“œ 블둝에 걸친 GPU 동기화λ₯Ό μ–΄λ–»κ²Œ λ³€ν™˜ν•˜λŠ”μ§€ λ³΄μ—¬μ€λ‹ˆλ‹€.

핡심 톡찰: block.sum() 연산은 블둝 전체 싀행을 ν™œμš©ν•˜μ—¬ 곡유 λ©”λͺ¨λ¦¬ + 배리어 + 트리 λ¦¬λ•μ…˜μ„ 블둝 λ‚΄ λͺ¨λ“  μŠ€λ ˆλ“œμ— 걸쳐 μ›Œν”„ νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λŠ” μ •κ΅ν•˜κ²Œ μ΅œμ ν™”λœ κ΅¬ν˜„μœΌλ‘œ λŒ€μ²΄ν•©λ‹ˆλ‹€. LLVM 뢄석은 기술 뢄석을 μ°Έκ³ ν•˜μ„Έμš”.

핡심 κ°œλ…

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

  • block.sum()을 ν™œμš©ν•œ 블둝 레벨 λ¦¬λ•μ…˜
  • 블둝 전체 동기화와 μŠ€λ ˆλ“œ 쑰율
  • 단일 블둝 λ‚΄ 크둜슀 μ›Œν”„ 톡신
  • λ³΅μž‘ν•œ νŒ¨ν„΄μ—μ„œ κ°„λ‹¨ν•œ νŒ¨ν„΄μœΌλ‘œμ˜ μ„±λŠ₯ λ³€ν™˜
  • μŠ€λ ˆλ“œ 0 κ²°κ³Ό 관리와 쑰건뢀 μ“°κΈ°

μˆ˜ν•™μ  연산은 λ‚΄μ μž…λ‹ˆλ‹€: \[\Large \text{output}[0] = \sum_{i=0}^{N-1} a[i] \times b[i]\]

ν•˜μ§€λ§Œ κ΅¬ν˜„ κ³Όμ •μ—μ„œ Mojo의 λͺ¨λ“  블둝 레벨 GPU ν”„λ‘œκ·Έλž˜λ°μ— μ μš©λ˜λŠ” κΈ°λ³Έ νŒ¨ν„΄μ„ λ°°μ›λ‹ˆλ‹€.

ꡬ성

  • 벑터 크기: SIZE = 128 μš”μ†Œ
  • 데이터 νƒ€μž…: DType.float32
  • 블둝 ꡬ성: (128, 1) 블둝당 μŠ€λ ˆλ“œ 수 (TPB = 128)
  • κ·Έλ¦¬λ“œ ꡬ성: (1, 1) κ·Έλ¦¬λ“œλ‹Ή 블둝 수
  • λ ˆμ΄μ•„μ›ƒ: Layout.row_major(SIZE) (1D row-major)
  • 블둝당 μ›Œν”„ 수: 128 / WARP_SIZE (NVIDIAμ—μ„œ 4개, AMDμ—μ„œ 2개 λ˜λŠ” 4개)

κΈ°μ‘΄ λ°©μ‹μ˜ λ³΅μž‘μ„± (Puzzle 12μ—μ„œ)

Puzzle 12의 λ³΅μž‘ν•œ 방식을 λ– μ˜¬λ € λ΄…μ‹œλ‹€. 곡유 λ©”λͺ¨λ¦¬, 배리어, 트리 λ¦¬λ•μ…˜μ΄ ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€:

fn traditional_dot_product[
    in_layout: Layout, out_layout: Layout, tpb: Int
](
    output: LayoutTensor[dtype, out_layout, MutAnyOrigin],
    a: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
    b: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
    size: Int,
):
    """Traditional dot product using shared memory + barriers + tree reduction.
    Educational but complex - shows the manual coordination needed."""

    shared = LayoutTensor[
        dtype,
        Layout.row_major(tpb),
        MutAnyOrigin,
        address_space = AddressSpace.SHARED,
    ].stack_allocation()
    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)
    local_i = Int(thread_idx.x)

    # Each thread computes partial product
    if global_i < size:
        a_val = rebind[Scalar[dtype]](a[global_i])
        b_val = rebind[Scalar[dtype]](b[global_i])
        shared[local_i] = a_val * b_val

    barrier()

    # Tree reduction in shared memory - complex but educational
    var stride = tpb // 2
    while stride > 0:
        if local_i < stride:
            shared[local_i] += shared[local_i + stride]
        barrier()
        stride //= 2

    # Only thread 0 writes final result
    if local_i == 0:
        output[0] = shared[0]


이 방식이 λ³΅μž‘ν•œ 이유:

  • 곡유 λ©”λͺ¨λ¦¬ ν• λ‹Ή: 블둝 λ‚΄μ—μ„œ μˆ˜λ™μœΌλ‘œ λ©”λͺ¨λ¦¬λ₯Ό 관리
  • λͺ…μ‹œμ  배리어: 블둝 λ‚΄ λͺ¨λ“  μŠ€λ ˆλ“œλ₯Ό λ™κΈ°ν™”ν•˜κΈ° μœ„ν•œ barrier() 호좜
  • 트리 λ¦¬λ•μ…˜: μŠ€νŠΈλΌμ΄λ“œ 기반 인덱싱을 μ‚¬μš©ν•˜λŠ” λ³΅μž‘ν•œ 루프 (64β†’32β†’16β†’8β†’4β†’2β†’1)
  • 크둜슀 μ›Œν”„ 쑰율: μ—¬λŸ¬ μ›Œν”„ κ°„ 동기화가 ν•„μš”
  • 쑰건뢀 μ“°κΈ°: μŠ€λ ˆλ“œ 0만 μ΅œμ’… κ²°κ³Όλ₯Ό 기둝

이 방식은 전체 블둝(GPU에 따라 2개 λ˜λŠ” 4개 μ›Œν”„μ— 걸친 128 μŠ€λ ˆλ“œ)μ—μ„œ λ™μž‘ν•˜μ§€λ§Œ, μ½”λ“œκ°€ μž₯ν™©ν•˜κ³  였λ₯˜κ°€ λ°œμƒν•˜κΈ° μ‰¬μš°λ©° 블둝 레벨 GPU 동기화에 λŒ€ν•œ κΉŠμ€ 이해가 ν•„μš”ν•©λ‹ˆλ‹€.

μ›Œν”„ 레벨 κ°œμ„  (Puzzle 24μ—μ„œ)

블둝 레벨 μ—°μ‚°μœΌλ‘œ λ„˜μ–΄κ°€κΈ° 전에, Puzzle 24μ—μ„œ warp.sum()을 μ‚¬μš©ν•˜μ—¬ 단일 μ›Œν”„ λ‚΄ λ¦¬λ•μ…˜μ„ μ–΄λ–»κ²Œ λ‹¨μˆœν™”ν–ˆλŠ”μ§€ λ– μ˜¬λ € λ΄…μ‹œλ‹€:

fn simple_warp_dot_product[
    in_layout: Layout, out_layout: Layout, size: Int
](
    output: LayoutTensor[dtype, out_layout, MutAnyOrigin],
    a: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
    b: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
):
    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)

    # Each thread computes one partial product using vectorized approach as values in Mojo are SIMD based
    var partial_product: Scalar[dtype] = 0
    if global_i < size:
        partial_product = (a[global_i] * b[global_i]).reduce_add()

    # warp_sum() replaces all the shared memory + barriers + tree reduction
    total = warp_sum(partial_product)

    # Only lane 0 writes the result (all lanes have the same total)
    if lane_id() == 0:
        output[global_i // WARP_SIZE] = total


warp.sum()이 λ‹¬μ„±ν•œ 것:

  • 단일 μ›Œν”„ λ²”μœ„: 32 μŠ€λ ˆλ“œ(NVIDIA) λ˜λŠ” 32/64 μŠ€λ ˆλ“œ(AMD) λ‚΄μ—μ„œ λ™μž‘
  • ν•˜λ“œμ›¨μ–΄ μ…”ν”Œ: 효율적인 shfl.sync.bfly.b32 λͺ…λ Ή μ‚¬μš©
  • 곡유 λ©”λͺ¨λ¦¬ λΆˆν•„μš”: λͺ…μ‹œμ  λ©”λͺ¨λ¦¬ 관리 μ—†μŒ
  • ν•œ 쀄 λ¦¬λ•μ…˜: total = warp_sum[warp_size=WARP_SIZE](val=partial_product)

κ·ΈλŸ¬λ‚˜ ν•œκ³„κ°€ μžˆμŠ΅λ‹ˆλ‹€: warp.sum()은 단일 μ›Œν”„ λ‚΄μ—μ„œλ§Œ λ™μž‘ν•©λ‹ˆλ‹€. μ—¬λŸ¬ μ›Œν”„κ°€ ν•„μš”ν•œ 문제(예: 128 μŠ€λ ˆλ“œ 블둝)μ—μ„œλŠ” μ—¬μ „νžˆ μ›Œν”„ κ°„ μ‘°μœ¨μ„ μœ„ν•΄ λ³΅μž‘ν•œ 곡유 λ©”λͺ¨λ¦¬ + 배리어 방식이 ν•„μš”ν•©λ‹ˆλ‹€.

κΈ°μ‘΄ 방식 ν…ŒμŠ€νŠΈ:

pixi run p27 --traditional-dot-product
pixi run -e amd p27 --traditional-dot-product
pixi run -e apple p27 --traditional-dot-product
uv run poe p27 --traditional-dot-product

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

block.sum() 방식

λ³΅μž‘ν•œ κΈ°μ‘΄ 방식을 block.sum()을 μ‚¬μš©ν•˜λŠ” κ°„λ‹¨ν•œ 블둝 μ»€λ„λ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€:

comptime SIZE = 128
comptime TPB = 128
comptime NUM_BINS = 8
comptime in_layout = Layout.row_major(SIZE)
comptime out_layout = Layout.row_major(1)
comptime dtype = DType.float32


fn block_sum_dot_product[
    in_layout: Layout, out_layout: Layout, tpb: Int
](
    output: LayoutTensor[dtype, out_layout, MutAnyOrigin],
    a: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
    b: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
    size: Int,
):
    """Dot product using block.sum() - convenience function like warp.sum()!
    Replaces manual shared memory + barriers + tree reduction with one line."""

    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)
    local_i = thread_idx.x

    # FILL IN (roughly 6 lines)


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

pixi run p27 --block-sum-dot-product
pixi run -e amd p27 --block-sum-dot-product
uv run poe p27 --block-sum-dot-product

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

SIZE: 128
TPB: 128
Expected result: 1381760.0
Block.sum result: 1381760.0
Block.sum() gives identical results!
Compare the code: 15+ lines of barriers β†’ 1 line of block.sum()!
Just like warp.sum() but for the entire block
팁

1. μ„Έ 단계 νŒ¨ν„΄ μ΄ν•΄ν•˜κΈ°

λͺ¨λ“  블둝 λ¦¬λ•μ…˜μ€ λ™μΌν•œ κ°œλ…μ  νŒ¨ν„΄μ„ λ”°λ¦…λ‹ˆλ‹€:

  1. 각 μŠ€λ ˆλ“œκ°€ μžμ‹ μ˜ 둜컬 기여뢄을 계산
  2. λͺ¨λ“  μŠ€λ ˆλ“œκ°€ 블둝 전체 λ¦¬λ•μ…˜μ— μ°Έμ—¬
  3. μ§€μ •λœ ν•˜λ‚˜μ˜ μŠ€λ ˆλ“œκ°€ μ΅œμ’… κ²°κ³Όλ₯Ό 처리

2. 내적 μˆ˜ν•™ κΈ°μ–΅ν•˜κΈ°

각 μŠ€λ ˆλ“œλŠ” 벑터 a와 bμ—μ„œ ν•˜λ‚˜μ˜ μš”μ†Œ μŒμ„ μ²˜λ¦¬ν•΄μ•Ό ν•©λ‹ˆλ‹€. 이듀을 μŠ€λ ˆλ“œ 간에 ν•©μ‚°ν•  수 μžˆλŠ” β€œλΆ€λΆ„ κ²°κ³Όβ€œλ‘œ ν•©μΉ˜λŠ” 연산은 λ¬΄μ—‡μΌκΉŒμš”?

3. LayoutTensor 인덱싱 νŒ¨ν„΄

LayoutTensor μš”μ†Œμ— μ ‘κ·Όν•  λ•Œ, 인덱싱이 SIMD 값을 λ°˜ν™˜ν•œλ‹€λŠ” 점을 κΈ°μ–΅ν•˜μ„Έμš”. μ‚°μˆ  연산을 μœ„ν•΄ 슀칼라 값을 μΆ”μΆœν•΄μ•Ό ν•©λ‹ˆλ‹€.

4. block.sum() API κ°œλ…

ν•¨μˆ˜ μ‹œκ·Έλ‹ˆμ²˜λ₯Ό μ‚΄νŽ΄λ³΄μ„Έμš” - λ‹€μŒμ΄ ν•„μš”ν•©λ‹ˆλ‹€:

  • 블둝 크기λ₯Ό μ§€μ •ν•˜λŠ” ν…œν”Œλ¦Ώ νŒŒλΌλ―Έν„°
  • κ²°κ³Ό λΆ„λ°° 방식을 μœ„ν•œ ν…œν”Œλ¦Ώ νŒŒλΌλ―Έν„° (broadcast)
  • λ¦¬λ“€μŠ€ν•  값을 담은 λŸ°νƒ€μž„ νŒŒλΌλ―Έν„°

5. μŠ€λ ˆλ“œ 쑰율 원칙

  • μ–΄λ–€ μŠ€λ ˆλ“œκ°€ μ²˜λ¦¬ν•  μœ νš¨ν•œ 데이터λ₯Ό κ°€μ§€κ³  μžˆμ„κΉŒμš”? (힌트: 경계 검사)
  • μ–΄λ–€ μŠ€λ ˆλ“œκ°€ μ΅œμ’… κ²°κ³Όλ₯Ό 기둝해야 ν• κΉŒμš”? (힌트: μΌκ΄€λœ 선택)
  • κ·Έ νŠΉμ • μŠ€λ ˆλ“œλ₯Ό μ–΄λ–»κ²Œ μ‹λ³„ν• κΉŒμš”? (힌트: μŠ€λ ˆλ“œ 인덱싱)

μ†”λ£¨μ…˜

fn block_sum_dot_product[
    in_layout: Layout, out_layout: Layout, tpb: Int
](
    output: LayoutTensor[dtype, out_layout, MutAnyOrigin],
    a: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
    b: LayoutTensor[dtype, in_layout, ImmutAnyOrigin],
    size: Int,
):
    """Dot product using block.sum() - convenience function like warp.sum()!
    Replaces manual shared memory + barriers + tree reduction with one line."""

    global_i = Int(block_dim.x * block_idx.x + thread_idx.x)
    local_i = thread_idx.x

    # Each thread computes partial product
    var partial_product: Scalar[dtype] = 0.0
    if global_i < size:
        # LayoutTensor indexing `[0]` returns the underlying SIMD value
        partial_product = a[global_i][0] * b[global_i][0]

    # The magic: block.sum() replaces 15+ lines of manual reduction!
    # Just like warp.sum() but for the entire block
    total = block.sum[block_size=tpb, broadcast=False](
        val=SIMD[DType.float32, 1](partial_product)
    )

    # Only thread 0 writes the result
    if local_i == 0:
        output[0] = total[0]


block.sum() 컀널은 λ³΅μž‘ν•œ 블둝 λ™κΈ°ν™”μ—μ„œ μ •κ΅ν•˜κ²Œ μ΅œμ ν™”λœ κ΅¬ν˜„μœΌλ‘œμ˜ 근본적인 λ³€ν™˜μ„ λ³΄μ—¬μ€λ‹ˆλ‹€:

κΈ°μ‘΄ λ°©μ‹μ—μ„œ 사라진 것듀:

  • 15쀄 이상 β†’ 8쀄: 획기적인 μ½”λ“œ μΆ•μ†Œ
  • 곡유 λ©”λͺ¨λ¦¬ ν• λ‹Ή: λ©”λͺ¨λ¦¬ 관리 λΆˆν•„μš”
  • 7회 μ΄μƒμ˜ barrier() 호좜: λͺ…μ‹œμ  동기화 제둜
  • λ³΅μž‘ν•œ 트리 λ¦¬λ•μ…˜: 단일 ν•¨μˆ˜ 호좜둜 λŒ€μ²΄
  • μŠ€νŠΈλΌμ΄λ“œ 기반 인덱싱: μ™„μ „νžˆ 제거
  • 크둜슀 μ›Œν”„ 쑰율: μ΅œμ ν™”λœ κ΅¬ν˜„μ΄ μžλ™μœΌλ‘œ 처리

블둝 전체 μ‹€ν–‰ λͺ¨λΈ:

블둝 μŠ€λ ˆλ“œ (128 μŠ€λ ˆλ“œ, 4개 μ›Œν”„):
μ›Œν”„ 0 (μŠ€λ ˆλ“œ 0-31):
  μŠ€λ ˆλ“œ 0: partial_product = a[0] * b[0] = 0.0
  μŠ€λ ˆλ“œ 1: partial_product = a[1] * b[1] = 2.0
  ...
  μŠ€λ ˆλ“œ 31: partial_product = a[31] * b[31] = 1922.0

μ›Œν”„ 1 (μŠ€λ ˆλ“œ 32-63):
  μŠ€λ ˆλ“œ 32: partial_product = a[32] * b[32] = 2048.0
  ...

μ›Œν”„ 2 (μŠ€λ ˆλ“œ 64-95):
  μŠ€λ ˆλ“œ 64: partial_product = a[64] * b[64] = 8192.0
  ...

μ›Œν”„ 3 (μŠ€λ ˆλ“œ 96-127):
  μŠ€λ ˆλ“œ 96: partial_product = a[96] * b[96] = 18432.0
  μŠ€λ ˆλ“œ 127: partial_product = a[127] * b[127] = 32258.0

block.sum() ν•˜λ“œμ›¨μ–΄ μ—°μ‚°:
λͺ¨λ“  μŠ€λ ˆλ“œ β†’ 0.0 + 2.0 + 1922.0 + 2048.0 + ... + 32258.0 = 1381760.0
μŠ€λ ˆλ“œ 0이 μˆ˜μ‹  β†’ total = 1381760.0 (broadcast=False일 λ•Œ)

배리어 없이 λ™μž‘ν•˜λŠ” 이유:

  1. 블둝 전체 μ‹€ν–‰: λͺ¨λ“  μŠ€λ ˆλ“œκ°€ μ›Œν”„ λ‚΄μ—μ„œ λ‘μŠ€ν…μœΌλ‘œ 각 λͺ…령을 μ‹€ν–‰
  2. λ‚΄μž₯ 동기화: block.sum() κ΅¬ν˜„μ΄ 동기화λ₯Ό λ‚΄λΆ€μ μœΌλ‘œ 처리
  3. 크둜슀 μ›Œν”„ 톡신: 블둝 λ‚΄ μ›Œν”„ κ°„ μ΅œμ ν™”λœ 톡신
  4. 쑰율된 κ²°κ³Ό 전달: μŠ€λ ˆλ“œ 0만 μ΅œμ’… κ²°κ³Όλ₯Ό μˆ˜μ‹ 

warp.sum() (Puzzle 24)과의 비ꡐ:

  • μ›Œν”„ λ²”μœ„: warp.sum()은 32/64 μŠ€λ ˆλ“œ(단일 μ›Œν”„) λ‚΄μ—μ„œ λ™μž‘
  • 블둝 λ²”μœ„: block.sum()은 전체 블둝(μ—¬λŸ¬ μ›Œν”„)에 걸쳐 λ™μž‘
  • λ™μΌν•œ λ‹¨μˆœν•¨: λ‘˜ λ‹€ λ³΅μž‘ν•œ μˆ˜λ™ λ¦¬λ•μ…˜μ„ ν•œ 쀄 호좜둜 λŒ€μ²΄
  • μžλ™ 쑰율: block.sum()은 warp.sum()이 μ²˜λ¦¬ν•  수 μ—†λŠ” 크둜슀 μ›Œν”„ 배리어λ₯Ό μžλ™μœΌλ‘œ 처리

기술 뢄석: block.sum()은 μ‹€μ œλ‘œ λ¬΄μ—‡μœΌλ‘œ 컴파일될까?

block.sum()이 μ‹€μ œλ‘œ 무엇을 μƒμ„±ν•˜λŠ”μ§€ μ΄ν•΄ν•˜κΈ° μœ„ν•΄, 디버그 정보와 ν•¨κ»˜ 퍼즐을 μ»΄νŒŒμΌν–ˆμŠ΅λ‹ˆλ‹€:

pixi run mojo build --emit llvm --debug-level=line-tables solutions/p27/p27.mojo -o solutions/p27/p27.ll

μ΄λ ‡κ²Œ μƒμ„±λœ LLVM 파일 solutions/p27/p27.llμ—λŠ”, ν˜Έν™˜ NVIDIA GPUμ—μ„œ μ‹€μ œ GPU λͺ…령을 λ³΄μ—¬μ£ΌλŠ” PTX μ–΄μ…ˆλΈ”λ¦¬κ°€ λ‚΄μž₯λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€:

발견 1: 단일 λͺ…령이 μ•„λ‹ˆλ‹€

block.sum()은 μ•½ 20개 μ΄μƒμ˜ PTX λͺ…λ ΉμœΌλ‘œ 컴파일되며, 2단계 λ¦¬λ•μ…˜μœΌλ‘œ κ΅¬μ„±λ©λ‹ˆλ‹€:

1단계: μ›Œν”„ 레벨 λ¦¬λ•μ…˜ (λ²„ν„°ν”ŒλΌμ΄ μ…”ν”Œ)

shfl.sync.bfly.b32 %r23, %r46, 16, 31, -1;   // μ˜€ν”„μ…‹ 16으둜 μ…”ν”Œ
add.f32            %r24, %r46, %r23;         // μ…”ν”Œλœ 값을 ν•©μ‚°
shfl.sync.bfly.b32 %r25, %r24, 8, 31, -1;    // μ˜€ν”„μ…‹ 8둜 μ…”ν”Œ
add.f32            %r26, %r24, %r25;         // μ…”ν”Œλœ 값을 ν•©μ‚°
// ... μ˜€ν”„μ…‹ 4, 2, 1에 λŒ€ν•΄ 계속

2단계: 크둜슀 μ›Œν”„ 쑰율

shr.u32            %r32, %r1, 5;             // μ›Œν”„ IDλ₯Ό 계산
mov.b32            %r34, _global_alloc_$__gpu_shared_mem; // 곡유 λ©”λͺ¨λ¦¬
bar.sync           0;                        // 배리어 동기화
// ... 크둜슀 μ›Œν”„ λ¦¬λ•μ…˜μ„ μœ„ν•œ 또 λ‹€λ₯Έ λ²„ν„°ν”ŒλΌμ΄ μ…”ν”Œ μ‹œν€€μŠ€

발견 2: ν•˜λ“œμ›¨μ–΄ μ΅œμ ν™” κ΅¬ν˜„

  • λ²„ν„°ν”ŒλΌμ΄ μ…”ν”Œ: 트리 λ¦¬λ•μ…˜λ³΄λ‹€ 효율적
  • μžλ™ 배리어 배치: 크둜슀 μ›Œν”„ 동기화λ₯Ό μžλ™μœΌλ‘œ 처리
  • μ΅œμ ν™”λœ λ©”λͺ¨λ¦¬ μ ‘κ·Ό: 곡유 λ©”λͺ¨λ¦¬λ₯Ό μ „λž΅μ μœΌλ‘œ μ‚¬μš©
  • μ•„ν‚€ν…μ²˜ 인식: λ™μΌν•œ APIκ°€ NVIDIA(32 μŠ€λ ˆλ“œ μ›Œν”„)와 AMD(32 λ˜λŠ” 64 μŠ€λ ˆλ“œ μ›Œν”„)μ—μ„œ λ™μž‘

발견 3: μ•Œκ³ λ¦¬μ¦˜ λ³΅μž‘λ„ 뢄석

뢄석 μ ‘κ·Ό 방식:

  1. λ°”μ΄λ„ˆλ¦¬ ELF μ„Ήμ…˜(.nv_debug_ptx_txt)μ—μ„œ PTX μ–΄μ…ˆλΈ”λ¦¬λ₯Ό 확인
  2. κ°œλ³„ λͺ…λ Ή 수λ₯Ό 세기보닀 μ•Œκ³ λ¦¬μ¦˜μ  차이λ₯Ό 식별

κ΄€μ°°λœ μ£Όμš” μ•Œκ³ λ¦¬μ¦˜ 차이:

  • κΈ°μ‘΄ 방식: 곡유 λ©”λͺ¨λ¦¬λ₯Ό μ‚¬μš©ν•œ 트리 λ¦¬λ•μ…˜ + λ‹€μˆ˜μ˜ bar.sync 호좜
  • block.sum(): λ²„ν„°ν”ŒλΌμ΄ μ…”ν”Œ νŒ¨ν„΄ + μ΅œμ ν™”λœ 크둜슀 μ›Œν”„ 쑰율

μ„±λŠ₯ 이점은 λͺ…λ Ή μˆ˜λ‚˜ λ§ˆλ²• 같은 ν•˜λ“œμ›¨μ–΄κ°€ μ•„λ‹ˆλΌ μ •κ΅ν•˜κ²Œ μ΅œμ ν™”λœ μ•Œκ³ λ¦¬μ¦˜ 선택(λ²„ν„°ν”ŒλΌμ΄ > 트리)μ—μ„œ λΉ„λ‘―λ©λ‹ˆλ‹€. κ΅¬ν˜„μ— λŒ€ν•œ μžμ„Έν•œ λ‚΄μš©μ€ Mojo gpu λͺ¨λ“ˆμ˜ block.mojoλ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

μ„±λŠ₯ μΈμ‚¬μ΄νŠΈ

block.sum() vs κΈ°μ‘΄ 방식:

  • μ½”λ“œ λ‹¨μˆœν•¨: λ¦¬λ•μ…˜ 뢀뢄이 15쀄 이상 β†’ 1μ€„λ‘œ
  • λ©”λͺ¨λ¦¬ μ‚¬μš©: 곡유 λ©”λͺ¨λ¦¬ ν• λ‹Ή λΆˆν•„μš”
  • 동기화: λͺ…μ‹œμ  배리어 λΆˆν•„μš”
  • ν™•μž₯μ„±: ν•˜λ“œμ›¨μ–΄ ν•œλ„ λ‚΄μ—μ„œ λͺ¨λ“  블둝 크기에 λ™μž‘

block.sum() vs warp.sum():

  • λ²”μœ„: 블둝 전체(128 μŠ€λ ˆλ“œ) vs μ›Œν”„ 전체(32 μŠ€λ ˆλ“œ)
  • μš©λ„: 전체 블둝에 걸친 λ¦¬λ•μ…˜μ΄ ν•„μš”ν•  λ•Œ
  • νŽΈμ˜μ„±: λ™μΌν•œ ν”„λ‘œκ·Έλž˜λ° λͺ¨λΈ, λ‹€λ₯Έ 규λͺ¨

block.sum()을 μ‚¬μš©ν•΄μ•Ό ν•  λ•Œ:

  • 단일 블둝 문제: λͺ¨λ“  데이터가 ν•˜λ‚˜μ˜ 블둝에 λ“€μ–΄κ°ˆ λ•Œ
  • 블둝 레벨 μ•Œκ³ λ¦¬μ¦˜: λ¦¬λ•μ…˜μ΄ ν•„μš”ν•œ 곡유 λ©”λͺ¨λ¦¬ μ—°μ‚°
  • ν™•μž₯성보닀 νŽΈμ˜μ„±: λ©€ν‹° 블둝 방식보닀 λ‹¨μˆœ

이전 퍼즐과의 관계

Puzzle 12 (κΈ°μ‘΄ 방식)μ—μ„œ:

λ³΅μž‘ν•¨: 곡유 λ©”λͺ¨λ¦¬ + 배리어 + 트리 λ¦¬λ•μ…˜
↓
λ‹¨μˆœν•¨: block.sum() ν•˜λ“œμ›¨μ–΄ κΈ°λ³Έ μš”μ†Œ

Puzzle 24 (warp.sum())μ—μ„œ:

μ›Œν”„ 레벨: warp.sum() - 32 μŠ€λ ˆλ“œ (단일 μ›Œν”„)
↓
블둝 레벨: block.sum() - 128 μŠ€λ ˆλ“œ (μ—¬λŸ¬ μ›Œν”„)

3단계 μ§„ν–‰:

  1. μˆ˜λ™ λ¦¬λ•μ…˜ (Puzzle 12): λ³΅μž‘ν•œ 곡유 λ©”λͺ¨λ¦¬ + 배리어 + 트리 λ¦¬λ•μ…˜
  2. μ›Œν”„ κΈ°λ³Έ μš”μ†Œ (Puzzle 24): warp.sum() - λ‹¨μˆœν•˜μ§€λ§Œ 단일 μ›Œν”„λ‘œ μ œν•œ
  3. 블둝 κΈ°λ³Έ μš”μ†Œ (Puzzle 27): block.sum() - μ›Œν”„μ˜ λ‹¨μˆœν•¨μ„ μ—¬λŸ¬ μ›Œν”„λ‘œ ν™•μž₯

핡심 톡찰: block.sum()은 warp.sum()의 λ‹¨μˆœν•¨μ„ μ œκ³΅ν•˜λ©΄μ„œ 전체 λΈ”λ‘μœΌλ‘œ ν™•μž₯λ©λ‹ˆλ‹€. μˆ˜λ™μœΌλ‘œ κ΅¬ν˜„ν•΄μ•Ό ν–ˆλ˜ λ³΅μž‘ν•œ 크둜슀 μ›Œν”„ μ‘°μœ¨μ„ μžλ™μœΌλ‘œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.

λ‹€μŒ 단계

block.sum() 연산을 λ°°μ› μœΌλ‹ˆ, λ‹€μŒμœΌλ‘œ μ§„ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

πŸ’‘ 핡심 μš”μ : 블둝 연산은 μ›Œν”„ ν”„λ‘œκ·Έλž˜λ° κ°œλ…μ„ 전체 μŠ€λ ˆλ“œ λΈ”λ‘μœΌλ‘œ ν™•μž₯ν•˜μ—¬, μ—¬λŸ¬ μ›Œν”„μ— 걸쳐 λ™μ‹œμ— λ™μž‘ν•˜λ©΄μ„œ λ³΅μž‘ν•œ 동기화 νŒ¨ν„΄μ„ λŒ€μ²΄ν•˜λŠ” μ΅œμ ν™”λœ κΈ°λ³Έ μš”μ†Œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. warp.sum()이 μ›Œν”„ 레벨 λ¦¬λ•μ…˜μ„ λ‹¨μˆœν™”ν•œ κ²ƒμ²˜λŸΌ, block.sum()은 μ„±λŠ₯을 ν¬μƒν•˜μ§€ μ•Šκ³  블둝 레벨 λ¦¬λ•μ…˜μ„ λ‹¨μˆœν™”ν•©λ‹ˆλ‹€.