FMA: 같은 a×b+c가 0과 작은 음수로 갈리는 순간
a*b+c와 곱셈 후 덧셈을 두 명령으로 수행하는 코드는 수학적으로 같아 보인다. 그러나 저장 가능한 숫자의 정밀도가 제한되면 답이 달라질 수 있다. FMA는 fused multiply-add의 약자로, 곱과 합을 한 연산으로 묶어 중간 곱을 반올림하지 않고 최종 결과를 한 번 반올림하는 계약이다.
회로를 합쳐 명령 수를 줄이는 것만이 핵심은 아니다. 어디서 숫자를 버리는지 바꾸므로 관측되는 수치 결과가 달라진다.
0이 되는 답과 작은 음수가 되는 답
binary32, round-to-nearest-even을 가정하자. 유효숫자의 정밀도는 24비트다. 다음 세 값은 binary32로 정확하게 표현할 수 있다.
a = 1 + 2⁻²³
b = 1 − 2⁻²³
c = −1
정확한 곱은 (1+x)(1−x)=1−x²이므로 1−2⁻⁴⁶이다. 곱셈을 먼저 binary32로 반올림하면 1과 매우 가까워 1로 저장된다. 여기에 −1을 더하면 0이다.
반면 FMA는 정확한 곱의 작은 차이를 합 단계까지 유지한다. 1−2⁻⁴⁶−1=−2⁻⁴⁶이고 이 값 자체는 binary32로 정확히 표현할 수 있다. 따라서 FMA 결과는 −2⁻⁴⁶이다. 입력은 같지만 중간 반올림 하나가 상쇄 뒤의 답을 없앴다.
| 계산 계약 | 중간 곱 | 최종 값 |
|---|---|---|
| binary32 곱, 이어서 binary32 합 | 1로 반올림 | 0 |
| binary32 FMA | 곱의 차이를 내부 유지 | −2⁻⁴⁶ |
표는 형식과 반올림을 고정한 자체 수치 예다. 특정 GPU나 CPU의 측정 결과가 아니다.
작은 차이를 보관하려면 내부가 넓어야 한다
24비트 유효숫자 두 개의 곱은 더 넓은 유효숫자 정보를 만들 수 있다. FMA는 그 정보를 충분히 유지한 상태에서 addend의 지수를 맞추고 더하거나 뺀다. 특히 상쇄가 있을 때 낮은 곱 비트가 최종 결과의 높은 유효 비트가 될 수 있다.
Berkeley HardFloat의 FMA 문서는 mulAddRecFN과 raw 변형에서 single rounding을 명시한다. SoftFloat의 fused multiply-add 설명 역시 세 입력을 한 번의 최종 반올림으로 계산하는 의미를 정의한다. 넓은 내부 곱을 가진다는 것과 특정 사용자 형식에서 double precision 결과를 낸다는 것은 다르다.
단순히 좁은 multiplier의 반올림 결과를 adder에 연결하고 묶음 이름을 붙이면 위의 FMA 계약을 만족하지 않는다. 출력 bit 결과와 예외 flag를 포함한 수치 의미로 확인해야 한다.
정확도가 좋아져도 이전 프로그램과 같은 답은 아니다
한 번 덜 반올림하면 해당 곱셈·덧셈의 정확한 수학 결과에 더 가까운 답을 얻을 수 있다. 하지만 전체 알고리즘의 모든 오차가 줄거나, 결과가 기존 코드와 bit 단위로 같다는 뜻은 아니다. 이전 연산 순서가 중간 반올림을 포함했다면 변환 자체가 다른 수치 프로그램이다.
예를 들어 반복 계산에서 0과 작은 음수를 다른 분기로 사용하는 경우, 위 결과 차이가 제어 흐름을 바꿀 수 있다. bitwise 재현성이나 기존 reference model과의 비교가 요구된다면 FMA 사용 여부를 계산 계약에 포함해야 한다.
컴파일러가 별도 곱과 합을 FMA로 바꾸는지, 프로그램이 명시적으로 FMA를 요청했는지도 확인한다. 최적화 설정과 대상 명령이 달라졌는데 원래의 두 단계 반올림 모델을 그대로 정답으로 쓰면, 하드웨어가 올바르게 동작해도 mismatch가 날 수 있다.
특별값과 flag도 하나의 연산으로 정의된다
FMA는 정상 유한 입력만 다루는 식이 아니다. infinity, zero와 NaN 조합에서도 결과와 invalid flag를 정해야 한다. RISC-V FMA 명세는 infinity와 zero가 multiplicand일 때 addend가 quiet NaN이어도 invalid flag를 설정하도록 명시한다.
따라서 기준 모델이 “곱셈에서 NaN이면 뒤 계산을 생략”하는 식으로 예외를 처리하면 flag 계약을 놓칠 수 있다. 원하는 bit 결과가 NaN이라는 사실과 어떤 사건으로 그 결과가 생겼는지는 다른 관측 정보다.
반올림 모드, 부호 있는 0, overflow와 underflow도 목표의 정의를 따라야 한다. single rounding은 이 규칙들을 없애지 않고 최종 연산의 경계에서 적용하게 한다. 지원 형식과 명령의 세 입력 부호 변형도 별도 확인이 필요하다.
성능은 명령 이름만으로 정해지지 않는다
FMA는 하나의 명령이지만 내부 pipeline 단계와 실행 자원, 지수 정렬 경로를 가진다. latency와 새로운 요청을 받는 간격은 같지 않을 수 있다. 한 결과를 다음 FMA가 바로 읽는 의존 사슬과 독립적인 여러 FMA는 자원 활용 조건도 다르다.
회로 비용과 처리율을 비교할 때는 같은 형식·반올림 의미를 보존한 대상을 두어야 한다. 별도 반올림을 수행하는 작은 두 유닛과 single rounding 유닛은 수치 계약부터 다르다. 덜 정확한 다른 함수를 계산한 결과로 면적이나 속도만 비교하면 결론이 흐려진다.
FMA의 핵심은 곱셈과 덧셈을 가까이 붙인 모양이 아니라 중간 곱의 정보를 언제 버리느냐다. 한 번의 반올림을 요구한다면 내부 정밀도, 최종 bit 결과와 flag를 함께 검증해야 한다. 이 조건을 고정해야 0과 −2⁻⁴⁶의 차이를 오류와 의도된 결과 중 어느 것으로 볼지 판단할 수 있다.