GPU μ„±λŠ₯ μ΄ν•΄ν•˜κΈ°: 루프라인 λͺ¨λΈ

κΈ°λ³Έ ν–‰λ ¬ κ³±μ…ˆμ„ κ΅¬ν˜„ν–ˆμœΌλ‹ˆ, 이런 ꢁ금증이 생길 수 μžˆμŠ΅λ‹ˆλ‹€: 우리 컀널은 μ‹€μ œλ‘œ μ–Όλ§ˆλ‚˜ 잘 λ™μž‘ν•˜κ³  μžˆμ„κΉŒ? GPU의 μ—°μ‚° λŠ₯λ ₯에 μ˜ν•΄ μ œν•œλ˜λŠ” 걸까, μ•„λ‹ˆλ©΄ λ‹€λ₯Έ 무언가가 발λͺ©μ„ 작고 μžˆλŠ” 걸까?

루프라인 λͺ¨λΈ(μ—­μ£Ό: 루프라인은 β€œμƒν•œμ„ β€œμ΄λΌλŠ” 뜻으둜, μ„±λŠ₯이 λ„˜μ„ 수 μ—†λŠ” ν•œκ³„λ₯Ό μ§€λΆ• 선에 λΉ„μœ ν•œ μ΄λ¦„μž…λ‹ˆλ‹€)은 GPU μ΅œμ ν™”μ˜ λ‚˜μΉ¨λ°˜μž…λ‹ˆλ‹€. μ»€λ„μ˜ μ„±λŠ₯을 μ œν•œν•˜λŠ” ν•˜λ“œμ›¨μ–΄ 병λͺ©μ΄ 무엇인지 μ•Œλ €μ£Όκ³ , κ°€μž₯ 효과적인 μ΅œμ ν™” λ°©ν–₯으둜 μ•ˆλ‚΄ν•©λ‹ˆλ‹€. 감으둜 κ°œμ„ ν•˜λŠ” λŒ€μ‹ , 루프라인 λͺ¨λΈμ΄ μ •ν™•νžˆ 어디에 집쀑해야 ν•˜λŠ”μ§€ λ³΄μ—¬μ€λ‹ˆλ‹€.

1. λͺ¨λ“  GPU μ»€λ„μ˜ 두 κ°€μ§€ μ„±λŠ₯ μƒν•œ

λͺ¨λ“  GPU 컀널은 두 κ°€μ§€ 근본적인 μ œμ•½ μ•„λž˜μ—μ„œ λ™μž‘ν•©λ‹ˆλ‹€:

  • μ—°μ‚° μƒν•œ(compute ceiling) – μ½”μ–΄κ°€ λΆ€λ™μ†Œμˆ˜μ  연산을 μ–Όλ§ˆλ‚˜ λΉ λ₯΄κ²Œ μˆ˜ν–‰ν•  수 μžˆλŠ”κ°€ (μ΅œλŒ€ FLOPs/s)
  • λ©”λͺ¨λ¦¬ μƒν•œ(memory ceiling) – λ©”λͺ¨λ¦¬ μ‹œμŠ€ν…œμ΄ 코어에 데이터λ₯Ό μ–Όλ§ˆλ‚˜ λΉ λ₯΄κ²Œ 곡급할 수 μžˆλŠ”κ°€ (μ΅œλŒ€ bytes/s)

μ–΄λ–€ μƒν•œμ΄ 컀널을 μ œμ•½ν•˜λŠ”μ§€ νŒŒμ•…ν•˜λŠ” 것이 μ΅œμ ν™” μ „λž΅μ˜ ν•΅μ‹¬μž…λ‹ˆλ‹€. 루프라인 λͺ¨λΈμ€ 두 κ°€μ§€ 핡심 μ§€ν‘œλ₯Ό κ·Έλž˜ν”„λ‘œ ν‘œν˜„ν•˜μ—¬ 이 관계λ₯Ό μ‹œκ°ν™”ν•©λ‹ˆλ‹€:

XμΆ•: μ‚°μˆ  강도(Arithmetic Intensity) – 데이터 1λ°”μ΄νŠΈλ‹Ή μˆ˜ν–‰ν•˜λŠ” μ—°μ‚°λŸ‰

