리서치

LAB / 15번째 글

Chinchilla의 계수로 최적 배분을 직접 풀어 봤다: 20토큰/파라미터는 Approach 3에 없었다

Gopher 예산에 파라메트릭 적합 계수를 넣고 풀면 토큰/파라미터가 59.1로 나온다. 20이 나오는 열은 IsoFLOP 쪽이고 논문 표에 이미 그렇게 적혀 있다. 재적합 계수의 표준오차를 그대로 흘리면 95% 구간이 [0.02, 2470]으로 벌어진다.

PALDYN Team26 MIN READ

「모델 파라미터 하나당 학습 토큰 20개」는 아마 지금 가장 자주 인용되는 스케일링 규칙일 것이다. 출처는 Chinchilla 논문(arXiv 2203.15556)이고, 그 논문이 최적 배분을 구한 방법 중 하나가 손실 함수를 직접 적합해서 푸는 파라메트릭 모델링(Approach 3)이다. 계수는 공개돼 있다. 그러면 그 계수를 넣고 직접 풀면 20이 나와야 한다.

넣어 봤다. 59.1이 나왔다.

이 글은 그 숫자가 어디서 어긋났는지를 추적한 기록이다. 결론부터 적으면 세 가지다.

  • 우리 최적화 코드는 맞다. 같은 계수로 Gopher 예산을 풀면 N∗=40.31N^{*} = 40.31B가 나오고, 지수도 논문이 보고한 a=0.46a = 0.46·b=0.54b = 0.54와 소수 셋째 자리까지 맞는다.
  • 20이라는 값은 Approach 3이 아니라 Approach 2(IsoFLOP)의 성질이다. 논문 부록 표에 두 열이 나란히 인쇄돼 있는데, Approach 2 열의 토큰/파라미터는 19.2~29.2로 거의 평평하고 Approach 3 열은 23.0에서 142.6까지 6.20배 커진다. 표에 이미 답이 적혀 있었다.
  • 재현 논문(arXiv 2404.10102)의 재적합 계수를 쓰면 18.4가 나와 20 근처로 돌아온다. 그런데 그 논문이 함께 보고한 표준오차를 그대로 흘리면 Gopher 예산에서 이 비의 95% 구간이 [0.02, 2470]이다. 20도 59도 전부 그 안이다.

덤으로 하나 더 잡혔다. 논문 부록 표의 175B 행에서 인쇄된 FLOPs가 자기 표의 토큰 수와 정확히 10배 어긋난다.

스케일링 법칙이 무엇을 주장하는지는 스케일링 법칙이 맡는다. 이 글은 계수를 넣고 푸는 산수만 맡는다.

파라메트릭 모델

손실 함수

Chinchilla의 파라메트릭 모델은 손실을 파라미터 수 NN과 토큰 수 DD의 함수로 놓는다.

L(N,D)=E+ANα+BDβL(N, D) = E + \frac{A}{N^{\alpha}} + \frac{B}{D^{\beta}}

EE는 아무리 키워도 남는 바닥, 나머지 두 항은 각각 모델을 키워서 줄이는 몫과 데이터를 늘려서 줄이는 몫이다. 여기에 학습 연산량을 C=6NDC = 6ND로 근사하는 제약을 걸고 LL을 최소화하면 예산 CC에서의 최적 배분 (N∗,D∗)(N^{*}, D^{*})가 나온다. 6ND 근사는 파라미터 하나를 학습시키는 데 토큰당 6 FLOPs가 든다고 보는 관례적인 어림이다.

해석해

제약을 대입하면 변수가 하나로 줄어든다.

L(N)=E+AN−α+B(6NC)βL(N) = E + A N^{-\alpha} + B \left(\frac{6N}{C}\right)^{\beta}

dL/dN=0dL/dN = 0을 풀면 해석해가 나온다.

