Stage 07 — Shape가 맞아도 같은 모델은 아니다: Linear parameter identity와 실행 계획
Linear,ReLU MLP의 parameter identity와 model-wide forward 비용을 실행 전에 봉인한다.
선형 변환은 matrix multiplication 한 번으로 설명할 수 있지만, 여러 layer를 쌓는 모델은 출력 shape만 맞는다고 올바르지 않다. initialization scale이 신호를 폭발시키거나 소실시킬 수 있고, 같은 Value가 여러 parameter 위치에 숨어들 수 있으며, 마지막 layer에서 budget을 초과할 때는 앞쪽 graph가 이미 만들어졌을 수 있다. Stage 07은 parameter를 만드는 단계와 activation graph를 만드는 단계를 서로 다른 신뢰 경계로 분리한다.
테스트: Stage 07 테스트
이번 단계가 답할 질문
선형 변환을 여러 번 합치는 것만으로 왜 충분하지 않은가? 선형 layer의 weight를
Matrix로 보관하고 출력 shape만 맞추면 충분할까? initialization scale, bias, parameter identity, graph budget 가운데 무엇을 생성 시점과 forward 시점에 각각 강제해야 Blast Radius를 작게 유지할 수 있을까?
처음 세운 가설
구현 전에 작성한 답의 핵심은 다음과 같다.
선형 layer의 weight를 Matrix로 보관하고 최종 출력 shape만 확인하는 것으로는 충분하지 않다. 출력 shape가 맞아도 initialization scale이 잘못되어 신호가 폭발하거나 소실될 수 있고, bias가 암묵적으로 broadcast될 수 있으며, forward마다 새로운 parameter가 생성되거나 parameter alias가 섞일 수 있고, 여러 layer를 지난 뒤에야 graph budget이 초과될 수 있다. 가장 작은 Blast Radius를 유지하려면 생성 시점에는 구조, 초기화 정책, bias, parameter identity, 전체 parameter budget을 확정하고**, **forward 시점에는 입력과 parameter의 관계, 전체 실행 graph budget, parameter 재사용과 비변경성을 강제한다. 여러 layer를 쌓을 때는 각 layer가 독립적으로 계산을 시작하기 전에 모델 전체 forward를 먼저 계획해야 한다.
생성 시점: 무엇이 parameter인가를 확정한다.
forward 시점: 그 parameter로 이번 실행을 감당할 수 있는지 증명한다.
이 가설은 구현의 중심 구조로 그대로 채택했다. 다만 현재 로드맵의 실제 학습 경로는 bias 없는 Linear → ReLU → bias 없는 Linear로 제한했다. optional bias는 shape, parameter 수, graph node 비용, zero initialization 계약까지만 검증한다. weight tying은 아직 필요하지 않으므로 명시적 alias group까지 일반화하지 않고 모든 parameter alias를 거부한다.
shape-correct but semantically wrong
입력, weight, bias를 다음처럼 정의한다.
Stage 07의 weight layout에서는 row 하나가 output feature 하나를 담당한다.
따라서 출력 shape는 다음과 같다.
하지만 이 식과 shape만으로는 다음을 증명하지 못한다.
- 서로 다른 위치가 서로 다른 physical parameter인가?
- 같은 parameter가 다음 forward에서도 재사용되는가?
- initialization scale이 activation 역할과 맞는가?
- bias가 실제 parameter인지 암묵적 broadcast 값인지.
- 여러 layer 전체를 실행할 graph budget이 충분한가?
- forward가 parameter data와 gradient를 변경하지 않는가? 그래서 출력 shape는 유일한 계약이 아니라 마지막 postcondition으로 둔다.
precondition = input relation, parameter seal, 전체 budget
execution = 봉인된 parameter로 activation graph 생성
postcondition = 실제 output shape와 parameter state 재검사
생성 전에 모델 전체를 계획한다
LinearPlan은 아직 Value나 Matrix를 만들지 않는다. 다음 정보만 검증하고 계산한다.
name
in_features / out_features
use_bias
initializer distribution / gain / seed / role
weight와 bias shape
parameter count
feature 수는 양수인 정확한 int여야 하며 Python에서 int의 subclass인 bool은 거부한다. name은 공백일 수 없고 initializer gain은 유한한 양수여야 한다.
Linear 하나의 parameter 수는 다음과 같다.
이번 bias 없는 MLP는 이므로 다음과 같다.
각 layer가 자기 parameter budget만 통과한 뒤 allocation을 시작하게 두지 않는다.
ModelPlan은 먼저 모든 layer를 검증하고, 인접 layer의 width 관계와 name 중복, 모델 전체 parameter 수를 확인한다.
모든 LinearPlan 검증
-> layer name과 연결 shape 검증
-> model parameter 수 합산
-> global parameter budget 검증
-> raw initializer 실행
-> 모든 결과의 type과 finite 검증
-> Value와 Matrix materialization
-> parameter identity seal
상한이 19라면 필요한 20개 중 어느 것도 초기화하지 않는다. 실행 결과에서 initializer 호출 횟수는 정확히 0이었다.

