AEGLOSLAB ChatGPT 같은 AI를 만들고 서비스하기

제2부 사전학습(Pretraining)

2.6 학습 안정화 — 혼합정밀도·발산 대응

수천 장의 GPU로 수 주에서 수 개월을 실행하는 대규모 사전학습에서 가장 값비싼 실패는 학습이 중단되는 것이다. 손실 곡선이 매끄럽게 내려가다 어느 순간 위로 급등하고(loss spike), 몇 스텝 못 가 NaN이 되고, 그러면 마지막 정상 체크포인트로 되감아 그 사이의 계산 전체를 버린다. 이 손실이 얼마나 큰지는 계산으로 드러난다. 1024장 규모의 클러스터에서 스텝당 3초, 체크포인트를 2000스텝마다 저장한다면, 발산이 하필 체크포인트 직전에 발생했을 때 손실되는 것은 약 $2000 \times 3 = 6000$ 초, 곧 1.7시간의 벽시계 시간이고 GPU-시간으로는 $1.7 \times 1024 \approx 1740$ GPU-시간이다. 클라우드 단가를 GPU-시간당 2달러로 낮게 설정해도 한 번의 스파이크에 수천 달러가 사라지며, 그런 발산이 재시작을 몇 번씩 요구하면 이 손실이 배가된다. 그래서 대규모 학습에서 "모델을 죽지 않게 굴리는 기술"은 곁가지가 아니라 본론이다.

이 죽음은 크게 두 갈래에서 온다. 하나는 수(numeric)의 문제다. 학습을 빠르고 경량으로 하려고 정밀도를 FP32에서 FP16·BF16·FP8로 낮추는 순간, 표현할 수 있는 수의 범위와 해상도가 좁아진다 — 그 경계를 넘는 값이 나오면 오버플로로 무한대가 되거나 언더플로로 0이 되어 학습 신호가 사라진다. 다른 하나는 동역학(dynamics)의 문제다. 학습률·데이터·구조가 맞물려 어떤 값이 통제를 벗어나 커지면 손실이 스파이크를 내고 발산한다. 앞 갈래의 대응이 혼합정밀도·손실 스케일링·마스터 웨이트이고, 뒤 갈래의 대응이 그래디언트 클리핑·모니터링·재시작·안정화 트릭이다. 둘은 서로 얽혀 있다 — FP16의 좁은 범위가 작은 스파이크를 발산으로 키우고, 통제를 벗어나 커진 어텐션 로짓이 저정밀도의 한계를 때려 수치를 붕괴시킨다. 이 장은 두 갈래를 차례로, 그러나 얽힘을 놓치지 않고 다룬다.

미리 명시해 둘 것이 하나 있다. 이 장에 나오는 구체적 하이퍼파라미터 — 손실 스케일 초기값, 클리핑 임계, FP8 최대치, z-loss 계수 — 는 레시피·구현·하드웨어마다 다르다. 특정 숫자를 정답으로 외우지 말고 "무엇이 무엇을 막으려는 장치인가"의 구조로 읽어야 한다. 그리고 이 장 후반의 안정화 트릭 상당수는 이론적으로 완결된 처방이 아니라 "이 조건에서 도움이 됐다"는 경험칙이다. 어디까지가 확립된 공학이고 어디부터가 경험적 처방인지, 그 경계를 흐리지 않고 표시하겠다.

2.6.1 지수는 범위를, 가수는 정밀도를 정한다

혼합정밀도를 이해하려면 부동소수점 수가 실제로 어떻게 저장되는지부터 손에 쥐어야 한다. 한 실수는 세 조각으로 분할해 저장된다.

$$x = (-1)^{\text{sign}} \times (1 + \text{mantissa}) \times 2^{\,\text{exponent} - \text{bias}}$$

부호(sign) 1비트는 양수·음수를 가른다. 지수(exponent)는 수의 크기, 즉 $2$ 의 몇 제곱 근처인지를 정한다 — 지수 비트가 많을수록 담을 수 있는 값의 범위(dynamic range)가 넓어져, 아주 큰 수와 아주 작은 수를 동시에 표현할 수 있다. 가수(mantissa·significand)는 그 크기 안에서의 해상도를 정한다 — 가수 비트가 많을수록 인접한 두 표현 가능한 수 사이가 조밀해져 정밀도가 높다.

핵심 대립은 여기 있다. 총 비트 예산이 정해지면 지수와 가수는 서로 경쟁한다. 지수에 비트를 더 주면 범위가 넓어지는 대신 정밀도가 거칠어지고, 가수에 더 주면 반대가 된다. 이 한 줄의 교환이 이 장 전체의 저류다 — FP16·BF16·FP8이 갈리는 지점도, 대규모 학습이 BF16으로 수렴한 이유도 전부 이 교환을 어느 쪽으로 했느냐에서 나온다.

부호 지수=범위 가수=정밀도 FP32 지수 8 가수 23 FP16 지수 5 가수 10 BF16 지수 8 가수 7 FP8 E4M3 지수 4 · 가수 3 FP8 E5M2 지수 5 · 가수 2 BF16의 지수 8비트는 FP32와 같다 → 표현 범위 동일(오버· 언더플로 회피). 대신 가수를 23→7로 줄여 정밀도를 내줬다.
부동소수점 포맷 — 비트 배분(부호·지수=범위·가수=정밀도).

그림에서 챙길 관찰은 두 가지다. 첫째, BF16은 FP32에서 가수만 잘라낸 것이다 — 지수 8비트를 그대로 물려받았으니 FP32가 담는 수의 범위를 똑같이 담고, 정밀도만 거칠어진다. 둘째, FP16은 반대의 선택을 했다 — 지수를 5비트로 줄여 범위를 좁힌 대신 가수를 10비트로 늘려 BF16보다 오히려 정밀하다. 같은 16비트인데 FP16과 BF16은 정확히 반대편에 서 있고, 이 한 차이가 뒤에 나올 모든 이야기를 가른다. FP8 두 형제(E4M3·E5M2)도 8비트라는 더 제한적인 예산 안에서 같은 선택을 각자 다르게 한 것이다.

2.6.2 범위와 정밀도를 직접 계산하기

"범위가 넓다·정밀도가 거칠다"는 말은 실제 숫자를 뽑아 봐야 명확해진다. FP16을 예로 끝까지 계산해 보자. FP16은 지수 5비트, 가수 10비트, 바이어스 15다.

가장 큰 유한값부터. 지수 필드의 최대는 $11111_2 = 31$ 인데 이것은 무한대·NaN에 예약돼 있으므로, 정규수의 최대 지수 필드는 $30$ 이라 실효 지수는 $30 - 15 = 15$ 다. 가수는 전부 1이면 $1.1111111111_2 = 2 - 2^{-10}$ 이다. 따라서

