Skip to content

fix(glm53): fp8 dense 사본을 적재가 끝난 뒤에 뜬다 — 이른 사본이 그대로 서빙됐다 - #98

Merged
choiceoh merged 1 commit into
mainfrom
fix/fp8-dense-late-arm
Aug 31, 2026
Merged

fix(glm53): fp8 dense 사본을 적재가 끝난 뒤에 뜬다 — 이른 사본이 그대로 서빙됐다#98
choiceoh merged 1 commit into
mainfrom
fix/fp8-dense-late-arm

Conversation

@choiceoh

@choiceoh choiceoh commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Summary

#94 로 기본 무장한 첫 부팅에서 타깃이 드래프트 토큰 51,786 개 중 0 개를 수용했다. 디코드 8.8 tok/s(대조 ~32), Mean acceptance length 1.00. 드래프트 생성은 정상(7,398 라운드 × 7 토큰)이므로 망가진 것은 검증하는 쪽이다.

부팅 로그가 시점을 그대로 보여준다:

05:39:32  가중치 적재 시작 (294.50 초)
05:40:02  [fp8-dense] 180 linears quantized (3.67 GB)   ← 적재 30 초 시점
05:43:59  [fp8-dense] 0 quantized, 180 kept bf16
05:44:26  [fp8-dense] 0 quantized, 180 kept bf16
05:44:26  Loading weights took 294.50 seconds

훅이 Glm5NextModel.load_weights 끝에 있었다. 그건 자식 훅이고 AutoWeightsLoader 는 체크포인트를 다 훑기 전에 그 안으로 들어올 수 있다. 그리고 사본은 스냅샷이다 — quant_method 를 갈아끼우는 순간 apply()q/ws 만 읽고 bf16 텐서는 다시는 안 읽힌다. 30 초 시점의 사본이 부팅 내내 서빙됐고, 뒤의 두 호출이 "0 quantized" 인 것은 이미 갈아끼워져 UnquantizedLinearMethod 가 아니었기 때문이다. 기본 off 였을 때는 아무도 이 경로를 밟지 않았다.

Changes

  • 훅을 Glm5NextForConditionalGeneration.load_weightsloader.load_weights() 반환 뒤로 이동 — 최상위이고 한 번만 불린다
  • 재무장: 이미 설치된 Fp8DenseMethod 를 건너뛰지 않고 ._base 로 풀어 오늘의 가중치로 다시 만든다. 이르게 부른 호출자가 낡은 사본을 남길 수 없고 마지막 호출이 이긴다
  • 사본 검사: 무장 직전 랜덤 입력으로 fp8 경로와 bf16 경로를 맞춰 본다. 블록-fp8 은 bf16 에서 몇 % 안쪽이고 낡은 사본은 아예 다른 행렬이라 자릿수로 빗나가므로 문턱(0.25)은 빡빡할 필요가 없다. 빗나가면 그 층만 bf16 으로 되돌리고 이름을 로그에 남긴다
  • 프로브를 못 돌린 경우(None)는 무장 유지 — 실행이 안 됐다는 이유로 무장을 거부하면 실측된 이득을 미측정 걱정과 바꾸는 셈이다. 되돌리는 것은 실제로 빗나간 False 뿐이다
  • 지문에 N disarmed by the copy check 추가

#95 의 드래프터 호출부(dflash_utils)도 같은 함수를 쓰므로 검사가 함께 걸린다.

왜 검사까지 넣나

시점만 고치면 이 사례는 막지만 부류는 안 막는다. "사본을 떴는데 원본이 나중에 바뀐다"는 이 레포가 반복해 겪는 조용한 결함이고, 이번엔 self-test PASS 같은 기존 게이트를 전부 통과한 채 서빙까지 갔다. 검사는 원본과 사본이 어긋나는 순간을 부팅 로그에서 잡는다.