결정적 초기화 계약과 통계적 성질을 구분한다
입력 성분과 weight가 서로 독립이고 평균이 0이라고 단순화하면 출력 분산은 다음
규모를 가진다.
standard linear 경로는 다음 scale을 사용한다.
ReLU 뒤에서 양수 branch만 남는 효과를 보상하는 He scale은 다음과 같다.
InitSpec은 distribution, gain, seed, role을 생성 계약으로 고정한다. 같은 seed와 parameter 위치는 같은 값을 만든다. initializer 호출 횟수와 모든 반환값의 finite 여부도 결정적으로 검사한다.
반면 작은 matrix 하나의 표본 분산을 이론값과 정확히 같게 만드는 것은 실행 계약이 아니다. 표본 분산은 확률적 결과이므로 충분히 큰 표본과 여러 seed를 사용하는 통계 테스트에서 tolerance로 검증한다.
직접 실행한 , 표본 4096개의 결과는 다음과 같다.
standard target variance = 0.015625
standard sample variance = 0.016176814002937006
relu target variance = 0.03125
relu sample variance = 0.031363356828254293


이 구분은 과도한 runtime guard를 피한다. policy와 seed는 계약이지만 우연히 뽑힌 작은 표본의 분산은 계약이 아니다.
bias가 없으면 정말 존재하지 않는다
use_bias=True이면 shape (C_out,)의 독립 Vector를 만들고 0으로 초기화한다.
use_bias=False이면 zero placeholder조차 만들지 않는다. 없는 parameter를 영벡터로 위장하면 manifest, parameter count, checkpoint 의미가 모두 흐려지기 때문이다. bias가 있으면 parameter 수는 만큼 늘고 각 output scalar에 addition node 하나가 추가된다. 두 row를 처리하는 이번 MLP에서 bias를 켜면 다음 차이가 정확히 발생한다.
테스트는 parameter 차이 6과 graph node 차이 12를 executor의 실제 graph와 함께 검증한다. 실제 Stage 07 MLP는 로드맵에 맞게 bias를 끈다.
logical identity와 physical identity
parameter identity는 두 층으로 나뉜다.
logical identity = mlp.fc1.weight[2,1]
physical identity = 실제 Value 객체의 id
모든 logical name은 유일해야 하고, 현재 Stage에서는 모든 physical identity도 서로 달라야 한다. 다음 코드는 shape (2,2)를 만족하지만 합법적인 weight가 아니다.
shared = Value(0.5)
weight = Matrix(((shared, shared), (Value(1.0), Value(2.0))))첫 row의 두 위치가 같은 parameter를 공유한다. 하나를 update하면 다른 위치도 동시에 바뀌어 optimizer와 checkpoint의 의미가 깨진다. Linear는 materialization 직후 name과 object identity를 함께 검사해 이 alias를 거부한다.


alias: ParameterIdentityError: 선언되지 않은 parameter alias입니다
repeated forward identity 유지 = True
replacement: ParameterIdentityError: fc1 parameter storage identity가 변경됐습니다
manifest만 믿으면 교체된 storage가 가면을 쓴다
초기 구현은 생성 시 만든 parameter manifest tuple의 identity만 다시 확인했다.
그러나 다음과 같이 실제 layer.weight를 교체하면 예전 manifest는 여전히 정상으로 보인다.
layer.weight = another_matrixmanifest 검사 자체는 통과하지만 executor는 새 weight를 읽게 된다. cache나 복사본이 실제 상태를 정상처럼 보이게 만드는 것과 같은 실패다. 이를 막기 위해 forward 직전에 실제 weight와 bias storage에서 parameter reference를 다시 구성하고, 생성 시 seal과 name·identity 순서 전체를 대조한다.
현재 weight/bias storage
-> parameter reference 재구성
-> 중복 name과 alias 검사
-> sealed identity와 순서 대조
-> 모든 parameter finite 검사
-> 실행 허가
forward 전후에는 parameter data와 gradient snapshot도 비교한다. optimizer는 forward 사이에서 parameter를 변경할 수 있지만, 한 번의 forward 자체는 parameter storage, data, gradient를 생성하거나 교체하거나 변경할 수 없다.
Linear graph 비용을 정확히 센다
입력이 (T,C_in), 출력 width가 이라고 하자. output scalar 수는 이다. scalar 하나는 multiplication 번과 product 합산 번을 만든다. bias가 있으면 addition 한 번을 더한다.
ReLU는 scalar마다 operation node 하나를 만든다.
이번 실행은 인 MLP다. 첫 Linear는
ReLU는
두 번째 Linear는
개 node를 만든다. 따라서 전체 비용은 다음과 같다.
planner의 76과 최종 output에서 parent 방향으로 순회해 센 실제 operation node 76이 정확히 일치했다. 복잡도 만 적은 것이 아니라 현재 executor가 물질화할 정확한 수를 계약으로 만든다.
여러 layer의 forward를 먼저 끝까지 계획한다
layer별 local budget만 검사하면 마지막 layer에서 실패하기 전까지 앞쪽 activation graph가 이미 만들어진다.
잘못된 순서
fc1 실행 -> ReLU 실행 -> fc2 budget 실패
ModelPlan.plan_forward()는 scalar 연산을 하지 않고 shape를 끝까지 전파한다.
input shape
-> fc1 output shape와 비용
-> ReLU shape와 비용
-> fc2 output shape와 비용
-> model-wide total
-> global graph budget 검사
-> 성공한 경우에만 fc1 실행
통제 실험의 비용은 다음과 같다.
상한 37에서는 CountingValue 연산 횟수 0으로 실패하고, 상한 38에서는 output shape (1,2)를 만들며 실제 연산 횟수도 38이었다.


