Puzzle 3: κ°€λ“œ

κ°œμš”

벑터 a의 각 μœ„μΉ˜μ— 10을 더해 output에 μ €μž₯ν•˜λŠ” 컀널을 κ΅¬ν˜„ν•΄ λ³΄μ„Έμš”.

μ°Έκ³ : μŠ€λ ˆλ“œ μˆ˜κ°€ 데이터 κ°œμˆ˜λ³΄λ‹€ λ§Žμ•„μ„œ, 일뢀 μŠ€λ ˆλ“œλŠ” μ²˜λ¦¬ν•  데이터가 μ—†μŠ΅λ‹ˆλ‹€. 이런 μŠ€λ ˆλ“œκ°€ λ²”μœ„λ₯Ό λ²—μ–΄λ‚œ λ©”λͺ¨λ¦¬μ— μ ‘κ·Όν•˜μ§€ μ•Šλ„λ‘ λ°©μ§€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Guard Guard

핡심 κ°œλ…

이 νΌμ¦μ—μ„œ λ‹€λ£¨λŠ” λ‚΄μš©:

  • μŠ€λ ˆλ“œ μˆ˜μ™€ 데이터 크기 뢈일치 처리
  • λ²”μœ„λ₯Ό λ²—μ–΄λ‚œ λ©”λͺ¨λ¦¬ μ ‘κ·Ό λ°©μ§€
  • GPU μ»€λ„μ—μ„œ 쑰건뢀 μ‹€ν–‰ μ‚¬μš©
  • μ•ˆμ „ν•œ λ©”λͺ¨λ¦¬ μ ‘κ·Ό νŒ¨ν„΄

μˆ˜ν•™μ  ν‘œν˜„

각 μŠ€λ ˆλ“œ \(i\)에 λŒ€ν•΄: \[\Large \text{if}\ i < \text{size}: output[i] = a[i] + 10\]

λ©”λͺ¨λ¦¬ μ•ˆμ „ νŒ¨ν„΄

Thread 0 (i=0):  if 0 < size:  output[0] = a[0] + 10  βœ“ Valid
Thread 1 (i=1):  if 1 < size:  output[1] = a[1] + 10  βœ“ Valid
Thread 2 (i=2):  if 2 < size:  output[2] = a[2] + 10  βœ“ Valid
Thread 3 (i=3):  if 3 < size:  output[3] = a[3] + 10  βœ“ Valid
Thread 4 (i=4):  if 4 < size:  ❌ Skip (out of bounds)
Thread 5 (i=5):  if 5 < size:  ❌ Skip (out of bounds)

πŸ’‘ μ°Έκ³ : λ‹€μŒ μƒν™©μ—μ„œ 경계(boundary) κ²€μ‚¬λŠ” 점점 λ³΅μž‘ν•΄μ§‘λ‹ˆλ‹€:

  • 닀차원 λ°°μ—΄
  • λ‹€μ–‘ν•œ λ°°μ—΄ ν˜•νƒœ
  • λ³΅μž‘ν•œ μ ‘κ·Ό νŒ¨ν„΄

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

comptime SIZE = 4
comptime BLOCKS_PER_GRID = 1
comptime THREADS_PER_BLOCK = 8
comptime dtype = DType.float32


fn add_10_guard(
    output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
    a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
    size: UInt,
):
    i = thread_idx.x
    # FILL ME IN (roughly 2 lines)


전체 μ½”λ“œ 보기: problems/p03/p03.mojo

팁
  1. thread_idx.xλ₯Ό i에 μ €μž₯ν•©λ‹ˆλ‹€
  2. κ°€λ“œ μΆ”κ°€: if i < size
  3. κ°€λ“œ λ‚΄λΆ€: output[i] = a[i] + 10.0

μ½”λ“œ μ‹€ν–‰

μ†”λ£¨μ…˜μ„ ν…ŒμŠ€νŠΈν•˜λ €λ©΄ ν„°λ―Έλ„μ—μ„œ λ‹€μŒ λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰ν•˜μ„Έμš”:

pixi run p03
pixi run -e amd p03
pixi run -e apple p03
uv run poe p03

퍼즐을 아직 ν’€μ§€ μ•Šμ•˜λ‹€λ©΄ 좜λ ₯이 λ‹€μŒκ³Ό 같이 λ‚˜νƒ€λ‚©λ‹ˆλ‹€:

out: HostBuffer([0.0, 0.0, 0.0, 0.0])
expected: HostBuffer([10.0, 11.0, 12.0, 13.0])

μ†”λ£¨μ…˜

fn add_10_guard(
    output: UnsafePointer[Scalar[dtype], MutAnyOrigin],
    a: UnsafePointer[Scalar[dtype], MutAnyOrigin],
    size: UInt,
):
    i = thread_idx.x
    if i < size:
        output[i] = a[i] + 10.0


이 μ†”λ£¨μ…˜μ€:

  • i = thread_idx.x둜 μŠ€λ ˆλ“œ 인덱슀λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€
  • if i < size둜 λ²”μœ„λ₯Ό λ²—μ–΄λ‚œ 접근을 λ°©μ§€ν•©λ‹ˆλ‹€
  • κ°€λ“œ λ‚΄λΆ€: μž…λ ₯값에 10을 λ”ν•©λ‹ˆλ‹€

경계 검사 없이도 ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όλ˜λŠ” μ΄μœ κ°€ κΆκΈˆν•  수 μžˆμŠ΅λ‹ˆλ‹€! ν…ŒμŠ€νŠΈ 톡과가 μ½”λ“œμ˜ μ•ˆμ „μ„±μ΄λ‚˜ λ―Έμ •μ˜ λ™μž‘(Undefined Behavior) λΆ€μž¬λ₯Ό 보μž₯ν•˜μ§€λŠ” μ•ŠλŠ”λ‹€λŠ” 점을 항상 κΈ°μ–΅ν•˜μ„Έμš”. Puzzle 10μ—μ„œ 이런 경우λ₯Ό μ‚΄νŽ΄λ³΄κ³ , μ•ˆμ „μ„± 버그λ₯Ό μž‘λŠ” 도ꡬλ₯Ό μ‚¬μš©ν•΄ λ΄…λ‹ˆλ‹€.

μ•žμœΌλ‘œ λ‹€λ£° λ‚΄μš©

κ°„λ‹¨ν•œ 경계 κ²€μ‚¬λŠ” μ—¬κΈ°μ„œ 잘 μž‘λ™ν•˜μ§€λ§Œ, λ‹€μŒ 상황을 생각해 λ³΄μ„Έμš”:

  • 2D/3D λ°°μ—΄μ˜ κ²½κ³„λŠ” μ–΄λ–»κ²Œ μ²˜λ¦¬ν• κΉŒ?
  • λ‹€μ–‘ν•œ ν˜•νƒœλ₯Ό 효율적으둜 μ²˜λ¦¬ν•˜λ €λ©΄?
  • νŒ¨λ”©μ΄λ‚˜ κ°€μž₯자리 μ²˜λ¦¬κ°€ ν•„μš”ν•˜λ‹€λ©΄?

λ³΅μž‘λ„κ°€ μ¦κ°€ν•˜λŠ” μ˜ˆμ‹œ:

# ν˜„μž¬: 1D 경계 검사
if i < size: ...

# κ³§ λ‹€λ£° λ‚΄μš©: 2D 경계 검사
if i < height and j < width: ...

# 이후: νŒ¨λ”©μ΄ μžˆλŠ” 3D
if i < height and j < width and k < depth and
   i >= padding and j >= padding: ...

이런 경계 처리 νŒ¨ν„΄μ€ Puzzle 4의 LayoutTensor μ•Œμ•„λ³΄κΈ°μ—μ„œ 배우면 훨씬 κΉ”λ”ν•΄μ§‘λ‹ˆλ‹€. LayoutTensorλŠ” ν˜•νƒœ 관리 κΈ°λŠ₯을 기본으둜 μ œκ³΅ν•©λ‹ˆλ‹€.