$$x_{\max} = (2 - 2^{-10}) \times 2^{15} = (2 - 0.000977) \times 32768 \approx 65504$$

FP16이 표현할 수 있는 가장 큰 수가 겨우 $65504$ 다. 가장 작은 정규값은 지수 필드 $1$(실효 지수 $-14$), 가수 $1.0$ 일 때 $2^{-14} \approx 6.1\times10^{-5}$ 이고, 그보다 작은 값은 서브노멀 구간에서 정밀도를 잃어 가다 $2^{-24} \approx 6\times10^{-8}$ 아래로 가면 0으로 언더플로한다. 그리고 $1.0$ 바로 옆 수와의 간격(ulp)은 $2^{-10} \approx 9.8\times10^{-4}$ 이라, 상대 오차의 상한(단위 반올림)은 그 절반인 $2^{-11} \approx 4.9\times10^{-4}$ 다.

BF16은 지수 8비트라 FP32와 바이어스·범위가 같아 최대 $\approx 3.4\times10^{38}$, 최소 정규값 $\approx 1.2\times10^{-38}$ 이고, 가수가 7비트뿐이라 $1.0$ 옆 간격이 $2^{-7} \approx 7.8\times10^{-3}$, 단위 반올림 $2^{-8} \approx 3.9\times10^{-3}$ 로 FP16보다도 거칠다. FP8 E5M2도 같은 방식으로 최대 $(2-2^{-2})\times2^{15} = 1.75\times32768 = 57344$ 가 나온다. E4M3는 표준(OCP FP8)이 무한대 인코딩을 최대값으로 재활용해 최대가 $448$ 로 정의돼 있다 — 이 값은 유도가 아니라 규약이므로 그대로 외운다.

포맷비트지수가수최대 유한값(약)최소 정규값(약)$1.0$ 옆 간격(약)십진 유효자리(약)
FP3232823$3.4\times10^{38}$$1.2\times10^{-38}$$1.2\times10^{-7}$~7
FP1616510$6.6\times10^{4}$$6.1\times10^{-5}$$9.8\times10^{-4}$~3
BF161687$3.4\times10^{38}$$1.2\times10^{-38}$$7.8\times10^{-3}$~2–3
FP8 E4M3843$448$$\sim1.5\times10^{-2}$$1.25\times10^{-1}$~1
FP8 E5M2852$5.7\times10^{4}$$\sim6\times10^{-5}$$2.5\times10^{-1}$~1

이 표에서 FP16 학습의 두 위험 요소가 곧장 읽힌다. 최대값이 $65504$ 라는 것은, 학습 중 어떤 값 — 활성값·그래디언트·어텐션 로짓 — 이 이 장벽을 넘으면 즉시 Inf가 되고 이어 NaN으로 번진다는 뜻이다. 이것이 첫 번째 위험 요소다. 최소 정규값이 $6.1\times10^{-5}$ 라는 것은, 그보다 작은 그래디언트가 서브노멀을 거쳐 결국 0으로 언더플로해 학습 신호 전체가 사라진다는 뜻이다. 이것이 두 번째 위험 요소고, 손실 스케일링(2.6.7)이 정확히 이것을 겨냥한다. BF16에는 이 두 위험 요소가 원천적으로 없다 — 범위가 FP32와 같으니 오버플로·언더플로가 실질적으로 일어나지 않는다. 대신 간격이 $7.8\times10^{-3}$ 로 정밀도가 거칠고, 이 거친 정밀도를 어디서 감당하느냐가 다음 절들의 주제다.

지수 비트 하나의 차이가 왜 이렇게 큰지는 정리해 둘 가치가 있다. 지수는 $2$ 의 거듭제곱을 정하므로 비트 하나가 범위를 대략 제곱만큼 넓히지만, 가수 비트 하나는 정밀도를 2배 조밀하게 할 뿐이다. 딥러닝 학습에서 값들은 층·파라미터·학습 단계에 따라 수십 자릿수(orders of magnitude)에 걸쳐 흩어진다 — 그래서 "조밀함"보다 "넓음"이 대개 더 급하다. FP16이 지수 하나를 아껴 정밀도를 산 것이 그토록 자주 후회로 돌아오는 이유가 여기 있다.

2.6.3 왜 정밀도를 낮추나 — 메모리와 텐서 코어

FP32로만 학습하면 위의 위험 요소가 전혀 없어 안정적이다. 그런데도 저정밀도로 내려가는 이유는 두 가지 자원이 아깝기 때문이다.

메모리가 첫째다. 파라미터·그래디언트·활성값이 전부 원소당 4바이트인데, 이를 2바이트로 줄이면 같은 GPU에 두 배를 적재하거나 더 큰 배치·모델을 담는다. 대규모 학습에서 VRAM은 항상 부족한 자원이라(2.4), 이 절반은 곧바로 배치 크기나 시퀀스 길이로 환산된다. 속도가 둘째다. 현대 GPU의 텐서 코어(Tensor Core)는 FP16·BF16·FP8 행렬곱을 FP32보다 몇 배 빠르게 처리하고, FP8까지 내려가면 다시 배가 된다. 신경망 계산의 대부분이 행렬곱(GEMM)이므로 이 가속이 곧 학습 처리량이고, 처리량은 곧 같은 예산으로 볼 수 있는 토큰 수, 즉 최종 모델 품질이다(2.2).

문제는 순진하게 "전부 FP16으로" 하면 앞 절의 두 위험 요소와 거친 정밀도가 겹쳐 학습이 붕괴한다는 것이다. 그래서 이름이 혼합(mixed) 정밀도다 — 연산은 낮은 정밀도로 빠르게 하되, 정밀도가 결정적으로 중요한 지점만 골라 FP32로 지킨다. 어디를 FP32로 지켜야 하는가, 그것을 저정밀도의 어떤 치명적 오류로부터 지키는가가 다음 두 절이다.

2.6.4 표준 레시피 — 마스터 웨이트·FP32 누산·FP32 옵티마이저

혼합정밀도의 고전 레시피(Micikevicius 등, 2017 계열)는 세 장치로 요약된다. 연산은 저정밀도로 흘러가지만, 그 흐름의 세 지점에 FP32가 앵커처럼 고정되어 있다.

캐스트 ↓ FP32로 캐스트 · (FP16이면 언스케일) 갱신분 적용 마스터 웨이트 (FP32) 옵티마이저가 갱신하는 원본 연산용 웨이트 (BF16/FP16) 순전파 (BF16/FP16 GEMM) 누산은 FP32 활성값 (BF16/FP16) 역전파 (BF16/FP16 GEMM) 누산은 FP32 그래디언트 (BF16/FP16) AdamW 갱신 (FP32) m·v 상태도 FP32