\[\Large I = \frac{\text{Total FLOPs}}{\text{Total Bytes from Memory}} \quad [\text{FLOP/B}]\]

YμΆ•: μ‹€μΈ‘ μ„±λŠ₯(Sustained Performance) – 컀널이 μ‹€μ œλ‘œ λ‹¬μ„±ν•˜λŠ” 속도

\[\Large P_{\text{sustained}} = \frac{\text{Total FLOPs}}{\text{Elapsed Time}} \quad [\text{GFLOP/s}]\]

두 개의 β€œμƒν•œ(roof)β€œμ΄ 달성 κ°€λŠ₯ν•œ μ„±λŠ₯의 μƒν•œμ„ μ •ν•©λ‹ˆλ‹€:

μƒν•œμˆ˜μ‹μ˜λ―Έ
λ©”λͺ¨λ¦¬ μƒν•œ\(P = B_{\text{peak}} \cdot I\)κΈ°μšΈμ–΄μ§„ 직선. λ©”λͺ¨λ¦¬ λŒ€μ—­ν­μ— μ˜ν•΄ μ„±λŠ₯이 μ œν•œλ¨
μ—°μ‚° μƒν•œ\(P = P_{\text{peak}}\)μˆ˜ν‰μ„ . μ—°μ‚° μ²˜λ¦¬λŸ‰μ— μ˜ν•΄ μ„±λŠ₯이 μ œν•œλ¨

μž„κ³„ 강도(critical intensity)

\[\Large I^* = \frac{P_{\text{peak}}}{B_{\text{peak}}}\]

λŠ” 컀널이 λ©”λͺ¨λ¦¬ λ°”μš΄λ“œ(\(I < I^\ast\) )μ—μ„œ μ—°μ‚° λ°”μš΄λ“œ(\(I > I^\ast\) )둜 μ „ν™˜λ˜λŠ” μ§€μ μž…λ‹ˆλ‹€.

2. ν•˜λ“œμ›¨μ–΄ μ˜ˆμ‹œ: NVIDIA A100 사양

이둠을 NVIDIA A100의 ꡬ체적인 숫자둜 확인해 λ³΄κ² μŠ΅λ‹ˆλ‹€:

μ΅œλŒ€ FP32 μ²˜λ¦¬λŸ‰ \[\Large P_{\text{peak}} = 19.5 \text{ TFLOP/s} = 19{,}500 \text{ GFLOP/s}\]

μ΅œλŒ€ HBM2 λŒ€μ—­ν­ \[\Large B_{\text{peak}} = 1{,}555 \text{ GB/s}\]

μž„κ³„ 강도 \[\Large I^* = \frac{19{,}500}{1{,}555} \approx 12.5 \text{ FLOP/B}\]

좜처: NVIDIA A100 Tensor Core GPU Architecture

μ΄λŠ” μ‚°μˆ  강도가 12.5 FLOP/B 미만인 컀널은 λ©”λͺ¨λ¦¬ λ°”μš΄λ“œ, κ·Έ 이상인 컀널은 μ—°μ‚° λ°”μš΄λ“œμž„μ„ λœ»ν•©λ‹ˆλ‹€.

3. ν–‰λ ¬ κ³±μ…ˆ κ΅¬ν˜„μ˜ μ‹œκ°ν™”

μ•„λž˜ μ• λ‹ˆλ©”μ΄μ…˜μ€ 이 퍼즐의 κ΅¬ν˜„λ“€μ΄ A100의 루프라인 λͺ¨λΈμ— μ–΄λ–»κ²Œ λŒ€μ‘ν•˜λŠ”μ§€ λ³΄μ—¬μ€λ‹ˆλ‹€:

루프라인 λͺ¨λΈ μ‹œκ°ν™”