Verification

  • tests/test_logic.py 669 검사 통과
  • launchers/compose-overlays.sh glm53 완주 (14 overlays / 12 modules)
  • py_compile 통과
  • 플릿 미검증 — 부팅 필요. 판정 전제:
    • 지문에 disarmed by the copy check 0
    • 지문 호출이 적재 완료 이후 1 회
    • 수용률 회복 — pos-0 조건부 대조 62.7% 대비 −2%p 이내 (깨진 부팅은 0.0%)
    • C=1/2/4 디코드, 대조 = 팔 A 13.55 / 팔 B 13.76 step/s (수락률 정규화)

🤖 Generated with Claude Code


Note

Medium Risk
Changes when dense fp8 quantization arms and what gets served at inference; mistakes would affect decode quality and speculative acceptance, but the move and per-layer probe reduce stale-weight risk.

Overview
Fixes a boot where fp8 dense snapshots were taken ~30s into a ~295s load and then served for the whole run, breaking target verification (0/51k draft accepts). maybe_build_fp8_dense is no longer invoked from Glm5NextModel.load_weights; it runs once after AutoWeightsLoader.load_weights returns on Glm5NextForCausalLM, when checkpoint weights are settled.

maybe_build_fp8_dense is now safe to call repeatedly: existing Fp8DenseMethod layers are unwrapped to _base and re-quantized from current bf16 weights so an early caller cannot leave a stale copy. Before swapping quant_method, _copy_matches_source compares fp8 vs bf16 on a random probe (relative error ≤ 0.25); only a definite mismatch disarms that layer (probe failure keeps fp8 armed). Boot logs report layers disarmed by the copy check.

The same function is used for the DFlash2 drafter (VLLM_DFLASH2_FP8_DENSE), so re-arm and copy-check apply there too.

Reviewed by Cursor Bugbot for commit 31894c9. Bugbot is set up for automated code reviews on this repo. Configure here.

#94 로 기본 무장한 첫 부팅에서 타깃이 드래프트 토큰 51,786 개 중 0 개를 수용했다.
디코드 8.8 tok/s (대조 ~32), Mean acceptance length 1.00. 드래프트 생성은 정상
(7,398 라운드 x 7 토큰) 이므로 망가진 것은 검증하는 쪽이다.

부팅 로그가 시점을 그대로 보여준다:

    05:39:32  가중치 적재 시작 (294.50 초)
    05:40:02  [fp8-dense] 180 linears quantized (3.67 GB)   <- 적재 30 초 시점
    05:43:59  [fp8-dense] 0 quantized, 180 kept bf16
    05:44:26  [fp8-dense] 0 quantized, 180 kept bf16
    05:44:26  Loading weights took 294.50 seconds

훅이 Glm5NextModel.load_weights 끝에 있었다. 그건 자식 훅이고 AutoWeightsLoader
는 체크포인트를 다 훑기 전에 그 안으로 들어올 수 있다. 그리고 사본은 스냅샷이다
-- quant_method 를 갈아끼우는 순간 apply() 는 q/ws 만 읽고 bf16 텐서는 다시는
안 읽힌다. 그래서 30 초 시점의 사본이 부팅 내내 서빙됐고, 뒤의 두 호출이
"0 quantized" 인 것은 이미 갈아끼워져 UnquantizedLinearMethod 가 아니었기
때문이다. 기본 off 였을 때는 아무도 이 경로를 밟지 않았다.

- 훅을 Glm5NextForConditionalGeneration.load_weights 의 loader.load_weights()
  반환 뒤로 옮긴다. 최상위이고 한 번만 불린다.
- maybe_build_fp8_dense 를 재무장 가능하게: 이미 설치된 Fp8DenseMethod 는
  건너뛰지 않고 ._base 로 풀어 오늘의 가중치로 다시 만든다. 이르게 부른
  호출자가 낡은 사본을 남길 수 없다 -- 마지막 호출이 이긴다.