첫 장치는 FP32 마스터 웨이트다. 옵티마이저는 FP32 원본을 들고 갱신하고, 순전파·역전파에 쓸 때만 저정밀도로 캐스트한 사본을 만든다. 왜 이것이 필수인지는 다음 절에서 따로 다룬다. 둘째는 FP32 누산이다. 텐서 코어는 입력을 저정밀도로 받아도 곱들의 합만은 FP32 레지스터에 쌓는다 — $d_{model}$ 개 항의 긴 내적을 저정밀도로 누적하면 반올림 오차가 쌓여 값이 망가지므로, 누산은 하드웨어가 대신 FP32로 해 준다. 셋째는 FP32 옵티마이저 상태다. AdamW의 1·2차 모멘트 $m, v$(1.3·2.3)는 미세한 통계라, 저정밀도로 두면 작은 값이 반올림에 불안정해져 학습이 불안정해진다.

여기서 흔히 오해되는 지점을 계산으로 확인하자. "저정밀도로 하면 메모리가 반으로 준다"는 통념은 파라미터·옵티마이저 버킷에는 맞지 않는다. BF16 혼합정밀도의 파라미터당 상시 메모리를 세어 보면, BF16 웨이트 2B + BF16 그래디언트 2B + FP32 마스터 웨이트 4B + FP32 $m$ 4B + FP32 $v$ 4B = 16B/파라미터다(활성값 별도). 그런데 FP32 단독 학습도 FP32 웨이트 4 + 그래디언트 4 + $m$ 4 + $v$ 4 = 16B로 사실상 같다. 7B 모델이면 어느 쪽이든 이 버킷만 $7\times10^{9} \times 16 = 112$ GB이고, 마스터 웨이트와 옵티마이저 상태가 그 대부분을 차지한다. 즉 혼합정밀도의 메모리 절감은 파라미터 상태가 아니라 활성값(순전파에서 역전파까지 들고 있어야 하는 중간값이 절반으로)과 속도(텐서 코어 가속)에서 온다. 파라미터만 보고 "반이 줄겠지"라고 용량을 계획하면 어긋난다.

2.6.5 소실되는 갱신 — 마스터 웨이트가 FP32여야 하는 이유

마스터 웨이트가 왜 필수인지가 이 절이다. AdamW 갱신은 $w \leftarrow w - \eta\,\Delta$ 꼴인데, 학습 후반이면 갱신분 $\eta\Delta$ 가 가중치 $w$ 에 비해 극도로 작아진다. 파라미터가 대략 자리를 잡은 뒤에는 한 스텝의 이동이 미세 조정이기 때문이다. $w \approx 1.0$, 갱신분 $\eta\Delta \approx 1\times10^{-5}$ 인 전형적 상황을 상정해 두 정밀도에서 무슨 일이 벌어지는지 보자.

BF16에 직접 더하면, $1.0$ 옆의 표현 가능한 다음 수는 $1.0 + 2^{-7} = 1.0078\ldots$ 다. 갱신분 $10^{-5}$ 는 이 간격($7.8\times10^{-3}$)의 약 780분의 1이라, 반올림되어 전부 사라진다. $w$ 는 $1.0$ 그대로 남고, 다음 스텝에도 같은 크기의 갱신이 또 소실된다. 작은 갱신이 아무리 여러 번 와도 매번 개별적으로 반올림되어 없어지므로, 저정밀도 웨이트는 학습 후반에 사실상 얼어붙는다. 이것을 삼킴(swamping)이라 부른다.

FP32에 더하면 이야기가 다르다. $1.0$ 옆 간격이 $2^{-23} \approx 1.2\times10^{-7}$ 이라, 갱신분 $10^{-5}$ 는 그 간격의 약 80배다 — 온전히 반영되고도 남는다. 그래서 마스터 웨이트를 FP32로 두면 작은 갱신을 오랜 스텝에 걸쳐 누적할 수 있다. 핵심은 이 누적이다. 개별 갱신은 저정밀도 간격보다 작아도, FP32에 쌓으면 수백·수천 스텝 뒤 누적분이 저정밀도 간격을 넘어서 저정밀도 웨이트에도 반영된다. 마스터 웨이트가 없으면 이 누적 통로가 막혀 학습 후반의 미세 개선 전체가 손실된다. 정밀도가 거친 BF16일수록 이 문제가 심하다는 점도 위 계산이 그대로 보여 준다 — 간격이 넓을수록 더 큰 갱신까지 소실되기 때문이다.

여기에 곁가지 하나. 일부 옵티마이저(8비트 Adam 계열)는 마스터 웨이트를 저정밀도에 두되 확률적 반올림(stochastic rounding)으로 삼킴을 우회한다 — 작은 갱신을 위·아래로 확률적으로 반올림해 기댓값으로는 누적되게 하는 트릭이다. 메모리를 아끼는 데 효과가 있으나 레시피·구현 의존이 크고, 대규모 사전학습의 기본은 여전히 FP32 마스터 웨이트다.

2.6.6 왜 대규모는 대개 BF16인가

특정 벤더·모델을 단정하진 않되, 업계 다수가 대규모 사전학습에 BF16을 택하는 경향은 뚜렷하다. 그 근거는 FP16과의 정면 비교에서 나온다.

기준FP16BF16
표현 범위좁음(max $\approx 65504$) → 오버·언더플로 위험FP32와 동일 → 사실상 위험 없음
정밀도높음(가수 10)낮음(가수 7)
손실 스케일링필요(언더플로 방지)대개 불필요
운영 복잡도스케일 튜닝·오버플로 감시단순(캐스트만)
활성·그래디언트 극단값취약(스파이크에 곧장 Inf)견딤

이 표의 무게중심은 하나로 모인다. 대규모 학습의 적은 "정밀도 부족"이 아니라 "범위 초과"다. 층이 깊고 파라미터가 많으면 활성·그래디언트·어텐션 로짓이 넓은 크기 범위에 흩어지고, 학습 중 예기치 않게 커지는 값이 FP16의 $65504$ 장벽을 때린다 — 스파이크 한 번이면 곧장 Inf다. BF16은 그 장벽이 없어 손실 스케일링이라는 운영 부담 없이 운영되고, 정밀도 손실은 FP32 누산과 마스터 웨이트가 상당 부분 흡수한다. 그래서 "범위를 사고 정밀도를 판다"가 대규모에서 대체로 남는 장사가 된다.

다만 이것을 "BF16이 항상 낫다"로 읽으면 틀린다. FP16 + 손실 스케일링으로도 잘 학습된 대형 모델은 많았고, 하드웨어가 BF16을 지원하지 않던 시절에는 FP16이 사실상 유일한 선택이었다. 정밀도가 특별히 중요한 특정 연산이나 기존 FP16 파이프라인을 물려받은 경우에는 FP16이 여전히 합리적이다. 균형 잡힌 서술은 이렇다 — BF16을 지원하는 하드웨어에서 새 사전학습을 시작한다면, 운영이 단순하고 발산 위험이 낮은 BF16이 합리적 기본값이다. 내 견해로는 "범위가 넓어 사고가 덜 난다"는 운영상의 이점이 정밀도 손실보다 대개 크다. 그러나 이것은 조건부 판단이지 물리 법칙이 아니다.