N∗=(αA CββB⋅6β)1/(α+β),D∗=C6N∗N^{*} = \left(\frac{\alpha A\, C^{\beta}}{\beta B \cdot 6^{\beta}}\right)^{1/(\alpha+\beta)}, \qquad D^{*} = \frac{C}{6N^{*}}

이 식은 두 가지를 바로 알려 준다. 첫째, N∗∝CaN^{*} \propto C^{a}이고 a=β/(α+β)a = \beta/(\alpha+\beta)다. 둘째, 토큰/파라미터 비는 D∗/N∗∝C1−2aD^{*}/N^{*} \propto C^{1-2a} 라서 aa가 정확히 0.5일 때만 예산과 무관한 상수가 된다. 이 사실이 이 글의 나머지 전부를 설명한다.

아래에서는 해석해와 scipy 수치해를 둘 다 계산해서 서로 대조한다. 한쪽이 틀리면 상대차가 벌어지므로 최적화 코드 자체의 검산이 된다.

Approach 3 계수

pip install numpy scipy
python optimum.py
import numpy as np
from scipy.optimize import minimize_scalar

# L(N, D) = E + A/N^alpha + B/D^beta 의 계수 세 벌
FITS = {
    "Hoffmann A3": dict(E=1.6934, A=406.4, alpha=0.3392, B=410.7, beta=0.2849),
    "printed 2dp": dict(E=1.69, A=406.4, alpha=0.34, B=410.7, beta=0.28),
    "Besiroglu": dict(E=1.8172, A=482.01, alpha=0.3478, B=2085.43, beta=0.3658),
}
GOPHER = 5.76e23


def numeric(f, C):  # C = 6ND 제약 아래 수치 최소화
    def L(lnN):
        N = np.exp(lnN)
        return f["E"] + f["A"] * N ** -f["alpha"] + f["B"] * (6 * N / C) ** f["beta"]
    r = minimize_scalar(L, bounds=(np.log(1e6), np.log(1e15)), method="bounded",
                        options={"xatol": 1e-12})
    return np.exp(r.x), r.fun


def closed(f, C):  # dL/dN = 0 을 손으로 푼 해
    a, b = f["alpha"], f["beta"]
    N = (a * f["A"] * C ** b / (b * f["B"] * 6 ** b)) ** (1 / (a + b))
    return N, C / (6 * N)


print("=== 1. Gopher 예산 C = 5.76e23 에서의 최적 배분 ===")
for name, f in FITS.items():
    Nn, L = numeric(f, GOPHER)
    N, D = closed(f, GOPHER)
    print(f"{name:12s} N*={N/1e9:7.2f}B  D*={D/1e12:5.2f}T  D*/N*={D/N:6.1f}  L={L:.4f}"
          f"   해석해와 수치해의 상대차 {abs(Nn-N)/N:.1e}")

print("\n=== 2. 지수 N* ∝ C^a, D* ∝ C^b ===")
for name, f in FITS.items():
    a, b = f["alpha"], f["beta"]
    print(f"{name:12s} a = {b/(a+b):.4f}   b = {a/(a+b):.4f}   D*/N* ∝ C^{1-2*b/(a+b):+.4f}")

print("\n=== 3. 예산을 6자릿수 흔들면 D*/N* 는 상수인가 ===")
print(f"{'C (FLOPs)':>10} |" + "".join(f"{n:>19}" for n in FITS))
for C in [1e21, 1e22, 1e23, 5.76e23, 1e24, 1e25, 1e26, 1e27]:
    cells = ""
    for f in FITS.values():
        N, D = closed(f, C)
        cells += f"{N/1e9:12.1f}B{D/N:6.1f}"
    print(f"{C:10.2e} |" + cells)

