Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Autodiff를 위한 TypeTree

TypeTree란 무엇입니까?

Enzyme을 위한 메모리 레이아웃 기술자입니다. 타입이 메모리에서 어떻게 구성되는지 Enzyme에 정확히 알려 주어 도함수를 효율적으로 계산할 수 있게 합니다.

구조

#![allow(unused)]
fn main() {
TypeTree(Vec<Type>)

Type {
    offset: isize,  // byte offset (-1 = everywhere)
    size: usize,    // size in bytes
    kind: Kind,     // Float, Integer, Pointer, etc.
    child: TypeTree // nested structure
}
}

예시: fn compute(x: &f32, data: &[f32]) -> f32

입력 0: x: &f32

#![allow(unused)]
fn main() {
TypeTree(vec![Type {
    offset: -1, size: 8, kind: Pointer,
    child: TypeTree(vec![Type {
        offset: 0, size: 4, kind: Float,  // Single value: use offset 0
        child: TypeTree::new()
    }])
}])
}

입력 1: data: &[f32]

#![allow(unused)]
fn main() {
TypeTree(vec![Type {
    offset: -1, size: 8, kind: Pointer,
    child: TypeTree(vec![Type {
        offset: -1, size: 4, kind: Float,  // -1 = all elements
        child: TypeTree::new()
    }])
}])
}

출력: f32

#![allow(unused)]
fn main() {
TypeTree(vec![Type {
    offset: 0, size: 4, kind: Float,  // Single scalar: use offset 0
    child: TypeTree::new()
}])
}

왜 필요합니까?

  • Enzyme은 LLVM IR로부터 복잡한 타입 레이아웃을 추론할 수 없습니다
  • 느린 메모리 패턴 분석을 피할 수 있습니다
  • 중첩된 구조체에 대해 올바른 도함수 계산을 가능하게 합니다
  • 어떤 바이트가 미분 가능한 값이고 어떤 바이트가 메타데이터인지 Enzyme에 알려 줍니다

Enzyme이 이 정보로 하는 일:

TypeTree가 없는 경우:

; Enzyme sees generic LLVM IR:
define float @distance(ptr %p1, ptr %p2) {
; Has to guess what these pointers point to
; Slow analysis of all memory operations
; May miss optimization opportunities
}

TypeTree가 있는 경우:

define "enzyme_type"="{[-1]:Float@float}" float @distance(
    ptr "enzyme_type"="{[-1]:Pointer, [-1,0]:Float@float}" %p1, 
    ptr "enzyme_type"="{[-1]:Pointer, [-1,0]:Float@float}" %p2
) {
; Enzyme knows exact type layout
; Can generate efficient derivative code directly
}

TypeTree - 오프셋과 -1에 대한 설명

타입 구조

#![allow(unused)]
fn main() {
Type {
    offset: isize, // WHERE this type starts
    size: usize,   // HOW BIG this type is
    kind: Kind,    // WHAT KIND of data (Float, Int, Pointer)
    child: TypeTree // WHAT'S INSIDE (for pointers/containers)
}
}

오프셋 값

일반 오프셋(0, 4, 8 등)

구조체 내의 특정 바이트 위치

#![allow(unused)]
fn main() {
struct Point {
    x: f32, // offset 0, size 4
    y: f32, // offset 4, size 4
    id: i32, // offset 8, size 4
}
}

&Point에 대한 TypeTree(내부 표현):

#![allow(unused)]
fn main() {
TypeTree(vec![
    Type { offset: 0, size: 4, kind: Float },   // x at byte 0
    Type { offset: 4, size: 4, kind: Float },   // y at byte 4
    Type { offset: 8, size: 4, kind: Integer }  // id at byte 8
])
}

생성되는 LLVM

"enzyme_type"="{[-1]:Pointer, [-1,0]:Float@float, [-1,4]:Float@float, [-1,8]:Integer, [-1,9]:Integer, [-1,10]:Integer, [-1,11]:Integer}"

오프셋 -1(특수 값: “모든 곳”)

“이 패턴이 모든 요소에 대해 반복됨“을 의미합니다

예시 1: 직접 배열 [f32; 100](포인터 간접 참조 없음)

#![allow(unused)]
fn main() {
TypeTree(vec![Type {
    offset: -1, // ALL positions
    size: 4,    // each f32 is 4 bytes
    kind: Float, // every element is float
}])
}

생성되는 LLVM: "enzyme_type"="{[-1]:Float@float}"

예시 1b: 배열 참조 &[f32; 100](포인터 간접 참조 있음)

#![allow(unused)]
fn main() {
TypeTree(vec![Type {
    offset: -1, size: 8, kind: Pointer,
    child: TypeTree(vec![Type {
        offset: -1, // ALL array elements
        size: 4,    // each f32 is 4 bytes
        kind: Float, // every element is float
    }])
}])
}

생성되는 LLVM: "enzyme_type"="{[-1]:Pointer, [-1,-1]:Float@float}"

오프셋 0,4,8,12...396을 가진 100개의 개별 Type을 나열하는 대신

예시 2: 슬라이스 &[i32]

#![allow(unused)]
fn main() {
// Pointer to slice data
TypeTree(vec![Type {
    offset: -1, size: 8, kind: Pointer,
    child: TypeTree(vec![Type {
        offset: -1, // ALL slice elements
        size: 4,    // each i32 is 4 bytes
        kind: Integer
    }])
}])
}

LLVM을 생성합니다: "enzyme_type"="{[-1]:Pointer, [-1,-1]:Integer}"

예제 3: 혼합 구조

#![allow(unused)]
fn main() {
struct Container {
    header: i64,        // offset 0
    data: [f32; 1000],  // offset 8, but elements use -1
}
}
#![allow(unused)]
fn main() {
TypeTree(vec![
    Type { offset: 0, size: 8, kind: Integer }, // header
    Type { offset: 8, size: 4000, kind: Pointer,
        child: TypeTree(vec![Type {
            offset: -1, size: 4, kind: Float // ALL array elements
        }])
    }
])
}

핵심 차이점: 단일 값과 배열

단일 값은 정밀도를 위해 오프셋 0을 사용합니다:

  • &f32는 오프셋 0에 정확히 하나의 f32 값을 가집니다
  • -1(“모든 위치”)을 사용하는 것보다 더 정밀합니다
  • 생성 결과: {[-1]:Pointer, [-1,0]:Float@float}

배열은 효율성을 위해 오프셋 -1을 사용합니다:

  • &[f32; 100]에는 같은 패턴이 100번 반복됩니다
  • -1을 사용하면 100개의 개별 오프셋을 나열하지 않아도 됩니다
  • 생성 결과: {[-1]:Pointer, [-1,-1]:Float@float}