2.6.7 손실 스케일링 — FP16 언더플로 막기

BF16을 쓰면 이 절은 대개 건너뛴다. 그래도 정확히 지적하는 이유는 두 가지다 — FP16을 쓰는 파이프라인이 여전히 있고, FP8 스케일링(2.6.9)의 원형이 바로 여기 있기 때문이다.

문제는 그래디언트가 0으로 사라지는 것이다. 역전파의 그래디언트는 흔히 아주 작다 — 학습이 진행되거나 층이 깊으면 $10^{-5}$, $10^{-7}$ 규모가 흔하다. FP16의 최소 정규값이 $6.1\times10^{-5}$ 이므로, 그보다 작은 그래디언트는 서브노멀을 거쳐 0으로 언더플로한다. 0이 된 그래디언트는 아무 학습도 일으키지 못한다. FP16 그래디언트 히스토그램을 실제로 측정해 보면 상당 비율이 표현 범위 아래에 놓여 있다.

해법은 우아하다. 역전파는 선형이므로, 손실에 상수 $S$ 를 곱하면 모든 그래디언트가 정확히 $S$ 배가 된다.

$$\tilde L = S\cdot L \quad\Rightarrow\quad \frac{\partial \tilde L}{\partial w} = S\cdot\frac{\partial L}{\partial w}$$

그래서 역전파 전에 손실을 $S$ 배(예: $S = 1024 = 2^{10}$)하면 그래디언트 전체가 $S$ 배 커져 FP16의 표현 범위 안으로 들어온다 — 언더플로를 탈출한다. 그리고 옵티마이저 스텝 전에 그래디언트를 다시 $1/S$ 로 언스케일(unscale)해 원래 크기로 되돌린 뒤 갱신한다. $S$ 를 $2$ 의 거듭제곱으로 설정하면 곱·나눗셈이 지수 필드만 변경해 추가 반올림 오차가 없다 — 이것이 스케일을 항상 $2$ 의 거듭제곱으로 두는 이유다.

× S (스케일) ÷ S (언스케일, FP32) 손실 L S·L 역전파 그래디언트가 S배 → FP16 범위 안 스케일된 그래디언트 g·S 원래 그래디언트 g 클리핑·AdamW 갱신

순서에 함정이 하나 있다. 언스케일은 반드시 클리핑·갱신 전에 해야 한다. 그래디언트 클리핑 임계값(2.6.12)은 원래 스케일 기준이므로, 언스케일하지 않은 채 클리핑하면 임계가 $S$ 배만큼 어긋난다 — 조용히 학습을 망치는 흔한 버그다.

이제 $S$ 를 얼마로 설정하느냐가 남는다. $S$ 가 너무 작으면 여전히 언더플로가 남고, 너무 크면 그래디언트가 반대로 FP16 상한을 넘어 오버플로한다. 게다가 최적 $S$ 는 학습이 진행되며 변한다. 그래서 동적 손실 스케일링이 이를 자동으로 추적한다 — 큰 $S$(예: $2^{16}$)로 시작해, 매 스텝 그래디언트에 Inf/NaN이 있는지 검사한다. 있으면 이번 스텝을 버리고(파라미터를 갱신하지 않는다) $S$ 를 절반으로 줄인다. 없으면 정상 갱신하고, 일정 스텝(예: 2000) 동안 오버플로가 없었으면 $S$ 를 2배로 늘려 본다. 이렇게 하면 $S$ 가 "오버플로 직전의 최대"를 자동으로 추적해, 언더플로는 최소화하면서 오버플로는 스텝 폐기로 방어한다. 오버플로로 버리는 스텝은 불리하지만 대개 전체의 극소수라 무시할 만하다.

2.6.8 수계산 — 스케일이 살리는 그래디언트

한 그래디언트 성분 $g = 3\times10^{-8}$ 을 두고 스케일이 실제로 무엇을 하는지 끝까지 따라가자. 이 값은 FP16 최소 정규값 $6.1\times10^{-5}$ 은 물론 최소 서브노멀 $6\times10^{-8}$ 보다도 작다.

스케일 없이 FP16에 저장하면, $3\times10^{-8}$ 은 표현 가능한 가장 작은 값보다도 작아 그대로 0으로 언더플로한다. 이 성분에 해당하는 파라미터는 이번 스텝에 갱신되지 않는다 — 학습 신호 전체가 버려진 것이다.

이제 $S = 2^{20} \approx 1.05\times10^{6}$ 으로 손실을 스케일하면, 역전파를 거친 이 성분은 $g\cdot S = 3\times10^{-8} \times 1.05\times10^{6} \approx 3.1\times10^{-2}$ 가 된다. $0.031$ 은 FP16 범위의 한가운데라 명료하게 표현된다 — 언더플로가 없다. 옵티마이저 스텝 직전에 FP32에서 언스케일하면 $3.1\times10^{-2} / 1.05\times10^{6} = 3\times10^{-8}$ 이 정확히 복원되어, 옵티마이저가 이 작은 신호를 반영한다. 스케일 하나로 죽었던 신호가 살아났다.

"작은 것을 표현 범위의 좋은 자리로 크게 옮겼다가, 쓸 때 되돌린다"는 이 발상이 손실 스케일링의 전부이고, 다음 절 FP8 스케일링에서 전역 상수 $S$ 하나가 텐서마다의 스케일 계수로 일반화된다.

2.6.9 FP8 학습과 per-tensor 스케일링

FP8은 8비트로 GEMM을 실행해 BF16보다 또 빨라지고 메모리도 줄이려는 흐름이다. 그러나 정밀도가 극도로 거칠고(가수 2~3비트) 범위도 좁아, FP16보다 훨씬 정교한 스케일링이 필요하다. 대규모 사전학습 전체를 FP8로 하는 것은 아직 성숙 중이며, 실무는 흔히 일부 GEMM만 FP8로 하고 나머지는 BF16으로 섞는다 — 레시피 표준화가 미완이라 새 값을 신뢰하려면 프레임워크·하드웨어 문서 확인이 필요하다.

FP8은 하나가 아니라 둘을 역할별로 쓴다는 점이 먼저다. E4M3(지수4·가수3)는 범위가 좁으나(max $\approx 448$) 가수가 하나 더 많아 상대적으로 정밀해서, 순전파의 웨이트·활성처럼 정밀도가 더 필요한 텐서에 쓴다. E5M2(지수5·가수2)는 범위가 넓고(max $\approx 57344$) 정밀도가 더 거칠어서, 역전파의 그래디언트처럼 넓은 크기 범위가 필요한 텐서에 쓴다. 이 역할 분담은 2.6.2에서 본 "그래디언트엔 범위, 순전파엔 정밀도"라는 원리를 8비트 안에서 다시 적용한 것이다.