이 μ‹œκ°ν™”λŠ” 이 νΌμ¦μ—μ„œ 거치게 될 μ΅œμ ν™” 과정을 λ³΄μ—¬μ€λ‹ˆλ‹€:

  1. ν•˜λ“œμ›¨μ–΄ μ œμ•½ – 빨간색 λ©”λͺ¨λ¦¬ μƒν•œκ³Ό νŒŒλž€μƒ‰ μ—°μ‚° μƒν•œμ΄ μ„±λŠ₯ ν•œκ³„λ₯Ό μ •μ˜
  2. 좜발점 – κΈ°λ³Έ κ΅¬ν˜„(주황색 점)이 λ©”λͺ¨λ¦¬ μƒν•œ μœ„μ— μœ„μΉ˜
  3. μ΅œμ ν™” λͺ©ν‘œ – 곡유 λ©”λͺ¨λ¦¬ 버전(청둝색 점)으둜 μ‚°μˆ  강도가 κ°œμ„ λ¨
  4. ꢁ극적 λͺ©ν‘œ – κΈˆμƒ‰ ν™”μ‚΄ν‘œλŠ” 컀널이 μ—°μ‚° λ°”μš΄λ“œκ°€ λ˜λŠ” μž„κ³„ 강도 지점을 가리킴

4. κΈ°λ³Έ κ΅¬ν˜„ 뢄석

이전 μ„Ήμ…˜μ˜ κΈ°λ³Έ 컀널이 μ™œ 이런 μ„±λŠ₯을 λ³΄μ΄λŠ”μ§€ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€. \(2 \times 2\) ν–‰λ ¬ κ³±μ…ˆμ˜ 경우:

좜λ ₯ μ›μ†Œλ‹Ή μ—°μ‚°λŸ‰: \(\text{SIZE} + (\text{SIZE}-1) = 3 \text{ FLOPs }\)

각 μ›μ†Œμ—λŠ” \(\text{SIZE}\) 회의 κ³±μ…ˆκ³Ό \(\text{SIZE} - 1\) 회의 λ§μ…ˆμ΄ ν•„μš”ν•©λ‹ˆλ‹€: \[C_{00} = A_{00} \cdot B_{00} + A_{01} \cdot B_{10}\] \(\text{SIZE} = 2\) 일 λ•Œ κ³±μ…ˆ 2회 + λ§μ…ˆ 1회 = 3 FLOPs

좜λ ₯ μ›μ†Œλ‹Ή λ©”λͺ¨λ¦¬ μ ‘κ·Ό:

  • ν–‰λ ¬ A의 ν–‰: \(2 \times 4 = 8\) bytes (FP32)
  • ν–‰λ ¬ B의 μ—΄: \(2 \times 4 = 8\) bytes (FP32)
  • 합계: 좜λ ₯ μ›μ†Œλ‹Ή \(16\) bytes

μ‚°μˆ  강도: \[\Large I_{\text{naive}} = \frac{3 \text{ FLOPs}}{16 \text{ bytes}} = 0.1875 \text{ FLOP/B}\]

이 μ‚°μˆ  κ°•λ„λŠ” A100의 μž„κ³„ 강도에 ν•œμ°Έ λͺ» λ―ΈμΉ˜λ―€λ‘œ, κΈ°λ³Έ 컀널은 μ‹¬κ°ν•œ λ©”λͺ¨λ¦¬ λ°”μš΄λ“œ μƒνƒœμž…λ‹ˆλ‹€.

\[\Large I_{\text{naive}} = 0.1875 \ll I^* = 12.5\]

μ˜ˆμƒ μ„±λŠ₯: \[\Large P \approx B_{\text{peak}} \times I_{\text{naive}} = 1{,}555 \times 0.1875 \approx 292 \text{ GFLOP/s}\]

μ΄λŠ” GPU μ—°μ‚° 잠재λ ₯의 \(\frac{292}{19{,}500} \approx 1.5\%\) 에 λΆˆκ³Όν•©λ‹ˆλ‹€! μ‹œκ°ν™”μ—μ„œ λ…Έλž€μƒ‰ 점이 λ©”λͺ¨λ¦¬ μƒν•œ μœ„μ— 놓인 것이 이λ₯Ό 잘 λ³΄μ—¬μ€λ‹ˆλ‹€ β€” μ—°μ‚° μƒν•œμ—λŠ” ν•œμ°Έ λ―ΈμΉ˜μ§€ λͺ»ν•˜λŠ” μˆ˜μ€€μž…λ‹ˆλ‹€.

5. λ‹€μŒ 단계: 곡유 λ©”λͺ¨λ¦¬ μ΅œμ ν™”