이 검사는 마지막 layer의 비용 실패가 첫 layer까지 번지는 Blast Radius를 0으로 줄인다.
gradient도 단순한 해석식으로 검증한다
한 output을 가진 bias 없는 Linear를 다음처럼 둔다.
, 이면 순전파 값은 다음과 같다.
해석적 gradient는 다음과 같다.
Stage 03 autodiff 결과가 이 네 값과 정확히 일치한다. Linear의 shape와 비용뿐 아니라 parameter가 올바른 parent로 graph에 연결됐다는 뜻이다.
비용과 Blast Radius
| 실패 | 차단 위치 | 늦게 발견했을 때의 영향 |
|---|---|---|
| invalid feature, name, init | LinearPlan 검증 | 일부 parameter allocation |
| 인접 layer width 불일치 | ModelPlan 검증 | 연결 불가능한 layer materialization |
| model parameter 초과 | initializer 호출 전 | 큰 Python object allocation |
| non-finite initializer 결과 | Value materialization 전 | 부분 parameter model |
| duplicate layer/parameter name | identity seal | checkpoint key 충돌 |
| 암묵적 physical alias | Linear 생성 경계 | 여러 위치가 한 parameter처럼 update됨 |
| 교체된 weight storage | forward seal 검사 | optimizer와 다른 parameter로 계산 |
| input width 불일치 | model forward 진입점 | 일부 dot product graph 생성 |
| model graph budget 초과 | 첫 multiplication 전 | 앞 layer graph만 남는 부분 실행 |
| parameter state 변경 | forward postcondition | 실행이 optimizer 상태를 오염시킴 |
| planner/executor shape 불일치 | output postcondition | 내부 비용 모델과 실제 의미 분리 |
현재는 Python Value object와 scalar operation 비용을 센다. GPU와 NPU를 아직 사용하지 않지만 parameter manifest, initializer role, exact graph plan은 Stage 15의 PyTorch port에서 tensor shape, allocation, kernel 비용을 비교할 reference specification이 된다.
직접 실행한 결과
다음 명령을 실행했다.
python .\stages\stage_07_linear_mlp.py
python -m unittest tests.test_stage_07_linear_mlp -v관찰 결과는 다음과 같다.
- MLP parameter 수는 20이었다.
- input
(2,3)에서 output(2,2)를 계획하고 실제로 만들었다. - planned graph node와 actual graph node는 모두 76이었다.
- 20개 parameter의 physical identity가 모두 달랐다.
- model parameter 상한 19에서 initializer 호출 횟수는 0이었다.
- model forward 상한 37에서 scalar 연산 횟수는 0이었다.
- 정확한 상한 38에서 실제 scalar 연산 횟수도 38이었다.
- 반복 forward 전후 parameter identity가 같았다.
- alias와 교체된 storage가 모두
ParameterIdentityError로 실패했다. - standard와 ReLU 표본 분산이 각 목표 분산에 가까웠다.
- Stage 07 테스트 15개가 통과했다.
- 전체 테스트 85개가 통과했다.
- sanity check는
PASS였다.
Stage 07 테스트 15개 통과
이번 Stage가 증명한 것
Linear의 shape는 계산 외형일 뿐이며 모델 의미를 보장하지 않는다. 무엇이 parameter인지 생성 시점에 확정하고, 이번 실행을 감당할 수 있는지 forward 전에 모델 전체로 증명해야 한다.
LinearPlan과 ModelPlan은 parameter allocation 이전의 구조, 초기화, 비용 경계다. ParameterRef와 storage seal은 logical name과 physical Value identity를 묶는다. ModelForwardPlan은 모든 layer와 ReLU의 shape와 정확한 graph node 수를 첫 multiplication 전에 합산한다.
구현 중에는 manifest만 재검사하면 실제 storage 교체를 놓친다는 가설도 통제 실험으로 파괴했다. 실제 weight와 bias에서 reference를 재구성하는 seal로 보강해 forward가 checkpoint와 optimizer가 아는 것과 같은 parameter를 사용한다는 사실을 검사한다.
다음 Stage
Stage 08에서는 activation 크기를 제어하는 RMSNorm과 값을 우회해 전달하는 residual 경로를 추가한다. 다음 질문은 다음과 같다.
RMSNorm과 residual은 값의 크기와 gradient 경로를 어떻게 바꾸는가? epsilon은 단순한 0 나누기 방지 상수인가, 입력 규모, gradient, 수치 정밀도를 함께 결정하는 실행 계약인가?