FP8의 범위가 워낙 좁아, 손실 스케일 하나(전역 $S$)로는 부족하다. 대신 텐서(또는 텐서 블록)마다 자기 스케일 계수를 둔다 — per-tensor scaling이다. 아이디어는 손실 스케일링과 같다. 텐서의 값들을 FP8 표현 범위의 좋은 자리로 옮겼다가 GEMM 후 되돌린다.

$$x_{\text{fp8}} = \text{round}_{\text{fp8}}\!\left(\frac{x}{s}\right), \qquad s = \frac{\text{amax}(x)}{f_{\max}}$$

여기서 $\text{amax}(x)$ 는 그 텐서의 절댓값 최대이고 $f_{\max}$ 는 해당 FP8 포맷의 최대 표현값이다. 이렇게 스케일을 설정하면 텐서의 가장 큰 값이 FP8 상한 근처에 오게 되어, 좁은 범위를 최대한 활용하면서 오버플로를 피한다. 텐서마다 값의 크기대가 다르므로 각자 스케일을 두면 전역 스케일 하나보다 훨씬 잘 맞고, 최근에는 텐서 안을 더 잘게 나눠 블록 단위로 스케일을 두어 정밀도를 더 확보하는 방식도 쓴다(대신 오버헤드가 는다).

한 가지 실무 문제가 더 있다. 스케일 $s$ 를 정하려면 amax가 필요한데, amax를 매 스텝 실시간으로 재려면 GEMM 전에 텐서를 한 번 훑어야 해 느리다. 그래서 지연 스케일링(delayed scaling)을 쓴다 — 최근 몇 스텝의 amax 이력(history)을 저장해, 이번 스텝의 스케일을 그 이력으로 예측한다. 실시간 스캔을 피해 빠르지만, amax가 갑자기 급등하면(스파이크) 예측이 빗나가 오버플로할 수 있어 안전장치가 필요하다. 정리하면, 정밀도가 낮아질수록 "값을 표현 범위의 좋은 자리로 옮기는" 스케일링을 더 잘게(전역→텐서→블록), 더 조심스럽게(실시간→지연+안전장치) 해야 한다. FP16의 전역 손실 스케일과 FP8의 per-tensor·delayed scaling은 같은 발상의 점진적 정교화이고, 추론용 FP8·INT8 양자화(5.2)도 이 per-tensor·per-channel 스케일 발상을 공유한다.

2.6.10 무엇이 학습을 중단시키나 — 손실 스파이크와 발산

이제 수의 문제에서 동역학의 문제로 넘어간다. 대규모 학습에서 손실 곡선은 매끈하게 내려가지 않는다. 갑자기 위로 급등하는 손실 스파이크가 흔하고, 그중 일부는 스스로 회복하지만 일부는 발산해 NaN으로 끝난다.

학습 스텝 손실 마지막 정상 체크포인트로 되감기 → 일시적 스파이크(자가 회복) 발산(회복 실패) ✕ NaN 문제 배치 스킵 후 재개
손실 곡선의 스파이크 — 자가 회복형과 발산형

스파이크와 발산은 대개 다섯 갈래 중 하나이거나 여럿이 겹친 것이다. 진단은 이 갈래를 좁혀 가는 일이다.

원인무엇이 벌어지나흔한 신호
학습률 과다스텝이 손실 골짜기를 건너뛰어 발산(1.3의 1차원 예시)그래디언트 노름 급증, 웜업 직후·피크 LR 구간에서 발생
불량 데이터 배치깨진·중복·이상 배치(반복 토큰·비정상 문자열)가 큰 손실·그래디언트 유발특정 스텝에서만 스파이크, 그 배치 제거 시 사라짐
수치 오버플로어텐션 로짓·활성이 커져 FP16 범위 초과, 또는 softmax 분모 폭주Inf/NaN이 특정 층·연산에서 시작, FP16에서 흔함
초기화·구조 불안정나쁜 초기화·정규화 배치로 활성·잔차 노름이 층 따라 폭증(1.3)극초반부터 노름이 비정상, Post-LN 깊은 스택
누적 불안정어텐션·출력 로짓이 서서히 커지다 임계에서 붕괴스파이크 전 수천 스텝에 걸쳐 특정 노름이 완만히 상승

두 가지를 강조할 만하다. 첫째, 불량 데이터는 과소평가된 주범이다. 수조 토큰 코퍼스(2.1)에는 반드시 이상 구간이 있고, 하필 그 배치가 큰 그래디언트를 만들면 스파이크가 된다. "같은 스텝에서 반복 재현되면 데이터, 재시작하니 사라지면 데이터 순서나 난수"가 첫 감별점이다. 둘째, 누적 불안정이 가장 까다롭다. 원인이 그 스텝이 아니라 수천 스텝 전부터 자라 온 것이라, 스파이크 순간만 봐선 드러나지 않는다. 그래서 노름류 지표를 장기 추세로 봐야 하고(2.6.11), 여기서 z-loss·QK-norm 같은 트릭이 등장한다(2.6.13).

스파이크가 때로 스스로 회복하는 이유도 정리해 두면 대응 판단이 선다. 일시적 스파이크가 회복되는 것은 AdamW의 적응적 스케일·그래디언트 클리핑·정규화가 튄 그래디언트를 한두 스텝 흡수해 다시 궤도로 끌어오기 때문이다. 반대로 클리핑도 못 막을 만큼 크거나 파라미터가 이미 나쁜 영역으로 넘어가 버리면 회복하지 못하고 발산한다. 그래서 실무 관점은 이렇다 — 작은 스파이크는 지켜보되, 되돌아오지 않거나 그래디언트 노름이 계속 치솟으면 즉시 개입한다.

2.6.11 진단 — 무엇을 모니터링하나

발산을 막는 첫걸음은 보는 것이다. 대규모 학습은 실시간 대시보드에 여러 지표를 기록하는데, 각 지표가 어떤 고장을 드러내는가가 핵심이다.

지표무엇인가무엇을 드러내나
손실학습·검증 손실전반 건강. 스파이크·정체·발산의 1차 신호
그래디언트 노름전역 $\lVert g\rVert_2$(클리핑 전)발산의 가장 빠른 조기 경보. 손실보다 먼저 급등한다
파라미터 노름층별·전역 $\lVert w\rVert$가중치 폭주·붕괴, weight decay 균형
활성 통계층별 활성·어텐션 로짓의 max·평균·분산수치 오버플로 임박, 누적 불안정(로짓 성장)
학습률현재 스케줄 값스파이크가 피크 LR·웜업 구간과 겹치나
처리량초당 토큰·MFU·스텝 시간성능 회귀·통신 병목·하드웨어 이상(2.5)
손실 스케일 / amaxFP16 동적 스케일, FP8 amax 이력오버플로 빈도, 스케일이 최저 수준에 머물면 문제
스킵 스텝 비율오버플로·이상으로 버린 스텝 %높으면 정밀도·데이터·LR 문제

이 중 그래디언트 노름이 조기 경보의 왕이다. 그래디언트 노름은 손실보다 먼저 반응한다 — 손실이 아직 정상으로 보여도 그래디언트 노름이 평소의 수 배로 급증하면 몇 스텝 안에 스파이크가 온다. 그래서 대규모 학습은 이 값을 가장 면밀히 본다. 전역 노름은

$$\lVert g\rVert_2 = \sqrt{\sum_{p}\lVert g_p\rVert_2^2}$$

로, 모든 파라미터 그래디언트를 하나의 벡터로 본 크기다. 이 값은 그래디언트 클리핑의 기준이기도 하다(2.6.12). 임계는 절대값이 아니라 "평소 대비 몇 배 튀는지"라는 상대 배수로 설정하는 것이 실무적이다 — 학습 단계마다 평소 노름 자체가 변하기 때문이다.

활성·로짓 통계는 다른 렌즈다. 누적 불안정은 어텐션 로짓의 최댓값이나 출력 로짓의 크기가 수천 스텝에 걸쳐 서서히 커지는 것으로 나타나곤 한다. 어텐션 로짓 $q\cdot k/\sqrt{d_k}$ 가 커지면 softmax가 포화하고(1.4.6에서 본 스케일링이 겨냥한 바로 그 병이 대규모에서 실제로 발생한다 — 1.4.18), 어느 순간 수치가 붕괴한다. 그래서 층별 활성 max·어텐션 로짓 max를 장기 추세로 기록해, "완만한 상승"이 보이면 스파이크가 발생하기 전에 z-loss·QK-norm 같은 대책을 넣는다(2.6.13). 이 사전 관찰이 사후 재시작보다 훨씬 저렴하다는 것이 핵심이다.

2.6.12 대응 — 클리핑·LR·배치 스킵·재시작

원인을 좁혔으면 대응한다. 아래 흐름이 실무의 골격이고, 순서 자체에 원칙이 담겨 있다 — 저렴하고 안전한 조치를 앞에, 비용이 크고 위험한 조치를 뒤에 둔다.

예 아니오(스파이크만) 아니오 예 예 → 데이터 의심 스파이크·발산 감지 (grad norm↑ 또는 loss↑ 또는 NaN) NaN/Inf 즉시? 그래디언트 클리핑 동작 중인가? 마지막 정상 체크포인트로 즉시 되감기 같은 스텝에서 반복 재현? 전역 노름 클리핑 켜기·강화 문제 배치· 구간 식별→스킵 다른 seed· 데이터 순서로 재개 재개 후 grad norm·loss 감시 아니오 → 순서/난수
발산 즉시 대응(1/2) — 감지부터 재개까지
예 아니오 재개 후 grad norm·loss 감시 여전히 발산? 피크 LR 하향· 웜업 연장 정상 학습 지속
발산 대응(2/2) — 재개 후 모니터링과 재발 대응

그래디언트 클리핑이 발산의 1차 방어선이다(1.3). 전역 노름이 임계 $\tau$ 를 넘으면 그래디언트 전체를 비례 축소한다.

$$g \leftarrow g \cdot \min\!\left(1,\ \frac{\tau}{\lVert g\rVert_2}\right)$$

방향은 보존하고 크기만 $\tau$ 이하로 자른다. 튄 그래디언트 한 방에 파라미터가 나쁜 영역으로 이탈하는 것을 막아, 일시적 스파이크가 발산으로 번지지 않게 한다. 임계 $\tau$(흔히 $1.0$ 근처지만 레시피마다 다르다)는 평소 노름보다 크되 스파이크는 자를 정도로 설정한다. FP16이면 앞서 못 박았듯 언스케일 후에 클리핑해야 한다.

학습률 과다가 원인이면 피크 LR을 낮추거나(예: 20~30% 하향) 웜업을 늘린다. 이미 진행 중인 학습이라면 스파이크 직전 체크포인트에서 낮춘 LR로 재개한다. 발산이 특정 구간(피크 도달 직후)에서 반복되면 LR 스케줄 자체를 의심한다. 다만 LR을 너무 낮추면 학습이 느려지므로, 발산을 막는 최소한으로만 낮추는 것이 합리적이다.

데이터가 원인으로 좁혀지면 그 배치나 구간을 건너뛴다. 두 방식이 있다. 온라인 스킵은 그래디언트 노름이 임계를 크게 넘는 스텝의 파라미터 갱신을 건너뛰는 것으로(동적 손실 스케일링의 오버플로 스킵과 같은 발상), 자동이라 단순하지만 실제 원인이 데이터인지 다른 것인지는 가리지 못한다. 오프라인 제거는 재현되는 문제 구간을 코퍼스에서 찾아 정제·제외하고 재시작하는 것으로(2.1), 근본 대책이지만 원인 배치를 특정하는 수고가 든다.

NaN이 이미 났으면 되돌릴 수 없다 — 마지막 정상 체크포인트로 되감아 재개한다. 이때 그대로 재개하면 같은 데이터·순서로 같은 스파이크가 재발하기 쉽다. 그래서 재개 시 반드시 하나 이상을 바꾼다 — 문제 배치 스킵, 데이터 순서나 seed 변경, LR 하향, 클리핑 강화. 되감기 후 아무것도 바꾸지 않고 재개하는 것은 이 절에서 가장 흔한 함정이다. 결정적 요소(데이터 순서)가 그대로면 발산이 그대로 재현되어 시간만 버린다. 그리고 되감기 손실의 크기는 체크포인트 간격이 정한다 — 서두에서 계산했듯 간격이 넓을수록 한 번의 발산에 손실되는 GPU-시간이 커지므로, 저장 비용과 재계산 손실을 비교 평가해 간격을 설정한다(체크포인팅 메커니즘은 2.5).

2.6.13 안정화 트릭 — 각각 무엇을 막나

여기서 다루는 기법들은 효과의 확실성이 제각각이다. 일부는 잘 이해된 실패 모드를 겨냥한 거의 검증된 공학이고, 일부는 "이 레시피에서 도움이 됐다"는 경험칙이다. 공통 주제는 하나다 — 어떤 값이 무한정 커지는 것을 막는다. 어느 쪽이 어느 신뢰도인지 문장으로 분명히 하겠다.