루프라인 λͺ¨λΈμ΄ μ•Œλ €μ£ΌλŠ” μ΅œμ ν™” μ „λž΅μ€ λͺ…ν™•ν•©λ‹ˆλ‹€: 쀑볡 λ©”λͺ¨λ¦¬ 접근을 쀄여 μ‚°μˆ  강도λ₯Ό λ†’μ΄λŠ” κ²ƒμž…λ‹ˆλ‹€. 곡유 λ©”λͺ¨λ¦¬ 접근법이 λ°”λ‘œ 이λ₯Ό λ‹¬μ„±ν•©λ‹ˆλ‹€:

곡유 λ©”λͺ¨λ¦¬μ˜ 이점:

  • ν˜‘λ ₯적 λ‘œλ”©: μŠ€λ ˆλ“œλ“€μ΄ ν•¨κ»˜ ν–‰λ ¬ 블둝을 λΉ λ₯Έ 곡유 λ©”λͺ¨λ¦¬μ— λ‘œλ“œ
  • 데이터 μž¬μ‚¬μš©: λ‘œλ“œν•œ μ›μ†Œ ν•˜λ‚˜λ₯Ό μ—¬λŸ¬ 연산에 ν™œμš©
  • μ „μ—­ λ©”λͺ¨λ¦¬ νŠΈλž˜ν”½ κ°μ†Œ: 느린 μ „μ—­ λ©”λͺ¨λ¦¬μ— λŒ€ν•œ μ ‘κ·Ό 횟수 κ°μ†Œ

μ‚°μˆ  강도 κ°œμ„  μ˜ˆμƒμΉ˜: \[\Large I_{\text{shared}} = \frac{12 \text{ FLOPs}}{32 \text{ bytes}} = 0.375 \text{ FLOP/B}\]

μž‘μ€ \(2 \times 2\) 규λͺ¨μ—μ„œλŠ” μ—¬μ „νžˆ λ©”λͺ¨λ¦¬ λ°”μš΄λ“œμ΄μ§€λ§Œ, 이 2배의 μ‚°μˆ  강도 ν–₯상은 곡유 λ©”λͺ¨λ¦¬ 타일을 훨씬 더 많이 μž¬μ‚¬μš©ν•  수 μžˆλŠ” 큰 ν–‰λ ¬μ—μ„œ 극적인 효과λ₯Ό λ°œνœ˜ν•©λ‹ˆλ‹€.

6. 루프라인이 μ•Œλ €μ£ΌλŠ” μ΅œμ ν™” μ „λž΅

루프라인 λͺ¨λΈμ€ ν˜„μž¬ μ„±λŠ₯을 진단할 뿐 μ•„λ‹ˆλΌ, μ΅œμ ν™” λ°©ν–₯κΉŒμ§€ μ•Œλ €μ€λ‹ˆλ‹€. 이후 νΌμ¦μ—μ„œ μ‚΄νŽ΄λ³Ό 핡심 기법은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

기법루프라인 νš¨κ³Όκ΅¬ν˜„ 방법
곡유 λ©”λͺ¨λ¦¬ 타일링데이터 μž¬μ‚¬μš©μœΌλ‘œ μ‚°μˆ  강도 β†‘ν˜‘λ ₯적 λ‘œλ”©, 블둝 λ‹¨μœ„ μ—°μ‚°
λ ˆμ§€μŠ€ν„° λΈ”λ‘œν‚Ήλ ˆμ§€μŠ€ν„° λˆ„μ μœΌλ‘œ λ©”λͺ¨λ¦¬ νŠΈλž˜ν”½ κ°μ†Œλ ˆμ§€μŠ€ν„° λ³€μˆ˜μ™€ 루프 μ „κ°œ
컀널 퓨전연산 κ²°ν•©μœΌλ‘œ λ°”μ΄νŠΈλ‹Ή FLOPs 증가단일 μ»€λ„μ—μ„œ μ—¬λŸ¬ μ—°μ‚° 단계 처리
λ©”λͺ¨λ¦¬ 병합(coalescing)μ‹€νš¨ λŒ€μ—­ν­ ν™œμš© κ·ΉλŒ€ν™”κ΅¬μ‘°ν™”λœ μ ‘κ·Ό νŒ¨ν„΄, μ μ ˆν•œ μŠ€λ ˆλ“œ ꡬ성
비동기 λ©”λͺ¨λ¦¬ λ³΅μ‚¬μ „μš© 볡사 μ—”μ§„μœΌλ‘œ μ—°μ‚°-λ©”λͺ¨λ¦¬ 쀑첩copy_dram_to_sram_async와 μ—°μ‚° 쀑첩
ν˜Όν•© μ •λ°€λ„μž‘μ€ 데이터 νƒ€μž…μœΌλ‘œ λ©”λͺ¨λ¦¬ λΆ€ν•˜ κ°μ†ŒFP16/BF16 μž…λ ₯ + FP32 λˆ„μ 

