torch.compile 해부

TorchDynamo · AOTAutograd · TorchInductor — 그리고 왜 이렇게 지었나

스터디 발표 · 2026-09

torch.compile02 / 30

목차

한 예제를 끝까지 따라간 뒤, 실제 출력으로 확인한다
파트핵심 질문
00 · 배경PyTorch와 CUDA는 어떻게 연결되는가? 그래프는 무엇인가?
01 · 전체 구조한 함수는 각 단계에서 어떻게 표현되는가?
02 · Dynamo무엇을 캡처하고, 어디에서 그래프가 끊기는가?
03 · AOTAutograd변이·별칭·복합 연산을 어떻게 다루는가?
04 · Inductor어떤 실행 코드가 생기며, 비용은 어디서 줄어드는가?
실측 · 정리CPU 생성 코드·실행 시간·확장성
부록 A–F실제 그래프 / IR / 심화 수식 / 출처 / 재현 자료
00

배경 지식

CUDA 생태계는 왜 단단한가 · 그래프란 무엇인가 · FX 그래프란 무엇인가

Part 004 / 30

PyTorch를 썼는데, CUDA는 어디서 등장할까?

사용자 코드 · 실행할 연산 · 하드웨어 구현을 구분하자
관점eager 실행compile 실행
사용자 코드torch 연산으로 모델 작성같은 함수를 torch.compile로 감쌈
연산 처리호출된 연산을 그때그때 실행캡처한 그래프를 분석·최적화
GPU 실행기존 CUDA 커널·라이브러리 구현 활용생성 커널 + 기존 라이브러리 구현 활용
CPU 실행CPU 연산 구현 활용CPU용 코드 생성·라이브러리 활용

PyTorch는 모델을 작성하고 실행하는 프레임워크, CUDA는 NVIDIA GPU용 소프트웨어 플랫폼이다. CUDA core는 GPU 안의 연산 장치 명칭으로, CUDA 플랫폼 자체와 구분한다.

참고 · Compiler

Part 005 / 30

CUDA 생태계와 컴파일러의 역할

코드 재사용은 쉬워져도, 하드웨어 지원과 최적화는 별도 과제다
층이식할 때 확인할 것torch.compile이 돕는 부분
모델 코드CUDA 전용 코드·확장 연산·지원 연산일반적인 tensor 코드를 그래프로 캡처
연산·커널라이브러리 품질과 생성 커널 성능fusion·코드 생성·기존 커널 선택
장치·런타임메모리 할당·실행·동기화·드라이버백엔드 밖의 장치 통합도 필요
생태계도구·분산 통신·개발 경험컴파일러만으로 모두 해결되지는 않음

CUDA의 강점은 라이브러리와 도구, 개발 경험이 함께 쌓인 생태계다. torch.compile은 공통 그래프 캡처를 재사용하게 해 새 백엔드의 구현 부담을 줄인다.

참고 · Backends · 원문

Part 006 / 30

“그래프”란 무엇인가

연산들의 의존 관계를 표현하는 방향 비순환 그래프(DAG)
  • 노드 = 연산, 간선 = 데이터 흐름
  • eager 실행은 파이썬이 한 줄씩 커널을 호출한다 — 다음 줄에 무엇이 올지 모른다
  • 그래프로 잡아 두면 전체를 보고 결정할 수 있다: 무엇을 합칠지, 어떤 순서로 돌릴지, 메모리를 언제 놓을지
  • 이 단계에서 컴파일러는 그래프로 표현된 연산과 의존 관계를 분석한다
x ──▶ floor ──▶ y ──▶ add(1) ──▶ z

# 노드: floor, add
# 간선: x→floor, floor→add(y), add→z
Part 007 / 30

“FX 그래프”란 무엇인가