- 사본 검사: 무장 직전에 랜덤 입력으로 fp8 경로와 bf16 경로를 맞춰 본다.
  블록-fp8 은 bf16 에서 몇 % 안쪽이고 낡은 사본은 아예 다른 행렬이라 자릿수로
  빗나가므로, 문턱(0.25)은 빡빡할 필요가 없다. 빗나가면 그 층만 bf16 으로
  되돌리고 이름을 로그에 남긴다.
- 프로브를 못 돌린 경우(None)는 무장을 유지한다. 실행이 안 됐다는 이유로
  무장을 거부하면 실측된 이득을 미측정 걱정과 바꾸는 셈이다. 되돌리는 것은
  실제로 빗나간 False 뿐이다.
- 지문에 "N disarmed by the copy check" 를 더한다.

#95 의 드래프터 호출부(dflash_utils)도 같은 함수를 쓰므로 검사가 함께 걸린다.

tests/test_logic.py 669 검사 통과, compose-overlays.sh glm53 완주(14/12).
플릿 미검증 -- 부팅 필요. 판정 전제는 지문의 disarmed=0 과 수용률 회복이다.
Copilot AI lite review requested due to automatic review settings August 31, 2026 06:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cursor

cursor Bot commented Aug 31, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_2f2ae752-2616-41be-9f00-3addf5e6402b)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T06:08:23.223388Z 31894c9 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@choiceoh
choiceoh merged commit eb679fe into main Aug 31, 2026
3 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not approved: Cursor Bugbot was present but did not complete successfully (check skipped — usage limit), so there is no clean automated-review signal. Human review is needed; no reviewers were assigned because this repository has no assignable reviewer other than the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31894c9f71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +234 to +235
if isinstance(base, Fp8DenseMethod):
base = base._base

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore the base method when re-arming fails

When this is the final re-arm after an earlier call installed a stale copy, these lines unwrap only the local base variable while mod.quant_method still references the old Fp8DenseMethod. If _quantize_fp8_block_padded or another operation in the subsequent try raises, the exception handler merely logs that the layer “stayed bf16,” leaving the stale copy active and allowing the same corrupted output this change is intended to prevent. Restore mod.quant_method = base on the failure path or before attempting the rebuild.

Useful? React with 👍 / 👎.