각 기법은 컀널을 루프라인 μœ„μ—μ„œ μ΄λ™μ‹œν‚΅λ‹ˆλ‹€ β€” λ©”λͺ¨λ¦¬ μƒν•œμ„ 따라 μœ„λ‘œ(λŒ€μ—­ν­ ν™œμš© κ°œμ„ ), λ˜λŠ” 였λ₯Έμͺ½ μ—°μ‚° μƒν•œμ„ ν–₯ν•΄(μ‚°μˆ  강도 ν–₯상).

비동기 연산에 λŒ€ν•œ μ°Έκ³ : ν‘œμ€€ GPU λ©”λͺ¨λ¦¬ λ‘œλ“œ(ld.global)λŠ” 이미 λΉ„λ™κΈ°μž…λ‹ˆλ‹€ β€” μ›Œν”„λŠ” λ‘œλ“œν•œ 데이터가 μ‹€μ œλ‘œ ν•„μš”ν•΄μ§ˆ λ•ŒκΉŒμ§€ 계속 μ‹€ν–‰λ©λ‹ˆλ‹€. cp.async(CUDA)λ‚˜ copy_dram_to_sram_async(Mojo) 같은 μ „μš© 비동기 볡사 λͺ…령은 μ—¬κΈ°μ„œ ν•œ 걸음 더 λ‚˜μ•„κ°€, μ „μš© 볡사 엔진을 μ‚¬μš©ν•˜κ³  λ ˆμ§€μŠ€ν„°λ₯Ό μš°νšŒν•˜μ—¬ μžμ› ν™œμš©μ„ λ†’μž…λ‹ˆλ‹€. λ‹¨μˆœνžˆ 동기 연산을 λΉ„λ™κΈ°λ‘œ λ°”κΎΈλŠ” κ²ƒκ³ΌλŠ” λ‹€λ¦…λ‹ˆλ‹€.

7. λ‹¨μˆœν•œ 루프라인을 λ„˜μ–΄μ„œ

닀단계 λ©”λͺ¨λ¦¬: κ³ κΈ‰ 루프라인은 L2 μΊμ‹œ, 곡유 λ©”λͺ¨λ¦¬, λ ˆμ§€μŠ€ν„° λŒ€μ—­ν­μ— λŒ€ν•΄ λ³„λ„μ˜ μƒν•œμ„ ν¬ν•¨ν•˜μ—¬ μ–΄λ–€ λ©”λͺ¨λ¦¬ 계측이 μ„±λŠ₯을 μ œμ•½ν•˜λŠ”μ§€ μ‹λ³„ν•©λ‹ˆλ‹€.

톡신 루프라인: λ©€ν‹° GPU μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œλŠ” λ©”λͺ¨λ¦¬ λŒ€μ—­ν­ λŒ€μ‹  인터컀λ„₯트 λŒ€μ—­ν­(NVLink, InfiniBand)을 μ‚¬μš©ν•˜μ—¬ μŠ€μΌ€μΌλ§ νš¨μœ¨μ„ λΆ„μ„ν•©λ‹ˆλ‹€.

μ „μš© μœ λ‹›: μ΅œμ‹  GPUλŠ” κ³ μœ ν•œ μ„±λŠ₯ νŠΉμ„±μ„ κ°€μ§„ ν…μ„œ μ½”μ–΄λ₯Ό ν¬ν•¨ν•˜λ©°, λ³„λ„μ˜ 루프라인 뢄석이 ν•„μš”ν•©λ‹ˆλ‹€.