PyTorch의 자체 그래프 자료구조 — 평범한 파이썬 객체
  • torch.fx.Graph — 노드 리스트를 가진 평범한 파이썬 객체
  • 순회·수정이 쉽다: 노드를 돌면서 바꾸고, 다시 파이썬 코드로 찍어낼 수 있다
  • Dynamo·AOTAutograd·Inductor의 경계에서 FX 그래프를 주고받는다. Inductor 내부에서는 루프 수준 IR도 사용한다
  • 출력이 코드처럼 읽히는 이유: FX 는 그래프를 forward() 함수 형태로 되찍어 준다
# FX 그래프를 코드로 되찍은 형태 (graph_code 로그의 모양)
def forward(self, L_x_ : torch.Tensor):
    l_x_ = L_x_
    floor = torch.floor(l_x_);  l_x_ = None
    add = floor + 1;  floor = None
    return (add,)
01

torch.compile의 3대 요소

TorchDynamo → AOTAutograd → TorchInductor, 세 단계 파이프라인

Part 109 / 30

세 단계의 파이프라인

기본 Inductor 경로의 개념도 — 모든 custom backend가 같은 구성을 쓰는 것은 아니다
파이썬 함수1TorchDynamoFX 그래프 + guard2AOTAutograd정규화된 ATen 그래프3TorchInductorTriton / C++ 커널4
요소입력출력
TorchDynamo파이썬 함수FX 그래프 + guard + 잔여 바이트코드
AOTAutogradFX 그래프정규화된 ATen 그래프
TorchInductorATen 그래프생성 커널 + 기존 커널 호출 코드
Part 110 / 30

한 함수가 커널이 되기까지 — 단계를 눌러 보자

