본문 바로가기

AI Compiler framework

Add 연산자 의미 정의

1. 개요

Add 연산자는 두 입력 텐서를 원소 단위로 더해 하나의 출력 텐서를 생성한다.

입력 텐서의 shape가 서로 다를 경우 NumPy broadcasting 규칙을 적용하며, 두 입력의 dtype은 동일해야 한다.

수학적으로는 다음과 같이 표현된다.

z = x + y

Add는 단순한 산술 연산이지만 그래프 분석 관점에서는 다음과 같은 특징을 가진다.

  • 두 입력을 하나의 출력으로 병합한다.
  • 명시적인 입력 쌍 전체에 대해서는 선형 연산이다.
  • 출력만으로는 두 입력을 각각 복원할 수 없다.
  • 다른 한쪽 입력을 알고 있다면 이상적인 산술에서는 나머지 입력을 복원할 수 있다.
  • 실제 부동소수점 연산에서는 반올림으로 인해 복원이 실패할 수 있다.

2. 입력 및 출력 규칙

2.1 입력 개수

Add는 정확히 두 개의 입력을 요구한다.

Add(x, y)

입력 개수가 두 개가 아니면 연산은 유효하지 않다.


2.2 dtype 규칙

두 입력은 동일한 dtype을 가져야 한다.

x.dtype == y.dtype

현재 프레임워크에서는 NumPy와 같은 암묵적 dtype promotion을 허용하지 않는다.

예를 들어 다음 입력은 허용되지 않는다.

float32 + float64
int32 + float32

이 정책은 정적 분석과 실제 실행 결과 사이의 불일치를 줄이고 연산 의미를 단순하게 유지하기 위한 것이다.


2.3 shape 규칙

두 입력의 shape는 NumPy broadcasting 규칙과 호환되어야 한다.

예를 들어 다음 연산은 유효하다.

(3, 4) + (3, 4) → (3, 4)
(3, 4) + (4,)   → (3, 4)
(3, 1) + (3, 4) → (3, 4)
(1,)   + (2, 3) → (2, 3)

다음과 같이 broadcasting이 불가능한 shape 조합은 유효하지 않다.

(3, 4) + (2, 4)

출력 shape는 다음과 같이 결정된다.

output_shape = np.broadcast_shapes(left.shape, right.shape)

3. 대수적 의미

3.1 결합 입력에 대한 선형성

Add를 두 입력의 결합 공간에서 정의된 함수로 보면 다음과 같다.

F(x, y) = x + y

두 입력 쌍 (x₁, y₁), (x₂, y₂)와 스칼라 a, b에 대해 다음이 성립한다.

F(a(x₁, y₁) + b(x₂, y₂))
= F(ax₁ + bx₂, ay₁ + by₂)

= ax₁ + bx₂ + ay₁ + by₂

= a(x₁ + y₁) + b(x₂ + y₂)

= aF(x₁, y₁) + bF(x₂, y₂)

따라서 Add는 두 입력을 모두 변수로 보는 결합 입력 공간에서 선형 연산이다.

"linear": True
"nonlinear": False

보다 정확한 의미 표현은 다음과 같다.

jointly linear

즉, Add의 선형성은 명시적인 두 입력 (x, y) 전체를 대상으로 한다.


3.2 한 입력을 상수로 보는 경우

한쪽 입력을 상수 c로 고정하면 연산은 다음과 같이 보인다.

f(x) = x + c

c ≠ 0이라면 이 함수는 일반적인 의미의 선형 함수가 아니다.

f(0) = c ≠ 0

이 경우에는 affine transformation이다.

따라서 다음 두 상황을 구분해야 한다.

Add(x, y)

두 입력을 모두 변수로 보면 jointly linear이다.

Add(x, constant)

한 입력을 상수로 고정하면 다른 입력에 대해서는 affine이다.

프레임워크의 linear=True는 다음 기준으로 해석한다.

연산자의 모든 명시적 입력을 변수로 간주했을 때 선형인가?