8. μ‹€μ „μ—μ„œ 루프라인 ν™œμš©ν•˜κΈ°

  1. 컀널 ν”„λ‘œνŒŒμΌλ§: Nsight Compute 같은 λ„κ΅¬λ‘œ μ‹€μ œ FLOPs와 λ©”λͺ¨λ¦¬ νŠΈλž˜ν”½ μΈ‘μ •
  2. 데이터 포인트 ν‘œμ‹œ: μ‚°μˆ  강도와 μ‹€μΈ‘ μ„±λŠ₯ 계산
  3. 병λͺ© 식별: λ©”λͺ¨λ¦¬ λ°”μš΄λ“œ 컀널은 λ©”λͺ¨λ¦¬ μƒν•œ μœ„μ—, μ—°μ‚° λ°”μš΄λ“œ 컀널은 μ—°μ‚° μƒν•œμ— κ·Όμ ‘
  4. μ΅œμ ν™” 선택: λ©”λͺ¨λ¦¬ λ°”μš΄λ“œ μ»€λ„μ—λŠ” λŒ€μ—­ν­ κ°œμ„ μ—, μ—°μ‚° λ°”μš΄λ“œ μ»€λ„μ—λŠ” μ•Œκ³ λ¦¬μ¦˜ 변경에 집쀑
  5. μΈ‘μ •κ³Ό 반볡: μ΅œμ ν™”κ°€ 컀널을 κΈ°λŒ€ν•œ λ°©ν–₯으둜 μ΄λ™μ‹œν‚€λŠ”μ§€ 검증

곡유 λ©”λͺ¨λ¦¬ 퍼즐과의 μ—°κ²°

λ‹€μŒ μ„Ήμ…˜μ—μ„œλŠ” 컀널을 루프라인 μœ„λ‘œ λŒμ–΄μ˜¬λ¦¬κΈ° μ‹œμž‘ν•˜λŠ” 곡유 λ©”λͺ¨λ¦¬ μ΅œμ ν™”λ₯Ό κ΅¬ν˜„ν•©λ‹ˆλ‹€. μ‹œκ°ν™”μ—μ„œ λ³Ό 수 μžˆλ“―μ΄, 주황색 점(κΈ°λ³Έ)μ—μ„œ 청둝색 점(곡유 λ©”λͺ¨λ¦¬)으둜 μ΄λ™ν•˜κ²Œ λ©λ‹ˆλ‹€ β€” 데이터 μž¬μ‚¬μš© κ°œμ„ μ„ ν†΅ν•œ ν™•μ‹€ν•œ μ„±λŠ₯ ν–₯μƒμž…λ‹ˆλ‹€.

\(2 \times 2\) μ˜ˆμ œμ—μ„œλŠ” μ—°μ‚° μƒν•œμ— λ„λ‹¬ν•˜μ§€ λͺ»ν•˜μ§€λ§Œ, 곡유 λ©”λͺ¨λ¦¬κ°€ μ„±λŠ₯에 결정적인 역할을 ν•˜λŠ” 큰 ν–‰λ ¬μ—μ„œ λ™μΌν•œ 원리가 μ–΄λ–»κ²Œ ν™•μž₯λ˜λŠ”μ§€ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. 루프라인 λͺ¨λΈμ€ 곡유 λ©”λͺ¨λ¦¬κ°€ μ™œ 도움이 되고 μ–Όλ§ˆλ‚˜ κ°œμ„ μ„ κΈ°λŒ€ν•  수 μžˆλŠ”μ§€ μ΄ν•΄ν•˜κΈ° μœ„ν•œ 이둠적 ν† λŒ€λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

루프라인 λͺ¨λΈμ„ μ΄ν•΄ν•˜λ©΄ GPU μ΅œμ ν™”κ°€ μΆ”μΈ‘μ—μ„œ 체계적인 μ—”μ§€λ‹ˆμ–΄λ§μœΌλ‘œ λ°”λ€λ‹ˆλ‹€. 이 μ±…μ˜ λͺ¨λ“  μ΅œμ ν™” 기법은 이 λ‹¨μˆœν•˜μ§€λ§Œ κ°•λ ₯ν•œ μ„±λŠ₯ λͺ¨λΈμ— λŒ€ν•œ 효과둜 이해할 수 μžˆμŠ΅λ‹ˆλ‹€.