단계 상자를 클릭(또는 ↑↓)하면 그 단계의 역할과, 예시 함수가 그 시점에 어떤 모습인지 바뀐다
→backend=→→→

    Part 111 / 30

    backend=는 어디를 교체할까?

    Dynamo 출력 뒤에서 백엔드를 선택한다
    • 백엔드는 FX GraphModule과 예시 입력을 받고, 실행 가능한 함수를 반환한다.
    • 기본 Inductor 경로는 AOTAutograd를 활용한다. 사용자 백엔드도 필요하면 AOTAutograd를 연결할 수 있다.
    • 아래 예제는 그래프를 출력하고 eager 방식으로 실행하는 관찰용 백엔드다. 새 하드웨어 지원을 구현한 코드는 아니다.
    def inspect_backend(gm, example_inputs):
        print(gm.code)
        return gm.forward
    
    compiled = torch.compile(f, backend=inspect_backend)
    compiled(x)

    새 가속기에는 그래프 변환 외에도 장치·런타임·메모리·연산 지원·성능 최적화가 필요하다.

    참고 · Backends

    02

    TorchDynamo

    파이썬 코드를 그래프로 — 그리고 graph break

    Part 213 / 30

    파이썬 코드를 그래프로 변환

    계산은 하지 않고, 데이터 흐름만 추적한다
    • 파이썬 바이트코드를 해석하며 실행 흐름을 따라간다
    • 텐서 연산을 만나면 계산하지 않고 그래프에 기록한다 — 심볼릭 추적
    • 출력 세 가지: FX 그래프 · guard · 잔여 바이트코드
    • guard = 이 그래프가 유효한 전제 (입력 shape · dtype · 파이썬 값 등). 재사용 가능한 캐시 항목이 없으면 재컴파일할 수 있다
    • 그래프로 못 넣는 파이썬 동작은 잔여 바이트코드로 남겨 인터프리터가 그대로 실행
    바이트코드해석1텐서 연산그래프에 기록2guard유효 조건3잔여바이트코드4
    Part 214 / 30

    graph break

    그래프가 쪼개지는 지점 — 최적화가 멈추는 지점
    • 원인 ① 텐서 값에 의존하는 제어흐름 — if x.sum() > 0 은 값을 알아야 분기하는데, Dynamo 는 계산을 안 한다
    • 원인 ② 그래프화 불가능한 파이썬 동작 — print, I/O, 외부 라이브러리 호출
    • 결과 함수가 여러 그래프로 쪼개지고, 컴파일된 코드 ↔ 파이썬 인터프리터 사이 문맥 전환이 생긴다
    • 부작용 그래프 경계를 넘는 최적화 기회 소실 — 경계 양쪽 연산은 절대 합쳐지지 않는다
    def a_dynamo_break(x):
        y = x * 2
        if y.sum() > 0:      # ← 여기서 graph break
            return y + 1     #   그래프 1: x*2, sum   /  그래프 2: y+1 또는 y-1
        return y - 1
    
    torch._dynamo.explain(a_dynamo_break)(x)
    # Graph Count / Graph Break Count / Break Reasons 를 알려 준다

    CPU 실측: Graph Count = 2, Graph Break Count = 1. 입력은 torch.ones(4). 실제 사유·그래프는 부록 A에 수록.

    03

    AOTAutograd

    그래프 정규화 — functionalization · decomposition · 디스패처 아래에서 캡처

    Part 316 / 30

    그래프 정규화

    사용자 친화적 표현을 컴파일러 친화적 형태로 단순화한다
    • Dynamo 의 FX 그래프는 사람이 쓴 그대로다 — in-place, view, 고수준 연산이 섞여 있다
    • 컴파일러는 단순하고 예측 가능한 표현을 원한다
    • AOTAutograd 가 하는 일 두 가지: Functionalization + Decomposition
    • 이름대로 backward 그래프도 Ahead-Of-Time 으로 미리 만든다 (원문 1편은 forward 정규화에 집중)
    FX 그래프사용자 표현1Functionalizationin-place 제거2Decompositioncore ATen 으로3ATen 그래프컴파일러 표현4
    Part 317 / 30

    Functionalization — 변이와 별칭을 구분하자

    view는 메모리를 공유할 수 있다. mutation 제거가 곧 복사를 뜻하지 않는다
    # 사용자 코드
    y = x.clone()
    y.add_(1)          # 기존 y의 값을 변경
    y = y.view(-1)     # 같은 저장 공간을 볼 수 있음
    # 개념 예시: mutation 제거
    clone = aten.clone(x)
    add   = aten.add(clone, 1)
    view  = aten.view(add, [-1])
    # view가 남았다고 독립 메모리가 된 것은 아님
    • 변이 제거: add_를 함수형 add로 표현해 값의 변화를 명시한다.
    • 별칭 제거: remove="mutations_and_views" 설정에서는 view_copy 같은 비별칭 연산으로 바꿀 수 있다.
    • 최적화는 데이터 의존성과 원래 프로그램의 의미를 보존하는 범위에서 수행한다.

    참고 · Functionalize

    Part 318 / 30

    Decomposition — 실제로 어떤 연산이 남았을까?

    SiLU 예제 · 기본 Inductor 경로의 실제 ATen 출력 (파일 경로 주석 생략)
    • SiLU(x)는 x × sigmoid(x)로 표현할 수 있다.
    • 이번 실행에서는 neg → exp → add → div로 표현됐다. 식은 x / (1 + exp(−x))와 같다.
    • 남기는 연산 집합은 버전·백엔드·설정에 따라 달라진다.
    • 구현할 연산 종류는 줄어들지만, 모든 모델 지원이 자동으로 보장되지는 않는다.
    class <lambda>(torch.nn.Module):
        def forward(self, arg0_1: "f32[4]"):
            add: "f32[4]" = torch.ops.aten.add.Tensor(arg0_1, 1);  arg0_1 = None
    
            neg: "f32[4]" = torch.ops.aten.neg.default(add)
            exp: "f32[4]" = torch.ops.aten.exp.default(neg);  neg = None
            add_1: "f32[4]" = torch.ops.aten.add.Tensor(exp, 1);  exp = None
            div: "f32[4]" = torch.ops.aten.div.Tensor(add, add_1);  add = add_1 = None
            return (div,)

    참고 · Backends

    Part 319 / 30

    같은 연산을 ATen 수준에서 관찰한다

    Python API 이름과 컴파일러가 받는 연산 표현은 다를 수 있다
    사용자 표현정규화의 목적
    torch.relu(x)같은 의미를 갖는 연산을 공통 ATen 표현으로 다룸
    F.relu(x)Python API 표기의 차이를 줄임
    nn.ReLU()(x)모듈 호출 내부의 tensor 연산을 추적
    • AOTAutograd는 dispatcher를 활용한 추적으로 ATen 연산을 관찰한다.
    • 실제 CPU/GPU 커널을 전부 계산해 본다는 뜻은 아니다. FakeTensor 등으로 shape·dtype 같은 메타데이터를 추적할 수 있다.
    • 추적 설정과 변환 단계에 따라 최종 그래프 모양은 달라질 수 있다.

    참고 · 원문 · Compiler

    04

    TorchInductor

    커널 코드 생성 — 원소 단위 · fusion · FLOPs 와 bytes · 문자열 기반 codegen

    Part 421 / 30

    커널 코드 생성, 세 단계

    lowering → scheduling → codegen
    loweringATen → 루프 수준 IR1scheduling어떤 노드를 합칠지2codegenTriton / C++ 문자열3
    단계하는 일결과
    lowering텐서 단위 연산을 원소 단위 수식으로 내린다루프 수준 IR
    scheduling의존 관계를 보고 합칠 노드를 묶는다 (fusion 결정)커널 단위 그룹
    codegenIR 의 기본 동작을 코드 생성 함수로 갈아끼운다Triton(GPU) · C++(CPU) 소스
    Part 422 / 30

    텐서 단위에서 원소 단위로

    같은 수식, 다른 병렬화
    # FX / ATen 그래프 — 텐서 단위
    y = floor(x)
    z = y + 1
    # Inductor 루프 수준 IR — 원소 단위
    for i in range(N):
        y[i] = floor(x[i])
        z[i] = y[i] + 1
    • 텐서 단위 표현은 “무엇을” 만 말하고, 원소 단위 표현은 “어떻게 나눠 돌릴지” 까지 말할 수 있다
    • 장점 같은 수식에 다른 병렬화 방식을 적용할 수 있다 — GPU 는 블록 단위로, CPU 는 벡터화 루프로
    Part 423 / 30

    Fusion — 커널 융합

    eager 의 문제: 중간 결과가 메모리에 왕복한다
    eager — 커널 2개, 중간 텐서 쓰기·읽기x (mem)floor 커널y (mem)+1 커널z (mem)fused — 커널 1개: z[i] = floor(x[i]) + 1x (mem)floor + 1 (레지스터 안에서 이어붙임)z (mem)
    • 문제 eager 는 연산마다 커널을 띄우고, 중간 결과 y 를 메모리에 쓰고 다시 읽는다
    • 해결 연산들을 커널 하나로 합쳐 메모리 왕복을 최소화한다
    • 구현 계산식 이어붙이기 — y[i] = floor(x[i]) + z[i] = y[i] + 1 → z[i] = floor(x[i]) + 1
    Part 424 / 30

    FLOPs와 bytes — 서로 다른 두 가지 비용

    FLOPs는 계산 횟수, bytes는 옮기는 데이터의 크기다
    • FLOPs: 부동소수점 연산 횟수. FLOP/s는 초당 연산 처리량이다.
    • 길이 P 내적은 곱 P번 + 덧셈 P−1번. 관례적으로 약 2P FLOPs로 센다.
    • 행렬곱 [N,P] × [P,M]은 출력 NM개마다 길이 P 내적 → 약 2NPM FLOPs.
    • bytes: 원소 수 × 원소당 바이트 수. FP32는 원소당 4 bytes.
    A: [N, P]    B: [P, M]    C: [N, M]
    
    계산량 ≈ 2 × N × P × M FLOPs
    
    이상적인 최소 데이터 이동량
    ≈ (NP + PM + NM) × s bytes
    s = 원소당 바이트 수
    # 캐시·재사용·추가 버퍼에 따라 실측량은 달라짐

    참고 · Roofline · Transformer FLOPs

    Part 425 / 30

    Fusion — 중간 메모리 접근과 호출을 줄인다

    floor(x) + 1의 계산 의미는 같고, 중간 텐서 y를 저장할 필요가 줄어든다
    구분eager: floor → addfused: floor + add
    수학적 계산원소마다 floor, add원소마다 floor, add
    커널 수: 이 예제21
    읽기·쓰기x 읽기 / y 쓰기 / y 읽기 / z 쓰기x 읽기 / z 쓰기
    이상적 이동량4Ns bytes2Ns bytes
    FP32, N=1,048,57616 MiB8 MiB

    메모리 접근량 계산 예시이지, DRAM 트래픽 실측값은 아니다. 캐시와 커널 실행 비용도 영향을 준다. 이동량이 절반이라고 실행 시간이 반드시 절반이 되지는 않는다.

    참고 · Roofline

    Part 426 / 30

    병목은 FLOPs만으로 정해지지 않는다

    “얼마나 계산하나”를 “얼마나 빨리 계산하고 옮길 수 있나”와 함께 보자
    계산 시간 하한 ≈ FLOPs / 연산 처리량
    이동 시간 하한 ≈ bytes / 메모리 대역폭
    
    연산 집약도 = FLOPs / bytes
    
    단순 Roofline 관점:
    시간 ≳ max(계산 시간, 이동 시간)
    • 단순 원소 연산: 큰 배열에서는 메모리 대역폭이 중요해지기 쉽다.
    • 작은 연산: Python·커널 호출 오버헤드가 더 중요할 수 있다.
    • 행렬곱: shape·dtype·재사용·커널 구현에 따라 병목이 달라진다.
    • Fusion은 메모리 접근과 호출을 줄일 수 있다. 최종 효과는 측정해야 한다.

    참고 · Roofline

    Part 427 / 30

    실제 출력 — CPU용 C++ 커널

    PyTorch 2.14.0+cpu · floor + add · 생성 코드에서 연속 발췌
    • 이번 환경은 CPU다. GPU용 Triton 출력으로 표시하지 않는다.
    • 아래는 Inductor의 output_code.py 안 C++ 소스다. 원문 전체는 마지막 부록에서 내려받을 수 있다.
    • floor와 add가 같은 루프에서 수행되는지, 중간 y 배열이 필요한지 확인하자.
    • 다른 하드웨어를 지원하려면 코드 생성 외에도 실행·메모리·지원 연산을 연결해야 한다.
    auto tmp0 = at::vec::Vectorized<float>::loadu(in_ptr0 + static_cast<int64_t>(x0), static_cast<int64_t>(16));
    auto tmp1 = tmp0.floor();
    auto tmp2 = static_cast<float>(1.0);
    auto tmp3 = at::vec::Vectorized<float>(tmp2);
    auto tmp4 = tmp1 + tmp3;
    tmp4.store(out_ptr0 + static_cast<int64_t>(x0));

    참고 · Compiler

    Part 428 / 30

    실측 — 첫 호출과 반복 실행을 나눠 보자

    CPU · float32 · [1024, 1024] · 1 thread · 이 함수 하나에 대한 측정
    측정 항목결과해석
    compile 첫 호출19.150 s컴파일·로그·첫 실행을 포함
    eager 반복 중앙값1.6775 ms / call20개 라운드의 라운드당 평균에서 중앙값
    compiled 반복 중앙값0.1615 ms / call이 측정의 eager/compiled 비율: 10.39×
    결과 검증정확히 일치 · max abs diff = 0고정 입력에서 rtol=0, atol=0로 비교

    각 경로 10회 워밍업 후, 20회 호출 × 20라운드. 측정 순서를 번갈아 적용했다. 공유 CPU 환경의 예제 측정으로 GPU·LLM 전체 성능을 대표하지 않는다. 컴파일 캐시는 새 경로를 사용했지만 OS·컴파일러 캐시까지 초기화한 cold-start 측정은 아니다.

    Part 429 / 30

    확장성 — 재사용하는 부분과 구현할 부분

    원문의 확장성 관점을, 실제 백엔드 개발 범위와 구분하자
    재사용할 수 있는 것추가로 해결할 것
    Dynamo의 Python 캡처지원하지 않는 코드·연산 처리
    AOTAutograd와 decomposition 활용필요한 그래프 변환·학습 지원
    공통 그래프·IR 활용하드웨어별 커널·메모리·런타임
    기존 PyTorch 모델 표현정확성·성능·도구·운영 검증

    원문은 HyperAccel의 Legato Backend 연결을 언급한다. 이번 CPU 실험은 Legato나 LPU를 실행한 결과가 아니다. 확장 구조를 이해하는 실험이다.

    참고 · 원문 · Backends

    torch.compile30 / 30

    정리 — 코드가 어떻게 실행으로 이어지는가

    캡처 → 정규화 → 커널 생성·선택 → 실행·재사용
    질문답
    왜 그래프가 필요한가?여러 연산의 의존 관계를 보고 함께 최적화하기 위해
    각 단계는 무엇을 맡나?Dynamo: 캡처 / AOTAutograd: 정규화·학습 그래프 / Inductor: 실행 코드
    왜 빨라질 수 있나?중간 메모리 접근·호출 오버헤드 감소와 커널 최적화
    언제 효과가 달라지나?컴파일 비용·graph break·재컴파일·shape·장치·커널 구현에 따라
    이번에 직접 확인한 것은?CPU 실제 그래프·C++ 코드·결과 일치·반복 실행 시간

    다음 실험: 같은 재현 스크립트를 바탕으로 GPU 버전을 별도로 구성하고, 실제 모델의 병목과 compile 전후 시간을 비교한다.

    APPXAPPX 1 / 6

    실측 A — Graph break

    torch._dynamo.explain · 입력 torch.ones(4) · 실제 그래프
    Graph Count: 2
    Graph Break Count: 1
    
    
    
    
    def forward(self, L_x_ : torch.Tensor):
        l_x_ = L_x_
        y = l_x_ * 2;  l_x_ = None
        sum_1 = y.sum()
        gt = sum_1 > 0;  sum_1 = None
        return (gt, y)
    def forward(self, L_y_ : torch.Tensor):
        l_y_ = L_y_
        add = l_y_ + 1;  l_y_ = None
        return (add,)

    실제 사유 발췌: generic_jump TensorVariable()
    이 입력은 양수 분기를 실행했다. 음수 분기까지 하나의 그래프로 캡처했다는 뜻은 아니다.

    APPXAPPX 2 / 6

    실측 B — Functionalization과 ATen

    아래 코드는 make_fx(functionalize(...))의 실제 출력 · 기본 Inductor 출력과 구분
    # remove="mutations"
    
    
    
    def forward(self, x_1):
        clone = torch.ops.aten.clone.default(x_1);  x_1 = None
        add = torch.ops.aten.add.Tensor(clone, 1);  clone = None
        view = torch.ops.aten.view.default(add, [-1]);  view = None
        view_1 = torch.ops.aten.view.default(add, [-1]);  add = None
        silu = torch.ops.aten.silu.default(view_1);  view_1 = None
        return silu
    # remove="mutations_and_views"
    
    
    
    def forward(self, x_1):
        clone = torch.ops.aten.clone.default(x_1);  x_1 = None
        add = torch.ops.aten.add.Tensor(clone, 1);  clone = None
        view_copy = torch.ops.aten.view_copy.default(add, [-1]);  view_copy = None
        view_copy_1 = torch.ops.aten.view_copy.default(add, [-1]);  add = None
        silu = torch.ops.aten.silu.default(view_copy_1);  view_copy_1 = None
        return silu

    왼쪽과 오른쪽의 view / view_copy를 비교한다. 이 별도 실험은 설정 차이를 보여주기 위한 것이며, 오른쪽이 기본 torch.compile 파이프라인이라는 뜻은 아니다.

    APPXAPPX 3 / 6

    실측 C — Fusion을 어디에서 확인할까?

    기본 Inductor의 ATen 그래프와 스케줄링 전 IR · 연속 발췌
    # 실제 ATen (파일 경로 주석 생략)
    class <lambda>(torch.nn.Module):
        def forward(self, arg0_1: "f32[1024, 1024]"):
            floor: "f32[1024, 1024]" = torch.ops.aten.floor.default(arg0_1);  arg0_1 = None
    
            add: "f32[1024, 1024]" = torch.ops.aten.add.Tensor(floor, 1);  floor = None
            return (add,)
    class op0_loop_body:
        var_ranges = {p0: 1048576}
        index0 = p0
        def body(self, ops):
            get_index = self.get_index('index0')
            load = ops.load('arg0_1', get_index)
            floor = ops.floor(load)
            constant = ops.constant(1.0, torch.float32)
            add = ops.add(floor, constant)
            get_index_1 = self.get_index('index0')
            store = ops.store('buf0', get_index_1, add, None)
            return store

    pre/post 파일에서 노드 수가 반드시 2→1이 되는 것은 아니다. 더 이른 단계에서 이미 같은 루프 표현으로 묶일 수 있다. 생성 코드·중간 버퍼·호출을 함께 확인한다. 원문 IR 두 파일은 내려받기 자료에 포함했다.

    APPXAPPX 4 / 6

    심화 — Transformer FLOPs의 가정

    본문의 병목 판단과, 모델 구성요소의 FLOPs 비중을 구분하자
    • 선형층 A[N,P] × W[P,M]: forward ≈ 2NPM. 입력·가중치 기울기까지 계산하면 합계 ≈ 6NPM.
    • Dense Transformer에서 attention 행렬곱 비용 등을 제외하는 근사: 학습 FLOPs ≈ 6 × 파라미터 수 × 토큰 수.
    • Scaling Book의 T / (8D)는 F=4D, MHA, 해당 gated MLP와 학습 계산 가정하에서 dot-product attention / 나머지 주요 행렬곱의 FLOPs 비율이다.
    • T > 8D는 이 비율이 1을 넘는 조건이다. compute-bound의 판정 기준이 아니다.
    • GQA·MLP 구조·causal 계산량 산정·local attention 등 가정이 바뀌면 계수와 임계값도 바뀐다.

    참고 · Transformer FLOPs · Roofline

    APPXAPPX 5 / 6

    출처와 재현 조건

    2026-09-22 실행 · 기본 backend=inductor · CPU 추론
    항목조건
    PyTorch / Python2.14.0+cpu / 3.12.14
    장치 / dtype / threadsCPU / float32 / 1
    입력 / seed[1024, 1024] / 42
    검증eager와 compiled 결과 정확히 일치
    한계CPU 공유 실행 환경 · GPU 미측정 · 한 예제에 대한 단일 세션 결과

    참고 · 원문 · Compiler · Functionalize · Backends · Dynamic shapes · Roofline · Transformer FLOPs

    APPXAPPX 6 / 6

    재현 자료 — 실제 출력 원문 내려받기

    HTML 안에 스크립트·측정값·전체 그래프·생성 코드·로그를 함께 담았다
    python -m venv .venv
    # 가상환경 활성화 후 CPU 빌드 설치 (Linux 예시)
    python -m pip install torch==2.14.0 --index-url https://download.pytorch.org/whl/cpu
    python dump_torch_compile.py > run-summary.log 2> compiler.log
    
    # evidence/results.json : 실제 값과 그래프
    # evidence/debug/       : 생성 코드·IR
    # GPU 실험은 입력 device와 측정 동기화 등을 별도 수정해야 함

    원문 로그에는 실행 환경의 로컬 경로가 포함된다. 실제 출력은 그대로 보존했으며, 본문의 코드 발췌는 실측 파일의 연속 구간이다.