z-loss는 출력(또는 MoE 라우터) 로짓이 서서히 커져 softmax 정규화가 불안정해지는 누적 불안정을 겨냥한다. softmax의 분모(분배함수)의 로그가 0 근처에 머물도록 보조 손실을 더한다.

$$L_{\text{total}} = L_{\text{CE}} + \alpha\,(\log Z)^2, \qquad Z = \sum_j e^{z_j}$$

$\log Z$ 가 커진다는 것은 로짓들이 전반적으로 커졌다는 뜻이므로, 여기에 벌점을 주어 로짓 크기를 수치적으로 안전한 영역에 유지한다. $\alpha$ 는 작게 설정한다(대략 $10^{-4}$ 규모). 이것은 대형 모델 레시피에서 반복 채택된, 근거가 비교적 뚜렷한 경험적 처방이다.

QK-norm은 어텐션 로짓 $q\cdot k/\sqrt{d_k}$ 가 커져 softmax가 포화·붕괴하는 것(1.4.18의 어텐션 엔트로피 붕괴)을 겨냥한다. Q와 K에 어텐션 계산 직전 정규화(LayerNorm 또는 RMSNorm)를 적용해, 내적으로 들어가는 벡터의 크기를 제한한다.

$$\text{score}_{ij} = \frac{\text{norm}(q_i)\cdot \text{norm}(k_j)}{\sqrt{d_k}}$$

정규화된 q·k는 크기가 통제되어 로짓이 무한정 커지지 못한다. $\sqrt{d_k}$ 스케일(1.4.6)이 학습 초기의 분산을 제어한다면, QK-norm은 학습이 진행되며 로짓이 자라는 것을 검출한다 — 둘은 다른 시점의 같은 병을 겨냥한다. 겨냥하는 실패 모드가 잘 관찰되는 만큼 근거 있는 처방이지만, 여전히 경험적이다.

로짓 소프트 캡(soft-capping)은 출력이나 어텐션 로짓을 부드럽게 상한 안에 가둔다.

$$z \mapsto c\cdot\tanh(z/c)$$

$\tanh$ 는 입력이 커져도 출력이 $\pm c$ 를 넘지 않으므로 폭주를 원천 차단한다. 다만 로짓 값의 표현력을 약간 저하시키므로 채택은 레시피 판단이다 — 일부 모델 계열에서 쓰였고, 폭주는 막지만 무비용은 아니다.

임베딩·출력단의 크기 불균형을 교정하는 처방들은 신뢰도가 섞여 있다. 토큰 임베딩에 $\sqrt{d_{model}}$ 등을 곱해 위치 인코딩·이후 층과 크기를 맞추는 임베딩 스케일링은 원 트랜스포머부터의 관행이고, 언임베딩 직전의 최종 정규화(final norm)는 이미 표준이다(1.4.12). 반면 어떤 크기·상수를 쓰느냐의 세부는 레시피마다 다르고 경험적으로 조정된다.

residual 스케일은 층이 깊을수록 잔차 스트림(1.4.16)의 노름이 층 따라 누적·폭증하는 것을 겨냥한다. 각 서브층의 잔차 기여를 상수로 축소해 더한다.

$$x \leftarrow x + \gamma\cdot\text{Sublayer}(\text{Norm}(x)), \qquad \gamma \sim \frac{1}{\sqrt{2L}}$$

$L$ 이 클수록 각 층 기여를 줄여 스트림 노름이 층수에 따라 폭증하지 않게 한다. Pre-LN(1.4.12)이 이미 상당 부분을 해결하지만, 아주 깊은 Post-LN 스택을 안정화하려는 DeepNorm 계열이 이 발상을 밀고 나갔다 — 깊은 스택에서 근거 있는 처방이되 상수 선택은 경험적이다. 좋은 초기화도 같은 계열이다. 분산 보존 초기화(Xavier/He, 1.3)로 층을 지나도 활성 분산이 유지되게 하고, 잔차 출력 투영의 초기 가중치를 $1/\sqrt{2L}$ 등으로 작게 설정해 초기 잔차 기여를 억제해 스택을 안정화한다(1.4.12에서 단초를 본 관행). 초기화 자체는 잘 확립된 공학이지만 트랜스포머용 세부 스케일은 경험적이다.

트릭막으려는 실패겨냥 지점
z-loss출력·라우터 로짓 성장손실(보조항)
QK-norm어텐션 로짓 폭주Q·K(어텐션 전)
로짓 소프트 캡로짓 폭주출력·어텐션 로짓
residual 스케일잔차 스트림 폭증서브층 잔차
깊이 인식 초기화초기 폭발·소실잔차 투영 가중치
그래디언트 클리핑스파이크→발산 전이그래디언트
웜업초기 불안정LR 스케줄
Pre-LN깊은 스택 불안정정규화 위치

이 표는 무엇이 무엇을 어디서 막는지의 지도일 뿐, 신뢰도의 지도가 아니다. 신뢰도로 다시 줄을 세우면, 표 아래쪽 셋 — 클리핑·웜업·Pre-LN — 은 거의 모든 레시피의 무조건적 기본기다. 반면 위쪽 — z-loss·QK-norm·소프트 캡·residual 스케일 — 은 모델·규모·데이터에 따라 도움이 되기도 안 되기도 한다. 그래서 "이 트릭들을 다 켜면 안정된다"는 미신이다. 각 트릭은 특정 실패 모드를 겨냥하므로, 모니터링(2.6.11)으로 그 실패가 관찰될 때 해당 트릭을 넣는 것이 원칙이다. 근거 없이 쌓으면 표현력만 저하시키고 디버깅만 어려워진다 — 어떤 트릭이 문제를 막고 있는지, 애초에 문제가 있기는 했는지조차 흐려지기 때문이다.

2.6.14 재현성 — 완전 결정성이 왜 어려운가

"같은 코드·같은 데이터·같은 seed면 같은 결과가 나와야 하지 않나?" 소규모에선 대체로 맞지만, 대규모 분산 학습에서 비트 단위 완전 재현은 대단히 어렵고 대개 그 값어치도 없다. 세 가지 근본 이유가 있다.

첫째는 부동소수 덧셈이 결합법칙을 따르지 않는다는 것이다.

$$(a + b) + c \ \neq\ a + (b + c) \quad(\text{일반적으로})$$

반올림 때문이다. $a = 10^{16}$, $b = -10^{16}$, $c = 1$ 을 FP32에서 더해 보자. $(a+b)+c$ 는 $0 + 1 = 1$ 이다. 그런데 $a + (b+c)$ 는 먼저 $b + c = -10^{16} + 1$ 인데, FP32에서 $10^{16}$ 옆의 표현 간격이 $1$ 보다 훨씬 크므로 이 합은 $-10^{16}$ 으로 반올림되고, 따라서 $a + (-10^{16}) = 0$ 이 나온다. 같은 세 수의 합이 순서에 따라 $1$ 도 되고 $0$ 도 된다. 큰 텐서의 리덕션(합·평균)은 병렬화하며 합산 순서가 실행마다 달라질 수 있으므로, 같은 입력도 다른 비트 결과를 낸다.