이 기준을 사용하면 Add는 선형 연산으로 분류할 수 있다.


4. 병합 의미

Add는 두 개의 입력 텐서를 하나의 출력 텐서로 결합한다.

(x, y) → x + y

이 병합은 원소 단위 합에 의해 이루어진다.

"merge_kind": "elementwise_sum"

이는 Concat과 같은 구조적 결합과 다르다.

Concat은 각 입력의 값을 서로 다른 위치에 보존하지만, Add는 동일한 출력 위치에서 두 입력값을 합산한다.

Concat:
(x, y) → [x, y]

Add:
(x, y) → x + y

따라서 Add는 입력 정보를 위치적으로 보존하는 병합이 아니라, 값을 중첩시키는 병합이다.


5. 다대일 성질

Add는 입력 쌍 전체에 대해 다대일 함수이다.

동일한 출력이 여러 입력 쌍으로부터 생성될 수 있다.

1 + 2 = 3
0 + 3 = 3
-1 + 4 = 3

텐서에서도 동일한 성질이 나타난다.

x₁ + y₁ = z
x₂ + y₂ = z

서로 다른 (x₁, y₁)와 (x₂, y₂)가 동일한 출력 z를 만들 수 있다.

따라서 다음 의미는 보장된다.

SemanticFact(
    "many_to_one",
    True,
    EvidenceLevel.GUARANTEED,
)

보다 명확한 설명은 다음과 같다.

서로 다른 결합 입력 쌍이 동일한 출력 텐서를 생성할 수 있다.


6. 비가역성

6.1 출력만으로 두 입력을 복원하는 경우

출력이 다음과 같이 주어졌다고 하자.

z = x + y

출력 z만 알고 있을 때는 x와 y를 각각 결정할 수 없다.

x = arbitrary tensor
y = z - x

임의의 x를 선택하면 그에 대응하는 y를 만들 수 있다.

따라서 하나의 출력에 대응하는 입력 쌍이 여러 개 존재한다.

"irreversible": True

그리고 다음 의미가 보장된다.

SemanticFact(
    "joint_input_recoverable",
    False,
    EvidenceLevel.GUARANTEED,
)

여기서 joint_input_recoverable=False는 다음을 의미한다.

출력만으로 원래의 두 입력 전체를 동시에 복원할 수 없다.


6.2 다른 한쪽 입력이 주어진 경우

출력 z와 입력 y가 주어지면 수학적으로 x는 다음과 같이 복원할 수 있다.

x = z - y

마찬가지로 출력 z와 입력 x가 주어지면 y를 복원할 수 있다.

y = z - x

따라서 이상적인 정확한 산술에서는 다음 성질이 성립한다.

"recoverable_given_other_input": True

하지만 이 성질은 실제 유한 정밀도 연산에서 항상 보장되지는 않는다.


7. 유한 정밀도에 의한 정보 손실

부동소수점 덧셈에서는 작은 값이 큰 값에 흡수될 수 있다.

예를 들어 float32에서 다음 연산을 생각할 수 있다.

x = np.float32(1.0)
y = np.float32(1e20)

z = x + y
recovered_x = z - y

이론적으로는 다음이 기대된다.

recovered_x = 1.0

하지만 실제 부동소수점 표현에서는 x가 y에 비해 너무 작기 때문에 덧셈 과정에서 사라질 수 있다.

z ≈ y
z - y = 0

따라서 다음 식은 일반적으로 보장되지 않는다.

(x + y) - y = x

이는 Add가 수학적으로는 다른 입력이 알려진 경우 역산 가능하지만, 실제 수치 연산에서는 정보 손실을 일으킬 수 있음을 의미한다.

그러므로 다음 의미를 GUARANTEED로 정의하는 것은 부정확하다.

SemanticFact(
    "recoverable_given_other_input",
    True,
    EvidenceLevel.GUARANTEED,
)

권장되는 정의는 다음과 같다.

SemanticFact(
    "recoverable_given_other_input",
    True,
    EvidenceLevel.IDEALIZED,
    "Recoverable by subtraction under exact arithmetic; "
    "finite-precision rounding or overflow may destroy information.",
)