계수 세 벌의 출처는 이렇다. printed 2dp는 원논문 부록 D.2의 식 (10)에 실제로 인쇄된 값이다 — 그 식은 L(N,D)=E+A/N0.34+B/D0.28L(N,D) = E + A/N^{0.34} + B/D^{0.28} 에 E=1.69E = 1.69, A=406.4A = 406.4, B=410.7B = 410.7 로, 지수가 두 자리까지만 적혀 있다. Hoffmann A3의 0.3392·0.2849는 원논문에 없다 — 재현 논문(arXiv 2404.10102) Table 1이 「Hoffmann et al.」 열로 함께 싣는 전체 자릿수값이고, 이 글에서 원논문 계수라고 부르는 것은 그쪽이다. Besiroglu는 같은 표의 재적합 열이다.

실제 출력이다.

=== 1. Gopher 예산 C = 5.76e23 에서의 최적 배분 ===
Hoffmann A3  N*=  40.31B  D*= 2.38T  D*/N*=  59.1  L=1.9184   해석해와 수치해의 상대차 2.5e-09
printed 2dp  N*=  32.19B  D*= 2.98T  D*/N*=  92.6  L=1.9307   해석해와 수치해의 상대차 2.2e-08
Besiroglu    N*=  72.25B  D*= 1.33T  D*/N*=  18.4  L=1.9744   해석해와 수치해의 상대차 1.2e-07

=== 2. 지수 N* ∝ C^a, D* ∝ C^b ===
Hoffmann A3  a = 0.4565   b = 0.5435   D*/N* ∝ C^+0.0870
printed 2dp  a = 0.4516   b = 0.5484   D*/N* ∝ C^+0.0968
Besiroglu    a = 0.5126   b = 0.4874   D*/N* ∝ C^-0.0252

=== 3. 예산을 6자릿수 흔들면 D*/N* 는 상수인가 ===
 C (FLOPs) |        Hoffmann A3        printed 2dp          Besiroglu
  1.00e+21 |         2.2B  34.0         1.8B  50.1         2.8B  21.6
  1.00e+22 |         6.3B  41.5         5.2B  62.6         9.0B  20.4
  1.00e+23 |        18.1B  50.7        14.6B  78.2        29.4B  19.2
  5.76e+23 |        40.3B  59.1        32.2B  92.6        72.2B  18.4
  1.00e+24 |        51.9B  62.0        41.3B  97.7        95.9B  18.1
  1.00e+25 |       148.3B  75.7       116.8B 122.1       312.1B  17.1
  1.00e+26 |       424.4B  92.5       330.5B 152.6      1015.9B  16.1
  1.00e+27 |      1214.2B 113.1       934.9B 190.7      3307.3B  15.2

코드 검산

논문은 Gopher 예산 5.76×10235.76 \times 10^{23} FLOPs에서 Approach 3의 최적 모델 크기를 40B로 보고한다. 우리가 계수만 넣고 푼 값은 40.31B다. 지수도 논문 Table 2의 Approach 3 행이 a=0.46a = 0.46, b=0.54b = 0.54인데 우리 값이 0.4565와 0.5435다. 해석해와 수치해의 상대차도 10−710^{-7} 아래다. 최적화 코드에는 문제가 없다.

그러니 59.1이라는 값도 계산 실수가 아니다. 같은 코드가 40.31B를 맞혔다.

토큰/파라미터 비

3번 표가 이 글의 핵심이다. 예산을 102110^{21}에서 102710^{27}까지 여섯 자릿수 흔들면 Hoffmann 계수의 토큰/파라미터 비는 34.0에서 113.1까지 3.3배 커진다. 상수가 아니다.

이유는 앞에서 유도한 그대로다. a=0.4565a = 0.4565라서 D∗/N∗∝C+0.087D^{*}/N^{*} \propto C^{+0.087}이고, 지수가 0이 아니면 예산이 커질수록 비가 커진다. 반대로 재적합 계수는 a=0.5126a = 0.5126으로 0.5에 훨씬 가깝고 지수가 −0.025-0.025라 21.6에서 15.2로 거의 움직이지 않는다. 「20토큰/파라미터」가 예산과 무관한 규칙으로 성립하려면 aa가 0.5여야 한다. 원논문 Approach 3의 계수는 그 조건을 만족하지 않는다.

반올림 민감도

printed 2dp 행이 왜 있는지가 여기서 나온다. 원논문만 읽고 이 계산을 하려는 사람이 손에 쥘 수 있는 계수는 그 행뿐이다.

그 두 자리로 풀면 같은 Gopher 예산에서 N∗N^{*}가 40.31B에서 32.19B로 내려가고 토큰/파라미터는 59.1에서 92.6으로 뛴다. 소수 넷째 자리 두 개가 결과를 57% 움직인다. 이건 계수가 나쁘다는 뜻이 아니라, CC가 102310^{23}급이라 CβC^{\beta}에서 β\beta의 미세한 차이가 곱셈으로 증폭된다는 뜻이다. β\beta가 0.0049만 커져도 CβC^{\beta}는 1.3배가 된다.

부록 Table A3

여기서 방향을 바꿨다. 우리가 계수로 푼 값을 논문의 서술과 맞추는 대신, 논문이 부록에 인쇄한 표를 그대로 읽기로 했다. 부록 Table A3은 모델 크기별로 Approach 2와 Approach 3이 각각 예측한 FLOPs와 토큰 수를 두 열로 나란히 싣는다. 두 열의 토큰/파라미터를 직접 나눠 보면 된다.

pip install numpy
python table.py
import numpy as np

# arXiv 2203.15556 Appendix Table A3 을 그대로 옮긴 것 (N, A2 FLOPs, A2 토큰, A3 FLOPs, A3 토큰)
ROWS = [(4e8, 1.84e19, 7.7e9, 2.21e19, 9.2e9), (1e9, 1.20e20, 2.00e10, 1.62e20, 2.71e10),
        (1e10, 1.32e22, 2.195e11, 2.46e22, 4.101e11), (6.7e10, 6.88e23, 1.7e12, 1.71e24, 4.1e12),
        (1.75e11, 4.54e24, 4.3e12, 1.26e24, 1.20e13), (2.8e11, 1.18e25, 7.1e12, 3.52e25, 2.01e13),
        (5.2e11, 4.19e25, 1.34e13, 1.36e26, 4.35e13), (1e12, 1.59e26, 2.65e13, 5.65e26, 9.41e13),
        (1e13, 1.75e28, 2.92e14, 8.55e28, 1.4255e15)]

print("=== 논문 표에 이미 적혀 있는 D/N ===")
print(f"{'N':>9} | {'Approach 2':>10} | {'Approach 3':>10}")
for N, _, D2, _, D3 in ROWS:
    print(f"{N:9.2e} | {D2/N:10.1f} | {D3/N:10.1f}")
r2 = [D2 / N for N, _, D2, _, _ in ROWS]
r3 = [D3 / N for N, _, _, _, D3 in ROWS]
print(f"{'폭(최대/최소)':>9} | {max(r2)/min(r2):10.2f} | {max(r3)/min(r3):10.2f}")

print("\n=== 표에서 지수를 회귀하면 (175B 행은 아래 감사에서 걸러 제외) ===")
keep = [r for r in ROWS if r[0] != 1.75e11]
for tag, ci, cd in [("Approach 2", 1, 2), ("Approach 3", 3, 4)]:
    C = np.array([r[ci] for r in keep]); N = np.array([r[0] for r in keep])
    D = np.array([r[cd] for r in keep])
    a = np.polyfit(np.log(C), np.log(N), 1)[0]
    b = np.polyfit(np.log(C), np.log(D), 1)[0]
    print(f"{tag}: a = {a:.4f}  b = {b:.4f}  ->  D/N ∝ C^{b-a:+.4f}")
