반복 연산 시 정확도 유지 방안
부동소수점 연산 특성과 누적 오차
컴퓨터로 수치 계산을 수행하면 모든 실수를 정확하게 표현할 수 없다. 일반적으로 소수 부분을 유한 개 비트로 잘라 부동소수점 수로 근사하기 때문에, 사칙연산을 반복하면 아주 미세한 오차가 매번 더해지고 누적된다. 이러한 오차 누적 문제를 정확히 이해하기 위해서는 $\epsilon_{mach}$(머신 엡실론) 개념과 함께 각 연산 후 발생하는 반올림(또는 절단) 오차의 크기를 추정해야 한다. 예를 들어, $x, y$가 서로 다른 부동소수점 수일 때 덧셈 $x + y$를 실제 실수 연산과 비교하면 다음과 같은 관계로 나타낼 수 있다.
여기서 $\delta$는 부동소수점 반올림 과정에서 발생하는 상대적 오차를 의미한다. 반올림 연산이 여러 번 반복되는 알고리즘이라면, $\delta$가 매 스텝마다 누적될 수 있으므로 수치적 안정성을 갖추지 않은 알고리즘에서는 최종 결과가 큰 오차를 품을 위험이 있다.
이러한 누적 오차는 단순히 한 번의 사칙연산으로 발생하는 작은 $\delta$가 모여 거대하게 불어나기 때문이 아니라, 부분적으로는 "오차가 발생하기 쉬운 계산 순서"가 누적 오차를 증폭시키기 때문이다. 예컨대 $x \ll y$인 두 수 $x, y$를 더할 때, 상대적으로 매우 작은 $x$가 반올림되어 계산 과정에서 사라질 수 있다(큰 수와 작은 수를 직접 더하는 상황에서 흔히 일어나는 ‘소멸 현상’). 이를 방지하려면 덧셈 순서를 바꾸거나 보상(sum compensation) 연산 기법 등을 도입해야 한다.
반복 합산 과정에서의 안정성 유지 기법
가장 흔히 등장하는 반복 합산 문제를 생각해 보면, $x_i$($i=1,2,\ldots,n$)가 주어졌을 때 다음과 같은 합을 구하는 과정에서 많은 오차가 누적될 수 있다.
$n$이 매우 커질수록, 그리고 $x_i$의 크기 분포가 다양해질수록 단순 누산 방식으로는 수치적 안정성을 확보하기 어렵다. 이를 개선하는 방안 중 하나가 Kahan Summation 기법으로, 각 단계에서 사라질 뻔했던 작은 오차들을 보조 변수로 기록하여 오차 전파를 완화한다.
아래는 Python으로 Kahan Summation 기법을 구현해 반복 합산 시 오차를 줄이는 예시다.
# Python code
import math
import random
# 예시를 위해 작은 값과 큰 값을 섞어 난수 생성
values = [1e-12 * random.random() for _ in range(500000)]
values += [random.random() for _ in range(500000)]
def naive_sum(vals):
s = 0.0
for v in vals:
s += v
return s
def kahan_sum(vals):
s = 0.0
c = 0.0
for v in vals:
y = v - c
t = s + y
c = (t - s) - y
s = t
return s
print("Naive sum :", naive_sum(values))
print("Kahan sum :", kahan_sum(values))단순 합산(naive_sum)과 Kahan Summation(kahan_sum) 결과를 비교해 보면, 특히 $x_i$값이 매우 작거나 서로 스케일 차이가 큰 경우에 Kahan Summation이 훨씬 안정적인 결과를 준다는 것을 알 수 있다. 이러한 보상 기법을 도입하면 반복 연산에서 발생하는 누적 반올림 오차를 상당 부분 완화할 수 있다.
반복 연산에서의 조건수와 오차 증폭
수치 해석에서는 어떤 문제를 푸는 알고리즘의 ‘좋고 나쁨’을 단순히 오차 크기로만 평가하기 어려울 때, ‘조건수(condition number)’와 ‘안정성(stability)’ 개념을 이용해 본질적인 난이도를 파악한다. 반복 계산 구조를 포함한 수치 알고리즘에서 조건수는 문제 자체의 민감도를 반영하고, 안정성은 알고리즘이 문제의 민감도에 따른 오차를 얼마나 증폭하거나 완화하는지를 평가한다. 만약 알고리즘이 매우 안정적이라면, 실제 문제의 조건수가 다소 커도 반복 연산에서 오차가 폭발적으로 자라지 않는다.
선형 방정식 $\mathbf{A}\mathbf{x} = \mathbf{b}$를 풀이할 때 반복 연산(예: Jacobi, Gauss-Seidel, CG 등)을 사용하면, 각 단계의 연산이 축적되어 최종 해 $\mathbf{x}$가 반올림 오차로 왜곡될 가능성이 있다. 이때 $||\mathbf{A}||$의 크기와 역행렬 $||\mathbf{A}^{-1}||$의 크기를 모두 고려하여 문제의 조건수를 추정하면, 그 문제 자체가 얼마나 해에 민감한지 알 수 있다. 또한 반복법이 수렴해서 최종 단계에 도달했을 때, 이론적인 해와 부동소수점 환경에서 구해진 해 사이의 차이를 ‘전향 오차(forward error)’와 ‘후향 오차(backward error)’로 나누어 분석하기도 한다. 후향 오차 개념은 문제를 ‘살짝 변경한’ 정도에 해당하는데, 이것이 작다면 알고리즘이 안정적으로 동작했다고 본다.
반복 연산에서의 수치적 안정성과 병렬 연산
병렬 컴퓨팅 환경에서는 연산 순서가 변동될 수 있어, 시퀀셜 방식에서의 누적 오차와 달리 임의의 순서로 합산이 일어날 수 있다. 예컨대 병렬 합산 시 트리 형태로 여러 쓰레드가 동시에 부분합을 구하고, 최종적으로 이들을 합치는 방식이 일반적이다. 병렬 환경에서 일정한 오차 일관성을 유지하려면, 합산 혹은 반복 연산 과정을 알고리즘적으로 신중히 설계해야 한다. Kahan Summation을 병렬화하면, 각 쓰레드가 부분 집합에 대해 Kahan Summation을 적용한 뒤 최종적으로 병렬 Reduction을 수행함으로써 일부 오차 누적을 줄일 수 있다.
병렬 반복 알고리즘이 클수록(예: 수백만, 수억 개의 항을 합산) 부동소수점 표현의 한계가 더 두드러지며, 단계별 연산 재배치(associative 재배열)가 결과에 직접 영향을 주게 된다. 더 나아가 단정도(float) 대신 배정도(double) 혹은 사중 정밀도(quad precision)를 사용하여 기본적인 표현 한계를 늘리는 방법도 있지만, 당연히 연산 비용과 메모리 비용을 더 지불해야 한다. 따라서 문제의 규모와 필요한 정확도 사이에서 비용-효율적 절충안을 찾는 일이 중요하다.
반복 연산에서의 역분석(Backward Error Analysis)
단순히 결과의 전향적 차이만 보지 않고, "계산 과정에서 우리가 다룬 문제"가 실제 문제의 해와 얼만큼 다른지를 살피는 방법을 ‘역분석(Backward Error Analysis)’이라고 한다. 반복 연산에서는 각 단계에서 컴퓨터가 수행하는 유한정밀 연산을 수학적으로 엄밀하게 추적하기가 쉽지 않다. 하지만 역분석 방법론을 적용하면, "컴퓨터가 해결한 문제(오차가 포함된 행렬 혹은 벡터)는 얼마나 원래 문제에 가깝나"라는 점에 집중하게 된다. 만약 그 차이가 매우 작다면, 비록 중간 과정에서 반올림 오차가 존재하더라도 결과가 충분히 안정적으로 구해졌다고 볼 수 있다. 예컨대 선형 방정식 해법에서 반복 refinement 기법이 매우 유효한데, 이 기법은 "역방향"에서 시스템의 오차를 추정하고, 해당 오차를 통해 다시 보정하는 과정을 여러 번 반복한다.
연산 순서 재배치와 Catastrophic Cancellation
작은 수와 큰 수의 연산에서 발생하는 ‘소멸(cancellation)’은 반복 계산에서 특히나 자주 일어난다. 이 중 가장 위험한 형태가 Catastrophic Cancellation인데, 큰 두 수가 서로 비슷할 때 그 차이가 매우 작아지고, 이는 상대적으로 큰 반올림 오차를 일으킬 수 있다. 반복 알고리즘에서도 단계별로 유사한 크기의 수가 자주 등장하여 반복적으로 빼거나 더하게 되면 Catastrophic Cancellation이 발생해 결과가 크게 흔들릴 수 있다. 이를 완화하기 위한 한 가지 전형적인 방법은 연산 순서를 재배치하거나, 알고리즘 자체에서 발생 가능한 위험 구간을 미리 감지해 안전한 방식으로 우회시키는 것이다. 고전적인 예로, 2차 방정식의 근을 구할 때 $b^2 - 4ac$가 매우 작을 경우, 직접적으로 근의 공식을 적용하기보다 문제를 변형하여 안전한 수치 계산식을 택하는 것 등이 있다.
예측 불가능한 입력 데이터에 대한 방어
반복 연산 중 정확도를 유지하는 방안은 결국 입력 데이터의 분포와 문제의 조건수에 크게 의존한다. 온라인 처리나 실시간 분석 같이 입력 데이터가 사전에 예측 불가능한 환경이라면, 알고리즘 자체가 오차에 견고해야 한다. 이를 위해 오차 전파를 줄이는 수치 해석 기법을 사전에 탑재하거나, 반복 연산의 중간에 수치 안정도를 검사하고 필요하면 재정렬(reordering) 또는 보정(refinement) 과정을 삽입하는 식으로 설계한다. 이 때 병렬 연산 환경이라면 Race Condition이나 동기화 비용, 캐시 효율 등도 함께 고려되어야 한다.
최소제곱 문제와 반복 알고리즘에서의 오차 통제
수학적 모델링에서 오차가 포함된 측정값이나 데이터로부터 계수를 추정할 때, 일반적으로 최소제곱(Least Squares) 문제를 구성한다. 이 문제를 직접 QR 분해 등으로 푸는 방법도 있지만, 대규모 문제나 점진적(Online) 업데이트가 필요한 경우 반복 방식으로 접근하기도 한다. 그러나 반복 기반으로 최소제곱 문제를 풀면, 중간 계산에서 사소한 반올림 오차가 누적돼 결과가 크게 왜곡될 수 있다. 예컨대 정규방정식 $\mathbf{A}^T \mathbf{A} \mathbf{x} = \mathbf{A}^T \mathbf{b}$ 형태를 직접 반복으로 푸는 경우, $\mathbf{A}^T \mathbf{A}$가 매우 ill-conditioned라면 최소제곱 해에 대한 미묘한 변동이 엄청난 오차 증폭으로 이어질 수 있다.
안정적인 반복 알고리즘을 위해서는 최소제곱 문제에서 직접적으로 $\mathbf{A}^T \mathbf{A}$를 구하지 않고, Householder 변환이나 Givens 회전 등을 사용해 $\mathbf{A}$를 직교분해(Orthogonal Factorization)한 뒤 그 구조를 유지하며 반복법을 설계하기도 한다. 이때 Gram-Schmidt 정규직교화 방식을 단순히 적용하면 수치적 문제가 발생할 수 있지만, Modified Gram-Schmidt나 Householder 분해를 이용하면 훨씬 안정적인 결과를 얻을 수 있다. 이러한 기법들을 병렬 환경에서 구현할 때에도, 일관성 없는 연산 순서 변화로 인해 부분적인 오차가 갑자기 커지는 상황이 나타날 수 있음을 주의해야 한다.
그람-슈미트 정규직교화의 안정성
그람-슈미트(Gram-Schmidt) 정규직교화는 선형독립한 벡터들을 정규직교 집합으로 변환하는 가장 기초적인 알고리즘이다. 하지만 고전적 그람-슈미트(Classical Gram-Schmidt)는 부동소수점 연산으로 인한 소멸(cancellation) 문제가 심각해, 반복 연산으로 처리할 때 매우 불안정할 수 있다. 이를 개선한 수정 그람-슈미트(Modified Gram-Schmidt)는 각 단계에서 보다 안정적인 정규화와 투영 연산 순서를 택해, 반올림 오차를 줄이도록 설계되었다.
그람-슈미트 정규직교화에서 가장 흔히 발생하는 Catastrophic Cancellation은 서로 비슷한 크기의 내적 $\mathbf{u} \cdot \mathbf{v}$를 구할 때 반올림 손실이 커지는 과정이다. 수정 버전에서는 프로세스 내에서 한 벡터가 완전히 정규직교화되기 전에, 이전에 구했던 직교 벡터와의 내적을 여러 번 분할 계산하고 보상(summation compensation)을 더해 안정성을 높인다. 예컨대 Kahan Summation을 내적 계산에 부분 적용하고, 정상적인 범위 안에 있지 않은(너무 큰 혹은 너무 작은) 항은 중간 단계에서 따로 처리하여 오차 전파를 억제한다.
반복 연산에서의 혼합 정밀도(Mixed Precision) 기법
고정된 단일 정밀도(float)나 배정도(double)를 사용하는 대신, 상황에 따라 더 정밀한(또는 더 낮은 정밀도의) 부동소수점 연산을 혼합해 쓰는 방법이 있다. 이를 혼합 정밀도(Mixed Precision) 기법이라 한다. 반복 알고리즘 설계 시, 큰 연산 부하는 단정도 연산으로 빠르게 수행하되, 핵심 단계나 보정(refinement) 단계에는 배정도나 사중 정밀도(quad precision)를 사용해 오차를 크게 줄이는 식이다.
행렬-벡터 곱셈이나 큰 합산 연산은 단정도로 처리해도 결과가 충분히 정확하게 수렴하는 반면, 해를 업데이트하는 마지막 정규화 과정이나 잔차(residual)를 판단하는 구간에는 배정도를 활용하여, 극단적인 오차 누적을 막을 수 있다. 특히 GPU 기반 병렬 계산 환경에서 혼합 정밀도는 매우 일반적이며, 실제로 상당히 좋은 성능과 정확도를 모두 확보할 수 있다는 연구 결과가 많다.
연산 순서 최적화와 Pairwise Summation
덧셈 연산은 결합 법칙이 엄밀히 성립하지 않는다. 즉 $(a + b) + c$와 $a + (b + c)$는 부동소수점 오차를 고려하면 완전히 동일한 결과를 보장하지 않는다. 반복 연산에서 이를 최소화하기 위해, 모든 항을 한 줄로 단순 순회하며 덧셈하는 대신, Pairwise Summation 방식을 활용할 수 있다.
Pairwise Summation은 벡터 내의 항들을 쌍으로 묶어 먼저 합한 뒤, 그 결과들끼리 또 다시 합해나가는 식으로 트리 구조의 합산을 시도한다. 이렇게 하면 큰 항과 작은 항이 극단적으로 만나서 발생하는 소멸 현상을 어느 정도 방지할 수 있다. 병렬 연산과도 자연스럽게 어울리는데, 쓰레드 각각이 자기 할당 구간의 항들을 Pairwise Summation으로 처리하고, 마지막에 트리 형태로 합치는 과정을 수행한다면, 단순 순서 합산보다 더 나은 수치적 정확도를 기대할 수 있다.
잔차 기반 반복 보정(Iterative Refinement)의 흐름
반복 연산에서 오차를 줄이는 대표적인 기법 중 하나는 반복 보정(Iterative Refinement)이다. 일반적으로 선형 방정식 $\mathbf{A}\mathbf{x} = \mathbf{b}$를 일차적으로 해 $\mathbf{x}^{(0)}$로 구한 뒤, 잔차 $\mathbf{r} = \mathbf{b} - \mathbf{A}\mathbf{x}^{(0)}$를 계산하고, 이를 다시 보정량 $\Delta \mathbf{x}$로 변환해 $\mathbf{x}^{(1)} = \mathbf{x}^{(0)} + \Delta \mathbf{x}$ 형식으로 해를 개선한다. 이 과정을 필요할 만큼 반복한다.
이런 식으로 역오차(Backward Error) 관점에서 $\mathbf{x}^{(k)}$가 ‘실제 문제’에 점점 가까워지는지를 모니터링한다. 정밀도가 낮은 계산 환경에서 처음 해를 구하더라도, 반복 보정 단계에서 더 높은 정밀도의 연산을 사용해 잔차를 구하고 해를 재보정함으로써 전반적인 해의 정확도가 향상될 수 있다.
아래는 반복 보정의 흐름을 간단하게 나타낸 Mermaid 플로우차트 예시다.
현실적으로 $\mathbf{A}^{-1}$를 직접 구하지 않고, 같은 반복 해법(혹은 다른 더 안정적인 방법)으로 $\Delta \mathbf{x}$를 근사한다. 이 때도 수치적 안정성을 좀 더 높이기 위해 Kahan Summation, Pairwise Summation, 혹은 혼합 정밀도 기법을 적극 도입할 수 있다.
RNG와 난수 기반 반복 시뮬레이션에서의 수치 안정성
몬테카를로(Monte Carlo) 방법이나 랜덤 프로세스 시뮬레이션에서는 난수를 다량 발생시키고 이들을 반복 연산에 투입한다. 이 과정에서 오차 누적 문제가 더욱 민감해질 수 있는데, 특히 평균값이나 분산 등을 계산할 때 발생하는 반올림 손실이 크다. 병렬로 수많은 난수를 동시에 생성해 처리할 경우, 연산 순서가 비결정적으로 바뀔 수 있고 결과의 재현성(reproducibility)이 저하될 수 있다. 이를 극복하기 위해서는 통계적 방법(예: 여러 시드(seed)로 독립 수행 후 결과를 종합) 또는 수치 보상(sum compensation) 기법을 병행해 오차를 줄이는 전략을 사용한다.
난수 기반 시뮬레이션은 입력 데이터가 스스로 높은 불확실성을 가지고 있기 때문에, 계산 오차가 결과 해석에 아주 크게 영향을 주지 않을 수도 있다. 그러나 매우 긴 시뮬레이션이나 민감도 분석(sensitivity analysis)을 수행하려면, 작은 반올림 오차가 결과 전체를 왜곡하지 않는지 정기적으로 확인하는 과정이 필요하다.
정밀도 확장(Arbitrary Precision)의 활용
컴퓨터 내부에서의 부동소수점 표현은 기본적으로 이진부호화 방식(IEEE 754 등)에 따라 단정도(float) 혹은 배정도(double) 수준의 정밀도를 제공한다. 하지만 특정 문제에서는 이보다 훨씬 높은 정밀도가 필요하기도 하다. 예컨대 반복 연산으로 진행될 때, 결과가 매우 민감한 함수 계산(예: 여러 루프를 거쳐야 하는 초월함수 계산)이나, 극단적으로 큰 혹은 작은 수가 섞여 있어서 배정도만으로도 수치적 안정성이 불충분한 경우에는, 임의 정밀(Arbitrary Precision) 라이브러리를 사용하여 소프트웨어적으로 정밀도를 확장할 수 있다.
이러한 정밀도 확장 기법을 사용하는 주요 동기는 다음과 같다. 첫째, 반복 과정에서 소멸(cancellation) 문제가 극심할 때 부동소수점 반올림 오차가 일정 수준 이상으로 쌓이지 않도록 안정적인 정밀도를 확보한다. 둘째, 일반적인 환경에서 무시할 수준의 $\epsilon_{mach}$가, 반복 계산을 통해 특정 중간 변수에 비정상적으로 큰 비율의 영향력을 주게 될 때 이를 차단한다. 예시로, 다항식 근 찾기(특히 고차 다항식)를 반복 알고리즘으로 풀이하는 경우, 배정도 precision만으로는 수렴 과정에서 해가 불안정하게 튀거나 소수점 자릿수를 보존하지 못하는 상황이 나타난다.
물론 임의 정밀 연산은 속도가 느리며, 구현 복잡도가 높아진다는 치명적 단점이 있으므로, 병렬 연산 환경에서는 큰 오버헤드를 초래할 수 있다. 따라서 "어떤 단계에서만" 특별히 높은 정밀도 연산을 적용하고, 나머지는 배정도 수준에서 빠르게 처리하는 식으로 혼합하는 전략도 병행한다. 한편, Python에서는 decimal 모듈이나 mpmath 라이브러리를, C++에서는 GMP/MPFR 계열 라이브러리를 활용해 임의 정밀 연산을 구현할 수 있다.
수치미분과 반복 계산에서의 반올림 오차
수치미분(Numerical Differentiation) 과정에서 작은 증가량 $h$를 사용하여 차분(예: 전진 차분 $\frac{f(x+h) - f(x)}{h}$)을 구하면, $h$가 너무 작을 경우 함숫값의 차이가 반올림 오차 범위 내로 가라앉아버리는 문제가 발생할 수 있다. 즉, $f(x+h)$와 $f(x)$가 너무 비슷해지면 그 차이가 유효숫자 몇 자리 수준에서 소멸되어, 실제 미분값이 왜곡된다. 반복 연산 시에 이런 미세한 오차가 누적되면, 추정된 도함수 값이 상당히 불안정해질 수 있다.
이를 완화하기 위해서는 $h$를 단순히 ‘매우 작게’만 잡는 것이 아니라, 부동소수점 표현 한계와 $f$의 스케일을 함께 고려해 적절히 설정해야 한다. Central Difference(중심 차분)나 고차 차분 공식을 사용하면 정확도가 더 좋아질 뿐 아니라, $h$에 대해 조금 덜 민감해진다. 그러나 반복 연산으로 미분을 여러 번 수행하거나, 고차 미분값을 추정하는 경우라면, 반올림 오차가 여러 차례 누적되어 결과가 크게 왜곡될 수 있으므로 수치적 안정성을 좀 더 엄밀히 분석해야 한다.
예컨대 $n$번째 미분값을 반복 차분으로 구할 때, 각 차분 단계마다 작은 차가 반올림으로 인해 크게 달라질 수 있다. 극단적인 경우엔 "미분값을 구하는 과정"이 ill-conditioned가 되어, 수치적으로 해석하기 어려운 결과가 나온다. 수치미분 문제에서도 Catastrophic Cancellation이 잘 일어나므로, 적절한 보상 연산이나, 고차 차분 없이도 근사도를 높일 수 있는 보간(interpolation) 기반 방법 등을 고민해볼 수 있다.
GPU/컴파일러 최적화와 반복 알고리즘
GPU나 벡터화 SIMD(AVX, SSE 등)를 이용해 반복 연산을 병렬화할 경우, 컴파일러 혹은 런타임 환경이 연산 순서를 자동으로 최적화(rearrange)하거나, FMA(Fused Multiply-Add) 등을 사용해 성능을 높인다. 이 과정에서 반올림 오차의 누적 형태가 CPU 시퀀셜 계산과 달라질 수 있다. FMA는 $a \times b + c$ 연산을 한 번의 라운딩으로 처리하기 때문에 일반적인 곱셈-덧셈보다 반올림 오차가 작다는 장점이 있지만, 특정 경우에는 중간 단계에서의 정밀도 보상이 달라질 수 있다.
또한, GPU 커널 내부에서 여러 쓰레드가 같은 데이터를 서로 다른 순서로 처리하면, 실제 계산 결과가 쓰레드 스케줄링이나 동기화 타이밍에 따라 비결정적이 될 수 있다. 반복 알고리즘에서는 단순한 합산이라도 쓰레드마다 분산된 항의 크기나 순서가 달라지면 결과 오차가 달라진다. 이를 완화하는 전략으로는 (1) 고정된 순서로 Reduction을 수행하도록 로직을 설계하거나, (2) Pairwise Summation 혹은 Kahan Summation을 병렬화하는 방식을 택하거나, (3) 연산 순서를 확정적으로 결정하는 Sync Barrier(동기화 장벽) 사용 등이 있다. 물론 이때 성능 저하와 정확도 향상 사이에서 균형점을 찾아야 한다.
동적 재정렬(Dynamic Reordering)과 자동 튜닝(Autotuning)
반복 연산의 순서는 가능하면 ‘잘 정렬된(ordered)’ 상태로 유지하는 것이 바람직하지만, 실시간 데이터가 유입되거나 중간 계산이 무작위로 섞이는 경우에는 구현 단계에서 일일이 수작업으로 순서를 고정하기 어렵다. 동적 재정렬(Dynamic Reordering)은 실행 중에 데이터 블록 혹은 연산 순서를 재배치하여 오차가 커지는 상황을 피하는 기법이다. 일종의 자가 적응(Adaptive) 알고리즘으로, 중간 결과를 모니터링해 위험도가 감지되면 연산 순서를 다시 묶어주거나, 더 안전한 보상 연산으로 전환한다.
자동 튜닝(Autotuning)은 수치 알고리즘 구현 시, 서로 다른 매개변수(블록 크기, 쓰레드 할당, FMA 사용 여부 등)를 여러 모드로 시도해 보고, 가장 효율적인(또는 안정적인) 세팅을 동적으로 결정하는 기술이다. 반복 연산에서 이런 autotuning을 도입하면, 어느 조건에서 병렬화 스레드 수를 어떻게 배분해야 오차가 커지지 않는지, 어느 단계에서 혼합 정밀도를 적용해야 계산 비용 대비 정확도 개선이 최적화되는지 등을 실험적으로 찾아낼 수 있다.
수치 안전성 향상을 위한 테스트 전략
정해진 입력 데이터만으로는 반복 연산에서의 오차 누적 양상을 충분히 파악하기 힘들 수 있다. 특히 알고리즘의 수치 안전성을 확인하기 위해서는, (1) 극단값이나 경계값 테스트(예: 매우 큰 값, 매우 작은 값, 서로 크기가 극단적으로 다른 값), (2) 난수 기반 스트레스 테스트, (3) 비정형 데이터(예: 선형종속에 가까운 벡터들, 강하게 ill-conditioned 행렬 등)를 적극 활용할 필요가 있다. 이런 테스트 전략을 통해 알고리즘의 한계를 찾고, 실제 사용자에게 치명적 오류가 발생하기 전에 미리 대비책(재정렬, 정밀도 확장, 보상 합산 등)을 설계할 수 있다.
예컨대 선형 방정식 반복 해법을 개발한다면, $\mathbf{A}$가 거의 특이(singular)에 가까운 행렬, 대각 성분이 매우 작은 행렬, 스펙트럼 분포가 편향된 행렬 등을 준비해두고, 해당 경우에 수렴 속도와 잔차가 어떻게 변화하는지 꼼꼼히 살핀다. 만약 특정 형태의 입력에 대해 부동소수점 오차가 과도하게 커진다면, 알고리즘 수정 혹은 보정 기법을 추가 적용하는 것이 바람직하다.
선형 반복 해법과 전처리(Preconditioning)
직접 해법으로 $\mathbf{A}\mathbf{x}=\mathbf{b}$를 풀기 어려운 대규모 문제나 희소(sparse) 행렬 문제에 자주 등장하는 방법이 반복(iterative) 해법이다. 대표적인 알고리즘으로 CG(Conjugate Gradient), GMRES(Generalized Minimal Residual), BiCGSTAB 등이 있다. 이들은 단계별로 $\mathbf{x}$를 갱신하면서 오차(잔차)를 줄여나가는데, 연산을 반복하기 때문에 부동소수점 반올림이 누적될 위험이 있다. 이때 "전처리(Preconditioning)"를 사용하면, $\mathbf{A}$ 자체를 직접 다루는 대신 전처리 행렬 $\mathbf{M}$(또는 $\mathbf{M}^{-1}$)을 곱해 해석적으로 동일한 문제
로 바꾼 후, 이 새로운 계수를 사용해 반복 알고리즘을 진행한다. $\mathbf{M}$를 잘 골라서 $\mathbf{M}^{-1}\mathbf{A}$의 조건수(condition number)를 낮추면, 잔차 감소 속도가 빨라지고 반복 횟수가 줄어든다. 그 결과 전체 연산 횟수, 그리고 부동소수점 연산으로 인해 발생하는 누적 오차가 큰 폭으로 감소한다.
GMRES 알고리즘과 수치 안정성
GMRES는 최소제곱 원리를 이용하여, Krylov 공간에서 잔차를 가장 작게 만드는 해를 단계적으로 구성한다. 한편, 반복 단계가 쌓여가면서 직교화 과정(Arnoldi 절차)에서 누적 오차가 커질 수 있다. 이때 Modified Gram-Schmidt나 Householder 반사(Reflection) 방식을 섞어 수치적으로 안전한 직교화를 수행하고, 필요하다면 restart 기법을 적용해 쓸데없이 큰 Krylov 차원을 막는다. 즉, GMRES($m$)과 같은 재시작(리스타트) 기법을 사용하면, 무작정 차원이 커지면서 직교화가 불안정해지는 사태를 피할 수 있다.
전처리된 GMRES는 $\mathbf{M}^{-1}\mathbf{A}\mathbf{x} = \mathbf{M}^{-1}\mathbf{b}$ 형태에 대해 Arnoldi 절차를 수행한다. 적절한 전처리 행렬 $\mathbf{M}$를 선택하면, 유효차원(실질적으로 GMRES가 커버하는 Krylov 공간의 크기) 안에서 충분한 정확도로 해를 수렴시키며 반올림 오차를 통제하기가 수월해진다. 물론 전처리 행렬 역시 희소성을 유지할 수 있도록 잘 설계해야, 반복 과정의 효율과 안정성이 동시에 보장된다.
CG 알고리즘에서의 반올림 오차 분석
CG(Conjugate Gradient)는 대칭양의정부호(SPD) 행렬 문제에 최적화된 반복 해법이다. CG는 잔차 벡터를 직교(conjugate) 성분으로 분해하며, 각 단계에서 방향 벡터를 갱신하고 해를 업데이트한다. 순수 이론 관점에서 CG는 유한 번(최대 $n$번) 안에 정확한 해에 도달할 수도 있지만, 실제 부동소수점 환경에서는 반올림 오차 때문에 직교성이 완벽하게 지켜지지 않고, 곧 "직교 붕괴(Orthogonality Loss)" 문제가 발생한다.
이를 방지하기 위해서는 세심한 오차 보정 기법이 필요하다. 예컨대 방향 벡터 갱신 시 Kahan Summation을 적용하거나, 내적 계산에서 Modified Gram-Schmidt를 사용하는 등 미세한 반올림 오차를 줄이는 다양한 세부 기법을 도입할 수 있다. 또한 전처리를 함께 사용하면, CG의 전체 반복 횟수가 줄어듦과 동시에 반복 단계가 지속될수록 커질 수 있는 수치적 불안정성도 완화된다.
Multi-Precision Preconditioning과 하이브리드 접근
혼합 정밀도(Mixed Precision) 전처리 기법은, 큰 스케일의 반복 계산은 빠른 단정도(float) 연산으로 진행하되, 전처리 연산(또는 핵심 부분)을 배정도(double) 이상으로 처리하는 방식이다. 예컨대 전처리 행렬 $\mathbf{M}$를 구축할 때는 배정도 정밀도로 안정성을 높이고, 반복 알고리즘 본체(예: Sparse Matrix-Vector 곱)은 단정도로 빠르게 수행한다. 이 하이브리드 방식은 GPU 기반 대규모 연산에서 점차 널리 사용되며, 대규모 문제를 효율적으로 해결하면서도 반올림 오차 누적을 제한하려는 목표를 달성한다.
실제로 CG, BiCGSTAB, GMRES 같은 고차 방정식 반복 해법에 높은 정밀도의 전처리 방법을 섞으면, 적은 반복 횟수 안에 비교적 정확한 해에 근접하여 중간 반올림 오차가 줄어든다. 물론 전처리 행렬 구성(혹은 업데이트)이 병렬화나 메모리 사용 측면에서 부하가 커질 수 있으므로, 문제 스펙에 맞추어 적절히 균형을 맞춰야 한다.
적응형 스텝사이즈(Adaptive Step Size)와 안정성
비선형 방정식이나 미분방정식을 반복적으로 푸는 과정에서는, 스텝사이즈를 적절히 조절하는 것이 핵심이다. 뉴턴-라프슨(Newton-Raphson) 방법이나 $\mathbf{x}_{k+1} = \mathbf{x}_k - \alpha \nabla f(\mathbf{x}_k)$ 형태의 경사하강법에서 $\alpha$가 지나치게 크면 발산, 지나치게 작으면 수렴 속도가 너무 느려진다. 수치적 안정성을 확보하기 위해선, 스텝사이즈를 "문제의 스케일"과 "반올림 오차 한계"에 맞게 동적으로 조절해야 한다.
예컨대 수치미분이나 Jacobian 근사 등을 통해 구한 방향벡터의 정밀도가 부족할 경우, 스텝사이즈가 부정확하게 설정되어 오차 증폭을 일으킬 수 있다. 이때는 혼합 정밀도 기법으로 Jacobian(또는 Hessian)을 더 정확하게 계산하거나, backtracking line search로 스텝사이즈를 보수적으로 조정함으로써 안정적인 수렴을 도모한다.
부분 갱신(Partial Update)과 백트래킹
비선형 반복법에서 해를 대규모 벡터(혹은 행렬)로 취급할 경우, 각 단계에서 전체를 일시에 갱신하는 대신, "부분 갱신(Partial Update)"을 적용해 국소적으로 보정하는 기법을 사용할 수 있다. 일부 컴포넌트만 갱신하고 나머지는 유지하면서, 혹여 발생할 대규모 소멸 현상을 완화한다.
백트래킹(Backtracking)은 스텝이 과도하게 크거나, 소멸 현상이 우려되는 상황에서 단계별로 $\alpha$를 줄여 나가며 안전한 지점을 찾는다. 이 과정을 반복 계산에 결합하면, 반올림 오차가 누적되어도 해가 불안정하게 튀지 않고 서서히 수렴 경로에 붙잡힐 가능성이 높아진다.
해석적 비교(Analytical Verification)와 수치 해석
반복 연산 결과를 해석적으로 검증해볼 수 있는 형태라면, 사전에 일부 구간에서 정확한(또는 매우 높은 정밀도로 계산된) 해를 구해 두고, 반복 알고리즘이 산출한 해와 비교하는 방식을 사용할 수 있다. 예컨대 특정 매개변수에 대해 문제가 단순화되거나, 작게 축소된 테스트 케이스에서 해를 정밀 계산해 둔다. 이후 실제 대규모 반복 연산이 이와 유사한 스케일로 확장되었을 때, 오차가 어느 정도 비율로 증폭되는지 모니터링하면, 반올림 오차가 폭발적으로 커지는 시점을 미리 감지할 수 있다.
이런 과정은 단순히 "숫자를 비교"하는 것만이 아니라, "오차가 왜 발생했는지"에 대한 원인 분석에도 활용된다. 예컨대 소멸(cancellation)이 집중적으로 일어나는 부분이 특정 조건에서만 발생한다면, 실제 계산 순서를 재정렬해 이 구간을 우회하거나, 더 정밀한 연산을 그 구간에만 적용함으로써 전반적인 누적 오차를 줄일 수 있다.
반복 연산과 패킷 손실(Numerical Fault Tolerance)
고성능 컴퓨팅(HPC) 환경에서 노드 장애나 통신 에러로 인해 중간 단계의 데이터를 잃는 경우도 발생한다. 이는 네트워크의 패킷 손실이나 GPU 메모리 장애 등으로 이어져, 반복 알고리즘의 중간 결과가 손상될 수 있다. 이때 단순히 "다시 처음부터" 계산하는 것은 비용이 크므로, 체크포인트(checkpoint)를 통해 일정 단계마다 데이터를 저장해 두고, 장애가 발생하면 해당 단계부터 다시 진행하기도 한다. 그러나 체크포인트 주기가 너무 길면 비용이 커지고, 너무 짧으면 수치적 안정성 관점에서 복구 불확실성이 커진다.
현대 수치 해석 연구에서는 "적응형 장애 복원(Adaptive Fault Tolerance)" 기법도 강조된다. 예컨대 현재 잔차가 충분히 작아 진 경우 체크포인트를 찍고, 다음 단계에서 오류가 나면 이를 활용해 해를 재생성한다. 반복 연산 자체가 내재적으로 잔차를 줄여가는 과정을 가진 만큼, 부분적 데이터 손실이 발생해도 비교적 빠르게 원래 궤도로 복구될 가능성이 있다. 물론 이런 과정에서도 부동소수점 반올림 오차가 추가로 끼어들 수 있으므로, 장애 복원 로직에 오차 추정을 포함해 설계하는 것이 바람직하다.
결합된 문제에서의 반복 연산
실제 공학 문제나 과학 계산에서는, 서로 다른 물리 방정식을 동시에 풀어야 하는 "결합된 문제(coupled problem)"가 자주 등장한다. 예컨대 열전달 방정식과 유체역학 방정식을 함께 풀어야 하는 상황에서, 각 부분 문제별로 반복 알고리즘을 병렬로 돌리다가, 일정 단계에서 상호 간의 경계 조건을 주고받는다. 이런 다중 물리(Multiphysics) 환경에서는, 한 쪽 부분 문제의 오차가 다른 쪽 문제로 확산되어 전체 해가 왜곡될 위험이 더 높아진다.
이를 완화하려면, 결합 계(system coupling)에서 반복 순서를 정해주거나(교대 혹은 동시 반복), 데이터 교환 시 오차가 커지지 않도록 수치 안정화 기법을 적용한다. 또한 각 부분 문제에 대해 전처리나 보상 합산을 세심하게 적용하고, 혹시라도 반올림 오차가 폭발적으로 커지는 구간을 실시간 모니터링해 필요하면 부분 정밀도 확장 기법을 활용한다.
Last updated