IDEALIZED 수준을 사용하지 않는다면 CONDITIONAL로 표현할 수 있다.

EvidenceLevel.CONDITIONAL

8. 정수 연산과 overflow

고정 폭 정수 연산에서도 overflow가 발생할 수 있다.

예를 들어 8비트 signed integer의 표현 범위는 다음과 같다.

-128 ~ 127

다음 덧셈은 표현 범위를 벗어난다.

120 + 20 = 140

실제 실행 환경에서는 overflow 정책에 따라 wraparound, saturation 또는 오류가 발생할 수 있다.

따라서 정수 덧셈에서도 수학적인 정수 연산과 실제 실행 결과를 구분해야 한다.

다만 wraparound가 정확한 modular arithmetic으로 정의되어 있다면, 다른 한쪽 입력을 알고 있을 때 modular subtraction을 통해 원래 값을 복원할 수 있다.

복원 가능성은 프레임워크가 채택한 overflow 의미에 의존한다.


9. Broadcasting과 복원 가능성

다음 입력을 생각할 수 있다.

x.shape = (3, 1)
y.shape = (3, 4)

덧셈 과정에서 x는 다음과 같이 broadcasting된다.

broadcast(x).shape = (3, 4)

출력은 다음과 같다.

z = broadcast(x) + y

y를 알고 있으면 다음을 계산할 수 있다.

broadcast(x) = z - y

이상적인 산술에서는 broadcasting된 결과의 반복 축을 제거해 원래 x를 복원할 수 있다.

하지만 원래 shape를 복원하려면 입력의 TensorSpec이 필요하다.

x.shape = (3, 1)

따라서 broadcasting이 포함된 복원 가능성은 다음 정보를 전제로 한다.

  • 다른 한쪽 입력
  • 출력
  • 원래 입력의 shape 또는 TensorSpec
  • 정확한 산술

현재 그래프가 각 입력의 TensorSpec을 보존하므로 semantic fact의 이름을 간단히 유지할 수 있다.

"recoverable_given_other_input"

보다 엄밀한 이름은 다음과 같다.

"recoverable_given_other_input_and_input_spec"

10. 붕괴 관점에서의 해석

Add는 두 입력 공간의 정보를 동일한 출력 공간에 중첩한다.

입력이 각각 n개의 원소를 가진다고 하면 결합 입력은 총 2n개의 스칼라 값을 가진다.

(x, y) ∈ Rⁿ × Rⁿ

출력은 n개의 스칼라 값만 가진다.

z ∈ Rⁿ

따라서 Add는 결합 입력 공간을 더 낮은 차원의 출력 공간으로 사상한다.

R²ⁿ → Rⁿ

이 과정에서 두 입력 사이의 구분 정보가 사라진다.

예를 들어 출력의 특정 값이 3이라는 사실만으로는 다음 중 어떤 입력 조합이 사용되었는지 알 수 없다.

1 + 2
0 + 3
-1 + 4

이 의미에서 Add는 다음과 같은 붕괴적 특징을 가진다.

두 입력의 개별 기여를 하나의 합으로 압축하면서 입력 간 구분 정보를 제거한다.

다만 이 붕괴는 모든 정보를 무조건 제거하는 것은 아니다.

한쪽 입력이 별도로 보존되어 있다면 다른 입력을 역산할 수 있다.

따라서 Add의 붕괴는 다음과 같이 분류할 수 있다.

조건부 복원 가능한 병합 붕괴

또는 다음과 같이 표현할 수 있다.

jointly irreversible, conditionally recoverable merge

11. 권장 분석 정의

def analyze(
    self,
    inputs: list[TensorSpec],
    output: TensorSpec,
) -> AnalysisDict:
    return {
        "op_type": self.op_type,
        "parameter_count": 0,
        "linear": True,
        "nonlinear": False,
        "irreversible": True,
        "merge_kind": "elementwise_sum",
    }

각 항목의 의미는 다음과 같다.

parameter_count 0 학습 가능한 파라미터가 없음
linear True 두 명시적 입력의 결합 공간에 대해 선형
nonlinear False 비선형 활성화나 비선형 변환이 없음
irreversible True 출력만으로 두 입력 전체를 복원할 수 없음
merge_kind elementwise_sum 원소 단위 합을 통한 입력 병합

항목값의미


12. 권장 semantic facts 정의

def semantic_facts(
    self,
    inputs: list[TensorSpec],
    output: TensorSpec,
) -> tuple[SemanticFact, ...]:
    return (
        SemanticFact(
            "algebraic_kind",
            "jointly_linear_merge",
            EvidenceLevel.GUARANTEED,
            "Linear with respect to the joint input pair (x, y).",
        ),
        SemanticFact(
            "many_to_one",
            True,
            EvidenceLevel.GUARANTEED,
            "Distinct joint input pairs can produce the same output tensor.",
        ),
        SemanticFact(
            "joint_input_recoverable",
            False,
            EvidenceLevel.GUARANTEED,
            "The output alone does not determine both original inputs.",
        ),
        SemanticFact(
            "recoverable_given_other_input",
            True,
            EvidenceLevel.IDEALIZED,
            "Recoverable by subtraction under exact arithmetic; "
            "finite-precision rounding or overflow may destroy information.",
        ),
        SemanticFact(
            "merge_kind",
            "elementwise_sum",
            EvidenceLevel.GUARANTEED,
        ),
    )

13. EvidenceLevel 해석

Add의 의미를 정확히 표현하려면 수학적 성질과 실제 실행 성질을 구분할 필요가 있다.

권장되는 증거 수준은 다음과 같다.

class EvidenceLevel(Enum):
    GUARANTEED = "guaranteed"
    CONDITIONAL = "conditional"
    IDEALIZED = "idealized"
    HEURISTIC = "heuristic"
    EMPIRICAL = "empirical"

GUARANTEED

실제 프레임워크의 연산 의미에서도 항상 성립하는 성질이다.

예:

Add는 두 입력을 하나의 출력으로 병합한다.
출력만으로 원래 입력 쌍 전체를 복원할 수 없다.

CONDITIONAL

특정 dtype, shape, 값 또는 실행 조건에서만 성립한다.

예:

overflow가 발생하지 않는 정수 덧셈에서는 다른 입력을 이용해 복원할 수 있다.

IDEALIZED

무한 정밀도 실수 또는 정확한 대수 연산을 가정할 때 성립한다.

예:

x + y와 y가 주어지면 x를 정확히 복원할 수 있다.

HEURISTIC

수학적으로 보장되지는 않지만 일반적으로 관찰되는 경향이다.

EMPIRICAL

실제 실행 데이터나 runtime profiling을 통해 관측된 성질이다.


14. 최종 의미 요약

Add는 두 입력을 원소 단위로 합산하는 jointly linear merge 연산이다.

수학적으로는 선형 연산이지만 두 입력의 개별 정보를 하나의 합에 중첩하므로 출력만으로 원래 입력 쌍을 복원할 수 없다.

따라서 결합 입력에 대해서는 다대일이며 비가역적이다.

다른 한쪽 입력이 보존되어 있다면 이상적인 정확한 산술에서는 나머지 입력을 뺄셈으로 복원할 수 있다.

그러나 실제 부동소수점 및 고정 폭 정수 연산에서는 반올림과 overflow로 인해 정보가 손실될 수 있으므로, 이 복원 가능성은 실행 수준에서 보장되는 성질이 아니다.

Add의 핵심 의미는 다음과 같이 정리할 수 있다.

대수적 성질:
jointly linear

병합 방식:
elementwise sum

정보 구조:
many-to-one

전체 입력 복원:
불가능

한쪽 입력이 주어진 경우:
이상적인 산술에서는 복원 가능

붕괴적 특징:
두 입력의 개별 기여를 하나의 합으로 중첩하여
입력 간 구분 정보를 제거함