둘째는 비결정 커널이다. GPU 커널 중 상당수가 성능을 위해 원자적 덧셈(atomicAdd) 등 순서가 비결정적인 연산을 쓴다(일부 scatter/gather·역전파 리덕션). 실행마다 스레드 완료 순서가 달라 합산 순서가 바뀌고, 그러면 방금 본 비결합성 때문에 결과가 변동한다. 게다가 라이브러리가 입력 크기에 따라 다른 알고리즘을 자동 선택하기도 한다. 완전 결정 모드를 강제할 수는 있으나(결정적 커널만 사용) 느려지고, 일부 연산은 아예 결정적 구현이 없다.

셋째는 통신 순서다. 분산 학습의 all-reduce(그래디언트 집계, 2.4·2.5)는 여러 GPU의 부분합을 합치는데, 어느 GPU 것을 먼저 더하느냐가 실행마다 달라질 수 있다. 다시 비결합성 때문에 최종 그래디언트의 하위 비트가 달라지고, 비동기 통신·네트워크 타이밍이 이 순서를 좌우한다.

그래서 현실적인 목표는 비트 재현이 아니라 통계적 재현이다. 디버깅할 때는 작은 스케일에서 결정적 커널·고정 seed·단일 GPU로 비트 재현을 시도한다 — 느려도 버그를 검출하려면 감수한다. 대규모 운영에서는 완전 결정성을 포기하고, 손실 곡선이 통계적으로 재현되면(평균적 궤적·최종 성능이 일관되면) 충분하다고 본다. 대신 체크포인트·데이터 순서·난수 상태를 저장해 발산 후 재개나 사후 분석이 가능하게 한다. 특히 되감기(2.6.12) 후 정확히 같은 궤적으로 재개하려면 옵티마이저 상태·데이터로더 위치·RNG 상태까지 체크포인트에 담아야 하는데, 이 재개 재현성은 비트 재현과 별개로 운영상 필수다. "우리 학습은 완전히 재현 가능하다"는 대규모에서 대개 과장이다. 정확히는 "통계적으로 재현 가능하고, 체크포인트에서 재개 가능"하다 — 규제·감사(9.3)로 재현성이 요구되면 이 구분과 한계를 명시해야 한다.

2.6.15 워크스루 — 손실 스파이크 런북

앞의 조각들이 실제 상황에서 어떤 순서로 맞물리는지, 예시 시나리오를 시간순으로 따라가 보자. 수치는 개략이고, 이 절차의 형태가 곧 런북이다.

상황은 이렇다. 스텝 48,200에서 그래디언트 노름이 평소 $\approx 0.4$ 에서 $\approx 12$ 로 급등하고, 200스텝 뒤 손실이 $2.1 \to 6.8$ 로 스파이크했다. NaN은 아직 없다.

먼저 감지·분류다. 그래디언트 노름이 손실보다 먼저 급등했으니 2.6.11의 조기 경보가 제대로 작동한 것이고, NaN이 없으니 즉시 되감는 최악의 경로는 아직 아니다 — 자가 회복 가능성부터 본다. 다음으로 자동 방어를 확인한다. 그래디언트 클리핑이 임계 $\tau = 1.0$ 으로 켜져 있는데 노름 12가 관측됐다면, 클리핑이 동작은 하되 스파이크를 온전히 못 막은 것이다. 온라인 스킵(노름이 $10\tau$ 를 넘는 스텝의 갱신을 건너뛰기)이 켜져 있는지도 본다.

이제 원인을 감별한다. 데이터인가부터. 로그에서 스파이크 스텝의 배치 인덱스를 뽑고, 체크포인트(스텝 48,000)에서 같은 데이터 순서로 재개해 본다. 스파이크가 정확히 같은 스텝에 재현되면 데이터 배치가 범인이니, 그 배치·구간을 식별해 스킵하거나 제외한다(2.6.12). 재현되지 않으면 데이터 순서·난수의 상호작용이거나 누적 불안정이므로 다음으로 넘어간다. LR인가는 스파이크 스텝이 피크 LR 도달 직후인지를 스케줄과 대조해 본다. 그렇다면 LR 과다 혐의이므로, 체크포인트에서 피크 LR을 20~30% 낮추거나 웜업을 연장해 재개한다. 누적 불안정인가는 어텐션 로짓 max 추세를 스파이크 이전 수천 스텝까지 되짚어 본다. 완만한 상승이 보이면 QK-norm이나 z-loss 도입을 검토하되, 이것은 코드 변경이라 신중히 — 작은 재현 실험으로 먼저 확인한 뒤 넣는다.

원인을 좁혔으면 되감고 재개한다. 마지막 정상 체크포인트(48,000)로 되감고, 위에서 좁힌 원인에 맞는 변경을 하나 이상 적용한다 — 배치 스킵에, 필요하면 LR 하향과 클리핑 강화를 더한다. 아무것도 바꾸지 않고 재개하지 않는다. 재개 후에는 그래디언트 노름·손실·로짓 max를 면밀히 감시한다. 노름이 평소로 복귀하고 손실이 스파이크 전 궤적을 회복하면 성공이고, 또 발생하면 LR을 더 낮추거나 구조적 대책을 강화한다. 마지막으로 무엇이 원인이었고 무엇으로 고쳤는지 남긴다 — 같은 코퍼스·레시피에서 재발할 수 있으므로 데이터 정제(2.1)나 레시피에 반영한다.

이 런북의 척추는 "자동 방어로 1차 흡수 → 원인을 데이터·LR·누적불안정으로 감별 → 원인에 맞춰 되감기·변경·재개 → 감시"다. 순서를 지키는 이유는 앞에서 말한 원칙 그대로다 — 비용이 큰 되감기를 저렴한 원인 감별 뒤로 미루고, 위험한 코드 변경을 안전한 데이터·LR 조정 뒤로 미룬다. 저렴하고 안전한 것부터 시도한다.

이 장은 사전학습으로 얻은 원석 모델을 죽지 않게 끝까지 운용하는 기술을 다뤘다. 다음 부(제3부)는 그 원석을 유용·정직·무해하게 다듬는 사후학습·정렬로 넘어간다 — SFT·RLHF·DPO의 세계다. 정밀도와 안정화의 상당수는 정렬 단계의 학습에도 그대로 적용되니, 여기서 세운 개략을 그대로 들고 가면 된다. 추론 단계에서 정밀도를 낮추는 양자화는 학습과 목적·제약이 다르므로 따로 다룬다(5.2) — 그쪽은 그래디언트가 흐를 필요가 없어 제약이 오히려 느슨하다.