al, be = 0.3392, 0.2849
print(f"Approach 3 계수 (alpha={al}, beta={be}) 에서 나오는 값: "
      f"a = {be/(al+be):.4f}  b = {al/(al+be):.4f}  ->  D/N ∝ C^{1-2*be/(al+be):+.4f}")

print("\n=== 인쇄된 (N, FLOPs, 토큰) 세 값을 6ND 로 대조 ===")
for N, C2, D2, C3, D3 in ROWS:
    for tag, C, D in [("A2", C2, D2), ("A3", C3, D3)]:
        r = 6 * N * D / C
        if not 0.9 < r < 1.1:
            print(f"  N={N:9.2e} {tag}: 6ND = {6*N*D:9.2e} 인쇄된 FLOPs = {C:9.2e} 비 = {r:.3f}")
print("  (그 밖의 모든 칸은 비가 0.9~1.1 안에 든다)")
=== 논문 표에 이미 적혀 있는 D/N ===
        N | Approach 2 | Approach 3
 4.00e+08 |       19.2 |       23.0
 1.00e+09 |       20.0 |       27.1
 1.00e+10 |       21.9 |       41.0
 6.70e+10 |       25.4 |       61.2
 1.75e+11 |       24.6 |       68.6
 2.80e+11 |       25.4 |       71.8
 5.20e+11 |       25.8 |       83.7
 1.00e+12 |       26.5 |       94.1
 1.00e+13 |       29.2 |      142.6
 폭(최대/최소) |       1.52 |       6.20

=== 표에서 지수를 회귀하면 (175B 행은 아래 감사에서 걸러 제외) ===
Approach 2: a = 0.4899  b = 0.5101  ->  D/N ∝ C^+0.0201
Approach 3: a = 0.4586  b = 0.5410  ->  D/N ∝ C^+0.0823
Approach 3 계수 (alpha=0.3392, beta=0.2849) 에서 나오는 값: a = 0.4565  b = 0.5435  ->  D/N ∝ C^+0.0870

=== 인쇄된 (N, FLOPs, 토큰) 세 값을 6ND 로 대조 ===
  N= 1.75e+11 A3: 6ND =  1.26e+25 인쇄된 FLOPs =  1.26e+24 비 = 10.000
  (그 밖의 모든 칸은 비가 0.9~1.1 안에 든다)

두 열의 비

첫 표가 답이다. Approach 2 열은 19.2에서 29.2까지 1.52배 안에서 움직이고, Approach 3 열은 23.0에서 142.6까지 6.20배로 벌어진다. 20 근처에 머무는 것은 IsoFLOP 쪽이다. 파라메트릭 적합 쪽은 67B 행에서 이미 61.2이고, 우리가 같은 계수로 푼 59.1과 거의 같은 자리다.

두 번째 출력은 이 대응을 지수로 확인한다. 논문 표에서 회귀한 Approach 2의 aa가 0.4899인데 논문 Table 2가 보고한 값이 0.49다. Approach 3은 회귀값 0.4586, 논문 보고값 0.46, 그리고 우리가 계수에서 유도한 값 0.4565다. 셋이 소수 둘째 자리까지 맞는다. 표와 계수와 우리 계산이 같은 것을 가리키고 있다는 확인이다.

aa가 0.49면 D/N∝C+0.02D/N \propto C^{+0.02}이라 여섯 자릿수를 가도 1.5배 안이고, 0.4586이면 C+0.082C^{+0.082}이라 6배가 된다. 표의 두 열이 갈리는 것은 지수 0.03 차이 하나 때문이다.

175B 행 오타

세 번째 출력은 표의 세 값 (N,FLOPs,D)(N, \text{FLOPs}, D)가 서로 C=6NDC = 6ND를 만족하는지를 전 칸에 대해 확인한 것이다. 18개 칸 중 17개는 비가 0.959~1.004로 들어오는데 175B 행의 Approach 3만 정확히 10.000이다. 인쇄된 1.26×10241.26 \times 10^{24}는 같은 행의 토큰 수 12.0조와 맞지 않고, 1.26×10251.26 \times 10^{25}여야 앞뒤가 맞는다. 지수 하나가 빠진 자리다.

우리 쪽 원인부터 지웠다. 옮겨 적는 실수라면 다른 칸에도 나와야 하는데 나머지 17칸이 전부 4% 안에 든다. 단위 착각이라면 열 전체가 어긋나야 하는데 같은 열의 위아래 행이 멀쩡하다. 10.000이라는 딱 떨어지는 비는 반올림으로 나올 수 있는 모양이 아니다. 그래서 표 쪽의 오타로 본다. 이 행을 회귀에서 뺀 이유이기도 하다.

재적합 계수

재현 논문(arXiv 2404.10102)은 원논문 그림에서 데이터 점을 복원해 같은 함수를 다시 적합했고, 그 계수로는 Gopher 예산에서 18.4가 나온다. 20 근처다. 그런데 이 논문은 원논문과 달리 표준오차를 함께 보고한다. 그 오차를 그대로 흘려 보면 어떻게 되는지가 마지막 실험이다.

pip install numpy
python uncertainty.py
import numpy as np

rng = np.random.default_rng(0)
GOPHER, M = 5.76e23, 200000
# Besiroglu et al. Table 1 재적합: 추정값과 표준오차
SE = dict(A=(482.01, 124.58), alpha=(0.3478, 0.02), B=(2085.43, 1293.23), beta=(0.3658, 0.02))
valid = lambda d: (d["A"] > 0) & (d["B"] > 0) & (d["alpha"] > 0) & (d["beta"] > 0)


def ratio(d, C=GOPHER):
    A, a, B, b = d["A"], d["alpha"], d["B"], d["beta"]
    N = (a * A * C ** b / (b * B * 6 ** b)) ** (1 / (a + b))
    return C / (6 * N) / N


draws = {k: rng.normal(m, s, M) for k, (m, s) in SE.items()}
ok = valid(draws)
r = ratio({k: v[ok] for k, v in draws.items()})
q = np.percentile(r, [2.5, 25, 50, 75, 97.5])
print(f"표본 {M}개 중 네 계수가 모두 양수인 것 {ok.sum()}개 ({100*ok.mean():.1f}%)")
print(f"점추정 D*/N* = {ratio({k: np.array([v[0]]) for k, v in SE.items()})[0]:.2f}")
print(f"중앙값 {q[2]:.2f}   IQR [{q[1]:.2f}, {q[3]:.2f}]   95% 구간 [{q[0]:.2f}, {q[4]:.2f}]")
for t in [20, 30, 59.1]:
    print(f"  D*/N* > {t:4.1f} 일 확률 = {100*(r > t).mean():5.1f}%")

print("\n계수를 하나씩만 흔들었을 때의 95% 구간")
for k in SE:
    d = {n: np.full(M, SE[n][0]) for n in SE}
    d[k] = draws[k]
    m = valid(d)
    lo, hi = np.percentile(ratio({n: v[m] for n, v in d.items()}), [2.5, 97.5])
    print(f"  {k:5s} ±{SE[k][1]:8.2f} -> [{lo:6.2f}, {hi:6.2f}]   폭 {hi-lo:7.2f}")

print("\nHoffmann 계수에서 D*/N* 를 59.1 -> 20 으로 옮기려면 예산이 몇 배여야 하는가")
e = 1 - 2 * 0.2849 / (0.3392 + 0.2849)
print(f"  D*/N* ∝ C^{e:+.4f} 이므로 필요한 배수 = (20/59.1)^(1/{e:.4f}) = {(20/59.1)**(1/e):.2e}")
표본 200000개 중 네 계수가 모두 양수인 것 189416개 (94.7%)
점추정 D*/N* = 18.39
중앙값 17.75   IQR [2.71, 101.19]   95% 구간 [0.02, 2470.08]
  D*/N* > 20.0 일 확률 =  48.2%
  D*/N* > 30.0 일 확률 =  42.1%
  D*/N* > 59.1 일 확률 =  32.1%

계수를 하나씩만 흔들었을 때의 95% 구간
  A     ±  124.58 -> [  5.82, 133.18]   폭  127.36
  alpha ±    0.02 -> [  1.43, 187.61]   폭  186.18
  B     ± 1293.23 -> [  0.04, 175.23]   폭  175.18
  beta  ±    0.02 -> [  1.32, 335.51]   폭  334.18

Hoffmann 계수에서 D*/N* 를 59.1 -> 20 으로 옮기려면 예산이 몇 배여야 하는가
  D*/N* ∝ C^+0.0870 이므로 필요한 배수 = (20/59.1)^(1/0.0870) = 3.91e-06

오차 전파 구간

점추정은 18.39로 20 근처가 맞다. 그런데 계수를 각자의 표준오차만큼 흔들면 중앙값 17.75에 IQR이 [2.71, 101.19]이고 95% 구간이 [0.02, 2470.08]이다. 원논문 계수가 준 59.1보다 큰 값이 나올 확률이 32.1%다. 다시 말해 재적합 계수의 자기 오차만으로도 20과 59는 서로 구별되지 않는다.

지수의 증폭

무엇이 이 폭을 만드는지는 그다음 출력이 말해 준다. β\beta 하나만 ±0.02\pm 0.02 흔들어도 구간 폭이 334다. BB의 표준오차(1293)가 추정값(2085)의 62%나 되는 것이 눈에 먼저 띄지만, 실제로 폭을 키우는 것은 BB가 아니라 지수 쪽이다. 앞서 반올림 두 자리가 59.1을 92.6으로 옮긴 것과 같은 메커니즘이다 — CβC^{\beta}에서 CC가 102310^{23}이라 β\beta의 0.02가 e0.02×54.7≈3.0e^{0.02 \times 54.7} \approx 3.0배로 증폭되고, 그것이 다시 1/(α+β)≈1.41/(\alpha+\beta) \approx 1.4제곱으로 N∗N^{*}에 실린 뒤 비에서 제곱으로 들어간다.

예산 역산

마지막 줄은 반대 방향의 확인이다. Hoffmann 계수를 그대로 두고 예산만 조정해서 59.1을 20으로 끌어내리려면 예산이 지금의 3.91×10−63.91 \times 10^{-6}배, 즉 26만분의 1이어야 한다. 예산을 어떻게 세든 이 간극은 안 메워진다. 6ND 근사 대신 다른 FLOPs 회계를 써서 예산이 10% 달라지는 정도로는 비가 1% 남짓 움직일 뿐이다.

꺾이는 지점

공짜와 손해의 경계

논문에서 그대로 가져다 쓸 수 있는 것은 N∗∝C0.46N^{*} \propto C^{0.46}이라는 지수까지다. 여기까지는 세 경로(계수에서 유도, 표에서 회귀, 논문 Table 2 보고값)가 소수 둘째 자리까지 일치하므로 공짜다. 토큰/파라미터 비부터가 손해다 — 같은 계수에서 예산을 여섯 자릿수 옮기면 3.3배, 인쇄된 두 자리로 반올림하면 57%, 재적합의 표준오차를 흘리면 두 자릿수가 움직인다. 「20토큰/파라미터」를 예산과 무관한 상수로 쓰는 순간부터 근거가 없다.

실무 적용

실무로 옮기면 이렇다. 자기 예산이 Gopher 급(102310^{23}~102410^{24})이 아니라면 20을 그대로 대입하지 말고 위 스크립트에 자기 CC를 넣어 다시 풀어야 한다. 예산이 작을수록(102110^{21}) 재적합 계수도 21.6으로 올라가고, 원논문 계수를 믿으면 34.0이다.

한계

계산만의 검증

전부 산수다. 우리는 모델을 하나도 학습시키지 않았고 손실을 하나도 측정하지 않았다. 이 글이 검증한 것은 「공개된 계수를 논문이 명시한 제약에 넣고 풀면 무엇이 나오는가」뿐이다. 계수 자체가 맞는지, 즉 그 함수가 실제 학습 곡선을 잘 설명하는지는 이 글에서 아무것도 말하지 않는다.

6ND 근사

C=6NDC = 6ND는 근사다. 어텐션 항과 임베딩 항을 빼고 세는 어림이고, 논문 부록의 더 자세한 FLOPs 회계와는 몇 %에서 십몇 % 정도 차이가 난다. 다만 앞서 봤듯 그 정도 차이는 비를 1% 남짓 움직일 뿐이라 이 글의 결론을 바꾸지 못한다.

계수 상관

표준오차 전파에서 계수 사이의 상관을 무시했다. 재현 논문은 표준오차는 주지만 공분산 행렬은 주지 않는다. 실제로는 α\alpha와 AA, β\beta와 BB가 강하게 상관돼 있을 것이고, 그렇다면 위의 95% 구간 [0.02, 2470]은 실제보다 넓다. 그래서 이 구간을 「진짜 불확실성은 이만큼이다」로 읽으면 안 되고 「보고된 오차만으로 좁혀지지 않는다」로만 읽어야 한다. 상관을 넣으면 얼마나 좁아지는지는 공분산이 공개돼야 답할 수 있다.

Approach 1 제외

Approach 1은 다루지 않았다. 부록 Table A3이 Approach 2와 3만 나란히 싣기 때문이다. Approach 1의 (67B, 1.5조 토큰)만으로는 열 전체의 기울기를 볼 수 없어 표에서 뺐다.

재현 기록

측정 환경

항목 값
OS Linux 6.18.5 x86_64 (컨테이너)
CPU Intel Xeon @ 2.80GHz, 4코어
Python 3.11.15
numpy 2.4.6
scipy 1.17.1
측정일 2026-08-19

세 스크립트를 각각 5회 반복 실행했다. optimum.py 0.420.47초, table.py 0.09초, uncertainty.py 0.210.22초이고, 다섯 번의 표준출력이 바이트 단위로 전부 동일했다. 몬테카를로에는 default_rng(0)으로 시드를 고정했다. 위 시간은 이 컨테이너 값이고 하드웨어가 다르면 바뀐다 — 결론에 쓰는 것은 비율뿐이다.

논문 원문은 ar5iv.labs.arxiv.org/html/2203.15556에서 받았다. 2022년 논문이라 arxiv.org/html/에는 HTML 전문이 없다. 부록 표는 HTML의 해당 <table>을 직접 읽어 옮겼다.

자기검사 수정

초안의 몬테카를로는 B > 0만 걸렀다. 그러면 AA나 α\alpha가 음수인 표본에서 음수의 실수 제곱이 되어 nan이 섞이는데, numpy는 RuntimeWarning 한 줄만 찍고 계속 간다. np.percentile이 전부 nan을 반환했고 그 위에서 계산한 (r > 20).mean()은 nan을 False로 세어 48.2%라는 그럴듯한 숫자를 그대로 냈다. 경고를 무시했다면 중앙값 칸만 비어 있고 확률 칸은 멀쩡해 보이는 표를 실었을 것이다. 네 계수 모두에 양수 조건을 걸어 고쳤고, 걸러진 비율(5.3%)을 출력에 남겼다.

또 하나. 처음에는 표의 열 이름을 한글로 두었는데 f-string의 폭 지정은 글자 수로 세고 터미널은 한글을 두 칸으로 그린다. 열이 어긋나 보여 printed 2dp 같은 영문 키로 바꿨다. 숫자는 그대로다.


읽어주셔서 감사합니다. 😊

LATEST

과학·실험의 최신 글