choiceoh added a commit that referenced this pull request Aug 31, 2026
_copy_matches_source 가 method.apply() 를 호출하면, apply 의 자체
try/except 가 (1) 예외를 삼키고 (2) 그 자리에서 layer.quant_method 를
폴백으로 교체하며(빌드 중 상태 변이!) (3) 폴백의 출력을 반환한다 —
그러면 ref 도 폴백과 같은 경로라 오차 0, 'True' 판정. 깨진 W4 커널도
'무장 성공'으로 거짓 지문이 찍히고, 그 층은 매 호출 예외→fp8 폴백,
W4 텐서는 순수 낭비. 이 마스킹은 main 의 fp8 경로(#98 도입)에도
동일하게 존재했다.

수정: 체크에 got_fn(직접 GEMM 호출)을 전달 — 깨진 커널은 예외(→None,
fp8 은 종전 철학대로 무장) 또는 가비지(→False, W4 의 is-True 게이트는
기각)로 드러난다. 빌드 중 상태 변이도 사라짐.

덤: 모듈 문서의 무장 행을 스킴 노브(0|1|w4a8)로 갱신.
choiceoh added a commit that referenced this pull request Aug 31, 2026
… 사다리 (#106)

* perf(glm53): dense W4A8 — fp8_fp4_gemm_nt 로 가중치 한 단계 더, 액티베이션은 fp8 유지

스킴 노브: VLLM_GLM53_FP8_DENSE = 0 | 1(W8A8) | w4a8. W4 팔은:

- 가중치: per_token_cast_to_fp4 (e2m1, 행별 128-블록 ue8m0 스케일, 함수가
  K 패딩을 스스로 처리 — #92 의 행 패딩/출력 슬라이스 불필요)
- 액티베이션: 기존 fp8 per-token-group quant 그대로 — 문헌이 정당화하는 축
  (QServe: W4A4 는 20-25% 손실, W4A8 이 타협점)
- GEMM: fp8_fp4_gemm_nt — MoE 전문가 178GB 가 타는 sm120_fp8_fp4_gemm_1d1d
  패밀리의 dense 형태. vLLM 래퍼는 노출 안 하지만 vendored deep_gemm 에
  nn/nt/tn/tt 전부 존재

안전 사다리 (전부 빌드 시점, 캡처 전):
  W4 패킹 → 프로브 GEMM(실제 커널 2행 시험) + 완화 copy check(4×_STALE_RTOL)
  → 실패 시 W8A8 쌍 → 그마저 실패 시 bf16. 재무장 언랩은 #98 로직 보존
  (Fp8DenseMethod·W4A8DenseMethod 둘 다 언랩). 최악의 부팅 = 기본 팔과 동일.

기대: dense 읽기 1.93→0.97GB, 이상 −4.3ms, 1단계 실현율(76%) 준용
−3.3~−4.5ms ≈ +5.4~7.4% step/s. 게이트: 9/9, 한글 0/16, pos-1 ±2%p —
W8A8 게이트 통과가 선행 조건이었으나 프로브/폴백 설계로 단독 팔로도
안전하므로 실험 팔로 게이트를 직접 받는다.

* fix(w4a8): 리뷰 4건 — 죽은 코드, 이중 스케일 형식 순회, 관대한 카피체크, 메모리 문서화

- 죽은 _w4_dense_gemm_call/_flag 제거
- 스케일 형식을 packed-ue8m0 한 가지만 시도하던 것을 packed→plain 순회로:
  커널의 C++ 체크를 못 읽으니 값 검증이 통과하는 쪽을 찾게 한다 —
  W4 팔이 실제로 무장할 확률을 높임
- _w4_probe_ok 제거·통합: _copy_matches_source 가 실GEMM 값비교라 프로브를
  완전히 대체. 실험 스킴은 값 체크가 '실제로 실행되고 통과'(True)일 때만
  무장 — fp8 경로의 '실패 안 함'보다 한 단계 엄격
- 카피체크 허용오차 4×(1.0)→2×(0.5): e2m1 행블록 실측 오차 0.02-0.08,
  비상관 가비지 ~√2=1.41 — 1.0은 반쯤 가비지(faulty scale layout)가
  새어들 수 있는 폭이었다
- W4A8DenseMethod 문서에 삼중 상주(bf16+fp8폴백+fp4, 랭크당 ~+1GB)와
  런타임 폴백이 W8A8로 한 단계 떨어진다는 것 명시

* fix(fp8-dense): 카피체크가 메서드 내부 폴백에 마스킹되던 버그 — 2차 리뷰 발견

_copy_matches_source 가 method.apply() 를 호출하면, apply 의 자체
try/except 가 (1) 예외를 삼키고 (2) 그 자리에서 layer.quant_method 를
폴백으로 교체하며(빌드 중 상태 변이!) (3) 폴백의 출력을 반환한다 —
그러면 ref 도 폴백과 같은 경로라 오차 0, 'True' 판정. 깨진 W4 커널도
'무장 성공'으로 거짓 지문이 찍히고, 그 층은 매 호출 예외→fp8 폴백,
W4 텐서는 순수 낭비. 이 마스킹은 main 의 fp8 경로(#98 도입)에도
동일하게 존재했다.

수정: 체크에 got_fn(직접 GEMM 호출)을 전달 — 깨진 커널은 예외(→None,
fp8 은 종전 철학대로 무장) 또는 가비지(→False, W4 의 is-True 게이트는
기각)로 드러난다. 빌드 중 상태 변이도 사라짐.

덤: 모듈 문서의 무장 행을 스킴 노브(0|1|w4a8)로 갱신.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants