본문 바로가기

05. compute-bound, memory-bound, 데이터 이동

이 장의 핵심 연결: 04장의 가속기가 어디서 막히는가를 엽니다. 01장에서 본 data path의 "가속기 ↔ 메모리 오가기"(F↔G)가 왜 병목의 핵심인지 여기서 밝혀집니다.
다음 질문: 무엇이 성능 상한을 정하는가 — 연산 능력인가, 데이터 이동인가? 같은 하드웨어인데 어떤 작업은 연산에서, 어떤 작업은 데이터 이동에서 막히는 이유는?
목표:
  • 연산 강도(operational intensity)로 병목의 성격을 판단하는 틀을 잡는다.
  • compute-bound와 memory-bound를 정의하고, prefill·decode(01장)에 연결한다.
  • "더 센 가속기를 사면 빨라진다"가 왜 조건부인지 읽는다.

먼저: 병목(bottleneck)이 뭔가요?

일이 여러 단계로 이뤄질 때, 가장 느린 단계가 전체 속도를 정합니다. 그 단계가 병목입니다. 물이 여러 관을 지날 때 가장 좁은 관이 유량을 정하는 것과 같죠.

가속기에서 일은 크게 두 종류의 자원을 씁니다.

  • 연산(compute): 실제 산술(곱셈·덧셈 등)을 하는 능력.
  • 데이터 이동(data movement): 그 산술에 넣을 숫자를 메모리에서 코어로 실어 나르는 능력(=대역폭, 06장).

둘 중 무엇이 병목이냐에 따라 "빠르게 만드는 방법"이 완전히 달라집니다. 이걸 가르는 개념이 연산 강도입니다.

일상 비유: 요리사와 식재료 배달

주방에 요리사(연산)와 식재료를 나르는 배달원(데이터 이동)이 있습니다.

  • 요리 자체가 오래 걸리는 경우(compute-bound): 배달원은 한가한데 요리사가 계속 바쁩니다. 요리사를 더 뽑으면(연산 능력↑) 빨라집니다. 배달 트럭을 키워봤자 소용없습니다.
  • 재료 나르기가 병목인 경우(memory-bound): 요리사는 재료를 기다리며 놀고, 배달원이 쉴 새 없이 왕복합니다. 이땐 요리사를 더 뽑아도 소용없고, 배달 통로를 넓혀야(대역폭↑) 빨라집니다.

핵심 질문: "재료 한 번 나르면 요리를 얼마나 많이 하느냐." 이 비율이 높으면 요리(연산)가 병목, 낮으면 배달(데이터 이동)이 병목입니다. 이 비율이 바로 연산 강도입니다.

정확한 정의: 연산 강도(operational intensity)

CODE
연산 강도(operational intensity)
  = 수행한 연산 횟수 / 이동한 데이터 바이트 수
  = FLOP / byte

기호의 뜻:
  FLOP  … 부동소수점 연산 횟수(FLoating-point OPerations).
          "몇 번 곱하고 더했나". 개수(횟수)입니다.
  byte  … 그 연산을 위해 메모리에서 실어 나른 데이터 양.

읽는 법:
  값이 크다  → 데이터 한 바이트당 연산을 많이 함 → compute 쪽에서 막히기 쉬움
  값이 작다  → 데이터를 많이 나르는데 연산은 적음 → memory(대역폭) 쪽에서 막히기 쉬움
약어 풀이 — FLOP vs FLOPS를 꼭 구분하세요
  • FLOP(FLoating-point OPeration): 연산의 횟수(개수). "이 작업은 X FLOP이 든다."
  • FLOPS(FLoating-point OPerations per Second): 초당 연산 속도(능력). "이 가속기는 초당 Y FLOP을 한다." 끝의 S(per Second)가 결정적 차이입니다.
  • 즉 연산 강도의 분자는 FLOP(횟수), 가속기 능력은 FLOPS(속도). 헷갈리면 병목 판단이 통째로 어긋납니다.

시각적 직관: 병목 판단 흐름

flowchart TB
    START[이 작업의 연산 강도<br/>= FLOP / byte 는?]:::req --> Q{연산 강도가<br/>이 하드웨어의<br/>임계값보다 큰가?}:::req
    Q -->|크다| CB[compute-bound<br/>연산이 병목]:::compute
    Q -->|작다| MB[memory-bound<br/>데이터 이동이 병목]:::warn

    CB --> CBFIX[빠르게 하려면:<br/>연산 능력·병렬성 활용<br/>정밀도 활용 등]:::compute
    MB --> MBFIX[빠르게 하려면:<br/>대역폭 확보·데이터 재사용<br/>이동량 줄이기]:::warn

    classDef req fill:#fef9c3,stroke:#ca8a04,color:#713f12
    classDef compute fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
임계값의 뜻: 하드웨어마다 "연산 능력(FLOPS)"과 "대역폭(byte/s)"의 비율이 있습니다. 작업의 연산 강도가 그 비율보다 크면 연산이, 작으면 데이터 이동이 병목이 됩니다. (이 관계를 그림으로 정리한 것이 흔히 말하는 루프라인(roofline) 모델인데, 이 안내서는 이름과 직관만 챙깁니다.)

LLM에 연결: prefill은 왜 다르고 decode는 왜 다른가

01장의 두 국면을 기억하세요. 이 둘은 연산 강도가 다른 성향을 보입니다.

국면성향이유(직관)
prefill상대적으로 연산 강도 높은 편 → compute 쪽 성향입력 토큰 전체를 한 번에 처리하며, 실어 온 가중치로 많은 연산을 함 (데이터 재사용이 큼)
decode상대적으로 연산 강도 낮은 편 → memory 쪽 성향토큰을 하나씩 만들며, 매 스텝 큰 가중치를 다시 읽는데 그 스텝의 연산량은 상대적으로 적음
📌 핵심: "decode가 memory-bound 성향"이라는 관찰이 06장(메모리·대역폭)과 08장(throughput을 위한 배칭)의 동기가 됩니다. 데이터 이동이 병목이면, 이동을 줄이거나(캐시·재사용) 여러 요청이 같은 가중치 이동을 나눠 쓰게(배칭) 해서 효율을 올립니다.
단, 위 표의 "성향"은 일반적 경향(inferred)입니다. 실제 어느 쪽에 막히는지는 모델 구조·시퀀스 길이·배치 크기·하드웨어에 따라 달라지며 measured로 확인할 문제입니다.

⚖️ 절충(tradeoff): 병목에 맞는 처방

CODE
compute-bound일 때:
  - 연산 능력을 더 쓰는 것이 도움 → 더 센 연산 유닛, 병렬성 활용,
    (03장) 낮은 정밀도로 연산 처리량을 올리는 것 등이 유효할 수 있음
  - 대역폭만 늘려선 이득이 작음

memory-bound일 때:
  - 대역폭 확보, 데이터 재사용(캐시), 이동량 줄이기가 도움
  - 연산 능력만 늘려선 이득이 작음 (요리사는 이미 놀고 있음)
  - (03장) 낮은 정밀도는 "이동 바이트를 줄여" 도움이 될 수 있음
    — 여기선 속도 이득의 경로가 compute-bound와 다름에 주목

숨은 전제:
  "더 센 가속기를 사면 빨라진다"는 병목이 그 자원에 있을 때만 참.
  병목이 반대쪽이면 그 투자는 헛돈다.

⚠️ 흔한 오해

  • "연산 능력(FLOPS)이 높은 가속기면 항상 빠르다." — memory-bound 작업이면 연산 능력이 남아돌아도 소용없습니다. 대역폭이 병목이니까요.
  • "FLOP과 FLOPS는 같은 말이다." — 아닙니다. FLOP은 연산 횟수, FLOPS는 초당 연산 속도. 하나는 작업의 크기, 하나는 하드웨어의 능력입니다.
  • "낮은 정밀도는 언제나 같은 방식으로 빠르게 한다." — compute-bound에선 연산 처리량으로, memory-bound에선 이동 바이트 감소로 — 작동 경로가 다릅니다. 병목을 모르면 효과도 예측 못 합니다.
  • "decode는 항상 memory-bound다." — 성향일 뿐, 배치 크기·구조·하드웨어에 따라 달라집니다. measured로 확인할 문제입니다.

조건 명시

내용
성립하는 조건"연산 강도(FLOP/byte)로 병목의 성격을 가른다"는 틀은 일반적으로 유효합니다. compute/memory-bound의 정의도 안정적입니다.
성립하지 않는 결론"이 작업은 반드시 compute/memory-bound다"를 작업·하드웨어 조건 없이 단정할 수 없습니다. 임계값은 하드웨어마다 다르고, 연산 강도는 배치·시퀀스 길이 등에 따라 변합니다. 특정 작업의 병목은 measured로 확인해야 합니다.

🔬 증거 읽기: 병목 주장의 유형

  • "이 작업의 연산 강도는 X FLOP/byte" → calculated (연산량·이동량을 세어 나눈 값). 무엇을 셌는지 밝혀야 함.
  • "이 하드웨어의 임계값은 Y" → vendor-reported 스펙(FLOPS, 대역폭)에서 나온 calculated. 단 스펙이 peak이면 실제 임계값과 다를 수 있음.
  • "그래서 이 작업은 memory-bound다" → 위 둘을 연결한 inferred. 실제로 그런지는 프로파일링한 measured로 확인해야 확정됨.

이 장에서 배운 것

  • 병목은 가장 느린 단계가 정한다. 가속기에선 연산(compute)과 데이터 이동(memory) 둘 중 하나.
  • 연산 강도 = FLOP / byte. 크면 compute-bound 성향, 작으면 memory-bound 성향.
  • FLOP(횟수) ≠ FLOPS(초당 속도). 분자는 FLOP, 하드웨어 능력은 FLOPS.
  • LLM에서 prefill은 compute 쪽, decode는 memory 쪽 성향(단정 아님). 이것이 캐시·배칭(06·08장)의 동기.
  • 병목에 맞지 않는 자원 투자는 헛돈다 — "더 센 가속기"는 조건부 처방.

🔎 이 장의 '지지하지 않는 결론'

  • 이 장으로 특정 작업이 "반드시" compute/memory-bound라고 단정할 수 없다. 임계값·연산 강도가 조건에 따라 변하므로 measured가 필요하다.
  • "연산 능력이 높으면 무조건 빠르다"는 성립하지 않는다. memory-bound면 소용없다.

✍️ 확인 문제

  1. 어떤 작업의 연산 강도가 매우 낮게 나왔습니다. 이 작업을 빠르게 하려면 연산 능력을 늘려야 할까요, 대역폭을 확보해야 할까요? 왜죠?
  2. FLOP과 FLOPS의 차이를 한 문장씩으로 설명하고, 연산 강도의 분자에 들어가는 것은 어느 쪽인지 답하세요.
  3. [조건 묻기 / peak-measured] "이 하드웨어는 연산 능력이 높으니 이 LLM 추론이 빠를 것이다"라는 추론이 성립하려면 무엇을 먼저 확인해야 하나요? 그리고 "연산 능력 X FLOPS"가 peak 스펙일 때 주의할 점은?
다음 장: 06 · RAM, VRAM, cache, bandwidth — 데이터 이동이 지나는 메모리 계층을 엽니다. capacity·bandwidth·latency를 구분하고, 하드웨어 메모리와 소프트웨어가 관리하는 KV·prefix 캐시의 경계를 그립니다.